Files
immich-folio/lib/__tests__/zoomSource.test.ts
2026-10-02 18:14:23 +02:00

144 lines
5.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
});
});