@12-apps/ui 1.5.0 → 1.5.2
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/package.json +17 -9
- package/src/button/index.ts +1 -0
- package/src/social-login-button/index.ts +1 -0
- package/src/{social-login-button.tsx → social-login-button/social-login-button.tsx} +5 -5
- package/src/user-avatar/index.ts +1 -0
- package/src/{user-avatar.tsx → user-avatar/user-avatar.tsx} +4 -4
- package/eslint.config.js +0 -89
- package/src/__tests__/data-grid.test.tsx +0 -125
- package/src/__tests__/package-wiring.test.ts +0 -41
- package/src/__tests__/setup.ts +0 -1
- package/src/__tests__/social-login-button.test.tsx +0 -186
- package/src/__tests__/user-avatar.test.tsx +0 -195
- package/src/__tests__/viewport.test.ts +0 -50
- package/src/components/command/__tests__/command.test.tsx +0 -112
- package/src/components/data-display/Alert/Alert.stories.tsx +0 -559
- package/src/components/data-display/Alert/Alert.test.stories.tsx +0 -707
- package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +0 -362
- package/src/components/data-display/AlertDialog/AlertDialog.test.stories.tsx +0 -763
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.stories.tsx +0 -230
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +0 -270
- package/src/components/data-display/Avatar/Avatar.stories.tsx +0 -721
- package/src/components/data-display/Avatar/Avatar.test.stories.tsx +0 -822
- package/src/components/data-display/Badge/Badge.stories.tsx +0 -1134
- package/src/components/data-display/Badge/Badge.test.stories.tsx +0 -1194
- package/src/components/data-display/Banner/Banner.stories.tsx +0 -528
- package/src/components/data-display/Banner/Banner.test.stories.tsx +0 -511
- package/src/components/data-display/Carousel/Carousel.stories.tsx +0 -607
- package/src/components/data-display/Carousel/Carousel.test.stories.tsx +0 -701
- package/src/components/data-display/Chart/Chart.stories.tsx +0 -568
- package/src/components/data-display/Chart/Chart.test.stories.tsx +0 -174
- package/src/components/data-display/Chart/SpecChart.stories.tsx +0 -150
- package/src/components/data-display/Chart/SpecChart.test.stories.tsx +0 -56
- package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +0 -60
- package/src/components/data-display/Chart/__tests__/chart-format.test.ts +0 -43
- package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +0 -119
- package/src/components/data-display/Chip/Chip.stories.tsx +0 -252
- package/src/components/data-display/Chip/Chip.test.stories.tsx +0 -266
- package/src/components/data-display/DataGrid/DataGrid.stories.tsx +0 -515
- package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +0 -653
- package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +0 -271
- package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +0 -134
- package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +0 -83
- package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +0 -115
- package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +0 -53
- package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +0 -114
- package/src/components/data-display/DataViews/base-card.stories.tsx +0 -136
- package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +0 -254
- package/src/components/data-display/EmptyState/EmptyState.stories.tsx +0 -282
- package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +0 -441
- package/src/components/data-display/ErrorState/ErrorState.stories.tsx +0 -110
- package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +0 -133
- package/src/components/data-display/HoverCard/HoverCard.stories.tsx +0 -650
- package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +0 -929
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.stories.tsx +0 -411
- package/src/components/data-display/LazyImage/LazyImage.stories.tsx +0 -387
- package/src/components/data-display/Lightbox/Lightbox.stories.tsx +0 -315
- package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +0 -598
- package/src/components/data-display/LoadingState/LoadingState.stories.tsx +0 -124
- package/src/components/data-display/LoadingState/LoadingState.test.stories.tsx +0 -99
- package/src/components/data-display/MapPreview/MapPreview.stories.tsx +0 -458
- package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +0 -394
- package/src/components/data-display/Popover/Popover.stories.tsx +0 -295
- package/src/components/data-display/Popover/Popover.test.stories.tsx +0 -1162
- package/src/components/data-display/Progress/Progress.stories.tsx +0 -481
- package/src/components/data-display/Progress/Progress.test.stories.tsx +0 -2070
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +0 -160
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.test.stories.tsx +0 -190
- package/src/components/data-display/Sheet/Sheet.stories.tsx +0 -883
- package/src/components/data-display/Sheet/Sheet.test.stories.tsx +0 -1105
- package/src/components/data-display/StatCard/StatCard.stories.tsx +0 -167
- package/src/components/data-display/StatCard/StatCard.test.stories.tsx +0 -240
- package/src/components/data-display/Stepper/Stepper.stories.tsx +0 -540
- package/src/components/data-display/Stepper/Stepper.test.stories.tsx +0 -600
- package/src/components/data-display/Table/Table.stories.tsx +0 -803
- package/src/components/data-display/Table/Table.test.stories.tsx +0 -369
- package/src/components/data-display/Timeline/Timeline.stories.tsx +0 -442
- package/src/components/data-display/Timeline/Timeline.test.stories.tsx +0 -930
- package/src/components/data-display/TimingDiagram/TimingDiagram.stories.tsx +0 -475
- package/src/components/data-display/TimingDiagram/TimingDiagram.test.stories.tsx +0 -734
- package/src/components/data-display/Tooltip/Tooltip.stories.tsx +0 -468
- package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +0 -1154
- package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +0 -146
- package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +0 -114
- package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +0 -288
- package/src/components/feedback/Dialog/Dialog.stories.tsx +0 -833
- package/src/components/feedback/Dialog/Dialog.test.stories.tsx +0 -964
- package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +0 -66
- package/src/components/feedback/Modal/Modal.stories.tsx +0 -765
- package/src/components/feedback/Modal/Modal.test.stories.tsx +0 -251
- package/src/components/feedback/Sonner/Sonner.stories.tsx +0 -698
- package/src/components/feedback/Sonner/Sonner.test.stories.tsx +0 -896
- package/src/components/feedback/StackedModal/StackedModal.stories.tsx +0 -1783
- package/src/components/feedback/StackedModal/StackedModal.test.stories.tsx +0 -973
- package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +0 -104
- package/src/components/feedback/Toast/Toast.stories.tsx +0 -548
- package/src/components/feedback/Toast/Toast.test.stories.tsx +0 -834
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.stories.tsx +0 -691
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +0 -634
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +0 -665
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +0 -727
- package/src/components/form/Autocomplete/Autocomplete.stories.tsx +0 -715
- package/src/components/form/Autocomplete/Autocomplete.test.stories.tsx +0 -825
- package/src/components/form/Button/Button.stories.tsx +0 -365
- package/src/components/form/Button/Button.test.stories.tsx +0 -607
- package/src/components/form/Calendar/Calendar.stories.tsx +0 -349
- package/src/components/form/Calendar/Calendar.test.stories.tsx +0 -464
- package/src/components/form/CepField/CepField.stories.tsx +0 -138
- package/src/components/form/CepField/CepField.test.stories.tsx +0 -281
- package/src/components/form/Checkbox/Checkbox.stories.tsx +0 -265
- package/src/components/form/Checkbox/Checkbox.test.stories.tsx +0 -181
- package/src/components/form/CodeEditor/CodeEditor.stories.tsx +0 -871
- package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +0 -574
- package/src/components/form/Command/Command.stories.tsx +0 -580
- package/src/components/form/Command/Command.test.stories.tsx +0 -215
- package/src/components/form/CreatableSelect/CreatableSelect.stories.tsx +0 -112
- package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +0 -134
- package/src/components/form/Form/Form.stories.tsx +0 -687
- package/src/components/form/Form/Form.test.stories.tsx +0 -601
- package/src/components/form/Input/Input.stories.tsx +0 -260
- package/src/components/form/Input/Input.test.stories.tsx +0 -1191
- package/src/components/form/InputOTP/InputOTP.stories.tsx +0 -774
- package/src/components/form/InputOTP/InputOTP.test.stories.tsx +0 -588
- package/src/components/form/Label/Label.stories.tsx +0 -473
- package/src/components/form/Label/Label.test.stories.tsx +0 -915
- package/src/components/form/Menubar/Menubar.stories.tsx +0 -472
- package/src/components/form/Menubar/Menubar.test.stories.tsx +0 -584
- package/src/components/form/PasswordStrength/PasswordStrength.stories.tsx +0 -562
- package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +0 -327
- package/src/components/form/PhoneInput/PhoneInput.stories.tsx +0 -495
- package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +0 -666
- package/src/components/form/RadioGroup/RadioGroup.stories.tsx +0 -518
- package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +0 -685
- package/src/components/form/RichTextEditor/RichTextEditor.stories.tsx +0 -261
- package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +0 -491
- package/src/components/form/SearchPalette/SearchPalette.stories.tsx +0 -135
- package/src/components/form/Select/Select.stories.tsx +0 -364
- package/src/components/form/Select/Select.test.stories.tsx +0 -985
- package/src/components/form/Slider/Slider.stories.tsx +0 -418
- package/src/components/form/Slider/Slider.test.stories.tsx +0 -1042
- package/src/components/form/Switch/Switch.stories.tsx +0 -697
- package/src/components/form/Switch/Switch.test.stories.tsx +0 -597
- package/src/components/form/Textarea/Textarea.stories.tsx +0 -473
- package/src/components/form/Textarea/Textarea.test.stories.tsx +0 -702
- package/src/components/form/Toggle/Toggle.stories.tsx +0 -638
- package/src/components/form/Toggle/Toggle.test.stories.tsx +0 -1159
- package/src/components/form/ToggleGroup/ToggleGroup.stories.tsx +0 -617
- package/src/components/form/ToggleGroup/ToggleGroup.test.stories.tsx +0 -915
- package/src/components/form/UploadButton/UploadButton.stories.tsx +0 -367
- package/src/components/form/UploadButton/UploadButton.test.stories.tsx +0 -573
- package/src/components/form/total-form/total-form.test.tsx +0 -214
- package/src/components/layout/Accordion/Accordion.stories.tsx +0 -694
- package/src/components/layout/Accordion/Accordion.test.stories.tsx +0 -1344
- package/src/components/layout/Card/Card.stories.tsx +0 -591
- package/src/components/layout/Card/Card.test.stories.tsx +0 -1134
- package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +0 -92
- package/src/components/layout/Collapsible/Collapsible.stories.tsx +0 -180
- package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +0 -308
- package/src/components/layout/Container/Container.stories.tsx +0 -557
- package/src/components/layout/Container/Container.test.stories.tsx +0 -745
- package/src/components/layout/ContentToolbar/ContentToolbar.stories.tsx +0 -129
- package/src/components/layout/ContentToolbar/ContentToolbar.test.stories.tsx +0 -251
- package/src/components/layout/ContentToolbar/__tests__/ColumnsMenu.test.tsx +0 -39
- package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +0 -131
- package/src/components/layout/ContentToolbar/__tests__/SavedViewsMenu.test.tsx +0 -99
- package/src/components/layout/Dashboard/Dashboard.stories.tsx +0 -104
- package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +0 -190
- package/src/components/layout/Drawer/Drawer.stories.tsx +0 -658
- package/src/components/layout/Drawer/Drawer.test.stories.tsx +0 -1343
- package/src/components/layout/Resizable/Resizable.stories.tsx +0 -1039
- package/src/components/layout/Resizable/Resizable.test.stories.tsx +0 -754
- package/src/components/layout/ScrollArea/ScrollArea.stories.tsx +0 -651
- package/src/components/layout/ScrollArea/ScrollArea.test.stories.tsx +0 -619
- package/src/components/layout/Separator/Separator.stories.tsx +0 -563
- package/src/components/layout/Separator/Separator.test.stories.tsx +0 -518
- package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +0 -192
- package/src/components/layout/SettingsLayout/SettingsLayout.test.stories.tsx +0 -175
- package/src/components/layout/Sidebar/Sidebar.stories.tsx +0 -609
- package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +0 -1324
- package/src/components/layout/Skeleton/Skeleton.stories.tsx +0 -689
- package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +0 -521
- package/src/components/layout/Spacer/Spacer.stories.tsx +0 -502
- package/src/components/layout/Spacer/Spacer.test.stories.tsx +0 -474
- package/src/components/layout/TableFilter/TableFilter.stories.tsx +0 -82
- package/src/components/layout/TableFilter/TableFilter.test.stories.tsx +0 -87
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +0 -638
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +0 -1008
- package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +0 -386
- package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +0 -435
- package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +0 -614
- package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +0 -845
- package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +0 -578
- package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +0 -660
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +0 -516
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +0 -935
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +0 -501
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +0 -453
- package/src/components/navigation/Pagination/Pagination.stories.tsx +0 -602
- package/src/components/navigation/Pagination/Pagination.test.stories.tsx +0 -520
- package/src/components/navigation/Tabs/Tabs.stories.tsx +0 -1004
- package/src/components/navigation/Tabs/Tabs.test.stories.tsx +0 -1225
- package/src/components/typography/Blockquote/Blockquote.stories.tsx +0 -359
- package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +0 -432
- package/src/components/typography/Code/Code.stories.tsx +0 -686
- package/src/components/typography/Code/Code.test.stories.tsx +0 -703
- package/src/components/typography/Heading/Heading.stories.tsx +0 -451
- package/src/components/typography/Heading/Heading.test.stories.tsx +0 -830
- package/src/components/typography/Paragraph/Paragraph.stories.tsx +0 -280
- package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +0 -231
- package/src/components/typography/Text/Text.stories.tsx +0 -359
- package/src/components/typography/Text/Text.test.stories.tsx +0 -759
- package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +0 -945
- package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +0 -603
- package/src/components/utility/AspectRatio/AspectRatio.stories.tsx +0 -414
- package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +0 -859
- package/src/components/utility/InfiniteScroll/InfiniteScroll.stories.tsx +0 -620
- package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +0 -361
- package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +0 -118
- package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +0 -83
- package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +0 -65
- package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +0 -271
- package/src/components/utility/Portal/Portal.stories.tsx +0 -340
- package/src/components/utility/Portal/Portal.test.stories.tsx +0 -2086
- package/src/components/utility/Transition/Transition.stories.tsx +0 -493
- package/src/components/utility/Transition/Transition.test.stories.tsx +0 -854
- package/src/components/utility/VirtualList/VirtualList.stories.tsx +0 -1073
- package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +0 -1085
- package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +0 -431
- package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +0 -364
- /package/src/{button.tsx → button/button.tsx} +0 -0
|
@@ -1,930 +0,0 @@
|
|
|
1
|
-
import CheckCircle from '@mui/icons-material/CheckCircle';
|
|
2
|
-
import ErrorIcon from '@mui/icons-material/Error';
|
|
3
|
-
import Info from '@mui/icons-material/Info';
|
|
4
|
-
import Warning from '@mui/icons-material/Warning';
|
|
5
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
6
|
-
import { expect, fn,Mock,userEvent, waitFor, within } from 'storybook/test';
|
|
7
|
-
|
|
8
|
-
import { Timeline } from './Timeline';
|
|
9
|
-
import type { TimelineItem } from './Timeline.types';
|
|
10
|
-
|
|
11
|
-
const meta: Meta<typeof Timeline> = {
|
|
12
|
-
title: 'Enhanced/Timeline/Tests',
|
|
13
|
-
component: Timeline,
|
|
14
|
-
parameters: {
|
|
15
|
-
layout: 'centered',
|
|
16
|
-
chromatic: { disableSnapshot: false },
|
|
17
|
-
},
|
|
18
|
-
tags: ['autodocs', 'test', 'component:Timeline'],
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export default meta;
|
|
22
|
-
type Story = StoryObj<typeof meta>;
|
|
23
|
-
|
|
24
|
-
// Sample data for testing
|
|
25
|
-
const sampleItems: TimelineItem[] = [
|
|
26
|
-
{
|
|
27
|
-
id: '1',
|
|
28
|
-
title: 'Order Placed',
|
|
29
|
-
description: 'Your order has been successfully placed',
|
|
30
|
-
timestamp: '2024-01-01 10:00 AM',
|
|
31
|
-
icon: <CheckCircle />,
|
|
32
|
-
color: '#4caf50',
|
|
33
|
-
metadata: { 'Order ID': '#12345', Status: 'Confirmed' },
|
|
34
|
-
action: {
|
|
35
|
-
label: 'View Details',
|
|
36
|
-
onClick: fn(),
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: '2',
|
|
41
|
-
title: 'Processing',
|
|
42
|
-
description: 'Your order is being processed',
|
|
43
|
-
timestamp: '2024-01-01 11:00 AM',
|
|
44
|
-
icon: <Info />,
|
|
45
|
-
color: '#2196f3',
|
|
46
|
-
metadata: { Location: 'Warehouse A', Expected: '2 hours' },
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
id: '3',
|
|
50
|
-
title: 'Quality Check',
|
|
51
|
-
description: 'Items are undergoing quality inspection',
|
|
52
|
-
timestamp: '2024-01-01 02:00 PM',
|
|
53
|
-
icon: <Warning />,
|
|
54
|
-
color: '#ff9800',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: '4',
|
|
58
|
-
title: 'Shipped',
|
|
59
|
-
description: 'Your order has been shipped',
|
|
60
|
-
timestamp: '2024-01-02 09:00 AM',
|
|
61
|
-
icon: <CheckCircle />,
|
|
62
|
-
color: '#4caf50',
|
|
63
|
-
metadata: { Tracking: 'ABC123XYZ', Carrier: 'FedEx' },
|
|
64
|
-
},
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
// Test Stories
|
|
68
|
-
|
|
69
|
-
export const BasicInteraction: Story = {
|
|
70
|
-
args: {
|
|
71
|
-
items: sampleItems,
|
|
72
|
-
variant: 'default',
|
|
73
|
-
orientation: 'vertical',
|
|
74
|
-
onItemClick: fn(),
|
|
75
|
-
},
|
|
76
|
-
play: async ({ canvasElement, args }) => {
|
|
77
|
-
const canvas = within(canvasElement);
|
|
78
|
-
|
|
79
|
-
// Verify all timeline items are rendered
|
|
80
|
-
const timelineItems = canvas.getAllByRole('article');
|
|
81
|
-
await expect(timelineItems).toHaveLength(sampleItems.length);
|
|
82
|
-
|
|
83
|
-
// Verify each item displays its title and timestamp
|
|
84
|
-
for (const item of sampleItems) {
|
|
85
|
-
await expect(canvas.getByText(item.title)).toBeInTheDocument();
|
|
86
|
-
await expect(canvas.getByText(item.timestamp)).toBeInTheDocument();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Test clicking on timeline items
|
|
90
|
-
const firstItem = timelineItems[0];
|
|
91
|
-
await userEvent.click(firstItem);
|
|
92
|
-
await expect(args.onItemClick).toHaveBeenCalledWith(sampleItems[0]);
|
|
93
|
-
await expect(args.onItemClick).toHaveBeenCalledTimes(1);
|
|
94
|
-
|
|
95
|
-
// Reset mock
|
|
96
|
-
(args?.onItemClick as Mock)?.mockClear?.();
|
|
97
|
-
|
|
98
|
-
// Test expand/collapse functionality for items with descriptions
|
|
99
|
-
const expandButtons = Array.from(canvasElement.querySelectorAll('button')).filter(
|
|
100
|
-
(btn) => btn.querySelector('svg[data-testid="ExpandMoreIcon"]')
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
await expect(expandButtons.length).toBeGreaterThan(0);
|
|
104
|
-
|
|
105
|
-
// Initially collapsed - description should not be visible
|
|
106
|
-
const firstDescription = sampleItems[0].description!;
|
|
107
|
-
await waitFor(() => expect(canvas.queryByText(firstDescription)).not.toBeVisible());
|
|
108
|
-
|
|
109
|
-
// Click to expand
|
|
110
|
-
await userEvent.click(expandButtons[0]);
|
|
111
|
-
|
|
112
|
-
// Wait for expansion animation and verify description is now visible
|
|
113
|
-
await waitFor(() => {
|
|
114
|
-
expect(canvas.getByText(firstDescription)).toBeVisible();
|
|
115
|
-
}, { timeout: 3000 });
|
|
116
|
-
|
|
117
|
-
// Verify the expand button rotated (indicates expanded state)
|
|
118
|
-
// Check the computed style includes rotation (will be a matrix in computed styles)
|
|
119
|
-
const buttonStyle = window.getComputedStyle(expandButtons[0]);
|
|
120
|
-
await expect(buttonStyle.transform).not.toBe('none');
|
|
121
|
-
|
|
122
|
-
// Click to collapse
|
|
123
|
-
await userEvent.click(expandButtons[0]);
|
|
124
|
-
|
|
125
|
-
// Wait for collapse animation
|
|
126
|
-
await waitFor(() => {
|
|
127
|
-
expect(canvas.queryByText(firstDescription)).not.toBeVisible();
|
|
128
|
-
}, { timeout: 3000 });
|
|
129
|
-
|
|
130
|
-
// Verify the expand button rotated back
|
|
131
|
-
// The collapsed state should not have the same transform as when expanded
|
|
132
|
-
await waitFor(() => {
|
|
133
|
-
const _collapsedButtonStyle = window.getComputedStyle(expandButtons[0]);
|
|
134
|
-
const _expandedButtonStyle = window.getComputedStyle(expandButtons[0]);
|
|
135
|
-
// Just verify the animation has completed - description is not visible
|
|
136
|
-
expect(canvas.queryByText(firstDescription)).not.toBeVisible();
|
|
137
|
-
});
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
export const FormInteraction: Story = {
|
|
142
|
-
args: {
|
|
143
|
-
items: sampleItems.map((item) => ({
|
|
144
|
-
...item,
|
|
145
|
-
action: {
|
|
146
|
-
label: `Action ${item.id}`,
|
|
147
|
-
onClick: fn(),
|
|
148
|
-
},
|
|
149
|
-
})),
|
|
150
|
-
variant: 'detailed',
|
|
151
|
-
},
|
|
152
|
-
play: async ({ canvasElement, args }) => {
|
|
153
|
-
const canvas = within(canvasElement);
|
|
154
|
-
|
|
155
|
-
// In detailed variant, all descriptions should be visible
|
|
156
|
-
// Wait for animations to complete
|
|
157
|
-
await waitFor(async () => {
|
|
158
|
-
for (const item of args.items) {
|
|
159
|
-
if (item.description) {
|
|
160
|
-
const element = canvas.getByText(item.description);
|
|
161
|
-
expect(element).toBeVisible();
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}, { timeout: 5000 });
|
|
165
|
-
|
|
166
|
-
// Test action button clicks
|
|
167
|
-
const actionButtons = Array.from(canvasElement.querySelectorAll('button')).filter(
|
|
168
|
-
(btn) => btn.textContent?.includes('Action')
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
await expect(actionButtons).toHaveLength(args.items.length);
|
|
172
|
-
|
|
173
|
-
// Test each action button
|
|
174
|
-
for (let i = 0; i < actionButtons.length; i++) {
|
|
175
|
-
const button = actionButtons[i];
|
|
176
|
-
const expectedLabel = `Action ${args.items[i].id}`;
|
|
177
|
-
|
|
178
|
-
// Verify button has correct label
|
|
179
|
-
await expect(button).toHaveTextContent(expectedLabel);
|
|
180
|
-
|
|
181
|
-
// Click the button
|
|
182
|
-
await userEvent.click(button);
|
|
183
|
-
|
|
184
|
-
// Verify the corresponding onClick was called
|
|
185
|
-
await expect(args.items[i].action?.onClick).toHaveBeenCalledTimes(1);
|
|
186
|
-
|
|
187
|
-
// Reset for next iteration
|
|
188
|
-
(args.items[i].action?.onClick as Mock)?.mockClear?.();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Test that action button clicks don't trigger item click
|
|
192
|
-
const firstActionButton = actionButtons[0];
|
|
193
|
-
const itemClickMock = fn();
|
|
194
|
-
args.onItemClick = itemClickMock;
|
|
195
|
-
|
|
196
|
-
await userEvent.click(firstActionButton);
|
|
197
|
-
await expect(itemClickMock).not.toHaveBeenCalled();
|
|
198
|
-
|
|
199
|
-
// Verify metadata is displayed in detailed variant
|
|
200
|
-
for (const item of args.items) {
|
|
201
|
-
if (item.metadata) {
|
|
202
|
-
for (const [key, value] of Object.entries(item.metadata)) {
|
|
203
|
-
const metadataText = `${key}: ${value}`;
|
|
204
|
-
await expect(canvas.getByText(metadataText)).toBeInTheDocument();
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
export const KeyboardNavigation: Story = {
|
|
212
|
-
args: {
|
|
213
|
-
items: sampleItems,
|
|
214
|
-
variant: 'default',
|
|
215
|
-
orientation: 'vertical',
|
|
216
|
-
onItemClick: fn(),
|
|
217
|
-
},
|
|
218
|
-
play: async ({ canvasElement, args }) => {
|
|
219
|
-
const canvas = within(canvasElement);
|
|
220
|
-
|
|
221
|
-
// Wait for timeline to render
|
|
222
|
-
await waitFor(() => {
|
|
223
|
-
const cards = canvas.getAllByRole('article');
|
|
224
|
-
expect(cards).toHaveLength(sampleItems.length);
|
|
225
|
-
}, { timeout: 3000 });
|
|
226
|
-
|
|
227
|
-
// Get all interactive elements
|
|
228
|
-
const cards = canvas.getAllByRole('article');
|
|
229
|
-
const expandButtons = Array.from(canvasElement.querySelectorAll('button')).filter(
|
|
230
|
-
(btn) => btn.querySelector('svg[data-testid="ExpandMoreIcon"]')
|
|
231
|
-
);
|
|
232
|
-
// We don't need actionButtons in this test but keep for potential use
|
|
233
|
-
// const actionButtons = Array.from(canvasElement.querySelectorAll('button')).filter(
|
|
234
|
-
// (btn) => btn.textContent?.includes('View Details')
|
|
235
|
-
// );
|
|
236
|
-
|
|
237
|
-
// Test Tab navigation through timeline cards
|
|
238
|
-
// Find the first expand button and click it to test expansion
|
|
239
|
-
await userEvent.click(expandButtons[0]);
|
|
240
|
-
await waitFor(() => {
|
|
241
|
-
const description = canvas.getByText(sampleItems[0].description!);
|
|
242
|
-
expect(description).toBeVisible();
|
|
243
|
-
}, { timeout: 3000 });
|
|
244
|
-
|
|
245
|
-
// Verify button rotation indicates expanded state (transform will be a matrix in computed styles)
|
|
246
|
-
const expandedButtonStyle = window.getComputedStyle(expandButtons[0]);
|
|
247
|
-
await expect(expandedButtonStyle.transform).not.toBe('none');
|
|
248
|
-
|
|
249
|
-
// Test action button with keyboard
|
|
250
|
-
const actionButton = canvas.getByRole('button', { name: /View Details/i });
|
|
251
|
-
await userEvent.click(actionButton);
|
|
252
|
-
await expect(args.items[0].action?.onClick).toHaveBeenCalledTimes(1);
|
|
253
|
-
|
|
254
|
-
// Click expand button to collapse
|
|
255
|
-
await userEvent.click(expandButtons[0]);
|
|
256
|
-
await waitFor(() => {
|
|
257
|
-
const description = canvas.queryByText(sampleItems[0].description!);
|
|
258
|
-
expect(description).not.toBeVisible();
|
|
259
|
-
}, { timeout: 3000 });
|
|
260
|
-
|
|
261
|
-
// Test clicking card with Enter key
|
|
262
|
-
// Click the card instead since cards aren't naturally focusable
|
|
263
|
-
await userEvent.click(cards[1]);
|
|
264
|
-
await expect(args.onItemClick).toHaveBeenCalledWith(sampleItems[1]);
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
export const ScreenReader: Story = {
|
|
269
|
-
args: {
|
|
270
|
-
items: sampleItems,
|
|
271
|
-
variant: 'detailed',
|
|
272
|
-
},
|
|
273
|
-
play: async ({ canvasElement }) => {
|
|
274
|
-
const canvas = within(canvasElement);
|
|
275
|
-
|
|
276
|
-
// Wait for timeline to render
|
|
277
|
-
await waitFor(() => {
|
|
278
|
-
const cards = canvas.getAllByRole('article');
|
|
279
|
-
expect(cards).toHaveLength(sampleItems.length);
|
|
280
|
-
}, { timeout: 3000 });
|
|
281
|
-
|
|
282
|
-
// Verify timeline structure is accessible with semantic HTML
|
|
283
|
-
const cards = canvas.getAllByRole('article');
|
|
284
|
-
await expect(cards).toHaveLength(sampleItems.length);
|
|
285
|
-
|
|
286
|
-
// Verify all text content is accessible
|
|
287
|
-
for (const item of sampleItems) {
|
|
288
|
-
// Title and timestamp should be readable
|
|
289
|
-
await expect(canvas.getByText(item.title)).toBeInTheDocument();
|
|
290
|
-
await expect(canvas.getByText(item.timestamp)).toBeInTheDocument();
|
|
291
|
-
|
|
292
|
-
// Verify heading hierarchy
|
|
293
|
-
const headings = canvasElement.querySelectorAll('h6');
|
|
294
|
-
const titleHeading = Array.from(headings).find(h => h.textContent === item.title);
|
|
295
|
-
await expect(titleHeading).toBeInTheDocument();
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Verify interactive elements have accessible names
|
|
299
|
-
const expandButtons = Array.from(canvasElement.querySelectorAll('button')).filter(
|
|
300
|
-
(btn) => btn.querySelector('svg[data-testid="ExpandMoreIcon"]')
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
for (const button of expandButtons) {
|
|
304
|
-
// Button should be focusable
|
|
305
|
-
await expect(button).not.toHaveAttribute('disabled');
|
|
306
|
-
await expect(button).not.toHaveAttribute('tabindex', '-1');
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Verify icons are decorative (aria-hidden) or have labels
|
|
310
|
-
const icons = canvasElement.querySelectorAll('svg');
|
|
311
|
-
await expect(icons.length).toBeGreaterThan(0);
|
|
312
|
-
|
|
313
|
-
// Timeline dots should have color indication
|
|
314
|
-
const dots = canvasElement.querySelectorAll('[data-testid="timeline-dot"]');
|
|
315
|
-
await expect(dots).toHaveLength(sampleItems.length);
|
|
316
|
-
|
|
317
|
-
for (let i = 0; i < dots.length; i++) {
|
|
318
|
-
const dot = dots[i] as HTMLElement;
|
|
319
|
-
const item = sampleItems[i];
|
|
320
|
-
if (item.color) {
|
|
321
|
-
const style = window.getComputedStyle(dot);
|
|
322
|
-
await expect(style.background).toContain('gradient');
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Verify connectors are purely decorative
|
|
327
|
-
const connectors = canvasElement.querySelectorAll('[data-testid="timeline-connector"]');
|
|
328
|
-
await expect(connectors).toHaveLength(sampleItems.length - 1);
|
|
329
|
-
},
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
export const FocusManagement: Story = {
|
|
333
|
-
args: {
|
|
334
|
-
items: sampleItems,
|
|
335
|
-
variant: 'default',
|
|
336
|
-
onItemClick: fn(),
|
|
337
|
-
},
|
|
338
|
-
play: async ({ canvasElement, args }) => {
|
|
339
|
-
const canvas = within(canvasElement);
|
|
340
|
-
|
|
341
|
-
// Wait for timeline to render
|
|
342
|
-
await waitFor(() => {
|
|
343
|
-
const cards = canvas.getAllByRole('article');
|
|
344
|
-
expect(cards).toHaveLength(sampleItems.length);
|
|
345
|
-
}, { timeout: 3000 });
|
|
346
|
-
|
|
347
|
-
// Get interactive elements
|
|
348
|
-
const expandButtons = Array.from(canvasElement.querySelectorAll('button')).filter(
|
|
349
|
-
(btn) => btn.querySelector('svg[data-testid="ExpandMoreIcon"]')
|
|
350
|
-
);
|
|
351
|
-
const cards = canvas.getAllByRole('article');
|
|
352
|
-
|
|
353
|
-
// Test focus retention on expand/collapse
|
|
354
|
-
await expect(expandButtons.length).toBeGreaterThan(0);
|
|
355
|
-
|
|
356
|
-
// Tab in from the body to reach the first expand button.
|
|
357
|
-
await userEvent.tab();
|
|
358
|
-
await waitFor(() => expect(expandButtons[0]).toHaveFocus());
|
|
359
|
-
|
|
360
|
-
// Click to expand
|
|
361
|
-
await userEvent.click(expandButtons[0]);
|
|
362
|
-
|
|
363
|
-
// Focus should remain on the button after expansion
|
|
364
|
-
await waitFor(() => expect(expandButtons[0]).toHaveFocus());
|
|
365
|
-
|
|
366
|
-
// Wait for expansion to complete
|
|
367
|
-
await waitFor(() => {
|
|
368
|
-
const description = canvas.getByText(sampleItems[0].description!);
|
|
369
|
-
expect(description).toBeVisible();
|
|
370
|
-
}, { timeout: 3000 });
|
|
371
|
-
|
|
372
|
-
// Now action button should be available
|
|
373
|
-
const actionButton = canvas.getByRole('button', { name: /View Details/i });
|
|
374
|
-
|
|
375
|
-
// Clicking focuses the button, so the pre-focus only asserted focus() works.
|
|
376
|
-
await userEvent.click(actionButton);
|
|
377
|
-
await waitFor(() => expect(actionButton).toHaveFocus());
|
|
378
|
-
await expect(args.items[0].action?.onClick).toHaveBeenCalled();
|
|
379
|
-
|
|
380
|
-
// Test focus on card click
|
|
381
|
-
await userEvent.click(cards[0]);
|
|
382
|
-
await expect(args.onItemClick).toHaveBeenCalled();
|
|
383
|
-
|
|
384
|
-
// Test focus trap prevention (focus can move outside component)
|
|
385
|
-
const lastExpandButton = expandButtons[expandButtons.length - 1];
|
|
386
|
-
|
|
387
|
-
// Walk the keyboard to the last expand button, then once more: focus has to
|
|
388
|
-
// leave the timeline rather than wrap back into it.
|
|
389
|
-
await userEvent.click(lastExpandButton);
|
|
390
|
-
await waitFor(() => expect(lastExpandButton).toHaveFocus());
|
|
391
|
-
|
|
392
|
-
await userEvent.tab();
|
|
393
|
-
await waitFor(() => expect(lastExpandButton).not.toHaveFocus());
|
|
394
|
-
|
|
395
|
-
// Clicking the second item's expand button focuses it and keeps focus there.
|
|
396
|
-
await userEvent.click(expandButtons[1]);
|
|
397
|
-
await waitFor(() => expect(expandButtons[1]).toHaveFocus());
|
|
398
|
-
|
|
399
|
-
// Verify multiple items can be expanded simultaneously
|
|
400
|
-
await waitFor(() => {
|
|
401
|
-
const firstDescription = canvas.getByText(sampleItems[0].description!);
|
|
402
|
-
const secondDescription = canvas.getByText(sampleItems[1].description!);
|
|
403
|
-
expect(firstDescription).toBeVisible();
|
|
404
|
-
expect(secondDescription).toBeVisible();
|
|
405
|
-
}, { timeout: 3000 });
|
|
406
|
-
},
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
export const ResponsiveDesign: Story = {
|
|
410
|
-
args: {
|
|
411
|
-
items: sampleItems,
|
|
412
|
-
variant: 'default',
|
|
413
|
-
orientation: 'vertical',
|
|
414
|
-
onItemClick: fn(),
|
|
415
|
-
},
|
|
416
|
-
parameters: {
|
|
417
|
-
viewport: { defaultViewport: 'mobile1' },
|
|
418
|
-
},
|
|
419
|
-
play: async ({ canvasElement }) => {
|
|
420
|
-
const canvas = within(canvasElement);
|
|
421
|
-
|
|
422
|
-
// Verify timeline renders on mobile - wait longer for mobile viewport
|
|
423
|
-
// Read the container after the wait rather than assigning to an outer
|
|
424
|
-
// binding from inside the callback — the callback reruns, so the binding
|
|
425
|
-
// held whichever value the last retry happened to leave behind.
|
|
426
|
-
await waitFor(() => {
|
|
427
|
-
expect(canvas.getByTestId('timeline-container')).toBeInTheDocument();
|
|
428
|
-
}, { timeout: 5000 });
|
|
429
|
-
const container = canvas.getByTestId('timeline-container');
|
|
430
|
-
|
|
431
|
-
// Wait for all items to render
|
|
432
|
-
await waitFor(() => {
|
|
433
|
-
const items = canvas.getAllByRole('article');
|
|
434
|
-
expect(items).toHaveLength(sampleItems.length);
|
|
435
|
-
}, { timeout: 3000 });
|
|
436
|
-
|
|
437
|
-
// Verify all items are rendered
|
|
438
|
-
const items = canvas.getAllByRole('article');
|
|
439
|
-
await expect(items).toHaveLength(sampleItems.length);
|
|
440
|
-
|
|
441
|
-
// Check vertical orientation layout
|
|
442
|
-
const containerStyle = window.getComputedStyle(container);
|
|
443
|
-
await expect(containerStyle.flexDirection).toBe('column');
|
|
444
|
-
await expect(containerStyle.display).toBe('flex');
|
|
445
|
-
|
|
446
|
-
// Verify items stack vertically with proper spacing
|
|
447
|
-
const itemContainers = canvas.getAllByTestId('timeline-item-container');
|
|
448
|
-
await expect(itemContainers).toHaveLength(sampleItems.length);
|
|
449
|
-
|
|
450
|
-
// Check each item's layout
|
|
451
|
-
for (let i = 0; i < itemContainers.length; i++) {
|
|
452
|
-
const itemContainer = itemContainers[i] as HTMLElement;
|
|
453
|
-
const itemStyle = window.getComputedStyle(itemContainer);
|
|
454
|
-
|
|
455
|
-
// Items should use flex layout
|
|
456
|
-
await expect(itemStyle.display).toBe('flex');
|
|
457
|
-
|
|
458
|
-
// Gap should be applied
|
|
459
|
-
await expect(itemStyle.gap).toBeTruthy();
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Verify text doesn't overflow on mobile
|
|
463
|
-
const titles = canvasElement.querySelectorAll('h6');
|
|
464
|
-
for (const title of titles) {
|
|
465
|
-
const titleElement = title as HTMLElement;
|
|
466
|
-
const rect = titleElement.getBoundingClientRect();
|
|
467
|
-
const containerRect = (container as HTMLElement).getBoundingClientRect();
|
|
468
|
-
|
|
469
|
-
// Title should be within container bounds
|
|
470
|
-
await expect(rect.right).toBeLessThanOrEqual(containerRect.right);
|
|
471
|
-
await expect(rect.left).toBeGreaterThanOrEqual(containerRect.left);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// Test touch interactions work on mobile
|
|
475
|
-
const firstCard = items[0];
|
|
476
|
-
await userEvent.click(firstCard);
|
|
477
|
-
|
|
478
|
-
// Expand buttons should be large enough for touch
|
|
479
|
-
const expandButtons = Array.from(canvasElement.querySelectorAll('button')).filter(
|
|
480
|
-
(btn) => btn.querySelector('svg[data-testid="ExpandMoreIcon"]')
|
|
481
|
-
);
|
|
482
|
-
|
|
483
|
-
for (const button of expandButtons) {
|
|
484
|
-
const rect = button.getBoundingClientRect();
|
|
485
|
-
// Minimum touch target size (44x44 pixels recommended)
|
|
486
|
-
await expect(rect.width).toBeGreaterThanOrEqual(32);
|
|
487
|
-
await expect(rect.height).toBeGreaterThanOrEqual(32);
|
|
488
|
-
}
|
|
489
|
-
},
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
export const ThemeVariations: Story = {
|
|
493
|
-
args: {
|
|
494
|
-
items: sampleItems,
|
|
495
|
-
variant: 'detailed',
|
|
496
|
-
},
|
|
497
|
-
parameters: {
|
|
498
|
-
backgrounds: { default: 'dark' },
|
|
499
|
-
},
|
|
500
|
-
play: async ({ canvasElement }) => {
|
|
501
|
-
const canvas = within(canvasElement);
|
|
502
|
-
|
|
503
|
-
// Wait for timeline to render
|
|
504
|
-
await waitFor(() => {
|
|
505
|
-
const cards = canvas.getAllByRole('article');
|
|
506
|
-
expect(cards).toHaveLength(sampleItems.length);
|
|
507
|
-
}, { timeout: 3000 });
|
|
508
|
-
|
|
509
|
-
// Verify timeline renders in dark theme
|
|
510
|
-
const cards = canvas.getAllByRole('article');
|
|
511
|
-
await expect(cards).toHaveLength(sampleItems.length);
|
|
512
|
-
|
|
513
|
-
// Verify custom colors are applied to timeline dots
|
|
514
|
-
const dots = canvas.getAllByTestId('timeline-dot');
|
|
515
|
-
await expect(dots).toHaveLength(sampleItems.length);
|
|
516
|
-
|
|
517
|
-
for (let i = 0; i < dots.length; i++) {
|
|
518
|
-
const dot = dots[i] as HTMLElement;
|
|
519
|
-
const item = sampleItems[i];
|
|
520
|
-
const style = window.getComputedStyle(dot);
|
|
521
|
-
|
|
522
|
-
// Verify gradient background is applied
|
|
523
|
-
await expect(style.background).toContain('gradient');
|
|
524
|
-
|
|
525
|
-
// Verify custom color is used if provided
|
|
526
|
-
if (item.color) {
|
|
527
|
-
// Check box-shadow uses the custom color
|
|
528
|
-
await expect(style.boxShadow).toBeTruthy();
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Verify border for contrast
|
|
532
|
-
await expect(style.border).toBeTruthy();
|
|
533
|
-
|
|
534
|
-
// Verify dot has proper dimensions
|
|
535
|
-
await expect(style.width).toBe('40px');
|
|
536
|
-
await expect(style.height).toBe('40px');
|
|
537
|
-
await expect(style.borderRadius).toBe('50%');
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Verify cards have glass morphism effect
|
|
541
|
-
const timelineCards = canvas.getAllByTestId('timeline-card');
|
|
542
|
-
for (const card of timelineCards) {
|
|
543
|
-
const cardElement = card as HTMLElement;
|
|
544
|
-
const style = window.getComputedStyle(cardElement) as ReturnType<typeof window.getComputedStyle> & { webkitBackdropFilter?: string };
|
|
545
|
-
|
|
546
|
-
// Check glass effect properties
|
|
547
|
-
await expect(style.backdropFilter || style.webkitBackdropFilter).toContain('blur');
|
|
548
|
-
await expect(style.background).toContain('gradient');
|
|
549
|
-
await expect(style.border).toBeTruthy();
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Verify text contrast in dark theme
|
|
553
|
-
const timestamps = canvas.getAllByTestId('timeline-timestamp');
|
|
554
|
-
for (const timestamp of timestamps) {
|
|
555
|
-
const element = timestamp as HTMLElement;
|
|
556
|
-
const style = window.getComputedStyle(element);
|
|
557
|
-
|
|
558
|
-
// Timestamp should have secondary text color
|
|
559
|
-
await expect(style.color).toBeTruthy();
|
|
560
|
-
await expect(style.fontSize).toBe('12px'); // Computed value, not CSS value
|
|
561
|
-
await expect(style.textTransform).toBe('uppercase');
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// Verify connector gradient
|
|
565
|
-
const connectors = canvas.getAllByTestId('timeline-connector');
|
|
566
|
-
for (const connector of connectors) {
|
|
567
|
-
const element = connector as HTMLElement;
|
|
568
|
-
const style = window.getComputedStyle(element);
|
|
569
|
-
await expect(style.background).toContain('gradient');
|
|
570
|
-
}
|
|
571
|
-
},
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
export const VisualStates: Story = {
|
|
575
|
-
args: {
|
|
576
|
-
items: [
|
|
577
|
-
...sampleItems,
|
|
578
|
-
{
|
|
579
|
-
id: '5',
|
|
580
|
-
title: 'Error State',
|
|
581
|
-
description: 'An error occurred',
|
|
582
|
-
timestamp: '2024-01-02 10:00 AM',
|
|
583
|
-
icon: <ErrorIcon />,
|
|
584
|
-
color: '#f44336',
|
|
585
|
-
},
|
|
586
|
-
],
|
|
587
|
-
variant: 'default',
|
|
588
|
-
animated: true,
|
|
589
|
-
alternating: true,
|
|
590
|
-
},
|
|
591
|
-
play: async ({ canvasElement }) => {
|
|
592
|
-
const canvas = within(canvasElement);
|
|
593
|
-
const expectedItemCount = sampleItems.length + 1; // Including the error state item
|
|
594
|
-
|
|
595
|
-
// Wait for timeline to render with animations
|
|
596
|
-
await waitFor(() => {
|
|
597
|
-
const cards = canvas.getAllByRole('article');
|
|
598
|
-
expect(cards).toHaveLength(expectedItemCount);
|
|
599
|
-
}, { timeout: 3000 });
|
|
600
|
-
|
|
601
|
-
// Verify all items including error state are rendered
|
|
602
|
-
const cards = canvas.getAllByRole('article');
|
|
603
|
-
await expect(cards).toHaveLength(expectedItemCount);
|
|
604
|
-
|
|
605
|
-
// Test hover state on cards - verify cursor is set for interactivity
|
|
606
|
-
const firstCard = cards[0];
|
|
607
|
-
const initialStyle = window.getComputedStyle(firstCard);
|
|
608
|
-
await expect(initialStyle.cursor).toBe('pointer');
|
|
609
|
-
|
|
610
|
-
// Note: userEvent.hover() doesn't trigger CSS :hover pseudo-class
|
|
611
|
-
// The hover transform is verified visually and applied via CSS :hover
|
|
612
|
-
|
|
613
|
-
// Verify alternating layout
|
|
614
|
-
const itemContainers = canvas.getAllByTestId('timeline-item-container');
|
|
615
|
-
await expect(itemContainers).toHaveLength(expectedItemCount);
|
|
616
|
-
|
|
617
|
-
for (let i = 0; i < itemContainers.length; i++) {
|
|
618
|
-
const container = itemContainers[i] as HTMLElement;
|
|
619
|
-
const style = window.getComputedStyle(container);
|
|
620
|
-
|
|
621
|
-
// Check alternating flex direction
|
|
622
|
-
if (i % 2 === 0) {
|
|
623
|
-
await expect(style.flexDirection).toBe('row');
|
|
624
|
-
await expect(style.textAlign).toBe('left');
|
|
625
|
-
} else {
|
|
626
|
-
await expect(style.flexDirection).toBe('row-reverse');
|
|
627
|
-
await expect(style.textAlign).toBe('right');
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Verify animations are applied
|
|
632
|
-
for (let i = 0; i < itemContainers.length; i++) {
|
|
633
|
-
const container = itemContainers[i] as HTMLElement;
|
|
634
|
-
const style = window.getComputedStyle(container);
|
|
635
|
-
|
|
636
|
-
// Check slide-in animation with staggered delay
|
|
637
|
-
await expect(style.animation).toContain('0.5s');
|
|
638
|
-
await expect(style.animation).toContain('ease');
|
|
639
|
-
// Animation delay varies per item - just check it has some delay
|
|
640
|
-
await expect(style.animation).toBeTruthy();
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Verify error state styling
|
|
644
|
-
const allDots = canvas.getAllByTestId('timeline-dot');
|
|
645
|
-
const errorDot = allDots[4] as HTMLElement;
|
|
646
|
-
const errorStyle = window.getComputedStyle(errorDot);
|
|
647
|
-
await expect(errorStyle.background).toContain('gradient');
|
|
648
|
-
|
|
649
|
-
// Verify pulse animation on dots
|
|
650
|
-
for (const dot of allDots) {
|
|
651
|
-
const dotElement = dot as HTMLElement;
|
|
652
|
-
const style = window.getComputedStyle(dotElement);
|
|
653
|
-
await expect(style.animation).toContain('2s');
|
|
654
|
-
await expect(style.animation).toContain('ease');
|
|
655
|
-
await expect(style.animation).toContain('infinite');
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Test different icon states
|
|
659
|
-
const icons = canvasElement.querySelectorAll('svg');
|
|
660
|
-
await expect(icons.length).toBeGreaterThan(0);
|
|
661
|
-
|
|
662
|
-
// Verify icon colors within dots
|
|
663
|
-
for (const dot of allDots) {
|
|
664
|
-
const icon = dot.querySelector('svg');
|
|
665
|
-
if (icon) {
|
|
666
|
-
const iconStyle = window.getComputedStyle(icon);
|
|
667
|
-
await expect(iconStyle.fontSize).toBeTruthy();
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
},
|
|
671
|
-
};
|
|
672
|
-
|
|
673
|
-
export const Performance: Story = {
|
|
674
|
-
args: {
|
|
675
|
-
items: Array.from({ length: 50 }, (_, i) => ({
|
|
676
|
-
id: `${i}`,
|
|
677
|
-
title: `Event ${i + 1}`,
|
|
678
|
-
description: `Description for event ${i + 1}`,
|
|
679
|
-
timestamp: `2024-01-01 ${String(i % 12).padStart(2, '0')}:00 ${i < 12 ? 'AM' : 'PM'}`,
|
|
680
|
-
metadata: { Index: `${i}`, Category: `Category ${i % 5}` },
|
|
681
|
-
})),
|
|
682
|
-
variant: 'compact',
|
|
683
|
-
animated: false,
|
|
684
|
-
onItemClick: fn(),
|
|
685
|
-
},
|
|
686
|
-
play: async ({ canvasElement, args }) => {
|
|
687
|
-
const canvas = within(canvasElement);
|
|
688
|
-
|
|
689
|
-
// Wait for all items to render
|
|
690
|
-
await waitFor(() => {
|
|
691
|
-
const items = canvas.getAllByRole('article');
|
|
692
|
-
expect(items).toHaveLength(50);
|
|
693
|
-
}, { timeout: 3000 });
|
|
694
|
-
|
|
695
|
-
// Verify large number of items render correctly
|
|
696
|
-
const items = canvas.getAllByRole('article');
|
|
697
|
-
await expect(items).toHaveLength(50);
|
|
698
|
-
|
|
699
|
-
// Verify compact variant styling
|
|
700
|
-
const timelineCards = canvas.getAllByTestId('timeline-card');
|
|
701
|
-
for (const card of timelineCards.slice(0, 5)) { // Check first 5 for performance
|
|
702
|
-
const cardElement = card as HTMLElement;
|
|
703
|
-
// Compact variant should have reduced padding
|
|
704
|
-
// Skip checking style directly as it's not used beyond content check
|
|
705
|
-
const contentElement = cardElement.querySelector('.MuiCardContent-root') as HTMLElement;
|
|
706
|
-
if (contentElement) {
|
|
707
|
-
const contentStyle = window.getComputedStyle(contentElement);
|
|
708
|
-
await expect(contentStyle.padding).toBe('0px');
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// Test animations are disabled
|
|
713
|
-
const itemContainers = canvas.getAllByTestId('timeline-item-container');
|
|
714
|
-
for (const container of itemContainers.slice(0, 5)) { // Check first 5
|
|
715
|
-
const element = container as HTMLElement;
|
|
716
|
-
const style = window.getComputedStyle(element);
|
|
717
|
-
|
|
718
|
-
// No animation should be applied when animated=false
|
|
719
|
-
await expect(style.animation).toContain('none');
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
// Click multiple items quickly. No wall-clock budget: a 1s ceiling on three
|
|
723
|
-
// clicks measures how loaded the runner is, not how the component performs.
|
|
724
|
-
await userEvent.click(items[0]);
|
|
725
|
-
await userEvent.click(items[10]);
|
|
726
|
-
await userEvent.click(items[20]);
|
|
727
|
-
|
|
728
|
-
// Verify clicks were registered
|
|
729
|
-
await expect(args.onItemClick).toHaveBeenCalledTimes(3);
|
|
730
|
-
|
|
731
|
-
// Test scrolling performance for vertical timeline - ensure container exists
|
|
732
|
-
await waitFor(() => {
|
|
733
|
-
const container = canvas.getByTestId('timeline-container');
|
|
734
|
-
expect(container).toBeInTheDocument();
|
|
735
|
-
}, { timeout: 3000 });
|
|
736
|
-
|
|
737
|
-
// Verify all titles are rendered correctly
|
|
738
|
-
for (let i = 0; i < 5; i++) { // Check first 5 items
|
|
739
|
-
await expect(canvas.getByText(`Event ${i + 1}`)).toBeInTheDocument();
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
// In compact mode, descriptions should still be visible
|
|
743
|
-
for (let i = 0; i < 3; i++) { // Check first 3
|
|
744
|
-
await expect(canvas.getByText(`Description for event ${i + 1}`)).toBeInTheDocument();
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// In compact mode, metadata chips are NOT rendered (they require expanded state or detailed variant)
|
|
748
|
-
// Verify no metadata chips are present
|
|
749
|
-
const metadataChips = canvasElement.querySelectorAll('[class*="MuiChip"]');
|
|
750
|
-
await expect(metadataChips.length).toBe(0);
|
|
751
|
-
|
|
752
|
-
// Test connectors are rendered efficiently
|
|
753
|
-
const connectors = canvas.getAllByTestId('timeline-connector');
|
|
754
|
-
await expect(connectors).toHaveLength(49); // 50 items = 49 connectors
|
|
755
|
-
},
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
export const EdgeCases: Story = {
|
|
759
|
-
args: {
|
|
760
|
-
items: [
|
|
761
|
-
{
|
|
762
|
-
id: '1',
|
|
763
|
-
title:
|
|
764
|
-
'Very long title that should wrap properly and not break the layout even when it contains many words',
|
|
765
|
-
description:
|
|
766
|
-
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.',
|
|
767
|
-
timestamp: '2024-12-31 11:59 PM',
|
|
768
|
-
metadata: {
|
|
769
|
-
'Long Key Name': 'Long value that might need truncation',
|
|
770
|
-
'Another Key': 'Another value',
|
|
771
|
-
'Third Key': 'Third value',
|
|
772
|
-
'Fourth Key': 'Fourth value',
|
|
773
|
-
},
|
|
774
|
-
},
|
|
775
|
-
{
|
|
776
|
-
id: '2',
|
|
777
|
-
title: '',
|
|
778
|
-
timestamp: '',
|
|
779
|
-
},
|
|
780
|
-
{
|
|
781
|
-
id: '3',
|
|
782
|
-
title: 'Minimal item',
|
|
783
|
-
timestamp: 'Now',
|
|
784
|
-
},
|
|
785
|
-
],
|
|
786
|
-
variant: 'detailed',
|
|
787
|
-
onItemClick: fn(),
|
|
788
|
-
},
|
|
789
|
-
play: async ({ canvasElement, args }) => {
|
|
790
|
-
const canvas = within(canvasElement);
|
|
791
|
-
|
|
792
|
-
// Wait for timeline to render using a more robust query
|
|
793
|
-
const items = await canvas.findAllByRole('article', {}, { timeout: 3000 });
|
|
794
|
-
expect(items).toHaveLength(3);
|
|
795
|
-
|
|
796
|
-
// Verify long title wraps properly
|
|
797
|
-
const longTitleText =
|
|
798
|
-
'Very long title that should wrap properly and not break the layout even when it contains many words';
|
|
799
|
-
const longTitleElement = await canvas.findByText(longTitleText);
|
|
800
|
-
expect(longTitleElement).toBeInTheDocument();
|
|
801
|
-
|
|
802
|
-
const titleStyle = window.getComputedStyle(longTitleElement);
|
|
803
|
-
const titleRect = longTitleElement.getBoundingClientRect();
|
|
804
|
-
const cardRect = longTitleElement.closest('[data-testid="timeline-card"]')?.getBoundingClientRect();
|
|
805
|
-
|
|
806
|
-
if (cardRect) {
|
|
807
|
-
expect(titleRect.width).toBeLessThanOrEqual(cardRect.width);
|
|
808
|
-
}
|
|
809
|
-
expect(titleStyle.overflowWrap).toBeTruthy();
|
|
810
|
-
|
|
811
|
-
// Wait for the animation to complete by putting the visibility assertion inside waitFor.
|
|
812
|
-
const longDescription =
|
|
813
|
-
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.';
|
|
814
|
-
|
|
815
|
-
await waitFor(() => {
|
|
816
|
-
expect(canvas.getByText(longDescription)).toBeVisible();
|
|
817
|
-
}, { timeout: 3000 });
|
|
818
|
-
|
|
819
|
-
// Test empty title and timestamp handling
|
|
820
|
-
const emptyItemCard = items[1];
|
|
821
|
-
expect(emptyItemCard).toBeInTheDocument();
|
|
822
|
-
|
|
823
|
-
await userEvent.click(emptyItemCard);
|
|
824
|
-
await expect(args.onItemClick).toHaveBeenCalledWith(expect.objectContaining({ id: '2' }));
|
|
825
|
-
|
|
826
|
-
// Verify minimal item renders correctly
|
|
827
|
-
await expect(canvas.getByText('Minimal item')).toBeInTheDocument();
|
|
828
|
-
await expect(canvas.getByText('Now')).toBeInTheDocument();
|
|
829
|
-
|
|
830
|
-
// Test metadata chip rendering and overflow - wait for them to appear
|
|
831
|
-
const metadataChips = await canvas.findAllByText(/Key Name|Another Key|Third Key|Fourth Key/, {}, { timeout: 3000 });
|
|
832
|
-
expect(metadataChips).toHaveLength(4);
|
|
833
|
-
|
|
834
|
-
const metadataContainer = metadataChips[0]?.parentElement?.parentElement;
|
|
835
|
-
if (metadataContainer) {
|
|
836
|
-
await waitFor(() => {
|
|
837
|
-
const containerStyle = window.getComputedStyle(metadataContainer as HTMLElement);
|
|
838
|
-
expect(containerStyle.flexWrap).toBe('wrap');
|
|
839
|
-
});
|
|
840
|
-
const finalContainerStyle = window.getComputedStyle(metadataContainer as HTMLElement);
|
|
841
|
-
expect(finalContainerStyle.display).toBe('flex');
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
// Test timeline dots and connectors using test-ids for resilience
|
|
845
|
-
const dots = await canvas.findAllByTestId('timeline-dot');
|
|
846
|
-
expect(dots).toHaveLength(3);
|
|
847
|
-
|
|
848
|
-
const connectors = await canvas.findAllByTestId('timeline-connector');
|
|
849
|
-
expect(connectors).toHaveLength(2);
|
|
850
|
-
|
|
851
|
-
// Verify one expand button exists for the item with a description.
|
|
852
|
-
const expandButtons = canvas.queryAllByRole('button', { name: /expand item details/i });
|
|
853
|
-
expect(expandButtons).toHaveLength(1);
|
|
854
|
-
},
|
|
855
|
-
};
|
|
856
|
-
|
|
857
|
-
export const Integration: Story = {
|
|
858
|
-
args: {
|
|
859
|
-
items: sampleItems,
|
|
860
|
-
variant: 'default',
|
|
861
|
-
orientation: 'horizontal',
|
|
862
|
-
showConnector: true,
|
|
863
|
-
animated: true,
|
|
864
|
-
alternating: false,
|
|
865
|
-
onItemClick: fn(),
|
|
866
|
-
},
|
|
867
|
-
play: async ({ canvasElement, args }) => {
|
|
868
|
-
const canvas = within(canvasElement);
|
|
869
|
-
|
|
870
|
-
// 1. Wait for the main container to appear using its test-id.
|
|
871
|
-
const container = await canvas.findByTestId('timeline-container');
|
|
872
|
-
|
|
873
|
-
// 2. Assert styles on the now-guaranteed-to-exist container.
|
|
874
|
-
const containerStyle = window.getComputedStyle(container);
|
|
875
|
-
expect(containerStyle.flexDirection).toBe('row');
|
|
876
|
-
expect(containerStyle.overflowX).toBe('auto');
|
|
877
|
-
|
|
878
|
-
// 3. Wait for all item containers to render.
|
|
879
|
-
const itemContainers = await canvas.findAllByTestId('timeline-item-container');
|
|
880
|
-
expect(itemContainers).toHaveLength(sampleItems.length);
|
|
881
|
-
|
|
882
|
-
for (const itemContainer of itemContainers) {
|
|
883
|
-
const style = window.getComputedStyle(itemContainer);
|
|
884
|
-
expect(style.flexDirection).toBe('column');
|
|
885
|
-
expect(style.alignItems).toBe('center');
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
// 4. Test all interactive features work together
|
|
889
|
-
const cards = await canvas.findAllByRole('article');
|
|
890
|
-
await userEvent.click(cards[0]);
|
|
891
|
-
expect(args.onItemClick).toHaveBeenCalledWith(sampleItems[0]);
|
|
892
|
-
expect(args.onItemClick).toHaveBeenCalledTimes(1);
|
|
893
|
-
|
|
894
|
-
// 5. Expand an item by finding its accessible button label
|
|
895
|
-
const expandButtons = await canvas.findAllByRole('button', {
|
|
896
|
-
name: /Expand item details/i,
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
if (expandButtons.length > 0) {
|
|
900
|
-
await userEvent.click(expandButtons[0]);
|
|
901
|
-
// Wait for description to appear and become visible (animations take time)
|
|
902
|
-
await waitFor(async () => {
|
|
903
|
-
const description = await canvas.findByText(sampleItems[0].description!);
|
|
904
|
-
expect(description).toBeVisible();
|
|
905
|
-
}, { timeout: 3000 });
|
|
906
|
-
|
|
907
|
-
// Find and click the action button that is now visible
|
|
908
|
-
const actionButton = await canvas.findByRole('button', { name: /View Details/i });
|
|
909
|
-
await userEvent.click(actionButton);
|
|
910
|
-
expect(sampleItems[0].action?.onClick).toHaveBeenCalled();
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// 6. Verify all visual elements are properly rendered, waiting for each one.
|
|
914
|
-
const dots = await canvas.findAllByTestId('timeline-dot');
|
|
915
|
-
expect(dots).toHaveLength(sampleItems.length);
|
|
916
|
-
|
|
917
|
-
// Asynchronously verify that each dot contains an icon
|
|
918
|
-
for (const dot of dots) {
|
|
919
|
-
// Use `within` to scope the search inside each dot and wait for the SVG to appear.
|
|
920
|
-
// This is the key fix for the original error.
|
|
921
|
-
const icon = await within(dot).findByTestId(/item-icon/);
|
|
922
|
-
expect(icon).toBeInTheDocument();
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
// 7. Test scrolling behavior for horizontal timeline
|
|
926
|
-
if (container.scrollWidth > container.clientWidth) {
|
|
927
|
-
expect(container.scrollWidth).toBeGreaterThan(container.clientWidth);
|
|
928
|
-
}
|
|
929
|
-
},
|
|
930
|
-
};
|