@12-apps/ui 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -9
- package/src/button/index.ts +1 -0
- package/src/social-login-button/index.ts +1 -0
- package/src/{social-login-button.tsx → social-login-button/social-login-button.tsx} +5 -5
- package/src/user-avatar/index.ts +1 -0
- package/src/{user-avatar.tsx → user-avatar/user-avatar.tsx} +4 -4
- package/eslint.config.js +0 -89
- package/src/__tests__/data-grid.test.tsx +0 -125
- package/src/__tests__/package-wiring.test.ts +0 -41
- package/src/__tests__/setup.ts +0 -1
- package/src/__tests__/social-login-button.test.tsx +0 -186
- package/src/__tests__/user-avatar.test.tsx +0 -195
- package/src/__tests__/viewport.test.ts +0 -50
- package/src/components/command/__tests__/command.test.tsx +0 -112
- package/src/components/data-display/Alert/Alert.stories.tsx +0 -559
- package/src/components/data-display/Alert/Alert.test.stories.tsx +0 -707
- package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +0 -362
- package/src/components/data-display/AlertDialog/AlertDialog.test.stories.tsx +0 -763
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.stories.tsx +0 -230
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +0 -270
- package/src/components/data-display/Avatar/Avatar.stories.tsx +0 -721
- package/src/components/data-display/Avatar/Avatar.test.stories.tsx +0 -822
- package/src/components/data-display/Badge/Badge.stories.tsx +0 -1134
- package/src/components/data-display/Badge/Badge.test.stories.tsx +0 -1194
- package/src/components/data-display/Banner/Banner.stories.tsx +0 -528
- package/src/components/data-display/Banner/Banner.test.stories.tsx +0 -511
- package/src/components/data-display/Carousel/Carousel.stories.tsx +0 -607
- package/src/components/data-display/Carousel/Carousel.test.stories.tsx +0 -701
- package/src/components/data-display/Chart/Chart.stories.tsx +0 -568
- package/src/components/data-display/Chart/Chart.test.stories.tsx +0 -174
- package/src/components/data-display/Chart/SpecChart.stories.tsx +0 -150
- package/src/components/data-display/Chart/SpecChart.test.stories.tsx +0 -56
- package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +0 -60
- package/src/components/data-display/Chart/__tests__/chart-format.test.ts +0 -43
- package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +0 -119
- package/src/components/data-display/Chip/Chip.stories.tsx +0 -252
- package/src/components/data-display/Chip/Chip.test.stories.tsx +0 -266
- package/src/components/data-display/DataGrid/DataGrid.stories.tsx +0 -515
- package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +0 -653
- package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +0 -271
- package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +0 -134
- package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +0 -83
- package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +0 -115
- package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +0 -53
- package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +0 -114
- package/src/components/data-display/DataViews/base-card.stories.tsx +0 -136
- package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +0 -254
- package/src/components/data-display/EmptyState/EmptyState.stories.tsx +0 -282
- package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +0 -441
- package/src/components/data-display/ErrorState/ErrorState.stories.tsx +0 -110
- package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +0 -133
- package/src/components/data-display/HoverCard/HoverCard.stories.tsx +0 -650
- package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +0 -929
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.stories.tsx +0 -411
- package/src/components/data-display/LazyImage/LazyImage.stories.tsx +0 -387
- package/src/components/data-display/Lightbox/Lightbox.stories.tsx +0 -315
- package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +0 -598
- package/src/components/data-display/LoadingState/LoadingState.stories.tsx +0 -124
- package/src/components/data-display/LoadingState/LoadingState.test.stories.tsx +0 -99
- package/src/components/data-display/MapPreview/MapPreview.stories.tsx +0 -458
- package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +0 -394
- package/src/components/data-display/Popover/Popover.stories.tsx +0 -295
- package/src/components/data-display/Popover/Popover.test.stories.tsx +0 -1162
- package/src/components/data-display/Progress/Progress.stories.tsx +0 -481
- package/src/components/data-display/Progress/Progress.test.stories.tsx +0 -2070
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +0 -160
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.test.stories.tsx +0 -190
- package/src/components/data-display/Sheet/Sheet.stories.tsx +0 -883
- package/src/components/data-display/Sheet/Sheet.test.stories.tsx +0 -1105
- package/src/components/data-display/StatCard/StatCard.stories.tsx +0 -167
- package/src/components/data-display/StatCard/StatCard.test.stories.tsx +0 -240
- package/src/components/data-display/Stepper/Stepper.stories.tsx +0 -540
- package/src/components/data-display/Stepper/Stepper.test.stories.tsx +0 -600
- package/src/components/data-display/Table/Table.stories.tsx +0 -803
- package/src/components/data-display/Table/Table.test.stories.tsx +0 -369
- package/src/components/data-display/Timeline/Timeline.stories.tsx +0 -442
- package/src/components/data-display/Timeline/Timeline.test.stories.tsx +0 -930
- package/src/components/data-display/TimingDiagram/TimingDiagram.stories.tsx +0 -475
- package/src/components/data-display/TimingDiagram/TimingDiagram.test.stories.tsx +0 -734
- package/src/components/data-display/Tooltip/Tooltip.stories.tsx +0 -468
- package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +0 -1154
- package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +0 -146
- package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +0 -114
- package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +0 -288
- package/src/components/feedback/Dialog/Dialog.stories.tsx +0 -833
- package/src/components/feedback/Dialog/Dialog.test.stories.tsx +0 -964
- package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +0 -66
- package/src/components/feedback/Modal/Modal.stories.tsx +0 -765
- package/src/components/feedback/Modal/Modal.test.stories.tsx +0 -251
- package/src/components/feedback/Sonner/Sonner.stories.tsx +0 -698
- package/src/components/feedback/Sonner/Sonner.test.stories.tsx +0 -896
- package/src/components/feedback/StackedModal/StackedModal.stories.tsx +0 -1783
- package/src/components/feedback/StackedModal/StackedModal.test.stories.tsx +0 -973
- package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +0 -104
- package/src/components/feedback/Toast/Toast.stories.tsx +0 -548
- package/src/components/feedback/Toast/Toast.test.stories.tsx +0 -834
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.stories.tsx +0 -691
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +0 -634
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +0 -665
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +0 -727
- package/src/components/form/Autocomplete/Autocomplete.stories.tsx +0 -715
- package/src/components/form/Autocomplete/Autocomplete.test.stories.tsx +0 -825
- package/src/components/form/Button/Button.stories.tsx +0 -365
- package/src/components/form/Button/Button.test.stories.tsx +0 -607
- package/src/components/form/Calendar/Calendar.stories.tsx +0 -349
- package/src/components/form/Calendar/Calendar.test.stories.tsx +0 -464
- package/src/components/form/CepField/CepField.stories.tsx +0 -138
- package/src/components/form/CepField/CepField.test.stories.tsx +0 -281
- package/src/components/form/Checkbox/Checkbox.stories.tsx +0 -265
- package/src/components/form/Checkbox/Checkbox.test.stories.tsx +0 -181
- package/src/components/form/CodeEditor/CodeEditor.stories.tsx +0 -871
- package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +0 -574
- package/src/components/form/Command/Command.stories.tsx +0 -580
- package/src/components/form/Command/Command.test.stories.tsx +0 -215
- package/src/components/form/CreatableSelect/CreatableSelect.stories.tsx +0 -112
- package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +0 -134
- package/src/components/form/Form/Form.stories.tsx +0 -687
- package/src/components/form/Form/Form.test.stories.tsx +0 -601
- package/src/components/form/Input/Input.stories.tsx +0 -260
- package/src/components/form/Input/Input.test.stories.tsx +0 -1191
- package/src/components/form/InputOTP/InputOTP.stories.tsx +0 -774
- package/src/components/form/InputOTP/InputOTP.test.stories.tsx +0 -588
- package/src/components/form/Label/Label.stories.tsx +0 -473
- package/src/components/form/Label/Label.test.stories.tsx +0 -915
- package/src/components/form/Menubar/Menubar.stories.tsx +0 -472
- package/src/components/form/Menubar/Menubar.test.stories.tsx +0 -584
- package/src/components/form/PasswordStrength/PasswordStrength.stories.tsx +0 -562
- package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +0 -327
- package/src/components/form/PhoneInput/PhoneInput.stories.tsx +0 -495
- package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +0 -666
- package/src/components/form/RadioGroup/RadioGroup.stories.tsx +0 -518
- package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +0 -685
- package/src/components/form/RichTextEditor/RichTextEditor.stories.tsx +0 -261
- package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +0 -491
- package/src/components/form/SearchPalette/SearchPalette.stories.tsx +0 -135
- package/src/components/form/Select/Select.stories.tsx +0 -364
- package/src/components/form/Select/Select.test.stories.tsx +0 -985
- package/src/components/form/Slider/Slider.stories.tsx +0 -418
- package/src/components/form/Slider/Slider.test.stories.tsx +0 -1042
- package/src/components/form/Switch/Switch.stories.tsx +0 -697
- package/src/components/form/Switch/Switch.test.stories.tsx +0 -597
- package/src/components/form/Textarea/Textarea.stories.tsx +0 -473
- package/src/components/form/Textarea/Textarea.test.stories.tsx +0 -702
- package/src/components/form/Toggle/Toggle.stories.tsx +0 -638
- package/src/components/form/Toggle/Toggle.test.stories.tsx +0 -1159
- package/src/components/form/ToggleGroup/ToggleGroup.stories.tsx +0 -617
- package/src/components/form/ToggleGroup/ToggleGroup.test.stories.tsx +0 -915
- package/src/components/form/UploadButton/UploadButton.stories.tsx +0 -367
- package/src/components/form/UploadButton/UploadButton.test.stories.tsx +0 -573
- package/src/components/form/total-form/total-form.test.tsx +0 -214
- package/src/components/layout/Accordion/Accordion.stories.tsx +0 -694
- package/src/components/layout/Accordion/Accordion.test.stories.tsx +0 -1344
- package/src/components/layout/Card/Card.stories.tsx +0 -591
- package/src/components/layout/Card/Card.test.stories.tsx +0 -1134
- package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +0 -92
- package/src/components/layout/Collapsible/Collapsible.stories.tsx +0 -180
- package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +0 -308
- package/src/components/layout/Container/Container.stories.tsx +0 -557
- package/src/components/layout/Container/Container.test.stories.tsx +0 -745
- package/src/components/layout/ContentToolbar/ContentToolbar.stories.tsx +0 -129
- package/src/components/layout/ContentToolbar/ContentToolbar.test.stories.tsx +0 -251
- package/src/components/layout/ContentToolbar/__tests__/ColumnsMenu.test.tsx +0 -39
- package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +0 -131
- package/src/components/layout/ContentToolbar/__tests__/SavedViewsMenu.test.tsx +0 -99
- package/src/components/layout/Dashboard/Dashboard.stories.tsx +0 -104
- package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +0 -190
- package/src/components/layout/Drawer/Drawer.stories.tsx +0 -658
- package/src/components/layout/Drawer/Drawer.test.stories.tsx +0 -1343
- package/src/components/layout/Resizable/Resizable.stories.tsx +0 -1039
- package/src/components/layout/Resizable/Resizable.test.stories.tsx +0 -754
- package/src/components/layout/ScrollArea/ScrollArea.stories.tsx +0 -651
- package/src/components/layout/ScrollArea/ScrollArea.test.stories.tsx +0 -619
- package/src/components/layout/Separator/Separator.stories.tsx +0 -563
- package/src/components/layout/Separator/Separator.test.stories.tsx +0 -518
- package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +0 -192
- package/src/components/layout/SettingsLayout/SettingsLayout.test.stories.tsx +0 -175
- package/src/components/layout/Sidebar/Sidebar.stories.tsx +0 -609
- package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +0 -1324
- package/src/components/layout/Skeleton/Skeleton.stories.tsx +0 -689
- package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +0 -521
- package/src/components/layout/Spacer/Spacer.stories.tsx +0 -502
- package/src/components/layout/Spacer/Spacer.test.stories.tsx +0 -474
- package/src/components/layout/TableFilter/TableFilter.stories.tsx +0 -82
- package/src/components/layout/TableFilter/TableFilter.test.stories.tsx +0 -87
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +0 -638
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +0 -1008
- package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +0 -386
- package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +0 -435
- package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +0 -614
- package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +0 -845
- package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +0 -578
- package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +0 -660
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +0 -516
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +0 -935
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +0 -501
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +0 -453
- package/src/components/navigation/Pagination/Pagination.stories.tsx +0 -602
- package/src/components/navigation/Pagination/Pagination.test.stories.tsx +0 -520
- package/src/components/navigation/Tabs/Tabs.stories.tsx +0 -1004
- package/src/components/navigation/Tabs/Tabs.test.stories.tsx +0 -1225
- package/src/components/typography/Blockquote/Blockquote.stories.tsx +0 -359
- package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +0 -432
- package/src/components/typography/Code/Code.stories.tsx +0 -686
- package/src/components/typography/Code/Code.test.stories.tsx +0 -703
- package/src/components/typography/Heading/Heading.stories.tsx +0 -451
- package/src/components/typography/Heading/Heading.test.stories.tsx +0 -830
- package/src/components/typography/Paragraph/Paragraph.stories.tsx +0 -280
- package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +0 -231
- package/src/components/typography/Text/Text.stories.tsx +0 -359
- package/src/components/typography/Text/Text.test.stories.tsx +0 -759
- package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +0 -945
- package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +0 -603
- package/src/components/utility/AspectRatio/AspectRatio.stories.tsx +0 -414
- package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +0 -859
- package/src/components/utility/InfiniteScroll/InfiniteScroll.stories.tsx +0 -620
- package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +0 -361
- package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +0 -118
- package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +0 -83
- package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +0 -65
- package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +0 -271
- package/src/components/utility/Portal/Portal.stories.tsx +0 -340
- package/src/components/utility/Portal/Portal.test.stories.tsx +0 -2086
- package/src/components/utility/Transition/Transition.stories.tsx +0 -493
- package/src/components/utility/Transition/Transition.test.stories.tsx +0 -854
- package/src/components/utility/VirtualList/VirtualList.stories.tsx +0 -1073
- package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +0 -1085
- package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +0 -431
- package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +0 -364
- /package/src/{button.tsx → button/button.tsx} +0 -0
|
@@ -1,1324 +0,0 @@
|
|
|
1
|
-
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
|
2
|
-
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
3
|
-
import DashboardIcon from '@mui/icons-material/Dashboard';
|
|
4
|
-
import HomeIcon from '@mui/icons-material/Home';
|
|
5
|
-
import InboxIcon from '@mui/icons-material/Inbox';
|
|
6
|
-
import MenuIcon from '@mui/icons-material/Menu';
|
|
7
|
-
import PersonIcon from '@mui/icons-material/Person';
|
|
8
|
-
import SendIcon from '@mui/icons-material/Send';
|
|
9
|
-
import SettingsIcon from '@mui/icons-material/Settings';
|
|
10
|
-
import StarIcon from '@mui/icons-material/Star';
|
|
11
|
-
import {
|
|
12
|
-
Avatar,
|
|
13
|
-
Box,
|
|
14
|
-
Button,
|
|
15
|
-
IconButton,
|
|
16
|
-
List,
|
|
17
|
-
ListItemButton,
|
|
18
|
-
ListItemIcon,
|
|
19
|
-
ListItemText,
|
|
20
|
-
Typography,
|
|
21
|
-
} from '@mui/material';
|
|
22
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
23
|
-
import React, { useState } from 'react';
|
|
24
|
-
import { expect, fn,userEvent, waitFor, within } from 'storybook/test';
|
|
25
|
-
|
|
26
|
-
import { Sidebar, SidebarContent, SidebarFooter,SidebarHeader } from './Sidebar';
|
|
27
|
-
|
|
28
|
-
const meta: Meta<typeof Sidebar> = {
|
|
29
|
-
title: 'Layout/Sidebar/Tests',
|
|
30
|
-
component: Sidebar,
|
|
31
|
-
parameters: {
|
|
32
|
-
layout: 'fullscreen',
|
|
33
|
-
chromatic: { disableSnapshot: false },
|
|
34
|
-
docs: {
|
|
35
|
-
description: {
|
|
36
|
-
component:
|
|
37
|
-
'Comprehensive test suite for the Sidebar component covering interaction, accessibility, visual states, and performance.',
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
tags: ['autodocs', 'test', 'component:Sidebar'],
|
|
42
|
-
argTypes: {
|
|
43
|
-
variant: {
|
|
44
|
-
control: { type: 'select' },
|
|
45
|
-
options: ['fixed', 'collapsible', 'floating', 'glass'],
|
|
46
|
-
},
|
|
47
|
-
open: {
|
|
48
|
-
control: { type: 'boolean' },
|
|
49
|
-
},
|
|
50
|
-
width: {
|
|
51
|
-
control: { type: 'number' },
|
|
52
|
-
},
|
|
53
|
-
collapsedWidth: {
|
|
54
|
-
control: { type: 'number' },
|
|
55
|
-
},
|
|
56
|
-
position: {
|
|
57
|
-
control: { type: 'select' },
|
|
58
|
-
options: ['left', 'right'],
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
export default meta;
|
|
63
|
-
export type Story = StoryObj<typeof meta>;
|
|
64
|
-
|
|
65
|
-
const navigationItems = [
|
|
66
|
-
{ icon: <HomeIcon />, label: 'Home', 'data-testid': 'nav-home' },
|
|
67
|
-
{ icon: <DashboardIcon />, label: 'Dashboard', 'data-testid': 'nav-dashboard' },
|
|
68
|
-
{ icon: <InboxIcon />, label: 'Inbox', 'data-testid': 'nav-inbox' },
|
|
69
|
-
{ icon: <StarIcon />, label: 'Favorites', 'data-testid': 'nav-favorites' },
|
|
70
|
-
{ icon: <SendIcon />, label: 'Sent', 'data-testid': 'nav-sent' },
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
// Basic Interaction Tests
|
|
74
|
-
const BasicInteractionComponent = () => {
|
|
75
|
-
const [open, setOpen] = useState(true);
|
|
76
|
-
const mockOnToggle = fn();
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
|
|
80
|
-
<Sidebar variant="fixed" open={open} onToggle={mockOnToggle} data-testid="sidebar">
|
|
81
|
-
<SidebarHeader data-testid="sidebar-header">
|
|
82
|
-
<Typography variant="h6">Test Sidebar</Typography>
|
|
83
|
-
</SidebarHeader>
|
|
84
|
-
|
|
85
|
-
<SidebarContent data-testid="sidebar-content">
|
|
86
|
-
<List>
|
|
87
|
-
{navigationItems.map((item, index) => (
|
|
88
|
-
<ListItemButton key={index} data-testid={item['data-testid']}>
|
|
89
|
-
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
90
|
-
<ListItemText primary={item.label} />
|
|
91
|
-
</ListItemButton>
|
|
92
|
-
))}
|
|
93
|
-
</List>
|
|
94
|
-
</SidebarContent>
|
|
95
|
-
|
|
96
|
-
<SidebarFooter data-testid="sidebar-footer">
|
|
97
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
98
|
-
<Avatar sx={{ width: 32, height: 32 }} data-testid="user-avatar">
|
|
99
|
-
U
|
|
100
|
-
</Avatar>
|
|
101
|
-
<Typography variant="body2">Test User</Typography>
|
|
102
|
-
</Box>
|
|
103
|
-
</SidebarFooter>
|
|
104
|
-
</Sidebar>
|
|
105
|
-
|
|
106
|
-
<Box sx={{ flex: 1, p: 3 }}>
|
|
107
|
-
<Typography variant="h5" data-testid="main-content">
|
|
108
|
-
Main Content
|
|
109
|
-
</Typography>
|
|
110
|
-
<Button onClick={() => setOpen(!open)} data-testid="toggle-button">
|
|
111
|
-
Toggle Sidebar
|
|
112
|
-
</Button>
|
|
113
|
-
</Box>
|
|
114
|
-
</Box>
|
|
115
|
-
);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
export const BasicInteraction: Story = {
|
|
119
|
-
render: () => <BasicInteractionComponent />,
|
|
120
|
-
play: async ({ canvasElement }) => {
|
|
121
|
-
const canvas = within(canvasElement);
|
|
122
|
-
|
|
123
|
-
// Test basic rendering
|
|
124
|
-
await expect(canvas.getByTestId('sidebar')).toBeInTheDocument();
|
|
125
|
-
await expect(canvas.getByTestId('sidebar-header')).toBeInTheDocument();
|
|
126
|
-
await expect(canvas.getByTestId('sidebar-content')).toBeInTheDocument();
|
|
127
|
-
await expect(canvas.getByTestId('sidebar-footer')).toBeInTheDocument();
|
|
128
|
-
await expect(canvas.getByTestId('main-content')).toBeInTheDocument();
|
|
129
|
-
|
|
130
|
-
// Test navigation items are rendered
|
|
131
|
-
await expect(canvas.getByTestId('nav-home')).toBeInTheDocument();
|
|
132
|
-
await expect(canvas.getByTestId('nav-dashboard')).toBeInTheDocument();
|
|
133
|
-
await expect(canvas.getByTestId('nav-inbox')).toBeInTheDocument();
|
|
134
|
-
await expect(canvas.getByTestId('nav-favorites')).toBeInTheDocument();
|
|
135
|
-
await expect(canvas.getByTestId('nav-sent')).toBeInTheDocument();
|
|
136
|
-
|
|
137
|
-
// Test user avatar is rendered
|
|
138
|
-
await expect(canvas.getByTestId('user-avatar')).toBeInTheDocument();
|
|
139
|
-
|
|
140
|
-
// Test sidebar has expected width
|
|
141
|
-
const sidebar = canvas.getByTestId('sidebar');
|
|
142
|
-
const sidebarStyles = window.getComputedStyle(sidebar);
|
|
143
|
-
await expect(sidebarStyles.width).toBe('280px');
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
// Form Interaction Test
|
|
148
|
-
const FormInteractionComponent = () => {
|
|
149
|
-
const [selectedItem, setSelectedItem] = useState('');
|
|
150
|
-
|
|
151
|
-
return (
|
|
152
|
-
<Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
|
|
153
|
-
<Sidebar variant="fixed" data-testid="form-sidebar">
|
|
154
|
-
<SidebarHeader>
|
|
155
|
-
<Typography variant="h6">Form Sidebar</Typography>
|
|
156
|
-
</SidebarHeader>
|
|
157
|
-
|
|
158
|
-
<SidebarContent>
|
|
159
|
-
<Box sx={{ p: 2 }}>
|
|
160
|
-
<Typography variant="body1" gutterBottom>
|
|
161
|
-
Selected: {selectedItem}
|
|
162
|
-
</Typography>
|
|
163
|
-
<List>
|
|
164
|
-
{navigationItems.map((item, index) => (
|
|
165
|
-
<ListItemButton
|
|
166
|
-
key={index}
|
|
167
|
-
data-testid={`form-${item['data-testid']}`}
|
|
168
|
-
onClick={() => setSelectedItem(item.label)}
|
|
169
|
-
selected={selectedItem === item.label}
|
|
170
|
-
>
|
|
171
|
-
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
172
|
-
<ListItemText primary={item.label} />
|
|
173
|
-
</ListItemButton>
|
|
174
|
-
))}
|
|
175
|
-
</List>
|
|
176
|
-
</Box>
|
|
177
|
-
</SidebarContent>
|
|
178
|
-
</Sidebar>
|
|
179
|
-
|
|
180
|
-
<Box sx={{ flex: 1, p: 3 }}>
|
|
181
|
-
<Typography variant="h5">Form Content</Typography>
|
|
182
|
-
<Typography data-testid="selected-display">
|
|
183
|
-
Current selection: {selectedItem || 'None'}
|
|
184
|
-
</Typography>
|
|
185
|
-
</Box>
|
|
186
|
-
</Box>
|
|
187
|
-
);
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
export const FormInteraction: Story = {
|
|
191
|
-
render: () => <FormInteractionComponent />,
|
|
192
|
-
play: async ({ canvasElement }) => {
|
|
193
|
-
const canvas = within(canvasElement);
|
|
194
|
-
|
|
195
|
-
// Initially no item selected
|
|
196
|
-
await expect(canvas.getByTestId('selected-display')).toHaveTextContent(
|
|
197
|
-
'Current selection: None',
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
// Click on Home item
|
|
201
|
-
await userEvent.click(canvas.getByTestId('form-nav-home'));
|
|
202
|
-
await waitFor(() => {
|
|
203
|
-
expect(canvas.getByTestId('selected-display')).toHaveTextContent('Current selection: Home');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// Click on Dashboard item
|
|
207
|
-
await userEvent.click(canvas.getByTestId('form-nav-dashboard'));
|
|
208
|
-
await waitFor(() => {
|
|
209
|
-
expect(canvas.getByTestId('selected-display')).toHaveTextContent(
|
|
210
|
-
'Current selection: Dashboard',
|
|
211
|
-
);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Click on Inbox item
|
|
215
|
-
await userEvent.click(canvas.getByTestId('form-nav-inbox'));
|
|
216
|
-
await waitFor(() => {
|
|
217
|
-
expect(canvas.getByTestId('selected-display')).toHaveTextContent('Current selection: Inbox');
|
|
218
|
-
});
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
// Keyboard Navigation Test
|
|
223
|
-
export const KeyboardNavigation: Story = {
|
|
224
|
-
render: () => (
|
|
225
|
-
<Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
|
|
226
|
-
<Sidebar variant="fixed" data-testid="keyboard-sidebar">
|
|
227
|
-
<SidebarHeader>
|
|
228
|
-
<Typography variant="h6">Keyboard Navigation</Typography>
|
|
229
|
-
</SidebarHeader>
|
|
230
|
-
|
|
231
|
-
<SidebarContent>
|
|
232
|
-
<List>
|
|
233
|
-
{navigationItems.map((item, index) => (
|
|
234
|
-
<ListItemButton
|
|
235
|
-
key={index}
|
|
236
|
-
data-testid={`keyboard-${item['data-testid']}`}
|
|
237
|
-
tabIndex={0}
|
|
238
|
-
role="button"
|
|
239
|
-
aria-label={`Navigate to ${item.label}`}
|
|
240
|
-
>
|
|
241
|
-
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
242
|
-
<ListItemText primary={item.label} />
|
|
243
|
-
</ListItemButton>
|
|
244
|
-
))}
|
|
245
|
-
</List>
|
|
246
|
-
</SidebarContent>
|
|
247
|
-
|
|
248
|
-
<SidebarFooter>
|
|
249
|
-
<Button data-testid="footer-button" tabIndex={0}>
|
|
250
|
-
Settings
|
|
251
|
-
</Button>
|
|
252
|
-
</SidebarFooter>
|
|
253
|
-
</Sidebar>
|
|
254
|
-
|
|
255
|
-
<Box sx={{ flex: 1, p: 3 }}>
|
|
256
|
-
<Typography variant="h5">Main Content</Typography>
|
|
257
|
-
<Button data-testid="main-button" tabIndex={0}>
|
|
258
|
-
Main Action
|
|
259
|
-
</Button>
|
|
260
|
-
</Box>
|
|
261
|
-
</Box>
|
|
262
|
-
),
|
|
263
|
-
play: async ({ canvasElement }) => {
|
|
264
|
-
const canvas = within(canvasElement);
|
|
265
|
-
|
|
266
|
-
// Test Tab navigation through sidebar items. The first nav item is the
|
|
267
|
-
// first focusable node in the story, so tabbing in from the body reaches it
|
|
268
|
-
// — the old .focus() call asserted only that focus() works.
|
|
269
|
-
const firstNavItem = canvas.getByTestId('keyboard-nav-home');
|
|
270
|
-
await userEvent.tab();
|
|
271
|
-
await waitFor(() => expect(firstNavItem).toHaveFocus());
|
|
272
|
-
|
|
273
|
-
// Enter key activation, checked here while focus is already on the item
|
|
274
|
-
// rather than by re-focusing it at the end of the journey.
|
|
275
|
-
await userEvent.keyboard('{Enter}');
|
|
276
|
-
await expect(firstNavItem).toBeInTheDocument();
|
|
277
|
-
|
|
278
|
-
// Tab to next item
|
|
279
|
-
await userEvent.tab();
|
|
280
|
-
await waitFor(() => expect(canvas.getByTestId('keyboard-nav-dashboard')).toHaveFocus());
|
|
281
|
-
|
|
282
|
-
// Tab to next item
|
|
283
|
-
await userEvent.tab();
|
|
284
|
-
await waitFor(() => expect(canvas.getByTestId('keyboard-nav-inbox')).toHaveFocus());
|
|
285
|
-
|
|
286
|
-
// Tab to footer button
|
|
287
|
-
await userEvent.tab();
|
|
288
|
-
await userEvent.tab();
|
|
289
|
-
await userEvent.tab();
|
|
290
|
-
await waitFor(() => expect(canvas.getByTestId('footer-button')).toHaveFocus());
|
|
291
|
-
|
|
292
|
-
// Tab to main content
|
|
293
|
-
await userEvent.tab();
|
|
294
|
-
await waitFor(() => expect(canvas.getByTestId('main-button')).toHaveFocus());
|
|
295
|
-
},
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
// Screen Reader Accessibility Test
|
|
299
|
-
export const ScreenReader: Story = {
|
|
300
|
-
render: () => (
|
|
301
|
-
<Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
|
|
302
|
-
<Sidebar
|
|
303
|
-
variant="fixed"
|
|
304
|
-
data-testid="screen-reader-sidebar"
|
|
305
|
-
role="navigation"
|
|
306
|
-
aria-label="Main navigation sidebar"
|
|
307
|
-
>
|
|
308
|
-
<SidebarHeader>
|
|
309
|
-
<Typography variant="h6" role="banner">
|
|
310
|
-
Screen Reader Test
|
|
311
|
-
</Typography>
|
|
312
|
-
</SidebarHeader>
|
|
313
|
-
|
|
314
|
-
<SidebarContent>
|
|
315
|
-
<nav role="navigation" aria-label="Primary navigation">
|
|
316
|
-
<List role="menu">
|
|
317
|
-
{navigationItems.map((item, index) => (
|
|
318
|
-
<ListItemButton
|
|
319
|
-
key={index}
|
|
320
|
-
data-testid={`sr-${item['data-testid']}`}
|
|
321
|
-
role="menuitem"
|
|
322
|
-
aria-label={`Navigate to ${item.label} section`}
|
|
323
|
-
tabIndex={0}
|
|
324
|
-
>
|
|
325
|
-
<ListItemIcon aria-hidden="true">{item.icon}</ListItemIcon>
|
|
326
|
-
<ListItemText primary={item.label} />
|
|
327
|
-
</ListItemButton>
|
|
328
|
-
))}
|
|
329
|
-
</List>
|
|
330
|
-
</nav>
|
|
331
|
-
</SidebarContent>
|
|
332
|
-
|
|
333
|
-
<SidebarFooter>
|
|
334
|
-
<Box
|
|
335
|
-
role="contentinfo"
|
|
336
|
-
aria-label="User information"
|
|
337
|
-
sx={{ display: 'flex', alignItems: 'center', gap: 1 }}
|
|
338
|
-
>
|
|
339
|
-
<Avatar sx={{ width: 32, height: 32 }} aria-label="User avatar" data-testid="sr-avatar">
|
|
340
|
-
U
|
|
341
|
-
</Avatar>
|
|
342
|
-
<Box>
|
|
343
|
-
<Typography variant="body2">Screen Reader User</Typography>
|
|
344
|
-
<Typography variant="caption" color="text.secondary">
|
|
345
|
-
sr-user@example.com
|
|
346
|
-
</Typography>
|
|
347
|
-
</Box>
|
|
348
|
-
</Box>
|
|
349
|
-
</SidebarFooter>
|
|
350
|
-
</Sidebar>
|
|
351
|
-
|
|
352
|
-
<main role="main" aria-label="Main content area" style={{ flex: 1, padding: '24px' }}>
|
|
353
|
-
<Typography variant="h5">Main Content Area</Typography>
|
|
354
|
-
<Typography variant="body1">This area contains the main application content.</Typography>
|
|
355
|
-
</main>
|
|
356
|
-
</Box>
|
|
357
|
-
),
|
|
358
|
-
play: async ({ canvasElement }) => {
|
|
359
|
-
const canvas = within(canvasElement);
|
|
360
|
-
|
|
361
|
-
// Test ARIA attributes
|
|
362
|
-
const sidebar = canvas.getByTestId('screen-reader-sidebar');
|
|
363
|
-
await expect(sidebar).toHaveAttribute('role', 'navigation');
|
|
364
|
-
await expect(sidebar).toHaveAttribute('aria-label', 'Main navigation sidebar');
|
|
365
|
-
|
|
366
|
-
// Test navigation structure
|
|
367
|
-
const navigation = canvas.getByRole('navigation', { name: 'Primary navigation' });
|
|
368
|
-
await expect(navigation).toBeInTheDocument();
|
|
369
|
-
|
|
370
|
-
const menu = canvas.getByRole('menu');
|
|
371
|
-
await expect(menu).toBeInTheDocument();
|
|
372
|
-
|
|
373
|
-
// Test menu items have proper ARIA labels
|
|
374
|
-
const homeMenuItem = canvas.getByTestId('sr-nav-home');
|
|
375
|
-
await expect(homeMenuItem).toHaveAttribute('role', 'menuitem');
|
|
376
|
-
await expect(homeMenuItem).toHaveAttribute('aria-label', 'Navigate to Home section');
|
|
377
|
-
|
|
378
|
-
const dashboardMenuItem = canvas.getByTestId('sr-nav-dashboard');
|
|
379
|
-
await expect(dashboardMenuItem).toHaveAttribute('aria-label', 'Navigate to Dashboard section');
|
|
380
|
-
|
|
381
|
-
// Test avatar accessibility
|
|
382
|
-
const avatar = canvas.getByTestId('sr-avatar');
|
|
383
|
-
await expect(avatar).toHaveAttribute('aria-label', 'User avatar');
|
|
384
|
-
|
|
385
|
-
// Test main content area
|
|
386
|
-
const main = canvas.getByRole('main');
|
|
387
|
-
await expect(main).toHaveAttribute('aria-label', 'Main content area');
|
|
388
|
-
|
|
389
|
-
// Test contentinfo role
|
|
390
|
-
const userInfo = canvas.getByRole('contentinfo');
|
|
391
|
-
await expect(userInfo).toHaveAttribute('aria-label', 'User information');
|
|
392
|
-
},
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
// Focus Management Test
|
|
396
|
-
const FocusManagementComponent = () => {
|
|
397
|
-
const [sidebarVisible, setSidebarVisible] = useState(true);
|
|
398
|
-
|
|
399
|
-
return (
|
|
400
|
-
<Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
|
|
401
|
-
{sidebarVisible && (
|
|
402
|
-
<Sidebar variant="fixed" data-testid="focus-sidebar">
|
|
403
|
-
<SidebarHeader>
|
|
404
|
-
<Typography variant="h6">Focus Management</Typography>
|
|
405
|
-
<Button
|
|
406
|
-
data-testid="hide-sidebar-button"
|
|
407
|
-
onClick={() => setSidebarVisible(false)}
|
|
408
|
-
size="small"
|
|
409
|
-
>
|
|
410
|
-
Hide Sidebar
|
|
411
|
-
</Button>
|
|
412
|
-
</SidebarHeader>
|
|
413
|
-
|
|
414
|
-
<SidebarContent>
|
|
415
|
-
<List>
|
|
416
|
-
{navigationItems.slice(0, 3).map((item, index) => (
|
|
417
|
-
<ListItemButton
|
|
418
|
-
key={index}
|
|
419
|
-
data-testid={`focus-${item['data-testid']}`}
|
|
420
|
-
tabIndex={0}
|
|
421
|
-
>
|
|
422
|
-
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
423
|
-
<ListItemText primary={item.label} />
|
|
424
|
-
</ListItemButton>
|
|
425
|
-
))}
|
|
426
|
-
</List>
|
|
427
|
-
</SidebarContent>
|
|
428
|
-
|
|
429
|
-
<SidebarFooter>
|
|
430
|
-
<Button data-testid="sidebar-action" tabIndex={0}>
|
|
431
|
-
Sidebar Action
|
|
432
|
-
</Button>
|
|
433
|
-
</SidebarFooter>
|
|
434
|
-
</Sidebar>
|
|
435
|
-
)}
|
|
436
|
-
|
|
437
|
-
<Box sx={{ flex: 1, p: 3 }}>
|
|
438
|
-
<Typography variant="h5">Focus Management Test</Typography>
|
|
439
|
-
<Button
|
|
440
|
-
data-testid="show-sidebar-button"
|
|
441
|
-
onClick={() => setSidebarVisible(true)}
|
|
442
|
-
style={{ display: sidebarVisible ? 'none' : 'block' }}
|
|
443
|
-
>
|
|
444
|
-
Show Sidebar
|
|
445
|
-
</Button>
|
|
446
|
-
<Button data-testid="main-focus-button" tabIndex={0}>
|
|
447
|
-
Main Button
|
|
448
|
-
</Button>
|
|
449
|
-
</Box>
|
|
450
|
-
</Box>
|
|
451
|
-
);
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
export const FocusManagement: Story = {
|
|
455
|
-
render: () => <FocusManagementComponent />,
|
|
456
|
-
play: async ({ canvasElement }) => {
|
|
457
|
-
const canvas = within(canvasElement);
|
|
458
|
-
|
|
459
|
-
// Test initial focus on first sidebar item
|
|
460
|
-
const firstItem = canvas.getByTestId('focus-nav-home');
|
|
461
|
-
await userEvent.tab();
|
|
462
|
-
await waitFor(() => expect(firstItem).toHaveFocus());
|
|
463
|
-
|
|
464
|
-
// Test focus moves through sidebar elements
|
|
465
|
-
await userEvent.tab();
|
|
466
|
-
await waitFor(() => expect(canvas.getByTestId('focus-nav-dashboard')).toHaveFocus());
|
|
467
|
-
|
|
468
|
-
await userEvent.tab();
|
|
469
|
-
await waitFor(() => expect(canvas.getByTestId('focus-nav-inbox')).toHaveFocus());
|
|
470
|
-
|
|
471
|
-
await userEvent.tab();
|
|
472
|
-
await waitFor(() => expect(canvas.getByTestId('sidebar-action')).toHaveFocus());
|
|
473
|
-
|
|
474
|
-
// Test focus moves to main content
|
|
475
|
-
await userEvent.tab();
|
|
476
|
-
await waitFor(() => expect(canvas.getByTestId('main-focus-button')).toHaveFocus());
|
|
477
|
-
|
|
478
|
-
// Test hiding sidebar
|
|
479
|
-
const hideButton = canvas.getByTestId('hide-sidebar-button');
|
|
480
|
-
await userEvent.click(hideButton);
|
|
481
|
-
|
|
482
|
-
// Verify sidebar is hidden
|
|
483
|
-
await waitFor(() => {
|
|
484
|
-
expect(canvas.queryByTestId('focus-sidebar')).not.toBeInTheDocument();
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
// Test showing sidebar again
|
|
488
|
-
const showButton = canvas.getByTestId('show-sidebar-button');
|
|
489
|
-
await userEvent.click(showButton);
|
|
490
|
-
|
|
491
|
-
// Verify sidebar is visible again
|
|
492
|
-
await waitFor(() => {
|
|
493
|
-
expect(canvas.getByTestId('focus-sidebar')).toBeInTheDocument();
|
|
494
|
-
});
|
|
495
|
-
},
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
// Responsive Design Test
|
|
499
|
-
export const ResponsiveDesign: Story = {
|
|
500
|
-
parameters: {
|
|
501
|
-
viewport: {
|
|
502
|
-
viewports: {
|
|
503
|
-
mobile: { name: 'Mobile', styles: { width: '375px', height: '667px' } },
|
|
504
|
-
tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } },
|
|
505
|
-
desktop: { name: 'Desktop', styles: { width: '1200px', height: '800px' } },
|
|
506
|
-
},
|
|
507
|
-
},
|
|
508
|
-
},
|
|
509
|
-
render: () => (
|
|
510
|
-
<Box sx={{ display: 'flex', height: '100vh', bgcolor: 'grey.50' }}>
|
|
511
|
-
<Sidebar variant="fixed" width={280} data-testid="responsive-sidebar">
|
|
512
|
-
<SidebarHeader>
|
|
513
|
-
<Typography variant="h6" noWrap>
|
|
514
|
-
Responsive Sidebar
|
|
515
|
-
</Typography>
|
|
516
|
-
</SidebarHeader>
|
|
517
|
-
|
|
518
|
-
<SidebarContent>
|
|
519
|
-
<List>
|
|
520
|
-
{navigationItems.map((item, index) => (
|
|
521
|
-
<ListItemButton key={index} data-testid={`resp-${item['data-testid']}`}>
|
|
522
|
-
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
523
|
-
<ListItemText primary={item.label} primaryTypographyProps={{ noWrap: true }} />
|
|
524
|
-
</ListItemButton>
|
|
525
|
-
))}
|
|
526
|
-
</List>
|
|
527
|
-
</SidebarContent>
|
|
528
|
-
|
|
529
|
-
<SidebarFooter>
|
|
530
|
-
<Typography variant="caption" noWrap data-testid="responsive-footer">
|
|
531
|
-
Responsive Footer
|
|
532
|
-
</Typography>
|
|
533
|
-
</SidebarFooter>
|
|
534
|
-
</Sidebar>
|
|
535
|
-
|
|
536
|
-
<Box
|
|
537
|
-
sx={{
|
|
538
|
-
flex: 1,
|
|
539
|
-
p: { xs: 1, sm: 2, md: 3 },
|
|
540
|
-
overflow: 'auto',
|
|
541
|
-
}}
|
|
542
|
-
>
|
|
543
|
-
<Typography variant="h5" gutterBottom>
|
|
544
|
-
Responsive Content
|
|
545
|
-
</Typography>
|
|
546
|
-
<Typography variant="body1" data-testid="responsive-content">
|
|
547
|
-
This content adapts to different screen sizes. The sidebar maintains its functionality
|
|
548
|
-
across all viewport sizes.
|
|
549
|
-
</Typography>
|
|
550
|
-
</Box>
|
|
551
|
-
</Box>
|
|
552
|
-
),
|
|
553
|
-
play: async ({ canvasElement }) => {
|
|
554
|
-
const canvas = within(canvasElement);
|
|
555
|
-
|
|
556
|
-
// Test sidebar is present
|
|
557
|
-
const sidebar = canvas.getByTestId('responsive-sidebar');
|
|
558
|
-
await expect(sidebar).toBeInTheDocument();
|
|
559
|
-
|
|
560
|
-
// Test sidebar maintains structure
|
|
561
|
-
await expect(canvas.getByTestId('resp-nav-home')).toBeInTheDocument();
|
|
562
|
-
await expect(canvas.getByTestId('resp-nav-dashboard')).toBeInTheDocument();
|
|
563
|
-
await expect(canvas.getByTestId('responsive-footer')).toBeInTheDocument();
|
|
564
|
-
await expect(canvas.getByTestId('responsive-content')).toBeInTheDocument();
|
|
565
|
-
|
|
566
|
-
// Test sidebar width
|
|
567
|
-
const sidebarStyles = window.getComputedStyle(sidebar);
|
|
568
|
-
await expect(sidebarStyles.width).toBe('280px');
|
|
569
|
-
|
|
570
|
-
// Test overflow handling
|
|
571
|
-
await expect(sidebarStyles.overflow).toBe('hidden');
|
|
572
|
-
// Test that height is set (could be 100vh or computed pixels)
|
|
573
|
-
await expect(parseInt(sidebarStyles.height) > 0).toBe(true);
|
|
574
|
-
},
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
// Theme Variations Test
|
|
578
|
-
const ThemeVariationsComponent = () => {
|
|
579
|
-
const [variant, setVariant] = useState<'fixed' | 'glass' | 'floating'>('fixed');
|
|
580
|
-
|
|
581
|
-
const backgroundStyles =
|
|
582
|
-
variant === 'glass'
|
|
583
|
-
? { background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' }
|
|
584
|
-
: { bgcolor: 'grey.100' };
|
|
585
|
-
|
|
586
|
-
return (
|
|
587
|
-
<Box sx={{ ...backgroundStyles, height: '500px', position: 'relative' }}>
|
|
588
|
-
<Box sx={{ display: 'flex', height: '100%' }}>
|
|
589
|
-
<Sidebar variant={variant} data-testid="theme-sidebar">
|
|
590
|
-
<SidebarHeader>
|
|
591
|
-
<Typography variant="h6" sx={{ color: variant === 'glass' ? 'white' : 'inherit' }}>
|
|
592
|
-
{variant.charAt(0).toUpperCase() + variant.slice(1)} Theme
|
|
593
|
-
</Typography>
|
|
594
|
-
</SidebarHeader>
|
|
595
|
-
|
|
596
|
-
<SidebarContent>
|
|
597
|
-
<List>
|
|
598
|
-
{navigationItems.slice(0, 3).map((item, index) => (
|
|
599
|
-
<ListItemButton
|
|
600
|
-
key={index}
|
|
601
|
-
data-testid={`theme-${item['data-testid']}`}
|
|
602
|
-
sx={{ color: variant === 'glass' ? 'white' : 'inherit' }}
|
|
603
|
-
>
|
|
604
|
-
<ListItemIcon sx={{ color: variant === 'glass' ? 'white' : 'inherit' }}>
|
|
605
|
-
{item.icon}
|
|
606
|
-
</ListItemIcon>
|
|
607
|
-
<ListItemText primary={item.label} />
|
|
608
|
-
</ListItemButton>
|
|
609
|
-
))}
|
|
610
|
-
</List>
|
|
611
|
-
</SidebarContent>
|
|
612
|
-
|
|
613
|
-
<SidebarFooter>
|
|
614
|
-
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
|
615
|
-
{(['fixed', 'glass', 'floating'] as const).map((themeVariant) => (
|
|
616
|
-
<Button
|
|
617
|
-
key={themeVariant}
|
|
618
|
-
size="small"
|
|
619
|
-
variant={variant === themeVariant ? 'contained' : 'outlined'}
|
|
620
|
-
onClick={() => setVariant(themeVariant)}
|
|
621
|
-
data-testid={`theme-${themeVariant}-button`}
|
|
622
|
-
sx={{
|
|
623
|
-
color: variant === 'glass' ? 'white' : 'inherit',
|
|
624
|
-
borderColor: variant === 'glass' ? 'white' : 'inherit',
|
|
625
|
-
}}
|
|
626
|
-
>
|
|
627
|
-
{themeVariant}
|
|
628
|
-
</Button>
|
|
629
|
-
))}
|
|
630
|
-
</Box>
|
|
631
|
-
</SidebarFooter>
|
|
632
|
-
</Sidebar>
|
|
633
|
-
|
|
634
|
-
<Box sx={{ flex: 1, p: 3, color: variant === 'glass' ? 'white' : 'inherit' }}>
|
|
635
|
-
<Typography variant="h5" gutterBottom data-testid="theme-title">
|
|
636
|
-
{variant.charAt(0).toUpperCase() + variant.slice(1)} Variant
|
|
637
|
-
</Typography>
|
|
638
|
-
<Typography variant="body1" data-testid="theme-description">
|
|
639
|
-
Current theme variant: {variant}
|
|
640
|
-
</Typography>
|
|
641
|
-
</Box>
|
|
642
|
-
</Box>
|
|
643
|
-
</Box>
|
|
644
|
-
);
|
|
645
|
-
};
|
|
646
|
-
|
|
647
|
-
export const ThemeVariations: Story = {
|
|
648
|
-
render: () => <ThemeVariationsComponent />,
|
|
649
|
-
play: async ({ canvasElement }) => {
|
|
650
|
-
const canvas = within(canvasElement);
|
|
651
|
-
|
|
652
|
-
// Test initial fixed theme
|
|
653
|
-
await expect(canvas.getByTestId('theme-title')).toHaveTextContent('Fixed Variant');
|
|
654
|
-
await expect(canvas.getByTestId('theme-description')).toHaveTextContent(
|
|
655
|
-
'Current theme variant: fixed',
|
|
656
|
-
);
|
|
657
|
-
|
|
658
|
-
// Test switching to glass theme
|
|
659
|
-
await userEvent.click(canvas.getByTestId('theme-glass-button'));
|
|
660
|
-
await waitFor(() => {
|
|
661
|
-
expect(canvas.getByTestId('theme-title')).toHaveTextContent('Glass Variant');
|
|
662
|
-
expect(canvas.getByTestId('theme-description')).toHaveTextContent(
|
|
663
|
-
'Current theme variant: glass',
|
|
664
|
-
);
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
// Test switching to floating theme
|
|
668
|
-
await userEvent.click(canvas.getByTestId('theme-floating-button'));
|
|
669
|
-
await waitFor(() => {
|
|
670
|
-
expect(canvas.getByTestId('theme-title')).toHaveTextContent('Floating Variant');
|
|
671
|
-
expect(canvas.getByTestId('theme-description')).toHaveTextContent(
|
|
672
|
-
'Current theme variant: floating',
|
|
673
|
-
);
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
// Test switching back to fixed theme
|
|
677
|
-
await userEvent.click(canvas.getByTestId('theme-fixed-button'));
|
|
678
|
-
await waitFor(() => {
|
|
679
|
-
expect(canvas.getByTestId('theme-title')).toHaveTextContent('Fixed Variant');
|
|
680
|
-
expect(canvas.getByTestId('theme-description')).toHaveTextContent(
|
|
681
|
-
'Current theme variant: fixed',
|
|
682
|
-
);
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
// Verify sidebar maintains structure across theme changes
|
|
686
|
-
await expect(canvas.getByTestId('theme-sidebar')).toBeInTheDocument();
|
|
687
|
-
await expect(canvas.getByTestId('theme-nav-home')).toBeInTheDocument();
|
|
688
|
-
await expect(canvas.getByTestId('theme-nav-dashboard')).toBeInTheDocument();
|
|
689
|
-
await expect(canvas.getByTestId('theme-nav-inbox')).toBeInTheDocument();
|
|
690
|
-
},
|
|
691
|
-
};
|
|
692
|
-
|
|
693
|
-
// Visual States Test
|
|
694
|
-
const VisualStatesComponent = () => {
|
|
695
|
-
const [collapsed, setCollapsed] = useState(false);
|
|
696
|
-
|
|
697
|
-
return (
|
|
698
|
-
<Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50', position: 'relative' }}>
|
|
699
|
-
<Sidebar
|
|
700
|
-
variant="collapsible"
|
|
701
|
-
open={!collapsed}
|
|
702
|
-
width={280}
|
|
703
|
-
collapsedWidth={64}
|
|
704
|
-
data-testid="visual-states-sidebar"
|
|
705
|
-
>
|
|
706
|
-
<SidebarHeader>
|
|
707
|
-
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
708
|
-
<Typography variant="h6" sx={{ display: collapsed ? 'none' : 'block' }}>
|
|
709
|
-
Visual States
|
|
710
|
-
</Typography>
|
|
711
|
-
<IconButton
|
|
712
|
-
onClick={() => setCollapsed(!collapsed)}
|
|
713
|
-
data-testid="collapse-toggle"
|
|
714
|
-
size="small"
|
|
715
|
-
>
|
|
716
|
-
{collapsed ? <ChevronRightIcon /> : <ChevronLeftIcon />}
|
|
717
|
-
</IconButton>
|
|
718
|
-
</Box>
|
|
719
|
-
</SidebarHeader>
|
|
720
|
-
|
|
721
|
-
<SidebarContent>
|
|
722
|
-
<List>
|
|
723
|
-
{navigationItems.map((item, index) => (
|
|
724
|
-
<ListItemButton
|
|
725
|
-
key={index}
|
|
726
|
-
data-testid={`visual-${item['data-testid']}`}
|
|
727
|
-
sx={{
|
|
728
|
-
'&:hover': {
|
|
729
|
-
backgroundColor: 'action.hover',
|
|
730
|
-
},
|
|
731
|
-
'&:focus': {
|
|
732
|
-
backgroundColor: 'action.selected',
|
|
733
|
-
},
|
|
734
|
-
}}
|
|
735
|
-
>
|
|
736
|
-
<ListItemIcon
|
|
737
|
-
sx={{
|
|
738
|
-
minWidth: collapsed ? 40 : 56,
|
|
739
|
-
justifyContent: 'center',
|
|
740
|
-
}}
|
|
741
|
-
>
|
|
742
|
-
{item.icon}
|
|
743
|
-
</ListItemIcon>
|
|
744
|
-
{!collapsed && <ListItemText primary={item.label} />}
|
|
745
|
-
</ListItemButton>
|
|
746
|
-
))}
|
|
747
|
-
</List>
|
|
748
|
-
</SidebarContent>
|
|
749
|
-
|
|
750
|
-
<SidebarFooter>
|
|
751
|
-
{!collapsed ? (
|
|
752
|
-
<Typography variant="caption" data-testid="expanded-footer">
|
|
753
|
-
Expanded Footer
|
|
754
|
-
</Typography>
|
|
755
|
-
) : (
|
|
756
|
-
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
|
757
|
-
<Avatar sx={{ width: 24, height: 24 }} data-testid="collapsed-avatar">
|
|
758
|
-
U
|
|
759
|
-
</Avatar>
|
|
760
|
-
</Box>
|
|
761
|
-
)}
|
|
762
|
-
</SidebarFooter>
|
|
763
|
-
</Sidebar>
|
|
764
|
-
|
|
765
|
-
<Box sx={{ flex: 1, p: 3 }}>
|
|
766
|
-
<Typography variant="h5" gutterBottom>
|
|
767
|
-
Visual States Test
|
|
768
|
-
</Typography>
|
|
769
|
-
<Typography variant="body1" data-testid="collapse-status">
|
|
770
|
-
Sidebar is {collapsed ? 'collapsed' : 'expanded'}
|
|
771
|
-
</Typography>
|
|
772
|
-
<Typography variant="body2" color="text.secondary">
|
|
773
|
-
Toggle the sidebar to see different visual states and transitions.
|
|
774
|
-
</Typography>
|
|
775
|
-
</Box>
|
|
776
|
-
</Box>
|
|
777
|
-
);
|
|
778
|
-
};
|
|
779
|
-
|
|
780
|
-
export const VisualStates: Story = {
|
|
781
|
-
render: () => <VisualStatesComponent />,
|
|
782
|
-
play: async ({ canvasElement }) => {
|
|
783
|
-
const canvas = within(canvasElement);
|
|
784
|
-
|
|
785
|
-
// Test initial expanded state
|
|
786
|
-
const sidebar = canvas.getByTestId('visual-states-sidebar');
|
|
787
|
-
await expect(sidebar).toBeInTheDocument();
|
|
788
|
-
await expect(canvas.getByTestId('collapse-status')).toHaveTextContent('Sidebar is expanded');
|
|
789
|
-
await expect(canvas.getByTestId('expanded-footer')).toBeInTheDocument();
|
|
790
|
-
|
|
791
|
-
// Test sidebar width when expanded
|
|
792
|
-
await expect(window.getComputedStyle(sidebar).width).toBe('280px');
|
|
793
|
-
|
|
794
|
-
// Test collapsing
|
|
795
|
-
const collapseToggle = canvas.getByTestId('collapse-toggle');
|
|
796
|
-
await userEvent.click(collapseToggle);
|
|
797
|
-
|
|
798
|
-
await waitFor(() => {
|
|
799
|
-
expect(canvas.getByTestId('collapse-status')).toHaveTextContent('Sidebar is collapsed');
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
// Test sidebar width when collapsed. Read the computed style inside the
|
|
803
|
-
// waitFor rather than reassigning an outer variable from it — the callback
|
|
804
|
-
// reruns, so the shared binding held whichever value the last retry left.
|
|
805
|
-
await waitFor(() => {
|
|
806
|
-
expect(window.getComputedStyle(sidebar).width).toBe('64px');
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
// Test collapsed avatar is visible
|
|
810
|
-
await expect(canvas.getByTestId('collapsed-avatar')).toBeInTheDocument();
|
|
811
|
-
|
|
812
|
-
// Test expanding again
|
|
813
|
-
await userEvent.click(collapseToggle);
|
|
814
|
-
|
|
815
|
-
await waitFor(() => {
|
|
816
|
-
expect(canvas.getByTestId('collapse-status')).toHaveTextContent('Sidebar is expanded');
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
// Test navigation items are still clickable and accessible
|
|
820
|
-
await expect(canvas.getByTestId('visual-nav-home')).toBeInTheDocument();
|
|
821
|
-
await expect(canvas.getByTestId('visual-nav-dashboard')).toBeInTheDocument();
|
|
822
|
-
|
|
823
|
-
// Test that navigation items remain interactive during visual state transitions
|
|
824
|
-
const homeItem = canvas.getByTestId('visual-nav-home');
|
|
825
|
-
|
|
826
|
-
// Verify item is clickable
|
|
827
|
-
await userEvent.click(homeItem);
|
|
828
|
-
|
|
829
|
-
// Verify item is still present and enabled after click
|
|
830
|
-
await expect(homeItem).toBeInTheDocument();
|
|
831
|
-
await expect(homeItem).toBeEnabled();
|
|
832
|
-
},
|
|
833
|
-
};
|
|
834
|
-
|
|
835
|
-
// Performance Test
|
|
836
|
-
const PerformanceComponent = () => {
|
|
837
|
-
const [itemCount, setItemCount] = useState(50);
|
|
838
|
-
const [renderTime, setRenderTime] = useState<number>(0);
|
|
839
|
-
|
|
840
|
-
const generateItems = (count: number) => Array.from({ length: count }, (_, index) => ({
|
|
841
|
-
icon: <HomeIcon />,
|
|
842
|
-
label: `Item ${index + 1}`,
|
|
843
|
-
'data-testid': `perf-item-${index}`,
|
|
844
|
-
}));
|
|
845
|
-
|
|
846
|
-
const handleRender = () => {
|
|
847
|
-
const start = window.performance.now();
|
|
848
|
-
// Force re-render
|
|
849
|
-
setItemCount((prev) => prev + 0);
|
|
850
|
-
const end = window.performance.now();
|
|
851
|
-
setRenderTime(end - start);
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
const items = generateItems(itemCount);
|
|
855
|
-
|
|
856
|
-
return (
|
|
857
|
-
<Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50' }}>
|
|
858
|
-
<Sidebar variant="fixed" data-testid="performance-sidebar">
|
|
859
|
-
<SidebarHeader>
|
|
860
|
-
<Typography variant="h6">Performance Test</Typography>
|
|
861
|
-
<Typography variant="caption" data-testid="render-time">
|
|
862
|
-
Render time: {renderTime.toFixed(2)}ms
|
|
863
|
-
</Typography>
|
|
864
|
-
</SidebarHeader>
|
|
865
|
-
|
|
866
|
-
<SidebarContent>
|
|
867
|
-
<List data-testid="performance-list">
|
|
868
|
-
{items.map((item, index) => (
|
|
869
|
-
<ListItemButton key={index} data-testid={item['data-testid']}>
|
|
870
|
-
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
871
|
-
<ListItemText primary={item.label} />
|
|
872
|
-
</ListItemButton>
|
|
873
|
-
))}
|
|
874
|
-
</List>
|
|
875
|
-
</SidebarContent>
|
|
876
|
-
|
|
877
|
-
<SidebarFooter>
|
|
878
|
-
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
|
879
|
-
<Button size="small" onClick={() => setItemCount(25)} data-testid="perf-25-items">
|
|
880
|
-
25 Items
|
|
881
|
-
</Button>
|
|
882
|
-
<Button size="small" onClick={() => setItemCount(50)} data-testid="perf-50-items">
|
|
883
|
-
50 Items
|
|
884
|
-
</Button>
|
|
885
|
-
<Button size="small" onClick={() => setItemCount(100)} data-testid="perf-100-items">
|
|
886
|
-
100 Items
|
|
887
|
-
</Button>
|
|
888
|
-
<Button size="small" onClick={handleRender} data-testid="measure-render">
|
|
889
|
-
Measure
|
|
890
|
-
</Button>
|
|
891
|
-
</Box>
|
|
892
|
-
</SidebarFooter>
|
|
893
|
-
</Sidebar>
|
|
894
|
-
|
|
895
|
-
<Box sx={{ flex: 1, p: 3 }}>
|
|
896
|
-
<Typography variant="h5" gutterBottom>
|
|
897
|
-
Performance Metrics
|
|
898
|
-
</Typography>
|
|
899
|
-
<Typography variant="body1" data-testid="item-count">
|
|
900
|
-
Current items: {itemCount}
|
|
901
|
-
</Typography>
|
|
902
|
-
<Typography variant="body2" color="text.secondary">
|
|
903
|
-
Test sidebar performance with different numbers of items.
|
|
904
|
-
</Typography>
|
|
905
|
-
</Box>
|
|
906
|
-
</Box>
|
|
907
|
-
);
|
|
908
|
-
};
|
|
909
|
-
|
|
910
|
-
export const Performance: Story = {
|
|
911
|
-
render: () => <PerformanceComponent />,
|
|
912
|
-
play: async ({ canvasElement }) => {
|
|
913
|
-
const canvas = within(canvasElement);
|
|
914
|
-
|
|
915
|
-
// Test initial state
|
|
916
|
-
await expect(canvas.getByTestId('performance-sidebar')).toBeInTheDocument();
|
|
917
|
-
await expect(canvas.getByTestId('item-count')).toHaveTextContent('Current items: 50');
|
|
918
|
-
|
|
919
|
-
// Test items are rendered
|
|
920
|
-
await expect(canvas.getByTestId('perf-item-0')).toBeInTheDocument();
|
|
921
|
-
await expect(canvas.getByTestId('perf-item-49')).toBeInTheDocument();
|
|
922
|
-
|
|
923
|
-
// Test performance with different item counts
|
|
924
|
-
await userEvent.click(canvas.getByTestId('perf-25-items'));
|
|
925
|
-
await waitFor(() => {
|
|
926
|
-
expect(canvas.getByTestId('item-count')).toHaveTextContent('Current items: 25');
|
|
927
|
-
});
|
|
928
|
-
await expect(canvas.getByTestId('perf-item-0')).toBeInTheDocument();
|
|
929
|
-
await expect(canvas.getByTestId('perf-item-24')).toBeInTheDocument();
|
|
930
|
-
|
|
931
|
-
await userEvent.click(canvas.getByTestId('perf-100-items'));
|
|
932
|
-
await waitFor(() => {
|
|
933
|
-
expect(canvas.getByTestId('item-count')).toHaveTextContent('Current items: 100');
|
|
934
|
-
});
|
|
935
|
-
await expect(canvas.getByTestId('perf-item-0')).toBeInTheDocument();
|
|
936
|
-
await expect(canvas.getByTestId('perf-item-99')).toBeInTheDocument();
|
|
937
|
-
|
|
938
|
-
// Test render time measurement
|
|
939
|
-
await userEvent.click(canvas.getByTestId('measure-render'));
|
|
940
|
-
const renderTimeElement = canvas.getByTestId('render-time');
|
|
941
|
-
await expect(renderTimeElement).toBeInTheDocument();
|
|
942
|
-
|
|
943
|
-
// Verify list maintains scroll functionality
|
|
944
|
-
const performanceList = canvas.getByTestId('performance-list');
|
|
945
|
-
await expect(performanceList).toBeInTheDocument();
|
|
946
|
-
},
|
|
947
|
-
};
|
|
948
|
-
|
|
949
|
-
// Edge Cases Test
|
|
950
|
-
const EdgeCasesComponent = () => {
|
|
951
|
-
const [contentType, setContentType] = useState<'normal' | 'empty' | 'overflow' | 'special-chars'>(
|
|
952
|
-
'normal',
|
|
953
|
-
);
|
|
954
|
-
|
|
955
|
-
const getContent = () => {
|
|
956
|
-
switch (contentType) {
|
|
957
|
-
case 'empty':
|
|
958
|
-
return [];
|
|
959
|
-
case 'overflow':
|
|
960
|
-
return Array.from({ length: 100 }, (_, i) => ({
|
|
961
|
-
icon: <HomeIcon />,
|
|
962
|
-
label: `Very long navigation item name that might overflow the container ${i + 1}`,
|
|
963
|
-
'data-testid': `overflow-item-${i}`,
|
|
964
|
-
}));
|
|
965
|
-
case 'special-chars':
|
|
966
|
-
return [
|
|
967
|
-
{ icon: <HomeIcon />, label: '🏠 Home with Emoji', 'data-testid': 'emoji-item' },
|
|
968
|
-
{
|
|
969
|
-
icon: <DashboardIcon />,
|
|
970
|
-
label: 'Spëçíàl Çhäracters',
|
|
971
|
-
'data-testid': 'special-chars-item',
|
|
972
|
-
},
|
|
973
|
-
{ icon: <SettingsIcon />, label: '中文字符', 'data-testid': 'chinese-item' },
|
|
974
|
-
{ icon: <PersonIcon />, label: 'العربية', 'data-testid': 'arabic-item' },
|
|
975
|
-
{ icon: <InboxIcon />, label: 'Русский', 'data-testid': 'russian-item' },
|
|
976
|
-
];
|
|
977
|
-
default:
|
|
978
|
-
return navigationItems;
|
|
979
|
-
}
|
|
980
|
-
};
|
|
981
|
-
|
|
982
|
-
const content = getContent();
|
|
983
|
-
|
|
984
|
-
return (
|
|
985
|
-
<Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50' }}>
|
|
986
|
-
<Sidebar variant="fixed" data-testid="edge-cases-sidebar">
|
|
987
|
-
<SidebarHeader>
|
|
988
|
-
<Typography variant="h6" noWrap>
|
|
989
|
-
Edge Cases Test
|
|
990
|
-
</Typography>
|
|
991
|
-
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mt: 1 }}>
|
|
992
|
-
{(['normal', 'empty', 'overflow', 'special-chars'] as const).map((type) => (
|
|
993
|
-
<Button
|
|
994
|
-
key={type}
|
|
995
|
-
size="small"
|
|
996
|
-
variant={contentType === type ? 'contained' : 'outlined'}
|
|
997
|
-
onClick={() => setContentType(type)}
|
|
998
|
-
data-testid={`edge-${type}-button`}
|
|
999
|
-
sx={{ fontSize: '0.7rem', minWidth: 'auto' }}
|
|
1000
|
-
>
|
|
1001
|
-
{type}
|
|
1002
|
-
</Button>
|
|
1003
|
-
))}
|
|
1004
|
-
</Box>
|
|
1005
|
-
</SidebarHeader>
|
|
1006
|
-
|
|
1007
|
-
<SidebarContent data-testid="edge-cases-content">
|
|
1008
|
-
{content.length === 0 ? (
|
|
1009
|
-
<Box sx={{ p: 2, textAlign: 'center' }}>
|
|
1010
|
-
<Typography variant="body2" color="text.secondary" data-testid="empty-state">
|
|
1011
|
-
No navigation items available
|
|
1012
|
-
</Typography>
|
|
1013
|
-
</Box>
|
|
1014
|
-
) : (
|
|
1015
|
-
<List>
|
|
1016
|
-
{content.map((item, index) => (
|
|
1017
|
-
<ListItemButton key={index} data-testid={item['data-testid']}>
|
|
1018
|
-
<ListItemIcon>{item.icon}</ListItemIcon>
|
|
1019
|
-
<ListItemText
|
|
1020
|
-
primary={item.label}
|
|
1021
|
-
primaryTypographyProps={{
|
|
1022
|
-
noWrap: contentType === 'overflow',
|
|
1023
|
-
title: item.label,
|
|
1024
|
-
}}
|
|
1025
|
-
/>
|
|
1026
|
-
</ListItemButton>
|
|
1027
|
-
))}
|
|
1028
|
-
</List>
|
|
1029
|
-
)}
|
|
1030
|
-
</SidebarContent>
|
|
1031
|
-
|
|
1032
|
-
<SidebarFooter>
|
|
1033
|
-
<Typography variant="caption" data-testid="content-type-display">
|
|
1034
|
-
Current: {contentType} ({content.length} items)
|
|
1035
|
-
</Typography>
|
|
1036
|
-
</SidebarFooter>
|
|
1037
|
-
</Sidebar>
|
|
1038
|
-
|
|
1039
|
-
<Box sx={{ flex: 1, p: 3 }}>
|
|
1040
|
-
<Typography variant="h5" gutterBottom>
|
|
1041
|
-
Edge Cases
|
|
1042
|
-
</Typography>
|
|
1043
|
-
<Typography variant="body1" data-testid="edge-description">
|
|
1044
|
-
Testing sidebar with {contentType} content
|
|
1045
|
-
</Typography>
|
|
1046
|
-
</Box>
|
|
1047
|
-
</Box>
|
|
1048
|
-
);
|
|
1049
|
-
};
|
|
1050
|
-
|
|
1051
|
-
export const EdgeCases: Story = {
|
|
1052
|
-
render: () => <EdgeCasesComponent />,
|
|
1053
|
-
play: async ({ canvasElement }) => {
|
|
1054
|
-
const canvas = within(canvasElement);
|
|
1055
|
-
|
|
1056
|
-
// Test normal state
|
|
1057
|
-
await expect(canvas.getByTestId('edge-cases-sidebar')).toBeInTheDocument();
|
|
1058
|
-
await expect(canvas.getByTestId('content-type-display')).toHaveTextContent(
|
|
1059
|
-
'Current: normal (5 items)',
|
|
1060
|
-
);
|
|
1061
|
-
await expect(canvas.getByTestId('edge-description')).toHaveTextContent(
|
|
1062
|
-
'Testing sidebar with normal content',
|
|
1063
|
-
);
|
|
1064
|
-
|
|
1065
|
-
// Test empty state
|
|
1066
|
-
await userEvent.click(canvas.getByTestId('edge-empty-button'));
|
|
1067
|
-
await waitFor(() => {
|
|
1068
|
-
expect(canvas.getByTestId('content-type-display')).toHaveTextContent(
|
|
1069
|
-
'Current: empty (0 items)',
|
|
1070
|
-
);
|
|
1071
|
-
expect(canvas.getByTestId('empty-state')).toHaveTextContent('No navigation items available');
|
|
1072
|
-
});
|
|
1073
|
-
|
|
1074
|
-
// Test overflow state
|
|
1075
|
-
await userEvent.click(canvas.getByTestId('edge-overflow-button'));
|
|
1076
|
-
await waitFor(() => {
|
|
1077
|
-
expect(canvas.getByTestId('content-type-display')).toHaveTextContent(
|
|
1078
|
-
'Current: overflow (100 items)',
|
|
1079
|
-
);
|
|
1080
|
-
});
|
|
1081
|
-
await expect(canvas.getByTestId('overflow-item-0')).toBeInTheDocument();
|
|
1082
|
-
await expect(canvas.getByTestId('overflow-item-99')).toBeInTheDocument();
|
|
1083
|
-
|
|
1084
|
-
// Test special characters
|
|
1085
|
-
await userEvent.click(canvas.getByTestId('edge-special-chars-button'));
|
|
1086
|
-
await waitFor(() => {
|
|
1087
|
-
expect(canvas.getByTestId('content-type-display')).toHaveTextContent(
|
|
1088
|
-
'Current: special-chars (5 items)',
|
|
1089
|
-
);
|
|
1090
|
-
});
|
|
1091
|
-
await expect(canvas.getByTestId('emoji-item')).toHaveTextContent('🏠 Home with Emoji');
|
|
1092
|
-
await expect(canvas.getByTestId('special-chars-item')).toHaveTextContent('Spëçíàl Çhäracters');
|
|
1093
|
-
await expect(canvas.getByTestId('chinese-item')).toHaveTextContent('中文字符');
|
|
1094
|
-
await expect(canvas.getByTestId('arabic-item')).toHaveTextContent('العربية');
|
|
1095
|
-
await expect(canvas.getByTestId('russian-item')).toHaveTextContent('Русский');
|
|
1096
|
-
|
|
1097
|
-
// Test back to normal
|
|
1098
|
-
await userEvent.click(canvas.getByTestId('edge-normal-button'));
|
|
1099
|
-
await waitFor(() => {
|
|
1100
|
-
expect(canvas.getByTestId('content-type-display')).toHaveTextContent(
|
|
1101
|
-
'Current: normal (5 items)',
|
|
1102
|
-
);
|
|
1103
|
-
});
|
|
1104
|
-
},
|
|
1105
|
-
};
|
|
1106
|
-
|
|
1107
|
-
// Integration Test
|
|
1108
|
-
const IntegrationComponent = () => {
|
|
1109
|
-
const [activeSection, setActiveSection] = useState('home');
|
|
1110
|
-
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
1111
|
-
const [notifications, setNotifications] = useState(3);
|
|
1112
|
-
|
|
1113
|
-
const integrationItems = [
|
|
1114
|
-
{
|
|
1115
|
-
id: 'home',
|
|
1116
|
-
icon: <HomeIcon />,
|
|
1117
|
-
label: 'Home',
|
|
1118
|
-
'data-testid': 'integration-home',
|
|
1119
|
-
badge: null,
|
|
1120
|
-
},
|
|
1121
|
-
{
|
|
1122
|
-
id: 'dashboard',
|
|
1123
|
-
icon: <DashboardIcon />,
|
|
1124
|
-
label: 'Dashboard',
|
|
1125
|
-
'data-testid': 'integration-dashboard',
|
|
1126
|
-
badge: null,
|
|
1127
|
-
},
|
|
1128
|
-
{
|
|
1129
|
-
id: 'inbox',
|
|
1130
|
-
icon: <InboxIcon />,
|
|
1131
|
-
label: 'Messages',
|
|
1132
|
-
'data-testid': 'integration-inbox',
|
|
1133
|
-
badge: notifications > 0 ? notifications : null,
|
|
1134
|
-
},
|
|
1135
|
-
{
|
|
1136
|
-
id: 'settings',
|
|
1137
|
-
icon: <SettingsIcon />,
|
|
1138
|
-
label: 'Settings',
|
|
1139
|
-
'data-testid': 'integration-settings',
|
|
1140
|
-
badge: null,
|
|
1141
|
-
},
|
|
1142
|
-
];
|
|
1143
|
-
|
|
1144
|
-
return (
|
|
1145
|
-
<Box sx={{ display: 'flex', height: '500px', bgcolor: 'grey.50' }}>
|
|
1146
|
-
<Sidebar
|
|
1147
|
-
variant="collapsible"
|
|
1148
|
-
open={sidebarOpen}
|
|
1149
|
-
width={280}
|
|
1150
|
-
collapsedWidth={64}
|
|
1151
|
-
data-testid="integration-sidebar"
|
|
1152
|
-
>
|
|
1153
|
-
<SidebarHeader>
|
|
1154
|
-
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
1155
|
-
<Typography variant="h6" sx={{ display: sidebarOpen ? 'block' : 'none' }}>
|
|
1156
|
-
Integration Test
|
|
1157
|
-
</Typography>
|
|
1158
|
-
<IconButton
|
|
1159
|
-
onClick={() => setSidebarOpen(!sidebarOpen)}
|
|
1160
|
-
data-testid="integration-toggle"
|
|
1161
|
-
size="small"
|
|
1162
|
-
>
|
|
1163
|
-
{sidebarOpen ? <ChevronLeftIcon /> : <MenuIcon />}
|
|
1164
|
-
</IconButton>
|
|
1165
|
-
</Box>
|
|
1166
|
-
</SidebarHeader>
|
|
1167
|
-
|
|
1168
|
-
<SidebarContent>
|
|
1169
|
-
<List>
|
|
1170
|
-
{integrationItems.map((item) => (
|
|
1171
|
-
<ListItemButton
|
|
1172
|
-
key={item.id}
|
|
1173
|
-
data-testid={item['data-testid']}
|
|
1174
|
-
selected={activeSection === item.id}
|
|
1175
|
-
onClick={() => setActiveSection(item.id)}
|
|
1176
|
-
>
|
|
1177
|
-
<ListItemIcon
|
|
1178
|
-
sx={{
|
|
1179
|
-
minWidth: sidebarOpen ? 56 : 40,
|
|
1180
|
-
justifyContent: 'center',
|
|
1181
|
-
}}
|
|
1182
|
-
>
|
|
1183
|
-
<Box sx={{ position: 'relative' }}>
|
|
1184
|
-
{item.icon}
|
|
1185
|
-
{item.badge && (
|
|
1186
|
-
<Box
|
|
1187
|
-
sx={{
|
|
1188
|
-
position: 'absolute',
|
|
1189
|
-
top: -8,
|
|
1190
|
-
right: -8,
|
|
1191
|
-
backgroundColor: 'error.main',
|
|
1192
|
-
color: 'white',
|
|
1193
|
-
borderRadius: '50%',
|
|
1194
|
-
width: 16,
|
|
1195
|
-
height: 16,
|
|
1196
|
-
display: 'flex',
|
|
1197
|
-
alignItems: 'center',
|
|
1198
|
-
justifyContent: 'center',
|
|
1199
|
-
fontSize: '0.7rem',
|
|
1200
|
-
}}
|
|
1201
|
-
data-testid={`badge-${item.id}`}
|
|
1202
|
-
>
|
|
1203
|
-
{item.badge}
|
|
1204
|
-
</Box>
|
|
1205
|
-
)}
|
|
1206
|
-
</Box>
|
|
1207
|
-
</ListItemIcon>
|
|
1208
|
-
{sidebarOpen && <ListItemText primary={item.label} />}
|
|
1209
|
-
</ListItemButton>
|
|
1210
|
-
))}
|
|
1211
|
-
</List>
|
|
1212
|
-
</SidebarContent>
|
|
1213
|
-
|
|
1214
|
-
<SidebarFooter>
|
|
1215
|
-
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
1216
|
-
<Avatar sx={{ width: 32, height: 32 }}>I</Avatar>
|
|
1217
|
-
{sidebarOpen && (
|
|
1218
|
-
<Box>
|
|
1219
|
-
<Typography variant="body2">Integration User</Typography>
|
|
1220
|
-
<Button
|
|
1221
|
-
size="small"
|
|
1222
|
-
onClick={() => setNotifications(0)}
|
|
1223
|
-
data-testid="clear-notifications"
|
|
1224
|
-
disabled={notifications === 0}
|
|
1225
|
-
>
|
|
1226
|
-
Clear ({notifications})
|
|
1227
|
-
</Button>
|
|
1228
|
-
</Box>
|
|
1229
|
-
)}
|
|
1230
|
-
</Box>
|
|
1231
|
-
</SidebarFooter>
|
|
1232
|
-
</Sidebar>
|
|
1233
|
-
|
|
1234
|
-
<Box sx={{ flex: 1, p: 3 }}>
|
|
1235
|
-
<Typography variant="h5" gutterBottom data-testid="integration-title">
|
|
1236
|
-
{integrationItems.find((item) => item.id === activeSection)?.label} Section
|
|
1237
|
-
</Typography>
|
|
1238
|
-
<Typography variant="body1" data-testid="integration-content">
|
|
1239
|
-
Active section: {activeSection}
|
|
1240
|
-
</Typography>
|
|
1241
|
-
<Typography variant="body2" color="text.secondary">
|
|
1242
|
-
Sidebar is {sidebarOpen ? 'expanded' : 'collapsed'}
|
|
1243
|
-
</Typography>
|
|
1244
|
-
<Button
|
|
1245
|
-
variant="outlined"
|
|
1246
|
-
onClick={() => setNotifications((prev) => prev + 1)}
|
|
1247
|
-
data-testid="add-notification"
|
|
1248
|
-
sx={{ mt: 2 }}
|
|
1249
|
-
>
|
|
1250
|
-
Add Notification
|
|
1251
|
-
</Button>
|
|
1252
|
-
</Box>
|
|
1253
|
-
</Box>
|
|
1254
|
-
);
|
|
1255
|
-
};
|
|
1256
|
-
|
|
1257
|
-
export const Integration: Story = {
|
|
1258
|
-
render: () => <IntegrationComponent />,
|
|
1259
|
-
play: async ({ canvasElement }) => {
|
|
1260
|
-
const canvas = within(canvasElement);
|
|
1261
|
-
|
|
1262
|
-
// Test initial state
|
|
1263
|
-
await expect(canvas.getByTestId('integration-sidebar')).toBeInTheDocument();
|
|
1264
|
-
await expect(canvas.getByTestId('integration-title')).toHaveTextContent('Home Section');
|
|
1265
|
-
await expect(canvas.getByTestId('integration-content')).toHaveTextContent(
|
|
1266
|
-
'Active section: home',
|
|
1267
|
-
);
|
|
1268
|
-
|
|
1269
|
-
// Test notification badge
|
|
1270
|
-
await expect(canvas.getByTestId('badge-inbox')).toHaveTextContent('3');
|
|
1271
|
-
|
|
1272
|
-
// Test navigation
|
|
1273
|
-
await userEvent.click(canvas.getByTestId('integration-dashboard'));
|
|
1274
|
-
await waitFor(() => {
|
|
1275
|
-
expect(canvas.getByTestId('integration-title')).toHaveTextContent('Dashboard Section');
|
|
1276
|
-
expect(canvas.getByTestId('integration-content')).toHaveTextContent(
|
|
1277
|
-
'Active section: dashboard',
|
|
1278
|
-
);
|
|
1279
|
-
});
|
|
1280
|
-
|
|
1281
|
-
await userEvent.click(canvas.getByTestId('integration-inbox'));
|
|
1282
|
-
await waitFor(() => {
|
|
1283
|
-
expect(canvas.getByTestId('integration-title')).toHaveTextContent('Messages Section');
|
|
1284
|
-
expect(canvas.getByTestId('integration-content')).toHaveTextContent('Active section: inbox');
|
|
1285
|
-
});
|
|
1286
|
-
|
|
1287
|
-
// Test adding notifications
|
|
1288
|
-
await userEvent.click(canvas.getByTestId('add-notification'));
|
|
1289
|
-
await waitFor(() => {
|
|
1290
|
-
expect(canvas.getByTestId('badge-inbox')).toHaveTextContent('4');
|
|
1291
|
-
});
|
|
1292
|
-
|
|
1293
|
-
// Test clearing notifications
|
|
1294
|
-
await userEvent.click(canvas.getByTestId('clear-notifications'));
|
|
1295
|
-
await waitFor(() => {
|
|
1296
|
-
expect(canvas.queryByTestId('badge-inbox')).not.toBeInTheDocument();
|
|
1297
|
-
});
|
|
1298
|
-
|
|
1299
|
-
// Test sidebar collapse
|
|
1300
|
-
await userEvent.click(canvas.getByTestId('integration-toggle'));
|
|
1301
|
-
await waitFor(() => {
|
|
1302
|
-
const sidebar = canvas.getByTestId('integration-sidebar');
|
|
1303
|
-
const sidebarStyles = window.getComputedStyle(sidebar);
|
|
1304
|
-
expect(sidebarStyles.width).toBe('64px');
|
|
1305
|
-
});
|
|
1306
|
-
|
|
1307
|
-
// Test sidebar expand
|
|
1308
|
-
await userEvent.click(canvas.getByTestId('integration-toggle'));
|
|
1309
|
-
await waitFor(() => {
|
|
1310
|
-
const sidebar = canvas.getByTestId('integration-sidebar');
|
|
1311
|
-
const sidebarStyles = window.getComputedStyle(sidebar);
|
|
1312
|
-
expect(sidebarStyles.width).toBe('280px');
|
|
1313
|
-
});
|
|
1314
|
-
|
|
1315
|
-
// Test settings navigation
|
|
1316
|
-
await userEvent.click(canvas.getByTestId('integration-settings'));
|
|
1317
|
-
await waitFor(() => {
|
|
1318
|
-
expect(canvas.getByTestId('integration-title')).toHaveTextContent('Settings Section');
|
|
1319
|
-
expect(canvas.getByTestId('integration-content')).toHaveTextContent(
|
|
1320
|
-
'Active section: settings',
|
|
1321
|
-
);
|
|
1322
|
-
});
|
|
1323
|
-
},
|
|
1324
|
-
};
|