@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,1085 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Alert,
|
|
3
|
-
Avatar,
|
|
4
|
-
Box,
|
|
5
|
-
ListItem,
|
|
6
|
-
ListItemAvatar,
|
|
7
|
-
ListItemText,
|
|
8
|
-
Paper,
|
|
9
|
-
Skeleton,
|
|
10
|
-
Typography,
|
|
11
|
-
} from '@mui/material';
|
|
12
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
13
|
-
import { expect, fireEvent,fn, userEvent, waitFor, within } from 'storybook/test';
|
|
14
|
-
|
|
15
|
-
import { VirtualGrid,VirtualList } from './VirtualList';
|
|
16
|
-
import type { VirtualListItem } from './VirtualList.types';
|
|
17
|
-
|
|
18
|
-
const meta: Meta<typeof VirtualList> = {
|
|
19
|
-
title: 'Utility/VirtualList/Tests',
|
|
20
|
-
component: VirtualList,
|
|
21
|
-
parameters: {
|
|
22
|
-
layout: 'centered',
|
|
23
|
-
chromatic: { disableSnapshot: false },
|
|
24
|
-
},
|
|
25
|
-
tags: ['autodocs', 'test', 'component:VirtualList'],
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export default meta;
|
|
29
|
-
type Story = StoryObj<typeof meta>;
|
|
30
|
-
|
|
31
|
-
// Helper functions
|
|
32
|
-
const generateItems = (count: number): VirtualListItem[] => Array.from({ length: count }, (_, i) => ({
|
|
33
|
-
id: i,
|
|
34
|
-
data: {
|
|
35
|
-
name: `Item ${i + 1}`,
|
|
36
|
-
description: `This is the description for item ${i + 1}`,
|
|
37
|
-
avatar: `https://i.pravatar.cc/40?img=${(i % 70) + 1}`,
|
|
38
|
-
},
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
const generateVariableItems = (count: number): VirtualListItem[] => Array.from({ length: count }, (_, i) => ({
|
|
42
|
-
id: i,
|
|
43
|
-
// Cycles 60/95/130/165 so the mix of heights is fixed per run.
|
|
44
|
-
height: 60 + (i % 4) * 35,
|
|
45
|
-
data: {
|
|
46
|
-
name: `Variable Item ${i + 1}`,
|
|
47
|
-
description: `This item has a variable height. ${
|
|
48
|
-
i % 2 === 0
|
|
49
|
-
? 'It contains additional content that makes it taller than other items in the list.'
|
|
50
|
-
: 'Short description.'
|
|
51
|
-
}`,
|
|
52
|
-
avatar: `https://i.pravatar.cc/40?img=${(i % 70) + 1}`,
|
|
53
|
-
},
|
|
54
|
-
}));
|
|
55
|
-
|
|
56
|
-
// A virtual list's job is to swap which items are mounted as the container
|
|
57
|
-
// scrolls, so these tests assert on the rendered window. The raw scrollTop they
|
|
58
|
-
// checked before depends on the viewport and says nothing about virtualisation.
|
|
59
|
-
const renderedIndices = (root: HTMLElement): number[] =>
|
|
60
|
-
Array.from(root.querySelectorAll('[data-testid^="virtual-item-"]')).map((element) =>
|
|
61
|
-
Number((element.getAttribute('data-testid') ?? '').replace('virtual-item-', '')),
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const firstRenderedIndex = (root: HTMLElement): number => {
|
|
65
|
-
const indices = renderedIndices(root);
|
|
66
|
-
return indices.length > 0 ? Math.min(...indices) : -1;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// Larger than any fixture's total height, so the container lands at its end.
|
|
70
|
-
const SCROLL_TO_END = 50_000;
|
|
71
|
-
|
|
72
|
-
const SimpleItemRenderer = ({
|
|
73
|
-
item,
|
|
74
|
-
index,
|
|
75
|
-
style,
|
|
76
|
-
}: {
|
|
77
|
-
item: VirtualListItem;
|
|
78
|
-
index: number;
|
|
79
|
-
style: React.CSSProperties;
|
|
80
|
-
}) => (
|
|
81
|
-
<Box key={item.id} style={style} data-testid={`virtual-item-${index}`}>
|
|
82
|
-
<ListItem>
|
|
83
|
-
<ListItemAvatar>
|
|
84
|
-
<Avatar src={item.data.avatar} />
|
|
85
|
-
</ListItemAvatar>
|
|
86
|
-
<ListItemText primary={item.data.name} secondary={item.data.description} />
|
|
87
|
-
</ListItem>
|
|
88
|
-
</Box>
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const GridItemRenderer = ({
|
|
92
|
-
item,
|
|
93
|
-
index,
|
|
94
|
-
style,
|
|
95
|
-
}: {
|
|
96
|
-
item: VirtualListItem;
|
|
97
|
-
index: number;
|
|
98
|
-
style: React.CSSProperties;
|
|
99
|
-
}) => (
|
|
100
|
-
<Box
|
|
101
|
-
key={item.id}
|
|
102
|
-
style={style}
|
|
103
|
-
data-testid={`grid-item-${index}`}
|
|
104
|
-
sx={{
|
|
105
|
-
p: 2,
|
|
106
|
-
bgcolor: 'background.paper',
|
|
107
|
-
border: '1px solid',
|
|
108
|
-
borderColor: 'divider',
|
|
109
|
-
borderRadius: 2,
|
|
110
|
-
display: 'flex',
|
|
111
|
-
flexDirection: 'column',
|
|
112
|
-
alignItems: 'center',
|
|
113
|
-
textAlign: 'center',
|
|
114
|
-
}}
|
|
115
|
-
>
|
|
116
|
-
<Avatar src={item.data.avatar} sx={{ width: 56, height: 56, mb: 1 }} />
|
|
117
|
-
<Typography variant="body2" fontWeight="bold">
|
|
118
|
-
{item.data.name}
|
|
119
|
-
</Typography>
|
|
120
|
-
<Typography variant="caption" color="text.secondary">
|
|
121
|
-
Grid Item #{item.id + 1}
|
|
122
|
-
</Typography>
|
|
123
|
-
</Box>
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
// Basic Interaction Tests
|
|
127
|
-
export const BasicInteraction: Story = {
|
|
128
|
-
render: () => {
|
|
129
|
-
const items = generateItems(100);
|
|
130
|
-
const onScrollSpy = fn();
|
|
131
|
-
|
|
132
|
-
return (
|
|
133
|
-
<Paper sx={{ width: 400, height: 300 }}>
|
|
134
|
-
<VirtualList
|
|
135
|
-
items={items}
|
|
136
|
-
variant="fixed"
|
|
137
|
-
height={300}
|
|
138
|
-
itemHeight={60}
|
|
139
|
-
onScroll={onScrollSpy}
|
|
140
|
-
renderItem={SimpleItemRenderer}
|
|
141
|
-
data-testid="virtual-list"
|
|
142
|
-
/>
|
|
143
|
-
</Paper>
|
|
144
|
-
);
|
|
145
|
-
},
|
|
146
|
-
play: async ({ canvasElement }) => {
|
|
147
|
-
const canvas = within(canvasElement);
|
|
148
|
-
const virtualList = canvas.getByTestId('virtual-list');
|
|
149
|
-
|
|
150
|
-
// Test that virtual list renders
|
|
151
|
-
await expect(virtualList).toBeInTheDocument();
|
|
152
|
-
|
|
153
|
-
// Test that items are rendered
|
|
154
|
-
const firstItem = canvas.getByTestId('virtual-item-0');
|
|
155
|
-
await expect(firstItem).toBeInTheDocument();
|
|
156
|
-
|
|
157
|
-
// Test scrolling
|
|
158
|
-
fireEvent.scroll(virtualList, { target: { scrollTop: 200 } });
|
|
159
|
-
await waitFor(() => {
|
|
160
|
-
// After scrolling, different items should be visible
|
|
161
|
-
const newVisibleItem = canvas.queryByTestId('virtual-item-5');
|
|
162
|
-
expect(newVisibleItem).toBeInTheDocument();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// Test that scroll callback is called
|
|
166
|
-
await waitFor(() => {
|
|
167
|
-
expect(firstRenderedIndex(virtualList)).toBeGreaterThan(0);
|
|
168
|
-
});
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
export const GridInteraction: Story = {
|
|
173
|
-
render: () => {
|
|
174
|
-
const items = generateItems(100);
|
|
175
|
-
const onScrollSpy = fn();
|
|
176
|
-
|
|
177
|
-
return (
|
|
178
|
-
<Paper sx={{ width: 600, height: 400 }}>
|
|
179
|
-
<VirtualGrid
|
|
180
|
-
items={items}
|
|
181
|
-
height={400}
|
|
182
|
-
width={600}
|
|
183
|
-
columnCount={3}
|
|
184
|
-
rowHeight={150}
|
|
185
|
-
gap={8}
|
|
186
|
-
onScroll={onScrollSpy}
|
|
187
|
-
renderItem={GridItemRenderer}
|
|
188
|
-
data-testid="virtual-grid"
|
|
189
|
-
/>
|
|
190
|
-
</Paper>
|
|
191
|
-
);
|
|
192
|
-
},
|
|
193
|
-
play: async ({ canvasElement }) => {
|
|
194
|
-
const canvas = within(canvasElement);
|
|
195
|
-
const virtualGrid = canvas.getByTestId('virtual-grid');
|
|
196
|
-
|
|
197
|
-
// Test that virtual grid renders
|
|
198
|
-
await expect(virtualGrid).toBeInTheDocument();
|
|
199
|
-
|
|
200
|
-
// Test that grid items are rendered
|
|
201
|
-
const firstItem = canvas.getByTestId('grid-item-0');
|
|
202
|
-
await expect(firstItem).toBeInTheDocument();
|
|
203
|
-
|
|
204
|
-
// Test multiple items in first row
|
|
205
|
-
const secondItem = canvas.getByTestId('grid-item-1');
|
|
206
|
-
const thirdItem = canvas.getByTestId('grid-item-2');
|
|
207
|
-
await expect(secondItem).toBeInTheDocument();
|
|
208
|
-
await expect(thirdItem).toBeInTheDocument();
|
|
209
|
-
|
|
210
|
-
// Test scrolling in grid
|
|
211
|
-
fireEvent.scroll(virtualGrid, { target: { scrollTop: 300 } });
|
|
212
|
-
await waitFor(() => {
|
|
213
|
-
expect(firstRenderedIndex(virtualGrid)).toBeGreaterThan(0);
|
|
214
|
-
});
|
|
215
|
-
},
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
// Form Interaction Tests (Scroll behaviors)
|
|
219
|
-
// The read-out was a plain `let` mutated from the scroll callback, so it never
|
|
220
|
-
// re-rendered and always showed 0px.
|
|
221
|
-
const ScrollInteractionDemo: React.FC = () => {
|
|
222
|
-
const items = React.useMemo(() => generateItems(1000), []);
|
|
223
|
-
const [scrollPosition, setScrollPosition] = React.useState(0);
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<Box sx={{ display: 'flex', gap: 2 }}>
|
|
227
|
-
<Paper sx={{ width: 400, height: 300 }}>
|
|
228
|
-
<VirtualList
|
|
229
|
-
items={items}
|
|
230
|
-
variant="fixed"
|
|
231
|
-
height={300}
|
|
232
|
-
itemHeight={50}
|
|
233
|
-
onScroll={setScrollPosition}
|
|
234
|
-
renderItem={SimpleItemRenderer}
|
|
235
|
-
data-testid="scrollable-list"
|
|
236
|
-
/>
|
|
237
|
-
</Paper>
|
|
238
|
-
<Box sx={{ width: 200, p: 2 }}>
|
|
239
|
-
<Typography variant="body2" data-testid="scroll-info">
|
|
240
|
-
Scroll Position: {Math.round(scrollPosition)}px
|
|
241
|
-
</Typography>
|
|
242
|
-
</Box>
|
|
243
|
-
</Box>
|
|
244
|
-
);
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
export const ScrollInteraction: Story = {
|
|
248
|
-
render: () => <ScrollInteractionDemo />,
|
|
249
|
-
play: async ({ canvasElement }) => {
|
|
250
|
-
const canvas = within(canvasElement);
|
|
251
|
-
const scrollableList = canvas.getByTestId('scrollable-list');
|
|
252
|
-
|
|
253
|
-
// Test initial state
|
|
254
|
-
await expect(scrollableList).toBeInTheDocument();
|
|
255
|
-
expect(firstRenderedIndex(scrollableList)).toBe(0);
|
|
256
|
-
|
|
257
|
-
// Test scroll to middle
|
|
258
|
-
fireEvent.scroll(scrollableList, { target: { scrollTop: 500 } });
|
|
259
|
-
await waitFor(() => {
|
|
260
|
-
expect(firstRenderedIndex(scrollableList)).toBeGreaterThan(0);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// Test scroll to bottom
|
|
264
|
-
fireEvent.scroll(scrollableList, { target: { scrollTop: SCROLL_TO_END } });
|
|
265
|
-
await waitFor(() => {
|
|
266
|
-
expect(firstRenderedIndex(scrollableList)).toBeGreaterThan(0);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// Test scroll back to top
|
|
270
|
-
fireEvent.scroll(scrollableList, { target: { scrollTop: 0 } });
|
|
271
|
-
await waitFor(() => {
|
|
272
|
-
expect(firstRenderedIndex(scrollableList)).toBe(0);
|
|
273
|
-
});
|
|
274
|
-
},
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
// Keyboard Navigation Tests
|
|
278
|
-
export const KeyboardNavigation: Story = {
|
|
279
|
-
render: () => {
|
|
280
|
-
const items = generateItems(100);
|
|
281
|
-
|
|
282
|
-
return (
|
|
283
|
-
<Paper sx={{ width: 400, height: 300 }}>
|
|
284
|
-
<VirtualList
|
|
285
|
-
items={items}
|
|
286
|
-
variant="fixed"
|
|
287
|
-
height={300}
|
|
288
|
-
itemHeight={60}
|
|
289
|
-
renderItem={({ item, index, style }) => (
|
|
290
|
-
<Box key={item.id} style={style}>
|
|
291
|
-
<ListItem
|
|
292
|
-
tabIndex={0}
|
|
293
|
-
data-testid={`keyboard-item-${index}`}
|
|
294
|
-
sx={{
|
|
295
|
-
'&:focus': {
|
|
296
|
-
backgroundColor: 'primary.light',
|
|
297
|
-
outline: '2px solid',
|
|
298
|
-
outlineColor: 'primary.main',
|
|
299
|
-
},
|
|
300
|
-
}}
|
|
301
|
-
>
|
|
302
|
-
<ListItemAvatar>
|
|
303
|
-
<Avatar src={item.data.avatar} />
|
|
304
|
-
</ListItemAvatar>
|
|
305
|
-
<ListItemText primary={item.data.name} secondary={item.data.description} />
|
|
306
|
-
</ListItem>
|
|
307
|
-
</Box>
|
|
308
|
-
)}
|
|
309
|
-
data-testid="keyboard-list"
|
|
310
|
-
/>
|
|
311
|
-
</Paper>
|
|
312
|
-
);
|
|
313
|
-
},
|
|
314
|
-
play: async ({ canvasElement }) => {
|
|
315
|
-
const canvas = within(canvasElement);
|
|
316
|
-
const list = canvas.getByTestId('keyboard-list');
|
|
317
|
-
|
|
318
|
-
// Test initial focus
|
|
319
|
-
const firstItem = canvas.getByTestId('keyboard-item-0');
|
|
320
|
-
await userEvent.click(firstItem);
|
|
321
|
-
await waitFor(() => {
|
|
322
|
-
expect(firstItem).toHaveFocus();
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
// Test that second item can be focused (simulate arrow key behavior)
|
|
326
|
-
const secondItem = canvas.getByTestId('keyboard-item-1');
|
|
327
|
-
await userEvent.click(secondItem);
|
|
328
|
-
await waitFor(() => {
|
|
329
|
-
expect(secondItem).toHaveFocus();
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// Test scroll behavior using direct scroll event
|
|
333
|
-
fireEvent.scroll(list, { target: { scrollTop: 200 } });
|
|
334
|
-
await waitFor(
|
|
335
|
-
() => {
|
|
336
|
-
// Check the scroll position was applied
|
|
337
|
-
expect(firstRenderedIndex(list)).toBeGreaterThan(0);
|
|
338
|
-
},
|
|
339
|
-
{ timeout: 2000 },
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
// Test scroll back to top
|
|
343
|
-
fireEvent.scroll(list, { target: { scrollTop: 0 } });
|
|
344
|
-
await waitFor(
|
|
345
|
-
() => {
|
|
346
|
-
expect(firstRenderedIndex(list)).toBe(0);
|
|
347
|
-
},
|
|
348
|
-
{ timeout: 1000 },
|
|
349
|
-
);
|
|
350
|
-
},
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
// Screen Reader Tests
|
|
354
|
-
export const ScreenReader: Story = {
|
|
355
|
-
render: () => {
|
|
356
|
-
const items = generateItems(50);
|
|
357
|
-
|
|
358
|
-
return (
|
|
359
|
-
<Paper sx={{ width: 400, height: 300 }}>
|
|
360
|
-
<VirtualList
|
|
361
|
-
items={items}
|
|
362
|
-
variant="fixed"
|
|
363
|
-
height={300}
|
|
364
|
-
itemHeight={80}
|
|
365
|
-
renderItem={({ item, index, style }) => (
|
|
366
|
-
<Box key={item.id} style={style}>
|
|
367
|
-
<ListItem
|
|
368
|
-
role="listitem"
|
|
369
|
-
aria-label={`Item ${index + 1}: ${item.data.name}`}
|
|
370
|
-
data-testid={`screen-reader-item-${index}`}
|
|
371
|
-
>
|
|
372
|
-
<ListItemAvatar>
|
|
373
|
-
<Avatar src={item.data.avatar} alt={`Avatar for ${item.data.name}`} />
|
|
374
|
-
</ListItemAvatar>
|
|
375
|
-
<ListItemText primary={item.data.name} secondary={item.data.description} />
|
|
376
|
-
</ListItem>
|
|
377
|
-
</Box>
|
|
378
|
-
)}
|
|
379
|
-
role="list"
|
|
380
|
-
aria-label="Virtual scrollable list"
|
|
381
|
-
data-testid="screen-reader-list"
|
|
382
|
-
/>
|
|
383
|
-
</Paper>
|
|
384
|
-
);
|
|
385
|
-
},
|
|
386
|
-
play: async ({ canvasElement }) => {
|
|
387
|
-
const canvas = within(canvasElement);
|
|
388
|
-
const list = canvas.getByTestId('screen-reader-list');
|
|
389
|
-
|
|
390
|
-
// Test ARIA attributes
|
|
391
|
-
await expect(list).toHaveAttribute('role', 'list');
|
|
392
|
-
await expect(list).toHaveAttribute('aria-label', 'Virtual scrollable list');
|
|
393
|
-
|
|
394
|
-
// Test item accessibility
|
|
395
|
-
const firstItem = canvas.getByTestId('screen-reader-item-0');
|
|
396
|
-
await expect(firstItem).toHaveAttribute('role', 'listitem');
|
|
397
|
-
await expect(firstItem).toHaveAttribute('aria-label');
|
|
398
|
-
|
|
399
|
-
// Test that aria-label contains descriptive text
|
|
400
|
-
const ariaLabel = firstItem.getAttribute('aria-label');
|
|
401
|
-
expect(ariaLabel).toContain('Item 1:');
|
|
402
|
-
|
|
403
|
-
// Test avatar alt text
|
|
404
|
-
const avatar = firstItem.querySelector('img');
|
|
405
|
-
if (avatar) {
|
|
406
|
-
expect(avatar).toHaveAttribute('alt');
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
// Focus Management Tests
|
|
412
|
-
export const FocusManagement: Story = {
|
|
413
|
-
render: () => {
|
|
414
|
-
const items = generateItems(20);
|
|
415
|
-
|
|
416
|
-
return (
|
|
417
|
-
<Box sx={{ display: 'flex', gap: 2, alignItems: 'flex-start' }}>
|
|
418
|
-
<button data-testid="external-button">External Button</button>
|
|
419
|
-
<Paper sx={{ width: 400, height: 250 }}>
|
|
420
|
-
<VirtualList
|
|
421
|
-
items={items}
|
|
422
|
-
variant="fixed"
|
|
423
|
-
height={250}
|
|
424
|
-
itemHeight={60}
|
|
425
|
-
renderItem={({ item, index, style }) => (
|
|
426
|
-
<Box key={item.id} style={style}>
|
|
427
|
-
<ListItem
|
|
428
|
-
button
|
|
429
|
-
tabIndex={0}
|
|
430
|
-
data-testid={`focus-item-${index}`}
|
|
431
|
-
onFocus={(e) => {
|
|
432
|
-
e.currentTarget.scrollIntoView({ block: 'nearest' });
|
|
433
|
-
}}
|
|
434
|
-
>
|
|
435
|
-
<ListItemAvatar>
|
|
436
|
-
<Avatar src={item.data.avatar} />
|
|
437
|
-
</ListItemAvatar>
|
|
438
|
-
<ListItemText primary={item.data.name} secondary={item.data.description} />
|
|
439
|
-
</ListItem>
|
|
440
|
-
</Box>
|
|
441
|
-
)}
|
|
442
|
-
data-testid="focus-list"
|
|
443
|
-
/>
|
|
444
|
-
</Paper>
|
|
445
|
-
</Box>
|
|
446
|
-
);
|
|
447
|
-
},
|
|
448
|
-
play: async ({ canvasElement }) => {
|
|
449
|
-
const canvas = within(canvasElement);
|
|
450
|
-
|
|
451
|
-
// Test external focus
|
|
452
|
-
const externalButton = canvas.getByTestId('external-button');
|
|
453
|
-
await userEvent.click(externalButton);
|
|
454
|
-
await waitFor(() => expect(externalButton).toHaveFocus());
|
|
455
|
-
|
|
456
|
-
// Test tab into list
|
|
457
|
-
await userEvent.tab();
|
|
458
|
-
const firstItem = canvas.getByTestId('focus-item-0');
|
|
459
|
-
await waitFor(() => {
|
|
460
|
-
expect(firstItem).toHaveFocus();
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
// Test focus preservation during scroll
|
|
464
|
-
const list = canvas.getByTestId('focus-list');
|
|
465
|
-
fireEvent.scroll(list, { target: { scrollTop: 200 } });
|
|
466
|
-
|
|
467
|
-
// Focus should remain stable
|
|
468
|
-
await waitFor(() => {
|
|
469
|
-
const focusedElement = canvasElement.ownerDocument.activeElement;
|
|
470
|
-
expect(focusedElement?.getAttribute('data-testid')).toContain('focus-item-');
|
|
471
|
-
});
|
|
472
|
-
},
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
// Responsive Design Tests
|
|
476
|
-
export const ResponsiveDesign: Story = {
|
|
477
|
-
render: () => {
|
|
478
|
-
const items = generateItems(100);
|
|
479
|
-
|
|
480
|
-
return (
|
|
481
|
-
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, width: '100%' }}>
|
|
482
|
-
{/* Mobile viewport */}
|
|
483
|
-
<Paper sx={{ width: 320, height: 200 }}>
|
|
484
|
-
<Typography variant="caption" sx={{ p: 1, display: 'block' }}>
|
|
485
|
-
Mobile (320px)
|
|
486
|
-
</Typography>
|
|
487
|
-
<VirtualList
|
|
488
|
-
items={items}
|
|
489
|
-
variant="fixed"
|
|
490
|
-
height={180}
|
|
491
|
-
width={320}
|
|
492
|
-
itemHeight={50}
|
|
493
|
-
renderItem={({ item, style }) => (
|
|
494
|
-
<Box key={item.id} style={style}>
|
|
495
|
-
<ListItem sx={{ px: 1 }}>
|
|
496
|
-
<ListItemAvatar>
|
|
497
|
-
<Avatar src={item.data.avatar} sx={{ width: 32, height: 32 }} />
|
|
498
|
-
</ListItemAvatar>
|
|
499
|
-
<ListItemText
|
|
500
|
-
primary={
|
|
501
|
-
<Typography variant="body2" noWrap>
|
|
502
|
-
{item.data.name}
|
|
503
|
-
</Typography>
|
|
504
|
-
}
|
|
505
|
-
/>
|
|
506
|
-
</ListItem>
|
|
507
|
-
</Box>
|
|
508
|
-
)}
|
|
509
|
-
data-testid="mobile-list"
|
|
510
|
-
/>
|
|
511
|
-
</Paper>
|
|
512
|
-
|
|
513
|
-
{/* Desktop viewport */}
|
|
514
|
-
<Paper sx={{ width: 800, height: 200 }}>
|
|
515
|
-
<Typography variant="caption" sx={{ p: 1, display: 'block' }}>
|
|
516
|
-
Desktop (800px)
|
|
517
|
-
</Typography>
|
|
518
|
-
<VirtualList
|
|
519
|
-
items={items}
|
|
520
|
-
variant="fixed"
|
|
521
|
-
height={180}
|
|
522
|
-
width={800}
|
|
523
|
-
itemHeight={60}
|
|
524
|
-
renderItem={({ item, style }) => (
|
|
525
|
-
<Box key={item.id} style={style}>
|
|
526
|
-
<ListItem sx={{ px: 3 }}>
|
|
527
|
-
<ListItemAvatar>
|
|
528
|
-
<Avatar src={item.data.avatar} sx={{ width: 48, height: 48 }} />
|
|
529
|
-
</ListItemAvatar>
|
|
530
|
-
<ListItemText primary={item.data.name} secondary={item.data.description} />
|
|
531
|
-
</ListItem>
|
|
532
|
-
</Box>
|
|
533
|
-
)}
|
|
534
|
-
data-testid="desktop-list"
|
|
535
|
-
/>
|
|
536
|
-
</Paper>
|
|
537
|
-
</Box>
|
|
538
|
-
);
|
|
539
|
-
},
|
|
540
|
-
play: async ({ canvasElement }) => {
|
|
541
|
-
const canvas = within(canvasElement);
|
|
542
|
-
|
|
543
|
-
// Test mobile list
|
|
544
|
-
const mobileList = canvas.getByTestId('mobile-list');
|
|
545
|
-
await expect(mobileList).toBeInTheDocument();
|
|
546
|
-
|
|
547
|
-
// Check that mobile list container exists
|
|
548
|
-
const mobileContainer = mobileList.closest(
|
|
549
|
-
'[role="list"], .virtual-list, [data-testid="mobile-list"]',
|
|
550
|
-
);
|
|
551
|
-
expect(mobileContainer).toBeInTheDocument();
|
|
552
|
-
|
|
553
|
-
// Test desktop list
|
|
554
|
-
const desktopList = canvas.getByTestId('desktop-list');
|
|
555
|
-
await expect(desktopList).toBeInTheDocument();
|
|
556
|
-
|
|
557
|
-
// Check that desktop list container exists
|
|
558
|
-
const desktopContainer = desktopList.closest(
|
|
559
|
-
'[role="list"], .virtual-list, [data-testid="desktop-list"]',
|
|
560
|
-
);
|
|
561
|
-
expect(desktopContainer).toBeInTheDocument();
|
|
562
|
-
|
|
563
|
-
// Test that both lists have content (look for any rendered items)
|
|
564
|
-
await waitFor(
|
|
565
|
-
() => {
|
|
566
|
-
expect(mobileList.children.length).toBeGreaterThan(0);
|
|
567
|
-
},
|
|
568
|
-
{ timeout: 2000 },
|
|
569
|
-
);
|
|
570
|
-
|
|
571
|
-
await waitFor(
|
|
572
|
-
() => {
|
|
573
|
-
expect(desktopList.children.length).toBeGreaterThan(0);
|
|
574
|
-
},
|
|
575
|
-
{ timeout: 2000 },
|
|
576
|
-
);
|
|
577
|
-
},
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
// Theme Variations Tests
|
|
581
|
-
export const ThemeVariations: Story = {
|
|
582
|
-
render: () => {
|
|
583
|
-
const items = generateItems(50);
|
|
584
|
-
|
|
585
|
-
return (
|
|
586
|
-
<Box sx={{ display: 'flex', gap: 2 }}>
|
|
587
|
-
{/* Light theme */}
|
|
588
|
-
<Paper sx={{ width: 300, height: 250, bgcolor: 'background.paper' }}>
|
|
589
|
-
<Typography variant="caption" sx={{ p: 1, display: 'block' }}>
|
|
590
|
-
Light Theme
|
|
591
|
-
</Typography>
|
|
592
|
-
<VirtualList
|
|
593
|
-
items={items}
|
|
594
|
-
variant="fixed"
|
|
595
|
-
height={220}
|
|
596
|
-
itemHeight={55}
|
|
597
|
-
renderItem={({ item, style }) => (
|
|
598
|
-
<Box key={item.id} style={style}>
|
|
599
|
-
<ListItem sx={{ borderBottom: '1px solid', borderColor: 'divider' }}>
|
|
600
|
-
<ListItemAvatar>
|
|
601
|
-
<Avatar src={item.data.avatar} />
|
|
602
|
-
</ListItemAvatar>
|
|
603
|
-
<ListItemText
|
|
604
|
-
primary={<Typography color="text.primary">{item.data.name}</Typography>}
|
|
605
|
-
secondary={
|
|
606
|
-
<Typography color="text.secondary" variant="body2">
|
|
607
|
-
Light theme item
|
|
608
|
-
</Typography>
|
|
609
|
-
}
|
|
610
|
-
/>
|
|
611
|
-
</ListItem>
|
|
612
|
-
</Box>
|
|
613
|
-
)}
|
|
614
|
-
data-testid="light-theme-list"
|
|
615
|
-
/>
|
|
616
|
-
</Paper>
|
|
617
|
-
|
|
618
|
-
{/* Dark theme simulation */}
|
|
619
|
-
<Paper sx={{ width: 300, height: 250, bgcolor: '#1e1e1e', color: 'white' }}>
|
|
620
|
-
<Typography variant="caption" sx={{ p: 1, display: 'block', color: 'white' }}>
|
|
621
|
-
Dark Theme
|
|
622
|
-
</Typography>
|
|
623
|
-
<VirtualList
|
|
624
|
-
items={items}
|
|
625
|
-
variant="fixed"
|
|
626
|
-
height={220}
|
|
627
|
-
itemHeight={55}
|
|
628
|
-
renderItem={({ item, style }) => (
|
|
629
|
-
<Box key={item.id} style={style}>
|
|
630
|
-
<ListItem sx={{ borderBottom: '1px solid rgba(255,255,255,0.12)', color: 'white' }}>
|
|
631
|
-
<ListItemAvatar>
|
|
632
|
-
<Avatar src={item.data.avatar} />
|
|
633
|
-
</ListItemAvatar>
|
|
634
|
-
<ListItemText
|
|
635
|
-
primary={<Typography sx={{ color: 'white' }}>{item.data.name}</Typography>}
|
|
636
|
-
secondary={
|
|
637
|
-
<Typography sx={{ color: 'rgba(255,255,255,0.7)' }} variant="body2">
|
|
638
|
-
Dark theme item
|
|
639
|
-
</Typography>
|
|
640
|
-
}
|
|
641
|
-
/>
|
|
642
|
-
</ListItem>
|
|
643
|
-
</Box>
|
|
644
|
-
)}
|
|
645
|
-
data-testid="dark-theme-list"
|
|
646
|
-
/>
|
|
647
|
-
</Paper>
|
|
648
|
-
</Box>
|
|
649
|
-
);
|
|
650
|
-
},
|
|
651
|
-
play: async ({ canvasElement }) => {
|
|
652
|
-
const canvas = within(canvasElement);
|
|
653
|
-
|
|
654
|
-
// Test light theme list
|
|
655
|
-
const lightList = canvas.getByTestId('light-theme-list');
|
|
656
|
-
await expect(lightList).toBeInTheDocument();
|
|
657
|
-
|
|
658
|
-
// Test dark theme list
|
|
659
|
-
const darkList = canvas.getByTestId('dark-theme-list');
|
|
660
|
-
await expect(darkList).toBeInTheDocument();
|
|
661
|
-
|
|
662
|
-
// Verify both lists render items
|
|
663
|
-
await waitFor(
|
|
664
|
-
() => {
|
|
665
|
-
expect(lightList.children.length).toBeGreaterThan(0);
|
|
666
|
-
},
|
|
667
|
-
{ timeout: 2000 },
|
|
668
|
-
);
|
|
669
|
-
|
|
670
|
-
await waitFor(
|
|
671
|
-
() => {
|
|
672
|
-
expect(darkList.children.length).toBeGreaterThan(0);
|
|
673
|
-
},
|
|
674
|
-
{ timeout: 2000 },
|
|
675
|
-
);
|
|
676
|
-
},
|
|
677
|
-
};
|
|
678
|
-
|
|
679
|
-
// Visual States Tests
|
|
680
|
-
export const VisualStates: Story = {
|
|
681
|
-
render: () => {
|
|
682
|
-
const emptyItems: VirtualListItem[] = [];
|
|
683
|
-
|
|
684
|
-
return (
|
|
685
|
-
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
|
|
686
|
-
{/* Loading state */}
|
|
687
|
-
<Paper sx={{ width: 250, height: 200 }}>
|
|
688
|
-
<Typography variant="caption" sx={{ p: 1, display: 'block' }}>
|
|
689
|
-
Loading State
|
|
690
|
-
</Typography>
|
|
691
|
-
<VirtualList
|
|
692
|
-
items={Array.from({ length: 5 }, (_, i) => ({ id: i }))}
|
|
693
|
-
variant="fixed"
|
|
694
|
-
height={170}
|
|
695
|
-
itemHeight={40}
|
|
696
|
-
renderItem={({ style, index }) => (
|
|
697
|
-
<Box key={index} style={style} sx={{ p: 1 }}>
|
|
698
|
-
<Skeleton variant="rectangular" width="100%" height={30} />
|
|
699
|
-
</Box>
|
|
700
|
-
)}
|
|
701
|
-
data-testid="loading-list"
|
|
702
|
-
/>
|
|
703
|
-
</Paper>
|
|
704
|
-
|
|
705
|
-
{/* Empty state */}
|
|
706
|
-
<Paper sx={{ width: 250, height: 200 }}>
|
|
707
|
-
<Typography variant="caption" sx={{ p: 1, display: 'block' }}>
|
|
708
|
-
Empty State
|
|
709
|
-
</Typography>
|
|
710
|
-
<VirtualList
|
|
711
|
-
items={emptyItems}
|
|
712
|
-
variant="fixed"
|
|
713
|
-
height={170}
|
|
714
|
-
itemHeight={40}
|
|
715
|
-
renderItem={({ item, style }) => (
|
|
716
|
-
<Box key={item.id} style={style}>
|
|
717
|
-
<ListItem>
|
|
718
|
-
<ListItemText primary={item.data?.name} />
|
|
719
|
-
</ListItem>
|
|
720
|
-
</Box>
|
|
721
|
-
)}
|
|
722
|
-
data-testid="empty-list"
|
|
723
|
-
/>
|
|
724
|
-
{emptyItems.length === 0 && (
|
|
725
|
-
<Box sx={{ p: 2, textAlign: 'center' }}>
|
|
726
|
-
<Typography variant="body2" color="text.secondary">
|
|
727
|
-
No items to display
|
|
728
|
-
</Typography>
|
|
729
|
-
</Box>
|
|
730
|
-
)}
|
|
731
|
-
</Paper>
|
|
732
|
-
|
|
733
|
-
{/* Error state */}
|
|
734
|
-
<Paper sx={{ width: 250, height: 200 }}>
|
|
735
|
-
<Typography variant="caption" sx={{ p: 1, display: 'block' }}>
|
|
736
|
-
Error State
|
|
737
|
-
</Typography>
|
|
738
|
-
<Alert severity="error" sx={{ m: 1 }}>
|
|
739
|
-
Failed to load data
|
|
740
|
-
</Alert>
|
|
741
|
-
</Paper>
|
|
742
|
-
</Box>
|
|
743
|
-
);
|
|
744
|
-
},
|
|
745
|
-
play: async ({ canvasElement }) => {
|
|
746
|
-
const canvas = within(canvasElement);
|
|
747
|
-
|
|
748
|
-
// Test loading state
|
|
749
|
-
const loadingList = canvas.getByTestId('loading-list');
|
|
750
|
-
await expect(loadingList).toBeInTheDocument();
|
|
751
|
-
|
|
752
|
-
// Test skeleton loaders are present (wait for them to render)
|
|
753
|
-
await waitFor(
|
|
754
|
-
() => {
|
|
755
|
-
const skeletons = canvasElement.querySelectorAll('.MuiSkeleton-root');
|
|
756
|
-
expect(skeletons.length).toBeGreaterThan(0);
|
|
757
|
-
},
|
|
758
|
-
{ timeout: 2000 },
|
|
759
|
-
);
|
|
760
|
-
|
|
761
|
-
// Test empty state
|
|
762
|
-
const emptyList = canvas.getByTestId('empty-list');
|
|
763
|
-
await expect(emptyList).toBeInTheDocument();
|
|
764
|
-
|
|
765
|
-
// Test empty message
|
|
766
|
-
const emptyMessage = canvas.getByText('No items to display');
|
|
767
|
-
await expect(emptyMessage).toBeInTheDocument();
|
|
768
|
-
|
|
769
|
-
// Test error state
|
|
770
|
-
const errorAlert = canvas.getByRole('alert');
|
|
771
|
-
await expect(errorAlert).toBeInTheDocument();
|
|
772
|
-
},
|
|
773
|
-
};
|
|
774
|
-
|
|
775
|
-
// Performance Tests
|
|
776
|
-
export const Performance: Story = {
|
|
777
|
-
render: () => {
|
|
778
|
-
const largeDataset = generateItems(10000);
|
|
779
|
-
|
|
780
|
-
return (
|
|
781
|
-
<Paper sx={{ width: 400, height: 400 }}>
|
|
782
|
-
<Box sx={{ p: 2, borderBottom: '1px solid', borderColor: 'divider' }}>
|
|
783
|
-
<Typography variant="h6">Performance Test</Typography>
|
|
784
|
-
<Typography variant="body2" color="text.secondary">
|
|
785
|
-
10,000 items - Virtual scrolling
|
|
786
|
-
</Typography>
|
|
787
|
-
</Box>
|
|
788
|
-
<VirtualList
|
|
789
|
-
items={largeDataset}
|
|
790
|
-
variant="fixed"
|
|
791
|
-
height={350}
|
|
792
|
-
itemHeight={50}
|
|
793
|
-
overscan={10}
|
|
794
|
-
renderItem={({ item, index, style }) => {
|
|
795
|
-
// Simulate some computation
|
|
796
|
-
const computedValue = Math.sin(index) * 100;
|
|
797
|
-
|
|
798
|
-
return (
|
|
799
|
-
<Box key={item.id} style={style}>
|
|
800
|
-
<ListItem>
|
|
801
|
-
<ListItemAvatar>
|
|
802
|
-
<Avatar>{index + 1}</Avatar>
|
|
803
|
-
</ListItemAvatar>
|
|
804
|
-
<ListItemText
|
|
805
|
-
primary={`Item ${index + 1}`}
|
|
806
|
-
secondary={`Computed: ${computedValue.toFixed(2)}`}
|
|
807
|
-
/>
|
|
808
|
-
</ListItem>
|
|
809
|
-
</Box>
|
|
810
|
-
);
|
|
811
|
-
}}
|
|
812
|
-
data-testid="performance-list"
|
|
813
|
-
/>
|
|
814
|
-
</Paper>
|
|
815
|
-
);
|
|
816
|
-
},
|
|
817
|
-
play: async ({ canvasElement }) => {
|
|
818
|
-
const canvas = within(canvasElement);
|
|
819
|
-
const performanceList = canvas.getByTestId('performance-list');
|
|
820
|
-
|
|
821
|
-
// Test that performance list renders correctly
|
|
822
|
-
await expect(performanceList).toBeInTheDocument();
|
|
823
|
-
|
|
824
|
-
// Test rapid scrolling performance
|
|
825
|
-
// Perform scrolling with proper event simulation
|
|
826
|
-
fireEvent.scroll(performanceList, { target: { scrollTop: 500 } });
|
|
827
|
-
await waitFor(
|
|
828
|
-
() => {
|
|
829
|
-
expect(firstRenderedIndex(performanceList)).toBeGreaterThan(0);
|
|
830
|
-
},
|
|
831
|
-
{ timeout: 2000 },
|
|
832
|
-
);
|
|
833
|
-
|
|
834
|
-
// Test multiple scroll events
|
|
835
|
-
for (let i = 1; i <= 3; i++) {
|
|
836
|
-
fireEvent.scroll(performanceList, { target: { scrollTop: i * 200 } });
|
|
837
|
-
await waitFor(() => expect(performanceList).toBeInTheDocument());
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// Verify list is still responsive
|
|
841
|
-
await expect(performanceList).toBeInTheDocument();
|
|
842
|
-
await waitFor(
|
|
843
|
-
() => {
|
|
844
|
-
expect(firstRenderedIndex(performanceList)).toBeGreaterThan(0);
|
|
845
|
-
},
|
|
846
|
-
{ timeout: 1000 },
|
|
847
|
-
);
|
|
848
|
-
},
|
|
849
|
-
};
|
|
850
|
-
|
|
851
|
-
// Edge Cases Tests
|
|
852
|
-
export const EdgeCases: Story = {
|
|
853
|
-
render: () => {
|
|
854
|
-
const singleItem = generateItems(1);
|
|
855
|
-
const variableItems = generateVariableItems(20);
|
|
856
|
-
const emptyItems: VirtualListItem[] = [];
|
|
857
|
-
|
|
858
|
-
return (
|
|
859
|
-
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
|
|
860
|
-
{/* Single item */}
|
|
861
|
-
<Paper sx={{ width: 200, height: 150 }}>
|
|
862
|
-
<Typography variant="caption" sx={{ p: 1, display: 'block' }}>
|
|
863
|
-
Single Item
|
|
864
|
-
</Typography>
|
|
865
|
-
<VirtualList
|
|
866
|
-
items={singleItem}
|
|
867
|
-
variant="fixed"
|
|
868
|
-
height={120}
|
|
869
|
-
itemHeight={50}
|
|
870
|
-
renderItem={SimpleItemRenderer}
|
|
871
|
-
data-testid="single-item-list"
|
|
872
|
-
/>
|
|
873
|
-
</Paper>
|
|
874
|
-
|
|
875
|
-
{/* Variable heights */}
|
|
876
|
-
<Paper sx={{ width: 250, height: 150 }}>
|
|
877
|
-
<Typography variant="caption" sx={{ p: 1, display: 'block' }}>
|
|
878
|
-
Variable Heights
|
|
879
|
-
</Typography>
|
|
880
|
-
<VirtualList
|
|
881
|
-
items={variableItems}
|
|
882
|
-
variant="variable"
|
|
883
|
-
height={120}
|
|
884
|
-
estimatedItemHeight={80}
|
|
885
|
-
renderItem={({ item, style }) => (
|
|
886
|
-
<Box key={item.id} style={style}>
|
|
887
|
-
<ListItem sx={{ alignItems: 'flex-start' }}>
|
|
888
|
-
<ListItemAvatar>
|
|
889
|
-
<Avatar src={item.data.avatar} />
|
|
890
|
-
</ListItemAvatar>
|
|
891
|
-
<ListItemText primary={item.data.name} secondary={item.data.description} />
|
|
892
|
-
</ListItem>
|
|
893
|
-
</Box>
|
|
894
|
-
)}
|
|
895
|
-
data-testid="variable-height-list"
|
|
896
|
-
/>
|
|
897
|
-
</Paper>
|
|
898
|
-
|
|
899
|
-
{/* Zero items */}
|
|
900
|
-
<Paper sx={{ width: 200, height: 150 }}>
|
|
901
|
-
<Typography variant="caption" sx={{ p: 1, display: 'block' }}>
|
|
902
|
-
Zero Items
|
|
903
|
-
</Typography>
|
|
904
|
-
<VirtualList
|
|
905
|
-
items={emptyItems}
|
|
906
|
-
variant="fixed"
|
|
907
|
-
height={120}
|
|
908
|
-
itemHeight={50}
|
|
909
|
-
renderItem={SimpleItemRenderer}
|
|
910
|
-
data-testid="zero-items-list"
|
|
911
|
-
/>
|
|
912
|
-
</Paper>
|
|
913
|
-
</Box>
|
|
914
|
-
);
|
|
915
|
-
},
|
|
916
|
-
play: async ({ canvasElement }) => {
|
|
917
|
-
const canvas = within(canvasElement);
|
|
918
|
-
|
|
919
|
-
// Test single item
|
|
920
|
-
const singleItemList = canvas.getByTestId('single-item-list');
|
|
921
|
-
await expect(singleItemList).toBeInTheDocument();
|
|
922
|
-
|
|
923
|
-
// Wait for item to render
|
|
924
|
-
await waitFor(
|
|
925
|
-
() => {
|
|
926
|
-
expect(singleItemList.children.length).toBeGreaterThan(0);
|
|
927
|
-
},
|
|
928
|
-
{ timeout: 2000 },
|
|
929
|
-
);
|
|
930
|
-
|
|
931
|
-
// Test variable heights
|
|
932
|
-
const variableHeightList = canvas.getByTestId('variable-height-list');
|
|
933
|
-
await expect(variableHeightList).toBeInTheDocument();
|
|
934
|
-
|
|
935
|
-
// Wait for items to render before testing scroll
|
|
936
|
-
await waitFor(
|
|
937
|
-
() => {
|
|
938
|
-
expect(variableHeightList.children.length).toBeGreaterThan(0);
|
|
939
|
-
},
|
|
940
|
-
{ timeout: 2000 },
|
|
941
|
-
);
|
|
942
|
-
|
|
943
|
-
// Test scrolling with variable heights
|
|
944
|
-
fireEvent.scroll(variableHeightList, { target: { scrollTop: 100 } });
|
|
945
|
-
await waitFor(
|
|
946
|
-
() => {
|
|
947
|
-
expect(firstRenderedIndex(variableHeightList)).toBeGreaterThan(0);
|
|
948
|
-
},
|
|
949
|
-
{ timeout: 1000 },
|
|
950
|
-
);
|
|
951
|
-
|
|
952
|
-
// Test zero items
|
|
953
|
-
const zeroItemsList = canvas.getByTestId('zero-items-list');
|
|
954
|
-
await expect(zeroItemsList).toBeInTheDocument();
|
|
955
|
-
|
|
956
|
-
// Zero items list should have minimal or no child elements (it might have a container div)
|
|
957
|
-
// The inner container for virtualization might still be present even with no items
|
|
958
|
-
expect(zeroItemsList.children.length).toBeLessThanOrEqual(1);
|
|
959
|
-
},
|
|
960
|
-
};
|
|
961
|
-
|
|
962
|
-
// Integration Tests
|
|
963
|
-
export const Integration: Story = {
|
|
964
|
-
render: () => {
|
|
965
|
-
const items = generateItems(100);
|
|
966
|
-
const gridItems = generateItems(50);
|
|
967
|
-
|
|
968
|
-
return (
|
|
969
|
-
<Box sx={{ display: 'flex', gap: 2 }}>
|
|
970
|
-
{/* List + Grid Integration */}
|
|
971
|
-
<Paper sx={{ width: 300, height: 400 }}>
|
|
972
|
-
<Typography variant="h6" sx={{ p: 2 }}>
|
|
973
|
-
List View
|
|
974
|
-
</Typography>
|
|
975
|
-
<VirtualList
|
|
976
|
-
items={items}
|
|
977
|
-
variant="fixed"
|
|
978
|
-
height={350}
|
|
979
|
-
itemHeight={60}
|
|
980
|
-
renderItem={({ item, index, style }) => (
|
|
981
|
-
<Box key={item.id} style={style}>
|
|
982
|
-
<ListItem
|
|
983
|
-
button
|
|
984
|
-
onClick={() => {
|
|
985
|
-
}}
|
|
986
|
-
>
|
|
987
|
-
<ListItemAvatar>
|
|
988
|
-
<Avatar src={item.data.avatar} />
|
|
989
|
-
</ListItemAvatar>
|
|
990
|
-
<ListItemText primary={item.data.name} secondary={`List item ${index + 1}`} />
|
|
991
|
-
</ListItem>
|
|
992
|
-
</Box>
|
|
993
|
-
)}
|
|
994
|
-
data-testid="integration-list"
|
|
995
|
-
/>
|
|
996
|
-
</Paper>
|
|
997
|
-
|
|
998
|
-
<Paper sx={{ width: 400, height: 400 }}>
|
|
999
|
-
<Typography variant="h6" sx={{ p: 2 }}>
|
|
1000
|
-
Grid View
|
|
1001
|
-
</Typography>
|
|
1002
|
-
<VirtualGrid
|
|
1003
|
-
items={gridItems}
|
|
1004
|
-
height={350}
|
|
1005
|
-
width={400}
|
|
1006
|
-
columnCount={3}
|
|
1007
|
-
rowHeight={100}
|
|
1008
|
-
gap={8}
|
|
1009
|
-
renderItem={({ item, style }) => (
|
|
1010
|
-
<Box
|
|
1011
|
-
key={item.id}
|
|
1012
|
-
style={style}
|
|
1013
|
-
sx={{
|
|
1014
|
-
bgcolor: 'primary.light',
|
|
1015
|
-
borderRadius: 2,
|
|
1016
|
-
display: 'flex',
|
|
1017
|
-
alignItems: 'center',
|
|
1018
|
-
justifyContent: 'center',
|
|
1019
|
-
cursor: 'pointer',
|
|
1020
|
-
'&:hover': {
|
|
1021
|
-
bgcolor: 'primary.main',
|
|
1022
|
-
},
|
|
1023
|
-
}}
|
|
1024
|
-
onClick={() => {
|
|
1025
|
-
}}
|
|
1026
|
-
>
|
|
1027
|
-
<Typography variant="body2" color="white">
|
|
1028
|
-
#{item.id + 1}
|
|
1029
|
-
</Typography>
|
|
1030
|
-
</Box>
|
|
1031
|
-
)}
|
|
1032
|
-
data-testid="integration-grid"
|
|
1033
|
-
/>
|
|
1034
|
-
</Paper>
|
|
1035
|
-
</Box>
|
|
1036
|
-
);
|
|
1037
|
-
},
|
|
1038
|
-
play: async ({ canvasElement }) => {
|
|
1039
|
-
const canvas = within(canvasElement);
|
|
1040
|
-
|
|
1041
|
-
// Test list integration
|
|
1042
|
-
const integrationList = canvas.getByTestId('integration-list');
|
|
1043
|
-
await expect(integrationList).toBeInTheDocument();
|
|
1044
|
-
|
|
1045
|
-
// Test grid integration
|
|
1046
|
-
const integrationGrid = canvas.getByTestId('integration-grid');
|
|
1047
|
-
await expect(integrationGrid).toBeInTheDocument();
|
|
1048
|
-
|
|
1049
|
-
// Wait for items to render
|
|
1050
|
-
await waitFor(
|
|
1051
|
-
() => {
|
|
1052
|
-
expect(integrationList.children.length).toBeGreaterThan(0);
|
|
1053
|
-
},
|
|
1054
|
-
{ timeout: 2000 },
|
|
1055
|
-
);
|
|
1056
|
-
|
|
1057
|
-
await waitFor(
|
|
1058
|
-
() => {
|
|
1059
|
-
expect(integrationGrid.children.length).toBeGreaterThan(0);
|
|
1060
|
-
},
|
|
1061
|
-
{ timeout: 2000 },
|
|
1062
|
-
);
|
|
1063
|
-
|
|
1064
|
-
// Test clicking on first visible list item
|
|
1065
|
-
const firstListItem = integrationList.querySelector('div[style*="position"]');
|
|
1066
|
-
if (firstListItem) {
|
|
1067
|
-
await userEvent.click(firstListItem);
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
// Test clicking on first visible grid item
|
|
1071
|
-
const firstGridItem = integrationGrid.querySelector('div[style*="position"]');
|
|
1072
|
-
if (firstGridItem) {
|
|
1073
|
-
await userEvent.click(firstGridItem);
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
// Test that both components work independently
|
|
1077
|
-
fireEvent.scroll(integrationList, { target: { scrollTop: 200 } });
|
|
1078
|
-
fireEvent.scroll(integrationGrid, { target: { scrollTop: 200 } });
|
|
1079
|
-
|
|
1080
|
-
await waitFor(() => {
|
|
1081
|
-
expect(firstRenderedIndex(integrationList)).toBeGreaterThan(0);
|
|
1082
|
-
expect(firstRenderedIndex(integrationGrid)).toBeGreaterThan(0);
|
|
1083
|
-
});
|
|
1084
|
-
},
|
|
1085
|
-
};
|