- WidgetField: skeleton and aria-busy, the 5000 ms whenDefined timeout, script failure, attributes only, fill-values and locale sync, one POST while busy, fill-key-only patching, danger toasts, unmount - PartialHost and partialNodes: exhaustive tag, attribute and URL allowlist, depth and node caps, skeleton sizes, empty, failure, busy refetch and ?id= only with a record - PartialField, ListToolbar, ListView, registry, formState, FormField and FormView: group labels, registered toolbar actions, header refetch rules, form context provision and asset loading
151 lines
5.3 KiB
TypeScript
151 lines
5.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import CheckboxField from '../../src/components/form/fields/CheckboxField.vue'
|
|
import DropdownField from '../../src/components/form/fields/DropdownField.vue'
|
|
import NumberField from '../../src/components/form/fields/NumberField.vue'
|
|
import RelationField from '../../src/components/form/fields/RelationField.vue'
|
|
import SwitchField from '../../src/components/form/fields/SwitchField.vue'
|
|
import TextField from '../../src/components/form/fields/TextField.vue'
|
|
import TextareaField from '../../src/components/form/fields/TextareaField.vue'
|
|
import PartialField from '../../src/components/form/fields/PartialField.vue'
|
|
import UnsupportedField from '../../src/components/form/fields/UnsupportedField.vue'
|
|
import WidgetField from '../../src/components/form/fields/WidgetField.vue'
|
|
import RelationManager from '../../src/components/relation/RelationManager.vue'
|
|
import {
|
|
controlAttributes,
|
|
controlClass,
|
|
groupLabelled,
|
|
isRegistered,
|
|
needsRecord,
|
|
ownsLabel,
|
|
RELATION_MANAGER,
|
|
rendererFor,
|
|
toggleOn,
|
|
toggleValue,
|
|
} from '../../src/components/form/registry'
|
|
import * as control from '../../src/components/form/control'
|
|
import { formField } from '../fixtures/typed'
|
|
|
|
describe('field renderer registry (D-05)', () => {
|
|
it.each([
|
|
['text', TextField],
|
|
['textarea', TextareaField],
|
|
['number', NumberField],
|
|
['dropdown', DropdownField],
|
|
['switch', SwitchField],
|
|
['checkbox', CheckboxField],
|
|
['relation', RelationField],
|
|
[RELATION_MANAGER, RelationManager],
|
|
])('renders %s with its control', (type, component) => {
|
|
expect(rendererFor(type)).toBe(component)
|
|
})
|
|
|
|
it.each(['colorpicker', 'fileupload', '', 'TEXT', 'constructor'])('falls back to UnsupportedField for %j', (type) => {
|
|
expect(rendererFor(type)).toBe(UnsupportedField)
|
|
expect(isRegistered(type)).toBe(false)
|
|
})
|
|
|
|
it('edits values of every control type except the record-bound relation manager', () => {
|
|
expect(RELATION_MANAGER).toBe('relation-manager')
|
|
for (const type of ['text', 'textarea', 'number', 'dropdown', 'switch', 'checkbox', 'relation']) {
|
|
expect(isRegistered(type), type).toBe(true)
|
|
expect(needsRecord(type), type).toBe(false)
|
|
}
|
|
expect(isRegistered(RELATION_MANAGER)).toBe(false)
|
|
expect(needsRecord(RELATION_MANAGER)).toBe(true)
|
|
})
|
|
|
|
it('lets toggle cards and the relation manager render their own label', () => {
|
|
expect(ownsLabel('switch')).toBe(true)
|
|
expect(ownsLabel('checkbox')).toBe(true)
|
|
expect(ownsLabel(RELATION_MANAGER)).toBe(true)
|
|
expect(ownsLabel('text')).toBe(false)
|
|
expect(ownsLabel('relation')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('plugin extension types (Phase 10.1, D-09)', () => {
|
|
it('registers widget and partial with their controls', () => {
|
|
expect(rendererFor('widget')).toBe(WidgetField)
|
|
expect(rendererFor('partial')).toBe(PartialField)
|
|
})
|
|
|
|
it.each(['widget', 'partial'])('treats %s as valueless, rendered on create and group-labelled', (type) => {
|
|
expect(isRegistered(type)).toBe(false)
|
|
expect(needsRecord(type)).toBe(false)
|
|
expect(groupLabelled(type)).toBe(true)
|
|
expect(ownsLabel(type)).toBe(false)
|
|
})
|
|
|
|
it('labels every other type as a control, not a group', () => {
|
|
for (const type of ['text', 'textarea', 'number', 'dropdown', 'switch', 'checkbox', 'relation', RELATION_MANAGER, 'colorpicker']) {
|
|
expect(groupLabelled(type), type).toBe(false)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('control attributes', () => {
|
|
it('passes only allow-listed Winter attributes, lower-cased, and drops null and false', () => {
|
|
const field = formField('name')
|
|
field.attributes = {
|
|
placeholder: 'Type…',
|
|
MaxLength: 20,
|
|
readonly: true,
|
|
spellcheck: false,
|
|
pattern: null,
|
|
onclick: 'alert(1)',
|
|
onfocus: 'x',
|
|
style: 'color:red',
|
|
class: 'evil',
|
|
id: 'hijack',
|
|
autofocus: true,
|
|
}
|
|
expect(controlAttributes(field)).toEqual({ placeholder: 'Type…', maxlength: 20, readonly: true })
|
|
})
|
|
|
|
it('answers no attributes without a map', () => {
|
|
expect(controlAttributes(formField('code'))).toEqual({})
|
|
})
|
|
|
|
it('switches the border to danger when invalid', () => {
|
|
expect(controlClass(true)).toContain('border-danger')
|
|
expect(controlClass(false)).toContain('border-border-strong')
|
|
expect(controlClass(undefined)).not.toContain('border-danger')
|
|
})
|
|
})
|
|
|
|
describe('toggle values', () => {
|
|
it.each([
|
|
[true, true],
|
|
[1, true],
|
|
['1', true],
|
|
['true', true],
|
|
['yes', true],
|
|
[false, false],
|
|
[0, false],
|
|
['0', false],
|
|
['', false],
|
|
['False', false],
|
|
[null, false],
|
|
[undefined, false],
|
|
])('%j is %s', (value, on) => {
|
|
expect(toggleOn(value)).toBe(on)
|
|
})
|
|
|
|
it('keeps a numeric stored value numeric and sends booleans otherwise', () => {
|
|
expect(toggleValue(0, true)).toBe(1)
|
|
expect(toggleValue(1, false)).toBe(0)
|
|
expect(toggleValue(false, true)).toBe(true)
|
|
expect(toggleValue('1', false)).toBe(false)
|
|
expect(toggleValue(undefined, true)).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('control module', () => {
|
|
it('is what the registry re-exports, so field controls never import the registry', () => {
|
|
expect(controlAttributes).toBe(control.controlAttributes)
|
|
expect(controlClass).toBe(control.controlClass)
|
|
expect(toggleOn).toBe(control.toggleOn)
|
|
expect(toggleValue).toBe(control.toggleValue)
|
|
})
|
|
})
|