mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-12 16:48:12 +00:00

Instead of a global `jsPsych` object, there is a `JsPsych` class now which can be instantiated with the parameters that were previously accepted by `jsPsych.init`. Upon instantiation, the `JsPsych` class also instantiates classes for stateful modules (`jsPsych.data` and `jsPsych.pluginAPI`) and passes relevant `JsPsych` instance information to them. Plugins are expected to be classes now that are instantiated for each trial and receive the `JsPsych` instance as their only constructor argument.
573 lines
13 KiB
TypeScript
573 lines
13 KiB
TypeScript
import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response";
|
|
import htmlSliderResponse from "@jspsych/plugin-html-slider-response";
|
|
|
|
import { JsPsych, initJsPsych } from "../../src";
|
|
import { pressKey } from "../utils";
|
|
|
|
let jsPsych: JsPsych;
|
|
|
|
describe("on_finish (trial)", function () {
|
|
test("should get an object of data generated by the trial", function () {
|
|
return new Promise<any>(function (resolve, reject) {
|
|
var key_data = null;
|
|
|
|
var trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello",
|
|
on_finish: function (data) {
|
|
key_data = data.response;
|
|
},
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial],
|
|
on_finish: function () {
|
|
resolve({ key_data });
|
|
},
|
|
});
|
|
|
|
pressKey("a");
|
|
}).then(function (data) {
|
|
expect(data.key_data).toBe("a");
|
|
});
|
|
});
|
|
|
|
test("should be able to write to the data", function () {
|
|
return new Promise<any>(function (resolve, reject) {
|
|
var promise_data = <any>{};
|
|
|
|
var trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello",
|
|
on_finish: function (data) {
|
|
data.response = 1;
|
|
},
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial],
|
|
on_finish: function () {
|
|
promise_data.final_key_press = jsPsych.data.get().values()[0].response;
|
|
resolve(promise_data);
|
|
},
|
|
});
|
|
|
|
pressKey("a");
|
|
|
|
//resolve();
|
|
}).then(function (pd) {
|
|
expect(pd.final_key_press).toBe(1);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("on_start (trial)", function () {
|
|
test("should get trial data with function parameters evaluated", function () {
|
|
return new Promise(function (resolve, reject) {
|
|
var d = null;
|
|
|
|
var trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: function () {
|
|
return "hello";
|
|
},
|
|
on_start: function (trial) {
|
|
d = trial.stimulus;
|
|
},
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial],
|
|
on_finish: function () {
|
|
resolve(d);
|
|
},
|
|
});
|
|
|
|
pressKey("a");
|
|
}).then(function (data) {
|
|
expect(data).toBe("hello");
|
|
});
|
|
});
|
|
|
|
test("should get trial data with timeline variables evaluated", function () {
|
|
return new Promise(function (resolve, reject) {
|
|
var d = null;
|
|
|
|
var trial = {
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: jsPsych.timelineVariable("stimulus"),
|
|
on_start: function (trial) {
|
|
d = trial.stimulus;
|
|
},
|
|
},
|
|
],
|
|
timeline_variables: [{ stimulus: "hello" }],
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial],
|
|
on_finish: function () {
|
|
resolve(d);
|
|
},
|
|
});
|
|
|
|
pressKey("a");
|
|
}).then(function (data) {
|
|
expect(data).toBe("hello");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("on_trial_finish (experiment level)", function () {
|
|
test("should get an object containing the trial data", function () {
|
|
return new Promise<any>(function (resolve, reject) {
|
|
var promise_data = <any>{};
|
|
|
|
var trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello",
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial],
|
|
on_trial_finish: function (data) {
|
|
promise_data.key = data.response;
|
|
},
|
|
on_finish: function () {
|
|
resolve(promise_data);
|
|
},
|
|
});
|
|
|
|
pressKey("a");
|
|
|
|
//resolve();
|
|
}).then(function (pd) {
|
|
expect(pd.key).toBe("a");
|
|
});
|
|
});
|
|
|
|
test("should allow writing to the data object", function () {
|
|
return new Promise<any>(function (resolve, reject) {
|
|
var promise_data = <any>{};
|
|
|
|
var trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello",
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial],
|
|
on_trial_finish: function (data) {
|
|
data.write = true;
|
|
},
|
|
on_finish: function (data) {
|
|
promise_data.write = data.values()[0].write;
|
|
resolve(promise_data);
|
|
},
|
|
});
|
|
|
|
pressKey("a");
|
|
|
|
//resolve();
|
|
}).then(function (pd) {
|
|
expect(pd.write).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("on_data_update", function () {
|
|
test("should get an object containing the trial data", function () {
|
|
return new Promise<any>(function (resolve, reject) {
|
|
var promise_data = <any>{};
|
|
|
|
var trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello",
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial],
|
|
on_data_update: function (data) {
|
|
promise_data.key = data.response;
|
|
},
|
|
on_finish: function () {
|
|
resolve(promise_data);
|
|
},
|
|
});
|
|
|
|
pressKey("a");
|
|
|
|
//resolve();
|
|
}).then(function (pd) {
|
|
expect(pd.key).toBe("a");
|
|
});
|
|
});
|
|
|
|
test("should contain data with null values", function () {
|
|
return new Promise(function (resolve, reject) {
|
|
var promise_data = [];
|
|
|
|
var trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello",
|
|
trial_duration: 10,
|
|
};
|
|
|
|
var trial_2 = {
|
|
type: htmlSliderResponse,
|
|
stimulus: "hello",
|
|
trial_duration: 10,
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial, trial_2],
|
|
on_data_update: function (data) {
|
|
promise_data.push(data);
|
|
},
|
|
on_finish: function () {
|
|
resolve(promise_data);
|
|
},
|
|
});
|
|
|
|
//resolve();
|
|
}).then(function (pd) {
|
|
expect(pd[0].response).not.toBeUndefined();
|
|
expect(pd[0].response).toBeNull();
|
|
expect(pd[1].response).toBeNull();
|
|
expect(pd[1].rt).toBeNull();
|
|
});
|
|
});
|
|
|
|
test("should contain data added with on_finish (trial level)", function () {
|
|
return new Promise<any>(function (resolve, reject) {
|
|
var promise_data = <any>{};
|
|
|
|
var trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello",
|
|
on_finish: function (data) {
|
|
data.trial_level = true;
|
|
},
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial],
|
|
on_data_update: function (data) {
|
|
promise_data.trial_level = data.trial_level;
|
|
},
|
|
on_finish: function () {
|
|
resolve(promise_data);
|
|
},
|
|
});
|
|
|
|
pressKey("a");
|
|
|
|
//resolve();
|
|
}).then(function (pd) {
|
|
expect(pd.trial_level).toBe(true);
|
|
});
|
|
});
|
|
|
|
test("should contain data added with on_trial_finish (experiment level)", function () {
|
|
return new Promise<any>(function (resolve, reject) {
|
|
var promise_data = <any>{};
|
|
|
|
var trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello",
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial],
|
|
on_trial_finish: function (data) {
|
|
data.experiment_level = true;
|
|
},
|
|
on_data_update: function (data) {
|
|
promise_data.experiment_level = data.experiment_level;
|
|
},
|
|
on_finish: function () {
|
|
resolve(promise_data);
|
|
},
|
|
});
|
|
|
|
pressKey("a");
|
|
|
|
//resolve();
|
|
}).then(function (pd) {
|
|
expect(pd.experiment_level).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("on_trial_start", function () {
|
|
test("should get an object containing the trial properties", function () {
|
|
return new Promise<any>(function (resolve, reject) {
|
|
var promise_data = <any>{};
|
|
|
|
var trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello",
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial],
|
|
on_trial_start: function (trial) {
|
|
promise_data.text = trial.stimulus;
|
|
},
|
|
on_finish: function () {
|
|
resolve(promise_data);
|
|
},
|
|
});
|
|
|
|
pressKey("a");
|
|
|
|
//resolve();
|
|
}).then(function (pd) {
|
|
expect(pd.text).toBe("hello");
|
|
});
|
|
});
|
|
|
|
test("should allow modification of the trial properties", function () {
|
|
var trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello",
|
|
};
|
|
|
|
jsPsych = initJsPsych({
|
|
timeline: [trial],
|
|
on_trial_start: function (trial) {
|
|
trial.stimulus = "goodbye";
|
|
},
|
|
});
|
|
|
|
var display_element = jsPsych.getDisplayElement();
|
|
expect(display_element.innerHTML).toMatch("goodbye");
|
|
|
|
pressKey("a");
|
|
});
|
|
});
|
|
|
|
describe("on_timeline_finish", function () {
|
|
test("should fire once when timeline is complete", function () {
|
|
var on_finish_fn = jest.fn();
|
|
|
|
var mini_timeline = {
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
on_timeline_finish: on_finish_fn,
|
|
};
|
|
|
|
jsPsych = initJsPsych({ timeline: [mini_timeline] });
|
|
|
|
pressKey("a");
|
|
expect(on_finish_fn).not.toHaveBeenCalled();
|
|
pressKey("a");
|
|
expect(on_finish_fn).not.toHaveBeenCalled();
|
|
pressKey("a");
|
|
expect(on_finish_fn).toHaveBeenCalled();
|
|
});
|
|
|
|
test("should fire once even with timeline variables", function () {
|
|
var on_finish_fn = jest.fn();
|
|
|
|
var tvs = [{ x: 1 }, { x: 2 }];
|
|
|
|
var mini_timeline = {
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
on_timeline_finish: on_finish_fn,
|
|
timeline_variables: tvs,
|
|
};
|
|
|
|
jsPsych = initJsPsych({ timeline: [mini_timeline] });
|
|
|
|
pressKey("a");
|
|
pressKey("a");
|
|
expect(on_finish_fn.mock.calls.length).toBe(1);
|
|
});
|
|
|
|
test("should fire on every repetition", function () {
|
|
var on_finish_fn = jest.fn();
|
|
|
|
var mini_timeline = {
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
on_timeline_finish: on_finish_fn,
|
|
repetitions: 2,
|
|
};
|
|
|
|
jsPsych = initJsPsych({ timeline: [mini_timeline] });
|
|
|
|
pressKey("a");
|
|
pressKey("a");
|
|
expect(on_finish_fn.mock.calls.length).toBe(2);
|
|
});
|
|
|
|
test("should fire before a loop function", function () {
|
|
var callback = jest.fn().mockImplementation(function (str) {
|
|
return str;
|
|
});
|
|
var count = 0;
|
|
|
|
var mini_timeline = {
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
on_timeline_finish: function () {
|
|
callback("finish");
|
|
},
|
|
loop_function: function () {
|
|
callback("loop");
|
|
count++;
|
|
if (count == 2) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
},
|
|
};
|
|
|
|
jsPsych = initJsPsych({ timeline: [mini_timeline] });
|
|
|
|
pressKey("a");
|
|
pressKey("a");
|
|
expect(callback.mock.calls.length).toBe(4);
|
|
expect(callback.mock.calls[0][0]).toBe("finish");
|
|
expect(callback.mock.calls[1][0]).toBe("loop");
|
|
expect(callback.mock.calls[2][0]).toBe("finish");
|
|
expect(callback.mock.calls[3][0]).toBe("loop");
|
|
});
|
|
});
|
|
|
|
describe("on_timeline_start", function () {
|
|
test("should fire once when timeline starts", function () {
|
|
var on_start_fn = jest.fn();
|
|
|
|
var mini_timeline = {
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
on_timeline_start: on_start_fn,
|
|
};
|
|
|
|
jsPsych = initJsPsych({ timeline: [mini_timeline] });
|
|
|
|
expect(on_start_fn).toHaveBeenCalled();
|
|
pressKey("a");
|
|
pressKey("a");
|
|
pressKey("a");
|
|
expect(on_start_fn.mock.calls.length).toBe(1);
|
|
});
|
|
|
|
test("should fire once even with timeline variables", function () {
|
|
var on_start_fn = jest.fn();
|
|
|
|
var tvs = [{ x: 1 }, { x: 2 }];
|
|
|
|
var mini_timeline = {
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
on_timeline_start: on_start_fn,
|
|
timeline_variables: tvs,
|
|
};
|
|
|
|
jsPsych = initJsPsych({ timeline: [mini_timeline] });
|
|
|
|
expect(on_start_fn).toHaveBeenCalled();
|
|
pressKey("a");
|
|
pressKey("a");
|
|
expect(on_start_fn.mock.calls.length).toBe(1);
|
|
});
|
|
|
|
test("should fire on every repetition", function () {
|
|
var on_start_fn = jest.fn();
|
|
|
|
var mini_timeline = {
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
on_timeline_start: on_start_fn,
|
|
repetitions: 2,
|
|
};
|
|
|
|
jsPsych = initJsPsych({ timeline: [mini_timeline] });
|
|
|
|
expect(on_start_fn).toHaveBeenCalled();
|
|
pressKey("a");
|
|
pressKey("a");
|
|
expect(on_start_fn.mock.calls.length).toBe(2);
|
|
});
|
|
|
|
test("should fire after a conditional function", function () {
|
|
var callback = jest.fn().mockImplementation(function (str) {
|
|
return str;
|
|
});
|
|
|
|
var mini_timeline = {
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
on_timeline_start: function () {
|
|
callback("start");
|
|
},
|
|
conditional_function: function () {
|
|
callback("conditional");
|
|
return true;
|
|
},
|
|
};
|
|
|
|
jsPsych = initJsPsych({ timeline: [mini_timeline] });
|
|
|
|
expect(callback.mock.calls.length).toBe(2);
|
|
expect(callback.mock.calls[0][0]).toBe("conditional");
|
|
expect(callback.mock.calls[1][0]).toBe("start");
|
|
pressKey("a");
|
|
});
|
|
});
|