Phase 3 단위 테스트 및 접근성 검증 체크리스트
1. 검증 원칙
Phase 3의 테스트는 컴포넌트 내부 DOM 구조보다 공개 API, 상태 전이, 도메인 정책, 접근성 의미, Ant Design 연결 결과를 검증해야 한다. Ant Design이 생성하는 내부 class는 구현 세부사항이므로 snapshot만으로 계약을 고정하지 않는다. 대신 portal class, landmark, accessible name, callback payload, selected/disabled 상태를 직접 검증한다.
모든 테스트는 다음 세 계층으로 구성한다.
| 계층 | 목적 | 권장 방식 |
|---|---|---|
| 순수 변환 테스트 | domain model을 Ant Design model로 변환하는지 검증 | Vitest 함수 테스트 |
| 컴포넌트 계약 테스트 | props와 상태 전이가 보존되는지 검증 | React Testing Library + userEvent |
| 접근성 테스트 | role, name, keyboard, focus, contrast를 검증 | jest-axe 또는 axe-core + 수동 키보드 검증 |
2. 공통 테스트 준비
- [ ]
@testing-library/react,@testing-library/user-event,@testing-library/jest-dom을 테스트 workspace에서 사용할 수 있는지 확인한다. - [ ]
axe-core또는jest-axe를 테스트 환경에 추가한다. - [ ] 테스트 render helper가
PortalConfigProvider와 필요한ConfigProvider를 감싼다. - [ ] 테스트 render helper가 light/dark theme와 locale을 선택할 수 있도록 한다.
- [ ]
matchMedia,ResizeObserver,window.open,URL.createObjectURL을 테스트 환경에서 mock한다. - [ ] Ant Design portal이 document.body에 렌더링되는 경우 cleanup 후 남은 popup을 제거한다.
- [ ] 각 테스트는 portal class contract와 Ant Design primitive 사용 결과를 함께 검증한다.
- [ ] 테스트는 한국어와 영어 label을 각각 최소 한 번 검증한다.
3. PortalShell 테스트
3.1 기준선 및 rendering
- [ ] product name, product mark, sidebar header/footer, main header, children이 모두 렌더링된다.
- [ ]
pf-shell,pf-shell-topnav,pf-shell-sidebar,pf-shell-mainclass가 유지된다. - [ ] 최상위
Layout,Header,Sider,Content,Menu가 연결된다. - [ ]
navigationItems가 1-depth와 2-depth 메뉴로 변환된다. - [ ] active item이 selected state와
aria-current="page"의미를 유지한다. - [ ] active child가 있는 group이 open state로 표시된다.
- [ ]
mainHeader가 있으면 자동 header보다 우선한다. - [ ] active navigation item의
headerTitle과hero가 main 영역에 표시된다. - [ ]
exploreResults가 없을 때 navigation item에서 안전하게 생성된다.
3.2 Sidebar state
- [ ]
defaultSidebarOpen=false에서 desktop sidebar와 menu trigger가 존재한다. - [ ]
defaultSidebarOpen=true에서 mobile sidebar와 backdrop이 존재한다. - [ ]
sidebarOpencontrolled 값이 내부 state보다 우선한다. - [ ] mobile menu trigger가
onSidebarOpenChange(true)를 한 번 호출한다. - [ ] backdrop click이
onSidebarOpenChange(false)를 호출한다. - [ ] Escape가 open sidebar를 닫는다.
- [ ]
onToggleCollapse가 호출되고 collapsed class 및aria-expanded가 변경된다. - [ ] collapsed 상태에서 menu title과 icon은 유지되고 label 시각 노출만 축소된다.
- [ ]
onClose가 navigation selection 이후 호출된다.
3.3 Header, search, account
- [ ]
topNavStart와topNavEndslot이 보존된다. - [ ]
showSearch=false에서 search control이 렌더링되지 않는다. - [ ] controlled
searchValue와 uncontrolled 내부 search value가 각각 동작한다. - [ ] search input change가
onSearchChange에 정확한 문자열을 전달한다. - [ ] Enter와 search button이
onSearchSubmit에 동일한 값을 전달한다. - [ ] search submit 이후 explore dialog가 열린다.
- [ ]
onExploreSelectpayload가 그대로 전달된다. - [ ] account avatar가 image 또는 display name 첫 글자로 표시된다.
- [ ] account dropdown이 열리고 닫힌다.
- [ ] account item callback과
onAccountMenuItemSelect호출 순서가 유지된다. - [ ] logout item은
logoutPending=true일 때 disabled다. - [ ]
onLogout이 account item으로 노출되고 중복 호출되지 않는다. - [ ] theme mode, hero visibility, locale controls가 기존 state client와 연결된다.
4. PortalAdminShell 테스트
- [ ]
commonAdminItems와portalAdminItems가 order 순서로 정렬된다. - [ ]
visible=false항목이 navigation model에 포함되지 않는다. - [ ]
requiredRole이 현재adminRole과 다르면 항목이 제거된다. - [ ]
requiredCapability정책이 있다면 UI가 정책을 중복 판단하지 않고 adapter 결과를 사용한다. - [ ]
activeAdminMenuId가 selected navigation state로 전달된다. - [ ] 관리자 항목의
onSelectpayload와 호출 시점이 유지된다. - [ ]
onReturnToPortal이 있으면 role switch가 노출된다. - [ ]
onReturnToPortal이 없으면 role switch가 노출되지 않는다. - [ ] 일반 사용자에게 관리자 메뉴가 표시되지 않는다.
- [ ] 관리자 shell이 별도 layout을 복제하지 않고 PortalShell class와 landmark를 공유한다.
5. PortalThemePolicySettings 테스트
- [ ] APPROVED와 PUBLISHED 테마만 selectable catalog에 포함된다.
- [ ] DRAFT, VALIDATED, RETIRED 테마가 사용자 선택 목록에 표시되지 않는다.
- [ ]
FORCED선택 시allowUserOverride=false가 된다. - [ ]
ALLOW_USER_CHOICE선택 시allowUserOverride=true가 된다. - [ ] selectable theme checkbox group이 policy key set과 동기화된다.
- [ ] 마지막 selectable theme이 default theme이면 제거할 수 없다.
- [ ] default theme 제거 시 첫 번째 남은 selectable theme으로 안전하게 대체된다.
- [ ] selectable theme이 없으면 default select가 disabled다.
- [ ] catalog가 비어 있으면 empty message가 표시된다.
- [ ]
saving=true에서 save button이 loading 또는 disabled 상태다. - [ ] default theme이 없으면 save가 disabled다.
- [ ] save callback payload가 UI state와 동일하다.
- [ ]
portalName, section heading, helper text가 유지된다. - [ ]
Card,Radio.Group,Checkbox.Group,Select,Button이 연결된다.
6. Menu adapter와 icon facade 테스트
- [ ] 1-depth item이
MenuItemType으로 변환된다. - [ ] 2-depth group이
SubMenuType으로 변환된다. - [ ] active leaf가 selected key 하나로 계산된다.
- [ ] active child의 모든 부모 group이 open key로 계산된다.
- [ ] 권한 없는 항목을 adapter가 다시 생성하지 않는다.
- [ ] external link의 target과 rel이 유지된다.
- [ ] item
onSelect와 adapteronNavigate가 각각 한 번씩 호출된다. - [ ] unknown icon key가 안전한 fallback icon을 사용한다.
- [ ] known icon key가 Ant Design icon component로 매핑된다.
- [ ] decorative icon은
aria-hidden="true"다. - [ ] meaningful icon은 accessible title 또는 label을 가진다.
7. productMarks 테스트
- [ ] 기존 product mark export가 유지된다.
- [ ] compact와 normal variant가 모두 렌더링된다.
- [ ] mark color가 design token 또는
currentColor를 사용한다. - [ ] light/dark theme에서 mark contrast가 유지된다.
- [ ] 장식용 mark의 내부 SVG 또는 icon이
aria-hidden이다. - [ ] 제품명과 mark가 함께 있을 때 accessible name이 중복되지 않는다.
8. DirectoryManagement 테스트
- [ ] organization/user tab 전환 callback이 유지된다.
- [ ] controlled active tab과 default tab이 각각 동작한다.
- [ ] search/filter 값이 adapter callback에 전달된다.
- [ ] pagination page와 pageSize가 기존 payload로 전달된다.
- [ ] row key가 안정적이고 중복되지 않는다.
- [ ] row action callback이 올바른 entity를 전달한다.
- [ ] loading 상태에서
Spin과 loading label이 표시된다. - [ ] empty 상태에서
Empty와 설명이 표시된다. - [ ] error 상태에서
Alert와 retry callback이 표시된다. - [ ] unauthorized 상태에서 directory data가 렌더링되지 않는다.
9. dataShells 테스트
- [ ] list loading, empty, error, success 상태가 서로 배타적으로 렌더링된다.
- [ ] table column 순서와 cell renderer가 유지된다.
- [ ] selected row id와 selection callback이 유지된다.
- [ ] detail field 순서와 missing value 표시가 유지된다.
- [ ] retry, dismiss, action callback payload가 유지된다.
- [ ]
DataStatePanel의 tone이Alert,Result,Empty상태에 올바르게 매핑된다. - [ ] status badge의 label과 tone class가 유지된다.
- [ ] skeleton이 실제 data와 동시에 중복 표시되지 않는다.
- [ ] pagination 및 sorting이 기존 public API를 변경하지 않는다.
10. contentComponents 테스트
- [ ]
PortalStack,PortalToolbar,PortalPanel,PortalCard의 class와 spacing contract가 유지된다. - [ ] text field의
value,defaultValue,onChangepayload가 유지된다. - [ ] textarea의 rows, disabled, error state가 유지된다.
- [ ] select의 native value semantics가 Ant Design Select value로 정확히 변환된다.
- [ ] tabs의 active key와 change callback이 유지된다.
- [ ] data table의 columns, row key, selection, empty state가 유지된다.
- [ ] dialog open/close, Escape, backdrop, action slot이 유지된다.
- [ ] dialog title id와
aria-labelledby연결이 유지된다. - [ ] tree의 expanded ids와 selected id가 controlled/uncontrolled 양쪽에서 동작한다.
- [ ] disabled tree node가 select callback을 호출하지 않는다.
11. assetAttachments 테스트
- [ ] asset name과 formatted size가 렌더링된다.
- [ ] custom
formatSize가 사용된다. - [ ] custom
renderIcon이 사용된다. - [ ] download URL이 target
_blank와noreferrer를 유지한다. - [ ] download URL이 없으면 download action이 표시되지 않는다.
- [ ] editable=false이면 delete action이 표시되지 않는다.
- [ ] delete callback이 정확한 asset을 전달한다.
- [ ] assets가 비어 있으면
Empty와 empty label이 표시된다. - [ ] delete button의 accessible name이
deleteLabel과 일치한다.
12. 접근성 자동 검증
각 대상 컴포넌트의 대표 상태에 대해 axe 검사를 실행한다.
- [ ] PortalShell desktop
- [ ] PortalShell collapsed
- [ ] PortalShell mobile open
- [ ] PortalShell account dropdown open
- [ ] PortalShell explore dialog open
- [ ] PortalAdminShell administrator
- [ ] PortalAdminShell unauthorized
- [ ] PortalThemePolicySettings editable
- [ ] PortalThemePolicySettings saving
- [ ] DirectoryManagement loading, empty, error, success
- [ ] dataShells loading, empty, error, success
- [ ] content dialog open
- [ ] content tree expanded
- [ ] asset attachment list with actions
자동 검증에서 특히 확인할 규칙은 button-name, aria-allowed-attr, aria-required-children, color-contrast, dialog-name, duplicate-id, form-field-multiple-labels, landmark-unique, link-name, nested-interactive, tabindex, valid-aria-attribute다.
13. 키보드 및 focus 수동 검증
- [ ] Tab 순서가 header → sidebar trigger → search → account → main content 순서로 논리적이다.
- [ ] Menu가 ArrowUp, ArrowDown, Enter, Escape로 조작된다.
- [ ] collapsed Sider의 icon item이 tooltip 또는 accessible title을 가진다.
- [ ] Dropdown open 시 focus가 popup으로 이동하고 Escape로 복귀한다.
- [ ] Modal open 시 focus가 dialog 내부에 머문다.
- [ ] Modal close 후 trigger로 focus가 돌아온다.
- [ ] Radio, Checkbox, Select가 마우스 없이 변경된다.
- [ ] Table row selection이 keyboard로 가능하다.
- [ ] Tree expand/collapse와 selection이 keyboard로 가능하다.
- [ ] disabled control은 focus와 callback에서 제외된다.
- [ ] link와 button의 역할이 혼용되지 않는다.
14. 반응형·테마·브라우저 검증
- [ ] desktop 1280px에서 sidebar와 content가 겹치지 않는다.
- [ ] collapsed sidebar에서 content width가 정상적으로 확장된다.
- [ ] mobile 390px에서 open sidebar, backdrop, close action이 동작한다.
- [ ] mobile에서 horizontal overflow가 발생하지 않는다.
- [ ] light theme에서 text, border, interactive state contrast가 충분하다.
- [ ] dark theme에서 Ant Design token과 portal token이 충돌하지 않는다.
- [ ] reduced motion 환경에서 transition 의존 기능이 동작한다.
- [ ] Chrome, Firefox, Safari 계열에서 focus outline이 사라지지 않는다.
15. CI 완료 기준
- [ ]
pnpm --filter @hohyun94/portal-foundation... build통과 - [ ]
pnpm --filter @hohyun94/portal-foundation check통과 - [ ]
pnpm --filter @hohyun94/portal-foundation test통과 - [ ] axe 대표 상태 검사 통과
- [ ] TypeScript public declaration 생성 결과에서 기존 export가 유지됨
- [ ] 변경된 portal class contract 목록을 문서화함
- [ ] 실패한 접근성 검사의 원인과 예외를 기록함
- [ ] 각 작업 커밋이 독립적으로 revert 가능함