diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index 3af44b50b8330..619ef601e2dea 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -11,6 +11,7 @@ import { PersonCreateDto, SharedLinkCreateDto, UserAdminCreateDto, + UserPreferencesUpdateDto, ValidateLibraryDto, checkExistingAssets, createAlbum, @@ -19,6 +20,7 @@ import { createPartner, createPerson, createSharedLink, + createStack, createUserAdmin, deleteAssets, getAllJobsStatus, @@ -28,10 +30,13 @@ import { searchMetadata, setBaseUrl, signUpAdmin, + tagAssets, updateAdminOnboarding, updateAlbumUser, updateAssets, updateConfig, + updateMyPreferences, + upsertTags, validate, } from '@immich/sdk'; import { BrowserContext } from '@playwright/test'; @@ -444,6 +449,18 @@ export const utils = { createPartner: (accessToken: string, id: string) => createPartner({ id }, { headers: asBearerAuth(accessToken) }), + updateMyPreferences: (accessToken: string, userPreferencesUpdateDto: UserPreferencesUpdateDto) => + updateMyPreferences({ userPreferencesUpdateDto }, { headers: asBearerAuth(accessToken) }), + + createStack: (accessToken: string, assetIds: string[]) => + createStack({ stackCreateDto: { assetIds } }, { headers: asBearerAuth(accessToken) }), + + upsertTags: (accessToken: string, tags: string[]) => + upsertTags({ tagUpsertDto: { tags } }, { headers: asBearerAuth(accessToken) }), + + tagAssets: (accessToken: string, tagId: string, assetIds: string[]) => + tagAssets({ id: tagId, bulkIdsDto: { ids: assetIds } }, { headers: asBearerAuth(accessToken) }), + setAuthCookies: async (context: BrowserContext, accessToken: string, domain = '127.0.0.1') => await context.addCookies([ { diff --git a/e2e/src/web/specs/asset-viewer/stack.e2e-spec.ts b/e2e/src/web/specs/asset-viewer/stack.e2e-spec.ts new file mode 100644 index 0000000000000..cb40f82c0a813 --- /dev/null +++ b/e2e/src/web/specs/asset-viewer/stack.e2e-spec.ts @@ -0,0 +1,66 @@ +import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk'; +import { expect, Page, test } from '@playwright/test'; +import { utils } from 'src/utils'; + +async function ensureDetailPanelVisible(page: Page) { + await page.waitForSelector('#immich-asset-viewer'); + + const isVisible = await page.locator('#detail-panel').isVisible(); + if (!isVisible) { + await page.keyboard.press('i'); + await page.waitForSelector('#detail-panel'); + } +} + +test.describe('Asset Viewer stack', () => { + let admin: LoginResponseDto; + let assetOne: AssetMediaResponseDto; + let assetTwo: AssetMediaResponseDto; + + test.beforeAll(async () => { + utils.initSdk(); + await utils.resetDatabase(); + admin = await utils.adminSetup(); + await utils.updateMyPreferences(admin.accessToken, { tags: { enabled: true } }); + + assetOne = await utils.createAsset(admin.accessToken); + assetTwo = await utils.createAsset(admin.accessToken); + await utils.createStack(admin.accessToken, [assetOne.id, assetTwo.id]); + + const tags = await utils.upsertTags(admin.accessToken, ['test/1', 'test/2']); + const tagOne = tags.find((tag) => tag.value === 'test/1')!; + const tagTwo = tags.find((tag) => tag.value === 'test/2')!; + await utils.tagAssets(admin.accessToken, tagOne.id, [assetOne.id]); + await utils.tagAssets(admin.accessToken, tagTwo.id, [assetTwo.id]); + }); + + test('stack slideshow is visible', async ({ page, context }) => { + await utils.setAuthCookies(context, admin.accessToken); + await page.goto(`/photos/${assetOne.id}`); + + const stackAssets = page.locator('#stack-slideshow [data-asset]'); + await expect(stackAssets.first()).toBeVisible(); + await expect(stackAssets.nth(1)).toBeVisible(); + }); + + test('tags of primary asset are visible', async ({ page, context }) => { + await utils.setAuthCookies(context, admin.accessToken); + await page.goto(`/photos/${assetOne.id}`); + await ensureDetailPanelVisible(page); + + const tags = page.getByTestId('detail-panel-tags').getByRole('link'); + await expect(tags.first()).toHaveText('test/1'); + }); + + test('tags of second asset are visible', async ({ page, context }) => { + await utils.setAuthCookies(context, admin.accessToken); + await page.goto(`/photos/${assetOne.id}`); + await ensureDetailPanelVisible(page); + + const stackAssets = page.locator('#stack-slideshow [data-asset]'); + await stackAssets.nth(1).click(); + + const tags = page.getByTestId('detail-panel-tags').getByRole('link'); + await expect(tags.first()).toHaveText('test/2'); + }); +}); diff --git a/server/src/repositories/stack.repository.ts b/server/src/repositories/stack.repository.ts index ae1a7f70d4a3a..9e9f09c4429ca 100644 --- a/server/src/repositories/stack.repository.ts +++ b/server/src/repositories/stack.repository.ts @@ -127,6 +127,7 @@ export class StackRepository implements IStackRepository { relations: { assets: { exifInfo: true, + tags: true, }, }, order: { diff --git a/web/src/lib/components/asset-viewer/detail-panel-tags.svelte b/web/src/lib/components/asset-viewer/detail-panel-tags.svelte index c1175f5eb4a6d..39ca096efdb18 100644 --- a/web/src/lib/components/asset-viewer/detail-panel-tags.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel-tags.svelte @@ -46,7 +46,7 @@

{$t('tags').toUpperCase()}

-
+
{#each tags as tag (tag.id)}