fix(web): prevent AssetUpdate from adding unrelated timeline assets (#27369)

This commit is contained in:
Michel Heusschen 2026-04-01 14:14:28 +02:00 committed by GitHub
parent 505a07a825
commit 4eb531197e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 45 additions and 2 deletions

View File

@ -1,9 +1,10 @@
import { sdkMock } from '$lib/__mocks__/sdk.mock';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { getMonthGroupByDate } from '$lib/managers/timeline-manager/internal/search-support.svelte';
import { AbortError } from '$lib/utils';
import { fromISODateTimeUTCToObject } from '$lib/utils/timeline-util';
import { AssetVisibility, type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk';
import { timelineAssetFactory, toResponseDto } from '@test-data/factories/asset-factory';
import { assetFactory, timelineAssetFactory, toResponseDto } from '@test-data/factories/asset-factory';
import { tick } from 'svelte';
import { TimelineManager } from './timeline-manager.svelte';
import type { TimelineAsset } from './types';
@ -442,6 +443,48 @@ describe('TimelineManager', () => {
});
});
describe('AssetUpdate events', () => {
let timelineManager: TimelineManager;
beforeEach(async () => {
timelineManager = new TimelineManager();
sdkMock.getTimeBuckets.mockResolvedValue([]);
await timelineManager.updateViewport({ width: 1588, height: 1000 });
await timelineManager.updateOptions({ albumId: 'album-id' });
});
afterEach(() => {
timelineManager.destroy();
});
it('ignores unknown assets for album timelines', () => {
eventManager.emit('AssetUpdate', assetFactory.build());
expect(timelineManager.assetCount).toEqual(0);
expect(timelineManager.months).toHaveLength(0);
});
it('updates existing assets in the timeline', () => {
const existing = deriveLocalDateTimeFromFileCreatedAt(timelineAssetFactory.build({ isFavorite: false }));
timelineManager.upsertAssets([existing]);
eventManager.emit(
'AssetUpdate',
assetFactory.build({
id: existing.id,
ownerId: existing.ownerId,
isFavorite: true,
isTrashed: existing.isTrashed,
visibility: existing.visibility,
}),
);
expect(timelineManager.assetCount).toEqual(1);
expect(timelineManager.months[0].getFirstAsset().isFavorite).toEqual(true);
});
});
describe('removeAssets', () => {
let timelineManager: TimelineManager;

View File

@ -113,7 +113,7 @@ export class TimelineManager extends VirtualScrollManager {
this.#unsubscribes.push(
eventManager.on({
AssetUpdate: (asset: AssetResponseDto) => this.upsertAssets([toTimelineAsset(asset)]),
AssetUpdate: (asset: AssetResponseDto) => this.#updateAssets([toTimelineAsset(asset)]),
}),
);
}