const { expect } = require('chai'); const unified = require('unified'); const markdown = require('remark-parse'); const mdStringify = require('remark-html'); const { remarkExtend } = require('../src/remarkExtend.js'); async function expectThrowsAsync(method, errorMessage) { let error = null; try { await method(); } catch (err) { error = err; } expect(error).to.be.an('Error', 'No error was thrown'); if (errorMessage) { expect(error.message).to.equal(errorMessage); } } describe('remarkExtend', () => { it('does no modifications if no action is found', async () => { const input = [ '### Red', '', 'red is the fire', '', '#### More Red', '', 'the sun can get red', ].join('\n'); const extendMd = ''; const output = [ '
red is the fire
', 'the sun can get red
', '', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd }) .use(mdStringify); const result = await parser.process(input); expect(result.contents).to.equal(output); }); it('can do replacements on the full file', async () => { const input = [ '### Red', // <-- start '', 'red is the fire', '', '#### More Red', '', 'the sun can get red', ].join('\n'); const extendMd = [ // "```js ::replaceFrom(':root')", 'module.exports.replaceSection = (node) => {', ' if (node.value) {', " node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green');", ' }', ' return node;', '};', '```', ].join('\n'); const output = [ 'green is the fire
', 'the sun can get green
', '', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd }) .use(mdStringify); const result = await parser.process(input); expect(result.contents).to.equal(output); }); it('can replace from a starting point downward', async () => { const input = [ '### Red', '', 'red is the fire', '', '### More Red', // <-- start '', 'the sun can get red', ].join('\n'); const extendMd = [ "```js ::replaceFrom('heading[depth=3]:has([value=More Red])')", 'module.exports.replaceSection = (node) => {', ' if (node.value) {', " node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green');", ' }', ' return node;', '};', '```', ].join('\n'); const output = [ 'red is the fire
', 'the sun can get green
', '', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd }) .use(mdStringify); const result = await parser.process(input); expect(result.contents).to.equal(output); }); it('can replace within a range (start point included, endpoint not)', async () => { const input = [ '### Red', '', 'red is the fire', '', 'red is the cross', // <-- start '', 'red is the flag', '', '#### More Red', '', 'the sun can get red', ].join('\n'); const extendMd = [ "```js ::replaceBetween('heading:has([value=Red]) ~ paragraph:nth-of-type(2)', 'heading:has([value=More Red])')", 'module.exports.replaceSection = (node) => {', ' if (node.value) {', " node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green');", ' }', ' return node', '};', ].join('\n'); const output = [ 'red is the fire
', 'green is the cross
', // <-- start 'green is the flag
', 'the sun can get red
', '', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd }) .use(mdStringify); const result = await parser.process(input); expect(result.contents).to.equal(output); }); it('throws if a start selector is not found', async () => { const input = [ // '### Red', ].join('\n'); const extendMd = [ "```js ::replaceFrom('heading:has([value=More Red])')", 'module.exports.replaceSection = (node) => {}', '```', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd }) .use(mdStringify); await expectThrowsAsync( () => parser.process(input), 'The start selector "heading:has([value=More Red])" could not find a matching node.', ); }); it('throws with addition info (if provide as filePath, overrideFilePath) if a start selector is not found', async () => { const input = [ // '### Red', ].join('\n'); const extendMd = [ "```js ::replaceFrom('heading:has([value=More Red])')", 'module.exports.replaceSection = (node) => {}', '```', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd, filePath: '/path/to/input.md', overrideFilePath: '/path/to/override.md', }) .use(mdStringify); await expectThrowsAsync( () => parser.process(input), [ 'The start selector "heading:has([value=More Red])" could not find a matching node.', 'Markdown File: /path/to/input.md', 'Override File: /path/to/override.md', ].join('\n'), ); }); it('throws if a end selector is not found', async () => { const input = [ // '### Red', ].join('\n'); const extendMd = [ "```js ::replaceBetween('heading:has([value=Red])', 'heading:has([value=Red2])')", 'module.exports.replaceSection = (node) => {}', '```', ].join('\n'); const parser = unified().use(markdown).use(remarkExtend, { extendMd }).use(mdStringify); await expectThrowsAsync( () => parser.process(input), 'The end selector "heading:has([value=Red2])" could not find a matching node.', ); }); it('throws with addition info (if provide as filePath, overrideFilePath) if a end selector is not found', async () => { const input = [ // '### Red', ].join('\n'); const extendMd = [ "```js ::replaceBetween('heading:has([value=Red])', 'heading:has([value=Red2])')", 'module.exports.replaceSection = (node) => {}', '```', ].join('\n'); const parser = unified() .use(markdown) .use(remarkExtend, { extendMd, filePath: '/path/to/input.md', overrideFilePath: '/path/to/override.md', }) .use(mdStringify); await expectThrowsAsync( () => parser.process(input), [ 'The end selector "heading:has([value=Red2])" could not find a matching node.', 'Markdown File: /path/to/input.md', 'Override File: /path/to/override.md', ].join('\n'), ); }); it('replaces a single node if replacing between the start and end of the same node', async () => { const input = [ // '### Red', 'red', '### Red', ].join('\n'); const extendMd = [ "```js ::replaceBetween('heading:has([value=Red]) > text', 'heading:has([value=Red]) > text')", 'module.exports.replaceSection = (node) => {', ' if (node.value) {', " node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green');", ' }', ' return node', '};', '```', ].join('\n'); const output = [ 'red
', 'the ocean is blue
', 'red is the fire
', '', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd }) .use(mdStringify); const result = await parser.process(input); expect(result.contents).to.equal(output); }); it('adding stops at the next ::[action]', async () => { const input = [ // '### Red', '', 'red is the fire', '', '### More Red', ].join('\n'); const extendMd = [ '```', "::addMdAfter('heading:has([value=Red])')", '```', '', 'the ocean is blue', '', '```', "::addMdAfter('heading:has([value=More Red])')", '```', '', 'as in the sun is the ultimate red', '', "```js ::replaceBetween('heading:has([value=Red])', 'heading:has([value=Red])')", 'module.exports.replaceSection = (node) => {}', '```', 'content not part of an add so it gets ignored', ].join('\n'); const output = [ // 'the ocean is blue
', 'red is the fire
', 'as in the sun is the ultimate red
', '', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd }) .use(mdStringify); const result = await parser.process(input); expect(result.contents).to.equal(output); }); it('will throw if trying to immediate run replacements of added content', async () => { const input = [ // '### Red', '', ].join('\n'); const extendMd = [ '```', "::addMdAfter('heading:has([value=Red])')", '```', '', '## Blue', '', "```js ::replaceFrom('heading:has([value=Blue])')", 'module.exports.replaceSection = (node) => {', ' if (node.value) {', " node.value = node.value.replace(/Blue/g, 'Yellow');", ' }', ' return node', '}', '```', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd }) .use(mdStringify); await expectThrowsAsync( () => parser.process(input), 'The start selector "heading:has([value=Blue])" could not find a matching node.', ); }); it(`can put something right at the top via "::addMdAfter(':root')"`, async () => { const input = [ // '### Red', ].join('\n'); const extendMd = [ // '```', "::addMdAfter(':root')", '```', '', '# New Headline', ].join('\n'); const output = [ // 'red is the fire
', 'extra text
', '', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd }) .use(mdStringify); const result = await parser.process(input); expect(result.contents).to.equal(output); }); it('can put something before via "::addMdBefore"', async () => { const input = [ // '### Red', '', 'red is the fire', '', '### Blue', ].join('\n'); const extendMd = [ '```', "::addMdBefore('heading:has([value=Blue])')", '```', '', 'the ocean is blue', ].join('\n'); const output = [ // 'red is the fire
', 'the ocean is blue
', 'red is the fire
', 'the sun will be red
', 'the ocean is blue
', 'red is the fire
', 'red is the fire
', '', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd }) .use(mdStringify); const result = await parser.process(input); expect(result.contents).to.equal(output); }); it('can remove a range (start point included, endpoint not)', async () => { const input = [ '### Red', // <-- start '', 'red is the fire', '', '### More Red', // <-- end '', 'the sun can get red', ].join('\n'); const extendMd = [ '```', `::removeBetween('heading:has([value=Red])', 'heading:has([value=Red]) ~ heading[depth=3]')`, '```', ].join('\n'); const output = [ 'the sun can get red
', '', ].join('\n'); const parser = unified() // .use(markdown) .use(remarkExtend, { extendMd }) .use(mdStringify); const result = await parser.process(input); expect(result.contents).to.equal(output); }); it('does replacements in order of extendMd', async () => { const input = [ '### Red', // <-- start '', 'red is the fire', '### More', ].join('\n'); const extendMd = [ '```', "::removeBetween('heading:has([value=Red]) + *', 'heading:has([value=Red]) ~ heading')", '```', "```js ::replaceFrom(':root')", 'module.exports.replaceSection = (node) => {', ' if (node.value) {', " node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green');", ' }', ' return node;', '};', '```', "```js ::replaceFrom(':root')", 'module.exports.replaceSection = (node) => {', ' if (node.value) {', " node.value = node.value.replace(/green/g, 'yellow').replace(/Green/g, 'Yellow');", ' }', ' return node;', '};', '```', '```', "::addMdAfter('heading:has([value=Yellow])')", '```', 'This is added', ].join('\n'); const output = [ 'This is added
', '