Files
RenewCMS/web/src/views/__tests__/MediaVariantsTab.spec.ts
T

784 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import UButton from '@nuxt/ui/components/Button.vue'
import router from '@/router'
import { ApiError, NetworkError } from '@/composables/useApi'
import { MOBILE_WIDTH } from '@/components/__tests__/viewport'
import {
NORTH_WALL_BYTES,
callsTo,
cardFor,
cards,
deferred,
findButton,
kit,
listBody,
fixture,
mountPage,
panel,
press,
tab,
tabs,
textOf,
unmountPage,
variantItems,
variantList,
} from './mediaTestKit'
import { savedPercent, signedPercent } from '@/lib/variants/variantSaving'
vi.mock('@/composables/useApi', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/composables/useApi')>()
return { ...actual, useApi: vi.fn() }
})
const toasts = vi.hoisted(() => ({ add: vi.fn(), remove: vi.fn(), update: vi.fn() }))
vi.mock('@nuxt/ui/composables', async (importOriginal) => {
const actual = await importOriginal<typeof import('@nuxt/ui/composables')>()
return { ...actual, useToast: () => toasts }
})
const PARTLY = ['card', 'hero', 'square', 'thumb']
const SVG_LIST = { unsupported: 'svg', items: [] }
const writeText = vi.fn()
function mountTab(...[handlers, url, width, options]: Parameters<typeof mountPage>) {
return mountPage(handlers, url, width, { ...options, app: true })
}
function toastWith(id: string) {
return toasts.add.mock.calls.map(([content]) => content).find((content) => content.id === id)
}
function lastToast() {
return toasts.add.mock.calls.at(-1)![0]
}
async function openVariants(filename = 'north-wall.jpg') {
cardFor(filename).click()
await flushPromises()
activateTab('Variants')
await flushPromises()
}
function activateTab(label: string) {
const target = tab(label)
target.focus()
press(target, 'Enter')
}
function body() {
return panel()!.querySelector<HTMLElement>('[data-testid="variants-tab"]')!
}
function rows() {
return Array.from(body().querySelectorAll<HTMLElement>('[data-testid="variant-row"]'))
}
function rowOf(name: string) {
return rows().find((row) => row.getAttribute('data-variant') === name)!
}
function rowAction(name: string, label: string) {
return Array.from(rowOf(name).querySelectorAll<HTMLButtonElement>('button')).find(
(button) => (button.getAttribute('aria-label') ?? textOf(button)) === label,
)
}
function summary() {
return textOf(
Array.from(body().querySelectorAll('span')).find((span) =>
/of \d+ generated/.test(span.textContent ?? ''),
),
)
}
function generateAllButton() {
return findButton('Generate all', body()) ?? findButton('Generating…', body())
}
function variantRequests(method = 'GET') {
return callsTo(method, /\/v1\/media\/1\/variants$/)
}
beforeEach(() => {
toasts.add.mockClear()
writeText.mockReset().mockResolvedValue(undefined)
Object.defineProperty(navigator, 'clipboard', { value: { writeText }, configurable: true })
vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(true)
})
afterEach(unmountPage)
describe('Saving percent', () => {
it.each([
[18432, '-99%'],
[65536, '-98%'],
[98304, '-98%'],
[145408, '-96%'],
[3250586, '-21%'],
[4089446, '0%'],
[4500000, '+10%'],
])('Saving against the original for %i bytes reads %s', (size, text) => {
expect(signedPercent(NORTH_WALL_BYTES, size)).toBe(text)
})
it('never shows -100% for a non-empty file', () => {
expect(savedPercent(NORTH_WALL_BYTES, 1)).toBe(-99)
expect(savedPercent(NORTH_WALL_BYTES, 0)).toBe(-100)
})
it('rounds half away from zero', () => {
expect(savedPercent(200, 101)).toBe(-50)
expect(savedPercent(200, 301)).toBe(51)
expect(savedPercent(0, 10)).toBe(0)
})
})
describe('The panel tabs', () => {
it('gives an owner two tabs under the header with the first selected', async () => {
await mountTab({ variants: () => variantList() })
cardFor('north-wall.jpg').click()
await flushPromises()
const labels = tabs()
.map(textOf)
.filter((label) => label === 'Details' || label === 'Variants')
expect(labels).toEqual(['Details', 'Variants'])
expect(tab('Details').getAttribute('aria-selected')).toBe('true')
expect(tab('Variants').className).toContain('h-10')
expect(tab('Variants').className).toContain('max-md:h-11')
})
it('gives an admin the tabs too', async () => {
await mountTab({ variants: () => variantList() }, '/media', undefined, { role: 'admin' })
cardFor('north-wall.jpg').click()
await flushPromises()
expect(tab('Variants')).toBeDefined()
})
it('A session that is not owner or admin sees no tab and sends no request', async () => {
await mountTab({}, '/media', undefined, { role: 'user' })
cardFor('north-wall.jpg').click()
await flushPromises()
expect(tabs().filter((entry) => textOf(entry) === 'Variants')).toHaveLength(0)
expect(textOf(panel())).toContain('Copy URL')
expect(variantRequests()).toHaveLength(0)
})
it('Selecting the tab requests the variants once per open panel', async () => {
await mountTab({ variants: () => variantList(PARTLY) })
cardFor('north-wall.jpg').click()
await flushPromises()
expect(variantRequests()).toHaveLength(0)
activateTab('Variants')
await flushPromises()
activateTab('Details')
await flushPromises()
activateTab('Variants')
await flushPromises()
expect(variantRequests()).toHaveLength(1)
expect(textOf(panel())).toContain('Original · kept untouched')
})
it('keeps the Details body unchanged', async () => {
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
activateTab('Details')
await flushPromises()
const details = panel()!.querySelector<HTMLElement>('dl')!
expect(textOf(details)).toContain('JPEG')
expect(details.closest<HTMLElement>('[style*="display: none"]')).toBeNull()
})
})
describe('Loading and failing', () => {
it('draws nothing at 149 ms and five skeleton rows with aria-busy at 150 ms', async () => {
const pending = deferred<unknown>()
await mountTab({ variants: () => pending.promise })
cardFor('north-wall.jpg').click()
await flushPromises()
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
activateTab('Variants')
await flushPromises()
vi.advanceTimersByTime(149)
await flushPromises()
expect(body().children).toHaveLength(1)
expect(body().querySelector('[aria-busy="true"]')).toBeNull()
vi.advanceTimersByTime(1)
await flushPromises()
const region = body().querySelector<HTMLElement>('[aria-busy="true"]')!
expect(region.getAttribute('aria-label')).toBe('Loading variants')
expect(region.querySelectorAll('[data-testid="variant-skeleton-row"]')).toHaveLength(5)
vi.useRealTimers()
})
it.each([
['status 500', () => Promise.reject(new ApiError(500))],
['a network failure', () => Promise.reject(new NetworkError())],
])('The variants request fails with %s and Try again sends it again', async (_label, fail) => {
await mountTab({ variants: fail })
await openVariants()
const alert = body().querySelector<HTMLElement>('[role="alert"]')!
expect(textOf(alert)).toContain("Couldn't load variants")
expect(textOf(alert)).toContain('Check your connection and try again.')
const retry = findButton('Try again', alert)!
expect(retry).toBeDefined()
retry.click()
await flushPromises()
expect(variantRequests()).toHaveLength(2)
})
})
describe('The list', () => {
it('shows the Original block, the summary and the list when partly generated', async () => {
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
const children = Array.from(body().children)
expect(children[0]!.getAttribute('data-testid')).toBe('variants-original')
expect(textOf(children[0])).toBe(
'Original · kept untouched north-wall.jpg JPEG · 4032 × 3024 · 3.9 MB',
)
expect(summary()).toBe('4 of 5 generated')
expect(findButton('Generate all', body())).toBeDefined()
expect(rows().map((row) => row.getAttribute('data-variant'))).toEqual([
'card',
'hero',
'og',
'square',
'thumb',
])
})
it.each([
['thumb', '400×300 · WebP · 18 KB · -99% vs original'],
['card', '800×600 · WebP · 64 KB · -98% vs original'],
['hero', '1200×630 · WebP · 96 KB · -98% vs original'],
['square', '600×600 · JPEG · 82 KB · -98% vs original'],
])('A generated row: %s', async (name, meta) => {
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
const row = rowOf(name)
expect(textOf(row)).toContain('Generated')
expect(textOf(row)).toContain(meta)
const saving = Array.from(row.querySelectorAll('span')).find((span) =>
span.textContent?.endsWith('vs original'),
)!
expect(saving.className).toContain('text-success')
expect(row.querySelector('.font-mono')!.className).toContain('font-medium')
})
it('A not generated row', async () => {
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
const row = rowOf('og')
expect(textOf(row)).toContain('Not generated')
expect(textOf(row)).toContain('1200×630 · JPEG')
expect(textOf(row)).not.toContain('vs original')
expect(textOf(row)).not.toContain('KB')
})
it('Nothing generated yet', async () => {
await mountTab({ variants: () => variantList([]) })
await openVariants()
expect(textOf(body())).toContain('0 of 5 generated')
expect(textOf(body())).toContain('Nothing generated yet.')
expect(rows().every((row) => textOf(row).includes('Not generated'))).toBe(true)
})
it('No variants defined', async () => {
await mountTab({ variants: () => ({ unsupported: null, items: [] }) })
await openVariants()
expect(textOf(body())).toContain('No variants defined')
expect(textOf(body())).toContain(
'Define a variant in Settings to get resized copies of this image.',
)
const link = body().querySelector<HTMLAnchorElement>('a')!
expect(textOf(link)).toBe('Settings → Media → Variants')
expect(link.getAttribute('href')).toBe('/settings/media/variants')
expect(rows()).toHaveLength(0)
expect(generateAllButton()).toBeUndefined()
expect(body().querySelector('[data-testid="variants-original"]')).not.toBeNull()
})
it('An SVG source', async () => {
await mountTab({ variants: () => SVG_LIST })
cardFor('logo-renew.svg').click()
await flushPromises()
activateTab('Variants')
await flushPromises()
expect(textOf(body().firstElementChild)).toContain('SVG · Vector · 8 KB')
const alert = body().querySelector('[class*="bg-(--rc-info-soft)"], [data-slot="root"]')
expect(textOf(body())).toContain("Variants aren't generated for this format.")
expect(textOf(body())).toContain('SVG images are served as they are.')
expect(alert).not.toBeNull()
expect(rows()).toHaveLength(0)
expect(generateAllButton()).toBeUndefined()
})
})
describe('Row actions', () => {
it('shows icon buttons with accessible names on desktop', async () => {
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
const generated = Array.from(rowOf('thumb').querySelectorAll('button')).map((button) =>
button.getAttribute('aria-label'),
)
expect(generated).toEqual(['Copy URL', 'Preview', 'Purge'])
const missing = Array.from(rowOf('og').querySelectorAll('button')).map((button) =>
button.getAttribute('aria-label'),
)
expect(missing).toEqual(['Copy URL', 'Generate now'])
expect(rowAction('thumb', 'Copy URL')!.className).toContain('size-7')
})
it('shows labelled 44 px buttons on their own line below 640 px', async () => {
await mountTab({ variants: () => variantList(PARTLY) }, '/media', MOBILE_WIDTH)
await openVariants()
const buttons = Array.from(rowOf('thumb').querySelectorAll('button'))
expect(buttons.map(textOf)).toEqual(['Copy URL', 'Preview', 'Purge'])
expect(buttons.every((button) => button.className.includes('h-11'))).toBe(true)
expect(textOf(rowAction('og', 'Generate now'))).toBe('Generate now')
})
it('disables Generate all when everything is generated', async () => {
await mountTab({ variants: () => variantList() })
await openVariants()
expect(generateAllButton()!.disabled).toBe(true)
})
it('shows an icon and a word for every status', async () => {
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
const icons = kit
.wrapper!.findAllComponents({ name: 'Badge' })
.filter((badge) => badge.element.hasAttribute('data-status'))
.map((badge) => [badge.attributes('data-status'), badge.props('icon')])
expect(icons).toContainEqual(['generated', 'i-lucide-circle-check'])
expect(icons).toContainEqual(['missing', 'i-lucide-circle-dashed'])
})
})
describe('Generating', () => {
it('Generate now shows the row in flight and then generated with a toast', async () => {
const pending = deferred<unknown>()
await mountTab({
variants: () => variantList(PARTLY),
generate: () => pending.promise,
})
await openVariants()
rowAction('og', 'Generate now')!.click()
await flushPromises()
expect(callsTo('POST', /\/v1\/media\/1\/variants\/og$/)).toHaveLength(1)
expect(textOf(rowOf('og'))).toContain('Generating')
expect(rowOf('og').querySelectorAll('button')).toHaveLength(0)
expect(rowOf('og').className).toContain('bg-elevated')
expect(rowOf('hero').className).not.toContain('bg-elevated')
expect(textOf(body())).toContain('Generating variants…')
const all = generateAllButton()!
expect(textOf(all)).toBe('Generating…')
expect(all.disabled).toBe(true)
pending.resolve(variantItems().find((item) => item.name === 'og'))
await flushPromises()
expect(textOf(rowOf('og'))).toContain('Generated')
expect(summary()).toBe('5 of 5 generated')
expect(toastWith('media-variant-generated-1-og')).toMatchObject({
icon: 'i-lucide-circle-check',
title: 'Generated og',
})
})
it('Generate all shows the rows in flight and toasts the count', async () => {
const pending = deferred<unknown>()
await mountTab({
variants: () => variantList(['thumb']),
generateAll: () => pending.promise,
})
await openVariants()
generateAllButton()!.click()
await flushPromises()
expect(variantRequests('POST')).toHaveLength(1)
expect(
rows()
.filter((row) => textOf(row).includes('Generating'))
.map((row) => row.getAttribute('data-variant')),
).toEqual(['card', 'hero', 'og', 'square'])
expect(textOf(rowOf('thumb'))).toContain('Generated')
pending.resolve({ generated: 4, failed: 0, failures: [], items: variantItems() })
await flushPromises()
expect(rows().every((row) => textOf(row).includes('Generated'))).toBe(true)
expect(toastWith('media-variants-generated-1')).toMatchObject({
title: 'Generated 4 variants',
})
})
it('A single generated variant reads in the singular', async () => {
await mountTab({
variants: () => variantList(PARTLY),
generateAll: () => ({ generated: 1, failed: 0, failures: [], items: variantItems() }),
})
await openVariants()
generateAllButton()!.click()
await flushPromises()
expect(toastWith('media-variants-generated-1')).toMatchObject({
title: 'Generated 1 variant',
})
})
it('Generate all with a failure marks the row with its cause and Retry', async () => {
const items = variantItems().map((item) =>
item.name === 'hero' ? { ...item, generated: false, size: null } : item,
)
await mountTab({
variants: () => variantList(['thumb']),
generateAll: () => ({
generated: 4,
failed: 1,
failures: [{ name: 'hero', cause: 'too_large' }],
items,
}),
})
await openVariants()
generateAllButton()!.click()
await flushPromises()
const row = rowOf('hero')
expect(textOf(row)).toContain('Failed')
expect(textOf(row)).toContain('Image too large')
const retry = findButton('Retry', row)!
expect(retry.className).toContain('h-7')
expect(retry.className).toContain('text-xs')
expect(summary()).toBe('4 of 5 generated')
expect(toastWith('media-variants-generated-1')).toMatchObject({
title: 'Generated 4 variants',
})
})
it.each([
[413, 'Image too large'],
[415, 'Unsupported source format'],
])('A single generation fails with %i', async (status, cause) => {
await mountTab({
variants: () => variantList(PARTLY),
generate: () => Promise.reject(new ApiError(status, undefined, 'plain text body')),
})
await openVariants()
rowAction('og', 'Generate now')!.click()
await flushPromises()
expect(textOf(rowOf('og'))).toContain('Failed')
expect(textOf(rowOf('og'))).toContain(cause)
expect(findButton('Retry', rowOf('og'))).toBeDefined()
expect(lastToast()).toMatchObject({
color: 'error',
title: "Couldn't generate og.",
description: `${cause}.`,
})
expect(textOf(document.body)).not.toContain('plain text body')
})
it('does not remember a failed state when the panel is closed and opened again', async () => {
await mountTab({
variants: () => variantList(PARTLY),
generate: () => Promise.reject(new ApiError(413)),
})
await openVariants()
rowAction('og', 'Generate now')!.click()
await flushPromises()
expect(textOf(rowOf('og'))).toContain('Failed')
press(panel()!, 'Escape')
document.body
.querySelector<HTMLElement>('[role="dialog"] button[aria-label="Close"]')
?.click()
await flushPromises()
await new Promise((resolve) => setTimeout(resolve, 20))
await flushPromises()
await openVariants()
expect(textOf(rowOf('og'))).toContain('Not generated')
})
it('Retry sends the same request and shows Generating', async () => {
const pending = deferred<unknown>()
let calls = 0
await mountTab({
variants: () => variantList(PARTLY),
generate: () => (++calls === 1 ? Promise.reject(new ApiError(413)) : pending.promise),
})
await openVariants()
rowAction('og', 'Generate now')!.click()
await flushPromises()
findButton('Retry', rowOf('og'))!.click()
await flushPromises()
expect(callsTo('POST', /\/v1\/media\/1\/variants\/og$/)).toHaveLength(2)
expect(textOf(rowOf('og'))).toContain('Generating')
})
it.each([
['500', new ApiError(500), 'Something went wrong'],
['403', new ApiError(403), "You don't have access"],
['network failure', new NetworkError(), 'Something went wrong'],
])(
'Other failures of a generation: %s returns the row to Not generated',
async (_l, error, title) => {
await mountTab({
variants: () => variantList(PARTLY),
generate: () => Promise.reject(error),
})
await openVariants()
rowAction('og', 'Generate now')!.click()
await flushPromises()
expect(textOf(rowOf('og'))).toContain('Not generated')
expect(lastToast()).toMatchObject({ color: 'error', title })
},
)
it('a 404 returns the row, toasts and requests the tab again', async () => {
await mountTab({
variants: () => variantList(PARTLY),
generate: () => Promise.reject(new ApiError(404)),
})
await openVariants()
rowAction('og', 'Generate now')!.click()
await flushPromises()
expect(textOf(rowOf('og'))).toContain('Not generated')
expect(lastToast()).toMatchObject({
title: 'Not found',
description: 'This variant or image no longer exists. Refresh the page.',
})
expect(variantRequests()).toHaveLength(2)
})
it('closes the panel and drops the image when the second request is a 404', async () => {
let variantCalls = 0
let imageGone = false
await mountTab({
variants: () => {
if (++variantCalls === 1) {
return variantList(PARTLY)
}
imageGone = true
return Promise.reject(new ApiError(404))
},
generate: () => Promise.reject(new ApiError(404)),
list: () => listBody(fixture().filter((item) => !imageGone || item.id !== 1)),
})
await openVariants()
rowAction('og', 'Generate now')!.click()
await flushPromises()
await new Promise((resolve) => setTimeout(resolve, 20))
await flushPromises()
expect(cards().some((card) => textOf(card).includes('north-wall.jpg'))).toBe(false)
expect(toastWith('media-not-found-1')).toMatchObject({
title: 'That image no longer exists',
})
})
it('announces the progress in a polite live region', async () => {
const pending = deferred<unknown>()
await mountTab({ variants: () => variantList(PARTLY), generate: () => pending.promise })
await openVariants()
rowAction('og', 'Generate now')!.click()
await flushPromises()
const region = body().querySelector<HTMLElement>('[aria-live="polite"]')!
expect(textOf(region)).toBe('Generating variants')
pending.resolve(variantItems().find((item) => item.name === 'og'))
await flushPromises()
expect(textOf(region)).toBe('Generated og')
})
})
describe('Copy URL, Purge and Preview', () => {
it('copies the absolute variant url, also for a row that is not generated', async () => {
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
rowAction('thumb', 'Copy URL')!.click()
await flushPromises()
rowAction('og', 'Copy URL')!.click()
await flushPromises()
expect(writeText).toHaveBeenNthCalledWith(
1,
`${window.location.origin}/static/uploadedImages/1.jpg?v=thumb`,
)
expect(writeText).toHaveBeenNthCalledWith(
2,
`${window.location.origin}/static/uploadedImages/1.jpg?v=og`,
)
expect(toastWith('media-variant-url-copied-1-thumb')).toMatchObject({
title: 'URL copied',
description: 'Paste it wherever you need the image address.',
})
})
it('shows no success toast when the clipboard rejects', async () => {
writeText.mockRejectedValue(new Error('denied'))
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
rowAction('thumb', 'Copy URL')!.click()
await flushPromises()
expect(toastWith('media-variant-url-copied-1-thumb')).toBeUndefined()
})
it('Purge removes the file, updates the row and the summary, and toasts', async () => {
await mountTab({
variants: () => variantList(PARTLY),
purgeVariant: () => ({ purged_files: 1, purged_bytes: 18432 }),
})
await openVariants()
rowAction('thumb', 'Purge')!.click()
await flushPromises()
expect(callsTo('DELETE', /\/v1\/media\/1\/variants\/thumb$/)).toHaveLength(1)
expect(textOf(rowOf('thumb'))).toContain('Not generated')
expect(textOf(rowOf('thumb'))).not.toContain('KB')
expect(summary()).toBe('3 of 5 generated')
expect(toastWith('media-variants-purged-1-thumb')).toMatchObject({
title: 'Purged 1 file (18 KB)',
})
})
it.each([
[403, "You don't have access"],
[404, 'Not found'],
[500, 'Something went wrong'],
])('Purge fails with %i and the row stays Generated', async (status, title) => {
await mountTab({
variants: () => variantList(PARTLY),
purgeVariant: () => Promise.reject(new ApiError(status)),
})
await openVariants()
rowAction('thumb', 'Purge')!.click()
await flushPromises()
expect(textOf(rowOf('thumb'))).toContain('Generated')
expect(lastToast()).toMatchObject({ color: 'error', title })
})
it('Preview opens a dialog with both panes at actual size', async () => {
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
rowAction('thumb', 'Preview')!.click()
await flushPromises()
const dialogs = Array.from(document.body.querySelectorAll<HTMLElement>('[role="dialog"]'))
const preview = dialogs.find((entry) => textOf(entry).includes('Preview: thumb'))!
const content = textOf(preview)
expect(content).toContain('thumb · 400×300 · WebP')
expect(content).toContain('18 KB · saves 99%')
expect(content).toContain('Original · 4032×3024 · JPEG')
expect(content).toContain('3.9 MB')
expect(content).toContain('Both images are shown at actual size. Scroll to pan.')
expect(findButton('Close', preview)).toBeDefined()
expect(preview.querySelector('[data-testid="preview-variant-pane"]')!.className).toContain(
'sm:h-75',
)
expect(preview.querySelector('[data-testid="preview-variant-pane"]')!.className).toContain(
'h-45',
)
const sources = Array.from(preview.querySelectorAll('img')).map((image) =>
image.getAttribute('src'),
)
expect(sources).toEqual(['/static/uploadedImages/1.jpg?v=thumb', '/media/north-wall.jpg'])
})
it('returns focus to the Preview button when the dialog closes', async () => {
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
const opener = rowAction('thumb', 'Preview')!
opener.focus()
opener.click()
await flushPromises()
const preview = Array.from(
document.body.querySelectorAll<HTMLElement>('[role="dialog"]'),
).find((entry) => textOf(entry).includes('Preview: thumb'))!
findButton('Close', preview)!.click()
await flushPromises()
await new Promise((resolve) => setTimeout(resolve, 20))
await flushPromises()
expect(document.activeElement).toBe(opener)
})
it('uses the sheet layout from the same markup below 640 px', async () => {
await mountTab({ variants: () => variantList(PARTLY) }, '/media', MOBILE_WIDTH)
await openVariants()
rowAction('thumb', 'Preview')!.click()
await flushPromises()
const preview = Array.from(
document.body.querySelectorAll<HTMLElement>('[role="dialog"]'),
).find((entry) => textOf(entry).includes('Preview: thumb'))!
expect(preview.className).toContain('max-sm:!bottom-4')
expect(findButton('Close', preview)!.className).toContain('h-11')
})
})
describe('Vocabulary', () => {
it('never says thumbnail preset, version or rendition', async () => {
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
expect(textOf(body())).not.toMatch(/thumbnail preset|version|rendition/i)
})
it('keeps the buttons buttons', async () => {
await mountTab({ variants: () => variantList(PARTLY) })
await openVariants()
expect(kit.wrapper!.findAllComponents(UButton).length).toBeGreaterThan(0)
expect(router.currentRoute.value.path).toBe('/media')
})
})