// Row state badges (UI-SPEC S4, D-12). Fixtures are neutral; no application // names appear in framework tests. import { describe, expect, it } from 'vitest' import { mount } from '@vue/test-utils' import RowStateBadges from '../../src/components/list/RowStateBadges.vue' const labels = { deleted: 'Deleted', negative: 'Blocked', disabled: 'Not active' } const badges = (states: readonly string[], use: Record = labels) => mount(RowStateBadges, { props: { states, labels: use } }) describe('row state badges (UI-SPEC S4, D-12)', () => { it.each([ ['deleted', 'Deleted', ['border', 'border-border-strong', 'text-muted']], ['negative', 'Blocked', ['bg-danger-soft', 'text-danger']], ['disabled', 'Not active', ['bg-subtle', 'text-muted']], ])('renders %s as a text badge with its own classes', (state, text, classes) => { const wrapper = badges([state]) const all = wrapper.findAll('[data-row-state]') expect(all).toHaveLength(1) expect(all[0]!.attributes('data-row-state')).toBe(state) expect(all[0]!.text()).toBe(text) expect(all[0]!.classes()).toEqual(expect.arrayContaining([...classes, 'rounded-pill', 'whitespace-nowrap', 'shrink-0'])) // The state is said in text: colour alone would fail WCAG 1.4.1. expect(all[0]!.classes()).not.toContain('sr-only') expect(all[0]!.classes()).not.toContain('line-through') }) it('renders combined states in the fixed order deleted, negative, disabled', () => { for (const input of [ ['disabled', 'negative', 'deleted'], ['negative', 'deleted', 'disabled'], ['deleted', 'negative', 'disabled'], ]) { const wrapper = badges(input) expect(wrapper.findAll('[data-row-state]').map((badge) => badge.attributes('data-row-state'))).toEqual(['deleted', 'negative', 'disabled']) } expect(badges(['disabled', 'deleted']).findAll('[data-row-state]').map((badge) => badge.text())).toEqual(['Deleted', 'Not active']) }) it('renders a repeated state once', () => { const wrapper = badges(['negative', 'negative', 'negative']) expect(wrapper.findAll('[data-row-state]')).toHaveLength(1) }) it('renders nothing at all without states', () => { const wrapper = badges([]) expect(wrapper.find('span').exists()).toBe(false) expect(wrapper.text()).toBe('') }) it('falls back to the state name when the schema sends no label for it', () => { expect(badges(['negative'], {}).find('[data-row-state]').text()).toBe('negative') }) it('backstop: a row state outside the fixed set renders no badge and no class', () => { const hostile = ['starred', 'bg-danger-soft', 'deleted line-through', 'x', 'DELETED', '__proto__', 'constructor', ''] const wrapper = badges(hostile, { ...labels, starred: 'Starred', constructor: 'Constructor' }) expect(wrapper.find('[data-row-state]').exists()).toBe(false) expect(wrapper.find('span').exists()).toBe(false) expect(wrapper.html()).not.toMatch(/starred|bg-danger-soft|line-through|Starred|Constructor/) // Next to known states the unknown ones change nothing. const mixed = badges(['starred', 'negative', 'x']) expect(mixed.findAll('[data-row-state]').map((badge) => [badge.attributes('data-row-state'), badge.text()])).toEqual([['negative', 'Blocked']]) expect(mixed.html()).not.toContain('starred') expect(mixed.find('b').exists()).toBe(false) }) })