Files
immich-folio/lib/__tests__/zoomSource.test.ts

144 lines
5.2 KiB
TypeScript
Raw Normal View History

2026-10-02 18:14:23 +02:00
import { describe, it, expect } from 'vitest';
import { zoomDimensions, zoomSourceFor } from '../zoomSource';
/**
* Which file the zoom may show (#467). The rule that matters most is the
* negative one: a format whose metadata the scrubber does not edit must never
* be served, whatever a browser could display.
*/
describe('zoomSourceFor', () => {
const image = (originalMimeType: string | null, originalFileName = 'x') => ({
type: 'IMAGE',
originalMimeType,
originalFileName,
});
it('serves JPEG and AVIF originals, which the scrubber cleans', () => {
expect(zoomSourceFor(image('image/jpeg'))).toBe('original');
expect(zoomSourceFor(image('image/jpg'))).toBe('original');
expect(zoomSourceFor(image('image/avif'))).toBe('original');
expect(zoomSourceFor(image('IMAGE/JPEG; charset=binary'))).toBe('original');
});
it('uses Immich’s rendition for what a browser cannot show', () => {
for (const mime of [
'image/heic',
'image/heif',
'image/x-adobe-dng',
'image/x-sony-arw',
'image/tiff',
'image/jxl',
'image/bmp',
]) {
expect(zoomSourceFor(image(mime)), mime).toBe('fullsize');
}
});
it('never zooms PNG, WebP or GIF: their metadata would go out unscrubbed', () => {
for (const mime of ['image/png', 'image/webp', 'image/gif', 'image/svg+xml']) {
expect(zoomSourceFor(image(mime)), mime).toBeNull();
}
});
it('never zooms a video', () => {
expect(
zoomSourceFor({
type: 'VIDEO',
originalMimeType: 'video/quicktime',
originalFileName: 'a.mov',
}),
).toBeNull();
// Even one mislabelled as an image type.
expect(zoomSourceFor({ type: 'VIDEO', originalMimeType: 'image/jpeg' })).toBeNull();
});
it('falls back to the file extension without a MIME type', () => {
expect(zoomSourceFor(image(null, 'L1001783.JPG'))).toBe('original');
expect(zoomSourceFor(image(null, 'export.avif'))).toBe('original');
expect(zoomSourceFor(image(null, 'IMG_6109.HEIC'))).toBe('fullsize');
expect(zoomSourceFor(image(null, 'scan.png'))).toBeNull();
expect(zoomSourceFor(image(null, 'no-extension'))).toBeNull();
});
it('zooms a photo edited in Immich into its edited rendition (#831)', () => {
// Immich serves the unedited file as the original: a crop would come
// undone. The edited full-size rendition matches the edited preview.
expect(zoomSourceFor({ ...image('image/jpeg'), isEdited: true })).toBe('edited');
expect(zoomSourceFor({ ...image('image/heic'), isEdited: true })).toBe('edited');
expect(zoomSourceFor({ ...image('image/jpeg'), isEdited: false })).toBe('original');
expect(zoomSourceFor({ ...image('image/heic'), isEdited: false })).toBe('fullsize');
// Edits never make a video zoomable.
expect(
zoomSourceFor({ type: 'VIDEO', originalMimeType: 'video/mp4', isEdited: true }),
).toBeNull();
});
it('refuses a non-image MIME type on an image asset', () => {
expect(zoomSourceFor(image('application/octet-stream'))).toBeNull();
});
});
describe('zoomDimensions', () => {
it('gives an edited photo without its edited size no size at all (review of #832)', () => {
// EXIF describes the unedited file; a rotated photo would get the box of
// the unrotated one. No size means no zoom.
expect(
zoomDimensions({
isEdited: true,
exifInfo: { exifImageWidth: 1076, exifImageHeight: 723, orientation: null },
}),
).toBeUndefined();
});
it('gives an edited photo its edited size, which is what its rendition has (#831)', () => {
// Measured on Immich 3.2: an iPhone HEIC stored 5712×4284 with
// orientation 6, turned back to landscape in Immich's editor. The edited
// full-size rendition is 5712×4284; EXIF alone would say 4284×5712.
expect(
zoomDimensions({
width: 5712,
height: 4284,
exifInfo: { exifImageWidth: 5712, exifImageHeight: 4284, orientation: '6' },
}),
).toEqual({ width: 5712, height: 4284 });
// A crop: stored 8064×6048 (orientation 6), cropped to 6048×4838.
expect(
zoomDimensions({
width: 6048,
height: 4838,
exifInfo: { exifImageWidth: 8064, exifImageHeight: 6048, orientation: '6' },
}),
).toEqual({ width: 6048, height: 4838 });
});
it('prefers Immich’s upright width and height', () => {
expect(
zoomDimensions({
width: 3024,
height: 4032,
exifInfo: { exifImageWidth: 4032, exifImageHeight: 3024, orientation: '6' },
}),
).toEqual({ width: 3024, height: 4032 });
});
it('turns EXIF dimensions for a quarter-turn orientation', () => {
expect(
zoomDimensions({
exifInfo: { exifImageWidth: 4032, exifImageHeight: 3024, orientation: '6' },
}),
).toEqual({ width: 3024, height: 4032 });
expect(
zoomDimensions({
exifInfo: { exifImageWidth: 7242, exifImageHeight: 4294, orientation: null },
}),
).toEqual({ width: 7242, height: 4294 });
});
it('is undefined when nothing is known', () => {
expect(zoomDimensions({})).toBeUndefined();
expect(
zoomDimensions({ exifInfo: { exifImageWidth: 0, exifImageHeight: 10 } }),
).toBeUndefined();
});
});