Skip to content

Commit d7bcb14

Browse files
authored
Move Radio upgrade logic (#2142)
## Summary: We need to be able to upgrade widgets on the server, this supports doing that for Radio Issue: LEMS-2737 ## Test plan: Nothing should change, just moving code Author: handeyeco Reviewers: jeremywiebe Required Reviewers: Approved By: jeremywiebe Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x) Pull Request URL: #2142
1 parent 1355d6c commit d7bcb14

File tree

9 files changed

+113
-64
lines changed

9 files changed

+113
-64
lines changed

.changeset/curvy-dragons-wash.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@khanacademy/perseus-core": minor
3+
"@khanacademy/perseus": patch
4+
"@khanacademy/perseus-editor": patch
5+
---
6+
7+
Move Radio upgrade logic to Perseus Core

packages/perseus-core/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export {default as expressionLogic} from "./widgets/expression";
3636
export type {ExpressionDefaultWidgetOptions} from "./widgets/expression";
3737
export {default as measurerLogic} from "./widgets/measurer";
3838
export type {MeasurerDefaultWidgetOptions} from "./widgets/measurer";
39+
export {default as radioLogic} from "./widgets/radio";
40+
export type {RadioDefaultWidgetOptions} from "./widgets/radio";
3941

4042
export type * from "./widgets/logic-export.types";
4143

packages/perseus-core/src/widgets/expression/expression-upgrade.ts

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type {PerseusExpressionWidgetOptions} from "../../data-schema";
33
export const currentVersion = {major: 1, minor: 0};
44

55
export const widgetOptionsUpgrades = {
6-
/* c8 ignore next */
76
"1": (v0options: any): PerseusExpressionWidgetOptions => ({
87
times: v0options.times,
98
buttonSets: v0options.buttonSets,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {
2+
currentVersion,
3+
defaultWidgetOptions,
4+
widgetOptionsUpgrades,
5+
} from "./radio-upgrade";
6+
7+
import type {WidgetLogic} from "../logic-export.types";
8+
9+
export type {RadioDefaultWidgetOptions} from "./radio-upgrade";
10+
11+
const radioWidgetLogic: WidgetLogic = {
12+
name: "radio",
13+
version: currentVersion,
14+
widgetOptionsUpgrades: widgetOptionsUpgrades,
15+
defaultWidgetOptions: defaultWidgetOptions,
16+
};
17+
18+
export default radioWidgetLogic;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {widgetOptionsUpgrades} from "./radio-upgrade";
2+
3+
import type {PerseusRadioWidgetOptions} from "../../data-schema";
4+
5+
describe("widgetOptionsUpgrades", () => {
6+
it("can upgrade from v0 to v1", () => {
7+
const v0options = {
8+
choices: [{content: "Choice 1"}, {content: "Choice 2"}],
9+
};
10+
11+
const expected: PerseusRadioWidgetOptions = {
12+
choices: [{content: "Choice 1"}, {content: "Choice 2"}],
13+
hasNoneOfTheAbove: false,
14+
};
15+
16+
const result: PerseusRadioWidgetOptions =
17+
widgetOptionsUpgrades["1"](v0options);
18+
19+
expect(result).toEqual(expected);
20+
});
21+
22+
it("throws from noneOfTheAbove", () => {
23+
const v0options = {
24+
choices: [{content: "Choice 1"}, {content: "Choice 2"}],
25+
noneOfTheAbove: true,
26+
};
27+
28+
expect(() => widgetOptionsUpgrades["1"](v0options)).toThrow(
29+
"radio widget v0 no longer supports auto noneOfTheAbove",
30+
);
31+
});
32+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type {PerseusRadioWidgetOptions} from "../../data-schema";
2+
3+
export const currentVersion = {major: 1, minor: 0};
4+
5+
export const widgetOptionsUpgrades = {
6+
"1": (v0props: any): PerseusRadioWidgetOptions => {
7+
const {noneOfTheAbove, ...rest} = v0props;
8+
9+
if (noneOfTheAbove) {
10+
throw new Error(
11+
"radio widget v0 no longer supports auto noneOfTheAbove",
12+
);
13+
}
14+
15+
return {
16+
...rest,
17+
hasNoneOfTheAbove: false,
18+
};
19+
},
20+
} as const;
21+
22+
export type RadioDefaultWidgetOptions = Pick<
23+
PerseusRadioWidgetOptions,
24+
| "choices"
25+
| "displayCount"
26+
| "randomize"
27+
| "hasNoneOfTheAbove"
28+
| "multipleSelect"
29+
| "countChoices"
30+
| "deselectEnabled"
31+
>;
32+
33+
export const defaultWidgetOptions: RadioDefaultWidgetOptions = {
34+
choices: [{}, {}, {}, {}] as any,
35+
displayCount: null,
36+
randomize: false,
37+
hasNoneOfTheAbove: false,
38+
multipleSelect: false,
39+
countChoices: false,
40+
deselectEnabled: false,
41+
};

packages/perseus-editor/src/widgets/radio/editor.tsx

+6-9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import {
66
Changeable,
77
iconTrash,
88
} from "@khanacademy/perseus";
9+
import {
10+
radioLogic,
11+
type RadioDefaultWidgetOptions,
12+
} from "@khanacademy/perseus-core";
913
import {Checkbox} from "@khanacademy/wonder-blocks-form";
1014
import PropTypes from "prop-types";
1115
import * as React from "react";
@@ -119,15 +123,8 @@ class RadioEditor extends React.Component<any> {
119123

120124
static widgetName = "radio" as const;
121125

122-
static defaultProps: any = {
123-
choices: [{}, {}, {}, {}],
124-
displayCount: null,
125-
randomize: false,
126-
hasNoneOfTheAbove: false,
127-
multipleSelect: false,
128-
countChoices: false,
129-
deselectEnabled: false,
130-
};
126+
static defaultProps: RadioDefaultWidgetOptions =
127+
radioLogic.defaultWidgetOptions;
131128

132129
change: (...args: ReadonlyArray<unknown>) => any = (...args) => {
133130
// @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.

packages/perseus/src/widgets/radio/__tests__/radio.test.ts

+1-34
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import * as Dependencies from "../../../dependencies";
1212
import {scorePerseusItemTesting} from "../../../util/test-utils";
1313
import {renderQuestion} from "../../__testutils__/renderQuestion";
1414
import PassageWidget from "../../passage";
15-
import RadioWidgetExport from "../radio";
1615

1716
import {
1817
questionAndAnswer,
@@ -22,10 +21,7 @@ import {
2221
} from "./radio.testdata";
2322

2423
import type {APIOptions} from "../../../types";
25-
import type {
26-
PerseusRadioWidgetOptions,
27-
PerseusRenderer,
28-
} from "@khanacademy/perseus-core";
24+
import type {PerseusRenderer} from "@khanacademy/perseus-core";
2925
import type {UserEvent} from "@testing-library/user-event";
3026

3127
const selectOption = async (
@@ -1061,33 +1057,4 @@ describe("Radio Widget", () => {
10611057
expect(rendererScore).toHaveBeenAnsweredIncorrectly();
10621058
});
10631059
});
1064-
1065-
describe("propsUpgrade", () => {
1066-
it("can upgrade from v0 to v1", () => {
1067-
const v0props = {
1068-
choices: [{content: "Choice 1"}, {content: "Choice 2"}],
1069-
};
1070-
1071-
const expected: PerseusRadioWidgetOptions = {
1072-
choices: [{content: "Choice 1"}, {content: "Choice 2"}],
1073-
hasNoneOfTheAbove: false,
1074-
};
1075-
1076-
const result: PerseusRadioWidgetOptions =
1077-
RadioWidgetExport.propUpgrades["1"](v0props);
1078-
1079-
expect(result).toEqual(expected);
1080-
});
1081-
1082-
it("throws from noneOfTheAbove", () => {
1083-
const v0props = {
1084-
choices: [{content: "Choice 1"}, {content: "Choice 2"}],
1085-
noneOfTheAbove: true,
1086-
};
1087-
1088-
expect(() => RadioWidgetExport.propUpgrades["1"](v0props)).toThrow(
1089-
"radio widget v0 no longer supports auto noneOfTheAbove",
1090-
);
1091-
});
1092-
});
10931060
});

packages/perseus/src/widgets/radio/radio.ts

+6-20
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
radioLogic,
3+
type PerseusRadioWidgetOptions,
4+
} from "@khanacademy/perseus-core";
15
import {scoreRadio, validateRadio} from "@khanacademy/perseus-score";
26
import _ from "underscore";
37

@@ -8,7 +12,6 @@ import Radio from "./radio-component";
812
import type {RenderProps, RadioChoiceWithMetadata} from "./radio-component";
913
import type {PerseusStrings} from "../../strings";
1014
import type {WidgetExports} from "../../types";
11-
import type {PerseusRadioWidgetOptions} from "@khanacademy/perseus-core";
1215

1316
const {shuffle, random} = Util;
1417

@@ -125,32 +128,15 @@ const transform = (
125128
};
126129
};
127130

128-
const propUpgrades = {
129-
"1": (v0props: any): PerseusRadioWidgetOptions => {
130-
const {noneOfTheAbove, ...rest} = v0props;
131-
132-
if (noneOfTheAbove) {
133-
throw new Error(
134-
"radio widget v0 no longer supports auto noneOfTheAbove",
135-
);
136-
}
137-
138-
return {
139-
...rest,
140-
hasNoneOfTheAbove: false,
141-
};
142-
},
143-
} as const;
144-
145131
export default {
146132
name: "radio",
147133
displayName: "Radio / Multiple choice",
148134
accessible: true,
149135
widget: Radio,
150136
transform: transform,
151137
staticTransform: transform,
152-
version: {major: 1, minor: 0},
153-
propUpgrades: propUpgrades,
138+
version: radioLogic.version,
139+
propUpgrades: radioLogic.widgetOptionsUpgrades,
154140
isLintable: true,
155141
// TODO(LEMS-2656): remove TS suppression
156142
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput

0 commit comments

Comments
 (0)