@hyphen/hyphen-components 7.3.2 → 7.3.4
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/dist/css/utilities.css +1 -1
- package/dist/css/variables.css +18 -28
- package/dist/hyphen-components.cjs.development.js +5873 -5019
- package/dist/hyphen-components.cjs.development.js.map +1 -1
- package/dist/hyphen-components.cjs.production.min.js +18 -2
- package/dist/hyphen-components.cjs.production.min.js.map +1 -1
- package/dist/hyphen-components.esm.js +5731 -4844
- package/dist/hyphen-components.esm.js.map +1 -1
- package/dist/index.d.ts +2799 -57
- package/dist/index.js +0 -1
- package/package.json +18 -19
- package/src/components/Badge/Badge.module.scss +6 -0
- package/src/components/Badge/Badge.stories.tsx +1 -0
- package/src/components/Badge/Badge.test.tsx +3 -2
- package/src/components/Badge/Badge.tsx +5 -3
- package/src/components/Box/Box.tsx +5 -2
- package/src/components/Button/Button.module.scss +1 -1
- package/src/components/Button/Button.test.tsx +2 -2
- package/src/components/Calendar/Calendar.test.tsx +262 -0
- package/src/components/Card/Card.tsx +2 -0
- package/src/components/CheckboxInput/components/Checkbox.module.scss +1 -1
- package/src/components/CheckboxInput/components/Checkbox.tsx +2 -0
- package/src/components/Details/Details.module.scss +2 -2
- package/src/components/Details/Details.tsx +2 -0
- package/src/components/Drawer/Drawer.stories.tsx +1 -1
- package/src/components/Drawer/Drawer.test.tsx +494 -56
- package/src/components/Drawer/Drawer.tsx +7 -1
- package/src/components/DropdownMenu/DropdownMenu.test.tsx +532 -12
- package/src/components/FormControl/FormControl.tsx +2 -0
- package/src/components/Formik/Formik.stories.tsx +30 -7
- package/src/components/Formik/FormikSelectInput/FormikSelectInput.tsx +6 -5
- package/src/components/Formik/FormikToggleGroup/FormikToggleGroup.tsx +1 -1
- package/src/components/HelpText/HelpText.tsx +2 -0
- package/src/components/Icon/Icon.stories.tsx +1 -1
- package/src/components/Icon/Icon.tsx +2 -0
- package/src/components/Modal/Modal.test.tsx +630 -81
- package/src/components/Modal/Modal.tsx +2 -0
- package/src/components/Modal/components/ModalFooter/ModalFooter.test.tsx +2 -2
- package/src/components/Popover/Popover.tsx +2 -0
- package/src/components/RadioGroup/RadioInput/RadioInput.tsx +2 -0
- package/src/components/SelectInput/SelectInput.stories.tsx +22 -22
- package/src/components/SelectInput/SelectInput.tsx +13 -9
- package/src/components/SelectInputInset/SelectInputInset.tsx +2 -0
- package/src/components/Sidebar/Sidebar.module.scss +4 -0
- package/src/components/Sidebar/Sidebar.stories.tsx +8 -4
- package/src/components/Sidebar/Sidebar.test.tsx +7 -4
- package/src/components/Sidebar/Sidebar.tsx +7 -4
- package/src/components/Table/Table.stories.tsx +102 -52
- package/src/components/TextInput/TextInput.tsx +2 -0
- package/src/components/TextInputInset/TextInputInset.tsx +2 -0
- package/src/components/TextareaInputInset/TextareaInputInset.tsx +2 -0
- package/src/components/TimePickerNative/TimePickerNative.stories.tsx +0 -1
- package/src/components/Toast/Toast.store.ts +1 -1
- package/src/components/Toast/Toast.stories.tsx +3 -2
- package/src/components/Toast/Toast.test.tsx +8 -6
- package/src/components/Toggle/Toggle.tsx +2 -0
- package/src/components/ToggleGroup/ToggleGroup.tsx +2 -0
- package/src/docs/Colors.mdx +0 -13
- package/src/index.ts +2 -0
- package/src/lib/getColumnKeys.ts +3 -3
- package/src/lib/mergeRefs.ts +1 -1
- package/src/lib/tokens.ts +4 -4
- package/dist/components/Alert/Alert.constants.d.ts +0 -8
- package/dist/components/Alert/Alert.d.ts +0 -42
- package/dist/components/Alert/Alert.stories.d.ts +0 -12
- package/dist/components/Alert/Alert.types.d.ts +0 -7
- package/dist/components/AspectRatio/AspectRatio.d.ts +0 -3
- package/dist/components/AspectRatio/AspectRatio.stories.d.ts +0 -6
- package/dist/components/Badge/Badge.d.ts +0 -24
- package/dist/components/Badge/Badge.stories.d.ts +0 -8
- package/dist/components/Box/Box.d.ts +0 -247
- package/dist/components/Box/Box.stories.d.ts +0 -46
- package/dist/components/Button/Button.constants.d.ts +0 -3
- package/dist/components/Button/Button.d.ts +0 -53
- package/dist/components/Button/Button.stories.d.ts +0 -16
- package/dist/components/Calendar/Calendar.d.ts +0 -7
- package/dist/components/Calendar/Calendar.stories.d.ts +0 -12
- package/dist/components/Card/Card.d.ts +0 -17
- package/dist/components/Card/Card.stories.d.ts +0 -8
- package/dist/components/Card/components/CardFooter/CardFooter.d.ts +0 -13
- package/dist/components/Card/components/CardHeader/CardHeader.d.ts +0 -13
- package/dist/components/Card/components/CardSection/CardSection.d.ts +0 -46
- package/dist/components/Card/components/index.d.ts +0 -3
- package/dist/components/CheckboxInput/CheckboxInput.d.ts +0 -72
- package/dist/components/CheckboxInput/CheckboxInput.stories.d.ts +0 -18
- package/dist/components/CheckboxInput/components/Checkbox.d.ts +0 -71
- package/dist/components/CheckboxInput/components/CheckboxIcon.d.ts +0 -27
- package/dist/components/Collapsible/Collapsible.d.ts +0 -5
- package/dist/components/Collapsible/Collapsible.stories.d.ts +0 -9
- package/dist/components/Details/Details.d.ts +0 -15
- package/dist/components/Details/Details.stories.d.ts +0 -6
- package/dist/components/Details/DetailsSummary.d.ts +0 -7
- package/dist/components/Drawer/Drawer.d.ts +0 -105
- package/dist/components/Drawer/Drawer.stories.d.ts +0 -62
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +0 -25
- package/dist/components/DropdownMenu/DropdownMenu.stories.d.ts +0 -9
- package/dist/components/FormControl/FormControl.d.ts +0 -38
- package/dist/components/FormLabel/FormLabel.d.ts +0 -41
- package/dist/components/FormLabel/FormLabel.stories.d.ts +0 -6
- package/dist/components/Formik/Formik.stories.d.ts +0 -18
- package/dist/components/Formik/FormikCheckboxInput/FormikCheckboxInput.d.ts +0 -12
- package/dist/components/Formik/FormikRadioGroup/FormikRadioGroup.d.ts +0 -12
- package/dist/components/Formik/FormikSelectInput/FormikSelectInput.d.ts +0 -13
- package/dist/components/Formik/FormikSelectInputInset/FormikSelectInputInset.d.ts +0 -12
- package/dist/components/Formik/FormikSelectInputNative/FormikSelectInputNative.d.ts +0 -12
- package/dist/components/Formik/FormikSwitch/FormikSwitch.d.ts +0 -12
- package/dist/components/Formik/FormikTextInput/FormikTextInput.d.ts +0 -12
- package/dist/components/Formik/FormikTextInputInset/FormikTextInputInset.d.ts +0 -12
- package/dist/components/Formik/FormikTextareaInput/FormikTextareaInput.d.ts +0 -12
- package/dist/components/Formik/FormikTextareaInputInset/FormikTextareaInputInset.d.ts +0 -12
- package/dist/components/Formik/FormikTimePicker/FormikTimePicker.d.ts +0 -12
- package/dist/components/Formik/FormikTimePickerNative/FormikTimePickerNative.d.ts +0 -12
- package/dist/components/Formik/FormikToggleGroup/FormikToggleGroup.d.ts +0 -20
- package/dist/components/Formik/FormikToggleGroupMulti/FormikToggleGroupMulti.d.ts +0 -18
- package/dist/components/Heading/Heading.constants.d.ts +0 -10
- package/dist/components/Heading/Heading.d.ts +0 -35
- package/dist/components/Heading/Heading.stories.d.ts +0 -9
- package/dist/components/HelpText/HelpText.d.ts +0 -12
- package/dist/components/Icon/Icon.d.ts +0 -22
- package/dist/components/Icon/Icon.stories.d.ts +0 -10
- package/dist/components/InputValidationMessage/InputValidationMessage.d.ts +0 -9
- package/dist/components/Modal/Modal.d.ts +0 -83
- package/dist/components/Modal/Modal.stories.d.ts +0 -13
- package/dist/components/Modal/components/ModalBody/ModalBody.d.ts +0 -4
- package/dist/components/Modal/components/ModalFooter/ModalFooter.d.ts +0 -4
- package/dist/components/Modal/components/ModalHeader/ModalHeader.d.ts +0 -21
- package/dist/components/Modal/components/index.d.ts +0 -4
- package/dist/components/Pagination/Pagination.d.ts +0 -51
- package/dist/components/Pagination/Pagination.stories.d.ts +0 -8
- package/dist/components/Pagination/Pagination.utilities.d.ts +0 -10
- package/dist/components/Popover/Popover.d.ts +0 -8
- package/dist/components/Popover/Popover.stories.d.ts +0 -7
- package/dist/components/RadioGroup/RadioGroup.d.ts +0 -75
- package/dist/components/RadioGroup/RadioGroup.stories.d.ts +0 -16
- package/dist/components/RadioGroup/RadioInput/RadioInput.d.ts +0 -57
- package/dist/components/RadioGroup/RadioInput/RadioInputIcon.d.ts +0 -27
- package/dist/components/RangeInput/RangeInput.d.ts +0 -29
- package/dist/components/RangeInput/RangeInput.stories.d.ts +0 -7
- package/dist/components/ResponsiveProvider/ResponsiveProvider.d.ts +0 -17
- package/dist/components/ResponsiveProvider/ResponsiveProvider.stories.d.ts +0 -7
- package/dist/components/SelectInput/SelectInput.d.ts +0 -148
- package/dist/components/SelectInput/SelectInput.stories.d.ts +0 -24
- package/dist/components/SelectInputInset/SelectInputInset.d.ts +0 -92
- package/dist/components/SelectInputInset/SelectInputInset.stories.d.ts +0 -12
- package/dist/components/SelectInputNative/SelectInputNative.d.ts +0 -45
- package/dist/components/SelectInputNative/SelectInputNative.stories.d.ts +0 -19
- package/dist/components/Sidebar/Sidebar.d.ts +0 -57
- package/dist/components/Sidebar/Sidebar.stories.d.ts +0 -9
- package/dist/components/Spinner/Spinner.d.ts +0 -12
- package/dist/components/Spinner/Spinner.stories.d.ts +0 -8
- package/dist/components/Switch/Switch.d.ts +0 -64
- package/dist/components/Switch/Switch.stories.d.ts +0 -12
- package/dist/components/Table/Table.d.ts +0 -86
- package/dist/components/Table/Table.stories.d.ts +0 -31
- package/dist/components/Table/TableBody/TableBody.d.ts +0 -52
- package/dist/components/Table/TableBody/TableBodyCell/TableBodyCell.d.ts +0 -45
- package/dist/components/Table/TableHead/TableHead.d.ts +0 -46
- package/dist/components/Table/TableHead/TableHeaderCell/TableHeaderCell.d.ts +0 -65
- package/dist/components/Table/common/TableRow/TableRow.d.ts +0 -67
- package/dist/components/TextInput/TextInput.d.ts +0 -106
- package/dist/components/TextInput/TextInput.stories.d.ts +0 -19
- package/dist/components/TextInputInset/TextInputInset.d.ts +0 -102
- package/dist/components/TextInputInset/TextInputInset.stories.d.ts +0 -13
- package/dist/components/TextareaInput/TextareaInput.d.ts +0 -97
- package/dist/components/TextareaInput/TextareaInput.stories.d.ts +0 -23
- package/dist/components/TextareaInputInset/TextareaInputInset.d.ts +0 -105
- package/dist/components/TextareaInputInset/TextareaInputInset.stories.d.ts +0 -12
- package/dist/components/ThemeProvider/ThemeProvider.d.ts +0 -15
- package/dist/components/ThemeProvider/ThemeProvider.stories.d.ts +0 -6
- package/dist/components/TimePicker/TimePicker.d.ts +0 -35
- package/dist/components/TimePicker/TimePicker.stories.d.ts +0 -12
- package/dist/components/TimePickerNative/TimePickerNative.d.ts +0 -39
- package/dist/components/TimePickerNative/TimePickerNative.stories.d.ts +0 -11
- package/dist/components/Toast/Toast.store.d.ts +0 -36
- package/dist/components/Toast/Toast.stories.d.ts +0 -14
- package/dist/components/Toast/Toast.types.d.ts +0 -75
- package/dist/components/Toast/ToastContainer.d.ts +0 -43
- package/dist/components/Toast/ToastNotification.d.ts +0 -28
- package/dist/components/Toast/index.d.ts +0 -4
- package/dist/components/Toast/toast.d.ts +0 -20
- package/dist/components/Toast/useToasts.d.ts +0 -14
- package/dist/components/Toggle/Toggle.d.ts +0 -7
- package/dist/components/Toggle/Toggle.stories.d.ts +0 -11
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +0 -19
- package/dist/components/ToggleGroup/ToggleGroup.stories.d.ts +0 -12
- package/dist/components/Tooltip/Tooltip.d.ts +0 -8
- package/dist/components/Tooltip/Tooltip.stories.d.ts +0 -8
- package/dist/constants/keyCodes.d.ts +0 -2
- package/dist/css/index.css +0 -36
- package/dist/hooks/index.d.ts +0 -6
- package/dist/hooks/useBreakpoint/useBreakpoint.d.ts +0 -9
- package/dist/hooks/useBreakpoint/useBreakpoint.stories.d.ts +0 -6
- package/dist/hooks/useIsMobile/useIsMobile.d.ts +0 -1
- package/dist/hooks/useIsMobile/useIsMobile.stories.d.ts +0 -6
- package/dist/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.d.ts +0 -2
- package/dist/hooks/useOpenClose/useOpenClose.d.ts +0 -39
- package/dist/hooks/useOpenClose/useOpenClose.stories.d.ts +0 -6
- package/dist/hooks/useTheme/useTheme.d.ts +0 -5
- package/dist/hooks/useTheme/useTheme.stories.d.ts +0 -6
- package/dist/hooks/useWindowSize/useWindowSize.d.ts +0 -7
- package/dist/hooks/useWindowSize/useWindowSize.stories.d.ts +0 -6
- package/dist/lib/cssShorthandToClasses.d.ts +0 -4
- package/dist/lib/doesStringIncludeCssUnit.d.ts +0 -1
- package/dist/lib/generateResponsiveClasses.d.ts +0 -2
- package/dist/lib/getAutoCompleteValue.d.ts +0 -1
- package/dist/lib/getColumnKeys.d.ts +0 -3
- package/dist/lib/getDimensionCss.d.ts +0 -12
- package/dist/lib/getElementType.d.ts +0 -14
- package/dist/lib/getFlexCss.d.ts +0 -9
- package/dist/lib/index.d.ts +0 -15
- package/dist/lib/isFunction.d.ts +0 -3
- package/dist/lib/mergeRefs.d.ts +0 -2
- package/dist/lib/prefersReducedMotion.d.ts +0 -1
- package/dist/lib/react-children-utilities/filter.d.ts +0 -3
- package/dist/lib/react-children-utilities/index.d.ts +0 -1
- package/dist/lib/reactRouterClickHandler.d.ts +0 -12
- package/dist/lib/resolveValue.d.ts +0 -3
- package/dist/lib/tokens.d.ts +0 -22
- package/dist/modes.d.ts +0 -8
- package/dist/types/index.d.ts +0 -103
- package/dist/types/lib.types.d.ts +0 -3
|
@@ -1,88 +1,637 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
render,
|
|
4
|
+
screen,
|
|
5
|
+
fireEvent,
|
|
6
|
+
waitFor,
|
|
7
|
+
act,
|
|
8
|
+
} from '@testing-library/react';
|
|
9
|
+
import userEvent from '@testing-library/user-event';
|
|
3
10
|
import { Modal } from './Modal';
|
|
4
11
|
|
|
5
12
|
describe('Modal', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('Basic rendering', () => {
|
|
18
|
+
test('renders its children', () => {
|
|
19
|
+
const { getByText } = render(
|
|
20
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testDefault">
|
|
21
|
+
test modal
|
|
22
|
+
</Modal>
|
|
23
|
+
);
|
|
24
|
+
expect(getByText('test modal')).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('does not render when isOpen is false', () => {
|
|
28
|
+
const { queryByText } = render(
|
|
29
|
+
<Modal isOpen={false} onDismiss={() => {}} ariaLabel="testClosed">
|
|
30
|
+
test modal
|
|
31
|
+
</Modal>
|
|
32
|
+
);
|
|
33
|
+
expect(queryByText('test modal')).not.toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('it open and closes based on isOpen prop', () => {
|
|
37
|
+
const { queryByText, getByText, rerender } = render(
|
|
38
|
+
<Modal isOpen={false} onDismiss={() => {}} ariaLabel="testIsOpen">
|
|
39
|
+
test modal
|
|
40
|
+
</Modal>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(queryByText('test modal')).toBe(null);
|
|
44
|
+
|
|
45
|
+
rerender(
|
|
46
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testIsOpen">
|
|
47
|
+
test modal
|
|
48
|
+
</Modal>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(getByText('test modal')).toBeInTheDocument();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('applies custom className', () => {
|
|
55
|
+
render(
|
|
56
|
+
<Modal
|
|
57
|
+
isOpen
|
|
58
|
+
onDismiss={() => {}}
|
|
59
|
+
ariaLabel="testClassName"
|
|
60
|
+
className="custom-modal"
|
|
61
|
+
>
|
|
62
|
+
content
|
|
63
|
+
</Modal>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const modal = screen.getByRole('dialog');
|
|
67
|
+
expect(modal).toHaveClass('custom-modal');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('Subcomponents', () => {
|
|
72
|
+
test('renders Modal.Header with title', () => {
|
|
73
|
+
const { getByText } = render(
|
|
74
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testSubcomponents">
|
|
75
|
+
<Modal.Header id="titleFooterBody" title="The Modal Title" />
|
|
76
|
+
</Modal>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(getByText('The Modal Title')).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('renders Modal.Header with children', () => {
|
|
83
|
+
render(
|
|
84
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testHeaderChildren">
|
|
85
|
+
<Modal.Header id="header-id">
|
|
86
|
+
<span>Custom header content</span>
|
|
87
|
+
</Modal.Header>
|
|
88
|
+
</Modal>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(screen.getByText('Custom header content')).toBeInTheDocument();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('renders Modal.Header with title and children', () => {
|
|
95
|
+
render(
|
|
96
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testHeaderBoth">
|
|
97
|
+
<Modal.Header id="header-id" title="Main Title">
|
|
98
|
+
<span>Subtitle</span>
|
|
99
|
+
</Modal.Header>
|
|
100
|
+
</Modal>
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(screen.getByText('Main Title')).toBeInTheDocument();
|
|
104
|
+
expect(screen.getByText('Subtitle')).toBeInTheDocument();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('renders Modal.Header with close button when onDismiss is provided', () => {
|
|
108
|
+
const handleDismiss = jest.fn();
|
|
109
|
+
render(
|
|
110
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testCloseButton">
|
|
111
|
+
<Modal.Header
|
|
112
|
+
id="header-id"
|
|
113
|
+
title="Title"
|
|
114
|
+
onDismiss={handleDismiss}
|
|
115
|
+
/>
|
|
116
|
+
</Modal>
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const closeButton = screen.getByLabelText('close');
|
|
120
|
+
expect(closeButton).toBeInTheDocument();
|
|
121
|
+
fireEvent.click(closeButton);
|
|
122
|
+
expect(handleDismiss).toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('renders Modal.Body', () => {
|
|
126
|
+
const { getByText } = render(
|
|
127
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testBody">
|
|
128
|
+
<Modal.Body>Modal body content</Modal.Body>
|
|
129
|
+
</Modal>
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(getByText('Modal body content')).toBeInTheDocument();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('renders Modal.Footer', () => {
|
|
136
|
+
const { getByText } = render(
|
|
137
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testFooter">
|
|
138
|
+
<Modal.Footer>This is content in the modal footer</Modal.Footer>
|
|
139
|
+
</Modal>
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(
|
|
143
|
+
getByText('This is content in the modal footer')
|
|
144
|
+
).toBeInTheDocument();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('renders all subcomponents together', () => {
|
|
148
|
+
const { getByText } = render(
|
|
149
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testSubcomponents">
|
|
150
|
+
<Modal.Header
|
|
151
|
+
id="titleFooterBody"
|
|
152
|
+
title="The Modal Title"
|
|
153
|
+
onDismiss={() => {}}
|
|
154
|
+
/>
|
|
155
|
+
<Modal.Body>Modal body content</Modal.Body>
|
|
156
|
+
<Modal.Footer>This is content in the modal footer</Modal.Footer>
|
|
157
|
+
</Modal>
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
expect(getByText('The Modal Title')).toBeInTheDocument();
|
|
161
|
+
expect(getByText('Modal body content')).toBeInTheDocument();
|
|
162
|
+
expect(
|
|
163
|
+
getByText('This is content in the modal footer')
|
|
164
|
+
).toBeInTheDocument();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('Styling', () => {
|
|
169
|
+
test('applies maxWidth styles', () => {
|
|
170
|
+
const { getByLabelText } = render(
|
|
171
|
+
<Modal
|
|
172
|
+
isOpen
|
|
173
|
+
onDismiss={() => {}}
|
|
174
|
+
ariaLabel="testMaxWidth"
|
|
175
|
+
maxWidth="500px"
|
|
176
|
+
>
|
|
177
|
+
test modal
|
|
178
|
+
</Modal>
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(getByLabelText('testMaxWidth').parentElement).toHaveStyle(
|
|
182
|
+
'max-width: 500px'
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('applies custom style prop', () => {
|
|
187
|
+
const { getByLabelText } = render(
|
|
188
|
+
<Modal
|
|
189
|
+
isOpen
|
|
190
|
+
onDismiss={() => {}}
|
|
191
|
+
ariaLabel="testStyle"
|
|
192
|
+
style={{ backgroundColor: '#cacaca' }}
|
|
193
|
+
>
|
|
194
|
+
test modal
|
|
195
|
+
</Modal>
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
expect(getByLabelText('testStyle').parentElement).toHaveStyle(
|
|
199
|
+
'background-color: #cacaca'
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('applies overflow prop', () => {
|
|
204
|
+
render(
|
|
205
|
+
<Modal
|
|
206
|
+
isOpen
|
|
207
|
+
onDismiss={() => {}}
|
|
208
|
+
ariaLabel="testOverflow"
|
|
209
|
+
overflow="visible"
|
|
210
|
+
>
|
|
211
|
+
test modal
|
|
212
|
+
</Modal>
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const modal = screen.getByRole('dialog');
|
|
216
|
+
expect(modal).toHaveClass('overflow-visible');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('applies default overflow hidden', () => {
|
|
220
|
+
render(
|
|
221
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="testDefaultOverflow">
|
|
222
|
+
test modal
|
|
223
|
+
</Modal>
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const modal = screen.getByRole('dialog');
|
|
227
|
+
expect(modal).toHaveClass('overflow-hidden');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('applies background color', () => {
|
|
231
|
+
render(
|
|
232
|
+
<Modal
|
|
233
|
+
isOpen
|
|
234
|
+
onDismiss={() => {}}
|
|
235
|
+
ariaLabel="testBackground"
|
|
236
|
+
background="secondary"
|
|
237
|
+
>
|
|
238
|
+
test modal
|
|
239
|
+
</Modal>
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
expect(screen.getByLabelText('testBackground')).toHaveClass(
|
|
243
|
+
'background-color-secondary'
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('Accessibility', () => {
|
|
249
|
+
test('applies aria-label attribute', () => {
|
|
250
|
+
render(
|
|
251
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="Accessible Modal">
|
|
252
|
+
content
|
|
253
|
+
</Modal>
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
expect(screen.getByLabelText('Accessible Modal')).toBeInTheDocument();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('applies aria-labelledby attribute', () => {
|
|
260
|
+
const { getByLabelText } = render(
|
|
261
|
+
<Modal
|
|
262
|
+
isOpen
|
|
39
263
|
onDismiss={() => {}}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
'
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
onDismiss={() => {}}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'
|
|
85
|
-
|
|
86
|
-
|
|
264
|
+
ariaLabelledBy="modalTitle"
|
|
265
|
+
ariaLabel="testAriaLabelledBy"
|
|
266
|
+
>
|
|
267
|
+
<h1 id="modalTitle">Modal Title</h1>
|
|
268
|
+
test modal
|
|
269
|
+
</Modal>
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
expect(getByLabelText('testAriaLabelledBy')).toHaveAttribute(
|
|
273
|
+
'aria-labelledby',
|
|
274
|
+
'modalTitle'
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('Modal.Header sets the id for aria-labelledby', () => {
|
|
279
|
+
render(
|
|
280
|
+
<Modal
|
|
281
|
+
isOpen
|
|
282
|
+
onDismiss={() => {}}
|
|
283
|
+
ariaLabelledBy="custom-header-id"
|
|
284
|
+
ariaLabel="test"
|
|
285
|
+
>
|
|
286
|
+
<Modal.Header id="custom-header-id" title="Accessible Title" />
|
|
287
|
+
</Modal>
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const title = screen.getByText('Accessible Title');
|
|
291
|
+
expect(title).toHaveAttribute('id', 'custom-header-id');
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe('Focus management', () => {
|
|
296
|
+
test('focus is placed inside the modal when opened', async () => {
|
|
297
|
+
render(
|
|
298
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="Focus Trap Modal">
|
|
299
|
+
<Modal.Body>
|
|
300
|
+
<button>First button</button>
|
|
301
|
+
<button>Second button</button>
|
|
302
|
+
</Modal.Body>
|
|
303
|
+
</Modal>
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const dialog = screen.getByRole('dialog');
|
|
307
|
+
const firstButton = screen.getByText('First button');
|
|
308
|
+
const secondButton = screen.getByText('Second button');
|
|
309
|
+
|
|
310
|
+
// Verify focusable elements are present
|
|
311
|
+
expect(firstButton).toBeInTheDocument();
|
|
312
|
+
expect(secondButton).toBeInTheDocument();
|
|
313
|
+
|
|
314
|
+
// Focus should be within the modal (FocusLock sets focus inside)
|
|
315
|
+
await waitFor(() => {
|
|
316
|
+
expect(dialog.contains(document.activeElement)).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('tabbing cycles focus within the modal', async () => {
|
|
321
|
+
const user = userEvent.setup();
|
|
322
|
+
render(
|
|
323
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="Focus Trap Modal">
|
|
324
|
+
<Modal.Body>
|
|
325
|
+
<button data-testid="btn1">First button</button>
|
|
326
|
+
<button data-testid="btn2">Second button</button>
|
|
327
|
+
</Modal.Body>
|
|
328
|
+
</Modal>
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
const dialog = screen.getByRole('dialog');
|
|
332
|
+
|
|
333
|
+
// Wait for focus lock to activate
|
|
334
|
+
await waitFor(() => {
|
|
335
|
+
expect(dialog.contains(document.activeElement)).toBe(true);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Tab multiple times - focus should remain within the modal
|
|
339
|
+
await user.tab();
|
|
340
|
+
expect(dialog.contains(document.activeElement)).toBe(true);
|
|
341
|
+
|
|
342
|
+
await user.tab();
|
|
343
|
+
expect(dialog.contains(document.activeElement)).toBe(true);
|
|
344
|
+
|
|
345
|
+
await user.tab();
|
|
346
|
+
expect(dialog.contains(document.activeElement)).toBe(true);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test('accepts initialFocusRef prop and focuses the referenced element', () => {
|
|
350
|
+
jest.useFakeTimers();
|
|
351
|
+
|
|
352
|
+
const TestComponent = () => {
|
|
353
|
+
const ref = React.useRef<HTMLDivElement>(null);
|
|
354
|
+
return (
|
|
355
|
+
<Modal
|
|
356
|
+
isOpen
|
|
357
|
+
onDismiss={() => {}}
|
|
358
|
+
ariaLabel="Initial Focus Modal"
|
|
359
|
+
initialFocusRef={ref}
|
|
360
|
+
>
|
|
361
|
+
<Modal.Body>
|
|
362
|
+
<div ref={ref} tabIndex={-1} data-testid="focus-target">
|
|
363
|
+
Focus target
|
|
364
|
+
</div>
|
|
365
|
+
</Modal.Body>
|
|
366
|
+
</Modal>
|
|
367
|
+
);
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
render(<TestComponent />);
|
|
371
|
+
|
|
372
|
+
const focusTarget = screen.getByTestId('focus-target');
|
|
373
|
+
expect(focusTarget).toBeInTheDocument();
|
|
374
|
+
|
|
375
|
+
// The Modal uses a setTimeout (100ms) to focus the initialFocusRef element
|
|
376
|
+
act(() => {
|
|
377
|
+
jest.advanceTimersByTime(100);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect(focusTarget).toHaveFocus();
|
|
381
|
+
|
|
382
|
+
jest.useRealTimers();
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
describe('onDismiss behavior', () => {
|
|
387
|
+
test('calls onDismiss when Escape key is pressed', async () => {
|
|
388
|
+
const handleDismiss = jest.fn();
|
|
389
|
+
render(
|
|
390
|
+
<Modal isOpen onDismiss={handleDismiss} ariaLabel="Escape Modal">
|
|
391
|
+
content
|
|
392
|
+
</Modal>
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
expect(screen.getByText('content')).toBeInTheDocument();
|
|
396
|
+
|
|
397
|
+
// Simulate pressing Escape key - react-modal listens on the dialog
|
|
398
|
+
fireEvent.keyDown(screen.getByRole('dialog'), {
|
|
399
|
+
key: 'Escape',
|
|
400
|
+
code: 'Escape',
|
|
401
|
+
keyCode: 27,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
await waitFor(() => {
|
|
405
|
+
expect(handleDismiss).toHaveBeenCalledTimes(1);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
describe('fullScreenMobile', () => {
|
|
411
|
+
test('applies fullscreen class when fullScreenMobile is true', () => {
|
|
412
|
+
const { baseElement } = render(
|
|
413
|
+
<Modal
|
|
414
|
+
isOpen
|
|
415
|
+
onDismiss={() => {}}
|
|
416
|
+
ariaLabel="Fullscreen Modal"
|
|
417
|
+
fullScreenMobile
|
|
418
|
+
>
|
|
419
|
+
content
|
|
420
|
+
</Modal>
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
expect(screen.getByText('content')).toBeInTheDocument();
|
|
424
|
+
|
|
425
|
+
// The overlay is rendered in a portal, so query from baseElement (document.body)
|
|
426
|
+
const overlay = baseElement.querySelector('.ReactModal__Overlay');
|
|
427
|
+
expect(overlay).toHaveClass('fullscreen');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test('does not apply fullscreen class when fullScreenMobile is false', () => {
|
|
431
|
+
const { baseElement } = render(
|
|
432
|
+
<Modal
|
|
433
|
+
isOpen
|
|
434
|
+
onDismiss={() => {}}
|
|
435
|
+
ariaLabel="Non-Fullscreen Modal"
|
|
436
|
+
fullScreenMobile={false}
|
|
437
|
+
>
|
|
438
|
+
content
|
|
439
|
+
</Modal>
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
expect(screen.getByText('content')).toBeInTheDocument();
|
|
443
|
+
|
|
444
|
+
// The overlay is rendered in a portal, so query from baseElement (document.body)
|
|
445
|
+
const overlay = baseElement.querySelector('.ReactModal__Overlay');
|
|
446
|
+
expect(overlay).not.toHaveClass('fullscreen');
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe('containerRef', () => {
|
|
451
|
+
test('renders in custom container when containerRef is provided', () => {
|
|
452
|
+
const TestComponent = () => {
|
|
453
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
454
|
+
return (
|
|
455
|
+
<div>
|
|
456
|
+
<div ref={containerRef} data-testid="custom-container" />
|
|
457
|
+
<Modal
|
|
458
|
+
isOpen
|
|
459
|
+
onDismiss={() => {}}
|
|
460
|
+
ariaLabel="Custom Container Modal"
|
|
461
|
+
containerRef={containerRef as React.RefObject<Node>}
|
|
462
|
+
>
|
|
463
|
+
Modal content
|
|
464
|
+
</Modal>
|
|
465
|
+
</div>
|
|
466
|
+
);
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
render(<TestComponent />);
|
|
470
|
+
expect(screen.getByText('Modal content')).toBeInTheDocument();
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
describe('allowPinchZoom', () => {
|
|
475
|
+
test('accepts allowPinchZoom prop', () => {
|
|
476
|
+
render(
|
|
477
|
+
<Modal
|
|
478
|
+
isOpen
|
|
479
|
+
onDismiss={() => {}}
|
|
480
|
+
ariaLabel="Pinch Zoom Modal"
|
|
481
|
+
allowPinchZoom
|
|
482
|
+
>
|
|
483
|
+
content
|
|
484
|
+
</Modal>
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
expect(screen.getByText('content')).toBeInTheDocument();
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
describe('Ref forwarding', () => {
|
|
492
|
+
test('forwards ref to the modal container', () => {
|
|
493
|
+
const ref = React.createRef<HTMLDivElement>();
|
|
494
|
+
render(
|
|
495
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="Ref Modal" ref={ref}>
|
|
496
|
+
content
|
|
497
|
+
</Modal>
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
describe('Static properties', () => {
|
|
505
|
+
test('Modal has Header static property', () => {
|
|
506
|
+
expect(Modal.Header).toBeDefined();
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test('Modal has Body static property', () => {
|
|
510
|
+
expect(Modal.Body).toBeDefined();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test('Modal has Footer static property', () => {
|
|
514
|
+
expect(Modal.Footer).toBeDefined();
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
describe('Modal.Header', () => {
|
|
519
|
+
test('renders close button only when onDismiss is provided', () => {
|
|
520
|
+
const { rerender } = render(
|
|
521
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="test">
|
|
522
|
+
<Modal.Header id="header" title="Without Close" />
|
|
523
|
+
</Modal>
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
expect(screen.queryByLabelText('close')).not.toBeInTheDocument();
|
|
527
|
+
|
|
528
|
+
rerender(
|
|
529
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="test">
|
|
530
|
+
<Modal.Header id="header" title="With Close" onDismiss={() => {}} />
|
|
531
|
+
</Modal>
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
expect(screen.getByLabelText('close')).toBeInTheDocument();
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
test('header has correct justify-content when no title or children', () => {
|
|
538
|
+
render(
|
|
539
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="test">
|
|
540
|
+
<Modal.Header id="header" onDismiss={() => {}} />
|
|
541
|
+
</Modal>
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
// When no title/children, close button should be at flex-end
|
|
545
|
+
expect(screen.getByLabelText('close')).toBeInTheDocument();
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
describe('Modal.Body defaults', () => {
|
|
550
|
+
test('applies default flex, overflow, and height', () => {
|
|
551
|
+
render(
|
|
552
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="test">
|
|
553
|
+
<Modal.Body data-testid="modal-body">Body content</Modal.Body>
|
|
554
|
+
</Modal>
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
const body = screen.getByTestId('modal-body');
|
|
558
|
+
expect(body).toHaveClass('flex-auto', 'overflow-auto', 'h-100');
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test('allows overriding default props', () => {
|
|
562
|
+
render(
|
|
563
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="test">
|
|
564
|
+
<Modal.Body
|
|
565
|
+
flex="none"
|
|
566
|
+
overflow="hidden"
|
|
567
|
+
height="50"
|
|
568
|
+
data-testid="modal-body"
|
|
569
|
+
>
|
|
570
|
+
Body content
|
|
571
|
+
</Modal.Body>
|
|
572
|
+
</Modal>
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
const body = screen.getByTestId('modal-body');
|
|
576
|
+
expect(body).toHaveClass('flex-none', 'overflow-hidden', 'h-50');
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
describe('Modal.Footer defaults', () => {
|
|
581
|
+
test('applies default direction, alignItems, justifyContent, and gap', () => {
|
|
582
|
+
render(
|
|
583
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="test">
|
|
584
|
+
<Modal.Footer data-testid="modal-footer">Footer content</Modal.Footer>
|
|
585
|
+
</Modal>
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
const footer = screen.getByTestId('modal-footer');
|
|
589
|
+
expect(footer).toHaveClass(
|
|
590
|
+
'flex-direction-row',
|
|
591
|
+
'align-items-center',
|
|
592
|
+
'justify-content-flex-end',
|
|
593
|
+
'g-md'
|
|
594
|
+
);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test('allows overriding footer defaults', () => {
|
|
598
|
+
render(
|
|
599
|
+
<Modal isOpen onDismiss={() => {}} ariaLabel="test">
|
|
600
|
+
<Modal.Footer
|
|
601
|
+
direction="column"
|
|
602
|
+
justifyContent="flex-start"
|
|
603
|
+
gap="lg"
|
|
604
|
+
data-testid="modal-footer"
|
|
605
|
+
>
|
|
606
|
+
Footer content
|
|
607
|
+
</Modal.Footer>
|
|
608
|
+
</Modal>
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
const footer = screen.getByTestId('modal-footer');
|
|
612
|
+
expect(footer).toHaveClass(
|
|
613
|
+
'flex-direction-column',
|
|
614
|
+
'justify-content-flex-start',
|
|
615
|
+
'g-lg'
|
|
616
|
+
);
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
describe('Spread props', () => {
|
|
621
|
+
test('renders modal with additional props', () => {
|
|
622
|
+
render(
|
|
623
|
+
<Modal
|
|
624
|
+
isOpen
|
|
625
|
+
onDismiss={() => {}}
|
|
626
|
+
ariaLabel="test"
|
|
627
|
+
shouldCloseOnOverlayClick={false}
|
|
628
|
+
>
|
|
629
|
+
content
|
|
630
|
+
</Modal>
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
// Verify the modal renders with the content
|
|
634
|
+
expect(screen.getByText('content')).toBeInTheDocument();
|
|
635
|
+
});
|
|
87
636
|
});
|
|
88
637
|
});
|