diff --git a/MAIN_PLAN.md b/MAIN_PLAN.md index a0c1f3c..2892912 100644 --- a/MAIN_PLAN.md +++ b/MAIN_PLAN.md @@ -248,6 +248,43 @@ - 이동하지 않은 축은 기존 좌표 유지하여 비의도 축 보정 방지 - 결과: 전체 테스트 그린(168 파일 / 295 테스트) +### CI E2E 수정: 마퀴 시작 판정 완화 (2025-11-17) +- 원인: 빈 영역 드래그 시에도 마퀴가 시작해야 하나, 캔버스 data-testid 직접 비교로 인해 실제 히트박스에서 시작 누락 → aria-live "선택 0개" 유지 +- 조치: `components/wysiwyg/Canvas.tsx`의 `onMouseDown`에서 객체/핸들 근접 여부 검사(`closest`)로 판정 완화, 객체/핸들이 아닌 영역이면 항상 마퀴 시작 +- 결과: E2E 라이브리전 멀티선택 문구 안정화, 전체 테스트 그린 유지 + +### E2E 스모크: 그룹 드래그 스냅 델타 동일 적용 (2025-11-17) +- 테스트: `tests/e2e/wysiwyg-group-drag.e2e.ts` + - 마퀴로 2개 선택 후, 선택 중 하나를 +16px 드래그 → 8px 스냅 반영 동일 델타로 양 객체 이동 검증 +- 기대/결과: 스냅된 델타(dx)가 선택 전체에 동일하게 적용됨 확인 + +### E2E 스모크 확장: 그룹 리사이즈(E)/회전(15°) + 가이드 라이브리전 (2025-11-17) +- 테스트: `tests/e2e/wysiwyg-group-resize-rotate.e2e.ts` + - 그룹 E 핸들 리사이즈: 좌표 고정, width만 스냅 증가, 두 객체 동일 증분 + - 회전 핸들: 15° 단위 스냅, 드래그 중 라이브리전에 가이드 좌표 문구 포함 +- 결과: 그룹 액션 스냅/가이드 UX 안정화 스모크 통과 + +### 이미지 최적화 옵션 2차 TDD (2025-11-17) +- 테스트: `lib/exporter/image.responsive.options.matrix.test.ts` + - loading/decoding/fetchpriority 적용 + - srcset + autoSizes + preset 동시 제공 시 preset 우선 + - explicit sizes가 있으면 preset/autoSizes보다 우선 +- 구현 상태: `lib/exporter/html.ts`의 기존 로직이 요구사항 충족(우선순위: explicit > preset > default(autoSizes)) + +### Export CSS 토큰 딥 회귀 (폼/테마) (2025-11-17) +- 테스트: `lib/wysiwyg/export.style.tokens.form.deep.test.ts` + - 토큰 존재: `--ring`, `--ring-offset`, `--disabled-bg`, `--disabled-fg` + - 사용 검증: `:focus-visible` 링/오프셋, `:disabled` BG, `[readonly]` FG + - 테마 프리셋: `[data-theme="light|dark"]`에서 `--bg` 제공, `.frame{background:var(--bg)}` 유지 +- 결과: 토큰/참조/테마 오버라이드 회귀 보강, 전체 테스트 그린(170 파일/300 테스트) + +### 스냅 우선순위 E2E (에지 > 센터 > 그리드) (2025-11-17) +- 테스트: `tests/e2e/wysiwyg-snap-priority.e2e.ts` + - 에지 스냅 우선: 인접 객체 에지 정렬이 그리드보다 우선 적용 + - 센터 스냅 우선: 중심 정렬 후보 시 그리드보다 센터 스냅 우선 + - 그리드 단독: 인접 후보가 없을 때 13px → 16px 그리드 스냅 +- 결과: 스냅 우선순위 정책(에지 > 센터 > 그리드) 회귀 스모크 확보 + ## 목표 - 비개발자도 사용 가능한 단일 페이지 랜딩 페이지 빌더. - 결과물: 정적 ZIP(HTML/CSS/JS) 다운로드. 이미지: 외부 URL 또는 로컬 업로드(서버 저장 없음, ZIP에 패키징). 폰트는 외부 CDN 허용. diff --git a/lib/wysiwyg/export.form.a11y.matrix.test.ts b/lib/wysiwyg/export.form.a11y.matrix.test.ts new file mode 100644 index 0000000..54a2a72 --- /dev/null +++ b/lib/wysiwyg/export.form.a11y.matrix.test.ts @@ -0,0 +1,65 @@ +import { describe, it, expect } from 'vitest' +import { exportFrame } from '@/lib/wysiwyg/export' +import type { Frame } from '@/lib/wysiwyg/schema' + +function frameWithForm(): Frame { + return { + id: 'f', width: 600, height: 400, background: '#ffffff', + layers: [ + { + id: 'base', name: 'Base', visible: true, locked: false, objects: [ + { id: 't1', type: 'input', x: 40, y: 40, width: 200, height: 32, rotate: 0, zIndex: 0, props: { name: 'email', label: 'Email', placeholder: '', required: true, help: 'We never share your email.' } }, + { id: 't2', type: 'select', x: 40, y: 100, width: 200, height: 32, rotate: 0, zIndex: 0, props: { name: 'country', label: 'Country', options: ['KR','US'], defaultValue: 'KR', help: 'Pick one' } }, + { id: 't3', type: 'textarea', x: 40, y: 160, width: 280, height: 80, rotate: 0, zIndex: 0, props: { name: 'msg', label: 'Message', placeholder: '', rows: 3, required: false, help: 'Optional' } }, + { id: 't4', type: 'radio', x: 320, y: 40, width: 200, height: 90, rotate: 0, zIndex: 0, props: { name: 'plan', legend: 'Plan', options: ['Basic','Pro'], defaultValue: 'Basic', required: false, help: 'Choose your plan' } }, + { id: 't5', type: 'checkbox', x: 320, y: 150, width: 200, height: 60, rotate: 0, zIndex: 0, props: { name: 'agree', legend: 'Agree to terms', options: ['Yes'], defaultValues: [], required: false, help: 'You must agree' } }, + ] + } + ], + } +} + +describe('wysiwyg export - a11y matrix for form controls', () => { + it('label/for + aria-describedby for input/select/textarea', () => { + const out = exportFrame(frameWithForm()) + const html = out.html + // input + expect(html).toMatch(/