freetext_editor_spec.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. /* Copyright 2022 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. const {
  16. closePages,
  17. getEditorSelector,
  18. getSelectedEditors,
  19. loadAndWait,
  20. waitForEvent,
  21. waitForSelectedEditor,
  22. waitForStorageEntries,
  23. } = require("./test_utils.js");
  24. const copyPaste = async page => {
  25. let promise = waitForEvent(page, "copy");
  26. await page.keyboard.down("Control");
  27. await page.keyboard.press("c");
  28. await page.keyboard.up("Control");
  29. await promise;
  30. await page.waitForTimeout(10);
  31. promise = waitForEvent(page, "paste");
  32. await page.keyboard.down("Control");
  33. await page.keyboard.press("v");
  34. await page.keyboard.up("Control");
  35. await promise;
  36. };
  37. describe("Editor", () => {
  38. describe("FreeText", () => {
  39. let pages;
  40. beforeAll(async () => {
  41. pages = await loadAndWait("aboutstacks.pdf", ".annotationEditorLayer");
  42. });
  43. afterAll(async () => {
  44. await closePages(pages);
  45. });
  46. it("must write a string in a FreeText editor", async () => {
  47. await Promise.all(
  48. pages.map(async ([browserName, page]) => {
  49. await page.click("#editorFreeText");
  50. const rect = await page.$eval(".annotationEditorLayer", el => {
  51. // With Chrome something is wrong when serializing a DomRect,
  52. // hence we extract the values and just return them.
  53. const { x, y } = el.getBoundingClientRect();
  54. return { x, y };
  55. });
  56. const data = "Hello PDF.js World !!";
  57. await page.mouse.click(rect.x + 100, rect.y + 100);
  58. await page.type(`${getEditorSelector(0)} .internal`, data);
  59. const editorRect = await page.$eval(getEditorSelector(0), el => {
  60. const { x, y, width, height } = el.getBoundingClientRect();
  61. return {
  62. x,
  63. y,
  64. width,
  65. height,
  66. };
  67. });
  68. // Commit.
  69. await page.mouse.click(
  70. editorRect.x,
  71. editorRect.y + 2 * editorRect.height
  72. );
  73. await waitForSelectedEditor(page, getEditorSelector(0));
  74. await waitForStorageEntries(page, 1);
  75. const content = await page.$eval(getEditorSelector(0), el =>
  76. el.innerText.trimEnd()
  77. );
  78. expect(content).withContext(`In ${browserName}`).toEqual(data);
  79. })
  80. );
  81. });
  82. it("must copy/paste", async () => {
  83. // Run sequentially to avoid clipboard issues.
  84. for (const [browserName, page] of pages) {
  85. const editorRect = await page.$eval(getEditorSelector(0), el => {
  86. const { x, y, width, height } = el.getBoundingClientRect();
  87. return { x, y, width, height };
  88. });
  89. // Select the editor created previously.
  90. await page.mouse.click(
  91. editorRect.x + editorRect.width / 2,
  92. editorRect.y + editorRect.height / 2
  93. );
  94. await waitForSelectedEditor(page, getEditorSelector(0));
  95. await copyPaste(page);
  96. await waitForStorageEntries(page, 2);
  97. const content = await page.$eval(getEditorSelector(0), el =>
  98. el.innerText.trimEnd()
  99. );
  100. let pastedContent = await page.$eval(getEditorSelector(1), el =>
  101. el.innerText.trimEnd()
  102. );
  103. expect(pastedContent).withContext(`In ${browserName}`).toEqual(content);
  104. await copyPaste(page);
  105. await waitForStorageEntries(page, 3);
  106. pastedContent = await page.$eval(getEditorSelector(2), el =>
  107. el.innerText.trimEnd()
  108. );
  109. expect(pastedContent).withContext(`In ${browserName}`).toEqual(content);
  110. }
  111. });
  112. it("must clear all", async () => {
  113. await Promise.all(
  114. pages.map(async ([browserName, page]) => {
  115. await page.keyboard.down("Control");
  116. await page.keyboard.press("a");
  117. await page.keyboard.up("Control");
  118. await page.keyboard.down("Control");
  119. await page.keyboard.press("Backspace");
  120. await page.keyboard.up("Control");
  121. for (const n of [0, 1, 2]) {
  122. const hasEditor = await page.evaluate(sel => {
  123. return !!document.querySelector(sel);
  124. }, getEditorSelector(n));
  125. expect(hasEditor).withContext(`In ${browserName}`).toEqual(false);
  126. }
  127. await waitForStorageEntries(page, 0);
  128. })
  129. );
  130. });
  131. it("must check that a paste has been undone", async () => {
  132. // Run sequentially to avoid clipboard issues.
  133. for (const [browserName, page] of pages) {
  134. const rect = await page.$eval(".annotationEditorLayer", el => {
  135. const { x, y } = el.getBoundingClientRect();
  136. return { x, y };
  137. });
  138. const data = "Hello PDF.js World !!";
  139. await page.mouse.click(rect.x + 100, rect.y + 100);
  140. await page.type(`${getEditorSelector(3)} .internal`, data);
  141. const editorRect = await page.$eval(getEditorSelector(3), el => {
  142. const { x, y, width, height } = el.getBoundingClientRect();
  143. return { x, y, width, height };
  144. });
  145. // Commit.
  146. await page.mouse.click(
  147. editorRect.x,
  148. editorRect.y + 2 * editorRect.height
  149. );
  150. // And select it again.
  151. await page.mouse.click(
  152. editorRect.x + editorRect.width / 2,
  153. editorRect.y + editorRect.height / 2
  154. );
  155. await waitForSelectedEditor(page, getEditorSelector(3));
  156. await copyPaste(page);
  157. let hasEditor = await page.evaluate(sel => {
  158. return !!document.querySelector(sel);
  159. }, getEditorSelector(4));
  160. expect(hasEditor).withContext(`In ${browserName}`).toEqual(true);
  161. await page.keyboard.down("Control");
  162. await page.keyboard.press("z");
  163. await page.keyboard.up("Control");
  164. await page.waitForTimeout(10);
  165. hasEditor = await page.evaluate(sel => {
  166. return !!document.querySelector(sel);
  167. }, getEditorSelector(4));
  168. expect(hasEditor).withContext(`In ${browserName}`).toEqual(false);
  169. for (let i = 0; i < 2; i++) {
  170. const promise = waitForEvent(page, "paste");
  171. await page.keyboard.down("Control");
  172. await page.keyboard.press("v");
  173. await page.keyboard.up("Control");
  174. await promise;
  175. await page.waitForTimeout(10);
  176. }
  177. let length = await page.evaluate(sel => {
  178. return document.querySelectorAll(sel).length;
  179. }, `${getEditorSelector(5)}, ${getEditorSelector(6)}`);
  180. expect(length).withContext(`In ${browserName}`).toEqual(2);
  181. for (let i = 0; i < 2; i++) {
  182. await page.keyboard.down("Control");
  183. await page.keyboard.press("z");
  184. await page.keyboard.up("Control");
  185. await page.waitForTimeout(10);
  186. }
  187. length = await page.evaluate(sel => {
  188. return document.querySelectorAll(sel).length;
  189. }, `${getEditorSelector(5)}, ${getEditorSelector(6)}`);
  190. expect(length).withContext(`In ${browserName}`).toEqual(0);
  191. }
  192. });
  193. it("must check that aria-owns is correct", async () => {
  194. await Promise.all(
  195. pages.map(async ([browserName, page]) => {
  196. const [stacksRect, oldAriaOwns] = await page.$eval(
  197. ".textLayer",
  198. el => {
  199. for (const span of el.querySelectorAll(
  200. `span[role="presentation"]`
  201. )) {
  202. if (span.innerText.includes("Stacks are simple to create")) {
  203. span.setAttribute("pdfjs", true);
  204. const { x, y, width, height } = span.getBoundingClientRect();
  205. return [
  206. { x, y, width, height },
  207. span.getAttribute("aria-owns"),
  208. ];
  209. }
  210. }
  211. return null;
  212. }
  213. );
  214. expect(oldAriaOwns).withContext(`In ${browserName}`).toEqual(null);
  215. const data = "Hello PDF.js World !!";
  216. await page.mouse.click(
  217. stacksRect.x + stacksRect.width + 1,
  218. stacksRect.y + stacksRect.height / 2
  219. );
  220. await page.type(`${getEditorSelector(7)} .internal`, data);
  221. // Commit.
  222. await page.keyboard.press("Escape");
  223. const ariaOwns = await page.$eval(".textLayer", el => {
  224. const span = el.querySelector(`span[pdfjs="true"]`);
  225. return span?.getAttribute("aria-owns") || null;
  226. });
  227. expect(ariaOwns.endsWith("_7-editor"))
  228. .withContext(`In ${browserName}`)
  229. .toEqual(true);
  230. })
  231. );
  232. });
  233. it("must check that right click doesn't select", async () => {
  234. await Promise.all(
  235. pages.map(async ([browserName, page]) => {
  236. const rect = await page.$eval(".annotationEditorLayer", el => {
  237. const { x, y } = el.getBoundingClientRect();
  238. return { x, y };
  239. });
  240. await page.keyboard.down("Control");
  241. await page.keyboard.press("a");
  242. await page.keyboard.up("Control");
  243. await page.keyboard.down("Control");
  244. await page.keyboard.press("Backspace");
  245. await page.keyboard.up("Control");
  246. const data = "Hello PDF.js World !!";
  247. await page.mouse.click(rect.x + 100, rect.y + 100);
  248. await page.type(`${getEditorSelector(8)} .internal`, data);
  249. const editorRect = await page.$eval(getEditorSelector(8), el => {
  250. const { x, y, width, height } = el.getBoundingClientRect();
  251. return { x, y, width, height };
  252. });
  253. // Commit.
  254. await page.keyboard.press("Escape");
  255. expect(await getSelectedEditors(page))
  256. .withContext(`In ${browserName}`)
  257. .toEqual([8]);
  258. await page.keyboard.press("Escape");
  259. expect(await getSelectedEditors(page))
  260. .withContext(`In ${browserName}`)
  261. .toEqual([]);
  262. await page.mouse.click(
  263. editorRect.x + editorRect.width / 2,
  264. editorRect.y + editorRect.height / 2
  265. );
  266. await waitForSelectedEditor(page, getEditorSelector(8));
  267. expect(await getSelectedEditors(page))
  268. .withContext(`In ${browserName}`)
  269. .toEqual([8]);
  270. // Escape.
  271. await page.keyboard.press("Escape");
  272. expect(await getSelectedEditors(page))
  273. .withContext(`In ${browserName}`)
  274. .toEqual([]);
  275. // TODO: uncomment that stuff once we've a way to dismiss
  276. // the context menu.
  277. /* await page.mouse.click(
  278. editorRect.x + editorRect.width / 2,
  279. editorRect.y + editorRect.height / 2,
  280. { button: "right" }
  281. ); */
  282. })
  283. );
  284. });
  285. });
  286. describe("FreeText (multiselection)", () => {
  287. let pages;
  288. beforeAll(async () => {
  289. pages = await loadAndWait("aboutstacks.pdf", ".annotationEditorLayer");
  290. });
  291. afterAll(async () => {
  292. await closePages(pages);
  293. });
  294. it("must select/unselect several editors and check copy, paste and delete operations", async () => {
  295. // Run sequentially to avoid clipboard issues.
  296. for (const [browserName, page] of pages) {
  297. await page.click("#editorFreeText");
  298. const rect = await page.$eval(".annotationEditorLayer", el => {
  299. // With Chrome something is wrong when serializing a DomRect,
  300. // hence we extract the values and just return them.
  301. const { x, y } = el.getBoundingClientRect();
  302. return { x, y };
  303. });
  304. const editorCenters = [];
  305. for (let i = 0; i < 4; i++) {
  306. const data = `FreeText ${i}`;
  307. await page.mouse.click(
  308. rect.x + (i + 1) * 100,
  309. rect.y + (i + 1) * 100
  310. );
  311. await page.type(`${getEditorSelector(i)} .internal`, data);
  312. const editorRect = await page.$eval(getEditorSelector(i), el => {
  313. const { x, y, width, height } = el.getBoundingClientRect();
  314. return {
  315. x,
  316. y,
  317. width,
  318. height,
  319. };
  320. });
  321. editorCenters.push({
  322. x: editorRect.x + editorRect.width / 2,
  323. y: editorRect.y + editorRect.height / 2,
  324. });
  325. // Commit.
  326. await page.mouse.click(
  327. editorRect.x,
  328. editorRect.y + 2 * editorRect.height
  329. );
  330. }
  331. await page.keyboard.down("Control");
  332. await page.keyboard.press("a");
  333. await page.keyboard.up("Control");
  334. expect(await getSelectedEditors(page))
  335. .withContext(`In ${browserName}`)
  336. .toEqual([0, 1, 2, 3]);
  337. await page.keyboard.down("Control");
  338. await page.mouse.click(editorCenters[1].x, editorCenters[1].y);
  339. expect(await getSelectedEditors(page))
  340. .withContext(`In ${browserName}`)
  341. .toEqual([0, 2, 3]);
  342. await page.mouse.click(editorCenters[2].x, editorCenters[2].y);
  343. expect(await getSelectedEditors(page))
  344. .withContext(`In ${browserName}`)
  345. .toEqual([0, 3]);
  346. await page.mouse.click(editorCenters[1].x, editorCenters[1].y);
  347. await page.keyboard.up("Control");
  348. expect(await getSelectedEditors(page))
  349. .withContext(`In ${browserName}`)
  350. .toEqual([0, 1, 3]);
  351. await copyPaste(page);
  352. // 0,1,3 are unselected and new pasted editors are selected.
  353. expect(await getSelectedEditors(page))
  354. .withContext(`In ${browserName}`)
  355. .toEqual([4, 5, 6]);
  356. // No ctrl here, hence all are unselected and 2 is selected.
  357. await page.mouse.click(editorCenters[2].x, editorCenters[2].y);
  358. expect(await getSelectedEditors(page))
  359. .withContext(`In ${browserName}`)
  360. .toEqual([2]);
  361. await page.mouse.click(editorCenters[1].x, editorCenters[1].y);
  362. expect(await getSelectedEditors(page))
  363. .withContext(`In ${browserName}`)
  364. .toEqual([1]);
  365. await page.keyboard.down("Control");
  366. await page.mouse.click(editorCenters[3].x, editorCenters[3].y);
  367. expect(await getSelectedEditors(page))
  368. .withContext(`In ${browserName}`)
  369. .toEqual([1, 3]);
  370. await page.keyboard.up("Control");
  371. // Delete 1 and 3.
  372. await page.keyboard.press("Backspace");
  373. await page.keyboard.down("Control");
  374. await page.keyboard.press("a");
  375. await page.keyboard.up("Control");
  376. expect(await getSelectedEditors(page))
  377. .withContext(`In ${browserName}`)
  378. .toEqual([0, 2, 4, 5, 6]);
  379. // Create an empty editor.
  380. await page.mouse.click(rect.x + 700, rect.y + 100);
  381. expect(await getSelectedEditors(page))
  382. .withContext(`In ${browserName}`)
  383. .toEqual([7]);
  384. // Set the focus to 2 and check that only 2 is selected.
  385. await page.mouse.click(editorCenters[2].x, editorCenters[2].y);
  386. expect(await getSelectedEditors(page))
  387. .withContext(`In ${browserName}`)
  388. .toEqual([2]);
  389. // Create an empty editor.
  390. await page.mouse.click(rect.x + 700, rect.y + 100);
  391. expect(await getSelectedEditors(page))
  392. .withContext(`In ${browserName}`)
  393. .toEqual([8]);
  394. // Dismiss it.
  395. await page.keyboard.press("Escape");
  396. // Select all.
  397. await page.keyboard.down("Control");
  398. await page.keyboard.press("a");
  399. await page.keyboard.up("Control");
  400. // Check that all the editors are correctly selected (and the focus
  401. // didn't move to the body when the empty editor was removed).
  402. expect(await getSelectedEditors(page))
  403. .withContext(`In ${browserName}`)
  404. .toEqual([0, 2, 4, 5, 6]);
  405. }
  406. });
  407. });
  408. describe("FreeText (bugs)", () => {
  409. let pages;
  410. beforeAll(async () => {
  411. pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
  412. });
  413. afterAll(async () => {
  414. await closePages(pages);
  415. });
  416. it("must serialize invisible annotations", async () => {
  417. await Promise.all(
  418. pages.map(async ([browserName, page]) => {
  419. await page.click("#editorFreeText");
  420. let currentId = 0;
  421. const expected = [];
  422. const oneToFourteen = [...new Array(14).keys()].map(x => x + 1);
  423. for (const pageNumber of oneToFourteen) {
  424. const pageSelector = `.page[data-page-number = "${pageNumber}"]`;
  425. await page.evaluate(selector => {
  426. const element = window.document.querySelector(selector);
  427. element.scrollIntoView();
  428. }, pageSelector);
  429. const annotationLayerSelector = `${pageSelector} > .annotationEditorLayer`;
  430. await page.waitForSelector(annotationLayerSelector, {
  431. visible: true,
  432. timeout: 0,
  433. });
  434. await page.waitForTimeout(50);
  435. if (![1, 14].includes(pageNumber)) {
  436. continue;
  437. }
  438. const rect = await page.$eval(annotationLayerSelector, el => {
  439. // With Chrome something is wrong when serializing a DomRect,
  440. // hence we extract the values and just return them.
  441. const { x, y } = el.getBoundingClientRect();
  442. return { x, y };
  443. });
  444. const data = `Hello PDF.js World !! on page ${pageNumber}`;
  445. expected.push(data);
  446. await page.mouse.click(rect.x + 100, rect.y + 100);
  447. await page.type(`${getEditorSelector(currentId)} .internal`, data);
  448. // Commit.
  449. await page.keyboard.press("Escape");
  450. await page.waitForTimeout(10);
  451. await waitForSelectedEditor(page, getEditorSelector(currentId));
  452. await waitForStorageEntries(page, currentId + 1);
  453. const content = await page.$eval(getEditorSelector(currentId), el =>
  454. el.innerText.trimEnd()
  455. );
  456. expect(content).withContext(`In ${browserName}`).toEqual(data);
  457. currentId += 1;
  458. await page.waitForTimeout(10);
  459. }
  460. const serialize = proprName =>
  461. page.evaluate(
  462. name =>
  463. [
  464. ...window.PDFViewerApplication.pdfDocument.annotationStorage.serializable.values(),
  465. ].map(x => x[name]),
  466. proprName
  467. );
  468. expect(await serialize("value"))
  469. .withContext(`In ${browserName}`)
  470. .toEqual(expected);
  471. expect(await serialize("fontSize"))
  472. .withContext(`In ${browserName}`)
  473. .toEqual([10, 10]);
  474. expect(await serialize("color"))
  475. .withContext(`In ${browserName}`)
  476. .toEqual([
  477. [0, 0, 0],
  478. [0, 0, 0],
  479. ]);
  480. // Increase the font size for all the annotations.
  481. // Select all.
  482. await page.keyboard.down("Control");
  483. await page.keyboard.press("a");
  484. await page.keyboard.up("Control");
  485. await page.waitForTimeout(10);
  486. page.evaluate(() => {
  487. window.PDFViewerApplication.eventBus.dispatch(
  488. "switchannotationeditorparams",
  489. {
  490. source: null,
  491. type: /* AnnotationEditorParamsType.FREETEXT_SIZE */ 1,
  492. value: 13,
  493. }
  494. );
  495. });
  496. await page.waitForTimeout(10);
  497. expect(await serialize("fontSize"))
  498. .withContext(`In ${browserName}`)
  499. .toEqual([13, 13]);
  500. // Change the colors for all the annotations.
  501. page.evaluate(() => {
  502. window.PDFViewerApplication.eventBus.dispatch(
  503. "switchannotationeditorparams",
  504. {
  505. source: null,
  506. type: /* AnnotationEditorParamsType.FREETEXT_COLOR */ 2,
  507. value: "#FF0000",
  508. }
  509. );
  510. });
  511. await page.waitForTimeout(10);
  512. expect(await serialize("color"))
  513. .withContext(`In ${browserName}`)
  514. .toEqual([
  515. [255, 0, 0],
  516. [255, 0, 0],
  517. ]);
  518. })
  519. );
  520. });
  521. });
  522. describe("issue 15789", () => {
  523. let pages;
  524. beforeAll(async () => {
  525. pages = await loadAndWait("issue15789.pdf", ".annotationEditorLayer");
  526. pages = await Promise.all(
  527. pages.map(async ([browserName, page]) => {
  528. await page.select("#scaleSelect", "1");
  529. return [browserName, page];
  530. })
  531. );
  532. });
  533. afterAll(async () => {
  534. await closePages(pages);
  535. });
  536. it("must take the media box into account", async () => {
  537. await Promise.all(
  538. pages.map(async ([browserName, page]) => {
  539. await page.click("#editorFreeText");
  540. let currentId = 0;
  541. for (let step = 0; step < 3; step++) {
  542. const rect = await page.$eval(".annotationEditorLayer", el => {
  543. // With Chrome something is wrong when serializing a DomRect,
  544. // hence we extract the values and just return them.
  545. const { x, y, width, height } = el.getBoundingClientRect();
  546. return { x, y, width, height };
  547. });
  548. const data = `Hello ${step}`;
  549. const x = rect.x + 0.1 * rect.width;
  550. const y = rect.y + 0.1 * rect.height;
  551. await page.mouse.click(x, y);
  552. await page.type(`${getEditorSelector(currentId)} .internal`, data);
  553. // Commit.
  554. await page.keyboard.press("Escape");
  555. await page.waitForTimeout(10);
  556. await page.evaluate(() => {
  557. document.getElementById("pageRotateCw").click();
  558. });
  559. currentId += 1;
  560. await page.waitForTimeout(10);
  561. }
  562. const serialize = proprName =>
  563. page.evaluate(
  564. name =>
  565. [
  566. ...window.PDFViewerApplication.pdfDocument.annotationStorage.serializable.values(),
  567. ].map(x => x[name]),
  568. proprName
  569. );
  570. const rects = (await serialize("rect")).map(rect =>
  571. rect.slice(0, 2).map(x => Math.floor(x))
  572. );
  573. const expected = [
  574. [-28, 695],
  575. [-38, -10],
  576. [501, -20],
  577. ];
  578. // Dimensions aren't exactly the same from a platform to an other
  579. // so we're a bit tolerant here with the numbers.
  580. // Anyway the goal is to check that the bottom left corner of the
  581. // media box is taken into account.
  582. // The pdf has a media box equals to [-99 -99 612.0 792.0].
  583. const diffs = rects.map(
  584. (rect, i) =>
  585. Math.abs(rect[0] - expected[i][0]) < 10 &&
  586. Math.abs(rect[1] - expected[i][1]) < 10
  587. );
  588. expect(diffs)
  589. .withContext(`In ${browserName}`)
  590. .toEqual([true, true, true]);
  591. })
  592. );
  593. });
  594. });
  595. });