♻️(frontend) let print DOM helpers target a custom root

wrapMediaWithLink / wrapInterlinksWithAnchor defaulted to document.
Accept a root node so the presenter export can reuse them on its
off-screen tree.
This commit is contained in:
Nathan Panchout
2026-09-04 11:35:24 +02:00
parent 3bede0d9a0
commit f3dc31c13d
2 changed files with 74 additions and 4 deletions
@@ -0,0 +1,70 @@
import { afterEach, describe, expect, test } from 'vitest';
import { wrapInterlinksWithAnchor, wrapMediaWithLink } from '../utils_print';
describe('print DOM helpers', () => {
afterEach(() => {
document.body.innerHTML = '';
});
test('wraps media links only inside the provided root', () => {
document.body.innerHTML = `
<main>
<div id="outside" data-content-type="video" data-url="https://example.com/outside.mp4" data-name="outside.mp4">
<div class="bn-file-block-content-wrapper"></div>
</div>
<div id="root">
<div id="inside" data-content-type="audio" data-url="https://example.com/inside.mp3" data-name="inside.mp3">
<div class="bn-file-block-content-wrapper"></div>
</div>
</div>
</main>
`;
const root = document.getElementById('root');
if (!root) {
throw new Error('missing root');
}
const cleanup = wrapMediaWithLink(root);
const inside = document.getElementById('inside');
const outside = document.getElementById('outside');
expect(inside?.firstElementChild?.shadowRoot?.textContent).toContain(
'inside.mp3',
);
expect(outside?.firstElementChild?.shadowRoot).toBeNull();
cleanup();
expect(inside?.firstElementChild?.shadowRoot).toBeNull();
});
test('wraps interlinks only inside the provided root', () => {
document.body.innerHTML = `
<main>
<span class="--docs--interlinking-link-inline-content" data-href="https://example.com/outside">outside</span>
<div id="root">
<span class="--docs--interlinking-link-inline-content" data-href="https://example.com/inside">inside</span>
</div>
</main>
`;
const root = document.getElementById('root');
if (!root) {
throw new Error('missing root');
}
const cleanup = wrapInterlinksWithAnchor(root);
expect(root.querySelector('a[data-print-link]')?.textContent).toBe(
'inside',
);
expect(document.querySelectorAll('a[data-print-link]')).toHaveLength(1);
cleanup();
expect(document.querySelectorAll('a[data-print-link]')).toHaveLength(0);
});
});
@@ -175,7 +175,7 @@ function appendPrintOnlyStyles() {
/**
* Wraps media elements with links to their source URLs for printing.
*/
function wrapMediaWithLink() {
export function wrapMediaWithLink(root: ParentNode = document) {
const createdShadowWrapper: HTMLElement[] = [];
const prependLink = (
@@ -222,7 +222,7 @@ function wrapMediaWithLink() {
createdShadowWrapper.push(shadowWrapper);
};
document
root
.querySelectorAll(
'[data-content-type="pdf"], [data-content-type="file"], [data-content-type="audio"], [data-content-type="video"]',
)
@@ -248,14 +248,14 @@ function wrapMediaWithLink() {
* Wraps interlink inline content with anchor tags for printing,
* so they appear as clickable links in the printed PDF.
*/
function wrapInterlinksWithAnchor() {
export function wrapInterlinksWithAnchor(root: ParentNode = document) {
const wrappedElements: Array<{
el: Element;
anchor: HTMLAnchorElement;
parent: Node;
}> = [];
document
root
.querySelectorAll('.--docs--interlinking-link-inline-content[data-href]')
.forEach((el) => {
const href = el.getAttribute('data-href');