@jmruthers/pace-core 0.5.115 → 0.5.117
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/{AuthService-CVgsgtaZ.d.ts → AuthService-D4646R4b.d.ts} +9 -4
- package/dist/{DataTable-H5KJCAIS.js → DataTable-ZOAKQ3SU.js} +10 -9
- package/dist/{UnifiedAuthProvider-KZZUO27W.js → UnifiedAuthProvider-YFN7YGVN.js} +4 -3
- package/dist/{api-PKU4PUBO.js → api-TNIBJWLM.js} +3 -3
- package/dist/{audit-H4YJJF7R.js → audit-T36HM7IM.js} +2 -2
- package/dist/{chunk-SYXOZQ4P.js → chunk-2GJ5GL77.js} +1 -1
- package/dist/chunk-2GJ5GL77.js.map +1 -0
- package/dist/{chunk-XYRZV7R5.js → chunk-2LM4QQGH.js} +30 -34
- package/dist/chunk-2LM4QQGH.js.map +1 -0
- package/dist/{chunk-3OGQLOJM.js → chunk-3DBFLLLU.js} +30 -1
- package/dist/chunk-3DBFLLLU.js.map +1 -0
- package/dist/{chunk-KTHLNIMA.js → chunk-ECOVPXYS.js} +13 -62
- package/dist/chunk-ECOVPXYS.js.map +1 -0
- package/dist/{chunk-HKWQN44G.js → chunk-IZXS7RZK.js} +15 -15
- package/dist/{chunk-OO3V7W4H.js → chunk-KA3PSVNV.js} +87 -40
- package/dist/chunk-KA3PSVNV.js.map +1 -0
- package/dist/{chunk-L36JW4KV.js → chunk-LFS45U62.js} +2 -2
- package/dist/{chunk-BUN7NMV7.js → chunk-O3FTRYEU.js} +2 -2
- package/dist/{chunk-F6QB26OS.js → chunk-P3PUOL6B.js} +80 -8
- package/dist/chunk-P3PUOL6B.js.map +1 -0
- package/dist/{chunk-ZPXWJA4H.js → chunk-PHDAXDHB.js} +131 -5
- package/dist/chunk-PHDAXDHB.js.map +1 -0
- package/dist/chunk-UJI6WSMD.js +201 -0
- package/dist/{chunk-5CDJCTOO.js.map → chunk-UJI6WSMD.js.map} +1 -1
- package/dist/{chunk-OUU3SP6I.js → chunk-UKZWNQMB.js} +50 -7
- package/dist/{chunk-OUU3SP6I.js.map → chunk-UKZWNQMB.js.map} +1 -1
- package/dist/{chunk-7H75SHXZ.js → chunk-VN3OOE35.js} +2 -2
- package/dist/{chunk-QKIVSZ2O.js → chunk-WP5I5GLN.js} +2 -2
- package/dist/{chunk-NEONKMTU.js → chunk-XN2LYHDI.js} +47 -6
- package/dist/chunk-XN2LYHDI.js.map +1 -0
- package/dist/components.d.ts +1 -1
- package/dist/components.js +12 -11
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +10 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +19 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +3 -2
- package/dist/rbac/index.d.ts +82 -1
- package/dist/rbac/index.js +13 -10
- package/dist/{useToast-DVT4dMtf.d.ts → useToast-Cs_g32bg.d.ts} +1 -1
- package/dist/utils.js +6 -4
- package/dist/utils.js.map +1 -1
- package/dist/validation.js +3 -1
- package/dist/validation.js.map +1 -1
- package/docs/README.md +4 -0
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +35 -12
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +71 -0
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +122 -0
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +27 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +100 -0
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +52 -0
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +41 -14
- package/docs/architecture/rpc-function-standards.md +193 -0
- package/package.json +1 -1
- package/src/__tests__/TEST_STANDARD.md +244 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +46 -16
- package/src/components/DataTable/__tests__/keyboard.test.tsx +276 -217
- package/src/components/DataTable/components/DataTableCore.tsx +29 -2
- package/src/components/DataTable/components/DataTableToolbar.tsx +3 -2
- package/src/components/DataTable/components/EditableRow.tsx +18 -1
- package/src/components/DataTable/components/ViewRowModal.tsx +1 -1
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +735 -0
- package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +572 -0
- package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.test.tsx +708 -0
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +451 -0
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +456 -0
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +454 -0
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +462 -0
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +423 -0
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +393 -0
- package/src/components/DataTable/components/__tests__/GroupingDropdown.test.tsx +617 -0
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +734 -0
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +412 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +4 -0
- package/src/components/EventSelector/EventSelector.tsx +5 -25
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +12 -7
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +4 -0
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +7 -2
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +13 -8
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +109 -100
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +18 -13
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +17 -12
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +2 -0
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +11 -1
- package/src/components/PasswordReset/PasswordChangeForm.test.tsx +2 -2
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +648 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +10 -7
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +4 -12
- package/src/components/Select/Select.tsx +8 -0
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +367 -3
- package/src/hooks/__tests__/usePublicFileDisplay.test.ts +916 -0
- package/src/hooks/useEventTheme.ts +49 -18
- package/src/hooks/usePermissionCache.ts +5 -3
- package/src/hooks/useSecureDataAccess.ts +56 -3
- package/src/hooks/useToast.ts +1 -1
- package/src/providers/services/EventServiceProvider.tsx +15 -8
- package/src/rbac/__tests__/cache-invalidation.test.ts +385 -0
- package/src/rbac/audit.test.ts +206 -0
- package/src/rbac/audit.ts +37 -2
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +26 -23
- package/src/rbac/errors.test.ts +340 -0
- package/src/rbac/hooks/index.ts +9 -0
- package/src/rbac/hooks/useResolvedScope.test.ts +1063 -0
- package/src/rbac/hooks/useRoleManagement.test.ts +908 -0
- package/src/rbac/hooks/useRoleManagement.ts +255 -0
- package/src/services/AuthService.ts +10 -0
- package/src/services/EventService.ts +111 -50
- package/src/services/__tests__/AuthService.test.ts +1 -1
- package/src/services/__tests__/EventService.test.ts +60 -45
- package/src/services/interfaces/IEventService.ts +1 -1
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +320 -0
- package/src/utils/__tests__/logger.unit.test.ts +398 -0
- package/src/utils/__tests__/validation.unit.test.ts +225 -1
- package/src/utils/file-reference.test.ts +214 -0
- package/dist/chunk-3OGQLOJM.js.map +0 -1
- package/dist/chunk-5CDJCTOO.js +0 -190
- package/dist/chunk-F6QB26OS.js.map +0 -1
- package/dist/chunk-KTHLNIMA.js.map +0 -1
- package/dist/chunk-NEONKMTU.js.map +0 -1
- package/dist/chunk-OO3V7W4H.js.map +0 -1
- package/dist/chunk-SYXOZQ4P.js.map +0 -1
- package/dist/chunk-XYRZV7R5.js.map +0 -1
- package/dist/chunk-ZPXWJA4H.js.map +0 -1
- package/src/rbac/audit-enhanced.ts +0 -351
- /package/dist/{DataTable-H5KJCAIS.js.map → DataTable-ZOAKQ3SU.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-KZZUO27W.js.map → UnifiedAuthProvider-YFN7YGVN.js.map} +0 -0
- /package/dist/{api-PKU4PUBO.js.map → api-TNIBJWLM.js.map} +0 -0
- /package/dist/{audit-H4YJJF7R.js.map → audit-T36HM7IM.js.map} +0 -0
- /package/dist/{chunk-HKWQN44G.js.map → chunk-IZXS7RZK.js.map} +0 -0
- /package/dist/{chunk-L36JW4KV.js.map → chunk-LFS45U62.js.map} +0 -0
- /package/dist/{chunk-BUN7NMV7.js.map → chunk-O3FTRYEU.js.map} +0 -0
- /package/dist/{chunk-7H75SHXZ.js.map → chunk-VN3OOE35.js.map} +0 -0
- /package/dist/{chunk-QKIVSZ2O.js.map → chunk-WP5I5GLN.js.map} +0 -0
|
@@ -714,6 +714,9 @@ rbac_check_permission_simplified(
|
|
|
714
714
|
- ❌ **WRONG**: `SELECT mealtype_name FROM cake_mealtype`
|
|
715
715
|
- ✅ **CORRECT**: `SELECT cake_mealtype.mealtype_name FROM cake_mealtype`
|
|
716
716
|
- Always fully qualify table names when there might be ambiguity
|
|
717
|
+
- **MANDATORY**: When using JOINs, always use table aliases and prefix ALL column references
|
|
718
|
+
- Common ambiguous columns: `organisation_id`, `created_by`, `updated_by`, `created_at`, `updated_at`
|
|
719
|
+
- See "Avoiding Ambiguous Column References" section below for detailed guidance
|
|
717
720
|
|
|
718
721
|
5. **Not checking existing migrations**: Before creating a new migration, check if a similar one already exists to avoid duplicates
|
|
719
722
|
|
|
@@ -736,6 +739,190 @@ rbac_check_permission_simplified(
|
|
|
736
739
|
|
|
737
740
|
3. **Update references** - Update all callers before removing
|
|
738
741
|
|
|
742
|
+
## Avoiding Ambiguous Column References
|
|
743
|
+
|
|
744
|
+
### Overview
|
|
745
|
+
|
|
746
|
+
Ambiguous column reference errors occur when PostgreSQL cannot determine which table a column belongs to in a query with JOINs. This is a common source of silent failures in RPC functions.
|
|
747
|
+
|
|
748
|
+
### Mandatory Rules
|
|
749
|
+
|
|
750
|
+
1. **Always use table aliases in JOINs**: Never write JOINs without table aliases
|
|
751
|
+
2. **Fully qualify ALL column references**: Every column in SELECT, WHERE, ORDER BY, GROUP BY, and HAVING clauses must be prefixed with its table alias
|
|
752
|
+
3. **Common ambiguous columns**: The following columns exist in multiple tables and MUST always be prefixed:
|
|
753
|
+
- `organisation_id`
|
|
754
|
+
- `created_by`, `updated_by`
|
|
755
|
+
- `created_at`, `updated_at`
|
|
756
|
+
- `event_id` and its variations (e.g., `dish_event_id`, `meal_event_id`)
|
|
757
|
+
|
|
758
|
+
### Correct Patterns
|
|
759
|
+
|
|
760
|
+
```sql
|
|
761
|
+
-- ✅ CORRECT: All columns properly prefixed with table aliases
|
|
762
|
+
SELECT
|
|
763
|
+
d.dish_id,
|
|
764
|
+
d.dish_name,
|
|
765
|
+
d.organisation_id,
|
|
766
|
+
d.created_at,
|
|
767
|
+
mt.mealtype_name,
|
|
768
|
+
e.event_name,
|
|
769
|
+
e.organisation_id AS event_organisation_id
|
|
770
|
+
FROM cake_dish d
|
|
771
|
+
LEFT JOIN cake_mealtype mt ON d.dish_mealtype_id = mt.mealtype_id
|
|
772
|
+
LEFT JOIN event e ON d.dish_event_id = e.event_id
|
|
773
|
+
WHERE d.dish_event_id = p_event_id
|
|
774
|
+
AND d.organisation_id = p_organisation_id
|
|
775
|
+
ORDER BY d.dish_code, mt.mealtype_name;
|
|
776
|
+
|
|
777
|
+
-- ✅ CORRECT: CTEs with proper aliases
|
|
778
|
+
WITH distribution_items AS (
|
|
779
|
+
SELECT
|
|
780
|
+
d.item_id,
|
|
781
|
+
d.collection_date,
|
|
782
|
+
SUM(d.required_quantity) as total_quantity
|
|
783
|
+
FROM data_cake_distribution_list(p_event_id, p_user_id, p_organisation_id) d
|
|
784
|
+
GROUP BY d.item_id, d.collection_date
|
|
785
|
+
)
|
|
786
|
+
SELECT
|
|
787
|
+
di.item_id,
|
|
788
|
+
di.collection_date,
|
|
789
|
+
di.total_quantity
|
|
790
|
+
FROM distribution_items di
|
|
791
|
+
WHERE di.item_id = p_item_id;
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
### Incorrect Patterns
|
|
795
|
+
|
|
796
|
+
```sql
|
|
797
|
+
-- ❌ WRONG: Missing table prefixes in SELECT
|
|
798
|
+
SELECT
|
|
799
|
+
dish_id,
|
|
800
|
+
dish_name,
|
|
801
|
+
organisation_id, -- AMBIGUOUS: Could be from cake_dish or event
|
|
802
|
+
mealtype_name
|
|
803
|
+
FROM cake_dish d
|
|
804
|
+
LEFT JOIN cake_mealtype mt ON d.dish_mealtype_id = mt.mealtype_id
|
|
805
|
+
LEFT JOIN event e ON d.dish_event_id = e.event_id;
|
|
806
|
+
|
|
807
|
+
-- ❌ WRONG: Missing table prefix in WHERE clause
|
|
808
|
+
SELECT d.dish_id, d.dish_name
|
|
809
|
+
FROM cake_dish d
|
|
810
|
+
LEFT JOIN event e ON d.dish_event_id = e.event_id
|
|
811
|
+
WHERE organisation_id = p_organisation_id; -- AMBIGUOUS: Which table?
|
|
812
|
+
|
|
813
|
+
-- ❌ WRONG: Missing table prefix in ORDER BY
|
|
814
|
+
SELECT d.dish_id, d.dish_name, e.event_name
|
|
815
|
+
FROM cake_dish d
|
|
816
|
+
LEFT JOIN event e ON d.dish_event_id = e.event_id
|
|
817
|
+
ORDER BY created_at; -- AMBIGUOUS: Which table's created_at?
|
|
818
|
+
|
|
819
|
+
-- ❌ WRONG: SELECT INTO with multiple columns but wrong variable count
|
|
820
|
+
SELECT d.dish_code, d.dish_name, mt.mealtype_name
|
|
821
|
+
INTO v_mealtype_name -- WRONG: Only one variable for three columns
|
|
822
|
+
FROM cake_dish d
|
|
823
|
+
LEFT JOIN cake_mealtype mt ON d.dish_mealtype_id = mt.mealtype_id;
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### SELECT INTO Statements
|
|
827
|
+
|
|
828
|
+
When using `SELECT ... INTO`, ensure:
|
|
829
|
+
1. The number of columns matches the number of variables
|
|
830
|
+
2. All columns are properly prefixed with table aliases
|
|
831
|
+
3. Use separate variables for each column
|
|
832
|
+
|
|
833
|
+
```sql
|
|
834
|
+
-- ✅ CORRECT: Proper SELECT INTO with multiple variables
|
|
835
|
+
DECLARE
|
|
836
|
+
v_dish_code TEXT;
|
|
837
|
+
v_dish_name TEXT;
|
|
838
|
+
v_mealtype_name TEXT;
|
|
839
|
+
v_updated_at TIMESTAMPTZ;
|
|
840
|
+
BEGIN
|
|
841
|
+
SELECT
|
|
842
|
+
d.dish_code,
|
|
843
|
+
d.dish_name,
|
|
844
|
+
mt.mealtype_name,
|
|
845
|
+
d.updated_at
|
|
846
|
+
INTO
|
|
847
|
+
v_dish_code,
|
|
848
|
+
v_dish_name,
|
|
849
|
+
v_mealtype_name,
|
|
850
|
+
v_updated_at
|
|
851
|
+
FROM cake_dish d
|
|
852
|
+
LEFT JOIN cake_mealtype mt ON d.dish_mealtype_id = mt.mealtype_id
|
|
853
|
+
WHERE d.dish_id = p_dish_id;
|
|
854
|
+
END;
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### CTEs and Subqueries
|
|
858
|
+
|
|
859
|
+
When using CTEs (Common Table Expressions) or subqueries:
|
|
860
|
+
1. Use descriptive aliases for CTEs
|
|
861
|
+
2. Fully qualify all column references within CTEs
|
|
862
|
+
3. When referencing CTEs in the main query, use the CTE alias
|
|
863
|
+
|
|
864
|
+
```sql
|
|
865
|
+
-- ✅ CORRECT: CTE with proper aliases
|
|
866
|
+
WITH unit_diners AS (
|
|
867
|
+
SELECT
|
|
868
|
+
d.diner_unit_id,
|
|
869
|
+
d.diner_diettype_id,
|
|
870
|
+
SUM(COALESCE(d.diner_adult, 0)) as adults,
|
|
871
|
+
SUM(COALESCE(d.diner_youth, 0)) as youth
|
|
872
|
+
FROM cake_diner d
|
|
873
|
+
INNER JOIN event_units eu ON d.diner_unit_id = eu.unit_id
|
|
874
|
+
WHERE (d.diner_adult > 0 OR d.diner_youth > 0)
|
|
875
|
+
GROUP BY d.diner_unit_id, d.diner_diettype_id
|
|
876
|
+
)
|
|
877
|
+
SELECT
|
|
878
|
+
ud.diner_unit_id,
|
|
879
|
+
ud.adults,
|
|
880
|
+
ud.youth
|
|
881
|
+
FROM unit_diners ud
|
|
882
|
+
WHERE ud.diner_unit_id = p_unit_id;
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
### Table Alias Naming Conventions
|
|
886
|
+
|
|
887
|
+
Use short, descriptive aliases:
|
|
888
|
+
- `d` for `cake_dish`
|
|
889
|
+
- `r` for `cake_recipe`
|
|
890
|
+
- `m` for `cake_meal`
|
|
891
|
+
- `mt` for `cake_mealtype`
|
|
892
|
+
- `i` for `cake_item`
|
|
893
|
+
- `dt` for `cake_diettype`
|
|
894
|
+
- `e` for `event`
|
|
895
|
+
- `u` for `cake_unit`
|
|
896
|
+
- `mp` for `cake_mealplan`
|
|
897
|
+
|
|
898
|
+
### UPDATE Statements
|
|
899
|
+
|
|
900
|
+
For UPDATE statements, the WHERE clause should reference the table being updated:
|
|
901
|
+
|
|
902
|
+
```sql
|
|
903
|
+
-- ✅ CORRECT: WHERE clause uses table name or alias
|
|
904
|
+
UPDATE cake_dish
|
|
905
|
+
SET dish_name = p_dish_name,
|
|
906
|
+
updated_by = p_user_id,
|
|
907
|
+
updated_at = now()
|
|
908
|
+
WHERE dish_id = p_dish_id; -- dish_id is unambiguous (primary key)
|
|
909
|
+
|
|
910
|
+
-- ✅ CORRECT: When updating with JOIN, qualify all columns
|
|
911
|
+
UPDATE cake_dish d
|
|
912
|
+
SET d.dish_name = p_dish_name
|
|
913
|
+
FROM cake_mealtype mt
|
|
914
|
+
WHERE d.dish_mealtype_id = mt.mealtype_id
|
|
915
|
+
AND d.dish_id = p_dish_id;
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
### Testing for Ambiguous References
|
|
919
|
+
|
|
920
|
+
Before deploying, test your function:
|
|
921
|
+
1. Call the function with valid parameters
|
|
922
|
+
2. Check PostgreSQL logs for "ambiguous column reference" errors
|
|
923
|
+
3. Verify all column references are prefixed in JOINs
|
|
924
|
+
4. Test with multiple tables that share column names
|
|
925
|
+
|
|
739
926
|
## Common Patterns
|
|
740
927
|
|
|
741
928
|
### Pattern 1: Simple Data Retrieval
|
|
@@ -871,6 +1058,12 @@ When creating or reviewing an RPC function, ensure:
|
|
|
871
1058
|
- ✅ **File naming**: `YYYYMMDDHHMMSS_descriptive_name.sql`
|
|
872
1059
|
- ✅ **No ambiguous column references**: Fully qualify table names (e.g., `cake_mealtype.mealtype_name`)
|
|
873
1060
|
|
|
1061
|
+
### ⚠️ Column Reference Requirements
|
|
1062
|
+
- ✅ **All column references in JOINs are fully qualified with table aliases**: Every column in SELECT, WHERE, ORDER BY, GROUP BY, HAVING must have table prefix
|
|
1063
|
+
- ✅ **No ambiguous column references in WHERE, ORDER BY, GROUP BY, HAVING clauses**: All columns must be prefixed with table alias
|
|
1064
|
+
- ✅ **Table aliases used consistently and meaningfully**: Use short, descriptive aliases (e.g., `d` for dish, `r` for recipe)
|
|
1065
|
+
- ✅ **SELECT INTO statements match variable count**: Number of columns must match number of variables
|
|
1066
|
+
|
|
874
1067
|
## References
|
|
875
1068
|
|
|
876
1069
|
- [RBAC System Documentation](../core-concepts/rbac-system.md)
|
package/package.json
CHANGED
|
@@ -329,6 +329,19 @@ expect(screen.getByText('Visible content')).toBeInTheDocument();
|
|
|
329
329
|
expect(screen.queryByText('Hidden content')).not.toBeInTheDocument();
|
|
330
330
|
```
|
|
331
331
|
|
|
332
|
+
### 2. Skipped Tests Without Documentation
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
// ❌ Bad - Skipped test without explanation
|
|
336
|
+
it.skip('should navigate with keyboard', () => {});
|
|
337
|
+
|
|
338
|
+
// ✅ Good - Skipped test with clear documentation
|
|
339
|
+
// TODO: Enable when keyboard navigation is implemented
|
|
340
|
+
// See: https://github.com/org/repo/issues/123
|
|
341
|
+
// Expected: Q2 2025
|
|
342
|
+
it.skip('should navigate with keyboard', () => {});
|
|
343
|
+
```
|
|
344
|
+
|
|
332
345
|
### 2. Brittle Selectors
|
|
333
346
|
|
|
334
347
|
```typescript
|
|
@@ -530,9 +543,24 @@ await waitFor(() => {
|
|
|
530
543
|
|
|
531
544
|
await waitFor(() => {
|
|
532
545
|
expect(result.current.data).toBeDefined();
|
|
533
|
-
}, { timeout:
|
|
546
|
+
}, { timeout: 2000 }); // Most async operations (2-3 seconds)
|
|
547
|
+
|
|
548
|
+
// ⚠️ Only use 5+ seconds for truly slow operations
|
|
549
|
+
await waitFor(() => {
|
|
550
|
+
expect(result.current.data).toBeDefined();
|
|
551
|
+
}, { timeout: 5000 }); // Network calls, complex computations only
|
|
534
552
|
```
|
|
535
553
|
|
|
554
|
+
**Timeout Guidelines**:
|
|
555
|
+
- **Fast operations** (< 100ms): `timeout: 100-500`
|
|
556
|
+
- **Standard async** (100ms-2s): `timeout: 2000-3000`
|
|
557
|
+
- **Slow operations** (2s+): `timeout: 5000+` (with documentation)
|
|
558
|
+
|
|
559
|
+
**Avoid**:
|
|
560
|
+
- ❌ Using 5-6 second timeouts for mock operations that resolve immediately
|
|
561
|
+
- ❌ Using `waitFor` for synchronous operations
|
|
562
|
+
- ❌ Unnecessary delays in mock implementations
|
|
563
|
+
|
|
536
564
|
## 🔧 Test Configuration
|
|
537
565
|
|
|
538
566
|
### Vitest Configuration
|
|
@@ -618,7 +646,7 @@ screen.logTestingPlaygroundURL();
|
|
|
618
646
|
|
|
619
647
|
## 📋 Pre-Merge Checklist
|
|
620
648
|
|
|
621
|
-
- [ ] All tests pass without `it.only` or `test.skip`
|
|
649
|
+
- [ ] All tests pass without `it.only` or `test.skip` (see Skipped Test Policy below)
|
|
622
650
|
- [ ] Coverage thresholds are met
|
|
623
651
|
- [ ] Tests use semantic queries (avoid test IDs)
|
|
624
652
|
- [ ] Async operations use proper waiting patterns
|
|
@@ -626,6 +654,7 @@ screen.logTestingPlaygroundURL();
|
|
|
626
654
|
- [ ] Tests focus on behavior, not implementation
|
|
627
655
|
- [ ] Error scenarios are covered
|
|
628
656
|
- [ ] Tests are colocated with source files
|
|
657
|
+
- [ ] Timeout values are appropriate (see Performance Guidelines below)
|
|
629
658
|
|
|
630
659
|
## 🎯 Test Categories & Tags
|
|
631
660
|
|
|
@@ -672,3 +701,216 @@ A world-class test suite demonstrates:
|
|
|
672
701
|
- **Accessibility Awareness** - Tests include a11y considerations
|
|
673
702
|
|
|
674
703
|
Remember: **Tests are not just about catching bugs—they're about building confidence, enabling refactoring, and documenting expected behavior for future developers.**
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
707
|
+
## 🔄 Skipped Test Policy
|
|
708
|
+
|
|
709
|
+
### When to Skip Tests
|
|
710
|
+
|
|
711
|
+
**✅ Appropriate Reasons**:
|
|
712
|
+
- Feature is planned but not yet implemented
|
|
713
|
+
- Test documents future feature requirements
|
|
714
|
+
- Feature is blocked by dependencies
|
|
715
|
+
- Test is for experimental/optional feature
|
|
716
|
+
|
|
717
|
+
**❌ Inappropriate Reasons**:
|
|
718
|
+
- Test is flaky (fix the test instead)
|
|
719
|
+
- Test is slow (optimize instead)
|
|
720
|
+
- Feature is deprecated (remove test)
|
|
721
|
+
- Test is temporarily broken (fix immediately)
|
|
722
|
+
|
|
723
|
+
### Requirements for Skipped Tests
|
|
724
|
+
|
|
725
|
+
1. **Clear Documentation**: Add comment explaining why test is skipped
|
|
726
|
+
2. **Reference Tracking**: Link to feature issue or ticket
|
|
727
|
+
3. **Expected Timeline**: Include expected implementation date (if known)
|
|
728
|
+
4. **Quarterly Review**: Review skipped tests quarterly to determine if still relevant
|
|
729
|
+
|
|
730
|
+
### Example
|
|
731
|
+
|
|
732
|
+
```typescript
|
|
733
|
+
// TODO: Enable when keyboard navigation is implemented
|
|
734
|
+
// See: https://github.com/org/repo/issues/123
|
|
735
|
+
// Expected: Q2 2025
|
|
736
|
+
it.skip('should navigate between headers with arrow keys', async () => {
|
|
737
|
+
// Test implementation
|
|
738
|
+
});
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### Skipped Test Review Process
|
|
742
|
+
|
|
743
|
+
1. **Quarterly Audit**: Review all skipped tests
|
|
744
|
+
2. **Decision Matrix**:
|
|
745
|
+
- Feature implemented → Enable test
|
|
746
|
+
- Feature cancelled → Remove test
|
|
747
|
+
- Feature deferred → Update timeline
|
|
748
|
+
- Still relevant → Keep skipped with updated timeline
|
|
749
|
+
3. **Documentation**: Update test comments with current status
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
## 📊 Coverage Gap Remediation
|
|
754
|
+
|
|
755
|
+
### Coverage Thresholds
|
|
756
|
+
|
|
757
|
+
| Category | Target | CI Blocking | Rationale |
|
|
758
|
+
|----------|--------|-------------|-----------|
|
|
759
|
+
| **Utils/Hooks** | 95% | Yes | Core business logic, high reuse |
|
|
760
|
+
| **UI Components** | 90% | Yes | User-facing, must be reliable |
|
|
761
|
+
| **Services** | 85% | Yes | API interactions, critical paths |
|
|
762
|
+
| **Validation** | 95% | Yes | Security and data integrity |
|
|
763
|
+
| **Overall Project** | 80% | Yes | Quality baseline |
|
|
764
|
+
|
|
765
|
+
### Identifying Coverage Gaps
|
|
766
|
+
|
|
767
|
+
1. **Run Coverage Report**:
|
|
768
|
+
```bash
|
|
769
|
+
npm test -- --coverage
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
2. **Review Coverage Report**:
|
|
773
|
+
- Check per-file coverage percentages
|
|
774
|
+
- Identify files below thresholds
|
|
775
|
+
- Review uncovered lines/branches
|
|
776
|
+
|
|
777
|
+
3. **Prioritize Gaps**:
|
|
778
|
+
- **Critical**: RBAC, auth, services
|
|
779
|
+
- **High**: Business logic, hooks
|
|
780
|
+
- **Medium**: UI components, utilities
|
|
781
|
+
- **Low**: Type definitions, simple utilities
|
|
782
|
+
|
|
783
|
+
### Remediation Process
|
|
784
|
+
|
|
785
|
+
1. **Analyze Gap**: Understand what's not covered
|
|
786
|
+
2. **Create Test Plan**: Determine test type (unit, integration)
|
|
787
|
+
3. **Write Tests**: Follow TEST_STANDARD.md patterns
|
|
788
|
+
4. **Verify Coverage**: Run coverage report to confirm improvement
|
|
789
|
+
5. **Document**: Add comments for complex test scenarios
|
|
790
|
+
|
|
791
|
+
### Coverage Improvement Checklist
|
|
792
|
+
|
|
793
|
+
- [ ] Identify files below thresholds
|
|
794
|
+
- [ ] Prioritize critical paths
|
|
795
|
+
- [ ] Create test plan for each file
|
|
796
|
+
- [ ] Write tests following TEST_STANDARD.md
|
|
797
|
+
- [ ] Verify coverage improvement
|
|
798
|
+
- [ ] Document complex test scenarios
|
|
799
|
+
|
|
800
|
+
---
|
|
801
|
+
|
|
802
|
+
## ⚡ Performance Test Guidelines
|
|
803
|
+
|
|
804
|
+
### Test Execution Time Targets
|
|
805
|
+
|
|
806
|
+
- **Unit Tests**: < 50ms average
|
|
807
|
+
- **Component Tests**: < 100ms average
|
|
808
|
+
- **Integration Tests**: < 500ms average
|
|
809
|
+
- **E2E Tests**: < 2s average
|
|
810
|
+
|
|
811
|
+
### Timeout Best Practices
|
|
812
|
+
|
|
813
|
+
**Standard Timeouts**:
|
|
814
|
+
- Fast operations: `timeout: 100-500`
|
|
815
|
+
- Standard async: `timeout: 2000-3000`
|
|
816
|
+
- Slow operations: `timeout: 5000+` (with documentation)
|
|
817
|
+
|
|
818
|
+
**Optimization Tips**:
|
|
819
|
+
1. Use immediate mock resolution when possible
|
|
820
|
+
2. Avoid unnecessary `setTimeout` in mocks
|
|
821
|
+
3. Use `findBy` queries instead of `waitFor` + `getBy`
|
|
822
|
+
4. Reduce timeout values for fast operations
|
|
823
|
+
|
|
824
|
+
### Memory Management
|
|
825
|
+
|
|
826
|
+
```typescript
|
|
827
|
+
describe('Component with Resources', () => {
|
|
828
|
+
beforeEach(() => {
|
|
829
|
+
vi.useFakeTimers();
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
afterEach(() => {
|
|
833
|
+
// Prevent memory leaks
|
|
834
|
+
vi.clearAllTimers();
|
|
835
|
+
vi.useRealTimers();
|
|
836
|
+
cleanup();
|
|
837
|
+
vi.clearAllMocks();
|
|
838
|
+
localStorage.clear();
|
|
839
|
+
sessionStorage.clear();
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
### Performance Monitoring
|
|
845
|
+
|
|
846
|
+
- Monitor test execution times in CI
|
|
847
|
+
- Track slow tests (> 100ms)
|
|
848
|
+
- Alert on tests exceeding thresholds
|
|
849
|
+
- Review and optimize slow tests regularly
|
|
850
|
+
|
|
851
|
+
---
|
|
852
|
+
|
|
853
|
+
## 📁 Large Test File Management
|
|
854
|
+
|
|
855
|
+
### When to Split Test Files
|
|
856
|
+
|
|
857
|
+
**Consider Splitting When**:
|
|
858
|
+
- File exceeds 800 lines
|
|
859
|
+
- File contains multiple distinct feature areas
|
|
860
|
+
- File has slow execution times
|
|
861
|
+
- File is difficult to navigate
|
|
862
|
+
|
|
863
|
+
### Splitting Strategy
|
|
864
|
+
|
|
865
|
+
**By Feature Area**:
|
|
866
|
+
```
|
|
867
|
+
Component.test.tsx → Component.rendering.test.tsx
|
|
868
|
+
→ Component.interactions.test.tsx
|
|
869
|
+
→ Component.state.test.tsx
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
**By Test Type**:
|
|
873
|
+
```
|
|
874
|
+
Component.test.tsx → Component.unit.test.tsx
|
|
875
|
+
→ Component.integration.test.tsx
|
|
876
|
+
→ Component.accessibility.test.tsx
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
### Splitting Guidelines
|
|
880
|
+
|
|
881
|
+
1. **Keep Related Tests Together**: Don't split if tests are tightly coupled
|
|
882
|
+
2. **Maintain Test Organization**: Use describe blocks for organization
|
|
883
|
+
3. **Share Test Utilities**: Extract shared setup to test utilities
|
|
884
|
+
4. **Document Structure**: Add README explaining test organization
|
|
885
|
+
|
|
886
|
+
### File Size Recommendations
|
|
887
|
+
|
|
888
|
+
- **< 500 lines**: No splitting needed
|
|
889
|
+
- **500-800 lines**: Consider if organization improves
|
|
890
|
+
- **> 800 lines**: Strongly consider splitting
|
|
891
|
+
- **> 1000 lines**: Should be split
|
|
892
|
+
|
|
893
|
+
---
|
|
894
|
+
|
|
895
|
+
## 📈 Continuous Improvement
|
|
896
|
+
|
|
897
|
+
### Regular Reviews
|
|
898
|
+
|
|
899
|
+
1. **Quarterly**: Review skipped tests, coverage gaps, slow tests
|
|
900
|
+
2. **Monthly**: Review test execution times and patterns
|
|
901
|
+
3. **Per PR**: Ensure new tests follow TEST_STANDARD.md
|
|
902
|
+
|
|
903
|
+
### Metrics to Track
|
|
904
|
+
|
|
905
|
+
- Test execution time trends
|
|
906
|
+
- Coverage percentage trends
|
|
907
|
+
- Number of skipped tests
|
|
908
|
+
- Test helper usage patterns
|
|
909
|
+
- Test file sizes
|
|
910
|
+
|
|
911
|
+
### Resources
|
|
912
|
+
|
|
913
|
+
- [Coverage Gap Analysis](../../docs/testing/coverage-gap-analysis.md)
|
|
914
|
+
- [Test Quality Audit](../../docs/testing/test-quality-audit.md)
|
|
915
|
+
- [Test Helpers Audit](../../docs/testing/test-helpers-audit.md)
|
|
916
|
+
- [Skipped Tests Analysis](../../docs/testing/skipped-tests-analysis.md)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import React from 'react';
|
|
12
|
-
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
12
|
+
import { render, screen, fireEvent, waitFor, within } from '@testing-library/react';
|
|
13
13
|
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import { vi, beforeEach, afterEach } from 'vitest';
|
|
@@ -252,7 +252,10 @@ describe('DataTable Accessibility', () => {
|
|
|
252
252
|
}
|
|
253
253
|
});
|
|
254
254
|
|
|
255
|
-
it
|
|
255
|
+
it('should have proper aria-selected on selectable rows', async () => {
|
|
256
|
+
const user = userEvent.setup({ delay: 0 });
|
|
257
|
+
|
|
258
|
+
|
|
256
259
|
render(
|
|
257
260
|
<TestWrapper>
|
|
258
261
|
<DataTable
|
|
@@ -264,20 +267,41 @@ describe('DataTable Accessibility', () => {
|
|
|
264
267
|
</TestWrapper>
|
|
265
268
|
);
|
|
266
269
|
|
|
270
|
+
// Wait for table to render with selection enabled
|
|
271
|
+
await waitFor(() => {
|
|
272
|
+
const rows = screen.getAllByRole('row');
|
|
273
|
+
expect(rows.length).toBeGreaterThan(1); // Header + data rows
|
|
274
|
+
|
|
275
|
+
// Verify selection is enabled by checking for selection column header
|
|
276
|
+
const selectHeader = screen.queryByRole('columnheader', { name: /select all/i });
|
|
277
|
+
if (defaultFeatures.selection) {
|
|
278
|
+
expect(selectHeader).toBeInTheDocument();
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
267
282
|
// Find data rows (exclude header row)
|
|
268
283
|
const rows = screen.getAllByRole('row');
|
|
269
284
|
const dataRows = rows.slice(1); // Skip header row
|
|
270
285
|
|
|
271
|
-
// Check that
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
286
|
+
// Check that selectable data rows expose aria-selected state
|
|
287
|
+
const selectableRows = dataRows.filter(row => within(row).queryByRole('checkbox'));
|
|
288
|
+
expect(selectableRows.length).toBeGreaterThan(0);
|
|
289
|
+
|
|
290
|
+
selectableRows.forEach(row => {
|
|
276
291
|
const ariaSelected = row.getAttribute('aria-selected');
|
|
277
|
-
console.log(`Row ${index}:`, ariaSelected);
|
|
278
292
|
expect(row).toHaveAttribute('aria-selected');
|
|
279
|
-
expect(ariaSelected).toBe('false');
|
|
293
|
+
expect(ariaSelected).toBe('false');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const firstRowCheckbox = within(selectableRows[0]).getByRole('checkbox');
|
|
297
|
+
await user.click(firstRowCheckbox);
|
|
298
|
+
|
|
299
|
+
const updatedCheckbox = within(selectableRows[0]).getByRole('checkbox');
|
|
300
|
+
|
|
301
|
+
await waitFor(() => {
|
|
302
|
+
expect(updatedCheckbox).toHaveAttribute('aria-checked', 'true');
|
|
280
303
|
});
|
|
304
|
+
|
|
281
305
|
});
|
|
282
306
|
|
|
283
307
|
it('should have aria-busy when loading', () => {
|
|
@@ -416,7 +440,8 @@ describe('DataTable Accessibility', () => {
|
|
|
416
440
|
});
|
|
417
441
|
});
|
|
418
442
|
|
|
419
|
-
it
|
|
443
|
+
it('should expose sortable headers in the tab order', () => {
|
|
444
|
+
|
|
420
445
|
render(
|
|
421
446
|
<TestWrapper>
|
|
422
447
|
<DataTable
|
|
@@ -428,12 +453,17 @@ describe('DataTable Accessibility', () => {
|
|
|
428
453
|
</TestWrapper>
|
|
429
454
|
);
|
|
430
455
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
expect(
|
|
456
|
+
const sortableHeaderButtons = screen
|
|
457
|
+
.getAllByRole('columnheader')
|
|
458
|
+
.map(header => header.querySelector<HTMLButtonElement>('button'))
|
|
459
|
+
.filter((button): button is HTMLButtonElement => Boolean(button));
|
|
460
|
+
|
|
461
|
+
expect(sortableHeaderButtons.length).toBeGreaterThan(0);
|
|
462
|
+
|
|
463
|
+
sortableHeaderButtons.forEach(button => {
|
|
464
|
+
expect(button.tabIndex).toBeGreaterThanOrEqual(0);
|
|
465
|
+
expect(button).not.toHaveAttribute('aria-hidden', 'true');
|
|
466
|
+
});
|
|
437
467
|
});
|
|
438
468
|
});
|
|
439
469
|
|