fix: support Unicode characters in form field names (#38138)

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Wu Tianwei 2026-06-29 13:00:49 +08:00 committed by GitHub
parent 0d2084494e
commit d2216fe181
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 60 additions and 1 deletions

View File

@ -1937,6 +1937,9 @@
"web/app/components/base/markdown-blocks/form.tsx": {
"no-restricted-imports": {
"count": 1
},
"prefer-regex-literals": {
"count": 1
}
},
"web/app/components/base/markdown-blocks/index.ts": {

View File

@ -532,6 +532,54 @@ describe('MarkdownForm', () => {
})
})
// Unicode letters should be valid form field names.
describe('Unicode name support', () => {
it('should include Unicode-named fields from all supported controls in JSON output', async () => {
const user = userEvent.setup()
const node = createRootNode(
[
createElementNode('label', { for: '用户名' }, [createTextNode('Username:')]),
createElementNode('input', { type: 'text', name: '用户名', value: 'Alice' }),
createElementNode('label', { for: '密码' }, [createTextNode('Password:')]),
createElementNode('input', { type: 'password', name: '密码', value: 'secret' }),
createElementNode('label', { for: '内容' }, [createTextNode('Content:')]),
createElementNode('textarea', { name: '内容', value: 'Hello' }),
createElementNode('label', { for: '日期' }, [createTextNode('Date:')]),
createElementNode('input', { type: 'date', name: '日期', value: dayjs('2026-01-10') }),
createElementNode('label', { for: '时间' }, [createTextNode('Time:')]),
createElementNode('input', { type: 'time', name: '时间', value: '09:00' }),
createElementNode('label', { for: '日期时间' }, [createTextNode('Datetime:')]),
createElementNode('input', { type: 'datetime', name: '日期时间', value: dayjs('2026-01-10T08:30:00') }),
createElementNode('label', { for: 'café' }, [createTextNode('Select:')]),
createElementNode('input', { type: 'select', name: 'café', value: 'hello', dataOptions: ['hello', 'world'] }),
createElementNode('input', { type: 'checkbox', name: '同意条款', value: true, dataTip: 'By checking this means you agreed' }),
createElementNode('button', { dataSize: 'small', dataVariant: 'primary' }, [createTextNode('Login')]),
],
{ dataFormat: 'json' },
)
render(<MarkdownForm node={node} />)
await user.click(screen.getByRole('button', { name: 'Login' }))
await waitFor(() => {
expect(mockOnSend).toHaveBeenCalled()
})
const submittedPayload = JSON.parse(mockOnSend.mock.calls[0]![0]) as Record<string, unknown>
expect(submittedPayload).toMatchObject({
: 'Alice',
: 'secret',
: 'Hello',
: 'formatted-date',
: 'formatted-datetime',
café: 'hello',
同意条款: true,
})
expect(submittedPayload).toHaveProperty('时间')
})
})
// Double-click protection: button disables after the first submit.
describe('Double submit prevention', () => {
it('should disable submit button after first click', async () => {

View File

@ -41,7 +41,15 @@ type SupportedType = typeof SUPPORTED_TYPES[keyof typeof SUPPORTED_TYPES]
const SUPPORTED_TYPES_SET = new Set<string>(Object.values(SUPPORTED_TYPES))
const SAFE_NAME_RE = /^[a-z][\w-]*$/i
const SAFE_NAME_RE = (() => {
try {
return new RegExp('^\\p{L}[\\p{L}\\p{M}\\p{N}_-]*$', 'u')
}
catch {
// Fallback for browsers without Unicode property escape support.
return /^[a-z][\w-]*$/i
}
})()
const PROTOTYPE_POISON_KEYS = new Set(['__proto__', 'constructor', 'prototype'])
function isSafeName(name: unknown): name is string {