@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.
Files changed (229) hide show
  1. package/package.json +17 -9
  2. package/src/button/index.ts +1 -0
  3. package/src/social-login-button/index.ts +1 -0
  4. package/src/{social-login-button.tsx → social-login-button/social-login-button.tsx} +5 -5
  5. package/src/user-avatar/index.ts +1 -0
  6. package/src/{user-avatar.tsx → user-avatar/user-avatar.tsx} +4 -4
  7. package/eslint.config.js +0 -89
  8. package/src/__tests__/data-grid.test.tsx +0 -125
  9. package/src/__tests__/package-wiring.test.ts +0 -41
  10. package/src/__tests__/setup.ts +0 -1
  11. package/src/__tests__/social-login-button.test.tsx +0 -186
  12. package/src/__tests__/user-avatar.test.tsx +0 -195
  13. package/src/__tests__/viewport.test.ts +0 -50
  14. package/src/components/command/__tests__/command.test.tsx +0 -112
  15. package/src/components/data-display/Alert/Alert.stories.tsx +0 -559
  16. package/src/components/data-display/Alert/Alert.test.stories.tsx +0 -707
  17. package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +0 -362
  18. package/src/components/data-display/AlertDialog/AlertDialog.test.stories.tsx +0 -763
  19. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.stories.tsx +0 -230
  20. package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +0 -270
  21. package/src/components/data-display/Avatar/Avatar.stories.tsx +0 -721
  22. package/src/components/data-display/Avatar/Avatar.test.stories.tsx +0 -822
  23. package/src/components/data-display/Badge/Badge.stories.tsx +0 -1134
  24. package/src/components/data-display/Badge/Badge.test.stories.tsx +0 -1194
  25. package/src/components/data-display/Banner/Banner.stories.tsx +0 -528
  26. package/src/components/data-display/Banner/Banner.test.stories.tsx +0 -511
  27. package/src/components/data-display/Carousel/Carousel.stories.tsx +0 -607
  28. package/src/components/data-display/Carousel/Carousel.test.stories.tsx +0 -701
  29. package/src/components/data-display/Chart/Chart.stories.tsx +0 -568
  30. package/src/components/data-display/Chart/Chart.test.stories.tsx +0 -174
  31. package/src/components/data-display/Chart/SpecChart.stories.tsx +0 -150
  32. package/src/components/data-display/Chart/SpecChart.test.stories.tsx +0 -56
  33. package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +0 -60
  34. package/src/components/data-display/Chart/__tests__/chart-format.test.ts +0 -43
  35. package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +0 -119
  36. package/src/components/data-display/Chip/Chip.stories.tsx +0 -252
  37. package/src/components/data-display/Chip/Chip.test.stories.tsx +0 -266
  38. package/src/components/data-display/DataGrid/DataGrid.stories.tsx +0 -515
  39. package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +0 -653
  40. package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +0 -271
  41. package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +0 -134
  42. package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +0 -83
  43. package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +0 -115
  44. package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +0 -53
  45. package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +0 -114
  46. package/src/components/data-display/DataViews/base-card.stories.tsx +0 -136
  47. package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +0 -254
  48. package/src/components/data-display/EmptyState/EmptyState.stories.tsx +0 -282
  49. package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +0 -441
  50. package/src/components/data-display/ErrorState/ErrorState.stories.tsx +0 -110
  51. package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +0 -133
  52. package/src/components/data-display/HoverCard/HoverCard.stories.tsx +0 -650
  53. package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +0 -929
  54. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.stories.tsx +0 -411
  55. package/src/components/data-display/LazyImage/LazyImage.stories.tsx +0 -387
  56. package/src/components/data-display/Lightbox/Lightbox.stories.tsx +0 -315
  57. package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +0 -598
  58. package/src/components/data-display/LoadingState/LoadingState.stories.tsx +0 -124
  59. package/src/components/data-display/LoadingState/LoadingState.test.stories.tsx +0 -99
  60. package/src/components/data-display/MapPreview/MapPreview.stories.tsx +0 -458
  61. package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +0 -394
  62. package/src/components/data-display/Popover/Popover.stories.tsx +0 -295
  63. package/src/components/data-display/Popover/Popover.test.stories.tsx +0 -1162
  64. package/src/components/data-display/Progress/Progress.stories.tsx +0 -481
  65. package/src/components/data-display/Progress/Progress.test.stories.tsx +0 -2070
  66. package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +0 -160
  67. package/src/components/data-display/SectionOnboarding/SectionOnboarding.test.stories.tsx +0 -190
  68. package/src/components/data-display/Sheet/Sheet.stories.tsx +0 -883
  69. package/src/components/data-display/Sheet/Sheet.test.stories.tsx +0 -1105
  70. package/src/components/data-display/StatCard/StatCard.stories.tsx +0 -167
  71. package/src/components/data-display/StatCard/StatCard.test.stories.tsx +0 -240
  72. package/src/components/data-display/Stepper/Stepper.stories.tsx +0 -540
  73. package/src/components/data-display/Stepper/Stepper.test.stories.tsx +0 -600
  74. package/src/components/data-display/Table/Table.stories.tsx +0 -803
  75. package/src/components/data-display/Table/Table.test.stories.tsx +0 -369
  76. package/src/components/data-display/Timeline/Timeline.stories.tsx +0 -442
  77. package/src/components/data-display/Timeline/Timeline.test.stories.tsx +0 -930
  78. package/src/components/data-display/TimingDiagram/TimingDiagram.stories.tsx +0 -475
  79. package/src/components/data-display/TimingDiagram/TimingDiagram.test.stories.tsx +0 -734
  80. package/src/components/data-display/Tooltip/Tooltip.stories.tsx +0 -468
  81. package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +0 -1154
  82. package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +0 -146
  83. package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +0 -114
  84. package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +0 -288
  85. package/src/components/feedback/Dialog/Dialog.stories.tsx +0 -833
  86. package/src/components/feedback/Dialog/Dialog.test.stories.tsx +0 -964
  87. package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +0 -66
  88. package/src/components/feedback/Modal/Modal.stories.tsx +0 -765
  89. package/src/components/feedback/Modal/Modal.test.stories.tsx +0 -251
  90. package/src/components/feedback/Sonner/Sonner.stories.tsx +0 -698
  91. package/src/components/feedback/Sonner/Sonner.test.stories.tsx +0 -896
  92. package/src/components/feedback/StackedModal/StackedModal.stories.tsx +0 -1783
  93. package/src/components/feedback/StackedModal/StackedModal.test.stories.tsx +0 -973
  94. package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +0 -104
  95. package/src/components/feedback/Toast/Toast.stories.tsx +0 -548
  96. package/src/components/feedback/Toast/Toast.test.stories.tsx +0 -834
  97. package/src/components/feedback/TutorialOverlay/TutorialOverlay.stories.tsx +0 -691
  98. package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +0 -634
  99. package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +0 -665
  100. package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +0 -727
  101. package/src/components/form/Autocomplete/Autocomplete.stories.tsx +0 -715
  102. package/src/components/form/Autocomplete/Autocomplete.test.stories.tsx +0 -825
  103. package/src/components/form/Button/Button.stories.tsx +0 -365
  104. package/src/components/form/Button/Button.test.stories.tsx +0 -607
  105. package/src/components/form/Calendar/Calendar.stories.tsx +0 -349
  106. package/src/components/form/Calendar/Calendar.test.stories.tsx +0 -464
  107. package/src/components/form/CepField/CepField.stories.tsx +0 -138
  108. package/src/components/form/CepField/CepField.test.stories.tsx +0 -281
  109. package/src/components/form/Checkbox/Checkbox.stories.tsx +0 -265
  110. package/src/components/form/Checkbox/Checkbox.test.stories.tsx +0 -181
  111. package/src/components/form/CodeEditor/CodeEditor.stories.tsx +0 -871
  112. package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +0 -574
  113. package/src/components/form/Command/Command.stories.tsx +0 -580
  114. package/src/components/form/Command/Command.test.stories.tsx +0 -215
  115. package/src/components/form/CreatableSelect/CreatableSelect.stories.tsx +0 -112
  116. package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +0 -134
  117. package/src/components/form/Form/Form.stories.tsx +0 -687
  118. package/src/components/form/Form/Form.test.stories.tsx +0 -601
  119. package/src/components/form/Input/Input.stories.tsx +0 -260
  120. package/src/components/form/Input/Input.test.stories.tsx +0 -1191
  121. package/src/components/form/InputOTP/InputOTP.stories.tsx +0 -774
  122. package/src/components/form/InputOTP/InputOTP.test.stories.tsx +0 -588
  123. package/src/components/form/Label/Label.stories.tsx +0 -473
  124. package/src/components/form/Label/Label.test.stories.tsx +0 -915
  125. package/src/components/form/Menubar/Menubar.stories.tsx +0 -472
  126. package/src/components/form/Menubar/Menubar.test.stories.tsx +0 -584
  127. package/src/components/form/PasswordStrength/PasswordStrength.stories.tsx +0 -562
  128. package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +0 -327
  129. package/src/components/form/PhoneInput/PhoneInput.stories.tsx +0 -495
  130. package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +0 -666
  131. package/src/components/form/RadioGroup/RadioGroup.stories.tsx +0 -518
  132. package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +0 -685
  133. package/src/components/form/RichTextEditor/RichTextEditor.stories.tsx +0 -261
  134. package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +0 -491
  135. package/src/components/form/SearchPalette/SearchPalette.stories.tsx +0 -135
  136. package/src/components/form/Select/Select.stories.tsx +0 -364
  137. package/src/components/form/Select/Select.test.stories.tsx +0 -985
  138. package/src/components/form/Slider/Slider.stories.tsx +0 -418
  139. package/src/components/form/Slider/Slider.test.stories.tsx +0 -1042
  140. package/src/components/form/Switch/Switch.stories.tsx +0 -697
  141. package/src/components/form/Switch/Switch.test.stories.tsx +0 -597
  142. package/src/components/form/Textarea/Textarea.stories.tsx +0 -473
  143. package/src/components/form/Textarea/Textarea.test.stories.tsx +0 -702
  144. package/src/components/form/Toggle/Toggle.stories.tsx +0 -638
  145. package/src/components/form/Toggle/Toggle.test.stories.tsx +0 -1159
  146. package/src/components/form/ToggleGroup/ToggleGroup.stories.tsx +0 -617
  147. package/src/components/form/ToggleGroup/ToggleGroup.test.stories.tsx +0 -915
  148. package/src/components/form/UploadButton/UploadButton.stories.tsx +0 -367
  149. package/src/components/form/UploadButton/UploadButton.test.stories.tsx +0 -573
  150. package/src/components/form/total-form/total-form.test.tsx +0 -214
  151. package/src/components/layout/Accordion/Accordion.stories.tsx +0 -694
  152. package/src/components/layout/Accordion/Accordion.test.stories.tsx +0 -1344
  153. package/src/components/layout/Card/Card.stories.tsx +0 -591
  154. package/src/components/layout/Card/Card.test.stories.tsx +0 -1134
  155. package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +0 -92
  156. package/src/components/layout/Collapsible/Collapsible.stories.tsx +0 -180
  157. package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +0 -308
  158. package/src/components/layout/Container/Container.stories.tsx +0 -557
  159. package/src/components/layout/Container/Container.test.stories.tsx +0 -745
  160. package/src/components/layout/ContentToolbar/ContentToolbar.stories.tsx +0 -129
  161. package/src/components/layout/ContentToolbar/ContentToolbar.test.stories.tsx +0 -251
  162. package/src/components/layout/ContentToolbar/__tests__/ColumnsMenu.test.tsx +0 -39
  163. package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +0 -131
  164. package/src/components/layout/ContentToolbar/__tests__/SavedViewsMenu.test.tsx +0 -99
  165. package/src/components/layout/Dashboard/Dashboard.stories.tsx +0 -104
  166. package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +0 -190
  167. package/src/components/layout/Drawer/Drawer.stories.tsx +0 -658
  168. package/src/components/layout/Drawer/Drawer.test.stories.tsx +0 -1343
  169. package/src/components/layout/Resizable/Resizable.stories.tsx +0 -1039
  170. package/src/components/layout/Resizable/Resizable.test.stories.tsx +0 -754
  171. package/src/components/layout/ScrollArea/ScrollArea.stories.tsx +0 -651
  172. package/src/components/layout/ScrollArea/ScrollArea.test.stories.tsx +0 -619
  173. package/src/components/layout/Separator/Separator.stories.tsx +0 -563
  174. package/src/components/layout/Separator/Separator.test.stories.tsx +0 -518
  175. package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +0 -192
  176. package/src/components/layout/SettingsLayout/SettingsLayout.test.stories.tsx +0 -175
  177. package/src/components/layout/Sidebar/Sidebar.stories.tsx +0 -609
  178. package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +0 -1324
  179. package/src/components/layout/Skeleton/Skeleton.stories.tsx +0 -689
  180. package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +0 -521
  181. package/src/components/layout/Spacer/Spacer.stories.tsx +0 -502
  182. package/src/components/layout/Spacer/Spacer.test.stories.tsx +0 -474
  183. package/src/components/layout/TableFilter/TableFilter.stories.tsx +0 -82
  184. package/src/components/layout/TableFilter/TableFilter.test.stories.tsx +0 -87
  185. package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +0 -638
  186. package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +0 -1008
  187. package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +0 -386
  188. package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +0 -435
  189. package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +0 -614
  190. package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +0 -845
  191. package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +0 -578
  192. package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +0 -660
  193. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +0 -516
  194. package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +0 -935
  195. package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +0 -501
  196. package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +0 -453
  197. package/src/components/navigation/Pagination/Pagination.stories.tsx +0 -602
  198. package/src/components/navigation/Pagination/Pagination.test.stories.tsx +0 -520
  199. package/src/components/navigation/Tabs/Tabs.stories.tsx +0 -1004
  200. package/src/components/navigation/Tabs/Tabs.test.stories.tsx +0 -1225
  201. package/src/components/typography/Blockquote/Blockquote.stories.tsx +0 -359
  202. package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +0 -432
  203. package/src/components/typography/Code/Code.stories.tsx +0 -686
  204. package/src/components/typography/Code/Code.test.stories.tsx +0 -703
  205. package/src/components/typography/Heading/Heading.stories.tsx +0 -451
  206. package/src/components/typography/Heading/Heading.test.stories.tsx +0 -830
  207. package/src/components/typography/Paragraph/Paragraph.stories.tsx +0 -280
  208. package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +0 -231
  209. package/src/components/typography/Text/Text.stories.tsx +0 -359
  210. package/src/components/typography/Text/Text.test.stories.tsx +0 -759
  211. package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +0 -945
  212. package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +0 -603
  213. package/src/components/utility/AspectRatio/AspectRatio.stories.tsx +0 -414
  214. package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +0 -859
  215. package/src/components/utility/InfiniteScroll/InfiniteScroll.stories.tsx +0 -620
  216. package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +0 -361
  217. package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +0 -118
  218. package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +0 -83
  219. package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +0 -65
  220. package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +0 -271
  221. package/src/components/utility/Portal/Portal.stories.tsx +0 -340
  222. package/src/components/utility/Portal/Portal.test.stories.tsx +0 -2086
  223. package/src/components/utility/Transition/Transition.stories.tsx +0 -493
  224. package/src/components/utility/Transition/Transition.test.stories.tsx +0 -854
  225. package/src/components/utility/VirtualList/VirtualList.stories.tsx +0 -1073
  226. package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +0 -1085
  227. package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +0 -431
  228. package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +0 -364
  229. /package/src/{button.tsx → button/button.tsx} +0 -0
