mirror of
https://github.com/immich-app/immich
synced 2026-08-15 13:03:57 +00:00
fix(web): flag metadata fields missing from only some duplicates
This commit is contained in:
parent
0687c0d3f7
commit
01db549c7c
2 changed files with 95 additions and 3 deletions
89
web/src/lib/utils/duplicate-utils.spec.ts
Normal file
89
web/src/lib/utils/duplicate-utils.spec.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import type { AssetResponseDto, ExifResponseDto } from '@immich/sdk';
|
||||
import { computeDifferingMetadataFields, countDifferingMetadataItems } from '$lib/utils/duplicate-utils';
|
||||
import { assetFactory } from '@test-data/factories/asset-factory';
|
||||
|
||||
// Derives both assets from a single base so that everything except `exifInfo` is identical,
|
||||
// leaving the field under test as the only possible difference.
|
||||
const buildPair = (first: ExifResponseDto, second: ExifResponseDto): AssetResponseDto[] => {
|
||||
const base = assetFactory.build();
|
||||
return [
|
||||
{ ...base, id: 'first-asset', exifInfo: first },
|
||||
{ ...base, id: 'second-asset', exifInfo: second },
|
||||
];
|
||||
};
|
||||
|
||||
describe('computeDifferingMetadataFields', () => {
|
||||
it('flags coordinates when only one asset has them', () => {
|
||||
const assets = buildPair({ latitude: 48.8583, longitude: 2.2944 }, {});
|
||||
|
||||
const diffs = computeDifferingMetadataFields(assets);
|
||||
|
||||
expect(diffs.latitude).toBe(true);
|
||||
expect(diffs.longitude).toBe(true);
|
||||
});
|
||||
|
||||
it('does not flag coordinates when neither asset has them', () => {
|
||||
const assets = buildPair({}, {});
|
||||
|
||||
const diffs = computeDifferingMetadataFields(assets);
|
||||
|
||||
expect(diffs.latitude).toBe(false);
|
||||
expect(diffs.longitude).toBe(false);
|
||||
});
|
||||
|
||||
it('does not flag coordinates when both assets share them', () => {
|
||||
const coordinates = { latitude: 48.8583, longitude: 2.2944 };
|
||||
const assets = buildPair(coordinates, { ...coordinates });
|
||||
|
||||
const diffs = computeDifferingMetadataFields(assets);
|
||||
|
||||
expect(diffs.latitude).toBe(false);
|
||||
expect(diffs.longitude).toBe(false);
|
||||
});
|
||||
|
||||
it('flags coordinates when the assets disagree', () => {
|
||||
const assets = buildPair({ latitude: 48.8583, longitude: 2.2944 }, { latitude: 41.8902, longitude: 12.4922 });
|
||||
|
||||
const diffs = computeDifferingMetadataFields(assets);
|
||||
|
||||
expect(diffs.latitude).toBe(true);
|
||||
expect(diffs.longitude).toBe(true);
|
||||
});
|
||||
|
||||
it('treats an explicit null the same as an absent field', () => {
|
||||
const assets = buildPair({ description: null }, {});
|
||||
|
||||
const diffs = computeDifferingMetadataFields(assets);
|
||||
|
||||
expect(diffs.description).toBe(false);
|
||||
});
|
||||
|
||||
it('treats an empty string the same as an absent field', () => {
|
||||
const assets = buildPair({ description: '' }, { description: null });
|
||||
|
||||
const diffs = computeDifferingMetadataFields(assets);
|
||||
|
||||
expect(diffs.description).toBe(false);
|
||||
});
|
||||
|
||||
it('flags other nullable fields present on only one asset', () => {
|
||||
const assets = buildPair({ rating: 4, lensModel: 'EF24-70mm f/2.8L II USM', make: 'Canon' }, {});
|
||||
|
||||
const diffs = computeDifferingMetadataFields(assets);
|
||||
|
||||
expect(diffs.rating).toBe(true);
|
||||
expect(diffs.lensModel).toBe(true);
|
||||
expect(diffs.make).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('countDifferingMetadataItems', () => {
|
||||
it('counts a field that only one asset has', () => {
|
||||
const assets = buildPair({ latitude: 48.8583, longitude: 2.2944 }, {});
|
||||
|
||||
const diffs = computeDifferingMetadataFields(assets);
|
||||
|
||||
// latitude and longitude belong to the single "gps" field
|
||||
expect(countDifferingMetadataItems(diffs)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -27,6 +27,11 @@ import type { MessageFormatter } from 'svelte-i18n';
|
|||
import { getAssetResolution, getFileSize } from '$lib/utils/asset-utils';
|
||||
import { fromISODateTime, fromISODateTimeUTC } from '$lib/utils/timeline-util';
|
||||
|
||||
// Stands in for a value that is absent, so that "present on one asset, absent on another" counts as a difference.
|
||||
// Every value below collapses to it, since the metadata renderers fall back to "unknown" for each of them.
|
||||
const MISSING = Symbol('missing');
|
||||
const MISSING_VALUES = new Set<unknown>([undefined, null, '']);
|
||||
|
||||
const truncateMiddle = (path: string, maxLength = 50): string => {
|
||||
if (path.length <= maxLength) {
|
||||
return path;
|
||||
|
|
@ -289,9 +294,7 @@ export const computeDifferingMetadataFields = (assets: AssetResponseDto[]): Diff
|
|||
|
||||
for (const asset of assets) {
|
||||
const value = getValueForAsset(asset, key);
|
||||
if (value !== undefined && value !== null) {
|
||||
uniqueValues.add(normalizeForComparison(key, value));
|
||||
}
|
||||
uniqueValues.add(MISSING_VALUES.has(value) ? MISSING : normalizeForComparison(key, value));
|
||||
}
|
||||
|
||||
diffs[key] = uniqueValues.size > 1;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue