Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
63a1477
test(a11y): add automated WCAG 2.2 AA scan harness
therealbrad Jun 4, 2026
941fe5f
fix(a11y): resolve top shared-component WCAG violations
therealbrad Jun 4, 2026
e8b5bcb
test(a11y): seed a case-owned dataset so the dataset-detail route scans
therealbrad Jun 4, 2026
4d74095
fix(a11y): give icon-only controls accessible names
therealbrad Jun 4, 2026
fb06ffa
fix(a11y): name more icon controls and fix issue-title trigger semantics
therealbrad Jun 4, 2026
5693b7a
feat(theme): add opt-in Accessible (WCAG 2.2 AA) theme
therealbrad Jun 5, 2026
220dfc2
test(a11y): let the scan force a theme (A11Y_THEME)
therealbrad Jun 5, 2026
ee16556
fix(a11y): make the avatar tooltip non-interactive
therealbrad Jun 5, 2026
bb85efb
fix(a11y): give row-action menus accessible names (button-name)
therealbrad Jun 5, 2026
bd14dd5
fix(a11y): name admin toggles, selects, and edit buttons (button-name)
therealbrad Jun 5, 2026
d9e9ef2
fix(a11y): name edit/delete icon buttons in admin tables (button-name)
therealbrad Jun 5, 2026
0c18f0a
fix(a11y): name edit/delete buttons in config, llm, projects, roles t…
therealbrad Jun 5, 2026
028f6ec
fix(a11y): name edit/delete buttons in remaining admin tables (button…
therealbrad Jun 5, 2026
b454173
fix(a11y): name edit/delete buttons in workflow/group/milestone/promp…
therealbrad Jun 5, 2026
b092131
fix(a11y): name the project sidebar toggle and case back button (butt…
therealbrad Jun 5, 2026
465a967
fix(a11y): label enabled/status toggle switches in admin tables (butt…
therealbrad Jun 5, 2026
823d518
fix(a11y): name result-expand, remove-parent-folder, requires-review …
therealbrad Jun 5, 2026
fd008e1
fix(a11y): name the shared column-filter operator selects (button-name)
therealbrad Jun 5, 2026
5007050
docs(a11y): add WCAG 2.2 AA conformance report (ACR / VPAT draft)
therealbrad Jun 5, 2026
19ac2e0
fix(a11y): add accessible names for various UI elements in multiple l…
therealbrad Jun 5, 2026
78776f0
fix(a11y): show Accessible theme icon in profile and onboarding pickers
therealbrad Jun 5, 2026
03086b1
fix(a11y): escape backslashes in report Markdown cells
therealbrad Jun 5, 2026
3315283
test(a11y): update Avatar test for the asChild tooltip trigger
therealbrad Jun 5, 2026
f102795
chore(a11y): satisfy lint and prettier on the scan harness
therealbrad Jun 5, 2026
422f5ce
docs(a11y): document the opt-in Accessible theme
therealbrad Jun 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ TestPlanIt is a comprehensive test management platform designed to help teams pl
- **Custom statuses** - Create statuses with custom icons and colors
- **Templates** - Create templates for consistent test case structure
- **User preferences** - Configure theme, locale, timezone, and date/time formats; preference selections are visible from the profile view as well as the editor
- **Accessible theme** - Opt-in high-contrast theme tuned for WCAG 2.2 Level AA (stronger contrast, larger interactive targets, visible focus), selectable per user without affecting other themes
- **Configurations** - Author OS / browser / environment configurations scoped to projects, with an admin UX overhaul covering bulk assign and search

### Security & Compliance
Expand Down
15 changes: 14 additions & 1 deletion docs/docs/user-guide/user-menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,24 @@ Once opened, the menu displays:
1. **Your Name and Email**: Shown at the top for identification.
2. **View Profile**: Navigates you directly to your **[User Profile](./user-profile.md)** page.
3. **Theme**: Opens a sub-menu where you can select your preferred visual theme for the application.
- Options include Light, Dark, System (matches your operating system setting), Green, Orange, and Purple.
- Options include Light, Dark, System (matches your operating system setting), Green, Orange, Purple, and **Accessible**.
- **Accessible** is a high-contrast theme tuned for accessibility (WCAG 2.2 AA): it strengthens text and border contrast, enlarges small interactive targets, and adds a clearly visible keyboard focus ring. Choose it if you rely on these aids — see [Accessibility](#accessibility) below.
- A checkmark indicates the currently active theme.
- Selecting a new theme saves the preference to your profile and reloads the application to apply the change.
4. **Language**: Opens a sub-menu to select the display language for the application interface.
- Available languages are listed (e.g., English, Spanish).
- A checkmark indicates the currently active language.
- Selecting a new language saves the preference to your profile and reloads the application to apply the change.
5. **Sign Out**: Logs you out of your current session and redirects you to the Sign In page.

## Accessibility

TestPlanIt includes an opt-in **Accessible** theme designed to meet the [WCAG 2.2](https://www.w3.org/TR/WCAG22/) Level AA presentation requirements. It is one of the regular theme choices in the **Theme** sub-menu (and on your [User Profile](./user-profile.md) preferences), so you can turn it on or off at any time without affecting other users.

Compared to the default themes, the Accessible theme:

- **Increases contrast** for body text, secondary ("muted") text, and UI borders so content meets the minimum contrast ratio.
- **Enlarges small interactive targets** (such as compact icon buttons and toggles) to at least the recommended minimum size.
- **Adds a high-contrast keyboard focus ring** so the currently focused control is always clearly visible.

Your selection is saved to your profile and persists across sessions and devices. The other themes are left unchanged, so teammates who prefer them are unaffected.
2 changes: 1 addition & 1 deletion docs/docs/user-guide/user-profile.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ When viewing your own profile, you can view and edit these preferences:

#### Display Preferences

- **Theme**: Choose from Light, Dark, System, Green, Orange, or Purple themes (with color indicators)
- **Theme**: Choose from Light, Dark, System, Green, Orange, Purple, or **Accessible** themes (with color indicators). The Accessible theme is a high-contrast option tuned for [WCAG 2.2](https://www.w3.org/TR/WCAG22/) Level AA — see [Accessibility](./user-menu.md#accessibility)
- **Locale**: Language preference (English, German, Spanish, French, Italian, Dutch, Polish, Portuguese, Turkish, Vietnamese, Russian, Chinese Simplified, Chinese Traditional, Japanese, Korean)
- **Items Per Page**: Number of items to show in paginated tables (10, 25, 50, 100)

Expand Down
19 changes: 19 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions testplanit/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ e2e/playwright-report/
e2e/test-results/
.env.e2e

# Accessibility scan (e2e/a11y) — never commit results, seeded fixtures, or reports
e2e/a11y/results/
e2e/a11y/.a11y-fixtures.json
e2e/a11y/playwright-report/
e2e/a11y/test-results/

# DB backups
backups/

Expand Down
2 changes: 2 additions & 0 deletions testplanit/app/[locale]/admin/app-config/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export function getColumns(
variant="ghost"
className="px-2 py-1 h-auto"
data-testid="edit-config-button"
aria-label={t("actions.edit")}
onClick={() => onEditConfig?.(row.original)}
>
<Edit className="h-4 w-4" />
Expand All @@ -92,6 +93,7 @@ export function getColumns(
variant="destructive"
className="px-2 py-1 h-auto"
data-testid="delete-config"
aria-label={t("actions.delete")}
onClick={() => onDeleteConfig?.(row.original)}
>
<Trash2 className="h-4 w-4" />
Expand Down
2 changes: 2 additions & 0 deletions testplanit/app/[locale]/admin/code-repositories/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export function getColumns({
size="icon"
className="px-2 py-1 h-auto"
onClick={() => onEdit(row.original)}
aria-label={tCommon("actions.edit")}
>
<Edit className="h-4 w-4" />
</Button>
Expand All @@ -176,6 +177,7 @@ export function getColumns({
size="icon"
className="px-2 py-1 h-auto"
onClick={() => onDelete(row.original)}
aria-label={tCommon("actions.delete")}
>
<Trash2 className="h-4 w-4" />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,15 @@ export const useColumns = (
variant="ghost"
className="px-2 py-1 h-auto"
onClick={() => onEditCategory?.(row.original)}
aria-label={tCommon("actions.edit")}
>
<SquarePen className="h-5 w-5" />
</Button>
<Button
variant="destructive"
className="px-2 py-1 h-auto"
onClick={() => onDeleteCategory?.(row.original)}
aria-label={tCommon("actions.delete")}
>
<Trash2 className="h-5 w-5" />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,13 +329,15 @@ export const useColumns = (
variant="ghost"
className="px-2 py-1 h-auto"
onClick={() => onEditConfiguration?.(row.original)}
aria-label={t("actions.edit")}
>
<SquarePen className="h-5 w-5" />
</Button>
<Button
variant="destructive"
className="px-2 py-1 h-auto"
onClick={() => onDeleteConfiguration?.(row.original)}
aria-label={t("actions.delete")}
>
<Trash2 className="h-5 w-5" />
</Button>
Expand Down
5 changes: 5 additions & 0 deletions testplanit/app/[locale]/admin/fields/caseFieldColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export const useColumns = (
cell: ({ row }) => (
<div className="text-center">
<Switch
aria-label={tCommon("fields.enabled")}
checked={row.original.isEnabled}
onCheckedChange={(checked) =>
handleToggle(row.original.id, "isEnabled", checked)
Expand All @@ -114,6 +115,7 @@ export const useColumns = (
cell: ({ row }) => (
<div className="text-center">
<Switch
aria-label={tCommon("fields.required")}
checked={row.original.isRequired}
onCheckedChange={(checked) =>
handleToggle(row.original.id, "isRequired", checked)
Expand All @@ -132,6 +134,7 @@ export const useColumns = (
cell: ({ row }) => (
<div className="text-center">
<Switch
aria-label={tCommon("fields.restricted")}
checked={row.original.isRestricted}
onCheckedChange={(checked) =>
handleToggle(row.original.id, "isRestricted", checked)
Expand All @@ -155,6 +158,7 @@ export const useColumns = (
className="px-2 py-1 h-auto"
data-testid="edit-case-field-button"
onClick={() => onEditCaseField?.(row.original)}
aria-label={tCommon("actions.edit")}
>
<SquarePen className="h-5 w-5" />
</Button>
Expand All @@ -163,6 +167,7 @@ export const useColumns = (
className="px-2 py-1 h-auto"
data-testid="delete-case-field-button"
onClick={() => onDeleteCaseField?.(row.original)}
aria-label={tCommon("actions.delete")}
>
<Trash2 className="h-5 w-5" />
</Button>
Expand Down
5 changes: 5 additions & 0 deletions testplanit/app/[locale]/admin/fields/resultFieldColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export const useColumns = (
cell: ({ row }) => (
<div className="text-center">
<Switch
aria-label={tCommon("fields.enabled")}
checked={row.original.isEnabled}
onCheckedChange={(checked) =>
handleToggle(row.original.id, "isEnabled", checked)
Expand All @@ -113,6 +114,7 @@ export const useColumns = (
cell: ({ row }) => (
<div className="text-center">
<Switch
aria-label={tCommon("fields.required")}
checked={row.original.isRequired}
onCheckedChange={(checked) =>
handleToggle(row.original.id, "isRequired", checked)
Expand All @@ -131,6 +133,7 @@ export const useColumns = (
cell: ({ row }) => (
<div className="text-center">
<Switch
aria-label={tCommon("fields.restricted")}
checked={row.original.isRestricted}
onCheckedChange={(checked) =>
handleToggle(row.original.id, "isRestricted", checked)
Expand All @@ -154,6 +157,7 @@ export const useColumns = (
className="px-2 py-1 h-auto"
data-testid="edit-result-field-button"
onClick={() => onEditResultField?.(row.original)}
aria-label={tCommon("actions.edit")}
>
<SquarePen className="h-5 w-5" />
</Button>
Expand All @@ -162,6 +166,7 @@ export const useColumns = (
className="px-2 py-1 h-auto"
data-testid="delete-result-field-button"
onClick={() => onDeleteResultField?.(row.original)}
aria-label={tCommon("actions.delete")}
>
<Trash2 className="h-5 w-5" />
</Button>
Expand Down
4 changes: 4 additions & 0 deletions testplanit/app/[locale]/admin/fields/templateColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const useColumns = (
cell: ({ row }) => (
<div className="text-center">
<Switch
aria-label={tCommon("fields.enabled")}
checked={row.original.isEnabled}
onCheckedChange={(checked) =>
handleToggleEnabled(row.original.id, checked)
Expand All @@ -109,6 +110,7 @@ export const useColumns = (
cell: ({ row }) => (
<div className="text-center">
<Switch
aria-label={tCommon("fields.default")}
checked={row.original.isDefault}
disabled={row.original.isDefault}
onCheckedChange={(checked) =>
Expand All @@ -132,6 +134,7 @@ export const useColumns = (
variant="ghost"
className="px-2 py-1 h-auto"
data-testid="edit-template-button"
aria-label={tCommon("actions.edit")}
onClick={() => onEditTemplate?.(row.original)}
>
<SquarePen className="h-5 w-5" />
Expand All @@ -141,6 +144,7 @@ export const useColumns = (
variant="ghost"
className="px-2 py-1 h-auto text-muted-foreground cursor-not-allowed"
disabled
aria-label={tCommon("actions.delete")}
>
<Trash2 className="h-5 w-5" />
</Button>
Expand Down
2 changes: 2 additions & 0 deletions testplanit/app/[locale]/admin/groups/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,15 @@ export const useColumns = (
variant="ghost"
className="px-2 py-1 h-auto"
onClick={() => onEditGroup?.(row.original)}
aria-label={t("actions.edit")}
>
<SquarePen className="h-4 w-4" />
</Button>
<Button
variant="destructive"
className="px-2 py-1 h-auto"
onClick={() => onDeleteGroup?.(row.original)}
aria-label={t("actions.delete")}
>
<Trash2 className="h-5 w-5" />
</Button>
Expand Down
2 changes: 2 additions & 0 deletions testplanit/app/[locale]/admin/issues/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -449,13 +449,15 @@
variant="ghost"
className="px-2 py-1 h-auto"
onClick={() => onEditIssue?.(row.original)}
aria-label={tCommon("actions.edit")}
>
<SquarePen className="h-5 w-5" />
</Button>
<Button
variant="destructive"
className="px-2 py-1 h-auto"
onClick={() => onDeleteIssue?.(row.original)}
aria-label={tCommon("actions.delete")}
>
<Trash2 className="h-5 w-5" />
</Button>
Expand All @@ -463,6 +465,6 @@
),
},
],
[tCommon, onEditIssue, onDeleteIssue]

Check warning on line 468 in testplanit/app/[locale]/admin/issues/columns.tsx

View workflow job for this annotation

GitHub Actions / Test

React Hook useMemo has a missing dependency: 'isLoadingCounts'. Either include it or remove the dependency array
);
}
2 changes: 2 additions & 0 deletions testplanit/app/[locale]/admin/llm/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ export const useColumns = (
onClick={() => onEditIntegration?.(row.original)}
className="px-2 py-1 h-auto"
data-testid="llm-edit-button"
aria-label={tCommon("actions.edit")}
>
<Edit className="h-4 w-4" />
</Button>
Expand All @@ -298,6 +299,7 @@ export const useColumns = (
: undefined
}
data-testid="llm-delete-button"
aria-label={tCommon("actions.delete")}
>
<Trash2 className="h-8 w-8 shrink-0" />
</Button>
Expand Down
4 changes: 4 additions & 0 deletions testplanit/app/[locale]/admin/milestones/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const useColumns = (
cell: ({ row }) => (
<div className="text-center">
<Switch
aria-label={tCommon("fields.default")}
checked={row.original.isDefault}
disabled={row.original.isDefault}
onCheckedChange={(checked) =>
Expand All @@ -90,6 +91,7 @@ export const useColumns = (
variant="ghost"
className="px-2 py-1 h-auto"
onClick={() => onEditMilestoneType?.(row.original)}
aria-label={tCommon("actions.edit")}
>
<SquarePen className="h-5 w-5" />
</Button>
Expand All @@ -98,6 +100,7 @@ export const useColumns = (
variant="ghost"
className="px-2 py-1 h-auto text-muted-foreground cursor-not-allowed"
disabled
aria-label={tCommon("actions.delete")}
>
<Trash2 className="h-5 w-5" />
</Button>
Expand All @@ -106,6 +109,7 @@ export const useColumns = (
variant="destructive"
className="px-2 py-1 h-auto"
onClick={() => onDeleteMilestoneType?.(row.original)}
aria-label={tCommon("actions.delete")}
>
<Trash2 className="h-5 w-5" />
</Button>
Expand Down
3 changes: 3 additions & 0 deletions testplanit/app/[locale]/admin/projects/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ export const useColumns = (
cell: ({ row }) => (
<div className="text-center">
<Switch
aria-label={tCommon("fields.completed")}
checked={row.original.isCompleted}
onCheckedChange={(checked) =>
handleToggleCompleted(row.original.id, checked)
Expand Down Expand Up @@ -312,6 +313,7 @@ export const useColumns = (
size="icon"
onClick={() => handleOpenEditModal(row.original)}
className="px-2 py-1 h-auto"
aria-label={tCommon("actions.edit")}
>
<SquarePen className="h-4 w-4" />
</Button>
Expand All @@ -320,6 +322,7 @@ export const useColumns = (
size="icon"
className="px-2 py-1 h-auto"
onClick={() => onDeleteProject?.(row.original)}
aria-label={tCommon("actions.delete")}
>
<Trash2 className="h-4 w-4" />
</Button>
Expand Down
Loading
Loading