diff --git a/test/integration/reorganize_pages_spec.mjs b/test/integration/reorganize_pages_spec.mjs index dfb2d764e..f85945295 100644 --- a/test/integration/reorganize_pages_spec.mjs +++ b/test/integration/reorganize_pages_spec.mjs @@ -2908,4 +2908,83 @@ describe("Reorganize Pages View", () => { ); }); }); + + describe("Current page indicator (bug 2026639)", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait( + "page_with_number.pdf", + "#viewsManagerToggleButton", + "page-fit", + null, + { enableSplitMerge: true } + ); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("should have only one current page after repeated cut/undo operations", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await waitForThumbnailVisible(page, 1); + await page.waitForSelector("#viewsManagerStatusActionButton", { + visible: true, + }); + + const countCurrentThumbnails = () => + page.evaluate( + () => + document.querySelectorAll( + '.thumbnailImageContainer[aria-current="page"]' + ).length + ); + + // Copy page 1 and paste it after page 3. + 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); + + handlePagesEdited = await waitForPagesEdited(page); + await waitAndClick(page, `${getThumbnailSelector(3)}+button`); + await awaitPromise(handlePagesEdited); + + // Repeat cut/undo three times and check the current indicator each + // time. + for (let i = 0; i < 3; i++) { + await waitAndClick( + page, + `.thumbnail:has(${getThumbnailSelector(1)}) input` + ); + handlePagesEdited = await waitForPagesEdited(page, "cut"); + await waitAndClick(page, "#viewsManagerStatusActionButton"); + await waitAndClick(page, "#viewsManagerStatusActionCut"); + await awaitPromise(handlePagesEdited); + + expect(await countCurrentThumbnails()) + .withContext(`In ${browserName}, after cut #${i + 1}`) + .toBe(1); + + await page.waitForSelector("#viewsManagerStatusUndo", { + visible: true, + }); + handlePagesEdited = await waitForPagesEdited(page, "cancelDelete"); + await waitAndClick(page, "#viewsManagerStatusUndoButton"); + await awaitPromise(handlePagesEdited); + + expect(await countCurrentThumbnails()) + .withContext(`In ${browserName}, after undo #${i + 1}`) + .toBe(1); + } + }) + ); + }); + }); }); diff --git a/web/pdf_thumbnail_viewer.js b/web/pdf_thumbnail_viewer.js index 0c5f03b89..cf62bcdf0 100644 --- a/web/pdf_thumbnail_viewer.js +++ b/web/pdf_thumbnail_viewer.js @@ -766,6 +766,13 @@ class PDFThumbnailViewer { this.#isCut = false; if (this.#savedThumbnails) { + // The thumbnail objects are shared between the post-operation list and + // the saved (pre-operation) list. The object marked current in the + // post-operation list may reappear at a different index in the restored + // list. + const currentThumb = this._thumbnails[this._currentPageNumber - 1]; + currentThumb?.toggleCurrent(false); + const fragment = document.createDocumentFragment(); for (let i = 1, ii = this.#savedThumbnails.length; i <= ii; i++) { const thumbnail = this.#savedThumbnails[i - 1]; @@ -776,6 +783,13 @@ class PDFThumbnailViewer { this.container.replaceChildren(fragment); this._thumbnails = this.#savedThumbnails; this.#savedThumbnails = null; + + // Re-establish the current-page indicator at the position the current + // thumbnail now occupies in the restored list. + const newIdx = currentThumb ? this._thumbnails.indexOf(currentThumb) : -1; + this._currentPageNumber = newIdx + 1; + currentThumb?.toggleCurrent(newIdx !== -1); + this.#pagesMapper.cancelDelete(); this.eventBus.dispatch("pagesedited", {