diff --git a/.changeset/yellow-ducks-march.md b/.changeset/yellow-ducks-march.md new file mode 100644 index 0000000000..6346118468 --- /dev/null +++ b/.changeset/yellow-ducks-march.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": patch +--- + +Internal: Migrate expression widget options to the latest version in parseAndTypecheckPerseusItem (not yet used in production). diff --git a/packages/perseus/src/util/parse-perseus-json/perseus-parsers/expression-widget.test.ts b/packages/perseus/src/util/parse-perseus-json/perseus-parsers/expression-widget.test.ts new file mode 100644 index 0000000000..a0e623246a --- /dev/null +++ b/packages/perseus/src/util/parse-perseus-json/perseus-parsers/expression-widget.test.ts @@ -0,0 +1,76 @@ +import {parse} from "../parse"; +import {failure, success} from "../result"; + +import {parseExpressionWidget} from "./expression-widget"; + +describe("parseExpressionWidget", () => { + it("migrates v0 options to v1", () => { + const widget = { + type: "expression", + graded: true, + options: { + value: "the value", + form: false, + simplify: false, + times: false, + buttonsVisible: "never", + buttonSets: ["basic"], + functions: ["f", "g", "h"], + }, + version: { + major: 0, + minor: 1, + }, + }; + + expect(parse(widget, parseExpressionWidget)).toEqual( + success({ + type: "expression", + graded: true, + static: undefined, + key: undefined, + alignment: undefined, + options: { + times: false, + ariaLabel: undefined, + visibleLabel: undefined, + buttonsVisible: "never", + buttonSets: ["basic"], + functions: ["f", "g", "h"], + answerForms: [ + { + considered: "correct", + form: false, + simplify: false, + value: "the value", + }, + ], + }, + version: { + major: 1, + minor: 0, + }, + }), + ); + }); + + it("rejects a widget with unrecognized version", () => { + const widget = { + type: "expression", + version: { + major: -1, + minor: 0, + }, + graded: true, + options: {}, + }; + + expect(parse(widget, parseExpressionWidget)).toEqual( + failure( + expect.stringContaining( + "At (root).version.major -- expected 0, but got -1", + ), + ), + ); + }); +}); diff --git a/packages/perseus/src/util/parse-perseus-json/perseus-parsers/expression-widget.ts b/packages/perseus/src/util/parse-perseus-json/perseus-parsers/expression-widget.ts index 0622c99aa2..3d181092fe 100644 --- a/packages/perseus/src/util/parse-perseus-json/perseus-parsers/expression-widget.ts +++ b/packages/perseus/src/util/parse-perseus-json/perseus-parsers/expression-widget.ts @@ -1,3 +1,4 @@ +import ExpressionWidgetModule from "../../../widgets/expression/expression"; import { array, boolean, @@ -11,13 +12,18 @@ import { union, } from "../general-purpose-parsers"; -import {parseWidget} from "./widget"; +import {parseWidgetWithVersion} from "./widget"; import type { ExpressionWidget, PerseusExpressionAnswerForm, } from "../../../perseus-types"; -import type {Parser} from "../parser-types"; +import type { + ParseContext, + ParsedValue, + Parser, + ParseResult, +} from "../parser-types"; const parseAnswerForm: Parser = object({ value: string, @@ -29,14 +35,42 @@ const parseAnswerForm: Parser = object({ ).parser, }); -export const parseExpressionWidget: Parser = parseWidget( +const parseExpressionWidgetV1: Parser = + parseWidgetWithVersion( + object({major: constant(1), minor: number}), + constant("expression"), + object({ + answerForms: array(parseAnswerForm), + functions: array(string), + times: boolean, + visibleLabel: optional(string), + ariaLabel: optional(string), + buttonSets: array( + enumeration( + "basic", + "basic+div", + "trig", + "prealgebra", + "logarithms", + "basic relations", + "advanced relations", + ), + ), + buttonsVisible: optional(enumeration("always", "never", "focused")), + }), + ); + +const parseExpressionWidgetV0 = parseWidgetWithVersion( + optional(object({major: constant(0), minor: number})), constant("expression"), object({ - answerForms: array(parseAnswerForm), functions: array(string), times: boolean, visibleLabel: optional(string), ariaLabel: optional(string), + form: boolean, + simplify: boolean, + value: string, buttonSets: array( enumeration( "basic", @@ -51,3 +85,35 @@ export const parseExpressionWidget: Parser = parseWidget( buttonsVisible: optional(enumeration("always", "never", "focused")), }), ); + +function migrateV0ToV1( + widget: ParsedValue, + ctx: ParseContext, +): ParseResult { + const {options} = widget; + return ctx.success({ + ...widget, + version: ExpressionWidgetModule.version, + options: { + times: options.times, + buttonSets: options.buttonSets, + functions: options.functions, + buttonsVisible: options.buttonsVisible, + visibleLabel: options.visibleLabel, + ariaLabel: options.ariaLabel, + + answerForms: [ + { + considered: "correct", + form: options.form, + simplify: options.simplify, + value: options.value, + }, + ], + }, + }); +} + +export const parseExpressionWidget: Parser = union( + parseExpressionWidgetV1, +).or(pipeParsers(parseExpressionWidgetV0).then(migrateV0ToV1).parser).parser; diff --git a/packages/perseus/src/util/parse-perseus-json/perseus-parsers/widget.ts b/packages/perseus/src/util/parse-perseus-json/perseus-parsers/widget.ts index 8fb94450fa..330075c46a 100644 --- a/packages/perseus/src/util/parse-perseus-json/perseus-parsers/widget.ts +++ b/packages/perseus/src/util/parse-perseus-json/perseus-parsers/widget.ts @@ -28,3 +28,23 @@ export function parseWidget( ), }); } + +export function parseWidgetWithVersion< + Version extends {major: number; minor: number} | undefined, + Type extends string, + Options, +>( + parseVersion: Parser, + parseType: Parser, + parseOptions: Parser, +): Parser> { + return object({ + type: parseType, + static: optional(boolean), + graded: optional(boolean), + alignment: optional(string), + options: parseOptions, + key: optional(number), + version: parseVersion, + }); +} diff --git a/packages/perseus/src/util/parse-perseus-json/perseus-parsers/widgets-map.test.ts b/packages/perseus/src/util/parse-perseus-json/perseus-parsers/widgets-map.test.ts index 89f0dead25..9d6a672364 100644 --- a/packages/perseus/src/util/parse-perseus-json/perseus-parsers/widgets-map.test.ts +++ b/packages/perseus/src/util/parse-perseus-json/perseus-parsers/widgets-map.test.ts @@ -134,7 +134,7 @@ describe("parseWidgetsMap", () => { const widgetsMap: unknown = { "expression 1": { type: "expression", - version: {major: 0, minor: 0}, + version: {major: 1, minor: 0}, options: { answerForms: [], buttonSets: [],