@jmruthers/pace-core 0.6.6 → 0.6.7
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/{scripts/audit/audit-dependencies.cjs → audit-tool/00-dependencies.cjs} +12 -13
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/{00-pace-core-compliance.mdc → 01-pace-core-compliance.mdc} +187 -34
- package/cursor-rules/02-project-structure.mdc +37 -5
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +125 -11
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +55 -10
- package/cursor-rules/{09-rbac-compliance.mdc → 06-security-rbac.mdc} +62 -6
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-2N_tqbfq.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-BBH6Vqg7.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +26 -16
- package/dist/{chunk-FENMYN2U.js → chunk-5X4QLXRG.js} +1 -3
- package/dist/{chunk-4T7OBVTU.js → chunk-6F3IILHI.js} +1 -1
- package/dist/{chunk-SD6WQY43.js → chunk-7ILTDCL2.js} +9 -1
- package/dist/{chunk-3QC3KRHK.js → chunk-A3W6LW53.js} +16 -1
- package/dist/{chunk-7TYHROIV.js → chunk-BM4CQ5P3.js} +50 -8
- package/dist/{chunk-2HGJFNAH.js → chunk-FEJLJNWA.js} +1 -15
- package/dist/{chunk-OHIK3MIO.js → chunk-GHYHJTYV.js} +2 -2
- package/dist/{chunk-UIYSCEV7.js → chunk-IUBRCBSY.js} +1 -1
- package/dist/{chunk-LAZMKTTF.js → chunk-JGWDVX64.js} +281 -347
- package/dist/{chunk-MAGBIDNS.js → chunk-L4XMVJKY.js} +2 -2
- package/dist/{chunk-A55DK444.js → chunk-OJ4SKRSV.js} +1 -7
- package/dist/{chunk-ZS5VO5JB.js → chunk-Q7Q7V5NV.js} +406 -451
- package/dist/{chunk-3O3WHILE.js → chunk-VBCS3DUA.js} +236 -60
- package/dist/{chunk-BVP2BCJF.js → chunk-ZKAWKYT4.js} +8 -8
- package/dist/components.d.ts +5 -4
- package/dist/components.js +27 -32
- package/dist/eslint-rules/index.cjs +22 -9
- package/{src/eslint-rules/rules/compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +184 -23
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/{rbac.cjs → 06-security-rbac.cjs} +26 -10
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +18 -17
- package/dist/rbac/index.js +6 -6
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/{types-B-K_5VnO.d.ts → types-DXstZpNI.d.ts} +0 -17
- package/dist/{usePublicRouteParams-COZ28Mvq.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +19 -19
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +8 -8
- package/docs/README.md +1 -1
- package/docs/api/modules.md +47 -31
- package/docs/api-reference/components.md +18 -20
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +1 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/README.md +5 -5
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -1
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/{00-pace-core-compliance.md → 1-pace-core-compliance-standards.md} +204 -185
- package/docs/standards/{02-project-structure.md → 2-project-structure-standards.md} +11 -47
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/{08-markup-quality.md → 5-styling-standards.md} +12 -9
- package/docs/standards/{09-rbac-compliance.md → 6-security-rbac-standards.md} +126 -18
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +203 -104
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/eslint-config-pace-core.cjs +21 -10
- package/package.json +6 -5
- package/scripts/install-cursor-rules.cjs +11 -243
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +10 -10
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +137 -153
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +5 -16
- package/src/components/DataTable/components/EditableRow.tsx +5 -7
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/types.ts +0 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +7 -8
- package/src/components/Dialog/Dialog.test.tsx +1 -0
- package/src/components/Dialog/Dialog.tsx +25 -8
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +86 -77
- package/src/components/Select/types.ts +3 -0
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +49 -49
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/public/usePublicEvent.ts +5 -5
- package/src/hooks/public/usePublicEventLogo.ts +5 -5
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +2 -2
- package/src/hooks/useEventTheme.test.ts +7 -7
- package/src/hooks/useEventTheme.ts +1 -4
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +10 -10
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/storage/README.md +1 -1
- package/cursor-rules/01-standards-compliance.mdc +0 -285
- package/cursor-rules/04-testing-standards.mdc +0 -270
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -248
- package/cursor-rules/06-code-quality.mdc +0 -311
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -216
- package/cursor-rules/10-error-handling-patterns.mdc +0 -179
- package/cursor-rules/11-performance-optimization.mdc +0 -169
- package/cursor-rules/12-ci-cd-integration.mdc +0 -150
- package/dist/DataTable-LRJL4IRV.js +0 -15
- package/dist/eslint-rules/rules/compliance.cjs +0 -348
- package/dist/eslint-rules/rules/components.cjs +0 -113
- package/dist/eslint-rules/rules/imports.cjs +0 -102
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -604
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-standards-compliance.md +0 -188
- package/docs/standards/03-solid-principles.md +0 -39
- package/docs/standards/04-testing-standards.md +0 -36
- package/docs/standards/05-bug-reports-and-features.md +0 -27
- package/docs/standards/06-code-quality.md +0 -34
- package/docs/standards/07-tech-stack-compliance.md +0 -30
- package/docs/standards/10-error-handling-patterns.md +0 -401
- package/docs/standards/11-performance-optimization.md +0 -348
- package/docs/standards/12-ci-cd-integration.md +0 -370
- package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +0 -192
- package/scripts/audit/audit-compliance.cjs +0 -1295
- package/scripts/audit/audit-components.cjs +0 -260
- package/scripts/audit/audit-rbac.cjs +0 -954
- package/scripts/audit/audit-standards.cjs +0 -1268
- package/scripts/audit/index.cjs +0 -1927
- package/src/components/DataTable/components/DataTableBody.tsx +0 -478
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/index.cjs +0 -22
- package/src/eslint-rules/rules/components.cjs +0 -113
- package/src/eslint-rules/rules/imports.cjs +0 -102
- package/src/eslint-rules/rules/rbac.cjs +0 -790
- package/src/eslint-rules/utils/helpers.cjs +0 -42
- package/src/eslint-rules/utils/manifest-loader.cjs +0 -75
|
@@ -36,10 +36,12 @@
|
|
|
36
36
|
* - Customizable text content
|
|
37
37
|
*/
|
|
38
38
|
import React, { useState, useRef, useEffect } from 'react';
|
|
39
|
-
import { Dialog, DialogContent, DialogHeader } from '../../Dialog';
|
|
39
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '../../Dialog';
|
|
40
40
|
import { Button } from '../../Button/Button';
|
|
41
41
|
import { Input } from '../../Input/Input';
|
|
42
42
|
import { Progress } from '../../Progress';
|
|
43
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../Table/Table';
|
|
44
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../../Card/Card';
|
|
43
45
|
import { Upload, FileText, AlertCircle } from 'lucide-react';
|
|
44
46
|
import { createLogger } from '../../../utils/core/logger';
|
|
45
47
|
|
|
@@ -372,150 +374,146 @@ export function ImportModal({ isOpen, onClose, onImport, config = {} }: ImportMo
|
|
|
372
374
|
|
|
373
375
|
return (
|
|
374
376
|
<Dialog open={isOpen} onOpenChange={handleClose}>
|
|
375
|
-
<DialogContent
|
|
377
|
+
<DialogContent
|
|
378
|
+
maxWidthPercent={95}
|
|
379
|
+
className="w-auto"
|
|
380
|
+
title={title}
|
|
381
|
+
description={description}
|
|
382
|
+
>
|
|
376
383
|
<DialogHeader>
|
|
377
|
-
<
|
|
378
|
-
<
|
|
384
|
+
<DialogTitle>{title}</DialogTitle>
|
|
385
|
+
<DialogDescription>{description}</DialogDescription>
|
|
379
386
|
</DialogHeader>
|
|
380
387
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
388
|
+
<Card className="text-center">
|
|
389
|
+
<CardHeader>
|
|
390
|
+
<FileText className="size-8 mx-auto text-sec-400 mb-2" />
|
|
391
|
+
</CardHeader>
|
|
392
|
+
<CardDescription>
|
|
385
393
|
{file ? `Selected: ${file.name}` : uploadText}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
File selected, processing preview...
|
|
390
|
-
</p>
|
|
391
|
-
)}
|
|
392
|
-
<Button
|
|
393
|
-
variant="outline"
|
|
394
|
-
size="sm"
|
|
395
|
-
onClick={() => fileInputRef.current?.click()}
|
|
396
|
-
>
|
|
397
|
-
<Upload className="size-4 mr-2" />
|
|
398
|
-
{selectFileButtonText}
|
|
399
|
-
</Button>
|
|
400
|
-
<Input
|
|
401
|
-
ref={fileInputRef}
|
|
402
|
-
type="file"
|
|
403
|
-
accept=".csv"
|
|
404
|
-
onChange={handleFileSelect}
|
|
405
|
-
className="hidden"
|
|
406
|
-
/>
|
|
407
|
-
</div>
|
|
408
|
-
|
|
409
|
-
{error && (
|
|
410
|
-
<div className="flex items-center gap-2 p-3 bg-acc-50 border border-acc-200 rounded text-acc-700">
|
|
411
|
-
<AlertCircle className="size-4" />
|
|
412
|
-
<span className="text-sm">{error}</span>
|
|
413
|
-
</div>
|
|
414
|
-
)}
|
|
415
|
-
|
|
416
|
-
{validationErrors.length > 0 && (
|
|
417
|
-
<div className="space-y-2">
|
|
418
|
-
<div className="flex items-center gap-2 p-3 bg-acc-50 border border-acc-200 rounded text-acc-700">
|
|
419
|
-
<AlertCircle className="size-4" />
|
|
420
|
-
<span className="text-sm font-medium">
|
|
421
|
-
{validationErrors.length} validation error{validationErrors.length !== 1 ? 's' : ''} found
|
|
394
|
+
{file && (
|
|
395
|
+
<span className="block text-xs text-sec-500 mt-1">
|
|
396
|
+
File selected, processing preview...
|
|
422
397
|
</span>
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
398
|
+
)}
|
|
399
|
+
</CardDescription>
|
|
400
|
+
<CardContent className="text-center">
|
|
401
|
+
{error && (
|
|
402
|
+
<>
|
|
403
|
+
<AlertCircle className="inline-block size-4 mr-2" />
|
|
404
|
+
<span className="text-sm">{error}</span>
|
|
405
|
+
</>
|
|
406
|
+
)}
|
|
407
|
+
|
|
408
|
+
{validationErrors.length > 0 && (
|
|
409
|
+
<>
|
|
410
|
+
<AlertCircle className="size-4" />
|
|
411
|
+
<span className="text-sm font-medium">
|
|
412
|
+
{validationErrors.length} validation error{validationErrors.length !== 1 ? 's' : ''} found
|
|
413
|
+
</span>
|
|
414
|
+
{validationErrors.slice(0, 10).map((err, idx) => (
|
|
415
|
+
<p key={idx} className="text-xs text-acc-600 p-2 bg-acc-50 border border-acc-200 rounded">
|
|
416
|
+
<span className="font-medium">Row {err.row}</span> • {err.field}: {err.message}
|
|
417
|
+
</p>
|
|
418
|
+
))}
|
|
419
|
+
{validationErrors.length > 10 && (
|
|
420
|
+
<p className="text-xs text-sec-500 italic">
|
|
421
|
+
... and {validationErrors.length - 10} more errors
|
|
422
|
+
</p>
|
|
423
|
+
)}
|
|
424
|
+
</>
|
|
425
|
+
)}
|
|
439
426
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
427
|
+
{importProgress && isProcessing && (
|
|
428
|
+
<>
|
|
429
|
+
<span className="text-sm font-medium text-sec-900">
|
|
430
|
+
{importProgress.stage === 'parsing' ? 'Parsing CSV file...' : 'Importing data...'}
|
|
431
|
+
</span>
|
|
432
|
+
<span className="text-sm text-sec-600">
|
|
433
|
+
{importProgress.current.toLocaleString()} / {importProgress.total.toLocaleString()} rows
|
|
434
|
+
</span>
|
|
435
|
+
<Progress
|
|
436
|
+
value={importProgress.total > 0 ? (importProgress.current / importProgress.total) * 100 : 0}
|
|
437
|
+
className="h-2 bg-sec-200"
|
|
438
|
+
/>
|
|
439
|
+
<p className="text-xs text-sec-500">
|
|
440
|
+
{importProgress.total > 0 && importProgress.current < importProgress.total
|
|
441
|
+
? `${Math.round((importProgress.current / importProgress.total) * 100)}% complete`
|
|
442
|
+
: importProgress.stage === 'importing' && importProgress.current === 0
|
|
443
|
+
? 'Processing your data...'
|
|
444
|
+
: importProgress.current === importProgress.total
|
|
445
|
+
? 'Complete!'
|
|
446
|
+
: 'Processing...'}
|
|
447
|
+
</p>
|
|
448
|
+
</>
|
|
449
|
+
)}
|
|
450
|
+
</CardContent>
|
|
451
|
+
<CardFooter>
|
|
452
|
+
<Button
|
|
453
|
+
variant="outline"
|
|
454
|
+
size="sm"
|
|
455
|
+
onClick={() => fileInputRef.current?.click()}
|
|
456
|
+
>
|
|
457
|
+
<Upload className="size-4 mr-2" />
|
|
458
|
+
{selectFileButtonText}
|
|
459
|
+
</Button>
|
|
460
|
+
<Input
|
|
461
|
+
ref={fileInputRef}
|
|
462
|
+
type="file"
|
|
463
|
+
accept=".csv"
|
|
464
|
+
onChange={handleFileSelect}
|
|
465
|
+
className="hidden"
|
|
453
466
|
/>
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
? `${Math.round((importProgress.current / importProgress.total) * 100)}% complete`
|
|
457
|
-
: importProgress.stage === 'importing' && importProgress.current === 0
|
|
458
|
-
? 'Processing your data...'
|
|
459
|
-
: importProgress.current === importProgress.total
|
|
460
|
-
? 'Complete!'
|
|
461
|
-
: 'Processing...'}
|
|
462
|
-
</p>
|
|
463
|
-
</div>
|
|
464
|
-
)}
|
|
467
|
+
</CardFooter>
|
|
468
|
+
</Card>
|
|
465
469
|
|
|
466
470
|
{file && previewData && previewData.length > 0 && !isProcessing ? (
|
|
467
|
-
<
|
|
468
|
-
<
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
471
|
+
<Card>
|
|
472
|
+
<CardHeader>
|
|
473
|
+
<CardTitle>{previewHeaderText}</CardTitle>
|
|
474
|
+
</CardHeader>
|
|
475
|
+
<CardContent className="overflow-x-auto">
|
|
476
|
+
<Table className="text-xs min-w-full">
|
|
477
|
+
<TableHeader>
|
|
478
|
+
<TableRow>
|
|
474
479
|
{Object.keys(previewData[0]).map((header) => (
|
|
475
|
-
<
|
|
480
|
+
<TableHead key={header} >
|
|
476
481
|
{header}
|
|
477
|
-
</
|
|
482
|
+
</TableHead>
|
|
478
483
|
))}
|
|
479
|
-
</
|
|
480
|
-
</
|
|
481
|
-
<
|
|
484
|
+
</TableRow>
|
|
485
|
+
</TableHeader>
|
|
486
|
+
<TableBody>
|
|
482
487
|
{previewData.map((row, index) => (
|
|
483
|
-
<
|
|
488
|
+
<TableRow key={index} >
|
|
484
489
|
{Object.values(row).map((value, cellIndex) => (
|
|
485
|
-
<
|
|
490
|
+
<TableCell key={cellIndex} >
|
|
486
491
|
{String(value)}
|
|
487
|
-
</
|
|
492
|
+
</TableCell>
|
|
488
493
|
))}
|
|
489
|
-
</
|
|
494
|
+
</TableRow>
|
|
490
495
|
))}
|
|
491
|
-
</
|
|
492
|
-
</
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
</div>
|
|
499
|
-
) : !file ? (
|
|
500
|
-
<div className="border rounded-lg p-6 text-center bg-sec-50">
|
|
501
|
-
<p className="text-sec-500">
|
|
502
|
-
Select a CSV file to preview
|
|
503
|
-
</p>
|
|
504
|
-
</div>
|
|
496
|
+
</TableBody>
|
|
497
|
+
</Table>
|
|
498
|
+
</CardContent>
|
|
499
|
+
<CardFooter>
|
|
500
|
+
{totalRowsText.replace('{count}', totalCount.toString())}
|
|
501
|
+
</CardFooter>
|
|
502
|
+
</Card>
|
|
505
503
|
) : null}
|
|
506
504
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
</
|
|
518
|
-
</
|
|
505
|
+
|
|
506
|
+
<DialogFooter>
|
|
507
|
+
<Button variant="outline" onClick={handleClose}>
|
|
508
|
+
{cancelButtonText}
|
|
509
|
+
</Button>
|
|
510
|
+
<Button
|
|
511
|
+
onClick={handleImport}
|
|
512
|
+
disabled={!file || isProcessing}
|
|
513
|
+
>
|
|
514
|
+
{isProcessing ? importButtonProcessingText : importButtonText}
|
|
515
|
+
</Button>
|
|
516
|
+
</DialogFooter>
|
|
519
517
|
</DialogContent>
|
|
520
518
|
</Dialog>
|
|
521
519
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { LoadingSpinner } from '../../LoadingSpinner/LoadingSpinner';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Loading state component for DataTable.
|
|
@@ -8,11 +9,9 @@ import React from 'react';
|
|
|
8
9
|
*/
|
|
9
10
|
export function LoadingState() {
|
|
10
11
|
return (
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
12
|
+
<p className="grid place-items-center text-center p-8">
|
|
13
|
+
<LoadingSpinner />
|
|
14
|
+
<strong>Loading...</strong>
|
|
15
|
+
</p>
|
|
17
16
|
);
|
|
18
17
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Sort Indicator Component
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/DataTable/Components
|
|
5
|
+
* @since 0.4.0
|
|
6
|
+
*
|
|
7
|
+
* Shared component for displaying column sort indicators.
|
|
8
|
+
* Provides consistent sorting chevron icons across all DataTable components.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react';
|
|
13
|
+
import { cn } from '../../../utils/core/cn';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Props for the SortIndicator component
|
|
17
|
+
*/
|
|
18
|
+
export interface SortIndicatorProps {
|
|
19
|
+
/** Current sort state: 'asc' for ascending, 'desc' for descending, false for unsorted */
|
|
20
|
+
sortState: 'asc' | 'desc' | false;
|
|
21
|
+
/** Optional className for styling */
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Sort indicator component that displays the appropriate chevron icon
|
|
27
|
+
* based on the current sort state.
|
|
28
|
+
*
|
|
29
|
+
* @param props - Sort indicator configuration
|
|
30
|
+
* @returns The rendered sort indicator icon
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* <SortIndicator sortState={column.getIsSorted()} />
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function SortIndicator({ sortState, className }: SortIndicatorProps) {
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
{sortState === 'asc' ? (
|
|
41
|
+
<ChevronUp className={cn('size-4', className)} />
|
|
42
|
+
) : sortState === 'desc' ? (
|
|
43
|
+
<ChevronDown className={cn('size-4', className)} />
|
|
44
|
+
) : (
|
|
45
|
+
<ChevronsUpDown className={cn('size-4', className)} />
|
|
46
|
+
)}
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Summary
|
|
4
4
|
|
|
5
|
-
The DataTable subcomponents (FilterRow, EditableRow, ColumnFilter,
|
|
5
|
+
The DataTable subcomponents (FilterRow, EditableRow, ColumnFilter, etc.) are **intentionally not tested in isolation** because:
|
|
6
6
|
|
|
7
7
|
1. They are tightly integrated with TanStack React Table
|
|
8
8
|
2. They are extensively tested through DataTable integration tests
|
|
@@ -35,9 +35,9 @@ The following DataTable integration tests provide comprehensive coverage:
|
|
|
35
35
|
| **ColumnFilter** | ✅ | Integration tests validate filter input behavior |
|
|
36
36
|
| **FilterRow** | ✅ | Integration tests validate filtering across columns |
|
|
37
37
|
| **EditableRow** | ✅ | Integration tests validate editing workflows |
|
|
38
|
-
| **
|
|
39
|
-
| **
|
|
40
|
-
| **
|
|
38
|
+
| **Grouped rows** | ✅ | Integration tests validate grouping functionality (handled inline by RowComponent) |
|
|
39
|
+
| **Modal display** | ✅ | Integration tests validate modal functionality |
|
|
40
|
+
| **Column reordering** | ✅ | Integration tests validate column order persistence |
|
|
41
41
|
| **ActionButtons** | ✅ | Integration tests validate action buttons |
|
|
42
42
|
|
|
43
43
|
## Why This Approach Works
|
|
@@ -57,8 +57,8 @@ vi.mock('../../Select/Select', async () => {
|
|
|
57
57
|
{children}
|
|
58
58
|
</button>
|
|
59
59
|
),
|
|
60
|
-
SelectValue: ({
|
|
61
|
-
<span data-testid="select-value">{
|
|
60
|
+
SelectValue: ({ children }: any) => (
|
|
61
|
+
<span data-testid="select-value">{children}</span>
|
|
62
62
|
),
|
|
63
63
|
SelectContent: ({ children }: any) => (
|
|
64
64
|
<div data-testid="select-content">{children}</div>
|
|
@@ -449,79 +449,6 @@ describe('[component] ColumnFilter', () => {
|
|
|
449
449
|
});
|
|
450
450
|
});
|
|
451
451
|
|
|
452
|
-
describe('Clear Filter Button', () => {
|
|
453
|
-
it('shows clear button when filter has value', () => {
|
|
454
|
-
const column = createMockColumn({
|
|
455
|
-
getFilterValue: vi.fn(() => 'test'),
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
render(<ColumnFilter column={column} />);
|
|
459
|
-
|
|
460
|
-
const clearButton = screen.getByRole('button');
|
|
461
|
-
expect(clearButton).toBeInTheDocument();
|
|
462
|
-
expect(screen.getByTestId('x-icon')).toBeInTheDocument();
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
it('hides clear button when filter has no value', () => {
|
|
466
|
-
const column = createMockColumn({
|
|
467
|
-
getFilterValue: vi.fn(() => undefined),
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
render(<ColumnFilter column={column} />);
|
|
471
|
-
|
|
472
|
-
expect(screen.queryByTestId('x-icon')).not.toBeInTheDocument();
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
it('hides clear button when filter value is empty string', () => {
|
|
476
|
-
const column = createMockColumn({
|
|
477
|
-
getFilterValue: vi.fn(() => ''),
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
render(<ColumnFilter column={column} />);
|
|
481
|
-
|
|
482
|
-
expect(screen.queryByTestId('x-icon')).not.toBeInTheDocument();
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
it('clears filter when clear button is clicked', async () => {
|
|
486
|
-
const user = userEvent.setup();
|
|
487
|
-
const setFilterValue = vi.fn();
|
|
488
|
-
const column = createMockColumn({
|
|
489
|
-
getFilterValue: vi.fn(() => 'test'),
|
|
490
|
-
setFilterValue,
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
render(<ColumnFilter column={column} />);
|
|
494
|
-
|
|
495
|
-
const clearButton = screen.getByRole('button');
|
|
496
|
-
await user.click(clearButton);
|
|
497
|
-
|
|
498
|
-
expect(setFilterValue).toHaveBeenCalledWith(undefined);
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
describe('Filter Indicator', () => {
|
|
503
|
-
it('shows filter indicator dot when filter has value', () => {
|
|
504
|
-
const column = createMockColumn({
|
|
505
|
-
getFilterValue: vi.fn(() => 'test'),
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
render(<ColumnFilter column={column} />);
|
|
509
|
-
|
|
510
|
-
const indicator = document.querySelector('.bg-main-500.rounded-full');
|
|
511
|
-
expect(indicator).toBeInTheDocument();
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
it('hides filter indicator dot when filter has no value', () => {
|
|
515
|
-
const column = createMockColumn({
|
|
516
|
-
getFilterValue: vi.fn(() => undefined),
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
render(<ColumnFilter column={column} />);
|
|
520
|
-
|
|
521
|
-
const indicator = document.querySelector('.bg-main-500.rounded-full');
|
|
522
|
-
expect(indicator).not.toBeInTheDocument();
|
|
523
|
-
});
|
|
524
|
-
});
|
|
525
452
|
|
|
526
453
|
describe('Edge Cases', () => {
|
|
527
454
|
it('handles undefined filter value gracefully', () => {
|
|
@@ -618,15 +545,29 @@ describe('[component] ColumnFilter', () => {
|
|
|
618
545
|
expect(input).toBeInTheDocument();
|
|
619
546
|
});
|
|
620
547
|
|
|
621
|
-
it('
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
548
|
+
it('renders Filter icon in select filter', () => {
|
|
549
|
+
render(
|
|
550
|
+
<ColumnFilter
|
|
551
|
+
column={mockColumn}
|
|
552
|
+
filterType="select"
|
|
553
|
+
options={[{ value: 'option1', label: 'Option 1' }]}
|
|
554
|
+
/>
|
|
555
|
+
);
|
|
625
556
|
|
|
626
|
-
|
|
557
|
+
expect(screen.getByTestId('filter-icon')).toBeInTheDocument();
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it('renders column name in select filter value', () => {
|
|
561
|
+
render(
|
|
562
|
+
<ColumnFilter
|
|
563
|
+
column={mockColumn}
|
|
564
|
+
filterType="select"
|
|
565
|
+
options={[{ value: 'option1', label: 'Option 1' }]}
|
|
566
|
+
/>
|
|
567
|
+
);
|
|
627
568
|
|
|
628
|
-
const
|
|
629
|
-
expect(
|
|
569
|
+
const selectValue = screen.getByTestId('select-value');
|
|
570
|
+
expect(selectValue).toHaveTextContent('test-column...');
|
|
630
571
|
});
|
|
631
572
|
});
|
|
632
573
|
|
|
@@ -89,8 +89,9 @@ describe('[component] DataTableErrorBoundary', () => {
|
|
|
89
89
|
</DataTableErrorBoundary>
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
-
//
|
|
93
|
-
|
|
92
|
+
// There may be multiple alerts (outer and inner), use getAllByRole
|
|
93
|
+
const alerts = screen.getAllByRole('alert');
|
|
94
|
+
expect(alerts.length).toBeGreaterThan(0);
|
|
94
95
|
expect(screen.getByText('DataTable Error')).toBeInTheDocument();
|
|
95
96
|
expect(screen.getByText('Something went wrong')).toBeInTheDocument();
|
|
96
97
|
});
|
|
@@ -127,8 +128,16 @@ describe('[component] DataTableErrorBoundary', () => {
|
|
|
127
128
|
</DataTableErrorBoundary>
|
|
128
129
|
);
|
|
129
130
|
|
|
131
|
+
// Check for error details summary
|
|
130
132
|
const details = screen.getByText('Error Details');
|
|
131
133
|
expect(details).toBeInTheDocument();
|
|
134
|
+
// Check that error message is displayed in the details/pre element
|
|
135
|
+
// The error message is inside a <pre> tag within <details>
|
|
136
|
+
const preElement = screen.getByText((content, element) => {
|
|
137
|
+
return element?.tagName.toLowerCase() === 'pre' && content.includes('Test error');
|
|
138
|
+
}, { selector: 'pre' });
|
|
139
|
+
expect(preElement).toBeInTheDocument();
|
|
140
|
+
expect(preElement).toHaveTextContent('Test error');
|
|
132
141
|
});
|
|
133
142
|
|
|
134
143
|
it('hides error details when showErrorDetails is false', () => {
|
|
@@ -140,10 +149,11 @@ describe('[component] DataTableErrorBoundary', () => {
|
|
|
140
149
|
|
|
141
150
|
// Error details section is shown when error.message exists
|
|
142
151
|
// showErrorDetails only controls stack trace visibility, not the details section
|
|
143
|
-
// So we check that the details section exists but stack trace is not shown
|
|
144
152
|
expect(screen.getByText('Error Details')).toBeInTheDocument();
|
|
145
153
|
// Stack trace should not be visible when showErrorDetails is false
|
|
146
154
|
expect(screen.queryByText(/Stack Trace/i)).not.toBeInTheDocument();
|
|
155
|
+
// Error message should still be visible
|
|
156
|
+
expect(screen.getByText('Test error')).toBeInTheDocument();
|
|
147
157
|
});
|
|
148
158
|
|
|
149
159
|
it('displays stack trace when showErrorDetails is true', () => {
|
|
@@ -155,6 +165,8 @@ describe('[component] DataTableErrorBoundary', () => {
|
|
|
155
165
|
|
|
156
166
|
// Stack trace should be in the details section
|
|
157
167
|
expect(screen.getByText('Error Details')).toBeInTheDocument();
|
|
168
|
+
// Stack trace text should be present when showErrorDetails is true
|
|
169
|
+
expect(screen.getByText(/Stack Trace/i)).toBeInTheDocument();
|
|
158
170
|
});
|
|
159
171
|
});
|
|
160
172
|
|
|
@@ -237,7 +249,8 @@ describe('[component] DataTableErrorBoundary', () => {
|
|
|
237
249
|
</DataTableErrorBoundary>
|
|
238
250
|
);
|
|
239
251
|
|
|
240
|
-
|
|
252
|
+
const alerts = screen.getAllByRole('alert');
|
|
253
|
+
expect(alerts.length).toBeGreaterThan(0);
|
|
241
254
|
|
|
242
255
|
const retryButton = screen.getByRole('button', { name: /retry/i });
|
|
243
256
|
await user.click(retryButton);
|
|
@@ -317,7 +330,8 @@ describe('[component] DataTableErrorBoundary', () => {
|
|
|
317
330
|
</DataTableErrorBoundary>
|
|
318
331
|
);
|
|
319
332
|
|
|
320
|
-
|
|
333
|
+
const alerts = screen.getAllByRole('alert');
|
|
334
|
+
expect(alerts.length).toBeGreaterThan(0);
|
|
321
335
|
|
|
322
336
|
const resetButton = screen.getByRole('button', { name: /reset/i });
|
|
323
337
|
|
|
@@ -352,7 +366,9 @@ describe('[component] DataTableErrorBoundary', () => {
|
|
|
352
366
|
</DataTableErrorBoundary>
|
|
353
367
|
);
|
|
354
368
|
|
|
355
|
-
|
|
369
|
+
// There may be multiple alerts when error has no message
|
|
370
|
+
const alerts = screen.getAllByRole('alert');
|
|
371
|
+
expect(alerts.length).toBeGreaterThan(0);
|
|
356
372
|
expect(screen.getByText('An unexpected error occurred')).toBeInTheDocument();
|
|
357
373
|
});
|
|
358
374
|
|
|
@@ -365,19 +381,31 @@ describe('[component] DataTableErrorBoundary', () => {
|
|
|
365
381
|
</DataTableErrorBoundary>
|
|
366
382
|
);
|
|
367
383
|
|
|
368
|
-
|
|
384
|
+
const alerts = screen.getAllByRole('alert');
|
|
385
|
+
expect(alerts.length).toBeGreaterThan(0);
|
|
369
386
|
|
|
370
387
|
const retryButton = screen.getByRole('button', { name: /retry/i });
|
|
371
388
|
await user.click(retryButton);
|
|
372
389
|
|
|
373
|
-
//
|
|
390
|
+
// Wait for retry to complete and error state to reset
|
|
391
|
+
await waitFor(() => {
|
|
392
|
+
// Wait for the timeout to complete
|
|
393
|
+
}, { timeout: 200 });
|
|
394
|
+
|
|
395
|
+
// Trigger another error - rerender with new error
|
|
374
396
|
rerender(
|
|
375
397
|
<DataTableErrorBoundary showRetryButton={true}>
|
|
376
398
|
<ThrowError shouldThrow={true} message="Second error" />
|
|
377
399
|
</DataTableErrorBoundary>
|
|
378
400
|
);
|
|
379
401
|
|
|
380
|
-
|
|
402
|
+
// Wait for the new error to be caught and displayed
|
|
403
|
+
await waitFor(() => {
|
|
404
|
+
const newAlerts = screen.getAllByRole('alert');
|
|
405
|
+
expect(newAlerts.length).toBeGreaterThan(0);
|
|
406
|
+
const preElement = document.querySelector('pre');
|
|
407
|
+
expect(preElement).toHaveTextContent('Second error');
|
|
408
|
+
}, { timeout: 300 });
|
|
381
409
|
});
|
|
382
410
|
|
|
383
411
|
it('handles cleanup on unmount', () => {
|
|
@@ -306,7 +306,8 @@ describe('[component] EmptyState', () => {
|
|
|
306
306
|
it('uses semantic heading for title', () => {
|
|
307
307
|
render(<EmptyState title="Custom Title" />);
|
|
308
308
|
|
|
309
|
-
|
|
309
|
+
// AlertTitle renders as h5, not h3
|
|
310
|
+
const heading = screen.getByRole('heading', { level: 5 });
|
|
310
311
|
expect(heading).toHaveTextContent('Custom Title');
|
|
311
312
|
});
|
|
312
313
|
|
|
@@ -410,11 +411,12 @@ describe('[component] EmptyState', () => {
|
|
|
410
411
|
});
|
|
411
412
|
|
|
412
413
|
describe('Layout and Styling', () => {
|
|
413
|
-
it('renders with centered
|
|
414
|
+
it('renders with centered grid layout', () => {
|
|
414
415
|
render(<EmptyState />);
|
|
415
416
|
|
|
416
417
|
const container = screen.getByRole('status');
|
|
417
|
-
|
|
418
|
+
// EmptyState uses grid place-items-center, not flex
|
|
419
|
+
expect(container).toHaveClass('grid', 'place-items-center');
|
|
418
420
|
});
|
|
419
421
|
|
|
420
422
|
it('applies text-center class', () => {
|
|
@@ -428,7 +430,8 @@ describe('[component] EmptyState', () => {
|
|
|
428
430
|
render(<EmptyState />);
|
|
429
431
|
|
|
430
432
|
const container = screen.getByRole('status');
|
|
431
|
-
|
|
433
|
+
// EmptyState uses p-4, not p-8
|
|
434
|
+
expect(container).toHaveClass('p-4');
|
|
432
435
|
});
|
|
433
436
|
});
|
|
434
437
|
});
|