fork from https://github.com/mozilla/pdf.js.git
This commit is contained in:
547
test/integration/thumbnail_view_spec.mjs
Normal file
547
test/integration/thumbnail_view_spec.mjs
Normal file
@@ -0,0 +1,547 @@
|
||||
/* 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,
|
||||
closePages,
|
||||
FSI,
|
||||
getThumbnailSelector,
|
||||
kbFocusNext,
|
||||
loadAndWait,
|
||||
PDI,
|
||||
showViewsManager,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
function waitForThumbnailVisible(page, pageNum) {
|
||||
return page.waitForSelector(getThumbnailSelector(pageNum), { visible: true });
|
||||
}
|
||||
|
||||
async function waitForMenu(page, buttonSelector, visible = true) {
|
||||
return page.waitForFunction(
|
||||
(selector, vis) => {
|
||||
const button = document.querySelector(selector);
|
||||
if (!button) {
|
||||
return false;
|
||||
}
|
||||
return button.getAttribute("aria-expanded") === (vis ? "true" : "false");
|
||||
},
|
||||
{},
|
||||
buttonSelector,
|
||||
visible
|
||||
);
|
||||
}
|
||||
|
||||
describe("PDF Thumbnail View", () => {
|
||||
describe("Works without errors", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should render thumbnails without errors", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
|
||||
const thumbSelector =
|
||||
"#thumbnailsView .thumbnailImageContainer > img";
|
||||
await page.waitForSelector(thumbSelector, { visible: true });
|
||||
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
await page.waitForSelector(`${thumbSelector}[src^="blob:http:"]`, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
const title = await page.$eval(
|
||||
getThumbnailSelector(1),
|
||||
el => el.title
|
||||
);
|
||||
expect(title)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(`Page ${FSI}1${PDI} of ${FSI}14${PDI}`);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should have accessible label on resizer", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
|
||||
const ariaLabel = await page.$eval("#viewsManagerResizer", el =>
|
||||
el.getAttribute("aria-label")
|
||||
);
|
||||
expect(ariaLabel)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe("Sidebar resizer");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("The view is scrolled correctly", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
async function goToPage(page, number) {
|
||||
const handle = await page.evaluateHandle(
|
||||
num => [
|
||||
new Promise(resolve => {
|
||||
const container = document.getElementById("viewsManagerContent");
|
||||
container.addEventListener("scrollend", resolve, { once: true });
|
||||
// eslint-disable-next-line no-undef
|
||||
PDFViewerApplication.pdfLinkService.goToPage(num);
|
||||
}),
|
||||
],
|
||||
number
|
||||
);
|
||||
return awaitPromise(handle);
|
||||
}
|
||||
|
||||
it("should scroll the view", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
for (const pageNum of [14, 1, 13, 2]) {
|
||||
await goToPage(page, pageNum);
|
||||
const thumbSelector = getThumbnailSelector(pageNum);
|
||||
await page.waitForSelector(
|
||||
`.thumbnail ${thumbSelector}[aria-current="page"]`,
|
||||
{ visible: true }
|
||||
);
|
||||
await page.waitForSelector(
|
||||
`${thumbSelector} > img[src^="blob:http:"]`,
|
||||
{
|
||||
visible: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("The view is accessible with the keyboard", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should navigate with the keyboard", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
await waitForThumbnailVisible(page, 2);
|
||||
await waitForThumbnailVisible(page, 3);
|
||||
|
||||
await kbFocusNext(page, "#viewsManagerSelectorButton");
|
||||
await kbFocusNext(page, "#viewsManagerStatusActionButton");
|
||||
await kbFocusNext(page, `#thumbnailsView ${getThumbnailSelector(1)}`);
|
||||
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.waitForSelector(
|
||||
`#thumbnailsView ${getThumbnailSelector(2)}:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
await page.keyboard.press("ArrowUp");
|
||||
await page.waitForSelector(`${getThumbnailSelector(1)}:focus`, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.waitForSelector(
|
||||
`#thumbnailsView ${getThumbnailSelector(3)}:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
const currentPage = await page.$eval(
|
||||
"#pageNumber",
|
||||
el => el.valueAsNumber
|
||||
);
|
||||
expect(currentPage).withContext(`In ${browserName}`).toBe(3);
|
||||
|
||||
await page.keyboard.press("End");
|
||||
await page.waitForSelector(
|
||||
`#thumbnailsView ${getThumbnailSelector(14)}:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
await page.keyboard.press("Home");
|
||||
await page.waitForSelector(
|
||||
`#thumbnailsView ${getThumbnailSelector(1)}:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must navigate when a synthetic click is dispatched on the thumbnail image (bug 2034568)", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 3);
|
||||
|
||||
// Simulate a screen reader (e.g. NVDA) firing a synthetic click on
|
||||
// the <img> child rather than the thumbnailImageContainer button.
|
||||
await page.evaluate(() => {
|
||||
const img = document.querySelector(
|
||||
`.thumbnail[page-number="3"] .thumbnailImageContainer img`
|
||||
);
|
||||
img.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
|
||||
const currentPage = await page.$eval(
|
||||
"#pageNumber",
|
||||
el => el.valueAsNumber
|
||||
);
|
||||
expect(currentPage).withContext(`In ${browserName}`).toBe(3);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("The manage dropdown menu", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey.pdf",
|
||||
"#viewsManagerToggleButton",
|
||||
null,
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
async function enableMenuItems(page) {
|
||||
await page.evaluate(() => {
|
||||
document
|
||||
.querySelectorAll("#viewsManagerStatusActionOptions button")
|
||||
.forEach(button => {
|
||||
button.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it("should open with Enter key and remain open", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
await enableMenuItems(page);
|
||||
|
||||
// Focus the manage button
|
||||
await kbFocusNext(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Press Enter to open the menu
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Verify first menu item can be focused
|
||||
await page.waitForSelector("#viewsManagerStatusActionCopy:focus", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Close menu with Escape
|
||||
await page.keyboard.press("Escape");
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton", false);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should open with Space key and remain open", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
await enableMenuItems(page);
|
||||
|
||||
// Focus the manage button
|
||||
await kbFocusNext(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Press Space to open the menu
|
||||
await page.keyboard.press(" ");
|
||||
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Verify first menu item can be focused
|
||||
await page.waitForSelector("#viewsManagerStatusActionCopy:focus", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Navigate menu items with arrow keys
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.waitForSelector("#viewsManagerStatusActionCut:focus", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Menu should still be open
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Close menu with Escape
|
||||
await page.keyboard.press("Escape");
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton", false);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Checkbox accessibility", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey.pdf",
|
||||
"#viewsManagerToggleButton",
|
||||
null,
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should have a title on the checkbox", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
const title = await page.$eval(
|
||||
`.thumbnail[page-number="1"] input[type="checkbox"]`,
|
||||
el => el.title
|
||||
);
|
||||
expect(title)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(`Select page ${FSI}1${PDI}`);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Menu keyboard navigation with multi-character keys (bug 2016212)", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"page_with_number_and_link.pdf",
|
||||
"#viewsManagerSelectorButton",
|
||||
null,
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must navigate menus with ArrowDown and Tab keys", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
// Focus the views manager selector button
|
||||
await page.waitForSelector("#viewsManagerSelectorButton", {
|
||||
visible: true,
|
||||
});
|
||||
await page.focus("#viewsManagerSelectorButton");
|
||||
|
||||
// Open menu with Enter key
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
// Wait for menu to be expanded
|
||||
await waitForMenu(page, "#viewsManagerSelectorButton");
|
||||
|
||||
// Check that focus moved to the first menu button (pages)
|
||||
await page.waitForSelector("#thumbnailsViewMenu:focus", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Press ArrowDown to navigate to second item
|
||||
await page.keyboard.press("ArrowDown");
|
||||
|
||||
// Should now be on outlines button
|
||||
await page.waitForSelector("#outlinesViewMenu:focus", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Press Tab to move to the manage button (should close views menu)
|
||||
await kbFocusNext(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Wait for views manager menu to be collapsed
|
||||
await waitForMenu(page, "#viewsManagerSelectorButton", false);
|
||||
|
||||
// Open manage menu with Space key
|
||||
await page.keyboard.press(" ");
|
||||
|
||||
// Wait for manage menu to be expanded
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Views manager status visibility (bug 2016656)", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"page_with_number_and_link.pdf",
|
||||
"#viewsManagerToggleButton",
|
||||
null,
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should show the manage button in thumbnail view and hide it in outline view", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
// The status bar (Select pages + Manage button) must be visible in
|
||||
// thumbnail view.
|
||||
await page.waitForSelector("#viewsManagerStatus", { visible: true });
|
||||
|
||||
// Switch to outline view.
|
||||
await page.click("#viewsManagerSelectorButton");
|
||||
await page.waitForSelector("#outlinesViewMenu", { visible: true });
|
||||
await page.click("#outlinesViewMenu");
|
||||
await page.waitForSelector("#outlinesView", { visible: true });
|
||||
|
||||
// The status bar must no longer be visible in outline view.
|
||||
await page.waitForSelector("#viewsManagerStatus", { hidden: true });
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Checkbox keyboard navigation", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey.pdf",
|
||||
"#viewsManagerToggleButton",
|
||||
null,
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should focus checkboxes with Tab key", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
// Focus the first thumbnail button
|
||||
await kbFocusNext(page, getThumbnailSelector(1));
|
||||
|
||||
// Verify we're on the first thumbnail
|
||||
await page.waitForSelector(`${getThumbnailSelector(1)}:focus`, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Tab to checkbox
|
||||
await kbFocusNext(
|
||||
page,
|
||||
`.thumbnail[page-number="1"] input[type="checkbox"]`
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should navigate checkboxes with arrow keys", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
await waitForThumbnailVisible(page, 2);
|
||||
|
||||
// Navigate to first checkbox
|
||||
await kbFocusNext(
|
||||
page,
|
||||
`.thumbnail[page-number="1"] input[type="checkbox"]`
|
||||
);
|
||||
|
||||
// Verify first checkbox is focused
|
||||
await page.waitForSelector(
|
||||
`.thumbnail[page-number="1"] input[type="checkbox"]:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
// Navigate to next checkbox with ArrowDown
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.waitForSelector(
|
||||
`.thumbnail[page-number="2"] input[type="checkbox"]:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
// Navigate back with ArrowUp
|
||||
await page.keyboard.press("ArrowUp");
|
||||
await page.waitForSelector(
|
||||
`.thumbnail[page-number="1"] input[type="checkbox"]:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user