mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-11 16:18:11 +00:00
Implement basic result data handling
This commit is contained in:
parent
a876d215c0
commit
76a02685d8
@ -898,7 +898,7 @@ export class JsPsych {
|
||||
});
|
||||
};
|
||||
|
||||
finishTrial(data: Record<string, any> = {}) {
|
||||
finishTrial(data?: Record<string, any>) {
|
||||
this._resolveTrialPromise(data);
|
||||
this._resetTrialPromise();
|
||||
}
|
||||
|
@ -60,6 +60,9 @@ describe("Timeline", () => {
|
||||
|
||||
await timeline.run();
|
||||
expect(loopFunction).toHaveBeenCalledTimes(2);
|
||||
expect(loopFunction).toHaveBeenNthCalledWith(1, Array(3).fill({ my: "result" }));
|
||||
expect(loopFunction).toHaveBeenNthCalledWith(2, Array(6).fill({ my: "result" }));
|
||||
|
||||
expect(timeline.children.length).toBe(6);
|
||||
});
|
||||
|
||||
@ -327,4 +330,20 @@ describe("Timeline", () => {
|
||||
expect(timeline.getParameterValue("object.childObject.childString")).toBe("bar");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getResults()", () => {
|
||||
it("recursively returns all results", async () => {
|
||||
const timeline = new Timeline(jsPsych, exampleTimeline);
|
||||
await timeline.run();
|
||||
expect(timeline.getResults()).toEqual(Array(3).fill({ my: "result" }));
|
||||
});
|
||||
|
||||
it("does not include `undefined` results", async () => {
|
||||
const timeline = new Timeline(jsPsych, exampleTimeline);
|
||||
await timeline.run();
|
||||
|
||||
jest.spyOn(timeline.children[0] as Trial, "getResult").mockReturnValue(undefined);
|
||||
expect(timeline.getResults()).toEqual(Array(2).fill({ my: "result" }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { JsPsych } from "../JsPsych";
|
||||
import { JsPsychPlugin, PluginInfo } from "../modules/plugins";
|
||||
import {
|
||||
repeat,
|
||||
sampleWithReplacement,
|
||||
@ -7,7 +6,6 @@ import {
|
||||
shuffle,
|
||||
shuffleAlternateGroups,
|
||||
} from "../modules/randomization";
|
||||
import { deepCopy } from "../modules/utils";
|
||||
import { BaseTimelineNode } from "./BaseTimelineNode";
|
||||
import { Trial } from "./Trial";
|
||||
import {
|
||||
@ -46,7 +44,7 @@ export class Timeline extends BaseTimelineNode {
|
||||
await childNode.run();
|
||||
}
|
||||
}
|
||||
} while (description.loop_function && description.loop_function([])); // TODO What data?
|
||||
} while (description.loop_function && description.loop_function(this.getResults()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,4 +133,23 @@ export class Timeline extends BaseTimelineNode {
|
||||
}
|
||||
return super.getParameterValue(parameterName, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a flat array containing the results of all nested trials that have results so far
|
||||
*/
|
||||
public getResults() {
|
||||
const results = [];
|
||||
for (const child of this.children) {
|
||||
if (child instanceof Trial) {
|
||||
const childResult = child.getResult();
|
||||
if (childResult) {
|
||||
results.push(childResult);
|
||||
}
|
||||
} else if (child instanceof Timeline) {
|
||||
results.push(...child.getResults());
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
@ -103,10 +103,22 @@ describe("Trial", () => {
|
||||
expect(onLoadCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("picks up the result data from the promise", async () => {
|
||||
const trial = createTrial({ type: TestPlugin });
|
||||
await trial.run();
|
||||
expect(trial.resultData).toEqual({ promised: "result" });
|
||||
it("picks up the result data from the promise or the `finishTrial()` function (where the latter one takes precedence)", async () => {
|
||||
const trial1 = createTrial({ type: TestPlugin });
|
||||
await trial1.run();
|
||||
expect(trial1.getResult()).toEqual({ promised: "result" });
|
||||
|
||||
TestPluginMock.prototype.trial.mockImplementation(
|
||||
async (display_element, trial, on_load) => {
|
||||
on_load();
|
||||
jsPsych.finishTrial({ my: "result" });
|
||||
return { promised: "result" };
|
||||
}
|
||||
);
|
||||
|
||||
const trial2 = createTrial({ type: TestPlugin });
|
||||
await trial2.run();
|
||||
expect(trial2.getResult()).toEqual({ my: "result" });
|
||||
});
|
||||
});
|
||||
|
||||
@ -123,7 +135,7 @@ describe("Trial", () => {
|
||||
const trial = createTrial({ type: TestPlugin });
|
||||
|
||||
await trial.run();
|
||||
expect(trial.resultData).toEqual({ my: "result" });
|
||||
expect(trial.getResult()).toEqual({ my: "result" });
|
||||
});
|
||||
});
|
||||
|
||||
@ -136,6 +148,12 @@ describe("Trial", () => {
|
||||
expect(onFinishCallback).toHaveBeenCalledWith({ my: "result" });
|
||||
});
|
||||
|
||||
it("includes result data from the `data` property", async () => {
|
||||
const trial = createTrial({ type: TestPlugin, data: { custom: "value" } });
|
||||
await trial.run();
|
||||
expect(trial.getResult()).toEqual({ my: "result", custom: "value" });
|
||||
});
|
||||
|
||||
describe("with a plugin parameter specification", () => {
|
||||
const functionDefaultValue = () => {};
|
||||
beforeEach(() => {
|
||||
|
@ -6,15 +6,15 @@ import { BaseTimelineNode } from "./BaseTimelineNode";
|
||||
import { Timeline } from "./Timeline";
|
||||
import {
|
||||
GetParameterValueOptions,
|
||||
TimelineNode,
|
||||
TimelineVariable,
|
||||
TrialDescription,
|
||||
TrialResult,
|
||||
isPromise,
|
||||
trialDescriptionKeys,
|
||||
} from ".";
|
||||
|
||||
export class Trial extends BaseTimelineNode {
|
||||
resultData: Record<string, any>;
|
||||
private result: TrialResult;
|
||||
|
||||
public pluginInstance: JsPsychPlugin<any>;
|
||||
public readonly trialObject: TrialDescription;
|
||||
@ -35,20 +35,35 @@ export class Trial extends BaseTimelineNode {
|
||||
this.pluginInstance = new this.description.type(this.jsPsych);
|
||||
|
||||
let trialPromise = this.jsPsych._trialPromise;
|
||||
|
||||
/** Used as a way to figure out if `finishTrial()` has ben called without awaiting `trialPromise` */
|
||||
let hasTrialPromiseBeenResolved = false;
|
||||
trialPromise.then(() => {
|
||||
hasTrialPromiseBeenResolved = true;
|
||||
});
|
||||
|
||||
const trialReturnValue = this.pluginInstance.trial(
|
||||
this.jsPsych.getDisplayElement() ?? document.createElement("div"), // TODO Remove this hack once getDisplayElement() returns something
|
||||
this.trialObject,
|
||||
this.onLoad
|
||||
);
|
||||
|
||||
// Wait until the trial has completed and grab result data
|
||||
let result: TrialResult;
|
||||
if (isPromise(trialReturnValue)) {
|
||||
trialPromise = trialReturnValue;
|
||||
result = await Promise.race([trialReturnValue, trialPromise]);
|
||||
|
||||
// If `finishTrial()` was called, use the result provided to it. This may happen although
|
||||
// `trialReturnValue` won the race ("run-to-completion").
|
||||
if (hasTrialPromiseBeenResolved) {
|
||||
result = await trialPromise;
|
||||
}
|
||||
} else {
|
||||
this.onLoad();
|
||||
result = await trialPromise;
|
||||
}
|
||||
|
||||
// Wait until the trial has completed and grab result data
|
||||
this.resultData = (await trialPromise) ?? {};
|
||||
this.result = { ...this.trialObject.data, ...result };
|
||||
|
||||
this.onFinish();
|
||||
}
|
||||
@ -67,7 +82,7 @@ export class Trial extends BaseTimelineNode {
|
||||
|
||||
private onFinish() {
|
||||
if (this.description.on_finish) {
|
||||
this.description.on_finish(this.resultData);
|
||||
this.description.on_finish(this.getResult());
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,6 +99,13 @@ export class Trial extends BaseTimelineNode {
|
||||
return super.getParameterValue(parameterName, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result object of this trial or `undefined` if the result is not yet known.
|
||||
*/
|
||||
public getResult() {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the parameters provided in the trial description align with the plugin's info
|
||||
* object, resolves missing parameter values from the parent timeline, resolves timeline variable
|
||||
|
@ -136,3 +136,6 @@ export interface TimelineNode {
|
||||
*/
|
||||
getParameterValue(parameterName: string, options?: GetParameterValueOptions): any;
|
||||
}
|
||||
|
||||
export type TrialResult = Record<string, any>;
|
||||
export type TrialResults = Array<Record<string, any>>;
|
||||
|
Loading…
Reference in New Issue
Block a user