172 lines
5.5 KiB
JavaScript
172 lines
5.5 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
import * as cheerio from 'cheerio'
|
|
import { buildExportHtml } from '../src/html-builder.js'
|
|
import { normalizeBaseUrl } from '../src/utils.js'
|
|
|
|
const baseUrl = normalizeBaseUrl('https://wiki.example.com')
|
|
|
|
const pages = [
|
|
{
|
|
path: 'docs',
|
|
locale: 'en',
|
|
title: 'Docs',
|
|
depth: 0,
|
|
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
render: `
|
|
<h1 id="welcome">Docs</h1>
|
|
<p><a href="#local">Local</a></p>
|
|
<p><a href="/en/docs/child#sub-heading">Child heading</a></p>
|
|
<p><a href="/en/docs/child">Child</a></p>
|
|
<p><a href="/en/docs-other">Outside subtree</a></p>
|
|
<p><a href="/de/docs">German docs</a></p>
|
|
<p><a href="https://example.com/page">External</a></p>
|
|
<span id="local">Anchor target</span>
|
|
`,
|
|
},
|
|
{
|
|
path: 'docs/child',
|
|
locale: 'en',
|
|
title: 'Child',
|
|
depth: 1,
|
|
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
render: `
|
|
<h2 id="sub-heading">Sub heading</h2>
|
|
<h3>Auto heading</h3>
|
|
<p aria-labelledby="sub-heading other-id"><a name="old-anchor">Back</a>
|
|
<a href="/en/docs">Docs</a>
|
|
<a href="/en/docs/child/grand#child-heading">Grand</a></p>
|
|
<img src="/assets/pic.png" srcset="/assets/pic-2x.png 2x" loading="lazy" alt="Picture">
|
|
<script>window.bad = true</script>
|
|
<div onclick="alert(1)">Clickable</div>
|
|
<iframe src="/embedded"></iframe>
|
|
<form><input value="x"><p>Form content</p></form>
|
|
`,
|
|
},
|
|
{
|
|
path: 'docs/child/grand',
|
|
locale: 'en',
|
|
title: 'Grand',
|
|
depth: 2,
|
|
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
render: `<h2 id="child-heading">Grand child heading</h2>`,
|
|
},
|
|
]
|
|
|
|
async function build(overrides = {}) {
|
|
return buildExportHtml({
|
|
pages,
|
|
baseUrl,
|
|
locale: 'en',
|
|
startPath: 'docs',
|
|
startPage: { title: 'Docs' },
|
|
localization: { namespacing: true, namespaces: ['en'] },
|
|
includeToc: true,
|
|
inlineImages: false,
|
|
apiKey: 'test-key',
|
|
...overrides,
|
|
})
|
|
}
|
|
|
|
test('rewrites internal links to PDF anchors and keeps external links online', async () => {
|
|
const html = await build()
|
|
const $ = cheerio.load(html)
|
|
|
|
assert.equal($('.wiki-content a[href="#wp1-sub-heading"]').length, 1)
|
|
assert.equal($('.wiki-content a[href="#wiki-page-1"]').length, 1)
|
|
assert.equal($('.wiki-content a[href="#wp2-child-heading"]').length, 1)
|
|
assert.equal($('.wiki-content a[href="#wp0-local"]').length, 1)
|
|
assert.ok(
|
|
$('.wiki-content a[data-wiki-path="docs"]')
|
|
.toArray()
|
|
.map((element) => $(element).attr('href'))
|
|
.includes('#wiki-page-0'),
|
|
)
|
|
|
|
assert.equal($('a[href="https://wiki.example.com/en/docs-other"]').length, 1)
|
|
assert.equal($('a[href="https://wiki.example.com/de/docs"]').length, 1)
|
|
assert.equal($('a[href="https://example.com/page"]').length, 1)
|
|
|
|
assert.equal($('#wp1-sub-heading').length, 1)
|
|
assert.equal($('#wp1-auto-heading').length, 1)
|
|
assert.equal($('a[name="wp1-old-anchor"]').length, 1)
|
|
assert.equal($('[aria-labelledby="wp1-sub-heading other-id"]').length, 1)
|
|
|
|
const childHrefs = $('.wiki-content a[data-wiki-path="docs/child"]')
|
|
.toArray()
|
|
.map((element) => $(element).attr('href'))
|
|
assert.ok(childHrefs.includes('#wp1-sub-heading'))
|
|
assert.ok(childHrefs.includes('#wiki-page-1'))
|
|
assert.equal(
|
|
$('.wiki-content a[data-wiki-path="docs/child/grand"]').attr('href'),
|
|
'#wp2-child-heading',
|
|
)
|
|
assert.ok(
|
|
$('.wiki-content a[data-wiki-path="docs"]')
|
|
.toArray()
|
|
.map((element) => $(element).attr('href'))
|
|
.includes('#wiki-page-0'),
|
|
)
|
|
})
|
|
|
|
test('sanitizes scripts, event handlers, forms and iframes', async () => {
|
|
const html = await build()
|
|
const $ = cheerio.load(html)
|
|
|
|
assert.equal($('script').length, 0)
|
|
assert.equal($('[onclick]').length, 0)
|
|
assert.equal($('form').length, 0)
|
|
assert.equal($('input').length, 0)
|
|
assert.equal($('iframe').length, 0)
|
|
assert.equal($('.pdf-embed a[href="https://wiki.example.com/embedded"]').length, 1)
|
|
assert.match($('.wiki-content').text(), /Form content/)
|
|
})
|
|
|
|
test('resolves images even when embedding is disabled', async () => {
|
|
const html = await build()
|
|
const $ = cheerio.load(html)
|
|
|
|
const image = $('img[alt="Picture"]')
|
|
assert.equal(image.attr('src'), 'https://wiki.example.com/assets/pic.png')
|
|
assert.equal(image.attr('srcset'), undefined)
|
|
assert.equal(image.attr('loading'), undefined)
|
|
})
|
|
|
|
test('supports wikis without URL locale namespacing', async () => {
|
|
const withoutNamespacing = pages.map((page) => ({
|
|
...page,
|
|
render: page.render.replaceAll('/en/', '/'),
|
|
}))
|
|
|
|
const html = await build({
|
|
pages: withoutNamespacing,
|
|
localization: { namespacing: false, namespaces: [] },
|
|
})
|
|
const $ = cheerio.load(html)
|
|
|
|
const childHrefs = $('.wiki-content a[data-wiki-path="docs/child"]')
|
|
.toArray()
|
|
.map((element) => $(element).attr('href'))
|
|
assert.ok(childHrefs.includes('#wp1-sub-heading'))
|
|
assert.ok(childHrefs.includes('#wiki-page-1'))
|
|
assert.equal(
|
|
$('.wiki-content a[data-wiki-path="docs/child/grand"]').attr('href'),
|
|
'#wp2-child-heading',
|
|
)
|
|
assert.ok(
|
|
$('.wiki-content a[data-wiki-path="docs"]')
|
|
.toArray()
|
|
.map((element) => $(element).attr('href'))
|
|
.includes('#wiki-page-0'),
|
|
)
|
|
})
|
|
|
|
test('includes only the exported pages in the table of contents', async () => {
|
|
const html = await build()
|
|
const $ = cheerio.load(html)
|
|
|
|
const tocHrefs = $('.toc-entry a').toArray().map((element) => $(element).attr('href'))
|
|
assert.deepEqual(tocHrefs, ['#wiki-page-0', '#wiki-page-1', '#wiki-page-2'])
|
|
assert.equal($('.wiki-page').length, 3)
|
|
})
|