@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
|
@@ -347,7 +347,7 @@ describe("group widget", () => {
|
|
|
347
347
|
|
|
348
348
|
// Arrange
|
|
349
349
|
const {renderer} = renderQuestion(question1);
|
|
350
|
-
userEvent.click(screen.getAllByRole("
|
|
350
|
+
userEvent.click(screen.getAllByRole("radio")[4]);
|
|
351
351
|
userEvent.type(screen.getAllByRole("textbox")[0], "1000");
|
|
352
352
|
userEvent.type(screen.getAllByRole("textbox")[1], "9999");
|
|
353
353
|
|
|
@@ -360,7 +360,7 @@ describe("group widget", () => {
|
|
|
360
360
|
renderer1.restoreSerializedState(state);
|
|
361
361
|
|
|
362
362
|
// Assert
|
|
363
|
-
expect(screen.getAllByRole("
|
|
363
|
+
expect(screen.getAllByRole("radio")[4]).toBeChecked();
|
|
364
364
|
expect(screen.getAllByRole("textbox")[0]).toHaveValue("1000");
|
|
365
365
|
expect(screen.getAllByRole("textbox")[1]).toHaveValue("9999");
|
|
366
366
|
});
|
|
@@ -369,7 +369,7 @@ describe("group widget", () => {
|
|
|
369
369
|
// Arrange
|
|
370
370
|
const {renderer} = renderQuestion(question1);
|
|
371
371
|
// Answer all widgets correctly
|
|
372
|
-
userEvent.click(screen.getAllByRole("
|
|
372
|
+
userEvent.click(screen.getAllByRole("radio")[4]);
|
|
373
373
|
userEvent.type(screen.getAllByRole("textbox")[0], "230");
|
|
374
374
|
userEvent.type(screen.getAllByRole("textbox")[1], "200");
|
|
375
375
|
|
|
@@ -453,7 +453,7 @@ describe("group widget", () => {
|
|
|
453
453
|
it("should show rationales for contained widgets", () => {
|
|
454
454
|
// Arrange
|
|
455
455
|
const {renderer} = renderQuestion(question1);
|
|
456
|
-
userEvent.click(screen.getAllByRole("
|
|
456
|
+
userEvent.click(screen.getAllByRole("radio")[2]); // Incorrect!
|
|
457
457
|
|
|
458
458
|
// Act
|
|
459
459
|
renderer.showRationalesForCurrentlySelectedChoices();
|
|
@@ -529,14 +529,18 @@ describe("unionAnswerForms utility function", () => {
|
|
|
529
529
|
it("removes duplicates", () => {
|
|
530
530
|
// arrange
|
|
531
531
|
const forms = [
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
532
|
+
[
|
|
533
|
+
{
|
|
534
|
+
simplify: "required",
|
|
535
|
+
name: "integer",
|
|
536
|
+
},
|
|
537
|
+
],
|
|
538
|
+
[
|
|
539
|
+
{
|
|
540
|
+
simplify: "required",
|
|
541
|
+
name: "integer",
|
|
542
|
+
},
|
|
543
|
+
],
|
|
540
544
|
];
|
|
541
545
|
|
|
542
546
|
// act
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
|
|
3
3
|
import "@testing-library/jest-dom";
|
|
4
|
+
import {render, screen} from "@testing-library/react";
|
|
5
|
+
import React from "react";
|
|
4
6
|
|
|
5
7
|
import {testDependencies} from "../../../../../testing/test-dependencies.js";
|
|
6
8
|
import * as Dependencies from "../../dependencies.js";
|
|
9
|
+
import {ApiOptions} from "../../perseus-api.jsx";
|
|
7
10
|
import {question1, question2} from "../__testdata__/passage_testdata.js";
|
|
8
11
|
import PassageWidgetExport from "../passage.jsx";
|
|
9
12
|
import * as getLineHeightModule from "../passage/get-line-height-for-node.js";
|
|
@@ -14,6 +17,40 @@ import type {APIOptions} from "../../types.js";
|
|
|
14
17
|
|
|
15
18
|
jest.mock("../passage/get-line-height-for-node.js");
|
|
16
19
|
|
|
20
|
+
function renderPassage(overwrite) {
|
|
21
|
+
const widgetPropsBase = {
|
|
22
|
+
footnotes: "",
|
|
23
|
+
passageText: "",
|
|
24
|
+
passageTitle: "",
|
|
25
|
+
showLineNumbers: false,
|
|
26
|
+
static: true,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const base = {
|
|
30
|
+
...widgetPropsBase,
|
|
31
|
+
alignment: null,
|
|
32
|
+
apiOptions: {
|
|
33
|
+
...ApiOptions.defaults,
|
|
34
|
+
},
|
|
35
|
+
containerSizeClass: "small",
|
|
36
|
+
findWidgets: (callback) => [],
|
|
37
|
+
isLastUsedWidget: false,
|
|
38
|
+
onBlur: () => {},
|
|
39
|
+
onChange: () => {},
|
|
40
|
+
onFocus: () => {},
|
|
41
|
+
problemNum: 1,
|
|
42
|
+
reviewModeRubric: {
|
|
43
|
+
...widgetPropsBase,
|
|
44
|
+
},
|
|
45
|
+
static: true,
|
|
46
|
+
trackInteraction: () => {},
|
|
47
|
+
widgetId: "passage",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const extended = {...base, ...overwrite};
|
|
51
|
+
return render(<PassageWidgetExport.widget {...extended} />);
|
|
52
|
+
}
|
|
53
|
+
|
|
17
54
|
describe("passage widget", () => {
|
|
18
55
|
beforeEach(() => {
|
|
19
56
|
jest.spyOn(getLineHeightModule, "getLineHeightForNode").mockReturnValue(
|
|
@@ -130,4 +167,48 @@ describe("passage widget", () => {
|
|
|
130
167
|
// Assert
|
|
131
168
|
expect(reference).toBe(null);
|
|
132
169
|
});
|
|
170
|
+
|
|
171
|
+
it("should render passage title", () => {
|
|
172
|
+
renderPassage({passageTitle: "Passage title"});
|
|
173
|
+
|
|
174
|
+
expect(screen.getByText("Passage title")).toBeInTheDocument();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should render passage text", () => {
|
|
178
|
+
renderPassage({passageText: "Passage text"});
|
|
179
|
+
|
|
180
|
+
expect(screen.getByText("Passage text")).toBeInTheDocument();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should render footnotes", () => {
|
|
184
|
+
renderPassage({footnotes: "Footnote text"});
|
|
185
|
+
|
|
186
|
+
expect(screen.getByText("Footnote text")).toBeInTheDocument();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("should render first question instructions", () => {
|
|
190
|
+
renderPassage({passageText: "[[test]] Passage text"});
|
|
191
|
+
|
|
192
|
+
expect(screen.getByText("The symbol")).toBeInTheDocument();
|
|
193
|
+
expect(screen.getAllByText("[Marker for question test]")).toHaveLength(
|
|
194
|
+
2,
|
|
195
|
+
);
|
|
196
|
+
expect(
|
|
197
|
+
screen.getByText(
|
|
198
|
+
"indicates that question test references this portion of the passage",
|
|
199
|
+
),
|
|
200
|
+
).toBeInTheDocument();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("should render first sentence instructions", () => {
|
|
204
|
+
renderPassage({passageText: "[[1]] Passage text"});
|
|
205
|
+
|
|
206
|
+
expect(screen.getByText("The symbol")).toBeInTheDocument();
|
|
207
|
+
expect(screen.getAllByText("[Marker for question 1]")).toHaveLength(2);
|
|
208
|
+
expect(
|
|
209
|
+
screen.getByText(
|
|
210
|
+
"indicates that question 1 references this portion of the passage",
|
|
211
|
+
),
|
|
212
|
+
).toBeInTheDocument();
|
|
213
|
+
});
|
|
133
214
|
});
|
|
@@ -6,77 +6,327 @@ import * as React from "react";
|
|
|
6
6
|
|
|
7
7
|
import "@testing-library/jest-dom"; // Imports custom mathers
|
|
8
8
|
|
|
9
|
+
import {testDependencies} from "../../../../../../testing/test-dependencies.js";
|
|
10
|
+
import {setDependencies} from "../../../dependencies.js";
|
|
11
|
+
import {generateChoice} from "../../__testdata__/base-radio_testdata.js";
|
|
9
12
|
import BaseRadio from "../../radio/base-radio.jsx";
|
|
10
13
|
|
|
11
14
|
import type {APIOptions} from "../../../types.js";
|
|
12
15
|
|
|
16
|
+
function renderBaseRadio(props) {
|
|
17
|
+
const apiOptions: APIOptions = Object.freeze({});
|
|
18
|
+
|
|
19
|
+
const baseProps = {
|
|
20
|
+
apiOptions,
|
|
21
|
+
choices: [],
|
|
22
|
+
deselectEnabled: false,
|
|
23
|
+
editMode: false,
|
|
24
|
+
labelWrap: false,
|
|
25
|
+
countChoices: false,
|
|
26
|
+
numCorrect: 1,
|
|
27
|
+
multipleSelect: false,
|
|
28
|
+
|
|
29
|
+
// A callback indicating that this choice has changed. Its argument is
|
|
30
|
+
// an object with two keys: `checked` and `crossedOut`. Each contains
|
|
31
|
+
// an array of boolean values, specifying the new checked and
|
|
32
|
+
// crossed-out value of each choice.
|
|
33
|
+
onChange: ({checked, crossedOut}) => {},
|
|
34
|
+
|
|
35
|
+
// Whether this widget was the most recently used widget in this
|
|
36
|
+
// Renderer. Determines whether we'll auto-scroll the page upon
|
|
37
|
+
// entering review mode.
|
|
38
|
+
isLastUsedWidget: false,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const overwrittenProps = {...baseProps, ...props};
|
|
42
|
+
|
|
43
|
+
return render(<BaseRadio {...overwrittenProps} />);
|
|
44
|
+
}
|
|
45
|
+
|
|
13
46
|
describe("base-radio", () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
// because choice-none-above uses a Renderer
|
|
49
|
+
// we need to set dependencies when it's rendered
|
|
50
|
+
// (this probably needs to be fixed)
|
|
51
|
+
setDependencies(testDependencies);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("renders a none of the above choice", () => {
|
|
55
|
+
// Arrange / Act
|
|
56
|
+
renderBaseRadio({
|
|
57
|
+
editMode: true,
|
|
58
|
+
choices: [
|
|
59
|
+
generateChoice({content: "Option 1", correct: false}),
|
|
60
|
+
generateChoice({content: "Option B", correct: false}),
|
|
61
|
+
generateChoice({content: "Option Gamma", correct: false}),
|
|
62
|
+
generateChoice({content: "Option Delta", correct: false}),
|
|
63
|
+
generateChoice({
|
|
64
|
+
isNoneOfTheAbove: true,
|
|
65
|
+
correct: true,
|
|
66
|
+
}),
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Assert
|
|
71
|
+
expect(
|
|
72
|
+
screen.getByRole("radio", {
|
|
73
|
+
name: "(Choice E) None of the above",
|
|
74
|
+
}),
|
|
75
|
+
).toBeInTheDocument();
|
|
76
|
+
expect(
|
|
77
|
+
screen.getByLabelText("(Choice E) None of the above"),
|
|
78
|
+
).toBeInTheDocument();
|
|
79
|
+
// Note(Tamara): There should be two of these options: one for the
|
|
80
|
+
// screen reader only radio input and a button for learners
|
|
81
|
+
// using the visual choices
|
|
82
|
+
expect(screen.getAllByText("None of the above")).toHaveLength(2);
|
|
83
|
+
});
|
|
84
|
+
|
|
14
85
|
describe("edit mode", () => {
|
|
15
86
|
it("should render <li>'s for each choice", () => {
|
|
87
|
+
// Arrange / Act
|
|
88
|
+
renderBaseRadio({
|
|
89
|
+
editMode: true,
|
|
90
|
+
choices: [
|
|
91
|
+
generateChoice({content: "Option 1", correct: false}),
|
|
92
|
+
generateChoice({content: "Option B", correct: false}),
|
|
93
|
+
generateChoice({content: "Option Gamma", correct: true}),
|
|
94
|
+
generateChoice({content: "Option Delta", correct: false}),
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Assert
|
|
99
|
+
expect(screen.getAllByRole("listitem")).toHaveLength(4);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should toggle choice when inner element clicked", () => {
|
|
16
103
|
// Arrange
|
|
17
|
-
|
|
18
|
-
|
|
104
|
+
let updatedValues = null;
|
|
105
|
+
const onChangeHandler = (newValues) => {
|
|
106
|
+
updatedValues = newValues;
|
|
19
107
|
};
|
|
20
|
-
|
|
108
|
+
|
|
109
|
+
renderBaseRadio({
|
|
110
|
+
editMode: true,
|
|
111
|
+
choices: [
|
|
112
|
+
generateChoice({content: "Option 1", correct: false}),
|
|
113
|
+
generateChoice({content: "Option B", correct: false}),
|
|
114
|
+
generateChoice({content: "Option Gamma", correct: true}),
|
|
115
|
+
generateChoice({content: "Option Delta", correct: false}),
|
|
116
|
+
],
|
|
117
|
+
onChange: onChangeHandler,
|
|
118
|
+
});
|
|
21
119
|
|
|
22
120
|
// Act
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
multipleSelect={false}
|
|
26
|
-
countChoices={false}
|
|
27
|
-
numCorrect={1}
|
|
28
|
-
editMode={true}
|
|
29
|
-
labelWrap={false}
|
|
30
|
-
apiOptions={apiOptions}
|
|
31
|
-
choices={[
|
|
32
|
-
{content: "Option 1", correct: false},
|
|
33
|
-
{content: "Option B", correct: false},
|
|
34
|
-
{content: "Option Gamma", correct: true},
|
|
35
|
-
{content: "Option Delta", correct: false},
|
|
36
|
-
]}
|
|
37
|
-
onChange={onChangeHandler}
|
|
38
|
-
/>,
|
|
121
|
+
userEvent.click(
|
|
122
|
+
screen.getByRole("radio", {name: "(Choice C) Option Gamma"}),
|
|
39
123
|
);
|
|
40
124
|
|
|
41
125
|
// Assert
|
|
42
|
-
expect(
|
|
126
|
+
expect(updatedValues).toMatchObject({
|
|
127
|
+
checked: [false, false, true, false],
|
|
128
|
+
});
|
|
43
129
|
});
|
|
130
|
+
});
|
|
44
131
|
|
|
45
|
-
|
|
132
|
+
describe("selecting and deselecting options", () => {
|
|
133
|
+
it("deselects multi-select choices", () => {
|
|
46
134
|
// Arrange
|
|
47
|
-
|
|
48
|
-
|
|
135
|
+
let updatedValues = null;
|
|
136
|
+
const onChangeHandler = (newValues) => {
|
|
137
|
+
updatedValues = newValues;
|
|
49
138
|
};
|
|
139
|
+
|
|
140
|
+
renderBaseRadio({
|
|
141
|
+
multipleSelect: true,
|
|
142
|
+
choices: [
|
|
143
|
+
generateChoice({
|
|
144
|
+
content: "Option 1",
|
|
145
|
+
correct: false,
|
|
146
|
+
checked: false,
|
|
147
|
+
}),
|
|
148
|
+
generateChoice({
|
|
149
|
+
content: "Option B",
|
|
150
|
+
correct: false,
|
|
151
|
+
checked: false,
|
|
152
|
+
}),
|
|
153
|
+
generateChoice({
|
|
154
|
+
content: "Option Gamma",
|
|
155
|
+
correct: true,
|
|
156
|
+
checked: true,
|
|
157
|
+
}),
|
|
158
|
+
generateChoice({
|
|
159
|
+
content: "Option Delta",
|
|
160
|
+
correct: false,
|
|
161
|
+
checked: false,
|
|
162
|
+
}),
|
|
163
|
+
],
|
|
164
|
+
onChange: onChangeHandler,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Act
|
|
168
|
+
const radioButton = screen.getByRole("checkbox", {
|
|
169
|
+
name: "(Choice C, Checked) Option Gamma",
|
|
170
|
+
});
|
|
171
|
+
userEvent.click(radioButton);
|
|
172
|
+
|
|
173
|
+
// Assert
|
|
174
|
+
expect(updatedValues).toMatchObject({
|
|
175
|
+
checked: [false, false, false, false],
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Equivalent to "should toggle choice when inner element clicked" but with editMode set to false
|
|
180
|
+
it("selects single select choices", () => {
|
|
181
|
+
// Arrange
|
|
50
182
|
let updatedValues = null;
|
|
51
183
|
const onChangeHandler = (newValues) => {
|
|
52
184
|
updatedValues = newValues;
|
|
53
185
|
};
|
|
54
186
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
187
|
+
renderBaseRadio({
|
|
188
|
+
multipleSelect: false,
|
|
189
|
+
choices: [
|
|
190
|
+
generateChoice({
|
|
191
|
+
content: "Option 1",
|
|
192
|
+
correct: false,
|
|
193
|
+
}),
|
|
194
|
+
generateChoice({
|
|
195
|
+
content: "Option B",
|
|
196
|
+
correct: false,
|
|
197
|
+
}),
|
|
198
|
+
generateChoice({
|
|
199
|
+
content: "Option Gamma",
|
|
200
|
+
correct: true,
|
|
201
|
+
}),
|
|
202
|
+
generateChoice({
|
|
203
|
+
content: "Option Delta",
|
|
204
|
+
correct: false,
|
|
205
|
+
}),
|
|
206
|
+
],
|
|
207
|
+
onChange: onChangeHandler,
|
|
208
|
+
});
|
|
72
209
|
|
|
73
210
|
// Act
|
|
74
|
-
userEvent.click(
|
|
211
|
+
userEvent.click(
|
|
212
|
+
screen.getByRole("radio", {name: "(Choice C) Option Gamma"}),
|
|
213
|
+
);
|
|
75
214
|
|
|
76
215
|
// Assert
|
|
77
216
|
expect(updatedValues).toMatchObject({
|
|
78
217
|
checked: [false, false, true, false],
|
|
79
218
|
});
|
|
80
219
|
});
|
|
220
|
+
|
|
221
|
+
it("deselects single select selected choice", () => {
|
|
222
|
+
// Arrange
|
|
223
|
+
let updatedValues = null;
|
|
224
|
+
const onChangeHandler = (newValues) => {
|
|
225
|
+
updatedValues = newValues;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
renderBaseRadio({
|
|
229
|
+
multipleSelect: false,
|
|
230
|
+
choices: [
|
|
231
|
+
generateChoice({
|
|
232
|
+
content: "Option 1",
|
|
233
|
+
correct: false,
|
|
234
|
+
checked: true,
|
|
235
|
+
}),
|
|
236
|
+
generateChoice({
|
|
237
|
+
content: "Option B",
|
|
238
|
+
correct: false,
|
|
239
|
+
}),
|
|
240
|
+
generateChoice({
|
|
241
|
+
content: "Option Gamma",
|
|
242
|
+
correct: true,
|
|
243
|
+
}),
|
|
244
|
+
generateChoice({
|
|
245
|
+
content: "Option Delta",
|
|
246
|
+
correct: false,
|
|
247
|
+
}),
|
|
248
|
+
],
|
|
249
|
+
onChange: onChangeHandler,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Act
|
|
253
|
+
userEvent.click(screen.getAllByRole("radio")[0]);
|
|
254
|
+
|
|
255
|
+
// Assert
|
|
256
|
+
expect(updatedValues).toMatchObject({
|
|
257
|
+
checked: [false, false, false, false],
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe("instruction text", () => {
|
|
263
|
+
it("displays correct text for single select", () => {
|
|
264
|
+
// Arrange / Act
|
|
265
|
+
renderBaseRadio({
|
|
266
|
+
multipleSelect: false,
|
|
267
|
+
choices: [
|
|
268
|
+
generateChoice({content: "Option 1", correct: false}),
|
|
269
|
+
generateChoice({content: "Option B", correct: false}),
|
|
270
|
+
generateChoice({content: "Option Gamma", correct: true}),
|
|
271
|
+
generateChoice({content: "Option Delta", correct: false}),
|
|
272
|
+
],
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Assert
|
|
276
|
+
// note(matthewc) role=group is for the fieldset/legend
|
|
277
|
+
// https://www.w3.org/TR/html-aria/
|
|
278
|
+
const group = screen.getByRole("group", {
|
|
279
|
+
name: "Choose 1 answer:",
|
|
280
|
+
});
|
|
281
|
+
expect(group).toBeInTheDocument();
|
|
282
|
+
expect(group).toBeVisible();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("displays correct text for multi select (count choices off)", () => {
|
|
286
|
+
// Arrange / Act
|
|
287
|
+
renderBaseRadio({
|
|
288
|
+
multipleSelect: true,
|
|
289
|
+
countChoices: false,
|
|
290
|
+
choices: [
|
|
291
|
+
generateChoice({content: "Option 1", correct: false}),
|
|
292
|
+
generateChoice({content: "Option B", correct: false}),
|
|
293
|
+
generateChoice({content: "Option Gamma", correct: true}),
|
|
294
|
+
generateChoice({content: "Option Delta", correct: true}),
|
|
295
|
+
],
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Assert
|
|
299
|
+
// note(matthewc) role=group is for the fieldset/legend
|
|
300
|
+
// https://www.w3.org/TR/html-aria/
|
|
301
|
+
const group = screen.getByRole("group", {
|
|
302
|
+
name: "Choose all answers that apply:",
|
|
303
|
+
});
|
|
304
|
+
expect(group).toBeInTheDocument();
|
|
305
|
+
expect(group).toBeVisible();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("displays correct text for multi select (count choices on)", () => {
|
|
309
|
+
// Arrange / Act
|
|
310
|
+
renderBaseRadio({
|
|
311
|
+
multipleSelect: true,
|
|
312
|
+
countChoices: true,
|
|
313
|
+
numCorrect: 2,
|
|
314
|
+
choices: [
|
|
315
|
+
generateChoice({content: "Option 1", correct: false}),
|
|
316
|
+
generateChoice({content: "Option B", correct: false}),
|
|
317
|
+
generateChoice({content: "Option Gamma", correct: true}),
|
|
318
|
+
generateChoice({content: "Option Delta", correct: true}),
|
|
319
|
+
],
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Assert
|
|
323
|
+
// note(matthewc) role=group is for the fieldset/legend
|
|
324
|
+
// https://www.w3.org/TR/html-aria/
|
|
325
|
+
const group = screen.getByRole("group", {
|
|
326
|
+
name: "Choose 2 answers:",
|
|
327
|
+
});
|
|
328
|
+
expect(group).toBeInTheDocument();
|
|
329
|
+
expect(group).toBeVisible();
|
|
330
|
+
});
|
|
81
331
|
});
|
|
82
332
|
});
|
|
@@ -0,0 +1,67 @@
|
|
|
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 ChoiceIcon from "../../radio/choice-icon/choice-icon.jsx";
|
|
9
|
+
import {
|
|
10
|
+
SAT_ICON_SIZE,
|
|
11
|
+
LIBRARY_ICON_SIZE,
|
|
12
|
+
} from "../../radio/choice-icon/shared-styles.js";
|
|
13
|
+
|
|
14
|
+
function renderChoiceIcon(options) {
|
|
15
|
+
const defaultOptions = {
|
|
16
|
+
pos: 0,
|
|
17
|
+
checked: false,
|
|
18
|
+
crossedOut: false,
|
|
19
|
+
focused: false,
|
|
20
|
+
hovered: false,
|
|
21
|
+
pressed: false,
|
|
22
|
+
correct: false,
|
|
23
|
+
showCorrectness: false,
|
|
24
|
+
product: "library",
|
|
25
|
+
multipleSelect: false,
|
|
26
|
+
reviewMode: false,
|
|
27
|
+
previouslyAnswered: false,
|
|
28
|
+
transparentBackground: false,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const overwrittenOptions = {...defaultOptions, ...options};
|
|
32
|
+
|
|
33
|
+
return render(<ChoiceIcon {...overwrittenOptions} />);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("choice icon", () => {
|
|
37
|
+
describe.each([["sat"], ["library"]])("product: %s", (product) => {
|
|
38
|
+
describe.each([[true], [false]])(
|
|
39
|
+
"multipleSelect: %s",
|
|
40
|
+
(multipleSelect) => {
|
|
41
|
+
it("renders with the correct border radius", () => {
|
|
42
|
+
// Arrange
|
|
43
|
+
let expectedRadius;
|
|
44
|
+
if (multipleSelect) {
|
|
45
|
+
expectedRadius = 3;
|
|
46
|
+
} else if (product === "sat") {
|
|
47
|
+
expectedRadius = SAT_ICON_SIZE;
|
|
48
|
+
} else {
|
|
49
|
+
expectedRadius = LIBRARY_ICON_SIZE;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Act
|
|
53
|
+
renderChoiceIcon({product, multipleSelect});
|
|
54
|
+
|
|
55
|
+
const choiceWrapper = screen.getByTestId(
|
|
56
|
+
`choice-icon__${product}-choice-icon`,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Assert
|
|
60
|
+
expect(choiceWrapper).toHaveStyle(
|
|
61
|
+
`border-radius: ${expectedRadius}px`,
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|