@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,915 +0,0 @@
|
|
|
1
|
-
import InfoIcon from '@mui/icons-material/Info';
|
|
2
|
-
import { Box, Stack,TextField } from '@mui/material';
|
|
3
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
4
|
-
import React from 'react';
|
|
5
|
-
import { expect, fireEvent, fn,userEvent, waitFor, within } from 'storybook/test';
|
|
6
|
-
|
|
7
|
-
import { Label } from './Label';
|
|
8
|
-
|
|
9
|
-
const meta: Meta<typeof Label> = {
|
|
10
|
-
title: 'Form/Label/Tests',
|
|
11
|
-
component: Label,
|
|
12
|
-
parameters: {
|
|
13
|
-
layout: 'centered',
|
|
14
|
-
chromatic: { disableSnapshot: false },
|
|
15
|
-
},
|
|
16
|
-
tags: ['autodocs', 'test', 'component:Label'],
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export default meta;
|
|
20
|
-
type Story = StoryObj<typeof meta>;
|
|
21
|
-
|
|
22
|
-
// ====================================
|
|
23
|
-
// TestId Tests
|
|
24
|
-
// ====================================
|
|
25
|
-
|
|
26
|
-
export const DataTestIdVerification: Story = {
|
|
27
|
-
name: '🔍 Data TestId Verification',
|
|
28
|
-
args: {
|
|
29
|
-
children: 'Test Label',
|
|
30
|
-
dataTestId: 'custom-label-testid',
|
|
31
|
-
},
|
|
32
|
-
play: async ({ canvasElement, step }) => {
|
|
33
|
-
const canvas = within(canvasElement);
|
|
34
|
-
|
|
35
|
-
await step('Verify dataTestId prop applies to label element', async () => {
|
|
36
|
-
const label = canvas.getByTestId('custom-label-testid');
|
|
37
|
-
await expect(label).toBeInTheDocument();
|
|
38
|
-
await expect(label.tagName.toLowerCase()).toBe('label');
|
|
39
|
-
await expect(label).toHaveTextContent('Test Label');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
await step('Verify dataTestId works with all variants', async () => {
|
|
43
|
-
const label = canvas.getByTestId('custom-label-testid');
|
|
44
|
-
await expect(label).toHaveAttribute('data-testid', 'custom-label-testid');
|
|
45
|
-
});
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export const DataTestIdWithSrOnly: Story = {
|
|
50
|
-
name: '🔍 Data TestId with Screen Reader Only',
|
|
51
|
-
args: {
|
|
52
|
-
children: 'SR Only Label',
|
|
53
|
-
dataTestId: 'sr-only-testid',
|
|
54
|
-
srOnly: true,
|
|
55
|
-
},
|
|
56
|
-
play: async ({ canvasElement, step }) => {
|
|
57
|
-
const canvas = within(canvasElement);
|
|
58
|
-
|
|
59
|
-
await step('Verify dataTestId applies to srOnly label', async () => {
|
|
60
|
-
const label = canvas.getByTestId('sr-only-testid');
|
|
61
|
-
await expect(label).toBeInTheDocument();
|
|
62
|
-
await expect(label.tagName.toLowerCase()).toBe('label');
|
|
63
|
-
|
|
64
|
-
const computedStyle = window.getComputedStyle(label);
|
|
65
|
-
await expect(computedStyle.position).toBe('absolute');
|
|
66
|
-
await expect(computedStyle.width).toBe('1px');
|
|
67
|
-
await expect(computedStyle.height).toBe('1px');
|
|
68
|
-
});
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// ====================================
|
|
73
|
-
// Interaction Tests
|
|
74
|
-
// ====================================
|
|
75
|
-
|
|
76
|
-
export const BasicInteraction: Story = {
|
|
77
|
-
name: '🧪 Basic Interaction Test',
|
|
78
|
-
args: {
|
|
79
|
-
children: 'Interactive Label',
|
|
80
|
-
htmlFor: 'test-input',
|
|
81
|
-
onClick: fn(),
|
|
82
|
-
'data-testid': 'interactive-label',
|
|
83
|
-
},
|
|
84
|
-
play: async ({ canvasElement, step, args }) => {
|
|
85
|
-
const canvas = within(canvasElement);
|
|
86
|
-
|
|
87
|
-
await step('Initial render verification', async () => {
|
|
88
|
-
const label = canvas.getByTestId('interactive-label');
|
|
89
|
-
await expect(label).toBeInTheDocument();
|
|
90
|
-
await expect(label).toHaveTextContent('Interactive Label');
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
await step('Click interaction', async () => {
|
|
94
|
-
const label = canvas.getByTestId('interactive-label');
|
|
95
|
-
await userEvent.click(label);
|
|
96
|
-
await expect(args.onClick).toHaveBeenCalledTimes(1);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
await step('Hover interaction', async () => {
|
|
100
|
-
const label = canvas.getByTestId('interactive-label');
|
|
101
|
-
await userEvent.hover(label);
|
|
102
|
-
// Visual state should change (verified through visual regression)
|
|
103
|
-
await userEvent.unhover(label);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
await step('Verify htmlFor attribute', async () => {
|
|
107
|
-
const label = canvas.getByTestId('interactive-label');
|
|
108
|
-
await expect(label).toHaveAttribute('for', 'test-input');
|
|
109
|
-
});
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export const RequiredFieldTest: Story = {
|
|
114
|
-
name: '📝 Required Field Test',
|
|
115
|
-
args: {
|
|
116
|
-
children: 'Required Field',
|
|
117
|
-
required: true,
|
|
118
|
-
asteriskPlacement: 'end',
|
|
119
|
-
'data-testid': 'required-label',
|
|
120
|
-
},
|
|
121
|
-
play: async ({ canvasElement, step }) => {
|
|
122
|
-
const canvas = within(canvasElement);
|
|
123
|
-
|
|
124
|
-
await step('Verify required asterisk is present', async () => {
|
|
125
|
-
const label = canvas.getByTestId('required-label');
|
|
126
|
-
await expect(label).toBeInTheDocument();
|
|
127
|
-
|
|
128
|
-
// Check for asterisk
|
|
129
|
-
const asterisk = within(label).getByText('*');
|
|
130
|
-
await expect(asterisk).toBeInTheDocument();
|
|
131
|
-
|
|
132
|
-
// Verify asterisk color (should be error color)
|
|
133
|
-
const computedStyle = window.getComputedStyle(asterisk);
|
|
134
|
-
await expect(computedStyle.color).toContain('rgb');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
await step('Test asterisk placement at start', async () => {
|
|
138
|
-
// This would require prop change - verified through separate story
|
|
139
|
-
const label = canvas.getByTestId('required-label');
|
|
140
|
-
await expect(label.textContent).toContain('*');
|
|
141
|
-
});
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
export const StateChangeTest: Story = {
|
|
146
|
-
name: '🔄 State Change Test',
|
|
147
|
-
render: function StateChangeComponent() {
|
|
148
|
-
const [state, setState] = React.useState<'normal' | 'disabled' | 'error' | 'loading'>('normal');
|
|
149
|
-
|
|
150
|
-
return (
|
|
151
|
-
<Stack spacing={2}>
|
|
152
|
-
<Label
|
|
153
|
-
disabled={state === 'disabled'}
|
|
154
|
-
error={state === 'error'}
|
|
155
|
-
loading={state === 'loading'}
|
|
156
|
-
data-testid="stateful-label"
|
|
157
|
-
>
|
|
158
|
-
Stateful Label
|
|
159
|
-
</Label>
|
|
160
|
-
<Stack direction="row" spacing={1}>
|
|
161
|
-
<button onClick={() => setState('normal')}>Normal</button>
|
|
162
|
-
<button onClick={() => setState('disabled')}>Disabled</button>
|
|
163
|
-
<button onClick={() => setState('error')}>Error</button>
|
|
164
|
-
<button onClick={() => setState('loading')}>Loading</button>
|
|
165
|
-
</Stack>
|
|
166
|
-
</Stack>
|
|
167
|
-
);
|
|
168
|
-
},
|
|
169
|
-
play: async ({ canvasElement, step }) => {
|
|
170
|
-
const canvas = within(canvasElement);
|
|
171
|
-
|
|
172
|
-
await step('Verify initial normal state', async () => {
|
|
173
|
-
const label = canvas.getByTestId('stateful-label');
|
|
174
|
-
await expect(label).toBeInTheDocument();
|
|
175
|
-
await expect(label).not.toHaveStyle({ opacity: '0.5' });
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
await step('Change to disabled state', async () => {
|
|
179
|
-
const disabledButton = canvas.getByRole('button', { name: 'Disabled' });
|
|
180
|
-
await userEvent.click(disabledButton);
|
|
181
|
-
|
|
182
|
-
const label = canvas.getByTestId('stateful-label');
|
|
183
|
-
const computedStyle = window.getComputedStyle(label);
|
|
184
|
-
// Disabled state should have different color
|
|
185
|
-
await expect(computedStyle.color).toBeDefined();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
await step('Change to error state', async () => {
|
|
189
|
-
const errorButton = canvas.getByRole('button', { name: 'Error' });
|
|
190
|
-
await userEvent.click(errorButton);
|
|
191
|
-
|
|
192
|
-
const label = canvas.getByTestId('stateful-label');
|
|
193
|
-
const computedStyle = window.getComputedStyle(label);
|
|
194
|
-
// Error state should have error color
|
|
195
|
-
await expect(computedStyle.color).toBeDefined();
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
await step('Change to loading state', async () => {
|
|
199
|
-
const loadingButton = canvas.getByRole('button', { name: 'Loading' });
|
|
200
|
-
await userEvent.click(loadingButton);
|
|
201
|
-
|
|
202
|
-
await waitFor(() => {
|
|
203
|
-
const spinner = canvas.queryByRole('progressbar');
|
|
204
|
-
expect(spinner).toBeInTheDocument();
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
},
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
// ====================================
|
|
211
|
-
// Accessibility Tests
|
|
212
|
-
// ====================================
|
|
213
|
-
|
|
214
|
-
export const KeyboardNavigation: Story = {
|
|
215
|
-
name: '⌨️ Keyboard Navigation Test',
|
|
216
|
-
render: () => (
|
|
217
|
-
<Stack spacing={2}>
|
|
218
|
-
<Label htmlFor="input-1" onClick={fn()} data-testid="first-label" tabIndex={0}>
|
|
219
|
-
First Label
|
|
220
|
-
</Label>
|
|
221
|
-
<TextField id="input-1" data-testid="first-input" />
|
|
222
|
-
|
|
223
|
-
<Label htmlFor="input-2" onClick={fn()} data-testid="second-label" tabIndex={0}>
|
|
224
|
-
Second Label
|
|
225
|
-
</Label>
|
|
226
|
-
<TextField id="input-2" data-testid="second-input" />
|
|
227
|
-
</Stack>
|
|
228
|
-
),
|
|
229
|
-
parameters: {
|
|
230
|
-
a11y: {
|
|
231
|
-
element: '#storybook-root',
|
|
232
|
-
config: {
|
|
233
|
-
rules: [
|
|
234
|
-
{ id: 'color-contrast', enabled: true },
|
|
235
|
-
{ id: 'label', enabled: true },
|
|
236
|
-
{ id: 'aria-valid-attr-value', enabled: true },
|
|
237
|
-
],
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
play: async ({ canvasElement, step }) => {
|
|
242
|
-
const canvas = within(canvasElement);
|
|
243
|
-
|
|
244
|
-
await step('Tab navigation forward', async () => {
|
|
245
|
-
const firstLabel = canvas.getByTestId('first-label');
|
|
246
|
-
const firstInputContainer = canvas.getByTestId('first-input');
|
|
247
|
-
const firstInput = firstInputContainer.querySelector('input');
|
|
248
|
-
|
|
249
|
-
// Focus first label
|
|
250
|
-
await userEvent.click(firstLabel);
|
|
251
|
-
await waitFor(() => expect(firstLabel).toHaveFocus());
|
|
252
|
-
|
|
253
|
-
// Tab to input
|
|
254
|
-
await userEvent.tab();
|
|
255
|
-
await waitFor(() => expect(firstInput).toHaveFocus());
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
await step('Tab navigation backward', async () => {
|
|
259
|
-
await userEvent.tab({ shift: true });
|
|
260
|
-
const firstLabel = canvas.getByTestId('first-label');
|
|
261
|
-
await waitFor(() => expect(firstLabel).toHaveFocus());
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
await step('Enter key activation on clickable label', async () => {
|
|
265
|
-
const firstLabel = canvas.getByTestId('first-label');
|
|
266
|
-
await userEvent.click(firstLabel);
|
|
267
|
-
await userEvent.keyboard('{Enter}');
|
|
268
|
-
// Verify click handler would be called
|
|
269
|
-
});
|
|
270
|
-
},
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
export const ScreenReaderTest: Story = {
|
|
274
|
-
name: '🔊 Screen Reader Test',
|
|
275
|
-
args: {
|
|
276
|
-
children: 'Accessible Label',
|
|
277
|
-
htmlFor: 'accessible-input',
|
|
278
|
-
required: true,
|
|
279
|
-
helperText: 'This field is required',
|
|
280
|
-
tooltip: 'Additional information about this field',
|
|
281
|
-
'aria-label': 'Accessible form label',
|
|
282
|
-
'aria-describedby': 'helper-text',
|
|
283
|
-
'data-testid': 'accessible-label',
|
|
284
|
-
},
|
|
285
|
-
render: (args) => (
|
|
286
|
-
<Stack spacing={1}>
|
|
287
|
-
<Label {...args} />
|
|
288
|
-
<TextField id="accessible-input" aria-describedby="helper-text" />
|
|
289
|
-
<span id="helper-text" style={{ display: 'none' }}>
|
|
290
|
-
This field is required for accessibility
|
|
291
|
-
</span>
|
|
292
|
-
</Stack>
|
|
293
|
-
),
|
|
294
|
-
play: async ({ canvasElement, step }) => {
|
|
295
|
-
const canvas = within(canvasElement);
|
|
296
|
-
|
|
297
|
-
await step('Verify ARIA attributes', async () => {
|
|
298
|
-
const label = canvas.getByTestId('accessible-label');
|
|
299
|
-
await expect(label).toHaveAttribute('aria-label', 'Accessible form label');
|
|
300
|
-
await expect(label).toHaveAttribute('aria-describedby', 'helper-text');
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
await step('Verify htmlFor association', async () => {
|
|
304
|
-
const label = canvas.getByTestId('accessible-label');
|
|
305
|
-
await expect(label.tagName.toLowerCase()).toBe('label');
|
|
306
|
-
await expect(label).toHaveAttribute('for', 'accessible-input');
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
await step('Verify helper text presence', async () => {
|
|
310
|
-
const helperText = canvas.getByText('This field is required');
|
|
311
|
-
await expect(helperText).toBeInTheDocument();
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
await step('Verify screen reader only label', async () => {
|
|
315
|
-
const srDescription = canvas.getByText('This field is required for accessibility');
|
|
316
|
-
await expect(srDescription).toBeInTheDocument();
|
|
317
|
-
await expect(srDescription).toHaveStyle({ display: 'none' });
|
|
318
|
-
});
|
|
319
|
-
},
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
export const ScreenReaderOnlyTest: Story = {
|
|
323
|
-
name: '👁️🗨️ Screen Reader Only Test',
|
|
324
|
-
args: {
|
|
325
|
-
children: 'This label is only for screen readers',
|
|
326
|
-
srOnly: true,
|
|
327
|
-
htmlFor: 'sr-only-input',
|
|
328
|
-
'data-testid': 'sr-only-label',
|
|
329
|
-
},
|
|
330
|
-
render: (args) => (
|
|
331
|
-
<Box>
|
|
332
|
-
<Label {...args} />
|
|
333
|
-
<TextField id="sr-only-input" placeholder="Field with screen reader only label" fullWidth />
|
|
334
|
-
</Box>
|
|
335
|
-
),
|
|
336
|
-
play: async ({ canvasElement, step }) => {
|
|
337
|
-
const canvas = within(canvasElement);
|
|
338
|
-
|
|
339
|
-
await step('Verify label is visually hidden', async () => {
|
|
340
|
-
// The label should be in the document but visually hidden
|
|
341
|
-
const hiddenLabel = canvas.getByText('This label is only for screen readers');
|
|
342
|
-
await expect(hiddenLabel).toBeInTheDocument();
|
|
343
|
-
|
|
344
|
-
// For srOnly labels, the text is inside a label element with special styles
|
|
345
|
-
// Get the parent label element which should have the srOnly styles
|
|
346
|
-
const labelElement = hiddenLabel.parentElement;
|
|
347
|
-
|
|
348
|
-
if (labelElement && labelElement.tagName === 'LABEL') {
|
|
349
|
-
const computedStyle = window.getComputedStyle(labelElement);
|
|
350
|
-
// The srOnly label should be absolutely positioned
|
|
351
|
-
await expect(computedStyle.position).toBe('absolute');
|
|
352
|
-
// Check for visually hidden styles - srOnly uses 1px width/height
|
|
353
|
-
const widthValue = computedStyle.width;
|
|
354
|
-
const heightValue = computedStyle.height;
|
|
355
|
-
// MUI Box with sx might return '1px' as string
|
|
356
|
-
await expect(widthValue).toBe('1px');
|
|
357
|
-
await expect(heightValue).toBe('1px');
|
|
358
|
-
} else {
|
|
359
|
-
// If the direct parent isn't the label, find it
|
|
360
|
-
const label = hiddenLabel.closest('label');
|
|
361
|
-
if (label) {
|
|
362
|
-
const computedStyle = window.getComputedStyle(label);
|
|
363
|
-
await expect(computedStyle.position).toBe('absolute');
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
await step('Verify input association', async () => {
|
|
369
|
-
const input = canvas.getByPlaceholderText('Field with screen reader only label');
|
|
370
|
-
await expect(input).toBeInTheDocument();
|
|
371
|
-
await expect(input).toHaveAttribute('id', 'sr-only-input');
|
|
372
|
-
});
|
|
373
|
-
},
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
// ====================================
|
|
377
|
-
// Visual Tests
|
|
378
|
-
// ====================================
|
|
379
|
-
|
|
380
|
-
export const ResponsiveDesign: Story = {
|
|
381
|
-
name: '📱 Responsive Design Test',
|
|
382
|
-
render: () => (
|
|
383
|
-
<Stack spacing={2} sx={{ width: '100%' }}>
|
|
384
|
-
<Label size="xs" variant="filled">
|
|
385
|
-
Extra Small Label
|
|
386
|
-
</Label>
|
|
387
|
-
<Label size="sm" variant="outlined">
|
|
388
|
-
Small Label
|
|
389
|
-
</Label>
|
|
390
|
-
<Label size="md" variant="glass">
|
|
391
|
-
Medium Label
|
|
392
|
-
</Label>
|
|
393
|
-
<Label size="lg" variant="gradient" color="primary">
|
|
394
|
-
Large Label
|
|
395
|
-
</Label>
|
|
396
|
-
<Label size="xl" variant="default">
|
|
397
|
-
Extra Large Label
|
|
398
|
-
</Label>
|
|
399
|
-
</Stack>
|
|
400
|
-
),
|
|
401
|
-
parameters: {
|
|
402
|
-
viewport: {
|
|
403
|
-
viewports: {
|
|
404
|
-
mobile: {
|
|
405
|
-
name: 'Mobile',
|
|
406
|
-
styles: { width: '375px', height: '667px' },
|
|
407
|
-
type: 'mobile',
|
|
408
|
-
},
|
|
409
|
-
tablet: {
|
|
410
|
-
name: 'Tablet',
|
|
411
|
-
styles: { width: '768px', height: '1024px' },
|
|
412
|
-
type: 'tablet',
|
|
413
|
-
},
|
|
414
|
-
desktop: {
|
|
415
|
-
name: 'Desktop',
|
|
416
|
-
styles: { width: '1920px', height: '1080px' },
|
|
417
|
-
type: 'desktop',
|
|
418
|
-
},
|
|
419
|
-
},
|
|
420
|
-
defaultViewport: 'mobile',
|
|
421
|
-
},
|
|
422
|
-
chromatic: {
|
|
423
|
-
viewports: [375, 768, 1920],
|
|
424
|
-
delay: 300,
|
|
425
|
-
},
|
|
426
|
-
},
|
|
427
|
-
play: async ({ canvasElement, step }) => {
|
|
428
|
-
const canvas = within(canvasElement);
|
|
429
|
-
|
|
430
|
-
await step('Verify all size variants render', async () => {
|
|
431
|
-
await expect(canvas.getByText('Extra Small Label')).toBeInTheDocument();
|
|
432
|
-
await expect(canvas.getByText('Small Label')).toBeInTheDocument();
|
|
433
|
-
await expect(canvas.getByText('Medium Label')).toBeInTheDocument();
|
|
434
|
-
await expect(canvas.getByText('Large Label')).toBeInTheDocument();
|
|
435
|
-
await expect(canvas.getByText('Extra Large Label')).toBeInTheDocument();
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
await step('Verify responsive sizing', async () => {
|
|
439
|
-
const labels = canvas.getAllByText(/Label/);
|
|
440
|
-
labels.forEach((label) => {
|
|
441
|
-
const computedStyle = window.getComputedStyle(label);
|
|
442
|
-
expect(parseFloat(computedStyle.fontSize)).toBeGreaterThan(0);
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
},
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
export const ThemeVariations: Story = {
|
|
449
|
-
name: '🎨 Theme Variations Test',
|
|
450
|
-
render: () => (
|
|
451
|
-
<Stack spacing={2}>
|
|
452
|
-
<Box sx={{ p: 2, backgroundColor: '#ffffff' }}>
|
|
453
|
-
<Label color="primary">Light Theme Primary</Label>
|
|
454
|
-
</Box>
|
|
455
|
-
<Box sx={{ p: 2, backgroundColor: '#1a1a1a' }}>
|
|
456
|
-
<Label color="primary">Dark Theme Primary</Label>
|
|
457
|
-
</Box>
|
|
458
|
-
<Label variant="glass" glow>
|
|
459
|
-
Glass with Glow
|
|
460
|
-
</Label>
|
|
461
|
-
<Label variant="gradient" color="secondary">
|
|
462
|
-
Gradient Secondary
|
|
463
|
-
</Label>
|
|
464
|
-
</Stack>
|
|
465
|
-
),
|
|
466
|
-
parameters: {
|
|
467
|
-
backgrounds: {
|
|
468
|
-
default: 'light',
|
|
469
|
-
values: [
|
|
470
|
-
{ name: 'light', value: '#ffffff' },
|
|
471
|
-
{ name: 'dark', value: '#1a1a1a' },
|
|
472
|
-
],
|
|
473
|
-
},
|
|
474
|
-
},
|
|
475
|
-
play: async ({ canvasElement, step }) => {
|
|
476
|
-
const canvas = within(canvasElement);
|
|
477
|
-
|
|
478
|
-
await step('Verify theme-aware rendering', async () => {
|
|
479
|
-
const labels = canvas.getAllByText(/Theme/);
|
|
480
|
-
labels.forEach((label) => {
|
|
481
|
-
const computedStyle = window.getComputedStyle(label);
|
|
482
|
-
expect(computedStyle.color).toBeDefined();
|
|
483
|
-
});
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
await step('Verify glass effect', async () => {
|
|
487
|
-
const glassLabel = canvas.getByText('Glass with Glow');
|
|
488
|
-
const computedStyle = window.getComputedStyle(glassLabel);
|
|
489
|
-
// Glass effect uses backdrop-filter
|
|
490
|
-
expect(computedStyle.backdropFilter || computedStyle.webkitBackdropFilter).toBeDefined();
|
|
491
|
-
});
|
|
492
|
-
},
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
export const VisualStates: Story = {
|
|
496
|
-
name: '👁️ Visual States Test',
|
|
497
|
-
render: function VisualStatesComponent() {
|
|
498
|
-
const [hovered, setHovered] = React.useState(false);
|
|
499
|
-
const [active, setActive] = React.useState(false);
|
|
500
|
-
|
|
501
|
-
return (
|
|
502
|
-
<Stack spacing={2}>
|
|
503
|
-
<Label data-testid="normal-label">Normal State</Label>
|
|
504
|
-
|
|
505
|
-
<Label
|
|
506
|
-
data-testid="hover-label"
|
|
507
|
-
variant="filled"
|
|
508
|
-
color="primary"
|
|
509
|
-
onMouseEnter={() => setHovered(true)}
|
|
510
|
-
onMouseLeave={() => setHovered(false)}
|
|
511
|
-
style={{ transform: hovered ? 'scale(1.02)' : 'scale(1)' }}
|
|
512
|
-
>
|
|
513
|
-
Hover State (hover me)
|
|
514
|
-
</Label>
|
|
515
|
-
|
|
516
|
-
<Label
|
|
517
|
-
data-testid="active-label"
|
|
518
|
-
variant="outlined"
|
|
519
|
-
onClick={() => setActive(!active)}
|
|
520
|
-
className={active ? 'active' : ''}
|
|
521
|
-
>
|
|
522
|
-
Active State (click me)
|
|
523
|
-
</Label>
|
|
524
|
-
|
|
525
|
-
<Label disabled data-testid="disabled-label">
|
|
526
|
-
Disabled State
|
|
527
|
-
</Label>
|
|
528
|
-
|
|
529
|
-
<Label loading data-testid="loading-label">
|
|
530
|
-
Loading State
|
|
531
|
-
</Label>
|
|
532
|
-
|
|
533
|
-
<Label error data-testid="error-label">
|
|
534
|
-
Error State
|
|
535
|
-
</Label>
|
|
536
|
-
|
|
537
|
-
<Label glow pulse data-testid="animated-label">
|
|
538
|
-
Animated State
|
|
539
|
-
</Label>
|
|
540
|
-
</Stack>
|
|
541
|
-
);
|
|
542
|
-
},
|
|
543
|
-
play: async ({ canvasElement, step }) => {
|
|
544
|
-
const canvas = within(canvasElement);
|
|
545
|
-
|
|
546
|
-
await step('Default state', async () => {
|
|
547
|
-
const label = canvas.getByTestId('normal-label');
|
|
548
|
-
await expect(label).toHaveStyle({ opacity: '1' });
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
await step('Hover state', async () => {
|
|
552
|
-
const label = canvas.getByTestId('hover-label');
|
|
553
|
-
await userEvent.hover(label);
|
|
554
|
-
await waitFor(() => {
|
|
555
|
-
// Browsers may convert scale() to matrix(), so we check for either format
|
|
556
|
-
const computedStyle = window.getComputedStyle(label);
|
|
557
|
-
const transform = computedStyle.transform;
|
|
558
|
-
const isScaled =
|
|
559
|
-
transform === 'scale(1.02)' || transform === 'matrix(1.02, 0, 0, 1.02, 0, 0)';
|
|
560
|
-
expect(isScaled).toBe(true);
|
|
561
|
-
});
|
|
562
|
-
await userEvent.unhover(label);
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
await step('Active state', async () => {
|
|
566
|
-
const label = canvas.getByTestId('active-label');
|
|
567
|
-
await userEvent.click(label);
|
|
568
|
-
await expect(label).toHaveClass('active');
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
await step('Disabled state', async () => {
|
|
572
|
-
const label = canvas.getByTestId('disabled-label');
|
|
573
|
-
const computedStyle = window.getComputedStyle(label);
|
|
574
|
-
// Disabled labels should have reduced opacity or different color
|
|
575
|
-
expect(computedStyle.color).toBeDefined();
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
await step('Loading state', async () => {
|
|
579
|
-
const label = canvas.getByTestId('loading-label');
|
|
580
|
-
const spinner = within(label.parentElement!).queryByRole('progressbar');
|
|
581
|
-
await expect(spinner).toBeInTheDocument();
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
await step('Error state', async () => {
|
|
585
|
-
const label = canvas.getByTestId('error-label');
|
|
586
|
-
const computedStyle = window.getComputedStyle(label);
|
|
587
|
-
// Error state should have error color
|
|
588
|
-
expect(computedStyle.color).toContain('rgb');
|
|
589
|
-
});
|
|
590
|
-
},
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
// ====================================
|
|
594
|
-
// Performance Tests
|
|
595
|
-
// ====================================
|
|
596
|
-
|
|
597
|
-
export const PerformanceTest: Story = {
|
|
598
|
-
name: '⚡ Performance Test',
|
|
599
|
-
render: () => {
|
|
600
|
-
const labels = Array.from({ length: 100 }, (_, i) => ({
|
|
601
|
-
id: i,
|
|
602
|
-
text: `Label ${i}`,
|
|
603
|
-
variant: (['default', 'filled', 'outlined', 'glass', 'gradient'] as const)[i % 5],
|
|
604
|
-
color: (['primary', 'secondary', 'success', 'error', 'warning'] as const)[i % 5],
|
|
605
|
-
}));
|
|
606
|
-
|
|
607
|
-
return (
|
|
608
|
-
<Box sx={{ maxHeight: 400, overflow: 'auto' }} data-testid="scroll-container">
|
|
609
|
-
<Stack spacing={1}>
|
|
610
|
-
{labels.map((label) => (
|
|
611
|
-
<Label
|
|
612
|
-
key={label.id}
|
|
613
|
-
variant={label.variant}
|
|
614
|
-
color={label.color}
|
|
615
|
-
data-testid={`label-${label.id}`}
|
|
616
|
-
>
|
|
617
|
-
{label.text}
|
|
618
|
-
</Label>
|
|
619
|
-
))}
|
|
620
|
-
</Stack>
|
|
621
|
-
</Box>
|
|
622
|
-
);
|
|
623
|
-
},
|
|
624
|
-
play: async ({ canvasElement, step }) => {
|
|
625
|
-
const canvas = within(canvasElement);
|
|
626
|
-
|
|
627
|
-
await step('Measure render time', async () => {
|
|
628
|
-
const startTime = window.performance.now();
|
|
629
|
-
const labels = canvas.getAllByTestId(/label-\d+/);
|
|
630
|
-
const endTime = window.performance.now();
|
|
631
|
-
|
|
632
|
-
const renderTime = endTime - startTime;
|
|
633
|
-
// Log render time for debugging (commented out for production)
|
|
634
|
-
// console.log(`Render time for ${labels.length} labels: ${renderTime}ms`);
|
|
635
|
-
|
|
636
|
-
// Assert reasonable render time
|
|
637
|
-
await expect(renderTime).toBeLessThan(1000);
|
|
638
|
-
await expect(labels).toHaveLength(100);
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
await step('Test scroll performance', async () => {
|
|
642
|
-
const scrollContainer = canvas.getByTestId('scroll-container');
|
|
643
|
-
|
|
644
|
-
// Scrolling is the one thing this story is about, so it has to move the
|
|
645
|
-
// container; what it must not do is assert a pixel offset, which depends
|
|
646
|
-
// on the viewport the story happens to render in.
|
|
647
|
-
// eslint-disable-next-line test-flakiness/no-viewport-dependent -- the subject under test is scrolling itself
|
|
648
|
-
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
|
649
|
-
fireEvent.scroll(scrollContainer);
|
|
650
|
-
|
|
651
|
-
// The labels are not virtualised, so scrolling must leave every one of
|
|
652
|
-
// them mounted — which is what the old toBeInTheDocument() on the
|
|
653
|
-
// container itself could never have caught, since the container was
|
|
654
|
-
// already there before the loop ran.
|
|
655
|
-
await waitFor(() => {
|
|
656
|
-
expect(canvas.getAllByTestId(/^label-\d+$/)).toHaveLength(100);
|
|
657
|
-
});
|
|
658
|
-
});
|
|
659
|
-
},
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
// ====================================
|
|
663
|
-
// Edge Cases Tests
|
|
664
|
-
// ====================================
|
|
665
|
-
|
|
666
|
-
export const EdgeCases: Story = {
|
|
667
|
-
name: '🔧 Edge Cases Test',
|
|
668
|
-
render: () => (
|
|
669
|
-
<Stack spacing={2}>
|
|
670
|
-
<Box sx={{ width: 200 }}>
|
|
671
|
-
<Label truncate data-testid="truncate-label">
|
|
672
|
-
This is a very long label text that should be truncated when it exceeds the container
|
|
673
|
-
width
|
|
674
|
-
</Label>
|
|
675
|
-
</Box>
|
|
676
|
-
|
|
677
|
-
<Label data-testid="empty-label"></Label>
|
|
678
|
-
|
|
679
|
-
<Label data-testid="special-chars">{`Label with special chars: !@#$%^&*()_+{}|:"<>?`}</Label>
|
|
680
|
-
|
|
681
|
-
<Label
|
|
682
|
-
data-testid="multiple-props"
|
|
683
|
-
variant="gradient"
|
|
684
|
-
color="primary"
|
|
685
|
-
size="lg"
|
|
686
|
-
weight="bold"
|
|
687
|
-
transform="uppercase"
|
|
688
|
-
glow
|
|
689
|
-
pulse
|
|
690
|
-
ripple
|
|
691
|
-
required
|
|
692
|
-
icon={<InfoIcon />}
|
|
693
|
-
tooltip="Complex tooltip"
|
|
694
|
-
helperText="Complex helper text"
|
|
695
|
-
>
|
|
696
|
-
All Props Label
|
|
697
|
-
</Label>
|
|
698
|
-
|
|
699
|
-
<Label nowrap data-testid="nowrap-label">
|
|
700
|
-
{`This label has nowrap and will not break to a new line even if it's very long`}
|
|
701
|
-
</Label>
|
|
702
|
-
</Stack>
|
|
703
|
-
),
|
|
704
|
-
play: async ({ canvasElement, step }) => {
|
|
705
|
-
const canvas = within(canvasElement);
|
|
706
|
-
|
|
707
|
-
await step('Text truncation handling', async () => {
|
|
708
|
-
const label = canvas.getByTestId('truncate-label');
|
|
709
|
-
const computedStyle = window.getComputedStyle(label);
|
|
710
|
-
// The truncate prop should apply text-overflow: ellipsis
|
|
711
|
-
await expect(computedStyle.textOverflow).toBe('ellipsis');
|
|
712
|
-
// Overflow should be hidden for truncation to work
|
|
713
|
-
await expect(['hidden', 'clip']).toContain(computedStyle.overflow);
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
await step('Empty content handling', async () => {
|
|
717
|
-
const label = canvas.getByTestId('empty-label');
|
|
718
|
-
await expect(label).toBeInTheDocument();
|
|
719
|
-
await expect(label.textContent).toBe('');
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
await step('Special characters handling', async () => {
|
|
723
|
-
const label = canvas.getByTestId('special-chars');
|
|
724
|
-
await expect(label).toHaveTextContent('!@#$%^&*()_+{}|:"<>?');
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
await step('Multiple props combination', async () => {
|
|
728
|
-
const label = canvas.getByTestId('multiple-props');
|
|
729
|
-
await expect(label).toBeInTheDocument();
|
|
730
|
-
|
|
731
|
-
// Check for required asterisk
|
|
732
|
-
const asterisk = within(label.parentElement!).getByText('*');
|
|
733
|
-
await expect(asterisk).toBeInTheDocument();
|
|
734
|
-
|
|
735
|
-
// Check for icon
|
|
736
|
-
const icon = label.querySelector('svg');
|
|
737
|
-
await expect(icon).toBeInTheDocument();
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
await step('No wrap handling', async () => {
|
|
741
|
-
const label = canvas.getByTestId('nowrap-label');
|
|
742
|
-
const computedStyle = window.getComputedStyle(label);
|
|
743
|
-
await expect(computedStyle.whiteSpace).toBe('nowrap');
|
|
744
|
-
});
|
|
745
|
-
},
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
// ====================================
|
|
749
|
-
// Integration Tests
|
|
750
|
-
// ====================================
|
|
751
|
-
|
|
752
|
-
export const FormIntegration: Story = {
|
|
753
|
-
name: '🔗 Form Integration Test',
|
|
754
|
-
render: function FormIntegrationComponent() {
|
|
755
|
-
const [email, setEmail] = React.useState('');
|
|
756
|
-
const [password, setPassword] = React.useState('');
|
|
757
|
-
const [errors, setErrors] = React.useState<{ email?: string; password?: string }>({});
|
|
758
|
-
|
|
759
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
760
|
-
e.preventDefault();
|
|
761
|
-
const newErrors: typeof errors = {};
|
|
762
|
-
|
|
763
|
-
if (!email) newErrors.email = 'Email is required';
|
|
764
|
-
if (!password) newErrors.password = 'Password is required';
|
|
765
|
-
|
|
766
|
-
setErrors(newErrors);
|
|
767
|
-
|
|
768
|
-
if (Object.keys(newErrors).length === 0) {
|
|
769
|
-
window.alert('Form submitted successfully!');
|
|
770
|
-
}
|
|
771
|
-
};
|
|
772
|
-
|
|
773
|
-
return (
|
|
774
|
-
<form onSubmit={handleSubmit}>
|
|
775
|
-
<Stack spacing={2}>
|
|
776
|
-
<Box>
|
|
777
|
-
<Label
|
|
778
|
-
htmlFor="email-field"
|
|
779
|
-
required
|
|
780
|
-
error={!!errors.email}
|
|
781
|
-
helperText={errors.email}
|
|
782
|
-
data-testid="email-label"
|
|
783
|
-
>
|
|
784
|
-
Email Address
|
|
785
|
-
</Label>
|
|
786
|
-
<TextField
|
|
787
|
-
id="email-field"
|
|
788
|
-
fullWidth
|
|
789
|
-
value={email}
|
|
790
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
791
|
-
error={!!errors.email}
|
|
792
|
-
size="small"
|
|
793
|
-
sx={{ mt: 1 }}
|
|
794
|
-
data-testid="email-input"
|
|
795
|
-
/>
|
|
796
|
-
</Box>
|
|
797
|
-
|
|
798
|
-
<Box>
|
|
799
|
-
<Label
|
|
800
|
-
htmlFor="password-field"
|
|
801
|
-
required
|
|
802
|
-
error={!!errors.password}
|
|
803
|
-
helperText={errors.password}
|
|
804
|
-
data-testid="password-label"
|
|
805
|
-
>
|
|
806
|
-
Password
|
|
807
|
-
</Label>
|
|
808
|
-
<TextField
|
|
809
|
-
id="password-field"
|
|
810
|
-
type="password"
|
|
811
|
-
fullWidth
|
|
812
|
-
value={password}
|
|
813
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
814
|
-
error={!!errors.password}
|
|
815
|
-
size="small"
|
|
816
|
-
sx={{ mt: 1 }}
|
|
817
|
-
data-testid="password-input"
|
|
818
|
-
/>
|
|
819
|
-
</Box>
|
|
820
|
-
|
|
821
|
-
<button type="submit" data-testid="submit-button">
|
|
822
|
-
Submit
|
|
823
|
-
</button>
|
|
824
|
-
</Stack>
|
|
825
|
-
</form>
|
|
826
|
-
);
|
|
827
|
-
},
|
|
828
|
-
play: async ({ canvasElement, step }) => {
|
|
829
|
-
const canvas = within(canvasElement);
|
|
830
|
-
|
|
831
|
-
await step('Initial form state', async () => {
|
|
832
|
-
const emailLabel = canvas.getByTestId('email-label');
|
|
833
|
-
const passwordLabel = canvas.getByTestId('password-label');
|
|
834
|
-
|
|
835
|
-
await expect(emailLabel).toBeInTheDocument();
|
|
836
|
-
await expect(passwordLabel).toBeInTheDocument();
|
|
837
|
-
|
|
838
|
-
// Both should show required asterisks
|
|
839
|
-
const asterisks = canvas.getAllByText('*');
|
|
840
|
-
await expect(asterisks).toHaveLength(2);
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
await step('Submit empty form shows errors', async () => {
|
|
844
|
-
const submitButton = canvas.getByTestId('submit-button');
|
|
845
|
-
await userEvent.click(submitButton);
|
|
846
|
-
|
|
847
|
-
await waitFor(() => {
|
|
848
|
-
const emailError = canvas.getByText('Email is required');
|
|
849
|
-
const passwordError = canvas.getByText('Password is required');
|
|
850
|
-
expect(emailError).toBeInTheDocument();
|
|
851
|
-
expect(passwordError).toBeInTheDocument();
|
|
852
|
-
});
|
|
853
|
-
});
|
|
854
|
-
|
|
855
|
-
await step('Fill form and submit', async () => {
|
|
856
|
-
const emailInputContainer = canvas.getByTestId('email-input');
|
|
857
|
-
const passwordInputContainer = canvas.getByTestId('password-input');
|
|
858
|
-
|
|
859
|
-
// Get the actual input elements within the MUI TextFields
|
|
860
|
-
const emailInput = emailInputContainer.querySelector('input') as HTMLInputElement;
|
|
861
|
-
const passwordInput = passwordInputContainer.querySelector('input') as HTMLInputElement;
|
|
862
|
-
|
|
863
|
-
await userEvent.clear(emailInput);
|
|
864
|
-
await userEvent.type(emailInput, 'test@example.com');
|
|
865
|
-
|
|
866
|
-
await userEvent.clear(passwordInput);
|
|
867
|
-
await userEvent.type(passwordInput, 'password123');
|
|
868
|
-
|
|
869
|
-
const submitButton = canvas.getByTestId('submit-button');
|
|
870
|
-
await userEvent.click(submitButton);
|
|
871
|
-
|
|
872
|
-
// Form should submit successfully (alert would show)
|
|
873
|
-
});
|
|
874
|
-
},
|
|
875
|
-
};
|
|
876
|
-
|
|
877
|
-
export const TooltipIntegration: Story = {
|
|
878
|
-
name: '💡 Tooltip Integration Test',
|
|
879
|
-
args: {
|
|
880
|
-
children: 'Hover for more info',
|
|
881
|
-
tooltip: 'This is additional information about the label',
|
|
882
|
-
icon: <InfoIcon fontSize="small" />,
|
|
883
|
-
iconPosition: 'end',
|
|
884
|
-
'data-testid': 'tooltip-label',
|
|
885
|
-
},
|
|
886
|
-
play: async ({ canvasElement, step }) => {
|
|
887
|
-
const canvas = within(canvasElement);
|
|
888
|
-
|
|
889
|
-
await step('Verify label with icon renders', async () => {
|
|
890
|
-
const label = canvas.getByTestId('tooltip-label');
|
|
891
|
-
await expect(label).toBeInTheDocument();
|
|
892
|
-
|
|
893
|
-
const icon = label.querySelector('svg');
|
|
894
|
-
await expect(icon).toBeInTheDocument();
|
|
895
|
-
});
|
|
896
|
-
|
|
897
|
-
await step('Tooltip appears on hover', async () => {
|
|
898
|
-
const label = canvas.getByTestId('tooltip-label');
|
|
899
|
-
|
|
900
|
-
await userEvent.hover(label);
|
|
901
|
-
|
|
902
|
-
// MUI tooltips appear in a portal, so we need to search in the whole document
|
|
903
|
-
await waitFor(
|
|
904
|
-
() => {
|
|
905
|
-
const tooltip = document.querySelector('[role="tooltip"]');
|
|
906
|
-
expect(tooltip).toBeInTheDocument();
|
|
907
|
-
expect(tooltip).toHaveTextContent('This is additional information about the label');
|
|
908
|
-
},
|
|
909
|
-
{ timeout: 3000 },
|
|
910
|
-
);
|
|
911
|
-
|
|
912
|
-
await userEvent.unhover(label);
|
|
913
|
-
});
|
|
914
|
-
},
|
|
915
|
-
};
|