@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,409 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import { render, screen, within } from '@testing-library/react';
|
|
3
|
+
import TaskList, {Task, TaskGroup} from './task-list';
|
|
4
|
+
|
|
5
|
+
const taskListHeadingText = 'Application incomplete';
|
|
6
|
+
|
|
7
|
+
const tasks = [
|
|
8
|
+
{ id: 'task-conditions', statusText: 'Cannot start yet', title: 'Conditions' },
|
|
9
|
+
{ id: 'task-medications', statusText: 'Cannot start yet', title: 'Medications' },
|
|
10
|
+
{ id: 'task-contacts', statusText: 'Cannot start yet', title: 'Contacts and supporting information' },
|
|
11
|
+
];
|
|
12
|
+
const taskItem = {
|
|
13
|
+
id: 'task-conditions',
|
|
14
|
+
statusText: 'Cannot start yet',
|
|
15
|
+
title: 'Conditions',
|
|
16
|
+
href: '#foo'
|
|
17
|
+
};
|
|
18
|
+
const taskSummaryContent = 'Tell us about your conditions, symptoms and any sensory issues you have.';
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
test('task list renders correctly', () => {
|
|
22
|
+
render(
|
|
23
|
+
<TaskList
|
|
24
|
+
title={taskListHeadingText}
|
|
25
|
+
>
|
|
26
|
+
<Task
|
|
27
|
+
id={tasks[0].id}
|
|
28
|
+
statusText={tasks[0].statusText}
|
|
29
|
+
title={tasks[0].title}
|
|
30
|
+
>
|
|
31
|
+
Tell us about your conditions, symptoms and any sensory issues you have.
|
|
32
|
+
</Task>
|
|
33
|
+
<Task
|
|
34
|
+
id={tasks[1].id}
|
|
35
|
+
statusText={tasks[1].statusText}
|
|
36
|
+
title={tasks[1].title}
|
|
37
|
+
>
|
|
38
|
+
Tell us about any medication you need.
|
|
39
|
+
</Task>
|
|
40
|
+
<Task
|
|
41
|
+
id={tasks[2].id}
|
|
42
|
+
statusText={tasks[2].statusText}
|
|
43
|
+
title={tasks[2].title}
|
|
44
|
+
>
|
|
45
|
+
Share any supporting documents and provide details of people we can talk to about you.
|
|
46
|
+
</Task>
|
|
47
|
+
</TaskList>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const taskList = screen.getByRole('list');
|
|
51
|
+
const taskListStatus = screen.getByRole('navigation');
|
|
52
|
+
const taskListStatus1 = taskListStatus.children[0];
|
|
53
|
+
const taskListStatus2 = taskListStatus.children[1];
|
|
54
|
+
|
|
55
|
+
const taskListStatusLink = within(taskListStatus).getByRole('link');
|
|
56
|
+
const taskListHeading = screen.getAllByRole('heading')[0];
|
|
57
|
+
|
|
58
|
+
expect(taskListHeading).toHaveClass('ds_task-list-status-heading');
|
|
59
|
+
expect(taskListHeading).toHaveAttribute('id', 'task-list-status');
|
|
60
|
+
expect(taskListHeading.textContent).toEqual(taskListHeadingText);
|
|
61
|
+
|
|
62
|
+
expect(taskListStatus).toHaveClass('ds_task-list-status');
|
|
63
|
+
expect(taskListStatus).toHaveAttribute('aria-labelledby', taskListHeading.id);
|
|
64
|
+
|
|
65
|
+
expect(taskListStatus1.textContent).toEqual('You have completed 0 of 3 sections.');
|
|
66
|
+
expect(taskListStatus1.tagName).toEqual('P');
|
|
67
|
+
expect(taskListStatus2.tagName).toEqual('P');
|
|
68
|
+
expect(taskListStatusLink.tagName).toEqual('A');
|
|
69
|
+
expect(taskListStatusLink.textContent).toEqual('Skip to first incomplete section');
|
|
70
|
+
expect(taskListStatusLink).toHaveAttribute('href', `#${tasks[0].id}`);
|
|
71
|
+
|
|
72
|
+
expect(taskList).toHaveClass('ds_task-list');
|
|
73
|
+
expect(taskList.tagName).toEqual('UL');
|
|
74
|
+
expect(taskList.children.length).toEqual(tasks.length);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('task list with custom ID', () => {
|
|
78
|
+
const headingId = 'my-id';
|
|
79
|
+
render(
|
|
80
|
+
<TaskList
|
|
81
|
+
title={taskListHeadingText}
|
|
82
|
+
headingId={headingId}
|
|
83
|
+
>
|
|
84
|
+
</TaskList>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const taskListStatus = screen.getByRole('navigation');
|
|
88
|
+
const taskListHeading = screen.getAllByRole('heading')[0];
|
|
89
|
+
|
|
90
|
+
expect(taskListHeading).toHaveAttribute('id', `${headingId}-status`);
|
|
91
|
+
expect(taskListStatus).toHaveAttribute('aria-labelledby', taskListHeading.id);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('task list with completed tasks', () => {
|
|
95
|
+
render(
|
|
96
|
+
<TaskList
|
|
97
|
+
title={taskListHeadingText}
|
|
98
|
+
>
|
|
99
|
+
<Task
|
|
100
|
+
id={tasks[0].id}
|
|
101
|
+
statusText={tasks[0].statusText}
|
|
102
|
+
title={tasks[0].title}
|
|
103
|
+
isComplete
|
|
104
|
+
>
|
|
105
|
+
Tell us about your conditions, symptoms and any sensory issues you have.
|
|
106
|
+
</Task>
|
|
107
|
+
<Task
|
|
108
|
+
id={tasks[1].id}
|
|
109
|
+
statusText={tasks[1].statusText}
|
|
110
|
+
title={tasks[1].title}
|
|
111
|
+
isComplete
|
|
112
|
+
>
|
|
113
|
+
Tell us about any medication you need.
|
|
114
|
+
</Task>
|
|
115
|
+
<Task
|
|
116
|
+
id={tasks[2].id}
|
|
117
|
+
statusText={tasks[2].statusText}
|
|
118
|
+
title={tasks[2].title}
|
|
119
|
+
>
|
|
120
|
+
Share any supporting documents and provide details of people we can talk to about you.
|
|
121
|
+
</Task>
|
|
122
|
+
</TaskList>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const taskListStatus = screen.getByRole('navigation');
|
|
126
|
+
const taskListStatus1 = taskListStatus.children[0];
|
|
127
|
+
|
|
128
|
+
const taskListStatusLink = within(taskListStatus).getByRole('link');
|
|
129
|
+
|
|
130
|
+
expect(taskListStatus1.textContent).toEqual('You have completed 2 of 3 sections.');
|
|
131
|
+
expect(taskListStatusLink).toHaveAttribute('href', `#${tasks[2].id}`);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('task renders correctly', () => {
|
|
135
|
+
render(
|
|
136
|
+
<Task
|
|
137
|
+
id={taskItem.id}
|
|
138
|
+
statusText={taskItem.statusText}
|
|
139
|
+
title={taskItem.title}
|
|
140
|
+
>
|
|
141
|
+
{taskSummaryContent}
|
|
142
|
+
</Task>
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const task = screen.getByRole('listitem');
|
|
146
|
+
const tag = document.querySelector('.ds_tag');
|
|
147
|
+
const taskHeading = within(task).getByRole('heading');
|
|
148
|
+
const taskDetails = taskHeading.parentNode;
|
|
149
|
+
const taskSummary = taskHeading.nextSibling;
|
|
150
|
+
|
|
151
|
+
expect(task).toHaveClass('ds_task-list__task');
|
|
152
|
+
expect(task).toHaveAttribute('id', taskItem.id);
|
|
153
|
+
|
|
154
|
+
expect(taskDetails).toHaveClass('ds_task-list__task-details');
|
|
155
|
+
expect(taskDetails.parentNode).toEqual(task);
|
|
156
|
+
expect(taskDetails.tagName).toEqual('DIV');
|
|
157
|
+
|
|
158
|
+
expect(taskHeading).toHaveClass('ds_task-list__task-heading');
|
|
159
|
+
expect(taskHeading.tagName).toEqual('H3');
|
|
160
|
+
expect(taskHeading.textContent).toEqual(`${taskItem.title}(${taskItem.statusText})`);
|
|
161
|
+
|
|
162
|
+
expect(taskSummary).toHaveClass('ds_task-list__task-summary');
|
|
163
|
+
expect(taskSummary.tagName).toEqual('P');
|
|
164
|
+
expect(taskSummary.textContent).toEqual(taskSummaryContent);
|
|
165
|
+
|
|
166
|
+
expect(tag).toHaveAttribute('aria-hidden', 'true');
|
|
167
|
+
expect(tag.textContent).toEqual(taskItem.statusText);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('task with link', () => {
|
|
171
|
+
const href = '#foo';
|
|
172
|
+
|
|
173
|
+
render(
|
|
174
|
+
<Task
|
|
175
|
+
id={taskItem.id}
|
|
176
|
+
statusText={taskItem.statusText}
|
|
177
|
+
title={taskItem.title}
|
|
178
|
+
href={href}
|
|
179
|
+
>
|
|
180
|
+
{taskSummaryContent}
|
|
181
|
+
</Task>
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const task = screen.getByRole('listitem');
|
|
185
|
+
const taskHeading = within(task).getByRole('heading');
|
|
186
|
+
const link = within(task).getByRole('link');
|
|
187
|
+
|
|
188
|
+
expect(link).toHaveClass('ds_task-list__task-link');
|
|
189
|
+
expect(link).toHaveAttribute('href', href);
|
|
190
|
+
expect(link.textContent).toEqual(`${taskItem.title}(${taskItem.statusText})`);
|
|
191
|
+
expect(link.parentNode).toEqual(taskHeading);
|
|
192
|
+
expect(link.textContent).toEqual(taskHeading.textContent);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('completed task has green tag', () => {
|
|
196
|
+
render(
|
|
197
|
+
<Task
|
|
198
|
+
id={taskItem.id}
|
|
199
|
+
statusText={taskItem.statusText}
|
|
200
|
+
title={taskItem.title}
|
|
201
|
+
isComplete
|
|
202
|
+
>
|
|
203
|
+
{taskSummaryContent}
|
|
204
|
+
</Task>
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const tag = document.querySelector('.ds_tag');
|
|
208
|
+
|
|
209
|
+
expect(tag).toHaveClass('ds_tag--green');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('specific tag colour', () => {
|
|
213
|
+
const tagColour = 'red';
|
|
214
|
+
|
|
215
|
+
render(
|
|
216
|
+
<Task
|
|
217
|
+
id={taskItem.id}
|
|
218
|
+
statusText={taskItem.statusText}
|
|
219
|
+
title={taskItem.title}
|
|
220
|
+
tagColour={tagColour}
|
|
221
|
+
>
|
|
222
|
+
{taskSummaryContent}
|
|
223
|
+
</Task>
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const tag = document.querySelector('.ds_tag');
|
|
227
|
+
|
|
228
|
+
expect(tag).toHaveClass(`ds_tag--${tagColour}`);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('no status tag when no status text provided', () => {
|
|
232
|
+
render(
|
|
233
|
+
<Task
|
|
234
|
+
id={taskItem.id}
|
|
235
|
+
title={taskItem.title}
|
|
236
|
+
>
|
|
237
|
+
{taskSummaryContent}
|
|
238
|
+
</Task>
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const tag = document.querySelector('.ds_tag');
|
|
242
|
+
|
|
243
|
+
expect(tag).toBeNull();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('task group renders correctly', () => {
|
|
247
|
+
const taskGroupHeadingText = 'Provide your health details';
|
|
248
|
+
|
|
249
|
+
render(
|
|
250
|
+
<TaskGroup title={taskGroupHeadingText}>
|
|
251
|
+
<Task
|
|
252
|
+
id={tasks[0].id}
|
|
253
|
+
statusText={tasks[0].statusText}
|
|
254
|
+
title={tasks[0].title}
|
|
255
|
+
>
|
|
256
|
+
Tell us about your conditions, symptoms and any sensory issues you have.
|
|
257
|
+
</Task>
|
|
258
|
+
<Task
|
|
259
|
+
id={tasks[1].id}
|
|
260
|
+
statusText={tasks[1].statusText}
|
|
261
|
+
title={tasks[1].title}
|
|
262
|
+
>
|
|
263
|
+
Tell us about any medication you need.
|
|
264
|
+
</Task>
|
|
265
|
+
<Task
|
|
266
|
+
id={tasks[2].id}
|
|
267
|
+
statusText={tasks[2].statusText}
|
|
268
|
+
title={tasks[2].title}
|
|
269
|
+
>
|
|
270
|
+
Share any supporting documents and provide details of people we can talk to about you.
|
|
271
|
+
</Task>
|
|
272
|
+
</TaskGroup>
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const taskGroup = document.querySelector('.ds_task-list-group__section');
|
|
276
|
+
const taskGroupHeading = taskGroup.querySelector('.ds_task-list-heading');
|
|
277
|
+
const innerTaskList = taskGroup.querySelector('.ds_task-list');
|
|
278
|
+
|
|
279
|
+
expect(taskGroup).toHaveClass('ds_task-list-group__section');
|
|
280
|
+
expect(taskGroup.tagName).toEqual('LI');
|
|
281
|
+
|
|
282
|
+
expect(taskGroupHeading.tagName).toEqual('H2');
|
|
283
|
+
expect(taskGroupHeading.textContent).toEqual(taskGroupHeadingText);
|
|
284
|
+
|
|
285
|
+
// a bit hacky, but list should be last child
|
|
286
|
+
expect(taskGroup.children[taskGroup.children.length - 1]).toEqual(innerTaskList);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('task group with intro text', () => {
|
|
290
|
+
const taskGroupIntroText = 'This information will be used to check what additional benefits you may be able to apply for.';
|
|
291
|
+
|
|
292
|
+
render(
|
|
293
|
+
<TaskGroup title="Provide your health details" intro={taskGroupIntroText}>
|
|
294
|
+
<Task
|
|
295
|
+
id={tasks[0].id}
|
|
296
|
+
statusText={tasks[0].statusText}
|
|
297
|
+
title={tasks[0].title}
|
|
298
|
+
>
|
|
299
|
+
Tell us about your conditions, symptoms and any sensory issues you have.
|
|
300
|
+
</Task>
|
|
301
|
+
<Task
|
|
302
|
+
id={tasks[1].id}
|
|
303
|
+
statusText={tasks[1].statusText}
|
|
304
|
+
title={tasks[1].title}
|
|
305
|
+
>
|
|
306
|
+
Tell us about any medication you need.
|
|
307
|
+
</Task>
|
|
308
|
+
<Task
|
|
309
|
+
id={tasks[2].id}
|
|
310
|
+
statusText={tasks[2].statusText}
|
|
311
|
+
title={tasks[2].title}
|
|
312
|
+
>
|
|
313
|
+
Share any supporting documents and provide details of people we can talk to about you.
|
|
314
|
+
</Task>
|
|
315
|
+
</TaskGroup>
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const taskGroup = document.querySelector('.ds_task-list-group__section');
|
|
319
|
+
const taskGroupHeading = taskGroup?.querySelector('.ds_task-list-heading');
|
|
320
|
+
const taskGroupIntro = taskGroup?.querySelector('.ds_task-list-intro');
|
|
321
|
+
|
|
322
|
+
expect(taskGroupIntro?.textContent).toEqual(taskGroupIntroText);
|
|
323
|
+
expect(taskGroupIntro?.previousSibling).toEqual(taskGroupHeading);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('task list counts completed items from all groups for its status text and the first incomplete section wraps to the next group sensibly', () => {
|
|
327
|
+
render(
|
|
328
|
+
<TaskList>
|
|
329
|
+
<TaskGroup title="Provide your health details">
|
|
330
|
+
<Task
|
|
331
|
+
id={tasks[0].id}
|
|
332
|
+
statusText={tasks[0].statusText}
|
|
333
|
+
title={tasks[0].title}
|
|
334
|
+
isComplete
|
|
335
|
+
>
|
|
336
|
+
Tell us about your conditions, symptoms and any sensory issues you have.
|
|
337
|
+
</Task>
|
|
338
|
+
<Task
|
|
339
|
+
id={tasks[1].id}
|
|
340
|
+
statusText={tasks[1].statusText}
|
|
341
|
+
title={tasks[1].title}
|
|
342
|
+
isComplete
|
|
343
|
+
>
|
|
344
|
+
Tell us about any medication you need.
|
|
345
|
+
</Task>
|
|
346
|
+
<Task
|
|
347
|
+
id={tasks[2].id}
|
|
348
|
+
statusText={tasks[2].statusText}
|
|
349
|
+
title={tasks[2].title}
|
|
350
|
+
isComplete
|
|
351
|
+
>
|
|
352
|
+
Share any supporting documents and provide details of people we can talk to about you.
|
|
353
|
+
</Task>
|
|
354
|
+
</TaskGroup>
|
|
355
|
+
<TaskGroup title="Provide your health details">
|
|
356
|
+
<Task
|
|
357
|
+
id={tasks[0].id + '2'}
|
|
358
|
+
statusText={tasks[0].statusText}
|
|
359
|
+
title={tasks[0].title}
|
|
360
|
+
>
|
|
361
|
+
Tell us about your conditions, symptoms and any sensory issues you have.
|
|
362
|
+
</Task>
|
|
363
|
+
<Task
|
|
364
|
+
id={tasks[1].id + '2'}
|
|
365
|
+
statusText={tasks[1].statusText}
|
|
366
|
+
title={tasks[1].title}
|
|
367
|
+
>
|
|
368
|
+
Tell us about any medication you need.
|
|
369
|
+
</Task>
|
|
370
|
+
<Task
|
|
371
|
+
id={tasks[2].id + '2'}
|
|
372
|
+
statusText={tasks[2].statusText}
|
|
373
|
+
title={tasks[2].title}
|
|
374
|
+
isComplete
|
|
375
|
+
>
|
|
376
|
+
Share any supporting documents and provide details of people we can talk to about you.
|
|
377
|
+
</Task>
|
|
378
|
+
</TaskGroup>
|
|
379
|
+
</TaskList>
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
const taskListStatus = screen.getByRole('navigation');
|
|
383
|
+
const taskListStatus1 = taskListStatus.children[0];
|
|
384
|
+
|
|
385
|
+
const taskListStatusLink = within(taskListStatus).getByRole('link');
|
|
386
|
+
|
|
387
|
+
expect(taskListStatus1.textContent).toEqual('You have completed 4 of 6 sections.');
|
|
388
|
+
expect(taskListStatusLink).toHaveAttribute('href', `#${tasks[0].id + '2'}`);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('passing additional props to task group', () => {
|
|
392
|
+
render(
|
|
393
|
+
<TaskGroup data-test="foo">
|
|
394
|
+
</TaskGroup>
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const taskGroup = document.querySelector('.ds_task-list-group__section');
|
|
398
|
+
expect(taskGroup?.dataset.test).toEqual('foo');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test('passing additional props to task', () => {
|
|
402
|
+
render(
|
|
403
|
+
<Task data-test="foo">
|
|
404
|
+
</Task>
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const task = document.querySelector('.ds_task-list__task');
|
|
408
|
+
expect(task?.dataset.test).toEqual('foo');
|
|
409
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Children } from 'react';
|
|
2
|
+
import ConditionalWrapper from '../../common/conditional-wrapper';
|
|
3
|
+
import HintText from '../../common/hint-text';
|
|
4
|
+
import ScreenReaderText from '../../common/screen-reader-text';
|
|
5
|
+
import Tag from '../tag/tag';
|
|
6
|
+
|
|
7
|
+
export const Task: React.FC<SGDS.Component.TaskList.Task> = ({
|
|
8
|
+
children,
|
|
9
|
+
href,
|
|
10
|
+
id,
|
|
11
|
+
isComplete = false,
|
|
12
|
+
statusText,
|
|
13
|
+
tagColour = 'grey',
|
|
14
|
+
title,
|
|
15
|
+
...props
|
|
16
|
+
}) => {
|
|
17
|
+
if (isComplete) {
|
|
18
|
+
tagColour = 'green';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<li
|
|
23
|
+
className="ds_task-list__task"
|
|
24
|
+
id={id}
|
|
25
|
+
{...props}
|
|
26
|
+
>
|
|
27
|
+
<div className="ds_task-list__task-details">
|
|
28
|
+
<h3 className="ds_task-list__task-heading">
|
|
29
|
+
<ConditionalWrapper
|
|
30
|
+
condition={typeof href !== 'undefined'}
|
|
31
|
+
wrapper={(children: React.JSX.Element) => <a className="ds_task-list__task-link" href={href}>{children}</a>}
|
|
32
|
+
>
|
|
33
|
+
{title}
|
|
34
|
+
{statusText && <ScreenReaderText>({statusText})</ScreenReaderText>}
|
|
35
|
+
</ConditionalWrapper>
|
|
36
|
+
</h3>
|
|
37
|
+
<HintText className="ds_task-list__task-summary">{children}</HintText>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
{typeof statusText !== 'undefined' &&
|
|
41
|
+
<Tag
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
colour={tagColour}
|
|
44
|
+
title={statusText}
|
|
45
|
+
/>
|
|
46
|
+
}
|
|
47
|
+
</li>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {Object} props
|
|
53
|
+
* @param {string} props.intro - Intro text
|
|
54
|
+
* @param {string} props.title - The title of the task group
|
|
55
|
+
* @returns {JSX.Element} - The element
|
|
56
|
+
*/
|
|
57
|
+
export const TaskGroup: React.FC<SGDS.Component.TaskList.Group> = ({
|
|
58
|
+
children,
|
|
59
|
+
intro,
|
|
60
|
+
title,
|
|
61
|
+
...props
|
|
62
|
+
}) => {
|
|
63
|
+
return (
|
|
64
|
+
<li
|
|
65
|
+
className="ds_task-list-group__section"
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
<h2 className="ds_task-list-heading">{title}</h2>
|
|
69
|
+
{intro && <p className="ds_task-list-intro">{intro}</p>}
|
|
70
|
+
<ul className="ds_task-list">
|
|
71
|
+
{children}
|
|
72
|
+
</ul>
|
|
73
|
+
</li>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const TaskList: React.FC<SGDS.Component.TaskList> = ({
|
|
78
|
+
children,
|
|
79
|
+
headingId = 'task-list',
|
|
80
|
+
title
|
|
81
|
+
}) => {
|
|
82
|
+
let taskCount = 0;
|
|
83
|
+
let incompleteTaskIds: string[] = [];
|
|
84
|
+
let completedTasksCount = 0;
|
|
85
|
+
|
|
86
|
+
function processChild(item: any) {
|
|
87
|
+
if (item.type.displayName === 'Task') {
|
|
88
|
+
taskCount = taskCount + 1;
|
|
89
|
+
|
|
90
|
+
if (item.props.isComplete) {
|
|
91
|
+
completedTasksCount = completedTasksCount + 1;
|
|
92
|
+
} else {
|
|
93
|
+
incompleteTaskIds.push(item.props.id);
|
|
94
|
+
}
|
|
95
|
+
} else if (item.type.displayName === 'TaskGroup') {
|
|
96
|
+
Children.forEach(item.props.children, child => {
|
|
97
|
+
processChild(child);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function firstIncompleteTaskLink() {
|
|
103
|
+
if (incompleteTaskIds.length > 0) {
|
|
104
|
+
return (
|
|
105
|
+
<p><a href={'#' + incompleteTaskIds[0]} className="js-task-list-skip-link">Skip to first incomplete section</a></p>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Children.forEach(children, child => {
|
|
111
|
+
processChild(child);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<>
|
|
116
|
+
<h2 id={`${headingId}-status`} className="ds_task-list-status-heading">{title}</h2>
|
|
117
|
+
<nav aria-labelledby={`${headingId}-status`} className="ds_task-list-status">
|
|
118
|
+
<p>You have completed {completedTasksCount} of {taskCount} sections.</p>
|
|
119
|
+
{firstIncompleteTaskLink()}
|
|
120
|
+
</nav>
|
|
121
|
+
<ul className="ds_task-list">
|
|
122
|
+
{children}
|
|
123
|
+
</ul>
|
|
124
|
+
</>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
TaskList.displayName = 'TaskList';
|
|
129
|
+
Task.displayName = 'Task';
|
|
130
|
+
TaskGroup.displayName = 'TaskGroup';
|
|
131
|
+
|
|
132
|
+
export default TaskList;
|