pdf.js.mirror/test/integration/reorganize_pages_spec.mjs
2026-01-19 17:38:17 +01:00

490 lines
15 KiB
JavaScript

/* 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,
dragAndDrop,
getAnnotationSelector,
getRect,
getThumbnailSelector,
loadAndWait,
scrollIntoView,
waitForDOMMutation,
} from "./test_utils.mjs";
async function waitForThumbnailVisible(page, pageNums) {
await page.click("#viewsManagerToggleButton");
const thumbSelector = "#thumbnailsView .thumbnailImage";
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) {
return createPromise(page, resolve => {
window.PDFViewerApplication.eventBus.on(
"pagesedited",
({ pagesMapper }) => {
resolve(Array.from(pagesMapper.getMapping()));
},
{
once: true,
}
);
});
}
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 pageNumber = parseInt(domPage.getAttribute("data-page-number"), 10);
const highlights = domPage.querySelectorAll("span.highlight");
if (highlights.length === 0) {
continue;
}
results.push([
i + 1,
pageNumber,
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 pagesToMove = Array.from(selected).sort((a, b) => a - b);
viewer.pagesMapper.pagesNumber =
document.querySelectorAll(".page").length;
viewer.pagesMapper.movePages(new Set(pagesToMove), pagesToMove, index);
window.PDFViewerApplication.eventBus.dispatch("pagesedited", {
pagesMapper: viewer.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;
}
);
const dndPromise = dragAndDrop(
page,
getThumbnailSelector(1),
[[0, rect2.y - rect1.y + rect2.height / 2]],
10
);
await dndPromise;
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);
expect(pagesMapping)
.withContext(`In ${browserName}`)
.toEqual([
2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
]);
})
);
});
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);
expect(pagesMapping)
.withContext(`In ${browserName}`)
.toEqual([
2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
]);
})
);
});
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);
expect(pagesMapping)
.withContext(`In ${browserName}`)
.toEqual([
3, 4, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
]);
})
);
});
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-id='14'")).scrollIntoView();
await page.waitForSelector(getThumbnailSelector(14), {
visible: true,
});
await page.click(`.thumbnail:has(${getThumbnailSelector(14)}) input`);
await (await page.$(".thumbnail[page-id='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);
expect(pagesMapping)
.withContext(`In ${browserName}`)
.toEqual([
2, 1, 14, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17,
]);
})
);
});
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="false"]`
);
await page.waitForSelector(
`${getThumbnailSelector(1)}[aria-current="page"]`
);
})
);
});
});
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, Id, [matches]
[1, 1, ["1"]],
[10, 10, ["1"]],
[11, 11, ["1", "1"]],
[12, 12, ["1"]],
[13, 13, ["1"]],
[14, 14, ["1"]],
[15, 15, ["1"]],
[16, 16, ["1"]],
[17, 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, Id, [matches]
[1, 1, ["1"]],
[4, 11, ["1", "1"]],
[11, 10, ["1"]],
[12, 12, ["1"]],
[13, 13, ["1"]],
[14, 14, ["1"]],
[15, 15, ["1"]],
[16, 16, ["1"]],
[17, 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, Id, [matches]
[1, 13, ["1"]],
[2, 1, ["1"]],
[5, 11, ["1", "1"]],
[12, 10, ["1"]],
[13, 12, ["1"]],
[14, 14, ["1"]],
[15, 15, ["1"]],
[16, 16, ["1"]],
[17, 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"));
await page.waitForSelector(
".page[data-page-number='10'] + .page[data-page-number='2']",
{
visible: true,
}
);
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)");
await page.waitForSelector(
".page[data-page-number='10'] + .page[data-page-number='2']",
{
visible: true,
}
);
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);
})
);
});
});
});