diff --git a/.changeset/sour-ways-wave.md b/.changeset/sour-ways-wave.md new file mode 100644 index 0000000000..33f6cc1eee --- /dev/null +++ b/.changeset/sour-ways-wave.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": minor +--- + +Create a helper function for generating correctly typed Perseus test items diff --git a/packages/perseus/src/index.ts b/packages/perseus/src/index.ts index d6a689ab15..0f3d2147a9 100644 --- a/packages/perseus/src/index.ts +++ b/packages/perseus/src/index.ts @@ -84,6 +84,10 @@ export { injectWidgets, } from "./util/extract-perseus-data"; export {parsePerseusItem} from "./util/parse-perseus-json"; +export { + generateTestPerseusItem, + genericPerseusItemData, +} from "./util/test-utils"; /** * Mixins diff --git a/packages/perseus/src/util/test-utils.test.ts b/packages/perseus/src/util/test-utils.test.ts new file mode 100644 index 0000000000..c5905d3aa3 --- /dev/null +++ b/packages/perseus/src/util/test-utils.test.ts @@ -0,0 +1,34 @@ +import {generateTestPerseusItem} from "./test-utils"; +import { + basicObject, + customQuestionInfo, + expectedQuestionInfoAdded, + customAnswerAreaInfo, + expectedAnswerAreaInfoAdded, + customHintsInfo, + expectedHintsInfoAdded, +} from "./test-utils.testdata"; + +describe("generateTestPerseusItem", () => { + it("should provide a basic Perseus item object with no inputs", () => { + expect(generateTestPerseusItem()).toEqual(basicObject); + }); + + it("should replace question parts when given question custom info", () => { + expect(generateTestPerseusItem(customQuestionInfo)).toEqual( + expectedQuestionInfoAdded, + ); + }); + + it("should replace answer area parts when given answer area custom info", () => { + expect(generateTestPerseusItem(customAnswerAreaInfo)).toEqual( + expectedAnswerAreaInfoAdded, + ); + }); + + it("should add hints when given custom info containing hints", () => { + expect(generateTestPerseusItem(customHintsInfo)).toEqual( + expectedHintsInfoAdded, + ); + }); +}); diff --git a/packages/perseus/src/util/test-utils.testdata.ts b/packages/perseus/src/util/test-utils.testdata.ts new file mode 100644 index 0000000000..771f9a8012 --- /dev/null +++ b/packages/perseus/src/util/test-utils.testdata.ts @@ -0,0 +1,219 @@ +import type {PerseusItem} from "@khanacademy/perseus"; + +export const basicObject: PerseusItem = { + question: { + content: "", + images: {}, + widgets: {}, + }, + answerArea: { + calculator: false, + chi2Table: false, + periodicTable: false, + tTable: false, + zTable: false, + financialCalculatorMonthlyPayment: false, + financialCalculatorTotalAmount: false, + financialCalculatorTimeToPayOff: false, + periodicTableWithKey: false, + }, + itemDataVersion: { + major: 0, + minor: 1, + }, + hints: [], + _multi: null, + answer: null, +}; + +export const customQuestionInfo: Partial = { + question: { + content: "Test content string", + images: {"Test image string": {width: 200, height: 200}}, + widgets: { + "input-number 1": { + type: "input-number", + graded: true, + options: { + value: 123, + simplify: "required", + size: "small", + inexact: false, + maxError: 0.123, + answerType: "number", + }, + }, + }, + }, +}; + +export const expectedQuestionInfoAdded: PerseusItem = { + question: { + content: "Test content string", + images: {"Test image string": {width: 200, height: 200}}, + widgets: { + "input-number 1": { + type: "input-number", + graded: true, + options: { + value: 123, + simplify: "required", + size: "small", + inexact: false, + maxError: 0.123, + answerType: "number", + }, + }, + }, + }, + answerArea: { + calculator: false, + chi2Table: false, + periodicTable: false, + tTable: false, + zTable: false, + financialCalculatorMonthlyPayment: false, + financialCalculatorTotalAmount: false, + financialCalculatorTimeToPayOff: false, + periodicTableWithKey: false, + }, + itemDataVersion: { + major: 0, + minor: 1, + }, + hints: [], + _multi: null, + answer: null, +}; + +export const customAnswerAreaInfo: Partial = { + answerArea: { + calculator: true, + chi2Table: true, + periodicTable: true, + tTable: true, + zTable: true, + financialCalculatorMonthlyPayment: true, + financialCalculatorTotalAmount: true, + financialCalculatorTimeToPayOff: true, + periodicTableWithKey: true, + }, +}; + +export const expectedAnswerAreaInfoAdded: PerseusItem = { + question: { + content: "", + images: {}, + widgets: {}, + }, + answerArea: { + calculator: true, + chi2Table: true, + periodicTable: true, + tTable: true, + zTable: true, + financialCalculatorMonthlyPayment: true, + financialCalculatorTotalAmount: true, + financialCalculatorTimeToPayOff: true, + periodicTableWithKey: true, + }, + itemDataVersion: { + major: 0, + minor: 1, + }, + hints: [], + _multi: null, + answer: null, +}; + +export const customHintsInfo: Partial = { + hints: [ + { + content: "Test content string", + images: { + "Test images string": {height: 200, width: 200}, + }, + widgets: { + "radio 1": { + graded: true, + options: { + choices: [ + { + content: "Test content string", + correct: true, + }, + { + content: "Test content string 2", + correct: false, + }, + ], + deselectEnabled: false, + displayCount: null, + multipleSelect: false, + noneOfTheAbove: false, + onePerLine: true, + randomize: true, + }, + type: "radio", + }, + }, + }, + ], +}; + +export const expectedHintsInfoAdded: PerseusItem = { + question: { + content: "", + images: {}, + widgets: {}, + }, + answerArea: { + calculator: false, + chi2Table: false, + periodicTable: false, + tTable: false, + zTable: false, + financialCalculatorMonthlyPayment: false, + financialCalculatorTotalAmount: false, + financialCalculatorTimeToPayOff: false, + periodicTableWithKey: false, + }, + itemDataVersion: { + major: 0, + minor: 1, + }, + hints: [ + { + content: "Test content string", + images: { + "Test images string": {height: 200, width: 200}, + }, + widgets: { + "radio 1": { + graded: true, + options: { + choices: [ + { + content: "Test content string", + correct: true, + }, + { + content: "Test content string 2", + correct: false, + }, + ], + deselectEnabled: false, + displayCount: null, + multipleSelect: false, + noneOfTheAbove: false, + onePerLine: true, + randomize: true, + }, + type: "radio", + }, + }, + }, + ], + _multi: null, + answer: null, +}; diff --git a/packages/perseus/src/util/test-utils.ts b/packages/perseus/src/util/test-utils.ts new file mode 100644 index 0000000000..62c4c54994 --- /dev/null +++ b/packages/perseus/src/util/test-utils.ts @@ -0,0 +1,42 @@ +import type {PerseusItem} from "../perseus-types"; + +export const genericPerseusItemData: PerseusItem = { + question: { + content: "", + images: {}, + widgets: {}, + }, + answerArea: { + calculator: false, + chi2Table: false, + periodicTable: false, + tTable: false, + zTable: false, + financialCalculatorMonthlyPayment: false, + financialCalculatorTotalAmount: false, + financialCalculatorTimeToPayOff: false, + periodicTableWithKey: false, + }, + itemDataVersion: { + major: 0, + minor: 1, + }, + hints: [], + _multi: null, + answer: null, +} as const; + +/** + * Generate a Perseus item object for testing purposes. + * + * In order to better type Perseus objects used in testing, this function + * uses a basic Perseus object and updates it with custom values as needed. + * + * @param {Partial} customFields + * @returns PerseusItem + */ +export function generateTestPerseusItem( + customFields: Partial = {}, +): PerseusItem { + return {...genericPerseusItemData, ...customFields}; +}