pdf.js/test/integration/freetext_editor_spec.js
Jonas Jenwald 39113baa33 Move the transfers computation into the AnnotationStorage class
Rather than having to *manually* determine the potential `transfers` at various spots in the API, we can let the `AnnotationStorage.serializable` getter include this.
To further simplify things, we can also let the `serializable` getter compute and include the `hash`-string as well.
2023-06-29 19:51:57 +02:00

1182 lines
38 KiB
JavaScript

/* Copyright 2022 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.
*/
const {
closePages,
getEditors,
getEditorSelector,
getSelectedEditors,
getSerialized,
loadAndWait,
waitForEvent,
waitForSelectedEditor,
waitForStorageEntries,
} = require("./test_utils.js");
const copyPaste = async page => {
let promise = waitForEvent(page, "copy");
await page.keyboard.down("Control");
await page.keyboard.press("c");
await page.keyboard.up("Control");
await promise;
await page.waitForTimeout(10);
promise = waitForEvent(page, "paste");
await page.keyboard.down("Control");
await page.keyboard.press("v");
await page.keyboard.up("Control");
await promise;
};
describe("FreeText Editor", () => {
describe("FreeText", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("aboutstacks.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must write a string in a FreeText editor", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorFreeText");
const rect = await page.$eval(".annotationEditorLayer", el => {
// With Chrome something is wrong when serializing a DomRect,
// hence we extract the values and just return them.
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
const data = "Hello PDF.js World !!";
await page.mouse.click(rect.x + 100, rect.y + 100);
await page.type(`${getEditorSelector(0)} .internal`, data);
const editorRect = await page.$eval(getEditorSelector(0), el => {
const { x, y, width, height } = el.getBoundingClientRect();
return {
x,
y,
width,
height,
};
});
// Commit.
await page.mouse.click(
editorRect.x,
editorRect.y + 2 * editorRect.height
);
await waitForSelectedEditor(page, getEditorSelector(0));
await waitForStorageEntries(page, 1);
const content = await page.$eval(getEditorSelector(0), el =>
el.innerText.trimEnd()
);
expect(content).withContext(`In ${browserName}`).toEqual(data);
})
);
});
it("must copy/paste", async () => {
// Run sequentially to avoid clipboard issues.
for (const [browserName, page] of pages) {
const editorRect = await page.$eval(getEditorSelector(0), el => {
const { x, y, width, height } = el.getBoundingClientRect();
return { x, y, width, height };
});
// Select the editor created previously.
await page.mouse.click(
editorRect.x + editorRect.width / 2,
editorRect.y + editorRect.height / 2
);
await waitForSelectedEditor(page, getEditorSelector(0));
await copyPaste(page);
await waitForStorageEntries(page, 2);
const content = await page.$eval(getEditorSelector(0), el =>
el.innerText.trimEnd()
);
let pastedContent = await page.$eval(getEditorSelector(1), el =>
el.innerText.trimEnd()
);
expect(pastedContent).withContext(`In ${browserName}`).toEqual(content);
await copyPaste(page);
await waitForStorageEntries(page, 3);
pastedContent = await page.$eval(getEditorSelector(2), el =>
el.innerText.trimEnd()
);
expect(pastedContent).withContext(`In ${browserName}`).toEqual(content);
}
});
it("must clear all", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
await page.keyboard.down("Control");
await page.keyboard.press("Backspace");
await page.keyboard.up("Control");
for (const n of [0, 1, 2]) {
const hasEditor = await page.evaluate(sel => {
return !!document.querySelector(sel);
}, getEditorSelector(n));
expect(hasEditor).withContext(`In ${browserName}`).toEqual(false);
}
await waitForStorageEntries(page, 0);
})
);
});
it("must check that a paste has been undone", async () => {
// Run sequentially to avoid clipboard issues.
for (const [browserName, page] of pages) {
const rect = await page.$eval(".annotationEditorLayer", el => {
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
const data = "Hello PDF.js World !!";
await page.mouse.click(rect.x + 100, rect.y + 100);
await page.type(`${getEditorSelector(3)} .internal`, data);
const editorRect = await page.$eval(getEditorSelector(3), el => {
const { x, y, width, height } = el.getBoundingClientRect();
return { x, y, width, height };
});
// Commit.
await page.mouse.click(
editorRect.x,
editorRect.y + 2 * editorRect.height
);
// And select it again.
await page.mouse.click(
editorRect.x + editorRect.width / 2,
editorRect.y + editorRect.height / 2
);
await waitForSelectedEditor(page, getEditorSelector(3));
await copyPaste(page);
let hasEditor = await page.evaluate(sel => {
return !!document.querySelector(sel);
}, getEditorSelector(4));
expect(hasEditor).withContext(`In ${browserName}`).toEqual(true);
await page.keyboard.down("Control");
await page.keyboard.press("z");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
hasEditor = await page.evaluate(sel => {
return !!document.querySelector(sel);
}, getEditorSelector(4));
expect(hasEditor).withContext(`In ${browserName}`).toEqual(false);
for (let i = 0; i < 2; i++) {
const promise = waitForEvent(page, "paste");
await page.keyboard.down("Control");
await page.keyboard.press("v");
await page.keyboard.up("Control");
await promise;
await page.waitForTimeout(10);
}
let length = await page.evaluate(sel => {
return document.querySelectorAll(sel).length;
}, `${getEditorSelector(5)}, ${getEditorSelector(6)}`);
expect(length).withContext(`In ${browserName}`).toEqual(2);
for (let i = 0; i < 2; i++) {
await page.keyboard.down("Control");
await page.keyboard.press("z");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
}
length = await page.evaluate(sel => {
return document.querySelectorAll(sel).length;
}, `${getEditorSelector(5)}, ${getEditorSelector(6)}`);
expect(length).withContext(`In ${browserName}`).toEqual(0);
}
});
it("must check that aria-owns is correct", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
const [stacksRect, oldAriaOwns] = await page.$eval(
".textLayer",
el => {
for (const span of el.querySelectorAll(
`span[role="presentation"]`
)) {
if (span.innerText.includes("Stacks are simple to create")) {
span.setAttribute("pdfjs", true);
const { x, y, width, height } = span.getBoundingClientRect();
return [
{ x, y, width, height },
span.getAttribute("aria-owns"),
];
}
}
return null;
}
);
expect(oldAriaOwns).withContext(`In ${browserName}`).toEqual(null);
const data = "Hello PDF.js World !!";
await page.mouse.click(
stacksRect.x + stacksRect.width + 1,
stacksRect.y + stacksRect.height / 2
);
await page.type(`${getEditorSelector(7)} .internal`, data);
// Commit.
await page.keyboard.press("Escape");
const ariaOwns = await page.$eval(".textLayer", el => {
const span = el.querySelector(`span[pdfjs="true"]`);
return span?.getAttribute("aria-owns") || null;
});
expect(ariaOwns.endsWith("_7-editor"))
.withContext(`In ${browserName}`)
.toEqual(true);
})
);
});
it("must check that right click doesn't select", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
const rect = await page.$eval(".annotationEditorLayer", el => {
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
await page.keyboard.down("Control");
await page.keyboard.press("Backspace");
await page.keyboard.up("Control");
const data = "Hello PDF.js World !!";
await page.mouse.click(rect.x + 100, rect.y + 100);
await page.type(`${getEditorSelector(8)} .internal`, data);
const editorRect = await page.$eval(getEditorSelector(8), el => {
const { x, y, width, height } = el.getBoundingClientRect();
return { x, y, width, height };
});
// Commit.
await page.keyboard.press("Escape");
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([8]);
await page.keyboard.press("Escape");
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([]);
await page.mouse.click(
editorRect.x + editorRect.width / 2,
editorRect.y + editorRect.height / 2
);
await waitForSelectedEditor(page, getEditorSelector(8));
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([8]);
// Escape.
await page.keyboard.press("Escape");
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([]);
// TODO: uncomment that stuff once we've a way to dismiss
// the context menu.
/* await page.mouse.click(
editorRect.x + editorRect.width / 2,
editorRect.y + editorRect.height / 2,
{ button: "right" }
); */
})
);
});
it("must check that text change can be undone/redone", async () => {
// Run sequentially to avoid clipboard issues.
for (const [browserName, page] of pages) {
const rect = await page.$eval(".annotationEditorLayer", el => {
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
await page.keyboard.down("Control");
await page.keyboard.press("Backspace");
await page.keyboard.up("Control");
await page.mouse.click(rect.x + 200, rect.y + 100);
for (let i = 0; i < 5; i++) {
await page.type(`${getEditorSelector(9)} .internal`, "A");
const editorRect = await page.$eval(getEditorSelector(9), el => {
const { x, y, width, height } = el.getBoundingClientRect();
return { x, y, width, height };
});
// Commit.
await page.mouse.click(
editorRect.x,
editorRect.y + 2 * editorRect.height
);
if (i < 4) {
// And select it again.
await page.mouse.click(
editorRect.x + editorRect.width / 2,
editorRect.y + editorRect.height / 2,
{ clickCount: 2 }
);
}
}
await page.keyboard.down("Control");
await page.keyboard.press("z");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
let text = await page.$eval(`${getEditorSelector(9)} .internal`, el => {
return el.innerText;
});
expect(text).withContext(`In ${browserName}`).toEqual("AAAA");
await page.keyboard.down("Control");
await page.keyboard.press("z");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
text = await page.$eval(`${getEditorSelector(9)} .internal`, el => {
return el.innerText;
});
expect(text).withContext(`In ${browserName}`).toEqual("AAA");
await page.keyboard.down("Control");
await page.keyboard.press("y");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
text = await page.$eval(`${getEditorSelector(9)} .internal`, el => {
return el.innerText;
});
expect(text).withContext(`In ${browserName}`).toEqual("AAAA");
for (let i = 0; i < 4; i++) {
await page.keyboard.down("Control");
await page.keyboard.press("z");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
}
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([]);
await page.keyboard.down("Control");
await page.keyboard.press("y");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
text = await page.$eval(`${getEditorSelector(9)} .internal`, el => {
return el.innerText;
});
expect(text).withContext(`In ${browserName}`).toEqual("A");
// Add a new A.
const editorRect = await page.$eval(getEditorSelector(9), el => {
const { x, y, width, height } = el.getBoundingClientRect();
return { x, y, width, height };
});
await page.mouse.click(
editorRect.x + editorRect.width / 2,
editorRect.y + editorRect.height / 2,
{ clickCount: 2 }
);
await page.type(`${getEditorSelector(9)} .internal`, "A");
// Commit.
await page.mouse.click(
editorRect.x,
editorRect.y + 2 * editorRect.height
);
text = await page.$eval(`${getEditorSelector(9)} .internal`, el => {
return el.innerText;
});
expect(text).withContext(`In ${browserName}`).toEqual("AA");
}
});
});
describe("FreeText (multiselection)", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("aboutstacks.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must select/unselect several editors and check copy, paste and delete operations", async () => {
// Run sequentially to avoid clipboard issues.
for (const [browserName, page] of pages) {
await page.click("#editorFreeText");
const rect = await page.$eval(".annotationEditorLayer", el => {
// With Chrome something is wrong when serializing a DomRect,
// hence we extract the values and just return them.
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
const editorCenters = [];
for (let i = 0; i < 4; i++) {
const data = `FreeText ${i}`;
await page.mouse.click(
rect.x + (i + 1) * 100,
rect.y + (i + 1) * 100
);
await page.type(`${getEditorSelector(i)} .internal`, data);
const editorRect = await page.$eval(getEditorSelector(i), el => {
const { x, y, width, height } = el.getBoundingClientRect();
return {
x,
y,
width,
height,
};
});
editorCenters.push({
x: editorRect.x + editorRect.width / 2,
y: editorRect.y + editorRect.height / 2,
});
// Commit.
await page.mouse.click(
editorRect.x,
editorRect.y + 2 * editorRect.height
);
}
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([0, 1, 2, 3]);
await page.keyboard.down("Control");
await page.mouse.click(editorCenters[1].x, editorCenters[1].y);
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([0, 2, 3]);
await page.mouse.click(editorCenters[2].x, editorCenters[2].y);
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([0, 3]);
await page.mouse.click(editorCenters[1].x, editorCenters[1].y);
await page.keyboard.up("Control");
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([0, 1, 3]);
await copyPaste(page);
// 0,1,3 are unselected and new pasted editors are selected.
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([4, 5, 6]);
// No ctrl here, hence all are unselected and 2 is selected.
await page.mouse.click(editorCenters[2].x, editorCenters[2].y);
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([2]);
await page.mouse.click(editorCenters[1].x, editorCenters[1].y);
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([1]);
await page.keyboard.down("Control");
await page.mouse.click(editorCenters[3].x, editorCenters[3].y);
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([1, 3]);
await page.keyboard.up("Control");
// Delete 1 and 3.
await page.keyboard.press("Backspace");
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([0, 2, 4, 5, 6]);
// Create an empty editor.
await page.mouse.click(rect.x + 700, rect.y + 100);
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([7]);
// Set the focus to 2 and check that only 2 is selected.
await page.mouse.click(editorCenters[2].x, editorCenters[2].y);
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([2]);
// Create an empty editor.
await page.mouse.click(rect.x + 700, rect.y + 100);
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([8]);
// Dismiss it.
await page.keyboard.press("Escape");
// Select all.
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
// Check that all the editors are correctly selected (and the focus
// didn't move to the body when the empty editor was removed).
expect(await getSelectedEditors(page))
.withContext(`In ${browserName}`)
.toEqual([0, 2, 4, 5, 6]);
}
});
});
describe("FreeText (bugs)", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must serialize invisible annotations", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorFreeText");
let currentId = 0;
const expected = [];
const oneToFourteen = [...new Array(14).keys()].map(x => x + 1);
for (const pageNumber of oneToFourteen) {
const pageSelector = `.page[data-page-number = "${pageNumber}"]`;
await page.evaluate(selector => {
const element = window.document.querySelector(selector);
element.scrollIntoView();
}, pageSelector);
const annotationLayerSelector = `${pageSelector} > .annotationEditorLayer`;
await page.waitForSelector(annotationLayerSelector, {
visible: true,
timeout: 0,
});
await page.waitForTimeout(50);
if (![1, 14].includes(pageNumber)) {
continue;
}
const rect = await page.$eval(annotationLayerSelector, el => {
// With Chrome something is wrong when serializing a DomRect,
// hence we extract the values and just return them.
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
const data = `Hello PDF.js World !! on page ${pageNumber}`;
expected.push(data);
await page.mouse.click(rect.x + 100, rect.y + 100);
await page.type(`${getEditorSelector(currentId)} .internal`, data);
// Commit.
await page.keyboard.press("Escape");
await page.waitForTimeout(10);
await waitForSelectedEditor(page, getEditorSelector(currentId));
await waitForStorageEntries(page, currentId + 1);
const content = await page.$eval(getEditorSelector(currentId), el =>
el.innerText.trimEnd()
);
expect(content).withContext(`In ${browserName}`).toEqual(data);
currentId += 1;
await page.waitForTimeout(10);
}
const serialize = proprName =>
page.evaluate(name => {
const { map } =
window.PDFViewerApplication.pdfDocument.annotationStorage
.serializable;
return map ? Array.from(map.values(), x => x[name]) : [];
}, proprName);
expect(await serialize("value"))
.withContext(`In ${browserName}`)
.toEqual(expected);
expect(await serialize("fontSize"))
.withContext(`In ${browserName}`)
.toEqual([10, 10]);
expect(await serialize("color"))
.withContext(`In ${browserName}`)
.toEqual([
[0, 0, 0],
[0, 0, 0],
]);
// Increase the font size for all the annotations.
// Select all.
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
page.evaluate(() => {
window.PDFViewerApplication.eventBus.dispatch(
"switchannotationeditorparams",
{
source: null,
type: /* AnnotationEditorParamsType.FREETEXT_SIZE */ 1,
value: 13,
}
);
});
await page.waitForTimeout(10);
expect(await serialize("fontSize"))
.withContext(`In ${browserName}`)
.toEqual([13, 13]);
// Change the colors for all the annotations.
page.evaluate(() => {
window.PDFViewerApplication.eventBus.dispatch(
"switchannotationeditorparams",
{
source: null,
type: /* AnnotationEditorParamsType.FREETEXT_COLOR */ 2,
value: "#FF0000",
}
);
});
await page.waitForTimeout(10);
expect(await serialize("color"))
.withContext(`In ${browserName}`)
.toEqual([
[255, 0, 0],
[255, 0, 0],
]);
})
);
});
});
describe("issue 15789", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("issue15789.pdf", ".annotationEditorLayer");
pages = await Promise.all(
pages.map(async ([browserName, page]) => {
await page.select("#scaleSelect", "1");
return [browserName, page];
})
);
});
afterAll(async () => {
await closePages(pages);
});
it("must take the media box into account", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorFreeText");
let currentId = 0;
for (let step = 0; step < 3; step++) {
const rect = await page.$eval(".annotationEditorLayer", el => {
// With Chrome something is wrong when serializing a DomRect,
// hence we extract the values and just return them.
const { x, y, width, height } = el.getBoundingClientRect();
return { x, y, width, height };
});
const data = `Hello ${step}`;
const x = rect.x + 0.1 * rect.width;
const y = rect.y + 0.1 * rect.height;
await page.mouse.click(x, y);
await page.type(`${getEditorSelector(currentId)} .internal`, data);
// Commit.
await page.keyboard.press("Escape");
await page.waitForTimeout(10);
await page.evaluate(() => {
document.getElementById("pageRotateCw").click();
});
currentId += 1;
await page.waitForTimeout(10);
}
const serialize = proprName =>
page.evaluate(name => {
const { map } =
window.PDFViewerApplication.pdfDocument.annotationStorage
.serializable;
return map ? Array.from(map.values(), x => x[name]) : [];
}, proprName);
const rects = (await serialize("rect")).map(rect =>
rect.slice(0, 2).map(x => Math.floor(x))
);
const expected = [
[-28, 695],
[-38, -10],
[501, -20],
];
// Dimensions aren't exactly the same from a platform to an other
// so we're a bit tolerant here with the numbers.
// Anyway the goal is to check that the bottom left corner of the
// media box is taken into account.
// The pdf has a media box equals to [-99 -99 612.0 792.0].
const diffs = rects.map(
(rect, i) =>
Math.abs(rect[0] - expected[i][0]) < 10 &&
Math.abs(rect[1] - expected[i][1]) < 10
);
expect(diffs)
.withContext(`In ${browserName}`)
.toEqual([true, true, true]);
})
);
});
});
describe("FreeText (move existing)", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("freetexts.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must move an annotation", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
if (browserName === "firefox") {
pending(
"Disabled in Firefox, because DnD isn't implemented yet (see bug 1838638)."
);
}
await page.setDragInterception(true);
await page.click("#editorFreeText");
const editorIds = await getEditors(page, "freeText");
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
// All the current annotations should be serialized as null objects
// because they haven't been edited yet.
let serialized = await getSerialized(page);
expect(serialized).withContext(`In ${browserName}`).toEqual([]);
const editorRect = await page.$eval(getEditorSelector(0), el => {
const { x, y, width, height } = el.getBoundingClientRect();
return { x, y, width, height };
});
await page.mouse.dragAndDrop(
{
x: editorRect.x + editorRect.width / 2,
y: editorRect.y + editorRect.height / 2,
},
{
x: editorRect.x + editorRect.width / 2 + 100,
y: editorRect.y + editorRect.height / 2 + 100,
},
{ delay: 100 }
);
serialized = await getSerialized(page);
expect(serialized.length).withContext(`In ${browserName}`).toEqual(1);
})
);
});
});
describe("FreeText (update existing)", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("freetexts.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must update an existing annotation", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorFreeText");
let editorIds = await getEditors(page, "freeText");
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
const editorRect = await page.$eval(getEditorSelector(0), el => {
const { x, y, width, height } = el.getBoundingClientRect();
return { x, y, width, height };
});
await page.mouse.click(
editorRect.x + editorRect.width / 2,
editorRect.y + editorRect.height / 2,
{ clickCount: 2 }
);
await page.keyboard.down("Control");
await page.keyboard.press("End");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
await page.type(
`${getEditorSelector(0)} .internal`,
" and edited in Firefox"
);
// Commit.
await page.mouse.click(
editorRect.x,
editorRect.y + 2 * editorRect.height
);
let serialized = await getSerialized(page);
expect(serialized.length).withContext(`In ${browserName}`).toEqual(1);
expect(serialized[0]).toEqual(
jasmine.objectContaining({
color: [107, 217, 41],
fontSize: 14,
value: "Hello World from Acrobat and edited in Firefox",
id: "26R",
})
);
// Disable editing mode.
await page.click("#editorFreeText");
// We want to check that the editor is displayed but not the original
// annotation.
editorIds = await getEditors(page, "freeText");
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(1);
const hidden = await page.$eval(
"[data-annotation-id='26R']",
el => el.hidden
);
expect(hidden).withContext(`In ${browserName}`).toBeTrue();
// Re-enable editing mode.
await page.click("#editorFreeText");
await page.focus(".annotationEditorLayer");
// Undo.
await page.keyboard.down("Control");
await page.keyboard.press("z");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
serialized = await getSerialized(page);
expect(serialized).withContext(`In ${browserName}`).toEqual([]);
editorIds = await getEditors(page, "freeText");
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
// Undo again.
await page.keyboard.down("Control");
await page.keyboard.press("z");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
// We check that the editor hasn't been removed.
editorIds = await getEditors(page, "freeText");
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
})
);
});
});
describe("FreeText (update existing but not empty ones)", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("issue14438.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must update an existing annotation but not an empty one", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorFreeText");
const editorIds = await getEditors(page, "freeText");
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(1);
})
);
});
});
describe("FreeText (delete existing)", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("freetexts.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must delete an existing annotation", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorFreeText");
let editorIds = await getEditors(page, "freeText");
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
const editorRect = await page.$eval(getEditorSelector(3), el => {
const { x, y, width, height } = el.getBoundingClientRect();
return { x, y, width, height };
});
await page.mouse.click(
editorRect.x + editorRect.width / 2,
editorRect.y + editorRect.height / 2
);
await page.keyboard.press("Backspace");
await page.waitForTimeout(10);
let serialized = await getSerialized(page);
expect(serialized).toEqual([
{
pageIndex: 0,
id: "51R",
deleted: true,
},
]);
await page.click("#editorFreeText");
// We want to check that nothing is displayed.
editorIds = await getEditors(page, "freeText");
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(0);
const hidden = await page.$eval(
"[data-annotation-id='51R']",
el => el.hidden
);
expect(hidden).withContext(`In ${browserName}`).toBeTrue();
// Re-enable editing mode.
await page.click("#editorFreeText");
await page.focus(".annotationEditorLayer");
// Undo.
await page.keyboard.down("Control");
await page.keyboard.press("z");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
serialized = await getSerialized(page);
expect(serialized).withContext(`In ${browserName}`).toEqual([]);
})
);
});
});
describe("FreeText (copy/paste existing)", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("freetexts.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must copy and paste an existing annotation", async () => {
// Run sequentially to avoid clipboard issues.
for (const [browserName, page] of pages) {
await page.click("#editorFreeText");
const editorIds = await getEditors(page, "freeText");
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
const editorRect = await page.$eval(getEditorSelector(1), el => {
const { x, y, width, height } = el.getBoundingClientRect();
return { x, y, width, height };
});
await page.mouse.click(
editorRect.x + editorRect.width / 2,
editorRect.y + editorRect.height / 2
);
await copyPaste(page);
await waitForStorageEntries(page, 7);
}
});
});
describe("FreeText with popup", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait(
"annotation-freetext.pdf",
".annotationEditorLayer"
);
});
afterAll(async () => {
await closePages(pages);
});
it("must not remove an empty annotation", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.hover("[data-annotation-id='23R']");
// Wait for the popup to be displayed.
await page.waitForFunction(
`document.querySelector("[data-annotation-id='popup_23R']").hidden === false`
);
// Enter in editing mode.
await page.click("#editorFreeText");
await page.waitForTimeout(10);
await page.click("#editorFreeText");
await page.hover("[data-annotation-id='23R']");
// Wait for the popup to be displayed.
await page.waitForFunction(
`document.querySelector("[data-annotation-id='popup_23R']").hidden === false`
);
})
);
});
it("must hide the popup when editing", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("[data-annotation-id='20R']");
// Wait for the popup to be displayed.
await page.waitForFunction(
`document.querySelector("[data-annotation-id='popup_20R']").hidden === false`
);
// Enter in editing mode.
await page.click("#editorFreeText");
// Wait for the popup to be hidden.
await page.waitForFunction(
`document.querySelector("[data-annotation-id='popup_20R']").hidden === true`
);
// Exit editing mode.
await page.click("#editorFreeText");
// Wait for the popup to be visible.
await page.waitForFunction(
`document.querySelector("[data-annotation-id='popup_20R']").hidden === false`
);
})
);
});
});
});