@khanacademy/perseus-core 5.4.0 → 5.4.2

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.
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var _ = require('underscore');
6
6
  var KAS = require('@khanacademy/kas');
7
- var perseusCore = require('@khanacademy/perseus-core');
7
+ var perseusUtils = require('@khanacademy/perseus-utils');
8
8
 
9
9
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
10
10
 
@@ -1993,6 +1993,15 @@ const lockedFigureFillStyles = {
1993
1993
 
1994
1994
  // Not associated with a specific figure
1995
1995
 
1996
+ /**
1997
+ * Determines how unsimplified fractions are scored.
1998
+ *
1999
+ * - "required" means unsimplified fractions are considered invalid input, and
2000
+ * the learner can try again.
2001
+ * - "enforced" means unsimplified fractions are marked incorrect.
2002
+ * - "optional" means unsimplified fractions are accepted.
2003
+ */
2004
+
1996
2005
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
1997
2006
 
1998
2007
  // Used to represent 2-D points and ranges
@@ -2277,7 +2286,21 @@ const parseNumberLineWidget = parseWidget(constant("number-line"), object({
2277
2286
  }));
2278
2287
 
2279
2288
  const parseMathFormat = enumeration("integer", "mixed", "improper", "proper", "decimal", "percent", "pi");
2280
- const parseSimplify = enumeration("required", "correct", "enforced", "optional");
2289
+ const parseSimplify = pipeParsers(union(constant(null)).or(constant(undefined)).or(boolean).or(constant("required")).or(constant("correct")).or(constant("enforced")).or(constant("optional")).or(constant("accepted")).parser).then(convert(deprecatedSimplifyValuesToRequired)).parser;
2290
+ function deprecatedSimplifyValuesToRequired(simplify) {
2291
+ switch (simplify) {
2292
+ case "enforced":
2293
+ case "required":
2294
+ case "optional":
2295
+ return simplify;
2296
+ // NOTE(benchristel): "accepted", "correct", true, false, undefined, and
2297
+ // null are all treated the same as "required" during scoring, so we
2298
+ // convert them to "required" here to preserve behavior. See the tests
2299
+ // in score-numeric-input.test.ts
2300
+ default:
2301
+ return "required";
2302
+ }
2303
+ }
2281
2304
  const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
2282
2305
  answers: array(object({
2283
2306
  message: string,
@@ -2292,12 +2315,7 @@ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
2292
2315
  // TODO(benchristel): simplify should never be a boolean, but we
2293
2316
  // have some content where it is anyway. If we ever backfill
2294
2317
  // the data, we should simplify `simplify`.
2295
- simplify: optional(nullable(union(parseSimplify).or(pipeParsers(boolean).then(convert(value => {
2296
- if (typeof value === "boolean") {
2297
- return value ? "required" : "optional";
2298
- }
2299
- return value;
2300
- })).parser).parser))
2318
+ simplify: parseSimplify
2301
2319
  })),
2302
2320
  labelText: optional(string),
2303
2321
  size: string,
@@ -2306,7 +2324,7 @@ const parseNumericInputWidget = parseWidget(constant("numeric-input"), object({
2306
2324
  static: defaulted(boolean, () => false),
2307
2325
  answerForms: optional(array(object({
2308
2326
  name: parseMathFormat,
2309
- simplify: optional(nullable(enumeration("required", "correct", "enforced", "optional")))
2327
+ simplify: parseSimplify
2310
2328
  })))
2311
2329
  }));
2312
2330
 
@@ -2762,10 +2780,10 @@ const parsePerseusItem$1 = object({
2762
2780
  question: parsePerseusRenderer,
2763
2781
  hints: defaulted(array(parseHint), () => []),
2764
2782
  answerArea: parsePerseusAnswerArea,
2765
- itemDataVersion: optional(object({
2783
+ itemDataVersion: optional(nullable(object({
2766
2784
  major: number,
2767
2785
  minor: number
2768
- })),
2786
+ }))),
2769
2787
  // Deprecated field
2770
2788
  answer: any
2771
2789
  });
@@ -2842,51 +2860,10 @@ function throwErrorIfCheatingDetected() {
2842
2860
  }
2843
2861
  }
2844
2862
 
