fix: viewer PDF (TOC, largeur) et plein ecran assistant (BUG-061 a BUG-063)
This commit is contained in:
@@ -89,4 +89,49 @@ test.describe('PDF viewer — affichage inline (BUG-060)', () => {
|
||||
// Aucune violation CSP liée à object-src pendant l'ouverture
|
||||
expect(cspViolations).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('PDF viewer — TOC & plein largeur', () => {
|
||||
|
||||
test('les entrées de la TOC rechargent l\'iframe sur la page ciblée (#page=N)', async ({ page }) => {
|
||||
await login(page);
|
||||
await openFile(page, 'TestVault', 'sample-pdf-toc.pdf');
|
||||
|
||||
const tocLinks = page.locator('#content-area .pdf-toc a[data-page]');
|
||||
await expect(tocLinks).toHaveCount(3, { timeout: 10000 });
|
||||
|
||||
await page.locator('#content-area .pdf-toc a[data-page="3"]').click();
|
||||
await expect(page.locator('#content-area .pdf-iframe')).toHaveAttribute(
|
||||
'src',
|
||||
/\/pdf\/stream\?path=.*#page=3$/,
|
||||
{ timeout: 5000 },
|
||||
);
|
||||
});
|
||||
|
||||
test('le PDF occupe toute la largeur quand la navigation est masquée', async ({ page }) => {
|
||||
await login(page);
|
||||
await openFile(page, 'TestVault', 'sample-pdf-toc.pdf');
|
||||
await expect(page.locator('#content-area .pdf-viewer-container')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Masquer la barre de navigation (bouton réel).
|
||||
await page.locator('#sidebar-toggle-btn').click();
|
||||
await expect(page.locator('#sidebar')).toHaveClass(/hidden/);
|
||||
|
||||
const widths = await page.evaluate(() => {
|
||||
const area = document.getElementById('content-area');
|
||||
const cs = getComputedStyle(area);
|
||||
const container = document.querySelector('.pdf-viewer-container');
|
||||
const contentWidth =
|
||||
area.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight);
|
||||
return {
|
||||
container: container.getBoundingClientRect().width,
|
||||
contentWidth,
|
||||
maxWidth: cs.maxWidth,
|
||||
};
|
||||
});
|
||||
|
||||
// Le plafond de lecture (1200px) ne doit plus s'appliquer au viewer PDF.
|
||||
expect(widths.maxWidth).toBe('none');
|
||||
expect(Math.abs(widths.container - widths.contentWidth)).toBeLessThan(2);
|
||||
});
|
||||
});
|
||||
@@ -786,6 +786,29 @@ async function main() {
|
||||
panel.remove();
|
||||
});
|
||||
|
||||
// ── 10b. Fullscreen toggle must beat the persisted inline width ──
|
||||
await test("fullscreen button toggles the panel and CSS lifts the inline width", async () => {
|
||||
const { readFileSync } = await import("node:fs");
|
||||
const css = readFileSync(path.resolve(JS_DIR, "..", "style.css"), "utf-8");
|
||||
assert.match(
|
||||
css,
|
||||
/\.bookslm-panel\.fullscreen\s*\{\s*width:\s*100vw\s*!important;/,
|
||||
"the fullscreen width must override the inline width written by the resize handle",
|
||||
);
|
||||
|
||||
const b = new BooksLM();
|
||||
const panel = b._render();
|
||||
document.body.appendChild(panel);
|
||||
const btn = panel.querySelector(".bookslm-btn-fullscreen");
|
||||
btn.click();
|
||||
assert.ok(panel.classList.contains("fullscreen"), "fullscreen class added on first click");
|
||||
assert.equal(b._isFullscreen, true);
|
||||
btn.click();
|
||||
assert.ok(!panel.classList.contains("fullscreen"), "fullscreen class removed on second click");
|
||||
assert.equal(b._isFullscreen, false);
|
||||
panel.remove();
|
||||
});
|
||||
|
||||
// ── 11. Formatted markdown rendering ──
|
||||
await test("_renderMarkdown renders headings, lists, code and tables", () => {
|
||||
const b = new BooksLM();
|
||||
|
||||
@@ -39,12 +39,12 @@ test("viewer.js — PDF branch renders the stream in an <iframe class=pdf-iframe
|
||||
assert.ok(block, "PDF render block not found");
|
||||
assert.match(
|
||||
block[1],
|
||||
/<iframe src="\$\{pdfUrl\}" class="pdf-iframe"/,
|
||||
/<iframe src="\$\{pdfUrl\}" data-pdf-url="\$\{pdfUrl\}" class="pdf-iframe"/,
|
||||
"PDF must use <iframe>, not <embed>/<object> (CSP object-src 'none' otherwise blocks it)",
|
||||
);
|
||||
assert.match(
|
||||
block[1],
|
||||
/\.pdf-iframe[\s\S]*?src="\$\{pdfUrl\}"/,
|
||||
/<iframe src="\$\{pdfUrl\}"/,
|
||||
"iframe src must come from the /pdf/stream URL",
|
||||
);
|
||||
});
|
||||
@@ -54,12 +54,37 @@ test("viewer.js — no <embed>/<object> left in the source", () => {
|
||||
assert.doesNotMatch(viewer, /<object\b/i, "<object> is blocked by CSP object-src 'none'");
|
||||
});
|
||||
|
||||
test("viewer.js — TOC still targets the pdf-iframe via contentWindow", () => {
|
||||
test("viewer.js — TOC links carry a data-page and never poke contentWindow", () => {
|
||||
assert.match(
|
||||
viewer,
|
||||
/document\.querySelector\('\.pdf-iframe'\)\.contentWindow\.location\.hash='page=\$\{item\.page\}'/,
|
||||
"TOC links must keep navigating the iframe",
|
||||
/<a href="#" data-page="\$\{item\.page\}">/,
|
||||
"TOC links must expose the target page via data-page",
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
viewer,
|
||||
/contentWindow\.location\.hash\s*=/,
|
||||
"contentWindow is about:blank in the native PDF viewer: hash navigation never reaches the document",
|
||||
);
|
||||
});
|
||||
|
||||
test("viewer.js — navigatePdfToPage reloads the iframe with the #page fragment", () => {
|
||||
const fn = viewer.match(/export function navigatePdfToPage\(area, page\) \{([\s\S]*?)\n\}/);
|
||||
assert.ok(fn, "navigatePdfToPage helper not found");
|
||||
assert.match(fn[1], /data-pdf-url/, "the base URL must be preserved without the fragment");
|
||||
assert.match(fn[1], /iframe\.src = `\$\{base\}#page=\$\{page\}`/, "the iframe must be reloaded with #page=N");
|
||||
});
|
||||
|
||||
test("style.css — full-bleed viewers ignore the centered reading width", () => {
|
||||
assert.match(
|
||||
css,
|
||||
/\.sidebar\.hidden ~ \.content-wrapper \.content-area:has\(\.pdf-viewer-container\)/,
|
||||
"PDF viewer must fill the width when the navigation sidebar is hidden",
|
||||
);
|
||||
const rule = css.match(
|
||||
/\.content-area:has\(\.pdf-viewer-container\)[\s\S]*?\{([^}]*)\}/,
|
||||
);
|
||||
assert.ok(rule, "full-bleed rule not found");
|
||||
assert.match(rule[1], /max-width:\s*none/, "the 1200px reading cap must be lifted");
|
||||
});
|
||||
|
||||
// ── backend : la CSP autorise le cadre same-origin ─────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user