@scottish-government/designsystem-react 0.3.0 → 0.5.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/@types/common/AbstractNotificationBanner.d.ts +17 -0
- package/@types/components/Table.d.ts +8 -0
- package/@types/components/Tabs.d.ts +22 -0
- package/@types/components/TaskList.d.ts +1 -1
- package/dist/common/abstract-notification-banner.jsx +63 -0
- package/dist/components/accordion/accordion.jsx +2 -3
- package/dist/components/breadcrumbs/breadcrumbs.jsx +2 -2
- package/dist/components/cookie-banner/cookie-banner.jsx +21 -0
- package/dist/components/notification-banner/notification-banner.jsx +7 -34
- package/dist/components/page-metadata/page-metadata.jsx +2 -3
- package/dist/components/table/table.jsx +24 -0
- package/dist/components/tabs/tabs.jsx +99 -0
- package/dist/components/task-list/task-list.jsx +6 -7
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/slugify.js +15 -0
- package/package.json +1 -1
- package/src/common/abstract-notification-banner.test.tsx +126 -0
- package/src/common/abstract-notification-banner.tsx +87 -0
- package/src/common/conditional-wrapper.test.tsx +1 -1
- package/src/common/screen-reader-text.test.tsx +1 -1
- package/src/common/wrapper-tag.test.tsx +3 -3
- package/src/components/accordion/accordion.test.tsx +40 -40
- package/src/components/accordion/accordion.tsx +4 -2
- package/src/components/aspect-box/aspect-box.test.tsx +7 -7
- package/src/components/breadcrumbs/breadcrumbs.tsx +0 -2
- package/src/components/checkbox/checkbox.test.tsx +8 -8
- package/src/components/confirmation-message/confirmation-message.test.tsx +1 -1
- package/src/components/cookie-banner/cookie-banner.test.tsx +25 -0
- package/src/components/cookie-banner/cookie-banner.tsx +36 -0
- package/src/components/details/details.test.tsx +7 -7
- package/src/components/inset-text/inset-text.test.tsx +1 -1
- package/src/components/notification-banner/notification-banner.test.tsx +13 -73
- package/src/components/notification-banner/notification-banner.tsx +13 -41
- package/src/components/notification-panel/notification-panel.test.tsx +6 -6
- package/src/components/page-header/page-header.test.tsx +2 -2
- package/src/components/page-metadata/page-metadata.test.tsx +12 -12
- package/src/components/page-metadata/page-metadata.tsx +4 -2
- package/src/components/pagination/pagination.test.tsx +28 -30
- package/src/components/phase-banner/phase-banner.test.tsx +8 -8
- package/src/components/question/question.test.tsx +3 -3
- package/src/components/radio-button/radio-button.test.tsx +7 -7
- package/src/components/select/select.test.tsx +9 -9
- package/src/components/sequential-navigation/sequential-navigation.test.tsx +4 -4
- package/src/components/side-navigation/side-navigation.test.tsx +1 -1
- package/src/components/site-header/site-header.test.tsx +22 -22
- package/src/components/site-search/site-search.test.tsx +9 -9
- package/src/components/skip-links/skip-links.test.tsx +3 -3
- package/src/components/summary-card/summary-card.test.tsx +2 -2
- package/src/components/summary-list/summary-list.test.tsx +35 -16
- package/src/components/table/table.test.tsx +77 -0
- package/src/components/table/table.tsx +36 -0
- package/src/components/tabs/tabs.test.tsx +258 -0
- package/src/components/tabs/tabs.tsx +110 -0
- package/src/components/task-list/task-list.test.tsx +81 -83
- package/src/components/task-list/task-list.tsx +9 -5
- package/src/components/text-input/text-input.test.tsx +6 -6
- package/src/components/textarea/textarea.test.tsx +2 -2
- package/src/components/warning-text/warning-text.test.tsx +1 -1
- package/src/utils/slugify.ts +13 -0
- package/vite.config.ts +6 -1
- package/vitest-setup.ts +1 -1
- package/@types/components/NotificationBanner.d.ts +0 -9
|
@@ -37,33 +37,33 @@ test('site header renders correctly (maximal, testing markup structure)', () =>
|
|
|
37
37
|
const siteHeaderNavigationMobile = within(siteHeader).getAllByRole('navigation')[0];
|
|
38
38
|
const siteHeaderNavigationDesktop = within(siteHeader).getAllByRole('navigation')[1];
|
|
39
39
|
const siteHeaderPhaseBanner = siteHeader.querySelector('.ds_phase-banner');
|
|
40
|
-
const siteHeaderSearch = within(siteHeader).getByRole('search').
|
|
40
|
+
const siteHeaderSearch = within(siteHeader).getByRole('search').parentElement;
|
|
41
41
|
|
|
42
42
|
expect(siteHeader).toHaveClass('ds_site-header');
|
|
43
43
|
expect(siteHeaderContentWrapper).toHaveClass('ds_wrapper');
|
|
44
44
|
|
|
45
|
-
expect(siteHeaderBranding?.
|
|
46
|
-
expect(siteHeaderControls?.
|
|
47
|
-
expect(siteHeaderNavToggle?.
|
|
48
|
-
expect(siteHeaderNavigationMobile.
|
|
49
|
-
expect(siteHeaderSearch?.
|
|
45
|
+
expect(siteHeaderBranding?.parentElement).toEqual(siteHeaderContent);
|
|
46
|
+
expect(siteHeaderControls?.parentElement).toEqual(siteHeaderContent);
|
|
47
|
+
expect(siteHeaderNavToggle?.parentElement).toEqual(siteHeaderContent);
|
|
48
|
+
expect(siteHeaderNavigationMobile.parentElement).toEqual(siteHeaderContent);
|
|
49
|
+
expect(siteHeaderSearch?.parentElement).toEqual(siteHeaderContent);
|
|
50
50
|
|
|
51
|
-
expect(siteHeaderBranding?.
|
|
52
|
-
expect(siteHeaderControls?.
|
|
53
|
-
expect(siteHeaderNavToggle?.
|
|
54
|
-
expect(siteHeaderNavigationMobile.
|
|
55
|
-
expect(siteHeaderSearch?.
|
|
51
|
+
expect(siteHeaderBranding?.previousElementSibling).toBeNull();
|
|
52
|
+
expect(siteHeaderControls?.previousElementSibling).toEqual(siteHeaderBranding);
|
|
53
|
+
expect(siteHeaderNavToggle?.previousElementSibling).toEqual(siteHeaderControls);
|
|
54
|
+
expect(siteHeaderNavigationMobile.previousElementSibling).toEqual(siteHeaderNavToggle);
|
|
55
|
+
expect(siteHeaderSearch?.previousElementSibling).toEqual(siteHeaderNavigationMobile);
|
|
56
56
|
|
|
57
|
-
expect(siteHeaderNavigationDesktop.
|
|
58
|
-
expect(siteHeaderNavigationDesktop?.
|
|
57
|
+
expect(siteHeaderNavigationDesktop.parentElement).toHaveClass('ds_wrapper');
|
|
58
|
+
expect(siteHeaderNavigationDesktop?.parentElement?.parentElement).toHaveClass('ds_site-header__navigation');
|
|
59
59
|
|
|
60
|
-
expect(siteHeaderNavigationDesktop?.
|
|
60
|
+
expect(siteHeaderNavigationDesktop?.parentElement?.parentElement?.previousElementSibling).toEqual(siteHeaderPhaseBanner);
|
|
61
61
|
|
|
62
|
-
expect(siteHeaderPhaseBanner?.
|
|
62
|
+
expect(siteHeaderPhaseBanner?.previousElementSibling).toEqual(siteHeaderContentWrapper);
|
|
63
63
|
|
|
64
|
-
expect(siteHeaderPhaseBanner?.
|
|
65
|
-
expect(siteHeaderNavigationDesktop?.
|
|
66
|
-
expect(siteHeaderContentWrapper?.
|
|
64
|
+
expect(siteHeaderPhaseBanner?.parentElement).toEqual(siteHeader);
|
|
65
|
+
expect(siteHeaderNavigationDesktop?.parentElement?.parentElement?.parentElement).toEqual(siteHeader);
|
|
66
|
+
expect(siteHeaderContentWrapper?.parentElement).toEqual(siteHeader);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
test('site header branding: logo only, default URL', () => {
|
|
@@ -90,8 +90,8 @@ test('site header branding: logo only, default URL', () => {
|
|
|
90
90
|
expect(siteHeaderLogoImg).toHaveAttribute('src', logo.src);
|
|
91
91
|
expect(siteHeaderLogoImg).toHaveAttribute('alt', logo.alt);
|
|
92
92
|
|
|
93
|
-
expect(siteHeaderLogoImg.
|
|
94
|
-
expect(siteHeaderLogoLink.
|
|
93
|
+
expect(siteHeaderLogoImg.parentElement).toEqual(siteHeaderLogoLink);
|
|
94
|
+
expect(siteHeaderLogoLink.parentElement).toEqual(siteHeaderBranding);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
test('site header branding: logo and site title', () => {
|
|
@@ -115,8 +115,8 @@ test('site header branding: logo and site title', () => {
|
|
|
115
115
|
|
|
116
116
|
expect(siteTitle?.tagName).toEqual('DIV');
|
|
117
117
|
expect(siteTitle?.textContent).toEqual(siteTitleContent);
|
|
118
|
-
expect(siteTitle?.
|
|
119
|
-
expect(siteTitle?.
|
|
118
|
+
expect(siteTitle?.parentElement).toEqual(siteHeaderBranding);
|
|
119
|
+
expect(siteTitle?.previousElementSibling).toEqual(siteHeaderLogoLink);
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
test('site header branding: custom link URL', () => {
|
|
@@ -12,27 +12,27 @@ test('site search renders correctly', () => {
|
|
|
12
12
|
);
|
|
13
13
|
|
|
14
14
|
const searchForm = screen.getByRole('search');
|
|
15
|
-
const searchFormContainer = searchForm.
|
|
15
|
+
const searchFormContainer = searchForm.parentElement;
|
|
16
16
|
const searchLabel = document.querySelector('label');
|
|
17
17
|
const searchInput = within(searchForm).getByRole('searchbox');
|
|
18
|
-
const inputWrapper = searchInput.
|
|
18
|
+
const inputWrapper = searchInput.parentElement;
|
|
19
19
|
const searchButton = within(searchForm).getByRole('button');
|
|
20
20
|
|
|
21
21
|
expect(searchFormContainer).toHaveClass('ds_site-search');
|
|
22
|
-
expect(searchFormContainer
|
|
22
|
+
expect(searchFormContainer?.tagName).toEqual('DIV');
|
|
23
23
|
expect(searchFormContainer).not.toHaveAttribute('id', 'site-search-autocomplete');
|
|
24
24
|
|
|
25
25
|
expect(searchForm).toHaveClass('ds_site-search__form');
|
|
26
26
|
expect(searchForm).toHaveAttribute('method', 'GET');
|
|
27
27
|
expect(searchForm).toHaveAttribute('action', '/search');
|
|
28
28
|
|
|
29
|
-
expect(searchLabel
|
|
29
|
+
expect(searchLabel?.textContent).toEqual(labelText);
|
|
30
30
|
expect(searchLabel).toHaveAttribute('for', id);
|
|
31
31
|
expect(searchLabel).toHaveAttribute('id', `${id}-label`);
|
|
32
32
|
expect(searchLabel).toHaveClass('ds_label', 'visually-hidden');
|
|
33
33
|
|
|
34
34
|
expect(inputWrapper).toHaveClass('ds_input__wrapper ds_input__wrapper--has-icon');
|
|
35
|
-
expect(inputWrapper
|
|
35
|
+
expect(inputWrapper?.tagName).toEqual('DIV');
|
|
36
36
|
|
|
37
37
|
expect(searchInput).toHaveClass('ds_input', 'ds_site-search__input');
|
|
38
38
|
expect(searchInput).toHaveAttribute('id', id);
|
|
@@ -113,7 +113,7 @@ test('autocomplete', () => {
|
|
|
113
113
|
)
|
|
114
114
|
|
|
115
115
|
const searchForm = screen.getByRole('search');
|
|
116
|
-
const searchFormContainer = searchForm.
|
|
116
|
+
const searchFormContainer = searchForm.parentElement;
|
|
117
117
|
const searchInput = within(searchForm).getByRole('searchbox');
|
|
118
118
|
const autocompleteStatus = within(searchForm).getByRole('status');
|
|
119
119
|
const suggestionsContainer = document.querySelector('.ds_autocomplete__suggestions');
|
|
@@ -134,7 +134,7 @@ test('autocomplete', () => {
|
|
|
134
134
|
expect(searchInput).toHaveClass('js-autocomplete-input');
|
|
135
135
|
|
|
136
136
|
expect(suggestionsContainer).toHaveAttribute('id', suggestionsId);
|
|
137
|
-
expect(suggestionsContainer
|
|
137
|
+
expect(suggestionsContainer?.tagName).toEqual('DIV');
|
|
138
138
|
|
|
139
139
|
expect(suggestionsList).toHaveClass('ds_autocomplete__suggestions-list');
|
|
140
140
|
expect(suggestionsList).toHaveAttribute('aria-labelledby', `${id}-label`);
|
|
@@ -148,7 +148,7 @@ test('passing additional props', () => {
|
|
|
148
148
|
);
|
|
149
149
|
|
|
150
150
|
const searchForm = screen.getByRole('search');
|
|
151
|
-
const searchFormContainer = searchForm.
|
|
151
|
+
const searchFormContainer = searchForm.parentElement;
|
|
152
152
|
expect(searchFormContainer?.dataset.test).toEqual('foo');
|
|
153
153
|
});
|
|
154
154
|
|
|
@@ -158,6 +158,6 @@ test('passing additional CSS classes', () => {
|
|
|
158
158
|
);
|
|
159
159
|
|
|
160
160
|
const searchForm = screen.getByRole('search');
|
|
161
|
-
const searchFormContainer = searchForm.
|
|
161
|
+
const searchFormContainer = searchForm.parentElement;
|
|
162
162
|
expect(searchFormContainer).toHaveClass('foo', 'ds_site-search');
|
|
163
163
|
});
|
|
@@ -11,12 +11,12 @@ test('skip links renders correctly', () => {
|
|
|
11
11
|
);
|
|
12
12
|
|
|
13
13
|
const skipLinksList = screen.getByRole('list');
|
|
14
|
-
const skipLinksContainer = skipLinksList.
|
|
14
|
+
const skipLinksContainer = skipLinksList.parentElement;
|
|
15
15
|
const skipLinksListItem = within(skipLinksList).getByRole('listitem');
|
|
16
16
|
const skipLinksLink = within(skipLinksList).getByRole('link');
|
|
17
17
|
|
|
18
18
|
expect(skipLinksContainer).toHaveClass('ds_skip-links');
|
|
19
|
-
expect(skipLinksContainer
|
|
19
|
+
expect(skipLinksContainer?.tagName).toEqual('DIV');
|
|
20
20
|
|
|
21
21
|
expect(skipLinksList).toHaveClass('ds_skip-links__list');
|
|
22
22
|
expect(skipLinksList.tagName).toEqual('UL');
|
|
@@ -78,7 +78,7 @@ test('passing additional props', () => {
|
|
|
78
78
|
);
|
|
79
79
|
|
|
80
80
|
const skipLinksList = screen.getByRole('list');
|
|
81
|
-
const skipLinksContainer = skipLinksList.
|
|
81
|
+
const skipLinksContainer = skipLinksList.parentElement;
|
|
82
82
|
|
|
83
83
|
expect(skipLinksContainer?.dataset.test).toEqual('foo');
|
|
84
84
|
});
|
|
@@ -45,8 +45,8 @@ test('summary card renders correctly', () => {
|
|
|
45
45
|
|
|
46
46
|
const summaryCard = screen.getByTestId('foo');
|
|
47
47
|
const title = within(summaryCard).getByRole('heading');
|
|
48
|
-
const header = title.parentElement;
|
|
49
|
-
const content = header?.
|
|
48
|
+
const header = title.parentElement as HTMLElement;
|
|
49
|
+
const content = header?.nextElementSibling as HTMLElement;
|
|
50
50
|
const actionsList = within(header).getByRole('list')
|
|
51
51
|
|
|
52
52
|
const thisList = within(content).getByRole('list');
|
|
@@ -116,7 +116,7 @@ test('summary list item with multiple values', () => {
|
|
|
116
116
|
const item = screen.getAllByRole('listitem')[0];
|
|
117
117
|
const value = item.querySelector('.ds_summary-list__value');
|
|
118
118
|
const valueList = value?.children[0];
|
|
119
|
-
const valueListItems = valueList?.children;
|
|
119
|
+
const valueListItems = valueList?.children as HTMLCollection;
|
|
120
120
|
|
|
121
121
|
expect(valueList).toHaveClass('ds_no-bullets');
|
|
122
122
|
expect(valueList?.tagName).toEqual('UL');
|
|
@@ -143,31 +143,47 @@ test('summary list item with no value', () => {
|
|
|
143
143
|
});
|
|
144
144
|
|
|
145
145
|
test('summary list item with multiple actions', () => {
|
|
146
|
+
const title = 'Name';
|
|
147
|
+
const value = 'Jane Smith';
|
|
148
|
+
const actions = [
|
|
149
|
+
{
|
|
150
|
+
title: 'Change',
|
|
151
|
+
href: '#foo'
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
title: 'Delete',
|
|
155
|
+
onclick: onClickFn
|
|
156
|
+
}
|
|
157
|
+
];
|
|
158
|
+
|
|
146
159
|
render(
|
|
147
160
|
<Item
|
|
148
161
|
actions={items[0].actions}
|
|
149
|
-
title={
|
|
150
|
-
value={
|
|
162
|
+
title={title}
|
|
163
|
+
value={value}
|
|
151
164
|
/>
|
|
152
165
|
);
|
|
153
166
|
|
|
154
167
|
const item = screen.getAllByRole('listitem')[0];
|
|
155
|
-
const
|
|
168
|
+
const actionsElement = item.querySelector('.ds_summary-list__actions');
|
|
156
169
|
|
|
157
|
-
expect(
|
|
158
|
-
expect(
|
|
159
|
-
expect(
|
|
170
|
+
expect(actionsElement?.children.length).toEqual(actions.length);
|
|
171
|
+
expect(actionsElement?.children[0].textContent).toEqual(actions[0].title);
|
|
172
|
+
expect(actionsElement?.children[1].textContent).toEqual(actions[1].title);
|
|
160
173
|
});
|
|
161
174
|
|
|
162
175
|
test('button action', () => {
|
|
163
176
|
const describedById = 'q1-name';
|
|
177
|
+
const title = 'Name';
|
|
178
|
+
const href = undefined;
|
|
179
|
+
const onClick = onClickFn;
|
|
164
180
|
|
|
165
181
|
render(
|
|
166
182
|
<Action
|
|
167
183
|
describedby={describedById}
|
|
168
|
-
href={
|
|
169
|
-
onclick={
|
|
170
|
-
title={
|
|
184
|
+
href={href}
|
|
185
|
+
onclick={onClick}
|
|
186
|
+
title={title}
|
|
171
187
|
/>
|
|
172
188
|
);
|
|
173
189
|
|
|
@@ -178,7 +194,7 @@ test('button action', () => {
|
|
|
178
194
|
expect(action).toHaveAttribute('type', 'button');
|
|
179
195
|
expect(action).not.toHaveAttribute('href');
|
|
180
196
|
expect(action.tagName).toEqual('BUTTON');
|
|
181
|
-
expect(action.textContent).toEqual(
|
|
197
|
+
expect(action.textContent).toEqual(title);
|
|
182
198
|
|
|
183
199
|
fireEvent.click(action);
|
|
184
200
|
|
|
@@ -187,13 +203,16 @@ test('button action', () => {
|
|
|
187
203
|
|
|
188
204
|
test('link action', () => {
|
|
189
205
|
const describedById = 'q1-name';
|
|
206
|
+
const title = 'Name';
|
|
207
|
+
const href = "#foo"
|
|
208
|
+
const onClick = onClickFn;
|
|
190
209
|
|
|
191
210
|
render(
|
|
192
211
|
<Action
|
|
193
212
|
describedby={describedById}
|
|
194
|
-
href={
|
|
195
|
-
onclick={
|
|
196
|
-
title={
|
|
213
|
+
href={href}
|
|
214
|
+
onclick={onClick}
|
|
215
|
+
title={title}
|
|
197
216
|
/>
|
|
198
217
|
);
|
|
199
218
|
|
|
@@ -201,10 +220,10 @@ test('link action', () => {
|
|
|
201
220
|
|
|
202
221
|
expect(action).toHaveClass('ds_link');
|
|
203
222
|
expect(action).toHaveAttribute('aria-describedby', describedById);
|
|
204
|
-
expect(action).toHaveAttribute('href',
|
|
223
|
+
expect(action).toHaveAttribute('href', href);
|
|
205
224
|
expect(action).not.toHaveAttribute('type');
|
|
206
225
|
expect(action.tagName).toEqual('A');
|
|
207
|
-
expect(action.textContent).toEqual(
|
|
226
|
+
expect(action.textContent).toEqual(title);
|
|
208
227
|
});
|
|
209
228
|
|
|
210
229
|
test('multiline answer', () => {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import Table from './table';
|
|
4
|
+
|
|
5
|
+
test('table renders correctly', () => {
|
|
6
|
+
render(
|
|
7
|
+
<Table>
|
|
8
|
+
<caption>Public holidays in 2020</caption>
|
|
9
|
+
<thead>
|
|
10
|
+
<tr>
|
|
11
|
+
<th scope="col">Date</th>
|
|
12
|
+
<th scope="col">Day</th>
|
|
13
|
+
<th scope="col">Holiday</th>
|
|
14
|
+
</tr>
|
|
15
|
+
</thead>
|
|
16
|
+
<tbody>
|
|
17
|
+
<tr>
|
|
18
|
+
<td>10 April</td>
|
|
19
|
+
<td>Friday</td>
|
|
20
|
+
<td>Good Friday</td>
|
|
21
|
+
</tr>
|
|
22
|
+
</tbody>
|
|
23
|
+
</Table>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const table = screen.getByRole('table');
|
|
27
|
+
|
|
28
|
+
expect(table).toHaveClass('ds_table');
|
|
29
|
+
expect(table.nodeName).toEqual('TABLE');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('table with smallscreen behaviour', () => {
|
|
33
|
+
const behaviour = 'scrolling';
|
|
34
|
+
|
|
35
|
+
render(
|
|
36
|
+
<Table smallscreen={behaviour}>
|
|
37
|
+
<caption>Public holidays in 2020</caption>
|
|
38
|
+
<thead>
|
|
39
|
+
<tr>
|
|
40
|
+
<th scope="col">Date</th>
|
|
41
|
+
<th scope="col">Day</th>
|
|
42
|
+
<th scope="col">Holiday</th>
|
|
43
|
+
</tr>
|
|
44
|
+
</thead>
|
|
45
|
+
<tbody>
|
|
46
|
+
<tr>
|
|
47
|
+
<td>10 April</td>
|
|
48
|
+
<td>Friday</td>
|
|
49
|
+
<td>Good Friday</td>
|
|
50
|
+
</tr>
|
|
51
|
+
</tbody>
|
|
52
|
+
</Table>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const table = screen.getByRole('table');
|
|
56
|
+
|
|
57
|
+
expect(table).toHaveAttribute('data-smallscreen', behaviour);
|
|
58
|
+
expect(table.nodeName).toEqual('TABLE');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('passing additional props', () => {
|
|
62
|
+
render(
|
|
63
|
+
<Table data-test="foo"/>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const table = screen.getByRole('table');
|
|
67
|
+
expect(table?.dataset.test).toEqual('foo');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('table with additional CSS class', () => {
|
|
71
|
+
render(
|
|
72
|
+
<Table className="foo"/>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const table = screen.getByRole('table');
|
|
76
|
+
expect(table).toHaveClass('foo', 'ds_table');
|
|
77
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import DSTable from '@scottish-government/design-system/src/components/table/table';
|
|
4
|
+
|
|
5
|
+
const Table: React.FC<SGDS.Component.Table> = ({
|
|
6
|
+
children,
|
|
7
|
+
className,
|
|
8
|
+
smallscreen,
|
|
9
|
+
...props
|
|
10
|
+
}) => {
|
|
11
|
+
const ref = useRef(null);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (ref.current) {
|
|
15
|
+
new DSTable().init();
|
|
16
|
+
}
|
|
17
|
+
}, [ref]);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<table
|
|
21
|
+
className={[
|
|
22
|
+
'ds_table',
|
|
23
|
+
className
|
|
24
|
+
].join(' ')}
|
|
25
|
+
data-smallscreen={smallscreen}
|
|
26
|
+
ref={ref}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</table>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
Table.displayName = 'Table';
|
|
35
|
+
|
|
36
|
+
export default Table;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import { render, screen, within } from '@testing-library/react';
|
|
3
|
+
import Tabs from './tabs';
|
|
4
|
+
import slugify from '../../utils/slugify';
|
|
5
|
+
|
|
6
|
+
test('tab container renders correctly', () => {
|
|
7
|
+
render(
|
|
8
|
+
<Tabs data-testid="tabcontainer">
|
|
9
|
+
<Tabs.Item tabLabel="Tab 1" data-testid="tabpanel1">
|
|
10
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
11
|
+
</Tabs.Item>
|
|
12
|
+
<Tabs.Item tabLabel="Tab 2" data-testid="tabpanel2">
|
|
13
|
+
Content two
|
|
14
|
+
</Tabs.Item>
|
|
15
|
+
</Tabs>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const tabContainer = screen.getByTestId('tabcontainer');
|
|
19
|
+
const tabHeading = within(tabContainer).getByRole('heading');
|
|
20
|
+
const tabList = within(tabContainer).getByRole('tablist');
|
|
21
|
+
const tabListItems = within(tabList).getAllByRole('presentation');
|
|
22
|
+
const tabListLinkOne = within(tabListItems[0]).getByRole('tab');
|
|
23
|
+
const tabListLinkTwo = within(tabListItems[1]).getByRole('tab');
|
|
24
|
+
const tabPanels = document.querySelectorAll('.ds_tabs__content');
|
|
25
|
+
const tabPanelOne = screen.getByTestId('tabpanel1');
|
|
26
|
+
const tabPanelTwo = screen.getByTestId('tabpanel2');
|
|
27
|
+
|
|
28
|
+
expect(tabContainer).toHaveClass('ds_tabs');
|
|
29
|
+
expect(tabContainer.tagName).toEqual('DIV');
|
|
30
|
+
|
|
31
|
+
expect(tabHeading).toHaveClass('ds_tabs__title');
|
|
32
|
+
// default header level
|
|
33
|
+
expect(tabHeading.tagName).toEqual('H2');
|
|
34
|
+
expect(tabHeading.parentElement).toEqual(tabContainer);
|
|
35
|
+
// default ID-heading
|
|
36
|
+
expect(tabHeading).toHaveAttribute('id', 'tabs-heading');
|
|
37
|
+
|
|
38
|
+
expect(tabList).toHaveClass('ds_tabs__list');
|
|
39
|
+
expect(tabList.tagName).toEqual('UL');
|
|
40
|
+
expect(tabList.parentElement).toEqual(tabContainer);
|
|
41
|
+
expect(tabList.previousElementSibling).toEqual(tabHeading);
|
|
42
|
+
|
|
43
|
+
// number of items in the provided markup
|
|
44
|
+
expect(tabListItems.length).toEqual(2);
|
|
45
|
+
|
|
46
|
+
expect(tabListItems[0]).toHaveClass('ds_tabs__tab');
|
|
47
|
+
expect(tabListItems[0].tagName).toEqual('LI');
|
|
48
|
+
|
|
49
|
+
expect(tabListLinkOne).toHaveClass('ds_tabs__tab-link');
|
|
50
|
+
expect(tabListLinkOne).toHaveAttribute('href', `#${tabPanelOne.id}`);
|
|
51
|
+
expect(tabListLinkTwo).toHaveAttribute('href', `#${tabPanelTwo.id}`);
|
|
52
|
+
// text content in provided markup
|
|
53
|
+
expect(tabListLinkOne.textContent).toEqual('Tab 1');
|
|
54
|
+
expect(tabListLinkTwo.textContent).toEqual('Tab 2');
|
|
55
|
+
|
|
56
|
+
// number of items in the provided markup
|
|
57
|
+
expect(tabPanels.length).toEqual(2)
|
|
58
|
+
|
|
59
|
+
expect(tabPanelOne).toHaveClass('ds_tabs__content', 'ds_tabs__content--bordered');
|
|
60
|
+
|
|
61
|
+
// todo: this will be true after the tab script has been updated in the core DS
|
|
62
|
+
//expect(tabPanelOne.tabIndex).toEqual(0);
|
|
63
|
+
|
|
64
|
+
// tab panels have ID
|
|
65
|
+
expect(tabPanelOne).toHaveAttribute('id');
|
|
66
|
+
expect(tabPanelTwo).toHaveAttribute('id');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('non-bordered tabs', () => {
|
|
70
|
+
render(
|
|
71
|
+
<Tabs data-testid="tabcontainer" bordered={false}>
|
|
72
|
+
<Tabs.Item tabLabel="Tab 1" data-testid="tabpanel1">
|
|
73
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
74
|
+
</Tabs.Item>
|
|
75
|
+
<Tabs.Item tabLabel="Tab 2" data-testid="tabpanel2">
|
|
76
|
+
Content two
|
|
77
|
+
</Tabs.Item>
|
|
78
|
+
</Tabs>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const tabPanelOne = screen.getByTestId('tabpanel1');
|
|
82
|
+
expect(tabPanelOne).not.toHaveClass('ds_tabs__content--bordered');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('custom title', () => {
|
|
86
|
+
const title = 'My title';
|
|
87
|
+
|
|
88
|
+
render(
|
|
89
|
+
<Tabs data-testid="tabcontainer" title={title}>
|
|
90
|
+
<Tabs.Item tabLabel="Tab 1" data-testid="tabpanel1">
|
|
91
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
92
|
+
</Tabs.Item>
|
|
93
|
+
<Tabs.Item tabLabel="Tab 2" data-testid="tabpanel2">
|
|
94
|
+
Content two
|
|
95
|
+
</Tabs.Item>
|
|
96
|
+
</Tabs>
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const tabContainer = screen.getByTestId('tabcontainer');
|
|
100
|
+
const tabList = within(tabContainer).getByRole('tablist');
|
|
101
|
+
const tabHeading = within(tabContainer).getByRole('heading');
|
|
102
|
+
|
|
103
|
+
expect(tabList).toHaveAttribute('aria-labelledby', tabHeading.id);
|
|
104
|
+
expect(tabHeading.textContent).toEqual(title)
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('custom base ID', () => {
|
|
108
|
+
const baseId = 'foo';
|
|
109
|
+
|
|
110
|
+
render(
|
|
111
|
+
<Tabs data-testid="tabcontainer" baseId={baseId}>
|
|
112
|
+
<Tabs.Item tabLabel="Tab 1" data-testid="tabpanel1">
|
|
113
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
114
|
+
</Tabs.Item>
|
|
115
|
+
<Tabs.Item tabLabel="Tab 2" data-testid="tabpanel2">
|
|
116
|
+
Content two
|
|
117
|
+
</Tabs.Item>
|
|
118
|
+
</Tabs>
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const tabContainer = screen.getByTestId('tabcontainer');
|
|
122
|
+
const tabHeading = within(tabContainer).getByRole('heading');
|
|
123
|
+
|
|
124
|
+
expect(tabHeading).toHaveAttribute('id', `${baseId}-heading`);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('custom header level', () => {
|
|
128
|
+
const headerLevel = 'h3';
|
|
129
|
+
|
|
130
|
+
render(
|
|
131
|
+
<Tabs data-testid="tabcontainer" headerLevel={headerLevel}>
|
|
132
|
+
<Tabs.Item tabLabel="Tab 1" data-testid="tabpanel1">
|
|
133
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
134
|
+
</Tabs.Item>
|
|
135
|
+
<Tabs.Item tabLabel="Tab 2" data-testid="tabpanel2">
|
|
136
|
+
Content two
|
|
137
|
+
</Tabs.Item>
|
|
138
|
+
</Tabs>
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const tabContainer = screen.getByTestId('tabcontainer');
|
|
142
|
+
const tabHeading = within(tabContainer).getByRole('heading');
|
|
143
|
+
|
|
144
|
+
expect(tabHeading.tagName).toEqual(headerLevel.toUpperCase());
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('custom baseID', () => {
|
|
148
|
+
const baseId = 'myId';
|
|
149
|
+
|
|
150
|
+
render(
|
|
151
|
+
<Tabs data-testid="tabcontainer" baseId={baseId}>
|
|
152
|
+
<Tabs.Item tabLabel="Tab 1" data-testid="tabpanel1">
|
|
153
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
154
|
+
</Tabs.Item>
|
|
155
|
+
<Tabs.Item tabLabel="Tab 2" data-testid="tabpanel2">
|
|
156
|
+
Content two
|
|
157
|
+
</Tabs.Item>
|
|
158
|
+
</Tabs>
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const tabContainer = screen.getByTestId('tabcontainer');
|
|
162
|
+
const tabHeading = within(tabContainer).getByRole('heading');
|
|
163
|
+
const tabPanelOne = screen.getByTestId('tabpanel1');
|
|
164
|
+
const tabPanelTwo = screen.getByTestId('tabpanel2');
|
|
165
|
+
|
|
166
|
+
// default title slugified to part of the ID
|
|
167
|
+
expect(tabHeading).toHaveAttribute('id', `${baseId}-heading`);
|
|
168
|
+
// generated IDs using the slug of the tab title
|
|
169
|
+
expect(tabPanelOne).toHaveAttribute('id', `${baseId}-tab-1`);
|
|
170
|
+
expect(tabPanelTwo).toHaveAttribute('id', `${baseId}-tab-2`);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('tab with and without specific ID attribute', () => {
|
|
174
|
+
const baseId = 'foo'
|
|
175
|
+
const tabLabel = 'Tab 1';
|
|
176
|
+
const id = 'bar';
|
|
177
|
+
|
|
178
|
+
render(
|
|
179
|
+
<Tabs baseId={baseId}>
|
|
180
|
+
<Tabs.Item tabLabel={tabLabel} data-testid="tabpanel1">
|
|
181
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
182
|
+
</Tabs.Item>
|
|
183
|
+
<Tabs.Item tabLabel="Tab 2" id={id} data-testid="tabpanel2">
|
|
184
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
185
|
+
</Tabs.Item>
|
|
186
|
+
</Tabs>
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const tabPanelOne = screen.getByTestId('tabpanel1');
|
|
190
|
+
const tabPanelTwo = screen.getByTestId('tabpanel2');
|
|
191
|
+
|
|
192
|
+
expect(tabPanelOne).toHaveAttribute('id', `${baseId}-${slugify(tabLabel)}`);
|
|
193
|
+
expect(tabPanelTwo).toHaveAttribute('id', id);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('with manual activation', () => {
|
|
197
|
+
render(
|
|
198
|
+
<Tabs data-testid="tabcontainer" manual>
|
|
199
|
+
<Tabs.Item title="Tab 1" data-testid="tabpanel1">
|
|
200
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
201
|
+
</Tabs.Item>
|
|
202
|
+
<Tabs.Item title="Tab 2" data-testid="tabpanel2">
|
|
203
|
+
Content two
|
|
204
|
+
</Tabs.Item>
|
|
205
|
+
</Tabs>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const tabContainer = screen.getByTestId('tabcontainer');
|
|
209
|
+
expect(tabContainer).toHaveClass('ds_tabs--manual');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('passing additional props to tab container', () => {
|
|
213
|
+
render(
|
|
214
|
+
<Tabs data-testid="tabcontainer" data-test="foo">
|
|
215
|
+
<Tabs.Item tabLabel="Tab 1" data-testid="tabpanel1">
|
|
216
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
217
|
+
</Tabs.Item>
|
|
218
|
+
</Tabs>
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const tabContainer = screen.getByTestId('tabcontainer');
|
|
222
|
+
expect(tabContainer.dataset.test).toEqual('foo');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('passing additional CSS classes tab container', () => {
|
|
226
|
+
render(
|
|
227
|
+
<Tabs data-testid="tabcontainer" className="foo">
|
|
228
|
+
<Tabs.Item tabLabel="Tab 1" data-testid="tabpanel1">
|
|
229
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
230
|
+
</Tabs.Item>
|
|
231
|
+
</Tabs>
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const tabContainer = screen.getByTestId('tabcontainer');
|
|
235
|
+
expect(tabContainer).toHaveClass('foo');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('passing additional props to tab panel', () => {
|
|
239
|
+
render(
|
|
240
|
+
<Tabs.Item tabLabel="Tab 1" data-testid="tabpanel1" data-test="foo">
|
|
241
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
242
|
+
</Tabs.Item>
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const tabItem = screen.getByTestId('tabpanel1');
|
|
246
|
+
expect(tabItem.dataset.test).toEqual('foo');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('passing additional CSS classes tab panel', () => {
|
|
250
|
+
render(
|
|
251
|
+
<Tabs.Item tabLabel="Tab 1" data-testid="tabpanel1" className="foo">
|
|
252
|
+
<div data-testid="tabpanel1content">Content one</div>
|
|
253
|
+
</Tabs.Item>
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const tabItem = screen.getByTestId('tabpanel1');
|
|
257
|
+
expect(tabItem).toHaveClass('foo');
|
|
258
|
+
});
|