jspsych 8.2.2 → 8.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jspsych",
3
- "version": "8.2.2",
3
+ "version": "8.2.3",
4
4
  "description": "Behavioral experiments in a browser",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -51,6 +51,7 @@ export type ParameterInfo = (
51
51
  array?: boolean;
52
52
  pretty_name?: string;
53
53
  default?: any;
54
+ options?: any;
54
55
  };
55
56
 
56
57
  export type ParameterInfos = Record<string, ParameterInfo>;
@@ -235,7 +235,7 @@ describe("Trial", () => {
235
235
  });
236
236
 
237
237
  it("respects the `save_trial_parameters` parameter", async () => {
238
- const consoleSpy = jest.spyOn(console, "warn").mockImplementation();
238
+ const consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
239
239
 
240
240
  TestPlugin.setParameterInfos({
241
241
  stringParameter1: { type: ParameterType.STRING },
@@ -560,6 +560,110 @@ describe("Trial", () => {
560
560
  });
561
561
  });
562
562
 
563
+ describe("with parameter type mismatches", () => {
564
+ //TODO: redo these to expect errors on v9!
565
+ let consoleSpy: jest.SpyInstance;
566
+
567
+ beforeEach(() => {
568
+ consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
569
+ });
570
+
571
+ afterEach(() => {
572
+ consoleSpy.mockRestore();
573
+ });
574
+
575
+ it("errors on non-boolean values for boolean parameters", async () => {
576
+ TestPlugin.setParameterInfos({
577
+ boolParameter: { type: ParameterType.BOOL },
578
+ });
579
+
580
+ // this should work:
581
+ await createTrial({ type: TestPlugin, boolParameter: true }).run();
582
+
583
+ // this shouldn't:
584
+ await createTrial({ type: TestPlugin, boolParameter: "foo" }).run();
585
+ expect(consoleSpy).toHaveBeenCalledWith(
586
+ 'A non-boolean value (`foo`) was provided for the boolean parameter "boolParameter" in the "test" plugin.'
587
+ );
588
+ });
589
+
590
+ it("errors on non-string values for string parameters", async () => {
591
+ TestPlugin.setParameterInfos({
592
+ stringParameter: { type: ParameterType.STRING },
593
+ });
594
+
595
+ // this should work:
596
+ await createTrial({ type: TestPlugin, stringParameter: "foo" }).run();
597
+
598
+ // this shouldn't:
599
+ await createTrial({ type: TestPlugin, stringParameter: 1 }).run();
600
+ expect(consoleSpy).toHaveBeenCalledWith(
601
+ 'A non-string value (`1`) was provided for the parameter "stringParameter" in the "test" plugin.'
602
+ );
603
+ });
604
+
605
+ it("errors on non-numeric values for numeric parameters", async () => {
606
+ TestPlugin.setParameterInfos({
607
+ intParameter: { type: ParameterType.INT },
608
+ floatParameter: { type: ParameterType.FLOAT },
609
+ });
610
+
611
+ // this should work:
612
+ await createTrial({ type: TestPlugin, intParameter: 1, floatParameter: 1.5 }).run();
613
+
614
+ // this shouldn't:
615
+ await createTrial({ type: TestPlugin, intParameter: "foo", floatParameter: 1.5 }).run();
616
+ expect(consoleSpy).toHaveBeenCalledWith(
617
+ 'A non-numeric value (`foo`) was provided for the numeric parameter "intParameter" in the "test" plugin.'
618
+ );
619
+
620
+ await createTrial({ type: TestPlugin, intParameter: 1, floatParameter: "foo" }).run();
621
+ expect(consoleSpy).toHaveBeenCalledWith(
622
+ 'A non-numeric value (`foo`) was provided for the numeric parameter "floatParameter" in the "test" plugin.'
623
+ );
624
+
625
+ // this should warn but not error (behavior in v9):
626
+ await createTrial({ type: TestPlugin, intParameter: 1.5, floatParameter: 1.5 }).run();
627
+ expect(consoleSpy).toHaveBeenCalledWith(
628
+ `A float value (\`1.5\`) was provided for the integer parameter "intParameter" in the "test" plugin. The value will be truncated to an integer.`
629
+ );
630
+ });
631
+
632
+ it("errors on non-function values for function parameters", async () => {
633
+ TestPlugin.setParameterInfos({
634
+ functionParameter: { type: ParameterType.FUNCTION },
635
+ });
636
+
637
+ // this should work:
638
+ await createTrial({ type: TestPlugin, functionParameter: () => {} }).run();
639
+
640
+ // this shouldn't:
641
+ await createTrial({ type: TestPlugin, functionParameter: "foo" }).run();
642
+ expect(consoleSpy).toHaveBeenCalledWith(
643
+ 'A non-function value (`foo`) was provided for the function parameter "functionParameter" in the "test" plugin.'
644
+ );
645
+ });
646
+
647
+ it("errors on select parameters with values not in the options array", async () => {
648
+ TestPlugin.setParameterInfos({
649
+ selectParameter: {
650
+ type: ParameterType.SELECT,
651
+ options: ["foo", "bar"],
652
+ },
653
+ });
654
+
655
+ // this should work:
656
+ await createTrial({ type: TestPlugin, selectParameter: "foo" }).run();
657
+
658
+ // this shouldn't:
659
+
660
+ await createTrial({ type: TestPlugin, selectParameter: "baz" }).run();
661
+ expect(consoleSpy).toHaveBeenCalledWith(
662
+ 'The value "baz" is not a valid option for the parameter "selectParameter" in the "test" plugin. Valid options are: foo, bar.'
663
+ );
664
+ });
665
+ });
666
+
563
667
  it("respects `default_iti` and `post_trial_gap``", async () => {
564
668
  dependencies.getDefaultIti.mockReturnValue(100);
565
669
  TestPlugin.setManualFinishTrialMode();
@@ -360,6 +360,7 @@ export class Trial extends TimelineNode {
360
360
  for (const [parameterName, parameterConfig] of Object.entries(parameterInfos)) {
361
361
  const parameterPath = [...parentParameterPath, parameterName];
362
362
 
363
+ // evaluate parameter and validate required parameter
363
364
  let parameterValue = this.getParameterValue(parameterPath, {
364
365
  evaluateFunctions: parameterConfig.type !== ParameterType.FUNCTION,
365
366
  replaceResult: (originalResult) => {
@@ -379,6 +380,83 @@ export class Trial extends TimelineNode {
379
380
  },
380
381
  });
381
382
 
383
+ // TODO: ensure that this throws an error in v9!
384
+ // major parameter type validation
385
+ if (!parameterConfig.array && parameterValue !== null) {
386
+ switch (parameterConfig.type) {
387
+ case ParameterType.BOOL:
388
+ if (typeof parameterValue !== "boolean") {
389
+ const parameterPathString = parameterPathArrayToString(parameterPath);
390
+ console.warn(
391
+ `A non-boolean value (\`${parameterValue}\`) was provided for the boolean parameter "${parameterPathString}" in the "${this.pluginInfo.name}" plugin.`
392
+ );
393
+ }
394
+ break;
395
+ // @ts-ignore falls through
396
+ case ParameterType.KEYS: // "ALL_KEYS", "NO_KEYS", and single key strings are checked here
397
+ if (Array.isArray(parameterValue)) break;
398
+ case ParameterType.STRING:
399
+ case ParameterType.HTML_STRING:
400
+ case ParameterType.KEY:
401
+ case ParameterType.AUDIO:
402
+ case ParameterType.VIDEO:
403
+ case ParameterType.IMAGE:
404
+ if (typeof parameterValue !== "string") {
405
+ const parameterPathString = parameterPathArrayToString(parameterPath);
406
+ console.warn(
407
+ `A non-string value (\`${parameterValue}\`) was provided for the parameter "${parameterPathString}" in the "${this.pluginInfo.name}" plugin.`
408
+ );
409
+ }
410
+ break;
411
+ case ParameterType.FLOAT:
412
+ case ParameterType.INT:
413
+ if (typeof parameterValue !== "number") {
414
+ const parameterPathString = parameterPathArrayToString(parameterPath);
415
+ console.warn(
416
+ `A non-numeric value (\`${parameterValue}\`) was provided for the numeric parameter "${parameterPathString}" in the "${this.pluginInfo.name}" plugin.`
417
+ );
418
+ }
419
+ break;
420
+ case ParameterType.FUNCTION:
421
+ if (typeof parameterValue !== "function") {
422
+ const parameterPathString = parameterPathArrayToString(parameterPath);
423
+ console.warn(
424
+ `A non-function value (\`${parameterValue}\`) was provided for the function parameter "${parameterPathString}" in the "${this.pluginInfo.name}" plugin.`
425
+ );
426
+ }
427
+ break;
428
+ case ParameterType.SELECT:
429
+ if (!parameterConfig.options) {
430
+ const parameterPathString = parameterPathArrayToString(parameterPath);
431
+ console.warn(
432
+ `The "options" array is required for the "select" parameter "${parameterPathString}" in the "${this.pluginInfo.name}" plugin.`
433
+ );
434
+ }
435
+ }
436
+
437
+ // truncate floats to integers if the parameter type is INT
438
+ if (parameterConfig.type === ParameterType.INT && parameterValue % 1 !== 0) {
439
+ const parameterPathString = parameterPathArrayToString(parameterPath);
440
+ console.warn(
441
+ `A float value (\`${parameterValue}\`) was provided for the integer parameter "${parameterPathString}" in the "${this.pluginInfo.name}" plugin. The value will be truncated to an integer.`
442
+ );
443
+
444
+ parameterValue = Math.trunc(parameterValue);
445
+ }
446
+ }
447
+
448
+ if (parameterConfig.type === ParameterType.SELECT) {
449
+ if (!parameterConfig.options.includes(parameterValue)) {
450
+ const parameterPathString = parameterPathArrayToString(parameterPath);
451
+ console.warn(
452
+ `The value "${parameterValue}" is not a valid option for the parameter "${parameterPathString}" in the "${
453
+ this.pluginInfo.name
454
+ }" plugin. Valid options are: ${parameterConfig.options.join(", ")}.`
455
+ );
456
+ }
457
+ }
458
+
459
+ // array validation
382
460
  if (parameterConfig.array && !Array.isArray(parameterValue)) {
383
461
  const parameterPathString = parameterPathArrayToString(parameterPath);
384
462
  throw new Error(