mirror of
https://github.com/immich-app/immich.git
synced 2025-12-18 17:23:16 +03:00
fix: update timeline-manager after archive actions (#24010)
* fix: update timeline-manager after archive actions * Add locators to thumb icons
This commit is contained in:
@@ -611,6 +611,53 @@ test.describe('Timeline', () => {
|
|||||||
await page.getByText('Photos', { exact: true }).click();
|
await page.getByText('Photos', { exact: true }).click();
|
||||||
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
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 }) => {
|
test('open album, archive photo, open album, unarchive', async ({ page }) => {
|
||||||
const album = timelineRestData.album;
|
const album = timelineRestData.album;
|
||||||
await pageUtils.openAlbumPage(page, album.id);
|
await pageUtils.openAlbumPage(page, album.id);
|
||||||
@@ -633,8 +680,7 @@ test.describe('Timeline', () => {
|
|||||||
visibility: 'archive',
|
visibility: 'archive',
|
||||||
ids: [assetToArchive.id],
|
ids: [assetToArchive.id],
|
||||||
});
|
});
|
||||||
console.log('Skipping assertion - TODO - fix that archiving in album doesnt add icon');
|
await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id);
|
||||||
// await thumbnail.expectThumbnailIsArchive(page, assetToArchive.id);
|
|
||||||
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
await page.locator('#asset-selection-app-bar').getByLabel('Close').click();
|
||||||
await page.getByRole('link').getByText('Archive').click();
|
await page.getByRole('link').getByText('Archive').click();
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
@@ -656,8 +702,7 @@ test.describe('Timeline', () => {
|
|||||||
visibility: 'timeline',
|
visibility: 'timeline',
|
||||||
ids: [assetToArchive.id],
|
ids: [assetToArchive.id],
|
||||||
});
|
});
|
||||||
console.log('Skipping assertion - TODO - fix bug with not removing asset from timeline-manager after unarchive');
|
await expect(thumbnailUtils.withAssetId(page, assetToArchive.id)).toHaveCount(0);
|
||||||
// await expect(thumbnail.withAssetId(page, assetToArchive.id)).toHaveCount(0);
|
|
||||||
await pageUtils.openAlbumPage(page, album.id);
|
await pageUtils.openAlbumPage(page, album.id);
|
||||||
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
await thumbnailUtils.expectInViewport(page, assetToArchive.id);
|
||||||
});
|
});
|
||||||
@@ -712,6 +757,50 @@ test.describe('Timeline', () => {
|
|||||||
await page.getByText('Photos', { exact: true }).click();
|
await page.getByText('Photos', { exact: true }).click();
|
||||||
await thumbnailUtils.expectInViewport(page, assetToFavorite.id);
|
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 }) => {
|
test('Open album, favorite photo, open /favorites, remove favorite, Open album', async ({ page }) => {
|
||||||
const album = timelineRestData.album;
|
const album = timelineRestData.album;
|
||||||
await pageUtils.openAlbumPage(page, album.id);
|
await pageUtils.openAlbumPage(page, album.id);
|
||||||
|
|||||||
@@ -105,20 +105,16 @@ export const thumbnailUtils = {
|
|||||||
return await poll(page, () => thumbnailUtils.queryThumbnailInViewport(page, collector));
|
return await poll(page, () => thumbnailUtils.queryThumbnailInViewport(page, collector));
|
||||||
},
|
},
|
||||||
async expectThumbnailIsFavorite(page: Page, assetId: string) {
|
async expectThumbnailIsFavorite(page: Page, assetId: string) {
|
||||||
await expect(
|
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-favorite]')).toHaveCount(1);
|
||||||
thumbnailUtils
|
},
|
||||||
.withAssetId(page, assetId)
|
async expectThumbnailIsNotFavorite(page: Page, assetId: string) {
|
||||||
.locator(
|
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-favorite]')).toHaveCount(0);
|
||||||
'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);
|
|
||||||
},
|
},
|
||||||
async expectThumbnailIsArchive(page: Page, assetId: string) {
|
async expectThumbnailIsArchive(page: Page, assetId: string) {
|
||||||
await expect(
|
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(1);
|
||||||
thumbnailUtils
|
},
|
||||||
.withAssetId(page, assetId)
|
async expectThumbnailIsNotArchive(page: Page, assetId: string) {
|
||||||
.locator('path[d="M20 21H4V10H6V19H18V10H20V21M3 3H21V9H3V3M5 5V7H19V5M10.5 11V14H8L12 18L16 14H13.5V11"]'),
|
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
|
||||||
).toHaveCount(1);
|
|
||||||
},
|
},
|
||||||
async expectSelectedReadonly(page: Page, assetId: string) {
|
async expectSelectedReadonly(page: Page, assetId: string) {
|
||||||
// todo - need a data attribute for selected
|
// todo - need a data attribute for selected
|
||||||
@@ -208,10 +204,18 @@ export const pageUtils = {
|
|||||||
await page.goto(`/photos`);
|
await page.goto(`/photos`);
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
},
|
},
|
||||||
|
async openFavorites(page: Page) {
|
||||||
|
await page.goto(`/favorites`);
|
||||||
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
|
},
|
||||||
async openAlbumPage(page: Page, albumId: string) {
|
async openAlbumPage(page: Page, albumId: string) {
|
||||||
await page.goto(`/albums/${albumId}`);
|
await page.goto(`/albums/${albumId}`);
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
},
|
},
|
||||||
|
async openArchivePage(page: Page) {
|
||||||
|
await page.goto(`/archive`);
|
||||||
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
|
},
|
||||||
async deepLinkAlbumPage(page: Page, albumId: string, assetId: string) {
|
async deepLinkAlbumPage(page: Page, albumId: string, assetId: string) {
|
||||||
await page.goto(`/albums/${albumId}?at=${assetId}`);
|
await page.goto(`/albums/${albumId}?at=${assetId}`);
|
||||||
await timelineUtils.waitForTimelineLoad(page);
|
await timelineUtils.waitForTimelineLoad(page);
|
||||||
|
|||||||
@@ -264,20 +264,20 @@
|
|||||||
<!-- Favorite asset star -->
|
<!-- Favorite asset star -->
|
||||||
{#if !authManager.isSharedLink && asset.isFavorite}
|
{#if !authManager.isSharedLink && asset.isFavorite}
|
||||||
<div class="absolute bottom-2 start-2">
|
<div class="absolute bottom-2 start-2">
|
||||||
<Icon icon={mdiHeart} size="24" class="text-white" />
|
<Icon data-icon-favorite icon={mdiHeart} size="24" class="text-white" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !authManager.isSharedLink && showArchiveIcon && asset.visibility === AssetVisibility.Archive}
|
{#if !authManager.isSharedLink && showArchiveIcon && asset.visibility === AssetVisibility.Archive}
|
||||||
<div class={['absolute start-2', asset.isFavorite ? 'bottom-10' : 'bottom-2']}>
|
<div class={['absolute start-2', asset.isFavorite ? 'bottom-10' : 'bottom-2']}>
|
||||||
<Icon icon={mdiArchiveArrowDownOutline} size="24" class="text-white" />
|
<Icon data-icon-archive icon={mdiArchiveArrowDownOutline} size="24" class="text-white" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if asset.isImage && asset.projectionType === ProjectionType.EQUIRECTANGULAR}
|
{#if asset.isImage && asset.projectionType === ProjectionType.EQUIRECTANGULAR}
|
||||||
<div class="absolute end-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
|
<div class="absolute end-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
|
||||||
<span class="pe-2 pt-2">
|
<span class="pe-2 pt-2">
|
||||||
<Icon icon={mdiRotate360} size="24" />
|
<Icon data-icon-equirectangular icon={mdiRotate360} size="24" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -285,7 +285,7 @@
|
|||||||
{#if asset.isImage && asset.duration && !asset.duration.includes('0:00:00.000')}
|
{#if asset.isImage && asset.duration && !asset.duration.includes('0:00:00.000')}
|
||||||
<div class="absolute end-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
|
<div class="absolute end-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
|
||||||
<span class="pe-2 pt-2">
|
<span class="pe-2 pt-2">
|
||||||
<Icon icon={mdiFileGifBox} size="24" />
|
<Icon data-icon-playable icon={mdiFileGifBox} size="24" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -300,7 +300,7 @@
|
|||||||
>
|
>
|
||||||
<span class="pe-2 pt-2 flex place-items-center gap-1">
|
<span class="pe-2 pt-2 flex place-items-center gap-1">
|
||||||
<p>{asset.stack.assetCount.toLocaleString($locale)}</p>
|
<p>{asset.stack.assetCount.toLocaleString($locale)}</p>
|
||||||
<Icon icon={mdiCameraBurst} size="24" />
|
<Icon data-icon-stack icon={mdiCameraBurst} size="24" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -366,7 +366,7 @@
|
|||||||
/>
|
/>
|
||||||
<div class="absolute end-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
|
<div class="absolute end-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
|
||||||
<span class="pe-2 pt-2">
|
<span class="pe-2 pt-2">
|
||||||
<Icon icon={mdiMotionPauseOutline} size="24" />
|
<Icon data-icon-playable-pause icon={mdiMotionPauseOutline} size="24" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -406,13 +406,13 @@
|
|||||||
{disabled}
|
{disabled}
|
||||||
>
|
>
|
||||||
{#if disabled}
|
{#if disabled}
|
||||||
<Icon icon={mdiCheckCircle} size="24" class="text-zinc-800" />
|
<Icon data-icon-select icon={mdiCheckCircle} size="24" class="text-zinc-800" />
|
||||||
{:else if selected}
|
{:else if selected}
|
||||||
<div class="rounded-full bg-[#D9DCEF] dark:bg-[#232932]">
|
<div class="rounded-full bg-[#D9DCEF] dark:bg-[#232932]">
|
||||||
<Icon icon={mdiCheckCircle} size="24" class="text-primary" />
|
<Icon data-icon-select icon={mdiCheckCircle} size="24" class="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon={mdiCheckCircle} size="24" class="text-white/80 hover:text-white" />
|
<Icon data-icon-select icon={mdiCheckCircle} size="24" class="text-white/80 hover:text-white" />
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -567,7 +567,15 @@
|
|||||||
onClick={() => updateThumbnailUsingCurrentSelection()}
|
onClick={() => updateThumbnailUsingCurrentSelection()}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} />
|
<ArchiveAction
|
||||||
|
menuItem
|
||||||
|
unarchive={assetInteraction.isAllArchived}
|
||||||
|
onArchive={(ids, visibility) =>
|
||||||
|
timelineManager.updateAssetOperation(ids, (asset) => {
|
||||||
|
asset.visibility = visibility;
|
||||||
|
return { remove: false };
|
||||||
|
})}
|
||||||
|
/>
|
||||||
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
|
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,11 @@
|
|||||||
<ArchiveAction
|
<ArchiveAction
|
||||||
menuItem
|
menuItem
|
||||||
unarchive={assetInteraction.isAllArchived}
|
unarchive={assetInteraction.isAllArchived}
|
||||||
onArchive={(assetIds) => timelineManager.removeAssets(assetIds)}
|
onArchive={(ids, visibility) =>
|
||||||
|
timelineManager.updateAssetOperation(ids, (asset) => {
|
||||||
|
asset.visibility = visibility;
|
||||||
|
return { remove: false };
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
{#if $preferences.tags.enabled}
|
{#if $preferences.tags.enabled}
|
||||||
<TagAction menuItem />
|
<TagAction menuItem />
|
||||||
|
|||||||
@@ -511,7 +511,11 @@
|
|||||||
<ArchiveAction
|
<ArchiveAction
|
||||||
menuItem
|
menuItem
|
||||||
unarchive={assetInteraction.isAllArchived}
|
unarchive={assetInteraction.isAllArchived}
|
||||||
onArchive={(assetIds) => timelineManager.removeAssets(assetIds)}
|
onArchive={(ids, visibility) =>
|
||||||
|
timelineManager.updateAssetOperation(ids, (asset) => {
|
||||||
|
asset.visibility = visibility;
|
||||||
|
return { remove: false };
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
|
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
|
||||||
<TagAction menuItem />
|
<TagAction menuItem />
|
||||||
|
|||||||
@@ -146,7 +146,14 @@
|
|||||||
<ChangeDate menuItem />
|
<ChangeDate menuItem />
|
||||||
<ChangeDescription menuItem />
|
<ChangeDescription menuItem />
|
||||||
<ChangeLocation menuItem />
|
<ChangeLocation menuItem />
|
||||||
<ArchiveAction menuItem onArchive={(assetIds) => timelineManager.removeAssets(assetIds)} />
|
<ArchiveAction
|
||||||
|
menuItem
|
||||||
|
onArchive={(ids, visibility) =>
|
||||||
|
timelineManager.updateAssetOperation(ids, (asset) => {
|
||||||
|
asset.visibility = visibility;
|
||||||
|
return { remove: false };
|
||||||
|
})}
|
||||||
|
/>
|
||||||
{#if $preferences.tags.enabled}
|
{#if $preferences.tags.enabled}
|
||||||
<TagAction menuItem />
|
<TagAction menuItem />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user