2026-07-12 02:03:28 +08:00
<!doctype html>
< html >
< head >
< meta charset = "utf-8" >
< title > components.form.AutoForm tests< / title >
2026-07-12 13:00:02 +08:00
< script src = "../../ui.js" components = "API,AutoForm,DatePicker,ColorPicker,IconPicker,TagsInput" > < / script >
2026-07-12 02:03:28 +08:00
< script >
const profile = {
name: 'Ada', password: 'secret', email: 'ada@example.test', age: 36,
birthday: '1815-12-10', appointment: '1843-07-01T09:00', role: 'admin',
alerts: true, plan: 'pro', active: true, bio: 'First programmer.',
periodStart: '1843-07-01', periodEnd: '1843-07-31', color: '#0d6efd', icon: 'star', tags: ['math']
}
const profileSchema = [
{ name: 'name', label: 'Name', type: 'text', setting: { required: true }, placeholder: 'Name' },
{ name: 'password', label: 'Password', type: 'password' },
{ name: 'email', label: 'Email', type: 'email' },
{ name: 'age', label: 'Age', type: 'number' },
{ name: 'birthday', label: 'Birthday', type: 'date' },
{ name: 'appointment', label: 'Appointment', type: 'datetime' },
{ name: 'avatar', label: 'Avatar', type: 'file' },
{ name: 'role', label: 'Role', type: 'select', options: [{ value: 'admin', label: 'Admin' }, { value: 'user', label: 'User' }] },
{ name: 'alerts', label: 'Alerts', type: 'checkbox', text: 'Receive alerts' },
{ name: 'plan', label: 'Plan', type: 'radio', options: ['free', 'pro'] },
{ name: 'active', label: 'Active', type: 'switch' },
{ name: 'bio', label: 'Bio', type: 'textarea' },
{ name: 'periodStart', label: 'Period', type: 'DatePicker', setting: { rangeEnd: 'periodEnd' } },
{ name: 'color', label: 'Color', type: 'ColorPicker' },
{ name: 'icon', label: 'Icon', type: 'IconPicker' },
{ name: 'tags', label: 'Tags', type: 'TagsInput' },
{ name: 'hidden', label: 'Hidden', type: 'text', if: "this.data.role === 'user'" }
]
const compactSchema = [{ name: 'name', label: 'Name', type: 'text' }]
2026-07-13 22:24:23 +08:00
const databaseFields = [
{ id: 'f-name', tableID: 'users', name: 'name', type: 'v100', settings: { type: 'text', attrs: { required: true } } },
{ id: 'f-role', tableID: 'users', name: 'role', type: 'v30', settings: { type: 'select', options: ['admin', 'user'] } }
]
2026-07-12 02:03:28 +08:00
const saveRequest = { url: '/__http__/json', method: 'POST' }
const errorRequest = { url: '/__http__/api-error', method: 'POST' }
< / script >
< / head >
< body >
< API id = "saveApi" $ . request = "saveRequest" > < / API >
< API id = "errorApi" $ . request = "errorRequest" > < / API >
< AutoForm id = "profileForm" $ . state . data = "profile" $ . state . schema = "profileSchema" > < / AutoForm >
< AutoForm id = "verticalForm" vertical nobutton $ . state . data = "profile" $ . state . schema = "compactSchema" > < div slot = "actions" $ onclick = "this.submit()" > Save< / div > < / AutoForm >
< AutoForm id = "horizontalForm" horizontal submitlabel = "Update" $ . state . data = "profile" $ . state . schema = "compactSchema" > < / AutoForm >
< AutoForm id = "inlineForm" inline $ . state . data = "profile" $ . state . schema = "compactSchema" > < / AutoForm >
< AutoForm id = "saveForm" nobutton $ . api = "saveApi" $ . state . data = "profile" $ . state . schema = "compactSchema" > < / AutoForm >
< AutoForm id = "errorForm" nobutton $ . api = "errorApi" $ . state . data = "profile" $ . state . schema = "compactSchema" > < / AutoForm >
< AutoForm id = "cancelForm" nobutton $ . api = "saveApi" $ . state . data = "profile" $ . state . schema = "compactSchema" > < / AutoForm >
2026-07-13 22:24:23 +08:00
< AutoForm id = "databaseForm" nobutton $ . state . data = "profile" $ . state . schema = "databaseFields" > < / AutoForm >
2026-07-12 02:03:28 +08:00
< button id = "runAll" type = "button" > 测试全部< / button >
< pre id = "result" > < / pre >
< script >
const tick = () => new Promise(resolve => setTimeout(resolve, 80))
2026-07-13 22:24:23 +08:00
const cases = ['data', 'all-controls', 'setting', 'options', 'database-schema', 'if', 'custom-type', 'vertical', 'horizontal', 'inline', 'nobutton', 'submitlabel', 'slot', 'submit', 'submit-event', 'response-event', 'error-event']
2026-07-12 02:03:28 +08:00
const assert = (testResult, feature, condition, message) => { testResult.assertions++; if (condition) testResult.passed++; else { testResult.failed++; testResult.failures.push({ feature, message }) } }
window.runTest = async () => {
const testResult = { name: 'components.form.AutoForm', status: 'running', assertions: 0, passed: 0, failed: 0, failures: [], consoleErrors: [], coverage: { tested: [], total: cases, percent: 0 } }
await tick()
const form = document.querySelector('#profileForm')
assert(testResult, 'data', form.data.name === 'Ada' & & form.querySelector('input[name="name"]').value === 'Ada', 'data initializes form controls')
const standardTypes = ['text', 'password', 'email', 'number', 'date', 'datetime', 'file', 'select', 'checkbox', 'radio', 'switch', 'textarea']
const customTypes = ['DatePicker', 'ColorPicker', 'IconPicker', 'TagsInput']
assert(testResult, 'all-controls', form.querySelectorAll('[control-wrapper]').length === 16 & & standardTypes.every(type => form.querySelector(`[name="${profileSchema.find(item => item.type === type).name}"]`)) & & customTypes.every(type => form.querySelector(type)), 'one schema renders every documented standard and custom control type')
assert(testResult, 'setting', form.querySelector('input[name="name"]').required, 'setting attributes are copied to controls')
assert(testResult, 'options', form.querySelectorAll('[name="role"] option').length === 2, 'select options render schema options')
2026-07-13 22:24:23 +08:00
const databaseForm = document.querySelector('#databaseForm'); assert(testResult, 'database-schema', databaseForm.querySelector('input[name="name"]')?.required & & databaseForm.querySelectorAll('select[name="role"] option').length === 2, 'database field definitions are converted to a plain form schema without changing form data keys')
2026-07-12 02:03:28 +08:00
assert(testResult, 'if', !form.querySelector('input[name="hidden"]'), '$$if hides fields whose expression is false')
form.data.role = 'user'; await tick(); assert(testResult, 'if', !!form.querySelector('input[name="hidden"]'), '$$if evaluates item.if again after form data changes'); form.data.role = 'admin'
assert(testResult, 'custom-type', !!form.querySelector('TagsInput'), 'registered AutoForm component type renders its component')
assert(testResult, 'vertical', document.querySelector('#verticalForm form').classList.contains('flex-column'), 'vertical applies vertical layout')
assert(testResult, 'horizontal', document.querySelector('#horizontalForm form').classList.contains('forced-horizontal'), 'horizontal applies horizontal layout')
assert(testResult, 'inline', document.querySelector('#inlineForm form').classList.contains('flex-wrap'), 'inline applies inline layout')
assert(testResult, 'nobutton', !document.querySelector('#verticalForm [type="submit"]') & & !document.querySelector('#saveForm [type="submit"]'), 'nobutton hides the default submit button')
assert(testResult, 'submitlabel', document.querySelector('#horizontalForm [type="submit"]').textContent === 'Update', 'submitlabel changes default button text')
assert(testResult, 'slot', document.querySelector('#verticalForm').textContent.includes('Save'), 'actions slot renders custom controls')
let submitEvent; let responseEvent; document.querySelector('#saveForm').addEventListener('submit', event => { submitEvent = event.detail }, { once: true }); document.querySelector('#saveForm').addEventListener('response', event => { responseEvent = event.detail }, { once: true }); document.querySelector('#saveForm').submit(); await tick()
assert(testResult, 'submit', document.querySelector('#saveForm').result?.method === 'POST', 'submit sends data through the configured API')
assert(testResult, 'submit-event', submitEvent?.name === 'Ada', 'submit event is cancelable and exposes form data')
assert(testResult, 'response-event', responseEvent?.ok === true, 'response event exposes the HTTP response')
let errorEvent; document.querySelector('#errorForm').addEventListener('error', event => { errorEvent = event.detail }, { once: true }); document.querySelector('#errorForm').submit(); await tick(); assert(testResult, 'error-event', errorEvent instanceof Error & & errorEvent.message === 'application failed', 'error event exposes Error detail')
testResult.coverage.tested = [...cases]; testResult.coverage.percent = 100; testResult.status = testResult.failed ? 'failed' : 'passed'; document.querySelector('#result').textContent = JSON.stringify(testResult, null, 2); window.testResult = testResult; return testResult
}
document.querySelector('#runAll').onclick = window.runTest
< / script >
< / body >
< / html >