@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,1162 +0,0 @@
1
- import { Button, List, ListItem, ListItemText,Stack, Typography } from '@mui/material';
2
- import type { Meta, StoryObj } from '@storybook/react-vite';
3
- import React from 'react';
4
- import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
5
-
6
- import { Popover } from './Popover';
7
-
8
- const meta: Meta<typeof Popover> = {
9
- title: 'DataDisplay/Popover/Tests',
10
- component: Popover,
11
- parameters: {
12
- layout: 'centered',
13
- chromatic: { disableSnapshot: false },
14
- },
15
- tags: ['autodocs', 'test', 'component:Popover'],
16
- };
17
- export default meta;
18
- export type Story = StoryObj<typeof meta>;
19
-
20
- // Helper function to get popover elements from document (since MUI renders in portal)
21
- const getPopoverElement = (testId: string) => document.querySelector(`[data-testid="${testId}"]`);
22
- const queryPopoverElement = (testId: string) => document.querySelector(`[data-testid="${testId}"]`);
23
-
24
- // Helper component for tests
25
- const PopoverTestWrapper = ({
26
- children,
27
- onOpen = fn(),
28
- onClose = fn(),
29
- ...props
30
- }: {
31
- children?: React.ReactNode;
32
- onOpen?: () => void;
33
- onClose?: () => void;
34
- variant?: 'default' | 'glass' | 'arrow';
35
- glow?: boolean;
36
- pulse?: boolean;
37
- maxWidth?: number;
38
- }) => {
39
- const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
40
- const open = Boolean(anchorEl);
41
-
42
- const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
43
- setAnchorEl(event.currentTarget);
44
- onOpen();
45
- };
46
-
47
- const handleClose = () => {
48
- setAnchorEl(null);
49
- onClose();
50
- };
51
-
52
- return (
53
- <>
54
- <Button variant="contained" onClick={handleClick} data-testid="popover-trigger">
55
- Open Popover
56
- </Button>
57
- <Popover
58
- open={open}
59
- anchorEl={anchorEl}
60
- onClose={handleClose}
61
- anchorOrigin={{
62
- vertical: 'bottom',
63
- horizontal: 'left',
64
- }}
65
- data-testid="popover"
66
- {...props}
67
- >
68
- {children}
69
- </Popover>
70
- </>
71
- );
72
- };
73
-
74
- // 1. Basic Interaction Tests
75
- export const BasicInteraction: Story = {
76
- render: () => (
77
- <>
78
- <PopoverTestWrapper>
79
- <Typography sx={{ p: 2 }} data-testid="popover-content">
80
- This is basic popover content.
81
- </Typography>
82
- </PopoverTestWrapper>
83
- <div aria-label="Status of the test run" style={{ marginTop: 20 }}>
84
- PASS
85
- </div>
86
- </>
87
- ),
88
- play: async ({ canvasElement, step }) => {
89
- const canvas = within(canvasElement);
90
- const statusElement = canvas.getByLabelText('Status of the test run');
91
- statusElement.textContent = 'RUNS';
92
-
93
- await step('Initial state should be closed', async () => {
94
- const trigger = canvas.getByTestId('popover-trigger');
95
- expect(trigger).toBeInTheDocument();
96
-
97
- // Popover should not be visible initially
98
- await waitFor(() => expect(document.querySelector('[data-testid="popover-content"]')).not.toBeInTheDocument());
99
- });
100
-
101
- await step('Should open popover on trigger click', async () => {
102
- const trigger = canvas.getByTestId('popover-trigger');
103
- await userEvent.click(trigger);
104
-
105
- await waitFor(() => {
106
- // MUI Popover renders content in a portal (document body), not in canvas
107
- const content = document.querySelector('[data-testid="popover-content"]');
108
- expect(content).toBeInTheDocument();
109
- expect(content).toHaveTextContent('This is basic popover content.');
110
-
111
- // Verify popover is positioned relative to trigger
112
- const popoverContainer = document.querySelector('[role="presentation"]');
113
- expect(popoverContainer).toBeInTheDocument();
114
-
115
- // Test that popover Paper element exists with proper styling
116
- const paper = document.querySelector('.MuiPaper-root');
117
- expect(paper).toBeInTheDocument();
118
- });
119
- });
120
-
121
- await step('Should close popover on outside click', async () => {
122
- // Click outside the popover (on backdrop)
123
- const backdrop = document.querySelector('.MuiBackdrop-root');
124
- if (backdrop) {
125
- await userEvent.click(backdrop as HTMLElement);
126
- } else {
127
- await userEvent.click(canvasElement);
128
- }
129
-
130
- await waitFor(() => {
131
- expect(document.querySelector('[data-testid="popover-content"]')).not.toBeInTheDocument();
132
- });
133
- });
134
-
135
- // Mark test as passed
136
- statusElement.textContent = 'PASS';
137
- },
138
- };
139
-
140
- // 2. Form Interaction Tests
141
- export const FormInteraction: Story = {
142
- render: () => {
143
- const [clickedItem, setClickedItem] = React.useState('');
144
- return (
145
- <>
146
- <PopoverTestWrapper>
147
- <div style={{ padding: 16 }}>
148
- <Typography variant="h6" gutterBottom>
149
- User Menu
150
- </Typography>
151
- <List dense>
152
- <ListItem
153
- component="button"
154
- data-testid="menu-item-profile"
155
- onClick={() => setClickedItem('profile')}
156
- >
157
- <ListItemText primary="Profile Settings" />
158
- </ListItem>
159
- <ListItem
160
- component="button"
161
- data-testid="menu-item-account"
162
- onClick={() => setClickedItem('account')}
163
- >
164
- <ListItemText primary="Account Security" />
165
- </ListItem>
166
- <ListItem
167
- component="button"
168
- data-testid="menu-item-notifications"
169
- onClick={() => setClickedItem('notifications')}
170
- >
171
- <ListItemText primary="Notifications" />
172
- </ListItem>
173
- </List>
174
- </div>
175
- </PopoverTestWrapper>
176
- <div data-testid="clicked-item" style={{ display: 'none' }}>
177
- {clickedItem}
178
- </div>
179
- <div aria-label="Status of the test run" style={{ marginTop: 20 }}>
180
- PASS
181
- </div>
182
- </>
183
- );
184
- },
185
- play: async ({ canvasElement, step }) => {
186
- const canvas = within(canvasElement);
187
- const statusElement = canvas.getByLabelText('Status of the test run');
188
- statusElement.textContent = 'RUNS';
189
-
190
- await step('Should open popover with menu items', async () => {
191
- const trigger = canvas.getByTestId('popover-trigger');
192
- await userEvent.click(trigger);
193
-
194
- await waitFor(() => {
195
- const profileItem = getPopoverElement('menu-item-profile');
196
- const accountItem = getPopoverElement('menu-item-account');
197
- const notificationsItem = getPopoverElement('menu-item-notifications');
198
-
199
- expect(profileItem).toBeInTheDocument();
200
- expect(accountItem).toBeInTheDocument();
201
- expect(notificationsItem).toBeInTheDocument();
202
-
203
- // Verify menu items have proper text content
204
- expect(profileItem).toHaveTextContent('Profile Settings');
205
- expect(accountItem).toHaveTextContent('Account Security');
206
- expect(notificationsItem).toHaveTextContent('Notifications');
207
- });
208
- });
209
-
210
- await step('Should be able to click menu items and trigger actions', async () => {
211
- const profileItem = getPopoverElement('menu-item-profile') as HTMLElement;
212
- await userEvent.click(profileItem);
213
-
214
- // Verify click handler was called
215
- await waitFor(() => {
216
- const clickedItemElement = canvas.getByTestId('clicked-item');
217
- expect(clickedItemElement).toHaveTextContent('profile');
218
- });
219
-
220
- // Menu item should still be accessible after click
221
- expect(profileItem).toBeInTheDocument();
222
-
223
- // Test clicking another item
224
- const accountItem = getPopoverElement('menu-item-account') as HTMLElement;
225
- await userEvent.click(accountItem);
226
-
227
- await waitFor(() => {
228
- const clickedItemElement = canvas.getByTestId('clicked-item');
229
- expect(clickedItemElement).toHaveTextContent('account');
230
- });
231
- });
232
-
233
- statusElement.textContent = 'PASS';
234
- },
235
- };
236
-
237
- // 3. Keyboard Navigation Tests
238
- export const KeyboardNavigation: Story = {
239
- render: () => (
240
- <>
241
- <PopoverTestWrapper>
242
- <div style={{ padding: 16 }}>
243
- <Typography variant="h6" gutterBottom>
244
- Keyboard Navigation Test
245
- </Typography>
246
- <List dense>
247
- <ListItem component="button" tabIndex={0} data-testid="item-1">
248
- <ListItemText primary="First Item" />
249
- </ListItem>
250
- <ListItem component="button" tabIndex={0} data-testid="item-2">
251
- <ListItemText primary="Second Item" />
252
- </ListItem>
253
- <ListItem component="button" tabIndex={0} data-testid="item-3">
254
- <ListItemText primary="Third Item" />
255
- </ListItem>
256
- </List>
257
- </div>
258
- </PopoverTestWrapper>
259
- <div aria-label="Status of the test run" style={{ marginTop: 20 }}>
260
- PASS
261
- </div>
262
- </>
263
- ),
264
- play: async ({ canvasElement, step }) => {
265
- const canvas = within(canvasElement);
266
- const statusElement = canvas.getByLabelText('Status of the test run');
267
- statusElement.textContent = 'RUNS';
268
-
269
- await step('Should open popover with Enter key', async () => {
270
- const trigger = canvas.getByTestId('popover-trigger');
271
- await userEvent.click(trigger);
272
- await waitFor(() => expect(trigger).toHaveFocus());
273
-
274
- await userEvent.keyboard('{Enter}');
275
-
276
- await waitFor(() => {
277
- const firstItem = getPopoverElement('item-1');
278
- expect(firstItem).toBeInTheDocument();
279
-
280
- // Verify all items are present and focusable
281
- const secondItem = getPopoverElement('item-2');
282
- const thirdItem = getPopoverElement('item-3');
283
- expect(secondItem).toBeInTheDocument();
284
- expect(thirdItem).toBeInTheDocument();
285
-
286
- // Verify items have proper tabIndex for keyboard navigation
287
- expect(firstItem).toHaveAttribute('tabIndex', '0');
288
- expect(secondItem).toHaveAttribute('tabIndex', '0');
289
- expect(thirdItem).toHaveAttribute('tabIndex', '0');
290
- });
291
- });
292
-
293
- await step('Should navigate between items with Tab key', async () => {
294
- // Tab through items
295
- const firstItem = getPopoverElement('item-1') as HTMLElement;
296
- await userEvent.click(firstItem);
297
- await waitFor(() => expect(firstItem).toHaveFocus());
298
-
299
- await userEvent.keyboard('{Tab}');
300
- const secondItem = getPopoverElement('item-2') as HTMLElement;
301
- await waitFor(() => expect(secondItem).toHaveFocus());
302
-
303
- await userEvent.keyboard('{Tab}');
304
- const thirdItem = getPopoverElement('item-3') as HTMLElement;
305
- await waitFor(() => expect(thirdItem).toHaveFocus());
306
- });
307
-
308
- await step('Should close popover with Escape key', async () => {
309
- await userEvent.keyboard('{Escape}');
310
-
311
- await waitFor(() => {
312
- expect(queryPopoverElement('item-1')).not.toBeInTheDocument();
313
- expect(queryPopoverElement('item-2')).not.toBeInTheDocument();
314
- expect(queryPopoverElement('item-3')).not.toBeInTheDocument();
315
-
316
- // Verify focus returns to trigger
317
- const trigger = canvas.getByTestId('popover-trigger');
318
- expect(trigger).toHaveFocus();
319
- });
320
- });
321
-
322
- statusElement.textContent = 'PASS';
323
- },
324
- };
325
-
326
- // 4. Screen Reader Tests
327
- export const ScreenReader: Story = {
328
- render: () => (
329
- <>
330
- <PopoverTestWrapper>
331
- <div
332
- role="dialog"
333
- aria-label="User menu popover"
334
- aria-describedby="popover-desc"
335
- style={{ padding: 16 }}
336
- >
337
- <Typography variant="h6" gutterBottom id="popover-title">
338
- Accessible Menu
339
- </Typography>
340
- <Typography variant="body2" id="popover-desc" sx={{ mb: 1 }}>
341
- Navigate menu options using arrow keys
342
- </Typography>
343
- <List dense aria-labelledby="popover-title">
344
- <ListItem
345
- component="button"
346
- role="menuitem"
347
- data-testid="accessible-item-1"
348
- aria-label="Navigate to profile settings"
349
- >
350
- <ListItemText primary="Accessible Item 1" />
351
- </ListItem>
352
- <ListItem
353
- component="button"
354
- role="menuitem"
355
- data-testid="accessible-item-2"
356
- aria-label="Navigate to account settings"
357
- >
358
- <ListItemText primary="Accessible Item 2" />
359
- </ListItem>
360
- </List>
361
- </div>
362
- </PopoverTestWrapper>
363
- <div aria-label="Status of the test run" style={{ marginTop: 20 }}>
364
- PASS
365
- </div>
366
- </>
367
- ),
368
- play: async ({ canvasElement, step }) => {
369
- const canvas = within(canvasElement);
370
- const statusElement = canvas.getByLabelText('Status of the test run');
371
- statusElement.textContent = 'RUNS';
372
-
373
- await step('Should have proper ARIA attributes for screen readers', async () => {
374
- const trigger = canvas.getByTestId('popover-trigger');
375
- await userEvent.click(trigger);
376
-
377
- await waitFor(() => {
378
- // Use document queries for portal-rendered content
379
- const dialog = document.querySelector('[role="dialog"]');
380
- expect(dialog).toBeInTheDocument();
381
- expect(dialog).toHaveAttribute('aria-label', 'User menu popover');
382
- expect(dialog).toHaveAttribute('aria-describedby', 'popover-desc');
383
-
384
- // Verify menu items have proper roles and labels
385
- const menuItems = document.querySelectorAll('[role="menuitem"]');
386
- expect(menuItems).toHaveLength(2);
387
- expect(menuItems[0]).toHaveAttribute('aria-label', 'Navigate to profile settings');
388
- expect(menuItems[1]).toHaveAttribute('aria-label', 'Navigate to account settings');
389
- });
390
- });
391
-
392
- await step('Should have proper heading structure for screen readers', async () => {
393
- // Use document queries for portal-rendered content
394
- const title = document.querySelector('#popover-title');
395
- expect(title).toBeInTheDocument();
396
- expect(title).toHaveTextContent('Accessible Menu');
397
-
398
- const description = document.querySelector('#popover-desc');
399
- expect(description).toBeInTheDocument();
400
- expect(description).toHaveTextContent('Navigate menu options using arrow keys');
401
-
402
- const list = document.querySelector('[aria-labelledby="popover-title"]');
403
- expect(list).toBeInTheDocument();
404
-
405
- // Verify MUI Popover backdrop has proper role
406
- const backdrop = document.querySelector('.MuiBackdrop-root');
407
- if (backdrop) {
408
- expect(backdrop).toHaveAttribute('aria-hidden', 'true');
409
- }
410
- });
411
-
412
- statusElement.textContent = 'PASS';
413
- },
414
- };
415
-
416
- // 5. Focus Management Tests
417
- export const FocusManagement: Story = {
418
- render: () => (
419
- <>
420
- <div>
421
- <Button data-testid="before-popover">Before Popover</Button>
422
- <PopoverTestWrapper>
423
- <div style={{ padding: 16 }}>
424
- <Button data-testid="first-focusable" autoFocus>
425
- First Focusable
426
- </Button>
427
- <Button data-testid="second-focusable">Second Focusable</Button>
428
- <Button data-testid="third-focusable">Third Focusable</Button>
429
- </div>
430
- </PopoverTestWrapper>
431
- <Button data-testid="after-popover">After Popover</Button>
432
- </div>
433
- <div aria-label="Status of the test run" style={{ marginTop: 20 }}>
434
- PASS
435
- </div>
436
- </>
437
- ),
438
- play: async ({ canvasElement, step }) => {
439
- const canvas = within(canvasElement);
440
- const statusElement = canvas.getByLabelText('Status of the test run');
441
- statusElement.textContent = 'RUNS';
442
-
443
- await step('Should manage focus when popover opens', async () => {
444
- const trigger = canvas.getByTestId('popover-trigger');
445
-
446
- // Set initial focus to before button
447
- const beforeButton = canvas.getByTestId('before-popover');
448
- await userEvent.click(beforeButton);
449
- await waitFor(() => expect(beforeButton).toHaveFocus());
450
-
451
- // Open popover
452
- await userEvent.click(trigger);
453
-
454
- await waitFor(() => {
455
- // Use document queries for portal-rendered content
456
- const firstFocusable = document.querySelector('[data-testid="first-focusable"]');
457
- expect(firstFocusable).toBeInTheDocument();
458
-
459
- // Verify autoFocus worked
460
- expect(firstFocusable).toHaveFocus();
461
-
462
- // Verify all focusable elements are present
463
- const secondFocusable = document.querySelector('[data-testid="second-focusable"]');
464
- const thirdFocusable = document.querySelector('[data-testid="third-focusable"]');
465
- expect(secondFocusable).toBeInTheDocument();
466
- expect(thirdFocusable).toBeInTheDocument();
467
- });
468
- });
469
-
470
- await step('Should cycle focus within popover', async () => {
471
- // Tab through elements (use document queries for portal content)
472
- await userEvent.keyboard('{Tab}');
473
- const secondFocusable = document.querySelector('[data-testid="second-focusable"]');
474
- await waitFor(() => expect(secondFocusable).toHaveFocus());
475
-
476
- await userEvent.keyboard('{Tab}');
477
- const thirdFocusable = document.querySelector('[data-testid="third-focusable"]');
478
- await waitFor(() => expect(thirdFocusable).toHaveFocus());
479
-
480
- // Shift+Tab to go back
481
- await userEvent.keyboard('{Shift>}{Tab}{/Shift}');
482
- await waitFor(() => expect(secondFocusable).toHaveFocus());
483
- });
484
-
485
- await step('Should return focus to trigger when closed', async () => {
486
- await userEvent.keyboard('{Escape}');
487
-
488
- await waitFor(() => {
489
- const trigger = canvas.getByTestId('popover-trigger');
490
- expect(trigger).toHaveFocus();
491
-
492
- // Verify popover content is removed (check in document, not canvas)
493
- expect(document.querySelector('[data-testid="first-focusable"]')).not.toBeInTheDocument();
494
- expect(document.querySelector('[data-testid="second-focusable"]')).not.toBeInTheDocument();
495
- });
496
- });
497
-
498
- statusElement.textContent = 'PASS';
499
- },
500
- };
501
-
502
- // 6. Responsive Design Tests
503
- export const ResponsiveDesign: Story = {
504
- parameters: {
505
- viewport: {
506
- defaultViewport: 'mobile1',
507
- },
508
- },
509
- render: () => (
510
- <>
511
- <PopoverTestWrapper maxWidth={280}>
512
- <div style={{ padding: 16 }}>
513
- <Typography variant="h6" gutterBottom>
514
- Mobile Responsive Content
515
- </Typography>
516
- <Typography variant="body2" data-testid="mobile-content">
517
- This content should adapt to mobile viewport sizes and maintain readability.
518
- </Typography>
519
- <Button data-testid="mobile-button" size="small" fullWidth>
520
- Mobile Action
521
- </Button>
522
- </div>
523
- </PopoverTestWrapper>
524
- <div aria-label="Status of the test run" style={{ marginTop: 20 }}>
525
- PASS
526
- </div>
527
- </>
528
- ),
529
- play: async ({ canvasElement, step }) => {
530
- const canvas = within(canvasElement);
531
- const statusElement = canvas.getByLabelText('Status of the test run');
532
- statusElement.textContent = 'RUNS';
533
-
534
- await step('Should display properly on mobile viewport with constrained width', async () => {
535
- const trigger = canvas.getByTestId('popover-trigger');
536
- await userEvent.click(trigger);
537
-
538
- await waitFor(() => {
539
- const content = getPopoverElement('mobile-content');
540
- expect(content).toBeInTheDocument();
541
- expect(content).toHaveTextContent(
542
- 'This content should adapt to mobile viewport sizes and maintain readability.',
543
- );
544
-
545
- // Verify mobile button is full width
546
- const mobileButton = getPopoverElement('mobile-button');
547
- expect(mobileButton).toBeInTheDocument();
548
- expect(mobileButton).toHaveClass('MuiButton-fullWidth');
549
-
550
- // Verify popover Paper respects maxWidth
551
- const paper = document.querySelector('.MuiPaper-root') as HTMLElement;
552
- if (paper) {
553
- const computedStyle = window.getComputedStyle(paper);
554
- const maxWidth = computedStyle.maxWidth;
555
- expect(maxWidth).toBe('280px');
556
- }
557
- });
558
- });
559
-
560
- await step('Should position correctly on mobile screens', async () => {
561
- // Verify popover is visible within viewport
562
- const popoverRoot = document.querySelector('[role="presentation"]') as HTMLElement;
563
- if (popoverRoot) {
564
- const rect = popoverRoot.getBoundingClientRect();
565
- expect(rect.left).toBeGreaterThanOrEqual(0);
566
- expect(rect.top).toBeGreaterThanOrEqual(0);
567
- }
568
- });
569
-
570
- statusElement.textContent = 'PASS';
571
- },
572
- };
573
-
574
- // 7. Theme Variations Tests
575
- export const ThemeVariations: Story = {
576
- render: () => (
577
- <>
578
- <Stack spacing={2} direction="row">
579
- <PopoverTestWrapper variant="default" data-testid="default-popover">
580
- <Typography sx={{ p: 2 }} data-testid="default-content">
581
- Default Theme
582
- </Typography>
583
- </PopoverTestWrapper>
584
- <PopoverTestWrapper variant="glass" data-testid="glass-popover">
585
- <Typography sx={{ p: 2 }} data-testid="glass-content">
586
- Glass Theme
587
- </Typography>
588
- </PopoverTestWrapper>
589
- <PopoverTestWrapper variant="arrow" data-testid="arrow-popover">
590
- <Typography sx={{ p: 2 }} data-testid="arrow-content">
591
- Arrow Theme
592
- </Typography>
593
- </PopoverTestWrapper>
594
- </Stack>
595
- <div aria-label="Status of the test run" style={{ marginTop: 20 }}>
596
- PASS
597
- </div>
598
- </>
599
- ),
600
- play: async ({ canvasElement, step }) => {
601
- const canvas = within(canvasElement);
602
- const statusElement = canvas.getByLabelText('Status of the test run');
603
- statusElement.textContent = 'RUNS';
604
-
605
- await step('Should render and style all theme variants correctly', async () => {
606
- const triggers = canvas.getAllByTestId('popover-trigger');
607
- expect(triggers).toHaveLength(3);
608
-
609
- // Test default variant
610
- await userEvent.click(triggers[0]);
611
- await waitFor(() => {
612
- // Use document queries for portal-rendered content
613
- const content = document.querySelector('[data-testid="default-content"]');
614
- expect(content).toBeInTheDocument();
615
- expect(content).toHaveTextContent('Default Theme');
616
-
617
- // Verify default variant styling
618
- const paper = content?.closest('.MuiPaper-root') as HTMLElement;
619
- if (paper) {
620
- const computedStyle = window.getComputedStyle(paper);
621
- expect(computedStyle.borderRadius).toBeTruthy();
622
- expect(computedStyle.backgroundColor).toBeTruthy();
623
- }
624
- });
625
- await userEvent.keyboard('{Escape}');
626
-
627
- // Wait for animation
628
- await waitFor(() => {
629
- expect(document.querySelector('[data-testid="default-content"]')).not.toBeInTheDocument();
630
- });
631
-
632
- // Test glass variant
633
- await userEvent.click(triggers[1]);
634
- await waitFor(() => {
635
- // Use document queries for portal-rendered content
636
- const content = document.querySelector('[data-testid="glass-content"]');
637
- expect(content).toBeInTheDocument();
638
- expect(content).toHaveTextContent('Glass Theme');
639
-
640
- // Verify glass variant has backdrop filter
641
- const paper = content?.closest('.MuiPaper-root') as HTMLElement;
642
- if (paper) {
643
- const computedStyle = window.getComputedStyle(paper);
644
- // Glass variant should have backdrop-filter
645
- expect(computedStyle.backdropFilter || computedStyle.webkitBackdropFilter).toBeTruthy();
646
- }
647
- });
648
- await userEvent.keyboard('{Escape}');
649
-
650
- // Wait for animation
651
- await waitFor(() => {
652
- expect(document.querySelector('[data-testid="glass-content"]')).not.toBeInTheDocument();
653
- });
654
-
655
- // Test arrow variant
656
- await userEvent.click(triggers[2]);
657
- await waitFor(() => {
658
- // Use document queries for portal-rendered content
659
- const content = document.querySelector('[data-testid="arrow-content"]');
660
- expect(content).toBeInTheDocument();
661
- expect(content).toHaveTextContent('Arrow Theme');
662
-
663
- // Verify arrow variant styling
664
- const paper = content?.closest('.MuiPaper-root') as HTMLElement;
665
- if (paper) {
666
- const computedStyle = window.getComputedStyle(paper);
667
- expect(computedStyle.boxShadow).toBeTruthy();
668
- }
669
- });
670
- await userEvent.keyboard('{Escape}');
671
- });
672
-
673
- statusElement.textContent = 'PASS';
674
- },
675
- };
676
-
677
- // 8. Visual States Tests
678
- export const VisualStates: Story = {
679
- render: () => (
680
- <>
681
- <Stack spacing={2} direction="row">
682
- <PopoverTestWrapper glow data-testid="glow-popover">
683
- <Typography sx={{ p: 2 }} data-testid="glow-content">
684
- Glow Effect
685
- </Typography>
686
- </PopoverTestWrapper>
687
- <PopoverTestWrapper pulse data-testid="pulse-popover">
688
- <Typography sx={{ p: 2 }} data-testid="pulse-content">
689
- Pulse Effect
690
- </Typography>
691
- </PopoverTestWrapper>
692
- <PopoverTestWrapper glow pulse data-testid="both-effects-popover">
693
- <Typography sx={{ p: 2 }} data-testid="both-effects-content">
694
- Both Effects
695
- </Typography>
696
- </PopoverTestWrapper>
697
- </Stack>
698
- <div aria-label="Status of the test run" style={{ marginTop: 20 }}>
699
- PASS
700
- </div>
701
- </>
702
- ),
703
- play: async ({ canvasElement, step }) => {
704
- const canvas = within(canvasElement);
705
- const statusElement = canvas.getByLabelText('Status of the test run');
706
- statusElement.textContent = 'RUNS';
707
-
708
- await step('Should render and apply visual effect variants', async () => {
709
- const triggers = canvas.getAllByTestId('popover-trigger');
710
- expect(triggers).toHaveLength(3);
711
-
712
- // Test glow effect
713
- await userEvent.click(triggers[0]);
714
- await waitFor(() => {
715
- // Use document queries for portal-rendered content
716
- const content = document.querySelector('[data-testid="glow-content"]');
717
- expect(content).toBeInTheDocument();
718
-
719
- // Verify glow effect is applied (enhanced box-shadow)
720
- const paper = content?.closest('.MuiPaper-root') as HTMLElement;
721
- if (paper) {
722
- const computedStyle = window.getComputedStyle(paper);
723
- expect(computedStyle.boxShadow).toContain('rgb');
724
- expect(computedStyle.filter).toContain('brightness');
725
- }
726
- });
727
- await userEvent.keyboard('{Escape}');
728
-
729
- // Wait for close animation
730
- await waitFor(() => {
731
- expect(document.querySelector('[data-testid="glow-content"]')).not.toBeInTheDocument();
732
- });
733
-
734
- // Test pulse effect
735
- await userEvent.click(triggers[1]);
736
- await waitFor(() => {
737
- // Use document queries for portal-rendered content
738
- const content = document.querySelector('[data-testid="pulse-content"]');
739
- expect(content).toBeInTheDocument();
740
-
741
- // Verify pulse animation is present
742
- const paper = content?.closest('.MuiPaper-root') as HTMLElement;
743
- if (paper) {
744
- // Check for ::after pseudo element that creates pulse
745
- const computedStyle = window.getComputedStyle(paper, '::after');
746
- expect(computedStyle.animation || computedStyle.animationName).toBeTruthy();
747
- }
748
- });
749
- await userEvent.keyboard('{Escape}');
750
-
751
- // Wait for close animation
752
- await waitFor(() => {
753
- expect(document.querySelector('[data-testid="pulse-content"]')).not.toBeInTheDocument();
754
- });
755
-
756
- // Test both effects combined
757
- await userEvent.click(triggers[2]);
758
- await waitFor(() => {
759
- // Use document queries for portal-rendered content
760
- const content = document.querySelector('[data-testid="both-effects-content"]');
761
- expect(content).toBeInTheDocument();
762
-
763
- // Verify both effects are applied
764
- const paper = content?.closest('.MuiPaper-root') as HTMLElement;
765
- if (paper) {
766
- const computedStyle = window.getComputedStyle(paper);
767
- // Should have glow shadow
768
- expect(computedStyle.boxShadow).toContain('rgb');
769
- expect(computedStyle.filter).toContain('brightness');
770
- // Should have pulse animation
771
- const afterStyle = window.getComputedStyle(paper, '::after');
772
- expect(afterStyle.animation || afterStyle.animationName).toBeTruthy();
773
- }
774
- });
775
- await userEvent.keyboard('{Escape}');
776
- });
777
-
778
- statusElement.textContent = 'PASS';
779
- },
780
- };
781
-
782
- // 9. Performance Tests
783
- const PerformanceTestComponent = () => {
784
- const [renderCount, setRenderCount] = React.useState(0);
785
- const [openCount, setOpenCount] = React.useState(0);
786
-
787
- React.useEffect(() => {
788
- setRenderCount((c) => c + 1);
789
- }, []);
790
-
791
- return (
792
- <div>
793
- <div data-testid="render-count">Renders: {renderCount}</div>
794
- <div data-testid="open-count" style={{ display: 'none' }}>
795
- Opens: {openCount}
796
- </div>
797
- <PopoverTestWrapper onOpen={() => setOpenCount((c) => c + 1)}>
798
- <div style={{ padding: 16 }}>
799
- <Typography variant="h6">Performance Test</Typography>
800
- <Typography data-testid="performance-content">
801
- This tests if the popover triggers excessive re-renders.
802
- </Typography>
803
- </div>
804
- </PopoverTestWrapper>
805
- <div aria-label="Status of the test run" style={{ marginTop: 20 }}>
806
- PASS
807
- </div>
808
- </div>
809
- );
810
- };
811
-
812
- export const Performance: Story = {
813
- render: () => <PerformanceTestComponent />,
814
- play: async ({ canvasElement, step }) => {
815
- const canvas = within(canvasElement);
816
- const statusElement = canvas.getByLabelText('Status of the test run');
817
- statusElement.textContent = 'RUNS';
818
-
819
- await step('Should not cause excessive re-renders during interactions', async () => {
820
- const renderCountElement = canvas.getByTestId('render-count');
821
- const initialRenders = parseInt(renderCountElement.textContent?.split(': ')[1] || '0');
822
-
823
- // Open and close popover multiple times
824
- const trigger = canvas.getByTestId('popover-trigger');
825
-
826
- // First open/close cycle
827
- await userEvent.click(trigger);
828
- await waitFor(() => {
829
- expect(document.querySelector('[data-testid="performance-content"]')).toBeInTheDocument();
830
- });
831
- await userEvent.keyboard('{Escape}');
832
- await waitFor(() => {
833
- expect(
834
- document.querySelector('[data-testid="performance-content"]'),
835
- ).not.toBeInTheDocument();
836
- });
837
-
838
- // Second open/close cycle
839
- await userEvent.click(trigger);
840
- await waitFor(() => {
841
- expect(document.querySelector('[data-testid="performance-content"]')).toBeInTheDocument();
842
- });
843
- await userEvent.keyboard('{Escape}');
844
- await waitFor(() => {
845
- expect(
846
- document.querySelector('[data-testid="performance-content"]'),
847
- ).not.toBeInTheDocument();
848
- });
849
-
850
- // Third open/close cycle
851
- await userEvent.click(trigger);
852
- await waitFor(() => {
853
- expect(document.querySelector('[data-testid="performance-content"]')).toBeInTheDocument();
854
- });
855
- await userEvent.keyboard('{Escape}');
856
- await waitFor(() => {
857
- expect(
858
- document.querySelector('[data-testid="performance-content"]'),
859
- ).not.toBeInTheDocument();
860
- });
861
-
862
- // Check that renders didn't increase dramatically
863
- const finalRenders = parseInt(
864
- canvas.getByTestId('render-count').textContent?.split(': ')[1] || '0',
865
- );
866
- expect(finalRenders - initialRenders).toBeLessThan(10);
867
-
868
- // Verify open count matches expected
869
- const openCount = parseInt(
870
- canvas.getByTestId('open-count').textContent?.split(': ')[1] || '0',
871
- );
872
- expect(openCount).toBe(3);
873
- });
874
-
875
- await step('Should handle rapid open/close without performance degradation', async () => {
876
- const trigger = canvas.getByTestId('popover-trigger');
877
-
878
- // Rapid open/close
879
- for (let i = 0; i < 5; i++) {
880
- await userEvent.click(trigger);
881
- await userEvent.keyboard('{Escape}');
882
- }
883
-
884
- });
885
-
886
- statusElement.textContent = 'PASS';
887
- },
888
- };
889
-
890
- // 10. Edge Cases Tests
891
- export const EdgeCases: Story = {
892
- render: () => (
893
- <>
894
- <Stack spacing={2}>
895
- {/* Empty content */}
896
- <PopoverTestWrapper data-testid="empty-popover">
897
- <div style={{ padding: 16, minHeight: 50 }} data-testid="empty-content" />
898
- </PopoverTestWrapper>
899
-
900
- {/* Very long content */}
901
- <PopoverTestWrapper maxWidth={200} data-testid="long-content-popover">
902
- <Typography sx={{ p: 2 }} data-testid="long-content">
903
- This is a very long text content that should wrap properly and not break the popover
904
- layout. It should handle overflow gracefully and maintain proper spacing and typography.
905
- Additional text to ensure wrapping behavior is tested thoroughly.
906
- </Typography>
907
- </PopoverTestWrapper>
908
-
909
- {/* No maxWidth */}
910
- <PopoverTestWrapper maxWidth={undefined} data-testid="no-max-width-popover">
911
- <Typography sx={{ p: 2 }} data-testid="no-max-width-content">
912
- Content without maxWidth constraint should expand naturally
913
- </Typography>
914
- </PopoverTestWrapper>
915
-
916
- {/* Zero maxWidth edge case */}
917
- <PopoverTestWrapper maxWidth={0} data-testid="zero-width-popover">
918
- <Typography sx={{ p: 2 }} data-testid="zero-width-content">
919
- Zero width test
920
- </Typography>
921
- </PopoverTestWrapper>
922
- </Stack>
923
- <div aria-label="Status of the test run" style={{ marginTop: 20 }}>
924
- PASS
925
- </div>
926
- </>
927
- ),
928
- play: async ({ canvasElement, step }) => {
929
- const canvas = within(canvasElement);
930
- const statusElement = canvas.getByLabelText('Status of the test run');
931
- statusElement.textContent = 'RUNS';
932
-
933
- await step('Should handle empty content gracefully', async () => {
934
- const triggers = canvas.getAllByTestId('popover-trigger');
935
- await userEvent.click(triggers[0]);
936
-
937
- await waitFor(() => {
938
- // Use document queries for portal-rendered content
939
- const emptyContent = document.querySelector('[data-testid="empty-content"]');
940
- expect(emptyContent).toBeInTheDocument();
941
-
942
- // Verify minimum height is applied
943
- const computedStyle = window.getComputedStyle(emptyContent as HTMLElement);
944
- expect(computedStyle.minHeight).toBe('50px');
945
-
946
- // Verify popover renders even with empty content
947
- const paper = emptyContent?.closest('.MuiPaper-root');
948
- expect(paper).toBeInTheDocument();
949
- });
950
- await userEvent.keyboard('{Escape}');
951
- });
952
-
953
- await step('Should handle long content with proper text wrapping', async () => {
954
- const triggers = canvas.getAllByTestId('popover-trigger');
955
- await userEvent.click(triggers[1]);
956
-
957
- await waitFor(() => {
958
- // Use document queries for portal-rendered content
959
- const content = document.querySelector('[data-testid="long-content"]') as HTMLElement;
960
- expect(content).toBeInTheDocument();
961
-
962
- // Verify maxWidth is applied
963
- const paper = content?.closest('.MuiPaper-root') as HTMLElement;
964
- if (paper) {
965
- const computedStyle = window.getComputedStyle(paper);
966
- expect(computedStyle.maxWidth).toBe('200px');
967
- }
968
-
969
- // Verify text wraps properly
970
- const contentHeight = content?.offsetHeight;
971
- expect(contentHeight).toBeGreaterThan(50); // Should be taller due to wrapping
972
- });
973
- await userEvent.keyboard('{Escape}');
974
- });
975
-
976
- await step('Should handle content without maxWidth constraint', async () => {
977
- const triggers = canvas.getAllByTestId('popover-trigger');
978
- await userEvent.click(triggers[2]);
979
-
980
- await waitFor(() => {
981
- // Use document queries for portal-rendered content
982
- const content = document.querySelector('[data-testid="no-max-width-content"]');
983
- expect(content).toBeInTheDocument();
984
-
985
- // Verify no maxWidth constraint is applied (should use default)
986
- const paper = content?.closest('.MuiPaper-root') as HTMLElement;
987
- if (paper) {
988
- const computedStyle = window.getComputedStyle(paper);
989
- // When undefined, should use default maxWidth of 400px
990
- expect(computedStyle.maxWidth).toBe('400px');
991
- }
992
- });
993
- await userEvent.keyboard('{Escape}');
994
- });
995
-
996
- await step('Should handle zero maxWidth edge case', async () => {
997
- const triggers = canvas.getAllByTestId('popover-trigger');
998
- await userEvent.click(triggers[3]);
999
-
1000
- await waitFor(() => {
1001
- // Use document queries for portal-rendered content
1002
- const content = document.querySelector('[data-testid="zero-width-content"]');
1003
- expect(content).toBeInTheDocument();
1004
-
1005
- // Verify zero maxWidth is handled
1006
- const paper = content?.closest('.MuiPaper-root') as HTMLElement;
1007
- if (paper) {
1008
- const computedStyle = window.getComputedStyle(paper);
1009
- expect(computedStyle.maxWidth).toBe('0px');
1010
- }
1011
- });
1012
- await userEvent.keyboard('{Escape}');
1013
- });
1014
-
1015
- statusElement.textContent = 'PASS';
1016
- },
1017
- };
1018
-
1019
- // 11. Integration Tests
1020
- const IntegrationTestComponent = () => {
1021
- const [selectedItem, setSelectedItem] = React.useState<string>('');
1022
- const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
1023
- const isOpen = Boolean(anchorEl);
1024
-
1025
- const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
1026
- setAnchorEl(event.currentTarget);
1027
- };
1028
-
1029
- const handleClose = () => {
1030
- setAnchorEl(null);
1031
- };
1032
-
1033
- const handleItemClick = (option: string) => {
1034
- setSelectedItem(option);
1035
- handleClose(); // Properly close popover after selection
1036
- };
1037
-
1038
- return (
1039
- <>
1040
- <div>
1041
- <Typography data-testid="selected-item">Selected: {selectedItem || 'None'}</Typography>
1042
- <Button variant="contained" onClick={handleClick} data-testid="popover-trigger">
1043
- Open Popover
1044
- </Button>
1045
- <Popover
1046
- open={isOpen}
1047
- anchorEl={anchorEl}
1048
- onClose={handleClose}
1049
- anchorOrigin={{
1050
- vertical: 'bottom',
1051
- horizontal: 'left',
1052
- }}
1053
- data-testid="popover"
1054
- >
1055
- <div style={{ padding: 16 }}>
1056
- <Typography variant="h6" gutterBottom>
1057
- Select an Option
1058
- </Typography>
1059
- <List dense>
1060
- {['Option 1', 'Option 2', 'Option 3'].map((option) => (
1061
- <ListItem
1062
- key={option}
1063
- component="button"
1064
- data-testid={`option-${option.toLowerCase().replace(' ', '-')}`}
1065
- onClick={() => handleItemClick(option)}
1066
- >
1067
- <ListItemText primary={option} />
1068
- </ListItem>
1069
- ))}
1070
- </List>
1071
- </div>
1072
- </Popover>
1073
- <Typography data-testid="popover-state" style={{ display: 'none' }}>
1074
- {isOpen ? 'open' : 'closed'}
1075
- </Typography>
1076
- </div>
1077
- <div aria-label="Status of the test run" style={{ marginTop: 20 }}>
1078
- PASS
1079
- </div>
1080
- </>
1081
- );
1082
- };
1083
-
1084
- export const Integration: Story = {
1085
- render: () => <IntegrationTestComponent />,
1086
- play: async ({ canvasElement, step }) => {
1087
- const canvas = within(canvasElement);
1088
- const statusElement = canvas.getByLabelText('Status of the test run');
1089
- statusElement.textContent = 'RUNS';
1090
-
1091
- await step('Should integrate with external state management', async () => {
1092
- // Verify initial state
1093
- expect(canvas.getByTestId('selected-item')).toHaveTextContent('Selected: None');
1094
- expect(canvas.getByTestId('popover-state')).toHaveTextContent('closed');
1095
-
1096
- const trigger = canvas.getByTestId('popover-trigger');
1097
- await userEvent.click(trigger);
1098
-
1099
- await waitFor(() => {
1100
- expect(canvas.getByTestId('popover-state')).toHaveTextContent('open');
1101
- // Use document queries for portal-rendered content
1102
- expect(document.querySelector('[data-testid="option-option-1"]')).toBeInTheDocument();
1103
- expect(document.querySelector('[data-testid="option-option-2"]')).toBeInTheDocument();
1104
- expect(document.querySelector('[data-testid="option-option-3"]')).toBeInTheDocument();
1105
- });
1106
-
1107
- // Click an option
1108
- const option2 = document.querySelector('[data-testid="option-option-2"]') as HTMLElement;
1109
- await userEvent.click(option2);
1110
-
1111
- // Should update external state and close popover
1112
- await waitFor(() => {
1113
- expect(canvas.getByTestId('selected-item')).toHaveTextContent('Selected: Option 2');
1114
- expect(canvas.getByTestId('popover-state')).toHaveTextContent('closed');
1115
- });
1116
-
1117
- // Wait for popover content to be removed from DOM
1118
- await waitFor(
1119
- () => {
1120
- expect(document.querySelector('[data-testid="option-option-1"]')).not.toBeInTheDocument();
1121
- },
1122
- { timeout: 2000 },
1123
- );
1124
- });
1125
-
1126
- await step('Should maintain state across multiple interactions', async () => {
1127
- // Open again and select different option
1128
- const trigger = canvas.getByTestId('popover-trigger');
1129
- await userEvent.click(trigger);
1130
-
1131
- await waitFor(() => {
1132
- expect(canvas.getByTestId('popover-state')).toHaveTextContent('open');
1133
- });
1134
-
1135
- const option3 = document.querySelector('[data-testid="option-option-3"]') as HTMLElement;
1136
- await userEvent.click(option3);
1137
-
1138
- await waitFor(() => {
1139
- expect(canvas.getByTestId('selected-item')).toHaveTextContent('Selected: Option 3');
1140
- expect(canvas.getByTestId('popover-state')).toHaveTextContent('closed');
1141
- });
1142
-
1143
- // Open again and select first option
1144
- await userEvent.click(trigger);
1145
-
1146
- await waitFor(() => {
1147
- // Use document query for portal-rendered content
1148
- expect(document.querySelector('[data-testid="option-option-1"]')).toBeInTheDocument();
1149
- });
1150
-
1151
- const option1 = document.querySelector('[data-testid="option-option-1"]') as HTMLElement;
1152
- await userEvent.click(option1);
1153
-
1154
- await waitFor(() => {
1155
- expect(canvas.getByTestId('selected-item')).toHaveTextContent('Selected: Option 1');
1156
- expect(canvas.getByTestId('popover-state')).toHaveTextContent('closed');
1157
- });
1158
- });
1159
-
1160
- statusElement.textContent = 'PASS';
1161
- },
1162
- };