mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-10 11:10:54 +00:00

* modularize plugins: edit index.ts and example files (WIP)
* continue modularizing plugins: edit index.ts and example files (WIP)
* continue modularizing plugins: edit index.ts and example files (WIP)
* continue modularizing plugins and example files, make info static in plugin classes
* add preload flag to plugin info, fix a few parameterType values
* change preload flags in plugin info from audio/image/video strings to boolean
* convert more plugins and example files
* convert more plugins/examples, sync with webgazer branch plugins/examples (not working on this branch)
* remove preload flag from info, change to ParameterType, change to "ALL_KEYS"/"NO_KEYS", change KEY type to KEYS for array params
* remove descriptions from plugin info, fix some ALL_KEYS/NO_KEYS errors
* remove descriptions and add pretty names to plugin info
* remove/edit comments re ALL_KEYS/NO_KEYS and TS errors
* fix some preload plugin errors and convert example file (plugin still not working due to getAutoPreloadList)
* convert RDK plugin and example file
* convert preload plugin and example, add JsPsych getTimelineDescription method for auto-preloading (still not working for audio due to getAudioBuffer
* fix jsPsych getTimelineDescription method so that it does recursive search
* add image and video preloading to plugin examples
* Merge branch 'modularization' into modularization-plugins
* add tests for reconstruction
* fix timeline array getter method: add private timelineDescription var, set to timeline array in run(), rename getTimelineDescription to getTimeline
* changes in response to PR review: fix JsDoc comments, fix array: true in param info, change HTML string param types, add JsDoc author/file/see docs for all plugin classes, switch to @ts-expect-error. Thanks @bjoluc!
* fix JsDoc comments for plugin classes and preload trials parameter
* change preload type to boolean in ParameterInfo interface, and add `preload: false` to virtual-chinrest item_path image parameter
* All my tests except for preload plugin (will do that tomorrow)
* minor fixes to reconstruction tests
* Update preload plugin tests
* Finish test conversion
* switch to arrow functions to fix this keyword errors in plugins, add audio preloading into plugin example files, fix typos
* convert non-plugin example files (WIP, not tested)
* Fix allow_held_keys -> allow_held_key parameter in virtual-chinrest plugin
* Fix `keyboardListener` type in serial-reaction-time plugin
* type fixes for RDK, simplifying `correctOrNot()` function
* fixed ["ALL_KEYS"] => "ALL_KEYS" for iat plugins and tests
* Build jspsych packages before everything else
Dependent builds were previously failing due to missing type definitions
* Remove console.log from html-keyboard-response tests
I think I accidentially committed it.
* fix the delayed start to animation bug (#1885)
* round all RTs to nearest int (#2108)
* fixes and tests #1900 - IAT parameter problems
* finish converting/testing example files, add init settings, add audio preloading, fix errors
* fix progress-bar timeline to demo an example where auto-updating the progress bar works well
* Revert "round all RTs to nearest int (#2108)"
This reverts commit f53145d2e3
.
* change how delayed timeline variables are implemented
* use static for generate_stimulus method so that it can be called on the vsl-grid-scene class
* fix external-html plugin and example (switch to arrow function for proper this context, fix incorrect parameter name)
* remove outdated TO DO comments
Co-authored-by: bjoluc <mail@bjoluc.de>
Co-authored-by: Josh de Leeuw <josh.deleeuw@gmail.com>
476 lines
11 KiB
TypeScript
476 lines
11 KiB
TypeScript
import callFunction from "@jspsych/plugin-call-function";
|
|
import htmlButtonResponse from "@jspsych/plugin-html-button-response";
|
|
import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response";
|
|
|
|
import { initJsPsych } from "../../src";
|
|
import { pressKey, startTimeline } from "../utils";
|
|
|
|
describe("randomize order", () => {});
|
|
|
|
describe("repetitons", () => {});
|
|
|
|
describe("sampling", () => {
|
|
test("alternate-groups method produces alternating groups", async () => {
|
|
const jsPsych = initJsPsych();
|
|
|
|
const { getHTML } = await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: jsPsych.timelineVariable("stimulus"),
|
|
},
|
|
],
|
|
timeline_variables: ["a", "a", "b", "b", "c", "c"].map((stimulus) => ({ stimulus })),
|
|
sample: {
|
|
type: "alternate-groups",
|
|
groups: [
|
|
[0, 0, 0, 0, 1, 1, 1, 1],
|
|
[2, 2, 2, 2, 3, 3, 3, 3],
|
|
[4, 4, 4, 4, 5, 5, 5, 5],
|
|
],
|
|
randomize_group_order: true,
|
|
},
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
let last = getHTML();
|
|
for (let i = 0; i < 23; i++) {
|
|
pressKey("a");
|
|
let curr = getHTML();
|
|
expect(last).not.toMatch(curr);
|
|
last = curr;
|
|
}
|
|
pressKey("a");
|
|
});
|
|
|
|
test("sampling functions run when timeline loops", async () => {
|
|
let count = 0;
|
|
const reps = 100;
|
|
|
|
const jsPsych = initJsPsych();
|
|
|
|
const { getHTML } = await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: jsPsych.timelineVariable("stimulus"),
|
|
},
|
|
],
|
|
timeline_variables: ["1", "2", "3"].map((stimulus) => ({ stimulus })),
|
|
sample: {
|
|
type: "without-replacement",
|
|
size: 1,
|
|
},
|
|
loop_function: () => {
|
|
count++;
|
|
return count < reps;
|
|
},
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
const result1 = [];
|
|
const result2 = [];
|
|
for (var i = 0; i < reps / 2; i++) {
|
|
result1.push(getHTML());
|
|
pressKey("a");
|
|
result2.push(getHTML());
|
|
pressKey("a");
|
|
}
|
|
|
|
expect(result1).not.toEqual(result2);
|
|
});
|
|
});
|
|
|
|
describe("timeline variables are correctly evaluated", () => {
|
|
test("when used as trial type parameter", async () => {
|
|
const jsPsych = initJsPsych();
|
|
|
|
const { getHTML } = await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: jsPsych.timelineVariable("type"),
|
|
stimulus: "hello",
|
|
choices: ["a", "b"],
|
|
},
|
|
],
|
|
timeline_variables: [{ type: htmlKeyboardResponse }, { type: htmlButtonResponse }],
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
expect(getHTML()).not.toMatch("button");
|
|
pressKey("a");
|
|
expect(getHTML()).toMatch("button");
|
|
});
|
|
|
|
test("when used with a plugin that has a FUNCTION parameter type", async () => {
|
|
const jsPsych = initJsPsych();
|
|
|
|
const mockFn = jest.fn();
|
|
|
|
const { finished } = await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: callFunction,
|
|
func: jsPsych.timelineVariable("fn"),
|
|
},
|
|
],
|
|
timeline_variables: [{ fn: mockFn }, { fn: mockFn }],
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
await finished;
|
|
expect(mockFn).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
test("custom sampling returns correct trials", async () => {
|
|
const jsPsych = initJsPsych();
|
|
await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
data: {
|
|
id: jsPsych.timelineVariable("id"),
|
|
},
|
|
},
|
|
],
|
|
timeline_variables: [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }],
|
|
sample: {
|
|
type: "custom",
|
|
fn: () => [2, 0],
|
|
},
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
pressKey("a");
|
|
pressKey("a");
|
|
expect(jsPsych.data.get().select("id").values).toEqual([2, 0]);
|
|
});
|
|
|
|
test("custom sampling works with a loop", async () => {
|
|
let reps = 0;
|
|
let sample = 3;
|
|
|
|
const jsPsych = initJsPsych();
|
|
await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
data: {
|
|
id: jsPsych.timelineVariable("id"),
|
|
},
|
|
},
|
|
],
|
|
timeline_variables: [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }],
|
|
sample: {
|
|
type: "custom",
|
|
fn: () => [sample],
|
|
},
|
|
loop_function: () => {
|
|
reps++;
|
|
if (reps < 4) {
|
|
sample = 3 - reps;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
pressKey("a");
|
|
pressKey("a");
|
|
pressKey("a");
|
|
pressKey("a");
|
|
expect(jsPsych.data.get().select("id").values).toEqual([3, 2, 1, 0]);
|
|
});
|
|
|
|
test("when used inside a function", async () => {
|
|
const jsPsych = initJsPsych();
|
|
const { getHTML } = await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: () => jsPsych.timelineVariable("x"),
|
|
},
|
|
],
|
|
timeline_variables: [{ x: "foo" }, { x: "bar" }],
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
expect(getHTML()).toMatch("foo");
|
|
pressKey("a");
|
|
expect(getHTML()).toMatch("bar");
|
|
});
|
|
|
|
test("when used in a conditional_function", async () => {
|
|
let x: string;
|
|
|
|
const jsPsych = initJsPsych();
|
|
await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello world",
|
|
},
|
|
],
|
|
timeline_variables: [{ x: "foo" }],
|
|
conditional_function: () => {
|
|
x = jsPsych.timelineVariable("x");
|
|
return true;
|
|
},
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
pressKey("a");
|
|
expect(x).toBe("foo");
|
|
});
|
|
|
|
test("when used in a loop_function", async () => {
|
|
let x: string;
|
|
|
|
const jsPsych = initJsPsych();
|
|
await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello world",
|
|
},
|
|
],
|
|
timeline_variables: [{ x: "foo" }],
|
|
loop_function: () => {
|
|
x = jsPsych.timelineVariable("x");
|
|
return false;
|
|
},
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
pressKey("a");
|
|
expect(x).toBe("foo");
|
|
});
|
|
|
|
test("when used in on_finish", async () => {
|
|
const jsPsych = initJsPsych();
|
|
await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello world",
|
|
on_finish: (data) => {
|
|
data.x = jsPsych.timelineVariable("x");
|
|
},
|
|
},
|
|
],
|
|
timeline_variables: [{ x: "foo" }],
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
pressKey("a");
|
|
expect(jsPsych.data.get().values()[0].x).toBe("foo");
|
|
});
|
|
|
|
test("when used in on_start", async () => {
|
|
let x: string;
|
|
|
|
const jsPsych = initJsPsych();
|
|
await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello world",
|
|
on_start: () => {
|
|
x = jsPsych.timelineVariable("x");
|
|
},
|
|
},
|
|
],
|
|
timeline_variables: [{ x: "foo" }],
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
pressKey("a");
|
|
expect(x).toBe("foo");
|
|
});
|
|
|
|
test("when used in on_load", async () => {
|
|
let x: string;
|
|
|
|
const jsPsych = initJsPsych();
|
|
await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "hello world",
|
|
on_load: () => {
|
|
x = jsPsych.timelineVariable("x");
|
|
},
|
|
},
|
|
],
|
|
timeline_variables: [{ x: "foo" }],
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
pressKey("a");
|
|
expect(x).toBe("foo");
|
|
});
|
|
});
|
|
|
|
describe("jsPsych.getAllTimelineVariables()", () => {
|
|
test("gets all timeline variables for a simple timeline", async () => {
|
|
const jsPsych = initJsPsych();
|
|
await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
on_finish: (data) => {
|
|
var all_tvs = jsPsych.getAllTimelineVariables();
|
|
Object.assign(data, all_tvs);
|
|
},
|
|
},
|
|
],
|
|
timeline_variables: [
|
|
{ a: 1, b: 2 },
|
|
{ a: 2, b: 3 },
|
|
],
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
pressKey("a");
|
|
pressKey("a");
|
|
|
|
expect(jsPsych.data.get().values()).toEqual([
|
|
expect.objectContaining({ a: 1, b: 2 }),
|
|
expect.objectContaining({ a: 2, b: 3 }),
|
|
]);
|
|
});
|
|
|
|
test("gets all timeline variables for a nested timeline", async () => {
|
|
const jsPsych = initJsPsych();
|
|
await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
on_finish: (data) => {
|
|
var all_tvs = jsPsych.getAllTimelineVariables();
|
|
Object.assign(data, all_tvs);
|
|
},
|
|
},
|
|
],
|
|
timeline_variables: [
|
|
{ a: 1, b: 2 },
|
|
{ a: 2, b: 3 },
|
|
],
|
|
},
|
|
],
|
|
timeline_variables: [{ c: 1 }, { c: 2 }],
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
for (let i = 0; i < 4; i++) {
|
|
pressKey("a");
|
|
}
|
|
|
|
expect(jsPsych.data.get().values()).toEqual([
|
|
expect.objectContaining({ a: 1, b: 2, c: 1 }),
|
|
expect.objectContaining({ a: 2, b: 3, c: 1 }),
|
|
expect.objectContaining({ a: 1, b: 2, c: 2 }),
|
|
expect.objectContaining({ a: 2, b: 3, c: 2 }),
|
|
]);
|
|
});
|
|
|
|
test("gets the right values in a conditional_function", async () => {
|
|
let a: number, b: number;
|
|
|
|
const jsPsych = initJsPsych();
|
|
await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
timeline_variables: [
|
|
{ a: 1, b: 2 },
|
|
{ a: 2, b: 3 },
|
|
],
|
|
conditional_function: () => {
|
|
var all_tvs = jsPsych.getAllTimelineVariables();
|
|
a = all_tvs.a;
|
|
b = all_tvs.b;
|
|
return true;
|
|
},
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
pressKey("a");
|
|
pressKey("a");
|
|
|
|
expect(a).toBe(1);
|
|
expect(b).toBe(2);
|
|
});
|
|
});
|