@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,178 @@
|
|
|
1
|
+
// SideBarNav.test.tsx
|
|
2
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import { SideBarNav, SideBarNavProps } from "./SideBarNav";
|
|
4
|
+
import { MemoryRouter } from "react-router-dom";
|
|
5
|
+
import { axe } from "vitest-axe";
|
|
6
|
+
|
|
7
|
+
describe("SideBarNav Component", () => {
|
|
8
|
+
const defaultProps: SideBarNavProps = {
|
|
9
|
+
appName: "Test App",
|
|
10
|
+
version: "v1.0",
|
|
11
|
+
image: "test-image.png",
|
|
12
|
+
menuItems: [
|
|
13
|
+
{ target: "/home", children: "Home" },
|
|
14
|
+
{ target: "/about", children: "About" },
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
it("renders the component with image, app name, version, and menu items", () => {
|
|
19
|
+
render(
|
|
20
|
+
<MemoryRouter>
|
|
21
|
+
<SideBarNav {...defaultProps} />
|
|
22
|
+
</MemoryRouter>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// Check the image element
|
|
26
|
+
const image = screen.getByAltText("application icon");
|
|
27
|
+
expect(image).toBeInTheDocument();
|
|
28
|
+
expect(image).toHaveAttribute("src", "test-image.png");
|
|
29
|
+
|
|
30
|
+
// Check the app name
|
|
31
|
+
expect(screen.getByText("Test App")).toBeInTheDocument();
|
|
32
|
+
|
|
33
|
+
// Check the menu items rendered as NavLinks
|
|
34
|
+
const homeLink = screen.getByRole("link", { name: "Home" });
|
|
35
|
+
expect(homeLink).toBeInTheDocument();
|
|
36
|
+
expect(homeLink).toHaveAttribute("href", "/home");
|
|
37
|
+
|
|
38
|
+
const aboutLink = screen.getByRole("link", { name: "About" });
|
|
39
|
+
expect(aboutLink).toBeInTheDocument();
|
|
40
|
+
expect(aboutLink).toHaveAttribute("href", "/about");
|
|
41
|
+
|
|
42
|
+
// Check the version text
|
|
43
|
+
expect(screen.getByText("Version v1.0")).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("merges custom class names with default classes", () => {
|
|
47
|
+
const props: SideBarNavProps = {
|
|
48
|
+
appName: "Test App",
|
|
49
|
+
version: "v1.0",
|
|
50
|
+
menuItems: [],
|
|
51
|
+
className: "custom-sidebar-class",
|
|
52
|
+
image: "test-image.png",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const { container } = render(
|
|
56
|
+
<MemoryRouter>
|
|
57
|
+
<SideBarNav {...props} />
|
|
58
|
+
</MemoryRouter>
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// The outer div should contain both the default and custom classes.
|
|
62
|
+
const containerDiv = container.firstElementChild;
|
|
63
|
+
expect(containerDiv).toHaveClass("custom-sidebar-class");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("renders an empty menu list when no menu items are provided", () => {
|
|
67
|
+
const props: SideBarNavProps = {
|
|
68
|
+
appName: "Test App",
|
|
69
|
+
version: "v1.0",
|
|
70
|
+
menuItems: [],
|
|
71
|
+
image: "test-image.png",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
render(
|
|
75
|
+
<MemoryRouter>
|
|
76
|
+
<SideBarNav {...props} />
|
|
77
|
+
</MemoryRouter>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Since menuItems is empty, no NavLink should be rendered.
|
|
81
|
+
const links = screen.queryAllByRole("link");
|
|
82
|
+
expect(links.length).toBe(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("does not render a NavLink if a menu item lacks a target", () => {
|
|
86
|
+
const props: SideBarNavProps = {
|
|
87
|
+
appName: "Test App",
|
|
88
|
+
version: "v1.0",
|
|
89
|
+
menuItems: [
|
|
90
|
+
// Intentionally leaving out the 'target' property
|
|
91
|
+
{ children: "No Link" } as any,
|
|
92
|
+
],
|
|
93
|
+
image: "test-image.png",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
render(
|
|
97
|
+
<MemoryRouter>
|
|
98
|
+
<SideBarNav {...props} />
|
|
99
|
+
</MemoryRouter>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// There should be no link since the condition checks for item.target.
|
|
103
|
+
const link = screen.queryByRole("link");
|
|
104
|
+
expect(link).toBeNull();
|
|
105
|
+
// The text "No Link" should not be rendered since it’s inside the conditional.
|
|
106
|
+
expect(screen.queryByText("No Link")).not.toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("closes the menu when clicking outside if clickOutsideCloses is true", () => {
|
|
110
|
+
const props: SideBarNavProps = {
|
|
111
|
+
appName: "Test App",
|
|
112
|
+
version: "v1.0",
|
|
113
|
+
menu: true,
|
|
114
|
+
clickOutsideCloses: true,
|
|
115
|
+
menuItems: [{ target: "/home", children: "Home" }],
|
|
116
|
+
image: "test-image.png",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const { container } = render(
|
|
120
|
+
<MemoryRouter>
|
|
121
|
+
<SideBarNav {...props} />
|
|
122
|
+
</MemoryRouter>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Open the menu by clicking the hamburger button
|
|
126
|
+
const button = screen.getByTestId("hamburgerMenu");
|
|
127
|
+
fireEvent.click(button);
|
|
128
|
+
|
|
129
|
+
// Simulate clicking outside the sidebar (on document body)
|
|
130
|
+
fireEvent.mouseDown(document.body);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("toggles the sidebar when the hamburger button is clicked, displays sidebar on the right", () => {
|
|
134
|
+
const props: SideBarNavProps = {
|
|
135
|
+
appName: "Test App",
|
|
136
|
+
version: "v1.0",
|
|
137
|
+
menu: true,
|
|
138
|
+
menuItems: [{ target: "/home", children: "Home" }],
|
|
139
|
+
image: "test-image.png",
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// rendering this with the 'right' flag satisfies full coverage (no specific lines specified as uncovered)
|
|
143
|
+
// however, % Funcs still scores a 75 -- something to look into
|
|
144
|
+
const { container } = render(
|
|
145
|
+
<MemoryRouter>
|
|
146
|
+
<SideBarNav right {...props} />
|
|
147
|
+
</MemoryRouter>
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const button = screen.getByTestId("hamburgerMenu");
|
|
151
|
+
|
|
152
|
+
// Click the button to toggle the menu open
|
|
153
|
+
fireEvent.click(button);
|
|
154
|
+
|
|
155
|
+
// Click again to toggle it closed
|
|
156
|
+
fireEvent.click(button);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("SideBarNav Accessibility Tests", () => {
|
|
161
|
+
it("has no accessibility violations", async () => {
|
|
162
|
+
const props: SideBarNavProps = {
|
|
163
|
+
appName: "Accessible App",
|
|
164
|
+
version: "v2.0",
|
|
165
|
+
image: "accessible-image.png",
|
|
166
|
+
menuItems: [{ target: "/dashboard", children: "Dashboard" }],
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const { container } = render(
|
|
170
|
+
<MemoryRouter>
|
|
171
|
+
<SideBarNav {...props} />
|
|
172
|
+
</MemoryRouter>
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const results = await axe(container);
|
|
176
|
+
expect(results).toHaveNoViolations();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { forwardRef, useState, useEffect, useRef } from 'react';
|
|
3
|
+
import { NavLink } from 'react-router-dom';
|
|
4
|
+
import { List, ListItemProps, ListItem } from './List';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import backArrowRight from '../assets/img/backArrowRight.svg';
|
|
7
|
+
|
|
8
|
+
export interface SideBarNavProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
className?: string;
|
|
10
|
+
image: string;
|
|
11
|
+
classNameImage?: string;
|
|
12
|
+
classNameImageContainer?: string;
|
|
13
|
+
clickOutsideCloses?: boolean
|
|
14
|
+
menu?: boolean; // true - use integrated hamburger menu, false - react to customer event
|
|
15
|
+
classNameMenu?: string;
|
|
16
|
+
classNameMenuContainer?: string;
|
|
17
|
+
classNameMenuItem?: string;
|
|
18
|
+
version: string;
|
|
19
|
+
appName: string;
|
|
20
|
+
menuItems: ListItemProps[];
|
|
21
|
+
right?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const defaultItemClass = 'pb-4';
|
|
25
|
+
|
|
26
|
+
export const SideBarNav = forwardRef<HTMLDivElement, SideBarNavProps>(
|
|
27
|
+
({ className, appName, image, classNameImage = '', clickOutsideCloses,
|
|
28
|
+
classNameImageContainer = '', menu = false, classNameMenu = '',
|
|
29
|
+
classNameMenuItem = '',
|
|
30
|
+
classNameMenuContainer = '', version, menuItems, right=false,
|
|
31
|
+
children, ...props }, ref) => {
|
|
32
|
+
const sidebarRef = useRef<HTMLDivElement>(null);
|
|
33
|
+
|
|
34
|
+
const [showMenu, setShowMenu] = useState<boolean>(false);
|
|
35
|
+
const [itemClass, setItemClass] = useState<string>(defaultItemClass);
|
|
36
|
+
|
|
37
|
+
// build css class list for individual menu items (list items)
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (right)
|
|
40
|
+
setItemClass(twMerge(defaultItemClass + ' text-right pe-2', classNameMenuItem));
|
|
41
|
+
else
|
|
42
|
+
setItemClass(twMerge(defaultItemClass, classNameMenuItem));
|
|
43
|
+
}, [right, itemClass, classNameMenuItem]);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (clickOutsideCloses) {
|
|
47
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
48
|
+
if (sidebarRef.current && !sidebarRef.current.contains(event.target as Node)) {
|
|
49
|
+
setShowMenu(false);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// attach event listener
|
|
54
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
// cleanup listener on unmount
|
|
58
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}, [setShowMenu]);
|
|
63
|
+
|
|
64
|
+
// console.log('menu: ', menu);
|
|
65
|
+
// console.log('showMenu: ', showMenu);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
69
|
+
<>
|
|
70
|
+
{/* Hamburger Menu - display? */}
|
|
71
|
+
{ menu &&
|
|
72
|
+
<button aria-label='navigation' data-testid="hamburgerMenu" className={twMerge('size-8 mb-4', classNameMenuContainer)} onClick={() => setShowMenu(!showMenu)}>
|
|
73
|
+
<Menu classes={twMerge('fill-slate-500 hover:fill-black size-8', classNameMenu)} />
|
|
74
|
+
</button>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
{/* SideBarNav - display? */}
|
|
78
|
+
<div ref={sidebarRef}
|
|
79
|
+
className={twMerge("relative w-56 md:w-[328px] lg:w-[432px] h-screen flex flex-col px-4 pb-4 pt-10 bg-white overflow-y-auto",
|
|
80
|
+
// menu is used but hidden off screen
|
|
81
|
+
menu && 'fixed z-20 right-0 top-0 translate-x-full transition-transform duration-300',
|
|
82
|
+
// menu used and displayed
|
|
83
|
+
menu && showMenu && 'translate-x-0',
|
|
84
|
+
className
|
|
85
|
+
)}>
|
|
86
|
+
<button className={twMerge('-mt-8 w-full flex justify-end')} onClick={() => setShowMenu(!showMenu)}>
|
|
87
|
+
<img className={twMerge('fill-slate-500 hover:fill-black')} src={backArrowRight} alt='back arrow' />
|
|
88
|
+
</button>
|
|
89
|
+
<p className='w-full flex justify-end pr-0.5 -mb-8'>Back</p>
|
|
90
|
+
|
|
91
|
+
{/* Image */}
|
|
92
|
+
|
|
93
|
+
{/* Logo */}
|
|
94
|
+
<div className={twMerge("w-16 h-16 mx-auto", classNameImageContainer)}>
|
|
95
|
+
<img src={image} className={twMerge('border border-[#bbbabc] w-16 h-16 bg-[#eeeeef] ring-1', classNameImage)} alt="application icon" />
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div className="text-center text-xl mt-2">{appName}</div>
|
|
99
|
+
|
|
100
|
+
{/* Separator */}
|
|
101
|
+
<div className="[204px] mt-3 h-[2px] border-[0.5px] border-[#D9D9D9] bg-[#D9D9D9]"></div>
|
|
102
|
+
|
|
103
|
+
{/* Menu List */}
|
|
104
|
+
{/* <div className="mt-6 border-2 border-red-500"> */}
|
|
105
|
+
<List className="pl-4 lg:pl-6 mt-6">
|
|
106
|
+
{menuItems.map((item, index) => (
|
|
107
|
+
<ListItem
|
|
108
|
+
key={index}
|
|
109
|
+
className={itemClass}
|
|
110
|
+
>
|
|
111
|
+
{item.target && (
|
|
112
|
+
<NavLink to={item.target}>{item.children}</NavLink>
|
|
113
|
+
)}
|
|
114
|
+
</ListItem>
|
|
115
|
+
))}
|
|
116
|
+
</List>
|
|
117
|
+
|
|
118
|
+
{/* Version # - parent div must be 'relative' */}
|
|
119
|
+
<div className='align-bottom mt-auto text-right right-2 bottom-1'>Version {version}</div>
|
|
120
|
+
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
</>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
export const Menu = ({ classes='fill-green-600'}, ) => (
|
|
130
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" className={classes} >
|
|
131
|
+
<path d="M0 0h24v24H0z" fill="none"/>
|
|
132
|
+
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
|
|
133
|
+
</svg>
|
|
134
|
+
);
|
|
135
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Meta } from '@storybook/react';
|
|
2
|
+
import { Skeleton, SkeletonProps, SkelProps, SkelCircle, SkelLine, SkelRow, SkelSection } from './Skeleton';
|
|
3
|
+
|
|
4
|
+
// Meta object - defines basic storybook options for this story
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Components/Skeleton',
|
|
7
|
+
component: Skeleton,
|
|
8
|
+
argTypes: {
|
|
9
|
+
variant: {
|
|
10
|
+
control: 'select',
|
|
11
|
+
options: ['default','larger']
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
args: {
|
|
15
|
+
disabled: false, // set default argument values
|
|
16
|
+
children:
|
|
17
|
+
<Skeleton>
|
|
18
|
+
<SkelCircle inline /><SkelLine inline />
|
|
19
|
+
<SkelLine />
|
|
20
|
+
<SkelSection />
|
|
21
|
+
<SkelCircle />
|
|
22
|
+
<SkelRow />
|
|
23
|
+
<SkelLine />
|
|
24
|
+
</Skeleton>
|
|
25
|
+
// label: 'Button', // set default argument values
|
|
26
|
+
},
|
|
27
|
+
parameters: {
|
|
28
|
+
layout: 'centered', // options are 'centered', 'fullscreen', and 'padded' (default value)
|
|
29
|
+
backgrounds: { default: 'dark' }, // options are light, medium, or dark
|
|
30
|
+
},
|
|
31
|
+
} as Meta<SkeletonProps>;
|
|
32
|
+
|
|
33
|
+
// Define "Default" story
|
|
34
|
+
export const Default = {
|
|
35
|
+
args: {
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const AlternateBaseColor = {
|
|
41
|
+
args: {
|
|
42
|
+
children:
|
|
43
|
+
<Skeleton>
|
|
44
|
+
<SkelCircle className='bg-blue-500/50' inline /><SkelLine className='bg-blue-500/50' inline />
|
|
45
|
+
<SkelLine className='bg-blue-500/50' />
|
|
46
|
+
<SkelSection className='bg-blue-500/50' />
|
|
47
|
+
</Skeleton>
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
Skeleton, SkeletonProps, SkelProps, SkelCircle, SkelLine, SkelRow, SkelSection
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
// export const ImageRight = {
|
|
56
|
+
// args: {
|
|
57
|
+
// variant: 'imageRight',
|
|
58
|
+
// }
|
|
59
|
+
// };
|
|
60
|
+
|
|
61
|
+
// Define "DHA Med Card" story that extends "Default"
|
|
62
|
+
// export const DHAMedCard = {
|
|
63
|
+
// args: {
|
|
64
|
+
// children: 'DHA Med Card',
|
|
65
|
+
// variant: 'MedCard',
|
|
66
|
+
// }
|
|
67
|
+
// };
|
|
68
|
+
|
|
69
|
+
// Define "DHA Disabled" story
|
|
70
|
+
// export const DHADisabled = {
|
|
71
|
+
// args: {
|
|
72
|
+
// // children: 'Disabled Button',
|
|
73
|
+
// disabled: true,
|
|
74
|
+
// label: 'Disabled Button',
|
|
75
|
+
// }
|
|
76
|
+
// };
|
|
77
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.skelContainer {
|
|
2
|
+
border: 3px solid green;
|
|
3
|
+
background-color: blue;
|
|
4
|
+
width: 100%;
|
|
5
|
+
color: red;
|
|
6
|
+
/* background: linear-gradient(-45deg, #eee 40%, #fafafa 50%, #eee 60%);
|
|
7
|
+
background-size: 300%;
|
|
8
|
+
background-position-x: 100%;
|
|
9
|
+
animation: shimmer 1s infinite linear; */
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@keyframes shimmer {
|
|
13
|
+
to {
|
|
14
|
+
background-position: 0%;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
import { axe } from "vitest-axe";
|
|
4
|
+
import { Skeleton, SkelCircle, SkelLine, SkelSection, SkelRow } from "./Skeleton";
|
|
5
|
+
|
|
6
|
+
describe("Skeleton Component", () => {
|
|
7
|
+
it("renders the Skeleton component with default props", () => {
|
|
8
|
+
render(<Skeleton />);
|
|
9
|
+
const skeleton = screen.getByLabelText("Skeleton Component");
|
|
10
|
+
expect(skeleton).toBeInTheDocument();
|
|
11
|
+
expect(skeleton).toHaveClass("flex flex-row flex-wrap animate-pulse h-48 w-56 pt-2");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("renders the Skeleton component with custom className", () => {
|
|
15
|
+
render(<Skeleton className="custom-class" />);
|
|
16
|
+
const skeleton = screen.getByLabelText("Skeleton Component");
|
|
17
|
+
expect(skeleton).toHaveClass("custom-class");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("renders the Skeleton component with a custom variant", () => {
|
|
21
|
+
const classNames = "h-32 w-32";
|
|
22
|
+
render(<Skeleton className={classNames} />);
|
|
23
|
+
const skeleton = screen.getByLabelText("Skeleton Component");
|
|
24
|
+
expect(skeleton).toHaveClass("h-32 w-32");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("passes accessibility checks", async () => {
|
|
28
|
+
const { container } = render(<Skeleton />);
|
|
29
|
+
const results = await axe(container);
|
|
30
|
+
expect(results).toHaveNoViolations();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("Skeleton Subcomponents", () => {
|
|
35
|
+
it("renders SkelCircle in inline mode", () => {
|
|
36
|
+
render(<SkelCircle inline />);
|
|
37
|
+
const skelCircle = screen.getByRole("presentation", { hidden: true });
|
|
38
|
+
expect(skelCircle).toHaveClass("rounded-full bg-slate-300 h-6 w-6 inline-block mx-2 me-1 my-1");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("renders SkelCircle in non-inline mode", () => {
|
|
42
|
+
render(<SkelCircle />);
|
|
43
|
+
const skelCircle = screen.getByRole("presentation", { hidden: true });
|
|
44
|
+
expect(skelCircle).toHaveClass("rounded-full bg-slate-300 h-6 w-6 mx-2 my-1 me-0");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("renders SkelLine in inline mode", () => {
|
|
48
|
+
render(<SkelLine inline />);
|
|
49
|
+
const skelLine = screen.getByRole("presentation", { hidden: true });
|
|
50
|
+
expect(skelLine).toHaveClass("rounded-lg bg-slate-300 h-6 inline-block mx-2 my-1 grow");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("renders SkelLine in non-inline mode", () => {
|
|
54
|
+
render(<SkelLine />);
|
|
55
|
+
const skelLine = screen.getByRole("presentation", { hidden: true });
|
|
56
|
+
expect(skelLine).toHaveClass("rounded-lg bg-slate-300 h-6 mx-2 my-1 grow");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("renders SkelSection with default props", () => {
|
|
60
|
+
render(<SkelSection />);
|
|
61
|
+
const skelSection = screen.getByRole("presentation", { hidden: true });
|
|
62
|
+
expect(skelSection).toHaveClass("rounded-lg bg-slate-300 grow h-24 my-1 mx-2");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("renders SkelRow with default props", () => {
|
|
66
|
+
render(<SkelRow />);
|
|
67
|
+
const skelRow = screen.getByRole("presentation", { hidden: true });
|
|
68
|
+
expect(skelRow).toHaveClass("basis-full h-0");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("passes accessibility checks for all subcomponents", async () => {
|
|
72
|
+
const { container } = render(
|
|
73
|
+
<div>
|
|
74
|
+
<SkelCircle />
|
|
75
|
+
<SkelLine />
|
|
76
|
+
<SkelSection />
|
|
77
|
+
<SkelRow />
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
const results = await axe(container);
|
|
81
|
+
expect(results).toHaveNoViolations();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { FC } from 'react';
|
|
3
|
+
import { twMerge } from 'tailwind-merge';
|
|
4
|
+
|
|
5
|
+
interface VariantType {
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// variant styling definition, both size and layout (portrait vs landscape)
|
|
10
|
+
const variants: VariantType = {
|
|
11
|
+
default: ' h-48 w-56 pt-2',
|
|
12
|
+
custom: ' h-48 w-56 pt-2',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
16
|
+
variant?: string; //
|
|
17
|
+
className?: string; // container classes
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(
|
|
21
|
+
({ className, variant = 'default', children, ...props }, ref) => {
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className={
|
|
25
|
+
twMerge('flex flex-row flex-wrap animate-pulse',
|
|
26
|
+
variants[variant], className)}
|
|
27
|
+
aria-label='Skeleton Component'
|
|
28
|
+
role='status'
|
|
29
|
+
ref={ref}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
Skeleton.displayName = 'Card';
|
|
38
|
+
|
|
39
|
+
export interface SkelProps {
|
|
40
|
+
className?: string;
|
|
41
|
+
inline?: boolean; // inline (i.e. span) or not (i.e. div?)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// SkelCircle
|
|
45
|
+
export const SkelCircle: FC<SkelProps> = ({className = '', inline = false}) => {
|
|
46
|
+
return inline ? (
|
|
47
|
+
<div
|
|
48
|
+
role="presentation"
|
|
49
|
+
className={twMerge(
|
|
50
|
+
"rounded-full bg-slate-300 h-6 w-6 inline-block mx-2 me-1 my-1", className
|
|
51
|
+
)}
|
|
52
|
+
/>
|
|
53
|
+
) : (
|
|
54
|
+
<>
|
|
55
|
+
<div className="basis-full h-0"></div>
|
|
56
|
+
<div
|
|
57
|
+
role="presentation"
|
|
58
|
+
className={twMerge(
|
|
59
|
+
"rounded-full bg-slate-300 h-6 w-6 mx-2 my-1 me-0", className
|
|
60
|
+
)}
|
|
61
|
+
/>
|
|
62
|
+
<div className="basis-full h-0"></div>
|
|
63
|
+
</>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// SkeletonLine
|
|
68
|
+
export const SkelLine: FC<SkelProps> = ({className='', inline = false}) => {
|
|
69
|
+
return inline ? (
|
|
70
|
+
<div
|
|
71
|
+
role="presentation"
|
|
72
|
+
className={twMerge(
|
|
73
|
+
"rounded-lg bg-slate-300 h-6 inline-block mx-2 my-1 grow", className
|
|
74
|
+
)}
|
|
75
|
+
></div>
|
|
76
|
+
) : (
|
|
77
|
+
<>
|
|
78
|
+
<div className="basis-full h-0"></div>
|
|
79
|
+
<div
|
|
80
|
+
role="presentation"
|
|
81
|
+
className={twMerge(
|
|
82
|
+
"rounded-lg bg-slate-300 h-6 mx-2 my-1 grow", className
|
|
83
|
+
)}
|
|
84
|
+
></div>
|
|
85
|
+
<div className="basis-full h-0"></div>
|
|
86
|
+
</>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// SkelSection
|
|
91
|
+
export const SkelSection: FC<SkelProps> = ({className=''}) => {
|
|
92
|
+
return (
|
|
93
|
+
<div role="presentation" className={twMerge("rounded-lg bg-slate-300 grow h-24 my-1 mx-2", className)} />
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
// SkelRow
|
|
99
|
+
export const SkelRow: FC<SkelProps> = ({className = ''}) => {
|
|
100
|
+
return (
|
|
101
|
+
<div role="presentation" className="basis-full h-0"></div>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import { axe } from "vitest-axe";
|
|
5
|
+
import { SkipLink } from "./SkipLink";
|
|
6
|
+
|
|
7
|
+
describe("SkipLink Component", () => {
|
|
8
|
+
it("renders the child element and applies the default className", () => {
|
|
9
|
+
const child = <a href="#main">Skip Link</a>;
|
|
10
|
+
render(<SkipLink>{child}</SkipLink>);
|
|
11
|
+
const linkElement = screen.getByRole("link", { name: /skip link/i });
|
|
12
|
+
expect(linkElement).toBeInTheDocument();
|
|
13
|
+
// Default className should be applied to the child element
|
|
14
|
+
expect(linkElement).toHaveClass("skip-link");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("handles click events by focusing the target container and cleaning up tabindex", () => {
|
|
18
|
+
// Create a container element that matches the skipTo query
|
|
19
|
+
const container = document.createElement("main");
|
|
20
|
+
container.id = "main";
|
|
21
|
+
document.body.appendChild(container);
|
|
22
|
+
// Spy on the container's focus method
|
|
23
|
+
const focusSpy = vi.spyOn(container, "focus");
|
|
24
|
+
|
|
25
|
+
// Use fake timers to test the timeout behavior
|
|
26
|
+
vi.useFakeTimers();
|
|
27
|
+
const child = <a href="#main">Skip to main</a>;
|
|
28
|
+
// Pass a skipTo prop that matches our container
|
|
29
|
+
render(<SkipLink skipTo="#main">{child}</SkipLink>);
|
|
30
|
+
const linkElement = screen.getByRole("link", { name: /skip to main/i });
|
|
31
|
+
|
|
32
|
+
// Simulate click on the skip link
|
|
33
|
+
fireEvent.click(linkElement);
|
|
34
|
+
|
|
35
|
+
// After clicking, the container should have tabindex set to -1 and focus called
|
|
36
|
+
expect(container.tabIndex).toBe(-1);
|
|
37
|
+
expect(focusSpy).toHaveBeenCalled();
|
|
38
|
+
|
|
39
|
+
// Fast-forward time to trigger removal of tabindex (after 1000ms)
|
|
40
|
+
vi.advanceTimersByTime(1000);
|
|
41
|
+
expect(container.getAttribute("tabindex")).toBeNull();
|
|
42
|
+
|
|
43
|
+
// Clean up
|
|
44
|
+
vi.useRealTimers();
|
|
45
|
+
document.body.removeChild(container);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// What is the purpose of the timer in SkipLink?
|
|
49
|
+
// it("cleans up the timer on unmount", () => {
|
|
50
|
+
// // Spy on clearTimeout to ensure it is called during cleanup
|
|
51
|
+
// const clearTimeoutSpy = vi.spyOn(global, "clearTimeout");
|
|
52
|
+
// vi.useFakeTimers();
|
|
53
|
+
|
|
54
|
+
// const child = <a href="#main">Skip Link</a>;
|
|
55
|
+
// const { unmount } = render(<SkipLink skipTo="#main">{child}</SkipLink>);
|
|
56
|
+
// const linkElement = screen.getByRole("link", { name: /skip link/i });
|
|
57
|
+
|
|
58
|
+
// // Fire the click to create a timer
|
|
59
|
+
// fireEvent.click(linkElement);
|
|
60
|
+
|
|
61
|
+
// // Unmount the component which should trigger the cleanup useEffect
|
|
62
|
+
// unmount();
|
|
63
|
+
|
|
64
|
+
// expect(clearTimeoutSpy).toHaveBeenCalled();
|
|
65
|
+
|
|
66
|
+
// vi.useRealTimers();
|
|
67
|
+
// clearTimeoutSpy.mockRestore();
|
|
68
|
+
// });
|
|
69
|
+
|
|
70
|
+
it("has no accessibility violations", async () => {
|
|
71
|
+
const child = <a href="#main">Skip Link</a>;
|
|
72
|
+
const { container } = render(<SkipLink skipTo="#main">{child}</SkipLink>);
|
|
73
|
+
const results = await axe(container);
|
|
74
|
+
expect(results).toHaveNoViolations();
|
|
75
|
+
});
|
|
76
|
+
});
|