@jmruthers/pace-core 0.5.136 → 0.5.139
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/{DataTable-CYOHOX3O.js → DataTable-JXFCA2BJ.js} +10 -9
- package/dist/{EventLogo-801uofbR.d.ts → EventLogo-rFL_kRjk.d.ts} +73 -1
- package/dist/{UnifiedAuthProvider-5E5TUNMS.js → UnifiedAuthProvider-XIQQ7LVU.js} +4 -5
- package/dist/{chunk-YLKIDTUK.js → chunk-22WKWKRX.js} +4 -4
- package/dist/{chunk-TVYPTYOY.js → chunk-4C7EXCAR.js} +60 -24
- package/dist/chunk-4C7EXCAR.js.map +1 -0
- package/dist/{chunk-NOHEVYVX.js → chunk-5JMOHWDI.js} +417 -319
- package/dist/chunk-5JMOHWDI.js.map +1 -0
- package/dist/{chunk-FHWWBIHA.js → chunk-6DXZ6V5Q.js} +5 -5
- package/dist/{chunk-2TWNJ46Y.js → chunk-6LAAY47Q.js} +2 -2
- package/dist/{chunk-444EZN6N.js → chunk-7QCC6MCP.js} +88 -1
- package/dist/chunk-7QCC6MCP.js.map +1 -0
- package/dist/chunk-BJPBT3CU.js +21 -0
- package/dist/chunk-BJPBT3CU.js.map +1 -0
- package/dist/{chunk-L6PGMCMD.js → chunk-BOOI7GK2.js} +38 -12
- package/dist/chunk-BOOI7GK2.js.map +1 -0
- package/dist/{chunk-XARJS7CD.js → chunk-INQLMHPF.js} +2 -2
- package/dist/chunk-JISYG63F.js +70 -0
- package/dist/chunk-JISYG63F.js.map +1 -0
- package/dist/{chunk-SL2YQDR6.js → chunk-MA6EPSGZ.js} +2 -2
- package/dist/{chunk-5DPZ5EAT.js → chunk-OWAG3GSU.js} +1 -3
- package/dist/{chunk-LTV3XIJJ.js → chunk-T6JN6LH6.js} +4 -4
- package/dist/{chunk-HJGGOMQ6.js → chunk-TLT2ZR3L.js} +147 -103
- package/dist/chunk-TLT2ZR3L.js.map +1 -0
- package/dist/{chunk-4MT5BGGL.js → chunk-YCWDTTUK.js} +4 -6
- package/dist/{chunk-4MT5BGGL.js.map → chunk-YCWDTTUK.js.map} +1 -1
- package/dist/components.d.ts +1 -1
- package/dist/components.js +12 -11
- package/dist/components.js.map +1 -1
- package/dist/hooks.js +8 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15 -14
- package/dist/index.js.map +1 -1
- package/dist/providers.js +3 -4
- package/dist/rbac/index.js +8 -9
- package/dist/schema-DTDZQe2u.d.ts +28 -0
- package/dist/types.d.ts +152 -3
- package/dist/types.js +51 -16
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +89 -4
- package/dist/utils.js +214 -96
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +1 -343
- package/dist/validation.js +3 -100
- 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 +1 -1
- 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/BadgeProps.md +27 -0
- 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 +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- 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/SessionRestorationLoaderProps.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 +84 -15
- package/docs/architecture/README.md +0 -1
- package/docs/styles/README.md +0 -2
- package/examples/RBAC/CompleteRBACExample.tsx +324 -0
- package/examples/RBAC/EventBasedApp.tsx +239 -0
- package/examples/RBAC/PermissionExample.tsx +151 -0
- package/examples/RBAC/index.ts +13 -0
- package/examples/public-pages/CorrectPublicPageImplementation.tsx +301 -0
- package/examples/public-pages/PublicEventPage.tsx +274 -0
- package/examples/public-pages/PublicPageApp.tsx +308 -0
- package/examples/public-pages/PublicPageUsageExample.tsx +216 -0
- package/examples/public-pages/index.ts +14 -0
- package/package.json +1 -10
- package/src/__tests__/TEST_STANDARD.md +92 -0
- package/src/components/Badge/Badge.test.tsx +314 -0
- package/src/components/Badge/Badge.tsx +304 -0
- package/src/components/Badge/index.ts +3 -0
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +217 -0
- package/src/components/DataTable/__tests__/styles.test.ts +1 -1
- package/src/components/DataTable/components/ColumnFilter.tsx +8 -4
- package/src/components/DataTable/components/DataTableBody.tsx +461 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +144 -0
- package/src/components/DataTable/components/FilterRow.tsx +9 -3
- package/src/components/DataTable/components/PaginationControls.tsx +1 -0
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +513 -0
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +14 -68
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +62 -0
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +43 -0
- package/src/components/DataTable/core/ActionManager.ts +235 -0
- package/src/components/DataTable/core/ColumnManager.ts +205 -0
- package/src/components/DataTable/core/DataManager.ts +188 -0
- package/src/components/DataTable/core/DataTableContext.tsx +181 -0
- package/src/components/DataTable/core/LocalDataAdapter.ts +273 -0
- package/src/components/DataTable/core/PluginRegistry.ts +229 -0
- package/src/components/DataTable/core/StateManager.ts +311 -0
- package/src/components/DataTable/core/interfaces.ts +338 -0
- package/src/components/DataTable/styles.ts +27 -6
- package/src/components/DataTable/utils/__tests__/columnUtils.test.ts +94 -0
- package/src/components/DataTable/utils/columnUtils.ts +40 -0
- package/src/components/DataTable/utils/debugTools.ts +609 -0
- package/src/components/DataTable/utils/index.ts +1 -0
- package/src/components/Dialog/README.md +804 -0
- package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +611 -0
- package/src/components/Dialog/utils/safeHtml.ts +185 -0
- package/src/components/Footer/Footer.test.tsx +1 -1
- package/src/components/Form/Form.test.tsx +1 -1
- package/src/components/Form/FormErrorSummary.tsx +113 -0
- package/src/components/Form/FormFieldset.tsx +127 -0
- package/src/components/Form/FormLiveRegion.tsx +198 -0
- package/src/components/LoginForm/LoginForm.test.tsx +1 -1
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +76 -10
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
- package/src/components/PasswordReset/PasswordResetForm.test.tsx +597 -0
- package/src/components/PasswordReset/PasswordResetForm.tsx +201 -0
- package/src/components/PublicLayout/PublicPageDebugger.tsx +104 -0
- package/src/components/PublicLayout/PublicPageDiagnostic.tsx +162 -0
- package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +1 -1
- package/src/components/Select/Select.test.tsx +1 -1
- package/src/components/Select/Select.tsx +20 -8
- package/src/components/Table/__tests__/Table.test.tsx +1 -1
- package/src/components/index.ts +3 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +83 -85
- package/src/index.ts +4 -0
- package/src/rbac/hooks/useCan.test.ts +24 -0
- package/src/rbac/hooks/usePermissions.ts +49 -12
- package/src/styles/core.css +3 -0
- package/src/utils/appConfig.ts +47 -0
- package/src/utils/appIdResolver.test.ts +499 -0
- package/src/utils/appIdResolver.ts +130 -0
- package/src/utils/appNameResolver.simple.test.ts +212 -0
- package/src/utils/appNameResolver.test.ts +121 -0
- package/src/utils/appNameResolver.ts +191 -0
- package/src/utils/audit.ts +127 -0
- package/src/utils/auth-utils.ts +96 -0
- package/src/utils/bundleAnalysis.ts +129 -0
- package/src/utils/cn.ts +7 -0
- package/src/utils/debugLogger.ts +67 -0
- package/src/utils/deviceFingerprint.ts +215 -0
- package/src/utils/dynamicUtils.ts +105 -0
- package/src/utils/file-reference.test.ts +788 -0
- package/src/utils/file-reference.ts +519 -0
- package/src/utils/formatDate.test.ts +237 -0
- package/src/utils/formatting.ts +133 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/lazyLoad.tsx +44 -0
- package/src/utils/logger.ts +179 -0
- package/src/utils/organisationContext.test.ts +322 -0
- package/src/utils/organisationContext.ts +153 -0
- package/src/utils/performanceBenchmark.ts +64 -0
- package/src/utils/performanceBudgets.ts +110 -0
- package/src/utils/permissionTypes.ts +37 -0
- package/src/utils/permissionUtils.test.ts +393 -0
- package/src/utils/permissionUtils.ts +34 -0
- package/src/utils/sanitization.ts +264 -0
- package/src/utils/schemaUtils.ts +37 -0
- package/src/utils/secureDataAccess.test.ts +711 -0
- package/src/utils/secureDataAccess.ts +377 -0
- package/src/utils/secureErrors.ts +79 -0
- package/src/utils/secureStorage.ts +244 -0
- package/src/utils/security.ts +156 -0
- package/src/utils/securityMonitor.ts +45 -0
- package/src/utils/sessionTracking.ts +126 -0
- package/src/utils/validation.ts +111 -0
- package/src/utils/validationUtils.ts +120 -0
- package/src/validation/index.ts +2 -2
- package/dist/chunk-444EZN6N.js.map +0 -1
- package/dist/chunk-APIBCTL2.js +0 -670
- package/dist/chunk-APIBCTL2.js.map +0 -1
- package/dist/chunk-HJGGOMQ6.js.map +0 -1
- package/dist/chunk-K2WWTH7O.js +0 -94
- package/dist/chunk-K2WWTH7O.js.map +0 -1
- package/dist/chunk-L6PGMCMD.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-NOHEVYVX.js.map +0 -1
- package/dist/chunk-TVYPTYOY.js.map +0 -1
- package/dist/validation-8npbysjg.d.ts +0 -177
- /package/dist/{DataTable-CYOHOX3O.js.map → DataTable-JXFCA2BJ.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-5E5TUNMS.js.map → UnifiedAuthProvider-XIQQ7LVU.js.map} +0 -0
- /package/dist/{chunk-YLKIDTUK.js.map → chunk-22WKWKRX.js.map} +0 -0
- /package/dist/{chunk-FHWWBIHA.js.map → chunk-6DXZ6V5Q.js.map} +0 -0
- /package/dist/{chunk-2TWNJ46Y.js.map → chunk-6LAAY47Q.js.map} +0 -0
- /package/dist/{chunk-XARJS7CD.js.map → chunk-INQLMHPF.js.map} +0 -0
- /package/dist/{chunk-SL2YQDR6.js.map → chunk-MA6EPSGZ.js.map} +0 -0
- /package/dist/{chunk-5DPZ5EAT.js.map → chunk-OWAG3GSU.js.map} +0 -0
- /package/dist/{chunk-LTV3XIJJ.js.map → chunk-T6JN6LH6.js.map} +0 -0
- /package/examples/{components → components 2}/DataTable/HierarchicalActionsExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/HierarchicalExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/InitialPageSizeExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/PerformanceExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/index.ts +0 -0
- /package/examples/{components → components 2}/Dialog/BasicHtmlTest.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/DebugHtmlExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/HtmlDialogExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/ScrollableDialogExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/SimpleHtmlTest.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/SmartDialogExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/index.ts +0 -0
- /package/examples/{components → components 2}/index.ts +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Public Page Usage Example
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Examples/PublicPages
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* A complete example showing the correct usage pattern for public pages.
|
|
8
|
+
* This example demonstrates how to properly implement public pages without
|
|
9
|
+
* authentication context conflicts.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* import { PublicPageUsageExample } from '@jmruthers/pace-core/examples';
|
|
14
|
+
*
|
|
15
|
+
* function App() {
|
|
16
|
+
* return (
|
|
17
|
+
* <BrowserRouter>
|
|
18
|
+
* <Routes>
|
|
19
|
+
* <Route path="/events/:eventCode/recipe-grid-report" element={<PublicPageUsageExample />} />
|
|
20
|
+
* </Routes>
|
|
21
|
+
* </BrowserRouter>
|
|
22
|
+
* );
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import React from 'react';
|
|
28
|
+
import {
|
|
29
|
+
PublicPageLayout,
|
|
30
|
+
PublicPageHeader,
|
|
31
|
+
PublicPageFooter,
|
|
32
|
+
EventLogo,
|
|
33
|
+
usePublicEvent,
|
|
34
|
+
usePublicRouteParams,
|
|
35
|
+
PublicLoadingSpinner,
|
|
36
|
+
PublicErrorBoundary
|
|
37
|
+
} from '../../src';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Correct usage pattern for public pages
|
|
41
|
+
*
|
|
42
|
+
* This component demonstrates the proper way to implement public pages:
|
|
43
|
+
* 1. Use usePublicRouteParams to get the eventCode from URL
|
|
44
|
+
* 2. Use usePublicEvent to fetch event data
|
|
45
|
+
* 3. Pass event data to PublicPageLayout
|
|
46
|
+
* 4. No authentication context is triggered
|
|
47
|
+
*/
|
|
48
|
+
export function PublicPageUsageExample() {
|
|
49
|
+
// Step 1: Extract event code from URL parameters
|
|
50
|
+
const { eventCode } = usePublicRouteParams({ fetchEventData: false });
|
|
51
|
+
|
|
52
|
+
// Step 2: Fetch event data using the event code
|
|
53
|
+
const { event, isLoading, error, refetch } = usePublicEvent(eventCode || '');
|
|
54
|
+
|
|
55
|
+
// Step 3: Handle loading state
|
|
56
|
+
if (isLoading) {
|
|
57
|
+
return (
|
|
58
|
+
<PublicLoadingSpinner
|
|
59
|
+
message="Loading event details..."
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Step 4: Handle error state
|
|
65
|
+
if (error) {
|
|
66
|
+
return (
|
|
67
|
+
<div className="min-h-screen bg-main-50 flex items-center justify-center">
|
|
68
|
+
<div className="max-w-md mx-auto text-center px-4">
|
|
69
|
+
<div className="mb-6">
|
|
70
|
+
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-acc-100 mb-4">
|
|
71
|
+
<svg className="h-6 w-6 text-acc-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
72
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
73
|
+
</svg>
|
|
74
|
+
</div>
|
|
75
|
+
<h1 className="text-2xl font-bold text-sec-900 mb-2">
|
|
76
|
+
Event Not Found
|
|
77
|
+
</h1>
|
|
78
|
+
<p className="text-sec-600 mb-6">
|
|
79
|
+
The event code "{eventCode}" is invalid or the event is not available for public viewing.
|
|
80
|
+
</p>
|
|
81
|
+
<button
|
|
82
|
+
onClick={refetch}
|
|
83
|
+
className="px-4 py-2 bg-main-600 text-main-50 rounded-md hover:bg-main-700 transition-colors"
|
|
84
|
+
>
|
|
85
|
+
Try Again
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Step 5: Handle missing event
|
|
94
|
+
if (!event) {
|
|
95
|
+
return (
|
|
96
|
+
<div className="min-h-screen bg-main-50 flex items-center justify-center">
|
|
97
|
+
<div className="max-w-md mx-auto text-center px-4">
|
|
98
|
+
<h1 className="text-2xl font-bold text-sec-900 mb-4">
|
|
99
|
+
Event Not Available
|
|
100
|
+
</h1>
|
|
101
|
+
<p className="text-sec-600 mb-6">
|
|
102
|
+
This event is not available for public viewing.
|
|
103
|
+
</p>
|
|
104
|
+
<button
|
|
105
|
+
onClick={refetch}
|
|
106
|
+
className="px-4 py-2 bg-main-600 text-main-50 rounded-md hover:bg-main-700 transition-colors"
|
|
107
|
+
>
|
|
108
|
+
Try Again
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Step 6: Render the public page with event data
|
|
116
|
+
return (
|
|
117
|
+
<PublicErrorBoundary>
|
|
118
|
+
<PublicPageLayout eventCode={eventCode || ''} event={event}>
|
|
119
|
+
<PublicPageHeader
|
|
120
|
+
event={event}
|
|
121
|
+
eventCode={eventCode || ''}
|
|
122
|
+
title="Recipe Grid Report"
|
|
123
|
+
description="Public recipe grid report for this event"
|
|
124
|
+
/>
|
|
125
|
+
|
|
126
|
+
<main className="max-w-6xl mx-auto px-4 py-8">
|
|
127
|
+
{/* Event Overview */}
|
|
128
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
|
|
129
|
+
{/* Event Information */}
|
|
130
|
+
<div className="lg:col-span-2 space-y-6">
|
|
131
|
+
<div>
|
|
132
|
+
<h2 className="text-2xl font-bold text-sec-900 mb-4">Event Information</h2>
|
|
133
|
+
<div className="bg-sec-50 rounded-lg p-6 space-y-4">
|
|
134
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
135
|
+
<div>
|
|
136
|
+
<h3 className="font-semibold text-sec-700">Date</h3>
|
|
137
|
+
<p className="text-sec-900">
|
|
138
|
+
{event.event_date ? new Date(event.event_date).toLocaleDateString('en-AU', {
|
|
139
|
+
weekday: 'long',
|
|
140
|
+
year: 'numeric',
|
|
141
|
+
month: 'long',
|
|
142
|
+
day: 'numeric'
|
|
143
|
+
}) : 'TBA'}
|
|
144
|
+
</p>
|
|
145
|
+
</div>
|
|
146
|
+
<div>
|
|
147
|
+
<h3 className="font-semibold text-sec-700">Venue</h3>
|
|
148
|
+
<p className="text-sec-900">{event.event_venue || 'TBA'}</p>
|
|
149
|
+
</div>
|
|
150
|
+
<div>
|
|
151
|
+
<h3 className="font-semibold text-sec-700">Participants</h3>
|
|
152
|
+
<p className="text-sec-900">{event.event_participants || 'TBA'}</p>
|
|
153
|
+
</div>
|
|
154
|
+
<div>
|
|
155
|
+
<h3 className="font-semibold text-sec-700">Event Code</h3>
|
|
156
|
+
<p className="text-sec-900 font-mono">{event.event_code}</p>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
{event.event_news && (
|
|
161
|
+
<div>
|
|
162
|
+
<h3 className="font-semibold text-sec-700 mb-2">Event News</h3>
|
|
163
|
+
<p className="text-sec-900">{event.event_news}</p>
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{/* Event Logo */}
|
|
171
|
+
<div className="flex justify-center lg:justify-start">
|
|
172
|
+
<div className="text-center">
|
|
173
|
+
<EventLogo
|
|
174
|
+
eventId={event.event_id}
|
|
175
|
+
eventName={event.event_name}
|
|
176
|
+
organisationId={event.organisation_id}
|
|
177
|
+
size="2xl"
|
|
178
|
+
className="rounded-lg shadow-lg"
|
|
179
|
+
/>
|
|
180
|
+
<p className="mt-4 text-sm text-sec-600">Event Logo</p>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* Recipe Grid Report Content */}
|
|
186
|
+
<div className="mb-12">
|
|
187
|
+
<div className="bg-main-50 border border-main-200 rounded-lg p-6">
|
|
188
|
+
<h3 className="font-semibold text-main-900 mb-2">Recipe Grid Report</h3>
|
|
189
|
+
<p className="text-main-800">
|
|
190
|
+
This is where your recipe grid report content would go.
|
|
191
|
+
The public page is now working correctly without authentication context conflicts.
|
|
192
|
+
</p>
|
|
193
|
+
<div className="mt-4 text-sm text-main-700">
|
|
194
|
+
<p><strong>Event Code:</strong> {eventCode}</p>
|
|
195
|
+
<p><strong>Event ID:</strong> {event.event_id}</p>
|
|
196
|
+
<p><strong>Event Name:</strong> {event.event_name}</p>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
{/* Event Footer Information */}
|
|
202
|
+
{event.event_footer && (
|
|
203
|
+
<div className="bg-sec-50 rounded-lg p-6">
|
|
204
|
+
<h3 className="font-semibold text-sec-900 mb-2">Additional Information</h3>
|
|
205
|
+
<p className="text-sec-700">{event.event_footer}</p>
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
</main>
|
|
209
|
+
|
|
210
|
+
<PublicPageFooter event={event} />
|
|
211
|
+
</PublicPageLayout>
|
|
212
|
+
</PublicErrorBoundary>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export default PublicPageUsageExample;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Public Pages Examples
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Examples/PublicPages
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* Public page workflow examples
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { PublicPageUsageExample } from './PublicPageUsageExample';
|
|
11
|
+
export { PublicEventPage, PublicEventPageCompact } from './PublicEventPage';
|
|
12
|
+
export { PublicPageApp } from './PublicPageApp';
|
|
13
|
+
export { RootApp as CorrectPublicPageImplementation } from './CorrectPublicPageImplementation';
|
|
14
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jmruthers/pace-core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.139",
|
|
4
4
|
"description": "Clean, modern React component library with Tailwind v4 styling and native utilities",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -27,11 +27,6 @@
|
|
|
27
27
|
"import": "./dist/providers.js",
|
|
28
28
|
"default": "./dist/providers.js"
|
|
29
29
|
},
|
|
30
|
-
"./validation": {
|
|
31
|
-
"types": "./dist/validation.d.ts",
|
|
32
|
-
"import": "./dist/validation.js",
|
|
33
|
-
"default": "./dist/validation.js"
|
|
34
|
-
},
|
|
35
30
|
"./hooks": {
|
|
36
31
|
"types": "./dist/hooks.d.ts",
|
|
37
32
|
"import": "./dist/hooks.js",
|
|
@@ -93,10 +88,6 @@
|
|
|
93
88
|
"import": "./src/types/index.ts",
|
|
94
89
|
"default": "./src/types/index.ts"
|
|
95
90
|
},
|
|
96
|
-
"./source/validation": {
|
|
97
|
-
"import": "./src/validation/index.ts",
|
|
98
|
-
"default": "./src/validation/index.ts"
|
|
99
|
-
},
|
|
100
91
|
"./source/rbac": {
|
|
101
92
|
"import": "./src/rbac/index.ts",
|
|
102
93
|
"default": "./src/rbac/index.ts"
|
|
@@ -289,6 +289,50 @@ mockSupabase.rpc.mockImplementation((functionName, params) => {
|
|
|
289
289
|
});
|
|
290
290
|
```
|
|
291
291
|
|
|
292
|
+
### Mock Isolation Best Practices
|
|
293
|
+
|
|
294
|
+
**Problem**: Mocks can leak between test files, causing flaky tests.
|
|
295
|
+
|
|
296
|
+
**Solutions**:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// ✅ Good: Use vi.hoisted() for module mocks
|
|
300
|
+
const { mockGetSignedUrl } = vi.hoisted(() => {
|
|
301
|
+
return {
|
|
302
|
+
mockGetSignedUrl: vi.fn(() => Promise.resolve({ url: 'test' }))
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
vi.mock('../utils/storage/helpers', () => ({
|
|
307
|
+
getSignedUrl: mockGetSignedUrl
|
|
308
|
+
}));
|
|
309
|
+
|
|
310
|
+
// ✅ Good: Reset mocks in beforeEach
|
|
311
|
+
beforeEach(() => {
|
|
312
|
+
mockGetSignedUrl.mockClear();
|
|
313
|
+
mockGetSignedUrl.mockReset();
|
|
314
|
+
mockGetSignedUrl.mockImplementation(() => Promise.resolve({ url: 'test' }));
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// ✅ Good: Use vi.spyOn for better isolation (when possible)
|
|
318
|
+
const getSignedUrlSpy = vi.spyOn(storageHelpers, 'getSignedUrl');
|
|
319
|
+
getSignedUrlSpy.mockResolvedValue({ url: 'test' });
|
|
320
|
+
|
|
321
|
+
// ✅ Good: Use vi.stubGlobal for global mocks
|
|
322
|
+
vi.stubGlobal('window', undefined);
|
|
323
|
+
// Automatically restored after test
|
|
324
|
+
|
|
325
|
+
// ❌ Bad: Directly modifying global objects
|
|
326
|
+
delete global.window; // Can break other tests
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Mock Isolation Checklist**:
|
|
330
|
+
- [ ] Use `vi.hoisted()` for module mocks
|
|
331
|
+
- [ ] Reset mocks in `beforeEach`/`afterEach`
|
|
332
|
+
- [ ] Use `vi.stubGlobal` for global mocks
|
|
333
|
+
- [ ] Prefer `vi.spyOn` over `vi.mock` when possible
|
|
334
|
+
- [ ] Test files in isolation to verify no leakage
|
|
335
|
+
|
|
292
336
|
### Cleanup Best Practices
|
|
293
337
|
|
|
294
338
|
```typescript
|
|
@@ -821,6 +865,54 @@ it.skip('should navigate between headers with arrow keys', async () => {
|
|
|
821
865
|
3. Use `findBy` queries instead of `waitFor` + `getBy`
|
|
822
866
|
4. Reduce timeout values for fast operations
|
|
823
867
|
|
|
868
|
+
### Performance Testing Best Practices
|
|
869
|
+
|
|
870
|
+
**When to Test Performance**:
|
|
871
|
+
- ✅ Critical user-facing operations (rendering, navigation)
|
|
872
|
+
- ✅ Operations that must complete within SLA
|
|
873
|
+
- ✅ Memory-intensive operations
|
|
874
|
+
- ❌ Avoid testing performance of mocked operations
|
|
875
|
+
- ❌ Avoid absolute timing thresholds in CI environments
|
|
876
|
+
|
|
877
|
+
**Performance Test Patterns**:
|
|
878
|
+
|
|
879
|
+
```typescript
|
|
880
|
+
// ❌ Bad: Absolute thresholds (brittle in CI/coverage)
|
|
881
|
+
const startTime = performance.now();
|
|
882
|
+
render(<Component />);
|
|
883
|
+
const endTime = performance.now();
|
|
884
|
+
expect(endTime - startTime).toBeLessThan(200); // Fails under coverage
|
|
885
|
+
|
|
886
|
+
// ✅ Good: Behavioral assertions (focus on outcomes)
|
|
887
|
+
await waitFor(() => {
|
|
888
|
+
expect(screen.getByRole('navigation')).toBeInTheDocument();
|
|
889
|
+
}, { timeout: 2000 });
|
|
890
|
+
|
|
891
|
+
// ✅ Good: Relative comparisons (more tolerant)
|
|
892
|
+
const baseline = measureBaseline();
|
|
893
|
+
const actual = measureActual();
|
|
894
|
+
expect(actual).toBeLessThan(baseline * 1.5); // 50% tolerance
|
|
895
|
+
|
|
896
|
+
// ✅ Good: Smoke tests (verify no regressions)
|
|
897
|
+
it('renders without performance degradation', async () => {
|
|
898
|
+
const startTime = performance.now();
|
|
899
|
+
render(<Component />);
|
|
900
|
+
await waitFor(() => {
|
|
901
|
+
expect(screen.getByRole('main')).toBeInTheDocument();
|
|
902
|
+
});
|
|
903
|
+
const endTime = performance.now();
|
|
904
|
+
// Only fail if significantly slower (e.g., > 5s)
|
|
905
|
+
expect(endTime - startTime).toBeLessThan(5000);
|
|
906
|
+
});
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
**Performance Test Guidelines**:
|
|
910
|
+
1. **Prefer Behavioral Tests**: Focus on what users see, not raw timing
|
|
911
|
+
2. **Use Relative Thresholds**: Compare against baseline, not absolute values
|
|
912
|
+
3. **Skip in Coverage Mode**: Use `if (process.env.COVERAGE) return;` for timing tests
|
|
913
|
+
4. **Document Thresholds**: Explain why specific timing is critical
|
|
914
|
+
5. **Separate Performance Suite**: Consider separate test suite for performance tests
|
|
915
|
+
|
|
824
916
|
### Memory Management
|
|
825
917
|
|
|
826
918
|
```typescript
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Badge Component Tests
|
|
3
|
+
* @description Comprehensive test suite for Badge component
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { screen } from '@testing-library/react';
|
|
8
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
9
|
+
import { Badge } from './Badge';
|
|
10
|
+
import { renderWithProviders } from '../../__tests__/helpers/test-utils';
|
|
11
|
+
import type { BadgeVariant } from './Badge';
|
|
12
|
+
|
|
13
|
+
describe('Badge Component', () => {
|
|
14
|
+
describe('Rendering', () => {
|
|
15
|
+
it('renders with text content', () => {
|
|
16
|
+
renderWithProviders(<Badge>New</Badge>);
|
|
17
|
+
expect(screen.getByText('New')).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('renders as span element', () => {
|
|
21
|
+
const { container } = renderWithProviders(<Badge>Test</Badge>);
|
|
22
|
+
const badge = container.querySelector('span');
|
|
23
|
+
expect(badge).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('renders with custom className', () => {
|
|
27
|
+
renderWithProviders(<Badge className="custom-class">Styled badge</Badge>);
|
|
28
|
+
const badge = screen.getByText('Styled badge');
|
|
29
|
+
expect(badge).toHaveClass('custom-class');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('renders with data-testid', () => {
|
|
33
|
+
renderWithProviders(<Badge data-testid="test-badge">Test</Badge>);
|
|
34
|
+
expect(screen.getByTestId('test-badge')).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('renders with children elements', () => {
|
|
38
|
+
renderWithProviders(
|
|
39
|
+
<Badge>
|
|
40
|
+
<span>Icon</span> Text
|
|
41
|
+
</Badge>
|
|
42
|
+
);
|
|
43
|
+
expect(screen.getByText('Icon')).toBeInTheDocument();
|
|
44
|
+
expect(screen.getByText('Text')).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('Variants', () => {
|
|
49
|
+
const allVariants: BadgeVariant[] = [
|
|
50
|
+
// Solid variants
|
|
51
|
+
'solid-main-muted',
|
|
52
|
+
'solid-main-normal',
|
|
53
|
+
'solid-main-strong',
|
|
54
|
+
'solid-sec-muted',
|
|
55
|
+
'solid-sec-normal',
|
|
56
|
+
'solid-sec-strong',
|
|
57
|
+
'solid-acc-muted',
|
|
58
|
+
'solid-acc-normal',
|
|
59
|
+
'solid-acc-strong',
|
|
60
|
+
// Outline variants
|
|
61
|
+
'outline-main-muted',
|
|
62
|
+
'outline-main-normal',
|
|
63
|
+
'outline-main-strong',
|
|
64
|
+
'outline-sec-muted',
|
|
65
|
+
'outline-sec-normal',
|
|
66
|
+
'outline-sec-strong',
|
|
67
|
+
'outline-acc-muted',
|
|
68
|
+
'outline-acc-normal',
|
|
69
|
+
'outline-acc-strong',
|
|
70
|
+
// Soft variants
|
|
71
|
+
'soft-main-muted',
|
|
72
|
+
'soft-main-normal',
|
|
73
|
+
'soft-main-strong',
|
|
74
|
+
'soft-sec-muted',
|
|
75
|
+
'soft-sec-normal',
|
|
76
|
+
'soft-sec-strong',
|
|
77
|
+
'soft-acc-muted',
|
|
78
|
+
'soft-acc-normal',
|
|
79
|
+
'soft-acc-strong',
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
allVariants.forEach((variant) => {
|
|
83
|
+
it(`renders ${variant} variant correctly`, () => {
|
|
84
|
+
renderWithProviders(<Badge variant={variant}>Test {variant}</Badge>);
|
|
85
|
+
const badge = screen.getByText(`Test ${variant}`);
|
|
86
|
+
expect(badge).toBeInTheDocument();
|
|
87
|
+
expect(badge).toHaveClass('inline-flex', 'items-center', 'rounded-2xl');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('uses default variant when not specified', () => {
|
|
92
|
+
renderWithProviders(<Badge>Default</Badge>);
|
|
93
|
+
const badge = screen.getByText('Default');
|
|
94
|
+
expect(badge).toHaveClass('bg-main-500', 'text-main-50');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('applies solid variant classes correctly', () => {
|
|
98
|
+
renderWithProviders(<Badge variant="solid-main-normal">Solid</Badge>);
|
|
99
|
+
const badge = screen.getByText('Solid');
|
|
100
|
+
expect(badge).toHaveClass('bg-main-500', 'text-main-50');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('applies outline variant classes correctly', () => {
|
|
104
|
+
renderWithProviders(<Badge variant="outline-sec-muted">Outline</Badge>);
|
|
105
|
+
const badge = screen.getByText('Outline');
|
|
106
|
+
expect(badge).toHaveClass('outline', 'outline-1');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('applies soft variant classes correctly', () => {
|
|
110
|
+
renderWithProviders(<Badge variant="soft-acc-strong">Soft</Badge>);
|
|
111
|
+
const badge = screen.getByText('Soft');
|
|
112
|
+
expect(badge).toHaveClass('shadow-badge-soft');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('Color System', () => {
|
|
117
|
+
it('uses main color palette for main variants', () => {
|
|
118
|
+
renderWithProviders(<Badge variant="solid-main-normal">Main</Badge>);
|
|
119
|
+
const badge = screen.getByText('Main');
|
|
120
|
+
expect(badge).toHaveClass('bg-main-500', 'text-main-50');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('uses sec color palette for sec variants', () => {
|
|
124
|
+
renderWithProviders(<Badge variant="solid-sec-normal">Sec</Badge>);
|
|
125
|
+
const badge = screen.getByText('Sec');
|
|
126
|
+
expect(badge).toHaveClass('bg-sec-500', 'text-sec-50');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('uses acc color palette for acc variants', () => {
|
|
130
|
+
renderWithProviders(<Badge variant="solid-acc-normal">Acc</Badge>);
|
|
131
|
+
const badge = screen.getByText('Acc');
|
|
132
|
+
expect(badge).toHaveClass('bg-acc-500', 'text-acc-50');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('does not use standard Tailwind colors', () => {
|
|
136
|
+
renderWithProviders(<Badge variant="solid-main-normal">Test</Badge>);
|
|
137
|
+
const badge = screen.getByText('Test');
|
|
138
|
+
// Should not have standard Tailwind color classes
|
|
139
|
+
expect(badge).not.toHaveClass('bg-blue-', 'text-red-', 'bg-gray-', 'bg-white', 'bg-black');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('Shade Variants', () => {
|
|
144
|
+
it('applies muted shade correctly (dark text on light)', () => {
|
|
145
|
+
renderWithProviders(<Badge variant="solid-main-muted">Muted</Badge>);
|
|
146
|
+
const badge = screen.getByText('Muted');
|
|
147
|
+
expect(badge).toHaveClass('bg-main-200', 'text-main-600');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('applies normal shade correctly (light text on medium)', () => {
|
|
151
|
+
renderWithProviders(<Badge variant="solid-main-normal">Normal</Badge>);
|
|
152
|
+
const badge = screen.getByText('Normal');
|
|
153
|
+
expect(badge).toHaveClass('bg-main-500', 'text-main-50');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('applies strong shade correctly (light text on dark)', () => {
|
|
157
|
+
renderWithProviders(<Badge variant="solid-main-strong">Strong</Badge>);
|
|
158
|
+
const badge = screen.getByText('Strong');
|
|
159
|
+
expect(badge).toHaveClass('bg-main-700', 'text-main-50');
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('Props Forwarding', () => {
|
|
164
|
+
it('forwards HTML attributes', () => {
|
|
165
|
+
renderWithProviders(
|
|
166
|
+
<Badge id="test-id" data-custom="value" aria-label="Test badge">
|
|
167
|
+
Test
|
|
168
|
+
</Badge>
|
|
169
|
+
);
|
|
170
|
+
const badge = screen.getByText('Test');
|
|
171
|
+
expect(badge).toHaveAttribute('id', 'test-id');
|
|
172
|
+
expect(badge).toHaveAttribute('data-custom', 'value');
|
|
173
|
+
expect(badge).toHaveAttribute('aria-label', 'Test badge');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('forwards ref correctly', () => {
|
|
177
|
+
const ref = React.createRef<HTMLSpanElement>();
|
|
178
|
+
renderWithProviders(<Badge ref={ref}>Ref badge</Badge>);
|
|
179
|
+
expect(ref.current).toBeInstanceOf(HTMLSpanElement);
|
|
180
|
+
expect(ref.current).toHaveTextContent('Ref badge');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('merges custom className with variant classes', () => {
|
|
184
|
+
renderWithProviders(
|
|
185
|
+
<Badge variant="solid-main-normal" className="custom-class px-4">
|
|
186
|
+
Custom
|
|
187
|
+
</Badge>
|
|
188
|
+
);
|
|
189
|
+
const badge = screen.getByText('Custom');
|
|
190
|
+
expect(badge).toHaveClass('custom-class', 'px-4', 'bg-main-500');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('Accessibility', () => {
|
|
195
|
+
it('renders as non-interactive span element', () => {
|
|
196
|
+
const { container } = renderWithProviders(<Badge>Test</Badge>);
|
|
197
|
+
const badge = container.querySelector('span');
|
|
198
|
+
expect(badge).toBeInTheDocument();
|
|
199
|
+
// Should not be a button or have button role
|
|
200
|
+
expect(badge).not.toHaveAttribute('role', 'button');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('does not have focus styles by default', () => {
|
|
204
|
+
renderWithProviders(<Badge>Test</Badge>);
|
|
205
|
+
const badge = screen.getByText('Test');
|
|
206
|
+
// Badge should not have focus-visible classes since it's non-interactive
|
|
207
|
+
expect(badge).not.toHaveClass('focus-visible:outline-none');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('supports aria-label for screen readers', () => {
|
|
211
|
+
renderWithProviders(<Badge aria-label="Status: Active">Active</Badge>);
|
|
212
|
+
const badge = screen.getByText('Active');
|
|
213
|
+
expect(badge).toHaveAttribute('aria-label', 'Status: Active');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('supports aria-describedby', () => {
|
|
217
|
+
renderWithProviders(
|
|
218
|
+
<Badge aria-describedby="badge-description">Test</Badge>
|
|
219
|
+
);
|
|
220
|
+
const badge = screen.getByText('Test');
|
|
221
|
+
expect(badge).toHaveAttribute('aria-describedby', 'badge-description');
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('Integration', () => {
|
|
226
|
+
it('works with React.forwardRef', () => {
|
|
227
|
+
const ref = React.createRef<HTMLSpanElement>();
|
|
228
|
+
renderWithProviders(<Badge ref={ref}>Ref badge</Badge>);
|
|
229
|
+
expect(ref.current).toBeInstanceOf(HTMLSpanElement);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('can be wrapped in interactive elements', () => {
|
|
233
|
+
const handleClick = vi.fn();
|
|
234
|
+
renderWithProviders(
|
|
235
|
+
<button onClick={handleClick}>
|
|
236
|
+
<Badge>Clickable badge</Badge>
|
|
237
|
+
</button>
|
|
238
|
+
);
|
|
239
|
+
const button = screen.getByRole('button');
|
|
240
|
+
expect(button).toBeInTheDocument();
|
|
241
|
+
expect(button).toHaveTextContent('Clickable badge');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('works in complex layouts', () => {
|
|
245
|
+
renderWithProviders(
|
|
246
|
+
<div>
|
|
247
|
+
<h2>Status</h2>
|
|
248
|
+
<Badge variant="solid-main-normal">Active</Badge>
|
|
249
|
+
<Badge variant="outline-sec-muted">Pending</Badge>
|
|
250
|
+
<Badge variant="soft-acc-strong">Featured</Badge>
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
expect(screen.getByText('Active')).toBeInTheDocument();
|
|
254
|
+
expect(screen.getByText('Pending')).toBeInTheDocument();
|
|
255
|
+
expect(screen.getByText('Featured')).toBeInTheDocument();
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('Edge Cases', () => {
|
|
260
|
+
it('handles empty children gracefully', () => {
|
|
261
|
+
const { container } = renderWithProviders(<Badge></Badge>);
|
|
262
|
+
const badge = container.querySelector('span');
|
|
263
|
+
expect(badge).toBeInTheDocument();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('handles very long text content', () => {
|
|
267
|
+
const longText = 'This is a very long badge text that might cause layout issues but should be handled gracefully';
|
|
268
|
+
renderWithProviders(<Badge>{longText}</Badge>);
|
|
269
|
+
expect(screen.getByText(longText)).toBeInTheDocument();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('handles special characters in text', () => {
|
|
273
|
+
const specialText = 'Badge with special chars: !@#$%^&*()_+-=[]{}|;:,.<>?';
|
|
274
|
+
renderWithProviders(<Badge>{specialText}</Badge>);
|
|
275
|
+
expect(screen.getByText(specialText)).toBeInTheDocument();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('handles numeric content', () => {
|
|
279
|
+
renderWithProviders(<Badge>123</Badge>);
|
|
280
|
+
expect(screen.getByText('123')).toBeInTheDocument();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('handles multiple className overrides', () => {
|
|
284
|
+
renderWithProviders(
|
|
285
|
+
<Badge variant="solid-main-normal" className="px-8 py-2 text-sm">
|
|
286
|
+
Override
|
|
287
|
+
</Badge>
|
|
288
|
+
);
|
|
289
|
+
const badge = screen.getByText('Override');
|
|
290
|
+
expect(badge).toHaveClass('px-8', 'py-2', 'text-sm');
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('Style Variants', () => {
|
|
295
|
+
it('solid variant has no border or blur', () => {
|
|
296
|
+
renderWithProviders(<Badge variant="solid-main-normal">Solid</Badge>);
|
|
297
|
+
const badge = screen.getByText('Solid');
|
|
298
|
+
expect(badge).not.toHaveClass('border', 'backdrop-blur');
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('outline variant has outline', () => {
|
|
302
|
+
renderWithProviders(<Badge variant="outline-main-muted">Outline</Badge>);
|
|
303
|
+
const badge = screen.getByText('Outline');
|
|
304
|
+
expect(badge).toHaveClass('outline', 'outline-1');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('soft variant has shadow', () => {
|
|
308
|
+
renderWithProviders(<Badge variant="soft-main-normal">Soft</Badge>);
|
|
309
|
+
const badge = screen.getByText('Soft');
|
|
310
|
+
expect(badge).toHaveClass('shadow-badge-soft');
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|