import { beforeEach, describe, expect, it } from 'vitest' import { mount } from '@vue/test-utils' import { setBundle } from '../../src/app/i18n' import CellValue from '../../src/components/list/CellValue.vue' import DataTable from '../../src/components/list/DataTable.vue' import type { ListColumn } from '../../src/api/types' import { langFixture, listColumn } from '../fixtures/typed' import { resetState, routerAt, signIn } from '../helpers' function cell(column: ListColumn, value: unknown, row: Record = {}) { return mount({ components: { CellValue }, props: [], template: '', data: () => ({ column, value, row }), }) } const moneyFrom: ListColumn = { ...listColumn('name'), key: 'total', type: 'money', currencyFrom: 'currency', sortable: false } const moneyEur: ListColumn = { ...listColumn('name'), key: 'fee', type: 'money', currency: 'EUR', sortable: false } const text = listColumn('name') const datetime = listColumn('created_at') const toggle = listColumn('active') beforeEach(() => { resetState() }) describe('plugin field type columns (16.2)', () => { it('renders the ListValue text of a plugin type as plain text', () => { const wrapper = cell({ ...text, type: 'notes' }, '3 blocks') expect(wrapper.text()).toBe('3 blocks') expect(wrapper.find('b').exists()).toBe(false) }) }) describe('cell value (D-12)', () => { it.each([ ['a string', 'Blue', 'Blue'], ['a number', 12, '12'], ['an array', ['small', 3, null, ''], 'small, 3'], ['a boolean true', true, 'Tak'], ['a boolean false', false, 'Nie'], ])('renders %s as plain text', (_label, value, expected) => { const wrapper = cell(text, value) expect(wrapper.text()).toBe(expected) expect(wrapper.find('[data-empty]').exists()).toBe(false) }) it.each([ ['null', null], ['undefined', undefined], ['an empty string', ''], ['an object', { nested: true }], ['an empty array', []], ])('renders %s as a muted dash', (_label, value) => { const wrapper = cell(text, value) expect(wrapper.find('[data-empty]').text()).toBe('—') }) it('never renders markup from a value', () => { const wrapper = cell(text, '') expect(wrapper.find('img').exists()).toBe(false) expect(wrapper.text()).toBe('') }) it('formats datetimes as YYYY-MM-DD HH:mm in tabular numbers', () => { const wrapper = cell(datetime, '2026-09-21T14:32:00') expect(wrapper.text()).toBe('2026-09-21 14:32') expect(wrapper.find('span.tabular-nums').exists()).toBe(true) expect(cell(datetime, 'not a date').text()).toBe('not a date') expect(cell(datetime, null).find('[data-empty]').exists()).toBe(true) expect(cell(datetime, true).find('[data-empty]').exists()).toBe(true) }) it.each([ [true, 'true', 'Tak'], [1, 'true', 'Tak'], ['yes', 'true', 'Tak'], [false, 'false', 'Nie'], [0, 'false', 'Nie'], ['0', 'false', 'Nie'], ['FALSE', 'false', 'Nie'], ])('renders the switch value %j as the %s pill', (value, state, label) => { const wrapper = cell(toggle, value) expect(wrapper.find('[data-switch]').attributes('data-switch')).toBe(state) expect(wrapper.text()).toBe(label) }) it('renders an empty switch value as a dash', () => { expect(cell(toggle, null).find('[data-empty]').exists()).toBe(true) expect(cell(toggle, null).find('[data-switch]').exists()).toBe(false) }) }) describe('date and time cells (Phase 12.2)', () => { const date = { ...listColumn('created_at'), key: 'released_on', type: 'date' } as ListColumn const time = { ...listColumn('created_at'), key: 'opens_at', type: 'time' } as ListColumn it('shows a stored date as is, never shifted by a time zone', () => { expect(cell(date, '2026-10-02').text()).toBe('2026-10-02') expect(cell(date, '2026-01-01T00:00:00Z').text()).toBe('2026-01-01') expect(cell(date, '2026-10-02').find('span.tabular-nums').exists()).toBe(true) }) it('shows a stored time without its seconds', () => { expect(cell(time, '14:30:15').text()).toBe('14:30') expect(cell(time, '08:05').text()).toBe('08:05') expect(cell(time, '14:30:15').find('span.tabular-nums').exists()).toBe(true) }) it('renders an empty date or time as a muted dash', () => { for (const column of [date, time]) { for (const value of [null, undefined, '']) { expect(cell(column, value).find('[data-empty]').text()).toBe('—') } } }) }) describe('money column (UI-SPEC F1, D-07)', () => { it("formats the decimal string with the row's currencyFrom code in en and pl", () => { setBundle(langFixture.data, 'en') const en = cell(moneyFrom, '99999999.9999', { currency: 'PLN' }) expect(en.find('[data-money]').text()).toBe('PLN\u00a099,999,999.9999') expect(en.find('[data-money]').classes()).toEqual(expect.arrayContaining(['tabular-nums', 'whitespace-nowrap'])) setBundle(langFixture.data, 'pl') const pl = cell(moneyFrom, '39.8400', { currency: 'PLN' }) expect(pl.find('[data-money]').text().replace(/\s/g, ' ')).toBe('39,84 PLN') }) it('uses the literal currency of the column', () => { setBundle(langFixture.data, 'en') expect(cell(moneyEur, '1.5000').find('[data-money]').text()).toBe('EUR\u00a01.50') }) it('shows the amount alone when the row has no currency, and the dash when empty', () => { setBundle(langFixture.data, 'en') expect(cell(moneyFrom, '12.5', { currency: '' }).find('[data-money]').text()).toBe('12.50') expect(cell(moneyFrom, '12.5', {}).find('[data-money]').text()).toBe('12.50') expect(cell(moneyFrom, null, { currency: 'PLN' }).find('[data-empty]').text()).toBe('—') expect(cell(moneyFrom, '', { currency: 'PLN' }).find('[data-empty]').text()).toBe('—') }) it('aligns the header and the cells of a money column to the right', async () => { setBundle(langFixture.data, 'en') signIn() const router = await routerAt('/acme/demo/widgets') const wrapper = mount(DataTable, { props: { columns: [listColumn('name'), moneyFrom], rows: [ { id: 1, name: 'One', total: '85.4193', currency: 'PLN' }, { id: 2, name: 'Two', total: null, currency: 'PLN' }, ], }, global: { plugins: [router] }, }) const headers = wrapper.findAll('th') expect(headers[1]!.classes()).toContain('text-right') expect(headers[0]!.classes()).not.toContain('text-right') const cells = wrapper.findAll('tbody tr')[0]!.findAll('td') expect(cells[1]!.classes()).toContain('text-right') expect(cells[1]!.find('[data-money]').text()).toBe('PLN\u00a085.4193') expect(wrapper.findAll('tbody tr')[1]!.findAll('td')[1]!.find('[data-empty]').text()).toBe('—') }) })