test: expand exporter/css/a11y/i18n regressions and add e2e skip-link focus check

This commit is contained in:
2025-11-14 20:10:50 +09:00
parent d6510e2b05
commit 602f1eabde
26 changed files with 979 additions and 13 deletions
+55 -11
View File
@@ -40,6 +40,20 @@ function renderFeatures(section: { props: { items: string[] } }) {
`
}
function renderBenefits(section: { props: { items: string[] } }) {
const items: string[] = Array.isArray(section.props?.items) ? section.props.items : []
return `
<section class="benefits" aria-labelledby="benefits-heading">
<div class="container">
<h2 id="benefits-heading">Benefits</h2>
<ul class="benefits-list">
${items.map((it) => `<li class="benefit-item">${escapeHtml(String(it))}</li>`).join('')}
</ul>
</div>
</section>
`
}
function renderTestimonials(section: { props: { items: Array<{ author: string; quote: string }> } }) {
const items: Array<{ author: string; quote: string }> = Array.isArray(section.props?.items)
? section.props.items
@@ -102,45 +116,74 @@ function renderHero(section: { props: { heading: string; subheading?: string; im
}
function renderField(field: FormField) {
const id = `field-${escapeHtml(field.name)}`
const helpId = `field-${escapeHtml(field.name)}-help`
const hasHelp = !!field.help
const helpHtml = hasHelp ? `<div id="${helpId}" class="form-help">${escapeHtml(field.help!)}</div>` : ''
const pat = 'pattern' in field && field.pattern ? ` pattern="${escapeHtml(field.pattern)}"` : ''
const max = 'maxLength' in field && field.maxLength ? ` maxlength="${field.maxLength}"` : ''
const common = `name="${escapeHtml(field.name)}" aria-label="${escapeHtml(field.label)}" ${field.required ? 'required' : ''}${pat}${max}`
const describedBy = hasHelp ? ` aria-describedby="${helpId}"` : ''
const common = `id="${id}" name="${escapeHtml(field.name)}" aria-label="${escapeHtml(field.label)}" ${field.required ? 'required' : ''}${pat}${max}${describedBy}`
switch (field.type) {
case 'text':
case 'email':
case 'tel':
case 'date':
return `<label>${escapeHtml(field.label)}<input type="${field.type}" ${common} /></label>`
return `
<div class="form-control">
<label for="${id}">${escapeHtml(field.label)}</label>
<input type="${field.type}" ${common} />
${helpHtml}
</div>
`
case 'textarea':
return `<label>${escapeHtml(field.label)}<textarea ${common}></textarea></label>`
return `
<div class="form-control">
<label for="${id}">${escapeHtml(field.label)}</label>
<textarea ${common}></textarea>
${helpHtml}
</div>
`
case 'checkbox':
return `<label><input type="checkbox" ${common} />${escapeHtml(field.label)}</label>`
return `
<div class="form-control">
<label for="${id}">${escapeHtml(field.label)}</label>
<input type="checkbox" ${common} />
${helpHtml}
</div>
`
case 'radio': {
const { options } = field as Extract<FormField, { type: 'radio' }>
const fsDescribed = hasHelp ? ` aria-describedby="${helpId}"` : ''
return `
<fieldset>
<fieldset${fsDescribed}>
<legend>${escapeHtml(field.label)}</legend>
${options
.map(
(opt: string) =>
`<label><input type="radio" ${common} value="${escapeHtml(
opt
)}" />${escapeHtml(opt)}</label>`
(opt: string, i: number) => {
const optId = `${id}-opt-${i}`
return `<div><input id="${optId}" type="radio" name="${escapeHtml(field.name)}" aria-label="${escapeHtml(field.label)}" value="${escapeHtml(opt)}" ${field.required ? 'required' : ''}${pat}${max} /><label for="${optId}">${escapeHtml(opt)}</label></div>`
}
)
.join('')}
${helpHtml}
</fieldset>
`
}
case 'select': {
const { options } = field as Extract<FormField, { type: 'select' }>
return `
<label>${escapeHtml(field.label)}
<div class="form-control">
<label for="${id}">${escapeHtml(field.label)}</label>
<select ${common}>
${options
.map((opt: string) => `<option value="${escapeHtml(opt)}">${escapeHtml(opt)}</option>`)
.join('')}
</select>
</label>
${helpHtml}
</div>
`
}
default:
@@ -250,6 +293,7 @@ export function exportPage(page: Page, opts?: ExportOptions): Exported {
if (s.type === 'faq') return renderFaq(s)
if (s.type === 'cta') return renderCta(s)
if (s.type === 'features') return renderFeatures(s)
if (s.type === 'benefits') return renderBenefits(s)
if (s.type === 'testimonials') return renderTestimonials(s)
return ''
})