From c3a1c997ab0d79b6fccbf1d577a617347724e128 Mon Sep 17 00:00:00 2001 From: cchang-vassar <79338042+cchang-vassar@users.noreply.github.com> Date: Tue, 15 Jul 2025 19:09:23 -0700 Subject: [PATCH 1/5] add potential fix to JsPsychExtension not having version field issue --- packages/jspsych/src/modules/extensions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/jspsych/src/modules/extensions.ts b/packages/jspsych/src/modules/extensions.ts index dd4210a744..61a345bd32 100644 --- a/packages/jspsych/src/modules/extensions.ts +++ b/packages/jspsych/src/modules/extensions.ts @@ -8,6 +8,8 @@ export interface JsPsychExtensionInfo { } export interface JsPsychExtension { + //DEV: This breaks the builds for th current extensions because they do not implement 'info' as an instance-level property, but as a static-level one. + readonly info: JsPsychExtensionInfo; /** * Called once at the start of the experiment to initialize the extension */ From 287a349b9d818eb2d09574998bf0f27520e13141 Mon Sep 17 00:00:00 2001 From: cchang-vassar <79338042+cchang-vassar@users.noreply.github.com> Date: Tue, 15 Jul 2025 19:24:46 -0700 Subject: [PATCH 2/5] Make extension info.version access optional in ExtensionManager.ts --- packages/jspsych/src/ExtensionManager.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/jspsych/src/ExtensionManager.ts b/packages/jspsych/src/ExtensionManager.ts index 664162606c..d8c1ffb738 100644 --- a/packages/jspsych/src/ExtensionManager.ts +++ b/packages/jspsych/src/ExtensionManager.ts @@ -89,7 +89,9 @@ export class ExtensionManager { const extensionInfos = trialExtensionsConfiguration.length ? { extension_type: trialExtensionsConfiguration.map(({ type }) => type["info"].name), - extension_version: trialExtensionsConfiguration.map(({ type }) => type["info"].version), + extension_version: trialExtensionsConfiguration.map( + ({ type }) => type["info"].version ?? "" + ), } : {}; From eb7bb2266ffecfd4273d57f6c53894024a54fffe Mon Sep 17 00:00:00 2001 From: cchang-vassar <79338042+cchang-vassar@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:08:07 -0700 Subject: [PATCH 3/5] remove readonly info field in JsPsychExtension --- packages/jspsych/src/modules/extensions.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/jspsych/src/modules/extensions.ts b/packages/jspsych/src/modules/extensions.ts index 61a345bd32..dd4210a744 100644 --- a/packages/jspsych/src/modules/extensions.ts +++ b/packages/jspsych/src/modules/extensions.ts @@ -8,8 +8,6 @@ export interface JsPsychExtensionInfo { } export interface JsPsychExtension { - //DEV: This breaks the builds for th current extensions because they do not implement 'info' as an instance-level property, but as a static-level one. - readonly info: JsPsychExtensionInfo; /** * Called once at the start of the experiment to initialize the extension */ From c3bc0baf863a0e18ab47b0e7d68f71431fe0a71d Mon Sep 17 00:00:00 2001 From: cchang-vassar <79338042+cchang-vassar@users.noreply.github.com> Date: Tue, 22 Jul 2025 23:06:49 -0700 Subject: [PATCH 4/5] make it a broad expectation that Class & JsPsychExtensionInfo --- packages/jspsych/src/ExtensionManager.ts | 22 ++++++++++++++-------- packages/jspsych/src/modules/extensions.ts | 2 ++ packages/jspsych/src/timeline/index.ts | 4 ++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/jspsych/src/ExtensionManager.ts b/packages/jspsych/src/ExtensionManager.ts index d8c1ffb738..0d627316e6 100644 --- a/packages/jspsych/src/ExtensionManager.ts +++ b/packages/jspsych/src/ExtensionManager.ts @@ -4,7 +4,7 @@ import { JsPsychExtension, JsPsychExtensionInfo } from "./modules/extensions"; import { TrialExtensionsConfiguration } from "./timeline"; export type GlobalExtensionsConfiguration = Array<{ - type: Class; + type: Class & { info: JsPsychExtensionInfo }; params?: Record; }>; @@ -12,12 +12,16 @@ export interface ExtensionManagerDependencies { /** * Given an extension class, create a new instance of it and return it. */ - instantiateExtension(extensionClass: Class): JsPsychExtension; + instantiateExtension( + extensionClass: Class & { info: JsPsychExtensionInfo } + ): JsPsychExtension; } export class ExtensionManager { - private static getExtensionNameByClass(extensionClass: Class) { - return (extensionClass["info"] as JsPsychExtensionInfo).name; + private static getExtensionNameByClass( + extensionClass: Class & { info: JsPsychExtensionInfo } + ) { + return extensionClass.info.name; } public readonly extensions: Record; @@ -34,7 +38,9 @@ export class ExtensionManager { ); } - private getExtensionInstanceByClass(extensionClass: Class) { + private getExtensionInstanceByClass( + extensionClass: Class & { info: JsPsychExtensionInfo } + ) { return this.extensions[ExtensionManager.getExtensionNameByClass(extensionClass)]; } @@ -43,7 +49,7 @@ export class ExtensionManager { this.extensionsConfiguration.map(({ type, params = {} }) => { this.getExtensionInstanceByClass(type).initialize(params); - const extensionInfo = type["info"] as JsPsychExtensionInfo; + const extensionInfo = type.info; if (!("version" in extensionInfo) && !("data" in extensionInfo)) { console.warn( @@ -88,9 +94,9 @@ export class ExtensionManager { const extensionInfos = trialExtensionsConfiguration.length ? { - extension_type: trialExtensionsConfiguration.map(({ type }) => type["info"].name), + extension_type: trialExtensionsConfiguration.map(({ type }) => type.info.name), extension_version: trialExtensionsConfiguration.map( - ({ type }) => type["info"].version ?? "" + ({ type }) => type.info.version ?? "" ), } : {}; diff --git a/packages/jspsych/src/modules/extensions.ts b/packages/jspsych/src/modules/extensions.ts index dd4210a744..c9c60bf352 100644 --- a/packages/jspsych/src/modules/extensions.ts +++ b/packages/jspsych/src/modules/extensions.ts @@ -1,3 +1,5 @@ +import { Class } from "type-fest"; + import { ParameterInfos } from "./plugins"; export interface JsPsychExtensionInfo { diff --git a/packages/jspsych/src/timeline/index.ts b/packages/jspsych/src/timeline/index.ts index f929e00ccd..c7c4d364e9 100644 --- a/packages/jspsych/src/timeline/index.ts +++ b/packages/jspsych/src/timeline/index.ts @@ -1,6 +1,6 @@ import { Class } from "type-fest"; -import { JsPsychExtension } from "../modules/extensions"; +import { JsPsychExtension, JsPsychExtensionInfo } from "../modules/extensions"; import { JsPsychPlugin, PluginInfo } from "../modules/plugins"; import { Trial } from "./Trial"; import { PromiseWrapper } from "./util"; @@ -12,7 +12,7 @@ export class TimelineVariable { export type Parameter = T | (() => T) | TimelineVariable; export type TrialExtensionsConfiguration = Array<{ - type: Class; + type: Class & { info: JsPsychExtensionInfo }; params?: Record; }>; From 1cbfdc82b58d51bb0a88cf691b913796dc926317 Mon Sep 17 00:00:00 2001 From: cchang-vassar <79338042+cchang-vassar@users.noreply.github.com> Date: Tue, 22 Jul 2025 23:10:22 -0700 Subject: [PATCH 5/5] add mock extension class to Trial.spec.ts to include info object --- packages/jspsych/src/timeline/Trial.spec.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/jspsych/src/timeline/Trial.spec.ts b/packages/jspsych/src/timeline/Trial.spec.ts index 2a53226257..ebebc87ff2 100644 --- a/packages/jspsych/src/timeline/Trial.spec.ts +++ b/packages/jspsych/src/timeline/Trial.spec.ts @@ -615,8 +615,11 @@ describe("Trial", () => { it("invokes extension callbacks and includes extension results", async () => { dependencies.runOnFinishExtensionCallbacks.mockResolvedValue({ extension: "result" }); + const mockExtensionClass = jest.fn() as any; + mockExtensionClass.info = { name: "test-extension", version: "1.0.0" }; + const extensionsConfig: TrialExtensionsConfiguration = [ - { type: jest.fn(), params: { my: "option" } }, + { type: mockExtensionClass, params: { my: "option" } }, ]; const trial = createTrial({ @@ -654,9 +657,12 @@ describe("Trial", () => { ); } + const mockExtensionClass2 = jest.fn() as any; + mockExtensionClass2.info = { name: "test-extension-2", version: "1.0.0" }; + const trial = createTrial({ type: TestPlugin, - extensions: [{ type: jest.fn(), params: { my: "option" } }], + extensions: [{ type: mockExtensionClass2, params: { my: "option" } }], on_start: createInvocationOrderCallback("on_start"), on_load: createInvocationOrderCallback("on_load"), on_finish: createInvocationOrderCallback("on_finish"),