test(12.1-05): unit tests for the list, form, preview and routing behaviours of Phase 12.1 in the SPA
- list: the bulk menu in the toolbar and on the list view with its three failure rows, row states and invisible columns in the table - form: the forbidden banner, password and preset on the form view, locked relation options, the save body of password and permission fields - preview view: context fields, hidden tabs, the status hint, the footer with zero, one and several actions, load failures - routing: the preview route and mapWinterUrl - backstops: focus returns to the bulk menu trigger; preview URL mapping and the route replacement; locked relation options - the slug preset runs on the table the user plugin's slug test uses
This commit is contained in:
@@ -141,3 +141,105 @@ describe('data table', () => {
|
||||
expect(wrapper.findAll('[data-skeleton]')).toHaveLength(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('row states in the table (UI-SPEC S4, D-12)', () => {
|
||||
const stateLabels = { deleted: 'Deleted', negative: 'Blocked', disabled: 'Not active' }
|
||||
const rows = (wrapper: Awaited<ReturnType<typeof mountTable>>['wrapper']) => wrapper.findAll('tbody tr')
|
||||
const firstCell = (wrapper: Awaited<ReturnType<typeof mountTable>>['wrapper'], index: number) => rows(wrapper)[index]!.findAll('td:not([data-select])')[0]!
|
||||
|
||||
it('leaves every row unchanged without states', async () => {
|
||||
const { wrapper } = await mountTable({ rowLink, stateLabels })
|
||||
for (const row of rows(wrapper)) {
|
||||
expect(row.attributes('data-row-states')).toBeUndefined()
|
||||
expect(row.find('[data-row-state]').exists()).toBe(false)
|
||||
}
|
||||
expect(firstCell(wrapper, 0).find('a').classes()).toEqual(expect.arrayContaining(['text-text', 'no-underline']))
|
||||
const empty = await mountTable({ rowLink, stateLabels, rowStates: { '1': [] } })
|
||||
expect(rows(empty.wrapper)[0]!.attributes('data-row-states')).toBeUndefined()
|
||||
})
|
||||
|
||||
it.each([
|
||||
['deleted', ['line-through', 'text-muted'], ['text-danger']],
|
||||
['negative', ['no-underline', 'text-danger'], ['line-through']],
|
||||
['disabled', ['no-underline', 'text-muted'], ['line-through', 'text-danger']],
|
||||
])('styles the first-cell text of a %s row and says the state in a badge', async (state, present, absent) => {
|
||||
const { wrapper } = await mountTable({ rowLink, stateLabels, rowStates: { '1': [state] } })
|
||||
const row = rows(wrapper)[0]!
|
||||
expect(row.attributes('data-row-states')).toBe(state)
|
||||
const link = firstCell(wrapper, 0).find('a')
|
||||
expect(link.classes()).toEqual(expect.arrayContaining(present))
|
||||
for (const name of absent) {
|
||||
expect(link.classes()).not.toContain(name)
|
||||
}
|
||||
const badge = firstCell(wrapper, 0).find(`[data-row-state="${state}"]`)
|
||||
expect(badge.text()).toBe(stateLabels[state as keyof typeof stateLabels])
|
||||
// The badge sits beside the link, never inside the struck text.
|
||||
expect(link.find('[data-row-state]').exists()).toBe(false)
|
||||
// The other row is untouched.
|
||||
expect(rows(wrapper)[1]!.attributes('data-row-states')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('combines states: the attribute lists them and danger wins over muted', async () => {
|
||||
const { wrapper } = await mountTable({ rowLink, stateLabels, rowStates: { '1': ['deleted', 'negative', 'disabled'] } })
|
||||
expect(rows(wrapper)[0]!.attributes('data-row-states')).toBe('deleted negative disabled')
|
||||
const link = firstCell(wrapper, 0).find('a')
|
||||
expect(link.classes()).toEqual(expect.arrayContaining(['line-through', 'text-danger']))
|
||||
expect(firstCell(wrapper, 0).findAll('[data-row-state]').map((badge) => badge.attributes('data-row-state'))).toEqual(['deleted', 'negative', 'disabled'])
|
||||
// Every other cell of a row with a state is muted.
|
||||
expect(rows(wrapper)[0]!.findAll('td:not([data-select])')[1]!.classes()).toContain('text-muted')
|
||||
})
|
||||
|
||||
it('styles plain text the same way when the row has no link', async () => {
|
||||
const { wrapper } = await mountTable({ stateLabels, rowStates: { '1': ['deleted'] } })
|
||||
const cell = firstCell(wrapper, 0)
|
||||
expect(cell.find('a').exists()).toBe(false)
|
||||
expect(cell.find('span.line-through').classes()).toEqual(expect.arrayContaining(['line-through', 'text-muted', 'truncate']))
|
||||
expect(cell.find('[data-row-state="deleted"]').text()).toBe('Deleted')
|
||||
})
|
||||
|
||||
it('keys states by the row id as text and ignores ids that are not on the page', async () => {
|
||||
const { wrapper } = await mountTable({ rowLink, stateLabels, rowStates: { '2': ['disabled'], '99': ['deleted'], abc: ['negative'] } })
|
||||
expect(rows(wrapper)[0]!.attributes('data-row-states')).toBeUndefined()
|
||||
expect(rows(wrapper)[1]!.attributes('data-row-states')).toBe('disabled')
|
||||
expect(wrapper.findAll('[data-row-state]')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('backstop: a row state outside the fixed set renders no badge and no class', async () => {
|
||||
const hostile = ['starred', 'bg-danger-soft', 'line-through', '<b>x</b>', 'DELETED', 'deleted negative']
|
||||
const { wrapper } = await mountTable({ rowLink, stateLabels: { ...stateLabels, starred: 'Starred' }, rowStates: { '1': hostile } })
|
||||
const row = rows(wrapper)[0]!
|
||||
expect(row.attributes('data-row-states')).toBeUndefined()
|
||||
expect(row.find('[data-row-state]').exists()).toBe(false)
|
||||
expect(row.html()).not.toMatch(/starred|Starred|bg-danger-soft|<b>x<\/b>|DELETED/)
|
||||
const link = firstCell(wrapper, 0).find('a')
|
||||
expect(link.classes()).toEqual(expect.arrayContaining(['text-text', 'no-underline']))
|
||||
expect(link.classes()).not.toContain('line-through')
|
||||
expect(firstCell(wrapper, 0).classes()).not.toContain('text-muted')
|
||||
|
||||
// Beside a known state, only the known one counts.
|
||||
const mixed = await mountTable({ rowLink, stateLabels, rowStates: { '1': ['starred', 'disabled', '<b>x</b>'] } })
|
||||
expect(rows(mixed.wrapper)[0]!.attributes('data-row-states')).toBe('disabled')
|
||||
expect(rows(mixed.wrapper)[0]!.findAll('[data-row-state]').map((badge) => badge.text())).toEqual(['Not active'])
|
||||
expect(rows(mixed.wrapper)[0]!.html()).not.toContain('starred')
|
||||
})
|
||||
})
|
||||
|
||||
describe('invisible columns (D-27 G6)', () => {
|
||||
it('renders no header and no cell for an invisible column, and keeps the first visible column first', async () => {
|
||||
const hidden = [{ ...listColumn('name'), invisible: true }, listColumn('code'), { ...listColumn('tags'), invisible: true }]
|
||||
const { wrapper } = await mountTable({ rowLink, columns: hidden })
|
||||
expect(wrapper.findAll('th').map((th) => th.text())).toEqual(['Code'])
|
||||
const cells = wrapper.findAll('tbody tr').map((tr) => tr.findAll('td').map((td) => td.text()))
|
||||
expect(cells).toEqual([['W-01'], ['W-02']])
|
||||
// The value of an invisible column is nowhere in the markup.
|
||||
expect(wrapper.html()).not.toContain('Blue widget')
|
||||
// The first visible column carries the row link.
|
||||
expect(wrapper.find('tbody tr td a').text()).toBe('W-01')
|
||||
})
|
||||
|
||||
it('treats invisible false and a missing flag as visible', async () => {
|
||||
const shown = [{ ...listColumn('name'), invisible: false }, listColumn('code')]
|
||||
const { wrapper } = await mountTable({ columns: shown })
|
||||
expect(wrapper.findAll('th').map((th) => th.text())).toEqual(['Name', 'Code'])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user