diff --git a/test/integration/freetext_editor_spec.js b/test/integration/freetext_editor_spec.js index 098dc3fa7..daa8bb9ae 100644 --- a/test/integration/freetext_editor_spec.js +++ b/test/integration/freetext_editor_spec.js @@ -22,8 +22,11 @@ const { getFirstSerialized, getSerialized, loadAndWait, + scrollIntoView, waitForEvent, waitForSelectedEditor, + waitForUnselectedEditor, + waitForSerialized, waitForStorageEntries, } = require("./test_utils.js"); @@ -45,17 +48,44 @@ const copyPaste = async page => { await promise; }; -const clearAll = async page => { +const selectAll = async page => { await page.keyboard.down("Control"); await page.keyboard.press("a"); await page.keyboard.up("Control"); - await page.waitForTimeout(10); + await page.waitForFunction( + () => !document.querySelector(".freeTextEditor:not(.selectedEditor)") + ); +}; + +const clearAll = async page => { + await selectAll(page); await page.keyboard.down("Control"); await page.keyboard.press("Backspace"); await page.keyboard.up("Control"); - await page.waitForTimeout(10); + await waitForStorageEntries(page, 0); }; +const switchToFreeText = async page => { + await page.click("#editorFreeText"); + await page.waitForSelector(".annotationEditorLayer.freetextEditing"); +}; + +const getXY = (page, selector) => + page.evaluate(sel => { + const bbox = document.querySelector(sel).getBoundingClientRect(); + return `${bbox.x}::${bbox.y}`; + }, selector); +const waitForPositionChange = (page, selector, xy) => + page.waitForFunction( + (sel, currentXY) => { + const bbox = document.querySelector(sel).getBoundingClientRect(); + return `${bbox.x}::${bbox.y}` !== currentXY; + }, + {}, + selector, + xy + ); + describe("FreeText Editor", () => { describe("FreeText", () => { let pages; @@ -71,7 +101,7 @@ describe("FreeText Editor", () => { it("must write a string in a FreeText editor", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); const rect = await page.$eval(".annotationEditorLayer", el => { // With Chrome something is wrong when serializing a DomRect, @@ -82,7 +112,9 @@ describe("FreeText Editor", () => { const data = "Hello PDF.js World !!"; await page.mouse.click(rect.x + 100, rect.y + 100); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(0), { + visible: true, + }); await page.type(`${getEditorSelector(0)} .internal`, data); const editorRect = await page.$eval(getEditorSelector(0), el => { @@ -100,6 +132,9 @@ describe("FreeText Editor", () => { editorRect.x, editorRect.y + 2 * editorRect.height ); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay.enabled` + ); await waitForSelectedEditor(page, getEditorSelector(0)); await waitForStorageEntries(page, 1); @@ -111,11 +146,15 @@ describe("FreeText Editor", () => { // Edit again. await page.keyboard.press("Enter"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay:not(.enabled)` + ); // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay.enabled` + ); content = await page.$eval(getEditorSelector(0), el => el.innerText.trimEnd() @@ -141,6 +180,9 @@ describe("FreeText Editor", () => { await waitForSelectedEditor(page, getEditorSelector(0)); await copyPaste(page); + await page.waitForSelector(getEditorSelector(1), { + visible: true, + }); await waitForStorageEntries(page, 2); const content = await page.$eval(getEditorSelector(0), el => @@ -154,6 +196,9 @@ describe("FreeText Editor", () => { expect(pastedContent).withContext(`In ${browserName}`).toEqual(content); await copyPaste(page); + await page.waitForSelector(getEditorSelector(2), { + visible: true, + }); await waitForStorageEntries(page, 3); pastedContent = await page.$eval(getEditorSelector(2), el => @@ -183,7 +228,7 @@ describe("FreeText Editor", () => { it("must check that a paste has been undone", async () => { // Run sequentially to avoid clipboard issues. - for (const [browserName, page] of pages) { + for (const [, page] of pages) { const rect = await page.$eval(".annotationEditorLayer", el => { const { x, y } = el.getBoundingClientRect(); return { x, y }; @@ -191,7 +236,9 @@ describe("FreeText Editor", () => { const data = "Hello PDF.js World !!"; await page.mouse.click(rect.x + 100, rect.y + 100); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(3), { + visible: true, + }); await page.type(`${getEditorSelector(3)} .internal`, data); const editorRect = await page.$eval(getEditorSelector(3), el => { @@ -204,8 +251,7 @@ describe("FreeText Editor", () => { editorRect.x, editorRect.y + 2 * editorRect.height ); - - await page.waitForTimeout(10); + await page.waitForSelector(`${getEditorSelector(3)} .overlay.enabled`); // And select it again. await page.mouse.click( @@ -213,27 +259,20 @@ describe("FreeText Editor", () => { editorRect.y + editorRect.height / 2 ); - await page.waitForTimeout(10); - 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.waitForSelector(getEditorSelector(4), { + visible: 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); + await page.waitForFunction( + sel => !document.querySelector(sel), + {}, + getEditorSelector(4) + ); for (let i = 0; i < 2; i++) { const promise = waitForEvent(page, "paste"); @@ -241,31 +280,19 @@ describe("FreeText Editor", () => { await page.keyboard.press("v"); await page.keyboard.up("Control"); await promise; - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(5 + i)); } - 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); + await page.waitForFunction( + sel => !document.querySelector(sel), + {}, + getEditorSelector(6 - i) + ); } - - length = await page.evaluate( - sel => { - return document.querySelectorAll(sel).length; - }, - `${getEditorSelector(5)}, ${getEditorSelector(6)}` - ); - expect(length).withContext(`In ${browserName}`).toEqual(0); } }); @@ -298,12 +325,16 @@ describe("FreeText Editor", () => { stacksRect.x + stacksRect.width + 1, stacksRect.y + stacksRect.height / 2 ); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(7), { + visible: true, + }); await page.type(`${getEditorSelector(7)} .internal`, data); // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(7)} .overlay.enabled` + ); const ariaOwns = await page.$eval(".textLayer", el => { const span = el.querySelector(`span[pdfjs="true"]`); @@ -329,7 +360,9 @@ describe("FreeText Editor", () => { const data = "Hello PDF.js World !!"; await page.mouse.click(rect.x + 100, rect.y + 100); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(8), { + visible: true, + }); await page.type(`${getEditorSelector(8)} .internal`, data); const editorRect = await page.$eval(getEditorSelector(8), el => { @@ -339,15 +372,18 @@ describe("FreeText Editor", () => { // Commit. await page.keyboard.press("Escape"); + await page.waitForSelector( + `${getEditorSelector(8)} .overlay.enabled` + ); expect(await getSelectedEditors(page)) .withContext(`In ${browserName}`) .toEqual([8]); await page.keyboard.press("Escape"); - expect(await getSelectedEditors(page)) - .withContext(`In ${browserName}`) - .toEqual([]); + await page.waitForFunction( + () => !document.querySelector(".selectedEditor") + ); await page.mouse.click( editorRect.x + editorRect.width / 2, @@ -362,11 +398,9 @@ describe("FreeText Editor", () => { // Escape. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); - - expect(await getSelectedEditors(page)) - .withContext(`In ${browserName}`) - .toEqual([]); + await page.waitForFunction( + () => !document.querySelector(".selectedEditor") + ); // TODO: uncomment that stuff once we've a way to dismiss // the context menu. @@ -390,7 +424,9 @@ describe("FreeText Editor", () => { await clearAll(page); await page.mouse.click(rect.x + 200, rect.y + 100); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(9), { + visible: true, + }); for (let i = 0; i < 5; i++) { await page.type(`${getEditorSelector(9)} .internal`, "A"); @@ -405,7 +441,9 @@ describe("FreeText Editor", () => { editorRect.x, editorRect.y + 2 * editorRect.height ); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(9)} .overlay.enabled` + ); if (i < 4) { // And select it again. @@ -414,40 +452,51 @@ describe("FreeText Editor", () => { editorRect.y + editorRect.height / 2, { clickCount: 2 } ); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(9)} .overlay:not(.enabled)` + ); } } + let prevText = await page.$eval( + `${getEditorSelector(9)} .internal`, + el => el.innerText + ); + + const waitForTextChange = previous => + page.waitForFunction( + (prev, sel) => document.querySelector(sel).innerText !== prev, + {}, + previous, + `${getEditorSelector(9)} .internal` + ); + const getText = () => + page.$eval(`${getEditorSelector(9)} .internal`, el => el.innerText); + + // We're in the middle of the text. 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; - }); + await waitForTextChange(prevText); + let text = (prevText = await getText()); 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); + await waitForTextChange(prevText); - text = await page.$eval(`${getEditorSelector(9)} .internal`, el => { - return el.innerText; - }); + text = prevText = await getText(); 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); + await waitForTextChange(prevText); - text = await page.$eval(`${getEditorSelector(9)} .internal`, el => { - return el.innerText; - }); + text = prevText = await getText(); expect(text).withContext(`In ${browserName}`).toEqual("AAAA"); @@ -455,22 +504,24 @@ describe("FreeText Editor", () => { await page.keyboard.down("Control"); await page.keyboard.press("z"); await page.keyboard.up("Control"); - await page.waitForTimeout(10); + if (i < 3) { + await waitForTextChange(prevText); + prevText = await getText(); + } } - expect(await getSelectedEditors(page)) - .withContext(`In ${browserName}`) - .toEqual([]); + await page.waitForFunction( + () => !document.querySelector(".selectedEditor") + ); 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; + await page.waitForSelector(getEditorSelector(9), { + visible: true, }); + text = await getText(); expect(text).withContext(`In ${browserName}`).toEqual("A"); // Add a new A. @@ -483,7 +534,9 @@ describe("FreeText Editor", () => { editorRect.y + editorRect.height / 2, { clickCount: 2 } ); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(9)} .overlay:not(.enabled)` + ); await page.type(`${getEditorSelector(9)} .internal`, "A"); // Commit. @@ -491,11 +544,9 @@ describe("FreeText Editor", () => { editorRect.x, editorRect.y + 2 * editorRect.height ); - await page.waitForTimeout(10); + await page.waitForSelector(`${getEditorSelector(9)} .overlay.enabled`); - text = await page.$eval(`${getEditorSelector(9)} .internal`, el => { - return el.innerText; - }); + text = await getText(); expect(text).withContext(`In ${browserName}`).toEqual("AA"); } }); @@ -515,7 +566,7 @@ describe("FreeText Editor", () => { 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"); + await switchToFreeText(page); const rect = await page.$eval(".annotationEditorLayer", el => { // With Chrome something is wrong when serializing a DomRect, @@ -531,7 +582,9 @@ describe("FreeText Editor", () => { rect.x + (i + 1) * 100, rect.y + (i + 1) * 100 ); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(i), { + visible: true, + }); await page.type(`${getEditorSelector(i)} .internal`, data); const editorRect = await page.$eval(getEditorSelector(i), el => { @@ -553,26 +606,28 @@ describe("FreeText Editor", () => { editorRect.x, editorRect.y + 2 * editorRect.height ); + await page.waitForSelector( + `${getEditorSelector(i)} .overlay.enabled` + ); } - await page.keyboard.down("Control"); - await page.keyboard.press("a"); - await page.keyboard.up("Control"); - await page.waitForTimeout(10); + await selectAll(page); expect(await getSelectedEditors(page)) .withContext(`In ${browserName}`) .toEqual([0, 1, 2, 3]); + // Unselect the editor. await page.keyboard.down("Control"); await page.mouse.click(editorCenters[1].x, editorCenters[1].y); - await page.waitForTimeout(10); + await waitForUnselectedEditor(page, getEditorSelector(1)); expect(await getSelectedEditors(page)) .withContext(`In ${browserName}`) .toEqual([0, 2, 3]); await page.mouse.click(editorCenters[2].x, editorCenters[2].y); + await waitForUnselectedEditor(page, getEditorSelector(2)); expect(await getSelectedEditors(page)) .withContext(`In ${browserName}`) @@ -580,13 +635,16 @@ describe("FreeText Editor", () => { await page.mouse.click(editorCenters[1].x, editorCenters[1].y); await page.keyboard.up("Control"); - await page.waitForTimeout(10); + await waitForSelectedEditor(page, getEditorSelector(1)); expect(await getSelectedEditors(page)) .withContext(`In ${browserName}`) .toEqual([0, 1, 3]); await copyPaste(page); + await page.waitForSelector(getEditorSelector(6), { + visible: true, + }); // 0,1,3 are unselected and new pasted editors are selected. expect(await getSelectedEditors(page)) @@ -595,11 +653,13 @@ describe("FreeText Editor", () => { // No ctrl here, hence all are unselected and 2 is selected. await page.mouse.click(editorCenters[2].x, editorCenters[2].y); + await waitForSelectedEditor(page, getEditorSelector(2)); expect(await getSelectedEditors(page)) .withContext(`In ${browserName}`) .toEqual([2]); await page.mouse.click(editorCenters[1].x, editorCenters[1].y); + await waitForSelectedEditor(page, getEditorSelector(1)); expect(await getSelectedEditors(page)) .withContext(`In ${browserName}`) .toEqual([1]); @@ -607,6 +667,7 @@ describe("FreeText Editor", () => { await page.keyboard.down("Control"); await page.mouse.click(editorCenters[3].x, editorCenters[3].y); + await waitForSelectedEditor(page, getEditorSelector(3)); expect(await getSelectedEditors(page)) .withContext(`In ${browserName}`) .toEqual([1, 3]); @@ -615,12 +676,13 @@ describe("FreeText Editor", () => { // Delete 1 and 3. await page.keyboard.press("Backspace"); - await page.waitForTimeout(10); + await page.waitForFunction( + sels => sels.every(sel => !document.querySelector(sel)), + {}, + [1, 3].map(getEditorSelector) + ); - await page.keyboard.down("Control"); - await page.keyboard.press("a"); - await page.keyboard.up("Control"); - await page.waitForTimeout(10); + await selectAll(page); expect(await getSelectedEditors(page)) .withContext(`In ${browserName}`) @@ -628,30 +690,37 @@ describe("FreeText Editor", () => { // Create an empty editor. await page.mouse.click(rect.x + 700, rect.y + 100); + await page.waitForSelector(getEditorSelector(7), { + visible: true, + }); 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); + await waitForSelectedEditor(page, getEditorSelector(2)); expect(await getSelectedEditors(page)) .withContext(`In ${browserName}`) .toEqual([2]); // Create an empty editor. await page.mouse.click(rect.x + 700, rect.y + 100); + await page.waitForSelector(getEditorSelector(8), { + visible: true, + }); expect(await getSelectedEditors(page)) .withContext(`In ${browserName}`) .toEqual([8]); // Dismiss it. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForFunction( + sel => !document.querySelector(sel), + {}, + getEditorSelector(8) + ); - // Select all. - await page.keyboard.down("Control"); - await page.keyboard.press("a"); - await page.keyboard.up("Control"); - await page.waitForTimeout(10); + await selectAll(page); // Check that all the editors are correctly selected (and the focus // didn't move to the body when the empty editor was removed). @@ -676,7 +745,7 @@ describe("FreeText Editor", () => { it("must serialize invisible annotations", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); let currentId = 0; const expected = []; const oneToFourteen = Array.from(new Array(14).keys(), x => x + 1); @@ -684,17 +753,12 @@ describe("FreeText Editor", () => { 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 scrollIntoView(page, pageSelector); + const annotationLayerSelector = `${pageSelector} > .annotationEditorLayer.freetextEditing`; await page.waitForSelector(annotationLayerSelector, { visible: true, timeout: 0, }); - await page.waitForTimeout(50); if (![1, 14].includes(pageNumber)) { continue; } @@ -709,12 +773,16 @@ describe("FreeText Editor", () => { const data = `Hello PDF.js World !! on page ${pageNumber}`; expected.push(data); await page.mouse.click(rect.x + 100, rect.y + 100); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(currentId), { + visible: true, + }); await page.type(`${getEditorSelector(currentId)} .internal`, data); // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(currentId)} .overlay.enabled` + ); await waitForSelectedEditor(page, getEditorSelector(currentId)); await waitForStorageEntries(page, currentId + 1); @@ -725,7 +793,6 @@ describe("FreeText Editor", () => { expect(content).withContext(`In ${browserName}`).toEqual(data); currentId += 1; - await page.waitForTimeout(10); } const serialize = proprName => @@ -750,11 +817,15 @@ describe("FreeText Editor", () => { ]); // 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); + await selectAll(page); + + const [prevFontSize, prevColor] = await page.$eval( + ".selectedEditor .internal", + el => { + const style = getComputedStyle(el); + return [style.fontSize, style.color]; + } + ); page.evaluate(() => { window.PDFViewerApplication.eventBus.dispatch( @@ -766,8 +837,15 @@ describe("FreeText Editor", () => { } ); }); + await page.waitForFunction( + prev => + getComputedStyle( + document.querySelector(".selectedEditor .internal") + ).fontSize !== prev, + {}, + prevFontSize + ); - await page.waitForTimeout(10); expect(await serialize("fontSize")) .withContext(`In ${browserName}`) .toEqual([13, 13]); @@ -783,8 +861,15 @@ describe("FreeText Editor", () => { } ); }); + await page.waitForFunction( + prev => + getComputedStyle( + document.querySelector(".selectedEditor .internal") + ).color !== prev, + {}, + prevColor + ); - await page.waitForTimeout(10); expect(await serialize("color")) .withContext(`In ${browserName}`) .toEqual([ @@ -816,7 +901,7 @@ describe("FreeText Editor", () => { it("must take the media box into account", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); let currentId = 0; for (let step = 0; step < 3; step++) { @@ -831,18 +916,21 @@ describe("FreeText Editor", () => { const x = rect.x + 0.1 * rect.width; const y = rect.y + 0.1 * rect.height; await page.mouse.click(x, y); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(currentId), { + visible: true, + }); await page.type(`${getEditorSelector(currentId)} .internal`, data); // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(currentId)} .overlay.enabled` + ); await page.evaluate(() => { document.getElementById("pageRotateCw").click(); }); currentId += 1; - await page.waitForTimeout(10); await page.waitForSelector( ".page[data-page-number='1'] .canvasWrapper", { @@ -906,14 +994,14 @@ describe("FreeText Editor", () => { it("must move an annotation", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); 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); + const serialized = await getSerialized(page); expect(serialized).withContext(`In ${browserName}`).toEqual([]); const editorRect = await page.$eval(getEditorSelector(0), el => { @@ -923,6 +1011,7 @@ describe("FreeText Editor", () => { // Select the annotation we want to move. await page.mouse.click(editorRect.x + 2, editorRect.y + 2); + await waitForSelectedEditor(page, getEditorSelector(0)); await dragAndDropAnnotation( page, @@ -931,9 +1020,7 @@ describe("FreeText Editor", () => { 100, 100 ); - - serialized = await getSerialized(page); - expect(serialized.length).withContext(`In ${browserName}`).toEqual(1); + await waitForSerialized(page, 1); }) ); }); @@ -953,7 +1040,7 @@ describe("FreeText Editor", () => { it("must update an existing annotation", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); let editorIds = await getEditors(page, "freeText"); expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6); @@ -967,11 +1054,20 @@ describe("FreeText Editor", () => { editorRect.y + editorRect.height / 2, { clickCount: 2 } ); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay:not(.enabled)` + ); await page.keyboard.down("Control"); await page.keyboard.press("End"); await page.keyboard.up("Control"); - await page.waitForTimeout(10); + await page.waitForFunction( + sel => + document.getSelection().anchorOffset === + document.querySelector(sel).innerText.length, + {}, + `${getEditorSelector(0)} .internal` + ); await page.type( `${getEditorSelector(0)} .internal`, @@ -983,8 +1079,11 @@ describe("FreeText Editor", () => { editorRect.x, editorRect.y + 2 * editorRect.height ); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay.enabled` + ); - let serialized = await getSerialized(page); + const serialized = await getSerialized(page); expect(serialized.length).withContext(`In ${browserName}`).toEqual(1); expect(serialized[0]).toEqual( jasmine.objectContaining({ @@ -997,6 +1096,10 @@ describe("FreeText Editor", () => { // Disable editing mode. await page.click("#editorFreeText"); + await page.waitForSelector( + `.annotationEditorLayer:not(.freetextEditing)` + ); + // We want to check that the editor is displayed but not the original // annotation. editorIds = await getEditors(page, "freeText"); @@ -1008,17 +1111,14 @@ describe("FreeText Editor", () => { expect(hidden).withContext(`In ${browserName}`).toBeTrue(); // Re-enable editing mode. - await page.click("#editorFreeText"); + await switchToFreeText(page); 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([]); + await waitForSerialized(page, 0); editorIds = await getEditors(page, "freeText"); expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6); @@ -1027,7 +1127,9 @@ describe("FreeText Editor", () => { await page.keyboard.down("Control"); await page.keyboard.press("z"); await page.keyboard.up("Control"); - await page.waitForTimeout(10); + // Nothing should happen, it's why we can't wait for something + // specific! + await page.waitForTimeout(200); // We check that the editor hasn't been removed. editorIds = await getEditors(page, "freeText"); @@ -1051,7 +1153,7 @@ describe("FreeText Editor", () => { it("must update an existing annotation but not an empty one", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); const editorIds = await getEditors(page, "freeText"); expect(editorIds.length).withContext(`In ${browserName}`).toEqual(1); @@ -1074,7 +1176,7 @@ describe("FreeText Editor", () => { it("must delete an existing annotation", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); let editorIds = await getEditors(page, "freeText"); expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6); @@ -1087,11 +1189,15 @@ describe("FreeText Editor", () => { editorRect.x + editorRect.width / 2, editorRect.y + editorRect.height / 2 ); - await page.waitForTimeout(10); + await waitForSelectedEditor(page, getEditorSelector(3)); await page.keyboard.press("Backspace"); - await page.waitForTimeout(10); + await page.waitForFunction( + sel => !document.querySelector(sel), + {}, + getEditorSelector(3) + ); - let serialized = await getSerialized(page); + const serialized = await getSerialized(page); expect(serialized).toEqual([ { pageIndex: 0, @@ -1100,7 +1206,12 @@ describe("FreeText Editor", () => { }, ]); + // Disable editing mode. await page.click("#editorFreeText"); + await page.waitForSelector( + `.annotationEditorLayer:not(.freetextEditing)` + ); + // We want to check that nothing is displayed. editorIds = await getEditors(page, "freeText"); expect(editorIds.length).withContext(`In ${browserName}`).toEqual(0); @@ -1111,17 +1222,14 @@ describe("FreeText Editor", () => { expect(hidden).withContext(`In ${browserName}`).toBeTrue(); // Re-enable editing mode. - await page.click("#editorFreeText"); + await switchToFreeText(page); 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([]); + await waitForSerialized(page, 0); }) ); }); @@ -1141,7 +1249,7 @@ describe("FreeText Editor", () => { 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"); + await switchToFreeText(page); const editorIds = await getEditors(page, "freeText"); expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6); @@ -1154,8 +1262,12 @@ describe("FreeText Editor", () => { editorRect.x + editorRect.width / 2, editorRect.y + editorRect.height / 2 ); + await waitForSelectedEditor(page, getEditorSelector(1)); await copyPaste(page); + await page.waitForSelector(getEditorSelector(6), { + visible: true, + }); await waitForStorageEntries(page, 7); } }); @@ -1214,18 +1326,28 @@ describe("FreeText Editor", () => { 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` + () => + document.querySelector("[data-annotation-id='popup_23R']") + .hidden === false ); // Enter in editing mode. + await switchToFreeText(page); + + await page.waitForTimeout(200); + + // Disable editing mode. await page.click("#editorFreeText"); - await page.waitForTimeout(10); - await page.click("#editorFreeText"); + await page.waitForSelector( + `.annotationEditorLayer:not(.freetextEditing)` + ); 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` + () => + document.querySelector("[data-annotation-id='popup_23R']") + .hidden === false ); }) ); @@ -1237,21 +1359,31 @@ describe("FreeText Editor", () => { 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` + () => + document.querySelector("[data-annotation-id='popup_20R']") + .hidden === false ); // Enter in editing mode. - await page.click("#editorFreeText"); + await switchToFreeText(page); // Wait for the popup to be hidden. await page.waitForFunction( - `document.querySelector("[data-annotation-id='popup_20R']").hidden === true` + () => + document.querySelector("[data-annotation-id='popup_20R']") + .hidden === true ); // Exit editing mode. await page.click("#editorFreeText"); + await page.waitForSelector( + `.annotationEditorLayer:not(.freetextEditing)` + ); + // Wait for the popup to be visible. await page.waitForFunction( - `document.querySelector("[data-annotation-id='popup_20R']").hidden === false` + () => + document.querySelector("[data-annotation-id='popup_20R']") + .hidden === false ); }) ); @@ -1272,9 +1404,13 @@ describe("FreeText Editor", () => { it("must check that the dimensions of a rotated annotations are correct after a font size change", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.keyboard.press("r"); - await page.waitForTimeout(10); - await page.click("#editorFreeText"); + await page.evaluate(() => { + window.PDFViewerApplication.rotatePages(90); + }); + await page.waitForSelector( + ".annotationEditorLayer[data-main-rotation='90']" + ); + await switchToFreeText(page); const rect = await page.$eval(".annotationEditorLayer", el => { const { x, y } = el.getBoundingClientRect(); @@ -1283,7 +1419,9 @@ describe("FreeText Editor", () => { const data = "Hello PDF.js World !!"; await page.mouse.click(rect.x + 100, rect.y + 100); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(0), { + visible: true, + }); await page.type(`${getEditorSelector(0)} .internal`, data); const editorRect = await page.$eval(getEditorSelector(0), el => { @@ -1301,6 +1439,17 @@ describe("FreeText Editor", () => { editorRect.x, editorRect.y + 2 * editorRect.height ); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay.enabled` + ); + + // Make Chrome happy. + await page.waitForFunction(() => { + const box = [ + ...window.PDFViewerApplication.pdfDocument.annotationStorage.serializable.map.values(), + ][0].rect; + return box[2] !== box[0]; + }, {}); let serialized = await getSerialized(page); let bbox = serialized[0].rect; @@ -1310,15 +1459,19 @@ describe("FreeText Editor", () => { .withContext(`In ${browserName}`) .toEqual(true); - for (let i = 0; i < 3; i++) { - await page.keyboard.press("r"); - await page.waitForTimeout(10); - } + await page.evaluate(() => { + window.PDFViewerApplication.rotatePages(270); + }); + await page.waitForSelector( + ".annotationEditorLayer[data-main-rotation='0']" + ); - await page.keyboard.down("Control"); - await page.keyboard.press("a"); - await page.keyboard.up("Control"); - await page.waitForTimeout(10); + await selectAll(page); + + const prevWidth = await page.$eval( + ".selectedEditor .internal", + el => el.getBoundingClientRect().width + ); page.evaluate(() => { window.PDFViewerApplication.eventBus.dispatch( @@ -1330,7 +1483,23 @@ describe("FreeText Editor", () => { } ); }); - await page.waitForTimeout(10); + + await page.waitForFunction( + prev => + document + .querySelector(".selectedEditor .internal") + .getBoundingClientRect().width !== prev, + {}, + prevWidth + ); + + // Make Chrome happy. + await page.waitForFunction(() => { + const box = [ + ...window.PDFViewerApplication.pdfDocument.annotationStorage.serializable.map.values(), + ][0].rect; + return box[2] !== box[0]; + }, {}); serialized = await getSerialized(page); bbox = serialized[0].rect; @@ -1358,24 +1527,19 @@ describe("FreeText Editor", () => { it("must delete invisible annotations", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); let currentId = 0; const oneToFourteen = Array.from(new Array(14).keys(), 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 scrollIntoView(page, pageSelector); + const annotationLayerSelector = `${pageSelector} > .annotationEditorLayer.freetextEditing`; await page.waitForSelector(annotationLayerSelector, { visible: true, timeout: 0, }); - await page.waitForTimeout(50); if (![1, 14].includes(pageNumber)) { continue; } @@ -1389,21 +1553,21 @@ describe("FreeText Editor", () => { const data = `Hello PDF.js World !! on page ${pageNumber}`; await page.mouse.click(rect.x + 100, rect.y + 100); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(currentId), { + visible: true, + }); await page.type(`${getEditorSelector(currentId)} .internal`, data); // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(currentId)} .overlay.enabled` + ); currentId += 1; } - // Select all. - await page.keyboard.down("Control"); - await page.keyboard.press("a"); - await page.keyboard.up("Control"); - await page.waitForTimeout(10); + await selectAll(page); const serialize = () => page.evaluate(() => { @@ -1419,11 +1583,7 @@ describe("FreeText Editor", () => { // Delete await page.keyboard.press("Backspace"); - await page.waitForTimeout(10); - - expect(await serialize()) - .withContext(`In ${browserName}`) - .toEqual([]); + await waitForSerialized(page, 0); }) ); }); @@ -1447,7 +1607,7 @@ describe("FreeText Editor", () => { it("must open an existing annotation and check that the position are good", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); await page.evaluate(() => { document.getElementById("editorFreeTextParamsToolbar").remove(); @@ -1523,18 +1683,23 @@ describe("FreeText Editor", () => { editorImage.height ); - await page.evaluate(N => { + const annotationId = await page.evaluate(N => { const editor = document.getElementById( `pdfjs_internal_editor_${N}` ); - const annotationId = editor.getAttribute("annotation-id"); + const annId = editor.getAttribute("annotation-id"); const annotation = document.querySelector( - `[data-annotation-id="${annotationId}"]` + `[data-annotation-id="${annId}"]` ); editor.hidden = true; annotation.hidden = false; + return annId; }, n); - await page.waitForTimeout(10); + await page.waitForSelector(`${getEditorSelector(n)}[hidden]`); + await page.waitForSelector( + `[data-annotation-id="${annotationId}"]:not([hidden])` + ); + const annotationPng = await page.screenshot({ clip: rect, type: "png", @@ -1578,7 +1743,7 @@ describe("FreeText Editor", () => { it("must open an existing rotated annotation and check that the position are good", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); await page.evaluate(() => { document.getElementById("editorFreeTextParamsToolbar").remove(); @@ -1689,18 +1854,23 @@ describe("FreeText Editor", () => { start ); - await page.evaluate(N => { + const annotationId = await page.evaluate(N => { const editor = document.getElementById( `pdfjs_internal_editor_${N}` ); - const annotationId = editor.getAttribute("annotation-id"); + const annId = editor.getAttribute("annotation-id"); const annotation = document.querySelector( - `[data-annotation-id="${annotationId}"]` + `[data-annotation-id="${annId}"]` ); editor.hidden = true; annotation.hidden = false; + return annId; }, n); - await page.waitForTimeout(10); + await page.waitForSelector(`${getEditorSelector(n)}[hidden]`); + await page.waitForSelector( + `[data-annotation-id="${annotationId}"]:not([hidden])` + ); + const annotationPng = await page.screenshot({ clip: rect, type: "png", @@ -1741,7 +1911,7 @@ describe("FreeText Editor", () => { it("must check that the shortcuts are working correctly", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); const rect = await page.$eval(".annotationEditorLayer", el => { const { x, y } = el.getBoundingClientRect(); @@ -1750,7 +1920,9 @@ describe("FreeText Editor", () => { const data = "Hello PDF.js World !!"; await page.mouse.click(rect.x + 100, rect.y + 100); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(0), { + visible: true, + }); await page.type(`${getEditorSelector(0)} .internal`, data); const editorRect = await page.$eval(getEditorSelector(0), el => { @@ -1768,31 +1940,30 @@ describe("FreeText Editor", () => { editorRect.x, editorRect.y + 2 * editorRect.height ); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay.enabled` + ); await page.focus("#editorFreeTextColor"); await page.keyboard.down("Control"); await page.keyboard.press("z"); await page.keyboard.up("Control"); - await page.waitForTimeout(10); - let hasEditor = await page.evaluate(sel => { - return !!document.querySelector(sel); - }, getEditorSelector(0)); - - expect(hasEditor).withContext(`In ${browserName}`).toEqual(false); + await page.waitForFunction( + sel => !document.querySelector(sel), + {}, + getEditorSelector(0) + ); await page.keyboard.down("Control"); await page.keyboard.press("y"); await page.keyboard.up("Control"); - await page.waitForTimeout(10); - - hasEditor = await page.evaluate(sel => { - return !!document.querySelector(sel); - }, getEditorSelector(0)); - - expect(hasEditor).withContext(`In ${browserName}`).toEqual(true); + await page.waitForFunction( + sel => !!document.querySelector(sel), + {}, + getEditorSelector(0) + ); }) ); }); @@ -1812,7 +1983,7 @@ describe("FreeText Editor", () => { it("must check the position of moved editor", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); const rect = await page.$eval(".annotationEditorLayer", el => { const { x, y } = el.getBoundingClientRect(); @@ -1820,11 +1991,14 @@ describe("FreeText Editor", () => { }); const data = "Hello PDF.js World !!"; + const selectorEditor = getEditorSelector(0); await page.mouse.click(rect.x + 200, rect.y + 200); - await page.waitForTimeout(10); - await page.type(`${getEditorSelector(0)} .internal`, data); + await page.waitForSelector(selectorEditor, { + visible: true, + }); + await page.type(`${selectorEditor} .internal`, data); - const editorRect = await page.$eval(getEditorSelector(0), el => { + const editorRect = await page.$eval(selectorEditor, el => { const { x, y, width, height } = el.getBoundingClientRect(); return { x, @@ -1839,13 +2013,15 @@ describe("FreeText Editor", () => { editorRect.x, editorRect.y + 2 * editorRect.height ); - await page.waitForTimeout(10); + await page.waitForSelector(`${selectorEditor} .overlay.enabled`); const [pageX, pageY] = await getFirstSerialized(page, x => x.rect); + let xy = await getXY(page, selectorEditor); for (let i = 0; i < 20; i++) { await page.keyboard.press("ArrowRight"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } let [newX, newY] = await getFirstSerialized(page, x => x.rect); @@ -1858,7 +2034,8 @@ describe("FreeText Editor", () => { for (let i = 0; i < 20; i++) { await page.keyboard.press("ArrowDown"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } [newX, newY] = await getFirstSerialized(page, x => x.rect); @@ -1873,7 +2050,8 @@ describe("FreeText Editor", () => { await page.keyboard.down("Control"); await page.keyboard.press("ArrowLeft"); await page.keyboard.up("Control"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } [newX, newY] = await getFirstSerialized(page, x => x.rect); @@ -1888,7 +2066,8 @@ describe("FreeText Editor", () => { await page.keyboard.down("Control"); await page.keyboard.press("ArrowUp"); await page.keyboard.up("Control"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } [newX, newY] = await getFirstSerialized(page, x => x.rect); @@ -1905,11 +2084,7 @@ describe("FreeText Editor", () => { it("must check arrow doesn't move an editor when a slider is focused", 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.waitForTimeout(10); - + await selectAll(page); await page.focus("#editorFreeTextFontSize"); const [page1X, , page2X] = await getFirstSerialized( @@ -1918,11 +2093,13 @@ describe("FreeText Editor", () => { ); const pageWidth = page2X - page1X; + const selectorEditor = getEditorSelector(0); + let xy = await getXY(page, selectorEditor); for (let i = 0; i < 5; i++) { await page.keyboard.press("ArrowRight"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } - await page.waitForTimeout(10); const [new1X, , new2X] = await getFirstSerialized(page, x => x.rect); const newWidth = new2X - new1X; @@ -1947,11 +2124,14 @@ describe("FreeText Editor", () => { }); const data = "Hello PDF.js World !!"; + let selectorEditor = getEditorSelector(1); await page.mouse.click(rect.x + 100, rect.y + 100); - await page.waitForTimeout(10); - await page.type(`${getEditorSelector(1)} .internal`, data); + await page.waitForSelector(selectorEditor, { + visible: true, + }); + await page.type(`${selectorEditor} .internal`, data); - const editorRect = await page.$eval(getEditorSelector(1), el => { + const editorRect = await page.$eval(selectorEditor, el => { const { x, y, width, height } = el.getBoundingClientRect(); return { x, @@ -1966,60 +2146,65 @@ describe("FreeText Editor", () => { editorRect.x, editorRect.y + 2 * editorRect.height ); - await page.waitForTimeout(10); + await page.waitForSelector(`${selectorEditor} .overlay.enabled`); const [pageX, pageY] = await getFirstSerialized(page, x => x.rect); await clearAll(page); + selectorEditor = getEditorSelector(2); await page.mouse.click(rect.x + 100, rect.y + 100); + await page.waitForSelector(selectorEditor, { + visible: true, + }); + let xy = await getXY(page, selectorEditor); for (let i = 0; i < 20; i++) { await page.keyboard.press("ArrowRight"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } - await page.waitForTimeout(10); for (let i = 0; i < 2; i++) { await page.keyboard.down("Control"); await page.keyboard.press("ArrowDown"); await page.keyboard.up("Control"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } - await page.waitForTimeout(10); for (let i = 0; i < 20; i++) { await page.keyboard.press("ArrowLeft"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } - await page.waitForTimeout(10); for (let i = 0; i < 2; i++) { await page.keyboard.down("Control"); await page.keyboard.press("ArrowUp"); await page.keyboard.up("Control"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } - await page.waitForTimeout(10); for (let i = 0; i < 2; i++) { await page.keyboard.down("Control"); await page.keyboard.press("ArrowRight"); await page.keyboard.up("Control"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } - await page.waitForTimeout(10); for (let i = 0; i < 2; i++) { await page.keyboard.down("Control"); await page.keyboard.press("ArrowLeft"); await page.keyboard.up("Control"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } - await page.waitForTimeout(10); - await page.type(`${getEditorSelector(2)} .internal`, data); + await page.type(`${selectorEditor} .internal`, data); await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector(`${selectorEditor} .overlay.enabled`); const [newX, newY] = await getFirstSerialized(page, x => x.rect); expect(Math.round(newX)) @@ -2066,8 +2251,7 @@ describe("FreeText Editor", () => { it("must check that the focus is on the right page", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); - await page.waitForTimeout(10); + await switchToFreeText(page); const rect = await page.$eval(".annotationEditorLayer", el => { const { x, y } = el.getBoundingClientRect(); @@ -2076,28 +2260,26 @@ describe("FreeText Editor", () => { const data = "Hello PDF.js World !!"; await page.mouse.click(rect.x + 100, rect.y + 100); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(0), { + visible: true, + }); await page.type(`${getEditorSelector(0)} .internal`, data); // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay.enabled` + ); const oneToFourteen = Array.from(new Array(14).keys(), 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:not([hidden])`; + await scrollIntoView(page, pageSelector); + const annotationLayerSelector = `${pageSelector} > .annotationEditorLayer:not([hidden]).freetextEditing`; await page.waitForSelector(annotationLayerSelector, { visible: true, timeout: 0, }); - await page.waitForTimeout(10); } const visitedPages = await page.evaluate(() => { @@ -2130,7 +2312,7 @@ describe("FreeText Editor", () => { it("must keep the focus", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); const rect = await page.$eval(".annotationEditorLayer", el => { const { x, y } = el.getBoundingClientRect(); @@ -2138,23 +2320,31 @@ describe("FreeText Editor", () => { }); await page.mouse.click(rect.x + 100, rect.y + 100); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(0), { + visible: true, + }); await page.type(`${getEditorSelector(0)} .internal`, "A"); // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay.enabled` + ); await page.mouse.click(rect.x + 110, rect.y + 150); - await page.waitForTimeout(10); - await page.type(`${getEditorSelector(0)} .internal`, "B"); + await page.waitForSelector(getEditorSelector(1), { + visible: true, + }); + await page.type(`${getEditorSelector(1)} .internal`, "B"); // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(1)} .overlay.enabled` + ); await page.mouse.click(rect.x + 115, rect.y + 155); - await page.waitForTimeout(10); + await waitForSelectedEditor(page, getEditorSelector(1)); const pos = n => page.evaluate(sel => { @@ -2172,14 +2362,20 @@ describe("FreeText Editor", () => { .withContext(`In ${browserName}`) .toEqual(1); + const selectorEditor = getEditorSelector(1); + let xy = await getXY(page, selectorEditor); for (let i = 0; i < 6; i++) { await page.keyboard.down("Control"); await page.keyboard.press("ArrowUp"); await page.keyboard.up("Control"); - await page.waitForTimeout(1); + await waitForPositionChange(page, selectorEditor, xy); + xy = await getXY(page, selectorEditor); } - await page.waitForTimeout(100); + // The editor must be moved in the DOM and potentially the focus + // will be lost, hence there's a callback will get back the focus. + await page.waitForTimeout(200); + const focused = await page.evaluate(sel => { const editor = document.querySelector(sel); return editor === document.activeElement; @@ -2211,7 +2407,7 @@ describe("FreeText Editor", () => { it("must move several annotations", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); const rect = await page.$eval(".annotationEditorLayer", el => { const { x, y } = el.getBoundingClientRect(); @@ -2222,7 +2418,9 @@ describe("FreeText Editor", () => { for (let i = 0; i < 10; i++) { await page.mouse.click(rect.x + 10 + 30 * i, rect.y + 100 + 5 * i); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(i), { + visible: true, + }); await page.type( `${getEditorSelector(i)} .internal`, String.fromCharCode(65 + i) @@ -2230,7 +2428,9 @@ describe("FreeText Editor", () => { // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(i)} .overlay.enabled` + ); allPositions.push( await page.$eval(getEditorSelector(i), el => { @@ -2240,13 +2440,8 @@ describe("FreeText Editor", () => { ); } - await page.keyboard.down("Control"); - await page.keyboard.press("a"); - await page.keyboard.up("Control"); - - await page.waitForTimeout(10); + await selectAll(page); await dragAndDropAnnotation(page, rect.x + 161, rect.y + 126, 39, 74); - await page.waitForTimeout(10); for (let i = 0; i < 10; i++) { const pos = await page.$eval(getEditorSelector(i), el => { @@ -2298,7 +2493,7 @@ describe("FreeText Editor", () => { it("must check that selected editor stay selected", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); const rect = await page.$eval(".annotationEditorLayer", el => { const { x, y } = el.getBoundingClientRect(); @@ -2307,25 +2502,24 @@ describe("FreeText Editor", () => { const data = "Hello PDF.js World !!"; await page.mouse.click(rect.x + 100, rect.y + 100); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(0), { + visible: true, + }); await page.type(`${getEditorSelector(0)} .internal`, data); // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay.enabled` + ); await page.evaluate(() => { window.editingEvents = []; }); - await page.waitForTimeout(10); for (let pageNumber = 1; pageNumber <= 4; pageNumber++) { 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 scrollIntoView(page, pageSelector); + const annotationLayerSelector = `${pageSelector} > .annotationEditorLayer.freetextEditing`; await page.waitForSelector(annotationLayerSelector, { visible: true, timeout: 0, @@ -2359,53 +2553,63 @@ describe("FreeText Editor", () => { it("must check that first annotation is selected without errors", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); - const page1Selector = `.page[data-page-number = "1"] > .annotationEditorLayer`; + const page1Selector = `.page[data-page-number = "1"] > .annotationEditorLayer.freetextEditing`; let rect = await page.$eval(page1Selector, el => { const { x, y } = el.getBoundingClientRect(); return { x, y }; }); await page.mouse.click(rect.x + 10, rect.y + 10); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(0), { + visible: true, + }); await page.type(`${getEditorSelector(0)} .internal`, "Hello"); // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay.enabled` + ); // Go to the last page. await page.keyboard.press("End"); - await page.waitForTimeout(10); - const page14Selector = `.page[data-page-number = "14"] > .annotationEditorLayer`; + const page14Selector = `.page[data-page-number = "14"] > .annotationEditorLayer.freetextEditing`; await page.waitForSelector(page14Selector, { visible: true, timeout: 0, }); - await page.waitForTimeout(10); rect = await page.$eval(page14Selector, el => { const { x, y } = el.getBoundingClientRect(); return { x, y }; }); await page.mouse.click(rect.x + 10, rect.y + 10); - await page.waitForTimeout(10); - await page.type(`${getEditorSelector(0)} .internal`, "World"); + await page.waitForSelector(getEditorSelector(1), { + visible: true, + }); + await page.type(`${getEditorSelector(1)} .internal`, "World"); await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay.enabled` + ); - for (let i = 0; i <= 13; i++) { + for (let i = 0; i < 13; i++) { await page.keyboard.press("P"); - await page.waitForTimeout(10); + const pageSelector = `.page[data-page-number = "${ + 13 - i + }"] > .annotationEditorLayer.freetextEditing`; + await page.waitForSelector(pageSelector, { + visible: true, + timeout: 0, + }); } await page.waitForSelector(getEditorSelector(0), { visible: true, - timeout: 0, }); - await page.waitForTimeout(10); rect = await page.$eval(getEditorSelector(0), el => { const { x, y, width, height } = el.getBoundingClientRect(); @@ -2421,6 +2625,8 @@ describe("FreeText Editor", () => { rect.y + rect.height / 2 ); + await waitForSelectedEditor(page, getEditorSelector(0)); + const content = await page.$eval(getEditorSelector(0), el => el.innerText.trimEnd() ); @@ -2444,7 +2650,7 @@ describe("FreeText Editor", () => { it("must check that the parent structTree id is correct", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); const parentId = "p3R_mc8"; const rect = await page.evaluate(id => { @@ -2463,12 +2669,16 @@ describe("FreeText Editor", () => { rect.x + rect.width + 5, rect.y + rect.height / 2 ); - await page.waitForTimeout(10); + await page.waitForSelector(getEditorSelector(0), { + visible: true, + }); await page.type(`${getEditorSelector(0)} .internal`, "Hello Wolrd"); // Commit. await page.keyboard.press("Escape"); - await page.waitForTimeout(10); + await page.waitForSelector( + `${getEditorSelector(0)} .overlay.enabled` + ); await waitForStorageEntries(page, 1); @@ -2493,7 +2703,7 @@ describe("FreeText Editor", () => { it("must check the text can be selected with the mouse", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#editorFreeText"); + await switchToFreeText(page); const rect = await page.$eval(".annotationEditorLayer", el => { // With Chrome something is wrong when serializing a DomRect, diff --git a/test/integration/scripting_spec.js b/test/integration/scripting_spec.js index d6d19cce9..ae970c42c 100644 --- a/test/integration/scripting_spec.js +++ b/test/integration/scripting_spec.js @@ -21,6 +21,7 @@ const { getComputedStyleSelector, loadAndWait, getFirstSerialized, + scrollIntoView, } = require("./test_utils.js"); describe("Interaction", () => { @@ -538,12 +539,7 @@ describe("Interaction", () => { text = await actAndWaitForInput( page, getSelector(refOpen), - async () => { - await page.evaluate(selector => { - const element = window.document.querySelector(selector); - element.scrollIntoView(); - }, getSelector(refOpen)); - }, + () => scrollIntoView(page, getSelector(refOpen)), false ); expect(text) @@ -818,9 +814,7 @@ describe("Interaction", () => { "window.PDFViewerApplication.scriptingReady === true" ); - await page.evaluate(selector => { - window.document.querySelector(selector).scrollIntoView(); - }, getSelector("171R")); + await scrollIntoView(page, getSelector("171R")); let sum = 0; for (const [id, val] of [ @@ -853,11 +847,7 @@ describe("Interaction", () => { // Some unrendered annotations have been updated, so check // that they've the correct value when rendered. - await page.evaluate(() => { - window.document - .querySelector('.page[data-page-number = "4"]') - .scrollIntoView(); - }); + await scrollIntoView(page, '.page[data-page-number = "4"]'); await page.waitForSelector(getSelector("299R"), { timeout: 0, }); diff --git a/test/integration/test_utils.js b/test/integration/test_utils.js index d39426166..8beec0989 100644 --- a/test/integration/test_utils.js +++ b/test/integration/test_utils.js @@ -97,16 +97,27 @@ function getSelectedEditors(page) { } exports.getSelectedEditors = getSelectedEditors; -async function waitForEvent(page, eventName, timeout = 30000) { - await Promise.race([ +async function waitForEvent(page, eventName, timeout = 5000) { + const hasTimedout = await Promise.race([ // add event listener and wait for event to fire before returning - page.evaluate(name => { - return new Promise(resolve => { - document.addEventListener(name, resolve, { once: true }); - }); - }, eventName), - page.waitForTimeout(timeout), + page.evaluate( + name => + new Promise(resolve => { + document.addEventListener(name, () => resolve(false), { once: true }); + }), + eventName + ), + page.evaluate( + timeOut => + new Promise(resolve => { + setTimeout(() => resolve(true), timeOut); + }), + timeout + ), ]); + if (hasTimedout === true) { + console.log(`waitForEvent: timeout waiting for ${eventName}`); + } } exports.waitForEvent = waitForEvent; @@ -119,15 +130,27 @@ const waitForStorageEntries = async (page, nEntries) => { }; exports.waitForStorageEntries = waitForStorageEntries; -const waitForSelectedEditor = async (page, selector) => { +const waitForSerialized = async (page, nEntries) => { await page.waitForFunction( - sel => document.querySelector(sel).classList.contains("selectedEditor"), + n => + (window.PDFViewerApplication.pdfDocument.annotationStorage.serializable + .map?.size ?? 0) === n, {}, - selector + nEntries ); }; +exports.waitForSerialized = waitForSerialized; + +const waitForSelectedEditor = async (page, selector) => { + await page.waitForSelector(`${selector}.selectedEditor`); +}; exports.waitForSelectedEditor = waitForSelectedEditor; +const waitForUnselectedEditor = async (page, selector) => { + await page.waitForSelector(`${selector}:not(.selectedEditor)`); +}; +exports.waitForUnselectedEditor = waitForUnselectedEditor; + const mockClipboard = async pages => { await Promise.all( pages.map(async ([_, page]) => { @@ -203,6 +226,7 @@ async function dragAndDropAnnotation(page, startX, startY, tX, tY) { await page.waitForTimeout(10); await page.mouse.move(startX + tX, startY + tY); await page.mouse.up(); + await page.waitForSelector("#viewer:not(.noUserSelect)"); } exports.dragAndDropAnnotation = dragAndDropAnnotation; @@ -217,3 +241,39 @@ async function waitForAnnotationEditorLayer(page) { }); } exports.waitForAnnotationEditorLayer = waitForAnnotationEditorLayer; + +async function scrollIntoView(page, selector) { + const promise = page.evaluate( + sel => + new Promise(resolve => { + const el = document.querySelector(sel); + const observer = new IntersectionObserver( + () => { + observer.disconnect(); + resolve(); + }, + { + root: document.querySelector("#viewerContainer"), + threshold: 0.1, + } + ); + observer.observe(el); + }), + selector + ); + await page.evaluate(sel => { + const element = document.querySelector(sel); + element.scrollIntoView({ behavior: "instant", block: "start" }); + }, selector); + await promise; + await page.waitForFunction( + sel => { + const element = document.querySelector(sel); + const { top, bottom } = element.getBoundingClientRect(); + return Math.abs(top) < 100 || Math.abs(bottom - window.innerHeight) < 100; + }, + {}, + selector + ); +} +exports.scrollIntoView = scrollIntoView;