diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js index af0feec1b..d9c1e6576 100644 --- a/shared/naturalcrit/markdown.js +++ b/shared/naturalcrit/markdown.js @@ -380,15 +380,21 @@ const replaceVar = function(input, hoist=false, allowUnresolved=false) { }; const lookupVar = function(label, index, hoist=false) { - if(hoist) - index = Object.keys(globalVarsList).length; // Move index to start from last page - while (index >= 0) { if(globalVarsList[index]?.[label] !== undefined) return globalVarsList[index][label]; index--; } + if(hoist) { //If normal lookup failed, attempt hoisting + index = Object.keys(globalVarsList).length; // Move index to start from last page + while (index >= 0) { + if(globalVarsList[index]?.[label] !== undefined) + return globalVarsList[index][label]; + index--; + } + } + return undefined; }; diff --git a/tests/markdown/variables.test.js b/tests/markdown/variables.test.js index 8bf47fa66..885cca293 100644 --- a/tests/markdown/variables.test.js +++ b/tests/markdown/variables.test.js @@ -9,6 +9,16 @@ String.prototype.trimReturns = function(){ return this.replace(/\r?\n|\r/g, '').trim(); }; +renderAllPages = function(pages){ + const outputs = []; + pages.forEach((page, index) => { + const output = Markdown.render(page, index); + outputs.push(output); + }); + + return outputs; +}; + // Adding `.failing()` method to `describe` or `it` will make failing tests "pass" as long as they continue to fail. // Remove the `.failing()` method once you have fixed the issue. @@ -96,6 +106,36 @@ describe('Block-level variables', ()=>{ expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('
Welcome, Mr. Bob Jacobson!
'); }); + it('Handles variable reassignment', function() { + const source = dedent` + [var]: one + + $[var] + + [var]: two + + $[var] + `; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('one
two
'.trimReturns()); + }); + + it('Handles variable reassignment with hoisting', function() { + const source = dedent` + $[var] + + [var]: one + + $[var] + + [var]: two + + $[var] + `; + const rendered = Markdown.render(source).trimReturns(); + expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('two
one
two
'.trimReturns()); + }); + it("Ignores undefined variables that can't be hoisted", function() { const source = dedent` $[var](My name is $[first] $[last]) @@ -307,23 +347,27 @@ describe('Normal Links and Images', ()=>{ }); }); -// TODO: add tests for ID with accordance to CSS spec: -// -// From https://drafts.csswg.org/selectors/#id-selectors: -// -// > An ID selector consists of a “number sign” (U+0023, #) immediately followed by the ID value, which must be a CSS identifier. -// -// From: https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier: -// -// > In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9] -// > and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_); -// > they cannot start with a digit, two hyphens, or a hyphen followed by a digit. -// > Identifiers can also contain escaped characters and any ISO 10646 character as a numeric code (see next item). -// > For instance, the identifier "B&W?" may be written as "B\&W\?" or "B\26 W\3F". -// > Note that Unicode is code-by-code equivalent to ISO 10646 (see [UNICODE] and [ISO10646]). +describe('Cross-page variables', ()=>{ + it('Handles variable assignment and recall across pages', function() { + const source0 = `[var]: string`; + const source1 = `$[var]`; + const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); + expect(rendered, `Input:\n${[source0,source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('\\pagestring
'); + }); -// TODO: add tests for class with accordance to CSS spec: -// -// From: https://drafts.csswg.org/selectors/#class-html: -// -// > The class selector is given as a full stop (. U+002E) immediately followed by an identifier. + it('Handles hoisting across pages', function() { + const source0 = `$[var]`; + const source1 = `[var]: string`; + renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); //Requires one full render of document before hoisting is picked up + const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); + expect(rendered, `Input:\n${[source0,source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('string
\\page'); + }); + + it('Handles reassignment and hoisting across pages', function() { + const source0 = `$[var]\n\n[var]: one\n\n$[var]`; + const source1 = `[var]: two\n\n$[var]`; + renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); //Requires one full render of document before hoisting is picked up + const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns(); + expect(rendered, `Input:\n${[source0,source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('two
one
\\pagetwo
'); + }); +}); \ No newline at end of file