feat: keyboard navigation to timeline (#17798)
* feat: improve focus * feat: keyboard nav * feat: improve focus * typo * test * fix test * lint * bad merge * lint * inadvertent * lint * fix: flappy e2e test * bad merge and fix tests * use modulus in loop * tests * react to modal dialog refactor * regression due to deferLayout * Review comments * Re-use change-date instead of new component * bad merge * Review comments * rework moveFocus * lint * Fix outline * use Date * Finish up removing/reducing date parsing * lint * title * strings * Rework dates, rework earlier/later algorithm * bad merge * fix tests * Fix race in scroll comp * consolidate scroll methods * Review comments * console.log * Edge cases in scroll compensation * edge case, optimizations * review comments * lint * lint * More edge cases * lint --------- Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
@@ -1,9 +1,18 @@
|
||||
import { sdkMock } from '$lib/__mocks__/sdk.mock';
|
||||
import { AbortError } from '$lib/utils';
|
||||
import { fromLocalDateTimeToObject } from '$lib/utils/timeline-util';
|
||||
import { type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk';
|
||||
import { timelineAssetFactory, toResponseDto } from '@test-data/factories/asset-factory';
|
||||
import { AssetStore, type TimelineAsset } from './assets-store.svelte';
|
||||
|
||||
async function getAssets(store: AssetStore) {
|
||||
const assets = [];
|
||||
for await (const asset of store.assetsIterator()) {
|
||||
assets.push(asset);
|
||||
}
|
||||
return assets;
|
||||
}
|
||||
|
||||
describe('AssetStore', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
@@ -14,13 +23,13 @@ describe('AssetStore', () => {
|
||||
const bucketAssets: Record<string, TimelineAsset[]> = {
|
||||
'2024-03-01T00:00:00.000Z': timelineAssetFactory
|
||||
.buildList(1)
|
||||
.map((asset) => ({ ...asset, localDateTime: '2024-03-01T00:00:00.000Z' })),
|
||||
.map((asset) => ({ ...asset, localDateTime: fromLocalDateTimeToObject('2024-03-01T00:00:00.000Z') })),
|
||||
'2024-02-01T00:00:00.000Z': timelineAssetFactory
|
||||
.buildList(100)
|
||||
.map((asset) => ({ ...asset, localDateTime: '2024-02-01T00:00:00.000Z' })),
|
||||
.map((asset) => ({ ...asset, localDateTime: fromLocalDateTimeToObject('2024-02-01T00:00:00.000Z') })),
|
||||
'2024-01-01T00:00:00.000Z': timelineAssetFactory
|
||||
.buildList(3)
|
||||
.map((asset) => ({ ...asset, localDateTime: '2024-01-01T00:00:00.000Z' })),
|
||||
.map((asset) => ({ ...asset, localDateTime: fromLocalDateTimeToObject('2024-01-01T00:00:00.000Z') })),
|
||||
};
|
||||
|
||||
const bucketAssetsResponse: Record<string, TimeBucketAssetResponseDto> = Object.fromEntries(
|
||||
@@ -41,21 +50,21 @@ describe('AssetStore', () => {
|
||||
|
||||
it('should load buckets in viewport', () => {
|
||||
expect(sdkMock.getTimeBuckets).toBeCalledTimes(1);
|
||||
|
||||
expect(sdkMock.getTimeBucket).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('calculates bucket height', () => {
|
||||
const plainBuckets = assetStore.buckets.map((bucket) => ({
|
||||
bucketDate: bucket.bucketDate,
|
||||
year: bucket.yearMonth.year,
|
||||
month: bucket.yearMonth.month,
|
||||
bucketHeight: bucket.bucketHeight,
|
||||
}));
|
||||
|
||||
expect(plainBuckets).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ bucketDate: '2024-03-01T00:00:00.000Z', bucketHeight: 185.5 }),
|
||||
expect.objectContaining({ bucketDate: '2024-02-01T00:00:00.000Z', bucketHeight: 12_016 }),
|
||||
expect.objectContaining({ bucketDate: '2024-01-01T00:00:00.000Z', bucketHeight: 286 }),
|
||||
expect.objectContaining({ year: 2024, month: 3, bucketHeight: 185.5 }),
|
||||
expect.objectContaining({ year: 2024, month: 2, bucketHeight: 12_016 }),
|
||||
expect.objectContaining({ year: 2024, month: 1, bucketHeight: 286 }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
@@ -70,10 +79,10 @@ describe('AssetStore', () => {
|
||||
const bucketAssets: Record<string, TimelineAsset[]> = {
|
||||
'2024-01-03T00:00:00.000Z': timelineAssetFactory
|
||||
.buildList(1)
|
||||
.map((asset) => ({ ...asset, localDateTime: '2024-03-01T00:00:00.000Z' })),
|
||||
.map((asset) => ({ ...asset, localDateTime: fromLocalDateTimeToObject('2024-03-01T00:00:00.000Z') })),
|
||||
'2024-01-01T00:00:00.000Z': timelineAssetFactory
|
||||
.buildList(3)
|
||||
.map((asset) => ({ ...asset, localDateTime: '2024-01-01T00:00:00.000Z' })),
|
||||
.map((asset) => ({ ...asset, localDateTime: fromLocalDateTimeToObject('2024-01-01T00:00:00.000Z') })),
|
||||
};
|
||||
const bucketAssetsResponse: Record<string, TimeBucketAssetResponseDto> = Object.fromEntries(
|
||||
Object.entries(bucketAssets).map(([key, assets]) => [key, toResponseDto(...assets)]),
|
||||
@@ -95,47 +104,47 @@ describe('AssetStore', () => {
|
||||
});
|
||||
|
||||
it('loads a bucket', async () => {
|
||||
expect(assetStore.getBucketByDate(2024, 1)?.getAssets().length).toEqual(0);
|
||||
await assetStore.loadBucket('2024-01-01T00:00:00.000Z');
|
||||
expect(assetStore.getBucketByDate({ year: 2024, month: 1 })?.getAssets().length).toEqual(0);
|
||||
await assetStore.loadBucket({ year: 2024, month: 1 });
|
||||
expect(sdkMock.getTimeBucket).toBeCalledTimes(1);
|
||||
expect(assetStore.getBucketByDate(2024, 1)?.getAssets().length).toEqual(3);
|
||||
expect(assetStore.getBucketByDate({ year: 2024, month: 1 })?.getAssets().length).toEqual(3);
|
||||
});
|
||||
|
||||
it('ignores invalid buckets', async () => {
|
||||
await assetStore.loadBucket('2023-01-01T00:00:00.000Z');
|
||||
await assetStore.loadBucket({ year: 2023, month: 1 });
|
||||
expect(sdkMock.getTimeBucket).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('cancels bucket loading', async () => {
|
||||
const bucket = assetStore.getBucketByDate(2024, 1)!;
|
||||
void assetStore.loadBucket(bucket!.bucketDate);
|
||||
const bucket = assetStore.getBucketByDate({ year: 2024, month: 1 })!;
|
||||
void assetStore.loadBucket({ year: 2024, month: 1 });
|
||||
const abortSpy = vi.spyOn(bucket!.loader!.cancelToken!, 'abort');
|
||||
bucket?.cancel();
|
||||
expect(abortSpy).toBeCalledTimes(1);
|
||||
await assetStore.loadBucket(bucket!.bucketDate);
|
||||
expect(assetStore.getBucketByDate(2024, 1)?.getAssets().length).toEqual(3);
|
||||
await assetStore.loadBucket({ year: 2024, month: 1 });
|
||||
expect(assetStore.getBucketByDate({ year: 2024, month: 1 })?.getAssets().length).toEqual(3);
|
||||
});
|
||||
|
||||
it('prevents loading buckets multiple times', async () => {
|
||||
await Promise.all([
|
||||
assetStore.loadBucket('2024-01-01T00:00:00.000Z'),
|
||||
assetStore.loadBucket('2024-01-01T00:00:00.000Z'),
|
||||
assetStore.loadBucket({ year: 2024, month: 1 }),
|
||||
assetStore.loadBucket({ year: 2024, month: 1 }),
|
||||
]);
|
||||
expect(sdkMock.getTimeBucket).toBeCalledTimes(1);
|
||||
|
||||
await assetStore.loadBucket('2024-01-01T00:00:00.000Z');
|
||||
await assetStore.loadBucket({ year: 2024, month: 1 });
|
||||
expect(sdkMock.getTimeBucket).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('allows loading a canceled bucket', async () => {
|
||||
const bucket = assetStore.getBucketByDate(2024, 1)!;
|
||||
const loadPromise = assetStore.loadBucket(bucket!.bucketDate);
|
||||
const bucket = assetStore.getBucketByDate({ year: 2024, month: 1 })!;
|
||||
const loadPromise = assetStore.loadBucket({ year: 2024, month: 1 });
|
||||
|
||||
bucket.cancel();
|
||||
await loadPromise;
|
||||
expect(bucket?.getAssets().length).toEqual(0);
|
||||
|
||||
await assetStore.loadBucket(bucket.bucketDate);
|
||||
await assetStore.loadBucket({ year: 2024, month: 1 });
|
||||
expect(bucket!.getAssets().length).toEqual(3);
|
||||
});
|
||||
});
|
||||
@@ -152,48 +161,50 @@ describe('AssetStore', () => {
|
||||
|
||||
it('is empty initially', () => {
|
||||
expect(assetStore.buckets.length).toEqual(0);
|
||||
expect(assetStore.getAssets().length).toEqual(0);
|
||||
expect(assetStore.count).toEqual(0);
|
||||
});
|
||||
|
||||
it('adds assets to new bucket', () => {
|
||||
const asset = timelineAssetFactory.build({
|
||||
localDateTime: '2024-01-20T12:00:00.000Z',
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-20T12:00:00.000Z'),
|
||||
});
|
||||
assetStore.addAssets([asset]);
|
||||
|
||||
expect(assetStore.buckets.length).toEqual(1);
|
||||
expect(assetStore.getAssets().length).toEqual(1);
|
||||
expect(assetStore.count).toEqual(1);
|
||||
expect(assetStore.buckets[0].getAssets().length).toEqual(1);
|
||||
expect(assetStore.buckets[0].bucketDate).toEqual('2024-01-01T00:00:00.000Z');
|
||||
expect(assetStore.getAssets()[0].id).toEqual(asset.id);
|
||||
expect(assetStore.buckets[0].yearMonth.year).toEqual(2024);
|
||||
expect(assetStore.buckets[0].yearMonth.month).toEqual(1);
|
||||
expect(assetStore.buckets[0].getFirstAsset().id).toEqual(asset.id);
|
||||
});
|
||||
|
||||
it('adds assets to existing bucket', () => {
|
||||
const [assetOne, assetTwo] = timelineAssetFactory.buildList(2, {
|
||||
localDateTime: '2024-01-20T12:00:00.000Z',
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-20T12:00:00.000Z'),
|
||||
});
|
||||
assetStore.addAssets([assetOne]);
|
||||
assetStore.addAssets([assetTwo]);
|
||||
|
||||
expect(assetStore.buckets.length).toEqual(1);
|
||||
expect(assetStore.getAssets().length).toEqual(2);
|
||||
expect(assetStore.count).toEqual(2);
|
||||
expect(assetStore.buckets[0].getAssets().length).toEqual(2);
|
||||
expect(assetStore.buckets[0].bucketDate).toEqual('2024-01-01T00:00:00.000Z');
|
||||
expect(assetStore.buckets[0].yearMonth.year).toEqual(2024);
|
||||
expect(assetStore.buckets[0].yearMonth.month).toEqual(1);
|
||||
});
|
||||
|
||||
it('orders assets in buckets by descending date', () => {
|
||||
const assetOne = timelineAssetFactory.build({
|
||||
localDateTime: '2024-01-20T12:00:00.000Z',
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-20T12:00:00.000Z'),
|
||||
});
|
||||
const assetTwo = timelineAssetFactory.build({
|
||||
localDateTime: '2024-01-15T12:00:00.000Z',
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-15T12:00:00.000Z'),
|
||||
});
|
||||
const assetThree = timelineAssetFactory.build({
|
||||
localDateTime: '2024-01-16T12:00:00.000Z',
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-16T12:00:00.000Z'),
|
||||
});
|
||||
assetStore.addAssets([assetOne, assetTwo, assetThree]);
|
||||
|
||||
const bucket = assetStore.getBucketByDate(2024, 1);
|
||||
const bucket = assetStore.getBucketByDate({ year: 2024, month: 1 });
|
||||
expect(bucket).not.toBeNull();
|
||||
expect(bucket?.getAssets().length).toEqual(3);
|
||||
expect(bucket?.getAssets()[0].id).toEqual(assetOne.id);
|
||||
@@ -202,15 +213,26 @@ describe('AssetStore', () => {
|
||||
});
|
||||
|
||||
it('orders buckets by descending date', () => {
|
||||
const assetOne = timelineAssetFactory.build({ localDateTime: '2024-01-20T12:00:00.000Z' });
|
||||
const assetTwo = timelineAssetFactory.build({ localDateTime: '2024-04-20T12:00:00.000Z' });
|
||||
const assetThree = timelineAssetFactory.build({ localDateTime: '2023-01-20T12:00:00.000Z' });
|
||||
const assetOne = timelineAssetFactory.build({
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-20T12:00:00.000Z'),
|
||||
});
|
||||
const assetTwo = timelineAssetFactory.build({
|
||||
localDateTime: fromLocalDateTimeToObject('2024-04-20T12:00:00.000Z'),
|
||||
});
|
||||
const assetThree = timelineAssetFactory.build({
|
||||
localDateTime: fromLocalDateTimeToObject('2023-01-20T12:00:00.000Z'),
|
||||
});
|
||||
assetStore.addAssets([assetOne, assetTwo, assetThree]);
|
||||
|
||||
expect(assetStore.buckets.length).toEqual(3);
|
||||
expect(assetStore.buckets[0].bucketDate).toEqual('2024-04-01T00:00:00.000Z');
|
||||
expect(assetStore.buckets[1].bucketDate).toEqual('2024-01-01T00:00:00.000Z');
|
||||
expect(assetStore.buckets[2].bucketDate).toEqual('2023-01-01T00:00:00.000Z');
|
||||
expect(assetStore.buckets[0].yearMonth.year).toEqual(2024);
|
||||
expect(assetStore.buckets[0].yearMonth.month).toEqual(4);
|
||||
|
||||
expect(assetStore.buckets[1].yearMonth.year).toEqual(2024);
|
||||
expect(assetStore.buckets[1].yearMonth.month).toEqual(1);
|
||||
|
||||
expect(assetStore.buckets[2].yearMonth.year).toEqual(2023);
|
||||
expect(assetStore.buckets[2].yearMonth.month).toEqual(1);
|
||||
});
|
||||
|
||||
it('updates existing asset', () => {
|
||||
@@ -220,7 +242,7 @@ describe('AssetStore', () => {
|
||||
|
||||
assetStore.addAssets([asset]);
|
||||
expect(updateAssetsSpy).toBeCalledWith([asset]);
|
||||
expect(assetStore.getAssets().length).toEqual(1);
|
||||
expect(assetStore.count).toEqual(1);
|
||||
});
|
||||
|
||||
// disabled due to the wasm Justified Layout import
|
||||
@@ -231,7 +253,7 @@ describe('AssetStore', () => {
|
||||
const assetStore = new AssetStore();
|
||||
await assetStore.updateOptions({ isTrashed: true });
|
||||
assetStore.addAssets([asset, trashedAsset]);
|
||||
expect(assetStore.getAssets()).toEqual([trashedAsset]);
|
||||
expect(await getAssets(assetStore)).toEqual([trashedAsset]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -249,7 +271,7 @@ describe('AssetStore', () => {
|
||||
assetStore.updateAssets([timelineAssetFactory.build()]);
|
||||
|
||||
expect(assetStore.buckets.length).toEqual(0);
|
||||
expect(assetStore.getAssets().length).toEqual(0);
|
||||
expect(assetStore.count).toEqual(0);
|
||||
});
|
||||
|
||||
it('updates an asset', () => {
|
||||
@@ -257,29 +279,31 @@ describe('AssetStore', () => {
|
||||
const updatedAsset = { ...asset, isFavorite: true };
|
||||
|
||||
assetStore.addAssets([asset]);
|
||||
expect(assetStore.getAssets().length).toEqual(1);
|
||||
expect(assetStore.getAssets()[0].isFavorite).toEqual(false);
|
||||
expect(assetStore.count).toEqual(1);
|
||||
expect(assetStore.buckets[0].getFirstAsset().isFavorite).toEqual(false);
|
||||
|
||||
assetStore.updateAssets([updatedAsset]);
|
||||
expect(assetStore.getAssets().length).toEqual(1);
|
||||
expect(assetStore.getAssets()[0].isFavorite).toEqual(true);
|
||||
expect(assetStore.count).toEqual(1);
|
||||
expect(assetStore.buckets[0].getFirstAsset().isFavorite).toEqual(true);
|
||||
});
|
||||
|
||||
it('asset moves buckets when asset date changes', () => {
|
||||
const asset = timelineAssetFactory.build({ localDateTime: '2024-01-20T12:00:00.000Z' });
|
||||
const updatedAsset = { ...asset, localDateTime: '2024-03-20T12:00:00.000Z' };
|
||||
const asset = timelineAssetFactory.build({
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-20T12:00:00.000Z'),
|
||||
});
|
||||
const updatedAsset = { ...asset, localDateTime: fromLocalDateTimeToObject('2024-03-20T12:00:00.000Z') };
|
||||
|
||||
assetStore.addAssets([asset]);
|
||||
expect(assetStore.buckets.length).toEqual(1);
|
||||
expect(assetStore.getBucketByDate(2024, 1)).not.toBeUndefined();
|
||||
expect(assetStore.getBucketByDate(2024, 1)?.getAssets().length).toEqual(1);
|
||||
expect(assetStore.getBucketByDate({ year: 2024, month: 1 })).not.toBeUndefined();
|
||||
expect(assetStore.getBucketByDate({ year: 2024, month: 1 })?.getAssets().length).toEqual(1);
|
||||
|
||||
assetStore.updateAssets([updatedAsset]);
|
||||
expect(assetStore.buckets.length).toEqual(2);
|
||||
expect(assetStore.getBucketByDate(2024, 1)).not.toBeUndefined();
|
||||
expect(assetStore.getBucketByDate(2024, 1)?.getAssets().length).toEqual(0);
|
||||
expect(assetStore.getBucketByDate(2024, 3)).not.toBeUndefined();
|
||||
expect(assetStore.getBucketByDate(2024, 3)?.getAssets().length).toEqual(1);
|
||||
expect(assetStore.getBucketByDate({ year: 2024, month: 1 })).not.toBeUndefined();
|
||||
expect(assetStore.getBucketByDate({ year: 2024, month: 1 })?.getAssets().length).toEqual(0);
|
||||
expect(assetStore.getBucketByDate({ year: 2024, month: 3 })).not.toBeUndefined();
|
||||
expect(assetStore.getBucketByDate({ year: 2024, month: 3 })?.getAssets().length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -294,32 +318,36 @@ describe('AssetStore', () => {
|
||||
});
|
||||
|
||||
it('ignores invalid IDs', () => {
|
||||
assetStore.addAssets(timelineAssetFactory.buildList(2, { localDateTime: '2024-01-20T12:00:00.000Z' }));
|
||||
assetStore.addAssets(
|
||||
timelineAssetFactory.buildList(2, { localDateTime: fromLocalDateTimeToObject('2024-01-20T12:00:00.000Z') }),
|
||||
);
|
||||
assetStore.removeAssets(['', 'invalid', '4c7d9acc']);
|
||||
|
||||
expect(assetStore.getAssets().length).toEqual(2);
|
||||
expect(assetStore.count).toEqual(2);
|
||||
expect(assetStore.buckets.length).toEqual(1);
|
||||
expect(assetStore.buckets[0].getAssets().length).toEqual(2);
|
||||
});
|
||||
|
||||
it('removes asset from bucket', () => {
|
||||
const [assetOne, assetTwo] = timelineAssetFactory.buildList(2, {
|
||||
localDateTime: '2024-01-20T12:00:00.000Z',
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-20T12:00:00.000Z'),
|
||||
});
|
||||
assetStore.addAssets([assetOne, assetTwo]);
|
||||
assetStore.removeAssets([assetOne.id]);
|
||||
|
||||
expect(assetStore.getAssets().length).toEqual(1);
|
||||
expect(assetStore.count).toEqual(1);
|
||||
expect(assetStore.buckets.length).toEqual(1);
|
||||
expect(assetStore.buckets[0].getAssets().length).toEqual(1);
|
||||
});
|
||||
|
||||
it('does not remove bucket when empty', () => {
|
||||
const assets = timelineAssetFactory.buildList(2, { localDateTime: '2024-01-20T12:00:00.000Z' });
|
||||
const assets = timelineAssetFactory.buildList(2, {
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-20T12:00:00.000Z'),
|
||||
});
|
||||
assetStore.addAssets(assets);
|
||||
assetStore.removeAssets(assets.map((asset) => asset.id));
|
||||
|
||||
expect(assetStore.getAssets().length).toEqual(0);
|
||||
expect(assetStore.count).toEqual(0);
|
||||
expect(assetStore.buckets.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
@@ -339,28 +367,28 @@ describe('AssetStore', () => {
|
||||
|
||||
it('populated store returns first asset', () => {
|
||||
const assetOne = timelineAssetFactory.build({
|
||||
localDateTime: '2024-01-20T12:00:00.000Z',
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-20T12:00:00.000Z'),
|
||||
});
|
||||
const assetTwo = timelineAssetFactory.build({
|
||||
localDateTime: '2024-01-15T12:00:00.000Z',
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-15T12:00:00.000Z'),
|
||||
});
|
||||
assetStore.addAssets([assetOne, assetTwo]);
|
||||
expect(assetStore.getFirstAsset()).toEqual(assetOne);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPreviousAsset', () => {
|
||||
describe('getLaterAsset', () => {
|
||||
let assetStore: AssetStore;
|
||||
const bucketAssets: Record<string, TimelineAsset[]> = {
|
||||
'2024-03-01T00:00:00.000Z': timelineAssetFactory
|
||||
.buildList(1)
|
||||
.map((asset) => ({ ...asset, localDateTime: '2024-03-01T00:00:00.000Z' })),
|
||||
.map((asset) => ({ ...asset, localDateTime: fromLocalDateTimeToObject('2024-03-01T00:00:00.000Z') })),
|
||||
'2024-02-01T00:00:00.000Z': timelineAssetFactory
|
||||
.buildList(6)
|
||||
.map((asset) => ({ ...asset, localDateTime: '2024-02-01T00:00:00.000Z' })),
|
||||
.map((asset) => ({ ...asset, localDateTime: fromLocalDateTimeToObject('2024-02-01T00:00:00.000Z') })),
|
||||
'2024-01-01T00:00:00.000Z': timelineAssetFactory
|
||||
.buildList(3)
|
||||
.map((asset) => ({ ...asset, localDateTime: '2024-01-01T00:00:00.000Z' })),
|
||||
.map((asset) => ({ ...asset, localDateTime: fromLocalDateTimeToObject('2024-01-01T00:00:00.000Z') })),
|
||||
};
|
||||
const bucketAssetsResponse: Record<string, TimeBucketAssetResponseDto> = Object.fromEntries(
|
||||
Object.entries(bucketAssets).map(([key, assets]) => [key, toResponseDto(...assets)]),
|
||||
@@ -378,58 +406,59 @@ describe('AssetStore', () => {
|
||||
});
|
||||
|
||||
it('returns null for invalid assetId', async () => {
|
||||
expect(() => assetStore.getPreviousAsset({ id: 'invalid' } as AssetResponseDto)).not.toThrow();
|
||||
expect(await assetStore.getPreviousAsset({ id: 'invalid' } as AssetResponseDto)).toBeUndefined();
|
||||
expect(() => assetStore.getLaterAsset({ id: 'invalid' } as AssetResponseDto)).not.toThrow();
|
||||
expect(await assetStore.getLaterAsset({ id: 'invalid' } as AssetResponseDto)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns previous assetId', async () => {
|
||||
await assetStore.loadBucket('2024-01-01T00:00:00.000Z');
|
||||
const bucket = assetStore.getBucketByDate(2024, 1);
|
||||
await assetStore.loadBucket({ year: 2024, month: 1 });
|
||||
const bucket = assetStore.getBucketByDate({ year: 2024, month: 1 });
|
||||
|
||||
const a = bucket!.getAssets()[0];
|
||||
const b = bucket!.getAssets()[1];
|
||||
const previous = await assetStore.getPreviousAsset(b);
|
||||
const previous = await assetStore.getLaterAsset(b);
|
||||
expect(previous).toEqual(a);
|
||||
});
|
||||
|
||||
it('returns previous assetId spanning multiple buckets', async () => {
|
||||
await assetStore.loadBucket('2024-02-01T00:00:00.000Z');
|
||||
await assetStore.loadBucket('2024-03-01T00:00:00.000Z');
|
||||
await assetStore.loadBucket({ year: 2024, month: 2 });
|
||||
await assetStore.loadBucket({ year: 2024, month: 3 });
|
||||
|
||||
const bucket = assetStore.getBucketByDate(2024, 2);
|
||||
const previousBucket = assetStore.getBucketByDate(2024, 3);
|
||||
const bucket = assetStore.getBucketByDate({ year: 2024, month: 2 });
|
||||
const previousBucket = assetStore.getBucketByDate({ year: 2024, month: 3 });
|
||||
const a = bucket!.getAssets()[0];
|
||||
const b = previousBucket!.getAssets()[0];
|
||||
const previous = await assetStore.getPreviousAsset(a);
|
||||
const previous = await assetStore.getLaterAsset(a);
|
||||
expect(previous).toEqual(b);
|
||||
});
|
||||
|
||||
it('loads previous bucket', async () => {
|
||||
await assetStore.loadBucket('2024-02-01T00:00:00.000Z');
|
||||
|
||||
const loadBucketSpy = vi.spyOn(assetStore, 'loadBucket');
|
||||
const bucket = assetStore.getBucketByDate(2024, 2);
|
||||
const previousBucket = assetStore.getBucketByDate(2024, 3);
|
||||
const a = bucket!.getAssets()[0];
|
||||
const b = previousBucket!.getAssets()[0];
|
||||
const previous = await assetStore.getPreviousAsset(a);
|
||||
await assetStore.loadBucket({ year: 2024, month: 2 });
|
||||
const bucket = assetStore.getBucketByDate({ year: 2024, month: 2 });
|
||||
const previousBucket = assetStore.getBucketByDate({ year: 2024, month: 3 });
|
||||
const a = bucket!.getFirstAsset();
|
||||
const b = previousBucket!.getFirstAsset();
|
||||
const loadBucketSpy = vi.spyOn(bucket!.loader!, 'execute');
|
||||
const previousBucketSpy = vi.spyOn(previousBucket!.loader!, 'execute');
|
||||
const previous = await assetStore.getLaterAsset(a);
|
||||
expect(previous).toEqual(b);
|
||||
expect(loadBucketSpy).toBeCalledTimes(1);
|
||||
expect(loadBucketSpy).toBeCalledTimes(0);
|
||||
expect(previousBucketSpy).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('skips removed assets', async () => {
|
||||
await assetStore.loadBucket('2024-01-01T00:00:00.000Z');
|
||||
await assetStore.loadBucket('2024-02-01T00:00:00.000Z');
|
||||
await assetStore.loadBucket('2024-03-01T00:00:00.000Z');
|
||||
await assetStore.loadBucket({ year: 2024, month: 1 });
|
||||
await assetStore.loadBucket({ year: 2024, month: 2 });
|
||||
await assetStore.loadBucket({ year: 2024, month: 3 });
|
||||
|
||||
const [assetOne, assetTwo, assetThree] = assetStore.getAssets();
|
||||
const [assetOne, assetTwo, assetThree] = await getAssets(assetStore);
|
||||
assetStore.removeAssets([assetTwo.id]);
|
||||
expect(await assetStore.getPreviousAsset(assetThree)).toEqual(assetOne);
|
||||
expect(await assetStore.getLaterAsset(assetThree)).toEqual(assetOne);
|
||||
});
|
||||
|
||||
it('returns null when no more assets', async () => {
|
||||
await assetStore.loadBucket('2024-03-01T00:00:00.000Z');
|
||||
expect(await assetStore.getPreviousAsset(assetStore.getAssets()[0])).toBeUndefined();
|
||||
await assetStore.loadBucket({ year: 2024, month: 3 });
|
||||
expect(await assetStore.getLaterAsset(assetStore.buckets[0].getFirstAsset())).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -444,26 +473,37 @@ describe('AssetStore', () => {
|
||||
});
|
||||
|
||||
it('returns null for invalid buckets', () => {
|
||||
expect(assetStore.getBucketByDate(-1, -1)).toBeUndefined();
|
||||
expect(assetStore.getBucketByDate(2024, 3)).toBeUndefined();
|
||||
expect(assetStore.getBucketByDate({ year: -1, month: -1 })).toBeUndefined();
|
||||
expect(assetStore.getBucketByDate({ year: 2024, month: 3 })).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns the bucket index', () => {
|
||||
const assetOne = timelineAssetFactory.build({ localDateTime: '2024-01-20T12:00:00.000Z' });
|
||||
const assetTwo = timelineAssetFactory.build({ localDateTime: '2024-02-15T12:00:00.000Z' });
|
||||
const assetOne = timelineAssetFactory.build({
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-20T12:00:00.000Z'),
|
||||
});
|
||||
const assetTwo = timelineAssetFactory.build({
|
||||
localDateTime: fromLocalDateTimeToObject('2024-02-15T12:00:00.000Z'),
|
||||
});
|
||||
assetStore.addAssets([assetOne, assetTwo]);
|
||||
|
||||
expect(assetStore.getBucketIndexByAssetId(assetTwo.id)?.bucketDate).toEqual('2024-02-01T00:00:00.000Z');
|
||||
expect(assetStore.getBucketIndexByAssetId(assetOne.id)?.bucketDate).toEqual('2024-01-01T00:00:00.000Z');
|
||||
expect(assetStore.getBucketIndexByAssetId(assetTwo.id)?.yearMonth.year).toEqual(2024);
|
||||
expect(assetStore.getBucketIndexByAssetId(assetTwo.id)?.yearMonth.month).toEqual(2);
|
||||
expect(assetStore.getBucketIndexByAssetId(assetOne.id)?.yearMonth.year).toEqual(2024);
|
||||
expect(assetStore.getBucketIndexByAssetId(assetOne.id)?.yearMonth.month).toEqual(1);
|
||||
});
|
||||
|
||||
it('ignores removed buckets', () => {
|
||||
const assetOne = timelineAssetFactory.build({ localDateTime: '2024-01-20T12:00:00.000Z' });
|
||||
const assetTwo = timelineAssetFactory.build({ localDateTime: '2024-02-15T12:00:00.000Z' });
|
||||
const assetOne = timelineAssetFactory.build({
|
||||
localDateTime: fromLocalDateTimeToObject('2024-01-20T12:00:00.000Z'),
|
||||
});
|
||||
const assetTwo = timelineAssetFactory.build({
|
||||
localDateTime: fromLocalDateTimeToObject('2024-02-15T12:00:00.000Z'),
|
||||
});
|
||||
assetStore.addAssets([assetOne, assetTwo]);
|
||||
|
||||
assetStore.removeAssets([assetTwo.id]);
|
||||
expect(assetStore.getBucketIndexByAssetId(assetOne.id)?.bucketDate).toEqual('2024-01-01T00:00:00.000Z');
|
||||
expect(assetStore.getBucketIndexByAssetId(assetOne.id)?.yearMonth.year).toEqual(2024);
|
||||
expect(assetStore.getBucketIndexByAssetId(assetOne.id)?.yearMonth.month).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user