jspsych 8.1.0 → 8.2.1

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.1.0",
3
+ "version": "8.2.1",
4
4
  "description": "Behavioral experiments in a browser",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@fontsource/open-sans": "4.5.3",
51
- "@jspsych/config": "^3.0.1",
51
+ "@jspsych/config": "^3.2.2",
52
52
  "@types/dom-mediacapture-record": "^1.0.11",
53
53
  "base64-inline-loader": "^2.0.1",
54
54
  "css-loader": "^6.6.0",
@@ -1,6 +1,6 @@
1
1
  import { Class } from "type-fest";
2
2
 
3
- import { TestExtension } from "../tests/extensions/test-extension";
3
+ import { TestExtension } from "../tests/extensions/TestExtension";
4
4
  import { ExtensionManager, ExtensionManagerDependencies } from "./ExtensionManager";
5
5
  import { JsPsych } from "./JsPsych";
6
6
  import { JsPsychExtension } from "./modules/extensions";
package/src/JsPsych.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  import autoBind from "auto-bind";
2
+ // To work with citations
3
+ import { Class } from "type-fest";
2
4
 
3
5
  import { version } from "../package.json";
4
6
  import { ExtensionManager, ExtensionManagerDependencies } from "./ExtensionManager";
5
7
  import { JsPsychData, JsPsychDataDependencies } from "./modules/data";
8
+ import { JsPsychExtension } from "./modules/extensions";
6
9
  import { PluginAPI, createJointPluginAPIObject } from "./modules/plugin-api";
10
+ import { JsPsychPlugin } from "./modules/plugins";
7
11
  import * as randomization from "./modules/randomization";
8
12
  import * as turk from "./modules/turk";
9
13
  import * as utils from "./modules/utils";
@@ -32,6 +36,9 @@ export class JsPsych {
32
36
  return version;
33
37
  }
34
38
 
39
+ // prettier-ignore
40
+ private citation: any = '__CITATIONS__';
41
+
35
42
  /** Options */
36
43
  private options: any = {};
37
44
 
@@ -257,6 +264,46 @@ export class JsPsych {
257
264
  return this.timeline?.description.timeline;
258
265
  }
259
266
 
267
+ /**
268
+ * Prints out a string containing citations for the jsPsych library and all input plugins/extensions in the specified format.
269
+ * If called without input, prints citation for jsPsych library.
270
+ *
271
+ * @param plugins The plugins/extensions to generate citations for. Always prints the citation for the jsPsych library at the top.
272
+ * @param format The desired output citation format. Currently supports "apa" and "bibtex".
273
+ * @returns String containing citations separated with newline character.
274
+ */
275
+ getCitations(
276
+ plugins: Array<Class<JsPsychPlugin<any>> | Class<JsPsychExtension>> = [],
277
+ format: "apa" | "bibtex" = "apa"
278
+ ) {
279
+ const formatOptions = ["apa", "bibtex"];
280
+ format = format.toLowerCase() as "apa" | "bibtex";
281
+ // Check if plugins is an array
282
+ if (!Array.isArray(plugins)) {
283
+ throw new Error("Expected array of plugins/extensions");
284
+ }
285
+ // Check if format is supported
286
+ else if (!formatOptions.includes(format)) {
287
+ throw new Error("Unsupported citation format");
288
+ }
289
+ // Print citations
290
+ else {
291
+ const jsPsychCitation = this.citation[format];
292
+ const citationSet = new Set([jsPsychCitation]);
293
+
294
+ for (const plugin of plugins) {
295
+ try {
296
+ const pluginCitation = plugin["info"].citations[format];
297
+ citationSet.add(pluginCitation);
298
+ } catch {
299
+ console.error(`${plugin} does not have citation in ${format} format.`);
300
+ }
301
+ }
302
+ const citationList = Array.from(citationSet).join("\n");
303
+ return citationList;
304
+ }
305
+ }
306
+
260
307
  get extensions() {
261
308
  return this.extensionManager?.extensions ?? {};
262
309
  }
@@ -4,6 +4,7 @@ export interface JsPsychExtensionInfo {
4
4
  name: string;
5
5
  version?: string;
6
6
  data?: ParameterInfos;
7
+ citations?: Record<string, string> | string;
7
8
  }
8
9
 
