diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index 8e06cf266..dc38e83ad 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -329,11 +329,11 @@ const definitionLists = { //v=====--------------------< Variable Handling >-------------------=====v// 254 lines const replaceVar = function(input, hoist=false) { - const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)/g; + const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g; const match = regex.exec(input); const prefix = match[1]; - const label = match[2].toLowerCase(); + const label = match[2]; let missingValues = []; @@ -396,10 +396,10 @@ const replaceVar = function(input, hoist=false) { let value; if(!prefix[0] && href) // Link - value = `[${label}](${href}${title ? ` ${title}` : ''})`; + value = `[${label}](${href}${title ? ` "${title}"` : ''})`; if(prefix[0] == '!' && href) // Image - value = ``; + value = ``; if(prefix[0] == '$') // Variable value = foundVar.content; @@ -494,7 +494,7 @@ function MarkedVariables() { const blockSkip = /^(?: {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+|^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})(?:[^\n]*)(?:\n|$)(?:|(?:[\s\S]*?)(?:\n|$))(?: {0,3}\2[~`]* *(?=\n|$))|`[^`]*?`/; const blockDefRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]:(?!\() *((?:\n? *[^\s].*)+)(?=\n+|$)/; //Matches 3, [4]:5 const blockCallRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?=\n|$)/; //Matches 6, [7] - const inlineDefRegex = /([!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\])\(([^\n]+)\)/; //Matches 8, 9[10](11) + const inlineDefRegex = /([!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\])\(([^\n]+)\)/; //Matches 8, 9[10](11) const inlineCallRegex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?!\()/; //Matches 12, [13] // Combine regexes like so: (regex1)|(regex2)|(regex3)|(regex4) @@ -521,8 +521,8 @@ function MarkedVariables() { }); } if(match[3]) { // Block Definition - const label = match[4] ? match[4].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space - const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space + const label = match[4] ? match[4].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space + const content = match[5] ? match[5].trim().replace(/[ \t]+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space linksQueue.push( { type : 'varDefBlock', @@ -532,7 +532,7 @@ function MarkedVariables() { }); } if(match[6]) { // Block Call - const label = match[7] ? match[7].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space + const label = match[7] ? match[7].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space linksQueue.push( { type : 'varCallBlock', @@ -542,7 +542,7 @@ function MarkedVariables() { }); } if(match[8]) { // Inline Definition - const label = match[10] ? match[10].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space + const label = match[10] ? match[10].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space let content = match[11];// ? match[11].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space let level = 0; @@ -572,13 +572,13 @@ function MarkedVariables() { }); linksQueue.push( { type : 'varCallInline', - match : match[8], + match : match[9], varName : label, - content : match[8] + content : match[9] }); } if(match[12]) { // Inline Call - const label = match[13] ? match[13].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space + const label = match[13] ? match[13].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space linksQueue.push( { type : 'varCallInline', diff --git a/tests/markdown/variables.test.js b/tests/markdown/variables.test.js index 04d874d90..5c0ea8d7d 100644 --- a/tests/markdown/variables.test.js +++ b/tests/markdown/variables.test.js @@ -44,19 +44,240 @@ describe('Block-level variables', ()=>{ | C | D | $[var]`; - const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns(); + const rendered = Markdown.render(source).trimReturns(); expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
| H1 | -H2 | -
|---|---|
| A | -B | -
| C | -D | -
| H1 | +H2 | +
|---|---|
| A | +B | +
| C | +D | +
string
'); + }); + + it('Hoists last instance of variable', function() { + const source = dedent` + $[var] + + [var]: string + + [var]: new string`; + const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('new string
'); + }); + + it('Handles complex hoisting', function() { + const source = dedent` + $[titleAndName]: $[title] $[fullName] + + $[title]: Mr. + + $[fullName]: $[firstName] $[lastName] + + [firstName]: Bob + + Welcome, $[titleAndName]! + + [lastName]: Jacob + + [lastName]: $[lastName]son + `; + const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('Welcome, Mr. Bob Jacobson!
'); }); }); +describe('Inline-level variables', ()=>{ + it('Handles variable assignment and recall with simple text', function() { + const source = dedent` + $[var](string) + + $[var] + `; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('string
string
'); + }); + + it('Hoists undefined variables', function() { + const source = dedent` + $[var](My name is $[name] Jones) + + [name]: Bob`; + const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('My name is Bob Jones
'); + }); + + it('Hoists last instance of variable', function() { + const source = dedent` + $[var](My name is $[name] Jones) + + $[name](Bob) + + [name]: Bill`; + const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`My name is Bill Jones
Bob
`.trimReturns()); + }); +}); + +describe('Math', ()=>{ + it('Handles simple math using numbers only', function() { + const source = dedent` + $[1 + 3 * 5 - (1 / 4)] + `; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('15.75
'); + }); + + it('Handles round function', function() { + const source = dedent` + $[round(1/4)]`; + const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('0
'); + }); + + it('Handles floor function', function() { + const source = dedent` + $[floor(0.6)]`; + const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('0
'); + }); + + it('Handles ceil function', function() { + const source = dedent` + $[ceil(0.2)]`; + const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('1
'); + }); + + it('Handles nested functions', function() { + const source = dedent` + $[ceil(floor(round(0.6)))]`; + const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('1
'); + }); + + it('Handles simple math with variables', function() { + const source = dedent` + $[num1]: 5 + + $[num2]: 4 + + Answer is $[answer]($[1 + 3 * num1 - (1 / num2)]). + `; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('Answer is 15.75.
'); + }); + + it('Handles variable incrementing', function() { + const source = dedent` + $[num1]: 5 + + Increment num1 to get $[num1]($[num1 + 1]) and again to $[num1]($[num1 + 1]). + `; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('Increment num1 to get 6 and again to 7.
'); + }); +}); + +describe('Code blocks', ()=>{ + it('Ignores all variables in fenced code blocks', function() { + const source = dedent` + \`\`\` + [var]: string + + $[var] + + $[var](new string) + \`\`\` + `; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` +
+ [var]: string
+
+ $[var]
+
+ $[var](new string)
+ `.trimReturns());
+ });
+
+ it('Ignores all variables in indented code blocks', function() {
+ const source = dedent`
+ test
+
+ [var]: string
+
+ $[var]
+
+ $[var](new string)
+ `;
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
+ test
+ +
+ [var]: string
+
+ $[var]
+
+ $[var](new string)
+ `.trimReturns());
+ });
+
+ it('Ignores all variables in inline code blocks', function() {
+ const source = '[var](Hello) `[link](url)`. This `[var] does not work`';
+ const rendered = Markdown.render(source).trimReturns();
+ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent`
+ var [link](url). This [var] does not work
An image !
A Link to my website!
`.trimReturns()); + }); + + it('Renders normal links with a title', function() { + const source = 'A Link to my [website](url "and title")!'; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` +A Link to my website!
`.trimReturns()); + }); +}); // TODO: add tests for ID with accordance to CSS spec: //