feat(SPA): media page T0

This commit is contained in:
fsylvain committed 2026-09-30 19:45:47 +02:00
1 parent 0d99544579
commit 67d456baf6
32 files changed
+4874 -40

No files matched your search

+97
View File
@@ -0,0 +1,97 @@
import { describe, expect, it } from 'vitest'
import {
accessibleName,
formatDimensions,
formatSize,
formatType,
isValidFilenameStem,
splitFilename,
usedInLabel,
} from '../mediaFormat'
const base = {
id: 1,
filename: 'north-wall.jpg',
url: '/media/north-wall.jpg',
size: 4089446,
content_type: 'image/jpeg',
width: 4032,
height: 3024,
sha256: 'a',
uploaded_by: 'Marta Alves',
created_at: '2026-09-12T12:00:00Z',
used_in: 2,
duplicate: false,
}
describe('formatSize', () => {
it.each([
[8192, '8 KB'],
[880640, '860 KB'],
[4089446, '3.9 MB'],
[1048576, '1.0 MB'],
[1048575, '1024 KB'],
])('reads %i bytes as %s', (bytes, expected) => {
expect(formatSize(bytes)).toBe(expected)
})
})
describe('formatDimensions', () => {
it('reads width and height with a multiplication sign', () => {
expect(formatDimensions(4032, 3024)).toBe('4032 × 3024')
})
it('reads Vector when width and height are null', () => {
expect(formatDimensions(null, null)).toBe('Vector')
})
})
describe('formatType', () => {
it.each([
['image/jpeg', 'JPEG'],
['image/png', 'PNG'],
['image/webp', 'WEBP'],
['image/svg+xml', 'SVG'],
])('reads %s as %s', (contentType, expected) => {
expect(formatType(contentType)).toBe(expected)
})
})
describe('usedInLabel and accessibleName', () => {
it('pluralises the article count', () => {
expect(usedInLabel(1)).toBe('used in 1 article')
expect(usedInLabel(2)).toBe('used in 2 articles')
expect(usedInLabel(0)).toBe('unused')
})
it('builds the card name from filename, size and usage', () => {
expect(accessibleName(base)).toBe('north-wall.jpg, 3.9 MB, used in 2 articles')
expect(accessibleName({ ...base, used_in: 1 })).toBe(
'north-wall.jpg, 3.9 MB, used in 1 article',
)
expect(accessibleName({ ...base, used_in: 0 })).toBe('north-wall.jpg, 3.9 MB, unused')
})
})
describe('splitFilename', () => {
it('separates the stem from the extension', () => {
expect(splitFilename('north-wall.jpg')).toEqual({ stem: 'north-wall', extension: '.jpg' })
expect(splitFilename('a.b.webp')).toEqual({ stem: 'a.b', extension: '.webp' })
expect(splitFilename('noextension')).toEqual({ stem: 'noextension', extension: '' })
})
})
describe('isValidFilenameStem', () => {
it.each(['north-wall', 'north-wall-01', 'Été_2026', ' padded '])('accepts %j', (value) => {
expect(isValidFilenameStem(value)).toBe(true)
})
it.each(['', ' ', '---', '!!!', 'a b', 'a.b', '___'])('rejects %j', (value) => {
expect(isValidFilenameStem(value)).toBe(false)
})
it('rejects more than 100 characters', () => {
expect(isValidFilenameStem('a'.repeat(100))).toBe(true)
expect(isValidFilenameStem('a'.repeat(101))).toBe(false)
})
})
+51
View File
@@ -0,0 +1,51 @@
import { describe, expect, it } from 'vitest'
import { DEFAULT_CONTROLS, parseMediaControls, toRequestQuery, toRouteQuery } from '../mediaQuery'
describe('parseMediaControls', () => {
it('uses the defaults for an empty query', () => {
expect(parseMediaControls({})).toEqual(DEFAULT_CONTROLS)
})
it.each([
[{ filter: 'unused' }, { filter: 'unused' }],
[{ sort: 'largest' }, { sort: 'largest' }],
[{ sort: 'name' }, { sort: 'name' }],
[{ sort: 'oldest' }, { sort: 'oldest' }],
[{ q: 'wall' }, { q: 'wall' }],
[{ view: 'list' }, { view: 'list' }],
[{ filter: 'bogus', sort: 'zzz' }, {}],
[{ filter: 'duplicates' }, {}],
[{ view: 'grid' }, {}],
[{ sort: 'newest' }, {}],
])('maps %j', (query, expected) => {
expect(parseMediaControls(query)).toEqual({ ...DEFAULT_CONTROLS, ...expected })
})
it('takes the first value of a repeated parameter and trims the search', () => {
expect(parseMediaControls({ q: [' wall ', 'x'] }).q).toBe('wall')
})
})
describe('toRouteQuery', () => {
it('drops every default value', () => {
expect(toRouteQuery(DEFAULT_CONTROLS)).toEqual({})
})
it('keeps only the non-default values', () => {
expect(
toRouteQuery({ q: 'wall', filter: 'unused', sort: 'largest', view: 'list' }),
).toEqual({ q: 'wall', filter: 'unused', sort: 'largest', view: 'list' })
})
})
describe('toRequestQuery', () => {
it.each([
[{}, 'filter=all&sort=newest&limit=100'],
[{ filter: 'unused' }, 'filter=unused&sort=newest&limit=100'],
[{ sort: 'largest' }, 'filter=all&sort=largest&limit=100'],
[{ q: 'wall' }, 'q=wall&filter=all&sort=newest&limit=100'],
[{ q: 'a b&c', filter: 'unused' }, 'q=a%20b%26c&filter=unused&sort=newest&limit=100'],
])('builds the request for %j', (overrides, expected) => {
expect(toRequestQuery({ ...DEFAULT_CONTROLS, ...overrides })).toBe(expected)
})
})