lion/packages-node/remark-extend/test-node/remark-extend.test.js

695 lines
17 KiB
JavaScript

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 = [
'<h3>Red</h3>',
'<p>red is the fire</p>',
'<h4>More Red</h4>',
'<p>the sun can get red</p>',
'',
].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 = [
'<h3>Green</h3>', // <-- start
'<p>green is the fire</p>',
'<h4>More Green</h4>',
'<p>the sun can get green</p>',
'',
].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 = [
'<h3>Red</h3>',
'<p>red is the fire</p>',
'<h3>More Green</h3>', // <-- start
'<p>the sun can get green</p>',
'',
].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 = [
'<h3>Red</h3>',
'<p>red is the fire</p>',
'<p>green is the cross</p>', // <-- start
'<p>green is the flag</p>',
'<h4>More Red</h4>', // <-- end
'<p>the sun can get red</p>',
'',
].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 = [
'<h3>Green</h3>', // text node "Green" == start == end
'<p>red</p>',
'<h3>Red</h3>',
'',
].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 after via "::addMdAfter"', async () => {
const input = [
//
'### Red',
'',
'red is the fire',
].join('\n');
const extendMd = [
'```',
"::addMdAfter('heading:has([value=Red])')",
'```',
'',
'the ocean is blue',
].join('\n');
const output = [
//
'<h3>Red</h3>',
'<p>the ocean is blue</p>',
'<p>red is the fire</p>',
'',
].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 = [
//
'<h3>Red</h3>',
'<p>the ocean is blue</p>',
'<p>red is the fire</p>',
'<h3>More Red</h3>',
'<p>as in the sun is the ultimate red</p>',
'',
].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 = [
//
'<h1>New Headline</h1>',
'<h3>Red</h3>',
'',
].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 right at the bottom via "::addMdAfter(':scope:last-child')"`, async () => {
const input = [
//
'### Red',
'',
'red is the fire',
].join('\n');
const extendMd = [
//
'```',
"::addMdAfter(':scope:last-child')",
'```',
'',
'extra text',
].join('\n');
const output = [
//
'<h3>Red</h3>',
'<p>red is the fire</p>',
'<p>extra text</p>',
'',
].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 = [
//
'<h3>Red</h3>',
'<p>red is the fire</p>',
'<p>the ocean is blue</p>',
'<h3>Blue</h3>',
'',
].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 at the end of a "section" via "::addMdBefore('heading:has([value=Red]) ~ heading[depth=3]')"`, async () => {
const input = [
//
'### Red',
'',
'red is the fire',
'',
'### Blue',
].join('\n');
const extendMd = [
'```',
"::addMdBefore('heading:has([value=Red]) ~ heading[depth=3]')",
'```',
'',
'the sun will be red',
].join('\n');
const output = [
//
'<h3>Red</h3>',
'<p>red is the fire</p>',
'<p>the sun will be red</p>',
'<h3>Blue</h3>',
'',
].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 at the top of the file via '::addMdBefore(":root")'`, async () => {
const input = [
//
'### Red',
'',
'red is the fire',
'',
'### Blue',
].join('\n');
const extendMd = [
//
'```',
"::addMdBefore(':root')",
'```',
'',
'the ocean is blue',
].join('\n');
const output = [
//
'<p>the ocean is blue</p>',
'<h3>Red</h3>',
'<p>red is the fire</p>',
'<h3>Blue</h3>',
'',
].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 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 = [
//
'```',
"::removeFrom('heading:has([value=More Red])')",
'```',
].join('\n');
const output = [
//
'<h3>Red</h3>',
'<p>red is the fire</p>',
'',
].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 = [
'<h3>More Red</h3>', // <-- end
'<p>the sun can get red</p>',
'',
].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 = [
'<h3>Yellow</h3>', // <-- start
'<p>This is added</p>',
'<h3>More</h3>',
'',
].join('\n');
const parser = unified()
//
.use(markdown)
.use(remarkExtend, { extendMd })
.use(mdStringify);
const result = await parser.process(input);
expect(result.contents).to.equal(output);
});
});