mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-04-09 23:04:02 +02:00
This solves [bug 1989406](https://bugzilla.mozilla.org/show_bug.cgi?id=1989406). (“The user should be able to dismiss the in-content message displayed by clicking somewhere else in the PDF”) There’s a good gif there that shows the problematic behavior. In the thread, there are also mentions of 2 similar but slightly separate problems: * clicking on another highlight should also dismiss * the mention that hitting the escape key does not dismiss I found the last point, the escape key, to work already (first test case here). But this PR solves the main bug (second test case) and the adjacent one (third test case). It works by using the existing `unselectAll` handling.
1268 lines
42 KiB
JavaScript
1268 lines
42 KiB
JavaScript
/* Copyright 2025 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,
|
|
getEditorSelector,
|
|
getRect,
|
|
getSpanRectFromText,
|
|
highlightSpan,
|
|
kbModifierDown,
|
|
kbModifierUp,
|
|
kbRedo,
|
|
kbUndo,
|
|
loadAndWait,
|
|
scrollIntoView,
|
|
selectEditor,
|
|
switchToEditor,
|
|
waitAndClick,
|
|
waitForSerialized,
|
|
waitForTimeout,
|
|
} from "./test_utils.mjs";
|
|
|
|
const switchToHighlight = switchToEditor.bind(null, "Highlight");
|
|
const switchToStamp = switchToEditor.bind(null, "Stamp");
|
|
const switchToComment = switchToEditor.bind(null, "Comment");
|
|
const switchToFreeText = switchToEditor.bind(null, "FreeText");
|
|
|
|
const editComment = async (page, editorSelector, comment) => {
|
|
const commentButtonSelector = `${editorSelector} button.comment`;
|
|
await waitAndClick(page, commentButtonSelector);
|
|
|
|
const textInputSelector = "#commentManagerTextInput";
|
|
await page.waitForSelector(textInputSelector, {
|
|
visible: true,
|
|
});
|
|
await page.type(textInputSelector, comment);
|
|
await waitAndClick(page, "#commentManagerSaveButton");
|
|
await page.waitForSelector("#commentManagerDialog", {
|
|
visible: false,
|
|
});
|
|
};
|
|
|
|
describe("Comment", () => {
|
|
describe("Comment edit dialog must be visible in ltr", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"bug1989304.pdf",
|
|
".annotationEditorLayer",
|
|
"page-width",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must set the comment dialog in the viewport (LTR)", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
|
|
await scrollIntoView(page, ".textLayer span:last-of-type");
|
|
await highlightSpan(page, 1, "...");
|
|
const commentButtonSelector = `${getEditorSelector(0)} button.comment`;
|
|
await waitAndClick(page, commentButtonSelector);
|
|
|
|
await page.waitForSelector("#commentManagerDialog", {
|
|
visible: true,
|
|
});
|
|
const dialogRect = await getRect(page, "#commentManagerDialog");
|
|
const viewport = await page.evaluate(() => ({
|
|
width: document.documentElement.clientWidth,
|
|
height: document.documentElement.clientHeight,
|
|
}));
|
|
expect(dialogRect.x + dialogRect.width)
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(viewport.width + 1);
|
|
expect(dialogRect.y + dialogRect.height)
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(viewport.height + 1);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Comment edit dialog must be visible in rtl", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"bug1989304.pdf",
|
|
".annotationEditorLayer",
|
|
"page-width",
|
|
null,
|
|
{ enableComment: true, locale: "ar" }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must set the comment dialog in the viewport (RTL)", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
|
|
await scrollIntoView(page, ".textLayer span:nth-of-type(4)");
|
|
await highlightSpan(page, 1, "World");
|
|
const commentButtonSelector = `${getEditorSelector(0)} button.comment`;
|
|
await waitAndClick(page, commentButtonSelector);
|
|
|
|
await page.waitForSelector("#commentManagerDialog", {
|
|
visible: true,
|
|
});
|
|
const dialogRect = await getRect(page, "#commentManagerDialog");
|
|
const viewport = await page.evaluate(() => ({
|
|
height: window.innerHeight,
|
|
}));
|
|
expect(dialogRect.x + dialogRect.width)
|
|
.withContext(`In ${browserName}`)
|
|
.toBeGreaterThanOrEqual(-1);
|
|
expect(dialogRect.y + dialogRect.height)
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(viewport.height + 1);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Update comment position and color in reading mode", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"comments.pdf",
|
|
".annotationEditorLayer",
|
|
"page-fit",
|
|
null,
|
|
{
|
|
enableComment: true,
|
|
highlightEditorColors:
|
|
"yellow=#FFFF00,green=#00FF00,blue=#0000FF,pink=#FF00FF,red=#FF0000",
|
|
}
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must set the comment button at the right place", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToStamp(page);
|
|
|
|
const stampSelector = getEditorSelector(8);
|
|
await selectEditor(page, stampSelector);
|
|
await dragAndDrop(page, stampSelector, [[100, 100]]);
|
|
await waitForSerialized(page, 1);
|
|
const rectCommentButton = await getRect(
|
|
page,
|
|
`${stampSelector} .annotationCommentButton`
|
|
);
|
|
|
|
await switchToStamp(page, /* disable = */ true);
|
|
const rectCommentButtonAfter = await getRect(
|
|
page,
|
|
`#pdfjs_internal_id_713R + .annotationCommentButton`
|
|
);
|
|
|
|
expect(Math.abs(rectCommentButtonAfter.x - rectCommentButton.x))
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(1);
|
|
expect(Math.abs(rectCommentButtonAfter.y - rectCommentButton.y))
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(1);
|
|
expect(
|
|
Math.abs(rectCommentButtonAfter.width - rectCommentButton.width)
|
|
)
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(1);
|
|
expect(
|
|
Math.abs(rectCommentButtonAfter.height - rectCommentButton.height)
|
|
)
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(1);
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must set the right color to the comment button", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
|
|
const highlightSelector = getEditorSelector(0);
|
|
await selectEditor(page, highlightSelector);
|
|
const colorButtonSelector = `${highlightSelector} .editToolbar button`;
|
|
await page.waitForSelector(`${colorButtonSelector}.colorPicker`);
|
|
await page.click(`${colorButtonSelector}.colorPicker`);
|
|
await page.waitForSelector(`${colorButtonSelector}[title = "Red"]`);
|
|
await page.click(`${colorButtonSelector}[title = "Red"]`);
|
|
await page.waitForSelector(
|
|
`.page[data-page-number = "1"] svg.highlight[fill = "#FF0000"]`
|
|
);
|
|
|
|
const commentButtonColor = await page.evaluate(selector => {
|
|
const button = document.querySelector(
|
|
`${selector} .annotationCommentButton`
|
|
);
|
|
return window.getComputedStyle(button).backgroundColor;
|
|
}, highlightSelector);
|
|
|
|
await switchToHighlight(page, /* disable = */ true);
|
|
|
|
const commentButtonColorAfter = await page.evaluate(() => {
|
|
const button = document.querySelector(
|
|
"section[data-annotation-id='612R'] + .annotationCommentButton"
|
|
);
|
|
return window.getComputedStyle(button).backgroundColor;
|
|
});
|
|
|
|
expect(commentButtonColorAfter)
|
|
.withContext(`In ${browserName}`)
|
|
.toEqual(commentButtonColor);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Comment buttons", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"tracemonkey.pdf",
|
|
".annotationEditorLayer",
|
|
"page-fit",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must check that the comment button has a title", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Languages");
|
|
|
|
let commentButtonSelector = `${getEditorSelector(0)} button.comment`;
|
|
await page.waitForSelector(commentButtonSelector, { visible: true });
|
|
let title = await page.evaluate(
|
|
selector => document.querySelector(selector).title,
|
|
commentButtonSelector
|
|
);
|
|
expect(title).withContext(`In ${browserName}`).toEqual("Add comment");
|
|
await page.click(commentButtonSelector);
|
|
|
|
const textInputSelector = "#commentManagerTextInput";
|
|
await page.waitForSelector(textInputSelector, {
|
|
visible: true,
|
|
});
|
|
await page.type(textInputSelector, "Hello world!");
|
|
|
|
await page.click("#commentManagerSaveButton");
|
|
|
|
commentButtonSelector = `${getEditorSelector(0)} button.annotationCommentButton`;
|
|
await page.waitForSelector(commentButtonSelector, {
|
|
visible: true,
|
|
});
|
|
title = await page.evaluate(selector => {
|
|
const button = document.querySelector(selector);
|
|
return button.title;
|
|
}, commentButtonSelector);
|
|
expect(title)
|
|
.withContext(`In ${browserName}`)
|
|
.toEqual("Show comment");
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must check that the comment button is added in the annotation layer", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Abstract");
|
|
|
|
const comment = "Hello world!";
|
|
await editComment(page, getEditorSelector(0), comment);
|
|
await page.hover("#editorHighlightButton");
|
|
let buttonSelector =
|
|
".annotationEditorLayer .annotationCommentButton";
|
|
await page.waitForSelector(buttonSelector, { visible: true });
|
|
await page.hover(buttonSelector);
|
|
const popupSelector = "#commentPopup";
|
|
await page.waitForSelector(popupSelector, {
|
|
visible: true,
|
|
});
|
|
let popupText = await page.evaluate(
|
|
selector => document.querySelector(selector).textContent,
|
|
`${popupSelector} .commentPopupText`
|
|
);
|
|
expect(popupText).withContext(`In ${browserName}`).toEqual(comment);
|
|
|
|
await page.hover("#editorHighlightButton");
|
|
await switchToHighlight(page, /* disable = */ true);
|
|
|
|
buttonSelector = ".annotationLayer .annotationCommentButton";
|
|
await page.waitForSelector(buttonSelector, {
|
|
visible: true,
|
|
});
|
|
await page.hover(buttonSelector);
|
|
|
|
await page.waitForSelector(popupSelector, {
|
|
visible: true,
|
|
});
|
|
popupText = await page.evaluate(
|
|
selector => document.querySelector(selector).textContent,
|
|
`${popupSelector} .commentPopupText`
|
|
);
|
|
expect(popupText).withContext(`In ${browserName}`).toEqual(comment);
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must check that the button is removed in the annotation layer", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([, page]) => {
|
|
await switchToHighlight(page);
|
|
|
|
await highlightSpan(page, 1, "Abstract");
|
|
const editorSelector = getEditorSelector(0);
|
|
await editComment(page, editorSelector, "Hello world!");
|
|
|
|
await switchToHighlight(page, /* disable = */ true);
|
|
const buttonSelector = ".annotationLayer .annotationCommentButton";
|
|
await page.waitForSelector(buttonSelector, {
|
|
visible: true,
|
|
});
|
|
|
|
await switchToHighlight(page);
|
|
await selectEditor(page, editorSelector);
|
|
await waitAndClick(page, `${editorSelector} button.deleteButton`);
|
|
await waitForSerialized(page, 0);
|
|
|
|
await switchToHighlight(page, /* disable = */ true);
|
|
await page.waitForFunction(
|
|
sel => !document.querySelector(sel),
|
|
{},
|
|
buttonSelector
|
|
);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Focused element after editing", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"tracemonkey.pdf",
|
|
".annotationEditorLayer",
|
|
"page-width",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must check that the focus is moved on the comment button", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Languages");
|
|
|
|
const commentButtonSelector = `${getEditorSelector(0)} button.comment`;
|
|
await waitAndClick(page, commentButtonSelector);
|
|
|
|
await page.waitForSelector("#commentManagerCancelButton", {
|
|
visible: true,
|
|
});
|
|
const handle = await createPromise(page, resolve => {
|
|
document
|
|
.querySelector("button.comment")
|
|
.addEventListener("focus", resolve, { once: true });
|
|
});
|
|
await page.click("#commentManagerCancelButton");
|
|
await awaitPromise(handle);
|
|
|
|
await waitAndClick(page, commentButtonSelector);
|
|
|
|
const textInputSelector = "#commentManagerTextInput";
|
|
await page.waitForSelector(textInputSelector, {
|
|
visible: true,
|
|
});
|
|
await page.type(textInputSelector, "Hello world!");
|
|
|
|
await page.click("#commentManagerSaveButton");
|
|
await page.waitForSelector("button.annotationCommentButton", {
|
|
visible: true,
|
|
});
|
|
|
|
await page.waitForFunction(
|
|
sel => document.activeElement === document.querySelector(sel),
|
|
{},
|
|
"button.annotationCommentButton"
|
|
);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Focused element after editing in reading mode", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"comments.pdf",
|
|
".annotationLayer",
|
|
"page-width",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must check that the focus is moved on the comment button", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
const commentButtonSelector = `[data-annotation-id="612R"] + button.annotationCommentButton`;
|
|
await waitAndClick(page, commentButtonSelector);
|
|
const commentPopupSelector = "#commentPopup";
|
|
const editButtonSelector = `${commentPopupSelector} button.commentPopupEdit`;
|
|
await waitAndClick(page, editButtonSelector);
|
|
|
|
await page.waitForSelector("#commentManagerCancelButton", {
|
|
visible: true,
|
|
});
|
|
let handle = await createPromise(page, resolve => {
|
|
document
|
|
.querySelector(
|
|
`[data-annotation-id="612R"] + button.annotationCommentButton`
|
|
)
|
|
.addEventListener("focus", resolve, { once: true });
|
|
});
|
|
await page.click("#commentManagerCancelButton");
|
|
await awaitPromise(handle);
|
|
|
|
await waitAndClick(page, commentButtonSelector);
|
|
await waitAndClick(page, editButtonSelector);
|
|
|
|
const textInputSelector = "#commentManagerTextInput";
|
|
await page.waitForSelector(textInputSelector, {
|
|
visible: true,
|
|
});
|
|
await page.type(textInputSelector, "Hello world!");
|
|
|
|
handle = await createPromise(page, resolve => {
|
|
document
|
|
.querySelector(
|
|
`[data-annotation-id="612R"] + button.annotationCommentButton`
|
|
)
|
|
.addEventListener("focus", resolve, { once: true });
|
|
});
|
|
await page.click("#commentManagerSaveButton");
|
|
await awaitPromise(handle);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Comment sidebar", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"comments.pdf",
|
|
".annotationEditorLayer",
|
|
"page-fit",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must check that the comment sidebar is resizable", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToComment(page);
|
|
|
|
const sidebarSelector = "#editorCommentParamsToolbar";
|
|
for (const extraWidth of [100, -100]) {
|
|
const rect = await getRect(page, sidebarSelector);
|
|
const resizerRect = await getRect(
|
|
page,
|
|
"#editorCommentsSidebarResizer"
|
|
);
|
|
const startX = resizerRect.x + resizerRect.width / 2;
|
|
const startY = resizerRect.y + 2;
|
|
await page.mouse.move(startX, startY);
|
|
await page.mouse.down();
|
|
|
|
const steps = 20;
|
|
await page.mouse.move(startX - extraWidth, startY, { steps });
|
|
await page.mouse.up();
|
|
|
|
const rectAfter = await getRect(page, sidebarSelector);
|
|
expect(Math.abs(rectAfter.width - (rect.width + extraWidth)))
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(1);
|
|
expect(Math.abs(rectAfter.x - (rect.x - extraWidth)))
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(1);
|
|
}
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must check that the comment sidebar is resizable with the keyboard", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToComment(page);
|
|
|
|
const sidebarSelector = "#editorCommentParamsToolbar";
|
|
const handle = await createPromise(page, resolve => {
|
|
document
|
|
.getElementById("editorCommentsSidebarResizer")
|
|
.addEventListener("focus", () => resolve(), { once: true });
|
|
});
|
|
await page.focus(`${sidebarSelector} #editorCommentsSidebarResizer`);
|
|
await awaitPromise(handle);
|
|
|
|
// Use Ctrl+ArrowLeft/Right to resize the sidebar.
|
|
for (const extraWidth of [10, -10]) {
|
|
const rect = await getRect(page, sidebarSelector);
|
|
const arrowKey = extraWidth > 0 ? "ArrowLeft" : "ArrowRight";
|
|
for (let i = 0; i < Math.abs(extraWidth); i++) {
|
|
await kbModifierDown(page);
|
|
await page.keyboard.press(arrowKey);
|
|
await kbModifierUp(page);
|
|
}
|
|
|
|
const rectAfter = await getRect(page, sidebarSelector);
|
|
expect(Math.abs(rectAfter.width - (rect.width + 10 * extraWidth)))
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(1);
|
|
expect(Math.abs(rectAfter.x - (rect.x - 10 * extraWidth)))
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(1);
|
|
}
|
|
|
|
// Use ArrowLeft/Right to resize the sidebar.
|
|
for (const extraWidth of [10, -10]) {
|
|
const rect = await getRect(page, sidebarSelector);
|
|
const arrowKey = extraWidth > 0 ? "ArrowLeft" : "ArrowRight";
|
|
for (let i = 0; i < Math.abs(extraWidth); i++) {
|
|
await page.keyboard.press(arrowKey);
|
|
}
|
|
|
|
const rectAfter = await getRect(page, sidebarSelector);
|
|
expect(Math.abs(rectAfter.width - (rect.width + extraWidth)))
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(1);
|
|
expect(Math.abs(rectAfter.x - (rect.x - extraWidth)))
|
|
.withContext(`In ${browserName}`)
|
|
.toBeLessThanOrEqual(1);
|
|
}
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must check that comments are in chronological order", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToComment(page);
|
|
|
|
const checkDates = async () => {
|
|
const dates = await page.evaluate(() =>
|
|
Array.from(
|
|
document.querySelectorAll(
|
|
`#editorCommentParamsToolbar ul > li > time`
|
|
)
|
|
).map(time => new Date(time.getAttribute("datetime")))
|
|
);
|
|
for (let i = 0; i < dates.length - 1; i++) {
|
|
expect(dates[i])
|
|
.withContext(`In ${browserName}`)
|
|
.toBeGreaterThanOrEqual(dates[i + 1]);
|
|
}
|
|
};
|
|
await checkDates();
|
|
|
|
// Add an highlight with a comment and check the order again.
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Languages");
|
|
const editorSelector = getEditorSelector(9);
|
|
const commentButtonSelector = `${editorSelector} button.comment`;
|
|
await waitAndClick(page, commentButtonSelector);
|
|
|
|
const textInputSelector = "#commentManagerTextInput";
|
|
await page.waitForSelector(textInputSelector, {
|
|
visible: true,
|
|
});
|
|
await page.type(textInputSelector, "Hello world!");
|
|
await page.click("#commentManagerSaveButton");
|
|
await waitForSerialized(page, 1);
|
|
|
|
await switchToComment(page);
|
|
await checkDates();
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must check that comments can be selected/unselected", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToComment(page);
|
|
|
|
const firstElementSelector =
|
|
"#editorCommentsSidebarList li:first-child";
|
|
await waitAndClick(page, firstElementSelector);
|
|
const popupSelector = "#commentPopup";
|
|
await page.waitForSelector(popupSelector, { visible: true });
|
|
const popupTextSelector = `${popupSelector} .commentPopupText`;
|
|
await page.waitForSelector(popupTextSelector, {
|
|
visible: true,
|
|
});
|
|
const popupText = await page.evaluate(
|
|
selector => document.querySelector(selector).textContent,
|
|
popupTextSelector
|
|
);
|
|
expect(popupText)
|
|
.withContext(`In ${browserName}`)
|
|
.toEqual("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
|
|
|
// Click again to unselect the comment.
|
|
await waitAndClick(page, firstElementSelector);
|
|
await page.waitForSelector(popupSelector, { visible: false });
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Comment popup", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"tracemonkey.pdf",
|
|
".annotationEditorLayer",
|
|
"page-fit",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must check that the popup is deleted when the editor is", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([, page]) => {
|
|
await switchToHighlight(page);
|
|
|
|
await highlightSpan(page, 1, "Abstract");
|
|
const editorSelector = getEditorSelector(0);
|
|
await editComment(page, editorSelector, "Hello world!");
|
|
|
|
await waitAndClick(
|
|
page,
|
|
`${editorSelector} button.annotationCommentButton`
|
|
);
|
|
|
|
const popupSelector = "#commentPopup";
|
|
await page.waitForSelector(popupSelector, { visible: true });
|
|
await waitAndClick(page, `${editorSelector} button.deleteButton`);
|
|
|
|
// Check that the popup is removed from the DOM.
|
|
await page.waitForFunction(
|
|
sel => !document.querySelector(sel),
|
|
{},
|
|
popupSelector
|
|
);
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must check that the focus is moved on the editor once the popup is deleted", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([, page]) => {
|
|
await switchToHighlight(page);
|
|
|
|
await highlightSpan(page, 1, "Abstract");
|
|
const editorSelector = getEditorSelector(0);
|
|
await editComment(page, editorSelector, "Hello world!");
|
|
|
|
await waitAndClick(
|
|
page,
|
|
`${editorSelector} button.annotationCommentButton`
|
|
);
|
|
|
|
const popupSelector = "#commentPopup";
|
|
await page.waitForSelector(popupSelector, { visible: true });
|
|
const handle = await page.evaluateHandle(
|
|
sel => [
|
|
new Promise(resolve => {
|
|
document
|
|
.querySelector(sel)
|
|
.addEventListener("focusin", resolve, {
|
|
once: true,
|
|
});
|
|
}),
|
|
],
|
|
editorSelector
|
|
);
|
|
await waitAndClick(
|
|
page,
|
|
`${popupSelector} button.commentPopupDelete`
|
|
);
|
|
await awaitPromise(handle);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Annotations order in reading mode", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"comments.pdf",
|
|
".annotationEditorLayer",
|
|
"page-fit",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must check that the annotations are in the right order", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(
|
|
page,
|
|
1,
|
|
"method provides cheap inter-procedural type specialization, and an"
|
|
);
|
|
await editComment(page, getEditorSelector(9), "Hello world!");
|
|
|
|
await highlightSpan(page, 1, "Andreas Gal");
|
|
await editComment(page, getEditorSelector(10), "Hello world!");
|
|
|
|
await switchToHighlight(page, /* disable = */ true);
|
|
await page.waitForSelector(
|
|
".annotationLayer section:nth-child(4).editorAnnotation"
|
|
);
|
|
|
|
const sectionIds = await page.evaluate(() =>
|
|
[
|
|
...document.querySelectorAll(
|
|
".page[data-page-number='1'] .annotationLayer > section:not(.popupAnnotation)"
|
|
),
|
|
].map(el => el.id.split("_").pop())
|
|
);
|
|
expect(sectionIds).withContext(`In ${browserName}`).toEqual([
|
|
"612R",
|
|
"693R",
|
|
"10", // shortcut for pdfjs_internal_id_pdfjs_internal_editor_10
|
|
"687R",
|
|
"690R",
|
|
"713R",
|
|
"9", // shortcut for pdfjs_internal_id_pdfjs_internal_editor_9
|
|
"673R",
|
|
"613R",
|
|
"680R",
|
|
"661R",
|
|
]);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Focus annotation after comment has been deleted (bug 1994738)", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"tracemonkey.pdf",
|
|
".annotationEditorLayer",
|
|
"page-fit",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must check that the annotation is focused", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Abstract");
|
|
const editorSelector = getEditorSelector(0);
|
|
await editComment(page, editorSelector, "Hello world!");
|
|
|
|
await switchToHighlight(page, /* disable = */ true);
|
|
await waitAndClick(page, ".annotationLayer .annotationCommentButton");
|
|
|
|
const handle = await createPromise(page, resolve => {
|
|
document
|
|
.querySelector(".annotationLayer section.editorAnnotation")
|
|
.addEventListener("focus", resolve, { once: true });
|
|
});
|
|
await waitAndClick(page, "button.commentPopupDelete");
|
|
await awaitPromise(handle);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("FreeText annotation doesn't have a popup (bug 1995028)", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"empty.pdf",
|
|
".annotationEditorLayer",
|
|
"page-fit",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must check that comment button isn't in the annotation toolbar", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToFreeText(page);
|
|
|
|
const rect = await getRect(page, ".annotationEditorLayer");
|
|
const editorSelector = getEditorSelector(0);
|
|
const data = "Hello PDF.js World !!";
|
|
await page.mouse.click(rect.x + 100, rect.y + 100);
|
|
await page.waitForSelector(editorSelector, { visible: true });
|
|
await page.type(`${editorSelector} .internal`, data);
|
|
await page.keyboard.press("Escape");
|
|
|
|
await page.waitForSelector(`${editorSelector} .editToolbar`, {
|
|
visible: true,
|
|
});
|
|
|
|
// We want to be sure that the comment button isn't rendered.
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
await waitForTimeout(100);
|
|
|
|
const hasCommentButton = await page.evaluate(
|
|
selector =>
|
|
!!document.querySelector(
|
|
`${selector} .editToolbar button.comment`
|
|
),
|
|
editorSelector
|
|
);
|
|
expect(hasCommentButton).withContext(`In ${browserName}`).toBe(false);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Save a comment in using CTRL+Enter", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"comments.pdf",
|
|
".annotationEditorLayer",
|
|
"page-fit",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must check that the comment is saved", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
const commentButtonSelector = `[data-annotation-id="612R"] + button.annotationCommentButton`;
|
|
await waitAndClick(page, commentButtonSelector);
|
|
const commentPopupSelector = "#commentPopup";
|
|
const editButtonSelector = `${commentPopupSelector} button.commentPopupEdit`;
|
|
await waitAndClick(page, editButtonSelector);
|
|
|
|
const textInputSelector = "#commentManagerTextInput";
|
|
await page.waitForSelector(textInputSelector, {
|
|
visible: true,
|
|
});
|
|
await clearInput(page, textInputSelector, true);
|
|
const comment = "Comment saved using CTRL+Enter";
|
|
await page.type(textInputSelector, comment);
|
|
await page.focus(textInputSelector);
|
|
|
|
await page.keyboard.down("Control");
|
|
await page.keyboard.press("Enter");
|
|
await page.keyboard.up("Control");
|
|
|
|
await page.waitForSelector("#commentManagerDialog", {
|
|
visible: false,
|
|
});
|
|
|
|
await page.hover(commentButtonSelector);
|
|
await page.waitForSelector(commentPopupSelector, {
|
|
visible: true,
|
|
});
|
|
const popupTextSelector = `${commentPopupSelector} .commentPopupText`;
|
|
const popupText = await page.evaluate(
|
|
selector => document.querySelector(selector).textContent,
|
|
popupTextSelector
|
|
);
|
|
expect(popupText).withContext(`In ${browserName}`).toEqual(comment);
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Undo deletion popup for comments (bug 1999154)", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"tracemonkey.pdf",
|
|
".annotationEditorLayer",
|
|
"page-fit",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must check that deleting a comment can be undone using the undo button", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Abstract");
|
|
const editorSelector = getEditorSelector(0);
|
|
const comment = "Test comment for undo";
|
|
await editComment(page, editorSelector, comment);
|
|
|
|
// Stay in highlight mode - don't disable it
|
|
await waitAndClick(
|
|
page,
|
|
`${editorSelector} .annotationCommentButton`
|
|
);
|
|
|
|
await page.waitForSelector("#commentPopup", { visible: true });
|
|
|
|
// Capture the date before deletion
|
|
const dateBefore = await page.evaluate(
|
|
() =>
|
|
document.querySelector("#commentPopup .commentPopupTime")
|
|
?.textContent
|
|
);
|
|
|
|
await waitAndClick(page, "button.commentPopupDelete");
|
|
|
|
await page.waitForSelector("#editorUndoBar", { visible: true });
|
|
await page.waitForSelector("#editorUndoBarUndoButton", {
|
|
visible: true,
|
|
});
|
|
await page.click("#editorUndoBarUndoButton");
|
|
|
|
// Check that the comment is restored by hovering to show the popup
|
|
await page.hover(`${editorSelector} .annotationCommentButton`);
|
|
await page.waitForSelector("#commentPopup", { visible: true });
|
|
const popupText = await page.evaluate(
|
|
() =>
|
|
document.querySelector("#commentPopup .commentPopupText")
|
|
?.textContent
|
|
);
|
|
expect(popupText).withContext(`In ${browserName}`).toEqual(comment);
|
|
|
|
// Check that the date is preserved
|
|
const dateAfter = await page.evaluate(
|
|
() =>
|
|
document.querySelector("#commentPopup .commentPopupTime")
|
|
?.textContent
|
|
);
|
|
expect(dateAfter)
|
|
.withContext(`In ${browserName}`)
|
|
.toEqual(dateBefore);
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must check that the undo deletion popup displays 'Comment removed' message", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Abstract");
|
|
const editorSelector = getEditorSelector(0);
|
|
await editComment(page, editorSelector, "Test comment");
|
|
|
|
// Stay in highlight mode - don't disable it
|
|
await waitAndClick(
|
|
page,
|
|
`${editorSelector} .annotationCommentButton`
|
|
);
|
|
|
|
await page.waitForSelector("#commentPopup", { visible: true });
|
|
await waitAndClick(page, "button.commentPopupDelete");
|
|
|
|
await page.waitForFunction(() => {
|
|
const messageElement = document.querySelector(
|
|
"#editorUndoBarMessage"
|
|
);
|
|
return messageElement && messageElement.textContent.trim() !== "";
|
|
});
|
|
const message = await page.waitForSelector("#editorUndoBarMessage");
|
|
const messageText = await page.evaluate(
|
|
el => el.textContent,
|
|
message
|
|
);
|
|
expect(messageText)
|
|
.withContext(`In ${browserName}`)
|
|
.toContain("Comment removed");
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must check that the undo bar closes when clicking the close button", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Abstract");
|
|
const editorSelector = getEditorSelector(0);
|
|
await editComment(page, editorSelector, "Test comment");
|
|
|
|
// Stay in highlight mode - don't disable it
|
|
await waitAndClick(
|
|
page,
|
|
`${editorSelector} .annotationCommentButton`
|
|
);
|
|
|
|
await page.waitForSelector("#commentPopup", { visible: true });
|
|
await waitAndClick(page, "button.commentPopupDelete");
|
|
|
|
await page.waitForSelector("#editorUndoBar", { visible: true });
|
|
await waitAndClick(page, "#editorUndoBarCloseButton");
|
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must check that deleting a comment can be undone using Ctrl+Z", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Abstract");
|
|
const editorSelector = getEditorSelector(0);
|
|
const comment = "Test comment for Ctrl+Z undo";
|
|
await editComment(page, editorSelector, comment);
|
|
|
|
// Stay in highlight mode - don't disable it
|
|
await waitAndClick(
|
|
page,
|
|
`${editorSelector} .annotationCommentButton`
|
|
);
|
|
|
|
await page.waitForSelector("#commentPopup", { visible: true });
|
|
await waitAndClick(page, "button.commentPopupDelete");
|
|
|
|
await page.waitForSelector("#editorUndoBar", { visible: true });
|
|
|
|
// Use Ctrl+Z to undo
|
|
await kbUndo(page);
|
|
|
|
// The undo bar should be hidden after undo
|
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
|
|
|
// Check that the comment is restored by hovering to show the popup
|
|
await page.hover(`${editorSelector} .annotationCommentButton`);
|
|
await page.waitForSelector("#commentPopup", { visible: true });
|
|
const popupText = await page.evaluate(
|
|
() =>
|
|
document.querySelector("#commentPopup .commentPopupText")
|
|
?.textContent
|
|
);
|
|
expect(popupText).withContext(`In ${browserName}`).toEqual(comment);
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must check that the comment popup is hidden after redo", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Abstract");
|
|
const editorSelector = getEditorSelector(0);
|
|
const comment = "Test comment for redo";
|
|
await editComment(page, editorSelector, comment);
|
|
|
|
// Show the popup by clicking the comment button
|
|
await waitAndClick(
|
|
page,
|
|
`${editorSelector} .annotationCommentButton`
|
|
);
|
|
await page.waitForSelector("#commentPopup", { visible: true });
|
|
|
|
// Delete the comment
|
|
await waitAndClick(page, "button.commentPopupDelete");
|
|
await page.waitForSelector("#editorUndoBar", { visible: true });
|
|
|
|
// Undo the deletion
|
|
await kbUndo(page);
|
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
|
|
|
// Show the popup again by clicking the comment button
|
|
await waitAndClick(
|
|
page,
|
|
`${editorSelector} .annotationCommentButton`
|
|
);
|
|
await page.waitForSelector("#commentPopup", { visible: true });
|
|
|
|
// Redo the deletion - popup should be hidden
|
|
await kbRedo(page);
|
|
await page.waitForSelector("#commentPopup", { hidden: true });
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Must close comment popups (bug 1989406)", () => {
|
|
let pages;
|
|
|
|
beforeEach(async () => {
|
|
pages = await loadAndWait(
|
|
"tracemonkey.pdf",
|
|
".annotationEditorLayer",
|
|
"page-fit",
|
|
null,
|
|
{ enableComment: true }
|
|
);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await closePages(pages);
|
|
});
|
|
|
|
it("must close a comment popup on escape", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Abstract");
|
|
await editComment(page, getEditorSelector(0), "hi");
|
|
const rect = await getSpanRectFromText(page, 1, "Introduction");
|
|
|
|
// Unfocus.
|
|
await page.mouse.click(rect.x, rect.y);
|
|
|
|
await waitAndClick(page, ".annotationCommentButton");
|
|
|
|
await page.waitForSelector("#commentPopup", { visible: true });
|
|
|
|
await page.keyboard.press("Escape");
|
|
|
|
await page.waitForSelector("#commentPopup", { hidden: true });
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must close a comment popup on click outside", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
await highlightSpan(page, 1, "Abstract");
|
|
await editComment(page, getEditorSelector(0), "hi");
|
|
const rect = await getSpanRectFromText(page, 1, "Introduction");
|
|
|
|
// Unfocus.
|
|
await page.mouse.click(rect.x, rect.y);
|
|
|
|
await waitAndClick(page, ".annotationCommentButton");
|
|
|
|
await page.waitForSelector("#commentPopup", { visible: true });
|
|
|
|
// Click outside the popup.
|
|
await page.mouse.click(rect.x, rect.y);
|
|
|
|
await page.waitForSelector("#commentPopup", { hidden: true });
|
|
})
|
|
);
|
|
});
|
|
|
|
it("must close a comment popup on click on other highlight", async () => {
|
|
await Promise.all(
|
|
pages.map(async ([browserName, page]) => {
|
|
await switchToHighlight(page);
|
|
|
|
await highlightSpan(page, 1, "Abstract");
|
|
await editComment(page, getEditorSelector(0), "hello");
|
|
|
|
await highlightSpan(page, 1, "Introduction");
|
|
await editComment(page, getEditorSelector(1), "world");
|
|
|
|
// Open "Abstract" comment popup.
|
|
await waitAndClick(page, ".annotationCommentButton");
|
|
|
|
await page.waitForSelector("#commentPopup", { visible: true });
|
|
|
|
// Click on "Introduction" highlight.
|
|
await waitAndClick(page, getEditorSelector(1));
|
|
|
|
await page.waitForSelector("#commentPopup", { hidden: true });
|
|
})
|
|
);
|
|
});
|
|
});
|
|
});
|