Twenty-nine commits since v0.12.0. See CHANGELOG.md for the full list and the upgrade notes. Ten additions — the SEO package (site URL, robots.txt, sitemap.xml, JSON-LD), slideshow mode, opt-in original downloads, copy-link, previous/next album navigation, alt text from the Immich description, per-album location precision, the update notice, a Help tab, and a subpage cover grid separate from its photo grid. Two fixes: CJK album slugs, and the lightbox control bar. Photo of the Day (#476) was built on `dev` and reverted as not planned; it never appears in a release. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DmsHVRL9yQvckUB1ZpVWre --------- 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>
133 lines
4.3 KiB
TypeScript
133 lines
4.3 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
placeLabel,
|
|
isLocationPrecision,
|
|
strictestPrecision,
|
|
quantiseCoordinate,
|
|
applyPrecision,
|
|
LOCATION_PRECISIONS,
|
|
type LocationPrecision,
|
|
} from '@/lib/mapPrecision';
|
|
|
|
describe('isLocationPrecision', () => {
|
|
it.each(LOCATION_PRECISIONS)('accepts %s', (level) => {
|
|
expect(isLocationPrecision(level)).toBe(true);
|
|
});
|
|
|
|
it.each(['', 'EXACT', 'town', 'precise', 'none'])('rejects %s', (value) => {
|
|
expect(isLocationPrecision(value)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('strictestPrecision', () => {
|
|
it.each([
|
|
[['exact', 'city'], 'city'],
|
|
[['city', 'country'], 'country'],
|
|
[['exact', 'hidden'], 'hidden'],
|
|
[['country', 'city', 'exact'], 'country'],
|
|
[['exact'], 'exact'],
|
|
[['exact', 'exact'], 'exact'],
|
|
] as [LocationPrecision[], LocationPrecision][])('%s → %s', (levels, expected) => {
|
|
expect(strictestPrecision(levels)).toBe(expected);
|
|
});
|
|
|
|
it('is order-independent', () => {
|
|
expect(strictestPrecision(['country', 'exact'])).toBe(strictestPrecision(['exact', 'country']));
|
|
});
|
|
|
|
/** Nothing contributed, so there is nothing to place. */
|
|
it('treats an empty list as hidden', () => {
|
|
expect(strictestPrecision([])).toBe('hidden');
|
|
});
|
|
});
|
|
|
|
describe('quantiseCoordinate', () => {
|
|
it('leaves an exact coordinate untouched', () => {
|
|
expect(quantiseCoordinate(52.516272, 'exact')).toBe(52.516272);
|
|
});
|
|
|
|
it('snaps to 0.05° for city', () => {
|
|
expect(quantiseCoordinate(52.516272, 'city')).toBe(52.5);
|
|
expect(quantiseCoordinate(13.377704, 'city')).toBe(13.4);
|
|
});
|
|
|
|
it('snaps to 1° for country', () => {
|
|
expect(quantiseCoordinate(52.516272, 'country')).toBe(53);
|
|
expect(quantiseCoordinate(13.377704, 'country')).toBe(13);
|
|
});
|
|
|
|
it('leaves no floating-point tail', () => {
|
|
for (const value of [52.9123, 13.0001, -3.3333, 0.049]) {
|
|
const quantised = quantiseCoordinate(value, 'city');
|
|
expect(String(quantised)).toMatch(/^-?\d+(\.\d{1,2})?$/);
|
|
}
|
|
});
|
|
|
|
it('works south and west of zero', () => {
|
|
expect(quantiseCoordinate(-33.918861, 'city')).toBe(-33.9);
|
|
expect(quantiseCoordinate(-118.24368, 'country')).toBe(-118);
|
|
});
|
|
|
|
/**
|
|
* The property that matters: the output is a fixed grid, so asking twice
|
|
* cannot average back to the true position.
|
|
*/
|
|
it('is stable across repeated calls', () => {
|
|
const value = 47.376887;
|
|
const answers = new Set(Array.from({ length: 50 }, () => quantiseCoordinate(value, 'city')));
|
|
expect(answers.size).toBe(1);
|
|
});
|
|
|
|
it('places nearby points on the same grid node, so one album does not stand out', () => {
|
|
const garden = [52.5161, 52.5169, 52.5152].map((v) => quantiseCoordinate(v, 'city'));
|
|
expect(new Set(garden).size).toBe(1);
|
|
});
|
|
|
|
it('moves a coordinate far enough to stop naming a property', () => {
|
|
const exact = 52.516272;
|
|
expect(Math.abs(quantiseCoordinate(exact, 'city') - exact)).toBeGreaterThan(0.01);
|
|
});
|
|
});
|
|
|
|
describe('applyPrecision', () => {
|
|
const berlin = { lat: 52.516272, lng: 13.377704 };
|
|
|
|
it('quantises both axes', () => {
|
|
expect(applyPrecision(berlin, 'city')).toEqual({ lat: 52.5, lng: 13.4 });
|
|
});
|
|
|
|
it('passes an exact position through', () => {
|
|
expect(applyPrecision(berlin, 'exact')).toEqual(berlin);
|
|
});
|
|
|
|
/** Not {0,0} — the caller must drop the marker, not place it in the Atlantic. */
|
|
it('returns null for hidden', () => {
|
|
expect(applyPrecision(berlin, 'hidden')).toBeNull();
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Quantising the coordinates while still naming the city left the label more
|
|
* precise than the position it was meant to blur: a 1° grid cell is ~100 km,
|
|
* and "Kloster Chorin" is a village.
|
|
*/
|
|
describe('placeLabel', () => {
|
|
const place = { city: 'Chorin', country: 'Germany' };
|
|
|
|
it('keeps the city at exact', () => {
|
|
expect(placeLabel('exact', place)).toEqual({ city: 'Chorin', country: 'Germany' });
|
|
});
|
|
|
|
it('keeps the city at city precision, which is roughly what a city name says', () => {
|
|
expect(placeLabel('city', place)).toEqual({ city: 'Chorin', country: 'Germany' });
|
|
});
|
|
|
|
it('drops the city at country precision', () => {
|
|
expect(placeLabel('country', place)).toEqual({ city: '', country: 'Germany' });
|
|
});
|
|
|
|
it('drops both when the marker is hidden, so a caller cannot leak one by accident', () => {
|
|
expect(placeLabel('hidden', place)).toEqual({ city: '', country: '' });
|
|
});
|
|
});
|