E=mc^2 b');
assert.equal(htmlToMd('a E=mc^2 b'), 'a $E=mc^2$ b');
});
test('inline math converts both ways', () => {
assert.equal(mdToHtml('a $E=mc^2$ b'), 'a E=mc^2 b');
assert.equal(htmlToMd('a E=mc^2 b'), 'a $E=mc^2$ b');
});
test('inline math converts both ways', () => {
assert.equal(mdToHtml('a $E=mc^2$ b'), 'a E=mc^2 b');
assert.equal(htmlToMd('a E=mc^2 b'), 'a $E=mc^2$ b');
});
test('inline math converts both ways', () => {
assert.equal(mdToHtml('a $E=mc^2$ b'), 'a E=mc^2 b');
assert.equal(htmlToMd('a E=mc^2 b'), 'a $E=mc^2$ b');
});
test('inline formatting converts both ways and escapes HTML', () => {
assert.equal(mdToHtml('a **b** *c* `<d>');
assert.equal(htmlToMd('a b c <d>'), 'a **b** *c* `\[\[not a link\]\]<\/code>/);
assert.equal(htmlToMd(html), md);
assert.equal(htmlToMd(mdToHtml('[[A_b_c]] & [[xPlan '), '[[Plan]]');
assert.equal(htmlToMd('Plan and more'), '[[Plan]] and more');
});
test('images are their own block and round trip', () => {
const md = '\n\n\n\n![[photo.webp]]\n\nText with  stays text.\n';
const blocks = parseMarkdown(md);
assert.deepEqual(blocks.map((b) => b.type), ['image', 'image', 'image', 'p']);
assert.equal(blocks[1].src, 'my cat.jpg');
assert.equal(blocks[1].alt, 'A cat');
assert.equal(blocks[2].wiki, true);
assert.equal(toMarkdown(blocks), md);
});
test('numbered lists count per run and strikethrough round trips', () => {
const md = '1. one\n2. two\n\nBreak\n\n1. again\n- bullet\n1. new run\n\n~~gone~~ and **kept**\n';
const blocks = parseMarkdown(md);
assert.deepEqual(blocks.map((b) => b.type), ['number', 'number', 'p', 'number', 'bullet', 'number', 'p']);
assert.equal(toMarkdown(blocks), md);
// numbers in the file are rewritten to count from 1
assert.equal(toMarkdown(parseMarkdown('3. a\n7) b\n')), '1. a\n2. b\n');
assert.equal(htmlToMd('x|y');
assert.equal(t.rows[2][0], 'p | q');
assert.equal(toMarkdown([t]), md);
delete t.src;
t.rows[1][1] = '20';
assert.equal(toMarkdown([t]), '| a | b |\n| :----- | -------: |\n| `x\\|y` | **20** |\n| p \\| q | [[Plan]] |\n');
assert.equal(parseMarkdown('a | b\nno delimiter row')[0].type, 'p');
});
test('==highlight== round trips; == and === in normal text are left alone', () => {
for (const md of ['A ==bright== idea', '==**bold** inside== and ==*it*==', 'if a == b then', 'x === y === z', '`==code==` stays']) {
assert.equal(htmlToMd(mdToHtml(md)), md);
}
assert.equal(mdToHtml('A ==bright== idea'), 'A bright idea');
assert.ok(!mdToHtml('if a == b == c').includes(''));
assert.ok(!mdToHtml('`==code==`').includes(''));
});
test('footnotes: references, inline notes and footnote texts round trip; export numbers them in reading order', () => {
const md = 'A claim[^src] with an aside^[said in passing].\n\nAnother[^2] and again[^src].\n\n[^2]: Second note.\n[^src]: The source,\n on two lines.\n[^unused]: Nobody points here.\n';
const blocks = parseMarkdown(md);
assert.deepEqual(blocks.map((b) => b.type + (b.fid ? ':' + b.fid : '')), ['p', 'p', 'footnote:2', 'footnote:src', 'footnote:unused']);
assert.match(blocks[0].html, //);
assert.match(blocks[0].html, /data-inline="said in passing"/);
assert.equal(blocks[3].html, 'The source,\non two lines.');
assert.equal(toMarkdown(blocks), md);
const html = toHtmlDocument('t', blocks);
assert.deepEqual([...html.matchAll(/]*>(?:)?(\d+)/g)].map((m) => m[2] + (m[1] ? ':' + m[1] : '')), ['1:src', '2', '3:2', '1:src']);
assert.match(html, /3<\/sup> Second note\.<\/p>/);
assert.match(html, / unused<\/sup>/);
// "[^1]:" mid-line isn't a footnote text, and ids can't hold spaces
assert.deepEqual(parseMarkdown('Text [^1]: not a definition\n\n[^a b]: neither\n').map((b) => b.type), ['p', 'p']);
});
test('columns: fenced :::: columns round-trip, with any blocks in a column', () => {
const src = 'Before\n\n:::: columns\n::: column\nLeft **bold**\n\n- a\n- b\n:::\n::: column\n## Right\n\ntext\n:::\n::::\n\nAfter\n';
const blocks = parseMarkdown(src);
assert.deepEqual(blocks.map((b) => b.type + (b.col ? `[${b.col.i}]` : '')), ['p', 'p[0]', 'bullet[0]', 'bullet[0]', 'h2[1]', 'p[1]', 'p']);
const g = blocks[1].col.g;
assert.ok(blocks.slice(1, 6).every((b) => b.col.g === g), 'one group');
assert.equal(toMarkdown(blocks), src);
// three columns, one of them empty: it keeps its place
const three = ':::: columns\n::: column\na\n:::\n::: column\n:::\n::: column\nc\n:::\n::::\n';
const b3 = parseMarkdown(three);
assert.deepEqual(b3.map((b) => b.col.i), [0, 1, 2]);
// not closed: just text, nothing lost
const open = ':::: columns\n::: column\nlost?\n:::\n';
assert.ok(toMarkdown(parseMarkdown(open)).includes('lost?'));
// columns inside a code block are code
const code = '```\n:::: columns\n::: column\nx\n:::\n::::\n```\n';
assert.deepEqual(parseMarkdown(code).map((b) => b.type), ['code']);
// two groups in a row stay two groups
const two = ':::: columns\n::: column\na\n:::\n::::\n\n:::: columns\n::: column\nb\n:::\n::::\n';
const gs = new Set(parseMarkdown(two).map((b) => b.col.g));
assert.equal(gs.size, 2);
});