From 5226898184c4e46bf5c5101ff85d090a3338a491 Mon Sep 17 00:00:00 2001 From: Min Idzelis Date: Tue, 25 Nov 2025 15:06:29 -0500 Subject: [PATCH] fix: update timeline-manager after archive actions (#24010) * fix: update timeline-manager after archive actions * Add locators to thumb icons --- .../timeline/timeline.parallel-e2e-spec.ts | 97 ++++++++++++++++++- e2e/src/web/specs/timeline/utils.ts | 28 +++--- .../assets/thumbnail/thumbnail.svelte | 18 ++-- .../[[assetId=id]]/+page.svelte | 10 +- .../[[assetId=id]]/+page.svelte | 6 +- .../[[assetId=id]]/+page.svelte | 6 +- .../(user)/photos/[[assetId=id]]/+page.svelte | 9 +- 7 files changed, 145 insertions(+), 29 deletions(-) diff --git a/e2e/src/web/specs/timeline/timeline.parallel-e2e-spec.ts b/e2e/src/web/specs/timeline/timeline.parallel-e2e-spec.ts index 49a8f38312..6314688abb 100644 --- a/e2e/src/web/specs/timeline/timeline.parallel-e2e-spec.ts +++ b/e2e/src/web/specs/timeline/timeline.parallel-e2e-spec.ts @@ -611,6 +611,53 @@ test.describe('Timeline', () => { await page.getByText('Photos', { exact: true }).click(); await thumbnailUtils.expectInViewport(page, assetToArchive.id); }); + test('open /archive, favorite photo, unfavorite', async ({ page }) => { + const assetToFavorite = assets[0]; + changes.assetArchivals.push(assetToFavorite.id); + await pageUtils.openArchivePage(page); + const favorite = pageRoutePromise(page, '**/api/assets', async (route, request) => { + const requestJson = request.postDataJSON(); + if (requestJson.isFavorite === undefined) { + return await route.continue(); + } + const isFavorite = requestJson.isFavorite; + if (isFavorite) { + changes.assetFavorites.push(...requestJson.ids); + } + await route.fulfill({ + status: 204, + }); + }); + await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover(); + await thumbnailUtils.selectButton(page, assetToFavorite.id).click(); + await page.getByLabel('Favorite').click(); + await expect(favorite).resolves.toEqual({ + isFavorite: true, + ids: [assetToFavorite.id], + }); + await expect(thumbnailUtils.withAssetId(page, assetToFavorite.id)).toHaveCount(1); + await thumbnailUtils.expectInViewport(page, assetToFavorite.id); + await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id); + await thumbnailUtils.withAssetId(page, assetToFavorite.id).hover(); + await thumbnailUtils.selectButton(page, assetToFavorite.id).click(); + const unFavoriteRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => { + const requestJson = request.postDataJSON(); + if (requestJson.isFavorite === undefined) { + return await route.continue(); + } + changes.assetFavorites = changes.assetFavorites.filter((id) => !requestJson.ids.includes(id)); + await route.fulfill({ + status: 204, + }); + }); + await page.getByLabel('Remove from favorites').click(); + await expect(unFavoriteRequest).resolves.toEqual({ + isFavorite: false, + ids: [assetToFavorite.id], + }); + await expect(thumbnailUtils.withAssetId(page, assetToFavorite.id)).toHaveCount(1); + await thumbnailUtils.expectThumbnailIsNotFavorite(page, assetToFavorite.id); + }); test('open album, archive photo, open album, unarchive', async ({ page }) => { const album = timelineRestData.album; await pageUtils.openAlbumPage(page, album.id); @@ -633,8 +680,7 @@ test.describe('Timeline', () => { visibility: 'archive', ids: [assetToArchive.id], }); - console.log('Skipping assertion - TODO - fix that archiving in album doesnt add icon'); - // await thumbnail.expectThumbnailIsArchive(page, assetToArchive.id); + await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id); await page.locator('#asset-selection-app-bar').getByLabel('Close').click(); await page.getByRole('link').getByText('Archive').click(); await timelineUtils.waitForTimelineLoad(page); @@ -656,8 +702,7 @@ test.describe('Timeline', () => { visibility: 'timeline', ids: [assetToArchive.id], }); - console.log('Skipping assertion - TODO - fix bug with not removing asset from timeline-manager after unarchive'); - // await expect(thumbnail.withAssetId(page, assetToArchive.id)).toHaveCount(0); + await expect(thumbnailUtils.withAssetId(page, assetToArchive.id)).toHaveCount(0); await pageUtils.openAlbumPage(page, album.id); await thumbnailUtils.expectInViewport(page, assetToArchive.id); }); @@ -712,6 +757,50 @@ test.describe('Timeline', () => { await page.getByText('Photos', { exact: true }).click(); await thumbnailUtils.expectInViewport(page, assetToFavorite.id); }); + test('open /favorites, archive photo, unarchive photo', async ({ page }) => { + await pageUtils.openFavorites(page); + const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!; + await thumbnailUtils.withAssetId(page, assetToArchive.id).hover(); + await thumbnailUtils.selectButton(page, assetToArchive.id).click(); + await page.getByLabel('Menu').click(); + const archive = pageRoutePromise(page, '**/api/assets', async (route, request) => { + const requestJson = request.postDataJSON(); + if (requestJson.visibility !== 'archive') { + return await route.continue(); + } + await route.fulfill({ + status: 204, + }); + changes.assetArchivals.push(...requestJson.ids); + }); + await page.getByRole('menuitem').getByText('Archive').click(); + await expect(archive).resolves.toEqual({ + visibility: 'archive', + ids: [assetToArchive.id], + }); + await page.getByRole('link').getByText('Archive').click(); + await thumbnailUtils.expectInViewport(page, assetToArchive.id); + await thumbnailUtils.expectThumbnailIsNotArchive(page, assetToArchive.id); + await thumbnailUtils.withAssetId(page, assetToArchive.id).hover(); + await thumbnailUtils.selectButton(page, assetToArchive.id).click(); + const unarchiveRequest = pageRoutePromise(page, '**/api/assets', async (route, request) => { + const requestJson = request.postDataJSON(); + if (requestJson.visibility !== 'timeline') { + return await route.continue(); + } + changes.assetArchivals = changes.assetArchivals.filter((id) => !requestJson.ids.includes(id)); + await route.fulfill({ + status: 204, + }); + }); + await page.getByLabel('Unarchive').click(); + await expect(unarchiveRequest).resolves.toEqual({ + visibility: 'timeline', + ids: [assetToArchive.id], + }); + await expect(thumbnailUtils.withAssetId(page, assetToArchive.id)).toHaveCount(0); + await thumbnailUtils.expectThumbnailIsNotArchive(page, assetToArchive.id); + }); test('Open album, favorite photo, open /favorites, remove favorite, Open album', async ({ page }) => { const album = timelineRestData.album; await pageUtils.openAlbumPage(page, album.id); diff --git a/e2e/src/web/specs/timeline/utils.ts b/e2e/src/web/specs/timeline/utils.ts index 8d9e784d8f..0b49f02941 100644 --- a/e2e/src/web/specs/timeline/utils.ts +++ b/e2e/src/web/specs/timeline/utils.ts @@ -105,20 +105,16 @@ export const thumbnailUtils = { return await poll(page, () => thumbnailUtils.queryThumbnailInViewport(page, collector)); }, async expectThumbnailIsFavorite(page: Page, assetId: string) { - await expect( - thumbnailUtils - .withAssetId(page, assetId) - .locator( - 'path[d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"]', - ), - ).toHaveCount(1); + await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-favorite]')).toHaveCount(1); + }, + async expectThumbnailIsNotFavorite(page: Page, assetId: string) { + await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-favorite]')).toHaveCount(0); }, async expectThumbnailIsArchive(page: Page, assetId: string) { - await expect( - thumbnailUtils - .withAssetId(page, assetId) - .locator('path[d="M20 21H4V10H6V19H18V10H20V21M3 3H21V9H3V3M5 5V7H19V5M10.5 11V14H8L12 18L16 14H13.5V11"]'), - ).toHaveCount(1); + await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(1); + }, + async expectThumbnailIsNotArchive(page: Page, assetId: string) { + await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0); }, async expectSelectedReadonly(page: Page, assetId: string) { // todo - need a data attribute for selected @@ -208,10 +204,18 @@ export const pageUtils = { await page.goto(`/photos`); await timelineUtils.waitForTimelineLoad(page); }, + async openFavorites(page: Page) { + await page.goto(`/favorites`); + await timelineUtils.waitForTimelineLoad(page); + }, async openAlbumPage(page: Page, albumId: string) { await page.goto(`/albums/${albumId}`); await timelineUtils.waitForTimelineLoad(page); }, + async openArchivePage(page: Page) { + await page.goto(`/archive`); + await timelineUtils.waitForTimelineLoad(page); + }, async deepLinkAlbumPage(page: Page, albumId: string, assetId: string) { await page.goto(`/albums/${albumId}?at=${assetId}`); await timelineUtils.waitForTimelineLoad(page); diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte index 261829cfc6..0645541241 100644 --- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte @@ -264,20 +264,20 @@ {#if !authManager.isSharedLink && asset.isFavorite}
- +
{/if} {#if !authManager.isSharedLink && showArchiveIcon && asset.visibility === AssetVisibility.Archive}
- +
{/if} {#if asset.isImage && asset.projectionType === ProjectionType.EQUIRECTANGULAR}
- +
{/if} @@ -285,7 +285,7 @@ {#if asset.isImage && asset.duration && !asset.duration.includes('0:00:00.000')}
- +
{/if} @@ -300,7 +300,7 @@ >

{asset.stack.assetCount.toLocaleString($locale)}

- +
{/if} @@ -366,7 +366,7 @@ />
- +
@@ -406,13 +406,13 @@ {disabled} > {#if disabled} - + {:else if selected}
- +
{:else} - + {/if} {/if} diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte index dbaa8c085a..b99260eee4 100644 --- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -567,7 +567,15 @@ onClick={() => updateThumbnailUsingCurrentSelection()} /> {/if} - + + timelineManager.updateAssetOperation(ids, (asset) => { + asset.visibility = visibility; + return { remove: false }; + })} + /> {/if} diff --git a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte index 4770ba1638..7cb3bf8e17 100644 --- a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -85,7 +85,11 @@ timelineManager.removeAssets(assetIds)} + onArchive={(ids, visibility) => + timelineManager.updateAssetOperation(ids, (asset) => { + asset.visibility = visibility; + return { remove: false }; + })} /> {#if $preferences.tags.enabled} diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 5abcc6f5a3..5dabd58e76 100644 --- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -511,7 +511,11 @@ timelineManager.removeAssets(assetIds)} + onArchive={(ids, visibility) => + timelineManager.updateAssetOperation(ids, (asset) => { + asset.visibility = visibility; + return { remove: false }; + })} /> {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} diff --git a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte index e2fffa37c4..669ea23921 100644 --- a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte @@ -146,7 +146,14 @@ - timelineManager.removeAssets(assetIds)} /> + + timelineManager.updateAssetOperation(ids, (asset) => { + asset.visibility = visibility; + return { remove: false }; + })} + /> {#if $preferences.tags.enabled} {/if}