diff --git a/docs/docs/features.md b/docs/docs/features.md index 2ab0a2d76..29e8ca103 100644 --- a/docs/docs/features.md +++ b/docs/docs/features.md @@ -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 diff --git a/docs/docs/user-guide/user-menu.md b/docs/docs/user-guide/user-menu.md index 3fc89321c..ffd337c7f 100644 --- a/docs/docs/user-guide/user-menu.md +++ b/docs/docs/user-guide/user-menu.md @@ -14,7 +14,8 @@ 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. @@ -22,3 +23,15 @@ Once opened, the menu displays: - 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. diff --git a/docs/docs/user-guide/user-profile.md b/docs/docs/user-guide/user-profile.md index 710f088fe..dcfcbb320 100644 --- a/docs/docs/user-guide/user-profile.md +++ b/docs/docs/user-guide/user-profile.md @@ -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) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 566809169..a09347ce1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1017,6 +1017,9 @@ importers: specifier: ^4.4.3 version: 4.4.3 devDependencies: + '@axe-core/playwright': + specifier: ^4.11.3 + version: 4.11.3(playwright-core@1.60.0) '@babel/core': specifier: ^7.29.7 version: 7.29.7 @@ -3318,6 +3321,11 @@ packages: resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} engines: {node: '>=18.0.0'} + '@axe-core/playwright@4.11.3': + resolution: {integrity: sha512-h/kfksv4F0cVIDlKpT4700OehdRgpvuVskuQ2nb7/JmtWUXpe9ftHAPtwyXGvVSsa6SJ64A9ER7Zrzc/sIvC4w==} + peerDependencies: + playwright-core: '>= 1.0.0' + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -10095,6 +10103,10 @@ packages: resolution: {integrity: sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw==} engines: {node: '>=4'} + axe-core@4.11.4: + resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==} + engines: {node: '>=4'} + axios@1.16.1: resolution: {integrity: sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==} @@ -25600,6 +25612,11 @@ snapshots: '@aws/lambda-invoke-store@0.2.4': {} + '@axe-core/playwright@4.11.3(playwright-core@1.60.0)': + dependencies: + axe-core: 4.11.4 + playwright-core: 1.60.0 + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -34715,6 +34732,8 @@ snapshots: axe-core@4.11.2: {} + axe-core@4.11.4: {} + axios@1.16.1: dependencies: follow-redirects: 1.16.0(debug@4.3.7) diff --git a/testplanit/.gitignore b/testplanit/.gitignore index e424fb52b..523eaf245 100644 --- a/testplanit/.gitignore +++ b/testplanit/.gitignore @@ -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/ diff --git a/testplanit/app/[locale]/admin/app-config/columns.tsx b/testplanit/app/[locale]/admin/app-config/columns.tsx index 58d2f063b..a8fac1195 100644 --- a/testplanit/app/[locale]/admin/app-config/columns.tsx +++ b/testplanit/app/[locale]/admin/app-config/columns.tsx @@ -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)} > @@ -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)} > diff --git a/testplanit/app/[locale]/admin/code-repositories/columns.tsx b/testplanit/app/[locale]/admin/code-repositories/columns.tsx index 70fc9204c..5d8fe8551 100644 --- a/testplanit/app/[locale]/admin/code-repositories/columns.tsx +++ b/testplanit/app/[locale]/admin/code-repositories/columns.tsx @@ -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")} > @@ -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")} > diff --git a/testplanit/app/[locale]/admin/configurations/categoryColumns.tsx b/testplanit/app/[locale]/admin/configurations/categoryColumns.tsx index a400d349a..3d388441c 100644 --- a/testplanit/app/[locale]/admin/configurations/categoryColumns.tsx +++ b/testplanit/app/[locale]/admin/configurations/categoryColumns.tsx @@ -135,6 +135,7 @@ export const useColumns = ( variant="ghost" className="px-2 py-1 h-auto" onClick={() => onEditCategory?.(row.original)} + aria-label={tCommon("actions.edit")} > @@ -142,6 +143,7 @@ export const useColumns = ( variant="destructive" className="px-2 py-1 h-auto" onClick={() => onDeleteCategory?.(row.original)} + aria-label={tCommon("actions.delete")} > diff --git a/testplanit/app/[locale]/admin/configurations/configColumns.tsx b/testplanit/app/[locale]/admin/configurations/configColumns.tsx index df0da3472..2b2afd2e0 100644 --- a/testplanit/app/[locale]/admin/configurations/configColumns.tsx +++ b/testplanit/app/[locale]/admin/configurations/configColumns.tsx @@ -329,6 +329,7 @@ export const useColumns = ( variant="ghost" className="px-2 py-1 h-auto" onClick={() => onEditConfiguration?.(row.original)} + aria-label={t("actions.edit")} > @@ -336,6 +337,7 @@ export const useColumns = ( variant="destructive" className="px-2 py-1 h-auto" onClick={() => onDeleteConfiguration?.(row.original)} + aria-label={t("actions.delete")} > diff --git a/testplanit/app/[locale]/admin/fields/caseFieldColumns.tsx b/testplanit/app/[locale]/admin/fields/caseFieldColumns.tsx index 778dd626f..e18d42226 100644 --- a/testplanit/app/[locale]/admin/fields/caseFieldColumns.tsx +++ b/testplanit/app/[locale]/admin/fields/caseFieldColumns.tsx @@ -96,6 +96,7 @@ export const useColumns = ( cell: ({ row }) => (
handleToggle(row.original.id, "isEnabled", checked) @@ -114,6 +115,7 @@ export const useColumns = ( cell: ({ row }) => (
handleToggle(row.original.id, "isRequired", checked) @@ -132,6 +134,7 @@ export const useColumns = ( cell: ({ row }) => (
handleToggle(row.original.id, "isRestricted", checked) @@ -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")} > @@ -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")} > diff --git a/testplanit/app/[locale]/admin/fields/resultFieldColumns.tsx b/testplanit/app/[locale]/admin/fields/resultFieldColumns.tsx index 0e1eef60a..19ff9d3d3 100644 --- a/testplanit/app/[locale]/admin/fields/resultFieldColumns.tsx +++ b/testplanit/app/[locale]/admin/fields/resultFieldColumns.tsx @@ -95,6 +95,7 @@ export const useColumns = ( cell: ({ row }) => (
handleToggle(row.original.id, "isEnabled", checked) @@ -113,6 +114,7 @@ export const useColumns = ( cell: ({ row }) => (
handleToggle(row.original.id, "isRequired", checked) @@ -131,6 +133,7 @@ export const useColumns = ( cell: ({ row }) => (
handleToggle(row.original.id, "isRestricted", checked) @@ -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")} > @@ -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")} > diff --git a/testplanit/app/[locale]/admin/fields/templateColumns.tsx b/testplanit/app/[locale]/admin/fields/templateColumns.tsx index 255207b48..e1e8629cf 100644 --- a/testplanit/app/[locale]/admin/fields/templateColumns.tsx +++ b/testplanit/app/[locale]/admin/fields/templateColumns.tsx @@ -90,6 +90,7 @@ export const useColumns = ( cell: ({ row }) => (
handleToggleEnabled(row.original.id, checked) @@ -109,6 +110,7 @@ export const useColumns = ( cell: ({ row }) => (
@@ -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)} > @@ -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")} > diff --git a/testplanit/app/[locale]/admin/groups/columns.tsx b/testplanit/app/[locale]/admin/groups/columns.tsx index fc1253aa0..322eeff2a 100644 --- a/testplanit/app/[locale]/admin/groups/columns.tsx +++ b/testplanit/app/[locale]/admin/groups/columns.tsx @@ -80,6 +80,7 @@ export const useColumns = ( variant="ghost" className="px-2 py-1 h-auto" onClick={() => onEditGroup?.(row.original)} + aria-label={t("actions.edit")} > @@ -87,6 +88,7 @@ export const useColumns = ( variant="destructive" className="px-2 py-1 h-auto" onClick={() => onDeleteGroup?.(row.original)} + aria-label={t("actions.delete")} > diff --git a/testplanit/app/[locale]/admin/issues/columns.tsx b/testplanit/app/[locale]/admin/issues/columns.tsx index 49f4efa45..e53bf52ab 100644 --- a/testplanit/app/[locale]/admin/issues/columns.tsx +++ b/testplanit/app/[locale]/admin/issues/columns.tsx @@ -449,6 +449,7 @@ export function useIssueColumns({ variant="ghost" className="px-2 py-1 h-auto" onClick={() => onEditIssue?.(row.original)} + aria-label={tCommon("actions.edit")} > @@ -456,6 +457,7 @@ export function useIssueColumns({ variant="destructive" className="px-2 py-1 h-auto" onClick={() => onDeleteIssue?.(row.original)} + aria-label={tCommon("actions.delete")} > diff --git a/testplanit/app/[locale]/admin/llm/columns.tsx b/testplanit/app/[locale]/admin/llm/columns.tsx index 183abcb54..5adab3a0b 100644 --- a/testplanit/app/[locale]/admin/llm/columns.tsx +++ b/testplanit/app/[locale]/admin/llm/columns.tsx @@ -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")} > @@ -298,6 +299,7 @@ export const useColumns = ( : undefined } data-testid="llm-delete-button" + aria-label={tCommon("actions.delete")} > diff --git a/testplanit/app/[locale]/admin/milestones/columns.tsx b/testplanit/app/[locale]/admin/milestones/columns.tsx index da0f951d8..fb46d03b4 100644 --- a/testplanit/app/[locale]/admin/milestones/columns.tsx +++ b/testplanit/app/[locale]/admin/milestones/columns.tsx @@ -67,6 +67,7 @@ export const useColumns = ( cell: ({ row }) => (
@@ -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")} > @@ -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")} > @@ -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")} > diff --git a/testplanit/app/[locale]/admin/projects/columns.tsx b/testplanit/app/[locale]/admin/projects/columns.tsx index c4f82ad40..2c912d559 100644 --- a/testplanit/app/[locale]/admin/projects/columns.tsx +++ b/testplanit/app/[locale]/admin/projects/columns.tsx @@ -260,6 +260,7 @@ export const useColumns = ( cell: ({ row }) => (
handleToggleCompleted(row.original.id, checked) @@ -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")} > @@ -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")} > diff --git a/testplanit/app/[locale]/admin/prompts/columns.tsx b/testplanit/app/[locale]/admin/prompts/columns.tsx index f4311c6e1..53aa2b9ad 100644 --- a/testplanit/app/[locale]/admin/prompts/columns.tsx +++ b/testplanit/app/[locale]/admin/prompts/columns.tsx @@ -158,6 +158,7 @@ export const useColumns = ( cell: ({ row }) => (
@@ -233,6 +234,7 @@ export const useColumns = ( size="icon" onClick={() => onEditConfig?.(row.original)} className="px-2 py-1 h-auto" + aria-label={tCommon("actions.edit")} > @@ -245,6 +247,7 @@ export const useColumns = ( title={ row.original.isDefault ? t("cannotDeleteDefault") : undefined } + aria-label={tCommon("actions.delete")} > diff --git a/testplanit/app/[locale]/admin/quickscripts/quickScriptTemplateColumns.tsx b/testplanit/app/[locale]/admin/quickscripts/quickScriptTemplateColumns.tsx index 7697f8bfe..f7f2905cd 100644 --- a/testplanit/app/[locale]/admin/quickscripts/quickScriptTemplateColumns.tsx +++ b/testplanit/app/[locale]/admin/quickscripts/quickScriptTemplateColumns.tsx @@ -66,6 +66,7 @@ export const useColumns = ( cell: ({ row }) => (
handleToggleEnabled(row.original.id, checked) @@ -85,6 +86,7 @@ export const useColumns = ( cell: ({ row }) => (
@@ -109,6 +111,7 @@ export const useColumns = ( className="px-2 py-1 h-auto" data-testid="edit-export-template-button" onClick={() => onEditTemplate?.(row.original)} + aria-label={tCommon("actions.edit")} > @@ -117,6 +120,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")} > @@ -126,6 +130,7 @@ export const useColumns = ( className="px-2 py-1 h-auto" data-testid="delete-export-template-button" onClick={() => onDeleteTemplate?.(row.original)} + aria-label={tCommon("actions.delete")} > diff --git a/testplanit/app/[locale]/admin/roles/columns.tsx b/testplanit/app/[locale]/admin/roles/columns.tsx index 9e1770d40..09068621a 100644 --- a/testplanit/app/[locale]/admin/roles/columns.tsx +++ b/testplanit/app/[locale]/admin/roles/columns.tsx @@ -45,6 +45,7 @@ export const useColumns = ( cell: ({ row }) => (
@@ -82,6 +83,7 @@ export const useColumns = ( variant="ghost" className="px-2 py-1 h-auto" onClick={() => onEditRole?.(row.original)} + aria-label={tCommon("actions.edit")} > @@ -90,6 +92,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")} > @@ -98,6 +101,7 @@ export const useColumns = ( variant="destructive" className="px-2 py-1 h-auto" onClick={() => onDeleteRole?.(row.original)} + aria-label={tCommon("actions.delete")} > diff --git a/testplanit/app/[locale]/admin/security/page.tsx b/testplanit/app/[locale]/admin/security/page.tsx index 788b93230..e69f5d0a4 100644 --- a/testplanit/app/[locale]/admin/security/page.tsx +++ b/testplanit/app/[locale]/admin/security/page.tsx @@ -166,6 +166,7 @@ export default function SecurityAdminPage() {
diff --git a/testplanit/app/[locale]/admin/statuses/columns.tsx b/testplanit/app/[locale]/admin/statuses/columns.tsx index c97f1ba70..48b11b0ad 100644 --- a/testplanit/app/[locale]/admin/statuses/columns.tsx +++ b/testplanit/app/[locale]/admin/statuses/columns.tsx @@ -117,6 +117,7 @@ export const getColumns = ( cell: ({ row }) => (
handleToggleEnabled(row.original.id, checked) @@ -137,6 +138,7 @@ export const getColumns = ( cell: ({ row }) => (
handleToggleSuccess(row.original.id, checked) @@ -157,6 +159,7 @@ export const getColumns = ( cell: ({ row }) => (
handleToggleFailure(row.original.id, checked) @@ -177,6 +180,7 @@ export const getColumns = ( cell: ({ row }) => (
handleToggleCompleted(row.original.id, checked) @@ -244,6 +248,7 @@ export const getColumns = ( variant="ghost" className="px-2 py-1 h-auto text-muted-foreground cursor-not-allowed" disabled + aria-label={tCommon("actions.edit")} > @@ -252,6 +257,7 @@ export const getColumns = ( variant="ghost" className="px-2 py-1 h-auto" onClick={() => onEditStatus?.(row.original)} + aria-label={tCommon("actions.edit")} > @@ -261,6 +267,7 @@ export const getColumns = ( variant="destructive" className="px-2 py-1 h-auto" onClick={() => onDeleteStatus?.(row.original)} + aria-label={tCommon("actions.delete")} > @@ -269,6 +276,7 @@ export const getColumns = ( variant="ghost" className="px-2 py-1 h-auto text-muted-foreground cursor-not-allowed" disabled + aria-label={tCommon("actions.delete")} > diff --git a/testplanit/app/[locale]/admin/tags/columns.tsx b/testplanit/app/[locale]/admin/tags/columns.tsx index e71fbf2cf..78a51dec8 100644 --- a/testplanit/app/[locale]/admin/tags/columns.tsx +++ b/testplanit/app/[locale]/admin/tags/columns.tsx @@ -154,6 +154,7 @@ export const useColumns = ( variant="ghost" className="px-2 py-1 h-auto" onClick={() => onEditTag?.(row.original)} + aria-label={tCommon("actions.edit")} > @@ -161,6 +162,7 @@ export const useColumns = ( variant="destructive" className="px-2 py-1 h-auto" onClick={() => onDeleteTag?.(row.original)} + aria-label={tCommon("actions.delete")} > diff --git a/testplanit/app/[locale]/admin/users/columns.tsx b/testplanit/app/[locale]/admin/users/columns.tsx index 0b2314714..4b8cffa3f 100644 --- a/testplanit/app/[locale]/admin/users/columns.tsx +++ b/testplanit/app/[locale]/admin/users/columns.tsx @@ -115,6 +115,7 @@ export const useColumns = (
@@ -248,7 +249,11 @@ export const useColumns = (
- diff --git a/testplanit/app/[locale]/admin/workflows/ProjectReviewToggleList.tsx b/testplanit/app/[locale]/admin/workflows/ProjectReviewToggleList.tsx index 2a53f6436..349830d9d 100644 --- a/testplanit/app/[locale]/admin/workflows/ProjectReviewToggleList.tsx +++ b/testplanit/app/[locale]/admin/workflows/ProjectReviewToggleList.tsx @@ -174,6 +174,7 @@ export function ProjectReviewToggleList() {
handleToggle(id, name, checked)} /> diff --git a/testplanit/app/[locale]/admin/workflows/columns.tsx b/testplanit/app/[locale]/admin/workflows/columns.tsx index 2c74d1dca..8784da90b 100644 --- a/testplanit/app/[locale]/admin/workflows/columns.tsx +++ b/testplanit/app/[locale]/admin/workflows/columns.tsx @@ -85,6 +85,7 @@ export const useColumns = ( cell: ({ row }) => (
@@ -108,6 +109,7 @@ export const useColumns = ( cell: ({ row }) => (
handleToggleEnabled(row.original.id, checked) @@ -140,6 +142,7 @@ export const useColumns = ( > onEditWorkflow?.(workflow)} + aria-label={tCommon("actions.edit")} > @@ -197,6 +201,7 @@ export const useColumns = ( variant="destructive" className="px-2 py-1 h-auto" onClick={() => onDeleteWorkflow?.(workflow)} + aria-label={tCommon("actions.delete")} > @@ -205,6 +210,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")} > diff --git a/testplanit/app/[locale]/issues/columns.tsx b/testplanit/app/[locale]/issues/columns.tsx index 9414c4d6e..fb96561ec 100644 --- a/testplanit/app/[locale]/issues/columns.tsx +++ b/testplanit/app/[locale]/issues/columns.tsx @@ -146,13 +146,14 @@ export function useIssueColumns({ return ( -
{plainText} -
+
@@ -214,13 +215,14 @@ export function useIssueColumns({ return ( -
{plainText} -
+
diff --git a/testplanit/app/[locale]/projects/layout.tsx b/testplanit/app/[locale]/projects/layout.tsx index 5d0bc5b91..c11ef0573 100644 --- a/testplanit/app/[locale]/projects/layout.tsx +++ b/testplanit/app/[locale]/projects/layout.tsx @@ -3,9 +3,11 @@ import ProjectMenu from "@/components/ProjectMenu"; import { Button } from "@/components/ui/button"; import { ChevronLeft, ChevronRight } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useEffect, useState } from "react"; export default function ProjectsLayout(props: any) { + const t = useTranslations("common.aria"); const [isCollapsed, setIsCollapsed] = useState(false); // Load initial state from localStorage @@ -39,6 +41,7 @@ export default function ProjectsLayout(props: any) { onClick={handleToggleCollapse} variant="secondary" className="hidden md:flex absolute -right-4 top-12 z-20 p-0 rounded-l-none" + aria-label={t("togglePanel")} > {isCollapsed ? : } diff --git a/testplanit/app/[locale]/projects/milestones/[projectId]/MilestoneItemCard.tsx b/testplanit/app/[locale]/projects/milestones/[projectId]/MilestoneItemCard.tsx index e90a161ed..9c7d6839b 100644 --- a/testplanit/app/[locale]/projects/milestones/[projectId]/MilestoneItemCard.tsx +++ b/testplanit/app/[locale]/projects/milestones/[projectId]/MilestoneItemCard.tsx @@ -202,6 +202,7 @@ const MilestoneItemCard: React.FC = ({ variant="secondary" size="icon" className="p-0 m-0 h-7 w-7" + aria-label={tCommon("actions.actionsLabel")} > diff --git a/testplanit/app/[locale]/projects/repository/[projectId]/AddFolder.tsx b/testplanit/app/[locale]/projects/repository/[projectId]/AddFolder.tsx index c4769680d..a42976606 100644 --- a/testplanit/app/[locale]/projects/repository/[projectId]/AddFolder.tsx +++ b/testplanit/app/[locale]/projects/repository/[projectId]/AddFolder.tsx @@ -202,6 +202,7 @@ export function AddFolder({ className="h-5 w-5 p-0" onClick={() => setEffectiveParentId(null)} data-testid="remove-parent-folder-button" + aria-label={t("repository.removeParentFolder")} > diff --git a/testplanit/app/[locale]/projects/repository/[projectId]/TreeView.tsx b/testplanit/app/[locale]/projects/repository/[projectId]/TreeView.tsx index 7f90213c1..c5ab22cfd 100644 --- a/testplanit/app/[locale]/projects/repository/[projectId]/TreeView.tsx +++ b/testplanit/app/[locale]/projects/repository/[projectId]/TreeView.tsx @@ -1181,6 +1181,7 @@ const TreeView: React.FC<{ size="icon" className="h-7 w-7 p-0" data-testid={`folder-actions-trigger-${data?.folderId ?? 0}`} + aria-label={t("common.actions.actionsLabel")} > diff --git a/testplanit/app/[locale]/projects/repository/[projectId]/[caseId]/page.tsx b/testplanit/app/[locale]/projects/repository/[projectId]/[caseId]/page.tsx index abdb2e477..4facb062b 100644 --- a/testplanit/app/[locale]/projects/repository/[projectId]/[caseId]/page.tsx +++ b/testplanit/app/[locale]/projects/repository/[projectId]/[caseId]/page.tsx @@ -1913,6 +1913,7 @@ export default function TestCaseDetails() { variant="outline" size="icon" className="mr-2" + aria-label={t("common.aria.backToTestCase")} > @@ -2536,6 +2537,7 @@ export default function TestCaseDetails() { variant="secondary" size="sm" data-testid="toggle-left-panel-button" + aria-label={t("common.aria.togglePanel")} className={`p-0 transform ${ isCollapsedLeft ? "rounded-l-none rotate-180" @@ -2566,6 +2568,7 @@ export default function TestCaseDetails() { variant="secondary" size="sm" data-testid="toggle-right-panel-button" + aria-label={t("common.aria.togglePanel")} className={`p-0 transform ${ isCollapsedRight ? "rounded-l-none" diff --git a/testplanit/app/[locale]/projects/runs/[projectId]/TestRunItem.tsx b/testplanit/app/[locale]/projects/runs/[projectId]/TestRunItem.tsx index 619e8b44b..4c39ddfb2 100644 --- a/testplanit/app/[locale]/projects/runs/[projectId]/TestRunItem.tsx +++ b/testplanit/app/[locale]/projects/runs/[projectId]/TestRunItem.tsx @@ -406,7 +406,12 @@ const TestRunItem: React.FC = ({ {showMoreMenu && ( - diff --git a/testplanit/app/[locale]/projects/sessions/[projectId]/SessionItem.tsx b/testplanit/app/[locale]/projects/sessions/[projectId]/SessionItem.tsx index e96c00204..7da2a7fd9 100644 --- a/testplanit/app/[locale]/projects/sessions/[projectId]/SessionItem.tsx +++ b/testplanit/app/[locale]/projects/sessions/[projectId]/SessionItem.tsx @@ -262,7 +262,12 @@ const SessionItem: React.FC = ({ {showMoreMenu && ( - diff --git a/testplanit/app/[locale]/users/profile/[userId]/page.tsx b/testplanit/app/[locale]/users/profile/[userId]/page.tsx index 4ab988daf..3be9dcde8 100644 --- a/testplanit/app/[locale]/users/profile/[userId]/page.tsx +++ b/testplanit/app/[locale]/users/profile/[userId]/page.tsx @@ -56,6 +56,7 @@ import { } from "@prisma/client"; import { useQueryClient } from "@tanstack/react-query"; import { + Accessibility, Check, Circle, Moon, @@ -376,6 +377,8 @@ const UserProfile: React.FC = ({ return ; case "Purple": return ; + case "Accessible": + return ; default: return ; } @@ -395,6 +398,8 @@ const UserProfile: React.FC = ({ return "text-orange-500"; case "Purple": return "text-purple-500"; + case "Accessible": + return "text-blue-700"; default: return ""; } diff --git a/testplanit/components/Avatar.test.tsx b/testplanit/components/Avatar.test.tsx index bf8f9110c..267f446ad 100644 --- a/testplanit/components/Avatar.test.tsx +++ b/testplanit/components/Avatar.test.tsx @@ -47,17 +47,21 @@ describe("Avatar Component", () => { expect(textElement).toHaveStyle({ fontSize: expectedFontSize }); }); - it("should render with a tooltip by default", () => { + it("should render the avatar as the tooltip trigger without a nameless button", () => { render(); const img = screen.getByRole("img"); - expect(img.parentElement?.parentElement?.tagName).toBe("BUTTON"); + // a11y: the avatar element itself is the tooltip trigger (asChild), so it is + // never wrapped in a nameless