@12-apps/ui 1.4.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -8
- package/src/button/index.ts +1 -0
- package/src/social-login-button/index.ts +1 -0
- package/src/{social-login-button.tsx → social-login-button/social-login-button.tsx} +5 -5
- package/src/user-avatar/index.ts +1 -0
- package/src/{user-avatar.tsx → user-avatar/user-avatar.tsx} +4 -4
- package/eslint.config.js +0 -77
- package/src/__tests__/data-grid.test.tsx +0 -125
- package/src/__tests__/package-wiring.test.ts +0 -41
- package/src/__tests__/setup.ts +0 -1
- package/src/__tests__/social-login-button.test.tsx +0 -186
- package/src/__tests__/user-avatar.test.tsx +0 -195
- package/src/__tests__/viewport.test.ts +0 -50
- package/src/components/command/__tests__/command.test.tsx +0 -112
- package/src/components/data-display/Alert/Alert.stories.tsx +0 -559
- package/src/components/data-display/Alert/Alert.test.stories.tsx +0 -707
- package/src/components/data-display/AlertDialog/AlertDialog.stories.tsx +0 -362
- package/src/components/data-display/AlertDialog/AlertDialog.test.stories.tsx +0 -753
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.stories.tsx +0 -230
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.test.stories.tsx +0 -270
- package/src/components/data-display/Avatar/Avatar.stories.tsx +0 -721
- package/src/components/data-display/Avatar/Avatar.test.stories.tsx +0 -822
- package/src/components/data-display/Badge/Badge.stories.tsx +0 -1134
- package/src/components/data-display/Badge/Badge.test.stories.tsx +0 -1194
- package/src/components/data-display/Banner/Banner.stories.tsx +0 -528
- package/src/components/data-display/Banner/Banner.test.stories.tsx +0 -503
- package/src/components/data-display/Carousel/Carousel.stories.tsx +0 -607
- package/src/components/data-display/Carousel/Carousel.test.stories.tsx +0 -691
- package/src/components/data-display/Chart/Chart.stories.tsx +0 -568
- package/src/components/data-display/Chart/Chart.test.stories.tsx +0 -174
- package/src/components/data-display/Chart/SpecChart.stories.tsx +0 -150
- package/src/components/data-display/Chart/SpecChart.test.stories.tsx +0 -56
- package/src/components/data-display/Chart/__tests__/SpecChart.test.tsx +0 -60
- package/src/components/data-display/Chart/__tests__/chart-format.test.ts +0 -43
- package/src/components/data-display/Chart/__tests__/chart-spec.test.ts +0 -119
- package/src/components/data-display/Chip/Chip.stories.tsx +0 -252
- package/src/components/data-display/Chip/Chip.test.stories.tsx +0 -266
- package/src/components/data-display/DataGrid/DataGrid.stories.tsx +0 -515
- package/src/components/data-display/DataGrid/DataGrid.test.stories.tsx +0 -653
- package/src/components/data-display/DataViews/__tests__/DataViewsGrid.test.tsx +0 -271
- package/src/components/data-display/DataViews/__tests__/DataViewsTableBase.test.tsx +0 -134
- package/src/components/data-display/DataViews/__tests__/SaveViewModal.test.tsx +0 -83
- package/src/components/data-display/DataViews/__tests__/data-views-filter.test.ts +0 -115
- package/src/components/data-display/DataViews/__tests__/data-views-preview.test.ts +0 -53
- package/src/components/data-display/DataViews/__tests__/range-pill.test.tsx +0 -114
- package/src/components/data-display/DataViews/base-card.stories.tsx +0 -136
- package/src/components/data-display/DescriptionItem/DescriptionItem.stories.tsx +0 -254
- package/src/components/data-display/EmptyState/EmptyState.stories.tsx +0 -282
- package/src/components/data-display/EmptyState/EmptyState.test.stories.tsx +0 -441
- package/src/components/data-display/ErrorState/ErrorState.stories.tsx +0 -110
- package/src/components/data-display/ErrorState/ErrorState.test.stories.tsx +0 -133
- package/src/components/data-display/HoverCard/HoverCard.stories.tsx +0 -650
- package/src/components/data-display/HoverCard/HoverCard.test.stories.tsx +0 -929
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.stories.tsx +0 -411
- package/src/components/data-display/LazyImage/LazyImage.stories.tsx +0 -387
- package/src/components/data-display/Lightbox/Lightbox.stories.tsx +0 -315
- package/src/components/data-display/Lightbox/Lightbox.test.stories.tsx +0 -598
- package/src/components/data-display/LoadingState/LoadingState.stories.tsx +0 -124
- package/src/components/data-display/LoadingState/LoadingState.test.stories.tsx +0 -99
- package/src/components/data-display/MapPreview/MapPreview.stories.tsx +0 -458
- package/src/components/data-display/MapPreview/MapPreview.test.stories.tsx +0 -394
- package/src/components/data-display/Popover/Popover.stories.tsx +0 -295
- package/src/components/data-display/Popover/Popover.test.stories.tsx +0 -1162
- package/src/components/data-display/Progress/Progress.stories.tsx +0 -481
- package/src/components/data-display/Progress/Progress.test.stories.tsx +0 -2070
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.stories.tsx +0 -160
- package/src/components/data-display/SectionOnboarding/SectionOnboarding.test.stories.tsx +0 -190
- package/src/components/data-display/Sheet/Sheet.stories.tsx +0 -883
- package/src/components/data-display/Sheet/Sheet.test.stories.tsx +0 -1101
- package/src/components/data-display/StatCard/StatCard.stories.tsx +0 -167
- package/src/components/data-display/StatCard/StatCard.test.stories.tsx +0 -240
- package/src/components/data-display/Stepper/Stepper.stories.tsx +0 -540
- package/src/components/data-display/Stepper/Stepper.test.stories.tsx +0 -600
- package/src/components/data-display/Table/Table.stories.tsx +0 -803
- package/src/components/data-display/Table/Table.test.stories.tsx +0 -369
- package/src/components/data-display/Timeline/Timeline.stories.tsx +0 -442
- package/src/components/data-display/Timeline/Timeline.test.stories.tsx +0 -930
- package/src/components/data-display/TimingDiagram/TimingDiagram.stories.tsx +0 -475
- package/src/components/data-display/TimingDiagram/TimingDiagram.test.stories.tsx +0 -734
- package/src/components/data-display/Tooltip/Tooltip.stories.tsx +0 -468
- package/src/components/data-display/Tooltip/Tooltip.test.stories.tsx +0 -1154
- package/src/components/feedback/ConfirmAction/ConfirmAction.stories.tsx +0 -146
- package/src/components/feedback/ConfirmAction/ConfirmAction.test.stories.tsx +0 -114
- package/src/components/feedback/ConfirmAction/__tests__/confirm-action.test.tsx +0 -288
- package/src/components/feedback/Dialog/Dialog.stories.tsx +0 -833
- package/src/components/feedback/Dialog/Dialog.test.stories.tsx +0 -955
- package/src/components/feedback/Dialog/__tests__/dialog-content-spacing.test.tsx +0 -66
- package/src/components/feedback/Modal/Modal.stories.tsx +0 -765
- package/src/components/feedback/Modal/Modal.test.stories.tsx +0 -251
- package/src/components/feedback/Sonner/Sonner.stories.tsx +0 -698
- package/src/components/feedback/Sonner/Sonner.test.stories.tsx +0 -896
- package/src/components/feedback/StackedModal/StackedModal.stories.tsx +0 -1783
- package/src/components/feedback/StackedModal/StackedModal.test.stories.tsx +0 -975
- package/src/components/feedback/StackedModal/__tests__/stacked-modal-viewport.test.tsx +0 -104
- package/src/components/feedback/Toast/Toast.stories.tsx +0 -548
- package/src/components/feedback/Toast/Toast.test.stories.tsx +0 -834
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.stories.tsx +0 -691
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.test.stories.tsx +0 -634
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.stories.tsx +0 -665
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.test.stories.tsx +0 -727
- package/src/components/form/Autocomplete/Autocomplete.stories.tsx +0 -715
- package/src/components/form/Autocomplete/Autocomplete.test.stories.tsx +0 -825
- package/src/components/form/Button/Button.stories.tsx +0 -365
- package/src/components/form/Button/Button.test.stories.tsx +0 -607
- package/src/components/form/Calendar/Calendar.stories.tsx +0 -349
- package/src/components/form/Calendar/Calendar.test.stories.tsx +0 -464
- package/src/components/form/CepField/CepField.stories.tsx +0 -138
- package/src/components/form/CepField/CepField.test.stories.tsx +0 -281
- package/src/components/form/Checkbox/Checkbox.stories.tsx +0 -265
- package/src/components/form/Checkbox/Checkbox.test.stories.tsx +0 -181
- package/src/components/form/CodeEditor/CodeEditor.stories.tsx +0 -871
- package/src/components/form/CodeEditor/CodeEditor.test.stories.tsx +0 -574
- package/src/components/form/Command/Command.stories.tsx +0 -580
- package/src/components/form/Command/Command.test.stories.tsx +0 -215
- package/src/components/form/CreatableSelect/CreatableSelect.stories.tsx +0 -112
- package/src/components/form/CreatableSelect/CreatableSelect.test.stories.tsx +0 -134
- package/src/components/form/Form/Form.stories.tsx +0 -687
- package/src/components/form/Form/Form.test.stories.tsx +0 -601
- package/src/components/form/Input/Input.stories.tsx +0 -260
- package/src/components/form/Input/Input.test.stories.tsx +0 -1191
- package/src/components/form/InputOTP/InputOTP.stories.tsx +0 -774
- package/src/components/form/InputOTP/InputOTP.test.stories.tsx +0 -588
- package/src/components/form/Label/Label.stories.tsx +0 -473
- package/src/components/form/Label/Label.test.stories.tsx +0 -909
- package/src/components/form/Menubar/Menubar.stories.tsx +0 -472
- package/src/components/form/Menubar/Menubar.test.stories.tsx +0 -584
- package/src/components/form/PasswordStrength/PasswordStrength.stories.tsx +0 -562
- package/src/components/form/PasswordStrength/PasswordStrength.test.stories.tsx +0 -327
- package/src/components/form/PhoneInput/PhoneInput.stories.tsx +0 -495
- package/src/components/form/PhoneInput/PhoneInput.test.stories.tsx +0 -666
- package/src/components/form/RadioGroup/RadioGroup.stories.tsx +0 -518
- package/src/components/form/RadioGroup/RadioGroup.test.stories.tsx +0 -685
- package/src/components/form/RichTextEditor/RichTextEditor.stories.tsx +0 -261
- package/src/components/form/RichTextEditor/RichTextEditor.test.stories.tsx +0 -491
- package/src/components/form/SearchPalette/SearchPalette.stories.tsx +0 -135
- package/src/components/form/Select/Select.stories.tsx +0 -364
- package/src/components/form/Select/Select.test.stories.tsx +0 -985
- package/src/components/form/Slider/Slider.stories.tsx +0 -418
- package/src/components/form/Slider/Slider.test.stories.tsx +0 -1042
- package/src/components/form/Switch/Switch.stories.tsx +0 -697
- package/src/components/form/Switch/Switch.test.stories.tsx +0 -597
- package/src/components/form/Textarea/Textarea.stories.tsx +0 -473
- package/src/components/form/Textarea/Textarea.test.stories.tsx +0 -702
- package/src/components/form/Toggle/Toggle.stories.tsx +0 -638
- package/src/components/form/Toggle/Toggle.test.stories.tsx +0 -1159
- package/src/components/form/ToggleGroup/ToggleGroup.stories.tsx +0 -617
- package/src/components/form/ToggleGroup/ToggleGroup.test.stories.tsx +0 -918
- package/src/components/form/UploadButton/UploadButton.stories.tsx +0 -367
- package/src/components/form/UploadButton/UploadButton.test.stories.tsx +0 -573
- package/src/components/form/total-form/total-form.test.tsx +0 -214
- package/src/components/layout/Accordion/Accordion.stories.tsx +0 -694
- package/src/components/layout/Accordion/Accordion.test.stories.tsx +0 -1344
- package/src/components/layout/Card/Card.stories.tsx +0 -591
- package/src/components/layout/Card/Card.test.stories.tsx +0 -1134
- package/src/components/layout/CardGrid/__tests__/CardGrid.test.tsx +0 -92
- package/src/components/layout/Collapsible/Collapsible.stories.tsx +0 -180
- package/src/components/layout/Collapsible/Collapsible.test.stories.tsx +0 -308
- package/src/components/layout/Container/Container.stories.tsx +0 -557
- package/src/components/layout/Container/Container.test.stories.tsx +0 -745
- package/src/components/layout/ContentToolbar/ContentToolbar.stories.tsx +0 -129
- package/src/components/layout/ContentToolbar/ContentToolbar.test.stories.tsx +0 -251
- package/src/components/layout/ContentToolbar/__tests__/ColumnsMenu.test.tsx +0 -39
- package/src/components/layout/ContentToolbar/__tests__/MultiSelectDropdown.test.tsx +0 -131
- package/src/components/layout/ContentToolbar/__tests__/SavedViewsMenu.test.tsx +0 -99
- package/src/components/layout/Dashboard/Dashboard.stories.tsx +0 -104
- package/src/components/layout/Dashboard/Dashboard.test.stories.tsx +0 -190
- package/src/components/layout/Drawer/Drawer.stories.tsx +0 -658
- package/src/components/layout/Drawer/Drawer.test.stories.tsx +0 -1343
- package/src/components/layout/Resizable/Resizable.stories.tsx +0 -1039
- package/src/components/layout/Resizable/Resizable.test.stories.tsx +0 -750
- package/src/components/layout/ScrollArea/ScrollArea.stories.tsx +0 -651
- package/src/components/layout/ScrollArea/ScrollArea.test.stories.tsx +0 -619
- package/src/components/layout/Separator/Separator.stories.tsx +0 -563
- package/src/components/layout/Separator/Separator.test.stories.tsx +0 -518
- package/src/components/layout/SettingsLayout/SettingsLayout.stories.tsx +0 -192
- package/src/components/layout/SettingsLayout/SettingsLayout.test.stories.tsx +0 -175
- package/src/components/layout/Sidebar/Sidebar.stories.tsx +0 -609
- package/src/components/layout/Sidebar/Sidebar.test.stories.tsx +0 -1324
- package/src/components/layout/Skeleton/Skeleton.stories.tsx +0 -689
- package/src/components/layout/Skeleton/Skeleton.test.stories.tsx +0 -521
- package/src/components/layout/Spacer/Spacer.stories.tsx +0 -502
- package/src/components/layout/Spacer/Spacer.test.stories.tsx +0 -472
- package/src/components/layout/TableFilter/TableFilter.stories.tsx +0 -82
- package/src/components/layout/TableFilter/TableFilter.test.stories.tsx +0 -87
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx +0 -638
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.test.stories.tsx +0 -1008
- package/src/components/navigation/CommandPalette/CommandPalette.stories.tsx +0 -386
- package/src/components/navigation/CommandPalette/CommandPalette.test.stories.tsx +0 -435
- package/src/components/navigation/ContextMenu/ContextMenu.stories.tsx +0 -614
- package/src/components/navigation/ContextMenu/ContextMenu.test.stories.tsx +0 -845
- package/src/components/navigation/DropdownMenu/DropdownMenu.stories.tsx +0 -578
- package/src/components/navigation/DropdownMenu/DropdownMenu.test.stories.tsx +0 -660
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.stories.tsx +0 -516
- package/src/components/navigation/NavigationMenu/stories/NavigationMenu.test.stories.tsx +0 -935
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuHorizontal.stories.tsx +0 -501
- package/src/components/navigation/NavigationMenu/stories/NavigationMenuVertical.stories.tsx +0 -453
- package/src/components/navigation/Pagination/Pagination.stories.tsx +0 -602
- package/src/components/navigation/Pagination/Pagination.test.stories.tsx +0 -520
- package/src/components/navigation/Tabs/Tabs.stories.tsx +0 -1004
- package/src/components/navigation/Tabs/Tabs.test.stories.tsx +0 -1225
- package/src/components/typography/Blockquote/Blockquote.stories.tsx +0 -359
- package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +0 -432
- package/src/components/typography/Code/Code.stories.tsx +0 -686
- package/src/components/typography/Code/Code.test.stories.tsx +0 -703
- package/src/components/typography/Heading/Heading.stories.tsx +0 -451
- package/src/components/typography/Heading/Heading.test.stories.tsx +0 -830
- package/src/components/typography/Paragraph/Paragraph.stories.tsx +0 -280
- package/src/components/typography/Paragraph/Paragraph.test.stories.tsx +0 -231
- package/src/components/typography/Text/Text.stories.tsx +0 -359
- package/src/components/typography/Text/Text.test.stories.tsx +0 -762
- package/src/components/utility/AnimatedIcon/AnimatedIcon.stories.tsx +0 -945
- package/src/components/utility/AnimatedIcon/AnimatedIcon.test.stories.tsx +0 -603
- package/src/components/utility/AspectRatio/AspectRatio.stories.tsx +0 -414
- package/src/components/utility/AspectRatio/AspectRatio.test.stories.tsx +0 -859
- package/src/components/utility/InfiniteScroll/InfiniteScroll.stories.tsx +0 -620
- package/src/components/utility/InfiniteScroll/InfiniteScroll.test.stories.tsx +0 -361
- package/src/components/utility/InstallPrompt/InstallPrompt.stories.tsx +0 -118
- package/src/components/utility/InstallPrompt/InstallPrompt.test.stories.tsx +0 -83
- package/src/components/utility/InstallPrompt/__tests__/InstallPrompt.ios.test.tsx +0 -65
- package/src/components/utility/InstallPrompt/__tests__/usePwaInstall.test.tsx +0 -271
- package/src/components/utility/Portal/Portal.stories.tsx +0 -340
- package/src/components/utility/Portal/Portal.test.stories.tsx +0 -2086
- package/src/components/utility/Transition/Transition.stories.tsx +0 -493
- package/src/components/utility/Transition/Transition.test.stories.tsx +0 -854
- package/src/components/utility/VirtualList/VirtualList.stories.tsx +0 -1073
- package/src/components/utility/VirtualList/VirtualList.test.stories.tsx +0 -1085
- package/src/components/utility/WorkflowStep/WorkflowStep.stories.tsx +0 -431
- package/src/components/utility/WorkflowStep/WorkflowStep.test.stories.tsx +0 -364
- /package/src/{button.tsx → button/button.tsx} +0 -0
|
@@ -1,1191 +0,0 @@
|
|
|
1
|
-
import Email from '@mui/icons-material/Email';
|
|
2
|
-
import Lock from '@mui/icons-material/Lock';
|
|
3
|
-
import Person from '@mui/icons-material/Person';
|
|
4
|
-
import Visibility from '@mui/icons-material/Visibility';
|
|
5
|
-
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
|
6
|
-
import { IconButton } from '@mui/material';
|
|
7
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
8
|
-
import { useState } from 'react';
|
|
9
|
-
import { expect, fn,userEvent, waitFor, within } from 'storybook/test';
|
|
10
|
-
|
|
11
|
-
import { Input } from './Input';
|
|
12
|
-
|
|
13
|
-
const meta: Meta<typeof Input> = {
|
|
14
|
-
title: 'Form/Input/Tests',
|
|
15
|
-
component: Input,
|
|
16
|
-
parameters: {
|
|
17
|
-
layout: 'centered',
|
|
18
|
-
chromatic: { disableSnapshot: false },
|
|
19
|
-
},
|
|
20
|
-
tags: ['autodocs', 'test', 'component:Input'],
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export default meta;
|
|
24
|
-
type Story = StoryObj<typeof meta>;
|
|
25
|
-
|
|
26
|
-
// ============================================================================
|
|
27
|
-
// INTERACTION TESTS
|
|
28
|
-
// ============================================================================
|
|
29
|
-
|
|
30
|
-
export const BasicInteraction: Story = {
|
|
31
|
-
name: '🧪 Basic Interaction Test',
|
|
32
|
-
args: {
|
|
33
|
-
label: 'Test Input',
|
|
34
|
-
placeholder: 'Type here...',
|
|
35
|
-
'data-testid': 'basic-input',
|
|
36
|
-
onChange: fn(),
|
|
37
|
-
onFocus: fn(),
|
|
38
|
-
onBlur: fn(),
|
|
39
|
-
onClick: fn(),
|
|
40
|
-
},
|
|
41
|
-
play: async ({ canvasElement, step, args }) => {
|
|
42
|
-
const canvas = within(canvasElement);
|
|
43
|
-
|
|
44
|
-
await step('Initial render verification', async () => {
|
|
45
|
-
const input = canvas.getByLabelText('Test Input');
|
|
46
|
-
await expect(input).toBeInTheDocument();
|
|
47
|
-
await expect(input).toHaveAttribute('placeholder', 'Type here...');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
await step('Click interaction', async () => {
|
|
51
|
-
const input = canvas.getByLabelText('Test Input');
|
|
52
|
-
await userEvent.click(input);
|
|
53
|
-
await expect(args.onClick).toHaveBeenCalled();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
await step('Focus interaction', async () => {
|
|
57
|
-
const input = canvas.getByLabelText('Test Input');
|
|
58
|
-
await userEvent.click(input);
|
|
59
|
-
await expect(args.onFocus).toHaveBeenCalled();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
await step('Type interaction', async () => {
|
|
63
|
-
const input = canvas.getByLabelText('Test Input');
|
|
64
|
-
await userEvent.type(input, 'Hello World');
|
|
65
|
-
await expect(input).toHaveValue('Hello World');
|
|
66
|
-
await expect(args.onChange).toHaveBeenCalled();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
await step('Clear input', async () => {
|
|
70
|
-
const input = canvas.getByLabelText('Test Input');
|
|
71
|
-
await userEvent.clear(input);
|
|
72
|
-
await expect(input).toHaveValue('');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
await step('Blur interaction', async () => {
|
|
76
|
-
const input = canvas.getByLabelText('Test Input');
|
|
77
|
-
await userEvent.click(input);
|
|
78
|
-
await userEvent.tab(); // Move focus away
|
|
79
|
-
await expect(args.onBlur).toHaveBeenCalled();
|
|
80
|
-
});
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
export const FormInteraction: Story = {
|
|
85
|
-
name: '📝 Form Interaction Test',
|
|
86
|
-
render: () => {
|
|
87
|
-
const TestForm = () => {
|
|
88
|
-
const [email, setEmail] = useState('');
|
|
89
|
-
const [password, setPassword] = useState('');
|
|
90
|
-
const [submitted, setSubmitted] = useState(false);
|
|
91
|
-
|
|
92
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
93
|
-
e.preventDefault();
|
|
94
|
-
setSubmitted(true);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
<form
|
|
99
|
-
onSubmit={handleSubmit}
|
|
100
|
-
style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}
|
|
101
|
-
>
|
|
102
|
-
<Input
|
|
103
|
-
label="Email"
|
|
104
|
-
type="email"
|
|
105
|
-
value={email}
|
|
106
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
107
|
-
data-testid="form-email"
|
|
108
|
-
required
|
|
109
|
-
/>
|
|
110
|
-
<Input
|
|
111
|
-
label="Password"
|
|
112
|
-
type="password"
|
|
113
|
-
value={password}
|
|
114
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
115
|
-
data-testid="form-password"
|
|
116
|
-
required
|
|
117
|
-
/>
|
|
118
|
-
<button type="submit" data-testid="form-submit">
|
|
119
|
-
Submit
|
|
120
|
-
</button>
|
|
121
|
-
{submitted && <div data-testid="form-success">Form submitted with email: {email}</div>}
|
|
122
|
-
</form>
|
|
123
|
-
);
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
return <TestForm />;
|
|
127
|
-
},
|
|
128
|
-
play: async ({ canvasElement, step }) => {
|
|
129
|
-
const canvas = within(canvasElement);
|
|
130
|
-
|
|
131
|
-
await step('Fill email field', async () => {
|
|
132
|
-
const emailInput = canvas.getByTestId('form-email');
|
|
133
|
-
await userEvent.type(emailInput, 'test@example.com');
|
|
134
|
-
await expect(emailInput).toHaveValue('test@example.com');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
await step('Fill password field', async () => {
|
|
138
|
-
const passwordInput = canvas.getByTestId('form-password');
|
|
139
|
-
await userEvent.type(passwordInput, 'secretpassword');
|
|
140
|
-
await expect(passwordInput).toHaveValue('secretpassword');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
await step('Submit form', async () => {
|
|
144
|
-
const submitButton = canvas.getByTestId('form-submit');
|
|
145
|
-
await userEvent.click(submitButton);
|
|
146
|
-
|
|
147
|
-
await waitFor(async () => {
|
|
148
|
-
const success = canvas.getByTestId('form-success');
|
|
149
|
-
await expect(success).toHaveTextContent('Form submitted with email: test@example.com');
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
},
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
export const StateChangeTest: Story = {
|
|
156
|
-
name: '🔄 State Change Test',
|
|
157
|
-
render: () => {
|
|
158
|
-
const StatefulComponent = () => {
|
|
159
|
-
const [value, setValue] = useState('');
|
|
160
|
-
const [error, setError] = useState(false);
|
|
161
|
-
const [loading, setLoading] = useState(false);
|
|
162
|
-
|
|
163
|
-
const handleValidation = (inputValue: string) => {
|
|
164
|
-
setError(inputValue.length < 3);
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// Toggle only. The old version also queued a 2s timer to clear the flag,
|
|
168
|
-
// which made the rendered state depend on wall-clock time — a play
|
|
169
|
-
// function that ran slowly would see `loading` flip under it.
|
|
170
|
-
const handleLoadingToggle = () => {
|
|
171
|
-
setLoading((previous) => !previous);
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
return (
|
|
175
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
|
|
176
|
-
<Input
|
|
177
|
-
label="Stateful Input"
|
|
178
|
-
value={value}
|
|
179
|
-
onChange={(e) => {
|
|
180
|
-
setValue(e.target.value);
|
|
181
|
-
handleValidation(e.target.value);
|
|
182
|
-
}}
|
|
183
|
-
error={error}
|
|
184
|
-
helperText={error ? 'Must be at least 3 characters' : 'Type at least 3 characters'}
|
|
185
|
-
data-testid="stateful-input"
|
|
186
|
-
/>
|
|
187
|
-
<Input
|
|
188
|
-
label="Loading Input"
|
|
189
|
-
loading={loading}
|
|
190
|
-
placeholder="Click button to simulate loading"
|
|
191
|
-
data-testid="loading-input"
|
|
192
|
-
/>
|
|
193
|
-
<button onClick={handleLoadingToggle} data-testid="loading-toggle">
|
|
194
|
-
Toggle Loading
|
|
195
|
-
</button>
|
|
196
|
-
<div data-testid="state-display">
|
|
197
|
-
{`Value: ${value || ''} | Error: ${error.toString()} | Loading: ${loading.toString()}`}
|
|
198
|
-
</div>
|
|
199
|
-
</div>
|
|
200
|
-
);
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
return <StatefulComponent />;
|
|
204
|
-
},
|
|
205
|
-
play: async ({ canvasElement, step }) => {
|
|
206
|
-
const canvas = within(canvasElement);
|
|
207
|
-
|
|
208
|
-
await step('Verify initial state', async () => {
|
|
209
|
-
const stateDisplay = canvas.getByTestId('state-display');
|
|
210
|
-
await expect(stateDisplay.textContent).toMatch(
|
|
211
|
-
/Value:\s+\|\s+Error:\s+false\s+\|\s+Loading:\s+false/,
|
|
212
|
-
);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
await step('Type short text to trigger error', async () => {
|
|
216
|
-
const input = canvas.getByTestId('stateful-input');
|
|
217
|
-
await userEvent.type(input, 'ab');
|
|
218
|
-
|
|
219
|
-
await waitFor(async () => {
|
|
220
|
-
const stateDisplay = canvas.getByTestId('state-display');
|
|
221
|
-
await expect(stateDisplay).toHaveTextContent('Error: true');
|
|
222
|
-
await expect(input).toHaveAttribute('aria-invalid', 'true');
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
await step('Type longer text to clear error', async () => {
|
|
227
|
-
const input = canvas.getByTestId('stateful-input');
|
|
228
|
-
await userEvent.clear(input);
|
|
229
|
-
await userEvent.type(input, 'valid input');
|
|
230
|
-
|
|
231
|
-
await waitFor(async () => {
|
|
232
|
-
const stateDisplay = canvas.getByTestId('state-display');
|
|
233
|
-
await expect(stateDisplay).toHaveTextContent('Error: false');
|
|
234
|
-
await expect(input).not.toHaveAttribute('aria-invalid', 'true');
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
await step('Toggle loading state', async () => {
|
|
239
|
-
const loadingToggle = canvas.getByTestId('loading-toggle');
|
|
240
|
-
await userEvent.click(loadingToggle);
|
|
241
|
-
|
|
242
|
-
const stateDisplay = canvas.getByTestId('state-display');
|
|
243
|
-
await expect(stateDisplay).toHaveTextContent('Loading: true');
|
|
244
|
-
|
|
245
|
-
const loadingInput = canvas.getByTestId('loading-input');
|
|
246
|
-
await expect(loadingInput).toBeDisabled();
|
|
247
|
-
});
|
|
248
|
-
},
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
// ============================================================================
|
|
252
|
-
// ACCESSIBILITY TESTS
|
|
253
|
-
// ============================================================================
|
|
254
|
-
|
|
255
|
-
export const KeyboardNavigation: Story = {
|
|
256
|
-
name: '⌨️ Keyboard Navigation Test',
|
|
257
|
-
render: () => (
|
|
258
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
|
|
259
|
-
<Input label="First Input" placeholder="Tab to navigate" data-testid="first-input" />
|
|
260
|
-
<Input label="Second Input" placeholder="Continue tabbing" data-testid="second-input" />
|
|
261
|
-
<Input label="Third Input" placeholder="Final input" data-testid="third-input" />
|
|
262
|
-
<button data-testid="after-inputs">Button After Inputs</button>
|
|
263
|
-
</div>
|
|
264
|
-
),
|
|
265
|
-
parameters: {
|
|
266
|
-
a11y: {
|
|
267
|
-
element: '#storybook-root',
|
|
268
|
-
config: {
|
|
269
|
-
rules: [
|
|
270
|
-
{ id: 'color-contrast', enabled: true },
|
|
271
|
-
{ id: 'aria-required-attr', enabled: true },
|
|
272
|
-
{ id: 'aria-roles', enabled: true },
|
|
273
|
-
{ id: 'aria-valid-attr-value', enabled: true },
|
|
274
|
-
{ id: 'label', enabled: true },
|
|
275
|
-
],
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
play: async ({ canvasElement, step }) => {
|
|
280
|
-
const canvas = within(canvasElement);
|
|
281
|
-
|
|
282
|
-
await step('Tab navigation forward', async () => {
|
|
283
|
-
const firstInput = canvas.getByTestId('first-input');
|
|
284
|
-
const secondInput = canvas.getByTestId('second-input');
|
|
285
|
-
|
|
286
|
-
// Tab in from the body rather than calling .focus().
|
|
287
|
-
await userEvent.tab();
|
|
288
|
-
await waitFor(() => expect(firstInput).toHaveFocus());
|
|
289
|
-
|
|
290
|
-
// Tab to next element
|
|
291
|
-
await userEvent.tab();
|
|
292
|
-
await waitFor(() => expect(secondInput).toHaveFocus());
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
await step('Tab navigation continues', async () => {
|
|
296
|
-
const thirdInput = canvas.getByTestId('third-input');
|
|
297
|
-
const button = canvas.getByTestId('after-inputs');
|
|
298
|
-
|
|
299
|
-
await userEvent.tab();
|
|
300
|
-
await waitFor(() => expect(thirdInput).toHaveFocus());
|
|
301
|
-
|
|
302
|
-
await userEvent.tab();
|
|
303
|
-
await waitFor(() => expect(button).toHaveFocus());
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
await step('Tab navigation backward', async () => {
|
|
307
|
-
const thirdInput = canvas.getByTestId('third-input');
|
|
308
|
-
|
|
309
|
-
await userEvent.tab({ shift: true });
|
|
310
|
-
await waitFor(() => expect(thirdInput).toHaveFocus());
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
await step('Enter key in input (should not submit)', async () => {
|
|
314
|
-
const firstInput = canvas.getByTestId('first-input');
|
|
315
|
-
|
|
316
|
-
// Typing into an input focuses it, so the extra .focus() was redundant.
|
|
317
|
-
await userEvent.type(firstInput, 'test');
|
|
318
|
-
await userEvent.keyboard('{Enter}');
|
|
319
|
-
|
|
320
|
-
// Input should still have focus and content
|
|
321
|
-
await waitFor(() => expect(firstInput).toHaveFocus());
|
|
322
|
-
await expect(firstInput).toHaveValue('test');
|
|
323
|
-
});
|
|
324
|
-
},
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
export const ScreenReaderTest: Story = {
|
|
328
|
-
name: '🔊 Screen Reader Test',
|
|
329
|
-
render: () => (
|
|
330
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
|
|
331
|
-
<Input
|
|
332
|
-
label="Full Name"
|
|
333
|
-
placeholder="Enter your full name"
|
|
334
|
-
helperText="This field is required for registration"
|
|
335
|
-
required
|
|
336
|
-
data-testid="name-input"
|
|
337
|
-
aria-describedby="name-description"
|
|
338
|
-
/>
|
|
339
|
-
<div id="name-description" data-testid="name-description">
|
|
340
|
-
Please enter your first and last name
|
|
341
|
-
</div>
|
|
342
|
-
|
|
343
|
-
<Input
|
|
344
|
-
label="Email Address"
|
|
345
|
-
type="email"
|
|
346
|
-
error
|
|
347
|
-
helperText="Please enter a valid email address"
|
|
348
|
-
data-testid="email-input"
|
|
349
|
-
aria-invalid="true"
|
|
350
|
-
aria-describedby="email-error"
|
|
351
|
-
/>
|
|
352
|
-
<div id="email-error" data-testid="email-error" role="alert">
|
|
353
|
-
The email format is invalid
|
|
354
|
-
</div>
|
|
355
|
-
</div>
|
|
356
|
-
),
|
|
357
|
-
play: async ({ canvasElement, step }) => {
|
|
358
|
-
const canvas = within(canvasElement);
|
|
359
|
-
|
|
360
|
-
await step('Verify ARIA labels', async () => {
|
|
361
|
-
const nameInput = canvas.getByTestId('name-input');
|
|
362
|
-
await expect(nameInput).toBeInTheDocument();
|
|
363
|
-
await expect(nameInput).toHaveAttribute('required');
|
|
364
|
-
|
|
365
|
-
// Verify the label is associated correctly
|
|
366
|
-
const label = nameInput.closest('.MuiFormControl-root')?.querySelector('label');
|
|
367
|
-
await expect(label).toHaveTextContent('Full Name');
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
await step('Verify ARIA descriptions', async () => {
|
|
371
|
-
const nameInput = canvas.getByTestId('name-input');
|
|
372
|
-
// MUI generates its own aria-describedby ID for helper text
|
|
373
|
-
const ariaDescribedBy = nameInput.getAttribute('aria-describedby');
|
|
374
|
-
await expect(ariaDescribedBy).toBeTruthy();
|
|
375
|
-
await expect(ariaDescribedBy).toMatch(/helper-text/);
|
|
376
|
-
|
|
377
|
-
const description = canvas.getByTestId('name-description');
|
|
378
|
-
await expect(description).toBeInTheDocument();
|
|
379
|
-
await expect(description).toHaveTextContent('Please enter your first and last name');
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
await step('Verify error state attributes', async () => {
|
|
383
|
-
const emailInput = canvas.getByTestId('email-input');
|
|
384
|
-
await expect(emailInput).toHaveAttribute('aria-invalid', 'true');
|
|
385
|
-
// MUI generates its own aria-describedby ID for helper text
|
|
386
|
-
const emailAriaDescribedBy = emailInput.getAttribute('aria-describedby');
|
|
387
|
-
await expect(emailAriaDescribedBy).toBeTruthy();
|
|
388
|
-
await expect(emailAriaDescribedBy).toMatch(/helper-text/);
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
await step('Verify live regions', async () => {
|
|
392
|
-
const errorRegion = canvas.getByTestId('email-error');
|
|
393
|
-
await expect(errorRegion).toHaveAttribute('role', 'alert');
|
|
394
|
-
await expect(errorRegion).toHaveTextContent('The email format is invalid');
|
|
395
|
-
});
|
|
396
|
-
},
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
export const FocusManagement: Story = {
|
|
400
|
-
name: '🎯 Focus Management Test',
|
|
401
|
-
render: () => {
|
|
402
|
-
const FocusTestComponent = () => {
|
|
403
|
-
const [showModal, setShowModal] = useState(false);
|
|
404
|
-
|
|
405
|
-
return (
|
|
406
|
-
<div>
|
|
407
|
-
<Input
|
|
408
|
-
label="Trigger Input"
|
|
409
|
-
placeholder="Click here first"
|
|
410
|
-
data-testid="trigger-input"
|
|
411
|
-
autoFocus
|
|
412
|
-
/>
|
|
413
|
-
|
|
414
|
-
<button onClick={() => setShowModal(true)} data-testid="open-modal">
|
|
415
|
-
Open Modal
|
|
416
|
-
</button>
|
|
417
|
-
|
|
418
|
-
{showModal && (
|
|
419
|
-
<div
|
|
420
|
-
role="dialog"
|
|
421
|
-
aria-modal="true"
|
|
422
|
-
style={{
|
|
423
|
-
position: 'fixed',
|
|
424
|
-
top: '50%',
|
|
425
|
-
left: '50%',
|
|
426
|
-
transform: 'translate(-50%, -50%)',
|
|
427
|
-
background: 'white',
|
|
428
|
-
padding: '20px',
|
|
429
|
-
border: '1px solid #ccc',
|
|
430
|
-
borderRadius: '8px',
|
|
431
|
-
boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
|
|
432
|
-
}}
|
|
433
|
-
data-testid="modal"
|
|
434
|
-
>
|
|
435
|
-
<Input
|
|
436
|
-
label="Modal Input"
|
|
437
|
-
placeholder="Focus trapped here"
|
|
438
|
-
data-testid="modal-input"
|
|
439
|
-
autoFocus
|
|
440
|
-
/>
|
|
441
|
-
<button onClick={() => setShowModal(false)} data-testid="close-modal">
|
|
442
|
-
Close
|
|
443
|
-
</button>
|
|
444
|
-
</div>
|
|
445
|
-
)}
|
|
446
|
-
</div>
|
|
447
|
-
);
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
return <FocusTestComponent />;
|
|
451
|
-
},
|
|
452
|
-
play: async ({ canvasElement, step }) => {
|
|
453
|
-
const canvas = within(canvasElement);
|
|
454
|
-
|
|
455
|
-
await step('Auto focus on mount', async () => {
|
|
456
|
-
const triggerInput = canvas.getByTestId('trigger-input');
|
|
457
|
-
await waitFor(() => {
|
|
458
|
-
expect(triggerInput).toHaveFocus();
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
await step('Focus management in modal', async () => {
|
|
463
|
-
const openButton = canvas.getByTestId('open-modal');
|
|
464
|
-
await userEvent.click(openButton);
|
|
465
|
-
|
|
466
|
-
const modal = await canvas.findByTestId('modal');
|
|
467
|
-
await expect(modal).toBeInTheDocument();
|
|
468
|
-
|
|
469
|
-
const modalInput = within(modal).getByTestId('modal-input');
|
|
470
|
-
await waitFor(() => {
|
|
471
|
-
expect(modalInput).toHaveFocus();
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
await step('Focus restoration after modal close', async () => {
|
|
476
|
-
const modal = canvas.getByTestId('modal');
|
|
477
|
-
const closeButton = within(modal).getByTestId('close-modal');
|
|
478
|
-
const openButton = canvas.getByTestId('open-modal');
|
|
479
|
-
|
|
480
|
-
// No pre-focus: the assertion below is about the modal being removed, and
|
|
481
|
-
// .focus() on the open button neither affected that nor was ever checked.
|
|
482
|
-
await expect(openButton).toBeInTheDocument();
|
|
483
|
-
await userEvent.click(closeButton);
|
|
484
|
-
|
|
485
|
-
// Modal should be gone
|
|
486
|
-
await waitFor(() => {
|
|
487
|
-
expect(canvas.queryByTestId('modal')).not.toBeInTheDocument();
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
},
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
// ============================================================================
|
|
494
|
-
// VISUAL TESTS
|
|
495
|
-
// ============================================================================
|
|
496
|
-
|
|
497
|
-
export const ResponsiveDesign: Story = {
|
|
498
|
-
name: '📱 Responsive Design Test',
|
|
499
|
-
render: () => (
|
|
500
|
-
<div
|
|
501
|
-
style={{
|
|
502
|
-
display: 'grid',
|
|
503
|
-
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
|
|
504
|
-
gap: '16px',
|
|
505
|
-
}}
|
|
506
|
-
>
|
|
507
|
-
<Input
|
|
508
|
-
variant="outlined"
|
|
509
|
-
label="Responsive Input 1"
|
|
510
|
-
placeholder="Resize window to test"
|
|
511
|
-
data-testid="responsive-1"
|
|
512
|
-
/>
|
|
513
|
-
<Input
|
|
514
|
-
variant="glass"
|
|
515
|
-
label="Responsive Input 2"
|
|
516
|
-
placeholder="Watch layout adapt"
|
|
517
|
-
data-testid="responsive-2"
|
|
518
|
-
/>
|
|
519
|
-
<Input
|
|
520
|
-
variant="gradient"
|
|
521
|
-
label="Responsive Input 3"
|
|
522
|
-
placeholder="Grid adjusts automatically"
|
|
523
|
-
data-testid="responsive-3"
|
|
524
|
-
/>
|
|
525
|
-
</div>
|
|
526
|
-
),
|
|
527
|
-
parameters: {
|
|
528
|
-
viewport: {
|
|
529
|
-
viewports: {
|
|
530
|
-
mobile: {
|
|
531
|
-
name: 'Mobile',
|
|
532
|
-
styles: { width: '375px', height: '667px' },
|
|
533
|
-
type: 'mobile',
|
|
534
|
-
},
|
|
535
|
-
tablet: {
|
|
536
|
-
name: 'Tablet',
|
|
537
|
-
styles: { width: '768px', height: '1024px' },
|
|
538
|
-
type: 'tablet',
|
|
539
|
-
},
|
|
540
|
-
desktop: {
|
|
541
|
-
name: 'Desktop',
|
|
542
|
-
styles: { width: '1920px', height: '1080px' },
|
|
543
|
-
type: 'desktop',
|
|
544
|
-
},
|
|
545
|
-
},
|
|
546
|
-
defaultViewport: 'mobile',
|
|
547
|
-
},
|
|
548
|
-
chromatic: {
|
|
549
|
-
viewports: [375, 768, 1920],
|
|
550
|
-
delay: 300,
|
|
551
|
-
},
|
|
552
|
-
},
|
|
553
|
-
play: async ({ canvasElement, step }) => {
|
|
554
|
-
const canvas = within(canvasElement);
|
|
555
|
-
|
|
556
|
-
await step('Verify responsive container', async () => {
|
|
557
|
-
const container = canvasElement.querySelector('div[style*="grid"]') as HTMLElement;
|
|
558
|
-
const computedStyle = window.getComputedStyle(container);
|
|
559
|
-
|
|
560
|
-
// Check that CSS Grid is being used
|
|
561
|
-
await expect(computedStyle.display).toBe('grid');
|
|
562
|
-
|
|
563
|
-
// Check that grid has appropriate column layout (computed may be different from raw CSS)
|
|
564
|
-
const gridCols = computedStyle.gridTemplateColumns;
|
|
565
|
-
// Should have a meaningful grid layout (not none or empty)
|
|
566
|
-
await expect(gridCols).not.toBe('none');
|
|
567
|
-
await expect(gridCols.length).toBeGreaterThan(0);
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
await step('Verify inputs are functional at all sizes', async () => {
|
|
571
|
-
const input1 = canvas.getByTestId('responsive-1');
|
|
572
|
-
const input2 = canvas.getByTestId('responsive-2');
|
|
573
|
-
const input3 = canvas.getByTestId('responsive-3');
|
|
574
|
-
|
|
575
|
-
// Test interaction on each input
|
|
576
|
-
await userEvent.click(input1);
|
|
577
|
-
await waitFor(() => expect(input1).toHaveFocus());
|
|
578
|
-
|
|
579
|
-
await userEvent.click(input2);
|
|
580
|
-
await waitFor(() => expect(input2).toHaveFocus());
|
|
581
|
-
|
|
582
|
-
await userEvent.click(input3);
|
|
583
|
-
await waitFor(() => expect(input3).toHaveFocus());
|
|
584
|
-
});
|
|
585
|
-
},
|
|
586
|
-
};
|
|
587
|
-
|
|
588
|
-
export const ThemeVariations: Story = {
|
|
589
|
-
name: '🎨 Theme Variations Test',
|
|
590
|
-
render: () => (
|
|
591
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
|
|
592
|
-
<Input
|
|
593
|
-
variant="outlined"
|
|
594
|
-
label="Outlined Theme"
|
|
595
|
-
placeholder="Default theme"
|
|
596
|
-
data-testid="theme-outlined"
|
|
597
|
-
/>
|
|
598
|
-
<Input
|
|
599
|
-
variant="glass"
|
|
600
|
-
label="Glass Theme"
|
|
601
|
-
placeholder="Glass morphism"
|
|
602
|
-
data-testid="theme-glass"
|
|
603
|
-
/>
|
|
604
|
-
<Input
|
|
605
|
-
variant="gradient"
|
|
606
|
-
label="Gradient Theme"
|
|
607
|
-
placeholder="Gradient borders"
|
|
608
|
-
data-testid="theme-gradient"
|
|
609
|
-
/>
|
|
610
|
-
</div>
|
|
611
|
-
),
|
|
612
|
-
parameters: {
|
|
613
|
-
backgrounds: {
|
|
614
|
-
default: 'light',
|
|
615
|
-
values: [
|
|
616
|
-
{ name: 'light', value: '#ffffff' },
|
|
617
|
-
{ name: 'dark', value: '#1a1a1a' },
|
|
618
|
-
{ name: 'blue', value: '#1e3a8a' },
|
|
619
|
-
],
|
|
620
|
-
},
|
|
621
|
-
},
|
|
622
|
-
play: async ({ canvasElement, step }) => {
|
|
623
|
-
const canvas = within(canvasElement);
|
|
624
|
-
|
|
625
|
-
await step('Verify theme styling is applied', async () => {
|
|
626
|
-
const glassInput = canvas.getByTestId('theme-glass');
|
|
627
|
-
const gradientInput = canvas.getByTestId('theme-gradient');
|
|
628
|
-
|
|
629
|
-
// These inputs should be visible and styled
|
|
630
|
-
await expect(glassInput).toBeVisible();
|
|
631
|
-
await expect(gradientInput).toBeVisible();
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
await step('Test theme consistency on focus', async () => {
|
|
635
|
-
const outlinedInput = canvas.getByTestId('theme-outlined');
|
|
636
|
-
const glassInput = canvas.getByTestId('theme-glass');
|
|
637
|
-
|
|
638
|
-
// Focus each input to test theme-aware focus styles
|
|
639
|
-
await userEvent.click(outlinedInput);
|
|
640
|
-
await waitFor(() => expect(outlinedInput).toHaveFocus());
|
|
641
|
-
|
|
642
|
-
await userEvent.click(glassInput);
|
|
643
|
-
await waitFor(() => expect(glassInput).toHaveFocus());
|
|
644
|
-
});
|
|
645
|
-
},
|
|
646
|
-
};
|
|
647
|
-
|
|
648
|
-
export const VisualStates: Story = {
|
|
649
|
-
name: '👁️ Visual States Test',
|
|
650
|
-
render: () => (
|
|
651
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
|
|
652
|
-
<Input label="Default State" placeholder="Normal input" data-testid="default-state" />
|
|
653
|
-
<Input label="Hover State" placeholder="Hover over me" data-testid="hover-state" />
|
|
654
|
-
<Input label="Focus State" placeholder="Click to focus" data-testid="focus-state" />
|
|
655
|
-
<Input
|
|
656
|
-
label="Disabled State"
|
|
657
|
-
placeholder="Cannot interact"
|
|
658
|
-
disabled
|
|
659
|
-
data-testid="disabled-state"
|
|
660
|
-
/>
|
|
661
|
-
<Input
|
|
662
|
-
label="Error State"
|
|
663
|
-
placeholder="Has error"
|
|
664
|
-
error
|
|
665
|
-
helperText="This field has an error"
|
|
666
|
-
data-testid="error-state"
|
|
667
|
-
/>
|
|
668
|
-
<Input
|
|
669
|
-
label="Loading State"
|
|
670
|
-
placeholder="Processing..."
|
|
671
|
-
loading
|
|
672
|
-
data-testid="loading-state"
|
|
673
|
-
/>
|
|
674
|
-
<Input
|
|
675
|
-
variant="glass"
|
|
676
|
-
label="Glow Effect"
|
|
677
|
-
placeholder="Has glow"
|
|
678
|
-
glow
|
|
679
|
-
data-testid="glow-state"
|
|
680
|
-
/>
|
|
681
|
-
</div>
|
|
682
|
-
),
|
|
683
|
-
play: async ({ canvasElement, step }) => {
|
|
684
|
-
const canvas = within(canvasElement);
|
|
685
|
-
|
|
686
|
-
await step('Default state is visible', async () => {
|
|
687
|
-
const defaultInput = canvas.getByTestId('default-state');
|
|
688
|
-
await expect(defaultInput).toBeVisible();
|
|
689
|
-
await expect(defaultInput).not.toBeDisabled();
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
await step('Hover state interaction', async () => {
|
|
693
|
-
const hoverInput = canvas.getByTestId('hover-state');
|
|
694
|
-
await userEvent.hover(hoverInput);
|
|
695
|
-
// Visual hover effects should be applied via CSS
|
|
696
|
-
await expect(hoverInput).toBeVisible();
|
|
697
|
-
});
|
|
698
|
-
|
|
699
|
-
await step('Focus state interaction', async () => {
|
|
700
|
-
const focusInput = canvas.getByTestId('focus-state');
|
|
701
|
-
await userEvent.click(focusInput);
|
|
702
|
-
await waitFor(() => expect(focusInput).toHaveFocus());
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
await step('Disabled state verification', async () => {
|
|
706
|
-
const disabledInput = canvas.getByTestId('disabled-state');
|
|
707
|
-
await expect(disabledInput).toBeDisabled();
|
|
708
|
-
// Try to click (should not gain focus)
|
|
709
|
-
await userEvent.click(disabledInput);
|
|
710
|
-
await waitFor(() => expect(disabledInput).not.toHaveFocus());
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
await step('Error state verification', async () => {
|
|
714
|
-
const errorInput = canvas.getByTestId('error-state');
|
|
715
|
-
await expect(errorInput).toHaveAttribute('aria-invalid', 'true');
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
await step('Loading state verification', async () => {
|
|
719
|
-
const loadingInput = canvas.getByTestId('loading-state');
|
|
720
|
-
await expect(loadingInput).toBeDisabled();
|
|
721
|
-
|
|
722
|
-
// Should have a loading spinner in end adornment
|
|
723
|
-
const spinner = canvas.getByRole('progressbar');
|
|
724
|
-
await expect(spinner).toBeInTheDocument();
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
await step('Glow state interaction', async () => {
|
|
728
|
-
const glowInput = canvas.getByTestId('glow-state');
|
|
729
|
-
await userEvent.click(glowInput);
|
|
730
|
-
await waitFor(() => expect(glowInput).toHaveFocus());
|
|
731
|
-
// Glow effect should be visible via CSS
|
|
732
|
-
});
|
|
733
|
-
},
|
|
734
|
-
};
|
|
735
|
-
|
|
736
|
-
// ============================================================================
|
|
737
|
-
// EDGE CASES TESTS
|
|
738
|
-
// ============================================================================
|
|
739
|
-
|
|
740
|
-
export const EdgeCases: Story = {
|
|
741
|
-
name: '🔧 Edge Cases Test',
|
|
742
|
-
render: () => (
|
|
743
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '300px' }}>
|
|
744
|
-
<Input
|
|
745
|
-
label="Empty with Floating"
|
|
746
|
-
floating
|
|
747
|
-
placeholder="Floating label behavior"
|
|
748
|
-
data-testid="empty-floating"
|
|
749
|
-
/>
|
|
750
|
-
<Input
|
|
751
|
-
label="Long Text Overflow"
|
|
752
|
-
defaultValue="This is a very long text that should show how the input handles overflow and long content gracefully without breaking the layout"
|
|
753
|
-
data-testid="long-text"
|
|
754
|
-
/>
|
|
755
|
-
<Input
|
|
756
|
-
label="No Full Width"
|
|
757
|
-
fullWidth={false}
|
|
758
|
-
placeholder="Not full width"
|
|
759
|
-
data-testid="not-full-width"
|
|
760
|
-
/>
|
|
761
|
-
<Input
|
|
762
|
-
label="All Effects Combined"
|
|
763
|
-
variant="glass"
|
|
764
|
-
glow
|
|
765
|
-
pulse
|
|
766
|
-
floating
|
|
767
|
-
placeholder="Kitchen sink example"
|
|
768
|
-
helperText="All visual effects enabled"
|
|
769
|
-
data-testid="all-effects"
|
|
770
|
-
/>
|
|
771
|
-
<Input
|
|
772
|
-
label="Special Characters"
|
|
773
|
-
placeholder="Test: !@#$%^&*()_+-=[]{}|;:,.<>?"
|
|
774
|
-
data-testid="special-chars"
|
|
775
|
-
/>
|
|
776
|
-
<Input label="Unicode Support" placeholder="测试 العربية Русский 🚀" data-testid="unicode" />
|
|
777
|
-
</div>
|
|
778
|
-
),
|
|
779
|
-
play: async ({ canvasElement, step }) => {
|
|
780
|
-
const canvas = within(canvasElement);
|
|
781
|
-
|
|
782
|
-
await step('Empty floating label behavior', async () => {
|
|
783
|
-
const floatingInput = canvas.getByTestId('empty-floating');
|
|
784
|
-
|
|
785
|
-
// Label should be in placeholder position initially
|
|
786
|
-
await expect(floatingInput).toHaveAttribute('placeholder', 'Floating label behavior');
|
|
787
|
-
|
|
788
|
-
// Type something to trigger label float
|
|
789
|
-
await userEvent.click(floatingInput);
|
|
790
|
-
await userEvent.type(floatingInput, 'test');
|
|
791
|
-
await expect(floatingInput).toHaveValue('test');
|
|
792
|
-
|
|
793
|
-
// Clear and blur to test label return
|
|
794
|
-
await userEvent.clear(floatingInput);
|
|
795
|
-
await userEvent.tab();
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
await step('Long text overflow handling', async () => {
|
|
799
|
-
const longTextInput = canvas.getByTestId('long-text');
|
|
800
|
-
await expect(longTextInput.value).toContain('This is a very long text');
|
|
801
|
-
|
|
802
|
-
// Input should handle overflow gracefully
|
|
803
|
-
await userEvent.click(longTextInput);
|
|
804
|
-
await userEvent.keyboard('{End}');
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
await step('Width constraints', async () => {
|
|
808
|
-
const notFullWidthInput = canvas.getByTestId('not-full-width');
|
|
809
|
-
await expect(notFullWidthInput).toBeVisible();
|
|
810
|
-
|
|
811
|
-
// Should not fill full container width
|
|
812
|
-
const inputElement = notFullWidthInput.closest('.MuiTextField-root') as HTMLElement;
|
|
813
|
-
await expect(inputElement).toBeInTheDocument();
|
|
814
|
-
});
|
|
815
|
-
|
|
816
|
-
await step('Combined effects functionality', async () => {
|
|
817
|
-
const allEffectsInput = canvas.getByTestId('all-effects');
|
|
818
|
-
|
|
819
|
-
// Should be visible and interactive despite all effects
|
|
820
|
-
await userEvent.click(allEffectsInput);
|
|
821
|
-
await waitFor(() => expect(allEffectsInput).toHaveFocus());
|
|
822
|
-
|
|
823
|
-
await userEvent.type(allEffectsInput, 'works with all effects');
|
|
824
|
-
await expect(allEffectsInput).toHaveValue('works with all effects');
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
await step('Special characters input', async () => {
|
|
828
|
-
const specialCharsInput = canvas.getByTestId('special-chars');
|
|
829
|
-
const specialText = '!@#$%^&*()_+-=test'; // Simplified to avoid userEvent.type issues
|
|
830
|
-
|
|
831
|
-
await userEvent.click(specialCharsInput);
|
|
832
|
-
await userEvent.type(specialCharsInput, specialText);
|
|
833
|
-
await expect(specialCharsInput).toHaveValue(specialText);
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
await step('Unicode support', async () => {
|
|
837
|
-
const unicodeInput = canvas.getByTestId('unicode');
|
|
838
|
-
const unicodeText = '测试 العربية Русский 🚀';
|
|
839
|
-
|
|
840
|
-
await userEvent.click(unicodeInput);
|
|
841
|
-
await userEvent.type(unicodeInput, unicodeText);
|
|
842
|
-
await expect(unicodeInput).toHaveValue(unicodeText);
|
|
843
|
-
});
|
|
844
|
-
},
|
|
845
|
-
};
|
|
846
|
-
|
|
847
|
-
// ============================================================================
|
|
848
|
-
// PERFORMANCE TESTS
|
|
849
|
-
// ============================================================================
|
|
850
|
-
|
|
851
|
-
export const PerformanceTest: Story = {
|
|
852
|
-
name: '⚡ Performance Test',
|
|
853
|
-
render: () => {
|
|
854
|
-
const inputs = Array.from({ length: 50 }, (_, i) => (
|
|
855
|
-
<Input
|
|
856
|
-
key={i}
|
|
857
|
-
label={`Performance Input ${i + 1}`}
|
|
858
|
-
placeholder={`Input number ${i + 1}`}
|
|
859
|
-
data-testid={`perf-input-${i}`}
|
|
860
|
-
variant={i % 3 === 0 ? 'glass' : i % 3 === 1 ? 'gradient' : 'outlined'}
|
|
861
|
-
/>
|
|
862
|
-
));
|
|
863
|
-
|
|
864
|
-
return (
|
|
865
|
-
<div
|
|
866
|
-
style={{
|
|
867
|
-
display: 'grid',
|
|
868
|
-
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
|
|
869
|
-
gap: '16px',
|
|
870
|
-
maxHeight: '400px',
|
|
871
|
-
overflowY: 'auto',
|
|
872
|
-
padding: '16px',
|
|
873
|
-
}}
|
|
874
|
-
>
|
|
875
|
-
{inputs}
|
|
876
|
-
</div>
|
|
877
|
-
);
|
|
878
|
-
},
|
|
879
|
-
play: async ({ canvasElement, step }) => {
|
|
880
|
-
const canvas = within(canvasElement);
|
|
881
|
-
|
|
882
|
-
await step('Measure render time for multiple inputs', async () => {
|
|
883
|
-
const startTime = window.performance.now();
|
|
884
|
-
const inputs = canvas.getAllByTestId(/perf-input-/);
|
|
885
|
-
const endTime = window.performance.now();
|
|
886
|
-
|
|
887
|
-
const renderTime = endTime - startTime;
|
|
888
|
-
// console.log(`Found ${inputs.length} inputs in ${renderTime}ms`);
|
|
889
|
-
|
|
890
|
-
// Assert we have all inputs
|
|
891
|
-
await expect(inputs).toHaveLength(50);
|
|
892
|
-
|
|
893
|
-
// Assert reasonable performance (adjust threshold as needed)
|
|
894
|
-
await expect(renderTime).toBeLessThan(100);
|
|
895
|
-
});
|
|
896
|
-
|
|
897
|
-
await step('Test interaction performance', async () => {
|
|
898
|
-
// Test clicking on several inputs rapidly
|
|
899
|
-
const inputs = canvas.getAllByTestId(/perf-input-/).slice(0, 5);
|
|
900
|
-
|
|
901
|
-
const startTime = window.performance.now();
|
|
902
|
-
|
|
903
|
-
for (const input of inputs) {
|
|
904
|
-
await userEvent.click(input);
|
|
905
|
-
await userEvent.type(input, 'fast');
|
|
906
|
-
await userEvent.clear(input);
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
const endTime = window.performance.now();
|
|
910
|
-
const interactionTime = endTime - startTime;
|
|
911
|
-
|
|
912
|
-
// console.log(`Interaction test completed in ${interactionTime}ms`);
|
|
913
|
-
|
|
914
|
-
// Should complete interactions in reasonable time
|
|
915
|
-
await expect(interactionTime).toBeLessThan(2000);
|
|
916
|
-
});
|
|
917
|
-
|
|
918
|
-
await step('Test scroll performance', async () => {
|
|
919
|
-
const container = canvasElement.querySelector('[style*="overflowY"]') as HTMLElement;
|
|
920
|
-
|
|
921
|
-
if (container) {
|
|
922
|
-
// Simulate scrolling
|
|
923
|
-
container.scrollTop = 100;
|
|
924
|
-
await new Promise((resolve) => window.setTimeout(resolve, 100));
|
|
925
|
-
|
|
926
|
-
container.scrollTop = 200;
|
|
927
|
-
await new Promise((resolve) => window.setTimeout(resolve, 100));
|
|
928
|
-
|
|
929
|
-
// Verify scroll worked
|
|
930
|
-
await expect(container.scrollTop).toBeGreaterThan(0);
|
|
931
|
-
}
|
|
932
|
-
});
|
|
933
|
-
},
|
|
934
|
-
};
|
|
935
|
-
|
|
936
|
-
// ============================================================================
|
|
937
|
-
// INTEGRATION TESTS
|
|
938
|
-
// ============================================================================
|
|
939
|
-
|
|
940
|
-
export const IntegrationTest: Story = {
|
|
941
|
-
name: '🔗 Integration Test',
|
|
942
|
-
render: () => {
|
|
943
|
-
const IntegrationComponent = () => {
|
|
944
|
-
const [formData, setFormData] = useState({
|
|
945
|
-
firstName: '',
|
|
946
|
-
lastName: '',
|
|
947
|
-
email: '',
|
|
948
|
-
password: '',
|
|
949
|
-
confirmPassword: '',
|
|
950
|
-
});
|
|
951
|
-
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
952
|
-
const [showPassword, setShowPassword] = useState(false);
|
|
953
|
-
|
|
954
|
-
const handleChange = (field: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
955
|
-
const newValue = e.target.value;
|
|
956
|
-
const newFormData = { ...formData, [field]: newValue };
|
|
957
|
-
setFormData(newFormData);
|
|
958
|
-
|
|
959
|
-
// Validate this specific field
|
|
960
|
-
const newErrors = { ...errors };
|
|
961
|
-
|
|
962
|
-
if (field === 'firstName' && !newValue) {
|
|
963
|
-
newErrors.firstName = 'First name is required';
|
|
964
|
-
} else if (field === 'firstName') {
|
|
965
|
-
delete newErrors.firstName;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
if (field === 'lastName' && !newValue) {
|
|
969
|
-
newErrors.lastName = 'Last name is required';
|
|
970
|
-
} else if (field === 'lastName') {
|
|
971
|
-
delete newErrors.lastName;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
if (field === 'email' && !newValue) {
|
|
975
|
-
newErrors.email = 'Email is required';
|
|
976
|
-
} else if (field === 'email') {
|
|
977
|
-
delete newErrors.email;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
if (field === 'password' && !newValue) {
|
|
981
|
-
newErrors.password = 'Password is required';
|
|
982
|
-
} else if (field === 'password') {
|
|
983
|
-
delete newErrors.password;
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
if (field === 'confirmPassword') {
|
|
987
|
-
if (!newValue) {
|
|
988
|
-
newErrors.confirmPassword = 'Confirm password is required';
|
|
989
|
-
} else if (newFormData.password !== newValue) {
|
|
990
|
-
newErrors.confirmPassword = 'Passwords do not match';
|
|
991
|
-
} else {
|
|
992
|
-
delete newErrors.confirmPassword;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
// Also check password match when password field changes
|
|
997
|
-
if (field === 'password' && newFormData.confirmPassword) {
|
|
998
|
-
if (newValue !== newFormData.confirmPassword) {
|
|
999
|
-
newErrors.confirmPassword = 'Passwords do not match';
|
|
1000
|
-
} else {
|
|
1001
|
-
delete newErrors.confirmPassword;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
setErrors(newErrors);
|
|
1006
|
-
};
|
|
1007
|
-
|
|
1008
|
-
const validateForm = () => {
|
|
1009
|
-
const newErrors: Record<string, string> = {};
|
|
1010
|
-
|
|
1011
|
-
if (!formData.firstName) newErrors.firstName = 'First name is required';
|
|
1012
|
-
if (!formData.lastName) newErrors.lastName = 'Last name is required';
|
|
1013
|
-
if (!formData.email) newErrors.email = 'Email is required';
|
|
1014
|
-
if (!formData.password) newErrors.password = 'Password is required';
|
|
1015
|
-
if (!formData.confirmPassword) {
|
|
1016
|
-
newErrors.confirmPassword = 'Confirm password is required';
|
|
1017
|
-
} else if (formData.password !== formData.confirmPassword) {
|
|
1018
|
-
newErrors.confirmPassword = 'Passwords do not match';
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
setErrors(newErrors);
|
|
1022
|
-
return Object.keys(newErrors).length === 0;
|
|
1023
|
-
};
|
|
1024
|
-
|
|
1025
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
1026
|
-
e.preventDefault();
|
|
1027
|
-
if (validateForm()) {
|
|
1028
|
-
window.alert('Form submitted successfully!');
|
|
1029
|
-
}
|
|
1030
|
-
};
|
|
1031
|
-
|
|
1032
|
-
return (
|
|
1033
|
-
<form
|
|
1034
|
-
onSubmit={handleSubmit}
|
|
1035
|
-
style={{ display: 'flex', flexDirection: 'column', gap: '16px', width: '400px' }}
|
|
1036
|
-
>
|
|
1037
|
-
<div style={{ display: 'flex', gap: '16px' }}>
|
|
1038
|
-
<Input
|
|
1039
|
-
label="First Name"
|
|
1040
|
-
value={formData.firstName}
|
|
1041
|
-
onChange={handleChange('firstName')}
|
|
1042
|
-
error={!!errors.firstName}
|
|
1043
|
-
helperText={errors.firstName}
|
|
1044
|
-
data-testid="first-name"
|
|
1045
|
-
startAdornment={<Person />}
|
|
1046
|
-
fullWidth
|
|
1047
|
-
/>
|
|
1048
|
-
<Input
|
|
1049
|
-
label="Last Name"
|
|
1050
|
-
value={formData.lastName}
|
|
1051
|
-
onChange={handleChange('lastName')}
|
|
1052
|
-
error={!!errors.lastName}
|
|
1053
|
-
helperText={errors.lastName}
|
|
1054
|
-
data-testid="last-name"
|
|
1055
|
-
fullWidth
|
|
1056
|
-
/>
|
|
1057
|
-
</div>
|
|
1058
|
-
|
|
1059
|
-
<Input
|
|
1060
|
-
label="Email"
|
|
1061
|
-
type="email"
|
|
1062
|
-
value={formData.email}
|
|
1063
|
-
onChange={handleChange('email')}
|
|
1064
|
-
error={!!errors.email}
|
|
1065
|
-
helperText={errors.email}
|
|
1066
|
-
data-testid="email"
|
|
1067
|
-
startAdornment={<Email />}
|
|
1068
|
-
/>
|
|
1069
|
-
|
|
1070
|
-
<Input
|
|
1071
|
-
label="Password"
|
|
1072
|
-
type={showPassword ? 'text' : 'password'}
|
|
1073
|
-
value={formData.password}
|
|
1074
|
-
onChange={handleChange('password')}
|
|
1075
|
-
error={!!errors.password}
|
|
1076
|
-
helperText={errors.password}
|
|
1077
|
-
data-testid="password"
|
|
1078
|
-
startAdornment={<Lock />}
|
|
1079
|
-
endAdornment={
|
|
1080
|
-
<IconButton
|
|
1081
|
-
onClick={() => setShowPassword(!showPassword)}
|
|
1082
|
-
data-testid="password-toggle"
|
|
1083
|
-
>
|
|
1084
|
-
{showPassword ? <VisibilityOff /> : <Visibility />}
|
|
1085
|
-
</IconButton>
|
|
1086
|
-
}
|
|
1087
|
-
/>
|
|
1088
|
-
|
|
1089
|
-
<Input
|
|
1090
|
-
label="Confirm Password"
|
|
1091
|
-
type="password"
|
|
1092
|
-
value={formData.confirmPassword}
|
|
1093
|
-
onChange={handleChange('confirmPassword')}
|
|
1094
|
-
error={!!errors.confirmPassword}
|
|
1095
|
-
helperText={errors.confirmPassword}
|
|
1096
|
-
data-testid="confirm-password"
|
|
1097
|
-
startAdornment={<Lock />}
|
|
1098
|
-
/>
|
|
1099
|
-
|
|
1100
|
-
<button type="submit" data-testid="submit-form">
|
|
1101
|
-
Register
|
|
1102
|
-
</button>
|
|
1103
|
-
|
|
1104
|
-
<div data-testid="form-summary">
|
|
1105
|
-
Form Valid:{' '}
|
|
1106
|
-
{Object.keys(errors).length === 0 && Object.values(formData).every((v) => v.length > 0)
|
|
1107
|
-
? 'Yes'
|
|
1108
|
-
: 'No'}{' '}
|
|
1109
|
-
| Fields Filled: {Object.values(formData).filter((v) => v.length > 0).length}/5
|
|
1110
|
-
</div>
|
|
1111
|
-
</form>
|
|
1112
|
-
);
|
|
1113
|
-
};
|
|
1114
|
-
|
|
1115
|
-
return <IntegrationComponent />;
|
|
1116
|
-
},
|
|
1117
|
-
play: async ({ canvasElement, step }) => {
|
|
1118
|
-
const canvas = within(canvasElement);
|
|
1119
|
-
|
|
1120
|
-
await step('Fill out form fields', async () => {
|
|
1121
|
-
await userEvent.type(canvas.getByTestId('first-name'), 'John');
|
|
1122
|
-
await userEvent.type(canvas.getByTestId('last-name'), 'Doe');
|
|
1123
|
-
await userEvent.type(canvas.getByTestId('email'), 'john.doe@example.com');
|
|
1124
|
-
await userEvent.type(canvas.getByTestId('password'), 'securePassword123');
|
|
1125
|
-
await userEvent.type(canvas.getByTestId('confirm-password'), 'securePassword123');
|
|
1126
|
-
});
|
|
1127
|
-
|
|
1128
|
-
await step('Verify form state updates', async () => {
|
|
1129
|
-
const summary = canvas.getByTestId('form-summary');
|
|
1130
|
-
await expect(summary).toHaveTextContent('Fields Filled: 5/5');
|
|
1131
|
-
});
|
|
1132
|
-
|
|
1133
|
-
await step('Test password visibility toggle', async () => {
|
|
1134
|
-
const passwordInput = canvas.getByTestId('password');
|
|
1135
|
-
const toggleButton = canvas.getByTestId('password-toggle');
|
|
1136
|
-
|
|
1137
|
-
// Should be password type initially
|
|
1138
|
-
await expect(passwordInput).toHaveAttribute('type', 'password');
|
|
1139
|
-
|
|
1140
|
-
// Click toggle
|
|
1141
|
-
await userEvent.click(toggleButton);
|
|
1142
|
-
await expect(passwordInput).toHaveAttribute('type', 'text');
|
|
1143
|
-
|
|
1144
|
-
// Click again to hide
|
|
1145
|
-
await userEvent.click(toggleButton);
|
|
1146
|
-
await expect(passwordInput).toHaveAttribute('type', 'password');
|
|
1147
|
-
});
|
|
1148
|
-
|
|
1149
|
-
await step('Test form validation on submit', async () => {
|
|
1150
|
-
// Clear one field to trigger validation
|
|
1151
|
-
const firstNameInput = canvas.getByTestId('first-name');
|
|
1152
|
-
await userEvent.clear(firstNameInput);
|
|
1153
|
-
|
|
1154
|
-
const submitButton = canvas.getByTestId('submit-form');
|
|
1155
|
-
await userEvent.click(submitButton);
|
|
1156
|
-
|
|
1157
|
-
// Should show error
|
|
1158
|
-
await waitFor(async () => {
|
|
1159
|
-
await expect(canvas.getByText('First name is required')).toBeInTheDocument();
|
|
1160
|
-
});
|
|
1161
|
-
|
|
1162
|
-
// Form should be invalid
|
|
1163
|
-
const summary = canvas.getByTestId('form-summary');
|
|
1164
|
-
await expect(summary).toHaveTextContent('Form Valid: No');
|
|
1165
|
-
});
|
|
1166
|
-
|
|
1167
|
-
await step('Fix validation error', async () => {
|
|
1168
|
-
const firstNameInput = canvas.getByTestId('first-name');
|
|
1169
|
-
await userEvent.type(firstNameInput, 'John');
|
|
1170
|
-
|
|
1171
|
-
// Error should clear
|
|
1172
|
-
await waitFor(async () => {
|
|
1173
|
-
expect(canvas.queryByText('First name is required')).not.toBeInTheDocument();
|
|
1174
|
-
});
|
|
1175
|
-
|
|
1176
|
-
// Check that all fields are filled
|
|
1177
|
-
const summary = canvas.getByTestId('form-summary');
|
|
1178
|
-
await waitFor(async () => {
|
|
1179
|
-
await expect(summary).toHaveTextContent('Fields Filled: 5/5');
|
|
1180
|
-
});
|
|
1181
|
-
|
|
1182
|
-
// Form should be valid again (all fields filled and no errors)
|
|
1183
|
-
await waitFor(
|
|
1184
|
-
async () => {
|
|
1185
|
-
await expect(summary).toHaveTextContent('Form Valid: Yes');
|
|
1186
|
-
},
|
|
1187
|
-
{ timeout: 2000 },
|
|
1188
|
-
);
|
|
1189
|
-
});
|
|
1190
|
-
},
|
|
1191
|
-
};
|