2845
- /**
2846
- * Adds the given perseus library version information to the __perseus_debug__
2847
- * object and ensures that the object is attached to `globalThis` (`window` in
2848
- * browser environments).
2849
- *
2850
- * This allows each library to provide runtime version information to assist in
2851
- * debugging in production environments.
2852
- */
2853
- const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
2854
- // If the library version is the default value, then we don't want to
2855
- // prefix it with a "v" to indicate that it is a version number.
2856
- let prefix = "v";
2857
- if (libraryVersion === "__lib_version__") {
2858
- prefix = "";
2859
- }
2860
- const formattedVersion = `${prefix}${libraryVersion}`;
2861
- if (typeof globalThis !== "undefined") {
2862
- globalThis.__perseus_debug__ = globalThis.__perseus_debug__ ?? {};
2863
- const existingVersionEntry = globalThis.__perseus_debug__[libraryName];
2864
- if (existingVersionEntry) {
2865
- // If we already have an entry and it doesn't match the registered
2866
- // version, we morph the entry into an array and log a warning.
2867
- if (existingVersionEntry !== formattedVersion) {
2868
- // Existing entry might be an array already (oops, at least 2
2869
- // versions of the library already loaded!).
2870
- const allVersions = Array.isArray(existingVersionEntry) ? existingVersionEntry : [existingVersionEntry];
2871
- allVersions.push(formattedVersion);
2872
- globalThis.__perseus_debug__[libraryName] = allVersions;
2873
-
2874
- // eslint-disable-next-line no-console
2875
- console.warn(`Multiple versions of ${libraryName} loaded on this page: ${allVersions.sort().join(", ")}`);
2876
- }
2877
- } else {
2878
- globalThis.__perseus_debug__[libraryName] = formattedVersion;
2879
- }
2880
- } else {
2881
- // eslint-disable-next-line no-console
2882
- console.warn(`globalThis not found found (${formattedVersion})`);
2883
- }
2884
- };
2885
-
2886
2863
  // This file is processed by a Rollup plugin (replace) to inject the production
2887
2864
  const libName = "@khanacademy/perseus-core";
2888
- const libVersion = "5.4.0";
2889
- addLibraryVersionToPerseusDebug(libName, libVersion);
2865
+ const libVersion = "5.4.2";
2866
+ perseusUtils.addLibraryVersionToPerseusDebug(libName, libVersion);
2890
2867
 
2891
2868
  /**
2892
2869
  * @typedef {Object} Errors utility for referencing the Perseus error taxonomy.
@@ -3369,20 +3346,74 @@ const labelImageWidgetLogic = {
3369
3346
  getPublicWidgetOptions: getLabelImagePublicWidgetOptions
3370
3347
  };
3371
3348
 
3349
+ /* Note(tamara): Brought over from the perseus package packages/perseus/src/util.ts file.
3350
+ May be useful to bring other perseus package utilities here. Contains utility functions
3351
+ and types used across multiple widgets for randomization and shuffling. */
3352
+ const seededRNG = function (seed) {
3353
+ let randomSeed = seed;
3354
+ return function () {
3355
+ // Robert Jenkins' 32 bit integer hash function.
3356
+ let seed = randomSeed;
3357
+ seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
3358
+ seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
3359
+ seed = seed + 0x165667b1 + (seed << 5) & 0xffffffff;
3360
+ seed = (seed + 0xd3a2646c ^ seed << 9) & 0xffffffff;
3361
+ seed = seed + 0xfd7046c5 + (seed << 3) & 0xffffffff;
3362
+ seed = (seed ^ 0xb55a4f09 ^ seed >>> 16) & 0xffffffff;
3363
+ return (randomSeed = seed & 0xfffffff) / 0x10000000;
3364
+ };
3365
+ };
3366
+
3367
+ // Shuffle an array using a given random seed or function.
3368
+ // If `ensurePermuted` is true, the input and output are guaranteed to be
3369
+ // distinct permutations.
3370
+ function shuffle(array, randomSeed) {
3371
+ let ensurePermuted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
3372
+ // Always return a copy of the input array
3373
+ const shuffled = ___default["default"].clone(array);
3374
+
3375
+ // Handle edge cases (input array is empty or uniform)
3376
+ if (!shuffled.length || ___default["default"].all(shuffled, function (value) {
3377
+ return ___default["default"].isEqual(value, shuffled[0]);
3378
+ })) {
3379
+ return shuffled;
3380
+ }
3381
+ let random;
3382
+ if (typeof randomSeed === "function") {
3383
+ random = randomSeed;
3384
+ } else {
3385
+ random = seededRNG(randomSeed);
3386
+ }
3387
+ do {
3388
+ // Fischer-Yates shuffle
3389
+ for (let top = shuffled.length; top > 0; top--) {
3390
+ const newEnd = Math.floor(random() * top);
3391
+ const temp = shuffled[newEnd];
3392
+
3393
+ // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
3394
+ shuffled[newEnd] = shuffled[top - 1];
3395
+ // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
3396
+ shuffled[top - 1] = temp;
3397
+ }
3398
+ } while (ensurePermuted && ___default["default"].isEqual(array, shuffled));
3399
+ return shuffled;
3400
+ }
3401
+ const random = seededRNG(new Date().getTime() & 0xffffffff);
3402
+
3372
3403
  // TODO(LEMS-2841): Should be able to remove once getPublicWidgetOptions is hooked up
3373
3404
 
3374
3405
  // TODO(LEMS-2841): Should be able to remove once getPublicWidgetOptions is hooked up
