release: v0.19.0
Release v0.19.0. See the `## [0.19.0] — 2026-09-29` section of [CHANGELOG.md](https://github.com/ralksta/immich-folio/blob/dev/CHANGELOG.md) for the full list. Highlights: client proofing links (#683), custom content pages (#738), asset picker search (#740), concurrent-edit detection (#742), env-locked settings (#739), an accessibility baseline (#741), three security fixes with advisories (#737, #755, #759), and a pre-release quality pass (#780–#797). --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: ralksta <ralksta@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Ralf <rohlenbo79@gmail.com> Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com> Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com> Co-authored-by: Lance Martin <lancetaylor95@gmail.com>
This commit is contained in:
73
lib/__tests__/image-url-per-tier.test.ts
Normal file
73
lib/__tests__/image-url-per-tier.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { getImgProps } from 'next/dist/shared/lib/get-img-props';
|
||||
import { imageConfigDefault } from 'next/dist/shared/lib/image-config';
|
||||
import immichLoader from '../immichLoader';
|
||||
import { canonicalImageUrl, resolveImageSize } from '../imageSize';
|
||||
|
||||
/**
|
||||
* The proxy answers every width inside an Immich tier with the same bytes, but
|
||||
* each distinct URL is its own `immutable` cache entry. next/image used to
|
||||
* write a different `?w=` into every srcset candidate and the lightbox asked
|
||||
* for the bare URL, so a grid tile and the same photo in the lightbox — or a
|
||||
* tile before and after a rotation — downloaded the identical preview twice.
|
||||
*/
|
||||
|
||||
const PREVIEW = '/api/image/v2:tok?size=preview';
|
||||
const THUMB = '/api/image/v2:tok?size=thumbnail';
|
||||
|
||||
const ALL_WIDTHS = [...imageConfigDefault.deviceSizes, ...imageConfigDefault.imageSizes];
|
||||
|
||||
function srcSetUrls(src: string, sizes: string): string[] {
|
||||
const { props } = getImgProps(
|
||||
{ src, alt: '', fill: true, sizes },
|
||||
{
|
||||
defaultLoader: immichLoader,
|
||||
imgConf: { ...imageConfigDefault, loader: 'custom', loaderFile: './lib/immichLoader.ts' },
|
||||
showAltText: false,
|
||||
blurComplete: false,
|
||||
},
|
||||
);
|
||||
return (props.srcSet ?? '').split(', ').map((candidate) => candidate.split(' ')[0]);
|
||||
}
|
||||
|
||||
const tierOf = (url: string) => {
|
||||
const params = new URLSearchParams(url.slice(url.indexOf('?') + 1));
|
||||
return resolveImageSize(params.get('size'), params.get('w'));
|
||||
};
|
||||
|
||||
describe('one image URL per asset and tier', () => {
|
||||
it.each([
|
||||
['PhotoGrid', '(max-width: 600px) 50vw, (max-width: 1000px) 33vw, 25vw'],
|
||||
['SubpageGridView', '(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 33vw'],
|
||||
['album hero', '100vw'],
|
||||
['home hero', '(max-width: 640px) 100vw, 50vw'],
|
||||
['essay', '(max-width: 1100px) 100vw, 1100px'],
|
||||
])('every %s srcset candidate is the URL the lightbox opens', (_name, sizes) => {
|
||||
const urls = srcSetUrls(PREVIEW, sizes);
|
||||
expect(urls.length).toBeGreaterThan(3);
|
||||
expect(new Set(urls)).toEqual(new Set([canonicalImageUrl(PREVIEW)]));
|
||||
});
|
||||
|
||||
it('collapses a thumbnail-ceiling image to one URL as well', () => {
|
||||
expect(new Set(srcSetUrls(THUMB, '25vw'))).toEqual(new Set([canonicalImageUrl(THUMB)]));
|
||||
});
|
||||
|
||||
it('asks the proxy for the same rendition the raw width did', () => {
|
||||
for (const size of ['thumbnail', 'preview', 'original', null]) {
|
||||
const src = size ? `/api/image/v2:tok?size=${size}` : '/api/image/v2:tok';
|
||||
for (const w of [...ALL_WIDTHS, 1, 250, 251, 1440, 1441, 99999]) {
|
||||
expect(tierOf(canonicalImageUrl(src, w))).toBe(resolveImageSize(size, String(w)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps the cache buster and writes a width next/image accepts', () => {
|
||||
expect(canonicalImageUrl('/api/image/v2:tok?size=preview&v=7', 640)).toBe(
|
||||
'/api/image/v2:tok?size=preview&v=7&w=1440',
|
||||
);
|
||||
expect(canonicalImageUrl('https://cdn.example.com/api/image/v2:tok?size=thumbnail', 96)).toBe(
|
||||
'https://cdn.example.com/api/image/v2:tok?size=thumbnail&w=250',
|
||||
);
|
||||
expect(immichLoader({ src: PREVIEW, width: 400 })).not.toBe(PREVIEW);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user