@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,275 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi } from "vitest";
|
|
3
|
+
import { Shield, ShieldProps } from './Shield';
|
|
4
|
+
import React, { createRef } from 'react';
|
|
5
|
+
import { axe } from "vitest-axe";
|
|
6
|
+
import closeIcon from '../assets/img/close_small.svg';
|
|
7
|
+
import warning from '../assets/img/warning.svg';
|
|
8
|
+
import emergency from '../assets/img/emergency_home.svg';
|
|
9
|
+
import thumbUp from '../assets/img/thumb_up.svg';
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const imagePath = new URL('/src/assets/pill.svg', import.meta.url).href;
|
|
13
|
+
|
|
14
|
+
describe('Shield', () => {
|
|
15
|
+
|
|
16
|
+
it('renders the correct icon for variant "warning"', () => {
|
|
17
|
+
render(<Shield variant="warning" subVariant="half">Warning Shield</Shield>);
|
|
18
|
+
const icon = screen.getByRole('img', { name: /warning icon/i });
|
|
19
|
+
expect(icon).toHaveAttribute('src', warning);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('renders the correct icon for variant "hazard"', () => {
|
|
23
|
+
render(<Shield variant="hazard" subVariant="half">Hazard Shield</Shield>);
|
|
24
|
+
const icon = screen.getByRole('img', { name: /hazard icon/i });
|
|
25
|
+
expect(icon).toHaveAttribute('src', emergency);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders the correct icon for variant "thumbUp"', () => {
|
|
29
|
+
render(<Shield variant="go" subVariant="half">Thumb Up Shield</Shield>);
|
|
30
|
+
const icon = screen.getByRole('img', { name: /go icon/i });
|
|
31
|
+
expect(icon).toHaveAttribute('src', thumbUp);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders children inside the Shield', () => {
|
|
35
|
+
render(<Shield variant="warning" subVariant="half">This is a warning</Shield>);
|
|
36
|
+
expect(screen.getByText('This is a warning')).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('closes the Shield when the close button is clicked', () => {
|
|
40
|
+
render(<Shield variant="warning" subVariant="half">Closable Shield</Shield>);
|
|
41
|
+
|
|
42
|
+
const shieldElement = screen.getByText('Closable Shield').parentElement;
|
|
43
|
+
const closeButton = screen.getByRole('button', { name: /close modal/i });
|
|
44
|
+
|
|
45
|
+
expect(shieldElement).toBeVisible();
|
|
46
|
+
|
|
47
|
+
fireEvent.click(closeButton);
|
|
48
|
+
|
|
49
|
+
expect(shieldElement).toHaveClass('hidden');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders the close button with the correct icon', () => {
|
|
53
|
+
render(<Shield variant="warning" subVariant="half">Shield with Close</Shield>);
|
|
54
|
+
|
|
55
|
+
const closeIconImg = screen.getByRole('img', { name: /close alert icon/i });
|
|
56
|
+
|
|
57
|
+
expect(closeIconImg).toHaveAttribute('src', closeIcon);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ---
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
// Test basic render - default variant / default subvariant
|
|
64
|
+
it('should render successfully', () => {
|
|
65
|
+
const { baseElement } = render(<Shield variant={'default'} subVariant={'default'}>
|
|
66
|
+
<h1>Heading</h1>
|
|
67
|
+
<p>Content Body</p>
|
|
68
|
+
</Shield>);
|
|
69
|
+
expect(baseElement).toBeTruthy();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Test basic render - icon variant / blue subvariant
|
|
73
|
+
it('should render successfully', () => {
|
|
74
|
+
const { baseElement } = render(<Shield variant={'icon'} subVariant={'blue'}>
|
|
75
|
+
I'm a Shield!
|
|
76
|
+
</Shield>);
|
|
77
|
+
expect(baseElement).toBeTruthy();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Test basic render - media variant / default subvariant
|
|
81
|
+
it('should render successfully', () => {
|
|
82
|
+
const { baseElement } = render(<Shield variant={'media'} subVariant={'default'} imagePath={imagePath} imageAlt='Pill icon'>
|
|
83
|
+
<h1>Heading</h1>
|
|
84
|
+
<p>Content Body</p>
|
|
85
|
+
</Shield>);
|
|
86
|
+
expect(baseElement).toBeTruthy();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Test diff variant & size props
|
|
90
|
+
it('should apply the correct class for the default variant', () => {
|
|
91
|
+
const { container } = render(<Shield variant="default" subVariant={'default'} >I'm a Shield!</Shield>);
|
|
92
|
+
const shieldElement = container.firstChild as HTMLElement;
|
|
93
|
+
expect(shieldElement.className).toContain('bg-gray-500/10');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
it('should apply the correct class for the icon variant', () => {
|
|
98
|
+
const { container } = render(<Shield variant="icon" subVariant={'default'} >I'm a Shield!</Shield>);
|
|
99
|
+
const shieldElement = container.firstChild as HTMLElement;
|
|
100
|
+
expect(shieldElement.className).toContain('bg-gray-500');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
it('should apply the correct class for media variant', () => {
|
|
105
|
+
const { container } = render(<Shield variant={'media'} subVariant={'default'} >
|
|
106
|
+
<h1>Heading</h1>
|
|
107
|
+
<p>Body content</p>
|
|
108
|
+
</Shield>);
|
|
109
|
+
const shieldElement = container.firstChild as HTMLElement;
|
|
110
|
+
expect(shieldElement.className).toContain('w-[250px] mx-2 border-l-8 border-black grid grid-cols-6 rounded-md bg-gray-400/10 pr-2 text-xs font-medium text-gray-400 ring-1 ring-inset ring-gray-400/20 overflow-hidden');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Test image prop and accessibility
|
|
114
|
+
it('should render an image with the correct alt text', () => {
|
|
115
|
+
const { container } = render(<Shield variant={'media'} subVariant={'default'} imagePath={imagePath} imageAlt='Test Image'>
|
|
116
|
+
<h1>Heading</h1>
|
|
117
|
+
<p>Body Content</p>
|
|
118
|
+
</Shield>);
|
|
119
|
+
const image = container.querySelector('img');
|
|
120
|
+
expect(image).toBeTruthy();
|
|
121
|
+
// expect(image?.getAttribute('src')).toBe('path/to/image.jpg');
|
|
122
|
+
expect(image?.getAttribute('alt')).toBe('Test Image');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('renders without children if none are provided', () => {
|
|
126
|
+
const { container } = render(<Shield variant="default" subVariant="gray" />);
|
|
127
|
+
const shield = container.firstChild as HTMLElement;
|
|
128
|
+
expect(shield).toBeInTheDocument();
|
|
129
|
+
expect(shield).toHaveClass('mx-2 inline-flex items-center rounded-md bg-gray-500/10 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-400/20');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('renders a custom media shield with additional image classes', () => {
|
|
133
|
+
render(
|
|
134
|
+
<Shield
|
|
135
|
+
variant="media"
|
|
136
|
+
subVariant="green"
|
|
137
|
+
imagePath="/path/to/image.jpg"
|
|
138
|
+
imageAlt="Custom Image"
|
|
139
|
+
classNameImage="rounded-lg"
|
|
140
|
+
>
|
|
141
|
+
Custom Media Shield
|
|
142
|
+
</Shield>
|
|
143
|
+
);
|
|
144
|
+
const img = screen.getByAltText('Custom Image');
|
|
145
|
+
expect(img).toHaveClass('h-full rounded-lg');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// handles empty props gracefully
|
|
149
|
+
it('handles empty props gracefully', () => {
|
|
150
|
+
const { container } = render(<Shield variant="default" subVariant="default" />);
|
|
151
|
+
const shield = container.firstChild as HTMLElement;
|
|
152
|
+
expect(shield).toBeInTheDocument();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should forward the ref to the correct DOM element', () => {
|
|
156
|
+
const ref = createRef<HTMLDivElement>();
|
|
157
|
+
render(
|
|
158
|
+
<Shield ref={ref} variant="default" subVariant="default">
|
|
159
|
+
Shield with Ref
|
|
160
|
+
</Shield>
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Confirm the ref is attached to the span element
|
|
164
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
165
|
+
expect(ref.current?.textContent).toBe('Shield with Ref');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should forward the ref to the correct element for media variant', () => {
|
|
169
|
+
const ref = createRef<HTMLDivElement>();
|
|
170
|
+
render(
|
|
171
|
+
<Shield
|
|
172
|
+
ref={ref}
|
|
173
|
+
variant="media"
|
|
174
|
+
subVariant="default"
|
|
175
|
+
imagePath="/path/to/image.jpg"
|
|
176
|
+
imageAlt="Media Image"
|
|
177
|
+
>
|
|
178
|
+
Media Shield with Ref
|
|
179
|
+
</Shield>
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Confirm the ref is attached to the div element
|
|
183
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
184
|
+
expect(ref.current?.textContent).toContain('Media Shield with Ref');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
it('merges custom classNameSvg with default classNameSvg for "icon" variant and "custom" subVariant', () => {
|
|
189
|
+
const customSvgClasses = 'custom-size custom-fill';
|
|
190
|
+
|
|
191
|
+
// Render the Shield component with the "icon" variant and "custom" subVariant
|
|
192
|
+
render(
|
|
193
|
+
<Shield variant="icon" subVariant="custom" classNameSvg={customSvgClasses}>
|
|
194
|
+
Icon Shield
|
|
195
|
+
</Shield>
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Use act to ensure updates triggered by useEffect are applied
|
|
199
|
+
const svgElement = screen.getByRole('img', { hidden: true }); // Finds the SVG element
|
|
200
|
+
|
|
201
|
+
// Verify that the final svgClassValue is merged correctly
|
|
202
|
+
expect(svgElement).toHaveClass('size-1.5 fill-gray-400'); // From "default" subVariant in "icon" variant
|
|
203
|
+
expect(svgElement).toHaveClass('custom-size custom-fill'); // Custom svgClasses
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('Shield Accessibility Tests', () => {
|
|
209
|
+
const renderComponent = (props: ShieldProps) => render(<Shield {...props}>Accessible Shield</Shield>);
|
|
210
|
+
|
|
211
|
+
it('should have no accessibility violations for default variant', async () => {
|
|
212
|
+
const { container } = renderComponent({ variant: 'default', subVariant: 'default' });
|
|
213
|
+
const results = await axe(container);
|
|
214
|
+
expect(results).toHaveNoViolations();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should have no accessibility violations for icon variant', async () => {
|
|
218
|
+
const { container } = renderComponent({ variant: 'icon', subVariant: 'blue' });
|
|
219
|
+
const results = await axe(container);
|
|
220
|
+
expect(results).toHaveNoViolations();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should have no accessibility violations for media variant with image', async () => {
|
|
224
|
+
const { container } = renderComponent({
|
|
225
|
+
variant: 'media',
|
|
226
|
+
subVariant: 'green',
|
|
227
|
+
imagePath: 'https://via.placeholder.com/50',
|
|
228
|
+
imageAlt: 'Sample Image',
|
|
229
|
+
});
|
|
230
|
+
const results = await axe(container);
|
|
231
|
+
expect(results).toHaveNoViolations();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should include alt text for images in media variant', () => {
|
|
235
|
+
const { getByAltText } = renderComponent({
|
|
236
|
+
variant: 'media',
|
|
237
|
+
subVariant: 'green',
|
|
238
|
+
imagePath: 'https://via.placeholder.com/50',
|
|
239
|
+
imageAlt: 'Accessible Image',
|
|
240
|
+
});
|
|
241
|
+
expect(getByAltText('Accessible Image')).toBeTruthy();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should not have duplicate or missing roles in icon variant', async () => {
|
|
245
|
+
const { container } = renderComponent({ variant: 'icon', subVariant: 'red' });
|
|
246
|
+
const svg = container.querySelector('svg');
|
|
247
|
+
expect(svg).toHaveAttribute('role', 'img');
|
|
248
|
+
expect(svg).toHaveAttribute('aria-hidden', 'true');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should support custom class merging without accessibility issues', async () => {
|
|
252
|
+
const { container } = renderComponent({
|
|
253
|
+
variant: 'default',
|
|
254
|
+
subVariant: 'custom',
|
|
255
|
+
className: 'custom-class',
|
|
256
|
+
});
|
|
257
|
+
const results = await axe(container);
|
|
258
|
+
expect(results).toHaveNoViolations();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should handle empty or missing children gracefully', async () => {
|
|
262
|
+
const { container } = renderComponent({ variant: 'default', subVariant: 'default', children: null });
|
|
263
|
+
const results = await axe(container);
|
|
264
|
+
expect(results).toHaveNoViolations();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('should support ARIA attributes for assistive technologies', () => {
|
|
268
|
+
const { container } = renderComponent({
|
|
269
|
+
variant: 'icon',
|
|
270
|
+
subVariant: 'yellow',
|
|
271
|
+
'aria-label': 'Yellow Shield',
|
|
272
|
+
});
|
|
273
|
+
expect(container.querySelector('div')).toHaveAttribute('aria-label', 'Yellow Shield');
|
|
274
|
+
});
|
|
275
|
+
});
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { forwardRef, HTMLAttributes, ReactNode, useEffect, useState } from 'react';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
import thumbUp from '../assets/img/thumb_up.svg';
|
|
4
|
+
import warning from '../assets/img/warning.svg';
|
|
5
|
+
import emergency from '../assets/img/emergency_home.svg';
|
|
6
|
+
import closeIcon from '../assets/img/close_small.svg'
|
|
7
|
+
|
|
8
|
+
interface VariantType {
|
|
9
|
+
variant: string; // type of shield
|
|
10
|
+
subVariant: string; // variant of shield
|
|
11
|
+
classes?: string; // class property for this variant
|
|
12
|
+
classNameSvg?: string; // classNameSvg if relevant
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const variants: VariantType[] = [
|
|
16
|
+
{
|
|
17
|
+
variant: 'default',
|
|
18
|
+
subVariant: 'default',
|
|
19
|
+
classes: 'mx-2 inline-flex items-center rounded-md bg-gray-500/10 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-400/20'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
variant: 'default',
|
|
23
|
+
subVariant: 'gray',
|
|
24
|
+
classes: 'mx-2 inline-flex items-center rounded-md bg-gray-500/10 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-400/20'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
variant: 'default',
|
|
28
|
+
subVariant: 'red',
|
|
29
|
+
classes: 'mx-2 inline-flex items-center rounded-md bg-red-500/10 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-400/20'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
variant: 'default',
|
|
33
|
+
subVariant: 'yellow',
|
|
34
|
+
classes: 'mx-2 inline-flex items-center rounded-md bg-yellow-500/10 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 ring-inset ring-yellow-400/20'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
variant: 'default',
|
|
38
|
+
subVariant: 'green',
|
|
39
|
+
classes: 'mx-2 inline-flex items-center rounded-md bg-green-500/10 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-500/20'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
variant: 'default',
|
|
43
|
+
subVariant: 'blue',
|
|
44
|
+
classes: 'mx-2 inline-flex items-center rounded-md bg-blue-400/10 px-2 py-1 text-xs font-medium text-blue-400 ring-1 ring-inset ring-blue-400/30'
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
variant: 'warning',
|
|
49
|
+
subVariant: 'half',
|
|
50
|
+
classes: 'flex flex-row w-1/2 bg-[#fff1be] border-[#efbd1f] p-2 rounded-md border justify-center items-start gap-2.5 inline-flex">'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
variant: 'warning',
|
|
54
|
+
subVariant: 'full',
|
|
55
|
+
classes: 'flex flex-row w-full bg-[#fff1be] border-[#efbd1f] p-2 rounded-md border justify-center items-start gap-2.5 inline-flex'
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
{
|
|
59
|
+
variant: 'hazard',
|
|
60
|
+
subVariant: 'half',
|
|
61
|
+
classes: 'flex flex-row w-1/2 bg-[#f4c2c2] border-[#efbd1f] p-2 rounded-md border justify-center items-start gap-2.5 inline-flex">'
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
variant: 'hazard',
|
|
65
|
+
subVariant: 'full',
|
|
66
|
+
classes: 'flex flex-row w-full bg-[#f4c2c2] border-[#efbd1f] p-2 rounded-md border justify-center items-start gap-2.5 inline-flex">'
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
variant: 'go',
|
|
71
|
+
subVariant: 'half',
|
|
72
|
+
classes: 'flex flex-row w-1/2 bg-[#d6f4d5] border-[#40bf40] p-2 rounded-md border justify-center items-start gap-2.5 inline-flex">'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
variant: 'go',
|
|
76
|
+
subVariant: 'full',
|
|
77
|
+
classes: 'flex flex-row w-full bg-[#d6f4d5] border-[#40bf40] p-2 rounded-md border justify-center items-start gap-2.5 inline-flex">'
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
variant: 'icon',
|
|
83
|
+
subVariant: 'default',
|
|
84
|
+
classes: 'inline-flex items-center gap-x-1.5 rounded-full bg-gray-500 px-2 py-1 text-xs font-medium text-gray-700',
|
|
85
|
+
classNameSvg: 'size-1.5 fill-gray-400'
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
variant: 'icon',
|
|
89
|
+
subVariant: 'gray',
|
|
90
|
+
classes: 'inline-flex items-center gap-x-1.5 rounded-full bg-gray-100 px-2 py-1 text-xs font-medium text-gray-700',
|
|
91
|
+
classNameSvg: 'size-1.5 fill-gray-400'
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
variant: 'icon',
|
|
95
|
+
subVariant: 'red',
|
|
96
|
+
classes: 'inline-flex items-center gap-x-1.5 rounded-full bg-red-100 px-2 py-1 text-xs font-medium text-red-700',
|
|
97
|
+
classNameSvg: 'size-1.5 fill-red-400'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
variant: 'icon',
|
|
101
|
+
subVariant: 'yellow',
|
|
102
|
+
classes: 'inline-flex items-center gap-x-1.5 rounded-full bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-700',
|
|
103
|
+
classNameSvg: 'size-1.5 fill-yellow-400'
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
variant: 'icon',
|
|
107
|
+
subVariant: 'green',
|
|
108
|
+
classes: 'inline-flex items-center gap-x-1.5 rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-700',
|
|
109
|
+
classNameSvg: 'size-1.5 fill-green-400'
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
variant: 'icon',
|
|
113
|
+
subVariant: 'blue',
|
|
114
|
+
classes: 'inline-flex items-center gap-x-1.5 rounded-full bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700',
|
|
115
|
+
classNameSvg: 'size-1.5 fill-blue-400'
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
variant: 'media',
|
|
119
|
+
subVariant: 'default',
|
|
120
|
+
classes: 'w-[250px] mx-2 border-l-8 border-black grid grid-cols-6 rounded-md bg-gray-400/10 pr-2 text-xs font-medium text-gray-400 ring-1 ring-inset ring-gray-400/20 overflow-hidden',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
variant: 'media',
|
|
124
|
+
subVariant: 'gray',
|
|
125
|
+
classes: 'w-[250px] mx-2 border-l-8 border-black grid grid-cols-6 rounded-md bg-gray-400/10 pr-2 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-400/20 overflow-hidden',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
variant: 'media',
|
|
129
|
+
subVariant: 'green',
|
|
130
|
+
classes: 'w-[250px] mx-2 border-l-8 border-black grid grid-cols-6 rounded-md bg-gray-400/10 pr-2 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-400/20 overflow-hidden',
|
|
131
|
+
},
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
export interface ShieldProps extends HTMLAttributes<HTMLDivElement> {
|
|
135
|
+
variant: string;
|
|
136
|
+
subVariant: string;
|
|
137
|
+
className?: string;
|
|
138
|
+
classNameSvg?: string;
|
|
139
|
+
imagePath?: string;
|
|
140
|
+
imageAlt?: string;
|
|
141
|
+
classNameImage?: string;
|
|
142
|
+
children?: ReactNode;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const Shield = forwardRef<HTMLDivElement, ShieldProps>(
|
|
146
|
+
({ variant = 'default', subVariant = 'default', className,
|
|
147
|
+
classNameSvg = 'size-1.5 fill-yellow-400', imagePath, imageAlt,
|
|
148
|
+
classNameImage, children, ...props }, ref) => {
|
|
149
|
+
|
|
150
|
+
const [classValue, setClassValue] = useState<string>();
|
|
151
|
+
const [svgClassValue, setSvgClassValue] = useState<string>();
|
|
152
|
+
const [svgSource, setSvgSource] = useState(warning);
|
|
153
|
+
|
|
154
|
+
const onClose = () => {
|
|
155
|
+
setClassValue(twMerge(classValue, 'hidden'));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// set selected value for classValue and svgClassValue
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
|
|
161
|
+
// If dev specifies 'custom' for subVariant, use 'default' classes as the base
|
|
162
|
+
// so we can merge their custom classes later using twMerge
|
|
163
|
+
const tempSubVariant = (subVariant === 'custom') ? 'default' : subVariant;
|
|
164
|
+
|
|
165
|
+
const tempClasses = variants.find(
|
|
166
|
+
(v) => v.variant === variant && v.subVariant === tempSubVariant
|
|
167
|
+
)?.classes;
|
|
168
|
+
|
|
169
|
+
// assign classNameSvg after finding variant & subVariant
|
|
170
|
+
if (variant === 'icon') {
|
|
171
|
+
const tempSvgClasses = variants.find(
|
|
172
|
+
(v) => v.variant === variant && v.subVariant === tempSubVariant
|
|
173
|
+
)?.classNameSvg;
|
|
174
|
+
|
|
175
|
+
if (subVariant === 'custom') {
|
|
176
|
+
setSvgClassValue(twMerge(tempSvgClasses, classNameSvg));
|
|
177
|
+
} else {
|
|
178
|
+
setSvgClassValue(tempSvgClasses);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// set proper icon path if variant warning, hazard, or thumbUp
|
|
183
|
+
if (variant === 'hazard') {
|
|
184
|
+
setSvgSource(emergency);
|
|
185
|
+
} else if (variant === 'go') {
|
|
186
|
+
setSvgSource(thumbUp);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// merge custom classes (dev can set custom classes whether using custom component or not)
|
|
190
|
+
setClassValue(twMerge(tempClasses, className));
|
|
191
|
+
|
|
192
|
+
}, [subVariant, className, variant, classNameSvg]);
|
|
193
|
+
|
|
194
|
+
// console.log(imagePath);
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<>
|
|
198
|
+
{variant === 'default' &&
|
|
199
|
+
<div className={classValue} {...props} ref={ref}>
|
|
200
|
+
{children}
|
|
201
|
+
</div>
|
|
202
|
+
}
|
|
203
|
+
{ (variant === 'warning' || variant === 'hazard' || variant ==='go') &&
|
|
204
|
+
<div className={classValue}>
|
|
205
|
+
<div className="size-6 flex-col justify-center items-center gap-2.5 inline-flex overflow-hidden relative">
|
|
206
|
+
<div className="left-0 top-0 absolute"><img className='size-6' src={svgSource} alt={`${variant} icon`} /></div>
|
|
207
|
+
</div>
|
|
208
|
+
<div className="grow shrink basis-0 self-stretch text-black text-sm font-normal font-['Arial'] leading-[19px]">{children}</div>
|
|
209
|
+
<button
|
|
210
|
+
// ref={closeButtonRef}
|
|
211
|
+
onClick={onClose} // adds 'hidden' to class list for parent div
|
|
212
|
+
className="size-3 py-px justify-center items-center gap-2.5 flex"
|
|
213
|
+
aria-label="Close modal"
|
|
214
|
+
>
|
|
215
|
+
<img src={closeIcon} alt='close alert icon' />
|
|
216
|
+
</button>
|
|
217
|
+
</div>
|
|
218
|
+
}
|
|
219
|
+
{variant === 'icon' &&
|
|
220
|
+
<div className={classValue} {...props} ref={ref}>
|
|
221
|
+
<svg viewBox="0 0 6 6" aria-hidden="true" className={svgClassValue} role="img">
|
|
222
|
+
<circle r={3} cx={3} cy={3} />
|
|
223
|
+
</svg>
|
|
224
|
+
{children}
|
|
225
|
+
</div>
|
|
226
|
+
}
|
|
227
|
+
{variant === 'media' &&
|
|
228
|
+
<div className={classValue} {...props} ref={ref}>
|
|
229
|
+
<div className="col-start-1 col-span-2">
|
|
230
|
+
<img className={twMerge('h-full',classNameImage)} src={imagePath} alt={imageAlt} />
|
|
231
|
+
</div>
|
|
232
|
+
<div className="w-full ml-2 col-start-3 col-span-3">
|
|
233
|
+
{children}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
}
|
|
237
|
+
</>
|
|
238
|
+
);
|
|
239
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Meta, StoryContext } from '@storybook/react';
|
|
2
|
+
import { SideBarNav, SideBarNavProps } from './SideBarNav';
|
|
3
|
+
// import List from './List';
|
|
4
|
+
import { Link } from 'react-router-dom';
|
|
5
|
+
|
|
6
|
+
import hospital from '../assets/img/hospital.jpg';
|
|
7
|
+
import home from '../assets/img/home.svg';
|
|
8
|
+
import pill from '../assets/img/pill.svg';
|
|
9
|
+
import prescription from '../assets/img/prescription.svg';
|
|
10
|
+
import { userEvent, waitFor, within } from 'storybook/test';
|
|
11
|
+
import { expect } from 'storybook/test';
|
|
12
|
+
import { useState } from 'react';
|
|
13
|
+
|
|
14
|
+
const imagePath = 'src/assets/img/hospital.jpg';
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
className?: string;
|
|
18
|
+
variant?: string;
|
|
19
|
+
image?: ReactNode;
|
|
20
|
+
classNameImage?: string;
|
|
21
|
+
version: string;
|
|
22
|
+
*/
|
|
23
|
+
const menuItems = [
|
|
24
|
+
{
|
|
25
|
+
children: <>
|
|
26
|
+
<img className='max-h-[2em] max-w-[2em] pr-1 inline' src={pill} alt='home link icon' />
|
|
27
|
+
<p className='inline'>TEST Link 1</p>
|
|
28
|
+
{/* <Link to='https://google.com' target='_blank'>Link 1</Link> */}
|
|
29
|
+
</>
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
children: <>
|
|
33
|
+
<img className='max-h-[2em] max-w-[2em] pr-1 inline' src={pill} alt='link icon 2' />
|
|
34
|
+
<p className='inline'>Link 2</p>
|
|
35
|
+
{/* <Link to='https://google.com' target='_blank'>Link 2</Link> */}
|
|
36
|
+
</>
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
children: <>
|
|
40
|
+
<img className="max-h-[2em] max-w-[2em] pr-1 inline" src={prescription} alt='link icon 3' />
|
|
41
|
+
<p className='inline'>TEST Link 3</p>
|
|
42
|
+
{/* <Link to='https://google.com' target='_blank'>Link 3</Link> */}
|
|
43
|
+
</>
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
// A wrapper that lets you unmount the SideBarNav component, used in the test to cover line 40
|
|
48
|
+
const UnmountWrapper = (props: SideBarNavProps) => {
|
|
49
|
+
const [mounted, setMounted] = useState(true);
|
|
50
|
+
return (
|
|
51
|
+
<div>
|
|
52
|
+
{mounted && <SideBarNav {...props} />}
|
|
53
|
+
<button style={{ marginTop: '1rem' }} onClick={() => setMounted(false)}>
|
|
54
|
+
Unmount
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
// Meta object - defines basic storybook options for this story
|
|
62
|
+
export default {
|
|
63
|
+
title: 'Components/SideBarNav',
|
|
64
|
+
component: SideBarNav,
|
|
65
|
+
argTypes: {
|
|
66
|
+
// variant: {
|
|
67
|
+
// control: 'select',
|
|
68
|
+
// options: ['','']
|
|
69
|
+
// },
|
|
70
|
+
},
|
|
71
|
+
args: {
|
|
72
|
+
image: hospital,
|
|
73
|
+
menuItems: menuItems,
|
|
74
|
+
// label: 'Button', // set default argument values
|
|
75
|
+
},
|
|
76
|
+
parameters: {
|
|
77
|
+
layout: 'centered', // options are 'centered', 'fullscreen', and 'padded' (default value)
|
|
78
|
+
backgrounds: { default: 'dark' }, // options are light, medium, or dark
|
|
79
|
+
},
|
|
80
|
+
} as Meta<SideBarNavProps>;
|
|
81
|
+
|
|
82
|
+
// Define "Default" story
|
|
83
|
+
export const Default = {
|
|
84
|
+
args: {
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Story to test the hamburger menu, toggle behavior, and click-outside close
|
|
90
|
+
export const HamburgerMenuToggle = {
|
|
91
|
+
args: {
|
|
92
|
+
menu: true,
|
|
93
|
+
clickOutsideCloses: true
|
|
94
|
+
},
|
|
95
|
+
play: async ({ canvasElement }: StoryContext) => {
|
|
96
|
+
const canvas = within(canvasElement);
|
|
97
|
+
// Find the hamburger menu button (rendered only when menu=true)
|
|
98
|
+
const button = await canvas.findByTestId('hamburgerMenu');
|
|
99
|
+
// Click the button to toggle (open) the sidebar
|
|
100
|
+
await userEvent.click(button);
|
|
101
|
+
|
|
102
|
+
// Wait for the sidebar to slide in (i.e. have class 'translate-x-0')
|
|
103
|
+
await waitFor(() => {
|
|
104
|
+
// Query for the sidebar element by its unique width class
|
|
105
|
+
const sidebar = canvasElement.querySelector('div.w-56');
|
|
106
|
+
expect(sidebar?.className).toContain('translate-x-0');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Simulate a click outside the sidebar to trigger clickOutsideCloses logic
|
|
110
|
+
await userEvent.click(document.body);
|
|
111
|
+
await waitFor(() => {
|
|
112
|
+
const sidebar = canvasElement.querySelector('div.w-56');
|
|
113
|
+
expect(sidebar?.className).not.toContain('translate-x-0');
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
// Story to cover the unmount (cleanup) logic (line 40 of the component)
|
|
120
|
+
export const UnmountCleanup = {
|
|
121
|
+
render: (args: SideBarNavProps) => <UnmountWrapper {...args} menu={true} clickOutsideCloses={true} />,
|
|
122
|
+
play: async ({ canvasElement }: StoryContext) => {
|
|
123
|
+
const canvas = within(canvasElement);
|
|
124
|
+
|
|
125
|
+
// Open the sidebar by clicking the hamburger button
|
|
126
|
+
const toggleButton = await canvas.findByRole('button', { name: /menu/i }).catch(() => canvas.getAllByRole('button')[0]);
|
|
127
|
+
await userEvent.click(toggleButton);
|
|
128
|
+
|
|
129
|
+
// Click the "Unmount" button to remove the component (which triggers cleanup)
|
|
130
|
+
const unmountButton = await canvas.findByRole('button', { name: /unmount/i });
|
|
131
|
+
await userEvent.click(unmountButton);
|
|
132
|
+
|
|
133
|
+
// Optionally, click outside and verify no error occurs (since the listener is removed)
|
|
134
|
+
await userEvent.click(document.body);
|
|
135
|
+
}
|
|
136
|
+
};
|