142 lines
4.7 KiB
TypeScript
142 lines
4.7 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { flushPromises } from '@vue/test-utils'
|
|
import { addCollection } from '@iconify/vue'
|
|
import lucide from 'virtual:lucide-subset'
|
|
import FieldList from '../FieldList.vue'
|
|
import { createField, type BuilderField } from '@/lib/schemaBuilderModel'
|
|
import { mountInApp, text, unmountApp, holder } from './directoryKit'
|
|
|
|
vi.mock('@/composables/useApi', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('@/composables/useApi')>()
|
|
return { ...actual, useApi: vi.fn() }
|
|
})
|
|
|
|
addCollection(lucide)
|
|
|
|
afterEach(unmountApp)
|
|
|
|
const FIELDS: BuilderField[] = [
|
|
createField(1, { label: 'Title', key: 'title' }),
|
|
createField(2, { label: 'Starts on', key: 'starts_on', type: 'date' }),
|
|
createField(3, { label: 'Ends on', key: 'ends_on', type: 'date' }),
|
|
]
|
|
|
|
function props(extra: Record<string, unknown> = {}) {
|
|
return {
|
|
fields: FIELDS,
|
|
selectedUid: 2,
|
|
errors: new Map(),
|
|
disabled: false,
|
|
mobile: false,
|
|
...extra,
|
|
}
|
|
}
|
|
|
|
function emitted(name: string) {
|
|
return holder.wrapper!.findComponent(FieldList).emitted(name) ?? []
|
|
}
|
|
|
|
function grip(label: string) {
|
|
return document.body.querySelector<HTMLElement>(`[aria-label="Move ${label}"]`)!
|
|
}
|
|
|
|
async function press(target: Element, key: string) {
|
|
target.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true }))
|
|
await flushPromises()
|
|
}
|
|
|
|
function labels() {
|
|
return [...document.body.querySelectorAll('[data-testid="field-row-label"]')].map(text)
|
|
}
|
|
|
|
describe('FieldList', () => {
|
|
it('lists the rows and counts them against 50', async () => {
|
|
await mountInApp(FieldList, props())
|
|
|
|
expect(labels()).toEqual(['Title', 'Starts on', 'Ends on'])
|
|
expect(text(document.body.querySelector('[data-testid="field-count"]'))).toBe('3 of 50')
|
|
})
|
|
|
|
it('emits select, add, move and remove', async () => {
|
|
await mountInApp(FieldList, props())
|
|
|
|
document.body.querySelector<HTMLElement>('[data-field-row][data-uid="3"]')!.click()
|
|
;(document.body.querySelector('[data-action="up"]') as HTMLElement).click()
|
|
;(document.body.querySelector('[data-action="remove"]') as HTMLElement).click()
|
|
const add = [...document.body.querySelectorAll('button')].find(
|
|
(candidate) => text(candidate) === 'Add field',
|
|
)!
|
|
add.click()
|
|
await flushPromises()
|
|
|
|
expect(emitted('select')).toEqual([[3]])
|
|
expect(emitted('move')).toEqual([[2, -1]])
|
|
expect(emitted('remove')).toEqual([[2]])
|
|
expect(emitted('add')).toHaveLength(1)
|
|
})
|
|
|
|
it('disables Add field at 50 rows and when disabled', async () => {
|
|
const many = Array.from({ length: 50 }, (_, index) =>
|
|
createField(index + 1, { label: 'F' }),
|
|
)
|
|
await mountInApp(FieldList, props({ fields: many, selectedUid: 1 }))
|
|
|
|
const add = [...document.body.querySelectorAll<HTMLButtonElement>('button')].find(
|
|
(candidate) => text(candidate) === 'Add field',
|
|
)!
|
|
expect(add.disabled).toBe(true)
|
|
expect(text(document.body.querySelector('[data-testid="field-count"]'))).toBe('50 of 50')
|
|
})
|
|
|
|
it('shows the empty state with the error message in the error colour', async () => {
|
|
await mountInApp(FieldList, props({ fields: [], selectedUid: null, listError: 'Nope.' }))
|
|
|
|
const empty = document.body.querySelector('[data-testid="field-empty"]')!
|
|
expect(text(empty)).toBe('No fields yet Nope.')
|
|
expect(empty.querySelector('.text-error')).not.toBeNull()
|
|
})
|
|
|
|
it('emits the new order once, on drop, and nothing on cancel', async () => {
|
|
await mountInApp(FieldList, props())
|
|
|
|
await press(grip('Starts on'), ' ')
|
|
await press(grip('Starts on'), 'ArrowUp')
|
|
await press(grip('Starts on'), 'Escape')
|
|
expect(emitted('reorder')).toHaveLength(0)
|
|
expect(labels()).toEqual(['Title', 'Starts on', 'Ends on'])
|
|
|
|
await press(grip('Starts on'), ' ')
|
|
await press(grip('Starts on'), 'ArrowUp')
|
|
expect(emitted('reorder')).toHaveLength(0)
|
|
await press(grip('Starts on'), ' ')
|
|
|
|
expect(emitted('reorder')).toEqual([[[2, 1, 3]]])
|
|
})
|
|
|
|
it('moves the focus between rows with the arrow keys', async () => {
|
|
await mountInApp(FieldList, props())
|
|
const rows = [...document.body.querySelectorAll<HTMLElement>('[data-field-row]')]
|
|
rows[0]!.focus()
|
|
|
|
await press(rows[0]!, 'ArrowDown')
|
|
expect(document.activeElement).toBe(rows[1])
|
|
await press(rows[1]!, 'ArrowUp')
|
|
expect(document.activeElement).toBe(rows[0])
|
|
})
|
|
|
|
it('keeps one roving tab stop for the rows', async () => {
|
|
await mountInApp(FieldList, props())
|
|
|
|
const stops = [...document.body.querySelectorAll<HTMLElement>('[data-field-row]')].filter(
|
|
(row) => row.tabIndex === 0,
|
|
)
|
|
expect(stops.map((row) => row.getAttribute('data-uid'))).toEqual(['2'])
|
|
})
|
|
|
|
it('hides the row actions on mobile', async () => {
|
|
await mountInApp(FieldList, props({ mobile: true }))
|
|
|
|
expect(document.body.querySelector('[data-action]')).toBeNull()
|
|
})
|
|
})
|