/* Copyright 2026 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { awaitPromise, clearInput, closePages, createPromise, createPromiseWithArgs, dragAndDrop, FSI, getAnnotationSelector, getRect, getThumbnailSelector, kbCopy, kbCut, kbDelete, loadAndWait, PDI, scrollIntoView, showViewsManager, waitAndClick, waitForDOMMutation, waitForTextToBe, waitForTooltipToBe, } from "./test_utils.mjs"; async function waitForThumbnailVisible(page, pageNums) { await showViewsManager(page); const thumbSelector = "#thumbnailsView .thumbnailImageContainer > img"; await page.waitForSelector(thumbSelector, { visible: true }); if (!pageNums) { return null; } if (!Array.isArray(pageNums)) { pageNums = [pageNums]; } return Promise.all( pageNums.map(pageNum => page.waitForSelector(getThumbnailSelector(pageNum), { visible: true }) ) ); } function waitForPagesEdited(page, type) { return createPromiseWithArgs( page, resolve => { const listener = ({ pagesMapper, type: ty }) => { // eslint-disable-next-line no-undef if (args[0] && args[0] !== ty) { return; } window.PDFViewerApplication.eventBus.off("pagesedited", listener); resolve(Array.from(pagesMapper.getMapping() || [])); }; window.PDFViewerApplication.eventBus.on("pagesedited", listener); }, [type] ); } async function waitForHavingContents(page, expected) { await page.evaluate(() => { // Make sure all the pages will be visible. window.PDFViewerApplication.pdfViewer.scrollMode = 2 /* = ScrollMode.WRAPPED = */; window.PDFViewerApplication.pdfViewer.updateScale({ drawingDelay: 0, scaleFactor: 0.01, }); }); return page.waitForFunction( ex => { const buffer = []; for (const textLayer of document.querySelectorAll(".textLayer")) { buffer.push(parseInt(textLayer.textContent.trim(), 10)); } return ex.length === buffer.length && ex.every((v, i) => v === buffer[i]); }, {}, expected ); } function getSearchResults(page) { return page.evaluate(() => { const pages = document.querySelectorAll(".page"); const results = []; for (let i = 0; i < pages.length; i++) { const domPage = pages[i]; const highlights = domPage.querySelectorAll("span.highlight"); if (highlights.length === 0) { continue; } results.push([ i + 1, Array.from(highlights).map(span => span.textContent), ]); } return results; }); } function movePages(page, selectedPages, atIndex) { return page.evaluate( (selected, index) => { const viewer = window.PDFViewerApplication.pdfViewer; const pagesMapper = viewer.pdfDocument.pagesMapper; const pagesToMove = Array.from(selected).sort((a, b) => a - b); pagesMapper.pagesNumber = document.querySelectorAll(".page").length; pagesMapper.movePages(new Set(pagesToMove), pagesToMove, index); window.PDFViewerApplication.eventBus.dispatch("pagesedited", { pagesMapper, index, pagesToMove, }); }, selectedPages, atIndex ); } describe("Reorganize Pages View", () => { describe("Drag & Drop", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "page-fit", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should show a drag marker when dragging a thumbnail", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); const rect1 = await getRect(page, getThumbnailSelector(1)); const rect2 = await getRect(page, getThumbnailSelector(2)); const handleAddedMarker = await waitForDOMMutation( page, mutationList => { for (const mutation of mutationList) { if (mutation.type !== "childList") { continue; } for (const node of mutation.addedNodes) { if (node.classList?.contains("dragMarker")) { return true; } } } return false; } ); const handleRemovedMarker = await waitForDOMMutation( page, mutationList => { for (const mutation of mutationList) { if (mutation.type !== "childList") { continue; } for (const node of mutation.removedNodes) { if (node.classList?.contains("dragMarker")) { return true; } } } return false; } ); await dragAndDrop( page, getThumbnailSelector(1), [[0, rect2.y - rect1.y + rect2.height / 2]], 10 ); await awaitPromise(handleAddedMarker); await awaitPromise(handleRemovedMarker); }) ); }); it("should reorder thumbnails after dropping", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); const rect1 = await getRect(page, getThumbnailSelector(1)); const rect2 = await getRect(page, getThumbnailSelector(2)); const handlePagesEdited = await waitForPagesEdited(page); await dragAndDrop( page, getThumbnailSelector(1), [[0, rect2.y - rect1.y + rect2.height / 2]], 10 ); const pagesMapping = await awaitPromise(handlePagesEdited); const expected = [ 2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pagesMapping) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); }) ); }); it("should reorder thumbnails after dropping at position 0", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); const rect1 = await getRect(page, getThumbnailSelector(1)); const rect2 = await getRect(page, getThumbnailSelector(2)); const handlePagesEdited = await waitForPagesEdited(page); await dragAndDrop( page, getThumbnailSelector(2), [[0, rect1.y - rect2.y - rect1.height]], 10 ); const pagesMapping = await awaitPromise(handlePagesEdited); const expected = [ 2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pagesMapping) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); }) ); }); it("should reorder thumbnails after dropping two adjacent pages", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); const rect2 = await getRect(page, getThumbnailSelector(2)); const rect4 = await getRect(page, getThumbnailSelector(4)); await page.click(`.thumbnail:has(${getThumbnailSelector(1)}) input`); const handlePagesEdited = await waitForPagesEdited(page); await dragAndDrop( page, getThumbnailSelector(2), [[0, rect4.y - rect2.y]], 10 ); const pagesMapping = await awaitPromise(handlePagesEdited); const expected = [ 3, 4, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pagesMapping) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); }) ); }); it("should reorder thumbnails after dropping two non-adjacent pages", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); const rect1 = await getRect(page, getThumbnailSelector(1)); const rect2 = await getRect(page, getThumbnailSelector(2)); await (await page.$(".thumbnail[page-number='14'")).scrollIntoView(); await page.waitForSelector(getThumbnailSelector(14), { visible: true, }); await page.click(`.thumbnail:has(${getThumbnailSelector(14)}) input`); await (await page.$(".thumbnail[page-number='1'")).scrollIntoView(); await page.waitForSelector(getThumbnailSelector(1), { visible: true, }); const handlePagesEdited = await waitForPagesEdited(page); await dragAndDrop( page, getThumbnailSelector(1), [[0, rect2.y - rect1.y + rect2.height / 2]], 10 ); const pagesMapping = await awaitPromise(handlePagesEdited); const expected = [ 2, 1, 14, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, ]; expect(pagesMapping) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); }) ); }); it("should select the dropped page (bug 2010820)", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); const rect1 = await getRect(page, getThumbnailSelector(1)); const rect2 = await getRect(page, getThumbnailSelector(2)); await page.click(getThumbnailSelector(2)); await page.waitForSelector( `${getThumbnailSelector(2)}[aria-current="page"]` ); const handlePagesEdited = await waitForPagesEdited(page); await dragAndDrop( page, getThumbnailSelector(1), [[0, rect2.y - rect1.y + rect2.height / 2]], 10 ); await awaitPromise(handlePagesEdited); await page.waitForSelector( `${getThumbnailSelector(2)}[aria-current="page"]` ); await page.waitForSelector( `${getThumbnailSelector(1)}[aria-current="false"]` ); }) ); }); }); describe("Search in pdf", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should check if the search is working after moving pages", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await page.click("#viewFindButton"); await page.waitForSelector(":has(> #findHighlightAll)", { visible: true, }); await page.click(":has(> #findHighlightAll)"); await page.waitForSelector("#findInput", { visible: true }); await page.type("#findInput", "1"); await page.keyboard.press("Enter"); await page.waitForFunction( () => document.querySelectorAll("span.highlight").length === 10 ); let results = await getSearchResults(page); expect(results) .withContext(`In ${browserName}`) .toEqual([ // Page number, [matches] [1, ["1"]], [10, ["1"]], [11, ["1", "1"]], [12, ["1"]], [13, ["1"]], [14, ["1"]], [15, ["1"]], [16, ["1"]], [17, ["1"]], ]); await movePages(page, [11, 2], 3); await page.waitForFunction( () => document.querySelectorAll("span.highlight").length === 0 ); await clearInput(page, "#findInput", true); await page.type("#findInput", "1"); await page.keyboard.press("Enter"); await page.waitForFunction( () => document.querySelectorAll("span.highlight").length === 10 ); results = await getSearchResults(page); expect(results) .withContext(`In ${browserName}`) .toEqual([ // Page number, [matches] [1, ["1"]], [4, ["1", "1"]], [11, ["1"]], [12, ["1"]], [13, ["1"]], [14, ["1"]], [15, ["1"]], [16, ["1"]], [17, ["1"]], ]); await movePages(page, [13], 0); await page.waitForFunction( () => document.querySelectorAll("span.highlight").length === 0 ); await clearInput(page, "#findInput", true); await page.type("#findInput", "1"); await page.keyboard.press("Enter"); await page.waitForFunction( () => document.querySelectorAll("span.highlight").length === 10 ); results = await getSearchResults(page); expect(results) .withContext(`In ${browserName}`) .toEqual([ // Page number, [matches] [1, ["1"]], [2, ["1"]], [5, ["1", "1"]], [12, ["1"]], [13, ["1"]], [14, ["1"]], [15, ["1"]], [16, ["1"]], [17, ["1"]], ]); }) ); }); }); describe("Links and outlines", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number_and_link.pdf", "#viewsManagerToggleButton", "page-fit", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should check that link is updated after moving pages", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await movePages(page, [2], 10); await scrollIntoView(page, getAnnotationSelector("107R")); await page.click(getAnnotationSelector("107R")); const currentPage = await page.$eval( "#pageNumber", el => el.valueAsNumber ); expect(currentPage).withContext(`In ${browserName}`).toBe(10); }) ); }); it("should check that outlines are updated after moving pages", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await movePages(page, [2, 4], 10); await page.click("#viewsManagerSelectorButton"); await page.click("#outlinesViewMenu"); await page.waitForSelector("#outlinesView", { visible: true }); await page.click("#outlinesView .treeItem:nth-child(2)"); const currentPage = await page.$eval( "#pageNumber", el => el.valueAsNumber ); // 9 because 2 and 4 were moved after page 10. expect(currentPage).withContext(`In ${browserName}`).toBe(9); }) ); }); }); describe("Drag marker must have the right non-zero dimensions", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number_and_link.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true, sidebarViewOnLoad: 2 /* = SidebarView.OUTLINES */, } ); }); afterEach(async () => { await closePages(pages); }); it("should check if the drag marker width is non-zero", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await page.waitForSelector("#outlinesView", { visible: true }); await page.waitForSelector("#viewsManagerSelectorButton", { visible: true, }); await page.click("#viewsManagerSelectorButton"); await page.waitForSelector("#thumbnailsViewMenu", { visible: true }); await page.click("#thumbnailsViewMenu"); const thumbSelector = "#thumbnailsView .thumbnailImageContainer > img"; await page.waitForSelector(thumbSelector, { visible: true }); const rect1 = await getRect(page, getThumbnailSelector(1)); const rect2 = await getRect(page, getThumbnailSelector(2)); const handleAddedMarker = await waitForDOMMutation( page, mutationList => { for (const mutation of mutationList) { if (mutation.type !== "childList") { continue; } for (const node of mutation.addedNodes) { if (node.classList?.contains("dragMarker")) { const rect = node.getBoundingClientRect(); return rect.width !== 0; } } } return false; } ); await dragAndDrop( page, getThumbnailSelector(1), [[0, rect2.y - rect1.y + rect2.height / 2]], 10 ); await awaitPromise(handleAddedMarker); }) ); }); }); describe("Save a pdf", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should check that a save is triggered", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); const rect1 = await getRect(page, getThumbnailSelector(1)); const rect2 = await getRect(page, getThumbnailSelector(2)); await dragAndDrop( page, getThumbnailSelector(1), [[0, rect2.y - rect1.y + rect2.height / 2]], 10 ); const handleSave = await createPromise(page, resolve => { window.PDFViewerApplication.onSavePages = async ({ data }) => { resolve(Array.from(data[0].pageIndices)); }; }); await waitAndClick(page, "#downloadButton"); const pageIndices = await awaitPromise(handleSave); expect(pageIndices) .withContext(`In ${browserName}`) .toEqual([ 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, ]); }) ); }); }); describe("Delete some pages", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should check that the pages are deleted", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(3)}) input` ); const handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionDelete"); const pageIndices = await awaitPromise(handlePagesEdited); const expected = [ 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); }) ); }); it("should update the page count in the toolbar after deletion (bug 2018125)", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); const initialCount = await page.$eval("#pageNumber", el => el.max); expect(parseInt(initialCount, 10)) .withContext(`In ${browserName}`) .toBe(17); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(3)}) input` ); const handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionDelete"); await awaitPromise(handlePagesEdited); await page.waitForFunction( () => document.querySelector("#pageNumber").max === "15" ); }) ); }); }); describe("Cut and paste some pages", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should check that the pages has been cut and pasted correctly", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(3)}) input` ); let handlePagesEdited = await waitForPagesEdited(page, "cut"); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCut"); let pageIndices = await awaitPromise(handlePagesEdited); let expected = [2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, `${getThumbnailSelector(1)}+button`); pageIndices = await awaitPromise(handlePagesEdited); expected = [ 2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); }) ); }); }); describe("Copy and paste some pages", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should check that the paste button spans have the right l10n id depending on their position", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); const handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCopy"); await awaitPromise(handlePagesEdited); await waitForTextToBe( page, `button.thumbnailPasteButton:has(+ ${getThumbnailSelector(1)}) > span`, "Paste" ); await waitForTooltipToBe( page, `button.thumbnailPasteButton:has(+ ${getThumbnailSelector(1)})`, "Paste before the first page" ); await waitForTextToBe( page, `${getThumbnailSelector(1)} + button.thumbnailPasteButton > span`, "Paste" ); await waitForTooltipToBe( page, `${getThumbnailSelector(1)} + button.thumbnailPasteButton`, `Paste after page ${FSI}1${PDI}` ); }) ); }); it("should check that a page can be copied and pasted before the first thumbnail", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(2)}) input` ); let handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCopy"); let pageIndices = await awaitPromise(handlePagesEdited); let expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); handlePagesEdited = await waitForPagesEdited(page); await waitAndClick( page, `button.thumbnailPasteButton:has(+ ${getThumbnailSelector(1)})` ); pageIndices = await awaitPromise(handlePagesEdited); expected = [ 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); }) ); }); it("should check that the pages has been copied and pasted correctly", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(3)}) input` ); let handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCopy"); let pageIndices = await awaitPromise(handlePagesEdited); let expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, `${getThumbnailSelector(2)}+button`); pageIndices = await awaitPromise(handlePagesEdited); expected = [ 1, 2, 1, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); }) ); }); it("should have only one paste button next to the second thumbnail after cut, paste, and copy", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); // Select page 1 and cut it. await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); let handlePagesEdited = await waitForPagesEdited(page, "cut"); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCut"); await awaitPromise(handlePagesEdited); // Paste after the new first thumbnail (originally page 2). handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, `${getThumbnailSelector(1)}+button`); await awaitPromise(handlePagesEdited); // Select the new first thumbnail and copy it. handlePagesEdited = await waitForPagesEdited(page); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCopy"); await awaitPromise(handlePagesEdited); // The second thumbnail should have only one paste button after it. await page.waitForSelector( `${getThumbnailSelector(2)} + button.thumbnailPasteButton`, { visible: true } ); const pasteButtons = await page.$$( `${getThumbnailSelector(2)} + button.thumbnailPasteButton` ); expect(pasteButtons.length).withContext(`In ${browserName}`).toBe(1); }) ); }); it("should disable delete and cut entries when all pages are selected", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); // Select all pages. const totalPages = await page.evaluate( () => document.querySelectorAll("#thumbnailsView .thumbnail input") .length ); for (let i = 1; i <= totalPages; i++) { await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(i)}) input` ); } await waitAndClick(page, "#viewsManagerStatusActionButton"); await page.waitForSelector( "#viewsManagerStatusActionDelete:disabled" ); await page.waitForSelector("#viewsManagerStatusActionCut:disabled"); await page.waitForSelector( "#viewsManagerStatusActionCopy:not(:disabled)" ); await page.waitForSelector( "#viewsManagerStatusActionExport:not(:disabled)" ); await page.keyboard.press("Escape"); }) ); }); }); describe("Keyboard shortcuts for cut and copy (bug 2018139)", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should cut pages with Ctrl+X and paste them", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(3)}) input` ); let handlePagesEdited = await waitForPagesEdited(page, "cut"); await kbCut(page); let pageIndices = await awaitPromise(handlePagesEdited); let expected = [2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, `${getThumbnailSelector(1)}+button`); pageIndices = await awaitPromise(handlePagesEdited); expected = [ 2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); }) ); }); it("should copy pages with Ctrl+C and paste them", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(3)}) input` ); let handlePagesEdited = await waitForPagesEdited(page, "copy"); await kbCopy(page); let pageIndices = await awaitPromise(handlePagesEdited); let expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, `${getThumbnailSelector(2)}+button`); pageIndices = await awaitPromise(handlePagesEdited); expected = [ 1, 2, 1, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); }) ); }); }); describe("Keyboard shortcuts for delete (bug 2010831)", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should delete pages with the Delete key", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(3)}) input` ); const handlePagesEdited = await waitForPagesEdited(page); await kbDelete(page); const pageIndices = await awaitPromise(handlePagesEdited); const expected = [ 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await waitForHavingContents(page, expected); }) ); }); }); describe("Thumbnails are not blank after cut/paste (bug 2018162)", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "two_pages.pdf", "#viewsManagerToggleButton", "page-fit", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should check that the thumbnail has a blob src after cut and paste", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); for (let i = 1; i <= 2; i++) { await page.waitForSelector( `${getThumbnailSelector(i)} > img[src^="blob:http:"]`, { visible: true } ); } let handlePagesEdited = await waitForPagesEdited(page, "cut"); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCut"); await awaitPromise(handlePagesEdited); handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, `${getThumbnailSelector(1)}+button`); await awaitPromise(handlePagesEdited); for (let i = 1; i <= 2; i++) { await page.waitForSelector( `${getThumbnailSelector(i)} > img[src^="blob:http:"]`, { visible: true } ); } }) ); }); }); describe("Focus stays in sidebar after page operations (bug 2020731)", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should keep focus on a thumbnail after deleting pages", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); const handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionDelete"); await awaitPromise(handlePagesEdited); await page.waitForSelector( "#thumbnailsView .thumbnailImageContainer:focus", { visible: true, } ); }) ); }); it("should keep focus on a thumbnail after pasting pages", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); let handlePagesEdited = await waitForPagesEdited(page, "cut"); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCut"); await awaitPromise(handlePagesEdited); handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, `${getThumbnailSelector(1)}+button`); await awaitPromise(handlePagesEdited); await page.waitForSelector( "#thumbnailsView .thumbnailImageContainer:focus", { visible: true, } ); }) ); }); }); describe("Status label reflects number of checked thumbnails (bug 2010832)", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should update the status label when thumbnails are checked or unchecked", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); const labelSelector = "#viewsManagerStatusActionLabel"; // Initially no pages are selected. await waitForTextToBe(page, labelSelector, "Select pages"); // Check thumbnail 1: label should read "1 selected". await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitForTextToBe(page, labelSelector, `${FSI}1${PDI} selected`); // Check thumbnail 2: label should read "2 selected". await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(2)}) input` ); await waitForTextToBe(page, labelSelector, `${FSI}2${PDI} selected`); // Uncheck thumbnail 1: label should read "1 selected". await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitForTextToBe(page, labelSelector, `${FSI}1${PDI} selected`); // Uncheck thumbnail 2: label should revert to "Select pages". await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(2)}) input` ); await waitForTextToBe(page, labelSelector, "Select pages"); }) ); }); it("should deselect all thumbnails when the deselect button is clicked", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); const labelSelector = "#viewsManagerStatusActionLabel"; const deselectButtonSelector = "#viewsManagerStatusActionDeselectButton"; // Check thumbnails 1 and 2. await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(2)}) input` ); await waitForTextToBe(page, labelSelector, `${FSI}2${PDI} selected`); // Click the deselect button: all thumbnails should be unchecked. await waitAndClick(page, deselectButtonSelector); // Label should revert to "Select pages". await waitForTextToBe(page, labelSelector, "Select pages"); // The deselect button should be hidden again. await page.waitForSelector(deselectButtonSelector, { hidden: true }); // All checkboxes should be unchecked. await page.waitForSelector( "#thumbnailsView:not(:has(input:checked))", { visible: true, } ); }) ); }); }); describe("Undo label reflects number of cut/deleted pages (bug 2010832)", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should show the correct undo label after cutting one or two pages", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); const undoLabelSelector = "#viewsManagerStatusUndoLabel"; // Cut 1 page and check the undo label. await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); let handlePagesEdited = await waitForPagesEdited(page, "cut"); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCut"); await awaitPromise(handlePagesEdited); await page.waitForSelector("#viewsManagerStatusUndo", { visible: true, }); await waitForTextToBe(page, undoLabelSelector, "1 page cut"); // Undo the cut to restore the original state. handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, "#viewsManagerStatusUndoButton"); await awaitPromise(handlePagesEdited); // Cut 2 pages and check the undo label. await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(3)}) input` ); handlePagesEdited = await waitForPagesEdited(page, "cut"); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCut"); await awaitPromise(handlePagesEdited); await page.waitForSelector("#viewsManagerStatusUndo", { visible: true, }); await waitForTextToBe( page, undoLabelSelector, `${FSI}2${PDI} pages cut` ); }) ); }); it("should show the correct undo label after deleting one or two pages", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); const undoLabelSelector = "#viewsManagerStatusUndoLabel"; // Delete 1 page and check the undo label. await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); let handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionDelete"); await awaitPromise(handlePagesEdited); await page.waitForSelector("#viewsManagerStatusUndo", { visible: true, }); await waitForTextToBe(page, undoLabelSelector, "1 page deleted"); // Undo the deletion to restore the original state. handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, "#viewsManagerStatusUndoButton"); await awaitPromise(handlePagesEdited); // Delete 2 pages and check the undo label. await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(3)}) input` ); handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionDelete"); await awaitPromise(handlePagesEdited); await page.waitForSelector("#viewsManagerStatusUndo", { visible: true, }); await waitForTextToBe( page, undoLabelSelector, `${FSI}2${PDI} pages deleted` ); }) ); }); }); describe("Closing the undo bar after a cut is equivalent to a delete (bug 2010832)", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should permanently remove the cut page when the undo bar is closed", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); // Cut page 1. await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); let handlePagesEdited = await waitForPagesEdited(page, "cut"); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCut"); await awaitPromise(handlePagesEdited); await page.waitForSelector("#viewsManagerStatusUndo", { visible: true, }); // Close the undo bar instead of undoing. handlePagesEdited = await waitForPagesEdited(page, "cleanSavedData"); await waitAndClick(page, "#viewsManagerStatusUndoCloseButton"); const pageIndices = await awaitPromise(handlePagesEdited); // The result must equal a plain deletion of page 1. const expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await page.waitForSelector("#viewsManagerStatusUndo", { hidden: true, }); await waitForHavingContents(page, expected); }) ); }); }); describe("Closing the undo bar after a delete effectively deletes the page (bug 2010832)", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should permanently remove the deleted page when the undo bar is closed", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); // Delete page 1. await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); let handlePagesEdited = await waitForPagesEdited(page); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionDelete"); await awaitPromise(handlePagesEdited); await page.waitForSelector("#viewsManagerStatusUndo", { visible: true, }); // Close the undo bar instead of undoing. handlePagesEdited = await waitForPagesEdited(page, "cleanSavedData"); await waitAndClick(page, "#viewsManagerStatusUndoCloseButton"); const pageIndices = await awaitPromise(handlePagesEdited); // The page must be effectively deleted. const expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, ]; expect(pageIndices) .withContext(`In ${browserName}`) .toEqual(expected); await page.waitForSelector("#viewsManagerStatusUndo", { hidden: true, }); await waitForHavingContents(page, expected); }) ); }); }); describe("Clicking Done after copying removes paste buttons (bug 2010832)", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should show a Done button after copy and remove paste buttons when clicked", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await page.waitForSelector("#viewsManagerStatusActionButton", { visible: true, }); // Copy page 1. await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); const handlePagesEdited = await waitForPagesEdited(page, "copy"); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCopy"); await awaitPromise(handlePagesEdited); // The undo bar must appear with a "Done" label (not "Undo"). await page.waitForSelector("#viewsManagerStatusUndo", { visible: true, }); await waitForTextToBe( page, "#viewsManagerStatusUndoLabel", "1 page copied" ); await waitForTextToBe( page, "#viewsManagerStatusUndoButton span[data-l10n-id]", "Done" ); // The close button must be hidden for copy. const closeHidden = await page.$eval( "#viewsManagerStatusUndoCloseButton", el => el.classList.contains("hidden") ); expect(closeHidden).withContext(`In ${browserName}`).toBeTrue(); // Paste buttons must be present. await page.waitForSelector("button.thumbnailPasteButton"); // Click Done and wait for the cancelCopy pagesedited event. const handleCancelCopy = await waitForPagesEdited(page, "cancelCopy"); await waitAndClick(page, "#viewsManagerStatusUndoButton"); await awaitPromise(handleCancelCopy); // Undo bar must be hidden and paste buttons must be gone. await page.waitForSelector("#viewsManagerStatusUndo", { hidden: true, }); await page.waitForSelector("button.thumbnailPasteButton", { hidden: true, }); const pasteButtons = await page.$$("button.thumbnailPasteButton"); expect(pasteButtons.length).withContext(`In ${browserName}`).toBe(0); }) ); }); }); describe("Extract some pages from a pdf", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "page-fit", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should check that the pages are correctly extracted", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(3)}) input` ); const handleExport = await createPromise(page, resolve => { window.PDFViewerApplication.eventBus.on( "saveextractedpages", ({ data }) => { resolve(data); }, { once: true, } ); }); await page.click("#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionExport"); const pagesData = await awaitPromise(handleExport); expect(pagesData) .withContext(`In ${browserName}`) .toEqual([ { document: null, pageIndices: [0, 1], includePages: [0, 2] }, ]); }) ); }); }); describe("Dragging mustn't be possible when pasting (bug 2021934)", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "page-fit", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); it("should check that dragging is disabled when pasting", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await Promise.all([ page.waitForSelector(`#thumbnailsView.isDragging`, { visible: true, }), dragAndDrop(page, getThumbnailSelector(1), [[0, 10]], 10), ]); await page.waitForSelector(`#thumbnailsView.isDragging`, { hidden: true, }); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); await waitAndClick(page, "#viewsManagerStatusActionButton"); await waitAndClick(page, "#viewsManagerStatusActionCopy"); // If dragging isn't disabled, the promise will resolve with the // selector. Otherwise, it will resolve with undefined (dragAndDrop // has no return), which is the expected behavior. const abortController = new AbortController(); const first = await Promise.race([ page.waitForSelector(`#thumbnailsView.isDragging`, { visible: true, signal: abortController.signal, }), dragAndDrop(page, getThumbnailSelector(1), [[0, 10]], 10), ]); abortController.abort(); expect(first) .withContext( `In ${browserName}, dragging should be disabled when pasting` ) .toBeUndefined(); }) ); }); }); describe("Context menu triggers editingstateschanged event (bug 2021828)", () => { let pages; beforeEach(async () => { pages = await loadAndWait( "page_with_number.pdf", "#viewsManagerToggleButton", "1", null, { enableSplitMerge: true } ); }); afterEach(async () => { await closePages(pages); }); function getContextMenuPromise(page) { return createPromise(page, resolve => { window.addEventListener( "contextmenu", e => { e.preventDefault(); resolve(); }, { once: true } ); }); } it("should dispatch editingstateschanged with correct payload on right-click with no selection", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); const handleEditingStatesChanged = await createPromise( page, resolve => { window.PDFViewerApplication.eventBus.on( "editingstateschanged", ({ details }) => resolve(details), { once: true } ); } ); const contextMenuPromise = await getContextMenuPromise(page); await page.click(getThumbnailSelector(1), { button: "right" }); await awaitPromise(contextMenuPromise); const details = await awaitPromise(handleEditingStatesChanged); expect(details.thumbnailId).withContext(`In ${browserName}`).toBe(1); expect(details.hasSelectedPages) .withContext(`In ${browserName}`) .toBeFalse(); expect(details.canDeletePages) .withContext(`In ${browserName}`) .toBeFalse(); }) ); }); it("should dispatch editingstateschanged with correct payload on right-click with some pages selected", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(1)}) input` ); const handleEditingStatesChanged = await createPromise( page, resolve => { window.PDFViewerApplication.eventBus.on( "editingstateschanged", ({ details }) => resolve(details), { once: true } ); } ); const contextMenuPromise = await getContextMenuPromise(page); await page.click(getThumbnailSelector(1), { button: "right" }); await awaitPromise(contextMenuPromise); const details = await awaitPromise(handleEditingStatesChanged); expect(details.thumbnailId).withContext(`In ${browserName}`).toBe(1); expect(details.hasSelectedPages) .withContext(`In ${browserName}`) .toBeTrue(); expect(details.canDeletePages) .withContext(`In ${browserName}`) .toBeTrue(); }) ); }); it("should dispatch editingstateschanged with canDeletePages false when all pages are selected", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await waitForThumbnailVisible(page, 1); // Select all 17 pages. const totalPages = await page.evaluate( () => document.querySelectorAll("#thumbnailsView .thumbnail input") .length ); for (let i = 1; i <= totalPages; i++) { await waitAndClick( page, `.thumbnail:has(${getThumbnailSelector(i)}) input` ); } const handleEditingStatesChanged = await createPromise( page, resolve => { window.PDFViewerApplication.eventBus.on( "editingstateschanged", ({ details }) => resolve(details), { once: true } ); } ); const contextMenuPromise = await getContextMenuPromise(page); await page.click(getThumbnailSelector(1), { button: "right" }); await awaitPromise(contextMenuPromise); const details = await awaitPromise(handleEditingStatesChanged); expect(details.thumbnailId).withContext(`In ${browserName}`).toBe(1); expect(details.hasSelectedPages) .withContext(`In ${browserName}`) .toBeTrue(); expect(details.canDeletePages) .withContext(`In ${browserName}`) .toBeFalse(); }) ); }); }); });