@scottish-government/designsystem-react 0.0.0
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/.editorconfig +12 -0
- package/.github/workflows/release-package.yml +96 -0
- package/@types/common/ConditionalWrapper.d.ts +6 -0
- package/@types/common/HintText.d.ts +6 -0
- package/@types/common/Icon.d.ts +11 -0
- package/@types/common/ScreenReaderText.d.ts +4 -0
- package/@types/common/WrapperTag.d.ts +5 -0
- package/@types/components/Accordion.d.ts +15 -0
- package/@types/components/AspectBox.d.ts +5 -0
- package/@types/components/BackToTop.d.ts +5 -0
- package/@types/components/Breadcrumbs.d.ts +14 -0
- package/@types/components/Button.d.ts +17 -0
- package/@types/components/Checkbox.d.ts +13 -0
- package/@types/components/ConfirmationMessage.d.ts +7 -0
- package/@types/components/ContentsNav.d.ts +15 -0
- package/@types/components/DatePicker.d.ts +19 -0
- package/@types/components/Details.d.ts +6 -0
- package/@types/components/ErrorMessage.d.ts +6 -0
- package/@types/components/Metadata.d.ts +11 -0
- package/@types/components/NotificationBanner.d.ts +9 -0
- package/@types/components/NotificationPanel.d.ts +7 -0
- package/@types/components/PageHeader.d.ts +6 -0
- package/@types/components/PhaseBanner.d.ts +5 -0
- package/@types/components/Question.d.ts +11 -0
- package/@types/components/RadioButton.d.ts +15 -0
- package/@types/components/Select.d.ts +14 -0
- package/@types/components/SequentialNavigation.d.ts +14 -0
- package/@types/components/SideNavigation.d.ts +19 -0
- package/@types/components/SiteNavigation.d.ts +13 -0
- package/@types/components/SiteSearch.d.ts +14 -0
- package/@types/components/SkipLinks.d.ts +14 -0
- package/@types/components/Tag.d.ts +7 -0
- package/@types/components/TaskList.d.ts +21 -0
- package/@types/components/TextInput.d.ts +12 -0
- package/@types/components/Textarea.d.ts +4 -0
- package/@types/global.d.ts +1 -0
- package/@types/sgds.d.ts +35 -0
- package/package.json +36 -0
- package/src/common/conditional-wrapper.test.tsx +36 -0
- package/src/common/conditional-wrapper.tsx +9 -0
- package/src/common/hint-text.test.tsx +47 -0
- package/src/common/hint-text.tsx +21 -0
- package/src/common/icon.test.tsx +100 -0
- package/src/common/icon.tsx +28 -0
- package/src/common/screen-reader-text.test.tsx +31 -0
- package/src/common/screen-reader-text.tsx +17 -0
- package/src/common/wrapper-tag.test.tsx +42 -0
- package/src/common/wrapper-tag.tsx +15 -0
- package/src/components/accordion/accordion.test.tsx +212 -0
- package/src/components/accordion/accordion.tsx +108 -0
- package/src/components/aspect-box/aspect-box.test.tsx +81 -0
- package/src/components/aspect-box/aspect-box.tsx +57 -0
- package/src/components/back-to-top/back-to-top.test.tsx +45 -0
- package/src/components/back-to-top/back-to-top.tsx +33 -0
- package/src/components/breadcrumbs/breadcrumbs.test.tsx +77 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +53 -0
- package/src/components/button/button.test.tsx +125 -0
- package/src/components/button/button.tsx +48 -0
- package/src/components/checkbox/checkbox.test.tsx +180 -0
- package/src/components/checkbox/checkbox.tsx +107 -0
- package/src/components/confirmation-message/confirmation-message.test.tsx +46 -0
- package/src/components/confirmation-message/confirmation-message.tsx +32 -0
- package/src/components/contents-nav/contents-nav.test.tsx +136 -0
- package/src/components/contents-nav/contents-nav.tsx +54 -0
- package/src/components/date-picker/date-picker.test.tsx +209 -0
- package/src/components/date-picker/date-picker.tsx +129 -0
- package/src/components/details/details.test.tsx +38 -0
- package/src/components/details/details.tsx +25 -0
- package/src/components/error-message/error-message.test.tsx +40 -0
- package/src/components/error-message/error-message.tsx +23 -0
- package/src/components/inset-text/inset-text.test.tsx +33 -0
- package/src/components/inset-text/inset-text.tsx +19 -0
- package/src/components/notification-banner/notification-banner.test.tsx +93 -0
- package/src/components/notification-banner/notification-banner.tsx +70 -0
- package/src/components/notification-panel/notification-panel.test.tsx +77 -0
- package/src/components/notification-panel/notification-panel.tsx +31 -0
- package/src/components/page-header/page-header.test.tsx +48 -0
- package/src/components/page-header/page-header.tsx +22 -0
- package/src/components/page-metadata/page-metadata.test.tsx +56 -0
- package/src/components/page-metadata/page-metadata.tsx +39 -0
- package/src/components/phase-banner/phase-banner.test.tsx +67 -0
- package/src/components/phase-banner/phase-banner.tsx +27 -0
- package/src/components/question/question.test.tsx +69 -0
- package/src/components/question/question.tsx +33 -0
- package/src/components/radio-button/radio-button.test.tsx +190 -0
- package/src/components/radio-button/radio-button.tsx +88 -0
- package/src/components/select/select.test.tsx +208 -0
- package/src/components/select/select.tsx +86 -0
- package/src/components/sequential-navigation/sequential-navigation.test.tsx +67 -0
- package/src/components/sequential-navigation/sequential-navigation.tsx +55 -0
- package/src/components/side-navigation/side-navigation.test.tsx +156 -0
- package/src/components/side-navigation/side-navigation.tsx +85 -0
- package/src/components/site-navigation/site-navigation.test.tsx +63 -0
- package/src/components/site-navigation/site-navigation.tsx +40 -0
- package/src/components/site-search/site-search.test.tsx +153 -0
- package/src/components/site-search/site-search.tsx +97 -0
- package/src/components/skip-links/skip-links.test.tsx +84 -0
- package/src/components/skip-links/skip-links.tsx +39 -0
- package/src/components/tag/tag.test.tsx +45 -0
- package/src/components/tag/tag.tsx +23 -0
- package/src/components/task-list/task-list.test.tsx +409 -0
- package/src/components/task-list/task-list.tsx +132 -0
- package/src/components/text-input/text-input.test.tsx +307 -0
- package/src/components/text-input/text-input.tsx +98 -0
- package/src/components/textarea/textarea.test.tsx +212 -0
- package/src/components/textarea/textarea.tsx +82 -0
- package/src/components/warning-text/warning-text.test.tsx +40 -0
- package/src/components/warning-text/warning-text.tsx +21 -0
- package/tsconfig.json +45 -0
- package/vite.config.ts +12 -0
- package/vitest-setup.ts +13 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import DSCheckboxes from '@scottish-government/design-system/src/forms/checkbox/checkboxes'
|
|
4
|
+
import HintText from '../../common/hint-text';
|
|
5
|
+
|
|
6
|
+
export const Checkbox: React.FC<SGDS.Component.Checkbox> = ({
|
|
7
|
+
checked,
|
|
8
|
+
hintText,
|
|
9
|
+
id,
|
|
10
|
+
exclusive,
|
|
11
|
+
label,
|
|
12
|
+
name,
|
|
13
|
+
onBlur,
|
|
14
|
+
onChange,
|
|
15
|
+
small
|
|
16
|
+
}) => {
|
|
17
|
+
const hintTextId = `hint-text-${id}`;
|
|
18
|
+
const behaviour = exclusive && 'exclusive';
|
|
19
|
+
|
|
20
|
+
function handleBlur(event: React.FocusEvent) {
|
|
21
|
+
if (typeof onBlur === 'function') {
|
|
22
|
+
onBlur(event);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function handleChange(event: React.ChangeEvent) {
|
|
27
|
+
if (typeof onChange === 'function') {
|
|
28
|
+
onChange(event);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
{exclusive && <p className="ds_checkbox-separator">or</p>}
|
|
35
|
+
<div
|
|
36
|
+
className={[
|
|
37
|
+
'ds_checkbox',
|
|
38
|
+
small && 'ds_checkbox--small'
|
|
39
|
+
].join(' ')}>
|
|
40
|
+
|
|
41
|
+
<input
|
|
42
|
+
aria-describedby={hintText ? hintTextId : undefined}
|
|
43
|
+
className="ds_checkbox__input"
|
|
44
|
+
data-behaviour={behaviour}
|
|
45
|
+
defaultChecked={!!checked}
|
|
46
|
+
id={id}
|
|
47
|
+
name={name || id}
|
|
48
|
+
onBlur={handleBlur}
|
|
49
|
+
onChange={handleChange}
|
|
50
|
+
type="checkbox" />
|
|
51
|
+
<label
|
|
52
|
+
className="ds_checkbox__label"
|
|
53
|
+
htmlFor={id}
|
|
54
|
+
>{label}</label>
|
|
55
|
+
{hintText && <HintText id={hintTextId} text={hintText} />}
|
|
56
|
+
</div>
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {Object} props - Properties for the element
|
|
63
|
+
* @param {Array} items - Checkboxes
|
|
64
|
+
* @param {boolean} small - Use the small display style for all checkboxes
|
|
65
|
+
* @returns {JSX.Element} - The element
|
|
66
|
+
*/
|
|
67
|
+
export const CheckboxGroup: React.FC<SGDS.Component.Checkbox.Group> = ({
|
|
68
|
+
items,
|
|
69
|
+
small,
|
|
70
|
+
...props
|
|
71
|
+
}) => {
|
|
72
|
+
const ref = useRef(null);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (ref.current) {
|
|
76
|
+
new DSCheckboxes(ref.current).init();
|
|
77
|
+
}
|
|
78
|
+
}, [ref])
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
className="ds_checkboxes ds_field-group"
|
|
83
|
+
data-module="ds-checkboxes"
|
|
84
|
+
ref={ref}
|
|
85
|
+
{...props}
|
|
86
|
+
>
|
|
87
|
+
{items && items.map((item, index: number) => (
|
|
88
|
+
<Checkbox
|
|
89
|
+
exclusive={item.exclusive}
|
|
90
|
+
checked={item.checked}
|
|
91
|
+
hintText={item.hintText}
|
|
92
|
+
id={item.id}
|
|
93
|
+
key={'checkbox' + index}
|
|
94
|
+
label={item.label}
|
|
95
|
+
onBlur={item.onBlur}
|
|
96
|
+
onChange={item.onChange}
|
|
97
|
+
small={small || item.small}
|
|
98
|
+
/>
|
|
99
|
+
))}
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
Checkbox.displayName = 'Checkbox';
|
|
105
|
+
CheckboxGroup.displayName = 'CheckboxGroup';
|
|
106
|
+
|
|
107
|
+
export default CheckboxGroup;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import ConfirmationMessage from './confirmation-message';
|
|
4
|
+
|
|
5
|
+
const titleString = 'Landlord added successfully';
|
|
6
|
+
|
|
7
|
+
test('renders correctly with icon, title and message', () => {
|
|
8
|
+
render(
|
|
9
|
+
<ConfirmationMessage title={titleString}>
|
|
10
|
+
<p>You have added the landlord <strong>John Smith</strong> to the application.</p>
|
|
11
|
+
</ConfirmationMessage>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const container = document.querySelector('.ds_confirmation-message');
|
|
15
|
+
const header = screen.getByRole('heading');
|
|
16
|
+
|
|
17
|
+
expect(container?.ariaLive).toEqual('polite');
|
|
18
|
+
|
|
19
|
+
expect(header.tagName).toEqual('H3');
|
|
20
|
+
expect(header.textContent).toEqual(titleString)
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("does not render body when no children specified", () => {
|
|
24
|
+
const { container } = render(<ConfirmationMessage title={titleString} />);
|
|
25
|
+
|
|
26
|
+
expect(
|
|
27
|
+
container.querySelector(".ds_confirmation-message__body"),
|
|
28
|
+
).not.toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('renders confirmation message with custom aria live and custom heaer level', () => {
|
|
32
|
+
const titleString = 'Landlord added successfully';
|
|
33
|
+
|
|
34
|
+
render(
|
|
35
|
+
<ConfirmationMessage headerLevel="h2" ariaLive="alert" title={titleString}>
|
|
36
|
+
<p>You have added the landlord <strong>John Smith</strong> to the application.</p>
|
|
37
|
+
</ConfirmationMessage>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const container = document.querySelector('.ds_confirmation-message');
|
|
41
|
+
const header = screen.getByRole('heading');
|
|
42
|
+
|
|
43
|
+
expect(container?.ariaLive).toEqual('alert');
|
|
44
|
+
|
|
45
|
+
expect(header.tagName).toEqual('H2');
|
|
46
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Icon from '../../common/icon';
|
|
2
|
+
import WrapperTag from '../../common/wrapper-tag';
|
|
3
|
+
|
|
4
|
+
const ConfirmationMessage: React.FC<SGDS.Component.ConfirmationMessage> = ({
|
|
5
|
+
ariaLive = 'polite',
|
|
6
|
+
children,
|
|
7
|
+
headerLevel = 'h3',
|
|
8
|
+
title
|
|
9
|
+
}) => {
|
|
10
|
+
return (
|
|
11
|
+
<div
|
|
12
|
+
aria-live={ariaLive}
|
|
13
|
+
className="ds_confirmation-message"
|
|
14
|
+
>
|
|
15
|
+
<Icon className="ds_confirmation-message__icon" icon="check_circle" iconSize="24" />
|
|
16
|
+
|
|
17
|
+
<WrapperTag
|
|
18
|
+
className="ds_confirmation-message__title"
|
|
19
|
+
tagName={headerLevel}
|
|
20
|
+
>
|
|
21
|
+
{title}
|
|
22
|
+
</WrapperTag>
|
|
23
|
+
{children && <div className="ds_confirmation-message__body">
|
|
24
|
+
{children}
|
|
25
|
+
</div>}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
ConfirmationMessage.displayName = 'ConfirmationMessage';
|
|
31
|
+
|
|
32
|
+
export default ConfirmationMessage;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import { render, screen, within } from '@testing-library/react';
|
|
3
|
+
import ContentsNav, {Link} from './contents-nav';
|
|
4
|
+
|
|
5
|
+
test('contents nav renders correctly', () => {
|
|
6
|
+
const items = [
|
|
7
|
+
{
|
|
8
|
+
title: 'Apply for Blue Badge',
|
|
9
|
+
current: true
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
title: 'Eligibility',
|
|
13
|
+
href: '#2'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
title: 'Using your Blue Badge',
|
|
17
|
+
href: '#3'
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
title: 'Report a lost, stolen or misuesd Blue Badge',
|
|
21
|
+
href: '#4'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
title: 'Changing or handing back a Blue Badge',
|
|
25
|
+
href: '#5'
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const label = 'Pages in this guide';
|
|
30
|
+
|
|
31
|
+
render(
|
|
32
|
+
<ContentsNav label={label} items={items} />
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const contentsNav = screen.getByRole('navigation');
|
|
36
|
+
const contentsNavTitle = within(contentsNav).getByRole('heading');
|
|
37
|
+
const contentsNavList = within(contentsNav).getByRole('list');
|
|
38
|
+
|
|
39
|
+
expect(contentsNav).toHaveClass('ds_contents-nav');
|
|
40
|
+
expect(contentsNav.ariaLabel).toEqual(label);
|
|
41
|
+
expect(contentsNav.tagName).toEqual('NAV');
|
|
42
|
+
expect(contentsNavTitle).toHaveClass('ds_contents-nav__title');
|
|
43
|
+
expect(contentsNavTitle.textContent).toEqual('Contents');
|
|
44
|
+
expect(contentsNavList).toHaveClass('ds_contents-nav__list');
|
|
45
|
+
expect(contentsNavList.tagName).toEqual('UL');
|
|
46
|
+
expect(contentsNavList.children.length).toEqual(items.length);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('contents nav with custom title', () => {
|
|
50
|
+
const title = 'My title';
|
|
51
|
+
|
|
52
|
+
render(
|
|
53
|
+
<ContentsNav title={title} items={[]} />
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const contentsNav = screen.getByRole('navigation');
|
|
57
|
+
const contentsNavTitle = within(contentsNav).getByRole('heading');
|
|
58
|
+
expect(contentsNavTitle.textContent).toEqual(title);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('contents nav item', () => {
|
|
62
|
+
const href = '#foo';
|
|
63
|
+
const content = 'My content';
|
|
64
|
+
|
|
65
|
+
render(
|
|
66
|
+
<Link href={href} title={content} />
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const listItem = screen.getByRole('listitem');
|
|
70
|
+
const link = within(listItem).getByRole('link');
|
|
71
|
+
|
|
72
|
+
expect(listItem).toHaveClass('ds_contents-nav__item');
|
|
73
|
+
expect(listItem.tagName).toEqual('LI');
|
|
74
|
+
expect(link).toHaveClass('ds_contents-nav__link');
|
|
75
|
+
expect(link.tagName).toEqual('A');
|
|
76
|
+
expect(link.textContent).toEqual(content);
|
|
77
|
+
expect(link).toHaveAttribute('href', href);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('contents nav current item with href', () => {
|
|
81
|
+
const href = '#foo';
|
|
82
|
+
const content = 'My content';
|
|
83
|
+
|
|
84
|
+
render(
|
|
85
|
+
<Link current href={href} title={content} />
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const listItem = screen.getByRole('listitem');
|
|
89
|
+
const link = within(listItem).getByText(content);
|
|
90
|
+
|
|
91
|
+
expect(listItem.ariaCurrent).toEqual('page');
|
|
92
|
+
expect(link.tagName).toEqual('SPAN');
|
|
93
|
+
expect(link).toHaveClass('ds_current');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('contents nav current item without href', () => {
|
|
97
|
+
const content = 'My content';
|
|
98
|
+
|
|
99
|
+
render(
|
|
100
|
+
<Link current title={content} />
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const listItem = screen.getByRole('listitem');
|
|
104
|
+
const link = within(listItem).getByText(content);
|
|
105
|
+
|
|
106
|
+
expect(listItem.ariaCurrent).toEqual('page');
|
|
107
|
+
expect(link.tagName).toEqual('SPAN');
|
|
108
|
+
expect(link).toHaveClass('ds_current');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('contents nav item without href', () => {
|
|
112
|
+
const content = 'My content';
|
|
113
|
+
|
|
114
|
+
render(
|
|
115
|
+
<Link title={content} />
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const listItem = screen.getByRole('listitem');
|
|
119
|
+
const link = within(listItem).getByText(content);
|
|
120
|
+
|
|
121
|
+
expect(link.tagName).toEqual('SPAN');
|
|
122
|
+
expect(link).not.toHaveClass('ds_current');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('passing additional props', () => {
|
|
126
|
+
render(
|
|
127
|
+
<ContentsNav data-test="foo" items={[
|
|
128
|
+
{
|
|
129
|
+
title: 'Apply for Blue Badge',
|
|
130
|
+
}
|
|
131
|
+
]} />
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const contentsNav = screen.getByRole('navigation');
|
|
135
|
+
expect(contentsNav?.dataset.test).toEqual('foo');
|
|
136
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import WrapperTag from '../../common/wrapper-tag';
|
|
2
|
+
|
|
3
|
+
export const Link: React.FC<SGDS.Component.ContentsNav.Link> = ({
|
|
4
|
+
title,
|
|
5
|
+
current,
|
|
6
|
+
href
|
|
7
|
+
}) => {
|
|
8
|
+
// determine which HTML tag to use
|
|
9
|
+
const tagName = href && !current ? 'a' : 'span';
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<li
|
|
13
|
+
aria-current={current && 'page' || undefined}
|
|
14
|
+
className="ds_contents-nav__item"
|
|
15
|
+
>
|
|
16
|
+
<WrapperTag
|
|
17
|
+
tagName={tagName}
|
|
18
|
+
className={[
|
|
19
|
+
'ds_contents-nav__link',
|
|
20
|
+
current && 'ds_current'
|
|
21
|
+
].join(' ')}
|
|
22
|
+
href={!current ? href : undefined}
|
|
23
|
+
>
|
|
24
|
+
{title}
|
|
25
|
+
</WrapperTag>
|
|
26
|
+
</li>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const ContentsNav: React.FC<SGDS.Component.ContentsNav> = function({
|
|
31
|
+
items,
|
|
32
|
+
label = 'Pages in this section',
|
|
33
|
+
title = 'Contents',
|
|
34
|
+
...props
|
|
35
|
+
}) {
|
|
36
|
+
return (
|
|
37
|
+
<nav
|
|
38
|
+
aria-label={label}
|
|
39
|
+
className="ds_contents-nav"
|
|
40
|
+
{...props}
|
|
41
|
+
>
|
|
42
|
+
<h2 className="ds_contents-nav__title">{title}</h2>
|
|
43
|
+
<ul className="ds_contents-nav__list">
|
|
44
|
+
{items && items.map((item, index: number) => (
|
|
45
|
+
<Link current={item.current} href={item.href} title={item.title} key={'link' + index} />
|
|
46
|
+
))}
|
|
47
|
+
</ul>
|
|
48
|
+
</nav>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
ContentsNav.displayName = 'ContentsNav';
|
|
53
|
+
|
|
54
|
+
export default ContentsNav;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { test, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, within, fireEvent } from '@testing-library/react';
|
|
3
|
+
import DatePicker from './date-picker';
|
|
4
|
+
|
|
5
|
+
const labelText = 'Date of birth';
|
|
6
|
+
const id = 'date-picker';
|
|
7
|
+
|
|
8
|
+
test('date picker renders correctly', () => {
|
|
9
|
+
render(
|
|
10
|
+
<DatePicker
|
|
11
|
+
id={id}
|
|
12
|
+
label={labelText}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// a little hacky. maybe testid would be better?
|
|
17
|
+
const datePicker = screen.getAllByRole('generic')[1];
|
|
18
|
+
const label = within(datePicker).getByText(labelText);
|
|
19
|
+
const textInput = within(datePicker).getByRole('textbox');
|
|
20
|
+
|
|
21
|
+
expect(datePicker).toHaveClass('ds_datepicker');
|
|
22
|
+
expect(label).toHaveClass('ds_label');
|
|
23
|
+
expect(label).toHaveAttribute('for', textInput.id);
|
|
24
|
+
expect(label.tagName).toEqual('LABEL');
|
|
25
|
+
expect(textInput).toHaveClass('ds_input', 'ds_input--fixed-10');
|
|
26
|
+
expect(textInput.id).toEqual(id);
|
|
27
|
+
|
|
28
|
+
// todo: check for DS script being fired
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('date picker with disabled dates', () => {
|
|
32
|
+
const disabledDates = '18/05/2023 19/05/2023'
|
|
33
|
+
render(
|
|
34
|
+
<DatePicker
|
|
35
|
+
id={id}
|
|
36
|
+
label={labelText}
|
|
37
|
+
disabledDates={disabledDates}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
const datePicker = screen.getAllByRole('generic')[1];
|
|
41
|
+
|
|
42
|
+
expect(datePicker).toHaveAttribute('data-disableddates', disabledDates);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('date picker with hint text', () => {
|
|
46
|
+
const hintText = 'My hint text'
|
|
47
|
+
render(
|
|
48
|
+
<DatePicker
|
|
49
|
+
id={id}
|
|
50
|
+
label={labelText}
|
|
51
|
+
hintText={hintText}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
const datePicker = screen.getAllByRole('generic')[1];
|
|
55
|
+
const hintTextEl = screen.getByText(hintText);
|
|
56
|
+
const textInput = within(datePicker).getByRole('textbox');
|
|
57
|
+
|
|
58
|
+
expect(hintTextEl).toBeInTheDocument();
|
|
59
|
+
expect(textInput).toHaveAttribute('aria-describedby', hintTextEl.id);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('date picker with custom icon path', () => {
|
|
63
|
+
const iconPath = '/my/icon/path'
|
|
64
|
+
render(
|
|
65
|
+
<DatePicker
|
|
66
|
+
id={id}
|
|
67
|
+
label={labelText}
|
|
68
|
+
iconPath={iconPath}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
const datePicker = screen.getAllByRole('generic')[1];
|
|
72
|
+
const label = within(datePicker).getByText(labelText);
|
|
73
|
+
const textInput = within(datePicker).getByRole('textbox');
|
|
74
|
+
|
|
75
|
+
// todo
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('date picker with max date', () => {
|
|
79
|
+
const maxDate = '28/05/2023'
|
|
80
|
+
render(
|
|
81
|
+
<DatePicker
|
|
82
|
+
id={id}
|
|
83
|
+
label={labelText}
|
|
84
|
+
maxDate={maxDate}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
const datePicker = screen.getAllByRole('generic')[1];
|
|
88
|
+
|
|
89
|
+
expect(datePicker).toHaveAttribute('data-maxDate', maxDate);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('date picker with min date', () => {
|
|
93
|
+
const minDate = '28/05/2023'
|
|
94
|
+
render(
|
|
95
|
+
<DatePicker
|
|
96
|
+
id={id}
|
|
97
|
+
label={labelText}
|
|
98
|
+
minDate={minDate}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
const datePicker = screen.getAllByRole('generic')[1];
|
|
102
|
+
|
|
103
|
+
expect(datePicker).toHaveAttribute('data-mindate', minDate);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('date picker with blur fn', () => {
|
|
107
|
+
const onBlurFn = vi.fn();
|
|
108
|
+
render(
|
|
109
|
+
<DatePicker
|
|
110
|
+
id={id}
|
|
111
|
+
label={labelText}
|
|
112
|
+
onBlur={onBlurFn}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
const datePicker = screen.getAllByRole('generic')[1];
|
|
116
|
+
const textInput = within(datePicker).getByRole('textbox');
|
|
117
|
+
|
|
118
|
+
fireEvent.blur(textInput);
|
|
119
|
+
|
|
120
|
+
expect(onBlurFn).toHaveBeenCalled();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('date picker with change fn', () => {
|
|
124
|
+
const onChangeFn = vi.fn();
|
|
125
|
+
render(
|
|
126
|
+
<DatePicker
|
|
127
|
+
id={id}
|
|
128
|
+
label={labelText}
|
|
129
|
+
onChange={onChangeFn}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
const datePicker = screen.getAllByRole('generic')[1];
|
|
133
|
+
const textInput = within(datePicker).getByRole('textbox');
|
|
134
|
+
|
|
135
|
+
fireEvent.change(textInput, {target: {value: 'foo'}});
|
|
136
|
+
|
|
137
|
+
expect(onChangeFn).toHaveBeenCalled();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('date picker with initial value', () => {
|
|
141
|
+
const initialValue = '28/05/2023';
|
|
142
|
+
render(
|
|
143
|
+
<DatePicker
|
|
144
|
+
id={id}
|
|
145
|
+
label={labelText}
|
|
146
|
+
value={initialValue}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
const datePicker = screen.getAllByRole('generic')[1];
|
|
150
|
+
const textInput = within(datePicker).getByRole('textbox');
|
|
151
|
+
|
|
152
|
+
expect(textInput).toHaveValue(initialValue);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('date picker with multiple inputs', () => {
|
|
156
|
+
const initialValue = '28/05/2023';
|
|
157
|
+
render(
|
|
158
|
+
<DatePicker
|
|
159
|
+
id={id}
|
|
160
|
+
label={labelText}
|
|
161
|
+
multiple
|
|
162
|
+
value={initialValue}
|
|
163
|
+
/>
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const datePicker = screen.getAllByRole('generic')[1];
|
|
167
|
+
const textInputs = within(datePicker).getAllByRole('textbox');
|
|
168
|
+
const dateInput = textInputs[0];
|
|
169
|
+
const monthInput = textInputs[1];
|
|
170
|
+
const yearInput = textInputs[2];
|
|
171
|
+
const dateLabel = within(datePicker).getByText('Day');
|
|
172
|
+
const monthLabel = within(datePicker).getByText('Month');
|
|
173
|
+
const yearLabel = within(datePicker).getByText('Year');
|
|
174
|
+
|
|
175
|
+
expect(textInputs.length).toEqual(3);
|
|
176
|
+
|
|
177
|
+
expect(dateInput).toHaveValue(initialValue.split('/')[0]);
|
|
178
|
+
expect(monthInput).toHaveValue(initialValue.split('/')[1]);
|
|
179
|
+
expect(yearInput).toHaveValue(initialValue.split('/')[2]);
|
|
180
|
+
|
|
181
|
+
expect(dateInput).toHaveAttribute('id', `${id}-day`);
|
|
182
|
+
expect(monthInput).toHaveAttribute('id', `${id}-month`);
|
|
183
|
+
expect(yearInput).toHaveAttribute('id', `${id}-year`);
|
|
184
|
+
|
|
185
|
+
expect(dateInput).toHaveClass('ds_input', 'ds_input--fixed-2', 'js-datepicker-date');
|
|
186
|
+
expect(monthInput).toHaveClass('ds_input', 'ds_input--fixed-2', 'js-datepicker-month');
|
|
187
|
+
expect(yearInput).toHaveClass('ds_input', 'ds_input--fixed-4', 'js-datepicker-year');
|
|
188
|
+
|
|
189
|
+
expect(dateLabel).toHaveClass('ds_label');
|
|
190
|
+
expect(monthLabel).toHaveClass('ds_label');
|
|
191
|
+
expect(yearLabel).toHaveClass('ds_label');
|
|
192
|
+
|
|
193
|
+
expect(dateLabel).toHaveAttribute('for', dateInput.id);
|
|
194
|
+
expect(monthLabel).toHaveAttribute('for', monthInput.id);
|
|
195
|
+
expect(yearLabel).toHaveAttribute('for', yearInput.id);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('passing additional props', () => {
|
|
199
|
+
render(
|
|
200
|
+
<DatePicker
|
|
201
|
+
id={id}
|
|
202
|
+
label={labelText}
|
|
203
|
+
data-test="foo"
|
|
204
|
+
/>
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
const datePicker = screen.getAllByRole('generic')[1];
|
|
208
|
+
expect(datePicker?.dataset.test).toEqual('foo');
|
|
209
|
+
});
|