diff --git a/test/integration/text_layer_spec.mjs b/test/integration/text_layer_spec.mjs index 8b323c0caea635..7482a8986d7bdc 100644 --- a/test/integration/text_layer_spec.mjs +++ b/test/integration/text_layer_spec.mjs @@ -1,12 +1,27 @@ +/* Copyright 2024 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { closePages, getSpanRectFromText, loadAndWait } from "./test_utils.mjs"; import { startBrowser } from "../test.mjs"; describe("Text layer", () => { describe("Text selection", () => { // page.mouse.move(x, y, { steps: ... }) doesn't work in Firefox, because - // puppeteer will send through the CDP protocol fractional intermediate - // positions and Firefox doesn't support them. Use this function to - // round each intermediate position to an integer. + // puppeteer will send fractional intermediate positions and Firefox doesn't + // support them. Use this function to round each intermediate position to an + // integer. async function moveInSteps(page, from, to, steps) { const deltaX = to.x - from.x; const deltaY = to.y - from.y; @@ -17,26 +32,63 @@ describe("Text layer", () => { } } - function getSelectedText(page) { - return page.evaluate(() => window.getSelection().toString()); - } - function middlePosition(rect) { return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }; } function middleLeftPosition(rect) { - return { x: rect.x, y: rect.y + rect.height / 2 }; - } - - function middleBottomPosition(rect) { - return { x: rect.x + rect.width / 2, y: rect.y + rect.height }; + return { x: rect.x + 1, y: rect.y + rect.height / 2 }; } function belowEndPosition(rect) { return { x: rect.x + rect.width, y: rect.y + rect.height * 1.5 }; } + beforeAll(() => { + jasmine.addAsyncMatchers({ + // Check that a page has a selection containing the given text, with + // some tolerance for extra characters before/after. + toHaveRoughlySelected({ pp }) { + return { + async compare(page, expected) { + const TOLERANCE = 10; + + const actual = await page.evaluate(() => + window.getSelection().toString() + ); + + let start, end; + if (expected instanceof RegExp) { + const match = expected.exec(actual); + start = -1; + if (match) { + start = match.index; + end = start + match[0].length; + } + } else { + start = actual.indexOf(expected); + if (start !== -1) end = start + expected.length; + } + + const pass = + start !== -1 && + start < TOLERANCE && + end > actual.length - TOLERANCE; + + return { + pass, + message: `Expected ${pp( + actual.length > 200 + ? actual.slice(0, 100) + "[...]" + actual.slice(-100) + : actual + )} to ${pass ? "not " : ""}roughly match ${pp(expected)}.`, + }; + }, + }; + }, + }); + }); + describe("using mouse", () => { let pages; @@ -71,14 +123,11 @@ describe("Text layer", () => { await moveInSteps(page, positionStart, positionEnd, 20); await page.mouse.up(); - expect(await getSelectedText(page)) + await expectAsync(page) .withContext(`In ${browserName}`) - .toBe( - browserName === "chrome" - ? "code sequences, records\n" + - "them, and compiles them to fast native code. We call such a se-" - : "ecode sequences, records\n" + - "them, and compiles them to fast native code. We call such " + .toHaveRoughlySelected( + "code sequences, records\n" + + "them, and compiles them to fast native code. We call suc" ); }) ); @@ -116,7 +165,7 @@ describe("Text layer", () => { page, 2, "Hence, recording and compiling a trace" - ).then(middleBottomPosition), + ).then(middlePosition), getSpanRectFromText( page, 2, @@ -130,23 +179,19 @@ describe("Text layer", () => { await moveInSteps(page, positionStartPage1, positionEndPage1, 20); await moveInSteps(page, positionEndPage1, positionStartPage2, 20); - expect(await getSelectedText(page)) + await expectAsync(page) .withContext(`In ${browserName}, first selection`) - .toMatch( - browserName === "chrome" - ? /^e path through the program .*Hence, recording an$/s - : /^ path through the program .*Hence, recording an$/s + .toHaveRoughlySelected( + /path through the program .*Hence, recording a/s ); await moveInSteps(page, positionStartPage2, positionEndPage2, 20); await page.mouse.up(); - expect(await getSelectedText(page)) + await expectAsync(page) .withContext(`In ${browserName}, second selection`) - .toMatch( - browserName === "chrome" - ? /^e path through.*Hence, recording and .* tracing, and give up$/s - : /^ path through.*Hence, recording and .* tracing, and give $/s + .toHaveRoughlySelected( + /path through.*Hence, recording and .* tracing, and give/s ); }) ); @@ -158,6 +203,8 @@ describe("Text layer", () => { let page; beforeAll(async () => { + // Chrome does not support simluating caret-based selection, so this + // test only runs in Firefox. browser = await startBrowser({ browserName: "firefox", startUrl: "", @@ -180,7 +227,7 @@ describe("Text layer", () => { await browser.close(); }); - it("doesn't jump when moving selection", async () => { + fit("doesn't jump when moving selection", async () => { const [initialStart, initialEnd, finalEnd] = await Promise.all([ getSpanRectFromText( page, @@ -204,9 +251,9 @@ describe("Text layer", () => { await moveInSteps(page, initialStart, initialEnd, 20); await page.mouse.up(); - expect(await getSelectedText(page)) + await expectAsync(page) .withContext(`first selection`) - .toBe("(frequently executed) byt"); + .toHaveRoughlySelected("(frequently executed) byt"); const initialCaretPos = { x: initialEnd.x, @@ -226,17 +273,17 @@ describe("Text layer", () => { await moveInSteps(page, initialCaretPos, intermediateCaretPos, 20); await page.mouse.up(); - expect(await getSelectedText(page)) + await expectAsync(page) .withContext(`second selection`) - .toMatch(/^\(frequently .* We call such a se-$/s); + .toHaveRoughlySelected(/\(frequently .* We call such a se/s); await page.mouse.down(); await moveInSteps(page, intermediateCaretPos, finalCaretPos, 20); await page.mouse.up(); - expect(await getSelectedText(page)) + await expectAsync(page) .withContext(`third selection`) - .toMatch(/^\(frequently .* We call such a se-$/s); + .toHaveRoughlySelected(/\(frequently .* We call such a se/s); }); }); });