9
10
  export interface JsPsychExtension {
@@ -141,6 +141,7 @@ export interface PluginInfo {
141
141
  version?: string;
142
142
  parameters: ParameterInfos;
143
143
  data?: ParameterInfos;
144
+ citations?: Record<string, string> | string;
144
145
  }
145
146
 
146
147
  export interface JsPsychPlugin<I extends PluginInfo> {
@@ -1,5 +1,5 @@
1
1
  import rw from "random-words";
2
- import seedrandom from "seedrandom/lib/alea";
2
+ import seedrandom from "seedrandom/lib/alea.js";
3
3
 
4
4
  /**
5
5
  * Uses the `seedrandom` package to replace Math.random() with a seedable PRNG.
@@ -586,10 +586,10 @@ describe("Timeline", () => {
586
586
  const variable = new TimelineVariable("x");
587
587
 
588
588
  await timeline.run();
589
- expect(() => timeline.evaluateTimelineVariable(variable)).toThrowError("");
589
+ expect(() => timeline.evaluateTimelineVariable(variable)).toThrow();
590
590
  expect(() =>
591
591
  (timeline.children[0] as Timeline).evaluateTimelineVariable(variable)
592
- ).toThrowError("");
592
+ ).toThrow();
593
593
  });
594
594
  });
595
595
  });
@@ -36,6 +36,16 @@ describe("Trial", () => {
36
36
  return trial;
37
37
  };
38
38
 
39
+ it("throws an error upon construction when the `type` parameter or plugin info object is undefined", () => {
40
+ for (const description of [{}, { type: {} }] as TrialDescription[]) {
41
+ expect(
42
+ () => new Trial(dependencies, description, timeline)
43
+ ).toThrowErrorMatchingInlineSnapshot(
44
+ "\"Plugin not recognized. Please provide a valid plugin using the 'type' parameter.\""
45
+ );
46
+ }
47
+ });
48
+
39
49
  describe("run()", () => {
40
50
  it("instantiates the corresponding plugin", async () => {
41
51
  const trial = createTrial({ type: TestPlugin });
@@ -436,6 +446,8 @@ describe("Trial", () => {
436
446
  return TestPlugin;
437
447
  case "x":
438
448
  return "foo";
449
+ case "y":
450
+ return ["foo", "bar"];
439
451
  default:
440
452
  return undefined;
441
453
  }
@@ -444,17 +456,19 @@ describe("Trial", () => {
444
456
  const trial = createTrial({
445
457
  type: new TimelineVariable("t"),
446
458
  requiredString: new TimelineVariable("x"),
459
+ stringArray: new TimelineVariable("y"),
447
460
  requiredComplexNested: { requiredChild: () => new TimelineVariable("x") },
448
461
  requiredComplexNestedArray: [{ requiredChild: () => new TimelineVariable("x") }],
449
462
  });
450
463
 
451
464
  await trial.run();
452
465
 
453
- // The `x` timeline variables should have been replaced with `foo`
466
+ // The `x` and `y` timeline variables should have been evaluated
454
467
  expect(trial.pluginInstance.trial).toHaveBeenCalledWith(
455
468
  expect.anything(),
456
469
  expect.objectContaining({
457
470
  requiredString: "foo",
471
+ stringArray: ["foo", "bar"],
458
472
  requiredComplexNested: expect.objectContaining({ requiredChild: "foo" }),
459
473
  requiredComplexNestedArray: [expect.objectContaining({ requiredChild: "foo" })],
460
474
  }),
@@ -510,7 +524,7 @@ describe("Trial", () => {
510
524
  );
511
525
  await expect(
512
526
  createTrial({ type: TestPlugin, requiredComplexNested: {} }).run()
513
- ).rejects.toThrowError('"requiredComplexNested.requiredChild" parameter');
527
+ ).rejects.toThrow('"requiredComplexNested.requiredChild" parameter');
514
528
  });
515
529
 
516
530
  it("errors on missing parameters nested in `COMPLEX` array parameters", async () => {
@@ -35,7 +35,12 @@ export class Trial extends TimelineNode {
35
35
 
36
36
  this.trialObject = deepCopy(description);
37
37
  this.pluginClass = this.getParameterValue("type", { evaluateFunctions: false });
38
- this.pluginInfo = this.pluginClass["info"];
38
+ this.pluginInfo = this.pluginClass?.["info"];
39
+ if (!this.pluginInfo) {
40
+ throw new Error(
41
+ "Plugin not recognized. Please provide a valid plugin using the 'type' parameter."
42
+ );
43
+ }
39
44
 
40
45
  if (!("version" in this.pluginInfo) && !("data" in this.pluginInfo)) {
41
46
  console.warn(