@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.
Files changed (71) hide show
  1. package/.eslintrc.js +6 -1
  2. package/CHANGELOG.md +45 -0
  3. package/dist/es/index.css +15 -8
  4. package/dist/es/index.js +1214 -1404
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +15 -8
  7. package/dist/index.js +1217 -1411
  8. package/dist/index.js.map +1 -1
  9. package/dist/strings.js +0 -4
  10. package/package.json +9 -8
  11. package/src/components/number-input.jsx +2 -2
  12. package/src/components/simple-keypad-input.jsx +18 -21
  13. package/src/index.js +1 -1
  14. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +426 -135
  15. package/src/multi-items/__tests__/multi-renderer_test.js +9 -9
  16. package/src/perseus-api.jsx +10 -36
  17. package/src/perseus-types.js +18 -145
  18. package/src/styles/constants.js +0 -2
  19. package/src/styles/perseus-renderer.less +8 -2
  20. package/src/styles/variables.less +0 -2
  21. package/src/styles/widgets/passage.less +9 -5
  22. package/src/types.js +18 -35
  23. package/src/util/answer-types.js +2 -3
  24. package/src/util/math.js +3 -10
  25. package/src/util.js +0 -6
  26. package/src/version.js +1 -1
  27. package/src/widgets/__stories__/numeric-input.stories.jsx +43 -2
  28. package/src/widgets/__testdata__/base-radio_testdata.js +22 -0
  29. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +4 -4
  30. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +291 -130
  31. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +3293 -1415
  32. package/src/widgets/__tests__/group_test.jsx +4 -4
  33. package/src/widgets/__tests__/numeric-input_test.jsx +12 -8
  34. package/src/widgets/__tests__/passage_test.jsx +81 -0
  35. package/src/widgets/__tests__/radio/base-radio_test.jsx +291 -41
  36. package/src/widgets/__tests__/radio/choice-icon_test.jsx +67 -0
  37. package/src/widgets/__tests__/radio/choice_test.jsx +302 -0
  38. package/src/widgets/__tests__/radio/focus-ring_test.jsx +23 -0
  39. package/src/widgets/__tests__/radio/option-status_test.jsx +65 -0
  40. package/src/widgets/__tests__/radio_test.jsx +39 -15
  41. package/src/widgets/definition.jsx +1 -1
  42. package/src/widgets/group.jsx +84 -102
  43. package/src/widgets/input-number.jsx +6 -1
  44. package/src/widgets/numeric-input.jsx +87 -80
  45. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +0 -9
  46. package/src/widgets/passage/get-line-height-for-node.js +4 -5
  47. package/src/widgets/passage/passage-markdown.jsx +176 -120
  48. package/src/widgets/passage.jsx +48 -46
  49. package/src/widgets/radio/__stories__/base-radio.stories.jsx +164 -17
  50. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +202 -111
  51. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +21 -6
  52. package/src/widgets/radio/__stories__/choice.stories.jsx +138 -0
  53. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +29 -23
  54. package/src/widgets/radio/__stories__/option-status.stories.jsx +93 -99
  55. package/src/widgets/radio/base-radio.jsx +485 -556
  56. package/src/widgets/radio/choice-a11y-text.js +62 -0
  57. package/src/widgets/radio/choice-icon/choice-icon.jsx +87 -0
  58. package/src/widgets/radio/choice-icon/cross-out-line.jsx +55 -0
  59. package/src/widgets/radio/choice-icon/library-choice-icon.jsx +214 -0
  60. package/src/widgets/radio/choice-icon/sat-choice-icon.jsx +120 -0
  61. package/src/widgets/radio/choice-icon/shared-styles.js +17 -0
  62. package/src/widgets/radio/choice-none-above.jsx +49 -40
  63. package/src/widgets/radio/choice.jsx +319 -347
  64. package/src/widgets/radio/choice.jsx.fixture.js +0 -18
  65. package/src/widgets/radio/focus-ring.jsx +29 -22
  66. package/src/widgets/radio/option-status.jsx +58 -36
  67. package/src/widgets/radio/widget.jsx +181 -136
  68. package/src/widgets/radio.jsx +16 -17
  69. package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +0 -64
  70. package/src/widgets/radio/choice-icon.jsx +0 -469
  71. 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("checkbox")[4]);
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("checkbox")[4]).toBeChecked();
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("checkbox")[4]);
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("checkbox")[2]); // Incorrect!
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
- status: "correct",
534
- value: 1,
535
- },
536
- {
537
- status: "correct",
538
- value: 1,
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
- const apiOptions: APIOptions = {
18
- styling: {radioStyleVersion: "final"},
104
+ let updatedValues = null;
105
+ const onChangeHandler = (newValues) => {
106
+ updatedValues = newValues;
19
107
  };
20
- const onChangeHandler = () => {};
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
- render(
24
- <BaseRadio
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(screen.getAllByRole("listitem")).toHaveLength(4);
126
+ expect(updatedValues).toMatchObject({
127
+ checked: [false, false, true, false],
128
+ });
43
129
  });
130
+ });
44
131
 
45
- it("should toggle choice when inner element clicked", () => {
132
+ describe("selecting and deselecting options", () => {
133
+ it("deselects multi-select choices", () => {
46
134
  // Arrange
47
- const apiOptions: APIOptions = {
48
- styling: {radioStyleVersion: "final"},
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
- render(
56
- <BaseRadio
57
- multipleSelect={false}
58
- countChoices={false}
59
- numCorrect={1}
60
- editMode={true}
61
- labelWrap={false}
62
- apiOptions={apiOptions}
63
- choices={[
64
- {content: "Option 1", correct: false},
65
- {content: "Option B", correct: false},
66
- {content: "Option Gamma", correct: true},
67
- {content: "Option Delta", correct: false},
68
- ]}
69
- onChange={onChangeHandler}
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(screen.getByText("(Choice C)", {selector: "div"}));
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
+ });