From bde7c8785d4d5b50db99932b2d8d78ee97baaca7 Mon Sep 17 00:00:00 2001 From: Jaybe Date: Tue, 18 Nov 2025 01:48:34 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AC=B8=EC=84=9C:=20MAIN=5FPLAN=20Export=20?= =?UTF-8?q?=ED=8F=BC=20a11y(fieldset=20aria-describedby/help/id)=20?= =?UTF-8?q?=EB=B0=B0=EC=B9=98=20=EA=B8=B0=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MAIN_PLAN.md | 7 +++++ lib/exporter/formFieldset.a11y.test.ts | 39 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 lib/exporter/formFieldset.a11y.test.ts diff --git a/MAIN_PLAN.md b/MAIN_PLAN.md index 7e416f8..a89658e 100644 --- a/MAIN_PLAN.md +++ b/MAIN_PLAN.md @@ -291,6 +291,13 @@ - 드래그 중 라이브리전 가이드 텍스트 노출 확인(가이드 x/y) - 결과: 회전 상태에서도 스냅 우선순위 일관성 확보, 라이브리전 가이드 동작 스모크 통과 +### Export 폼 a11y: radio fieldset aria-describedby/help/id 연결 (2025-11-18) +- 테스트: `lib/exporter/formFieldset.a11y.test.ts` + - fieldset에 `aria-describedby`로 help id 연결(`field-*-help`) + - legend는 label과 동일 텍스트 사용 + - 각 option은 고유 id(`field-*-opt-*`)와 label for 매핑, 동일 name 공유 +- 결과: 라디오 그룹 a11y 구조 회귀 스냅샷 확보(legend/fieldset/help/id 일관성) + ## 목표 - 비개발자도 사용 가능한 단일 페이지 랜딩 페이지 빌더. - 결과물: 정적 ZIP(HTML/CSS/JS) 다운로드. 이미지: 외부 URL 또는 로컬 업로드(서버 저장 없음, ZIP에 패키징). 폰트는 외부 CDN 허용. diff --git a/lib/exporter/formFieldset.a11y.test.ts b/lib/exporter/formFieldset.a11y.test.ts new file mode 100644 index 0000000..4b61042 --- /dev/null +++ b/lib/exporter/formFieldset.a11y.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from 'vitest' +import { exportPage } from '@/lib/exporter/html' +import type { Page } from '@/lib/schema/page' + +const makePage = (overrides: Partial = {}): Page => ({ + title: 'Fieldset A11y', + locale: 'en', + theme: { primaryColor: '#0ea5e9', fontFamily: 'Inter' }, + sections: [], + form: { + actionUrl: '', + fields: [ + { type: 'radio', name: 'plan', label: 'Plan', options: ['Basic','Pro'], required: false, help: 'Choose your plan' }, + ], + spamProtection: { honeypotFieldName: '_hp', minSubmitSeconds: 2 }, + }, + analytics: { ga4MeasurementId: '', metaPixelId: '', customHeadHtml: '' }, + seo: { title: 'T', description: 'D' }, + ...overrides, +}) + +describe('Exporter - radio fieldset a11y', () => { + it('fieldset gets aria-describedby to help id; legend equals label; options share name', () => { + const page = makePage() + const { html } = exportPage(page) + + // fieldset with describedby + expect(html).toMatch(/]*aria-describedby="field-plan-help"/) + // legend uses label + expect(html).toMatch(/Plan<\/legend>/) + // help element id matches describedby target + expect(html).toMatch(/
Choose your plan<\/div>/) + // radio inputs share name and ids are unique + expect(html).toMatch(/]*value="Basic"/) + expect(html).toMatch(/