@jmruthers/pace-core 0.5.187 → 0.5.189
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-K3RJRSOX.js → DataTable-GUFUNZ3N.js} +5 -5
- package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-B8HaLe69.d.ts} +47 -17
- package/dist/{UnifiedAuthProvider-B76OWOAT.js → UnifiedAuthProvider-643PUAIM.js} +2 -2
- package/dist/{chunk-FMTK4XNN.js → chunk-2UUZZJFT.js} +3 -3
- package/dist/{chunk-3IC5WCMO.js → chunk-3GOZZZYH.js} +3 -3
- package/dist/{chunk-ULX5FYEM.js → chunk-DDM4CCYT.js} +3 -3
- package/dist/{chunk-K2JGDXGU.js → chunk-E7UAOUMY.js} +2 -2
- package/dist/{chunk-T6ZJVI3A.js → chunk-IM4QE42D.js} +4 -4
- package/dist/{chunk-3NFNJOO7.js → chunk-MX64ZF6I.js} +4 -4
- package/dist/{chunk-C4OYJOV4.js → chunk-UCQSRW7Z.js} +829 -829
- package/dist/chunk-UCQSRW7Z.js.map +1 -0
- package/dist/{chunk-WK2Y6TGA.js → chunk-VGZZXKBR.js} +3 -3
- package/dist/chunk-VGZZXKBR.js.map +1 -0
- package/dist/{chunk-LBBUPSSC.js → chunk-YGPFYGA6.js} +3760 -3692
- package/dist/chunk-YGPFYGA6.js.map +1 -0
- package/dist/components.d.ts +1 -2
- package/dist/components.js +6 -10
- package/dist/components.js.map +1 -1
- package/dist/hooks.js +5 -5
- package/dist/index.d.ts +1 -2
- package/dist/index.js +9 -13
- package/dist/index.js.map +1 -1
- package/dist/providers.js +1 -1
- package/dist/rbac/index.js +5 -5
- package/dist/utils.js +1 -1
- 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/Logger.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/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/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +128 -0
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.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/ComplianceResult.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/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.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/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/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.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/LoggerConfig.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/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.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/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.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/RuntimeComplianceResult.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/SetupIssue.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/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.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/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.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/UseResourcePermissionsOptions.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 +6 -45
- package/docs/api-reference/components.md +57 -22
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/implementation-guides/public-pages.md +140 -1230
- package/docs/standards/05-security-standard.md +3 -1
- package/docs/standards/07-rbac-and-rls-standard.md +356 -0
- package/package.json +1 -2
- package/src/__tests__/public-recipe-view.test.ts +199 -0
- package/src/__tests__/rls-policies.test.ts +333 -0
- package/src/components/Avatar/Avatar.test.tsx +183 -209
- package/src/components/Avatar/Avatar.tsx +179 -53
- package/src/components/Avatar/index.ts +1 -1
- package/src/components/UserMenu/UserMenu.test.tsx +7 -9
- package/src/components/UserMenu/UserMenu.tsx +7 -5
- package/src/components/index.ts +2 -1
- package/src/index.ts +2 -1
- package/src/services/OrganisationService.ts +5 -4
- package/dist/chunk-C4OYJOV4.js.map +0 -1
- package/dist/chunk-LBBUPSSC.js.map +0 -1
- package/dist/chunk-WK2Y6TGA.js.map +0 -1
- /package/dist/{DataTable-K3RJRSOX.js.map → DataTable-GUFUNZ3N.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-B76OWOAT.js.map → UnifiedAuthProvider-643PUAIM.js.map} +0 -0
- /package/dist/{chunk-FMTK4XNN.js.map → chunk-2UUZZJFT.js.map} +0 -0
- /package/dist/{chunk-3IC5WCMO.js.map → chunk-3GOZZZYH.js.map} +0 -0
- /package/dist/{chunk-ULX5FYEM.js.map → chunk-DDM4CCYT.js.map} +0 -0
- /package/dist/{chunk-K2JGDXGU.js.map → chunk-E7UAOUMY.js.map} +0 -0
- /package/dist/{chunk-T6ZJVI3A.js.map → chunk-IM4QE42D.js.map} +0 -0
- /package/dist/{chunk-3NFNJOO7.js.map → chunk-MX64ZF6I.js.map} +0 -0
|
@@ -1,38 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file Avatar Component Tests
|
|
3
|
-
* @description Comprehensive tests for Avatar
|
|
3
|
+
* @description Comprehensive tests for Avatar component
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { screen, waitFor } from '@testing-library/react';
|
|
8
8
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
9
|
-
import { Avatar
|
|
9
|
+
import { Avatar } from './Avatar';
|
|
10
|
+
import { FileCategory } from '../../types/file-reference';
|
|
10
11
|
import { renderWithProviders, userEvent } from '../../__tests__/helpers/test-utils';
|
|
11
12
|
|
|
13
|
+
// Size classes for testing
|
|
14
|
+
const sizeClasses = {
|
|
15
|
+
xs: 'h-4 w-4 text-xs',
|
|
16
|
+
sm: 'h-6 w-6 text-sm',
|
|
17
|
+
md: 'h-10 w-10 text-base',
|
|
18
|
+
lg: 'h-12 w-12 text-lg',
|
|
19
|
+
xl: 'h-16 w-16 text-xl',
|
|
20
|
+
'2xl': 'h-20 w-20 text-2xl'
|
|
21
|
+
};
|
|
22
|
+
|
|
12
23
|
// Helper function to get the avatar container element
|
|
13
24
|
const getAvatarContainer = (fallbackText: string) => {
|
|
14
|
-
return screen.getByText(fallbackText).closest('
|
|
25
|
+
return screen.getByText(fallbackText).closest('div');
|
|
15
26
|
};
|
|
16
27
|
|
|
17
28
|
describe('Avatar Component', () => {
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('Rendering', () => {
|
|
34
|
+
it('renders with fallback only', () => {
|
|
20
35
|
renderWithProviders(
|
|
21
|
-
<Avatar
|
|
22
|
-
<AvatarFallback>AB</AvatarFallback>
|
|
23
|
-
</Avatar>
|
|
36
|
+
<Avatar fallback="AB" />
|
|
24
37
|
);
|
|
25
38
|
|
|
26
39
|
const avatar = getAvatarContainer('AB');
|
|
27
40
|
expect(avatar).toBeInTheDocument();
|
|
28
41
|
expect(avatar).toHaveClass('relative', 'flex', 'h-10', 'w-10', 'shrink-0', 'overflow-hidden', 'rounded-full');
|
|
42
|
+
expect(screen.getByText('AB')).toBeInTheDocument();
|
|
29
43
|
});
|
|
30
44
|
|
|
31
45
|
it('renders with custom className', () => {
|
|
32
46
|
renderWithProviders(
|
|
33
|
-
<Avatar className="custom-avatar-class"
|
|
34
|
-
<AvatarFallback>JD</AvatarFallback>
|
|
35
|
-
</Avatar>
|
|
47
|
+
<Avatar fallback="JD" className="custom-avatar-class" />
|
|
36
48
|
);
|
|
37
49
|
|
|
38
50
|
const avatar = getAvatarContainer('JD');
|
|
@@ -41,32 +53,35 @@ describe('Avatar Component', () => {
|
|
|
41
53
|
|
|
42
54
|
it('renders with custom size via className', () => {
|
|
43
55
|
renderWithProviders(
|
|
44
|
-
<Avatar className="h-16 w-16"
|
|
45
|
-
<AvatarFallback>LG</AvatarFallback>
|
|
46
|
-
</Avatar>
|
|
56
|
+
<Avatar fallback="LG" className="h-16 w-16" />
|
|
47
57
|
);
|
|
48
58
|
|
|
49
59
|
const avatar = getAvatarContainer('LG');
|
|
50
60
|
expect(avatar).toHaveClass('h-16', 'w-16');
|
|
51
61
|
});
|
|
52
62
|
|
|
63
|
+
it('renders with size prop', () => {
|
|
64
|
+
renderWithProviders(
|
|
65
|
+
<Avatar fallback="SM" size="sm" />
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const avatar = getAvatarContainer('SM');
|
|
69
|
+
expect(avatar).toHaveClass('h-6', 'w-6', 'text-sm');
|
|
70
|
+
});
|
|
71
|
+
|
|
53
72
|
it('forwards ref correctly', () => {
|
|
54
|
-
const ref = React.createRef<
|
|
73
|
+
const ref = React.createRef<HTMLDivElement>();
|
|
55
74
|
|
|
56
75
|
renderWithProviders(
|
|
57
|
-
<Avatar ref={ref}
|
|
58
|
-
<AvatarFallback>RF</AvatarFallback>
|
|
59
|
-
</Avatar>
|
|
76
|
+
<Avatar ref={ref} fallback="RF" />
|
|
60
77
|
);
|
|
61
78
|
|
|
62
|
-
expect(ref.current).toBeInstanceOf(
|
|
79
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
63
80
|
});
|
|
64
81
|
|
|
65
82
|
it('accepts HTML attributes', () => {
|
|
66
83
|
renderWithProviders(
|
|
67
|
-
<Avatar data-testid="avatar" aria-label="User avatar"
|
|
68
|
-
<AvatarFallback>AT</AvatarFallback>
|
|
69
|
-
</Avatar>
|
|
84
|
+
<Avatar data-testid="avatar" aria-label="User avatar" fallback="AT" />
|
|
70
85
|
);
|
|
71
86
|
|
|
72
87
|
const avatar = screen.getByTestId('avatar');
|
|
@@ -74,138 +89,126 @@ describe('Avatar Component', () => {
|
|
|
74
89
|
});
|
|
75
90
|
});
|
|
76
91
|
|
|
77
|
-
describe('
|
|
78
|
-
it('renders with src and alt attributes
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Object.defineProperty(global, 'Image', {
|
|
90
|
-
value: vi.fn(() => {
|
|
91
|
-
setTimeout(() => {
|
|
92
|
-
if (mockImage.onload) mockImage.onload();
|
|
93
|
-
}, 0);
|
|
94
|
-
return mockImage;
|
|
95
|
-
}),
|
|
96
|
-
writable: true,
|
|
97
|
-
});
|
|
92
|
+
describe('Direct URL Approach', () => {
|
|
93
|
+
it('renders image with src and alt attributes', () => {
|
|
94
|
+
renderWithProviders(
|
|
95
|
+
<Avatar src="/user.jpg" alt="User profile" fallback="UI" />
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const image = screen.getByAltText('User profile');
|
|
99
|
+
expect(image).toBeInTheDocument();
|
|
100
|
+
expect(image).toHaveAttribute('src', '/user.jpg');
|
|
101
|
+
expect(image).toHaveAttribute('alt', 'User profile');
|
|
102
|
+
expect(image.tagName).toBe('IMG');
|
|
103
|
+
});
|
|
98
104
|
|
|
105
|
+
it('shows fallback when image fails to load', async () => {
|
|
99
106
|
renderWithProviders(
|
|
100
|
-
<Avatar
|
|
101
|
-
<AvatarImage src="/user.jpg" alt="User profile" />
|
|
102
|
-
<AvatarFallback>UI</AvatarFallback>
|
|
103
|
-
</Avatar>
|
|
107
|
+
<Avatar src="/broken-image.jpg" alt="User" fallback="FL" />
|
|
104
108
|
);
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
expect(
|
|
110
|
+
const image = screen.getByAltText('User');
|
|
111
|
+
expect(image).toBeInTheDocument();
|
|
112
|
+
|
|
113
|
+
// Simulate image error
|
|
114
|
+
const errorEvent = new Event('error');
|
|
115
|
+
image.dispatchEvent(errorEvent);
|
|
108
116
|
|
|
109
|
-
// Wait for image to load
|
|
110
117
|
await waitFor(() => {
|
|
111
|
-
|
|
112
|
-
if (image) {
|
|
113
|
-
expect(image).toHaveAttribute('src', '/user.jpg');
|
|
114
|
-
expect(image).toHaveAttribute('alt', 'User profile');
|
|
115
|
-
}
|
|
118
|
+
expect(screen.getByText('FL')).toBeInTheDocument();
|
|
116
119
|
});
|
|
117
120
|
});
|
|
118
121
|
|
|
119
|
-
it('
|
|
122
|
+
it('uses fallback as alt text when alt not provided', () => {
|
|
120
123
|
renderWithProviders(
|
|
121
|
-
<Avatar
|
|
122
|
-
<AvatarImage src="/broken-image.jpg" alt="User" />
|
|
123
|
-
<AvatarFallback>FL</AvatarFallback>
|
|
124
|
-
</Avatar>
|
|
124
|
+
<Avatar src="/user.jpg" fallback="JD" />
|
|
125
125
|
);
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
expect(
|
|
129
|
-
expect(screen.queryByAltText('User')).not.toBeInTheDocument();
|
|
127
|
+
const image = screen.getByAltText('JD');
|
|
128
|
+
expect(image).toBeInTheDocument();
|
|
130
129
|
});
|
|
131
130
|
|
|
132
|
-
it('
|
|
131
|
+
it('applies image classes correctly', () => {
|
|
133
132
|
renderWithProviders(
|
|
134
|
-
<Avatar
|
|
135
|
-
<AvatarImage alt="User" />
|
|
136
|
-
<AvatarFallback>MS</AvatarFallback>
|
|
137
|
-
</Avatar>
|
|
133
|
+
<Avatar src="/user.jpg" alt="User" fallback="IC" className="custom-img-class" />
|
|
138
134
|
);
|
|
139
135
|
|
|
140
|
-
|
|
141
|
-
expect(
|
|
136
|
+
const image = screen.getByAltText('User');
|
|
137
|
+
expect(image).toHaveClass('object-cover', 'h-full', 'w-full', 'custom-img-class');
|
|
142
138
|
});
|
|
139
|
+
});
|
|
143
140
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
141
|
+
describe('File Reference Props Approach', () => {
|
|
142
|
+
it('renders FileDisplay when file props provided', () => {
|
|
143
|
+
const { container } = renderWithProviders(
|
|
144
|
+
<Avatar
|
|
145
|
+
table_name="user_profiles"
|
|
146
|
+
record_id="user-123"
|
|
147
|
+
organisation_id="org-123"
|
|
148
|
+
category={FileCategory.PROFILE_PHOTOS}
|
|
149
|
+
fallback="FP"
|
|
150
|
+
/>
|
|
152
151
|
);
|
|
153
152
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
expect(
|
|
153
|
+
// FileDisplay should be rendered (it will show fallback if no file exists)
|
|
154
|
+
const avatar = getAvatarContainer('FP');
|
|
155
|
+
expect(avatar).toBeInTheDocument();
|
|
157
156
|
});
|
|
158
157
|
|
|
159
|
-
it('
|
|
158
|
+
it('requires all file props for file reference approach', () => {
|
|
160
159
|
renderWithProviders(
|
|
161
|
-
<Avatar
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// Check if image element exists with attributes
|
|
173
|
-
const image = screen.queryByTestId('avatar-image');
|
|
174
|
-
if (image) {
|
|
175
|
-
expect(image).toHaveAttribute('loading', 'lazy');
|
|
176
|
-
}
|
|
160
|
+
<Avatar
|
|
161
|
+
table_name="user_profiles"
|
|
162
|
+
record_id="user-123"
|
|
163
|
+
organisation_id="org-123"
|
|
164
|
+
// category missing
|
|
165
|
+
fallback="MP"
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Should show fallback when props incomplete
|
|
170
|
+
expect(screen.getByText('MP')).toBeInTheDocument();
|
|
177
171
|
});
|
|
178
172
|
});
|
|
179
173
|
|
|
180
|
-
describe('
|
|
181
|
-
it('
|
|
174
|
+
describe('File ID Approach', () => {
|
|
175
|
+
it('shows fallback when fileId provided but no supabase', () => {
|
|
182
176
|
renderWithProviders(
|
|
183
|
-
<Avatar
|
|
184
|
-
|
|
185
|
-
|
|
177
|
+
<Avatar
|
|
178
|
+
fileId="file-123"
|
|
179
|
+
organisation_id="org-123"
|
|
180
|
+
fallback="FI"
|
|
181
|
+
/>
|
|
186
182
|
);
|
|
187
183
|
|
|
188
|
-
|
|
189
|
-
expect(
|
|
190
|
-
expect(fallback).toHaveClass('flex', 'h-full', 'w-full', 'items-center', 'justify-center', 'rounded-full', 'text-sec-50', 'bg-sec-500');
|
|
184
|
+
// Should show fallback when supabase not available
|
|
185
|
+
expect(screen.getByText('FI')).toBeInTheDocument();
|
|
191
186
|
});
|
|
187
|
+
});
|
|
192
188
|
|
|
193
|
-
|
|
189
|
+
describe('Fallback Display', () => {
|
|
190
|
+
it('renders fallback with correct styling', () => {
|
|
194
191
|
renderWithProviders(
|
|
195
|
-
<Avatar
|
|
196
|
-
<AvatarFallback className="custom-fallback-class">CF</AvatarFallback>
|
|
197
|
-
</Avatar>
|
|
192
|
+
<Avatar fallback="AB" />
|
|
198
193
|
);
|
|
199
194
|
|
|
200
|
-
const fallback = screen.getByText('
|
|
201
|
-
expect(fallback).
|
|
195
|
+
const fallback = screen.getByText('AB');
|
|
196
|
+
expect(fallback).toBeInTheDocument();
|
|
197
|
+
expect(fallback).toHaveClass(
|
|
198
|
+
'flex',
|
|
199
|
+
'h-full',
|
|
200
|
+
'w-full',
|
|
201
|
+
'items-center',
|
|
202
|
+
'justify-center',
|
|
203
|
+
'rounded-full',
|
|
204
|
+
'text-sec-50',
|
|
205
|
+
'bg-sec-500'
|
|
206
|
+
);
|
|
202
207
|
});
|
|
203
208
|
|
|
204
209
|
it('renders with initials', () => {
|
|
205
210
|
renderWithProviders(
|
|
206
|
-
<Avatar
|
|
207
|
-
<AvatarFallback>John Doe</AvatarFallback>
|
|
208
|
-
</Avatar>
|
|
211
|
+
<Avatar fallback="John Doe" />
|
|
209
212
|
);
|
|
210
213
|
|
|
211
214
|
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
@@ -213,90 +216,49 @@ describe('Avatar Component', () => {
|
|
|
213
216
|
|
|
214
217
|
it('renders with single character', () => {
|
|
215
218
|
renderWithProviders(
|
|
216
|
-
<Avatar
|
|
217
|
-
<AvatarFallback>J</AvatarFallback>
|
|
218
|
-
</Avatar>
|
|
219
|
+
<Avatar fallback="J" />
|
|
219
220
|
);
|
|
220
221
|
|
|
221
222
|
expect(screen.getByText('J')).toBeInTheDocument();
|
|
222
223
|
});
|
|
223
224
|
|
|
224
|
-
it('
|
|
225
|
-
const ref = React.createRef<HTMLSpanElement>();
|
|
226
|
-
|
|
225
|
+
it('has aria-label on fallback', () => {
|
|
227
226
|
renderWithProviders(
|
|
228
|
-
<Avatar
|
|
229
|
-
<AvatarFallback ref={ref}>RF</AvatarFallback>
|
|
230
|
-
</Avatar>
|
|
227
|
+
<Avatar fallback="AB" alt="User avatar" />
|
|
231
228
|
);
|
|
232
229
|
|
|
233
|
-
|
|
230
|
+
const fallback = screen.getByText('AB');
|
|
231
|
+
expect(fallback).toHaveAttribute('aria-label', 'User avatar');
|
|
234
232
|
});
|
|
235
233
|
|
|
236
|
-
it('
|
|
234
|
+
it('uses fallback as aria-label when alt not provided', () => {
|
|
237
235
|
renderWithProviders(
|
|
238
|
-
<Avatar
|
|
239
|
-
<AvatarFallback data-testid="fallback" aria-label="User initials">
|
|
240
|
-
AT
|
|
241
|
-
</AvatarFallback>
|
|
242
|
-
</Avatar>
|
|
236
|
+
<Avatar fallback="JD" />
|
|
243
237
|
);
|
|
244
238
|
|
|
245
|
-
const fallback = screen.
|
|
246
|
-
expect(fallback).toHaveAttribute('aria-label', '
|
|
239
|
+
const fallback = screen.getByText('JD');
|
|
240
|
+
expect(fallback).toHaveAttribute('aria-label', 'JD');
|
|
247
241
|
});
|
|
248
242
|
});
|
|
249
243
|
|
|
250
244
|
describe('Composition', () => {
|
|
251
|
-
it('works with fallback only', () => {
|
|
252
|
-
renderWithProviders(
|
|
253
|
-
<Avatar>
|
|
254
|
-
<AvatarFallback>JD</AvatarFallback>
|
|
255
|
-
</Avatar>
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
const avatar = getAvatarContainer('JD');
|
|
259
|
-
expect(avatar).toBeInTheDocument();
|
|
260
|
-
expect(screen.getByText('JD')).toBeInTheDocument();
|
|
261
|
-
});
|
|
262
|
-
|
|
263
245
|
it('works with multiple avatars', () => {
|
|
264
246
|
renderWithProviders(
|
|
265
247
|
<div>
|
|
266
|
-
<Avatar
|
|
267
|
-
|
|
268
|
-
</Avatar>
|
|
269
|
-
<Avatar>
|
|
270
|
-
<AvatarFallback>CD</AvatarFallback>
|
|
271
|
-
</Avatar>
|
|
248
|
+
<Avatar fallback="AB" />
|
|
249
|
+
<Avatar fallback="CD" />
|
|
272
250
|
</div>
|
|
273
251
|
);
|
|
274
252
|
|
|
275
253
|
expect(screen.getByText('AB')).toBeInTheDocument();
|
|
276
254
|
expect(screen.getByText('CD')).toBeInTheDocument();
|
|
277
255
|
});
|
|
278
|
-
|
|
279
|
-
it('works with complex fallback content', () => {
|
|
280
|
-
renderWithProviders(
|
|
281
|
-
<Avatar>
|
|
282
|
-
<AvatarFallback>
|
|
283
|
-
<span>👤</span>
|
|
284
|
-
<span>User</span>
|
|
285
|
-
</AvatarFallback>
|
|
286
|
-
</Avatar>
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
expect(screen.getByText('👤')).toBeInTheDocument();
|
|
290
|
-
expect(screen.getByText('User')).toBeInTheDocument();
|
|
291
|
-
});
|
|
292
256
|
});
|
|
293
257
|
|
|
294
258
|
describe('Accessibility', () => {
|
|
295
259
|
it('supports screen readers with fallback', () => {
|
|
296
260
|
renderWithProviders(
|
|
297
|
-
<Avatar
|
|
298
|
-
<AvatarFallback>John Doe</AvatarFallback>
|
|
299
|
-
</Avatar>
|
|
261
|
+
<Avatar fallback="John Doe" />
|
|
300
262
|
);
|
|
301
263
|
|
|
302
264
|
const avatar = getAvatarContainer('John Doe');
|
|
@@ -306,9 +268,7 @@ describe('Avatar Component', () => {
|
|
|
306
268
|
|
|
307
269
|
it('maintains focus management', () => {
|
|
308
270
|
renderWithProviders(
|
|
309
|
-
<Avatar tabIndex={0}
|
|
310
|
-
<AvatarFallback>FM</AvatarFallback>
|
|
311
|
-
</Avatar>
|
|
271
|
+
<Avatar tabIndex={0} fallback="FM" />
|
|
312
272
|
);
|
|
313
273
|
|
|
314
274
|
const avatar = getAvatarContainer('FM');
|
|
@@ -319,9 +279,7 @@ describe('Avatar Component', () => {
|
|
|
319
279
|
const user = userEvent.setup();
|
|
320
280
|
|
|
321
281
|
renderWithProviders(
|
|
322
|
-
<Avatar tabIndex={0}
|
|
323
|
-
<AvatarFallback>KN</AvatarFallback>
|
|
324
|
-
</Avatar>
|
|
282
|
+
<Avatar tabIndex={0} fallback="KN" />
|
|
325
283
|
);
|
|
326
284
|
|
|
327
285
|
const avatar = getAvatarContainer('KN');
|
|
@@ -339,22 +297,17 @@ describe('Avatar Component', () => {
|
|
|
339
297
|
describe('Edge Cases', () => {
|
|
340
298
|
it('handles empty fallback content', () => {
|
|
341
299
|
renderWithProviders(
|
|
342
|
-
<Avatar
|
|
343
|
-
<AvatarFallback></AvatarFallback>
|
|
344
|
-
</Avatar>
|
|
300
|
+
<Avatar fallback="" />
|
|
345
301
|
);
|
|
346
302
|
|
|
347
|
-
//
|
|
348
|
-
// by looking for the specific class structure
|
|
303
|
+
// Avatar container should exist even with empty fallback
|
|
349
304
|
const avatar = document.querySelector('.relative.flex.h-10.w-10');
|
|
350
305
|
expect(avatar).toBeInTheDocument();
|
|
351
306
|
});
|
|
352
307
|
|
|
353
308
|
it('handles very long fallback text', () => {
|
|
354
309
|
renderWithProviders(
|
|
355
|
-
<Avatar
|
|
356
|
-
<AvatarFallback>Very Long Username That Might Overflow</AvatarFallback>
|
|
357
|
-
</Avatar>
|
|
310
|
+
<Avatar fallback="Very Long Username That Might Overflow" />
|
|
358
311
|
);
|
|
359
312
|
|
|
360
313
|
expect(screen.getByText('Very Long Username That Might Overflow')).toBeInTheDocument();
|
|
@@ -362,9 +315,7 @@ describe('Avatar Component', () => {
|
|
|
362
315
|
|
|
363
316
|
it('handles special characters in fallback', () => {
|
|
364
317
|
renderWithProviders(
|
|
365
|
-
<Avatar
|
|
366
|
-
<AvatarFallback>@#$%</AvatarFallback>
|
|
367
|
-
</Avatar>
|
|
318
|
+
<Avatar fallback="@#$%" />
|
|
368
319
|
);
|
|
369
320
|
|
|
370
321
|
expect(screen.getByText('@#$%')).toBeInTheDocument();
|
|
@@ -372,23 +323,37 @@ describe('Avatar Component', () => {
|
|
|
372
323
|
|
|
373
324
|
it('handles numeric fallback content', () => {
|
|
374
325
|
renderWithProviders(
|
|
375
|
-
<Avatar
|
|
376
|
-
<AvatarFallback>123</AvatarFallback>
|
|
377
|
-
</Avatar>
|
|
326
|
+
<Avatar fallback="123" />
|
|
378
327
|
);
|
|
379
328
|
|
|
380
329
|
expect(screen.getByText('123')).toBeInTheDocument();
|
|
381
330
|
});
|
|
331
|
+
|
|
332
|
+
it('prioritizes direct URL over file props', () => {
|
|
333
|
+
renderWithProviders(
|
|
334
|
+
<Avatar
|
|
335
|
+
src="/user.jpg"
|
|
336
|
+
alt="User"
|
|
337
|
+
table_name="user_profiles"
|
|
338
|
+
record_id="user-123"
|
|
339
|
+
organisation_id="org-123"
|
|
340
|
+
category={FileCategory.PROFILE_PHOTOS}
|
|
341
|
+
fallback="PR"
|
|
342
|
+
/>
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// Should show image, not FileDisplay
|
|
346
|
+
const image = screen.getByAltText('User');
|
|
347
|
+
expect(image).toBeInTheDocument();
|
|
348
|
+
expect(image).toHaveAttribute('src', '/user.jpg');
|
|
349
|
+
});
|
|
382
350
|
});
|
|
383
351
|
|
|
384
352
|
describe('Integration', () => {
|
|
385
353
|
it('works within user profile components', () => {
|
|
386
354
|
renderWithProviders(
|
|
387
355
|
<div className="user-profile">
|
|
388
|
-
<Avatar
|
|
389
|
-
<AvatarImage src="/user.jpg" alt="Profile" />
|
|
390
|
-
<AvatarFallback>UP</AvatarFallback>
|
|
391
|
-
</Avatar>
|
|
356
|
+
<Avatar src="/user.jpg" alt="Profile" fallback="UP" />
|
|
392
357
|
<div>
|
|
393
358
|
<h3>User Name</h3>
|
|
394
359
|
<p>User description</p>
|
|
@@ -396,7 +361,7 @@ describe('Avatar Component', () => {
|
|
|
396
361
|
</div>
|
|
397
362
|
);
|
|
398
363
|
|
|
399
|
-
expect(screen.
|
|
364
|
+
expect(screen.getByAltText('Profile')).toBeInTheDocument();
|
|
400
365
|
expect(screen.getByText('User Name')).toBeInTheDocument();
|
|
401
366
|
expect(screen.getByText('User description')).toBeInTheDocument();
|
|
402
367
|
});
|
|
@@ -404,9 +369,7 @@ describe('Avatar Component', () => {
|
|
|
404
369
|
it('works within navigation menus', () => {
|
|
405
370
|
renderWithProviders(
|
|
406
371
|
<nav>
|
|
407
|
-
<Avatar
|
|
408
|
-
<AvatarFallback>NM</AvatarFallback>
|
|
409
|
-
</Avatar>
|
|
372
|
+
<Avatar fallback="NM" />
|
|
410
373
|
<ul>
|
|
411
374
|
<li>Dashboard</li>
|
|
412
375
|
<li>Settings</li>
|
|
@@ -424,9 +387,7 @@ describe('Avatar Component', () => {
|
|
|
424
387
|
const handleClick = vi.fn();
|
|
425
388
|
|
|
426
389
|
renderWithProviders(
|
|
427
|
-
<Avatar onClick={handleClick} tabIndex={0}
|
|
428
|
-
<AvatarFallback>CH</AvatarFallback>
|
|
429
|
-
</Avatar>
|
|
390
|
+
<Avatar onClick={handleClick} tabIndex={0} fallback="CH" />
|
|
430
391
|
);
|
|
431
392
|
|
|
432
393
|
const avatar = getAvatarContainer('CH');
|
|
@@ -440,9 +401,7 @@ describe('Avatar Component', () => {
|
|
|
440
401
|
describe('Styling and Layout', () => {
|
|
441
402
|
it('applies correct default styling', () => {
|
|
442
403
|
renderWithProviders(
|
|
443
|
-
<Avatar
|
|
444
|
-
<AvatarFallback>ST</AvatarFallback>
|
|
445
|
-
</Avatar>
|
|
404
|
+
<Avatar fallback="ST" />
|
|
446
405
|
);
|
|
447
406
|
|
|
448
407
|
const avatar = getAvatarContainer('ST');
|
|
@@ -459,9 +418,7 @@ describe('Avatar Component', () => {
|
|
|
459
418
|
|
|
460
419
|
it('applies correct fallback styling', () => {
|
|
461
420
|
renderWithProviders(
|
|
462
|
-
<Avatar
|
|
463
|
-
<AvatarFallback>FS</AvatarFallback>
|
|
464
|
-
</Avatar>
|
|
421
|
+
<Avatar fallback="FS" />
|
|
465
422
|
);
|
|
466
423
|
|
|
467
424
|
const fallback = screen.getByText('FS');
|
|
@@ -479,13 +436,30 @@ describe('Avatar Component', () => {
|
|
|
479
436
|
|
|
480
437
|
it('handles custom size overrides', () => {
|
|
481
438
|
renderWithProviders(
|
|
482
|
-
<Avatar className="h-20 w-20"
|
|
483
|
-
<AvatarFallback>CS</AvatarFallback>
|
|
484
|
-
</Avatar>
|
|
439
|
+
<Avatar className="h-20 w-20" fallback="CS" />
|
|
485
440
|
);
|
|
486
441
|
|
|
487
442
|
const avatar = getAvatarContainer('CS');
|
|
488
443
|
expect(avatar).toHaveClass('h-20', 'w-20');
|
|
489
444
|
});
|
|
445
|
+
|
|
446
|
+
it('applies size variants correctly', () => {
|
|
447
|
+
const sizes: Array<'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'> = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
|
|
448
|
+
|
|
449
|
+
sizes.forEach(size => {
|
|
450
|
+
const { unmount } = renderWithProviders(
|
|
451
|
+
<Avatar size={size} fallback={size.toUpperCase()} />
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
const avatar = getAvatarContainer(size.toUpperCase());
|
|
455
|
+
if (size === 'md') {
|
|
456
|
+
expect(avatar).toHaveClass('h-10', 'w-10');
|
|
457
|
+
} else {
|
|
458
|
+
expect(avatar).toHaveClass(sizeClasses[size].split(' ')[0], sizeClasses[size].split(' ')[1]);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
unmount();
|
|
462
|
+
});
|
|
463
|
+
});
|
|
490
464
|
});
|
|
491
|
-
});
|
|
465
|
+
});
|