diff --git a/test/integration/reorganize_pages_spec.mjs b/test/integration/reorganize_pages_spec.mjs index 9a6d060e1..8d7572e77 100644 --- a/test/integration/reorganize_pages_spec.mjs +++ b/test/integration/reorganize_pages_spec.mjs @@ -28,24 +28,13 @@ import { kbDelete, loadAndWait, scrollIntoView, + showViewsManager, waitAndClick, waitForDOMMutation, } from "./test_utils.mjs"; async function waitForThumbnailVisible(page, pageNums) { - const hasAnimations = await page.evaluate( - () => !window.matchMedia("(prefers-reduced-motion: reduce)").matches - ); - await page.click("#viewsManagerToggleButton"); - if (hasAnimations) { - await page.waitForSelector("#outerContainer.viewsManagerMoving", { - visible: true, - }); - } - await page.waitForSelector( - "#outerContainer:not(.viewsManagerMoving).viewsManagerOpen", - { visible: true } - ); + await showViewsManager(page); const thumbSelector = "#thumbnailsView .thumbnailImageContainer > img"; await page.waitForSelector(thumbSelector, { visible: true }); diff --git a/test/integration/test_utils.mjs b/test/integration/test_utils.mjs index 6e561ac1a..b3f3416c7 100644 --- a/test/integration/test_utils.mjs +++ b/test/integration/test_utils.mjs @@ -964,6 +964,26 @@ async function highlightSpan( await page.waitForSelector(getEditorSelector(nextId)); } +async function showViewsManager(page) { + const hasAnimations = await page.evaluate( + () => !window.matchMedia("(prefers-reduced-motion: reduce)").matches + ); + const movingPromise = hasAnimations + ? page.waitForSelector("#outerContainer.viewsManagerMoving", { + visible: true, + }) + : Promise.resolve(); + await page.click("#viewsManagerToggleButton"); + if (hasAnimations) { + await movingPromise; + } + await page.waitForSelector("#viewsManager", { visible: true }); + await page.waitForSelector( + "#outerContainer:not(.viewsManagerMoving).viewsManagerOpen", + { visible: true } + ); +} + // Unicode bidi isolation characters, Fluent adds these markers to the text. const FSI = "\u2068"; const PDI = "\u2069"; @@ -1030,6 +1050,7 @@ export { selectEditors, serializeBitmapDimensions, setCaretAt, + showViewsManager, switchToEditor, unselectEditor, waitAndClick, diff --git a/test/integration/thumbnail_view_spec.mjs b/test/integration/thumbnail_view_spec.mjs index 919431733..28e11fd4d 100644 --- a/test/integration/thumbnail_view_spec.mjs +++ b/test/integration/thumbnail_view_spec.mjs @@ -5,6 +5,7 @@ import { kbFocusNext, loadAndWait, PDI, + showViewsManager, } from "./test_utils.mjs"; function waitForThumbnailVisible(page, pageNum) { @@ -29,23 +30,6 @@ async function waitForMenu(page, buttonSelector, visible = true) { ); } -async function showViewsManager(page) { - const hasAnimations = await page.evaluate( - () => !window.matchMedia("(prefers-reduced-motion: reduce)").matches - ); - await page.click("#viewsManagerToggleButton"); - if (hasAnimations) { - await page.waitForSelector("#outerContainer.viewsManagerMoving", { - visible: true, - }); - } - await page.waitForSelector("#viewsManager", { visible: true }); - await page.waitForSelector( - "#outerContainer:not(.viewsManagerMoving).viewsManagerOpen", - { visible: true } - ); -} - describe("PDF Thumbnail View", () => { describe("Works without errors", () => { let pages; diff --git a/test/integration/viewer_spec.mjs b/test/integration/viewer_spec.mjs index 68f5e69c7..b9b3f07d1 100644 --- a/test/integration/viewer_spec.mjs +++ b/test/integration/viewer_spec.mjs @@ -21,6 +21,7 @@ import { getSpanRectFromText, loadAndWait, scrollIntoView, + showViewsManager, waitForPageChanging, waitForPageRendered, } from "./test_utils.mjs"; @@ -1451,6 +1452,82 @@ describe("PDF viewer", () => { }); }); + describe("Outline tree shift-click toggle (PR 20740)", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait( + "nested_outline.pdf", + "#viewsManagerToggleButton" + ); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("should only toggle the clicked item's subtree, not the whole outline", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + // Open the sidebar. + await showViewsManager(page); + + // Switch to outline view. + await page.click("#viewsManagerSelectorButton"); + await page.waitForSelector("#outlinesViewMenu", { visible: true }); + await page.click("#outlinesViewMenu"); + + // Wait for the outline tree to render with nesting (toggle buttons). + await page.waitForSelector("#outlinesView.withNesting"); + + // Initially all three top-level togglers must be expanded. + const initialHiddenCount = await page.$$eval( + "#outlinesView > .treeItem > .treeItemToggler", + togglers => + togglers.filter(t => t.classList.contains("treeItemsHidden")) + .length + ); + expect(initialHiddenCount).withContext(`In ${browserName}`).toBe(0); + + // Shift-click the first top-level toggler (section "1. Introduction") + // to collapse only its subtree. + // The toggler has width/height 0 (visual content via ::before), so + // we dispatch the MouseEvent directly rather than using page.click(). + await page.evaluate(() => { + const toggler = document.querySelector( + "#outlinesView > .treeItem:nth-child(1) > .treeItemToggler" + ); + toggler.dispatchEvent( + new MouseEvent("click", { + shiftKey: true, + bubbles: true, + cancelable: true, + }) + ); + }); + + // Section 1's toggler must now be collapsed. + const section1Collapsed = await page.$eval( + "#outlinesView > .treeItem:nth-child(1) > .treeItemToggler", + t => t.classList.contains("treeItemsHidden") + ); + expect(section1Collapsed).withContext(`In ${browserName}`).toBeTrue(); + + // Sections 2 and 3 must remain expanded (the bug collapsed the whole + // outline by passing `this.container` instead of + // `target.parentNode`). + const otherHiddenCount = await page.$$eval( + "#outlinesView > .treeItem:nth-child(n+2) > .treeItemToggler", + togglers => + togglers.filter(t => t.classList.contains("treeItemsHidden")) + .length + ); + expect(otherHiddenCount).withContext(`In ${browserName}`).toBe(0); + }) + ); + }); + }); + describe("Scroll into view", () => { let pages; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 86cd69044..ea075e8b6 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -874,3 +874,4 @@ !bug2013793.pdf !bug2014080.pdf !two_pages.pdf +!nested_outline.pdf diff --git a/test/pdfs/nested_outline.pdf b/test/pdfs/nested_outline.pdf new file mode 100644 index 000000000..fa4a2558e Binary files /dev/null and b/test/pdfs/nested_outline.pdf differ