@dhasdk/simple-ui 1.0.7 → 1.0.8
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/.babelrc +12 -0
- package/.storybook/main.ts +35 -0
- package/.storybook/preview.ts +4 -0
- package/BAKpostcss.config.jsBAK +15 -0
- package/BAKtailwind.config.mjsBAK +99 -0
- package/README.md +464 -16
- package/coverage/storybook/coverage-storybook.json +32411 -0
- package/coverage/storybook/lcov-report/Accordion.tsx.html +805 -0
- package/coverage/storybook/lcov-report/Badge.tsx.html +346 -0
- package/coverage/storybook/lcov-report/Breadcrumbs.tsx.html +742 -0
- package/coverage/storybook/lcov-report/Button.tsx.html +448 -0
- package/coverage/storybook/lcov-report/ButtonGroup.tsx.html +403 -0
- package/coverage/storybook/lcov-report/Card.tsx.html +292 -0
- package/coverage/storybook/lcov-report/CharacterCounter.tsx.html +253 -0
- package/coverage/storybook/lcov-report/CheckBox.tsx.html +1555 -0
- package/coverage/storybook/lcov-report/DatePicker.tsx.html +826 -0
- package/coverage/storybook/lcov-report/Input.tsx.html +1012 -0
- package/coverage/storybook/lcov-report/List.tsx.html +364 -0
- package/coverage/storybook/lcov-report/Modal.tsx.html +745 -0
- package/coverage/storybook/lcov-report/Pill.tsx.html +358 -0
- package/coverage/storybook/lcov-report/Search.tsx.html +997 -0
- package/coverage/storybook/lcov-report/SearchContent.tsx.html +235 -0
- package/coverage/storybook/lcov-report/SectionHeader.tsx.html +358 -0
- package/coverage/storybook/lcov-report/Select.tsx.html +1012 -0
- package/coverage/storybook/lcov-report/Shield.tsx.html +802 -0
- package/coverage/storybook/lcov-report/SideBarNav.tsx.html +490 -0
- package/coverage/storybook/lcov-report/Skeleton.tsx.html +394 -0
- package/coverage/storybook/lcov-report/Slider.tsx.html +385 -0
- package/coverage/storybook/lcov-report/Status.tsx.html +322 -0
- package/coverage/storybook/lcov-report/Tabs.tsx.html +610 -0
- package/coverage/storybook/lcov-report/Toggle.tsx.html +373 -0
- package/coverage/storybook/lcov-report/Tooltip.tsx.html +496 -0
- package/coverage/storybook/lcov-report/base.css +224 -0
- package/coverage/storybook/lcov-report/block-navigation.js +87 -0
- package/coverage/storybook/lcov-report/favicon.png +0 -0
- package/coverage/storybook/lcov-report/index.html +476 -0
- package/coverage/storybook/lcov-report/prettify.css +1 -0
- package/coverage/storybook/lcov-report/prettify.js +2 -0
- package/coverage/storybook/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/storybook/lcov-report/sorter.js +196 -0
- package/coverage/storybook/lcov.info +2312 -0
- package/dist/README.md +1815 -0
- package/eslint.config.mjs +13 -0
- package/package.json +6 -7
- package/project.json +11 -0
- package/src/assets/img/Frame.svg +5 -0
- package/src/assets/img/backArrowRight.svg +10 -0
- package/src/assets/img/bc-separator.png +0 -0
- package/src/assets/img/calendar.png +0 -0
- package/src/assets/img/calendar.svg +4 -0
- package/src/assets/img/check.svg +5 -0
- package/src/assets/img/check_box.svg +10 -0
- package/src/assets/img/check_box_empty.svg +10 -0
- package/src/assets/img/check_box_fill.svg +10 -0
- package/src/assets/img/check_box_fill_empty.svg +10 -0
- package/src/assets/img/chevron-down-white.svg +2 -0
- package/src/assets/img/chevron-down.svg +2 -0
- package/src/assets/img/chevron-left.svg +1 -0
- package/src/assets/img/chevron-right-light.svg +4 -0
- package/src/assets/img/chevron-right.svg +3 -0
- package/src/assets/img/chevron-up-white.svg +1 -0
- package/src/assets/img/chevron-up.svg +1 -0
- package/src/assets/img/clock.svg +6 -0
- package/src/assets/img/close.svg +1 -0
- package/src/assets/img/close2.svg +6 -0
- package/src/assets/img/closeModal.svg +10 -0
- package/src/assets/img/close_icon_dark.svg +10 -0
- package/src/assets/img/close_small.svg +3 -0
- package/src/assets/img/emergency_home.svg +10 -0
- package/src/assets/img/first-aid-kit.svg +7 -0
- package/src/assets/img/heartbeat.svg +4 -0
- package/src/assets/img/home-gray.svg +3 -0
- package/src/assets/img/home.svg +3 -0
- package/src/assets/img/hospital.jpg +0 -0
- package/src/assets/img/indeterminate_check_box.svg +10 -0
- package/src/assets/img/indeterminate_check_box_fill.svg +10 -0
- package/src/assets/img/info_24_ 1d4ed8.svg +3 -0
- package/src/assets/img/info_24_ 2c6441.svg +3 -0
- package/src/assets/img/marker_check_by_default.svg +10 -0
- package/src/assets/img/marker_check_by_default_fill.svg +10 -0
- package/src/assets/img/minus-accordion.svg +5 -0
- package/src/assets/img/minus.svg +3 -0
- package/src/assets/img/open.svg +1 -0
- package/src/assets/img/pill-white.svg +7 -0
- package/src/assets/img/pill.svg +5 -0
- package/src/assets/img/plus-accordion.svg +5 -0
- package/src/assets/img/plus.svg +4 -0
- package/src/assets/img/prescription.svg +6 -0
- package/src/assets/img/search.svg +10 -0
- package/src/assets/img/search_icon_light.svg +10 -0
- package/src/assets/img/separator.svg +3 -0
- package/src/assets/img/stethoscope-white.svg +8 -0
- package/src/assets/img/stethoscope.svg +8 -0
- package/src/assets/img/thumb_up.svg +10 -0
- package/src/assets/img/vector.svg +3 -0
- package/src/assets/img/warning-badge-disabled.svg +11 -0
- package/src/assets/img/warning-badge-green.svg +11 -0
- package/src/assets/img/warning-badge-red.svg +11 -0
- package/src/assets/img/warning-badge-yellow.svg +11 -0
- package/src/assets/img/warning.svg +10 -0
- package/src/global.d.ts +13 -0
- package/{index.d.ts → src/index.ts} +13 -5
- package/src/lib/Accordian--Accordian.stories.tsx +312 -0
- package/src/lib/Accordion.spec.tsx +384 -0
- package/src/lib/Accordion.tsx +240 -0
- package/src/lib/AppointmentPicker.spec.tsx +138 -0
- package/src/lib/AppointmentPicker.tsx +97 -0
- package/src/lib/Badge--Badge.stories.tsx +60 -0
- package/src/lib/Badge.spec.tsx +70 -0
- package/src/lib/Badge.tsx +87 -0
- package/src/lib/Breadcrumbs-Breadcrumbs.stories.tsx +114 -0
- package/src/lib/Breadcrumbs.spec.tsx +218 -0
- package/src/lib/Breadcrumbs.tsx +219 -0
- package/src/lib/Button--Button.stories.tsx +220 -0
- package/src/lib/Button.spec.tsx +241 -0
- package/src/lib/Button.tsx +121 -0
- package/src/lib/ButtonGroup--ButtonGroup.stories.tsx +129 -0
- package/src/lib/ButtonGroup.spec.tsx +89 -0
- package/src/lib/ButtonGroup.tsx +107 -0
- package/src/lib/Card--Card.stories.tsx +113 -0
- package/src/lib/Card.spec.tsx +112 -0
- package/src/lib/Card.tsx +69 -0
- package/src/lib/CharacterCounter--CharacterCounter.stories.tsx +169 -0
- package/src/lib/CharacterCounter.spec.tsx +123 -0
- package/src/lib/CharacterCounter.tsx +56 -0
- package/src/lib/CheckBox--CheckBox.stories.tsx +107 -0
- package/src/lib/CheckBox.spec.tsx +412 -0
- package/src/lib/CheckBox.tsx +491 -0
- package/src/lib/DatePicker--DatePicker.stories.tsx +228 -0
- package/src/lib/DatePicker.spec.tsx +424 -0
- package/src/lib/DatePicker.tsx +247 -0
- package/src/lib/Input--Input.stories.tsx +449 -0
- package/src/lib/Input.spec.tsx +281 -0
- package/src/lib/Input.tsx +309 -0
- package/src/lib/List--List.stories.tsx +157 -0
- package/src/lib/List.spec.tsx +211 -0
- package/src/lib/List.tsx +93 -0
- package/src/lib/Modal--Modal.stories.tsx +454 -0
- package/src/lib/Modal.spec.tsx +202 -0
- package/src/lib/Modal.tsx +220 -0
- package/src/lib/Pill--Pill.stories.tsx +98 -0
- package/src/lib/Pill.spec.tsx +103 -0
- package/src/lib/Pill.tsx +91 -0
- package/src/lib/ProgressBar.spec.tsx +106 -0
- package/src/lib/ProgressBar.tsx +112 -0
- package/src/lib/RadioGroup.spec.tsx +84 -0
- package/src/lib/RadioGroup.tsx +74 -0
- package/src/lib/RadioIcon.tsx +13 -0
- package/src/lib/Search--Search.stories.tsx +67 -0
- package/src/lib/Search.spec.tsx +182 -0
- package/src/lib/Search.tsx +304 -0
- package/src/lib/SearchContent.tsx +51 -0
- package/src/lib/SectionHeader--SectionHeader.stories.tsx +98 -0
- package/src/lib/SectionHeader.spec.tsx +60 -0
- package/src/lib/SectionHeader.tsx +91 -0
- package/src/lib/Select--Select.stories.tsx +387 -0
- package/src/lib/Select.spec.tsx +493 -0
- package/src/lib/Select.tsx +311 -0
- package/src/lib/Shield--Shield.stories.tsx +196 -0
- package/src/lib/Shield.spec.tsx +275 -0
- package/src/lib/Shield.tsx +239 -0
- package/src/lib/SideBarNav--SideBarNav.stories.tsx +136 -0
- package/src/lib/SideBarNav.spec.tsx +178 -0
- package/src/lib/SideBarNav.tsx +135 -0
- package/src/lib/Skeleton--Skeleton.stories.tsx +77 -0
- package/src/lib/Skeleton.module.css +16 -0
- package/src/lib/Skeleton.spec.tsx +83 -0
- package/src/lib/Skeleton.tsx +103 -0
- package/src/lib/SkipLink.spec.tsx +76 -0
- package/src/lib/SkipLink.tsx +48 -0
- package/src/lib/Slider--Slider.stories.tsx +108 -0
- package/src/lib/Slider.module.css +109 -0
- package/src/lib/Slider.spec.tsx +67 -0
- package/src/lib/Slider.tsx +101 -0
- package/src/lib/Status--Status.stories.tsx +93 -0
- package/src/lib/Status.spec.tsx +118 -0
- package/src/lib/Status.tsx +79 -0
- package/src/lib/Tabs--Tabs.stories.tsx +294 -0
- package/src/lib/Tabs.spec.tsx +249 -0
- package/src/lib/Tabs.tsx +188 -0
- package/src/lib/Tester.spec.tsx +17 -0
- package/src/lib/Toggle--Toggle.stories.tsx +162 -0
- package/src/lib/Toggle.spec.tsx +122 -0
- package/src/lib/Toggle.tsx +96 -0
- package/src/lib/Tooltip--Tooltip.stories.tsx +315 -0
- package/src/lib/Tooltip.spec.tsx +307 -0
- package/src/lib/Tooltip.tsx +137 -0
- package/src/lib/bak-simple-ui.stories.tsx-bak +24 -0
- package/src/styles.css +190 -0
- package/tsconfig.json +25 -0
- package/tsconfig.lib.json +42 -0
- package/tsconfig.spec.json +29 -0
- package/tsconfig.storybook.json +36 -0
- package/vite.config.mts +87 -0
- package/vitest.setup.ts +12 -0
- package/index.css +0 -1
- package/index.js +0 -35
- package/index.mjs +0 -4981
- package/lib/Accordion.d.ts +0 -36
- package/lib/AppointmentPicker.d.ts +0 -21
- package/lib/Badge.d.ts +0 -11
- package/lib/Breadcrumbs.d.ts +0 -13
- package/lib/Button.d.ts +0 -15
- package/lib/ButtonGroup.d.ts +0 -8
- package/lib/Card.d.ts +0 -11
- package/lib/CharacterCounter.d.ts +0 -11
- package/lib/CheckBox.d.ts +0 -30
- package/lib/DatePicker.d.ts +0 -7
- package/lib/Input.d.ts +0 -16
- package/lib/List.d.ts +0 -22
- package/lib/Modal.d.ts +0 -18
- package/lib/Pill.d.ts +0 -13
- package/lib/ProgressBar.d.ts +0 -19
- package/lib/RadioGroup.d.ts +0 -15
- package/lib/Search.d.ts +0 -26
- package/lib/SearchContent.d.ts +0 -6
- package/lib/SectionHeader.d.ts +0 -18
- package/lib/Select.d.ts +0 -19
- package/lib/Shield.d.ts +0 -12
- package/lib/SideBarNav.d.ts +0 -21
- package/lib/Skeleton.d.ts +0 -15
- package/lib/SkipLink.d.ts +0 -22
- package/lib/Slider.d.ts +0 -14
- package/lib/Status.d.ts +0 -10
- package/lib/Tabs.d.ts +0 -23
- package/lib/Toggle.d.ts +0 -11
- package/lib/Tooltip.d.ts +0 -14
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { vi } from "vitest";
|
|
4
|
+
import { axe } from "vitest-axe";
|
|
5
|
+
import { Select, SelectProps } from "./Select";
|
|
6
|
+
|
|
7
|
+
describe("Select Component", () => {
|
|
8
|
+
const options = [
|
|
9
|
+
{ name: "Option 1", value: "1" },
|
|
10
|
+
{ name: "Option 2", value: "2" },
|
|
11
|
+
{ name: "Option 3", value: "3" },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
it("renders the select dropdown with the default state, error, and variant fill", () => {
|
|
15
|
+
render(<Select variant="fill" error options={options} setSelectedOption={vi.fn()} />);
|
|
16
|
+
const button = screen.getByRole("button", { name: /Options/i });
|
|
17
|
+
|
|
18
|
+
//clicks the button twice to open and close the menu, verifying correct chevrons render
|
|
19
|
+
fireEvent.click(button);
|
|
20
|
+
fireEvent.click(button);
|
|
21
|
+
|
|
22
|
+
expect(button).toBeInTheDocument();
|
|
23
|
+
expect(button).toHaveTextContent("Options");
|
|
24
|
+
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("renders the dropdown label when provided", () => {
|
|
28
|
+
render(<Select label="Dropdown Label" options={options} setSelectedOption={vi.fn()} />);
|
|
29
|
+
const label = screen.getByText("Dropdown Label");
|
|
30
|
+
|
|
31
|
+
expect(label).toBeInTheDocument();
|
|
32
|
+
expect(label).toHaveClass("text-black");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("toggles the dropdown menu on button click", () => {
|
|
36
|
+
render(<Select options={options} setSelectedOption={vi.fn()} />);
|
|
37
|
+
const button = screen.getByRole("button");
|
|
38
|
+
|
|
39
|
+
// Menu should not be present initially
|
|
40
|
+
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
41
|
+
|
|
42
|
+
// Open the dropdown
|
|
43
|
+
fireEvent.click(button);
|
|
44
|
+
expect(screen.getByRole("listbox")).toBeInTheDocument();
|
|
45
|
+
|
|
46
|
+
// Close the dropdown
|
|
47
|
+
fireEvent.click(button);
|
|
48
|
+
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
it("selects an option and calls the callback", () => {
|
|
53
|
+
const setSelectedOptionMock = vi.fn();
|
|
54
|
+
render(<Select options={options} setSelectedOption={setSelectedOptionMock} />);
|
|
55
|
+
|
|
56
|
+
const button = screen.getByRole("button");
|
|
57
|
+
fireEvent.click(button);
|
|
58
|
+
|
|
59
|
+
const option = screen.getByText("Option 1");
|
|
60
|
+
fireEvent.click(option);
|
|
61
|
+
|
|
62
|
+
expect(setSelectedOptionMock).toHaveBeenCalledWith("1");
|
|
63
|
+
expect(button).toHaveTextContent("Option 1");
|
|
64
|
+
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("falls back to the option name if no value is provided", () => {
|
|
68
|
+
const customOptions = [{ name: "No Value Option" }];
|
|
69
|
+
const setSelectedOptionMock = vi.fn();
|
|
70
|
+
|
|
71
|
+
render(<Select options={customOptions} setSelectedOption={setSelectedOptionMock} />);
|
|
72
|
+
const button = screen.getByRole("button");
|
|
73
|
+
|
|
74
|
+
fireEvent.click(button);
|
|
75
|
+
|
|
76
|
+
const option = screen.getByText("No Value Option");
|
|
77
|
+
fireEvent.click(option);
|
|
78
|
+
|
|
79
|
+
expect(setSelectedOptionMock).toHaveBeenCalledWith("No Value Option");
|
|
80
|
+
expect(button).toHaveTextContent("No Value Option");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("closes the dropdown when clicking outside", () => {
|
|
84
|
+
render(<Select options={options} setSelectedOption={vi.fn()} />);
|
|
85
|
+
const button = screen.getByRole("button");
|
|
86
|
+
|
|
87
|
+
// Open the dropdown
|
|
88
|
+
fireEvent.click(button);
|
|
89
|
+
expect(screen.getByRole("listbox")).toBeInTheDocument();
|
|
90
|
+
|
|
91
|
+
// Click outside
|
|
92
|
+
fireEvent.mouseDown(document.body);
|
|
93
|
+
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
it("applies custom classes and merges them with default styles", () => {
|
|
98
|
+
render(
|
|
99
|
+
<Select
|
|
100
|
+
className="custom-class"
|
|
101
|
+
options={options}
|
|
102
|
+
setSelectedOption={vi.fn()}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const button = screen.getByRole("button");
|
|
107
|
+
expect(button).toHaveClass("custom-class");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("applies variant-specific styles", () => {
|
|
111
|
+
render(<Select variant="outline" options={options} setSelectedOption={vi.fn()} />);
|
|
112
|
+
const button = screen.getByRole("button");
|
|
113
|
+
|
|
114
|
+
expect(button).toHaveClass("border-[#b3b3b3] bg-white border");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("renders a disabled dropdown", () => {
|
|
118
|
+
render(
|
|
119
|
+
<Select
|
|
120
|
+
options={options}
|
|
121
|
+
setSelectedOption={vi.fn()}
|
|
122
|
+
disabled
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const button = screen.getByRole("button");
|
|
127
|
+
expect(button).toBeDisabled();
|
|
128
|
+
expect(button).toHaveClass("disabled:bg-dha-mc-bottom-nav-background disabled:text-dha-mc-checkbox-inactive");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
it('falls back to the default variant when an invalid variant is provided', () => {
|
|
133
|
+
const { container } = render(
|
|
134
|
+
<Select
|
|
135
|
+
options={options}
|
|
136
|
+
variant="InvalidVariant"
|
|
137
|
+
setSelectedOption={vitest.fn()}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Verify that the classes for the "default" variant are applied
|
|
142
|
+
const button = container.querySelector('button');
|
|
143
|
+
expect(button).toHaveClass('hover:bg-gray-200');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
describe('Select Accessibility Tests', () => {
|
|
150
|
+
const mockSetSelectedOption = vi.fn();
|
|
151
|
+
const options = [
|
|
152
|
+
{ name: 'Option 1', value: '1' },
|
|
153
|
+
{ name: 'Option 2', value: '2' },
|
|
154
|
+
{ name: 'Option 3', value: '3' },
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
const renderComponent = (props: Partial<SelectProps> = {}) =>
|
|
158
|
+
render(
|
|
159
|
+
<Select
|
|
160
|
+
label="Choose an option"
|
|
161
|
+
options={options}
|
|
162
|
+
setSelectedOption={mockSetSelectedOption}
|
|
163
|
+
{...props}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
it('should have no accessibility violations in the default state', async () => {
|
|
168
|
+
const { container } = renderComponent();
|
|
169
|
+
const results = await axe(container);
|
|
170
|
+
expect(results).toHaveNoViolations();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should have no accessibility violations when open', async () => {
|
|
174
|
+
const { container, getByRole } = renderComponent();
|
|
175
|
+
const button = getByRole('button', { name: /Select options/i });
|
|
176
|
+
fireEvent.click(button);
|
|
177
|
+
const results = await axe(container);
|
|
178
|
+
expect(results).toHaveNoViolations();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should include a label if provided', () => {
|
|
182
|
+
const { getByText } = renderComponent({ label: 'Accessible Label' });
|
|
183
|
+
expect(getByText('Accessible Label')).toBeInTheDocument();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should render options correctly', () => {
|
|
187
|
+
const { getByRole, getByLabelText } = renderComponent();
|
|
188
|
+
const button = getByRole('button', { name: /Select options/i });
|
|
189
|
+
fireEvent.click(button);
|
|
190
|
+
|
|
191
|
+
options.forEach((option) => {
|
|
192
|
+
const optionElement = getByLabelText(`option ${option.name}`);
|
|
193
|
+
expect(optionElement).toBeInTheDocument();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should allow selecting an option', () => {
|
|
198
|
+
const { getByRole, getByLabelText } = renderComponent();
|
|
199
|
+
const button = getByRole('button', { name: /Select options/i });
|
|
200
|
+
fireEvent.click(button);
|
|
201
|
+
|
|
202
|
+
const optionToSelect = getByLabelText('option Option 2');
|
|
203
|
+
fireEvent.click(optionToSelect);
|
|
204
|
+
|
|
205
|
+
expect(mockSetSelectedOption).toHaveBeenCalledWith('2');
|
|
206
|
+
expect(button).toHaveTextContent('Option 2');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should support disabled state', () => {
|
|
210
|
+
const { getByRole } = renderComponent({ disabled: true });
|
|
211
|
+
const button = getByRole('button', { name: /Select options/i });
|
|
212
|
+
expect(button).toBeDisabled();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should handle custom classes without affecting accessibility', async () => {
|
|
216
|
+
const { container } = renderComponent({ className: 'custom-class' });
|
|
217
|
+
const results = await axe(container);
|
|
218
|
+
expect(results).toHaveNoViolations();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should handle an empty options array gracefully', () => {
|
|
222
|
+
const { queryByRole } = renderComponent({ options: [] });
|
|
223
|
+
const dropdown = queryByRole('listbox');
|
|
224
|
+
expect(dropdown).not.toBeInTheDocument();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should close the dropdown when clicking outside', () => {
|
|
228
|
+
const { getByRole, container } = renderComponent();
|
|
229
|
+
const button = getByRole('button', { name: /Options/i });
|
|
230
|
+
fireEvent.click(button);
|
|
231
|
+
|
|
232
|
+
fireEvent.mouseDown(container);
|
|
233
|
+
expect(button).toHaveAttribute('aria-expanded', 'false');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
it('should maintain focus management for keyboard users', () => {
|
|
238
|
+
const options = [
|
|
239
|
+
{ name: 'Option 1', value: '1' },
|
|
240
|
+
{ name: 'Option 2', value: '2' },
|
|
241
|
+
{ name: 'Option 3', value: '3' },
|
|
242
|
+
];
|
|
243
|
+
|
|
244
|
+
const { getByRole, getByLabelText } = renderComponent({
|
|
245
|
+
options,
|
|
246
|
+
optionsLabel: 'Choose an option',
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const button = getByRole('button', { name: /Choose an option/i });
|
|
250
|
+
fireEvent.click(button); // Open the dropdown
|
|
251
|
+
|
|
252
|
+
const firstOption = getByLabelText('option Option 1');
|
|
253
|
+
fireEvent.keyDown(firstOption, { key: 'Enter', code: 'Enter' }); // Simulate Enter key press
|
|
254
|
+
// fireEvent.click(firstOption); // works, meaning keyboard is NOT. Need to fix ...
|
|
255
|
+
|
|
256
|
+
expect(mockSetSelectedOption).toHaveBeenCalledWith('1'); // Verify correct value is selected
|
|
257
|
+
expect(button).toHaveTextContent('Option 1'); // Verify button displays the selected option
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should move focus to the next option with ArrowDown key', () => {
|
|
261
|
+
renderComponent();
|
|
262
|
+
const button = screen.getByRole('button', { name: /Select options/i });
|
|
263
|
+
fireEvent.click(button);
|
|
264
|
+
|
|
265
|
+
const listbox = screen.getByRole('listbox');
|
|
266
|
+
|
|
267
|
+
// Press ArrowDown
|
|
268
|
+
fireEvent.keyDown(listbox, { key: 'ArrowDown' });
|
|
269
|
+
|
|
270
|
+
// Second option should be focused (because the first key press advances the focus to first option)
|
|
271
|
+
expect(screen.getByRole('option', { name: 'option Option 2' })).toHaveFocus();
|
|
272
|
+
|
|
273
|
+
// Press ArrowDown again
|
|
274
|
+
fireEvent.keyDown(listbox, { key: 'ArrowDown' });
|
|
275
|
+
|
|
276
|
+
// Third option should now be focused
|
|
277
|
+
expect(screen.getByRole('option', { name: 'option Option 3' })).toHaveFocus();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should move focus to the previous option with ArrowUp key', () => {
|
|
281
|
+
renderComponent();
|
|
282
|
+
const button = screen.getByRole('button', { name: /Select options/i });
|
|
283
|
+
fireEvent.click(button);
|
|
284
|
+
|
|
285
|
+
const listbox = screen.getByRole('listbox');
|
|
286
|
+
|
|
287
|
+
// Press ArrowUp
|
|
288
|
+
fireEvent.keyDown(listbox, { key: 'ArrowUp' });
|
|
289
|
+
|
|
290
|
+
// Wraps around to the last option
|
|
291
|
+
expect(screen.getByRole('option', { name: 'option Option 3' })).toHaveFocus();
|
|
292
|
+
|
|
293
|
+
// Press ArrowUp again
|
|
294
|
+
fireEvent.keyDown(listbox, { key: 'ArrowUp' });
|
|
295
|
+
|
|
296
|
+
// Second option should be focused
|
|
297
|
+
expect(screen.getByRole('option', { name: 'option Option 2' })).toHaveFocus();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
it('should move focus to the first option with Home key', () => {
|
|
302
|
+
renderComponent();
|
|
303
|
+
const button = screen.getByRole('button', { name: /Select options/i });
|
|
304
|
+
fireEvent.click(button);
|
|
305
|
+
|
|
306
|
+
const listbox = screen.getByRole('listbox');
|
|
307
|
+
|
|
308
|
+
// Press Home
|
|
309
|
+
fireEvent.keyDown(listbox, { key: 'Home' });
|
|
310
|
+
|
|
311
|
+
// First option should be focused
|
|
312
|
+
expect(screen.getByRole('option', { name: 'option Option 1' })).toHaveFocus();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should move focus to the last option with End key', () => {
|
|
316
|
+
renderComponent();
|
|
317
|
+
const button = screen.getByRole('button', { name: /Select options/i });
|
|
318
|
+
fireEvent.click(button);
|
|
319
|
+
|
|
320
|
+
const listbox = screen.getByRole('listbox');
|
|
321
|
+
|
|
322
|
+
// Press End
|
|
323
|
+
fireEvent.keyDown(listbox, { key: 'End' });
|
|
324
|
+
|
|
325
|
+
// Last option should be focused
|
|
326
|
+
expect(screen.getByRole('option', { name: 'option Option 3' })).toHaveFocus();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// it('should call preventDefault for navigation keys', () => {
|
|
330
|
+
// renderComponent();
|
|
331
|
+
// const button = screen.getByRole('button', { name: /Select options/i });
|
|
332
|
+
// fireEvent.click(button);
|
|
333
|
+
|
|
334
|
+
// const listbox = screen.getByRole('listbox');
|
|
335
|
+
|
|
336
|
+
// // Spy on preventDefault
|
|
337
|
+
// const preventDefaultSpy = vi.fn();
|
|
338
|
+
|
|
339
|
+
// // Simulate the keydown event
|
|
340
|
+
// fireEvent.keyDown(listbox, {
|
|
341
|
+
// key: 'ArrowDown',
|
|
342
|
+
// preventDefault: preventDefaultSpy,
|
|
343
|
+
// });
|
|
344
|
+
|
|
345
|
+
// // Ensure preventDefault was called
|
|
346
|
+
// expect(preventDefaultSpy).toHaveBeenCalled();
|
|
347
|
+
// });
|
|
348
|
+
|
|
349
|
+
it('should close the select box when Escape key is pressed', () => {
|
|
350
|
+
// Render the component
|
|
351
|
+
renderComponent();
|
|
352
|
+
|
|
353
|
+
// Open the dropdown
|
|
354
|
+
const button = screen.getByRole('button', { name: /Select options/i });
|
|
355
|
+
fireEvent.click(button);
|
|
356
|
+
|
|
357
|
+
// Verify that the dropdown is open
|
|
358
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
359
|
+
|
|
360
|
+
// Press the Escape key
|
|
361
|
+
fireEvent.keyDown(screen.getByRole('listbox'), { key: 'Escape' });
|
|
362
|
+
|
|
363
|
+
// Verify that the dropdown is closed
|
|
364
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should close the dropdown when focus moves outside', () => {
|
|
368
|
+
renderComponent();
|
|
369
|
+
|
|
370
|
+
// Open the dropdown
|
|
371
|
+
const button = screen.getByRole('button', { name: /Select options/i });
|
|
372
|
+
fireEvent.click(button);
|
|
373
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
374
|
+
|
|
375
|
+
// Simulate focus moving outside
|
|
376
|
+
fireEvent.focusIn(document.body);
|
|
377
|
+
|
|
378
|
+
// Verify the dropdown is closed
|
|
379
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should select an option when Enter is pressed (using event.code)', () => {
|
|
383
|
+
renderComponent();
|
|
384
|
+
|
|
385
|
+
// Open the dropdown
|
|
386
|
+
const button = screen.getByRole('button', { name: /Select options/i });
|
|
387
|
+
fireEvent.click(button);
|
|
388
|
+
|
|
389
|
+
// Verify that the dropdown is open
|
|
390
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
391
|
+
|
|
392
|
+
// Press Enter (using event.code)
|
|
393
|
+
fireEvent.keyDown(screen.getByRole('option', { name: 'option Option 1' }), {
|
|
394
|
+
code: 'Enter',
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Verify that the dropdown is closed
|
|
398
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('should select an option when Space key is pressed', () => {
|
|
402
|
+
renderComponent();
|
|
403
|
+
|
|
404
|
+
// Open the dropdown
|
|
405
|
+
const button = screen.getByRole('button', { name: /Select options/i });
|
|
406
|
+
fireEvent.click(button);
|
|
407
|
+
|
|
408
|
+
// Verify that the dropdown is open
|
|
409
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
410
|
+
|
|
411
|
+
// Press Space key
|
|
412
|
+
fireEvent.keyDown(screen.getByRole('option', { name: 'option Option 1' }), {
|
|
413
|
+
key: ' ',
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Verify that the dropdown is closed
|
|
417
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('should close the select box when Escape key is pressed without selecting an option', () => {
|
|
421
|
+
renderComponent();
|
|
422
|
+
|
|
423
|
+
// Open the dropdown
|
|
424
|
+
const button = screen.getByRole('button', { name: /Select options/i });
|
|
425
|
+
fireEvent.click(button);
|
|
426
|
+
|
|
427
|
+
// Verify that the dropdown is open
|
|
428
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
429
|
+
|
|
430
|
+
// Press Escape key on an option
|
|
431
|
+
fireEvent.keyDown(screen.getByRole('option', { name: 'option Option 1' }), {
|
|
432
|
+
key: 'Escape',
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// Verify that the dropdown is closed
|
|
436
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
437
|
+
|
|
438
|
+
// Ensure no option was selected
|
|
439
|
+
expect(button).toHaveTextContent('Options');
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("positions dropdown above the button when there is insufficient space below", () => {
|
|
443
|
+
// Set global dimensions so that containerHeight is 600.
|
|
444
|
+
Object.defineProperty(window, "innerHeight", {
|
|
445
|
+
writable: true,
|
|
446
|
+
configurable: true,
|
|
447
|
+
value: 600,
|
|
448
|
+
});
|
|
449
|
+
Object.defineProperty(document.body, "offsetHeight", {
|
|
450
|
+
writable: true,
|
|
451
|
+
configurable: true,
|
|
452
|
+
value: 600,
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
const setSelectedOptionMock = vi.fn();
|
|
456
|
+
const { container } = render(<Select options={options} setSelectedOption={setSelectedOptionMock} />);
|
|
457
|
+
|
|
458
|
+
// Override container's bounding rect to simulate a button with top:200 and bottom:500.
|
|
459
|
+
const outerContainer = container.firstChild as HTMLElement;
|
|
460
|
+
outerContainer.getBoundingClientRect = () => ({
|
|
461
|
+
top: 200,
|
|
462
|
+
bottom: 500, // available space below = 600 - 500 = 100
|
|
463
|
+
left: 0,
|
|
464
|
+
right: 600,
|
|
465
|
+
width: 600,
|
|
466
|
+
height: 300,
|
|
467
|
+
x: 0,
|
|
468
|
+
y: 0,
|
|
469
|
+
toJSON: () => {('')}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Save the original descriptor for offsetHeight.
|
|
473
|
+
const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLDivElement.prototype, 'offsetHeight');
|
|
474
|
+
// Override offsetHeight so that any element with role "listbox" reports a height of 150.
|
|
475
|
+
Object.defineProperty(HTMLDivElement.prototype, 'offsetHeight', {
|
|
476
|
+
configurable: true,
|
|
477
|
+
get: function () {
|
|
478
|
+
if (this.getAttribute && this.getAttribute('role') === 'listbox') {
|
|
479
|
+
return 150;
|
|
480
|
+
}
|
|
481
|
+
return originalOffsetHeight && originalOffsetHeight.get ? originalOffsetHeight.get.call(this) : 0;
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Open the dropdown so that updateDropdownPosition runs.
|
|
486
|
+
const button = screen.getByRole("button");
|
|
487
|
+
fireEvent.click(button);
|
|
488
|
+
|
|
489
|
+
// With spaceBelow (100) < dropdownHeight (150) and container top (200) > 150,
|
|
490
|
+
// updateDropdownPosition should set isAbove to true, causing the button to have "rounded-b-lg".
|
|
491
|
+
expect(button.className).toMatch(/rounded-b-lg/);
|
|
492
|
+
});
|
|
493
|
+
});
|