diff --git a/test/integration/reorganize_pages_spec.mjs b/test/integration/reorganize_pages_spec.mjs index 7db03df9d..28c0dd8c0 100644 --- a/test/integration/reorganize_pages_spec.mjs +++ b/test/integration/reorganize_pages_spec.mjs @@ -15,7 +15,6 @@ import { awaitPromise, - clearInput, closePages, createPromise, createPromiseWithArgs, @@ -404,14 +403,6 @@ describe("Reorganize Pages View", () => { ]); await movePages(page, [11, 2], 3); - await page.waitForFunction( - () => document.querySelectorAll("span.highlight").length === 0 - ); - - await clearInput(page, "#findInput", true); - await page.type("#findInput", "1"); - await page.keyboard.press("Enter"); - await page.waitForFunction( () => document.querySelectorAll("span.highlight").length === 10 ); @@ -433,13 +424,6 @@ describe("Reorganize Pages View", () => { ]); await movePages(page, [13], 0); - await page.waitForFunction( - () => document.querySelectorAll("span.highlight").length === 0 - ); - - await clearInput(page, "#findInput", true); - await page.type("#findInput", "1"); - await page.keyboard.press("Enter"); await page.waitForFunction( () => document.querySelectorAll("span.highlight").length === 10 @@ -463,6 +447,115 @@ describe("Reorganize Pages View", () => { }) ); }); + + it("should check if the search is working after copy and paste (bug 2023150)", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await waitForThumbnailVisible(page, 1); + await page.waitForSelector("#viewsManagerStatusActionButton", { + visible: true, + }); + + await waitAndClick(page, "#viewFindButton"); + await waitAndClick(page, ":has(> #findHighlightAll)"); + + await page.waitForSelector("#findInput", { visible: true }); + await page.type("#findInput", "1"); + await page.keyboard.press("Enter"); + + await page.waitForFunction( + () => document.querySelectorAll("span.highlight").length === 10 + ); + + // Select page 1 and copy it. + await waitAndClick( + page, + `.thumbnail:has(${getThumbnailSelector(1)}) input` + ); + let handlePagesEdited = await waitForPagesEdited(page, "copy"); + await waitAndClick(page, "#viewsManagerStatusActionButton"); + await waitAndClick(page, "#viewsManagerStatusActionCopy"); + await awaitPromise(handlePagesEdited); + + // Paste after page 3. + handlePagesEdited = await waitForPagesEdited(page); + await waitAndClick(page, `${getThumbnailSelector(3)}+button`); + await awaitPromise(handlePagesEdited); + + await page.waitForFunction( + () => document.querySelectorAll("span.highlight").length === 11 + ); + + const results = await getSearchResults(page); + expect(results) + .withContext(`In ${browserName}`) + .toEqual([ + // Page number, [matches]; copy of page 1 inserted at position 4 + [1, ["1"]], + [4, ["1"]], + [11, ["1"]], + [12, ["1", "1"]], + [13, ["1"]], + [14, ["1"]], + [15, ["1"]], + [16, ["1"]], + [17, ["1"]], + [18, ["1"]], + ]); + }) + ); + }); + + it("should check if the search is working after deleting pages (bug 2023150)", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await waitForThumbnailVisible(page, 1); + await page.waitForSelector("#viewsManagerStatusActionButton", { + visible: true, + }); + + await waitAndClick(page, "#viewFindButton"); + await waitAndClick(page, ":has(> #findHighlightAll)"); + + await page.waitForSelector("#findInput", { visible: true }); + await page.type("#findInput", "1"); + await page.keyboard.press("Enter"); + + await page.waitForFunction( + () => document.querySelectorAll("span.highlight").length === 10 + ); + + // Select page 1 and delete it. + await waitAndClick( + page, + `.thumbnail:has(${getThumbnailSelector(1)}) input` + ); + const handlePagesEdited = await waitForPagesEdited(page); + await waitAndClick(page, "#viewsManagerStatusActionButton"); + await waitAndClick(page, "#viewsManagerStatusActionDelete"); + await awaitPromise(handlePagesEdited); + + await page.waitForFunction( + () => document.querySelectorAll("span.highlight").length === 9 + ); + + const results = await getSearchResults(page); + expect(results) + .withContext(`In ${browserName}`) + .toEqual([ + // Page number, [matches]; page 1 removed, all positions shifted + [9, ["1"]], + [10, ["1", "1"]], + [11, ["1"]], + [12, ["1"]], + [13, ["1"]], + [14, ["1"]], + [15, ["1"]], + [16, ["1"]], + ]); + }) + ); + }); }); describe("Links and outlines", () => { diff --git a/web/pdf_find_controller.js b/web/pdf_find_controller.js index 7084fe245..c1b772db5 100644 --- a/web/pdf_find_controller.js +++ b/web/pdf_find_controller.js @@ -422,9 +422,9 @@ class PDFFindController { #visitedPagesCount = 0; - #copiedExtractTextPromises = null; + #copiedPageData = null; - #savedExtractTextPromises = null; + #savedPageData = null; /** * @param {PDFFindControllerOptions} options @@ -613,7 +613,7 @@ class PDFFindController { this._dirtyMatch = false; clearTimeout(this._findTimeout); this._findTimeout = null; - this.#copiedExtractTextPromises = null; + this.#copiedPageData = null; this._firstPageCapability = Promise.withResolvers(); } @@ -1138,51 +1138,82 @@ class PDFFindController { } if (type === "copy") { - this.#copiedExtractTextPromises = new Map(); + const promises = new Map(); + const contents = new Map(); + const diffs = new Map(); + const diacritics = new Map(); for (const pageNum of pageNumbers) { - this.#copiedExtractTextPromises.set( - pageNum, - this._extractTextPromises[pageNum - 1] - ); + promises.set(pageNum, this._extractTextPromises[pageNum - 1]); + contents.set(pageNum, this._pageContents[pageNum - 1]); + diffs.set(pageNum, this._pageDiffs[pageNum - 1]); + diacritics.set(pageNum, this._hasDiacritics[pageNum - 1]); } + this.#copiedPageData = { promises, contents, diffs, diacritics }; return; } if (type === "cancelCopy") { - this.#copiedExtractTextPromises = null; + this.#copiedPageData = null; return; } if (type === "delete") { - this.#savedExtractTextPromises = this._extractTextPromises; + this.#savedPageData = { + promises: this._extractTextPromises, + contents: this._pageContents, + diffs: this._pageDiffs, + diacritics: this._hasDiacritics, + }; } if (type === "cancelDelete") { - this._extractTextPromises = this.#savedExtractTextPromises; + this._extractTextPromises = this.#savedPageData.promises; + this._pageContents = this.#savedPageData.contents; + this._pageDiffs = this.#savedPageData.diffs; + this._hasDiacritics = this.#savedPageData.diacritics; return; } if (type === "cleanSavedData") { - this.#savedExtractTextPromises = null; + this.#savedPageData = null; return; } this.#onFindBarClose(); this._dirtyMatch = true; - const prevTextPromises = this._extractTextPromises; + const prevPromises = this._extractTextPromises; + const prevContents = this._pageContents; + const prevDiffs = this._pageDiffs; + const prevDiacritics = this._hasDiacritics; const extractTextPromises = (this._extractTextPromises = []); - for (let i = 1, ii = pagesMapper.length; i <= ii; i++) { + const pageContents = (this._pageContents = []); + const pageDiffs = (this._pageDiffs = []); + const hasDiacritics = (this._hasDiacritics = []); + for (let i = 1, ii = pagesMapper.pagesNumber; i <= ii; i++) { const prevPageNumber = pagesMapper.getPrevPageNumber(i); if (prevPageNumber < 0) { + const src = -prevPageNumber; extractTextPromises.push( - this.#copiedExtractTextPromises?.get(-prevPageNumber) || - Promise.resolve() + this.#copiedPageData?.promises.get(src) || Promise.resolve() ); + pageContents.push(this.#copiedPageData?.contents.get(src) ?? ""); + pageDiffs.push(this.#copiedPageData?.diffs.get(src) ?? null); + hasDiacritics.push(this.#copiedPageData?.diacritics.get(src) ?? false); continue; } extractTextPromises.push( - prevTextPromises[prevPageNumber - 1] || Promise.resolve() + prevPromises[prevPageNumber - 1] || Promise.resolve() ); + pageContents.push(prevContents[prevPageNumber - 1] ?? ""); + pageDiffs.push(prevDiffs[prevPageNumber - 1] ?? null); + hasDiacritics.push(prevDiacritics[prevPageNumber - 1] ?? false); + } + if (this.#state) { + this._eventBus.dispatch("find", { + source: this, + type: "", + ...this.#state, + }); } }