feat: media page T1

This commit is contained in:
fsylvain committed 2026-10-01 01:36:35 +02:00
1 parent 67d456baf6
commit 008b0dfbb9
46 files changed
+5742 -204

No files matched your search

+22 -27
View File
@@ -18,7 +18,7 @@ let toastAdd: ReturnType<typeof vi.fn>
vi.mock('@nuxt/ui/composables', async (importOriginal) => {
const actual = await importOriginal<typeof import('@nuxt/ui/composables')>()
return { ...actual, useToast: () => ({ add: toastAdd }) }
return { ...actual, useToast: () => ({ add: toastAdd, remove: vi.fn() }) }
})
type Row = [
@@ -455,32 +455,28 @@ describe('Default, many images', () => {
expect(document.body.querySelector('[data-testid="image-count"]')?.textContent).toBe(
'12 images',
)
expect(document.body.textContent).not.toContain('Select all')
expect(document.body.querySelector('input[type="checkbox"], [role="checkbox"]')).toBeNull()
expect(document.body.textContent).toContain('Select all')
expect(document.body.querySelectorAll('[role="checkbox"]')).toHaveLength(13)
expect(searchInput()).toBeTruthy()
expect(textOf(ariaLabel('Sort'))).toBe('Newest')
expect(ariaLabel('Grid view').getAttribute('aria-pressed')).toBe('true')
expect(ariaLabel('List view').getAttribute('aria-pressed')).toBe('false')
})
it('renders the header Upload button disabled and inert', async () => {
it('renders the header Upload button enabled', async () => {
await mountPage()
const upload = findButton('Upload')!
expect(upload.disabled).toBe(true)
upload.click()
await flushPromises()
expect(panel()).toBeNull()
expect(findButton('Upload')!.disabled).toBe(false)
})
it('renders the mobile pinned Upload button disabled, full width, 48 px', async () => {
it('renders the mobile pinned Upload button enabled, full width, 48 px', async () => {
await mountPage({}, '/media', MOBILE_WIDTH)
const buttons = Array.from(document.body.querySelectorAll('button')).filter(
(button) => textOf(button) === 'Upload',
)
expect(buttons).toHaveLength(1)
expect(buttons[0]!.disabled).toBe(true)
expect(buttons[0]!.disabled).toBe(false)
expect(buttons[0]!.className).toContain('h-12')
expect(buttons[0]!.className).toContain('inset-x-4')
expect(document.body.querySelector('nav[aria-label="breadcrumb"]')?.className).toContain(
@@ -614,7 +610,7 @@ describe('The query string is the source of truth', () => {
['?q=wall', 'q=wall&filter=all&sort=newest&limit=100'],
['?view=list', 'filter=all&sort=newest&limit=100'],
['?filter=bogus&sort=zzz', 'filter=all&sort=newest&limit=100'],
['?filter=duplicates', 'filter=all&sort=newest&limit=100'],
['?filter=duplicates', 'filter=duplicates&sort=newest&limit=100'],
])('opening /media%s requests %s', async (url, request) => {
await mountPage({}, `/media${url}`)
@@ -638,7 +634,7 @@ describe('The query string is the source of truth', () => {
document.body.innerHTML = ''
await mountPage({}, '/media?filter=duplicates')
expect(tab('All').getAttribute('aria-selected')).toBe('true')
expect(tab('Duplicates').getAttribute('aria-selected')).toBe('true')
})
it('drops unknown values from the URL', async () => {
@@ -703,23 +699,20 @@ describe('The query string is the source of truth', () => {
})
})
describe('The disabled Duplicates tab', () => {
it('does not become selected, does not navigate and sends no request', async () => {
describe('The Duplicates tab', () => {
it('becomes selected, writes the filter to the URL and requests the duplicates', async () => {
await mountPage()
const duplicates = tab('Duplicates')
expect(duplicates.hasAttribute('disabled')).toBe(true)
expect(duplicates.hasAttribute('data-disabled')).toBe(true)
expect(duplicates.hasAttribute('disabled')).toBe(false)
duplicates.focus()
duplicates.click()
duplicates.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, button: 0 }))
press(duplicates, 'Enter')
duplicates.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, button: 0 }))
await flushPromises()
expect(duplicates.getAttribute('aria-selected')).toBe('false')
expect(tab('All').getAttribute('aria-selected')).toBe('true')
expect(router.currentRoute.value.query).toEqual({})
expect(listCalls()).toHaveLength(1)
expect(tab('Duplicates').getAttribute('aria-selected')).toBe('true')
expect(router.currentRoute.value.query).toEqual({ filter: 'duplicates' })
expect(lastListPath()).toBe('/v1/media/?filter=duplicates&sort=newest&limit=100')
})
})
@@ -799,7 +792,7 @@ describe('Empty library and no results', () => {
expect(document.body.textContent).toContain(
'Upload your first image. You can use it in any article.',
)
expect(findButton('Upload an image')!.disabled).toBe(true)
expect(findButton('Upload an image')!.disabled).toBe(false)
expect(document.body.textContent).toContain('PNG, JPEG, WEBP, SVG · max 10 MB each')
})
@@ -885,7 +878,7 @@ describe('Hover, focus and keyboard', () => {
expect(document.activeElement).toBe(list[2])
})
it('opens the panel on Enter and does nothing on Space', async () => {
it('opens the panel on Enter and Space does not open it', async () => {
await mountPage()
cards()[1]!.focus()
@@ -1000,7 +993,7 @@ describe('Detail panel', () => {
expect(textOf(panel())).toContain('This image is safe to delete. No article shows it.')
})
it('duplicate: looks up the hash, excludes the image itself and disables Review duplicates', async () => {
it('duplicate: looks up the hash, excludes the image itself and Review duplicates opens the tab', async () => {
await mountPage()
await clickCard('floorboards.jpg')
@@ -1012,10 +1005,12 @@ describe('Detail panel', () => {
'floorboards-copy.jpg and IMG_2208.jpg have the same content.',
)
const review = findButton('Review duplicates', alert)!
expect(review.disabled).toBe(true)
expect(review.disabled).toBe(false)
review.click()
await flushPromises()
expect(router.currentRoute.value.path).toBe('/media')
expect(router.currentRoute.value.query).toEqual({ filter: 'duplicates' })
expect(panel()).toBeNull()
})
it('duplicate: uses the singular for one other copy', async () => {