3375
3406
  const shuffleMatcher = props => {
3376
3407
  // Use the same random() function to shuffle both columns sequentially
3377
- const rng = perseusCore.seededRNG(props.problemNum);
3408
+ const rng = seededRNG(props.problemNum);
3378
3409
  let left;
3379
3410
  if (!props.orderMatters) {
3380
3411
  // If the order doesn't matter, don't shuffle the left column
3381
3412
  left = props.left;
3382
3413
  } else {
3383
- left = perseusCore.shuffle(props.left, rng, /* ensurePermuted */true);
3414
+ left = shuffle(props.left, rng, /* ensurePermuted */true);
3384
3415
  }
3385
- const right = perseusCore.shuffle(props.right, rng, /* ensurePermuted */true);
3416
+ const right = shuffle(props.right, rng, /* ensurePermuted */true);
3386
3417
  return {
3387
3418
  left,
3388
3419
  right
@@ -3397,9 +3428,9 @@ function shuffleMatcherWithRandom(data) {
3397
3428
  // If the order doesn't matter, don't shuffle the left column
3398
3429
  left = data.left;
3399
3430
  } else {
3400
- left = perseusCore.shuffle(data.left, Math.random, /* ensurePermuted */true);
3431
+ left = shuffle(data.left, Math.random, /* ensurePermuted */true);
3401
3432
  }
3402
- const right = perseusCore.shuffle(data.right, Math.random, /* ensurePermuted */true);
3433
+ const right = shuffle(data.right, Math.random, /* ensurePermuted */true);
3403
3434
  return {
3404
3435
  left,
3405
3436
  right
@@ -3789,7 +3820,7 @@ const radioWidgetLogic = {
3789
3820
  * the public options that should be exposed to the client.
3790
3821
  */
3791
3822
  function getSorterPublicWidgetOptions(options) {
3792
- const shuffledCorrect = perseusCore.shuffle(options.correct, Math.random, /* ensurePermuted */true);
3823
+ const shuffledCorrect = shuffle(options.correct, Math.random, /* ensurePermuted */true);
3793
3824
  return {
3794
3825
  ...options,
3795
3826
  // Note(Tamara): This does not provide correct answer information any longer.
@@ -4089,8 +4120,9 @@ function getUpgradedWidgetOptions(oldWidgetOptions) {
4089
4120
  }
4090
4121
 
4091
4122
  /**
4092
- * Upgrades widget options and removes answerful data for all the widgets in a
4093
- * Perseus item.
4123
+ * Return a copy of a Perseus item with rubric data removed (ie answers)
4124
+ *
4125
+ * @param originalItem - the original, full Perseus item (which includes the rubric - aka answer data)
4094
4126
  */
4095
4127
  function splitPerseusItem(originalItem) {
4096
4128
  const item = ___default["default"].clone(originalItem);
@@ -4110,60 +4142,6 @@ function splitPerseusItem(originalItem) {
4110
4142
  };
4111
4143
  }
4112
4144
 
4113
- /* Note(tamara): Brought over from the perseus package packages/perseus/src/util.ts file.
4114
- May be useful to bring other perseus package utilities here. Contains utility functions
4115
- and types used across multiple widgets for randomization and shuffling. */
4116
- const seededRNG = function (seed) {
4117
- let randomSeed = seed;
4118
- return function () {
4119
- // Robert Jenkins' 32 bit integer hash function.
4120
- let seed = randomSeed;
4121
- seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
4122
- seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
4123
- seed = seed + 0x165667b1 + (seed << 5) & 0xffffffff;
4124
- seed = (seed + 0xd3a2646c ^ seed << 9) & 0xffffffff;
4125
- seed = seed + 0xfd7046c5 + (seed << 3) & 0xffffffff;
4126
- seed = (seed ^ 0xb55a4f09 ^ seed >>> 16) & 0xffffffff;
4127
- return (randomSeed = seed & 0xfffffff) / 0x10000000;
4128
- };
4129
- };
4130
-
4131
- // Shuffle an array using a given random seed or function.
4132
- // If `ensurePermuted` is true, the input and output are guaranteed to be
4133
- // distinct permutations.
4134
- function shuffle(array, randomSeed) {
4135
- let ensurePermuted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
4136
- // Always return a copy of the input array
4137
- const shuffled = ___default["default"].clone(array);
4138
-
4139
- // Handle edge cases (input array is empty or uniform)
4140
- if (!shuffled.length || ___default["default"].all(shuffled, function (value) {
4141
- return ___default["default"].isEqual(value, shuffled[0]);
4142
- })) {
4143
- return shuffled;
4144
- }
4145
- let random;
4146
- if (typeof randomSeed === "function") {
4147
- random = randomSeed;
4148
- } else {
4149
- random = seededRNG(randomSeed);
4150
- }
4151
- do {
4152
- // Fischer-Yates shuffle
4153
- for (let top = shuffled.length; top > 0; top--) {
4154
- const newEnd = Math.floor(random() * top);
4155
- const temp = shuffled[newEnd];
4156
-
4157
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
4158
- shuffled[newEnd] = shuffled[top - 1];
4159
- // @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
4160
- shuffled[top - 1] = temp;
4161
- }
4162
- } while (ensurePermuted && ___default["default"].isEqual(array, shuffled));
4163
- return shuffled;
4164
- }
4165
- const random = seededRNG(new Date().getTime() & 0xffffffff);
4166
-
4167
4145
  exports.CoreWidgetRegistry = coreWidgetRegistry;
4168
4146
  exports.Errors = Errors;
4169
4147
  exports.GrapherUtil = grapherUtil;