@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,825 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { expect, userEvent, waitFor,within } from 'storybook/test';
|
|
4
|
-
|
|
5
|
-
import { Autocomplete } from './Autocomplete';
|
|
6
|
-
import type { AutocompleteProps } from './Autocomplete.types';
|
|
7
|
-
|
|
8
|
-
interface Person {
|
|
9
|
-
id: string;
|
|
10
|
-
name: string;
|
|
11
|
-
email: string;
|
|
12
|
-
department?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const samplePeople: Person[] = [
|
|
16
|
-
{ id: '1', name: 'John Doe', email: 'john@example.com', department: 'Engineering' },
|
|
17
|
-
{ id: '2', name: 'Jane Smith', email: 'jane@example.com', department: 'Design' },
|
|
18
|
-
{ id: '3', name: 'Bob Johnson', email: 'bob@example.com', department: 'Marketing' },
|
|
19
|
-
{ id: '4', name: 'Alice Brown', email: 'alice@example.com', department: 'Engineering' },
|
|
20
|
-
{ id: '5', name: 'Charlie Wilson', email: 'charlie@example.com', department: 'Sales' },
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
const stringSuggestions = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape'];
|
|
24
|
-
|
|
25
|
-
const meta: Meta<typeof Autocomplete> = {
|
|
26
|
-
title: 'Form/Autocomplete/Tests',
|
|
27
|
-
component: Autocomplete,
|
|
28
|
-
tags: ['autodocs', 'test', 'component:Autocomplete'],
|
|
29
|
-
parameters: {
|
|
30
|
-
docs: {
|
|
31
|
-
description: {
|
|
32
|
-
component:
|
|
33
|
-
'A comprehensive autocomplete component with ARIA support, keyboard navigation, and multiple selection modes.',
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
argTypes: {
|
|
38
|
-
value: { control: 'text' },
|
|
39
|
-
suggestions: { control: false },
|
|
40
|
-
multiple: { control: 'boolean' },
|
|
41
|
-
disabled: { control: 'boolean' },
|
|
42
|
-
allowFreeText: { control: 'boolean' },
|
|
43
|
-
showGhostText: { control: 'boolean' },
|
|
44
|
-
matchMode: { control: { type: 'select', options: ['contains', 'startsWith', 'fuzzy'] } },
|
|
45
|
-
debounceMs: { control: { type: 'number', min: 0, max: 1000 } },
|
|
46
|
-
maxVisibleItems: { control: { type: 'number', min: 1, max: 20 } },
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export default meta;
|
|
51
|
-
type Story = StoryObj<typeof Autocomplete>;
|
|
52
|
-
|
|
53
|
-
// Test Component wrapper for controlled state
|
|
54
|
-
const AutocompleteWrapper = (props: Partial<AutocompleteProps<Person | string>>) => {
|
|
55
|
-
const [value, setValue] = useState(props.value || '');
|
|
56
|
-
const [selectedItems, setSelectedItems] = useState(props.selectedItems || []);
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<div style={{ padding: '20px', minHeight: '200px' }}>
|
|
60
|
-
<Autocomplete
|
|
61
|
-
{...props}
|
|
62
|
-
value={value}
|
|
63
|
-
onChange={setValue}
|
|
64
|
-
selectedItems={selectedItems}
|
|
65
|
-
onSelectedItemsChange={setSelectedItems}
|
|
66
|
-
/>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Basic Tests
|
|
72
|
-
export const Default: Story = {
|
|
73
|
-
render: () => (
|
|
74
|
-
<AutocompleteWrapper suggestions={stringSuggestions} placeholder="Type to search fruits..." />
|
|
75
|
-
),
|
|
76
|
-
play: async ({ canvasElement }) => {
|
|
77
|
-
const canvas = within(canvasElement);
|
|
78
|
-
const input = canvas.getByRole('combobox');
|
|
79
|
-
|
|
80
|
-
// Test input is present and accessible
|
|
81
|
-
await expect(input).toBeInTheDocument();
|
|
82
|
-
await expect(input).toHaveAttribute('aria-expanded', 'false');
|
|
83
|
-
|
|
84
|
-
// Test typing shows suggestions
|
|
85
|
-
await userEvent.click(input);
|
|
86
|
-
await userEvent.type(input, 'ap');
|
|
87
|
-
|
|
88
|
-
// Wait for listbox to appear first (which confirms dropdown opened)
|
|
89
|
-
const listbox = await canvas.findByRole('listbox');
|
|
90
|
-
await expect(listbox).toBeInTheDocument();
|
|
91
|
-
|
|
92
|
-
// Now check aria-expanded is true
|
|
93
|
-
await expect(input).toHaveAttribute('aria-expanded', 'true');
|
|
94
|
-
|
|
95
|
-
const options = canvas.getAllByRole('option');
|
|
96
|
-
await expect(options).toHaveLength(2); // Apple, Grape (both contain "ap")
|
|
97
|
-
await expect(options[0]).toHaveTextContent('Apple');
|
|
98
|
-
await expect(options[1]).toHaveTextContent('Grape');
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export const WithObjectSuggestions: Story = {
|
|
103
|
-
render: () => (
|
|
104
|
-
<AutocompleteWrapper
|
|
105
|
-
suggestions={samplePeople}
|
|
106
|
-
getKey={(person: Person) => person.id}
|
|
107
|
-
getLabel={(person: Person) => person.name}
|
|
108
|
-
getDescription={(person: Person) => `${person.email} - ${person.department}`}
|
|
109
|
-
placeholder="Search people..."
|
|
110
|
-
/>
|
|
111
|
-
),
|
|
112
|
-
play: async ({ canvasElement }) => {
|
|
113
|
-
const canvas = within(canvasElement);
|
|
114
|
-
const input = canvas.getByRole('combobox');
|
|
115
|
-
|
|
116
|
-
await userEvent.click(input);
|
|
117
|
-
await userEvent.type(input, 'john');
|
|
118
|
-
|
|
119
|
-
// Wait for listbox to appear first
|
|
120
|
-
const listbox = await canvas.findByRole('listbox');
|
|
121
|
-
await expect(listbox).toBeInTheDocument();
|
|
122
|
-
|
|
123
|
-
const options = canvas.getAllByRole('option');
|
|
124
|
-
await expect(options).toHaveLength(2); // John Doe, Bob Johnson
|
|
125
|
-
|
|
126
|
-
// Test custom rendering with description
|
|
127
|
-
await expect(options[0]).toHaveTextContent('john@example.com - Engineering');
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// Ghost Text Tests
|
|
132
|
-
export const GhostText: Story = {
|
|
133
|
-
render: () => (
|
|
134
|
-
<AutocompleteWrapper
|
|
135
|
-
suggestions={stringSuggestions}
|
|
136
|
-
showGhostText={true}
|
|
137
|
-
placeholder="Type 'ap' to see ghost text..."
|
|
138
|
-
/>
|
|
139
|
-
),
|
|
140
|
-
play: async ({ canvasElement }) => {
|
|
141
|
-
const canvas = within(canvasElement);
|
|
142
|
-
const input = canvas.getByRole('combobox');
|
|
143
|
-
|
|
144
|
-
// Focus input first to enable ghost text
|
|
145
|
-
await userEvent.click(input);
|
|
146
|
-
await userEvent.type(input, 'ap');
|
|
147
|
-
|
|
148
|
-
// Wait for listbox to appear first
|
|
149
|
-
const listbox = await canvas.findByRole('listbox');
|
|
150
|
-
await expect(listbox).toBeInTheDocument();
|
|
151
|
-
|
|
152
|
-
// Test ghost text appears - look for the new inline suggestion pattern
|
|
153
|
-
// Since we're using the new pattern, check for the presence of the suggestion
|
|
154
|
-
const options = canvas.getAllByRole('option');
|
|
155
|
-
await expect(options).toHaveLength(2); // Apple, Grape both contain "ap"
|
|
156
|
-
await expect(options[0]).toHaveTextContent('Apple');
|
|
157
|
-
|
|
158
|
-
// Test that Tab key completes the ghost text
|
|
159
|
-
await userEvent.keyboard('{Tab}');
|
|
160
|
-
await expect(input).toHaveValue('apple'); // lowercase because user typed "ap"
|
|
161
|
-
// Verify dropdown closes after completion
|
|
162
|
-
await waitFor(() => expect(input).toHaveAttribute('aria-expanded', 'false'));
|
|
163
|
-
},
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
export const GhostTextArrowRight: Story = {
|
|
167
|
-
render: () => (
|
|
168
|
-
<AutocompleteWrapper
|
|
169
|
-
suggestions={stringSuggestions}
|
|
170
|
-
showGhostText={true}
|
|
171
|
-
placeholder="Type 'ch' and press ArrowRight..."
|
|
172
|
-
/>
|
|
173
|
-
),
|
|
174
|
-
play: async ({ canvasElement }) => {
|
|
175
|
-
const canvas = within(canvasElement);
|
|
176
|
-
const input = canvas.getByRole('combobox');
|
|
177
|
-
|
|
178
|
-
// Focus and type to trigger ghost text
|
|
179
|
-
await userEvent.click(input);
|
|
180
|
-
await userEvent.type(input, 'ch');
|
|
181
|
-
|
|
182
|
-
// Wait for listbox to appear first
|
|
183
|
-
const listbox = await canvas.findByRole('listbox');
|
|
184
|
-
await expect(listbox).toBeInTheDocument();
|
|
185
|
-
|
|
186
|
-
// Verify suggestion appears
|
|
187
|
-
const options = canvas.getAllByRole('option');
|
|
188
|
-
await expect(options).toHaveLength(1);
|
|
189
|
-
await expect(options[0]).toHaveTextContent('Cherry');
|
|
190
|
-
|
|
191
|
-
// Test that ArrowRight key completes the ghost text
|
|
192
|
-
await userEvent.keyboard('{ArrowRight}');
|
|
193
|
-
await expect(input).toHaveValue('Cherry');
|
|
194
|
-
await waitFor(() => expect(input).toHaveAttribute('aria-expanded', 'false'));
|
|
195
|
-
},
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
export const FocusBlurBehavior: Story = {
|
|
199
|
-
name: '🔄 Focus/Blur Behavior Test',
|
|
200
|
-
render: () => (
|
|
201
|
-
<AutocompleteWrapper
|
|
202
|
-
suggestions={stringSuggestions}
|
|
203
|
-
showGhostText={true}
|
|
204
|
-
placeholder="Test focus/blur behavior..."
|
|
205
|
-
data-testid="focus-blur-autocomplete"
|
|
206
|
-
/>
|
|
207
|
-
),
|
|
208
|
-
play: async ({ canvasElement, step }) => {
|
|
209
|
-
const canvas = within(canvasElement);
|
|
210
|
-
|
|
211
|
-
await step('Initial state verification', async () => {
|
|
212
|
-
const input = canvas.getByRole('combobox');
|
|
213
|
-
await expect(input).toBeInTheDocument();
|
|
214
|
-
await expect(input).toHaveAttribute('aria-expanded', 'false');
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
await step('Open dropdown with typing', async () => {
|
|
218
|
-
const input = canvas.getByRole('combobox');
|
|
219
|
-
await userEvent.click(input);
|
|
220
|
-
await userEvent.type(input, 'ap');
|
|
221
|
-
|
|
222
|
-
// Wait for listbox to appear
|
|
223
|
-
const listbox = await canvas.findByRole('listbox');
|
|
224
|
-
await expect(listbox).toBeInTheDocument();
|
|
225
|
-
await expect(input).toHaveAttribute('aria-expanded', 'true');
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
await step('Close dropdown (test dropdown close functionality)', async () => {
|
|
229
|
-
const input = canvas.getByRole('combobox');
|
|
230
|
-
// Try multiple methods to close dropdown
|
|
231
|
-
await userEvent.keyboard('{Escape}');
|
|
232
|
-
|
|
233
|
-
// Give some time for the state to update
|
|
234
|
-
await waitFor(() => expect(input).toHaveAttribute('aria-expanded', 'false'), {
|
|
235
|
-
timeout: 1000,
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
},
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// Keyboard Navigation Tests
|
|
242
|
-
export const KeyboardNavigation: Story = {
|
|
243
|
-
name: '⌨️ Keyboard Navigation Test',
|
|
244
|
-
render: () => (
|
|
245
|
-
<AutocompleteWrapper
|
|
246
|
-
suggestions={stringSuggestions}
|
|
247
|
-
placeholder="Test keyboard navigation..."
|
|
248
|
-
data-testid="keyboard-nav-autocomplete"
|
|
249
|
-
/>
|
|
250
|
-
),
|
|
251
|
-
play: async ({ canvasElement, step }) => {
|
|
252
|
-
const canvas = within(canvasElement);
|
|
253
|
-
|
|
254
|
-
await step('Open dropdown with typing', async () => {
|
|
255
|
-
const input = canvas.getByRole('combobox');
|
|
256
|
-
await userEvent.click(input);
|
|
257
|
-
await userEvent.type(input, 'e');
|
|
258
|
-
|
|
259
|
-
// Wait for listbox to appear
|
|
260
|
-
const listbox = await canvas.findByRole('listbox');
|
|
261
|
-
await expect(listbox).toBeInTheDocument();
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
await step('Verify options are displayed', async () => {
|
|
265
|
-
const options = canvas.getAllByRole('option');
|
|
266
|
-
await expect(options).toHaveLength(5); // Apple, Cherry, Date, Elderberry, Grape
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
await step('Test arrow key navigation', async () => {
|
|
270
|
-
const input = canvas.getByRole('combobox');
|
|
271
|
-
const options = canvas.getAllByRole('option');
|
|
272
|
-
|
|
273
|
-
// Navigate down - should select some option
|
|
274
|
-
await userEvent.keyboard('{ArrowDown}');
|
|
275
|
-
|
|
276
|
-
// Wait for some option to become selected
|
|
277
|
-
await waitFor(() => {
|
|
278
|
-
const selectedOptions = options.filter(
|
|
279
|
-
(option) => option.getAttribute('aria-selected') === 'true',
|
|
280
|
-
);
|
|
281
|
-
expect(selectedOptions.length).toBeGreaterThan(0);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// Test selection with Enter
|
|
285
|
-
await userEvent.keyboard('{Enter}');
|
|
286
|
-
|
|
287
|
-
// Should have some value selected
|
|
288
|
-
const finalValue = input.getAttribute('value') || '';
|
|
289
|
-
expect(finalValue.length).toBeGreaterThan(0);
|
|
290
|
-
});
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
// Multiple Selection Tests
|
|
295
|
-
export const MultipleSelection: Story = {
|
|
296
|
-
render: () => (
|
|
297
|
-
<AutocompleteWrapper
|
|
298
|
-
suggestions={stringSuggestions}
|
|
299
|
-
multiple={true}
|
|
300
|
-
placeholder="Select multiple fruits..."
|
|
301
|
-
/>
|
|
302
|
-
),
|
|
303
|
-
play: async ({ canvasElement }) => {
|
|
304
|
-
const canvas = within(canvasElement);
|
|
305
|
-
const input = canvas.getByRole('combobox');
|
|
306
|
-
|
|
307
|
-
// Select first item
|
|
308
|
-
await userEvent.click(input);
|
|
309
|
-
await userEvent.type(input, 'Apple', { delay: 50 });
|
|
310
|
-
|
|
311
|
-
// Wait for dropdown and select
|
|
312
|
-
await waitFor(() => expect(canvas.queryByRole('listbox')).toBeInTheDocument(), {
|
|
313
|
-
timeout: 1500,
|
|
314
|
-
});
|
|
315
|
-
await userEvent.keyboard('{Enter}');
|
|
316
|
-
|
|
317
|
-
// Wait for first chip to appear and input to clear
|
|
318
|
-
await waitFor(
|
|
319
|
-
() => {
|
|
320
|
-
const chip = canvas.getByTestId('selected-chip-0');
|
|
321
|
-
expect(chip).toBeInTheDocument();
|
|
322
|
-
expect(input).toHaveValue('');
|
|
323
|
-
},
|
|
324
|
-
{ timeout: 2000 },
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
// Wait for the dropdown to settle closed after the first selection (deterministic,
|
|
328
|
-
// instead of a fixed debounce sleep) before starting the second selection.
|
|
329
|
-
await waitFor(() => expect(canvas.queryByRole('listbox')).not.toBeInTheDocument());
|
|
330
|
-
|
|
331
|
-
// Select second item
|
|
332
|
-
await userEvent.type(input, 'Banana', { delay: 50 });
|
|
333
|
-
|
|
334
|
-
// Wait for dropdown and select
|
|
335
|
-
await waitFor(() => expect(canvas.queryByRole('listbox')).toBeInTheDocument(), {
|
|
336
|
-
timeout: 1500,
|
|
337
|
-
});
|
|
338
|
-
await userEvent.keyboard('{Enter}');
|
|
339
|
-
|
|
340
|
-
// Should have 2 chips
|
|
341
|
-
await waitFor(
|
|
342
|
-
() => {
|
|
343
|
-
const chip0 = canvas.getByTestId('selected-chip-0');
|
|
344
|
-
const chip1 = canvas.getByTestId('selected-chip-1');
|
|
345
|
-
expect(chip0).toBeInTheDocument();
|
|
346
|
-
expect(chip1).toBeInTheDocument();
|
|
347
|
-
},
|
|
348
|
-
{ timeout: 2000 },
|
|
349
|
-
);
|
|
350
|
-
},
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
// Async Loading Tests
|
|
354
|
-
export const AsyncLoading: Story = {
|
|
355
|
-
render: () => (
|
|
356
|
-
<AutocompleteWrapper
|
|
357
|
-
suggestions={stringSuggestions}
|
|
358
|
-
async={true}
|
|
359
|
-
isLoading={true}
|
|
360
|
-
placeholder="Async autocomplete..."
|
|
361
|
-
/>
|
|
362
|
-
),
|
|
363
|
-
play: async ({ canvasElement }) => {
|
|
364
|
-
const canvas = within(canvasElement);
|
|
365
|
-
const input = canvas.getByRole('combobox');
|
|
366
|
-
|
|
367
|
-
await userEvent.click(input);
|
|
368
|
-
await userEvent.type(input, 'test');
|
|
369
|
-
|
|
370
|
-
// Wait for dropdown to open due to loading state
|
|
371
|
-
await waitFor(
|
|
372
|
-
async () => {
|
|
373
|
-
await expect(input).toHaveAttribute('aria-expanded', 'true');
|
|
374
|
-
},
|
|
375
|
-
{ timeout: 1000 },
|
|
376
|
-
);
|
|
377
|
-
|
|
378
|
-
// Check loading indicator in input - it's in the InputProps endAdornment
|
|
379
|
-
// We verify loading state by checking for the loading text in dropdown
|
|
380
|
-
// The CircularProgress in the input is harder to query reliably
|
|
381
|
-
|
|
382
|
-
// Check for loading text in dropdown
|
|
383
|
-
await waitFor(
|
|
384
|
-
async () => {
|
|
385
|
-
const loadingText = canvas.getByText('Loading...');
|
|
386
|
-
await expect(loadingText).toBeInTheDocument();
|
|
387
|
-
},
|
|
388
|
-
{ timeout: 1000 },
|
|
389
|
-
);
|
|
390
|
-
},
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
// Match Mode Tests
|
|
394
|
-
export const FuzzyMatchingTest: Story = {
|
|
395
|
-
render: () => (
|
|
396
|
-
<AutocompleteWrapper
|
|
397
|
-
suggestions={['JavaScript', 'TypeScript', 'CoffeeScript', 'ActionScript']}
|
|
398
|
-
matchMode="fuzzy"
|
|
399
|
-
placeholder="Type 'jvs' for fuzzy matching..."
|
|
400
|
-
/>
|
|
401
|
-
),
|
|
402
|
-
play: async ({ canvasElement }) => {
|
|
403
|
-
const canvas = within(canvasElement);
|
|
404
|
-
const input = canvas.getByRole('combobox');
|
|
405
|
-
|
|
406
|
-
// Test fuzzy matching
|
|
407
|
-
await userEvent.click(input);
|
|
408
|
-
await userEvent.type(input, 'jvs');
|
|
409
|
-
|
|
410
|
-
// Wait for listbox to appear first
|
|
411
|
-
const listbox = await canvas.findByRole('listbox');
|
|
412
|
-
await expect(listbox).toBeInTheDocument();
|
|
413
|
-
|
|
414
|
-
const options = canvas.getAllByRole('option');
|
|
415
|
-
await expect(options).toHaveLength(1);
|
|
416
|
-
await expect(options[0]).toHaveTextContent('JavaScript');
|
|
417
|
-
},
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
// Disabled State Tests
|
|
421
|
-
export const DisabledState: Story = {
|
|
422
|
-
render: () => (
|
|
423
|
-
<AutocompleteWrapper
|
|
424
|
-
suggestions={stringSuggestions}
|
|
425
|
-
disabled={true}
|
|
426
|
-
placeholder="This is disabled..."
|
|
427
|
-
/>
|
|
428
|
-
),
|
|
429
|
-
play: async ({ canvasElement }) => {
|
|
430
|
-
const canvas = within(canvasElement);
|
|
431
|
-
const input = canvas.getByRole('combobox');
|
|
432
|
-
|
|
433
|
-
await expect(input).toBeDisabled();
|
|
434
|
-
|
|
435
|
-
// Try typing - should not work
|
|
436
|
-
await userEvent.type(input, 'test');
|
|
437
|
-
await expect(input).toHaveValue('');
|
|
438
|
-
},
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
// No Results Tests
|
|
442
|
-
export const NoResults: Story = {
|
|
443
|
-
render: () => (
|
|
444
|
-
<AutocompleteWrapper
|
|
445
|
-
suggestions={stringSuggestions}
|
|
446
|
-
placeholder="Type 'xyz' for no results..."
|
|
447
|
-
/>
|
|
448
|
-
),
|
|
449
|
-
play: async ({ canvasElement }) => {
|
|
450
|
-
const canvas = within(canvasElement);
|
|
451
|
-
const input = canvas.getByRole('combobox');
|
|
452
|
-
|
|
453
|
-
await userEvent.click(input);
|
|
454
|
-
await userEvent.type(input, 'xyz');
|
|
455
|
-
|
|
456
|
-
// Wait for the dropdown to open with no results message
|
|
457
|
-
await waitFor(
|
|
458
|
-
async () => {
|
|
459
|
-
const noResults = canvas.getByText('No results found');
|
|
460
|
-
await expect(noResults).toBeInTheDocument();
|
|
461
|
-
},
|
|
462
|
-
{ timeout: 1000 },
|
|
463
|
-
);
|
|
464
|
-
},
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
// Escape Key Tests
|
|
468
|
-
export const EscapeKey: Story = {
|
|
469
|
-
render: () => (
|
|
470
|
-
<AutocompleteWrapper
|
|
471
|
-
suggestions={stringSuggestions}
|
|
472
|
-
placeholder="Test escape key behavior..."
|
|
473
|
-
/>
|
|
474
|
-
),
|
|
475
|
-
play: async ({ canvasElement }) => {
|
|
476
|
-
const canvas = within(canvasElement);
|
|
477
|
-
const input = canvas.getByRole('combobox');
|
|
478
|
-
|
|
479
|
-
// Open suggestions
|
|
480
|
-
await userEvent.click(input);
|
|
481
|
-
await userEvent.type(input, 'apple');
|
|
482
|
-
|
|
483
|
-
// Wait for dropdown to open
|
|
484
|
-
await waitFor(() => expect(input).toHaveAttribute('aria-expanded', 'true'), {
|
|
485
|
-
timeout: 1000,
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
// Single escape closes dropdown
|
|
489
|
-
await userEvent.keyboard('{Escape}');
|
|
490
|
-
await waitFor(() => expect(input).toHaveAttribute('aria-expanded', 'false'));
|
|
491
|
-
await expect(input).toHaveValue('apple');
|
|
492
|
-
|
|
493
|
-
// Double escape clears value (simulated by double press)
|
|
494
|
-
await userEvent.keyboard('{Escape}');
|
|
495
|
-
await userEvent.keyboard('{Escape}');
|
|
496
|
-
// Note: Double escape detection requires special handling
|
|
497
|
-
},
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
// Free Text Tests
|
|
501
|
-
export const FreeText: Story = {
|
|
502
|
-
render: () => (
|
|
503
|
-
<AutocompleteWrapper
|
|
504
|
-
suggestions={stringSuggestions}
|
|
505
|
-
allowFreeText={true}
|
|
506
|
-
placeholder="Type anything and press Enter..."
|
|
507
|
-
/>
|
|
508
|
-
),
|
|
509
|
-
play: async ({ canvasElement }) => {
|
|
510
|
-
const canvas = within(canvasElement);
|
|
511
|
-
const input = canvas.getByRole('combobox');
|
|
512
|
-
|
|
513
|
-
// Type custom text not in suggestions
|
|
514
|
-
await userEvent.type(input, 'Custom Value');
|
|
515
|
-
await userEvent.keyboard('{Enter}');
|
|
516
|
-
|
|
517
|
-
await expect(input).toHaveValue('Custom Value');
|
|
518
|
-
},
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
// Accessibility Tests
|
|
522
|
-
export const AccessibilityCompliance: Story = {
|
|
523
|
-
render: () => (
|
|
524
|
-
<AutocompleteWrapper
|
|
525
|
-
suggestions={samplePeople}
|
|
526
|
-
getKey={(person: Person) => person.id}
|
|
527
|
-
getLabel={(person: Person) => person.name}
|
|
528
|
-
inputAriaLabel="Search for team members"
|
|
529
|
-
placeholder="Search team members..."
|
|
530
|
-
/>
|
|
531
|
-
),
|
|
532
|
-
play: async ({ canvasElement }) => {
|
|
533
|
-
const canvas = within(canvasElement);
|
|
534
|
-
const input = canvas.getByRole('combobox');
|
|
535
|
-
|
|
536
|
-
// Test ARIA attributes
|
|
537
|
-
await expect(input).toHaveAttribute('role', 'combobox');
|
|
538
|
-
await expect(input).toHaveAttribute('aria-autocomplete', 'list');
|
|
539
|
-
await expect(input).toHaveAttribute('aria-expanded', 'false');
|
|
540
|
-
await expect(input).toHaveAttribute('aria-label', 'Search for team members');
|
|
541
|
-
|
|
542
|
-
// Open dropdown
|
|
543
|
-
await userEvent.click(input);
|
|
544
|
-
await userEvent.type(input, 'john');
|
|
545
|
-
|
|
546
|
-
// Wait for dropdown to open
|
|
547
|
-
await waitFor(() => expect(input).toHaveAttribute('aria-expanded', 'true'), {
|
|
548
|
-
timeout: 1000,
|
|
549
|
-
});
|
|
550
|
-
await expect(input).toHaveAttribute('aria-controls');
|
|
551
|
-
|
|
552
|
-
const listbox = canvas.getByRole('listbox');
|
|
553
|
-
await expect(listbox).toBeInTheDocument();
|
|
554
|
-
|
|
555
|
-
const options = canvas.getAllByRole('option');
|
|
556
|
-
// Options should have aria-selected attribute (first may be true if active)
|
|
557
|
-
options.forEach((option) => {
|
|
558
|
-
expect(option).toHaveAttribute('aria-selected');
|
|
559
|
-
});
|
|
560
|
-
},
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
// Performance Tests
|
|
564
|
-
export const LargeDataset: Story = {
|
|
565
|
-
render: () => {
|
|
566
|
-
const largeSuggestions = Array.from({ length: 1000 }, (_, i) => `Item ${i + 1}`);
|
|
567
|
-
|
|
568
|
-
return (
|
|
569
|
-
<AutocompleteWrapper
|
|
570
|
-
suggestions={largeSuggestions}
|
|
571
|
-
maxVisibleItems={10}
|
|
572
|
-
debounceMs={300}
|
|
573
|
-
placeholder="Search 1000 items..."
|
|
574
|
-
/>
|
|
575
|
-
);
|
|
576
|
-
},
|
|
577
|
-
play: async ({ canvasElement }) => {
|
|
578
|
-
const canvas = within(canvasElement);
|
|
579
|
-
const input = canvas.getByRole('combobox');
|
|
580
|
-
|
|
581
|
-
// Test with large dataset
|
|
582
|
-
await userEvent.click(input);
|
|
583
|
-
await userEvent.type(input, '99');
|
|
584
|
-
|
|
585
|
-
// Wait for listbox to appear first
|
|
586
|
-
const listbox = await canvas.findByRole('listbox');
|
|
587
|
-
await expect(listbox).toBeInTheDocument();
|
|
588
|
-
|
|
589
|
-
const options = canvas.getAllByRole('option');
|
|
590
|
-
// Should limit visible items
|
|
591
|
-
await expect(options.length).toBeLessThanOrEqual(10);
|
|
592
|
-
},
|
|
593
|
-
};
|
|
594
|
-
|
|
595
|
-
// Edge Cases
|
|
596
|
-
export const EdgeCases: Story = {
|
|
597
|
-
render: () => (
|
|
598
|
-
<AutocompleteWrapper
|
|
599
|
-
suggestions={['', 'Normal Item', ' Spaces ', 'Special!@#$%^&*()']}
|
|
600
|
-
placeholder="Test edge cases..."
|
|
601
|
-
/>
|
|
602
|
-
),
|
|
603
|
-
play: async ({ canvasElement }) => {
|
|
604
|
-
const canvas = within(canvasElement);
|
|
605
|
-
const input = canvas.getByRole('combobox');
|
|
606
|
-
|
|
607
|
-
// Test searching for items with spaces
|
|
608
|
-
await userEvent.click(input);
|
|
609
|
-
await userEvent.type(input, 'Spaces');
|
|
610
|
-
|
|
611
|
-
// Wait for listbox to appear
|
|
612
|
-
await waitFor(() => expect(canvas.queryByRole('listbox')).toBeInTheDocument(), {
|
|
613
|
-
timeout: 1000,
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
// Should handle empty and special characters
|
|
617
|
-
const options = canvas.getAllByRole('option');
|
|
618
|
-
await expect(options.length).toBeGreaterThan(0);
|
|
619
|
-
},
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
// Form Interaction Tests
|
|
623
|
-
export const FormInteraction: Story = {
|
|
624
|
-
render: () => (
|
|
625
|
-
<AutocompleteWrapper suggestions={stringSuggestions} placeholder="Form interaction test..." />
|
|
626
|
-
),
|
|
627
|
-
play: async ({ canvasElement }) => {
|
|
628
|
-
const canvas = within(canvasElement);
|
|
629
|
-
const input = canvas.getByRole('combobox');
|
|
630
|
-
|
|
631
|
-
// Test form submission behavior
|
|
632
|
-
await userEvent.click(input);
|
|
633
|
-
await userEvent.type(input, 'Apple');
|
|
634
|
-
|
|
635
|
-
// Wait for dropdown and select
|
|
636
|
-
const listbox = await canvas.findByRole('listbox');
|
|
637
|
-
await expect(listbox).toBeInTheDocument();
|
|
638
|
-
|
|
639
|
-
await userEvent.keyboard('{Enter}');
|
|
640
|
-
await expect(input).toHaveValue('Apple');
|
|
641
|
-
|
|
642
|
-
// Test that form would submit correctly (value is selected)
|
|
643
|
-
const finalValue = input.getAttribute('value') || '';
|
|
644
|
-
expect(finalValue).toBe('Apple');
|
|
645
|
-
},
|
|
646
|
-
};
|
|
647
|
-
|
|
648
|
-
// Responsive Design Tests
|
|
649
|
-
export const ResponsiveDesign: Story = {
|
|
650
|
-
render: () => (
|
|
651
|
-
<div style={{ width: '100%', maxWidth: '300px' }}>
|
|
652
|
-
<AutocompleteWrapper suggestions={stringSuggestions} placeholder="Responsive test..." />
|
|
653
|
-
</div>
|
|
654
|
-
),
|
|
655
|
-
play: async ({ canvasElement }) => {
|
|
656
|
-
const canvas = within(canvasElement);
|
|
657
|
-
const input = canvas.getByRole('combobox');
|
|
658
|
-
|
|
659
|
-
// Test responsive behavior
|
|
660
|
-
await userEvent.click(input);
|
|
661
|
-
await userEvent.type(input, 'a');
|
|
662
|
-
|
|
663
|
-
// Wait for dropdown to appear
|
|
664
|
-
const listbox = await canvas.findByRole('listbox');
|
|
665
|
-
await expect(listbox).toBeInTheDocument();
|
|
666
|
-
|
|
667
|
-
// Verify dropdown adapts to container width
|
|
668
|
-
const options = canvas.getAllByRole('option');
|
|
669
|
-
expect(options.length).toBeGreaterThan(0);
|
|
670
|
-
|
|
671
|
-
// Test mobile-friendly interaction
|
|
672
|
-
const firstOption = options[0];
|
|
673
|
-
await userEvent.click(firstOption);
|
|
674
|
-
|
|
675
|
-
// Should select the option
|
|
676
|
-
const selectedValue = input.getAttribute('value') || '';
|
|
677
|
-
expect(selectedValue.length).toBeGreaterThan(0);
|
|
678
|
-
},
|
|
679
|
-
};
|
|
680
|
-
|
|
681
|
-
// Theme Variations Tests
|
|
682
|
-
export const ThemeVariations: Story = {
|
|
683
|
-
render: () => (
|
|
684
|
-
<AutocompleteWrapper suggestions={stringSuggestions} placeholder="Theme variation test..." />
|
|
685
|
-
),
|
|
686
|
-
play: async ({ canvasElement }) => {
|
|
687
|
-
const canvas = within(canvasElement);
|
|
688
|
-
const input = canvas.getByRole('combobox');
|
|
689
|
-
|
|
690
|
-
// Test that component renders with theme
|
|
691
|
-
await expect(input).toBeInTheDocument();
|
|
692
|
-
await expect(input).toHaveAttribute('placeholder', 'Theme variation test...');
|
|
693
|
-
|
|
694
|
-
// Test interaction with theming
|
|
695
|
-
await userEvent.click(input);
|
|
696
|
-
await userEvent.type(input, 'Apple');
|
|
697
|
-
|
|
698
|
-
// Wait for dropdown
|
|
699
|
-
const listbox = await canvas.findByRole('listbox');
|
|
700
|
-
await expect(listbox).toBeInTheDocument();
|
|
701
|
-
|
|
702
|
-
// Verify themed elements render correctly
|
|
703
|
-
const options = canvas.getAllByRole('option');
|
|
704
|
-
expect(options.length).toBeGreaterThan(0);
|
|
705
|
-
await expect(options[0]).toHaveTextContent('Apple');
|
|
706
|
-
},
|
|
707
|
-
};
|
|
708
|
-
|
|
709
|
-
// Visual States Tests
|
|
710
|
-
export const VisualStates: Story = {
|
|
711
|
-
render: () => (
|
|
712
|
-
<AutocompleteWrapper
|
|
713
|
-
suggestions={stringSuggestions}
|
|
714
|
-
placeholder="Visual states test..."
|
|
715
|
-
disabled={false}
|
|
716
|
-
/>
|
|
717
|
-
),
|
|
718
|
-
play: async ({ canvasElement }) => {
|
|
719
|
-
const canvas = within(canvasElement);
|
|
720
|
-
const input = canvas.getByRole('combobox');
|
|
721
|
-
|
|
722
|
-
// Test normal state
|
|
723
|
-
await expect(input).toBeInTheDocument();
|
|
724
|
-
await expect(input).not.toBeDisabled();
|
|
725
|
-
|
|
726
|
-
// Test focus state (waitFor keeps the focus assertion but avoids flakiness).
|
|
727
|
-
await userEvent.click(input);
|
|
728
|
-
await waitFor(() => expect(input).toHaveFocus());
|
|
729
|
-
|
|
730
|
-
// Test with content state
|
|
731
|
-
await userEvent.type(input, 'Test');
|
|
732
|
-
await expect(input).toHaveValue('Test');
|
|
733
|
-
|
|
734
|
-
// Test dropdown open state
|
|
735
|
-
await userEvent.clear(input);
|
|
736
|
-
await userEvent.type(input, 'Apple');
|
|
737
|
-
|
|
738
|
-
const listbox = await canvas.findByRole('listbox');
|
|
739
|
-
await expect(listbox).toBeInTheDocument();
|
|
740
|
-
|
|
741
|
-
// Test selection state
|
|
742
|
-
await userEvent.keyboard('{Enter}');
|
|
743
|
-
await expect(input).toHaveValue('Apple');
|
|
744
|
-
},
|
|
745
|
-
};
|
|
746
|
-
|
|
747
|
-
// Typed suggestions (search vs. link) — Google-style results
|
|
748
|
-
interface SearchLinkItem {
|
|
749
|
-
id: string;
|
|
750
|
-
text: string;
|
|
751
|
-
type: 'search' | 'link';
|
|
752
|
-
url?: string;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
const searchLinkItems: SearchLinkItem[] = [
|
|
756
|
-
{ id: 's1', text: 'Produtos', type: 'search' },
|
|
757
|
-
{ id: 's2', text: 'Pedidos', type: 'search' },
|
|
758
|
-
{ id: 'l1', text: 'Abrir documentação', type: 'link', url: 'https://example.com/docs' },
|
|
759
|
-
];
|
|
760
|
-
|
|
761
|
-
const SearchLinkWrapper = () => {
|
|
762
|
-
const [value, setValue] = useState('');
|
|
763
|
-
const [openedUrl, setOpenedUrl] = useState('');
|
|
764
|
-
|
|
765
|
-
return (
|
|
766
|
-
<div style={{ padding: '20px', minHeight: '260px' }}>
|
|
767
|
-
<Autocomplete<SearchLinkItem>
|
|
768
|
-
value={value}
|
|
769
|
-
onChange={setValue}
|
|
770
|
-
suggestions={searchLinkItems}
|
|
771
|
-
getKey={(item) => item.id}
|
|
772
|
-
getLabel={(item) => item.text}
|
|
773
|
-
getSuggestionType={(item) => item.type}
|
|
774
|
-
getUrl={(item) => item.url}
|
|
775
|
-
openLink={(url) => setOpenedUrl(url)}
|
|
776
|
-
placeholder="Buscar ou abrir link..."
|
|
777
|
-
/>
|
|
778
|
-
<div data-testid="opened-url">{openedUrl}</div>
|
|
779
|
-
</div>
|
|
780
|
-
);
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
export const SearchAndLinkSuggestions: Story = {
|
|
784
|
-
render: () => <SearchLinkWrapper />,
|
|
785
|
-
play: async ({ canvasElement, step }) => {
|
|
786
|
-
const canvas = within(canvasElement);
|
|
787
|
-
const input = canvas.getByRole('combobox');
|
|
788
|
-
|
|
789
|
-
await step('link suggestion opens the URL and leaves the input unchanged', async () => {
|
|
790
|
-
await userEvent.click(input);
|
|
791
|
-
await userEvent.type(input, 'o'); // matches all three (contains "o")
|
|
792
|
-
|
|
793
|
-
const listbox = await canvas.findByRole('listbox');
|
|
794
|
-
await expect(listbox).toBeInTheDocument();
|
|
795
|
-
|
|
796
|
-
const linkOption = await canvas.findByText('Abrir documentação');
|
|
797
|
-
// A `link` suggestion renders a leading icon.
|
|
798
|
-
await expect(linkOption.closest('[role="option"]')?.querySelector('svg')).toBeInTheDocument();
|
|
799
|
-
|
|
800
|
-
await userEvent.click(linkOption);
|
|
801
|
-
|
|
802
|
-
// openLink fired with the target URL...
|
|
803
|
-
await expect(canvas.getByTestId('opened-url')).toHaveTextContent(
|
|
804
|
-
'https://example.com/docs',
|
|
805
|
-
);
|
|
806
|
-
// ...and the input value was NOT replaced by the link label.
|
|
807
|
-
await expect(input).toHaveValue('o');
|
|
808
|
-
await waitFor(() => expect(input).toHaveAttribute('aria-expanded', 'false'));
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
await step('search suggestion fills the input', async () => {
|
|
812
|
-
await userEvent.clear(input);
|
|
813
|
-
await userEvent.type(input, 'produt');
|
|
814
|
-
|
|
815
|
-
const searchOption = await canvas.findByText('Produtos');
|
|
816
|
-
await userEvent.click(searchOption);
|
|
817
|
-
|
|
818
|
-
await expect(input).toHaveValue('Produtos');
|
|
819
|
-
// The link was not re-opened by a search selection.
|
|
820
|
-
await expect(canvas.getByTestId('opened-url')).toHaveTextContent(
|
|
821
|
-
'https://example.com/docs',
|
|
822
|
-
);
|
|
823
|
-
});
|
|
824
|
-
},
|
|
825
|
-
};
|