UNPKG

@mdfriday/foundry

Version:

The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.

182 lines 17.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const item_1 = require("./item"); const pageparser_1 = require("./pageparser"); // Helper function to create a new type and text test item function nti(typ, text) { return { Type: typ, text }; } // Constants for commonly used test items const tstEOF = nti(item_1.ItemType.tEOF, ""); const tstLeftNoMD = nti(item_1.ItemType.tLeftDelimScNoMarkup, "{{<"); const tstRightNoMD = nti(item_1.ItemType.tRightDelimScNoMarkup, ">}}"); const tstLeftMD = nti(item_1.ItemType.tLeftDelimScWithMarkup, "{{%"); const tstRightMD = nti(item_1.ItemType.tRightDelimScWithMarkup, "%}}"); const tstSCClose = nti(item_1.ItemType.tScClose, "/"); const tstSC1 = nti(item_1.ItemType.tScName, "sc1"); const tstSC1Inline = nti(item_1.ItemType.tScNameInline, "sc1.inline"); const tstSC2Inline = nti(item_1.ItemType.tScNameInline, "sc2.inline"); const tstSC2 = nti(item_1.ItemType.tScName, "sc2"); const tstSC3 = nti(item_1.ItemType.tScName, "sc3"); const tstSCSlash = nti(item_1.ItemType.tScName, "sc/sub"); const tstParam1 = nti(item_1.ItemType.tScParam, "param1"); const tstParam2 = nti(item_1.ItemType.tScParam, "param2"); const tstVal = nti(item_1.ItemType.tScParamVal, "Hello World"); const tstText = nti(item_1.ItemType.tText, "Hello World"); const shortCodeLexerTests = [ { name: "empty", input: "", items: [tstEOF] }, { name: "spaces", input: " \t\n", items: [nti(item_1.ItemType.tText, " \t\n"), tstEOF] }, { name: "text", input: "to be or not", items: [nti(item_1.ItemType.tText, "to be or not"), tstEOF] }, { name: "no markup", input: "{{< sc1 >}}", items: [tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF] }, { name: "with EOL", input: "{{< sc1 \n >}}", items: [tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF] }, { name: "forward slash inside name", input: "{{< sc/sub >}}", items: [tstLeftNoMD, tstSCSlash, tstRightNoMD, tstEOF] }, { name: "simple with markup", input: "{{% sc1 %}}", items: [tstLeftMD, tstSC1, tstRightMD, tstEOF] }, { name: "with spaces", input: "{{< sc1 >}}", items: [tstLeftNoMD, tstSC1, tstRightNoMD, tstEOF] }, { name: "indented on new line", input: "Hello\n {{% sc1 %}}", items: [nti(item_1.ItemType.tText, "Hello\n"), nti(item_1.ItemType.tIndentation, " "), tstLeftMD, tstSC1, tstRightMD, tstEOF] }, { name: "indented on new line tab", input: "Hello\n\t{{% sc1 %}}", items: [nti(item_1.ItemType.tText, "Hello\n"), nti(item_1.ItemType.tIndentation, "\t"), tstLeftMD, tstSC1, tstRightMD, tstEOF] }, { name: "indented on first line", input: " {{% sc1 %}}", items: [nti(item_1.ItemType.tIndentation, " "), tstLeftMD, tstSC1, tstRightMD, tstEOF] }, { name: "mismatched rightDelim", input: "{{< sc1 %}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tError, "unrecognized character in shortcode action: U+25 '%'. Note: Parameters with non-alphanumeric args must be quoted")] }, { name: "inner, markup", input: "{{% sc1 %}} inner {{% /sc1 %}}", items: [tstLeftMD, tstSC1, tstRightMD, nti(item_1.ItemType.tText, " inner "), tstLeftMD, tstSCClose, tstSC1, tstRightMD, tstEOF] }, { name: "close, but no open", input: "{{< /sc1 >}}", items: [tstLeftNoMD, nti(item_1.ItemType.tError, "got closing shortcode, but none is open")] }, { name: "close wrong", input: "{{< sc1 >}}{{< /another >}}", items: [tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, nti(item_1.ItemType.tError, "closing tag for shortcode 'another' does not match start tag")] }, { name: "close, but no open, more", input: "{{< sc1 >}}{{< /sc1 >}}{{< /another >}}", items: [tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, nti(item_1.ItemType.tError, "closing tag for shortcode 'another' does not match start tag")] }, { name: "close with extra keyword", input: "{{< sc1 >}}{{< /sc1 keyword>}}", items: [tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSCClose, tstSC1, nti(item_1.ItemType.tError, "unclosed shortcode")] }, { name: "float param, positional", input: "{{< sc1 3.14 >}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tScParam, "3.14"), tstRightNoMD, tstEOF] }, { name: "float param, named", input: "{{< sc1 param1=3.14 >}}", items: [tstLeftNoMD, tstSC1, tstParam1, nti(item_1.ItemType.tScParamVal, "3.14"), tstRightNoMD, tstEOF] }, { name: "named param, raw string", input: "{{< sc1 param1=`Hello World` >}}", items: [tstLeftNoMD, tstSC1, tstParam1, nti(item_1.ItemType.tScParamVal, "Hello World"), tstRightNoMD, tstEOF] }, { name: "float param, named, space before", input: "{{< sc1 param1= 3.14 >}}", items: [tstLeftNoMD, tstSC1, tstParam1, nti(item_1.ItemType.tScParamVal, "3.14"), tstRightNoMD, tstEOF] }, { name: "Youtube id", input: "{{< sc1 -ziL-Q_456igdO-4 >}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tScParam, "-ziL-Q_456igdO-4"), tstRightNoMD, tstEOF] }, { name: "non-alphanumerics param quoted", input: "{{< sc1 \"-ziL-.%QigdO-4\" >}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tScParam, "-ziL-.%QigdO-4"), tstRightNoMD, tstEOF] }, { name: "raw string", input: "{{< sc1`Hello World` >}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tScParam, "Hello World"), tstRightNoMD, tstEOF] }, { name: "raw string with newline", input: "{{< sc1`Hello \n\tWorld` >}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tScParam, "Hello \n\tWorld"), tstRightNoMD, tstEOF] }, { name: "raw string with escape character", input: "{{< sc1`Hello \\b World` >}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tScParam, "Hello \\b World"), tstRightNoMD, tstEOF] }, { name: "two params", input: "{{< sc1 param1 param2 >}}", items: [tstLeftNoMD, tstSC1, tstParam1, tstParam2, tstRightNoMD, tstEOF] }, { name: "self-closing", input: "{{< sc1 />}}", items: [tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD, tstEOF] }, { name: "multiple self-closing", input: "{{< sc1 />}}{{< sc1 />}}", items: [tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD, tstLeftNoMD, tstSC1, tstSCClose, tstRightNoMD, tstEOF] }, { name: "self-closing with param", input: "{{< sc1 param1 />}}", items: [tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, tstEOF] }, { name: "multiple self-closing with param", input: "{{< sc1 param1 />}}{{< sc1 param1 />}}", items: [tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, tstEOF] }, { name: "multiple different self-closing with param", input: "{{< sc1 param1 />}}{{< sc2 param1 />}}", items: [tstLeftNoMD, tstSC1, tstParam1, tstSCClose, tstRightNoMD, tstLeftNoMD, tstSC2, tstParam1, tstSCClose, tstRightNoMD, tstEOF] }, { name: "nested simple", input: "{{< sc1 >}}{{< sc2 >}}{{< /sc1 >}}", items: [tstLeftNoMD, tstSC1, tstRightNoMD, tstLeftNoMD, tstSC2, tstRightNoMD, tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, tstEOF] }, { name: "nested complex", input: "{{< sc1 >}}ab{{% sc2 param1 %}}cd{{< sc3 >}}ef{{< /sc3 >}}gh{{% /sc2 %}}ij{{< /sc1 >}}kl", items: [ tstLeftNoMD, tstSC1, tstRightNoMD, nti(item_1.ItemType.tText, "ab"), tstLeftMD, tstSC2, tstParam1, tstRightMD, nti(item_1.ItemType.tText, "cd"), tstLeftNoMD, tstSC3, tstRightNoMD, nti(item_1.ItemType.tText, "ef"), tstLeftNoMD, tstSCClose, tstSC3, tstRightNoMD, nti(item_1.ItemType.tText, "gh"), tstLeftMD, tstSCClose, tstSC2, tstRightMD, nti(item_1.ItemType.tText, "ij"), tstLeftNoMD, tstSCClose, tstSC1, tstRightNoMD, nti(item_1.ItemType.tText, "kl"), tstEOF ] }, { name: "two quoted params", input: "{{< sc1 \"param nr. 1\" \"param nr. 2\" >}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tScParam, "param nr. 1"), nti(item_1.ItemType.tScParam, "param nr. 2"), tstRightNoMD, tstEOF] }, { name: "two named params", input: "{{< sc1 param1=\"Hello World\" param2=\"p2Val\">}}", items: [tstLeftNoMD, tstSC1, tstParam1, tstVal, tstParam2, nti(item_1.ItemType.tScParamVal, "p2Val"), tstRightNoMD, tstEOF] }, { name: "escaped quotes", input: "{{< sc1 param1=\\\"Hello World\\\" >}}", items: [tstLeftNoMD, tstSC1, tstParam1, tstVal, tstRightNoMD, tstEOF] }, { name: "escaped quotes, positional param", input: "{{< sc1 \\\"param1\\\" >}}", items: [tstLeftNoMD, tstSC1, tstParam1, tstRightNoMD, tstEOF] }, { name: "escaped quotes inside escaped quotes", input: "{{< sc1 param1=\\\"Hello \\\"escaped\\\" World\\\" >}}", items: [tstLeftNoMD, tstSC1, tstParam1, nti(item_1.ItemType.tScParamVal, "Hello "), nti(item_1.ItemType.tError, "got positional parameter 'escaped'. Cannot mix named and positional parameters")] }, { name: "escaped quotes inside nonescaped quotes", input: "{{< sc1 param1=\"Hello \\\"escaped\\\" World\" >}}", items: [tstLeftNoMD, tstSC1, tstParam1, nti(item_1.ItemType.tScParamVal, "Hello \"escaped\" World"), tstRightNoMD, tstEOF] }, { name: "escaped quotes inside nonescaped quotes in positional param", input: "{{< sc1 \"Hello \\\"escaped\\\" World\" >}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tScParam, "Hello \"escaped\" World"), tstRightNoMD, tstEOF] }, { name: "escaped raw string, named param", input: "{{< sc1 param1=\\`Hello World\\` >}}", items: [tstLeftNoMD, tstSC1, tstParam1, nti(item_1.ItemType.tError, "unrecognized escape character")] }, { name: "escaped raw string, positional param", input: "{{< sc1 param1 \\`Hello World\\` >}}", items: [tstLeftNoMD, tstSC1, tstParam1, nti(item_1.ItemType.tError, "unrecognized escape character")] }, { name: "two raw string params", input: "{{< sc1`Hello World``Second Param` >}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tScParam, "Hello World"), nti(item_1.ItemType.tScParam, "Second Param"), tstRightNoMD, tstEOF] }, { name: "unterminated quote", input: "{{< sc1 param2=\"Hello World>}}", items: [tstLeftNoMD, tstSC1, tstParam2, nti(item_1.ItemType.tError, "unterminated quoted string in shortcode parameter-argument: 'Hello World>}}'")] }, { name: "unterminated raw string", input: "{{< sc1`Hello World >}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tError, "unterminated raw string in shortcode parameter-argument: 'Hello World >}}'")] }, { name: "unterminated raw string in second argument", input: "{{< sc1`Hello World``Second Param >}}", items: [tstLeftNoMD, tstSC1, nti(item_1.ItemType.tScParam, "Hello World"), nti(item_1.ItemType.tError, "unterminated raw string in shortcode parameter-argument: 'Second Param >}}'")] }, { name: "one named param, one not", input: "{{< sc1 param1=\"Hello World\" p2 >}}", items: [tstLeftNoMD, tstSC1, tstParam1, tstVal, nti(item_1.ItemType.tError, "got positional parameter 'p2'. Cannot mix named and positional parameters")] }, { name: "one named param, one quoted positional param, both raw strings", input: "{{< sc1 param1=`Hello World``Second Param` >}}", items: [tstLeftNoMD, tstSC1, tstParam1, tstVal, nti(item_1.ItemType.tError, "got quoted positional parameter. Cannot mix named and positional parameters")] }, { name: "one named param, one quoted positional param", input: "{{< sc1 param1=\"Hello World\" \"And Universe\" >}}", items: [tstLeftNoMD, tstSC1, tstParam1, tstVal, nti(item_1.ItemType.tError, "got quoted positional parameter. Cannot mix named and positional parameters")] }, { name: "one quoted positional param, one named param", input: "{{< sc1 \"param1\" param2=\"And Universe\" >}}", items: [tstLeftNoMD, tstSC1, tstParam1, nti(item_1.ItemType.tError, "got named parameter 'param2'. Cannot mix named and positional parameters")] }, { name: "one positional param, one not", input: "{{< sc1 param1 param2=\"Hello World\">}}", items: [tstLeftNoMD, tstSC1, tstParam1, nti(item_1.ItemType.tError, "got named parameter 'param2'. Cannot mix named and positional parameters")] }, { name: "commented out", input: "{{</* sc1 */>}}", items: [nti(item_1.ItemType.tText, "{{<"), nti(item_1.ItemType.tText, " sc1 "), nti(item_1.ItemType.tText, ">}}"), tstEOF] }, { name: "commented out, with asterisk inside", input: "{{</* sc1 \"**/*.pdf\" */>}}", items: [nti(item_1.ItemType.tText, "{{<"), nti(item_1.ItemType.tText, " sc1 \"**/*.pdf\" "), nti(item_1.ItemType.tText, ">}}"), tstEOF] }, { name: "commented out, missing close", input: "{{</* sc1 >}}", items: [nti(item_1.ItemType.tError, "comment must be closed")] }, { name: "commented out, misplaced close", input: "{{</* sc1 >}}*/", items: [nti(item_1.ItemType.tError, "comment must be closed")] }, { name: "basic inline", input: "{{< sc1.inline >}}Hello World{{< /sc1.inline >}}", items: [tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstEOF] }, { name: "basic inline with space", input: "{{< sc1.inline >}}Hello World{{< / sc1.inline >}}", items: [tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstEOF] }, { name: "inline self closing", input: "{{< sc1.inline >}}Hello World{{< /sc1.inline >}}Hello World{{< sc1.inline />}}", items: [tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSC1Inline, tstSCClose, tstRightNoMD, tstEOF] }, { name: "inline self closing, then a new inline", input: "{{< sc1.inline >}}Hello World{{< /sc1.inline >}}Hello World{{< sc1.inline />}}{{< sc2.inline >}}Hello World{{< /sc2.inline >}}", items: [ tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSC1Inline, tstSCClose, tstRightNoMD, tstLeftNoMD, tstSC2Inline, tstRightNoMD, tstText, tstLeftNoMD, tstSCClose, tstSC2Inline, tstRightNoMD, tstEOF ] }, { name: "inline with template syntax", input: "{{< sc1.inline >}}{{ .Get 0 }}{{ .Get 1 }}{{< /sc1.inline >}}", items: [tstLeftNoMD, tstSC1Inline, tstRightNoMD, nti(item_1.ItemType.tText, "{{ .Get 0 }}"), nti(item_1.ItemType.tText, "{{ .Get 1 }}"), tstLeftNoMD, tstSCClose, tstSC1Inline, tstRightNoMD, tstEOF] }, { name: "inline with nested shortcode (not supported)", input: "{{< sc1.inline >}}Hello World{{< sc1 >}}{{< /sc1.inline >}}", items: [tstLeftNoMD, tstSC1Inline, tstRightNoMD, tstText, nti(item_1.ItemType.tError, "inline shortcodes do not support nesting")] }, { name: "inline case mismatch", input: "{{< sc1.Inline >}}Hello World{{< /sc1.Inline >}}", items: [tstLeftNoMD, nti(item_1.ItemType.tError, "period in shortcode name only allowed for inline identifiers")] } ]; // Helper function to compare items function equal(input, got, expected) { if (got.length !== expected.length) { console.log('Length mismatch:', got.length, 'vs', expected.length); return false; } for (let i = 0; i < got.length; i++) { if (got[i].Type !== expected[i].Type) { console.log('Type mismatch at', i, ':', got[i].Type, 'vs', expected[i].Type); return false; } const gotText = got[i].Val ? new TextDecoder().decode(got[i].Val(new TextEncoder().encode(input))) : ''; if (gotText !== expected[i].text) { console.log('Text mismatch at', i, ':', gotText, 'vs', expected[i].text); return false; } } return true; } // Helper function to convert test items to string for error messages function testItemsToString(items) { return items.map(item => `{Type: ${item.Type}, text: "${item.text}"}`).join('\n'); } // Helper function to convert actual items to string for error messages function itemsToString(items, input) { return items.map(item => { let text; if (item.Err) { text = item.Err.message; } else { text = item.Val ? new TextDecoder().decode(item.Val(input)) : ''; } return `{Type: ${item.Type}, text: "${text}"}`; }).join('\n'); } describe('pageparser shortcode', () => { describe('lexer tests', () => { test.each(shortCodeLexerTests)('$name', ({ name, input, items }) => { const [result, err] = (0, pageparser_1.collectStringMain)(input); expect(err).toBeNull(); if (!equal(input, result, items)) { const got = itemsToString(result, new TextEncoder().encode(input)); const expected = testItemsToString(items); expect(got).toBe(expected); } }); }); // describe('performance', () => { // const withShortcode = Array(30).fill('this is text').join('') + // '{{< myshortcode >}}This is some inner content.{{< /myshortcode >}}' + // Array(30).fill('this is text').join(''); // const withoutShortcode = Array(30).fill('this is text').join('') + // 'This is some inner content.' + // Array(30).fill('this is text').join(''); // // test('should quickly identify shortcode when present', () => { // const start = performance.now(); // for(let i = 0; i < 100; i++) { // collectStringMain(withShortcode); // } // const duration = performance.now() - start; // expect(duration).toBeLessThan(2000); // }); // // test('should quickly identify when no shortcode present', () => { // const start = performance.now(); // for(let i = 0; i < 100; i++) { // collectStringMain(withoutShortcode); // } // const duration = performance.now() - start; // expect(duration).toBeLessThan(2000); // }); // }); }); //# sourceMappingURL=pageparser.shortcode.test.js.map