import path from 'path'; import url from 'url'; import { expect } from 'chai'; import unified from 'unified'; import markdown from 'remark-parse'; import mdStringify from 'remark-html'; import gfm from 'remark-gfm'; import { remarkExtend } from '../src/remarkExtend.js'; const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); /** * @param {function} method * @param {object} options * @param {string} [options.errorMatch] * @param {string} [options.errorMessage] */ async function expectThrowsAsync(method, { errorMatch, errorMessage } = {}) { let error = null; try { await method(); } catch (err) { error = err; } expect(error).to.be.an('Error', 'No error was thrown'); if (errorMatch) { expect(error.message).to.match(errorMatch); } if (errorMessage) { expect(error.message).to.equal(errorMessage); } } /** * * @param {string} input * @param {{shouldStringify?: boolean; globalReplaceFunction?: Function;}} param1 */ async function execute(input, { shouldStringify = true, globalReplaceFunction } = {}) { const parser = unified() // .use(markdown) .use(gfm) .use(remarkExtend, { rootDir: __dirname, page: { inputPath: 'test-file.md' }, globalReplaceFunction, }); if (shouldStringify) { parser.use(mdStringify); const result = await parser.process(input); return result.contents; } let tree; parser .use(() => _tree => { tree = _tree; }) // @ts-expect-error .use(function plugin() { this.Compiler = () => ''; }); await parser.process(input); return tree; } describe('remarkExtend', () => { it('does no modifications if no action is found', async () => { const result = await execute( ['### Red', 'red is the fire', '#### More Red', 'the sun can get red'].join('\n'), ); expect(result).to.equal( [ '
red is the fire
', 'the sun can get red
', '', ].join('\n'), ); }); it('can import another file', async () => { const result = await execute( [ // '### Red', "```js ::import('./fixtures/import-me.md')", '```', ].join('\n'), ); expect(result).to.equal(['',
'link to
the sun can get red
', 'the red sea
', '', ].join('\n'), ); }); it('can import another file from a start to an end point', async () => { const result = await execute( [ // '# Red', "```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=More Red])', 'heading:has([value=More Red]) ~ heading[depth=2]')", '```', ].join('\n'), ); expect(result).to.equal( ['the sun can get red
', ''].join('\n'), ); }); it('can do replacements on imports', async () => { const result = await execute( [ // '# Red', "```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=More Red])', 'heading:has([value=More Red]) ~ heading[depth=2]')", 'module.exports.replaceSection = (node) => {', ' if (node.value) {', " node.value = node.value.replace(/red/g, 'green').replace(/Red/g, 'Green');", ' }', ' return node;', '};', '```', ].join('\n'), ); expect(result).to.equal( ['the sun can get green
', ''].join('\n'), ); }); it('can do global replacements, that run before local replacements', async () => { const result = await execute( [ // '# Red', "```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=More Red])', 'heading:has([value=More Red]) ~ heading[depth=2]')", 'module.exports.replaceSection = (node) => {', ' if (node.value) {', " node.value = node.value.replace(/green/g, 'blue').replace(/Red/g, 'Blue');", ' }', ' return node;', '};', '```', ].join('\n'), { globalReplaceFunction: node => { if (node.value) { // eslint-disable-next-line no-param-reassign node.value = node.value.replace(/red/g, 'green'); } return node; }, }, ); expect(result).to.equal( ['the sun can get blue
', ''].join('\n'), ); }); it('throws if an import file does not exist', async () => { await expectThrowsAsync(() => execute("```js ::import('./fixtures/not-available.md')\n```"), { errorMatch: /The import "\.\/fixtures\/not-available.md" in "test-file.md" does not exist\. Resolved to ".*"\.$/, }); }); it('throws if an start selector can not be found', async () => { const input = "```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=Does not exit])')\n```"; await expectThrowsAsync(() => execute(input), { errorMatch: /The start selector "heading:has\(\[value=Does not exit\]\)", imported in ".*", could not find a matching node in ".*"\.$/, }); }); it('throws if an end selector can not be found', async () => { const input = "```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=More Red])', 'heading:has([value=Does not exit])')\n```"; await expectThrowsAsync(() => execute(input), { errorMatch: /The end selector "heading:has\(\[value=Does not exit\]\)", imported in ".*", could not find a matching node in ".*"\./, }); }); it('can import a block (from start headline to next headline same level)', async () => { const result = await execute( [ // '### Static Headline', "```js ::importBlock('./fixtures/three-sections-red.md', '## More Red')", '```', ].join('\n'), ); expect(result).to.equal( [ // 'the sun can get red
', '', ].join('\n'), ); }); it('can import the last block (from start headline to end of file)', async () => { const result = await execute( [ // '### Static Headline', "```js ::importBlock('./fixtures/three-sections-red.md', '## Additional Red')", '```', ].join('\n'), ); expect(result).to.equal( [ // 'the red sea
', '', ].join('\n'), ); }); it('can import a block content (from start headline (excluding) to next headline same level)', async () => { const result = await execute( [ // '### Static Headline', "```js ::importBlockContent('./fixtures/three-sections-red.md', '## More Red')", '```', ].join('\n'), ); expect(result).to.equal( [ // 'the sun can get red
', '', ].join('\n'), ); }); it('can import a small block (from start headline to next headline of any level)', async () => { const result = await execute( [ // '### Static Headline', "```js ::importSmallBlock('./fixtures/three-sections-red.md', '# Red')", '```', ].join('\n'), ); expect(result).to.equal( [ // 'red is the fire
', '', ].join('\n'), ); }); it('can import a small block content (from start headline (excluding) to next headline of any level)', async () => { const result = await execute( [ // '### Static Headline', "```js ::importSmallBlockContent('./fixtures/three-sections-red.md', '# Red')", '```', ].join('\n'), ); expect(result).to.equal( [ // 'red is the fire
', '', ].join('\n'), ); }); it('resolves imports via node resolution', async () => { const result = await execute( [ // '### Static Headline', "```js ::importBlock('remark-extend/docs/test.md', '## Test')", '```', ].join('\n'), ); expect(result).to.equal( [ 'the sun can get red
', '| Sign | ', 'Name | ', '
|---|---|
| $ | ', 'Dollar | ', '
| € | ', 'Euro | ', '
the sun can get red
', '', ].join('\n'), ); }); });