mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-02-08 00:21:11 +01:00
490 lines
15 KiB
JavaScript
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);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
});
|