release: v0.20.0
This commit is contained in:
93
lib/__tests__/justified-row.test.ts
Normal file
93
lib/__tests__/justified-row.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { justifiedTileStyle, JUSTIFIED_FALLBACK_RATIO } from '../justifiedRow';
|
||||
|
||||
/**
|
||||
* Evaluates a flex-basis calc() the way the browser would, with the custom
|
||||
* properties a frame sets (or their fallbacks when it sets none).
|
||||
*/
|
||||
function basisPx(basis: string, vars: Record<string, number>): number {
|
||||
const expr = basis
|
||||
.replace(/var\(--([\w-]+),\s*([\d.]+)px\)/g, (_, name: string, fallback: string) =>
|
||||
String(vars[name] ?? Number(fallback)),
|
||||
)
|
||||
.replace(/^calc/, '');
|
||||
expect(expr).toMatch(/^[\d.\s()*+-]+$/);
|
||||
return Function(`return ${expr}`)() as number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lays out one row the way `flex-wrap` does for a row that fits: every tile
|
||||
* starts at its basis and the leftover width is shared by flex-grow.
|
||||
*/
|
||||
function layoutRow(ratios: number[], rowWidth: number, vars: Record<string, number>) {
|
||||
const tiles = ratios.map((r) => justifiedTileStyle(r));
|
||||
const bases = tiles.map((t) => basisPx(t.flexBasis, vars));
|
||||
const free = rowWidth - bases.reduce((a, b) => a + b, 0);
|
||||
const grow = tiles.reduce((a, t) => a + t.flexGrow, 0);
|
||||
const rowHeight = vars['grid-row-height'] ?? 300;
|
||||
return tiles.map((t, i) => {
|
||||
const width = bases[i] + (free * t.flexGrow) / grow;
|
||||
// The photo inside the tile, once the mat around it is taken off.
|
||||
const photoW = width - (vars['photo-mat-x'] ?? 0);
|
||||
const photoH = rowHeight - (vars['photo-mat-y'] ?? 0);
|
||||
return { width, photoW, photoH };
|
||||
});
|
||||
}
|
||||
|
||||
describe('justifiedTileStyle', () => {
|
||||
it('grows by the aspect ratio', () => {
|
||||
expect(justifiedTileStyle(0.75).flexGrow).toBe(0.75);
|
||||
});
|
||||
|
||||
it('falls back to ~3:2 for assets without dimensions', () => {
|
||||
for (const ar of [undefined, 0, -1, NaN, Infinity]) {
|
||||
expect(justifiedTileStyle(ar).flexGrow).toBe(JUSTIFIED_FALLBACK_RATIO);
|
||||
}
|
||||
});
|
||||
|
||||
it('is unchanged without a frame: row height times the ratio', () => {
|
||||
const basis = justifiedTileStyle(1.5).flexBasis;
|
||||
expect(basisPx(basis, {})).toBe(450);
|
||||
expect(basisPx(basis, { 'grid-row-height': 240 })).toBe(360);
|
||||
});
|
||||
|
||||
it('sizes the photo inside a passepartout mat, not the whole tile', () => {
|
||||
// 8px mat, 32px caption band, 1px border: 18px across, 42px down.
|
||||
const vars = { 'grid-row-height': 300, 'photo-mat-x': 18, 'photo-mat-y': 42 };
|
||||
const basis = basisPx(justifiedTileStyle(1.5).flexBasis, vars);
|
||||
expect(basis).toBe((300 - 42) * 1.5 + 18);
|
||||
});
|
||||
|
||||
it('keeps every photo in a filled row at its own aspect ratio, scaled alike', () => {
|
||||
const vars = { 'grid-row-height': 300, 'photo-mat-x': 18, 'photo-mat-y': 42 };
|
||||
const ratios = [1.5, 0.6667, 1.7778];
|
||||
// A row with no leftover width: every photo shows at exactly its ratio.
|
||||
const exact = ratios.reduce((a, r) => a + (300 - 42) * r + 18, 0);
|
||||
for (const tile of layoutRow(ratios, exact, vars).map(
|
||||
(t, i) => t.photoW / t.photoH / ratios[i],
|
||||
)) {
|
||||
expect(tile).toBeCloseTo(1, 10);
|
||||
}
|
||||
// A row with leftover width: every photo widens by the same factor, so a
|
||||
// portrait is not cropped harder than the landscapes beside it.
|
||||
const stretched = layoutRow(ratios, exact + 120, vars).map(
|
||||
(t, i) => t.photoW / t.photoH / ratios[i],
|
||||
);
|
||||
for (const s of stretched) expect(s).toBeCloseTo(stretched[0], 10);
|
||||
});
|
||||
|
||||
it('would crop unevenly if the tile, mat included, were sized from the row height', () => {
|
||||
// Regression guard for the formula itself: the old basis (row height ×
|
||||
// ratio, no mat terms) gives photos in the same row different stretch.
|
||||
const vars = { 'grid-row-height': 300, 'photo-mat-x': 18, 'photo-mat-y': 42 };
|
||||
const ratios = [1.5, 0.6667];
|
||||
const oldBases = ratios.map((r) => 300 * r);
|
||||
const rowWidth = oldBases.reduce((a, b) => a + b, 0);
|
||||
const oldStretch = oldBases.map((w, i) => (w - 18) / (300 - 42) / ratios[i]);
|
||||
expect(Math.abs(oldStretch[0] - oldStretch[1])).toBeGreaterThan(0.05);
|
||||
const nowStretch = layoutRow(ratios, rowWidth, vars).map(
|
||||
(t, i) => t.photoW / t.photoH / ratios[i],
|
||||
);
|
||||
expect(nowStretch[0]).toBeCloseTo(nowStretch[1], 10);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user