From 317e534aa0c41f95ace6582a8018aeba315e185d Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 26 Jul 2026 15:36:03 -0700 Subject: [PATCH] Fix: ensure preview reload on live changes (#13321) --- .../document-detail/document-detail.spec.ts | 59 ++++++++++++++++++- .../pdf-viewer/pdf-viewer.component.spec.ts | 16 +++++ .../common/pdf-viewer/pdf-viewer.component.ts | 3 +- .../document-detail.component.html | 1 + .../document-detail.component.spec.ts | 1 + .../document-detail.component.ts | 4 ++ 6 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src-ui/e2e/document-detail/document-detail.spec.ts b/src-ui/e2e/document-detail/document-detail.spec.ts index ba10745ec..99080910a 100644 --- a/src-ui/e2e/document-detail/document-detail.spec.ts +++ b/src-ui/e2e/document-detail/document-detail.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from '@playwright/test' +import { expect, test, type WebSocketRoute } from '@playwright/test' import path from 'node:path' const REQUESTS_HAR = path.join(__dirname, 'requests/api-document-detail.har') @@ -95,3 +95,60 @@ test('should support quick filters', async ({ page }) => { .click() await expect(page).toHaveURL(/tags__id__all=4&sort=created&reverse=1&page=1/) }) + +test('should finish reloading the preview after a remote document update', async ({ + page, +}) => { + let resolveStatusSocket: (socket: WebSocketRoute) => void + const statusSocketReady = new Promise((resolve) => { + resolveStatusSocket = resolve + }) + await page.routeWebSocket(/\/ws\/status\/$/, (socket) => { + resolveStatusSocket(socket) + }) + await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' }) + let previewRequestCount = 0 + page.on('request', (request) => { + if (request.url().includes('/api/documents/175/preview/')) { + previewRequestCount++ + } + }) + await page.goto('/documents/175/details') + + await page.locator('pngx-document-detail').waitFor() + await expect(page.getByTitle('Storage path', { exact: true })).toHaveText( + /\w+/ + ) + const previewWasLoaded = await page.evaluate(() => { + const detail = document.querySelector('pngx-document-detail') + const component = (window as any).ng.getComponent(detail) + component.pdfPreviewLoaded({ numPages: 1 }) + return component.previewLoaded() + }) + expect(previewWasLoaded).toBe(true) + const previewRequestsBeforeReload = previewRequestCount + + const statusSocket = await statusSocketReady + const documentReloaded = page.waitForResponse( + (response) => + response.url().includes('/api/documents/175/?full_perms=true') && + response.request().method() === 'GET' + ) + statusSocket.send( + JSON.stringify({ + type: 'document_updated', + data: { + document_id: 175, + modified: '2026-07-26T20:00:00Z', + }, + }) + ) + await documentReloaded + + await expect( + page.getByText('Document reloaded with latest changes.').first() + ).toBeVisible() + await expect + .poll(() => previewRequestCount) + .toBeGreaterThan(previewRequestsBeforeReload + 1) +}) diff --git a/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.spec.ts b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.spec.ts index 3b6add68c..0e16ac2b2 100644 --- a/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.spec.ts +++ b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.spec.ts @@ -283,6 +283,22 @@ describe('PngxPdfViewerComponent', () => { expect(mockViewer.currentPageNumber).toBe(1) }) + it('reloads when the source revision changes', () => { + const resetSpy = jest.spyOn(component as any, 'resetViewerState') + const loadSpy = jest + .spyOn(component as any, 'loadDocument') + .mockImplementation(() => {}) + component.src = 'test.pdf' + component.sourceRevision = 1 + + component.ngOnChanges({ + sourceRevision: new SimpleChange(0, 1, false), + }) + + expect(resetSpy).toHaveBeenCalled() + expect(loadSpy).toHaveBeenCalled() + }) + it('applies viewer state after view init when already loaded', () => { const applySpy = jest.spyOn(component as any, 'applyViewerState') ;(component as any).hasLoaded = true diff --git a/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts index c86294b9d..4a0478316 100644 --- a/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts +++ b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts @@ -43,6 +43,7 @@ export class PngxPdfViewerComponent private readonly document = inject(DOCUMENT) @Input() src!: string + @Input() sourceRevision = 0 @Input() password?: string @Input() page?: number @Output() pageChange = new EventEmitter() @@ -93,7 +94,7 @@ export class PngxPdfViewerComponent } ngOnChanges(changes: SimpleChanges): void { - if (changes['src'] || changes['password']) { + if (changes['src'] || changes['sourceRevision'] || changes['password']) { this.resetViewerState() if (this.src) { this.loadDocument() diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html index 83e5b4bfb..6ee6b442a 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.html +++ b/src-ui/src/app/components/document-detail/document-detail.component.html @@ -472,6 +472,7 @@
{ expect(openDoc.__changedFields).toEqual([]) expect(setDirtySpy).toHaveBeenCalledWith(openDoc, false) expect(saveSpy).toHaveBeenCalled() + expect(component.previewRevision()).toBe(1) }) it('should ignore incoming update for a different document id', () => { diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index f2cc502c0..3820f9ed1 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -249,6 +249,7 @@ export class DocumentDetailComponent titleSubject: Subject = new Subject() readonly previewUrl = signal(undefined) readonly pdfSource = signal(undefined) + readonly previewRevision = signal(0) readonly pdfPassword = signal(undefined) readonly thumbUrl = signal(undefined) readonly previewText = signal(undefined) @@ -609,6 +610,9 @@ export class DocumentDetailComponent .subscribe() } this.updateComponent(useDoc) + if (forceRemote) { + this.previewRevision.update((revision) => revision + 1) + } this.titleSubject .pipe( debounceTime(1000),