@khanacademy/perseus 0.4.3 → 1.1.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/.eslintrc.js +6 -1
- package/CHANGELOG.md +45 -0
- package/dist/es/index.css +15 -8
- package/dist/es/index.js +1214 -1404
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +15 -8
- package/dist/index.js +1217 -1411
- package/dist/index.js.map +1 -1
- package/dist/strings.js +0 -4
- package/package.json +9 -8
- package/src/components/number-input.jsx +2 -2
- package/src/components/simple-keypad-input.jsx +18 -21
- package/src/index.js +1 -1
- package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +426 -135
- package/src/multi-items/__tests__/multi-renderer_test.js +9 -9
- package/src/perseus-api.jsx +10 -36
- package/src/perseus-types.js +18 -145
- package/src/styles/constants.js +0 -2
- package/src/styles/perseus-renderer.less +8 -2
- package/src/styles/variables.less +0 -2
- package/src/styles/widgets/passage.less +9 -5
- package/src/types.js +18 -35
- package/src/util/answer-types.js +2 -3
- package/src/util/math.js +3 -10
- package/src/util.js +0 -6
- package/src/version.js +1 -1
- package/src/widgets/__stories__/numeric-input.stories.jsx +43 -2
- package/src/widgets/__testdata__/base-radio_testdata.js +22 -0
- package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +4 -4
- package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +291 -130
- package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +3293 -1415
- package/src/widgets/__tests__/group_test.jsx +4 -4
- package/src/widgets/__tests__/numeric-input_test.jsx +12 -8
- package/src/widgets/__tests__/passage_test.jsx +81 -0
- package/src/widgets/__tests__/radio/base-radio_test.jsx +291 -41
- package/src/widgets/__tests__/radio/choice-icon_test.jsx +67 -0
- package/src/widgets/__tests__/radio/choice_test.jsx +302 -0
- package/src/widgets/__tests__/radio/focus-ring_test.jsx +23 -0
- package/src/widgets/__tests__/radio/option-status_test.jsx +65 -0
- package/src/widgets/__tests__/radio_test.jsx +39 -15
- package/src/widgets/definition.jsx +1 -1
- package/src/widgets/group.jsx +84 -102
- package/src/widgets/input-number.jsx +6 -1
- package/src/widgets/numeric-input.jsx +87 -80
- package/src/widgets/passage/__tests__/passage-markdown_test.jsx +0 -9
- package/src/widgets/passage/get-line-height-for-node.js +4 -5
- package/src/widgets/passage/passage-markdown.jsx +176 -120
- package/src/widgets/passage.jsx +48 -46
- package/src/widgets/radio/__stories__/base-radio.stories.jsx +164 -17
- package/src/widgets/radio/__stories__/choice-icon.stories.jsx +202 -111
- package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +21 -6
- package/src/widgets/radio/__stories__/choice.stories.jsx +138 -0
- package/src/widgets/radio/__stories__/focus-ring.stories.jsx +29 -23
- package/src/widgets/radio/__stories__/option-status.stories.jsx +93 -99
- package/src/widgets/radio/base-radio.jsx +485 -556
- package/src/widgets/radio/choice-a11y-text.js +62 -0
- package/src/widgets/radio/choice-icon/choice-icon.jsx +87 -0
- package/src/widgets/radio/choice-icon/cross-out-line.jsx +55 -0
- package/src/widgets/radio/choice-icon/library-choice-icon.jsx +214 -0
- package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +120 -0
- package/src/widgets/radio/choice-icon/shared-styles.js +17 -0
- package/src/widgets/radio/choice-none-above.jsx +49 -40
- package/src/widgets/radio/choice.jsx +319 -347
- package/src/widgets/radio/choice.jsx.fixture.js +0 -18
- package/src/widgets/radio/focus-ring.jsx +29 -22
- package/src/widgets/radio/option-status.jsx +58 -36
- package/src/widgets/radio/widget.jsx +181 -136
- package/src/widgets/radio.jsx +16 -17
- package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +0 -64
- package/src/widgets/radio/choice-icon.jsx +0 -469
- package/src/widgets/radio/cross-out-button.jsx +0 -134
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import {render, screen} from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
import "@testing-library/jest-dom"; // Imports custom mathers
|
|
8
|
+
|
|
9
|
+
import Choice from "../../radio/choice.jsx";
|
|
10
|
+
|
|
11
|
+
function renderChoice(options) {
|
|
12
|
+
const defaultOptions = {
|
|
13
|
+
checked: false,
|
|
14
|
+
rationale: "This is a good rationale",
|
|
15
|
+
content: "This is a possible choice",
|
|
16
|
+
correct: true,
|
|
17
|
+
disabled: false,
|
|
18
|
+
pos: 0,
|
|
19
|
+
reviewMode: false,
|
|
20
|
+
showRationale: false,
|
|
21
|
+
showCorrectness: false,
|
|
22
|
+
multipleSelect: false,
|
|
23
|
+
crossedOut: false,
|
|
24
|
+
previouslyAnswered: false,
|
|
25
|
+
ref: {current: null},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const overwrittenOptions = {...defaultOptions, ...options};
|
|
29
|
+
|
|
30
|
+
return render(<Choice {...overwrittenOptions} />);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe("all choice options", () => {
|
|
34
|
+
it("renders rationale", () => {
|
|
35
|
+
// Arrange / Act
|
|
36
|
+
renderChoice({showRationale: true});
|
|
37
|
+
|
|
38
|
+
// Assert
|
|
39
|
+
expect(screen.getByText("This is a good rationale")).toBeVisible();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("shows correct when in review mode", () => {
|
|
43
|
+
// Arrange / Act
|
|
44
|
+
renderChoice({
|
|
45
|
+
checked: false,
|
|
46
|
+
correct: true,
|
|
47
|
+
showCorrectness: true,
|
|
48
|
+
reviewMode: true,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Assert
|
|
52
|
+
expect(screen.getByText("Correct")).toBeVisible();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("shows incorrect when in review mode", () => {
|
|
56
|
+
// Arrange / Act
|
|
57
|
+
renderChoice({
|
|
58
|
+
checked: false,
|
|
59
|
+
correct: false,
|
|
60
|
+
showCorrectness: true,
|
|
61
|
+
reviewMode: true,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Assert
|
|
65
|
+
expect(screen.getByText("Incorrect")).toBeVisible();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("shows correct a11y text when in review mode", () => {
|
|
69
|
+
// Arrange / Act
|
|
70
|
+
renderChoice({
|
|
71
|
+
checked: false,
|
|
72
|
+
correct: true,
|
|
73
|
+
showCorrectness: true,
|
|
74
|
+
reviewMode: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Assert
|
|
78
|
+
expect(
|
|
79
|
+
screen.getByText("(Choice A, Correct) This is a possible choice"),
|
|
80
|
+
).toBeVisible();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("shows correct selected when in review mode", () => {
|
|
84
|
+
// Arrange / Act
|
|
85
|
+
renderChoice({
|
|
86
|
+
checked: true,
|
|
87
|
+
correct: true,
|
|
88
|
+
showCorrectness: true,
|
|
89
|
+
reviewMode: true,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Assert
|
|
93
|
+
expect(screen.getByText("Correct (selected)")).toBeVisible();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("shows incorrect selected when in review mode", () => {
|
|
97
|
+
// Arrange / Act
|
|
98
|
+
renderChoice({
|
|
99
|
+
checked: true,
|
|
100
|
+
correct: false,
|
|
101
|
+
showCorrectness: true,
|
|
102
|
+
reviewMode: true,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Assert
|
|
106
|
+
expect(screen.getByText("Incorrect (selected)")).toBeVisible();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Tests 1 of 2 element types used to select a choice
|
|
111
|
+
describe("choice button", () => {
|
|
112
|
+
it("selects the choice by clicking the button", () => {
|
|
113
|
+
// Arrange / Act
|
|
114
|
+
const onChangeSpy = jest.fn();
|
|
115
|
+
renderChoice({onChange: onChangeSpy});
|
|
116
|
+
|
|
117
|
+
const button = screen.getByRole("button", {hidden: true});
|
|
118
|
+
userEvent.click(button);
|
|
119
|
+
|
|
120
|
+
// Assert
|
|
121
|
+
expect(onChangeSpy).toHaveBeenCalledWith({
|
|
122
|
+
checked: true,
|
|
123
|
+
crossedOut: false,
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("has correct aria-disabled when disabled", () => {
|
|
128
|
+
// Arrange / Act
|
|
129
|
+
renderChoice({disabled: true});
|
|
130
|
+
|
|
131
|
+
const button = screen.getByRole("button", {hidden: true});
|
|
132
|
+
|
|
133
|
+
// Assert
|
|
134
|
+
expect(button.getAttribute("aria-disabled")).toBe("true");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("has correct aria-disabled when not disabled", () => {
|
|
138
|
+
// Arrange / Act
|
|
139
|
+
renderChoice({disabled: false});
|
|
140
|
+
|
|
141
|
+
const button = screen.getByRole("button", {hidden: true});
|
|
142
|
+
|
|
143
|
+
// Assert
|
|
144
|
+
expect(button.getAttribute("aria-disabled")).toBe("false");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("can be checked", () => {
|
|
148
|
+
// Arrange
|
|
149
|
+
let checked = false;
|
|
150
|
+
renderChoice({
|
|
151
|
+
checked,
|
|
152
|
+
onChange: (next) => {
|
|
153
|
+
checked = next.checked;
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Act
|
|
158
|
+
const button = screen.getByRole("button", {hidden: true});
|
|
159
|
+
userEvent.click(button);
|
|
160
|
+
|
|
161
|
+
// Assert
|
|
162
|
+
expect(checked).toBeTrue();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("can be unchecked", () => {
|
|
166
|
+
// Arrange
|
|
167
|
+
let checked = true;
|
|
168
|
+
renderChoice({
|
|
169
|
+
checked,
|
|
170
|
+
onChange: (next) => {
|
|
171
|
+
checked = next.checked;
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Act
|
|
176
|
+
const button = screen.getByRole("button", {hidden: true});
|
|
177
|
+
userEvent.click(button);
|
|
178
|
+
|
|
179
|
+
// Assert
|
|
180
|
+
expect(checked).toBeFalse();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Tests 2 of 2 element types used to select a choice
|
|
185
|
+
describe("choice input (screen reader only)", () => {
|
|
186
|
+
it("selects the choice by clicking the input", () => {
|
|
187
|
+
// Arrange / Act
|
|
188
|
+
const onChangeSpy = jest.fn();
|
|
189
|
+
renderChoice({onChange: onChangeSpy});
|
|
190
|
+
|
|
191
|
+
const input = screen.getByRole("radio", {
|
|
192
|
+
name: "(Choice A) This is a possible choice",
|
|
193
|
+
});
|
|
194
|
+
userEvent.click(input);
|
|
195
|
+
|
|
196
|
+
// Assert
|
|
197
|
+
expect(onChangeSpy).toHaveBeenCalledWith({
|
|
198
|
+
checked: true,
|
|
199
|
+
crossedOut: false,
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("registers as unchecked with checked set to false", () => {
|
|
204
|
+
// Arrange / Act
|
|
205
|
+
renderChoice({checked: false});
|
|
206
|
+
|
|
207
|
+
const input = screen.getByRole("radio", {
|
|
208
|
+
name: "(Choice A) This is a possible choice",
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Assert
|
|
212
|
+
expect(input).not.toBeChecked();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("registers as checked with checked set to true", () => {
|
|
216
|
+
// Arrange / Act
|
|
217
|
+
renderChoice({checked: true});
|
|
218
|
+
|
|
219
|
+
const input = screen.getByRole("radio", {
|
|
220
|
+
name: "(Choice A, Checked) This is a possible choice",
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Assert
|
|
224
|
+
expect(input).toBeChecked();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("has correct a11y text when crossed out", () => {
|
|
228
|
+
// Arrange / Act
|
|
229
|
+
renderChoice({crossedOut: true});
|
|
230
|
+
|
|
231
|
+
const input = screen.getByRole("radio", {
|
|
232
|
+
name: "(Choice A, Crossed out) This is a possible choice",
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Assert
|
|
236
|
+
expect(input).toBeVisible();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("has correct a11y text when incorrect, crossed out, and showCorrectness is true", () => {
|
|
240
|
+
// Arrange / Act
|
|
241
|
+
renderChoice({crossedOut: true, correct: false, showCorrectness: true});
|
|
242
|
+
|
|
243
|
+
const input = screen.getByRole("radio", {
|
|
244
|
+
name: "(Choice A, Crossed out, Incorrect) This is a possible choice",
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Assert
|
|
248
|
+
expect(input).toBeVisible();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("has correct a11y text when correct, crossed out, and showCorrectness is true", () => {
|
|
252
|
+
// Arrange / Act
|
|
253
|
+
renderChoice({crossedOut: true, correct: true, showCorrectness: true});
|
|
254
|
+
|
|
255
|
+
const input = screen.getByRole("radio", {
|
|
256
|
+
name: "(Choice A, Crossed out, Correct) This is a possible choice",
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Assert
|
|
260
|
+
expect(input).toBeVisible();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("can be checked", () => {
|
|
264
|
+
// Arrange
|
|
265
|
+
let checked = false;
|
|
266
|
+
renderChoice({
|
|
267
|
+
checked,
|
|
268
|
+
onChange: (next) => {
|
|
269
|
+
checked = next.checked;
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Act
|
|
274
|
+
const input = screen.getByRole("radio", {
|
|
275
|
+
name: "(Choice A) This is a possible choice",
|
|
276
|
+
});
|
|
277
|
+
userEvent.click(input);
|
|
278
|
+
|
|
279
|
+
// Assert
|
|
280
|
+
expect(checked).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("can be unchecked", () => {
|
|
284
|
+
// Arrange
|
|
285
|
+
let checked = true;
|
|
286
|
+
renderChoice({
|
|
287
|
+
checked,
|
|
288
|
+
onChange: (next) => {
|
|
289
|
+
checked = next.checked;
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Act
|
|
294
|
+
const input = screen.getByRole("radio", {
|
|
295
|
+
name: "(Choice A, Checked) This is a possible choice",
|
|
296
|
+
});
|
|
297
|
+
userEvent.click(input);
|
|
298
|
+
|
|
299
|
+
// Assert
|
|
300
|
+
expect(checked).toBeFalse();
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import {render, screen} from "@testing-library/react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
import "@testing-library/jest-dom"; // Imports custom mathers
|
|
7
|
+
|
|
8
|
+
import FocusRing from "../../radio/focus-ring.jsx";
|
|
9
|
+
|
|
10
|
+
describe("choice icon", () => {
|
|
11
|
+
describe.each([[true], [false]])("multipleSelect: %s", (multipleSelect) => {
|
|
12
|
+
it("renders with the correct border radius", () => {
|
|
13
|
+
// Arrange/Act
|
|
14
|
+
render(<FocusRing multipleSelect={multipleSelect} />);
|
|
15
|
+
|
|
16
|
+
const focusRing = screen.getByTestId("focus-ring");
|
|
17
|
+
|
|
18
|
+
// Assert
|
|
19
|
+
const expectedRadius = multipleSelect ? "5px" : "50%";
|
|
20
|
+
expect(focusRing).toHaveStyle(`border-radius: ${expectedRadius}`);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import {render, screen} from "@testing-library/react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
import "@testing-library/jest-dom"; // Imports custom mathers
|
|
7
|
+
|
|
8
|
+
import OptionStatus from "../../radio/option-status.jsx";
|
|
9
|
+
|
|
10
|
+
function renderOptionStatus(options) {
|
|
11
|
+
const defaultOptions = {
|
|
12
|
+
correct: false,
|
|
13
|
+
checked: false,
|
|
14
|
+
crossedOut: false,
|
|
15
|
+
previouslyAnswered: false,
|
|
16
|
+
reviewMode: true,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const overwrittenOptions = {...defaultOptions, ...options};
|
|
20
|
+
|
|
21
|
+
return render(<OptionStatus {...overwrittenOptions} />);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("answer text", () => {
|
|
25
|
+
it.each([
|
|
26
|
+
[true, "Correct"],
|
|
27
|
+
[false, "Incorrect"],
|
|
28
|
+
])(
|
|
29
|
+
"renders with the correct text based on 'correct' prop: %s => %s",
|
|
30
|
+
(correct, expectedText) => {
|
|
31
|
+
// Arrange/Act
|
|
32
|
+
renderOptionStatus({correct: correct});
|
|
33
|
+
const optionStatus = screen.getByText(expectedText);
|
|
34
|
+
|
|
35
|
+
// Assert
|
|
36
|
+
expect(optionStatus).toBeVisible();
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
it.each([
|
|
41
|
+
[true, "Incorrect (selected)"],
|
|
42
|
+
[false, "Incorrect"],
|
|
43
|
+
])(
|
|
44
|
+
"renders with the correct text based on 'checked' prop: %s => %s",
|
|
45
|
+
(checked, expectedText) => {
|
|
46
|
+
// Arrange/Act
|
|
47
|
+
renderOptionStatus({correct: false, checked: checked});
|
|
48
|
+
const optionStatus = screen.getByText(expectedText);
|
|
49
|
+
|
|
50
|
+
// Assert
|
|
51
|
+
expect(optionStatus).toBeVisible();
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
it("renders correctly based on 'crossedOut' and 'correct'", () => {
|
|
56
|
+
// Arrange/Act
|
|
57
|
+
renderOptionStatus({crossedOut: true, correct: true});
|
|
58
|
+
const optionStatus = screen.getByText(
|
|
59
|
+
"Correct (but you crossed it out)",
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Assert
|
|
63
|
+
expect(optionStatus).toBeVisible();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -25,7 +25,7 @@ import type {
|
|
|
25
25
|
import type {APIOptions} from "../../types.js";
|
|
26
26
|
|
|
27
27
|
const selectOption = (index: number) => {
|
|
28
|
-
const options = screen.getAllByRole("
|
|
28
|
+
const options = screen.getAllByRole("radio");
|
|
29
29
|
|
|
30
30
|
// element that is null/undefined (ie. if the index is invalid) so we
|
|
31
31
|
// manually check and throw here to protect future me, and others :)
|
|
@@ -101,8 +101,7 @@ describe("single-choice question", () => {
|
|
|
101
101
|
it("should accept the right answer (touch)", () => {
|
|
102
102
|
// Arrange
|
|
103
103
|
const {renderer} = renderQuestion(question, apiOptions);
|
|
104
|
-
const correctRadio =
|
|
105
|
-
screen.getAllByRole("checkbox")[correct];
|
|
104
|
+
const correctRadio = screen.getAllByRole("radio")[correct];
|
|
106
105
|
|
|
107
106
|
// Act
|
|
108
107
|
fireEvent.touchStart(correctRadio);
|
|
@@ -157,7 +156,7 @@ describe("single-choice question", () => {
|
|
|
157
156
|
renderer.deselectIncorrectSelectedChoices();
|
|
158
157
|
|
|
159
158
|
// Assert
|
|
160
|
-
screen.getAllByRole("
|
|
159
|
+
screen.getAllByRole("radio").forEach((r) => {
|
|
161
160
|
expect(r).not.toBeChecked();
|
|
162
161
|
});
|
|
163
162
|
});
|
|
@@ -181,9 +180,14 @@ describe("single-choice question", () => {
|
|
|
181
180
|
|
|
182
181
|
// Assert
|
|
183
182
|
// Everything's read-only so no selections made
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
// Note(TB): The visual buttons are hidden from screen readers
|
|
184
|
+
// so they need to be identified as hidden
|
|
185
|
+
// The visual button has the aria attributes
|
|
186
|
+
screen
|
|
187
|
+
.getAllByRole("button", {hidden: true})
|
|
188
|
+
.forEach((r) => {
|
|
189
|
+
expect(r).toHaveAttribute("aria-disabled", "true");
|
|
190
|
+
});
|
|
187
191
|
});
|
|
188
192
|
},
|
|
189
193
|
);
|
|
@@ -194,11 +198,18 @@ describe("single-choice question", () => {
|
|
|
194
198
|
|
|
195
199
|
// Act
|
|
196
200
|
userEvent.tab();
|
|
197
|
-
|
|
201
|
+
// Note(TB): The visual buttons are hidden from screen readers
|
|
202
|
+
// so they need to be identified as hidden;
|
|
203
|
+
// cannot access screen reader buttons via tabbing
|
|
204
|
+
expect(
|
|
205
|
+
screen.getAllByRole("button", {hidden: true})[0],
|
|
206
|
+
).toHaveFocus();
|
|
198
207
|
userEvent.tab();
|
|
199
208
|
|
|
200
209
|
// Assert
|
|
201
|
-
expect(
|
|
210
|
+
expect(
|
|
211
|
+
screen.getAllByRole("button", {hidden: true})[1],
|
|
212
|
+
).toHaveFocus();
|
|
202
213
|
});
|
|
203
214
|
|
|
204
215
|
it("should be able to navigate up by keyboard", () => {
|
|
@@ -207,13 +218,23 @@ describe("single-choice question", () => {
|
|
|
207
218
|
|
|
208
219
|
// Act
|
|
209
220
|
userEvent.tab();
|
|
210
|
-
|
|
221
|
+
// Note(TB): The visual buttons are hidden from screen readers
|
|
222
|
+
// so they need to be identified as hidden
|
|
223
|
+
expect(
|
|
224
|
+
screen.getAllByRole("button", {hidden: true})[0],
|
|
225
|
+
).toHaveFocus();
|
|
226
|
+
|
|
211
227
|
userEvent.tab();
|
|
212
|
-
expect(
|
|
228
|
+
expect(
|
|
229
|
+
screen.getAllByRole("button", {hidden: true})[1],
|
|
230
|
+
).toHaveFocus();
|
|
231
|
+
|
|
213
232
|
userEvent.tab({shift: true});
|
|
214
233
|
|
|
215
234
|
// Assert
|
|
216
|
-
expect(
|
|
235
|
+
expect(
|
|
236
|
+
screen.getAllByRole("button", {hidden: true})[0],
|
|
237
|
+
).toHaveFocus();
|
|
217
238
|
});
|
|
218
239
|
|
|
219
240
|
it("should be able to navigate through 'None of the above' choice by keyboard", () => {
|
|
@@ -229,7 +250,11 @@ describe("single-choice question", () => {
|
|
|
229
250
|
userEvent.tab();
|
|
230
251
|
|
|
231
252
|
// Assert
|
|
232
|
-
|
|
253
|
+
// Note(TB): The visual buttons are hidden from screen readers
|
|
254
|
+
// so they need to be identified as hidden
|
|
255
|
+
expect(
|
|
256
|
+
screen.getAllByRole("button", {hidden: true})[2],
|
|
257
|
+
).toHaveFocus();
|
|
233
258
|
});
|
|
234
259
|
|
|
235
260
|
it.each([
|
|
@@ -251,7 +276,7 @@ describe("single-choice question", () => {
|
|
|
251
276
|
// We click on the first item, which was the second (index == 1)
|
|
252
277
|
// item in the original choices. But because of enforced ordering,
|
|
253
278
|
// it is now at the top of the list (and thus our correct answer).
|
|
254
|
-
userEvent.click(screen.getAllByRole("
|
|
279
|
+
userEvent.click(screen.getAllByRole("radio")[0]);
|
|
255
280
|
|
|
256
281
|
// Assert
|
|
257
282
|
const items = screen.getAllByRole("listitem");
|
|
@@ -393,7 +418,6 @@ describe("single-choice question", () => {
|
|
|
393
418
|
|
|
394
419
|
it("should cross-out selection and dismiss button when clicked", async () => {
|
|
395
420
|
// Arrange
|
|
396
|
-
|
|
397
421
|
renderQuestion(question, crossOutApiOptions);
|
|
398
422
|
userEvent.click(
|
|
399
423
|
screen.getByRole("button", {
|