Skip to content

Commit 5c3578f

Browse files
authored
[Dropdown] Create dropdown widget generator for testdata (#3096)
## Summary: Create a generator for the Dropdown widget to make testdata easier to read and write. Issue: https://khanacademy.atlassian.net/browse/LEMS-3771 ## Test plan: `pnpm jest` Author: nishasy Reviewers: catandthemachines Required Reviewers: Approved By: catandthemachines Checks: ⏭️ 1 check has been skipped, ✅ 10 checks were successful Pull Request URL: #3096
1 parent de07b91 commit 5c3578f

File tree

17 files changed

+409
-344
lines changed

17 files changed

+409
-344
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@khanacademy/perseus": patch
3+
"@khanacademy/perseus-core": patch
4+
"@khanacademy/perseus-editor": patch
5+
"@khanacademy/perseus-score": patch
6+
---
7+
8+
[Dropdown] Create dropdown widget generator for testdata

packages/perseus-core/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ export {
207207
} from "./utils/util.graphie";
208208

209209
// Generators
210+
export {
211+
generateDropdownOptions,
212+
generateDropdownWidget,
213+
} from "./utils/generators/dropdown-widget-generator";
210214
export {
211215
generateExpressionOptions,
212216
generateExpressionAnswerForm,
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import {
2+
generateDropdownOptions,
3+
generateDropdownWidget,
4+
} from "./dropdown-widget-generator";
5+
6+
import type {
7+
DropdownWidget,
8+
PerseusDropdownWidgetOptions,
9+
} from "../../data-schema";
10+
11+
describe("generateDropdownOptions", () => {
12+
it("builds a default dropdown options", () => {
13+
// Arrange, Act
14+
const options: PerseusDropdownWidgetOptions = generateDropdownOptions();
15+
16+
// Assert
17+
expect(options.choices).toEqual([{content: "", correct: false}]);
18+
expect(options.placeholder).toBe("");
19+
expect(options.visibleLabel).toBeUndefined();
20+
expect(options.ariaLabel).toBeUndefined();
21+
});
22+
23+
it("builds a dropdown options with all props", () => {
24+
// Arrange, Act
25+
const options: PerseusDropdownWidgetOptions = generateDropdownOptions({
26+
choices: [
27+
{content: "test-choice", correct: true},
28+
{content: "test-choice-2", correct: false},
29+
],
30+
placeholder: "test-placeholder",
31+
visibleLabel: "test-visible-label",
32+
ariaLabel: "test-aria-label",
33+
});
34+
35+
// Assert
36+
expect(options.choices).toEqual([
37+
{content: "test-choice", correct: true},
38+
{content: "test-choice-2", correct: false},
39+
]);
40+
expect(options.placeholder).toBe("test-placeholder");
41+
expect(options.visibleLabel).toBe("test-visible-label");
42+
expect(options.ariaLabel).toBe("test-aria-label");
43+
});
44+
});
45+
46+
describe("generateDropdownWidget", () => {
47+
it("builds a default dropdown widget", () => {
48+
// Arrange, Act
49+
const widget: DropdownWidget = generateDropdownWidget();
50+
51+
// Assert
52+
expect(widget.type).toBe("dropdown");
53+
expect(widget.graded).toBe(true);
54+
expect(widget.static).toBe(false);
55+
expect(widget.version).toEqual({major: 0, minor: 0});
56+
expect(widget.alignment).toBe("default");
57+
expect(widget.options).toEqual({
58+
choices: [{content: "", correct: false}],
59+
placeholder: "",
60+
visibleLabel: undefined,
61+
ariaLabel: undefined,
62+
});
63+
});
64+
65+
it("builds a dropdown widget with all props", () => {
66+
// Arrange, Act
67+
const widget: DropdownWidget = generateDropdownWidget({
68+
graded: false,
69+
version: {major: 1, minor: 0},
70+
static: true,
71+
alignment: "block",
72+
options: {
73+
choices: [
74+
{content: "test-choice", correct: true},
75+
{content: "test-choice-2", correct: false},
76+
],
77+
placeholder: "test-placeholder",
78+
visibleLabel: "test-visible-label",
79+
ariaLabel: "test-aria-label",
80+
static: false,
81+
},
82+
});
83+
84+
// Assert
85+
expect(widget.static).toBe(true);
86+
expect(widget.graded).toBe(false);
87+
expect(widget.alignment).toBe("block");
88+
expect(widget.options).toEqual({
89+
choices: [
90+
{content: "test-choice", correct: true},
91+
{content: "test-choice-2", correct: false},
92+
],
93+
placeholder: "test-placeholder",
94+
visibleLabel: "test-visible-label",
95+
ariaLabel: "test-aria-label",
96+
static: false,
97+
});
98+
});
99+
100+
it("adds options when option builder is used", () => {
101+
// Arrange, Act
102+
const widget: DropdownWidget = generateDropdownWidget({
103+
graded: false,
104+
version: {major: 1, minor: 0},
105+
static: true,
106+
alignment: "block",
107+
options: generateDropdownOptions({
108+
choices: [
109+
{content: "test-choice", correct: true},
110+
{content: "test-choice-2", correct: false},
111+
],
112+
placeholder: "test-placeholder",
113+
visibleLabel: "test-visible-label",
114+
ariaLabel: "test-aria-label",
115+
}),
116+
});
117+
118+
// Assert
119+
expect(widget.static).toBe(true);
120+
expect(widget.graded).toBe(false);
121+
expect(widget.alignment).toBe("block");
122+
expect(widget.options).toEqual({
123+
choices: [
124+
{content: "test-choice", correct: true},
125+
{content: "test-choice-2", correct: false},
126+
],
127+
placeholder: "test-placeholder",
128+
visibleLabel: "test-visible-label",
129+
ariaLabel: "test-aria-label",
130+
});
131+
});
132+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import dropdownWidgetLogic from "../../widgets/dropdown";
2+
3+
import type {
4+
DropdownWidget,
5+
PerseusDropdownWidgetOptions,
6+
} from "../../data-schema";
7+
8+
export function generateDropdownOptions(
9+
options?: Partial<PerseusDropdownWidgetOptions>,
10+
): PerseusDropdownWidgetOptions {
11+
return {
12+
...dropdownWidgetLogic.defaultWidgetOptions,
13+
...options,
14+
};
15+
}
16+
17+
export function generateDropdownWidget(
18+
dropdownWidgetProperties?: Partial<Omit<DropdownWidget, "type">>,
19+
): DropdownWidget {
20+
return {
21+
type: "dropdown",
22+
graded: true,
23+
version: {major: 0, minor: 0},
24+
static: false,
25+
alignment: "default",
26+
options: generateDropdownOptions(),
27+
...dropdownWidgetProperties,
28+
};
29+
}

packages/perseus-editor/src/__testdata__/all-widgets.testdata.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
generateDropdownOptions,
3+
generateDropdownWidget,
24
generateExpressionAnswerForm,
35
generateExpressionOptions,
46
generateExpressionWidget,
@@ -73,21 +75,16 @@ export const comprehensiveQuestion: PerseusRenderer = {
7375
},
7476
},
7577

76-
"dropdown 1": {
77-
graded: true,
78-
version: {major: 0, minor: 0},
79-
static: false,
80-
type: "dropdown",
81-
options: {
82-
static: false,
78+
"dropdown 1": generateDropdownWidget({
79+
options: generateDropdownOptions({
8380
placeholder: "choose one",
8481
choices: [
8582
{content: "greater", correct: true},
8683
{content: "less", correct: false},
8784
{content: "equal", correct: false},
8885
],
89-
},
90-
},
86+
}),
87+
}),
9188
"categorizer 1": {
9289
graded: true,
9390
version: {major: 0, minor: 0},
Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
import type {PerseusRenderer} from "@khanacademy/perseus-core";
1+
import {
2+
generateDropdownOptions,
3+
generateDropdownWidget,
4+
type PerseusRenderer,
5+
} from "@khanacademy/perseus-core";
26

37
export const question: PerseusRenderer = {
48
content:
59
"The total number of boxes the forklift can carry is [[☃ dropdown 1]] $60$.",
610
images: {},
711
widgets: {
8-
"dropdown 1": {
9-
type: "dropdown",
10-
alignment: "default",
11-
static: false,
12-
graded: true,
13-
options: {
14-
static: false,
12+
"dropdown 1": generateDropdownWidget({
13+
options: generateDropdownOptions({
1514
placeholder: "greater/less than or equal to",
1615
choices: [
1716
{
@@ -23,11 +22,7 @@ export const question: PerseusRenderer = {
2322
correct: true,
2423
},
2524
],
26-
},
27-
version: {
28-
major: 0,
29-
minor: 0,
30-
},
31-
},
25+
}),
26+
}),
3227
},
3328
};

packages/perseus-score/src/had-empty-diner-widgets.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
generateDropdownOptions,
3+
generateDropdownWidget,
24
generateExpressionAnswerForm,
35
generateExpressionOptions,
46
generateExpressionWidget,
@@ -14,12 +16,11 @@ import {
1416
import hasEmptyDINERWidgets from "./has-empty-diner-widgets";
1517

1618
function generateBasicDropdownQuestion(): PerseusRenderer {
17-
return {
19+
return generateTestPerseusRenderer({
1820
content: "[[☃ dropdown 1]]",
1921
widgets: {
20-
"dropdown 1": {
21-
type: "dropdown",
22-
options: {
22+
"dropdown 1": generateDropdownWidget({
23+
options: generateDropdownOptions({
2324
choices: [
2425
{
2526
content: "Correct",
@@ -31,12 +32,11 @@ function generateBasicDropdownQuestion(): PerseusRenderer {
3132
},
3233
],
3334
placeholder: "Pick one",
34-
static: false,
35-
},
36-
},
35+
}),
36+
}),
3737
},
3838
images: {},
39-
};
39+
});
4040
}
4141

4242
function generateBasicNumericInputQuestion(): PerseusRenderer {

packages/perseus-score/src/score.test.ts

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
generateDropdownOptions,
3+
generateDropdownWidget,
24
type DropdownWidget,
35
type PerseusWidgetsMap,
46
type UserInputMap,
@@ -389,14 +391,9 @@ describe("scoreWidgetsFunctional", () => {
389391
});
390392
});
391393

392-
function generateDropdown(): DropdownWidget {
393-
return {
394-
type: "dropdown",
395-
alignment: "default",
396-
static: false,
397-
graded: true,
398-
options: {
399-
static: false,
394+
function generateBasicDropdown(): DropdownWidget {
395+
return generateDropdownWidget({
396+
options: generateDropdownOptions({
400397
ariaLabel: "Test ARIA label",
401398
visibleLabel: "Test visible label",
402399
placeholder: "Answer me",
@@ -410,20 +407,16 @@ function generateDropdown(): DropdownWidget {
410407
correct: true,
411408
},
412409
],
413-
},
414-
version: {
415-
major: 0,
416-
minor: 0,
417-
},
418-
};
410+
}),
411+
});
419412
}
420413

421414
describe("scorePerseusItem", () => {
422415
it("returns a score of 'invalid' if some widgets are missing from the user input", () => {
423416
// Arrange:
424417
const item = {
425418
content: "[[☃ dropdown 1]]",
426-
widgets: {"dropdown 1": generateDropdown()},
419+
widgets: {"dropdown 1": generateBasicDropdown()},
427420
images: {},
428421
};
429422
const userInputMap = {};
@@ -441,8 +434,8 @@ describe("scorePerseusItem", () => {
441434
{
442435
content: "[[☃ dropdown 1]] [[☃ dropdown 2]]",
443436
widgets: {
444-
"dropdown 1": generateDropdown(),
445-
"dropdown 2": generateDropdown(),
437+
"dropdown 1": generateBasicDropdown(),
438+
"dropdown 2": generateBasicDropdown(),
446439
},
447440
images: {},
448441
},
@@ -465,8 +458,8 @@ describe("scorePerseusItem", () => {
465458
{
466459
content: "[[☃ dropdown 1]] [[☃ dropdown 2]]",
467460
widgets: {
468-
"dropdown 1": generateDropdown(),
469-
"dropdown 2": generateDropdown(),
461+
"dropdown 1": generateBasicDropdown(),
462+
"dropdown 2": generateBasicDropdown(),
470463
},
471464
images: {},
472465
},
@@ -490,7 +483,7 @@ describe("scorePerseusItem", () => {
490483
const json = {
491484
content: "[[☃ dropdown 1]]",
492485
widgets: {
493-
"dropdown 1": generateDropdown(),
486+
"dropdown 1": generateBasicDropdown(),
494487
},
495488
images: {},
496489
};
@@ -507,8 +500,8 @@ describe("scorePerseusItem", () => {
507500
{
508501
content: "[[☃ dropdown 1]]",
509502
widgets: {
510-
"dropdown 1": generateDropdown(),
511-
"dropdown 2": generateDropdown(),
503+
"dropdown 1": generateBasicDropdown(),
504+
"dropdown 2": generateBasicDropdown(),
512505
},
513506
images: {},
514507
},

0 commit comments

Comments
 (0)