Plugin eier hele render/ (KTG-go 16.07, eierskapsvalg a). Portet fra maskinrommets tools/build-html.mjs (read-only): flerlinjers blockquote (avsnitt i quote), [tekst](url)-lenker med http/https/mailto-whitelist, FIGUR-markoer -> inline SVG med figcaption + fallback til blockquote. CSS for de tre konstruktene. 7 nye paritetstester (repoets testtilfeller kopiert + med-SVG-case); paritetsbevis: samme fixture gjennom begge motorer = byte-identisk parser-HTML. Render-floor 53 -> 60. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: c61b308e-e4fa-4a49-9ff0-a3ce725cf703
132 lines
4.9 KiB
JavaScript
132 lines
4.9 KiB
JavaScript
import { describe, test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { markdownToHtml, inline } from '../build-html.mjs';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
describe('markdownToHtml — tables (beslutning H)', () => {
|
|
test('converts a pipe table to <table>/<tr>/<td>', () => {
|
|
const md = [
|
|
'| Plattform | Pris |',
|
|
'| --- | --- |',
|
|
'| Azure | Høy |',
|
|
'| Foundry | Lav |',
|
|
].join('\n');
|
|
const html = markdownToHtml(md);
|
|
assert.match(html, /<table>/);
|
|
assert.match(html, /<tr>/);
|
|
assert.match(html, /<td>/);
|
|
// header cells render as <th>, body values as <td>
|
|
assert.match(html, /<th>Plattform<\/th>/);
|
|
assert.match(html, /<td>Azure<\/td>/);
|
|
// the separator row must NOT become a data row
|
|
assert.doesNotMatch(html, /<td>---<\/td>/);
|
|
});
|
|
|
|
test('empty input produces no table', () => {
|
|
assert.doesNotMatch(markdownToHtml(''), /<table>/);
|
|
});
|
|
|
|
test('tolerates a malformed row (uneven cell count) without throwing', () => {
|
|
const md = [
|
|
'| a | b |',
|
|
'| --- | --- |',
|
|
'| only-one-cell |',
|
|
'| x | y |',
|
|
].join('\n');
|
|
let html;
|
|
assert.doesNotThrow(() => { html = markdownToHtml(md); });
|
|
assert.match(html, /<table>/);
|
|
assert.match(html, /<td>x<\/td>/);
|
|
});
|
|
});
|
|
|
|
describe('markdownToHtml — heading levels # … #### (beslutning H)', () => {
|
|
test('# renders <h1>', () => {
|
|
assert.match(markdownToHtml('# Tittel'), /<h1>Tittel<\/h1>/);
|
|
});
|
|
test('## renders <h2>', () => {
|
|
assert.match(markdownToHtml('## Seksjon'), /<h2>Seksjon<\/h2>/);
|
|
});
|
|
test('### renders <h3>', () => {
|
|
assert.match(markdownToHtml('### Under'), /<h3>Under<\/h3>/);
|
|
});
|
|
test('#### renders <h4>', () => {
|
|
assert.match(markdownToHtml('#### Detalj'), /<h4>Detalj<\/h4>/);
|
|
});
|
|
});
|
|
|
|
describe('inline — backtick code span (beslutning H)', () => {
|
|
test('`code` renders <code>', () => {
|
|
assert.match(inline('bruk `npm install` her'), /<code>npm install<\/code>/);
|
|
});
|
|
test('still handles **bold** and *italic*', () => {
|
|
assert.match(inline('**fet** og *kursiv*'), /<strong>fet<\/strong>/);
|
|
assert.match(inline('**fet** og *kursiv*'), /<em>kursiv<\/em>/);
|
|
});
|
|
});
|
|
|
|
// Paritetstilfeller portet fra maskinrommets tools/__tests__/build-html.test.mjs
|
|
// (N3.5/MR-F8): lenker, flerlinjers blockquote, FIGUR→SVG. Samme markdown skal
|
|
// gi samme parser-HTML i begge motorer.
|
|
describe('inline — lenker [tekst](url) (N3.5 MR-F8)', () => {
|
|
test('renders [tekst](url) as a link with target/rel', () => {
|
|
const out = inline('se [Reuters](https://www.reuters.com/x) for detaljer');
|
|
assert.match(out, /<a href="https:\/\/www\.reuters\.com\/x"[^>]*>Reuters<\/a>/);
|
|
assert.match(out, /target="_blank"/);
|
|
assert.match(out, /rel="noopener"/);
|
|
});
|
|
|
|
test('mailto is allowed', () => {
|
|
assert.match(inline('[skriv](mailto:x@y.no)'), /<a href="mailto:x@y\.no"/);
|
|
});
|
|
|
|
test('drops non-http(s)/mailto schemes — link text survives, href does not', () => {
|
|
const out = inline('[klikk](javascript:alert(1))');
|
|
assert.doesNotMatch(out, /<a /);
|
|
assert.match(out, /klikk/);
|
|
});
|
|
});
|
|
|
|
describe('markdownToHtml — flerlinjers blockquote (N3.5 MR-F8)', () => {
|
|
test('a blockquote keeps its paragraph breaks', () => {
|
|
const md = ['> Første avsnitt.', '>', '> Andre avsnitt.'].join('\n');
|
|
const html = markdownToHtml(md);
|
|
assert.match(html, /<blockquote>/);
|
|
// two separate <p> inside the quote — the old parser collapsed these into one
|
|
const paras = html.match(/<blockquote>(.*?)<\/blockquote>/s)[1].match(/<p>/g) || [];
|
|
assert.equal(paras.length, 2);
|
|
});
|
|
|
|
test('a single-paragraph blockquote still renders one <p>', () => {
|
|
const html = markdownToHtml('> Bare ett avsnitt her.');
|
|
const paras = html.match(/<blockquote>(.*?)<\/blockquote>/s)[1].match(/<p>/g) || [];
|
|
assert.equal(paras.length, 1);
|
|
});
|
|
});
|
|
|
|
describe('markdownToHtml — FIGUR→SVG (N3.5 MR-F8)', () => {
|
|
test('a FIGUR blockquote with no matching SVG falls back to a plain blockquote', () => {
|
|
const html = markdownToHtml('> **[FIGUR 9 — «finnes ikke»]**');
|
|
assert.match(html, /<blockquote>/);
|
|
assert.doesNotMatch(html, /<figure/);
|
|
});
|
|
|
|
test('a FIGUR blockquote with matching figurer/figN*.svg inlines the SVG as <figure>', () => {
|
|
const prev = process.cwd();
|
|
process.chdir(path.join(__dirname, 'fixtures', 'figur-serie'));
|
|
try {
|
|
const html = markdownToHtml('> **[FIGUR 1 — «Demo-figur»]**');
|
|
assert.match(html, /<figure class="fig">/);
|
|
assert.match(html, /<svg/);
|
|
assert.match(html, /<figcaption>Figur 1 — Demo-figur<\/figcaption>/);
|
|
// xml-deklarasjonen i SVG-fila skal strippes før inlining
|
|
assert.doesNotMatch(html, /<\?xml/);
|
|
assert.doesNotMatch(html, /<blockquote>/);
|
|
} finally {
|
|
process.chdir(prev);
|
|
}
|
|
});
|
|
});
|