@potenlab/ui 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +756 -0
- package/package.json +8 -3
- package/template/admin/README.md +36 -0
- package/template/admin/_gitignore +41 -0
- package/template/admin/components.json +23 -0
- package/template/admin/docs/changes.json +295 -0
- package/template/admin/docs/dev-plan.md +822 -0
- package/template/admin/docs/frontend-plan.md +874 -0
- package/template/admin/docs/prd.md +408 -0
- package/template/admin/docs/progress.json +777 -0
- package/template/admin/docs/test-plan.md +790 -0
- package/template/admin/docs/ui-ux-plan.md +1664 -0
- package/template/admin/eslint.config.mjs +18 -0
- package/template/admin/next.config.ts +7 -0
- package/template/admin/package.json +43 -0
- package/template/admin/postcss.config.mjs +7 -0
- package/template/admin/public/avatars/user1.svg +4 -0
- package/template/admin/public/avatars/user2.svg +4 -0
- package/template/admin/public/avatars/user3.svg +4 -0
- package/template/admin/public/avatars/user4.svg +4 -0
- package/template/admin/public/avatars/user5.svg +4 -0
- package/template/admin/public/file.svg +1 -0
- package/template/admin/public/globe.svg +1 -0
- package/template/admin/public/next.svg +1 -0
- package/template/admin/public/profile/img1.svg +7 -0
- package/template/admin/public/profile/img2.svg +7 -0
- package/template/admin/public/profile/img3.svg +7 -0
- package/template/admin/public/vercel.svg +1 -0
- package/template/admin/public/window.svg +1 -0
- package/template/admin/src/app/favicon.ico +0 -0
- package/template/admin/src/app/layout.tsx +38 -0
- package/template/admin/src/app/page.tsx +5 -0
- package/template/admin/src/app/users/[id]/page.tsx +10 -0
- package/template/admin/src/components/layouts/app-sidebar.tsx +152 -0
- package/template/admin/src/components/user-management/profile-images.tsx +69 -0
- package/template/admin/src/components/user-management/user-detail-form.tsx +143 -0
- package/template/admin/src/features/user-management/components/user-columns.tsx +101 -0
- package/template/admin/src/features/user-management/components/user-detail.tsx +79 -0
- package/template/admin/src/features/user-management/components/user-list.tsx +74 -0
- package/template/admin/src/features/user-management/types/index.ts +113 -0
- package/template/admin/src/features/user-management/utils/format.ts +2 -0
- package/template/admin/src/lib/mock-data.ts +131 -0
- package/template/admin/src/styles/globals.css +26 -0
- package/template/admin/tsconfig.json +34 -0
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
# Test Plan
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
- **Project:** Admin User Management UI (frontend only)
|
|
6
|
+
- **Testing approach:** Component testing with Vitest + React Testing Library
|
|
7
|
+
- **No backend/API testing** -- all data is mock/static
|
|
8
|
+
- **Platform:** Desktop-only (1920px target viewport)
|
|
9
|
+
- **Tech Stack:** Next.js 16 (App Router), TypeScript, shadcn/ui, Tailwind CSS 4
|
|
10
|
+
- **Source documents:** `dev-plan.md` (phases/tasks), `frontend-plan.md` (component specs), `prd.md` (requirements/user stories)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Test Infrastructure
|
|
15
|
+
|
|
16
|
+
### Vitest Configuration
|
|
17
|
+
|
|
18
|
+
- **Environment:** `jsdom` for all component tests
|
|
19
|
+
- **Config file:** `vitest.config.ts` at project root
|
|
20
|
+
- **Required settings:**
|
|
21
|
+
- `environment: "jsdom"`
|
|
22
|
+
- `globals: true` (allows `describe`, `it`, `expect` without imports)
|
|
23
|
+
- `setupFiles: ["./tests/setup.ts"]`
|
|
24
|
+
- Path alias resolution matching `tsconfig.json` (`@/` -> `src/`)
|
|
25
|
+
- CSS module handling (mock or transform Tailwind CSS imports)
|
|
26
|
+
- Asset handling (mock image imports: `.jpg`, `.png`, `.svg`)
|
|
27
|
+
|
|
28
|
+
### React Testing Library Setup
|
|
29
|
+
|
|
30
|
+
- **Packages required:**
|
|
31
|
+
- `@testing-library/react` -- render, screen, fireEvent, waitFor
|
|
32
|
+
- `@testing-library/jest-dom` -- custom matchers (toBeInTheDocument, toHaveClass, toHaveAttribute, etc.)
|
|
33
|
+
- `@testing-library/user-event` -- realistic user interaction simulation (click, type, keyboard)
|
|
34
|
+
- `jsdom` -- DOM environment for Vitest
|
|
35
|
+
|
|
36
|
+
### Setup File (`tests/setup.ts`)
|
|
37
|
+
|
|
38
|
+
- Import `@testing-library/jest-dom/vitest` for matcher extensions
|
|
39
|
+
- Mock `next/navigation` (`useRouter`, `usePathname`, `useParams`)
|
|
40
|
+
- Mock `next/image` to render as standard `<img>` element
|
|
41
|
+
- Mock `next/link` to render as standard `<a>` element
|
|
42
|
+
- Mock `next/font/google` to return CSS variable objects
|
|
43
|
+
|
|
44
|
+
### Test Utilities Needed
|
|
45
|
+
|
|
46
|
+
- **Custom render helper** (`tests/utils/render.tsx`): Wraps components with necessary providers (if any) and exports a pre-configured `render` function
|
|
47
|
+
- **Mock data re-exports** (`tests/utils/mock-data.ts`): Re-exports `mockUsers` and `totalUserCount` from `src/lib/mock-data.ts` for test file convenience
|
|
48
|
+
- **Mock router** (`tests/utils/mock-router.ts`): Provides configurable mock for `useRouter().push()`, `usePathname()`, `useParams()` with assertion helpers
|
|
49
|
+
|
|
50
|
+
### Directory Structure
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
tests/
|
|
54
|
+
├── setup.ts # Global test setup
|
|
55
|
+
├── utils/
|
|
56
|
+
│ ├── render.tsx # Custom render helper
|
|
57
|
+
│ ├── mock-data.ts # Re-exports of mock data
|
|
58
|
+
│ └── mock-router.ts # Next.js router mocks
|
|
59
|
+
├── lib/
|
|
60
|
+
│ ├── mock-data.test.ts # Mock data structure validation
|
|
61
|
+
│ └── utils.test.ts # Utility function tests
|
|
62
|
+
├── components/
|
|
63
|
+
│ ├── ui/
|
|
64
|
+
│ │ ├── button.test.tsx # Button variants
|
|
65
|
+
│ │ ├── table.test.tsx # Table sub-components
|
|
66
|
+
│ │ ├── input.test.tsx # Input component
|
|
67
|
+
│ │ ├── select.test.tsx # Select component
|
|
68
|
+
│ │ ├── badge.test.tsx # Badge variants
|
|
69
|
+
│ │ ├── avatar.test.tsx # Avatar + fallback
|
|
70
|
+
│ │ ├── switch.test.tsx # Switch toggle
|
|
71
|
+
│ │ ├── tabs.test.tsx # Tabs active/inactive
|
|
72
|
+
│ │ ├── separator.test.tsx # Separator rendering
|
|
73
|
+
│ │ ├── accordion.test.tsx # Accordion expand/collapse
|
|
74
|
+
│ │ ├── label.test.tsx # Label + htmlFor
|
|
75
|
+
│ │ ├── card.test.tsx # Card container
|
|
76
|
+
│ │ └── tooltip.test.tsx # Tooltip rendering
|
|
77
|
+
│ ├── common/
|
|
78
|
+
│ │ └── page-header.test.tsx # PageHeader composite
|
|
79
|
+
│ └── layouts/
|
|
80
|
+
│ ├── app-sidebar.test.tsx # Sidebar navigation
|
|
81
|
+
│ └── content-layout.test.tsx # Content wrapper
|
|
82
|
+
├── features/
|
|
83
|
+
│ └── users/
|
|
84
|
+
│ ├── tab-navigation.test.tsx # Tab switching
|
|
85
|
+
│ ├── search-bar.test.tsx # Search input
|
|
86
|
+
│ ├── pagination-controls.test.tsx # Pagination UI
|
|
87
|
+
│ ├── user-table.test.tsx # Data table
|
|
88
|
+
│ ├── profile-images.test.tsx # Profile image display
|
|
89
|
+
│ ├── user-detail-form.test.tsx # Detail form fields + toggles
|
|
90
|
+
│ ├── dashboard-page.test.tsx # Dashboard page assembly
|
|
91
|
+
│ └── user-detail-page.test.tsx # User detail page assembly
|
|
92
|
+
└── accessibility/
|
|
93
|
+
└── a11y.test.tsx # Cross-cutting accessibility tests
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Phase-by-Phase Test Scenarios
|
|
99
|
+
|
|
100
|
+
### Phase 0: Foundation
|
|
101
|
+
|
|
102
|
+
#### P0-TS1: Mock Data Structure Validation
|
|
103
|
+
|
|
104
|
+
**Test file:** `tests/lib/mock-data.test.ts`
|
|
105
|
+
**Source tasks:** P0-T4, P0-T5
|
|
106
|
+
|
|
107
|
+
| # | Scenario | Expected Result |
|
|
108
|
+
|---|----------|-----------------|
|
|
109
|
+
| 1 | `mockUsers` is exported and is an array | `Array.isArray(mockUsers)` is `true` |
|
|
110
|
+
| 2 | `mockUsers` has exactly 5 entries | `mockUsers.length === 5` |
|
|
111
|
+
| 3 | `totalUserCount` is exported and equals 100000 | `totalUserCount === 100000` |
|
|
112
|
+
| 4 | Each user has all required fields from the `User` interface | Every user object contains: `id`, `nickname`, `grade`, `avatar`, `phone`, `age`, `gender`, `region`, `joinDate`, `withdrawalDate`, `role`, `exerciseStyle`, `gymRelocation`, `bench`, `deadlift`, `squat`, `intro`, `profileImages`, `settings` |
|
|
113
|
+
| 5 | Each user has a unique `id` string ("1" through "5") | No duplicate `id` values; all are string type |
|
|
114
|
+
| 6 | `profileImages` is an array of 3 strings per user | `user.profileImages.length === 3` for each user |
|
|
115
|
+
| 7 | `settings` contains all 3 boolean fields | Each user has `settings.profilePublic`, `settings.matchChatNotification`, `settings.marketingNotification` as booleans |
|
|
116
|
+
| 8 | Avatar paths follow expected pattern | Each `user.avatar` is a non-empty string |
|
|
117
|
+
|
|
118
|
+
#### P0-TS2: Utility Function Tests
|
|
119
|
+
|
|
120
|
+
**Test file:** `tests/lib/utils.test.ts`
|
|
121
|
+
**Source tasks:** P0-T6
|
|
122
|
+
|
|
123
|
+
| # | Scenario | Expected Result |
|
|
124
|
+
|---|----------|-----------------|
|
|
125
|
+
| 1 | `formatNumber(100000)` formats with commas | Returns `"100,000"` |
|
|
126
|
+
| 2 | `formatNumber(0)` returns zero string | Returns `"0"` |
|
|
127
|
+
| 3 | `formatNumber(1)` returns single digit | Returns `"1"` |
|
|
128
|
+
| 4 | `formatNumber(1000)` formats with comma | Returns `"1,000"` |
|
|
129
|
+
| 5 | `formatNumber(1000000)` formats with two commas | Returns `"1,000,000"` |
|
|
130
|
+
| 6 | `cn()` utility merges class names | `cn("px-4", "py-2")` returns combined string |
|
|
131
|
+
| 7 | `cn()` handles conditional classes | `cn("px-4", false && "hidden")` excludes falsy |
|
|
132
|
+
| 8 | `cn()` resolves Tailwind conflicts | `cn("px-4", "px-8")` resolves to `"px-8"` (tailwind-merge behavior) |
|
|
133
|
+
|
|
134
|
+
#### P0-TS3: TypeScript Types Compilation
|
|
135
|
+
|
|
136
|
+
**Test file:** `tests/lib/utils.test.ts` (type-level validation within the same file)
|
|
137
|
+
**Source tasks:** P0-T4
|
|
138
|
+
|
|
139
|
+
| # | Scenario | Expected Result |
|
|
140
|
+
|---|----------|-----------------|
|
|
141
|
+
| 1 | `User` interface is importable | Import succeeds without TypeScript errors |
|
|
142
|
+
| 2 | `UserTableColumn` type is importable | Import succeeds |
|
|
143
|
+
| 3 | `USER_TABLE_COLUMNS` constant has 10 entries | `USER_TABLE_COLUMNS.length === 10` |
|
|
144
|
+
| 4 | `PaginationState` type and `DEFAULT_PAGINATION` are importable | Import succeeds, `DEFAULT_PAGINATION.currentPage === 1` |
|
|
145
|
+
| 5 | `SidebarNavItem` and `SidebarSubItem` types are importable from shared types | Import from `@/types/index` succeeds |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### Phase 1: Shared UI Components
|
|
150
|
+
|
|
151
|
+
#### P1-TS1: Button Component
|
|
152
|
+
|
|
153
|
+
**Test file:** `tests/components/ui/button.test.tsx`
|
|
154
|
+
**Source tasks:** P1-T2
|
|
155
|
+
**PRD mapping:** Buttons used across all pages (US-1, US-5, US-6, US-7, US-8)
|
|
156
|
+
|
|
157
|
+
| # | Scenario | Expected Result |
|
|
158
|
+
|---|----------|-----------------|
|
|
159
|
+
| 1 | Renders with default (primary) variant | Button renders with text content visible |
|
|
160
|
+
| 2 | Primary variant has correct CSS classes | Contains `bg-primary` and `text-white` classes |
|
|
161
|
+
| 3 | Secondary variant renders correctly | Contains `bg-sidebar-selected` and `text-black` classes |
|
|
162
|
+
| 4 | Pagination variant renders as square | Contains `bg-primary-light` and dimension classes |
|
|
163
|
+
| 5 | Ghost variant renders with transparent bg | Contains `bg-transparent` and `text-delete-text` classes |
|
|
164
|
+
| 6 | Disabled state applies opacity and cursor | Disabled button has `opacity-50` and `cursor-not-allowed` styles |
|
|
165
|
+
| 7 | Click handler fires on enabled button | `onClick` callback is called once |
|
|
166
|
+
| 8 | Click handler does NOT fire on disabled button | `onClick` callback is not called |
|
|
167
|
+
| 9 | Focus ring classes are present | Contains `focus-visible:ring-2` and `focus-visible:ring-primary` |
|
|
168
|
+
| 10 | Button renders children content | Text content passed as children is visible |
|
|
169
|
+
| 11 | Button renders with icon and text | Icon element + text both render inside button |
|
|
170
|
+
|
|
171
|
+
#### P1-TS2: Table Component
|
|
172
|
+
|
|
173
|
+
**Test file:** `tests/components/ui/table.test.tsx`
|
|
174
|
+
**Source tasks:** P1-T3
|
|
175
|
+
**PRD mapping:** US-1 (View User List)
|
|
176
|
+
|
|
177
|
+
| # | Scenario | Expected Result |
|
|
178
|
+
|---|----------|-----------------|
|
|
179
|
+
| 1 | Table wrapper renders with border | Contains `border` and `border-border` classes |
|
|
180
|
+
| 2 | TableHeader renders with background | Contains `bg-table-header` class |
|
|
181
|
+
| 3 | TableHead renders with correct height and text | Contains `h-14`, `text-[14px]`, `font-semibold` classes |
|
|
182
|
+
| 4 | TableRow renders with correct height | Contains `h-[60px]` class |
|
|
183
|
+
| 5 | TableRow has hover styles | Contains `hover:bg-table-header` class |
|
|
184
|
+
| 6 | TableRow has pointer cursor | Contains `cursor-pointer` class |
|
|
185
|
+
| 7 | TableCell renders with correct padding | Contains `px-4` class |
|
|
186
|
+
| 8 | Semantic HTML: renders `<table>`, `<thead>`, `<th>`, `<tbody>`, `<tr>`, `<td>` | Query by role: `table`, `rowgroup`, `columnheader`, `row`, `cell` all found |
|
|
187
|
+
|
|
188
|
+
#### P1-TS3: Input Component
|
|
189
|
+
|
|
190
|
+
**Test file:** `tests/components/ui/input.test.tsx`
|
|
191
|
+
**Source tasks:** P1-T4
|
|
192
|
+
**PRD mapping:** US-3 (Search Users), US-7 (Edit User Detail)
|
|
193
|
+
|
|
194
|
+
| # | Scenario | Expected Result |
|
|
195
|
+
|---|----------|-----------------|
|
|
196
|
+
| 1 | Renders with placeholder text | Placeholder attribute matches passed prop |
|
|
197
|
+
| 2 | Accepts typed text input | After `userEvent.type()`, input value updates |
|
|
198
|
+
| 3 | Has correct base border class | Contains `border-border` class |
|
|
199
|
+
| 4 | Has focus styles | Contains `focus:border-primary` class |
|
|
200
|
+
| 5 | Disabled state applies correct styles | Contains `disabled:bg-table-header` and `disabled:cursor-not-allowed` |
|
|
201
|
+
| 6 | Passes `id` attribute for label association | `id` attribute matches passed prop |
|
|
202
|
+
| 7 | Renders with custom height class (h-12 or h-[52px]) | When className includes `h-12`, element has that class |
|
|
203
|
+
|
|
204
|
+
#### P1-TS4: Select Component
|
|
205
|
+
|
|
206
|
+
**Test file:** `tests/components/ui/select.test.tsx`
|
|
207
|
+
**Source tasks:** P1-T5
|
|
208
|
+
**PRD mapping:** US-7 (Edit User Detail)
|
|
209
|
+
|
|
210
|
+
| # | Scenario | Expected Result |
|
|
211
|
+
|---|----------|-----------------|
|
|
212
|
+
| 1 | SelectTrigger renders with placeholder text | Trigger button displays placeholder |
|
|
213
|
+
| 2 | SelectTrigger has correct height | Contains `h-[52px]` class |
|
|
214
|
+
| 3 | Clicking trigger opens dropdown content | After click, SelectContent is visible |
|
|
215
|
+
| 4 | SelectItem renders option text | Each option text is visible in opened dropdown |
|
|
216
|
+
| 5 | Selecting an item updates the displayed value | Trigger shows selected option text |
|
|
217
|
+
| 6 | Escape key closes the dropdown | After Escape, content is hidden |
|
|
218
|
+
| 7 | Chevron icon renders inside trigger | Chevron/arrow icon element is present |
|
|
219
|
+
|
|
220
|
+
#### P1-TS5: Badge Component
|
|
221
|
+
|
|
222
|
+
**Test file:** `tests/components/ui/badge.test.tsx`
|
|
223
|
+
**Source tasks:** P1-T6
|
|
224
|
+
**PRD mapping:** US-1 (View User List -- header badge count)
|
|
225
|
+
|
|
226
|
+
| # | Scenario | Expected Result |
|
|
227
|
+
|---|----------|-----------------|
|
|
228
|
+
| 1 | Default variant renders | Badge element is in the document |
|
|
229
|
+
| 2 | Green variant renders with correct background class | Contains `bg-badge-green-bg` class |
|
|
230
|
+
| 3 | Green variant renders with correct text class | Contains `text-badge-green-text` class |
|
|
231
|
+
| 4 | Badge displays content text | Text "100,000" is visible |
|
|
232
|
+
| 5 | Green variant has pill shape | Contains `rounded-full` class |
|
|
233
|
+
| 6 | Badge renders with correct font weight | Contains `font-bold` class |
|
|
234
|
+
|
|
235
|
+
#### P1-TS6: Avatar Component
|
|
236
|
+
|
|
237
|
+
**Test file:** `tests/components/ui/avatar.test.tsx`
|
|
238
|
+
**Source tasks:** P1-T7
|
|
239
|
+
**PRD mapping:** US-1 (View User List -- avatar column)
|
|
240
|
+
|
|
241
|
+
| # | Scenario | Expected Result |
|
|
242
|
+
|---|----------|-----------------|
|
|
243
|
+
| 1 | Avatar renders at 22px size | Contains `w-[22px]` and `h-[22px]` classes |
|
|
244
|
+
| 2 | Avatar is circular | Contains `rounded-full` class |
|
|
245
|
+
| 3 | AvatarImage has `alt` attribute | `<img>` has non-empty `alt` text |
|
|
246
|
+
| 4 | AvatarFallback renders when image fails | Fallback element visible with single letter |
|
|
247
|
+
| 5 | AvatarFallback has correct background | Contains `bg-sidebar-selected` class |
|
|
248
|
+
|
|
249
|
+
#### P1-TS7: Switch Component
|
|
250
|
+
|
|
251
|
+
**Test file:** `tests/components/ui/switch.test.tsx`
|
|
252
|
+
**Source tasks:** P1-T8
|
|
253
|
+
**PRD mapping:** US-7 (Edit User Detail -- toggle settings)
|
|
254
|
+
|
|
255
|
+
| # | Scenario | Expected Result |
|
|
256
|
+
|---|----------|-----------------|
|
|
257
|
+
| 1 | Switch renders in unchecked (OFF) state | `aria-checked` is `"false"` |
|
|
258
|
+
| 2 | Switch renders in checked (ON) state | When `checked={true}`, `aria-checked` is `"true"` |
|
|
259
|
+
| 3 | Clicking toggles the switch state | After click, `onCheckedChange` callback fires with opposite boolean |
|
|
260
|
+
| 4 | Space key toggles the switch | After `userEvent.keyboard(" ")` while focused, callback fires |
|
|
261
|
+
| 5 | Disabled switch does not toggle on click | `onCheckedChange` is not called |
|
|
262
|
+
| 6 | Switch has correct dimensions | Contains `w-[44px]` and `h-[24px]` classes |
|
|
263
|
+
| 7 | `role="switch"` is present | Element has `role="switch"` attribute |
|
|
264
|
+
|
|
265
|
+
#### P1-TS8: Tabs Component
|
|
266
|
+
|
|
267
|
+
**Test file:** `tests/components/ui/tabs.test.tsx`
|
|
268
|
+
**Source tasks:** P1-T9
|
|
269
|
+
**PRD mapping:** US-4 (Filter by Tabs)
|
|
270
|
+
|
|
271
|
+
| # | Scenario | Expected Result |
|
|
272
|
+
|---|----------|-----------------|
|
|
273
|
+
| 1 | TabsList renders with no background | Contains `bg-transparent` class |
|
|
274
|
+
| 2 | Active TabsTrigger has primary background | Active trigger contains `data-[state=active]` styles with `bg-primary` |
|
|
275
|
+
| 3 | Inactive TabsTrigger has gray background | Inactive trigger contains `bg-inactive-tab` class |
|
|
276
|
+
| 4 | Active trigger has correct height | Contains `h-[52px]` class |
|
|
277
|
+
| 5 | Clicking inactive tab activates it | After click, tab's `aria-selected` becomes `"true"` |
|
|
278
|
+
| 6 | Previously active tab becomes inactive | After clicking another tab, first tab's `aria-selected` becomes `"false"` |
|
|
279
|
+
| 7 | `aria-selected` attribute is present on all tabs | Each trigger has `aria-selected` attribute |
|
|
280
|
+
|
|
281
|
+
#### P1-TS9: Separator Component
|
|
282
|
+
|
|
283
|
+
**Test file:** `tests/components/ui/separator.test.tsx`
|
|
284
|
+
**Source tasks:** P1-T10
|
|
285
|
+
|
|
286
|
+
| # | Scenario | Expected Result |
|
|
287
|
+
|---|----------|-----------------|
|
|
288
|
+
| 1 | Separator renders as a horizontal line | Element has `role="separator"` or renders as `<hr>` |
|
|
289
|
+
| 2 | Has correct background color class | Contains `bg-divider` class |
|
|
290
|
+
| 3 | Renders at full width of parent | Element is in the document and visible |
|
|
291
|
+
|
|
292
|
+
#### P1-TS10: Accordion Component
|
|
293
|
+
|
|
294
|
+
**Test file:** `tests/components/ui/accordion.test.tsx`
|
|
295
|
+
**Source tasks:** P1-T11
|
|
296
|
+
**PRD mapping:** US-2 (Navigate with Sidebar)
|
|
297
|
+
|
|
298
|
+
| # | Scenario | Expected Result |
|
|
299
|
+
|---|----------|-----------------|
|
|
300
|
+
| 1 | Accordion trigger renders with text | Trigger text is visible |
|
|
301
|
+
| 2 | Content is hidden by default (when collapsed) | Content region is not visible initially |
|
|
302
|
+
| 3 | Clicking trigger expands content | After click, content becomes visible |
|
|
303
|
+
| 4 | Clicking expanded trigger collapses content | After second click, content is hidden |
|
|
304
|
+
| 5 | `aria-expanded` toggles on trigger | Attribute changes from `"false"` to `"true"` on expand |
|
|
305
|
+
| 6 | Enter key toggles accordion | After `userEvent.keyboard("{Enter}")` while trigger focused, content toggles |
|
|
306
|
+
| 7 | Space key toggles accordion | After `userEvent.keyboard(" ")` while trigger focused, content toggles |
|
|
307
|
+
|
|
308
|
+
#### P1-TS11: Label Component
|
|
309
|
+
|
|
310
|
+
**Test file:** `tests/components/ui/label.test.tsx`
|
|
311
|
+
**Source tasks:** P1-T12
|
|
312
|
+
|
|
313
|
+
| # | Scenario | Expected Result |
|
|
314
|
+
|---|----------|-----------------|
|
|
315
|
+
| 1 | Label renders with text content | Label text is visible |
|
|
316
|
+
| 2 | `htmlFor` attribute is set | Label element has `for` attribute matching provided value |
|
|
317
|
+
| 3 | Clicking label focuses associated input | After click, associated input element receives focus |
|
|
318
|
+
| 4 | Has correct font styling classes | Contains `font-pretendard`, `text-[20px]`, `font-semibold` classes |
|
|
319
|
+
|
|
320
|
+
#### P1-TS12: Card Component
|
|
321
|
+
|
|
322
|
+
**Test file:** `tests/components/ui/card.test.tsx`
|
|
323
|
+
**Source tasks:** P1-T1
|
|
324
|
+
|
|
325
|
+
| # | Scenario | Expected Result |
|
|
326
|
+
|---|----------|-----------------|
|
|
327
|
+
| 1 | Card renders children content | Children text is visible |
|
|
328
|
+
| 2 | Card has white background | Contains `bg-white` class |
|
|
329
|
+
| 3 | Card has border | Contains `border` and `border-border` classes |
|
|
330
|
+
| 4 | Card has rounded corners | Contains `rounded-lg` class |
|
|
331
|
+
| 5 | Card has 32px padding | Contains `p-8` class |
|
|
332
|
+
|
|
333
|
+
#### P1-TS13: PageHeader Component
|
|
334
|
+
|
|
335
|
+
**Test file:** `tests/components/common/page-header.test.tsx`
|
|
336
|
+
**Source tasks:** P1-T13
|
|
337
|
+
**PRD mapping:** US-1 (Dashboard header), US-6/US-7 (Detail page header)
|
|
338
|
+
|
|
339
|
+
| # | Scenario | Expected Result |
|
|
340
|
+
|---|----------|-----------------|
|
|
341
|
+
| 1 | Renders title text | "User Management" heading is visible |
|
|
342
|
+
| 2 | Renders subtitle text | "User list management page" is visible |
|
|
343
|
+
| 3 | Renders badge with formatted count when `badgeCount` provided | "100,000" is visible inside a Badge element |
|
|
344
|
+
| 4 | Badge is NOT rendered when `badgeCount` is omitted | No badge element in the document |
|
|
345
|
+
| 5 | Renders action button with label | "Write" button text is visible |
|
|
346
|
+
| 6 | Action button click fires `onAction` callback | Callback is called once on click |
|
|
347
|
+
| 7 | Renders separator below the header content | Separator element is in the document |
|
|
348
|
+
| 8 | Title uses heading element (`<h1>`) | `role="heading"` with level 1 is present |
|
|
349
|
+
| 9 | Action button renders with icon when `actionIcon` is provided | Icon element renders inside button |
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
### Phase 2: Layout & Navigation
|
|
354
|
+
|
|
355
|
+
#### P2-TS1: AppSidebar Component
|
|
356
|
+
|
|
357
|
+
**Test file:** `tests/components/layouts/app-sidebar.test.tsx`
|
|
358
|
+
**Source tasks:** P2-T1
|
|
359
|
+
**PRD mapping:** US-2 (Navigate with Sidebar), US-9 (Logout)
|
|
360
|
+
|
|
361
|
+
| # | Scenario | Expected Result |
|
|
362
|
+
|---|----------|-----------------|
|
|
363
|
+
| 1 | Sidebar renders "ADMIN" title | Text "ADMIN" is visible |
|
|
364
|
+
| 2 | Sidebar renders admin email/ID | "admin@potenlab.com" (or equivalent) is visible |
|
|
365
|
+
| 3 | "Home" menu item renders | Text "Home" is visible |
|
|
366
|
+
| 4 | "User" accordion section renders | Text "User" is visible as accordion trigger |
|
|
367
|
+
| 5 | "Match" accordion section renders | Text "Match" is visible as accordion trigger |
|
|
368
|
+
| 6 | "Admin" accordion section renders | Text "Admin" is visible as accordion trigger |
|
|
369
|
+
| 7 | Expanding "User" accordion reveals "User Management" sub-item | After clicking "User" trigger, "User Management" link is visible |
|
|
370
|
+
| 8 | Expanding "Match" accordion reveals "Match Management" sub-item | After clicking "Match" trigger, "Match Management" link is visible |
|
|
371
|
+
| 9 | Expanding "Admin" accordion reveals 3 sub-items | After clicking "Admin" trigger, "Notice Management", "Report Management", "Terms Management" are visible |
|
|
372
|
+
| 10 | Active sub-menu item has highlight background | When `usePathname()` returns `/`, "User Management" has `bg-sidebar-selected` class or equivalent active styling |
|
|
373
|
+
| 11 | Logout button renders at bottom | Text "Logout" is visible |
|
|
374
|
+
| 12 | Logout button has icon | LogOut icon element is present near "Logout" text |
|
|
375
|
+
| 13 | Sidebar has `<nav>` landmark | `role="navigation"` or `<nav>` element is present |
|
|
376
|
+
| 14 | Sub-menu items render as links | "User Management" renders as `<a>` with `href="/"` |
|
|
377
|
+
| 15 | Accordion trigger toggles `aria-expanded` | After clicking "User" trigger, `aria-expanded` changes to `"true"` |
|
|
378
|
+
|
|
379
|
+
#### P2-TS2: ContentLayout Component
|
|
380
|
+
|
|
381
|
+
**Test file:** `tests/components/layouts/content-layout.test.tsx`
|
|
382
|
+
**Source tasks:** P2-T2
|
|
383
|
+
|
|
384
|
+
| # | Scenario | Expected Result |
|
|
385
|
+
|---|----------|-----------------|
|
|
386
|
+
| 1 | Renders children content | Children text is visible in the document |
|
|
387
|
+
| 2 | Has correct left margin offset | Contains `ml-[324px]` class |
|
|
388
|
+
| 3 | Has correct top padding | Contains `pt-5` class |
|
|
389
|
+
| 4 | Has correct right padding | Contains `pr-8` class |
|
|
390
|
+
| 5 | Has correct bottom padding | Contains `pb-8` class |
|
|
391
|
+
|
|
392
|
+
#### P2-TS3: Root Layout
|
|
393
|
+
|
|
394
|
+
**Test file:** (Tested implicitly through page-level tests; layout structure verified via integration)
|
|
395
|
+
**Source tasks:** P2-T3
|
|
396
|
+
|
|
397
|
+
| # | Scenario | Expected Result |
|
|
398
|
+
|---|----------|-----------------|
|
|
399
|
+
| 1 | Skip link is rendered as first focusable element | `<a>` with text "Skip to main content" exists with `sr-only` class |
|
|
400
|
+
| 2 | Skip link href points to `#main-content` | `href` attribute is `#main-content` |
|
|
401
|
+
| 3 | Main content wrapper has `id="main-content"` | `<main>` element with `id="main-content"` exists |
|
|
402
|
+
| 4 | `<html>` element has `lang="ko"` | HTML lang attribute is "ko" |
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
### Phase 3: Dashboard (User Management List Page)
|
|
407
|
+
|
|
408
|
+
#### P3-TS1: TabNavigation Component
|
|
409
|
+
|
|
410
|
+
**Test file:** `tests/features/users/tab-navigation.test.tsx`
|
|
411
|
+
**Source tasks:** P3-T1
|
|
412
|
+
**PRD mapping:** US-4 (Filter by Tabs)
|
|
413
|
+
|
|
414
|
+
| # | Scenario | Expected Result |
|
|
415
|
+
|---|----------|-----------------|
|
|
416
|
+
| 1 | Renders 3 tabs: "All", "Tab", "Tab" | All 3 tab trigger texts are visible |
|
|
417
|
+
| 2 | "All" tab is active by default | First tab has `aria-selected="true"` |
|
|
418
|
+
| 3 | Other 2 tabs are inactive by default | Second and third tabs have `aria-selected="false"` |
|
|
419
|
+
| 4 | Clicking second tab activates it | After click, second tab has `aria-selected="true"` |
|
|
420
|
+
| 5 | Clicking second tab deactivates "All" | After click, "All" tab has `aria-selected="false"` |
|
|
421
|
+
| 6 | `onTabChange` callback fires with new value | Callback receives the value of the clicked tab |
|
|
422
|
+
| 7 | Active tab has teal background styling | Active tab element has primary background class |
|
|
423
|
+
| 8 | Inactive tabs have gray background styling | Inactive tabs have `bg-inactive-tab` class |
|
|
424
|
+
|
|
425
|
+
#### P3-TS2: SearchBar Component
|
|
426
|
+
|
|
427
|
+
**Test file:** `tests/features/users/search-bar.test.tsx`
|
|
428
|
+
**Source tasks:** P3-T2
|
|
429
|
+
**PRD mapping:** US-3 (Search Users)
|
|
430
|
+
|
|
431
|
+
| # | Scenario | Expected Result |
|
|
432
|
+
|---|----------|-----------------|
|
|
433
|
+
| 1 | Renders input with placeholder "Enter search keyword" | Placeholder text is present |
|
|
434
|
+
| 2 | Input renders at full width | Input element is in the document (full-width handled by CSS) |
|
|
435
|
+
| 3 | Typing text fires `onChange` callback | After typing "hello", callback receives "hello" |
|
|
436
|
+
| 4 | Input displays the controlled `value` prop | When `value="test"`, input displays "test" |
|
|
437
|
+
| 5 | Input is focusable | After `userEvent.click()`, input has focus |
|
|
438
|
+
| 6 | Input has accessible label | `aria-label="Search users"` or associated `<label>` exists |
|
|
439
|
+
|
|
440
|
+
#### P3-TS3: PaginationControls Component
|
|
441
|
+
|
|
442
|
+
**Test file:** `tests/features/users/pagination-controls.test.tsx`
|
|
443
|
+
**Source tasks:** P3-T3
|
|
444
|
+
**PRD mapping:** US-5 (Paginate User List)
|
|
445
|
+
|
|
446
|
+
| # | Scenario | Expected Result |
|
|
447
|
+
|---|----------|-----------------|
|
|
448
|
+
| 1 | Renders 4 navigation buttons (first, prev, next, last) | 4 icon buttons are present |
|
|
449
|
+
| 2 | "Go to first page" button has `aria-label` | Button has `aria-label="Go to first page"` |
|
|
450
|
+
| 3 | "Previous page" button has `aria-label` | Button has `aria-label="Previous page"` |
|
|
451
|
+
| 4 | "Next page" button has `aria-label` | Button has `aria-label="Next page"` |
|
|
452
|
+
| 5 | "Go to last page" button has `aria-label` | Button has `aria-label="Go to last page"` |
|
|
453
|
+
| 6 | Page indicator displays "1 / 1" | Text "1" and "1" (current/total) are visible |
|
|
454
|
+
| 7 | Page jump input renders with "Page" placeholder | Input with placeholder "Page" exists |
|
|
455
|
+
| 8 | "Go" button renders | Button with text "Go" is visible |
|
|
456
|
+
| 9 | Items per page select defaults to "10 items" | Select trigger displays "10 items" |
|
|
457
|
+
| 10 | Clicking items per page select opens dropdown | After click, options "10 items", "20 items", "50 items" are visible |
|
|
458
|
+
| 11 | Selecting "20 items" fires `onItemsPerPageChange(20)` | Callback receives `20` |
|
|
459
|
+
| 12 | Navigation buttons have `pagination` variant styling | Buttons contain `bg-primary-light` class |
|
|
460
|
+
| 13 | Page indicator region has `aria-live="polite"` | Region element has `aria-live` attribute |
|
|
461
|
+
| 14 | Typing in page jump input updates its value | After typing "5", input value is "5" |
|
|
462
|
+
|
|
463
|
+
#### P3-TS4: UserTable Component
|
|
464
|
+
|
|
465
|
+
**Test file:** `tests/features/users/user-table.test.tsx`
|
|
466
|
+
**Source tasks:** P3-T4
|
|
467
|
+
**PRD mapping:** US-1 (View User List), US-6 (View User Detail), US-8 (Delete User)
|
|
468
|
+
|
|
469
|
+
| # | Scenario | Expected Result |
|
|
470
|
+
|---|----------|-----------------|
|
|
471
|
+
| 1 | Table renders 10 column headers | 10 `<th>` elements: Nickname, Grade, Avatar, Phone Number, Age, Gender, Region, Join Date, Withdrawal Date, Delete |
|
|
472
|
+
| 2 | Table renders 5 data rows from mock data | 5 `<tr>` elements in `<tbody>` |
|
|
473
|
+
| 3 | Each row displays nickname text | "NicknameHere" (or equivalent mock data) visible in each row |
|
|
474
|
+
| 4 | Each row displays grade text | "Mania" visible in each row |
|
|
475
|
+
| 5 | Avatar column renders Avatar component | `<img>` elements with `alt` attribute present in avatar cells |
|
|
476
|
+
| 6 | Phone number displays in each row | "010-1234-1234" visible |
|
|
477
|
+
| 7 | Age displays in each row | "Born 1999" visible |
|
|
478
|
+
| 8 | Gender displays in each row | "Male" visible |
|
|
479
|
+
| 9 | Region displays in each row | "Gangnam-gu" visible |
|
|
480
|
+
| 10 | Join Date displays in each row | "Nov 1, 2022" visible |
|
|
481
|
+
| 11 | Withdrawal Date displays in each row | "Nov 1, 2022" visible |
|
|
482
|
+
| 12 | Delete column renders ghost button with "Delete" text | "Delete" text button visible in each row |
|
|
483
|
+
| 13 | Row click calls `router.push("/users/{id}")` | After clicking row, `useRouter().push` called with `/users/1` (or matching id) |
|
|
484
|
+
| 14 | Delete button click does NOT trigger row navigation | After clicking Delete, `router.push` is NOT called (stopPropagation verified) |
|
|
485
|
+
| 15 | Sort icon renders on "Join Date" column header | ChevronDown icon element present in "Join Date" header |
|
|
486
|
+
| 16 | Sort icon renders on "Withdrawal Date" column header | ChevronDown icon element present in "Withdrawal Date" header |
|
|
487
|
+
| 17 | Table has semantic HTML structure | `<table>`, `<thead>`, `<tbody>`, `<th>`, `<td>` elements present |
|
|
488
|
+
| 18 | Row has hover cursor style | Table rows contain `cursor-pointer` class |
|
|
489
|
+
| 19 | Delete button has accessible label | `aria-label` includes "Delete user" and the user's nickname |
|
|
490
|
+
|
|
491
|
+
#### P3-TS5: Dashboard Page Assembly
|
|
492
|
+
|
|
493
|
+
**Test file:** `tests/features/users/dashboard-page.test.tsx`
|
|
494
|
+
**Source tasks:** P3-T5
|
|
495
|
+
**PRD mapping:** US-1 (View User List), US-3 (Search), US-4 (Tabs), US-5 (Pagination)
|
|
496
|
+
|
|
497
|
+
| # | Scenario | Expected Result |
|
|
498
|
+
|---|----------|-----------------|
|
|
499
|
+
| 1 | Page renders inside a Card container | Card element with white bg is present |
|
|
500
|
+
| 2 | PageHeader renders with title "User Management" | Heading "User Management" is visible |
|
|
501
|
+
| 3 | PageHeader badge shows "100,000" | Badge text "100,000" is visible |
|
|
502
|
+
| 4 | PageHeader subtitle shows "User list management page" | Subtitle text is visible |
|
|
503
|
+
| 5 | "Write" action button renders | Button with text "Write" is visible |
|
|
504
|
+
| 6 | Separator renders below header | Separator element is present |
|
|
505
|
+
| 7 | TabNavigation renders with 3 tabs | 3 tab triggers are visible |
|
|
506
|
+
| 8 | SearchBar renders with placeholder | "Enter search keyword" placeholder input is present |
|
|
507
|
+
| 9 | PaginationControls render with navigation buttons | Pagination buttons are present |
|
|
508
|
+
| 10 | UserTable renders with 5 rows | 5 data rows are present |
|
|
509
|
+
| 11 | Component ordering is correct (header -> separator -> tabs -> search -> pagination -> table) | DOM order matches expected hierarchy |
|
|
510
|
+
| 12 | Tab switching updates local state (no errors) | Clicking a tab does not throw; tab becomes active |
|
|
511
|
+
| 13 | Typing in search input updates value (no errors) | Input accepts text without errors |
|
|
512
|
+
| 14 | "Write" button click does not throw | Click event completes without error |
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
### Phase 4: User Detail Page
|
|
517
|
+
|
|
518
|
+
#### P4-TS1: ProfileImages Component
|
|
519
|
+
|
|
520
|
+
**Test file:** `tests/features/users/profile-images.test.tsx`
|
|
521
|
+
**Source tasks:** P4-T1
|
|
522
|
+
**PRD mapping:** US-6 (View User Detail)
|
|
523
|
+
|
|
524
|
+
| # | Scenario | Expected Result |
|
|
525
|
+
|---|----------|-----------------|
|
|
526
|
+
| 1 | Renders 3 images | 3 `<img>` elements are present |
|
|
527
|
+
| 2 | Each image has `alt` text | "Profile photo 1", "Profile photo 2", "Profile photo 3" (or equivalent) alt texts |
|
|
528
|
+
| 3 | Images render at 116x116 size | Elements have `w-[116px]` and `h-[116px]` classes (or equivalent) |
|
|
529
|
+
| 4 | Images have 8px border radius | Elements have `rounded-lg` or equivalent border-radius class |
|
|
530
|
+
| 5 | Images are in a horizontal row | Parent container has `flex` and `flex-row` layout (or default flex) |
|
|
531
|
+
| 6 | Images have 16px gap between them | Parent container has `gap-4` class |
|
|
532
|
+
| 7 | Fallback renders when image src fails | On image error, fallback placeholder element appears |
|
|
533
|
+
|
|
534
|
+
#### P4-TS2: UserDetailForm Component
|
|
535
|
+
|
|
536
|
+
**Test file:** `tests/features/users/user-detail-form.test.tsx`
|
|
537
|
+
**Source tasks:** P4-T2
|
|
538
|
+
**PRD mapping:** US-6 (View User Detail), US-7 (Edit User Detail)
|
|
539
|
+
|
|
540
|
+
**Basic Info Section:**
|
|
541
|
+
|
|
542
|
+
| # | Scenario | Expected Result |
|
|
543
|
+
|---|----------|-----------------|
|
|
544
|
+
| 1 | "Basic Info" section label renders | Text "Basic Info" is visible |
|
|
545
|
+
| 2 | ProfileImages component renders (3 images) | 3 image elements present |
|
|
546
|
+
| 3 | "One-Line Intro" label renders | Label "One-Line Intro" is visible |
|
|
547
|
+
| 4 | "One-Line Intro" input pre-filled with mock value | Input value is "This is the one-line intro content." |
|
|
548
|
+
| 5 | Row 1: "Role" select renders with "User" value | Select trigger shows "User" |
|
|
549
|
+
| 6 | Row 1: "Nickname" input pre-filled | Input contains "AttackingHealthPerson" or mock nickname |
|
|
550
|
+
| 7 | Row 1: "Phone" input pre-filled | Input contains "01012341234" or mock phone |
|
|
551
|
+
| 8 | Row 1: "Age" input pre-filled | Input contains "24 years old" or mock age |
|
|
552
|
+
| 9 | Row 2: "Gender" select renders with "Male" value | Select trigger shows "Male" |
|
|
553
|
+
| 10 | Row 2: "Exercise Style" select renders | Select trigger shows "Bodybuilding" |
|
|
554
|
+
| 11 | Row 2: "Gym Relocation" select renders | Select trigger shows "Available" |
|
|
555
|
+
| 12 | Row 2: "Region" select renders | Select trigger shows "Seoul Mapo-gu" |
|
|
556
|
+
| 13 | Row 3: "Bench" input pre-filled | Input contains "100kg" |
|
|
557
|
+
| 14 | Row 3: "Deadlift" input pre-filled | Input contains "100kg" |
|
|
558
|
+
| 15 | Row 3: "Squat" input pre-filled | Input contains "100kg" |
|
|
559
|
+
| 16 | All form labels render with `htmlFor` attribute | Each label has corresponding `for` attribute |
|
|
560
|
+
| 17 | "Role" select opens and shows options "User", "Admin" | After click, both options visible |
|
|
561
|
+
| 18 | "Gender" select opens and shows options "Male", "Female" | After click, both options visible |
|
|
562
|
+
| 19 | "Exercise Style" select shows "Bodybuilding", "Crossfit", "Cardio" | After click, all 3 options visible |
|
|
563
|
+
| 20 | "Gym Relocation" select shows "Available", "Not Available" | After click, both options visible |
|
|
564
|
+
| 21 | "Region" select shows "Seoul Mapo-gu", "Gangnam-gu", "Songpa-gu" | After click, all 3 options visible |
|
|
565
|
+
| 22 | Text inputs are editable (typing changes value) | After typing in "Nickname" input, value updates |
|
|
566
|
+
|
|
567
|
+
**Other Settings Section:**
|
|
568
|
+
|
|
569
|
+
| # | Scenario | Expected Result |
|
|
570
|
+
|---|----------|-----------------|
|
|
571
|
+
| 23 | Separator renders between Basic Info and Other Settings | Separator element present between sections |
|
|
572
|
+
| 24 | "Other Settings" section label renders | Text "Other Settings" is visible |
|
|
573
|
+
| 25 | "Profile Public" toggle renders in OFF state | Switch has `aria-checked="false"` |
|
|
574
|
+
| 26 | "Match & Chat Notification" toggle renders in ON state | Switch has `aria-checked="true"` |
|
|
575
|
+
| 27 | "Marketing Notification" toggle renders in OFF state | Switch has `aria-checked="false"` |
|
|
576
|
+
| 28 | Clicking "Profile Public" toggle switches to ON | After click, `aria-checked` changes to `"true"` |
|
|
577
|
+
| 29 | Clicking "Match & Chat Notification" toggle switches to OFF | After click, `aria-checked` changes to `"false"` |
|
|
578
|
+
| 30 | 3 toggle labels are visible | "Profile Public", "Match & Chat Notification", "Marketing Notification" texts are all visible |
|
|
579
|
+
|
|
580
|
+
#### P4-TS3: User Detail Page Assembly
|
|
581
|
+
|
|
582
|
+
**Test file:** `tests/features/users/user-detail-page.test.tsx`
|
|
583
|
+
**Source tasks:** P4-T3
|
|
584
|
+
**PRD mapping:** US-6 (View User Detail), US-7 (Edit User Detail)
|
|
585
|
+
|
|
586
|
+
| # | Scenario | Expected Result |
|
|
587
|
+
|---|----------|-----------------|
|
|
588
|
+
| 1 | Page renders inside a Card container | Card element is present |
|
|
589
|
+
| 2 | PageHeader title is "User Management" | Heading "User Management" is visible |
|
|
590
|
+
| 3 | PageHeader subtitle is "You can edit user information." | Subtitle text is visible |
|
|
591
|
+
| 4 | NO badge rendered on detail page | Badge element is NOT in the document |
|
|
592
|
+
| 5 | "Save Changes" action button renders | Button with text "Save Changes" is visible |
|
|
593
|
+
| 6 | UserDetailForm renders with user data | Form fields with pre-filled mock data are visible |
|
|
594
|
+
| 7 | Page looks up user by `params.id` | When `params.id` is "1", form shows user 1 data |
|
|
595
|
+
| 8 | Invalid `params.id` falls back to first user | When `params.id` is "999", form still renders with first user data |
|
|
596
|
+
| 9 | "Save Changes" button click does not throw | Click completes without error (no-op) |
|
|
597
|
+
| 10 | Separator renders below header | Separator element is present |
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
### Phase 5: Polish (Accessibility & Interactions)
|
|
602
|
+
|
|
603
|
+
#### P5-TS1: Keyboard Navigation
|
|
604
|
+
|
|
605
|
+
**Test file:** `tests/accessibility/a11y.test.tsx` (keyboard section)
|
|
606
|
+
**Source tasks:** P5-T1
|
|
607
|
+
**PRD mapping:** All user stories (keyboard access is cross-cutting)
|
|
608
|
+
|
|
609
|
+
| # | Scenario | Expected Result |
|
|
610
|
+
|---|----------|-----------------|
|
|
611
|
+
| 1 | Tab key moves focus through all interactive sidebar items | Focus moves sequentially through sidebar links, accordion triggers, logout |
|
|
612
|
+
| 2 | Enter key toggles sidebar accordion | Pressing Enter on accordion trigger expands/collapses content |
|
|
613
|
+
| 3 | Space key toggles sidebar accordion | Pressing Space on accordion trigger expands/collapses content |
|
|
614
|
+
| 4 | Arrow Left/Right switches active tab | On tab trigger, ArrowRight moves focus and activates next tab |
|
|
615
|
+
| 5 | Enter key on table row triggers navigation | Pressing Enter on focused row calls `router.push` |
|
|
616
|
+
| 6 | Tab key reaches Delete button separately from row | Delete button is separately focusable via Tab |
|
|
617
|
+
| 7 | Enter/Space on Delete button triggers delete action | Pressing Enter on Delete button fires click handler |
|
|
618
|
+
| 8 | Space key toggles switch component | Focused switch toggles state on Space press |
|
|
619
|
+
| 9 | Enter/Space opens Select dropdown | Focused select trigger opens on Enter |
|
|
620
|
+
| 10 | Arrow Up/Down navigates select options | Options are navigable via arrow keys |
|
|
621
|
+
| 11 | Escape closes select dropdown | Open dropdown closes on Escape |
|
|
622
|
+
| 12 | Enter/Space activates buttons | Focused button fires click on Enter or Space |
|
|
623
|
+
| 13 | No keyboard traps exist (can Tab out of any component) | Focus always moves to next element; never stuck |
|
|
624
|
+
|
|
625
|
+
#### P5-TS2: ARIA Attributes and Screen Reader Support
|
|
626
|
+
|
|
627
|
+
**Test file:** `tests/accessibility/a11y.test.tsx` (ARIA section)
|
|
628
|
+
**Source tasks:** P5-T2
|
|
629
|
+
|
|
630
|
+
| # | Scenario | Expected Result |
|
|
631
|
+
|---|----------|-----------------|
|
|
632
|
+
| 1 | Badge has `aria-label="Total users: 100,000"` | Attribute present on badge element |
|
|
633
|
+
| 2 | Active tab has `aria-selected="true"` | Active TabsTrigger has correct attribute |
|
|
634
|
+
| 3 | Sort column headers have descriptive `aria-label` | "Join Date, sortable column" and "Withdrawal Date, sortable column" aria-labels present |
|
|
635
|
+
| 4 | Delete buttons have dynamic `aria-label` with user nickname | `aria-label="Delete user NicknameHere"` per row |
|
|
636
|
+
| 5 | Pagination icon buttons all have `aria-label` | 4 buttons have: "Go to first page", "Previous page", "Next page", "Go to last page" |
|
|
637
|
+
| 6 | Page indicator region has `aria-live="polite"` | Region element has attribute |
|
|
638
|
+
| 7 | All toggle switches have `aria-checked` | Each switch has `aria-checked="true"` or `"false"` |
|
|
639
|
+
| 8 | Accordion triggers have `aria-expanded` | Each trigger has `aria-expanded="true"` or `"false"` |
|
|
640
|
+
| 9 | Page title is `<h1>` | Heading level 1 exists with "User Management" |
|
|
641
|
+
| 10 | Section labels ("Basic Info", "Other Settings") are `<h2>` | Heading level 2 exists for each |
|
|
642
|
+
| 11 | All form `<label>` elements have `htmlFor` matching `<input>` `id` | Each label/input pair is correctly associated |
|
|
643
|
+
| 12 | Sidebar has `<nav>` or `role="navigation"` landmark | Navigation landmark present |
|
|
644
|
+
| 13 | Content area uses `<main>` landmark | `<main>` element present with `id="main-content"` |
|
|
645
|
+
| 14 | Search input has accessible label | `aria-label="Search users"` or visible label association |
|
|
646
|
+
|
|
647
|
+
#### P5-TS3: Focus Management
|
|
648
|
+
|
|
649
|
+
**Test file:** `tests/accessibility/a11y.test.tsx` (focus section)
|
|
650
|
+
**Source tasks:** P5-T1
|
|
651
|
+
|
|
652
|
+
| # | Scenario | Expected Result |
|
|
653
|
+
|---|----------|-----------------|
|
|
654
|
+
| 1 | Focus ring is visible on focused buttons | Focused button has `focus-visible:ring-2` class applied |
|
|
655
|
+
| 2 | Focus ring uses primary color | Ring classes include `focus-visible:ring-primary` |
|
|
656
|
+
| 3 | Skip link becomes visible on focus | After Tab, skip link has `focus:not-sr-only` behavior (removed from sr-only) |
|
|
657
|
+
| 4 | Skip link Enter moves focus to main content | After Enter on skip link, focus target is `#main-content` |
|
|
658
|
+
| 5 | Focus order follows logical reading order | Sidebar items -> Content area (top to bottom) |
|
|
659
|
+
| 6 | Select dropdown returns focus to trigger on close | After selecting an option, trigger element has focus |
|
|
660
|
+
|
|
661
|
+
---
|
|
662
|
+
|
|
663
|
+
## Accessibility Test Scenarios (Cross-Cutting)
|
|
664
|
+
|
|
665
|
+
These tests span multiple phases and validate WCAG 2.1 AA compliance.
|
|
666
|
+
|
|
667
|
+
### Keyboard Accessibility
|
|
668
|
+
|
|
669
|
+
| # | Requirement | Components Affected |
|
|
670
|
+
|---|-------------|-------------------|
|
|
671
|
+
| 1 | All interactive elements reachable via Tab | Buttons, links, inputs, selects, switches, table rows |
|
|
672
|
+
| 2 | All actions executable via keyboard (Enter/Space) | Buttons, accordion triggers, tab triggers, toggles |
|
|
673
|
+
| 3 | Arrow key navigation where expected | Tabs (Left/Right), Select options (Up/Down), Accordion (Up/Down) |
|
|
674
|
+
| 4 | Escape closes overlays/dropdowns | Select dropdown |
|
|
675
|
+
| 5 | No keyboard traps | All components |
|
|
676
|
+
|
|
677
|
+
### Color Contrast (WCAG 2.1 AA)
|
|
678
|
+
|
|
679
|
+
| # | Element | Foreground | Background | Expected Ratio | Passes |
|
|
680
|
+
|---|---------|-----------|------------|---------------|--------|
|
|
681
|
+
| 1 | Page title | `#000000` | `#FFFFFF` | 21:1 | Yes (AAA) |
|
|
682
|
+
| 2 | Subtitle | `#9DA0A8` | `#FFFFFF` | 3.01:1 | Yes (AA Large at 18px) |
|
|
683
|
+
| 3 | Table cell text | `#3B3F4A` | `#FFFFFF` | 9.21:1 | Yes (AAA) |
|
|
684
|
+
| 4 | Button text (primary) | `#FFFFFF` | `#509594` | 3.15:1 | Yes (AA Large at 18px SemiBold) |
|
|
685
|
+
| 5 | Badge text | `#22543D` | `#C6F6D5` | 7.05:1 | Yes (AAA) |
|
|
686
|
+
| 6 | Inactive tab text | `#1A202C` | `#EEF2F6` | 13.83:1 | Yes (AAA) |
|
|
687
|
+
| 7 | Sidebar text | `#5A5E6A` | `#FFFFFF` | 5.32:1 | Yes (AA) |
|
|
688
|
+
| 8 | Delete action text | `#3F7A79` | `#FFFFFF` | 4.87:1 | Yes (AA) |
|
|
689
|
+
|
|
690
|
+
### ARIA and Landmarks
|
|
691
|
+
|
|
692
|
+
| # | Requirement | Validation |
|
|
693
|
+
|---|-------------|-----------|
|
|
694
|
+
| 1 | `<nav>` landmark wraps sidebar navigation | Present in AppSidebar |
|
|
695
|
+
| 2 | `<main>` landmark wraps content area | Present in root layout |
|
|
696
|
+
| 3 | All form fields have associated `<label>` via `htmlFor`/`id` | All inputs and selects in UserDetailForm |
|
|
697
|
+
| 4 | All icon-only buttons have `aria-label` | Pagination buttons, sort icons |
|
|
698
|
+
| 5 | Page title uses `<h1>` | PageHeader component |
|
|
699
|
+
| 6 | Section labels use `<h2>` | "Basic Info", "Other Settings" in UserDetailForm |
|
|
700
|
+
|
|
701
|
+
### Focus Indicators
|
|
702
|
+
|
|
703
|
+
| # | Requirement | Validation |
|
|
704
|
+
|---|-------------|-----------|
|
|
705
|
+
| 1 | 2px `#509594` focus ring with 2px offset on all interactive elements | `focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2` classes present |
|
|
706
|
+
| 2 | Focus ring visible on buttons | Button component |
|
|
707
|
+
| 3 | Focus ring visible on inputs | Input component |
|
|
708
|
+
| 4 | Focus ring visible on select triggers | Select component |
|
|
709
|
+
| 5 | Focus ring visible on switch toggles | Switch component |
|
|
710
|
+
| 6 | Focus ring visible on tabs | TabsTrigger component |
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## User Story to Test Scenario Mapping
|
|
715
|
+
|
|
716
|
+
| User Story | ID | Test Scenarios |
|
|
717
|
+
|-----------|-----|----------------|
|
|
718
|
+
| US-1: View User List | P3-TS4 #1-#12, P3-TS5 #1-#10 | Table renders 10 columns, 5 rows; all cell data visible; page renders all dashboard sections |
|
|
719
|
+
| US-2: Navigate with Sidebar | P2-TS1 #1-#15, P1-TS10 #1-#7 | Sidebar renders all sections; accordion expand/collapse; active state highlights; links navigate |
|
|
720
|
+
| US-3: Search Users | P3-TS2 #1-#6, P3-TS5 #8, #13 | Search input renders; accepts text; placeholder visible |
|
|
721
|
+
| US-4: Filter by Tabs | P3-TS1 #1-#8, P1-TS8 #1-#7, P3-TS5 #7, #12 | 3 tabs render; active/inactive states; clicking switches tabs |
|
|
722
|
+
| US-5: Paginate User List | P3-TS3 #1-#14, P3-TS5 #9 | 4 nav buttons; page indicator; page jump; items per page select; aria-labels |
|
|
723
|
+
| US-6: View User Detail | P3-TS4 #13, P4-TS1 #1-#7, P4-TS2 #1-#22, P4-TS3 #1-#10 | Row click navigates; detail page renders; form pre-filled; images render |
|
|
724
|
+
| US-7: Edit User Detail | P4-TS2 #17-#22, #28-#29, P4-TS3 #5, #9 | Form fields editable; selects open with options; toggles switchable; "Save Changes" is no-op |
|
|
725
|
+
| US-8: Delete User | P3-TS4 #12, #14, #19 | Delete button renders; click does not navigate; has accessible label |
|
|
726
|
+
| US-9: Logout | P2-TS1 #11-#12 | Logout button renders with icon; visible at bottom of sidebar |
|
|
727
|
+
|
|
728
|
+
---
|
|
729
|
+
|
|
730
|
+
## Test Priority Matrix
|
|
731
|
+
|
|
732
|
+
| Priority | Category | Scenario Count | Description |
|
|
733
|
+
|----------|----------|---------------|-------------|
|
|
734
|
+
| **P0 (Critical)** | Component rendering | 52 | All components render without crashing; correct children/content visible; semantic HTML structure |
|
|
735
|
+
| **P1 (High)** | State management & user interactions | 38 | Tab switching, search input, pagination state changes, toggle switches, select dropdowns, row click navigation, delete click stopPropagation |
|
|
736
|
+
| **P2 (Medium)** | Accessibility (ARIA + keyboard) | 33 | All `aria-label`, `aria-checked`, `aria-expanded`, `aria-selected`, `aria-live` attributes; keyboard navigation; focus management; landmarks; heading hierarchy |
|
|
737
|
+
| **P3 (Low)** | Visual styling & CSS classes | 25 | Correct CSS classes applied per variant; hover/focus/disabled states; dimensions; color tokens |
|
|
738
|
+
| | **Total** | **148** | |
|
|
739
|
+
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
## Summary
|
|
743
|
+
|
|
744
|
+
| Metric | Value |
|
|
745
|
+
|--------|-------|
|
|
746
|
+
| **Total test files** | 22 |
|
|
747
|
+
| **Total test scenarios** | 148 |
|
|
748
|
+
| **Phase 0 scenarios** | 16 |
|
|
749
|
+
| **Phase 1 scenarios** | 45 |
|
|
750
|
+
| **Phase 2 scenarios** | 20 |
|
|
751
|
+
| **Phase 3 scenarios** | 42 |
|
|
752
|
+
| **Phase 4 scenarios** | 47 |
|
|
753
|
+
| **Phase 5 scenarios** | 33 (accessibility cross-cutting, overlaps with component tests) |
|
|
754
|
+
| **Coverage target: Statements** | 80% minimum |
|
|
755
|
+
| **Coverage target: Branches** | 75% minimum |
|
|
756
|
+
| **Coverage target: Functions** | 80% minimum |
|
|
757
|
+
| **Coverage target: Lines** | 80% minimum |
|
|
758
|
+
|
|
759
|
+
### Test File Inventory
|
|
760
|
+
|
|
761
|
+
| # | Test File | Phase | Scenario Count |
|
|
762
|
+
|---|-----------|-------|---------------|
|
|
763
|
+
| 1 | `tests/lib/mock-data.test.ts` | 0 | 8 |
|
|
764
|
+
| 2 | `tests/lib/utils.test.ts` | 0 | 8 |
|
|
765
|
+
| 3 | `tests/components/ui/button.test.tsx` | 1 | 11 |
|
|
766
|
+
| 4 | `tests/components/ui/table.test.tsx` | 1 | 8 |
|
|
767
|
+
| 5 | `tests/components/ui/input.test.tsx` | 1 | 7 |
|
|
768
|
+
| 6 | `tests/components/ui/select.test.tsx` | 1 | 7 |
|
|
769
|
+
| 7 | `tests/components/ui/badge.test.tsx` | 1 | 6 |
|
|
770
|
+
| 8 | `tests/components/ui/avatar.test.tsx` | 1 | 5 |
|
|
771
|
+
| 9 | `tests/components/ui/switch.test.tsx` | 1 | 7 |
|
|
772
|
+
| 10 | `tests/components/ui/tabs.test.tsx` | 1 | 7 |
|
|
773
|
+
| 11 | `tests/components/ui/separator.test.tsx` | 1 | 3 |
|
|
774
|
+
| 12 | `tests/components/ui/accordion.test.tsx` | 1 | 7 |
|
|
775
|
+
| 13 | `tests/components/ui/label.test.tsx` | 1 | 4 |
|
|
776
|
+
| 14 | `tests/components/ui/card.test.tsx` | 1 | 5 |
|
|
777
|
+
| 15 | `tests/components/common/page-header.test.tsx` | 1 | 9 |
|
|
778
|
+
| 16 | `tests/components/layouts/app-sidebar.test.tsx` | 2 | 15 |
|
|
779
|
+
| 17 | `tests/components/layouts/content-layout.test.tsx` | 2 | 5 |
|
|
780
|
+
| 18 | `tests/features/users/tab-navigation.test.tsx` | 3 | 8 |
|
|
781
|
+
| 19 | `tests/features/users/search-bar.test.tsx` | 3 | 6 |
|
|
782
|
+
| 20 | `tests/features/users/pagination-controls.test.tsx` | 3 | 14 |
|
|
783
|
+
| 21 | `tests/features/users/user-table.test.tsx` | 3 | 19 |
|
|
784
|
+
| 22 | `tests/features/users/dashboard-page.test.tsx` | 3 | 14 |
|
|
785
|
+
| 23 | `tests/features/users/profile-images.test.tsx` | 4 | 7 |
|
|
786
|
+
| 24 | `tests/features/users/user-detail-form.test.tsx` | 4 | 30 |
|
|
787
|
+
| 25 | `tests/features/users/user-detail-page.test.tsx` | 4 | 10 |
|
|
788
|
+
| 26 | `tests/accessibility/a11y.test.tsx` | 5 | 33 |
|
|
789
|
+
|
|
790
|
+
**Note:** The 26 test files listed above include the dedicated accessibility file which consolidates cross-cutting keyboard, ARIA, and focus management tests. Some accessibility scenarios overlap with individual component tests and are listed in both places intentionally -- the component-level tests verify per-component correctness while the accessibility file validates end-to-end keyboard flow and ARIA compliance across assembled pages.
|