From 1d1fa99b4b0e03e98000764db011d584fff69e59 Mon Sep 17 00:00:00 2001 From: Trevor Buckner Date: Mon, 19 Feb 2024 23:57:19 -0500 Subject: [PATCH] Pass all tests --- shared/naturalcrit/markdown.js | 24 ++-- tests/markdown/variables.test.js | 237 +++++++++++++++++++++++++++++-- 2 files changed, 241 insertions(+), 20 deletions(-) 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 = `![${label}](${href} ${title ? ` ${title}` : ''})`; + value = `![${label}](${href} ${title ? ` "${title}"` : ''})`; 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`
Title
- - - - - - -
H1H2
AB
CD
`.trimReturns()); + + + + + + +
H1H2
AB
CD
`.trimReturns()); + }); + + it('Hoists undefined variables', function() { + const source = dedent` + $[var] + + [var]: string`; + const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('

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

`.trimReturns()); + }); +}); + +describe('Normal Links and Images', ()=>{ + it('Renders normal images', function() { + const source = `![alt text](url)`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` +

alt text

`.trimReturns()); + }); + + it('Renders normal images with a title', function() { + const source = 'An image ![alt text](url "and title")!'; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` +

An image alt text!

`.trimReturns()); + }); + + it('Applies curly injectors to images', function() { + const source = `![alt text](url){width:100px}`; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` +

alt text

`.trimReturns()); + }); + + it('Renders normal links', function() { + const source = 'A Link to my [website](url)!'; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(dedent` +

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: //