diff --git a/tests/embeds.test.ts b/tests/embeds.test.ts index 819e4d1..d784c7f 100644 --- a/tests/embeds.test.ts +++ b/tests/embeds.test.ts @@ -118,3 +118,58 @@ test('the editor corpus embed entries expand after sanitizing', () => { assert.equal(expandEmbeds(clean), clean, entry.name) } }) + +test('expansion is last: sanitizing the expanded output would strip the iframe', () => { + // Why the order matters: the sanitizer never keeps an iframe, so a sink that + // sanitized after expanding would lose every video. + const again = sanitizeJournalHtml(IFRAME) + assert.equal(iframeCount(again), 0) + assert.doesNotMatch(again, /youtube-nocookie/) +}) + +test('the sinks expand after sanitizing and the payload transforms never expand', () => { + const read = (path: string) => readFileSync(new URL(path, import.meta.url), 'utf8') + for (const sink of ['../app/components/Journal/PostBody.vue', '../app/components/Journal/CategoryHeader.vue']) { + const source = read(sink) + assert.match(source, /expandEmbeds\(sanitizeJournalHtml\(/, `${sink} must render expandEmbeds(sanitizeJournalHtml(...))`) + assert.equal((source.match(/expandEmbeds\(/g) ?? []).length, 1, `${sink} expands once`) + } + for (const transform of ['../app/utils/postView.ts', '../app/journal-pages/CategoryPage.vue']) { + assert.doesNotMatch(read(transform), /expandEmbeds/, `${transform} must not expand`) + } +}) + +test('only the exact serialization expands: case, spacing and entities stay inert', () => { + const inert = [ + `
`, + `
`, + `
`, + `
`, + `
`, + `
`, + `
`, + `
\n
`, + `
`, + ] + for (const html of inert) { + assert.equal(expandEmbeds(html), html, html) + assert.equal(iframeCount(expandEmbeds(html)), 0, html) + } +}) + +test('expansion is stable across calls (the global pattern keeps no state)', () => { + const body = `

a

${PLACEHOLDER}

b

${PLACEHOLDER}` + const first = expandEmbeds(body) + assert.equal(expandEmbeds(body), first) + assert.equal(EMBED_PLACEHOLDER.lastIndex, 0) + assert.equal(iframeCount(first), 2) +}) + +test('the iframe src is a fixed host plus the validated id, never stored text', () => { + const out = sink(`
`) + const src = /', ''], + ['start with a fraction', '
  1. a
', '
  1. a
'], + ['alt on p', '

y

', '

y

'], + ] + for (const [name, html, want] of cases) { + assert.equal(sanitizeJournalHtml(html), want, name) + assert.equal(isSafeJournalHtml(html), false, `${name} reported safe`) + } +}) + +test('every corpus entry stays free of danger after the sink pipeline', () => { + for (const entry of CORPUS) { + const out = sanitizeJournalHtml(sanitizeJournalHtml(entry.html)) + for (const pattern of DANGER) assert.doesNotMatch(out, pattern, `${entry.name}: ${pattern}`) + } +})