@12-apps/ui 1.4.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -8
- package/src/button/index.ts +1 -0
- package/src/social-login-button/index.ts +1 -0
- package/src/{social-login-button.tsx → social-login-button/social-login-button.tsx} +5 -5
- package/src/user-avatar/index.ts +1 -0
- package/src/{user-avatar.tsx → user-avatar/user-avatar.tsx} +4 -4
- package/eslint.config.js +0 -77
- package/src/__tests__/data-grid.test.tsx +0 -125
- package/src/__tests__/package-wiring.test.ts +0 -41
- package/src/__tests__/setup.ts +0 -1
- package/src/__tests__/social-login-button.test.tsx +0 -186
- package/src/__tests__/user-avatar.test.tsx +0 -195
- package/src/__tests__/viewport.test.ts +0 -50
- package/src/components/command/__tests__/command.test.tsx +0 -112
- package/src/components/data-display/Alert/Alert.stories.tsx +0 -559
- package/src/components/data-display/Alert/Alert.test.stories.tsx +0 -707
- package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +0 -362
- package/src/components/data-display/AlertDialog/AlertDialog.test.stories.tsx +0 -753
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.stories.tsx +0 -230
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +0 -270
- package/src/components/data-display/Avatar/Avatar.stories.tsx +0 -721
- package/src/components/data-display/Avatar/Avatar.test.stories.tsx +0 -822
- package/src/components/data-display/Badge/Badge.stories.tsx +0 -1134
- package/src/components/data-display/Badge/Badge.test.stories.tsx +0 -1194
- package/src/components/data-display/Banner/Banner.stories.tsx +0 -528
- package/src/components/data-display/Banner/Banner.test.stories.tsx +0 -503
- package/src/components/data-display/Carousel/Carousel.stories.tsx +0 -607
- package/src/components/data-display/Carousel/Carousel.test.stories.tsx +0 -691
- package/src/components/data-display/Chart/Chart.stories.tsx +0 -568
- package/src/components/data-display/Chart/Chart.test.stories.tsx +0 -174
- package/src/components/data-display/Chart/SpecChart.stories.tsx +0 -150
- package/src/components/data-display/Chart/SpecChart.test.stories.tsx +0 -56
- package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +0 -60
- package/src/components/data-display/Chart/__tests__/chart-format.test.ts +0 -43
- package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +0 -119
- package/src/components/data-display/Chip/Chip.stories.tsx +0 -252
- package/src/components/data-display/Chip/Chip.test.stories.tsx +0 -266
- package/src/components/data-display/DataGrid/DataGrid.stories.tsx +0 -515
- package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +0 -653
- package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +0 -271
- package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +0 -134
- package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +0 -83
- package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +0 -115
- package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +0 -53
- package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +0 -114
- package/src/components/data-display/DataViews/base-card.stories.tsx +0 -136
- package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +0 -254
- package/src/components/data-display/EmptyState/EmptyState.stories.tsx +0 -282
- package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +0 -441
- package/src/components/data-display/ErrorState/ErrorState.stories.tsx +0 -110
- package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +0 -133
- package/src/components/data-display/HoverCard/HoverCard.stories.tsx +0 -650
- package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +0 -929
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.stories.tsx +0 -411
- package/src/components/data-display/LazyImage/LazyImage.stories.tsx +0 -387
- package/src/components/data-display/Lightbox/Lightbox.stories.tsx +0 -315
- package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +0 -598
- package/src/components/data-display/LoadingState/LoadingState.stories.tsx +0 -124
- package/src/components/data-display/LoadingState/LoadingState.test.stories.tsx +0 -99
- package/src/components/data-display/MapPreview/MapPreview.stories.tsx +0 -458
- package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +0 -394
- package/src/components/data-display/Popover/Popover.stories.tsx +0 -295
- package/src/components/data-display/Popover/Popover.test.stories.tsx +0 -1162
- package/src/components/data-display/Progress/Progress.stories.tsx +0 -481
- package/src/components/data-display/Progress/Progress.test.stories.tsx +0 -2070
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +0 -160
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.test.stories.tsx +0 -190
- package/src/components/data-display/Sheet/Sheet.stories.tsx +0 -883
- package/src/components/data-display/Sheet/Sheet.test.stories.tsx +0 -1101
- package/src/components/data-display/StatCard/StatCard.stories.tsx +0 -167
- package/src/components/data-display/StatCard/StatCard.test.stories.tsx +0 -240
- package/src/components/data-display/Stepper/Stepper.stories.tsx +0 -540
- package/src/components/data-display/Stepper/Stepper.test.stories.tsx +0 -600
- package/src/components/data-display/Table/Table.stories.tsx +0 -803
- package/src/components/data-display/Table/Table.test.stories.tsx +0 -369
- package/src/components/data-display/Timeline/Timeline.stories.tsx +0 -442
- package/src/components/data-display/Timeline/Timeline.test.stories.tsx +0 -930
- package/src/components/data-display/TimingDiagram/TimingDiagram.stories.tsx +0 -475
- package/src/components/data-display/TimingDiagram/TimingDiagram.test.stories.tsx +0 -734
- package/src/components/data-display/Tooltip/Tooltip.stories.tsx +0 -468
- package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +0 -1154
- package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +0 -146
- package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +0 -114
- package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +0 -288
- package/src/components/feedback/Dialog/Dialog.stories.tsx +0 -833
- package/src/components/feedback/Dialog/Dialog.test.stories.tsx +0 -955
- package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +0 -66
- package/src/components/feedback/Modal/Modal.stories.tsx +0 -765
- package/src/components/feedback/Modal/Modal.test.stories.tsx +0 -251
- package/src/components/feedback/Sonner/Sonner.stories.tsx +0 -698
- package/src/components/feedback/Sonner/Sonner.test.stories.tsx +0 -896
- package/src/components/feedback/StackedModal/StackedModal.stories.tsx +0 -1783
- package/src/components/feedback/StackedModal/StackedModal.test.stories.tsx +0 -975
- package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +0 -104
- package/src/components/feedback/Toast/Toast.stories.tsx +0 -548
- package/src/components/feedback/Toast/Toast.test.stories.tsx +0 -834
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.stories.tsx +0 -691
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +0 -634
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +0 -665
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +0 -727
- package/src/components/form/Autocomplete/Autocomplete.stories.tsx +0 -715
- package/src/components/form/Autocomplete/Autocomplete.test.stories.tsx +0 -825
- package/src/components/form/Button/Button.stories.tsx +0 -365
- package/src/components/form/Button/Button.test.stories.tsx +0 -607
- package/src/components/form/Calendar/Calendar.stories.tsx +0 -349
- package/src/components/form/Calendar/Calendar.test.stories.tsx +0 -464
- package/src/components/form/CepField/CepField.stories.tsx +0 -138
- package/src/components/form/CepField/CepField.test.stories.tsx +0 -281
- package/src/components/form/Checkbox/Checkbox.stories.tsx +0 -265
- package/src/components/form/Checkbox/Checkbox.test.stories.tsx +0 -181
- package/src/components/form/CodeEditor/CodeEditor.stories.tsx +0 -871
- package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +0 -574
- package/src/components/form/Command/Command.stories.tsx +0 -580
- package/src/components/form/Command/Command.test.stories.tsx +0 -215
- package/src/components/form/CreatableSelect/CreatableSelect.stories.tsx +0 -112
- package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +0 -134
- package/src/components/form/Form/Form.stories.tsx +0 -687
- package/src/components/form/Form/Form.test.stories.tsx +0 -601
- package/src/components/form/Input/Input.stories.tsx +0 -260
- package/src/components/form/Input/Input.test.stories.tsx +0 -1191
- package/src/components/form/InputOTP/InputOTP.stories.tsx +0 -774
- package/src/components/form/InputOTP/InputOTP.test.stories.tsx +0 -588
- package/src/components/form/Label/Label.stories.tsx +0 -473
- package/src/components/form/Label/Label.test.stories.tsx +0 -909
- package/src/components/form/Menubar/Menubar.stories.tsx +0 -472
- package/src/components/form/Menubar/Menubar.test.stories.tsx +0 -584
- package/src/components/form/PasswordStrength/PasswordStrength.stories.tsx +0 -562
- package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +0 -327
- package/src/components/form/PhoneInput/PhoneInput.stories.tsx +0 -495
- package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +0 -666
- package/src/components/form/RadioGroup/RadioGroup.stories.tsx +0 -518
- package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +0 -685
- package/src/components/form/RichTextEditor/RichTextEditor.stories.tsx +0 -261
- package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +0 -491
- package/src/components/form/SearchPalette/SearchPalette.stories.tsx +0 -135
- package/src/components/form/Select/Select.stories.tsx +0 -364
- package/src/components/form/Select/Select.test.stories.tsx +0 -985
- package/src/components/form/Slider/Slider.stories.tsx +0 -418
- package/src/components/form/Slider/Slider.test.stories.tsx +0 -1042
- package/src/components/form/Switch/Switch.stories.tsx +0 -697
- package/src/components/form/Switch/Switch.test.stories.tsx +0 -597
- package/src/components/form/Textarea/Textarea.stories.tsx +0 -473
- package/src/components/form/Textarea/Textarea.test.stories.tsx +0 -702
- package/src/components/form/Toggle/Toggle.stories.tsx +0 -638
- package/src/components/form/Toggle/Toggle.test.stories.tsx +0 -1159
- package/src/components/form/ToggleGroup/ToggleGroup.stories.tsx +0 -617
- package/src/components/form/ToggleGroup/ToggleGroup.test.stories.tsx +0 -918
- package/src/components/form/UploadButton/UploadButton.stories.tsx +0 -367
- package/src/components/form/UploadButton/UploadButton.test.stories.tsx +0 -573
- package/src/components/form/total-form/total-form.test.tsx +0 -214
- package/src/components/layout/Accordion/Accordion.stories.tsx +0 -694
- package/src/components/layout/Accordion/Accordion.test.stories.tsx +0 -1344
- package/src/components/layout/Card/Card.stories.tsx +0 -591
- package/src/components/layout/Card/Card.test.stories.tsx +0 -1134
- package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +0 -92
- package/src/components/layout/Collapsible/Collapsible.stories.tsx +0 -180
- package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +0 -308
- package/src/components/layout/Container/Container.stories.tsx +0 -557
- package/src/components/layout/Container/Container.test.stories.tsx +0 -745
- package/src/components/layout/ContentToolbar/ContentToolbar.stories.tsx +0 -129
- package/src/components/layout/ContentToolbar/ContentToolbar.test.stories.tsx +0 -251
- package/src/components/layout/ContentToolbar/__tests__/ColumnsMenu.test.tsx +0 -39
- package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +0 -131
- package/src/components/layout/ContentToolbar/__tests__/SavedViewsMenu.test.tsx +0 -99
- package/src/components/layout/Dashboard/Dashboard.stories.tsx +0 -104
- package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +0 -190
- package/src/components/layout/Drawer/Drawer.stories.tsx +0 -658
- package/src/components/layout/Drawer/Drawer.test.stories.tsx +0 -1343
- package/src/components/layout/Resizable/Resizable.stories.tsx +0 -1039
- package/src/components/layout/Resizable/Resizable.test.stories.tsx +0 -750
- package/src/components/layout/ScrollArea/ScrollArea.stories.tsx +0 -651
- package/src/components/layout/ScrollArea/ScrollArea.test.stories.tsx +0 -619
- package/src/components/layout/Separator/Separator.stories.tsx +0 -563
- package/src/components/layout/Separator/Separator.test.stories.tsx +0 -518
- package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +0 -192
- package/src/components/layout/SettingsLayout/SettingsLayout.test.stories.tsx +0 -175
- package/src/components/layout/Sidebar/Sidebar.stories.tsx +0 -609
- package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +0 -1324
- package/src/components/layout/Skeleton/Skeleton.stories.tsx +0 -689
- package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +0 -521
- package/src/components/layout/Spacer/Spacer.stories.tsx +0 -502
- package/src/components/layout/Spacer/Spacer.test.stories.tsx +0 -472
- package/src/components/layout/TableFilter/TableFilter.stories.tsx +0 -82
- package/src/components/layout/TableFilter/TableFilter.test.stories.tsx +0 -87
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +0 -638
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +0 -1008
- package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +0 -386
- package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +0 -435
- package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +0 -614
- package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +0 -845
- package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +0 -578
- package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +0 -660
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +0 -516
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +0 -935
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +0 -501
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +0 -453
- package/src/components/navigation/Pagination/Pagination.stories.tsx +0 -602
- package/src/components/navigation/Pagination/Pagination.test.stories.tsx +0 -520
- package/src/components/navigation/Tabs/Tabs.stories.tsx +0 -1004
- package/src/components/navigation/Tabs/Tabs.test.stories.tsx +0 -1225
- package/src/components/typography/Blockquote/Blockquote.stories.tsx +0 -359
- package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +0 -432
- package/src/components/typography/Code/Code.stories.tsx +0 -686
- package/src/components/typography/Code/Code.test.stories.tsx +0 -703
- package/src/components/typography/Heading/Heading.stories.tsx +0 -451
- package/src/components/typography/Heading/Heading.test.stories.tsx +0 -830
- package/src/components/typography/Paragraph/Paragraph.stories.tsx +0 -280
- package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +0 -231
- package/src/components/typography/Text/Text.stories.tsx +0 -359
- package/src/components/typography/Text/Text.test.stories.tsx +0 -762
- package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +0 -945
- package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +0 -603
- package/src/components/utility/AspectRatio/AspectRatio.stories.tsx +0 -414
- package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +0 -859
- package/src/components/utility/InfiniteScroll/InfiniteScroll.stories.tsx +0 -620
- package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +0 -361
- package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +0 -118
- package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +0 -83
- package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +0 -65
- package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +0 -271
- package/src/components/utility/Portal/Portal.stories.tsx +0 -340
- package/src/components/utility/Portal/Portal.test.stories.tsx +0 -2086
- package/src/components/utility/Transition/Transition.stories.tsx +0 -493
- package/src/components/utility/Transition/Transition.test.stories.tsx +0 -854
- package/src/components/utility/VirtualList/VirtualList.stories.tsx +0 -1073
- package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +0 -1085
- package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +0 -431
- package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +0 -364
- /package/src/{button.tsx → button/button.tsx} +0 -0
|
@@ -1,660 +0,0 @@
|
|
|
1
|
-
import Add from '@mui/icons-material/Add';
|
|
2
|
-
import Check from '@mui/icons-material/Check';
|
|
3
|
-
import Close from '@mui/icons-material/Close';
|
|
4
|
-
import Delete from '@mui/icons-material/Delete';
|
|
5
|
-
import Edit from '@mui/icons-material/Edit';
|
|
6
|
-
import FileCopy from '@mui/icons-material/FileCopy';
|
|
7
|
-
import Save from '@mui/icons-material/Save';
|
|
8
|
-
import Settings from '@mui/icons-material/Settings';
|
|
9
|
-
import Share from '@mui/icons-material/Share';
|
|
10
|
-
import { Box } from '@mui/material';
|
|
11
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
12
|
-
import { expect, fn,userEvent, waitFor, within } from 'storybook/test';
|
|
13
|
-
|
|
14
|
-
import { Button } from '../../form/Button';
|
|
15
|
-
import { DropdownMenu } from './DropdownMenu';
|
|
16
|
-
import type { DropdownMenuItem } from './DropdownMenu.types';
|
|
17
|
-
|
|
18
|
-
const meta: Meta<typeof DropdownMenu> = {
|
|
19
|
-
title: 'Navigation/DropdownMenu/Tests',
|
|
20
|
-
component: DropdownMenu,
|
|
21
|
-
parameters: {
|
|
22
|
-
layout: 'centered',
|
|
23
|
-
chromatic: { disableSnapshot: false },
|
|
24
|
-
},
|
|
25
|
-
tags: ['autodocs', 'test', 'component:DropdownMenu'],
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export default meta;
|
|
29
|
-
type Story = StoryObj<typeof meta>;
|
|
30
|
-
|
|
31
|
-
// Test data
|
|
32
|
-
const testItems = [
|
|
33
|
-
{ id: '1', label: 'Edit', icon: <Edit fontSize="small" />, onClick: fn() },
|
|
34
|
-
{ id: '2', label: 'Copy', icon: <FileCopy fontSize="small" />, onClick: fn() },
|
|
35
|
-
{ id: '3', label: 'Share', icon: <Share fontSize="small" />, onClick: fn() },
|
|
36
|
-
{ id: 'divider1', type: 'divider' as const },
|
|
37
|
-
{
|
|
38
|
-
id: '4',
|
|
39
|
-
label: 'Delete',
|
|
40
|
-
icon: <Delete fontSize="small" />,
|
|
41
|
-
color: 'error' as const,
|
|
42
|
-
onClick: fn(),
|
|
43
|
-
},
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
const itemsWithShortcuts = [
|
|
47
|
-
{ id: '1', label: 'Save', icon: <Save fontSize="small" />, shortcut: 'Ctrl+S', onClick: fn() },
|
|
48
|
-
{
|
|
49
|
-
id: '2',
|
|
50
|
-
label: 'Settings',
|
|
51
|
-
icon: <Settings fontSize="small" />,
|
|
52
|
-
shortcut: 'Ctrl+,',
|
|
53
|
-
onClick: fn(),
|
|
54
|
-
},
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
const itemsWithDisabled = [
|
|
58
|
-
{ id: '1', label: 'Enabled', icon: <Check fontSize="small" />, onClick: fn() },
|
|
59
|
-
{ id: '2', label: 'Disabled', icon: <Close fontSize="small" />, disabled: true, onClick: fn() },
|
|
60
|
-
{ id: '3', label: 'Also Enabled', icon: <Add fontSize="small" />, onClick: fn() },
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
// 1. Basic Interaction Test
|
|
64
|
-
export const BasicInteraction: Story = {
|
|
65
|
-
args: {
|
|
66
|
-
items: testItems,
|
|
67
|
-
trigger: <Button variant="outline">Open Menu</Button>,
|
|
68
|
-
variant: 'default',
|
|
69
|
-
size: 'md',
|
|
70
|
-
onOpen: fn(),
|
|
71
|
-
onClose: fn(),
|
|
72
|
-
},
|
|
73
|
-
play: async ({ canvasElement }) => {
|
|
74
|
-
const canvas = within(canvasElement);
|
|
75
|
-
|
|
76
|
-
// Find and click the trigger button
|
|
77
|
-
const trigger = await canvas.findByRole('button', { name: /open menu/i });
|
|
78
|
-
expect(trigger).toBeInTheDocument();
|
|
79
|
-
|
|
80
|
-
// Open the menu
|
|
81
|
-
await userEvent.click(trigger);
|
|
82
|
-
|
|
83
|
-
// Wait for menu to appear
|
|
84
|
-
await waitFor(async () => {
|
|
85
|
-
const menu = document.querySelector('[role="menu"]');
|
|
86
|
-
expect(menu).toBeInTheDocument();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Find menu items
|
|
90
|
-
const editItem = await within(document.body).findByText('Edit');
|
|
91
|
-
expect(editItem).toBeInTheDocument();
|
|
92
|
-
|
|
93
|
-
const copyItem = await within(document.body).findByText('Copy');
|
|
94
|
-
expect(copyItem).toBeInTheDocument();
|
|
95
|
-
|
|
96
|
-
// Click on Edit item
|
|
97
|
-
await userEvent.click(editItem);
|
|
98
|
-
|
|
99
|
-
// Verify onClick was called
|
|
100
|
-
await waitFor(() => {
|
|
101
|
-
expect(testItems[0].onClick).toHaveBeenCalled();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Menu should close after item click
|
|
105
|
-
await waitFor(() => {
|
|
106
|
-
const menu = document.querySelector('[role="menu"]');
|
|
107
|
-
expect(menu).not.toBeInTheDocument();
|
|
108
|
-
});
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// 2. Keyboard Navigation Test
|
|
113
|
-
export const KeyboardNavigation: Story = {
|
|
114
|
-
args: {
|
|
115
|
-
items: testItems,
|
|
116
|
-
trigger: <Button variant="outline">Keyboard Menu</Button>,
|
|
117
|
-
variant: 'default',
|
|
118
|
-
size: 'md',
|
|
119
|
-
onOpen: fn(),
|
|
120
|
-
onClose: fn(),
|
|
121
|
-
},
|
|
122
|
-
play: async ({ canvasElement }) => {
|
|
123
|
-
const canvas = within(canvasElement);
|
|
124
|
-
|
|
125
|
-
// Focus and open menu with keyboard
|
|
126
|
-
const trigger = await canvas.findByRole('button', { name: /keyboard menu/i });
|
|
127
|
-
// Tab to the trigger — clicking would open the menu, which is what the Enter
|
|
128
|
-
// key below is meant to prove.
|
|
129
|
-
await userEvent.tab();
|
|
130
|
-
await waitFor(() => expect(trigger).toHaveFocus());
|
|
131
|
-
|
|
132
|
-
// Open with Enter key
|
|
133
|
-
await userEvent.keyboard('{Enter}');
|
|
134
|
-
|
|
135
|
-
// Wait for menu to open
|
|
136
|
-
await waitFor(() => {
|
|
137
|
-
const menu = document.querySelector('[role="menu"]');
|
|
138
|
-
expect(menu).toBeInTheDocument();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// Navigate with arrow keys
|
|
142
|
-
await userEvent.keyboard('{ArrowDown}');
|
|
143
|
-
await waitFor(() => {
|
|
144
|
-
// MUI Menu focuses on an item when navigating - just check that some menu item has focus
|
|
145
|
-
const focusedItem = document.activeElement;
|
|
146
|
-
expect(focusedItem?.getAttribute('role')).toBe('menuitem');
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Continue navigation
|
|
150
|
-
await userEvent.keyboard('{ArrowDown}');
|
|
151
|
-
await userEvent.keyboard('{ArrowUp}');
|
|
152
|
-
|
|
153
|
-
// Close with Escape
|
|
154
|
-
await userEvent.keyboard('{Escape}');
|
|
155
|
-
|
|
156
|
-
await waitFor(() => {
|
|
157
|
-
const menu = document.querySelector('[role="menu"]');
|
|
158
|
-
expect(menu).not.toBeInTheDocument();
|
|
159
|
-
});
|
|
160
|
-
},
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// 3. Screen Reader Test
|
|
164
|
-
export const ScreenReader: Story = {
|
|
165
|
-
args: {
|
|
166
|
-
items: itemsWithShortcuts,
|
|
167
|
-
trigger: (
|
|
168
|
-
<Button variant="outline" aria-label="Settings menu">
|
|
169
|
-
Settings
|
|
170
|
-
</Button>
|
|
171
|
-
),
|
|
172
|
-
variant: 'default',
|
|
173
|
-
size: 'md',
|
|
174
|
-
onOpen: fn(),
|
|
175
|
-
onClose: fn(),
|
|
176
|
-
},
|
|
177
|
-
play: async ({ canvasElement }) => {
|
|
178
|
-
const canvas = within(canvasElement);
|
|
179
|
-
|
|
180
|
-
// Check trigger has proper aria-label
|
|
181
|
-
const trigger = await canvas.findByRole('button', { name: /settings menu/i });
|
|
182
|
-
expect(trigger).toHaveAttribute('aria-label', 'Settings menu');
|
|
183
|
-
|
|
184
|
-
// Open menu
|
|
185
|
-
await userEvent.click(trigger);
|
|
186
|
-
|
|
187
|
-
// Check menu has proper role
|
|
188
|
-
await waitFor(() => {
|
|
189
|
-
const menu = document.querySelector('[role="menu"]');
|
|
190
|
-
expect(menu).toBeInTheDocument();
|
|
191
|
-
expect(menu).toHaveAttribute('role', 'menu');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Check menu items have proper roles
|
|
195
|
-
const menuItems = document.querySelectorAll('[role="menuitem"]');
|
|
196
|
-
expect(menuItems.length).toBeGreaterThan(0);
|
|
197
|
-
|
|
198
|
-
// Check shortcuts are announced
|
|
199
|
-
const saveItem = await within(document.body).findByText('Save');
|
|
200
|
-
const shortcut = await within(document.body).findByText('Ctrl+S');
|
|
201
|
-
expect(saveItem).toBeInTheDocument();
|
|
202
|
-
expect(shortcut).toBeInTheDocument();
|
|
203
|
-
},
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// 4. Focus Management Test
|
|
207
|
-
export const FocusManagement: Story = {
|
|
208
|
-
args: {
|
|
209
|
-
items: testItems,
|
|
210
|
-
trigger: <Button variant="outline">Focus Test</Button>,
|
|
211
|
-
variant: 'default',
|
|
212
|
-
size: 'md',
|
|
213
|
-
onOpen: fn(),
|
|
214
|
-
onClose: fn(),
|
|
215
|
-
},
|
|
216
|
-
play: async ({ canvasElement }) => {
|
|
217
|
-
const canvas = within(canvasElement);
|
|
218
|
-
|
|
219
|
-
// Get trigger button
|
|
220
|
-
const trigger = await canvas.findByRole('button', { name: /focus test/i });
|
|
221
|
-
|
|
222
|
-
// Store initial focus — reached by keyboard so the "focus returns to the
|
|
223
|
-
// trigger on close" check below starts from a real user position.
|
|
224
|
-
await userEvent.tab();
|
|
225
|
-
await waitFor(() => expect(document.activeElement).toBe(trigger));
|
|
226
|
-
|
|
227
|
-
// Open menu
|
|
228
|
-
await userEvent.click(trigger);
|
|
229
|
-
|
|
230
|
-
// Wait for menu to open
|
|
231
|
-
await waitFor(() => {
|
|
232
|
-
const menu = document.querySelector('[role="menu"]');
|
|
233
|
-
expect(menu).toBeInTheDocument();
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Focus should move to first menu item or menu itself
|
|
237
|
-
await waitFor(() => {
|
|
238
|
-
const focusedElement = document.activeElement;
|
|
239
|
-
expect(focusedElement?.closest('[role="menu"]')).toBeInTheDocument();
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// Close menu
|
|
243
|
-
await userEvent.keyboard('{Escape}');
|
|
244
|
-
|
|
245
|
-
// Focus should return to trigger
|
|
246
|
-
await waitFor(() => {
|
|
247
|
-
expect(document.activeElement).toBe(trigger);
|
|
248
|
-
});
|
|
249
|
-
},
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
// 5. Responsive Design Test
|
|
253
|
-
export const ResponsiveDesign: Story = {
|
|
254
|
-
args: {
|
|
255
|
-
items: testItems,
|
|
256
|
-
trigger: <Button variant="outline">Responsive Menu</Button>,
|
|
257
|
-
variant: 'default',
|
|
258
|
-
size: 'md',
|
|
259
|
-
onOpen: fn(),
|
|
260
|
-
onClose: fn(),
|
|
261
|
-
},
|
|
262
|
-
parameters: {
|
|
263
|
-
viewport: {
|
|
264
|
-
viewports: {
|
|
265
|
-
mobile: { name: 'Mobile', styles: { width: '375px', height: '667px' } },
|
|
266
|
-
tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } },
|
|
267
|
-
desktop: { name: 'Desktop', styles: { width: '1440px', height: '900px' } },
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
play: async ({ canvasElement }) => {
|
|
272
|
-
const canvas = within(canvasElement);
|
|
273
|
-
|
|
274
|
-
// Test on different viewport sizes
|
|
275
|
-
const trigger = await canvas.findByRole('button', { name: /responsive menu/i });
|
|
276
|
-
|
|
277
|
-
// Open menu
|
|
278
|
-
await userEvent.click(trigger);
|
|
279
|
-
|
|
280
|
-
// Check menu renders properly
|
|
281
|
-
await waitFor(() => {
|
|
282
|
-
const menu = document.querySelector('[role="menu"]');
|
|
283
|
-
expect(menu).toBeInTheDocument();
|
|
284
|
-
|
|
285
|
-
// Check menu positioning
|
|
286
|
-
const menuRect = menu?.getBoundingClientRect();
|
|
287
|
-
expect(menuRect).toBeDefined();
|
|
288
|
-
expect(menuRect?.width).toBeGreaterThan(0);
|
|
289
|
-
expect(menuRect?.height).toBeGreaterThan(0);
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
// Close menu
|
|
293
|
-
await userEvent.keyboard('{Escape}');
|
|
294
|
-
},
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
// 6. Theme Variations Test
|
|
298
|
-
export const ThemeVariations: Story = {
|
|
299
|
-
render: () => (
|
|
300
|
-
<Box sx={{ display: 'flex', gap: 2, flexDirection: 'column' }}>
|
|
301
|
-
<Box sx={{ display: 'flex', gap: 2 }}>
|
|
302
|
-
<DropdownMenu
|
|
303
|
-
items={testItems as never}
|
|
304
|
-
trigger={<Button variant="solid">Default Theme</Button>}
|
|
305
|
-
variant="default"
|
|
306
|
-
size="md"
|
|
307
|
-
/>
|
|
308
|
-
<DropdownMenu
|
|
309
|
-
items={testItems as never}
|
|
310
|
-
trigger={<Button variant="glass">Glass Theme</Button>}
|
|
311
|
-
variant="glass"
|
|
312
|
-
size="md"
|
|
313
|
-
/>
|
|
314
|
-
<DropdownMenu
|
|
315
|
-
items={testItems as never}
|
|
316
|
-
trigger={<Button variant="ghost">Minimal Theme</Button>}
|
|
317
|
-
variant="minimal"
|
|
318
|
-
size="md"
|
|
319
|
-
/>
|
|
320
|
-
</Box>
|
|
321
|
-
</Box>
|
|
322
|
-
),
|
|
323
|
-
play: async ({ canvasElement }) => {
|
|
324
|
-
const canvas = within(canvasElement);
|
|
325
|
-
|
|
326
|
-
// Test each variant
|
|
327
|
-
const variants = ['Default Theme', 'Glass Theme', 'Minimal Theme'];
|
|
328
|
-
|
|
329
|
-
for (const variant of variants) {
|
|
330
|
-
const trigger = await canvas.findByRole('button', { name: new RegExp(variant, 'i') });
|
|
331
|
-
|
|
332
|
-
// Open menu
|
|
333
|
-
await userEvent.click(trigger);
|
|
334
|
-
|
|
335
|
-
// Verify menu appears with correct styling
|
|
336
|
-
await waitFor(() => {
|
|
337
|
-
const menu = document.querySelector('[role="menu"]');
|
|
338
|
-
expect(menu).toBeInTheDocument();
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// Close menu
|
|
342
|
-
await userEvent.keyboard('{Escape}');
|
|
343
|
-
|
|
344
|
-
// Wait for menu to close before testing next variant
|
|
345
|
-
await waitFor(() => {
|
|
346
|
-
const menu = document.querySelector('[role="menu"]');
|
|
347
|
-
expect(menu).not.toBeInTheDocument();
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
},
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
// 7. Visual States Test (hoverless, keyboard-driven)
|
|
354
|
-
export const VisualStates: Story = {
|
|
355
|
-
args: {
|
|
356
|
-
items: itemsWithDisabled,
|
|
357
|
-
trigger: <Button variant="outline">States Menu</Button>,
|
|
358
|
-
variant: 'default',
|
|
359
|
-
size: 'md',
|
|
360
|
-
onOpen: fn(),
|
|
361
|
-
onClose: fn(),
|
|
362
|
-
},
|
|
363
|
-
play: async ({ canvasElement }) => {
|
|
364
|
-
const canvas = within(canvasElement);
|
|
365
|
-
|
|
366
|
-
// --- Open the menu ---
|
|
367
|
-
const trigger = await canvas.findByRole('button', { name: /states menu/i });
|
|
368
|
-
await userEvent.click(trigger);
|
|
369
|
-
|
|
370
|
-
await waitFor(() => expect(document.querySelector('[role="menu"]')).toBeInTheDocument());
|
|
371
|
-
|
|
372
|
-
// --- Disabled item assertions ---
|
|
373
|
-
const disabledLi = await within(document.body).findByRole('menuitem', {
|
|
374
|
-
name: (n) => n.trim() === 'Disabled',
|
|
375
|
-
});
|
|
376
|
-
expect(disabledLi).toHaveAttribute('aria-disabled', 'true');
|
|
377
|
-
expect(window.getComputedStyle(disabledLi).pointerEvents).toBe('none');
|
|
378
|
-
|
|
379
|
-
// Even if we bypass pointer checks, the handler must not be called
|
|
380
|
-
const u = userEvent.setup({ pointerEventsCheck: 0 });
|
|
381
|
-
await u.click(disabledLi);
|
|
382
|
-
expect(itemsWithDisabled[1].onClick).not.toHaveBeenCalled();
|
|
383
|
-
|
|
384
|
-
// --- Enabled item assertions ---
|
|
385
|
-
const enabledLi = await within(document.body).findByRole('menuitem', {
|
|
386
|
-
name: (n) => n.trim() === 'Enabled', // disambiguates from "Also Enabled"
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
// The click below is what this story is about; it does not need the item
|
|
390
|
-
// pre-focused, and hover CSS isn’t reliable in JSDOM either way.
|
|
391
|
-
// Click should invoke the spy
|
|
392
|
-
await userEvent.click(enabledLi);
|
|
393
|
-
await waitFor(() => expect(itemsWithDisabled[0].onClick).toHaveBeenCalled());
|
|
394
|
-
},
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
// 8. Performance Test
|
|
398
|
-
export const Performance: Story = {
|
|
399
|
-
args: {
|
|
400
|
-
items: Array.from({ length: 50 }, (_, i) => ({
|
|
401
|
-
id: `item-${i}`,
|
|
402
|
-
label: `Option ${i + 1}`,
|
|
403
|
-
icon: i % 3 === 0 ? <Check fontSize="small" /> : undefined,
|
|
404
|
-
onClick: fn(),
|
|
405
|
-
})),
|
|
406
|
-
trigger: <Button variant="outline">Large Menu</Button>,
|
|
407
|
-
variant: 'default',
|
|
408
|
-
size: 'md',
|
|
409
|
-
maxHeight: 400,
|
|
410
|
-
onOpen: fn(),
|
|
411
|
-
onClose: fn(),
|
|
412
|
-
},
|
|
413
|
-
play: async ({ canvasElement }) => {
|
|
414
|
-
const canvas = within(canvasElement);
|
|
415
|
-
const trigger = await canvas.findByRole('button', { name: /large menu/i });
|
|
416
|
-
|
|
417
|
-
await userEvent.click(trigger);
|
|
418
|
-
|
|
419
|
-
// waitFor already bounds how long the menu may take to appear. Timing the
|
|
420
|
-
// open with a wall-clock budget on top of that measures the runner's load,
|
|
421
|
-
// not the component — the "allow headless/VM jitter" note it carried was the
|
|
422
|
-
// tell — so the budget assertion is gone and the open itself is the check.
|
|
423
|
-
await waitFor(() => {
|
|
424
|
-
expect(document.querySelector('[role="menu"]')).toBeInTheDocument();
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
// Grab the UL (role="menu") and its Paper container (actual scroll container)
|
|
428
|
-
const menu = document.querySelector('[role="menu"]') as HTMLElement;
|
|
429
|
-
const paper =
|
|
430
|
-
(menu.closest('.MuiPaper-root') as HTMLElement | null) ||
|
|
431
|
-
// Fallback: sometimes the list IS the paper in custom wrappers
|
|
432
|
-
(menu.parentElement as HTMLElement | null);
|
|
433
|
-
|
|
434
|
-
// Assert render + either overflow OR explicit max height on Paper
|
|
435
|
-
if (paper) {
|
|
436
|
-
const { scrollHeight, clientHeight } = paper;
|
|
437
|
-
const paperStyles = window.getComputedStyle(paper);
|
|
438
|
-
const maxH = parseFloat(paperStyles.maxHeight || '0');
|
|
439
|
-
|
|
440
|
-
// Either we have real overflow, or we applied a maxHeight (which is what we want)
|
|
441
|
-
expect(scrollHeight > clientHeight || maxH > 0).toBeTruthy();
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// All items rendered
|
|
445
|
-
const menuItems = document.querySelectorAll('[role="menuitem"]');
|
|
446
|
-
expect(menuItems.length).toBe(50);
|
|
447
|
-
},
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
// 9. Edge Cases Test
|
|
451
|
-
export const EdgeCases: Story = {
|
|
452
|
-
render: () => {
|
|
453
|
-
const emptyItems: DropdownMenuItem[] = [];
|
|
454
|
-
const singleItem = [{ id: '1', label: 'Only Item', onClick: fn() }];
|
|
455
|
-
const veryLongLabel = [
|
|
456
|
-
{
|
|
457
|
-
id: '1',
|
|
458
|
-
label:
|
|
459
|
-
'This is a very long menu item label that should wrap or truncate appropriately without breaking the layout',
|
|
460
|
-
onClick: fn(),
|
|
461
|
-
},
|
|
462
|
-
];
|
|
463
|
-
|
|
464
|
-
return (
|
|
465
|
-
<Box sx={{ display: 'flex', gap: 2, flexDirection: 'column', alignItems: 'center' }}>
|
|
466
|
-
<DropdownMenu
|
|
467
|
-
items={emptyItems}
|
|
468
|
-
trigger={<Button variant="outline">Empty Menu</Button>}
|
|
469
|
-
variant="default"
|
|
470
|
-
/>
|
|
471
|
-
<DropdownMenu
|
|
472
|
-
items={singleItem}
|
|
473
|
-
trigger={<Button variant="outline">Single Item</Button>}
|
|
474
|
-
variant="default"
|
|
475
|
-
/>
|
|
476
|
-
<DropdownMenu
|
|
477
|
-
items={veryLongLabel}
|
|
478
|
-
trigger={<Button variant="outline">Long Label</Button>}
|
|
479
|
-
variant="default"
|
|
480
|
-
/>
|
|
481
|
-
</Box>
|
|
482
|
-
);
|
|
483
|
-
},
|
|
484
|
-
play: async ({ canvasElement }) => {
|
|
485
|
-
const canvas = within(canvasElement);
|
|
486
|
-
|
|
487
|
-
// Test empty menu
|
|
488
|
-
const emptyTrigger = await canvas.findByRole('button', { name: /empty menu/i });
|
|
489
|
-
await userEvent.click(emptyTrigger);
|
|
490
|
-
|
|
491
|
-
// Empty menu should still render
|
|
492
|
-
await waitFor(() => {
|
|
493
|
-
const menu = document.querySelector('[role="menu"]');
|
|
494
|
-
expect(menu).toBeInTheDocument();
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
// Close empty menu
|
|
498
|
-
await userEvent.keyboard('{Escape}');
|
|
499
|
-
|
|
500
|
-
// Test single item menu
|
|
501
|
-
const singleTrigger = await canvas.findByRole('button', { name: /single item/i });
|
|
502
|
-
await userEvent.click(singleTrigger);
|
|
503
|
-
|
|
504
|
-
await waitFor(() => {
|
|
505
|
-
const menuItems = document.querySelectorAll('[role="menuitem"]');
|
|
506
|
-
expect(menuItems).toHaveLength(1);
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
// Close single item menu
|
|
510
|
-
await userEvent.keyboard('{Escape}');
|
|
511
|
-
|
|
512
|
-
// Test long label
|
|
513
|
-
const longTrigger = await canvas.findByRole('button', { name: /long label/i });
|
|
514
|
-
await userEvent.click(longTrigger);
|
|
515
|
-
|
|
516
|
-
await waitFor(() => {
|
|
517
|
-
const menu = document.querySelector('[role="menu"]');
|
|
518
|
-
expect(menu).toBeInTheDocument();
|
|
519
|
-
|
|
520
|
-
// Check that long text doesn't break layout. Width is only asserted to be
|
|
521
|
-
// non-zero: comparing it against the window width makes the result depend
|
|
522
|
-
// on the runner's screen rather than on the menu.
|
|
523
|
-
const menuRect = menu?.getBoundingClientRect();
|
|
524
|
-
expect(menuRect?.width).toBeGreaterThan(0);
|
|
525
|
-
});
|
|
526
|
-
},
|
|
527
|
-
};
|
|
528
|
-
|
|
529
|
-
// 10. Integration Test
|
|
530
|
-
export const Integration: Story = {
|
|
531
|
-
render: () => {
|
|
532
|
-
const handleAction = fn();
|
|
533
|
-
|
|
534
|
-
const menuItems = [
|
|
535
|
-
{
|
|
536
|
-
id: '1',
|
|
537
|
-
label: 'Save',
|
|
538
|
-
icon: <Save fontSize="small" />,
|
|
539
|
-
shortcut: 'Ctrl+S',
|
|
540
|
-
onClick: () => handleAction('save'),
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
id: '2',
|
|
544
|
-
label: 'Export',
|
|
545
|
-
icon: <Share fontSize="small" />,
|
|
546
|
-
onClick: () => handleAction('export'),
|
|
547
|
-
},
|
|
548
|
-
{ id: 'divider1', type: 'divider' as const },
|
|
549
|
-
{
|
|
550
|
-
id: '3',
|
|
551
|
-
label: 'Delete',
|
|
552
|
-
icon: <Delete fontSize="small" />,
|
|
553
|
-
color: 'error' as const,
|
|
554
|
-
onClick: () => handleAction('delete'),
|
|
555
|
-
},
|
|
556
|
-
];
|
|
557
|
-
|
|
558
|
-
return (
|
|
559
|
-
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
|
560
|
-
<Button variant="solid">Primary Action</Button>
|
|
561
|
-
<DropdownMenu
|
|
562
|
-
items={menuItems as never}
|
|
563
|
-
trigger={<Button variant="outline">More Actions</Button>}
|
|
564
|
-
variant="default"
|
|
565
|
-
size="md"
|
|
566
|
-
/>
|
|
567
|
-
<Button variant="ghost">Cancel</Button>
|
|
568
|
-
</Box>
|
|
569
|
-
);
|
|
570
|
-
},
|
|
571
|
-
play: async ({ canvasElement }) => {
|
|
572
|
-
const canvas = within(canvasElement);
|
|
573
|
-
|
|
574
|
-
// Open the menu first
|
|
575
|
-
const menuTrigger = await canvas.findByRole('button', { name: /more actions/i });
|
|
576
|
-
await userEvent.click(menuTrigger);
|
|
577
|
-
|
|
578
|
-
// Wait until the menu exists
|
|
579
|
-
await waitFor(() => {
|
|
580
|
-
const menu = document.querySelector('[role="menu"]');
|
|
581
|
-
expect(menu).toBeInTheDocument();
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
const menu = document.querySelector('[role="menu"]') as HTMLElement;
|
|
585
|
-
|
|
586
|
-
// Prefer the Modal/Popover container for z-index (MUI sets it there)
|
|
587
|
-
const container =
|
|
588
|
-
(menu.closest('.MuiModal-root') as HTMLElement | null) ||
|
|
589
|
-
(menu.closest('.MuiPopover-root') as HTMLElement | null) ||
|
|
590
|
-
menu;
|
|
591
|
-
|
|
592
|
-
const zStr = window.getComputedStyle(container).zIndex;
|
|
593
|
-
const zNum = Number.parseInt(zStr ?? '', 10);
|
|
594
|
-
|
|
595
|
-
// If z-index is numeric, assert it's positive; if 'auto', fall back to top-layer proof.
|
|
596
|
-
if (!Number.isNaN(zNum)) {
|
|
597
|
-
expect(zNum).toBeGreaterThan(0);
|
|
598
|
-
} else {
|
|
599
|
-
const rect = menu.getBoundingClientRect();
|
|
600
|
-
const elAtCenter = document.elementFromPoint(
|
|
601
|
-
rect.left + rect.width / 2,
|
|
602
|
-
rect.top + Math.min(8, rect.height / 2),
|
|
603
|
-
);
|
|
604
|
-
expect(menu.contains(elAtCenter!) || elAtCenter?.closest('[role="menu"]')).toBeTruthy();
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// Optionally click an item (and close)
|
|
608
|
-
const saveItem = await within(document.body).findByText('Save');
|
|
609
|
-
await userEvent.click(saveItem);
|
|
610
|
-
await waitFor(() => {
|
|
611
|
-
expect(document.querySelector('[role="menu"]')).not.toBeInTheDocument();
|
|
612
|
-
});
|
|
613
|
-
},
|
|
614
|
-
};
|
|
615
|
-
|
|
616
|
-
// 11. Click Outside Test
|
|
617
|
-
export const ClickOutside: Story = {
|
|
618
|
-
args: {
|
|
619
|
-
items: testItems,
|
|
620
|
-
trigger: <Button variant="outline">Click Outside Test</Button>,
|
|
621
|
-
variant: 'default',
|
|
622
|
-
size: 'md',
|
|
623
|
-
onOpen: fn(),
|
|
624
|
-
onClose: fn(),
|
|
625
|
-
},
|
|
626
|
-
play: async ({ canvasElement }) => {
|
|
627
|
-
const canvas = within(canvasElement);
|
|
628
|
-
|
|
629
|
-
// Open menu
|
|
630
|
-
const trigger = await canvas.findByRole('button', { name: /click outside test/i });
|
|
631
|
-
await userEvent.click(trigger);
|
|
632
|
-
|
|
633
|
-
await waitFor(() => {
|
|
634
|
-
expect(document.querySelector('[role="menu"]')).toBeInTheDocument();
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
// --- Strategy 1: click the backdrop (best for MUI Modal/Popover) ---
|
|
638
|
-
const backdrop = document.querySelector('.MuiBackdrop-root') as HTMLElement | null;
|
|
639
|
-
if (backdrop) {
|
|
640
|
-
await userEvent.click(backdrop);
|
|
641
|
-
} else {
|
|
642
|
-
// --- Strategy 2: real outside mousedown + click on body (MUI listens on mousedown) ---
|
|
643
|
-
await userEvent.pointer({
|
|
644
|
-
target: document.body,
|
|
645
|
-
keys: '[MouseLeft]',
|
|
646
|
-
coords: { x: 1, y: 1 },
|
|
647
|
-
});
|
|
648
|
-
await userEvent.click(document.body);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// If still open (headless jitter), use keyboard fallback
|
|
652
|
-
if (document.querySelector('[role="menu"]')) {
|
|
653
|
-
await userEvent.keyboard('{Escape}');
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
await waitFor(() => {
|
|
657
|
-
expect(document.querySelector('[role="menu"]')).not.toBeInTheDocument();
|
|
658
|
-
});
|
|
659
|
-
},
|
|
660
|
-
};
|