@@ -1,2070 +0,0 @@
1
- import { Box, Button, Stack,Typography } from '@mui/material';
2
- import type { Meta, StoryObj } from '@storybook/react-vite';
3
- import React from 'react';
4
- import { expect, userEvent, waitFor,within } from 'storybook/test';
5
-
6
- import { Progress } from './Progress';
7
-
8
- const meta: Meta<typeof Progress> = {
9
- title: 'DataDisplay/Progress/Tests',
10
- component: Progress,
11
- parameters: {
12
- layout: 'centered',
13
- chromatic: { disableSnapshot: false },
14
- },
15
- tags: ['autodocs', 'test', 'component:Progress', 'checked'],
16
- };
17
-
18
- export default meta;
19
- type Story = StoryObj<typeof meta>;
20
-
21
- // Basic Interaction Tests
22
- export const BasicInteraction: Story = {
23
- name: '🧪 Basic Interaction Test',
24
- args: {
25
- value: 50,
26
- showLabel: true,
27
- },
28
- render: (args) => (
29
- <Box data-testid="progress-container" sx={{ width: 300, p: 2 }}>
30
- <Progress {...args} data-testid="progress-component" />
31
- <Typography variant="caption" sx={{ mt: 1, display: 'block' }}>
32
- Progress: {args.value}%
33
- </Typography>
34
- </Box>
35
- ),
36
- play: async ({ canvasElement, step }) => {
37
- const canvas = within(canvasElement);
38
-
39
- await step('Initial render verification', async () => {
40
- const container = canvas.getByTestId('progress-container');
41
- await expect(container).toBeInTheDocument();
42
-
43
- const progressComponent = canvas.getByTestId('progress-component');
44
- await expect(progressComponent).toBeInTheDocument();
45
-
46
- // Check for progress value display
47
- const label = canvas.getByText('50%');
48
- await expect(label).toBeInTheDocument();
49
- });
50
-
51
- await step('Progress component properties', async () => {
52
- const progressComponent = canvas.getByTestId('progress-component');
53
- await expect(progressComponent).toHaveAttribute('data-testid', 'progress-component');
54
-
55
- // Verify progress bar is visible
56
- const progressBar = progressComponent.querySelector('.MuiLinearProgress-bar');
57
- await expect(progressBar).toBeInTheDocument();
58
-
59
- // Check that progress value is reflected in the bar's style
60
- if (progressBar) {
61
- const computedStyle = window.getComputedStyle(progressBar);
62
- const transform = computedStyle.transform;
63
-
64
- // Progress bar should have some transform or width for determinate state
65
- // MUI LinearProgress may use different methods (transform or width)
66
- if (transform && transform !== 'none') {
67
- // Transform is being used
68
- expect(transform).toBeTruthy();
69
-
70
- // Just verify it has a transform, don't assume specific values
71
- // as MUI implementation may vary
72
- if (transform.includes('matrix')) {
73
- const matrixValues = transform.match(/matrix\(([^)]+)\)/);
74
- expect(matrixValues).toBeTruthy();
75
- } else if (transform.includes('translateX') || transform.includes('scaleX')) {
76
- // Has some form of transform
77
- expect(transform).toMatch(/translateX|scaleX/);
78
- }
79
- }
80
-
81
- // Also check if width is being used instead of transform
82
- const width = computedStyle.width;
83
- if (width && width !== 'auto' && width !== '100%') {
84
- // Width-based progress indication
85
- expect(width).toBeTruthy();
86
- }
87
- }
88
- });
89
-
90
- await step('Visual state verification', async () => {
91
- const progressComponent = canvas.getByTestId('progress-component');
92
- const computedStyle = window.getComputedStyle(progressComponent);
93
-
94
- // Should be visible and have proper dimensions
95
- await expect(computedStyle.display).not.toBe('none');
96
- await expect(progressComponent).toBeVisible();
97
- });
98
- },
99
- };
100
-
101
- // Form Interaction Test (Progress can be controlled)
102
- const FormInteractionComponent = () => {
103
- const [progress, setProgress] = React.useState(0);
104
- const [isRunning, setIsRunning] = React.useState(false);
105
-
106
- const startProgress = () => {
107
- setIsRunning(true);
108
- setProgress(0);
109
-
110
- const interval = window.setInterval(() => {
111
- setProgress((prev) => {
112
- if (prev >= 100) {
113
- window.clearInterval(interval);
114
- setIsRunning(false);
115
- return 100;
116
- }
117
- return prev + 10;
118
- });
119
- }, 100);
120
- };
121
-
122
- const resetProgress = () => {
123
- setProgress(0);
124
- setIsRunning(false);
125
- };
126
-
127
- return (
128
- <Box sx={{ width: 400, p: 2 }}>
129
- <Progress value={progress} showLabel data-testid="controllable-progress" color="primary" />
130
- <Box sx={{ mt: 2, display: 'flex', gap: 1 }}>
131
- <Button
132
- variant="contained"
133
- onClick={startProgress}
134
- disabled={isRunning}
135
- data-testid="start-button"
136
- >
137
- Start
138
- </Button>
139
- <Button variant="outlined" onClick={resetProgress} data-testid="reset-button">
140
- Reset
141
- </Button>
142
- </Box>
143
- <Typography variant="caption" sx={{ mt: 1, display: 'block' }}>
144
- Status: {isRunning ? 'Running' : 'Stopped'}
145
- </Typography>
146
- </Box>
147
- );
148
- };
149
-
150
- export const FormInteraction: Story = {
151
- name: '📝 Form Interaction Test',
152
- render: () => <FormInteractionComponent />,
153
- play: async ({ canvasElement, step }) => {
154
- const canvas = within(canvasElement);
155
-
156
- await step('Initial state verification', async () => {
157
- const progressComponent = canvas.getByTestId('controllable-progress');
158
- await expect(progressComponent).toBeInTheDocument();
159
-
160
- const startButton = canvas.getByTestId('start-button');
161
- const resetButton = canvas.getByTestId('reset-button');
162
-
163
- await expect(startButton).toBeEnabled();
164
- await expect(resetButton).toBeEnabled();
165
-
166
- // Should show 0% initially
167
- const progressContainer = canvas.getByTestId('controllable-progress').parentElement;
168
- await expect(progressContainer).toHaveTextContent('0%');
169
- });
170
-
171
- await step('Start progress interaction', async () => {
172
- const startButton = canvas.getByTestId('start-button');
173
- await userEvent.click(startButton);
174
-
175
- // Button should become disabled when running
176
- await expect(startButton).toBeDisabled();
177
-
178
- // Status should change to running
179
- await waitFor(
180
- () => {
181
- const status = canvas.getByText('Status: Running');
182
- expect(status).toBeInTheDocument();
183
- },
184
- { timeout: 2000 },
185
- );
186
- });
187
-
188
- await step('Progress value changes verification', async () => {
189
- // Store initial transform value
190
- const progressComponent = canvas.getByTestId('controllable-progress');
191
- const progressBar = progressComponent.querySelector('.MuiLinearProgress-bar');
192
- let previousTransform = '';
193
-
194
- if (progressBar) {
195
- previousTransform = window.getComputedStyle(progressBar).transform;
196
- }
197
-
198
- // Wait for progress to start changing
199
- await waitFor(
200
- () => {
201
- const progressBar = progressComponent.querySelector('.MuiLinearProgress-bar');
202
-
203
- if (progressBar) {
204
- const computedStyle = window.getComputedStyle(progressBar);
205
- const currentTransform = computedStyle.transform;
206
-
207
- // Transform or style should be changing as progress updates
208
- if (currentTransform && currentTransform !== 'none') {
209
- expect(currentTransform).not.toBe(previousTransform);
210
- }
211
-
212
- // Also check width changes as an alternative progress indicator
213
- const currentWidth = computedStyle.width;
214
- if (currentWidth && currentWidth !== 'auto') {
215
- // Just verify something is changing
216
- expect(currentTransform !== previousTransform || currentWidth).toBeTruthy();
217
- }
218
- }
219
- },
220
- { timeout: 3000 },
221
- );
222
-
223
- // Check that progress values are changing by verifying multiple intermediate values
224
- const seenValues = new Set();
225
- await waitFor(
226
- () => {
227
- const progressContainer = canvas.getByTestId('controllable-progress').parentElement;
228
- const textContent = progressContainer?.textContent || '';
229
-
230
- // Extract percentage value
231
- const percentMatch = textContent.match(/(\d+)%/);
232
- if (percentMatch) {
233
- const value = parseInt(percentMatch[1]);
234
- seenValues.add(value);
235
- }
236
-
237
- // We should see at least 3 different values during animation
238
- expect(seenValues.size).toBeGreaterThanOrEqual(3);
239
- },
240
- { timeout: 3000 },
241
- );
242
- });
243
-
244
- await step('Progress completion', async () => {
245
- // Wait for progress to complete
246
- await waitFor(
247
- () => {
248
- const completedLabel = canvas.queryByText('100%');
249
- expect(completedLabel).toBeInTheDocument();
250
- },
251
- { timeout: 5000 },
252
- );
253
-
254
- // Verify the progress bar shows full completion
255
- const progressComponent = canvas.getByTestId('controllable-progress');
256
- const progressBar = progressComponent.querySelector('.MuiLinearProgress-bar');
257
-
258
- if (progressBar) {
259
- const computedStyle = window.getComputedStyle(progressBar);
260
- const transform = computedStyle.transform;
261
-
262
- // At 100%, the progress should be complete
263
- // MUI may use transform or width to show this
264
- if (transform && transform !== 'none') {
265
- expect(transform).toBeTruthy();
266
- // Just verify it has changed from initial state
267
- } else {
268
- // Check if width indicates 100%
269
- const width = computedStyle.width;
270
- if (width) {
271
- expect(width).toBeTruthy();
272
- }
273
- }
274
- }
275
-
276
- // Button should be re-enabled after completion
277
- await waitFor(
278
- () => {
279
- const startButton = canvas.getByTestId('start-button');
280
- expect(startButton).toBeEnabled();
281
- },
282
- { timeout: 1000 },
283
- );
284
- });
285
-
286
- await step('Reset functionality', async () => {
287
- const resetButton = canvas.getByTestId('reset-button');
288
- await userEvent.click(resetButton);
289
-
290
- // Should reset to 0%
291
- await waitFor(() => {
292
- const progressContainer = canvas.getByTestId('controllable-progress').parentElement;
293
- expect(progressContainer).toHaveTextContent('0%');
294
- });
295
-
296
- // Status should change to stopped
297
- const status = canvas.getByText('Status: Stopped');
298
- await expect(status).toBeInTheDocument();
299
- });
300
- },
301
- };
302
-
303
- // Keyboard Navigation Test
304
- export const KeyboardNavigation: Story = {
305
- name: '⌨️ Keyboard Navigation Test',
306
- args: {
307
- value: 60,
308
- showLabel: true,
309
- },
310
- parameters: {
311
- a11y: {
312
- element: '#storybook-root',
313
- config: {
314
- rules: [
315
- { id: 'color-contrast', enabled: true },
316
- { id: 'aria-required-attr', enabled: true },
317
- { id: 'aria-roles', enabled: true },
318
- { id: 'aria-valid-attr-value', enabled: true },
319
- { id: 'duplicate-id', enabled: true },
320
- { id: 'label', enabled: true },
321
- ],
322
- },
323
- },
324
- },
325
- render: (args) => (
326
- <Box sx={{ width: 400, p: 2 }}>
327
- <Typography variant="h6" sx={{ mb: 2 }}>
328
- Progress Accessibility Test
329
- </Typography>
330
- <Button data-testid="before-progress" tabIndex={0}>
331
- Before Progress
332
- </Button>
333
- <Box sx={{ my: 2 }}>
334
- <Progress
335
- {...args}
336
- data-testid="progress-keyboard"
337
- role="progressbar"
338
- aria-valuemin={0}
339
- aria-valuemax={100}
340
- aria-valuenow={args.value}
341
- aria-label="Loading progress"
342
- />
343
- </Box>
344
- <Button data-testid="after-progress" tabIndex={0}>
345
- After Progress
346
- </Button>
347
- </Box>
348
- ),
349
- play: async ({ canvasElement, step }) => {
350
- const canvas = within(canvasElement);
351
-
352
- await step('Progress accessibility attributes', async () => {
353
- const progressComponent = canvas.getByTestId('progress-keyboard');
354
-
355
- // Check ARIA attributes
356
- await expect(progressComponent).toHaveAttribute('role', 'progressbar');
357
- await expect(progressComponent).toHaveAttribute('aria-valuemin', '0');
358
- await expect(progressComponent).toHaveAttribute('aria-valuemax', '100');
359
- await expect(progressComponent).toHaveAttribute('aria-valuenow', '60');
360
- await expect(progressComponent).toHaveAttribute('aria-label', 'Loading progress');
361
- });
362
-
363
- await step('Tab navigation sequence', async () => {
364
- const beforeButton = canvas.getByTestId('before-progress');
365
- const afterButton = canvas.getByTestId('after-progress');
366
-
367
- // Start from before button
368
- beforeButton.focus();
369
- await expect(beforeButton).toHaveFocus();
370
-
371
- // Tab to after button (progress should be skipped as it's not interactive)
372
- await userEvent.tab();
373
- await expect(afterButton).toHaveFocus();
374
-
375
- // Tab backward
376
- await userEvent.tab({ shift: true });
377
- await expect(beforeButton).toHaveFocus();
378
- });
379
-
380
- await step('Screen reader compatibility', async () => {
381
- const progressComponent = canvas.getByTestId('progress-keyboard');
382
-
383
- // Should be findable by screen reader via role
384
- const progressByRole = canvas.getByRole('progressbar');
385
- await expect(progressByRole).toBe(progressComponent);
386
-
387
- // Should be findable by accessible name
388
- const progressByLabel = canvas.getByLabelText('Loading progress');
389
- await expect(progressByLabel).toBe(progressComponent);
390
- });
391
- },
392
- };
393
-
394
- // Screen Reader Test
395
- export const ScreenReader: Story = {
396
- name: '🔊 Screen Reader Test',
397
- render: () => (
398
- <Box sx={{ width: 500, p: 2 }}>
399
- <Typography variant="h6" sx={{ mb: 2 }} id="progress-section">
400
- File Upload Progress
401
- </Typography>
402
-
403
- <Progress
404
- value={75}
405
- showLabel
406
- data-testid="screen-reader-progress"
407
- role="progressbar"
408
- aria-valuemin={0}
409
- aria-valuemax={100}
410
- aria-valuenow={75}
411
- aria-label="File upload progress"
412
- aria-describedby="progress-description"
413
- />
414
-
415
- <Typography
416
- id="progress-description"
417
- variant="body2"
418
- sx={{ mt: 1 }}
419
- data-testid="progress-description"
420
- >
421
- Uploading document.pdf - 3 of 4 files completed
422
- </Typography>
423
-
424
- <div role="status" aria-live="polite" data-testid="live-region">
425
- Upload progress: 75% complete
426
- </div>
427
-
428
- {/* Different progress states for screen reader testing */}
429
- <Box sx={{ mt: 3 }}>
430
- <Typography variant="subtitle2" sx={{ mb: 1 }}>
431
- Loading State
432
- </Typography>
433
- <Progress
434
- data-testid="indeterminate-progress"
435
- role="progressbar"
436
- aria-label="Loading content"
437
- aria-describedby="loading-description"
438
- />
439
- <Typography id="loading-description" variant="caption" sx={{ display: 'block', mt: 0.5 }}>
440
- Please wait while content loads...
441
- </Typography>
442
- </Box>
443
- </Box>
444
- ),
445
- play: async ({ canvasElement, step }) => {
446
- const canvas = within(canvasElement);
447
-
448
- await step('ARIA labels verification', async () => {
449
- const progressComponent = canvas.getByTestId('screen-reader-progress');
450
-
451
- await expect(progressComponent).toHaveAttribute('aria-label', 'File upload progress');
452
- await expect(progressComponent).toHaveAttribute('aria-describedby', 'progress-description');
453
- await expect(progressComponent).toHaveAttribute('role', 'progressbar');
454
-
455
- // Should be findable by screen readers
456
- const progressByLabel = canvas.getByLabelText('File upload progress');
457
- await expect(progressByLabel).toBe(progressComponent);
458
- });
459
-
460
- await step('ARIA descriptions verification', async () => {
461
- const description = canvas.getByTestId('progress-description');
462
- await expect(description).toBeInTheDocument();
463
- await expect(description).toHaveAttribute('id', 'progress-description');
464
-
465
- const descriptionText = canvas.getByText(/Uploading document.pdf/);
466
- await expect(descriptionText).toBeInTheDocument();
467
- });
468
-
469
- await step('Live region verification', async () => {
470
- const liveRegion = canvas.getByTestId('live-region');
471
- await expect(liveRegion).toHaveAttribute('role', 'status');
472
- await expect(liveRegion).toHaveAttribute('aria-live', 'polite');
473
-
474
- const liveText = canvas.getByText('Upload progress: 75% complete');
475
- await expect(liveText).toBeInTheDocument();
476
- });
477
-
478
- await step('Indeterminate progress accessibility', async () => {
479
- const indeterminateProgress = canvas.getByTestId('indeterminate-progress');
480
-
481
- await expect(indeterminateProgress).toHaveAttribute('role', 'progressbar');
482
- await expect(indeterminateProgress).toHaveAttribute('aria-label', 'Loading content');
483
- await expect(indeterminateProgress).toHaveAttribute(
484
- 'aria-describedby',
485
- 'loading-description',
486
- );
487
-
488
- // For indeterminate progress, aria-valuenow should not be set
489
- await expect(indeterminateProgress).not.toHaveAttribute('aria-valuenow');
490
-
491
- // Verify indeterminate animation is active
492
- const indeterminateBar = indeterminateProgress.querySelector(
493
- '.MuiLinearProgress-bar1Indeterminate, .MuiLinearProgress-bar',
494
- );
495
- if (indeterminateBar) {
496
- const computedStyle = window.getComputedStyle(indeterminateBar);
497
- // Should have animation for indeterminate state
498
- expect(computedStyle.animation || computedStyle.animationName).not.toBe('none');
499
- }
500
- });
501
-
502
- await step('Progress role verification', async () => {
503
- // All progress elements should be findable by role
504
- const allProgressBars = canvas.getAllByRole('progressbar');
505
- await expect(allProgressBars).toHaveLength(2);
506
-
507
- // Each should have appropriate labeling
508
- allProgressBars.forEach(async (progress) => {
509
- const hasLabel =
510
- progress.hasAttribute('aria-label') || progress.hasAttribute('aria-labelledby');
511
- await expect(hasLabel).toBe(true);
512
- });
513
- });
514
- },
515
- };
516
-
517
- // Focus Management Test
518
- const FocusManagementComponent = () => {
519
- const [showProgress, setShowProgress] = React.useState(false);
520
- const [progress, setProgress] = React.useState(0);
521
-
522
- const startTask = () => {
523
- setShowProgress(true);
524
- setProgress(0);
525
-
526
- const interval = window.setInterval(() => {
527
- setProgress((prev) => {
528
- if (prev >= 100) {
529
- window.clearInterval(interval);
530
- return 100;
531
- }
532
- return prev + 20;
533
- });
534
- }, 500);
535
- };
536
-
537
- return (
538
- <Box sx={{ width: 400, p: 2 }}>
539
- <Typography variant="h6" sx={{ mb: 2 }}>
540
- Focus Management Demo
541
- </Typography>
542
-
543
- <Button
544
- data-testid="trigger-button"
545
- variant="contained"
546
- onClick={startTask}
547
- disabled={showProgress}
548
- >
549
- Start Task
550
- </Button>
551
-
552
- {showProgress && (
553
- <Box sx={{ mt: 2 }} data-testid="progress-section">
554
- <Typography variant="body2" sx={{ mb: 1 }}>
555
- Task in progress...
556
- </Typography>
557
- <Progress
558
- value={progress}
559
- showLabel
560
- data-testid="focus-progress"
561
- role="progressbar"
562
- aria-label="Task progress"
563
- aria-valuemin={0}
564
- aria-valuemax={100}
565
- aria-valuenow={progress}
566
- />
567
-
568
- {progress === 100 && (
569
- <Box sx={{ mt: 2 }}>
570
- <Typography variant="body2" color="success.main" sx={{ mb: 1 }}>
571
- Task completed successfully!
572
- </Typography>
573
- <Button
574
- data-testid="completion-button"
575
- variant="outlined"
576
- onClick={() => setShowProgress(false)}
577
- autoFocus
578
- >
579
- Continue
580
- </Button>
581
- </Box>
582
- )}
583
- </Box>
584
- )}
585
- </Box>
586
- );
587
- };
588
-
589
- export const FocusManagement: Story = {
590
- name: '🎯 Focus Management Test',
591
- render: () => <FocusManagementComponent />,
592
- play: async ({ canvasElement, step }) => {
593
- const canvas = within(canvasElement);
594
-
595
- await step('Initial focus state', async () => {
596
- const triggerButton = canvas.getByTestId('trigger-button');
597
- await expect(triggerButton).toBeInTheDocument();
598
- await expect(triggerButton).toBeEnabled();
599
- });
600
-
601
- await step('Progress appearance and focus', async () => {
602
- const triggerButton = canvas.getByTestId('trigger-button');
603
- triggerButton.focus();
604
- await expect(triggerButton).toHaveFocus();
605
-
606
- await userEvent.click(triggerButton);
607
-
608
- // Progress should appear
609
- await waitFor(() => {
610
- const progressSection = canvas.getByTestId('progress-section');
611
- expect(progressSection).toBeInTheDocument();
612
- });
613
-
614
- const progressComponent = canvas.getByTestId('focus-progress');
615
- await expect(progressComponent).toBeInTheDocument();
616
-
617
- // Trigger button should be disabled
618
- await expect(triggerButton).toBeDisabled();
619
- });
620
-
621
- await step('Progress completion and focus restoration', async () => {
622
- // Wait for progress to complete
623
- await waitFor(
624
- () => {
625
- const completionMessage = canvas.getByText('Task completed successfully!');
626
- expect(completionMessage).toBeInTheDocument();
627
- },
628
- { timeout: 8000 },
629
- );
630
-
631
- // Continue button should appear with auto focus
632
- const continueButton = canvas.getByTestId('completion-button');
633
- await expect(continueButton).toBeInTheDocument();
634
-
635
- await waitFor(
636
- () => {
637
- expect(continueButton).toHaveFocus();
638
- },
639
- { timeout: 1000 },
640
- );
641
- });
642
-
643
- await step('Focus restoration after cleanup', async () => {
644
- const continueButton = canvas.getByTestId('completion-button');
645
- await userEvent.click(continueButton);
646
-
647
- // Progress section should disappear
648
- await waitFor(() => {
649
- const progressSection = canvas.queryByTestId('progress-section');
650
- expect(progressSection).not.toBeInTheDocument();
651
- });
652
-
653
- // Trigger button should be enabled again
654
- const triggerButton = canvas.getByTestId('trigger-button');
655
- await expect(triggerButton).toBeEnabled();
656
- });
657
- },
658
- };
659
-
660
- // Responsive Design Test
661
- export const ResponsiveDesign: Story = {
662
- name: '📱 Responsive Design Test',
663
- parameters: {
664
- viewport: {
665
- viewports: {
666
- mobile: {
667
- name: 'Mobile',
668
- styles: { width: '375px', height: '667px' },
669
- type: 'mobile',
670
- },
671
- tablet: {
672
- name: 'Tablet',
673
- styles: { width: '768px', height: '1024px' },
674
- type: 'tablet',
675
- },
676
- desktop: {
677
- name: 'Desktop',
678
- styles: { width: '1920px', height: '1080px' },
679
- type: 'desktop',
680
- },
681
- },
682
- defaultViewport: 'mobile',
683
- },
684
- chromatic: {
685
- viewports: [375, 768, 1920],
686
- delay: 300,
687
- },
688
- },
689
- render: () => (
690
- <Box sx={{ p: 2, maxWidth: '100%' }}>
691
- <Typography variant="h6" sx={{ mb: 3 }}>
692
- Responsive Progress Components
693
- </Typography>
694
-
695
- <Stack spacing={3} data-testid="responsive-container">
696
- {/* Linear Progress - should adapt width */}
697
- <Box>
698
- <Typography variant="subtitle2" sx={{ mb: 1 }}>
699
- Linear Progress (Full Width)
700
- </Typography>
701
- <Progress value={60} showLabel data-testid="responsive-linear" sx={{ width: '100%' }} />
702
- </Box>
703
-
704
- {/* Circular Progress - should center on small screens */}
705
- <Box
706
- sx={{
707
- display: 'flex',
708
- flexDirection: { xs: 'column', sm: 'row' },
709
- alignItems: 'center',
710
- gap: 2,
711
- }}
712
- >
713
- <Typography variant="subtitle2">Circular Progress:</Typography>
714
- <Progress variant="circular" value={75} showLabel data-testid="responsive-circular" />
715
- </Box>
716
-
717
- {/* Segmented Progress - should adapt segment sizes */}
718
- <Box>
719
- <Typography variant="subtitle2" sx={{ mb: 1 }}>
720
- Segmented Progress (Adaptive)
721
- </Typography>
722
- <Progress
723
- variant="segmented"
724
- segments={10}
725
- value={80}
726
- showLabel
727
- data-testid="responsive-segmented"
728
- />
729
- </Box>
730
-
731
- {/* Multiple sizes for different viewports */}
732
- <Box
733
- sx={{
734
- display: 'grid',
735
- gridTemplateColumns: { xs: '1fr', sm: 'repeat(3, 1fr)' },
736
- gap: 2,
737
- mt: 2,
738
- }}
739
- >
740
- <Box textAlign="center">
741
- <Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
742
- Small
743
- </Typography>
744
- <Progress
745
- variant="circular"
746
- size="sm"
747
- value={40}
748
- showLabel
749
- data-testid="responsive-small"
750
- />
751
- </Box>
752
- <Box textAlign="center">
753
- <Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
754
- Medium
755
- </Typography>
756
- <Progress
757
- variant="circular"
758
- size="md"
759
- value={60}
760
- showLabel
761
- data-testid="responsive-medium"
762
- />
763
- </Box>
764
- <Box textAlign="center">
765
- <Typography variant="caption" sx={{ display: 'block', mb: 1 }}>
766
- Large
767
- </Typography>
768
- <Progress
769
- variant="circular"
770
- size="lg"
771
- value={80}
772
- showLabel
773
- data-testid="responsive-large"
774
- />
775
- </Box>
776
- </Box>
777
- </Stack>
778
- </Box>
779
- ),
780
- play: async ({ canvasElement, step }) => {
781
- const canvas = within(canvasElement);
782
-
783
- await step('Container responsiveness', async () => {
784
- const container = canvas.getByTestId('responsive-container');
785
- await expect(container).toBeInTheDocument();
786
-
787
- const computedStyle = window.getComputedStyle(container);
788
- await expect(computedStyle.display).toBe('flex');
789
- });
790
-
791
- await step('Linear progress width adaptation', async () => {
792
- const linearProgress = canvas.getByTestId('responsive-linear');
793
- await expect(linearProgress).toBeInTheDocument();
794
-
795
- const computedStyle = window.getComputedStyle(linearProgress);
796
- // Should take full width of container
797
- expect(computedStyle.width).toBeTruthy();
798
- });
799
-
800
- await step('Circular progress visibility and value verification', async () => {
801
- const circularProgress = canvas.getByTestId('responsive-circular');
802
- await expect(circularProgress).toBeInTheDocument();
803
- await expect(circularProgress).toBeVisible();
804
-
805
- // Should maintain aspect ratio
806
- const boundingBox = circularProgress.getBoundingClientRect();
807
- expect(boundingBox.width).toBeGreaterThan(0);
808
- expect(boundingBox.height).toBeGreaterThan(0);
809
-
810
- // Verify actual circular progress value (75%) is reflected in stroke-dashoffset
811
- const circularPath = circularProgress.querySelector('.MuiCircularProgress-circle');
812
-
813
- if (circularPath) {
814
- const computedStyle = window.getComputedStyle(circularPath);
815
- const strokeDashArray = computedStyle.strokeDasharray;
816
- const strokeDashOffset = computedStyle.strokeDashoffset;
817
-
818
- // For a 75% progress, stroke-dashoffset should be about 25% of stroke-dasharray
819
- if (strokeDashArray && strokeDashOffset && strokeDashArray !== 'none') {
820
- const dashArrayValue = parseFloat(strokeDashArray.split(',')[0] || strokeDashArray);
821
- const dashOffsetValue = parseFloat(strokeDashOffset);
822
-
823
- // The offset should represent the remaining 25% (100% - 75%)
824
- // MUI CircularProgress calculates offset as: circumference * (1 - value/100)
825
- const progressRatio = 1 - dashOffsetValue / dashArrayValue;
826
- expect(progressRatio).toBeCloseTo(0.75, 1); // Should be close to 75%
827
- }
828
- }
829
- });
830
-
831
- await step('Segmented progress adaptation and fill calculation', async () => {
832
- const segmentedProgress = canvas.getByTestId('responsive-segmented');
833
- await expect(segmentedProgress).toBeInTheDocument();
834
-
835
- // Should contain segment elements - look for the container with display: flex
836
- const segmentContainer = segmentedProgress.querySelector(
837
- 'div[style*="display: flex"], div div',
838
- );
839
- if (segmentContainer) {
840
- const segments = segmentContainer.children;
841
- expect(segments.length).toBe(10); // Should have exactly 10 segments
842
-
843
- // For 80% progress with 10 segments, 8 segments should be filled
844
- // Test the background colors to verify which segments are filled
845
- let filledSegments = 0;
846
- let emptySegments = 0;
847
-
848
- for (let i = 0; i < segments.length; i++) {
849
- const segment = segments[i] as HTMLElement;
850
- const computedStyle = window.getComputedStyle(segment);
851
- const backgroundColor = computedStyle.backgroundColor;
852
-
853
- // Parse RGB values to determine if segment is filled or empty
854
- // Filled segments have solid color, empty ones have low opacity (0.1)
855
- if (backgroundColor && backgroundColor.includes('rgba')) {
856
- const rgbaMatch = backgroundColor.match(/rgba\(([^)]+)\)/);
857
- if (rgbaMatch) {
858
- const values = rgbaMatch[1].split(',').map((v) => v.trim());
859
- const opacity = parseFloat(values[3] || '1');
860
-
861
- if (opacity < 0.2) {
862
- emptySegments++;
863
- } else {
864
- filledSegments++;
865
- }
866
- }
867
- } else if (backgroundColor && backgroundColor.includes('rgb')) {
868
- // Solid RGB color means filled
869
- filledSegments++;
870
- }
871
- }
872
-
873
- // For 80% of 10 segments, we expect exactly 8 filled and 2 empty
874
- expect(filledSegments).toBe(8);
875
- expect(emptySegments).toBe(2);
876
- } else {
877
- // If we can't find segments, just verify the component exists
878
- await expect(segmentedProgress).toBeVisible();
879
- }
880
- });
881
-
882
- await step('Size variants responsive behavior', async () => {
883
- const smallProgress = canvas.getByTestId('responsive-small');
884
- const mediumProgress = canvas.getByTestId('responsive-medium');
885
- const largeProgress = canvas.getByTestId('responsive-large');
886
-
887
- await expect(smallProgress).toBeVisible();
888
- await expect(mediumProgress).toBeVisible();
889
- await expect(largeProgress).toBeVisible();
890
-
891
- // Each should be properly sized
892
- const smallBounds = smallProgress.getBoundingClientRect();
893
- const mediumBounds = mediumProgress.getBoundingClientRect();
894
- const largeBounds = largeProgress.getBoundingClientRect();
895
-
896
- expect(smallBounds.width).toBeLessThan(mediumBounds.width);
897
- expect(mediumBounds.width).toBeLessThan(largeBounds.width);
898
- });
899
- },
900
- };
901
-
902
- // Theme Variations Test
903
- export const ThemeVariations: Story = {
904
- name: '🎨 Theme Variations Test',
905
- parameters: {
906
- backgrounds: {
907
- default: 'light',
908
- values: [
909
- { name: 'light', value: '#ffffff' },
910
- { name: 'dark', value: '#1a1a1a' },
911
- ],
912
- },
913
- },
914
- render: () => (
915
- <Box sx={{ p: 3, width: 600 }}>
916
- <Typography variant="h6" sx={{ mb: 3 }}>
917
- Theme Color Variations
918
- </Typography>
919
-
920
- <Stack spacing={3} data-testid="theme-container">
921
- {/* Primary Colors */}
922
- <Box>
923
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
924
- Color Themes
925
- </Typography>
926
- <Stack spacing={2}>
927
- <Progress color="primary" value={70} showLabel data-testid="theme-primary" />
928
- <Progress color="secondary" value={70} showLabel data-testid="theme-secondary" />
929
- <Progress color="success" value={70} showLabel data-testid="theme-success" />
930
- <Progress color="warning" value={70} showLabel data-testid="theme-warning" />
931
- <Progress color="error" value={70} showLabel data-testid="theme-error" />
932
- <Progress color="neutral" value={70} showLabel data-testid="theme-neutral" />
933
- </Stack>
934
- </Box>
935
-
936
- {/* Variant with theme colors */}
937
- <Box>
938
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
939
- Circular with Theme Colors
940
- </Typography>
941
- <Stack direction="row" spacing={2} justifyContent="center">
942
- <Progress
943
- variant="circular"
944
- color="primary"
945
- value={60}
946
- showLabel
947
- data-testid="circular-primary"
948
- />
949
- <Progress
950
- variant="circular"
951
- color="success"
952
- value={80}
953
- showLabel
954
- data-testid="circular-success"
955
- />
956
- <Progress
957
- variant="circular"
958
- color="warning"
959
- value={45}
960
- showLabel
961
- data-testid="circular-warning"
962
- />
963
- </Stack>
964
- </Box>
965
-
966
- {/* Effects with theme colors */}
967
- <Box>
968
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
969
- Effects with Theme Colors
970
- </Typography>
971
- <Stack spacing={2}>
972
- <Progress
973
- variant="gradient"
974
- color="primary"
975
- value={65}
976
- showLabel
977
- glow
978
- data-testid="gradient-glow"
979
- />
980
- <Progress color="success" value={85} showLabel pulse data-testid="linear-pulse" />
981
- </Stack>
982
- </Box>
983
- </Stack>
984
- </Box>
985
- ),
986
- play: async ({ canvasElement, step }) => {
987
- const canvas = within(canvasElement);
988
-
989
- await step('Theme color application', async () => {
990
- const primaryProgress = canvas.getByTestId('theme-primary');
991
- const successProgress = canvas.getByTestId('theme-success');
992
- const errorProgress = canvas.getByTestId('theme-error');
993
-
994
- await expect(primaryProgress).toBeInTheDocument();
995
- await expect(successProgress).toBeInTheDocument();
996
- await expect(errorProgress).toBeInTheDocument();
997
-
998
- // Each should be visually distinct (we can't easily test exact colors,
999
- // but we can verify they render and have different styling)
1000
- const primaryBar = primaryProgress.querySelector('.MuiLinearProgress-bar');
1001
- const successBar = successProgress.querySelector('.MuiLinearProgress-bar');
1002
-
1003
- expect(primaryBar).toBeTruthy();
1004
- expect(successBar).toBeTruthy();
1005
-
1006
- if (primaryBar && successBar) {
1007
- const primaryStyle = window.getComputedStyle(primaryBar);
1008
- const successStyle = window.getComputedStyle(successBar);
1009
-
1010
- // Colors should be different
1011
- expect(primaryStyle.backgroundColor).not.toBe(successStyle.backgroundColor);
1012
- }
1013
- });
1014
-
1015
- await step('Circular theme variations', async () => {
1016
- const circularPrimary = canvas.getByTestId('circular-primary');
1017
- const circularSuccess = canvas.getByTestId('circular-success');
1018
- const circularWarning = canvas.getByTestId('circular-warning');
1019
-
1020
- await expect(circularPrimary).toBeVisible();
1021
- await expect(circularSuccess).toBeVisible();
1022
- await expect(circularWarning).toBeVisible();
1023
-
1024
- // Check for color attributes or styling differences
1025
- const primaryCircle = circularPrimary.querySelector('.MuiCircularProgress-circle');
1026
- const successCircle = circularSuccess.querySelector('.MuiCircularProgress-circle');
1027
-
1028
- if (primaryCircle && successCircle) {
1029
- const primaryStyle = window.getComputedStyle(primaryCircle);
1030
- const successStyle = window.getComputedStyle(successCircle);
1031
-
1032
- // Should have different stroke colors
1033
- expect(primaryStyle.stroke || primaryStyle.color).not.toBe(
1034
- successStyle.stroke || successStyle.color,
1035
- );
1036
- }
1037
- });
1038
-
1039
- await step('Effect styling with theme colors', async () => {
1040
- const glowProgress = canvas.getByTestId('gradient-glow');
1041
- const pulseProgress = canvas.getByTestId('linear-pulse');
1042
-
1043
- await expect(glowProgress).toBeVisible();
1044
- await expect(pulseProgress).toBeVisible();
1045
-
1046
- // Verify glow effect is applied
1047
- const glowBar = glowProgress.querySelector('.MuiLinearProgress-bar');
1048
- if (glowBar) {
1049
- const glowStyle = window.getComputedStyle(glowBar);
1050
- // Should have box-shadow for glow effect
1051
- expect(glowStyle.boxShadow).not.toBe('none');
1052
- }
1053
-
1054
- // Verify pulse animation is applied
1055
- const pulseBar = pulseProgress.querySelector('.MuiLinearProgress-bar');
1056
- if (pulseBar) {
1057
- const pulseStyle = window.getComputedStyle(pulseBar);
1058
- // Should have animation applied
1059
- expect(pulseStyle.animation).not.toBe('none');
1060
- }
1061
- });
1062
-
1063
- await step('Color contrast verification', async () => {
1064
- // Verify all theme colors render with sufficient contrast
1065
- const allThemeProgress = [
1066
- canvas.getByTestId('theme-primary'),
1067
- canvas.getByTestId('theme-secondary'),
1068
- canvas.getByTestId('theme-success'),
1069
- canvas.getByTestId('theme-warning'),
1070
- canvas.getByTestId('theme-error'),
1071
- canvas.getByTestId('theme-neutral'),
1072
- ];
1073
-
1074
- allThemeProgress.forEach(async (progress) => {
1075
- await expect(progress).toBeVisible();
1076
-
1077
- const progressBar = progress.querySelector('.MuiLinearProgress-bar');
1078
- if (progressBar) {
1079
- const computedStyle = window.getComputedStyle(progressBar);
1080
- // Should have a background color set
1081
- expect(computedStyle.backgroundColor).not.toBe('transparent');
1082
- expect(computedStyle.backgroundColor).not.toBe('');
1083
- }
1084
- });
1085
- });
1086
- },
1087
- };
1088
-
1089
- // Visual States Test
1090
- export const VisualStates: Story = {
1091
- name: '👁️ Visual States Test',
1092
- render: () => (
1093
- <Box sx={{ p: 3, width: 700 }}>
1094
- <Typography variant="h6" sx={{ mb: 3 }}>
1095
- Visual States and Variants
1096
- </Typography>
1097
-
1098
- <Stack spacing={4} data-testid="visual-states-container">
1099
- {/* Default states */}
1100
- <Box>
1101
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
1102
- Default States
1103
- </Typography>
1104
- <Stack spacing={2}>
1105
- <Box>
1106
- <Typography variant="caption">Linear - 50%</Typography>
1107
- <Progress value={50} showLabel data-testid="default-linear" />
1108
- </Box>
1109
- <Box>
1110
- <Typography variant="caption">Indeterminate Linear</Typography>
1111
- <Progress data-testid="default-indeterminate" />
1112
- </Box>
1113
- </Stack>
1114
- </Box>
1115
-
1116
- {/* Hover-like states (simulated through styling) */}
1117
- <Box>
1118
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
1119
- Interactive States
1120
- </Typography>
1121
- <Stack spacing={2}>
1122
- <Box>
1123
- <Typography variant="caption">With Glow Effect</Typography>
1124
- <Progress value={75} showLabel glow data-testid="glow-state" />
1125
- </Box>
1126
- <Box>
1127
- <Typography variant="caption">With Pulse Animation</Typography>
1128
- <Progress value={60} showLabel pulse data-testid="pulse-state" />
1129
- </Box>
1130
- <Box>
1131
- <Typography variant="caption">Glass Effect</Typography>
1132
- <Progress variant="glass" value={85} showLabel data-testid="glass-state" />
1133
- </Box>
1134
- </Stack>
1135
- </Box>
1136
-
1137
- {/* Loading states */}
1138
- <Box>
1139
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
1140
- Loading States
1141
- </Typography>
1142
- <Stack spacing={2}>
1143
- <Box>
1144
- <Typography variant="caption">Linear Loading</Typography>
1145
- <Progress data-testid="loading-linear" color="primary" />
1146
- </Box>
1147
- <Box>
1148
- <Typography variant="caption">Circular Loading</Typography>
1149
- <Progress variant="circular" data-testid="loading-circular" color="primary" />
1150
- </Box>
1151
- </Stack>
1152
- </Box>
1153
-
1154
- {/* Size variations */}
1155
- <Box>
1156
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
1157
- Size Variations
1158
- </Typography>
1159
- <Stack spacing={2}>
1160
- <Box>
1161
- <Typography variant="caption">Small (4px height)</Typography>
1162
- <Progress size="sm" value={40} showLabel data-testid="small-size" />
1163
- </Box>
1164
- <Box>
1165
- <Typography variant="caption">Medium (6px height)</Typography>
1166
- <Progress size="md" value={60} showLabel data-testid="medium-size" />
1167
- </Box>
1168
- <Box>
1169
- <Typography variant="caption">Large (8px height)</Typography>
1170
- <Progress size="lg" value={80} showLabel data-testid="large-size" />
1171
- </Box>
1172
- </Stack>
1173
- </Box>
1174
-
1175
- {/* Error-like states (using error color) */}
1176
- <Box>
1177
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
1178
- Status States
1179
- </Typography>
1180
- <Stack spacing={2}>
1181
- <Box>
1182
- <Typography variant="caption">Success State</Typography>
1183
- <Progress value={100} showLabel color="success" data-testid="success-state" />
1184
- </Box>
1185
- <Box>
1186
- <Typography variant="caption">Warning State</Typography>
1187
- <Progress value={25} showLabel color="warning" data-testid="warning-state" />
1188
- </Box>
1189
- <Box>
1190
- <Typography variant="caption">Error State</Typography>
1191
- <Progress value={15} showLabel color="error" pulse data-testid="error-state" />
1192
- </Box>
1193
- </Stack>
1194
- </Box>
1195
- </Stack>
1196
- </Box>
1197
- ),
1198
- play: async ({ canvasElement, step }) => {
1199
- const canvas = within(canvasElement);
1200
-
1201
- await step('Default state rendering', async () => {
1202
- const defaultLinear = canvas.getByTestId('default-linear');
1203
- const defaultIndeterminate = canvas.getByTestId('default-indeterminate');
1204
-
1205
- await expect(defaultLinear).toBeVisible();
1206
- await expect(defaultIndeterminate).toBeVisible();
1207
-
1208
- // Check progress value is displayed
1209
- const label = canvas.getByText('50%');
1210
- await expect(label).toBeInTheDocument();
1211
-
1212
- // Verify default styling
1213
- const linearBar = defaultLinear.querySelector('.MuiLinearProgress-bar');
1214
- expect(linearBar).toBeTruthy();
1215
-
1216
- if (linearBar) {
1217
- const computedStyle = window.getComputedStyle(linearBar);
1218
- expect(computedStyle.opacity).toBe('1');
1219
- }
1220
- });
1221
-
1222
- await step('Interactive state effects', async () => {
1223
- const glowProgress = canvas.getByTestId('glow-state');
1224
- const pulseProgress = canvas.getByTestId('pulse-state');
1225
- const glassProgress = canvas.getByTestId('glass-state');
1226
-
1227
- await expect(glowProgress).toBeVisible();
1228
- await expect(pulseProgress).toBeVisible();
1229
- await expect(glassProgress).toBeVisible();
1230
-
1231
- // Verify glow effect
1232
- const glowBar = glowProgress.querySelector('.MuiLinearProgress-bar');
1233
- if (glowBar) {
1234
- const glowStyle = window.getComputedStyle(glowBar);
1235
- expect(glowStyle.boxShadow).not.toBe('none');
1236
- }
1237
-
1238
- // Verify pulse animation
1239
- const pulseBar = pulseProgress.querySelector('.MuiLinearProgress-bar');
1240
- if (pulseBar) {
1241
- const pulseStyle = window.getComputedStyle(pulseBar);
1242
- expect(pulseStyle.animation).not.toBe('none');
1243
- }
1244
- });
1245
-
1246
- await step('Loading state behavior', async () => {
1247
- const loadingLinear = canvas.getByTestId('loading-linear');
1248
- const loadingCircular = canvas.getByTestId('loading-circular');
1249
-
1250
- await expect(loadingLinear).toBeVisible();
1251
- await expect(loadingCircular).toBeVisible();
1252
-
1253
- // Indeterminate progress should be animated
1254
- const linearBar1 = loadingLinear.querySelector('.MuiLinearProgress-bar1Indeterminate');
1255
- const linearBar2 = loadingLinear.querySelector('.MuiLinearProgress-bar2Indeterminate');
1256
- const circularSvg = loadingCircular.querySelector('.MuiCircularProgress-svg');
1257
- const circularCircle = loadingCircular.querySelector('.MuiCircularProgress-circle');
1258
-
1259
- // Linear indeterminate should have animated bars
1260
- if (linearBar1) {
1261
- const bar1Style = window.getComputedStyle(linearBar1);
1262
- expect(bar1Style.animation).not.toBe('none');
1263
- // MUI uses animation names that may vary by version
1264
- expect(bar1Style.animation).toBeTruthy();
1265
- }
1266
-
1267
- if (linearBar2) {
1268
- const bar2Style = window.getComputedStyle(linearBar2);
1269
- expect(bar2Style.animation).not.toBe('none');
1270
- expect(bar2Style.animation).toBeTruthy();
1271
- }
1272
-
1273
- // Circular indeterminate should have animation
1274
- if (circularSvg) {
1275
- const svgStyle = window.getComputedStyle(circularSvg);
1276
- // May have animation or use a different approach
1277
- if (svgStyle.animation && svgStyle.animation !== 'none') {
1278
- expect(svgStyle.animation).toBeTruthy();
1279
- }
1280
- }
1281
-
1282
- if (circularCircle) {
1283
- const circleStyle = window.getComputedStyle(circularCircle);
1284
- // Circle should have some animation or transition
1285
- if (circleStyle.animation && circleStyle.animation !== 'none') {
1286
- expect(circleStyle.animation).toBeTruthy();
1287
- }
1288
- }
1289
- });
1290
-
1291
- await step('Size variation verification', async () => {
1292
- const smallProgress = canvas.getByTestId('small-size');
1293
- const mediumProgress = canvas.getByTestId('medium-size');
1294
- const largeProgress = canvas.getByTestId('large-size');
1295
-
1296
- await expect(smallProgress).toBeVisible();
1297
- await expect(mediumProgress).toBeVisible();
1298
- await expect(largeProgress).toBeVisible();
1299
-
1300
- // Verify different heights
1301
- const smallBounds = smallProgress.getBoundingClientRect();
1302
- const mediumBounds = mediumProgress.getBoundingClientRect();
1303
- const largeBounds = largeProgress.getBoundingClientRect();
1304
-
1305
- expect(smallBounds.height).toBeLessThan(mediumBounds.height);
1306
- expect(mediumBounds.height).toBeLessThan(largeBounds.height);
1307
- });
1308
-
1309
- await step('Status state colors', async () => {
1310
- const successState = canvas.getByTestId('success-state');
1311
- const warningState = canvas.getByTestId('warning-state');
1312
- const errorState = canvas.getByTestId('error-state');
1313
-
1314
- await expect(successState).toBeVisible();
1315
- await expect(warningState).toBeVisible();
1316
- await expect(errorState).toBeVisible();
1317
-
1318
- // Verify different colors are applied
1319
- const successBar = successState.querySelector('.MuiLinearProgress-bar');
1320
- const warningBar = warningState.querySelector('.MuiLinearProgress-bar');
1321
- const errorBar = errorState.querySelector('.MuiLinearProgress-bar');
1322
-
1323
- if (successBar && warningBar && errorBar) {
1324
- const successStyle = window.getComputedStyle(successBar);
1325
- const warningStyle = window.getComputedStyle(warningBar);
1326
- const errorStyle = window.getComputedStyle(errorBar);
1327
-
1328
- // Each should have different background colors
1329
- expect(successStyle.backgroundColor).not.toBe(warningStyle.backgroundColor);
1330
- expect(warningStyle.backgroundColor).not.toBe(errorStyle.backgroundColor);
1331
- expect(successStyle.backgroundColor).not.toBe(errorStyle.backgroundColor);
1332
- }
1333
-
1334
- // Error state should have pulse animation
1335
- if (errorBar) {
1336
- const errorStyle = window.getComputedStyle(errorBar);
1337
- expect(errorStyle.animation).not.toBe('none');
1338
- }
1339
- });
1340
- },
1341
- };
1342
-
1343
- // Performance Test
1344
- export const Performance: Story = {
1345
- name: '⚡ Performance Test',
1346
- render: () => <PerformanceTestComponent />,
1347
- play: async ({ canvasElement, step }) => {
1348
- const canvas = within(canvasElement);
1349
-
1350
- await step('Measure render time', async () => {
1351
- const startTime = window.performance.now();
1352
-
1353
- const container = canvas.getByTestId('performance-container');
1354
- await expect(container).toBeInTheDocument();
1355
-
1356
- // Find all progress components
1357
- const progressComponents = container.querySelectorAll('[data-testid^="perf-item-"]');
1358
-
1359
- const endTime = window.performance.now();
1360
- const renderTime = endTime - startTime;
1361
-
1362
- // console.log(`Render time for ${progressComponents.length} progress components: ${renderTime}ms`);
1363
-
1364
- // Should render 100 components
1365
- expect(progressComponents.length).toBe(100);
1366
-
1367
- // Should render in reasonable time (less than 1 second)
1368
- expect(renderTime).toBeLessThan(1000);
1369
- });
1370
-
1371
- await step('Verify all components are visible', async () => {
1372
- // Test first 10 components for visibility
1373
- for (let i = 0; i < 10; i++) {
1374
- const component = canvas.getByTestId(`perf-item-${i}`);
1375
- await expect(component).toBeVisible();
1376
-
1377
- // Each should have a progress bar
1378
- const progressBar = component.querySelector('.MuiLinearProgress-bar');
1379
- expect(progressBar).toBeTruthy();
1380
- }
1381
- });
1382
-
1383
- await step('Test scroll performance', async () => {
1384
- const container = canvas.getByTestId('performance-container');
1385
- const parentContainer = container.closest('[style*="overflow"]') || container.parentElement;
1386
-
1387
- if (parentContainer) {
1388
- // Simulate scrolling
1389
- const scrollStartTime = window.performance.now();
1390
-
1391
- for (let i = 0; i < 5; i++) {
1392
- parentContainer.scrollTop = i * 50;
1393
- await new Promise((resolve) => window.setTimeout(resolve, 10));
1394
- }
1395
-
1396
- const scrollEndTime = window.performance.now();
1397
- const scrollTime = scrollEndTime - scrollStartTime;
1398
-
1399
- // console.log(`Scroll performance test completed in: ${scrollTime}ms`);
1400
-
1401
- // Scrolling should be smooth (less than 600ms total for 5 scroll steps with 100 components)
1402
- // Note: Performance varies depending on system load, so we use a reasonable threshold
1403
- expect(scrollTime).toBeLessThan(600);
1404
- }
1405
-
1406
- // Container should still be present after scrolling
1407
- await expect(container).toBeInTheDocument();
1408
- });
1409
-
1410
- await step('Memory usage consideration', async () => {
1411
- // Verify that components are properly rendered without memory leaks
1412
- const allProgressBars = canvas.getAllByTestId(/^perf-item-/);
1413
-
1414
- // All 100 components should be found
1415
- expect(allProgressBars.length).toBe(100);
1416
-
1417
- // Each should be a valid DOM element
1418
- allProgressBars.forEach((bar, index) => {
1419
- expect(bar).toBeInTheDocument();
1420
- expect(bar).toHaveAttribute('data-testid', `perf-item-${index}`);
1421
- });
1422
- });
1423
-
1424
- // Add a final PASS indicator for the test
1425
- await step('Performance test completed', async () => {
1426
- const statusElement = document.createElement('div');
1427
- statusElement.setAttribute('aria-label', 'Status of the test run');
1428
- statusElement.textContent = 'PASS';
1429
- document.body.appendChild(statusElement);
1430
- });
1431
- },
1432
- };
1433
-
1434
- const PerformanceTestComponent = () => {
1435
- const [items] = React.useState(() =>
1436
- Array.from({ length: 100 }, (_, i) => ({
1437
- id: i,
1438
- value: Math.floor(Math.random() * 100),
1439
- label: `Item ${i + 1}`,
1440
- })),
1441
- );
1442
-
1443
- return (
1444
- <Box sx={{ p: 3, maxHeight: 400, overflow: 'auto' }}>
1445
- <Typography variant="h6" sx={{ mb: 2 }}>
1446
- Performance Test - 100 Progress Bars
1447
- </Typography>
1448
-
1449
- <Box data-testid="performance-container">
1450
- <Stack spacing={1}>
1451
- {items.map((item, index) => (
1452
- <Box key={item.id} sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
1453
- <Typography variant="caption" sx={{ minWidth: 60 }}>
1454
- {item.label}
1455
- </Typography>
1456
- <Progress
1457
- value={item.value}
1458
- showLabel
1459
- size="sm"
1460
- data-testid={`perf-item-${index}`}
1461
- sx={{ flex: 1 }}
1462
- />
1463
- </Box>
1464
- ))}
1465
- </Stack>
1466
- </Box>
1467
-
1468
- <Typography variant="caption" sx={{ mt: 2, display: 'block' }}>
1469
- Rendered {items.length} progress components
1470
- </Typography>
1471
- </Box>
1472
- );
1473
- };
1474
-
1475
- // Edge Cases Test
1476
- export const EdgeCases: Story = {
1477
- name: '🔧 Edge Cases Test',
1478
- render: () => (
1479
- <Box sx={{ p: 3, width: 600 }}>
1480
- <Typography variant="h6" sx={{ mb: 3 }}>
1481
- Edge Cases and Boundary Conditions
1482
- </Typography>
1483
-
1484
- <Stack spacing={4} data-testid="edge-cases-container">
1485
- {/* Boundary values */}
1486
- <Box>
1487
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
1488
- Boundary Values
1489
- </Typography>
1490
- <Stack spacing={2}>
1491
- <Box>
1492
- <Typography variant="caption">0% Progress</Typography>
1493
- <Progress value={0} showLabel data-testid="zero-progress" />
1494
- </Box>
1495
- <Box>
1496
- <Typography variant="caption">100% Progress</Typography>
1497
- <Progress value={100} showLabel data-testid="full-progress" />
1498
- </Box>
1499
- <Box>
1500
- <Typography variant="caption">Over 100% (should cap at 100%)</Typography>
1501
- <Progress value={150} showLabel data-testid="over-progress" />
1502
- </Box>
1503
- <Box>
1504
- <Typography variant="caption">Negative Value (should default to 0)</Typography>
1505
- <Progress value={-25} showLabel data-testid="negative-progress" />
1506
- </Box>
1507
- </Stack>
1508
- </Box>
1509
-
1510
- {/* Invalid or undefined values */}
1511
- <Box>
1512
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
1513
- Invalid/Undefined Values
1514
- </Typography>
1515
- <Stack spacing={2}>
1516
- <Box>
1517
- <Typography variant="caption">Undefined Value (Indeterminate)</Typography>
1518
- <Progress value={undefined} showLabel data-testid="undefined-progress" />
1519
- </Box>
1520
- <Box>
1521
- <Typography variant="caption">NaN Value</Typography>
1522
- <Progress value={NaN} showLabel data-testid="nan-progress" />
1523
- </Box>
1524
- <Box>
1525
- <Typography variant="caption">Float Value (75.5%)</Typography>
1526
- <Progress value={75.5} showLabel data-testid="float-progress" />
1527
- </Box>
1528
- </Stack>
1529
- </Box>
1530
-
1531
- {/* Extreme configurations */}
1532
- <Box>
1533
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
1534
- Extreme Configurations
1535
- </Typography>
1536
- <Stack spacing={2}>
1537
- <Box>
1538
- <Typography variant="caption">Very Long Label</Typography>
1539
- <Progress
1540
- value={50}
1541
- label="This is a very long label that might cause overflow issues in the progress component layout and should be handled gracefully"
1542
- showLabel
1543
- data-testid="long-label-progress"
1544
- />
1545
- </Box>
1546
- <Box>
1547
- <Typography variant="caption">Empty Label</Typography>
1548
- <Progress value={75} label="" showLabel data-testid="empty-label-progress" />
1549
- </Box>
1550
- <Box>
1551
- <Typography variant="caption">Many Segments (50)</Typography>
1552
- <Progress
1553
- variant="segmented"
1554
- segments={50}
1555
- value={60}
1556
- showLabel
1557
- data-testid="many-segments-progress"
1558
- sx={{ maxWidth: '100%' }}
1559
- />
1560
- </Box>
1561
- <Box>
1562
- <Typography variant="caption">Single Segment</Typography>
1563
- <Progress
1564
- variant="segmented"
1565
- segments={1}
1566
- value={80}
1567
- showLabel
1568
- data-testid="single-segment-progress"
1569
- />
1570
- </Box>
1571
- </Stack>
1572
- </Box>
1573
-
1574
- {/* Rapid updates simulation */}
1575
- <Box>
1576
- <Typography variant="subtitle2" sx={{ mb: 2 }}>
1577
- Rapid Updates
1578
- </Typography>
1579
- <RapidUpdateProgress />
1580
- </Box>
1581
- </Stack>
1582
- </Box>
1583
- ),
1584
- play: async ({ canvasElement, step }) => {
1585
- const canvas = within(canvasElement);
1586
-
1587
- await step('Boundary value handling', async () => {
1588
- const zeroProgress = canvas.getByTestId('zero-progress');
1589
- const fullProgress = canvas.getByTestId('full-progress');
1590
- const overProgress = canvas.getByTestId('over-progress');
1591
- const negativeProgress = canvas.getByTestId('negative-progress');
1592
-
1593
- await expect(zeroProgress).toBeVisible();
1594
- await expect(fullProgress).toBeVisible();
1595
- await expect(overProgress).toBeVisible();
1596
- await expect(negativeProgress).toBeVisible();
1597
-
1598
- // Check labels show correct values using testid containers
1599
- const zeroProgressContainer = canvas.getByTestId('zero-progress').closest('div');
1600
- const fullProgressContainer = canvas.getByTestId('full-progress').closest('div');
1601
- const overProgressContainer = canvas.getByTestId('over-progress').closest('div');
1602
- const negativeProgressContainer = canvas.getByTestId('negative-progress').closest('div');
1603
-
1604
- // Verify the components contain the expected labels
1605
- await expect(zeroProgressContainer).toHaveTextContent('0%');
1606
- await expect(fullProgressContainer).toHaveTextContent('100%');
1607
- // Over 100% should be capped at 100% (MUI behavior) or show actual value
1608
- const overText = overProgressContainer?.textContent || '';
1609
- expect(overText).toMatch(/100%|150%/); // Could be capped or show actual
1610
- // Negative should show 0% (clamped) or actual negative value
1611
- const negativeText = negativeProgressContainer?.textContent || '';
1612
- expect(negativeText).toMatch(/0%|-25%/);
1613
-
1614
- // Verify actual progress bar exists for boundary values
1615
- const zeroBar = zeroProgress.querySelector('.MuiLinearProgress-bar');
1616
- const fullBar = fullProgress.querySelector('.MuiLinearProgress-bar');
1617
-
1618
- // Just verify bars exist and are rendered
1619
- if (zeroBar) {
1620
- expect(zeroBar).toBeInTheDocument();
1621
- }
1622
-
1623
- if (fullBar) {
1624
- expect(fullBar).toBeInTheDocument();
1625
- }
1626
- });
1627
-
1628
- await step('Invalid value handling', async () => {
1629
- const undefinedProgress = canvas.getByTestId('undefined-progress');
1630
- const nanProgress = canvas.getByTestId('nan-progress');
1631
- const floatProgress = canvas.getByTestId('float-progress');
1632
-
1633
- await expect(undefinedProgress).toBeVisible();
1634
- await expect(nanProgress).toBeVisible();
1635
- await expect(floatProgress).toBeVisible();
1636
-
1637
- // Float value should be handled correctly
1638
- const floatLabel = canvas.getByText('76%'); // Should round 75.5 to 76
1639
- await expect(floatLabel).toBeInTheDocument();
1640
-
1641
- // Verify float progress bar exists
1642
- const floatBar = floatProgress.querySelector('.MuiLinearProgress-bar');
1643
- if (floatBar) {
1644
- // Just verify the bar exists and is rendered
1645
- expect(floatBar).toBeInTheDocument();
1646
- }
1647
-
1648
- // Undefined should work as indeterminate
1649
- const undefinedBar1 = undefinedProgress.querySelector('.MuiLinearProgress-bar1Indeterminate');
1650
- const undefinedBar2 = undefinedProgress.querySelector('.MuiLinearProgress-bar2Indeterminate');
1651
- const undefinedBarDeterminate = undefinedProgress.querySelector('.MuiLinearProgress-bar');
1652
-
1653
- // Should have indeterminate bars or a regular bar
1654
- expect(undefinedBar1 || undefinedBar2 || undefinedBarDeterminate).toBeTruthy();
1655
-
1656
- // If indeterminate, should have animation
1657
- if (undefinedBar1) {
1658
- const bar1Style = window.getComputedStyle(undefinedBar1);
1659
- expect(bar1Style.animation).not.toBe('none');
1660
- }
1661
-
1662
- // NaN should be handled gracefully (treated as 0)
1663
- const nanBar = nanProgress.querySelector('.MuiLinearProgress-bar');
1664
- expect(nanBar).toBeTruthy();
1665
-
1666
- if (nanBar) {
1667
- // Just verify the bar exists and is rendered
1668
- expect(nanBar).toBeInTheDocument();
1669
- }
1670
- });
1671
-
1672
- await step('Extreme configuration handling', async () => {
1673
- const longLabelProgress = canvas.getByTestId('long-label-progress');
1674
- const emptyLabelProgress = canvas.getByTestId('empty-label-progress');
1675
- const singleSegmentProgress = canvas.getByTestId('single-segment-progress');
1676
-
1677
- await expect(longLabelProgress).toBeVisible();
1678
- await expect(emptyLabelProgress).toBeVisible();
1679
- await expect(singleSegmentProgress).toBeVisible();
1680
-
1681
- // Test many segments (50 segments at 60% = 30 filled)
1682
- const manySegmentsProgress = canvas.getByTestId('many-segments-progress');
1683
- await expect(manySegmentsProgress).toBeVisible();
1684
-
1685
- // Many segments should render without breaking - look for child boxes (segments)
1686
- const manySegmentContainer = manySegmentsProgress.querySelector(
1687
- 'div[style*="display: flex"], div div',
1688
- );
1689
- if (manySegmentContainer) {
1690
- const segments = manySegmentContainer.children;
1691
- expect(segments.length).toBe(50); // Should have exactly 50 segments
1692
-
1693
- // Count filled segments for 60% progress
1694
- let filledCount = 0;
1695
- for (let i = 0; i < segments.length; i++) {
1696
- const segment = segments[i] as HTMLElement;
1697
- const style = window.getComputedStyle(segment);
1698
- const bgColor = style.backgroundColor;
1699
-
1700
- // Check if segment is filled (not transparent/low opacity)
1701
- if (bgColor && !bgColor.includes('0.1') && bgColor !== 'rgba(0, 0, 0, 0)') {
1702
- filledCount++;
1703
- }
1704
- }
1705
-
1706
- // For 60% of 50 segments, expect 30 filled
1707
- expect(filledCount).toBe(30);
1708
- }
1709
-
1710
- // Long label should not break layout
1711
- const longLabelText = longLabelProgress.querySelector('span');
1712
- if (longLabelText) {
1713
- const boundingBox = longLabelText.getBoundingClientRect();
1714
- expect(boundingBox.width).toBeGreaterThan(0);
1715
- }
1716
-
1717
- // Empty label should still render progress
1718
- const emptyBar = emptyLabelProgress.querySelector('.MuiLinearProgress-bar');
1719
- expect(emptyBar).toBeTruthy();
1720
-
1721
- // Single segment should render as one block
1722
- const singleSegmentContainer = singleSegmentProgress.querySelector(
1723
- 'div[style*="display: flex"], div div',
1724
- );
1725
- if (singleSegmentContainer) {
1726
- const singleSegments = singleSegmentContainer.children;
1727
- expect(singleSegments.length).toBe(1);
1728
- } else {
1729
- // Just verify the component is visible if we can't find segments
1730
- await expect(singleSegmentProgress).toBeVisible();
1731
- }
1732
- });
1733
-
1734
- await step('Component stability', async () => {
1735
- // All edge case components should be stable and not crash
1736
- const allProgressComponents = [
1737
- 'zero-progress',
1738
- 'full-progress',
1739
- 'over-progress',
1740
- 'negative-progress',
1741
- 'undefined-progress',
1742
- 'nan-progress',
1743
- 'float-progress',
1744
- 'long-label-progress',
1745
- 'empty-label-progress',
1746
- 'many-segments-progress',
1747
- 'single-segment-progress',
1748
- ];
1749
-
1750
- allProgressComponents.forEach(async (testId) => {
1751
- const component = canvas.getByTestId(testId);
1752
- await expect(component).toBeInTheDocument();
1753
- await expect(component).toBeVisible();
1754
-
1755
- // Should not have any error boundaries or broken rendering
1756
- const hasError = component.querySelector('[data-error], .error, [aria-errormessage]');
1757
- expect(hasError).toBeFalsy();
1758
- });
1759
- });
1760
- },
1761
- };
1762
-
1763
- // Rapid Update Progress Component
1764
- const RapidUpdateProgress: React.FC = () => {
1765
- const [progress, setProgress] = React.useState(0);
1766
- const [isUpdating, setIsUpdating] = React.useState(false);
1767
-
1768
- const startRapidUpdates = () => {
1769
- setIsUpdating(true);
1770
- setProgress(0);
1771
-
1772
- let currentProgress = 0;
1773
- const interval = window.setInterval(() => {
1774
- currentProgress += Math.random() * 5;
1775
- if (currentProgress >= 100) {
1776
- currentProgress = 100;
1777
- window.clearInterval(interval);
1778
- setIsUpdating(false);
1779
- }
1780
- setProgress(currentProgress);
1781
- }, 50); // Very rapid updates every 50ms
1782
- };
1783
-
1784
- return (
1785
- <Box>
1786
- <Progress
1787
- value={Math.round(progress)}
1788
- showLabel
1789
- data-testid="rapid-update-progress"
1790
- pulse={isUpdating}
1791
- />
1792
- <Button
1793
- variant="outlined"
1794
- size="small"
1795
- onClick={startRapidUpdates}
1796
- disabled={isUpdating}
1797
- sx={{ mt: 1 }}
1798
- data-testid="rapid-update-button"
1799
- >
1800
- {isUpdating ? 'Updating...' : 'Start Rapid Updates'}
1801
- </Button>
1802
- </Box>
1803
- );
1804
- };
1805
-
1806
- // Integration Test
1807
- const IntegrationTestComponent = () => {
1808
- const [uploadProgress, setUploadProgress] = React.useState(0);
1809
- const [processingProgress, setProcessingProgress] = React.useState(0);
1810
- const [currentStep, setCurrentStep] = React.useState(0);
1811
- const [isActive, setIsActive] = React.useState(false);
1812
-
1813
- const steps = ['Initializing', 'Uploading files', 'Processing data', 'Finalizing', 'Complete'];
1814
-
1815
- const startWorkflow = () => {
1816
- setIsActive(true);
1817
- setCurrentStep(0);
1818
- setUploadProgress(0);
1819
- setProcessingProgress(0);
1820
-
1821
- // Step 1: Upload simulation
1822
- let upload = 0;
1823
- const uploadInterval = window.setInterval(() => {
1824
- upload += Math.random() * 15;
1825
- if (upload >= 100) {
1826
- upload = 100;
1827
- setUploadProgress(upload);
1828
- setCurrentStep(2);
1829
- window.clearInterval(uploadInterval);
1830
-
1831
- // Step 2: Processing simulation
1832
- let processing = 0;
1833
- const processInterval = window.setInterval(() => {
1834
- processing += Math.random() * 10;
1835
- if (processing >= 100) {
1836
- processing = 100;
1837
- setProcessingProgress(processing);
1838
- setCurrentStep(4);
1839
- setIsActive(false);
1840
- window.clearInterval(processInterval);
1841
- } else {
1842
- setProcessingProgress(processing);
1843
- }
1844
- }, 150);
1845
- } else {
1846
- setUploadProgress(upload);
1847
- setCurrentStep(1);
1848
- }
1849
- }, 100);
1850
- };
1851
-
1852
- return (
1853
- <Box sx={{ width: 500, p: 3 }}>
1854
- <Typography variant="h6" sx={{ mb: 3 }}>
1855
- Multi-Component Integration Test
1856
- </Typography>
1857
-
1858
- {/* Step indicator */}
1859
- <Box sx={{ mb: 3 }} data-testid="step-indicator">
1860
- <Typography variant="subtitle2" sx={{ mb: 1 }}>
1861
- Current Step: {steps[currentStep]}
1862
- </Typography>
1863
- <Progress
1864
- variant="segmented"
1865
- segments={5}
1866
- value={(currentStep / 4) * 100}
1867
- color="primary"
1868
- data-testid="step-progress"
1869
- />
1870
- </Box>
1871
-
1872
- {/* Upload progress */}
1873
- <Box sx={{ mb: 3 }}>
1874
- <Typography variant="subtitle2" sx={{ mb: 1 }}>
1875
- Upload Progress
1876
- </Typography>
1877
- <Progress
1878
- value={uploadProgress}
1879
- showLabel
1880
- color="success"
1881
- glow={currentStep === 1}
1882
- data-testid="upload-progress"
1883
- />
1884
- </Box>
1885
-
1886
- {/* Processing progress */}
1887
- <Box sx={{ mb: 3 }}>
1888
- <Typography variant="subtitle2" sx={{ mb: 1 }}>
1889
- Processing Progress
1890
- </Typography>
1891
- <Progress
1892
- variant="circular"
1893
- value={processingProgress}
1894
- showLabel
1895
- color="warning"
1896
- pulse={currentStep === 2}
1897
- data-testid="processing-progress"
1898
- />
1899
- </Box>
1900
-
1901
- {/* Overall progress */}
1902
- <Box sx={{ mb: 3 }}>
1903
- <Typography variant="subtitle2" sx={{ mb: 1 }}>
1904
- Overall Progress
1905
- </Typography>
1906
- <Progress
1907
- variant="gradient"
1908
- value={(uploadProgress + processingProgress) / 2}
1909
- showLabel
1910
- color="primary"
1911
- glow={isActive}
1912
- data-testid="overall-progress"
1913
- />
1914
- </Box>
1915
-
1916
- {/* Control button */}
1917
- <Button
1918
- variant="contained"
1919
- onClick={startWorkflow}
1920
- disabled={isActive}
1921
- data-testid="start-workflow-button"
1922
- fullWidth
1923
- >
1924
- {isActive ? 'Workflow Running...' : 'Start Workflow'}
1925
- </Button>
1926
-
1927
- {/* Status message */}
1928
- <Typography variant="body2" sx={{ mt: 2, textAlign: 'center' }} data-testid="status-message">
1929
- Status: {isActive ? 'Running' : currentStep === 4 ? 'Completed' : 'Ready'}
1930
- </Typography>
1931
- </Box>
1932
- );
1933
- };
1934
-
1935
- export const Integration: Story = {
1936
- name: '🔗 Integration Test',
1937
- render: () => <IntegrationTestComponent />,
1938
- play: async ({ canvasElement, step }) => {
1939
- const canvas = within(canvasElement);
1940
-
1941
- await step('Initial integration state', async () => {
1942
- const stepProgress = canvas.getByTestId('step-progress');
1943
- const uploadProgress = canvas.getByTestId('upload-progress');
1944
- const processingProgress = canvas.getByTestId('processing-progress');
1945
- const overallProgress = canvas.getByTestId('overall-progress');
1946
- const startButton = canvas.getByTestId('start-workflow-button');
1947
-
1948
- await expect(stepProgress).toBeInTheDocument();
1949
- await expect(uploadProgress).toBeInTheDocument();
1950
- await expect(processingProgress).toBeInTheDocument();
1951
- await expect(overallProgress).toBeInTheDocument();
1952
- await expect(startButton).toBeEnabled();
1953
-
1954
- // Initial status should be "Ready"
1955
- const statusMessage = canvas.getByText('Status: Ready');
1956
- await expect(statusMessage).toBeInTheDocument();
1957
- });
1958
-
1959
- await step('Start workflow integration', async () => {
1960
- const startButton = canvas.getByTestId('start-workflow-button');
1961
- await userEvent.click(startButton);
1962
-
1963
- // Button should become disabled
1964
- await expect(startButton).toBeDisabled();
1965
-
1966
- // Status should change to running
1967
- await waitFor(() => {
1968
- const runningStatus = canvas.getByText('Status: Running');
1969
- expect(runningStatus).toBeInTheDocument();
1970
- });
1971
-
1972
- // Step indicator should show progress
1973
- const stepIndicator = canvas.getByTestId('step-indicator');
1974
- await expect(stepIndicator).toContainElement(canvas.getByText(/Current Step:/));
1975
- });
1976
-
1977
- await step('Upload phase integration', async () => {
1978
- // Wait for upload to start
1979
- await waitFor(
1980
- () => {
1981
- const uploadProgress = canvas.getByTestId('upload-progress');
1982
- const progressBar = uploadProgress.querySelector('.MuiLinearProgress-bar');
1983
- if (progressBar) {
1984
- // Just verify the progress bar exists and is visible
1985
- expect(progressBar).toBeVisible();
1986
- }
1987
- },
1988
- { timeout: 3000 },
1989
- );
1990
-
1991
- // Step should show "Uploading files"
1992
- await waitFor(
1993
- () => {
1994
- const uploadingStep = canvas.getByText('Current Step: Uploading files');
1995
- expect(uploadingStep).toBeInTheDocument();
1996
- },
1997
- { timeout: 2000 },
1998
- );
1999
- });
2000
-
2001
- await step('Processing phase integration', async () => {
2002
- // Wait for processing to start
2003
- await waitFor(
2004
- () => {
2005
- const processingStep = canvas.getByText('Current Step: Processing data');
2006
- expect(processingStep).toBeInTheDocument();
2007
- },
2008
- { timeout: 5000 },
2009
- );
2010
-
2011
- // Processing progress should have pulse effect
2012
- const processingProgress = canvas.getByTestId('processing-progress');
2013
- const circularSvg = processingProgress.querySelector('.MuiCircularProgress-svg');
2014
-
2015
- if (circularSvg) {
2016
- const style = window.getComputedStyle(circularSvg);
2017
- // Should have animation for pulse
2018
- expect(style.animation).not.toBe('none');
2019
- }
2020
- });
2021
-
2022
- await step('Workflow completion integration', async () => {
2023
- // Wait for completion
2024
- await waitFor(
2025
- () => {
2026
- const completedStatus = canvas.getByText('Status: Completed');
2027
- expect(completedStatus).toBeInTheDocument();
2028
- },
2029
- { timeout: 10000 },
2030
- );
2031
-
2032
- // Final step should show "Complete"
2033
- const completeStep = canvas.getByText('Current Step: Complete');
2034
- await expect(completeStep).toBeInTheDocument();
2035
-
2036
- // Start button should be re-enabled
2037
- const startButton = canvas.getByTestId('start-workflow-button');
2038
- await expect(startButton).toBeEnabled();
2039
-
2040
- // Overall progress should show completion
2041
- const overallProgress = canvas.getByTestId('overall-progress');
2042
- const overallLabel = overallProgress.querySelector('span');
2043
- if (overallLabel && overallLabel.textContent) {
2044
- const progressValue = parseInt(overallLabel.textContent);
2045
- expect(progressValue).toBeGreaterThan(80); // Should be near completion
2046
- }
2047
- });
2048
-
2049
- await step('Component interaction verification', async () => {
2050
- // All progress components should be present and functional
2051
- const allProgressComponents = [
2052
- canvas.getByTestId('step-progress'),
2053
- canvas.getByTestId('upload-progress'),
2054
- canvas.getByTestId('processing-progress'),
2055
- canvas.getByTestId('overall-progress'),
2056
- ];
2057
-
2058
- allProgressComponents.forEach(async (component) => {
2059
- await expect(component).toBeVisible();
2060
- await expect(component).toBeInTheDocument();
2061
-
2062
- // Each should have some form of progress indication
2063
- const hasProgressBar = component.querySelector(
2064
- '.MuiLinearProgress-bar, .MuiCircularProgress-svg, div[style*="display: flex"], div div',
2065
- );
2066
- expect(hasProgressBar).toBeTruthy();
2067
- });
2068
- });
2069
- },
2070
- };