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");
});
});