mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-10 11:10:54 +00:00
487 lines
11 KiB
TypeScript
487 lines
11 KiB
TypeScript
import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response";
|
|
import { pressKey, startTimeline } from "@jspsych/test-utils";
|
|
|
|
import { initJsPsych } from "../../src";
|
|
|
|
describe("loop function", () => {
|
|
test("repeats a timeline when returns true", async () => {
|
|
let count = 0;
|
|
|
|
const trial = {
|
|
timeline: [{ type: htmlKeyboardResponse, stimulus: "foo" }],
|
|
loop_function: () => {
|
|
if (count < 1) {
|
|
count++;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
};
|
|
|
|
const { jsPsych } = await startTimeline([trial]);
|
|
|
|
// first trial
|
|
await pressKey("a");
|
|
expect(jsPsych.data.get().count()).toBe(1);
|
|
|
|
// second trial
|
|
await pressKey("a");
|
|
expect(jsPsych.data.get().count()).toBe(2);
|
|
});
|
|
|
|
test("does not repeat when returns false", async () => {
|
|
const { jsPsych } = await startTimeline([
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
loop_function: () => false,
|
|
},
|
|
]);
|
|
|
|
// first trial
|
|
await pressKey("a");
|
|
expect(jsPsych.data.get().count()).toBe(1);
|
|
|
|
// second trial
|
|
await pressKey("a");
|
|
expect(jsPsych.data.get().count()).toBe(1);
|
|
});
|
|
|
|
test("gets the data from the most recent iteration", async () => {
|
|
let data_count = [];
|
|
let count = 0;
|
|
|
|
const { jsPsych } = await startTimeline([
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
loop_function: (data) => {
|
|
data_count.push(data.count());
|
|
if (count < 2) {
|
|
count++;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
},
|
|
]);
|
|
|
|
// first trial
|
|
await pressKey("a");
|
|
|
|
// second trial
|
|
await pressKey("a");
|
|
|
|
// third trial
|
|
await pressKey("a");
|
|
|
|
expect(data_count).toEqual([1, 1, 1]);
|
|
expect(jsPsych.data.get().count()).toBe(3);
|
|
});
|
|
|
|
test("timeline variables from nested timelines are available in loop function", async () => {
|
|
let counter = 0;
|
|
|
|
const jsPsych = initJsPsych();
|
|
const { getHTML } = await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: jsPsych.timelineVariable("word"),
|
|
},
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
loop_function: () => {
|
|
if (jsPsych.evaluateTimelineVariable("word") === "b" && counter < 2) {
|
|
counter++;
|
|
return true;
|
|
} else {
|
|
counter = 0;
|
|
return false;
|
|
}
|
|
},
|
|
},
|
|
],
|
|
timeline_variables: [{ word: "a" }, { word: "b" }, { word: "c" }],
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
expect(getHTML()).toMatch("a");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("foo");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("b");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("foo");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("foo");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("foo");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("c");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("foo");
|
|
await pressKey("a");
|
|
});
|
|
|
|
test("only runs once when timeline variables are used", async () => {
|
|
let count = 0;
|
|
|
|
await startTimeline([
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
timeline_variables: [{ a: 1 }, { a: 2 }],
|
|
loop_function: () => {
|
|
count++;
|
|
return false;
|
|
},
|
|
},
|
|
]);
|
|
|
|
// first trial
|
|
await pressKey("a");
|
|
expect(count).toBe(0);
|
|
|
|
// second trial
|
|
await pressKey("a");
|
|
expect(count).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe("conditional function", () => {
|
|
test("skips the timeline when returns false", async () => {
|
|
const conditional = {
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
conditional_function: () => false,
|
|
};
|
|
|
|
const trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "bar",
|
|
};
|
|
|
|
const { getHTML } = await startTimeline([conditional, trial]);
|
|
|
|
expect(getHTML()).toMatch("bar");
|
|
|
|
// clear
|
|
await pressKey("a");
|
|
});
|
|
|
|
test("completes the timeline when returns true", async () => {
|
|
const conditional = {
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
conditional_function: () => {
|
|
return true;
|
|
},
|
|
};
|
|
|
|
const trial = {
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "bar",
|
|
};
|
|
|
|
const { getHTML } = await startTimeline([conditional, trial]);
|
|
|
|
expect(getHTML()).toMatch("foo");
|
|
|
|
// next
|
|
await pressKey("a");
|
|
|
|
expect(getHTML()).toMatch("bar");
|
|
|
|
// clear
|
|
await pressKey("a");
|
|
});
|
|
|
|
// TODO What's the purpose of this? Is it documented anywhere?
|
|
test.skip("executes on every loop of the timeline", async () => {
|
|
let count = 0;
|
|
let conditional_count = 0;
|
|
|
|
await startTimeline([
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
loop_function: () => {
|
|
if (count < 1) {
|
|
count++;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
conditional_function: () => {
|
|
conditional_count++;
|
|
return true;
|
|
},
|
|
},
|
|
]);
|
|
|
|
expect(conditional_count).toBe(1);
|
|
|
|
// first trial
|
|
await pressKey("a");
|
|
|
|
expect(conditional_count).toBe(2);
|
|
|
|
// second trial
|
|
await pressKey("a");
|
|
|
|
expect(conditional_count).toBe(2);
|
|
});
|
|
|
|
test("executes only once even when repetitions is > 1", async () => {
|
|
let conditional_count = 0;
|
|
|
|
await startTimeline([
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
repetitions: 2,
|
|
conditional_function: () => {
|
|
conditional_count++;
|
|
return true;
|
|
},
|
|
},
|
|
]);
|
|
|
|
expect(conditional_count).toBe(1);
|
|
|
|
// first trial
|
|
await pressKey("a");
|
|
|
|
expect(conditional_count).toBe(1);
|
|
|
|
// second trial
|
|
await pressKey("a");
|
|
|
|
expect(conditional_count).toBe(1);
|
|
});
|
|
|
|
test("executes only once when timeline variables are used", async () => {
|
|
let conditional_count = 0;
|
|
|
|
await startTimeline([
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
timeline_variables: [{ a: 1 }, { a: 2 }],
|
|
conditional_function: () => {
|
|
conditional_count++;
|
|
return true;
|
|
},
|
|
},
|
|
]);
|
|
|
|
expect(conditional_count).toBe(1);
|
|
|
|
// first trial
|
|
await pressKey("a");
|
|
|
|
expect(conditional_count).toBe(1);
|
|
|
|
// second trial
|
|
await pressKey("a");
|
|
|
|
expect(conditional_count).toBe(1);
|
|
});
|
|
|
|
test("timeline variables from nested timelines are available", async () => {
|
|
const jsPsych = initJsPsych();
|
|
const { getHTML } = await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: jsPsych.timelineVariable("word"),
|
|
},
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
],
|
|
conditional_function: () => {
|
|
if (jsPsych.evaluateTimelineVariable("word") === "b") {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
},
|
|
},
|
|
],
|
|
timeline_variables: [{ word: "a" }, { word: "b" }, { word: "c" }],
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
expect(getHTML()).toMatch("a");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("foo");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("b");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("c");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("foo");
|
|
await pressKey("a");
|
|
});
|
|
});
|
|
|
|
describe("endCurrentTimeline", () => {
|
|
test("stops the current timeline, skipping to the end after the trial completes", async () => {
|
|
const jsPsych = initJsPsych();
|
|
const { getHTML } = await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
on_finish: () => {
|
|
jsPsych.abortCurrentTimeline();
|
|
},
|
|
},
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "bar",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "woo",
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
expect(getHTML()).toMatch("foo");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("woo");
|
|
await pressKey("a");
|
|
});
|
|
|
|
test("works inside nested timelines", async () => {
|
|
const jsPsych = initJsPsych();
|
|
const { getHTML } = await startTimeline(
|
|
[
|
|
{
|
|
timeline: [
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
on_finish: () => {
|
|
jsPsych.abortCurrentTimeline();
|
|
},
|
|
},
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "skip me!",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "bar",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "woo",
|
|
},
|
|
],
|
|
jsPsych
|
|
);
|
|
|
|
expect(getHTML()).toMatch("foo");
|
|
|
|
await pressKey("a");
|
|
|
|
expect(getHTML()).toMatch("bar");
|
|
|
|
await pressKey("a");
|
|
|
|
expect(getHTML()).toMatch("woo");
|
|
|
|
await pressKey("a");
|
|
});
|
|
});
|
|
|
|
describe("nested timelines", () => {
|
|
test("works without other parameters", async () => {
|
|
const { getHTML } = await startTimeline([
|
|
{
|
|
timeline: [
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "foo",
|
|
},
|
|
{
|
|
type: htmlKeyboardResponse,
|
|
stimulus: "bar",
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
|
|
expect(getHTML()).toMatch("foo");
|
|
await pressKey("a");
|
|
expect(getHTML()).toMatch("bar");
|
|
await pressKey("a");
|
|
});
|
|
});
|