@jmruthers/pace-core 0.5.193 → 0.6.1
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/CHANGELOG.md +29 -0
- package/README.md +7 -1
- package/cursor-rules/00-pace-core-compliance.mdc +372 -0
- package/cursor-rules/01-standards-compliance.mdc +275 -0
- package/cursor-rules/02-project-structure.mdc +200 -0
- package/cursor-rules/03-solid-principles.mdc +341 -0
- package/cursor-rules/04-testing-standards.mdc +315 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
- package/cursor-rules/06-code-quality.mdc +392 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +309 -0
- package/cursor-rules/CHANGELOG.md +101 -0
- package/cursor-rules/README.md +191 -0
- package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-CH1U5Tpy.d.ts} +1 -1
- package/dist/{DataTable-5FU7IESH.js → DataTable-DQ7RSOHE.js} +6 -6
- package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-ce4xlHYA.d.ts} +34 -155
- package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-ATAP5UTR.js} +2 -2
- package/dist/{chunk-6C4YBBJM 5.js → chunk-3QRJFVBR.js} +1 -1
- package/dist/chunk-3QRJFVBR.js.map +1 -0
- package/dist/{chunk-IIELH4DL.js → chunk-3XTALGJF.js} +2 -2
- package/dist/{chunk-IIELH4DL.js.map → chunk-3XTALGJF.js.map} +1 -1
- package/dist/{chunk-HWIIPPNI.js → chunk-4N5C5XZU.js} +20 -20
- package/dist/chunk-4N5C5XZU.js.map +1 -0
- package/dist/{chunk-7EQTDTTJ.js → chunk-4ZC4GX36.js} +5 -5
- package/dist/{chunk-7EQTDTTJ.js 2.map → chunk-4ZC4GX36.js.map} +1 -1
- package/dist/{chunk-7FLMSG37.js → chunk-BYFSK72L.js} +22 -22
- package/dist/chunk-BYFSK72L.js.map +1 -0
- package/dist/{chunk-LFNCN2SP.js → chunk-EXUD6RNJ.js} +46 -7
- package/dist/chunk-EXUD6RNJ.js.map +1 -0
- package/dist/{chunk-NOAYCWCX 5.js → chunk-GLK6VM3F.js} +167 -169
- package/dist/chunk-GLK6VM3F.js.map +1 -0
- package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
- package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
- package/dist/{chunk-BC4IJKSL.js → chunk-JBKQ3SAO.js} +2 -2
- package/dist/{chunk-QWWZ5CAQ.js → chunk-LXQLPRQ2.js} +2 -2
- package/dist/{chunk-E3SPN4VZ 5.js → chunk-T33XF5ZC.js} +119 -114
- package/dist/chunk-T33XF5ZC.js.map +1 -0
- package/dist/{chunk-XNXXZ43G.js → chunk-XM25TVIE.js} +27 -4
- package/dist/chunk-XM25TVIE.js.map +1 -0
- package/dist/components.d.ts +3 -3
- package/dist/components.js +8 -8
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +17 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +15 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.js +1 -1
- package/dist/rbac/index.d.ts +1 -1
- package/dist/rbac/index.js +5 -5
- package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-BJAlWfuJ.d.ts} +3 -3
- package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +3 -3
- package/docs/getting-started/cursor-rules.md +262 -0
- package/docs/getting-started/installation-guide.md +6 -1
- package/docs/getting-started/quick-start.md +6 -1
- package/docs/migration/MIGRATION_GUIDE.md +4 -4
- package/docs/migration/REACT_19_MIGRATION.md +227 -0
- package/docs/standards/README.md +39 -0
- package/docs/troubleshooting/migration.md +4 -4
- package/examples/PublicPages/PublicEventPage.tsx +1 -1
- package/package.json +11 -6
- package/scripts/audit-consuming-app.cjs +961 -0
- package/scripts/check-pace-core-compliance.cjs +34 -15
- package/scripts/install-cursor-rules.cjs +236 -0
- package/src/__tests__/helpers/test-providers.tsx +1 -1
- package/src/__tests__/helpers/test-utils.tsx +1 -1
- package/src/components/Badge/Badge.tsx +2 -4
- package/src/components/Button/Button.tsx +5 -4
- package/src/components/Calendar/Calendar.tsx +1 -1
- package/src/components/DataTable/DataTable.test.tsx +57 -93
- package/src/components/DataTable/DataTable.tsx +2 -2
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +13 -5
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
- package/src/components/DataTable/components/AccessDeniedPage.tsx +1 -1
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- package/src/components/DataTable/components/DataTableCore.tsx +4 -7
- package/src/components/DataTable/components/DataTableModals.tsx +1 -1
- package/src/components/DataTable/components/EditableRow.tsx +1 -1
- package/src/components/DataTable/components/UnifiedTableBody.tsx +6 -8
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
- package/src/components/DataTable/hooks/useColumnReordering.ts +2 -2
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
- package/src/components/Dialog/Dialog.tsx +6 -5
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +1 -1
- package/src/components/EventSelector/EventSelector.tsx +1 -1
- package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
- package/src/components/Footer/Footer.tsx +1 -1
- package/src/components/Form/Form.test.tsx +36 -15
- package/src/components/Form/Form.tsx +30 -26
- package/src/components/Header/Header.tsx +1 -1
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
- package/src/components/Input/Input.tsx +28 -30
- package/src/components/Label/Label.tsx +1 -1
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
- package/src/components/LoginForm/LoginForm.test.tsx +42 -42
- package/src/components/LoginForm/LoginForm.tsx +8 -8
- package/src/components/NavigationMenu/NavigationMenu.tsx +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +50 -50
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +1 -1
- package/src/components/PaceAppLayout/README.md +1 -1
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
- package/src/components/PasswordChange/PasswordChangeForm.tsx +1 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/PublicLayout/PublicPageLayout.tsx +1 -1
- package/src/components/Select/Select.tsx +33 -22
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +1 -1
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Textarea/Textarea.tsx +27 -29
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.tsx +1 -1
- package/src/components/UserMenu/UserMenu.tsx +1 -1
- package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
- package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
- package/src/hooks/public/usePublicEvent.ts +1 -1
- package/src/hooks/public/usePublicEventLogo.ts +1 -1
- package/src/hooks/public/usePublicRouteParams.ts +1 -1
- package/src/hooks/useDataTableState.ts +8 -18
- package/src/hooks/useFocusManagement.ts +2 -2
- package/src/hooks/useFocusTrap.ts +4 -4
- package/src/hooks/useFormDialog.ts +8 -7
- package/src/hooks/useInactivityTracker.ts +1 -1
- package/src/hooks/usePermissionCache.ts +1 -1
- package/src/hooks/useSecureDataAccess.ts +19 -4
- package/src/hooks/useToast.ts +2 -2
- package/src/providers/__tests__/OrganisationProvider.test.tsx +57 -13
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
- package/src/providers/services/UnifiedAuthProvider.tsx +22 -22
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +13 -3
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +24 -24
- package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
- package/src/rbac/components/NavigationGuard.tsx +1 -1
- package/src/rbac/components/NavigationProvider.tsx +1 -1
- package/src/rbac/components/PagePermissionGuard.tsx +1 -1
- package/src/rbac/components/PagePermissionProvider.tsx +1 -1
- package/src/rbac/components/PermissionEnforcer.tsx +1 -1
- package/src/rbac/components/RoleBasedRouter.tsx +1 -1
- package/src/rbac/components/SecureDataProvider.tsx +1 -1
- package/src/rbac/secureClient.ts +12 -0
- package/src/utils/security/secureDataAccess.test.ts +31 -20
- package/src/utils/security/secureDataAccess.ts +4 -3
- package/dist/chunk-6C4YBBJM.js +0 -628
- package/dist/chunk-6C4YBBJM.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js.map +0 -1
- package/dist/chunk-7FLMSG37.js 2.map +0 -1
- package/dist/chunk-7FLMSG37.js.map +0 -1
- package/dist/chunk-E3SPN4VZ.js +0 -12917
- package/dist/chunk-E3SPN4VZ.js.map +0 -1
- package/dist/chunk-E66EQZE6 5.js +0 -37
- package/dist/chunk-E66EQZE6.js 2.map +0 -1
- package/dist/chunk-HWIIPPNI.js.map +0 -1
- package/dist/chunk-I7PSE6JW 5.js +0 -191
- package/dist/chunk-I7PSE6JW.js 2.map +0 -1
- package/dist/chunk-KNC55RTG.js 5.map +0 -1
- package/dist/chunk-KQCRWDSA.js 5.map +0 -1
- package/dist/chunk-LFNCN2SP.js 2.map +0 -1
- package/dist/chunk-LFNCN2SP.js.map +0 -1
- package/dist/chunk-LMC26NLJ 2.js +0 -84
- package/dist/chunk-NOAYCWCX.js +0 -4993
- package/dist/chunk-NOAYCWCX.js.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js.map +0 -1
- package/dist/chunk-QXHPKYJV 3.js +0 -113
- package/dist/chunk-R77UEZ4E 3.js +0 -68
- package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
- package/dist/chunk-XNXXZ43G.js.map +0 -1
- package/dist/chunk-ZSAAAMVR 6.js +0 -25
- package/dist/components.js 5.map +0 -1
- package/dist/styles/index 2.js +0 -12
- package/dist/styles/index.js 5.map +0 -1
- package/dist/theming/runtime 5.js +0 -19
- package/dist/theming/runtime.js 5.map +0 -1
- /package/dist/{DataTable-5FU7IESH.js.map → DataTable-DQ7RSOHE.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-ATAP5UTR.js.map} +0 -0
- /package/dist/{chunk-BC4IJKSL.js.map → chunk-JBKQ3SAO.js.map} +0 -0
- /package/dist/{chunk-QWWZ5CAQ.js 3.map → chunk-LXQLPRQ2.js.map} +0 -0
- /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
- /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/index.ts +0 -0
|
@@ -55,13 +55,13 @@ vi.mock('../Label', () => ({
|
|
|
55
55
|
|
|
56
56
|
describe('PasswordChangeForm', () => {
|
|
57
57
|
const mockOnSubmit = vi.fn();
|
|
58
|
-
const
|
|
58
|
+
const baseProps: PasswordChangeFormProps = {
|
|
59
59
|
onSubmit: mockOnSubmit
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
describe('Rendering', () => {
|
|
63
63
|
it('renders password change form with all elements', () => {
|
|
64
|
-
render(<PasswordChangeForm {...
|
|
64
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
65
65
|
|
|
66
66
|
expect(screen.getByLabelText('New Password')).toBeInTheDocument();
|
|
67
67
|
expect(screen.getByLabelText('Confirm Password')).toBeInTheDocument();
|
|
@@ -70,14 +70,14 @@ describe('PasswordChangeForm', () => {
|
|
|
70
70
|
|
|
71
71
|
it('renders with custom className', () => {
|
|
72
72
|
const customClass = 'custom-password-change-form';
|
|
73
|
-
const { container } = render(<PasswordChangeForm {...
|
|
73
|
+
const { container } = render(<PasswordChangeForm {...baseProps} className={customClass} />);
|
|
74
74
|
|
|
75
75
|
const form = container.querySelector('form');
|
|
76
76
|
expect(form).toHaveClass(customClass);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it('has proper form structure and accessibility', () => {
|
|
80
|
-
render(<PasswordChangeForm {...
|
|
80
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
81
81
|
|
|
82
82
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
83
83
|
expect(newPasswordInput).toHaveAttribute('type', 'password');
|
|
@@ -97,7 +97,7 @@ describe('PasswordChangeForm', () => {
|
|
|
97
97
|
describe('Form Interaction', () => {
|
|
98
98
|
it('updates new password input value when user types', async () => {
|
|
99
99
|
const user = userEvent.setup();
|
|
100
|
-
render(<PasswordChangeForm {...
|
|
100
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
101
101
|
|
|
102
102
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
103
103
|
await user.type(newPasswordInput, 'newpassword123');
|
|
@@ -107,7 +107,7 @@ describe('PasswordChangeForm', () => {
|
|
|
107
107
|
|
|
108
108
|
it('updates confirm password input value when user types', async () => {
|
|
109
109
|
const user = userEvent.setup();
|
|
110
|
-
render(<PasswordChangeForm {...
|
|
110
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
111
111
|
|
|
112
112
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
113
113
|
await user.type(confirmPasswordInput, 'newpassword123');
|
|
@@ -117,7 +117,7 @@ describe('PasswordChangeForm', () => {
|
|
|
117
117
|
|
|
118
118
|
it('enables submit button when both passwords are provided', async () => {
|
|
119
119
|
const user = userEvent.setup();
|
|
120
|
-
render(<PasswordChangeForm {...
|
|
120
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
121
121
|
|
|
122
122
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
123
123
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -133,7 +133,7 @@ describe('PasswordChangeForm', () => {
|
|
|
133
133
|
|
|
134
134
|
it('disables submit button when new password is empty', async () => {
|
|
135
135
|
const user = userEvent.setup();
|
|
136
|
-
render(<PasswordChangeForm {...
|
|
136
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
137
137
|
|
|
138
138
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
139
139
|
const submitButton = screen.getByRole('button', { name: 'Change Password' });
|
|
@@ -145,7 +145,7 @@ describe('PasswordChangeForm', () => {
|
|
|
145
145
|
|
|
146
146
|
it('disables submit button when confirm password is empty', async () => {
|
|
147
147
|
const user = userEvent.setup();
|
|
148
|
-
render(<PasswordChangeForm {...
|
|
148
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
149
149
|
|
|
150
150
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
151
151
|
const submitButton = screen.getByRole('button', { name: 'Change Password' });
|
|
@@ -159,7 +159,7 @@ describe('PasswordChangeForm', () => {
|
|
|
159
159
|
describe('Form Validation', () => {
|
|
160
160
|
it('shows error when password is too short', async () => {
|
|
161
161
|
const user = userEvent.setup();
|
|
162
|
-
render(<PasswordChangeForm {...
|
|
162
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
163
163
|
|
|
164
164
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
165
165
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -175,7 +175,7 @@ describe('PasswordChangeForm', () => {
|
|
|
175
175
|
|
|
176
176
|
it('shows error when passwords do not match', async () => {
|
|
177
177
|
const user = userEvent.setup();
|
|
178
|
-
render(<PasswordChangeForm {...
|
|
178
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
179
179
|
|
|
180
180
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
181
181
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -191,7 +191,7 @@ describe('PasswordChangeForm', () => {
|
|
|
191
191
|
|
|
192
192
|
it('validates password length before checking match', async () => {
|
|
193
193
|
const user = userEvent.setup();
|
|
194
|
-
render(<PasswordChangeForm {...
|
|
194
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
195
195
|
|
|
196
196
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
197
197
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -207,7 +207,7 @@ describe('PasswordChangeForm', () => {
|
|
|
207
207
|
|
|
208
208
|
it('clears error when form is resubmitted with valid data', async () => {
|
|
209
209
|
const user = userEvent.setup();
|
|
210
|
-
render(<PasswordChangeForm {...
|
|
210
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
211
211
|
|
|
212
212
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
213
213
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -236,7 +236,7 @@ describe('PasswordChangeForm', () => {
|
|
|
236
236
|
const user = userEvent.setup();
|
|
237
237
|
mockOnSubmit.mockResolvedValue({});
|
|
238
238
|
|
|
239
|
-
render(<PasswordChangeForm {...
|
|
239
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
240
240
|
|
|
241
241
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
242
242
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -254,7 +254,7 @@ describe('PasswordChangeForm', () => {
|
|
|
254
254
|
|
|
255
255
|
it('prevents form submission when validation fails', async () => {
|
|
256
256
|
const user = userEvent.setup();
|
|
257
|
-
render(<PasswordChangeForm {...
|
|
257
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
258
258
|
|
|
259
259
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
260
260
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -273,7 +273,7 @@ describe('PasswordChangeForm', () => {
|
|
|
273
273
|
const user = userEvent.setup();
|
|
274
274
|
mockOnSubmit.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));
|
|
275
275
|
|
|
276
|
-
render(<PasswordChangeForm {...
|
|
276
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
277
277
|
|
|
278
278
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
279
279
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -291,7 +291,7 @@ describe('PasswordChangeForm', () => {
|
|
|
291
291
|
const user = userEvent.setup();
|
|
292
292
|
mockOnSubmit.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));
|
|
293
293
|
|
|
294
|
-
render(<PasswordChangeForm {...
|
|
294
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
295
295
|
|
|
296
296
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
297
297
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -309,7 +309,7 @@ describe('PasswordChangeForm', () => {
|
|
|
309
309
|
const user = userEvent.setup();
|
|
310
310
|
mockOnSubmit.mockResolvedValue({});
|
|
311
311
|
|
|
312
|
-
render(<PasswordChangeForm {...
|
|
312
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
313
313
|
|
|
314
314
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
315
315
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -329,7 +329,7 @@ describe('PasswordChangeForm', () => {
|
|
|
329
329
|
const user = userEvent.setup();
|
|
330
330
|
mockOnSubmit.mockResolvedValue({ error: { message: 'Test error' } });
|
|
331
331
|
|
|
332
|
-
render(<PasswordChangeForm {...
|
|
332
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
333
333
|
|
|
334
334
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
335
335
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -352,7 +352,7 @@ describe('PasswordChangeForm', () => {
|
|
|
352
352
|
const errorMessage = 'Password change failed';
|
|
353
353
|
mockOnSubmit.mockResolvedValue({ error: { message: errorMessage } });
|
|
354
354
|
|
|
355
|
-
render(<PasswordChangeForm {...
|
|
355
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
356
356
|
|
|
357
357
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
358
358
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -373,7 +373,7 @@ describe('PasswordChangeForm', () => {
|
|
|
373
373
|
const errorMessage = 'Network error';
|
|
374
374
|
mockOnSubmit.mockRejectedValue(new Error(errorMessage));
|
|
375
375
|
|
|
376
|
-
render(<PasswordChangeForm {...
|
|
376
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
377
377
|
|
|
378
378
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
379
379
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -393,7 +393,7 @@ describe('PasswordChangeForm', () => {
|
|
|
393
393
|
const user = userEvent.setup();
|
|
394
394
|
mockOnSubmit.mockRejectedValue('String error');
|
|
395
395
|
|
|
396
|
-
render(<PasswordChangeForm {...
|
|
396
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
397
397
|
|
|
398
398
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
399
399
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -412,7 +412,7 @@ describe('PasswordChangeForm', () => {
|
|
|
412
412
|
const user = userEvent.setup();
|
|
413
413
|
mockOnSubmit.mockResolvedValue({ error: { code: 'INVALID_PASSWORD' } });
|
|
414
414
|
|
|
415
|
-
render(<PasswordChangeForm {...
|
|
415
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
416
416
|
|
|
417
417
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
418
418
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -433,7 +433,7 @@ describe('PasswordChangeForm', () => {
|
|
|
433
433
|
.mockResolvedValueOnce({ error: { message: 'First error' } })
|
|
434
434
|
.mockResolvedValueOnce({});
|
|
435
435
|
|
|
436
|
-
render(<PasswordChangeForm {...
|
|
436
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
437
437
|
|
|
438
438
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
439
439
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -457,7 +457,7 @@ describe('PasswordChangeForm', () => {
|
|
|
457
457
|
|
|
458
458
|
describe('Accessibility', () => {
|
|
459
459
|
it('has proper form labels and associations', () => {
|
|
460
|
-
render(<PasswordChangeForm {...
|
|
460
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
461
461
|
|
|
462
462
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
463
463
|
expect(newPasswordInput).toHaveAttribute('id', 'new-password');
|
|
@@ -472,7 +472,7 @@ describe('PasswordChangeForm', () => {
|
|
|
472
472
|
const user = userEvent.setup();
|
|
473
473
|
mockOnSubmit.mockResolvedValue({ error: { message: 'Test error' } });
|
|
474
474
|
|
|
475
|
-
render(<PasswordChangeForm {...
|
|
475
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
476
476
|
|
|
477
477
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
478
478
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -492,7 +492,7 @@ describe('PasswordChangeForm', () => {
|
|
|
492
492
|
const user = userEvent.setup();
|
|
493
493
|
mockOnSubmit.mockResolvedValue({});
|
|
494
494
|
|
|
495
|
-
render(<PasswordChangeForm {...
|
|
495
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
496
496
|
|
|
497
497
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
498
498
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -510,7 +510,7 @@ describe('PasswordChangeForm', () => {
|
|
|
510
510
|
|
|
511
511
|
describe('Edge Cases', () => {
|
|
512
512
|
it('handles empty password values', () => {
|
|
513
|
-
render(<PasswordChangeForm {...
|
|
513
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
514
514
|
|
|
515
515
|
const submitButton = screen.getByRole('button', { name: 'Change Password' });
|
|
516
516
|
|
|
@@ -519,7 +519,7 @@ describe('PasswordChangeForm', () => {
|
|
|
519
519
|
|
|
520
520
|
it('handles whitespace-only passwords', async () => {
|
|
521
521
|
const user = userEvent.setup();
|
|
522
|
-
render(<PasswordChangeForm {...
|
|
522
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
523
523
|
|
|
524
524
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
525
525
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -538,7 +538,7 @@ describe('PasswordChangeForm', () => {
|
|
|
538
538
|
const longPassword = 'a'.repeat(1000);
|
|
539
539
|
mockOnSubmit.mockResolvedValue({});
|
|
540
540
|
|
|
541
|
-
render(<PasswordChangeForm {...
|
|
541
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
542
542
|
|
|
543
543
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
544
544
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -559,7 +559,7 @@ describe('PasswordChangeForm', () => {
|
|
|
559
559
|
const specialPassword = 'P@ssw0rd!@#$%^&*()_+-=';
|
|
560
560
|
mockOnSubmit.mockResolvedValue({});
|
|
561
561
|
|
|
562
|
-
render(<PasswordChangeForm {...
|
|
562
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
563
563
|
|
|
564
564
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
565
565
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -580,7 +580,7 @@ describe('PasswordChangeForm', () => {
|
|
|
580
580
|
const unicodePassword = 'pássw0rd_测试_пароль';
|
|
581
581
|
mockOnSubmit.mockResolvedValue({});
|
|
582
582
|
|
|
583
|
-
render(<PasswordChangeForm {...
|
|
583
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
584
584
|
|
|
585
585
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
586
586
|
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
@@ -600,7 +600,7 @@ describe('PasswordChangeForm', () => {
|
|
|
600
600
|
describe('Performance', () => {
|
|
601
601
|
it('handles rapid input changes efficiently', async () => {
|
|
602
602
|
const user = userEvent.setup();
|
|
603
|
-
render(<PasswordChangeForm {...
|
|
603
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
604
604
|
|
|
605
605
|
const newPasswordInput = screen.getByLabelText('New Password');
|
|
606
606
|
|
|
@@ -53,7 +53,7 @@ export interface UseSelectStateProps {
|
|
|
53
53
|
export interface UseSelectEventsProps {
|
|
54
54
|
state: SelectState;
|
|
55
55
|
actions: SelectActions;
|
|
56
|
-
selectRef: React.RefObject<HTMLElement>;
|
|
56
|
+
selectRef: React.RefObject<HTMLElement | null>;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export interface UseSelectSearchProps {
|
|
@@ -299,12 +299,13 @@ export const useSelectSearch = ({
|
|
|
299
299
|
if (!React.isValidElement(child)) return child;
|
|
300
300
|
|
|
301
301
|
// Check if this is a SelectItem by looking for the data-testid or value prop
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
302
|
+
const childProps = child.props as { 'data-testid'?: string; value?: unknown; children?: React.ReactNode; [key: string]: unknown };
|
|
303
|
+
const isSelectItem = childProps &&
|
|
304
|
+
(childProps['data-testid'] === 'select-item' ||
|
|
305
|
+
childProps.value !== undefined);
|
|
305
306
|
|
|
306
307
|
if (isSelectItem) {
|
|
307
|
-
const childText = React.Children.toArray(
|
|
308
|
+
const childText = React.Children.toArray(childProps.children).join(' ').toLowerCase();
|
|
308
309
|
const searchLower = searchTerm.toLowerCase();
|
|
309
310
|
|
|
310
311
|
if (childText.includes(searchLower)) {
|
|
@@ -313,8 +314,8 @@ export const useSelectSearch = ({
|
|
|
313
314
|
return null;
|
|
314
315
|
}
|
|
315
316
|
|
|
316
|
-
if (
|
|
317
|
-
const filteredChildChildren = filterChildren(
|
|
317
|
+
if (childProps.children) {
|
|
318
|
+
const filteredChildChildren = filterChildren(childProps.children);
|
|
318
319
|
if (React.Children.count(filteredChildChildren) > 0) {
|
|
319
320
|
return React.cloneElement(child, {}, filteredChildChildren);
|
|
320
321
|
}
|
|
@@ -359,8 +360,11 @@ const useSelectContext = () => {
|
|
|
359
360
|
const getTextContent = (children: React.ReactNode): string => {
|
|
360
361
|
if (typeof children === 'string') return children;
|
|
361
362
|
if (typeof children === 'number') return children.toString();
|
|
362
|
-
if (React.isValidElement(children)
|
|
363
|
-
|
|
363
|
+
if (React.isValidElement(children)) {
|
|
364
|
+
const props = children.props as { children?: React.ReactNode; [key: string]: unknown };
|
|
365
|
+
if (props.children) {
|
|
366
|
+
return getTextContent(props.children);
|
|
367
|
+
}
|
|
364
368
|
}
|
|
365
369
|
if (Array.isArray(children)) {
|
|
366
370
|
return children.map(getTextContent).join('');
|
|
@@ -443,7 +447,7 @@ export const Select = React.forwardRef<HTMLFormElement, SelectProps & UseSelectS
|
|
|
443
447
|
const internalRef = React.useRef<HTMLFormElement>(null);
|
|
444
448
|
const selectRef = React.useMemo(() => {
|
|
445
449
|
if (ref && typeof ref === 'object' && 'current' in ref) {
|
|
446
|
-
return ref as React.RefObject<HTMLFormElement>;
|
|
450
|
+
return ref as React.RefObject<HTMLFormElement | null>;
|
|
447
451
|
}
|
|
448
452
|
return internalRef;
|
|
449
453
|
}, [ref]);
|
|
@@ -620,7 +624,7 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
|
|
|
620
624
|
const opensUpward = direction === 'up';
|
|
621
625
|
|
|
622
626
|
// Use ref to store the latest handleClick to avoid re-creating the effect
|
|
623
|
-
const handleClickRef = React.useRef<(e: React.MouseEvent) => void>();
|
|
627
|
+
const handleClickRef = React.useRef<(e: React.MouseEvent) => void>(undefined);
|
|
624
628
|
|
|
625
629
|
const handleClick = React.useCallback((e: React.MouseEvent) => {
|
|
626
630
|
if (disabled) {
|
|
@@ -687,22 +691,26 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
|
|
|
687
691
|
};
|
|
688
692
|
|
|
689
693
|
if (asChild) {
|
|
690
|
-
const
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
(child
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
694
|
+
const childElement = children as React.ReactElement<{ children?: React.ReactNode; [key: string]: unknown }>;
|
|
695
|
+
const childChildren = React.Children.toArray(childElement.props.children);
|
|
696
|
+
const hasChevron = childChildren.some(child => {
|
|
697
|
+
if (React.isValidElement(child)) {
|
|
698
|
+
const childProps = child.props as { 'data-testid'?: string; [key: string]: unknown };
|
|
699
|
+
return child.type === ChevronDown ||
|
|
700
|
+
(child.type === 'svg' && childProps['data-testid'] === 'chevron-down') ||
|
|
701
|
+
(typeof child === 'object' && 'type' in child && typeof child.type === 'function' && child.type.name === 'ChevronDown');
|
|
702
|
+
}
|
|
703
|
+
return false;
|
|
704
|
+
});
|
|
697
705
|
|
|
698
706
|
// Merge child's className with triggerProps className
|
|
699
|
-
const childClassName = (children as React.ReactElement).props.className;
|
|
707
|
+
const childClassName = (children as React.ReactElement<any>).props.className;
|
|
700
708
|
const mergedClassName = cn(
|
|
701
709
|
triggerProps.className,
|
|
702
710
|
childClassName
|
|
703
711
|
);
|
|
704
712
|
|
|
705
|
-
return React.cloneElement(children as React.ReactElement
|
|
713
|
+
return React.cloneElement(children as React.ReactElement<any>, {
|
|
706
714
|
...triggerProps,
|
|
707
715
|
className: mergedClassName,
|
|
708
716
|
children: hasChevron ? childChildren : [
|
|
@@ -915,8 +923,11 @@ export const SelectItem = React.forwardRef<HTMLLIElement, SelectItemProps>(
|
|
|
915
923
|
const getTextContent = (children: React.ReactNode): string => {
|
|
916
924
|
if (typeof children === 'string') return children;
|
|
917
925
|
if (typeof children === 'number') return children.toString();
|
|
918
|
-
if (React.isValidElement(children)
|
|
919
|
-
|
|
926
|
+
if (React.isValidElement(children)) {
|
|
927
|
+
const props = children.props as { children?: React.ReactNode; [key: string]: unknown };
|
|
928
|
+
if (props.children) {
|
|
929
|
+
return getTextContent(props.children);
|
|
930
|
+
}
|
|
920
931
|
}
|
|
921
932
|
if (Array.isArray(children)) {
|
|
922
933
|
return children.map(getTextContent).join('');
|
|
@@ -93,35 +93,33 @@ export interface TextareaProps
|
|
|
93
93
|
* />
|
|
94
94
|
* ```
|
|
95
95
|
*/
|
|
96
|
-
|
|
97
|
-
(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
);
|
|
96
|
+
function Textarea({ className, variant = 'default', size = 'md', error, ref, ...props }: TextareaProps & { ref?: React.Ref<HTMLTextAreaElement> }) {
|
|
97
|
+
return (
|
|
98
|
+
<textarea
|
|
99
|
+
className={cn(
|
|
100
|
+
// Base styles (matching Input component)
|
|
101
|
+
'flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
102
|
+
|
|
103
|
+
// Variant styles
|
|
104
|
+
{
|
|
105
|
+
'border-input': variant === 'default' && !error,
|
|
106
|
+
'border-destructive focus-visible:ring-destructive': variant === 'destructive' || error,
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// Size styles
|
|
110
|
+
{
|
|
111
|
+
'min-h-[60px] px-2 py-1 text-xs': size === 'sm',
|
|
112
|
+
'min-h-[80px] px-3 py-2 text-sm': size === 'md',
|
|
113
|
+
'min-h-[100px] px-4 py-3 text-base': size === 'lg',
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
className
|
|
117
|
+
)}
|
|
118
|
+
ref={ref}
|
|
119
|
+
{...props}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
125
123
|
|
|
126
124
|
Textarea.displayName = 'Textarea';
|
|
127
125
|
|