- MLPluginField wraps PluginField in the MLField locale switcher; the
element gets the active content locale and that locale's string
- isMLField treats a translatable plugin field as a locale map in the form
state, FormView adopt and the relation child modal
- summer-upload posts the file with the admin session to the field upload
route and answers summer-upload-result {id, url} or {id, error}
- noticeLines shows meta.notices under each field as a warning status line
(text only) and in the saved toast; the stored value is reloaded
- docs/backend/field-types.md documents the full element contract;
modules/boardwalk/dist rebuilt
111 lines
4.1 KiB
TypeScript
111 lines
4.1 KiB
TypeScript
import { beforeEach, describe, expect, it } from 'vitest'
|
|
import { mount } from '@vue/test-utils'
|
|
import CellValue from '../../src/components/list/CellValue.vue'
|
|
import type { ListColumn } from '../../src/api/types'
|
|
import { listColumn } from '../fixtures/typed'
|
|
import { resetState } from '../helpers'
|
|
|
|
function cell(column: ListColumn, value: unknown) {
|
|
return mount({ components: { CellValue }, props: [], template: '<span data-cell><CellValue :column="column" :value="value" /></span>', data: () => ({ column, value }) })
|
|
}
|
|
|
|
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' }, '<b>3 blocks</b>')
|
|
expect(wrapper.text()).toBe('<b>3 blocks</b>')
|
|
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, '<img src=x onerror=alert(1)>')
|
|
expect(wrapper.find('img').exists()).toBe(false)
|
|
expect(wrapper.text()).toBe('<img src=x onerror=alert(1)>')
|
|
})
|
|
|
|
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('—')
|
|
}
|
|
}
|
|
})
|
|
})
|