@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,1191 +0,0 @@
1
- import Email from '@mui/icons-material/Email';
2
- import Lock from '@mui/icons-material/Lock';
3
- import Person from '@mui/icons-material/Person';
4
- import Visibility from '@mui/icons-material/Visibility';
5
- import VisibilityOff from '@mui/icons-material/VisibilityOff';
6
- import { IconButton } from '@mui/material';
7
- import type { Meta, StoryObj } from '@storybook/react-vite';
8
- import { useState } from 'react';
9
- import { expect, fn,userEvent, waitFor, within } from 'storybook/test';
10
-
11
- import { Input } from './Input';
12
-
13
- const meta: Meta<typeof Input> = {
14
- title: 'Form/Input/Tests',
15
- component: Input,
16
- parameters: {
17
- layout: 'centered',
18
- chromatic: { disableSnapshot: false },
19
- },
20
- tags: ['autodocs', 'test', 'component:Input'],
21
- };
22
-
23
- export default meta;
24
- type Story = StoryObj<typeof meta>;
25
-
26
- // ============================================================================
27
- // INTERACTION TESTS
28
- // ============================================================================
29
-
30
- export const BasicInteraction: Story = {
31
- name: '🧪 Basic Interaction Test',
32
- args: {
33
- label: 'Test Input',
34
- placeholder: 'Type here...',
35
- 'data-testid': 'basic-input',
36
- onChange: fn(),
37
- onFocus: fn(),
38
- onBlur: fn(),
39
- onClick: fn(),
40
- },
41
- play: async ({ canvasElement, step, args }) => {
42
- const canvas = within(canvasElement);
43
-
44
- await step('Initial render verification', async () => {
45
- const input = canvas.getByLabelText('Test Input');
46
- await expect(input).toBeInTheDocument();
47
- await expect(input).toHaveAttribute('placeholder', 'Type here...');
48
- });
49
-
50
- await step('Click interaction', async () => {
51
- const input = canvas.getByLabelText('Test Input');
52
- await userEvent.click(input);
53
- await expect(args.onClick).toHaveBeenCalled();
54
- });
55
-
56
- await step('Focus interaction', async () => {
57
- const input = canvas.getByLabelText('Test Input');
58
- await userEvent.click(input);
59
- await expect(args.onFocus).toHaveBeenCalled();
60
- });
61
-
62
- await step('Type interaction', async () => {
63
- const input = canvas.getByLabelText('Test Input');
64
- await userEvent.type(input, 'Hello World');
65
- await expect(input).toHaveValue('Hello World');
66
- await expect(args.onChange).toHaveBeenCalled();
67
- });
68
-
69
- await step('Clear input', async () => {
70
- const input = canvas.getByLabelText('Test Input');
71
- await userEvent.clear(input);
72
- await expect(input).toHaveValue('');
73
- });
74
-
75
- await step('Blur interaction', async () => {
76
- const input = canvas.getByLabelText('Test Input');
77
- await userEvent.click(input);
78
- await userEvent.tab(); // Move focus away
79
- await expect(args.onBlur).toHaveBeenCalled();
80
- });
81
- },
82
- };
83
-
84
- export const FormInteraction: Story = {
85
- name: '📝 Form Interaction Test',
86
- render: () => {
87
- const TestForm = () => {
88
- const [email, setEmail] = useState('');
89
- const [password, setPassword] = useState('');
90
- const [submitted, setSubmitted] = useState(false);
91
-
92
- const handleSubmit = (e: React.FormEvent) => {
93
- e.preventDefault();
94
- setSubmitted(true);
95
- };
96
-
97
- return (
98
- <form
99
- onSubmit={handleSubmit}
100
- style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}
101
- >
102
- <Input
103
- label="Email"
104
- type="email"
105
- value={email}
106
- onChange={(e) => setEmail(e.target.value)}
107
- data-testid="form-email"
108
- required
109
- />
110
- <Input
111
- label="Password"
112
- type="password"
113
- value={password}
114
- onChange={(e) => setPassword(e.target.value)}
115
- data-testid="form-password"
116
- required
117
- />
118
- <button type="submit" data-testid="form-submit">
119
- Submit
120
- </button>
121
- {submitted && <div data-testid="form-success">Form submitted with email: {email}</div>}
122
- </form>
123
- );
124
- };
125
-
126
- return <TestForm />;
127
- },
128
- play: async ({ canvasElement, step }) => {
129
- const canvas = within(canvasElement);
130
-
131
- await step('Fill email field', async () => {
132
- const emailInput = canvas.getByTestId('form-email');
133
- await userEvent.type(emailInput, 'test@example.com');
134
- await expect(emailInput).toHaveValue('test@example.com');
135
- });
136
-
137
- await step('Fill password field', async () => {
138
- const passwordInput = canvas.getByTestId('form-password');
139
- await userEvent.type(passwordInput, 'secretpassword');
140
- await expect(passwordInput).toHaveValue('secretpassword');
141
- });
142
-
143
- await step('Submit form', async () => {
144
- const submitButton = canvas.getByTestId('form-submit');
145
- await userEvent.click(submitButton);
146
-
147
- await waitFor(async () => {
148
- const success = canvas.getByTestId('form-success');
149
- await expect(success).toHaveTextContent('Form submitted with email: test@example.com');
150
- });
151
- });
152
- },
153
- };
154
-
155
- export const StateChangeTest: Story = {
156
- name: '🔄 State Change Test',
157
- render: () => {
158
- const StatefulComponent = () => {
159
- const [value, setValue] = useState('');
160
- const [error, setError] = useState(false);
161
- const [loading, setLoading] = useState(false);
162
-
163
- const handleValidation = (inputValue: string) => {
164
- setError(inputValue.length < 3);
165
- };
166
-
167
- // Toggle only. The old version also queued a 2s timer to clear the flag,
168
- // which made the rendered state depend on wall-clock time — a play
169
- // function that ran slowly would see `loading` flip under it.
170
- const handleLoadingToggle = () => {
171
- setLoading((previous) => !previous);
172
- };
173
-
174
- return (
175
- <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
176
- <Input
177
- label="Stateful Input"
178
- value={value}
179
- onChange={(e) => {
180
- setValue(e.target.value);
181
- handleValidation(e.target.value);
182
- }}
183
- error={error}
184
- helperText={error ? 'Must be at least 3 characters' : 'Type at least 3 characters'}
185
- data-testid="stateful-input"
186
- />
187
- <Input
188
- label="Loading Input"
189
- loading={loading}
190
- placeholder="Click button to simulate loading"
191
- data-testid="loading-input"
192
- />
193
- <button onClick={handleLoadingToggle} data-testid="loading-toggle">
194
- Toggle Loading
195
- </button>
196
- <div data-testid="state-display">
197
- {`Value: ${value || ''} | Error: ${error.toString()} | Loading: ${loading.toString()}`}
198
- </div>
199
- </div>
200
- );
201
- };
202
-
203
- return <StatefulComponent />;
204
- },
205
- play: async ({ canvasElement, step }) => {
206
- const canvas = within(canvasElement);
207
-
208
- await step('Verify initial state', async () => {
209
- const stateDisplay = canvas.getByTestId('state-display');
210
- await expect(stateDisplay.textContent).toMatch(
211
- /Value:\s+\|\s+Error:\s+false\s+\|\s+Loading:\s+false/,
212
- );
213
- });
214
-
215
- await step('Type short text to trigger error', async () => {
216
- const input = canvas.getByTestId('stateful-input');
217
- await userEvent.type(input, 'ab');
218
-
219
- await waitFor(async () => {
220
- const stateDisplay = canvas.getByTestId('state-display');
221
- await expect(stateDisplay).toHaveTextContent('Error: true');
222
- await expect(input).toHaveAttribute('aria-invalid', 'true');
223
- });
224
- });
225
-
226
- await step('Type longer text to clear error', async () => {
227
- const input = canvas.getByTestId('stateful-input');
228
- await userEvent.clear(input);
229
- await userEvent.type(input, 'valid input');
230
-
231
- await waitFor(async () => {
232
- const stateDisplay = canvas.getByTestId('state-display');
233
- await expect(stateDisplay).toHaveTextContent('Error: false');
234
- await expect(input).not.toHaveAttribute('aria-invalid', 'true');
235
- });
236
- });
237
-
238
- await step('Toggle loading state', async () => {
239
- const loadingToggle = canvas.getByTestId('loading-toggle');
240
- await userEvent.click(loadingToggle);
241
-
242
- const stateDisplay = canvas.getByTestId('state-display');
243
- await expect(stateDisplay).toHaveTextContent('Loading: true');
244
-
245
- const loadingInput = canvas.getByTestId('loading-input');
246
- await expect(loadingInput).toBeDisabled();
247
- });
248
- },
249
- };
250
-
251
- // ============================================================================
252
- // ACCESSIBILITY TESTS
253
- // ============================================================================
254
-
255
- export const KeyboardNavigation: Story = {
256
- name: '⌨️ Keyboard Navigation Test',
257
- render: () => (
258
- <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
259
- <Input label="First Input" placeholder="Tab to navigate" data-testid="first-input" />
260
- <Input label="Second Input" placeholder="Continue tabbing" data-testid="second-input" />
261
- <Input label="Third Input" placeholder="Final input" data-testid="third-input" />
262
- <button data-testid="after-inputs">Button After Inputs</button>
263
- </div>
264
- ),
265
- parameters: {
266
- a11y: {
267
- element: '#storybook-root',
268
- config: {
269
- rules: [
270
- { id: 'color-contrast', enabled: true },
271
- { id: 'aria-required-attr', enabled: true },
272
- { id: 'aria-roles', enabled: true },
273
- { id: 'aria-valid-attr-value', enabled: true },
274
- { id: 'label', enabled: true },
275
- ],
276
- },
277
- },
278
- },
279
- play: async ({ canvasElement, step }) => {
280
- const canvas = within(canvasElement);
281
-
282
- await step('Tab navigation forward', async () => {
283
- const firstInput = canvas.getByTestId('first-input');
284
- const secondInput = canvas.getByTestId('second-input');
285
-
286
- // Tab in from the body rather than calling .focus().
287
- await userEvent.tab();
288
- await waitFor(() => expect(firstInput).toHaveFocus());
289
-
290
- // Tab to next element
291
- await userEvent.tab();
292
- await waitFor(() => expect(secondInput).toHaveFocus());
293
- });
294
-
295
- await step('Tab navigation continues', async () => {
296
- const thirdInput = canvas.getByTestId('third-input');
297
- const button = canvas.getByTestId('after-inputs');
298
-
299
- await userEvent.tab();
300
- await waitFor(() => expect(thirdInput).toHaveFocus());
301
-
302
- await userEvent.tab();
303
- await waitFor(() => expect(button).toHaveFocus());
304
- });
305
-
306
- await step('Tab navigation backward', async () => {
307
- const thirdInput = canvas.getByTestId('third-input');
308
-
309
- await userEvent.tab({ shift: true });
310
- await waitFor(() => expect(thirdInput).toHaveFocus());
311
- });
312
-
313
- await step('Enter key in input (should not submit)', async () => {
314
- const firstInput = canvas.getByTestId('first-input');
315
-
316
- // Typing into an input focuses it, so the extra .focus() was redundant.
317
- await userEvent.type(firstInput, 'test');
318
- await userEvent.keyboard('{Enter}');
319
-
320
- // Input should still have focus and content
321
- await waitFor(() => expect(firstInput).toHaveFocus());
322
- await expect(firstInput).toHaveValue('test');
323
- });
324
- },
325
- };
326
-
327
- export const ScreenReaderTest: Story = {
328
- name: '🔊 Screen Reader Test',
329
- render: () => (
330
- <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
331
- <Input
332
- label="Full Name"
333
- placeholder="Enter your full name"
334
- helperText="This field is required for registration"
335
- required
336
- data-testid="name-input"
337
- aria-describedby="name-description"
338
- />
339
- <div id="name-description" data-testid="name-description">
340
- Please enter your first and last name
341
- </div>
342
-
343
- <Input
344
- label="Email Address"
345
- type="email"
346
- error
347
- helperText="Please enter a valid email address"
348
- data-testid="email-input"
349
- aria-invalid="true"
350
- aria-describedby="email-error"
351
- />
352
- <div id="email-error" data-testid="email-error" role="alert">
353
- The email format is invalid
354
- </div>
355
- </div>
356
- ),
357
- play: async ({ canvasElement, step }) => {
358
- const canvas = within(canvasElement);
359
-
360
- await step('Verify ARIA labels', async () => {
361
- const nameInput = canvas.getByTestId('name-input');
362
- await expect(nameInput).toBeInTheDocument();
363
- await expect(nameInput).toHaveAttribute('required');
364
-
365
- // Verify the label is associated correctly
366
- const label = nameInput.closest('.MuiFormControl-root')?.querySelector('label');
367
- await expect(label).toHaveTextContent('Full Name');
368
- });
369
-
370
- await step('Verify ARIA descriptions', async () => {
371
- const nameInput = canvas.getByTestId('name-input');
372
- // MUI generates its own aria-describedby ID for helper text
373
- const ariaDescribedBy = nameInput.getAttribute('aria-describedby');
374
- await expect(ariaDescribedBy).toBeTruthy();
375
- await expect(ariaDescribedBy).toMatch(/helper-text/);
376
-
377
- const description = canvas.getByTestId('name-description');
378
- await expect(description).toBeInTheDocument();
379
- await expect(description).toHaveTextContent('Please enter your first and last name');
380
- });
381
-
382
- await step('Verify error state attributes', async () => {
383
- const emailInput = canvas.getByTestId('email-input');
384
- await expect(emailInput).toHaveAttribute('aria-invalid', 'true');
385
- // MUI generates its own aria-describedby ID for helper text
386
- const emailAriaDescribedBy = emailInput.getAttribute('aria-describedby');
387
- await expect(emailAriaDescribedBy).toBeTruthy();
388
- await expect(emailAriaDescribedBy).toMatch(/helper-text/);
389
- });
390
-
391
- await step('Verify live regions', async () => {
392
- const errorRegion = canvas.getByTestId('email-error');
393
- await expect(errorRegion).toHaveAttribute('role', 'alert');
394
- await expect(errorRegion).toHaveTextContent('The email format is invalid');
395
- });
396
- },
397
- };
398
-
399
- export const FocusManagement: Story = {
400
- name: '🎯 Focus Management Test',
401
- render: () => {
402
- const FocusTestComponent = () => {
403
- const [showModal, setShowModal] = useState(false);
404
-
405
- return (
406
- <div>
407
- <Input
408
- label="Trigger Input"
409
- placeholder="Click here first"
410
- data-testid="trigger-input"
411
- autoFocus
412
- />
413
-
414
- <button onClick={() => setShowModal(true)} data-testid="open-modal">
415
- Open Modal
416
- </button>
417
-
418
- {showModal && (
419
- <div
420
- role="dialog"
421
- aria-modal="true"
422
- style={{
423
- position: 'fixed',
424
- top: '50%',
425
- left: '50%',
426
- transform: 'translate(-50%, -50%)',
427
- background: 'white',
428
- padding: '20px',
429
- border: '1px solid #ccc',
430
- borderRadius: '8px',
431
- boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
432
- }}
433
- data-testid="modal"
434
- >
435
- <Input
436
- label="Modal Input"
437
- placeholder="Focus trapped here"
438
- data-testid="modal-input"
439
- autoFocus
440
- />
441
- <button onClick={() => setShowModal(false)} data-testid="close-modal">
442
- Close
443
- </button>
444
- </div>
445
- )}
446
- </div>
447
- );
448
- };
449
-
450
- return <FocusTestComponent />;
451
- },
452
- play: async ({ canvasElement, step }) => {
453
- const canvas = within(canvasElement);
454
-
455
- await step('Auto focus on mount', async () => {
456
- const triggerInput = canvas.getByTestId('trigger-input');
457
- await waitFor(() => {
458
- expect(triggerInput).toHaveFocus();
459
- });
460
- });
461
-
462
- await step('Focus management in modal', async () => {
463
- const openButton = canvas.getByTestId('open-modal');
464
- await userEvent.click(openButton);
465
-
466
- const modal = await canvas.findByTestId('modal');
467
- await expect(modal).toBeInTheDocument();
468
-
469
- const modalInput = within(modal).getByTestId('modal-input');
470
- await waitFor(() => {
471
- expect(modalInput).toHaveFocus();
472
- });
473
- });
474
-
475
- await step('Focus restoration after modal close', async () => {
476
- const modal = canvas.getByTestId('modal');
477
- const closeButton = within(modal).getByTestId('close-modal');
478
- const openButton = canvas.getByTestId('open-modal');
479
-
480
- // No pre-focus: the assertion below is about the modal being removed, and
481
- // .focus() on the open button neither affected that nor was ever checked.
482
- await expect(openButton).toBeInTheDocument();
483
- await userEvent.click(closeButton);
484
-
485
- // Modal should be gone
486
- await waitFor(() => {
487
- expect(canvas.queryByTestId('modal')).not.toBeInTheDocument();
488
- });
489
- });
490
- },
491
- };
492
-
493
- // ============================================================================
494
- // VISUAL TESTS
495
- // ============================================================================
496
-
497
- export const ResponsiveDesign: Story = {
498
- name: '📱 Responsive Design Test',
499
- render: () => (
500
- <div
501
- style={{
502
- display: 'grid',
503
- gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
504
- gap: '16px',
505
- }}
506
- >
507
- <Input
508
- variant="outlined"
509
- label="Responsive Input 1"
510
- placeholder="Resize window to test"
511
- data-testid="responsive-1"
512
- />
513
- <Input
514
- variant="glass"
515
- label="Responsive Input 2"
516
- placeholder="Watch layout adapt"
517
- data-testid="responsive-2"
518
- />
519
- <Input
520
- variant="gradient"
521
- label="Responsive Input 3"
522
- placeholder="Grid adjusts automatically"
523
- data-testid="responsive-3"
524
- />
525
- </div>
526
- ),
527
- parameters: {
528
- viewport: {
529
- viewports: {
530
- mobile: {
531
- name: 'Mobile',
532
- styles: { width: '375px', height: '667px' },
533
- type: 'mobile',
534
- },
535
- tablet: {
536
- name: 'Tablet',
537
- styles: { width: '768px', height: '1024px' },
538
- type: 'tablet',
539
- },
540
- desktop: {
541
- name: 'Desktop',
542
- styles: { width: '1920px', height: '1080px' },
543
- type: 'desktop',
544
- },
545
- },
546
- defaultViewport: 'mobile',
547
- },
548
- chromatic: {
549
- viewports: [375, 768, 1920],
550
- delay: 300,
551
- },
552
- },
553
- play: async ({ canvasElement, step }) => {
554
- const canvas = within(canvasElement);
555
-
556
- await step('Verify responsive container', async () => {
557
- const container = canvasElement.querySelector('div[style*="grid"]') as HTMLElement;
558
- const computedStyle = window.getComputedStyle(container);
559
-
560
- // Check that CSS Grid is being used
561
- await expect(computedStyle.display).toBe('grid');
562
-
563
- // Check that grid has appropriate column layout (computed may be different from raw CSS)
564
- const gridCols = computedStyle.gridTemplateColumns;
565
- // Should have a meaningful grid layout (not none or empty)
566
- await expect(gridCols).not.toBe('none');
567
- await expect(gridCols.length).toBeGreaterThan(0);
568
- });
569
-
570
- await step('Verify inputs are functional at all sizes', async () => {
571
- const input1 = canvas.getByTestId('responsive-1');
572
- const input2 = canvas.getByTestId('responsive-2');
573
- const input3 = canvas.getByTestId('responsive-3');
574
-
575
- // Test interaction on each input
576
- await userEvent.click(input1);
577
- await waitFor(() => expect(input1).toHaveFocus());
578
-
579
- await userEvent.click(input2);
580
- await waitFor(() => expect(input2).toHaveFocus());
581
-
582
- await userEvent.click(input3);
583
- await waitFor(() => expect(input3).toHaveFocus());
584
- });
585
- },
586
- };
587
-
588
- export const ThemeVariations: Story = {
589
- name: '🎨 Theme Variations Test',
590
- render: () => (
591
- <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
592
- <Input
593
- variant="outlined"
594
- label="Outlined Theme"
595
- placeholder="Default theme"
596
- data-testid="theme-outlined"
597
- />
598
- <Input
599
- variant="glass"
600
- label="Glass Theme"
601
- placeholder="Glass morphism"
602
- data-testid="theme-glass"
603
- />
604
- <Input
605
- variant="gradient"
606
- label="Gradient Theme"
607
- placeholder="Gradient borders"
608
- data-testid="theme-gradient"
609
- />
610
- </div>
611
- ),
612
- parameters: {
613
- backgrounds: {
614
- default: 'light',
615
- values: [
616
- { name: 'light', value: '#ffffff' },
617
- { name: 'dark', value: '#1a1a1a' },
618
- { name: 'blue', value: '#1e3a8a' },
619
- ],
620
- },
621
- },
622
- play: async ({ canvasElement, step }) => {
623
- const canvas = within(canvasElement);
624
-
625
- await step('Verify theme styling is applied', async () => {
626
- const glassInput = canvas.getByTestId('theme-glass');
627
- const gradientInput = canvas.getByTestId('theme-gradient');
628
-
629
- // These inputs should be visible and styled
630
- await expect(glassInput).toBeVisible();
631
- await expect(gradientInput).toBeVisible();
632
- });
633
-
634
- await step('Test theme consistency on focus', async () => {
635
- const outlinedInput = canvas.getByTestId('theme-outlined');
636
- const glassInput = canvas.getByTestId('theme-glass');
637
-
638
- // Focus each input to test theme-aware focus styles
639
- await userEvent.click(outlinedInput);
640
- await waitFor(() => expect(outlinedInput).toHaveFocus());
641
-
642
- await userEvent.click(glassInput);
643
- await waitFor(() => expect(glassInput).toHaveFocus());
644
- });
645
- },
646
- };
647
-
648
- export const VisualStates: Story = {
649
- name: '👁️ Visual States Test',
650
- render: () => (
651
- <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
652
- <Input label="Default State" placeholder="Normal input" data-testid="default-state" />
653
- <Input label="Hover State" placeholder="Hover over me" data-testid="hover-state" />
654
- <Input label="Focus State" placeholder="Click to focus" data-testid="focus-state" />
655
- <Input
656
- label="Disabled State"
657
- placeholder="Cannot interact"
658
- disabled
659
- data-testid="disabled-state"
660
- />
661
- <Input
662
- label="Error State"
663
- placeholder="Has error"
664
- error
665
- helperText="This field has an error"
666
- data-testid="error-state"
667
- />
668
- <Input
669
- label="Loading State"
670
- placeholder="Processing..."
671
- loading
672
- data-testid="loading-state"
673
- />
674
- <Input
675
- variant="glass"
676
- label="Glow Effect"
677
- placeholder="Has glow"
678
- glow
679
- data-testid="glow-state"
680
- />
681
- </div>
682
- ),
683
- play: async ({ canvasElement, step }) => {
684
- const canvas = within(canvasElement);
685
-
686
- await step('Default state is visible', async () => {
687
- const defaultInput = canvas.getByTestId('default-state');
688
- await expect(defaultInput).toBeVisible();
689
- await expect(defaultInput).not.toBeDisabled();
690
- });
691
-
692
- await step('Hover state interaction', async () => {
693
- const hoverInput = canvas.getByTestId('hover-state');
694
- await userEvent.hover(hoverInput);
695
- // Visual hover effects should be applied via CSS
696
- await expect(hoverInput).toBeVisible();
697
- });
698
-
699
- await step('Focus state interaction', async () => {
700
- const focusInput = canvas.getByTestId('focus-state');
701
- await userEvent.click(focusInput);
702
- await waitFor(() => expect(focusInput).toHaveFocus());
703
- });
704
-
705
- await step('Disabled state verification', async () => {
706
- const disabledInput = canvas.getByTestId('disabled-state');
707
- await expect(disabledInput).toBeDisabled();
708
- // Try to click (should not gain focus)
709
- await userEvent.click(disabledInput);
710
- await waitFor(() => expect(disabledInput).not.toHaveFocus());
711
- });
712
-
713
- await step('Error state verification', async () => {
714
- const errorInput = canvas.getByTestId('error-state');
715
- await expect(errorInput).toHaveAttribute('aria-invalid', 'true');
716
- });
717
-
718
- await step('Loading state verification', async () => {
719
- const loadingInput = canvas.getByTestId('loading-state');
720
- await expect(loadingInput).toBeDisabled();
721
-
722
- // Should have a loading spinner in end adornment
723
- const spinner = canvas.getByRole('progressbar');
724
- await expect(spinner).toBeInTheDocument();
725
- });
726
-
727
- await step('Glow state interaction', async () => {
728
- const glowInput = canvas.getByTestId('glow-state');
729
- await userEvent.click(glowInput);
730
- await waitFor(() => expect(glowInput).toHaveFocus());
731
- // Glow effect should be visible via CSS
732
- });
733
- },
734
- };
735
-
736
- // ============================================================================
737
- // EDGE CASES TESTS
738
- // ============================================================================
739
-
740
- export const EdgeCases: Story = {
741
- name: '🔧 Edge Cases Test',
742
- render: () => (
743
- <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
744
- <Input
745
- label="Empty with Floating"
746
- floating
747
- placeholder="Floating label behavior"
748
- data-testid="empty-floating"
749
- />
750
- <Input
751
- label="Long Text Overflow"
752
- defaultValue="This is a very long text that should show how the input handles overflow and long content gracefully without breaking the layout"
753
- data-testid="long-text"
754
- />
755
- <Input
756
- label="No Full Width"
757
- fullWidth={false}
758
- placeholder="Not full width"
759
- data-testid="not-full-width"
760
- />
761
- <Input
762
- label="All Effects Combined"
763
- variant="glass"
764
- glow
765
- pulse
766
- floating
767
- placeholder="Kitchen sink example"
768
- helperText="All visual effects enabled"
769
- data-testid="all-effects"
770
- />
771
- <Input
772
- label="Special Characters"
773
- placeholder="Test: !@#$%^&*()_+-=[]{}|;:,.<>?"
774
- data-testid="special-chars"
775
- />
776
- <Input label="Unicode Support" placeholder="测试 العربية Русский 🚀" data-testid="unicode" />
777
- </div>
778
- ),
779
- play: async ({ canvasElement, step }) => {
780
- const canvas = within(canvasElement);
781
-
782
- await step('Empty floating label behavior', async () => {
783
- const floatingInput = canvas.getByTestId('empty-floating');
784
-
785
- // Label should be in placeholder position initially
786
- await expect(floatingInput).toHaveAttribute('placeholder', 'Floating label behavior');
787
-
788
- // Type something to trigger label float
789
- await userEvent.click(floatingInput);
790
- await userEvent.type(floatingInput, 'test');
791
- await expect(floatingInput).toHaveValue('test');
792
-
793
- // Clear and blur to test label return
794
- await userEvent.clear(floatingInput);
795
- await userEvent.tab();
796
- });
797
-
798
- await step('Long text overflow handling', async () => {
799
- const longTextInput = canvas.getByTestId('long-text');
800
- await expect(longTextInput.value).toContain('This is a very long text');
801
-
802
- // Input should handle overflow gracefully
803
- await userEvent.click(longTextInput);
804
- await userEvent.keyboard('{End}');
805
- });
806
-
807
- await step('Width constraints', async () => {
808
- const notFullWidthInput = canvas.getByTestId('not-full-width');
809
- await expect(notFullWidthInput).toBeVisible();
810
-
811
- // Should not fill full container width
812
- const inputElement = notFullWidthInput.closest('.MuiTextField-root') as HTMLElement;
813
- await expect(inputElement).toBeInTheDocument();
814
- });
815
-
816
- await step('Combined effects functionality', async () => {
817
- const allEffectsInput = canvas.getByTestId('all-effects');
818
-
819
- // Should be visible and interactive despite all effects
820
- await userEvent.click(allEffectsInput);
821
- await waitFor(() => expect(allEffectsInput).toHaveFocus());
822
-
823
- await userEvent.type(allEffectsInput, 'works with all effects');
824
- await expect(allEffectsInput).toHaveValue('works with all effects');
825
- });
826
-
827
- await step('Special characters input', async () => {
828
- const specialCharsInput = canvas.getByTestId('special-chars');
829
- const specialText = '!@#$%^&*()_+-=test'; // Simplified to avoid userEvent.type issues
830
-
831
- await userEvent.click(specialCharsInput);
832
- await userEvent.type(specialCharsInput, specialText);
833
- await expect(specialCharsInput).toHaveValue(specialText);
834
- });
835
-
836
- await step('Unicode support', async () => {
837
- const unicodeInput = canvas.getByTestId('unicode');
838
- const unicodeText = '测试 العربية Русский 🚀';
839
-
840
- await userEvent.click(unicodeInput);
841
- await userEvent.type(unicodeInput, unicodeText);
842
- await expect(unicodeInput).toHaveValue(unicodeText);
843
- });
844
- },
845
- };
846
-
847
- // ============================================================================
848
- // PERFORMANCE TESTS
849
- // ============================================================================
850
-
851
- export const PerformanceTest: Story = {
852
- name: '⚡ Performance Test',
853
- render: () => {
854
- const inputs = Array.from({ length: 50 }, (_, i) => (
855
- <Input
856
- key={i}
857
- label={`Performance Input ${i + 1}`}
858
- placeholder={`Input number ${i + 1}`}
859
- data-testid={`perf-input-${i}`}
860
- variant={i % 3 === 0 ? 'glass' : i % 3 === 1 ? 'gradient' : 'outlined'}
861
- />
862
- ));
863
-
864
- return (
865
- <div
866
- style={{
867
- display: 'grid',
868
- gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
869
- gap: '16px',
870
- maxHeight: '400px',
871
- overflowY: 'auto',
872
- padding: '16px',
873
- }}
874
- >
875
- {inputs}
876
- </div>
877
- );
878
- },
879
- play: async ({ canvasElement, step }) => {
880
- const canvas = within(canvasElement);
881
-
882
- await step('Measure render time for multiple inputs', async () => {
883
- const startTime = window.performance.now();
884
- const inputs = canvas.getAllByTestId(/perf-input-/);
885
- const endTime = window.performance.now();
886
-
887
- const renderTime = endTime - startTime;
888
- // console.log(`Found ${inputs.length} inputs in ${renderTime}ms`);
889
-
890
- // Assert we have all inputs
891
- await expect(inputs).toHaveLength(50);
892
-
893
- // Assert reasonable performance (adjust threshold as needed)
894
- await expect(renderTime).toBeLessThan(100);
895
- });
896
-
897
- await step('Test interaction performance', async () => {
898
- // Test clicking on several inputs rapidly
899
- const inputs = canvas.getAllByTestId(/perf-input-/).slice(0, 5);
900
-
901
- const startTime = window.performance.now();
902
-
903
- for (const input of inputs) {
904
- await userEvent.click(input);
905
- await userEvent.type(input, 'fast');
906
- await userEvent.clear(input);
907
- }
908
-
909
- const endTime = window.performance.now();
910
- const interactionTime = endTime - startTime;
911
-
912
- // console.log(`Interaction test completed in ${interactionTime}ms`);
913
-
914
- // Should complete interactions in reasonable time
915
- await expect(interactionTime).toBeLessThan(2000);
916
- });
917
-
918
- await step('Test scroll performance', async () => {
919
- const container = canvasElement.querySelector('[style*="overflowY"]') as HTMLElement;
920
-
921
- if (container) {
922
- // Simulate scrolling
923
- container.scrollTop = 100;
924
- await new Promise((resolve) => window.setTimeout(resolve, 100));
925
-
926
- container.scrollTop = 200;
927
- await new Promise((resolve) => window.setTimeout(resolve, 100));
928
-
929
- // Verify scroll worked
930
- await expect(container.scrollTop).toBeGreaterThan(0);
931
- }
932
- });
933
- },
934
- };
935
-
936
- // ============================================================================
937
- // INTEGRATION TESTS
938
- // ============================================================================
939
-
940
- export const IntegrationTest: Story = {
941
- name: '🔗 Integration Test',
942
- render: () => {
943
- const IntegrationComponent = () => {
944
- const [formData, setFormData] = useState({
945
- firstName: '',
946
- lastName: '',
947
- email: '',
948
- password: '',
949
- confirmPassword: '',
950
- });
951
- const [errors, setErrors] = useState<Record<string, string>>({});
952
- const [showPassword, setShowPassword] = useState(false);
953
-
954
- const handleChange = (field: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
955
- const newValue = e.target.value;
956
- const newFormData = { ...formData, [field]: newValue };
957
- setFormData(newFormData);
958
-
959
- // Validate this specific field
960
- const newErrors = { ...errors };
961
-
962
- if (field === 'firstName' && !newValue) {
963
- newErrors.firstName = 'First name is required';
964
- } else if (field === 'firstName') {
965
- delete newErrors.firstName;
966
- }
967
-
968
- if (field === 'lastName' && !newValue) {
969
- newErrors.lastName = 'Last name is required';
970
- } else if (field === 'lastName') {
971
- delete newErrors.lastName;
972
- }
973
-
974
- if (field === 'email' && !newValue) {
975
- newErrors.email = 'Email is required';
976
- } else if (field === 'email') {
977
- delete newErrors.email;
978
- }
979
-
980
- if (field === 'password' && !newValue) {
981
- newErrors.password = 'Password is required';
982
- } else if (field === 'password') {
983
- delete newErrors.password;
984
- }
985
-
986
- if (field === 'confirmPassword') {
987
- if (!newValue) {
988
- newErrors.confirmPassword = 'Confirm password is required';
989
- } else if (newFormData.password !== newValue) {
990
- newErrors.confirmPassword = 'Passwords do not match';
991
- } else {
992
- delete newErrors.confirmPassword;
993
- }
994
- }
995
-
996
- // Also check password match when password field changes
997
- if (field === 'password' && newFormData.confirmPassword) {
998
- if (newValue !== newFormData.confirmPassword) {
999
- newErrors.confirmPassword = 'Passwords do not match';
1000
- } else {
1001
- delete newErrors.confirmPassword;
1002
- }
1003
- }
1004
-
1005
- setErrors(newErrors);
1006
- };
1007
-
1008
- const validateForm = () => {
1009
- const newErrors: Record<string, string> = {};
1010
-
1011
- if (!formData.firstName) newErrors.firstName = 'First name is required';
1012
- if (!formData.lastName) newErrors.lastName = 'Last name is required';
1013
- if (!formData.email) newErrors.email = 'Email is required';
1014
- if (!formData.password) newErrors.password = 'Password is required';
1015
- if (!formData.confirmPassword) {
1016
- newErrors.confirmPassword = 'Confirm password is required';
1017
- } else if (formData.password !== formData.confirmPassword) {
1018
- newErrors.confirmPassword = 'Passwords do not match';
1019
- }
1020
-
1021
- setErrors(newErrors);
1022
- return Object.keys(newErrors).length === 0;
1023
- };
1024
-
1025
- const handleSubmit = (e: React.FormEvent) => {
1026
- e.preventDefault();
1027
- if (validateForm()) {
1028
- window.alert('Form submitted successfully!');
1029
- }
1030
- };
1031
-
1032
- return (
1033
- <form
1034
- onSubmit={handleSubmit}
1035
- style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '400px' }}
1036
- >
1037
- <div style={{ display: 'flex', gap: '16px' }}>
1038
- <Input
1039
- label="First Name"
1040
- value={formData.firstName}
1041
- onChange={handleChange('firstName')}
1042
- error={!!errors.firstName}
1043
- helperText={errors.firstName}
1044
- data-testid="first-name"
1045
- startAdornment={<Person />}
1046
- fullWidth
1047
- />
1048
- <Input
1049
- label="Last Name"
1050
- value={formData.lastName}
1051
- onChange={handleChange('lastName')}
1052
- error={!!errors.lastName}
1053
- helperText={errors.lastName}
1054
- data-testid="last-name"
1055
- fullWidth
1056
- />
1057
- </div>
1058
-
1059
- <Input
1060
- label="Email"
1061
- type="email"
1062
- value={formData.email}
1063
- onChange={handleChange('email')}
1064
- error={!!errors.email}
1065
- helperText={errors.email}
1066
- data-testid="email"
1067
- startAdornment={<Email />}
1068
- />
1069
-
1070
- <Input
1071
- label="Password"
1072
- type={showPassword ? 'text' : 'password'}
1073
- value={formData.password}
1074
- onChange={handleChange('password')}
1075
- error={!!errors.password}
1076
- helperText={errors.password}
1077
- data-testid="password"
1078
- startAdornment={<Lock />}
1079
- endAdornment={
1080
- <IconButton
1081
- onClick={() => setShowPassword(!showPassword)}
1082
- data-testid="password-toggle"
1083
- >
1084
- {showPassword ? <VisibilityOff /> : <Visibility />}
1085
- </IconButton>
1086
- }
1087
- />
1088
-
1089
- <Input
1090
- label="Confirm Password"
1091
- type="password"
1092
- value={formData.confirmPassword}
1093
- onChange={handleChange('confirmPassword')}
1094
- error={!!errors.confirmPassword}
1095
- helperText={errors.confirmPassword}
1096
- data-testid="confirm-password"
1097
- startAdornment={<Lock />}
1098
- />
1099
-
1100
- <button type="submit" data-testid="submit-form">
1101
- Register
1102
- </button>
1103
-
1104
- <div data-testid="form-summary">
1105
- Form Valid:{' '}
1106
- {Object.keys(errors).length === 0 && Object.values(formData).every((v) => v.length > 0)
1107
- ? 'Yes'
1108
- : 'No'}{' '}
1109
- | Fields Filled: {Object.values(formData).filter((v) => v.length > 0).length}/5
1110
- </div>
1111
- </form>
1112
- );
1113
- };
1114
-
1115
- return <IntegrationComponent />;
1116
- },
1117
- play: async ({ canvasElement, step }) => {
1118
- const canvas = within(canvasElement);
1119
-
1120
- await step('Fill out form fields', async () => {
1121
- await userEvent.type(canvas.getByTestId('first-name'), 'John');
1122
- await userEvent.type(canvas.getByTestId('last-name'), 'Doe');
1123
- await userEvent.type(canvas.getByTestId('email'), 'john.doe@example.com');
1124
- await userEvent.type(canvas.getByTestId('password'), 'securePassword123');
1125
- await userEvent.type(canvas.getByTestId('confirm-password'), 'securePassword123');
1126
- });
1127
-
1128
- await step('Verify form state updates', async () => {
1129
- const summary = canvas.getByTestId('form-summary');
1130
- await expect(summary).toHaveTextContent('Fields Filled: 5/5');
1131
- });
1132
-
1133
- await step('Test password visibility toggle', async () => {
1134
- const passwordInput = canvas.getByTestId('password');
1135
- const toggleButton = canvas.getByTestId('password-toggle');
1136
-
1137
- // Should be password type initially
1138
- await expect(passwordInput).toHaveAttribute('type', 'password');
1139
-
1140
- // Click toggle
1141
- await userEvent.click(toggleButton);
1142
- await expect(passwordInput).toHaveAttribute('type', 'text');
1143
-
1144
- // Click again to hide
1145
- await userEvent.click(toggleButton);
1146
- await expect(passwordInput).toHaveAttribute('type', 'password');
1147
- });
1148
-
1149
- await step('Test form validation on submit', async () => {
1150
- // Clear one field to trigger validation
1151
- const firstNameInput = canvas.getByTestId('first-name');
1152
- await userEvent.clear(firstNameInput);
1153
-
1154
- const submitButton = canvas.getByTestId('submit-form');
1155
- await userEvent.click(submitButton);
1156
-
1157
- // Should show error
1158
- await waitFor(async () => {
1159
- await expect(canvas.getByText('First name is required')).toBeInTheDocument();
1160
- });
1161
-
1162
- // Form should be invalid
1163
- const summary = canvas.getByTestId('form-summary');
1164
- await expect(summary).toHaveTextContent('Form Valid: No');
1165
- });
1166
-
1167
- await step('Fix validation error', async () => {
1168
- const firstNameInput = canvas.getByTestId('first-name');
1169
- await userEvent.type(firstNameInput, 'John');
1170
-
1171
- // Error should clear
1172
- await waitFor(async () => {
1173
- expect(canvas.queryByText('First name is required')).not.toBeInTheDocument();
1174
- });
1175
-
1176
- // Check that all fields are filled
1177
- const summary = canvas.getByTestId('form-summary');
1178
- await waitFor(async () => {
1179
- await expect(summary).toHaveTextContent('Fields Filled: 5/5');
1180
- });
1181
-
1182
- // Form should be valid again (all fields filled and no errors)
1183
- await waitFor(
1184
- async () => {
1185
- await expect(summary).toHaveTextContent('Form Valid: Yes');
1186
- },
1187
- { timeout: 2000 },
1188
- );
1189
- });
1190
- },
1191
- };