jsPsych/packages/jspsych/tests/core/timeline-variables.test.ts

460 lines
10 KiB
TypeScript

import callFunction from "@jspsych/plugin-call-function";
import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response";
import { pressKey, startTimeline } from "@jspsych/test-utils";
import { initJsPsych } from "../../src";
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++) {
await pressKey("a");
let curr = getHTML();
expect(last).not.toMatch(curr);
last = curr;
}
await 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 (let i = 0; i < reps / 2; i++) {
result1.push(getHTML());
await pressKey("a");
result2.push(getHTML());
await pressKey("a");
}
expect(result1).not.toEqual(result2);
});
test("fixed repetitions method produces random order", async () => {
const jsPsych = initJsPsych();
const seed = jsPsych.randomization.setSeed("jspsych");
const { expectFinished } = await startTimeline(
[
{
timeline: [
{
type: htmlKeyboardResponse,
stimulus: jsPsych.timelineVariable("stimulus"),
data: {
s: jsPsych.timelineVariable("stimulus"),
},
},
],
timeline_variables: [{ stimulus: "a" }, { stimulus: "b" }],
sample: {
type: "fixed-repetitions",
size: 5,
},
},
],
jsPsych
);
for (let i = 0; i < 10; i++) {
await pressKey("a");
}
await expectFinished();
const order = jsPsych.data.get().select("s").values;
expect(order).not.toEqual(["a", "b", "a", "b", "a", "b", "a", "b", "a", "b"]);
const countA = order.filter((s) => s === "a").length;
const countB = order.filter((s) => s === "b").length;
expect(countA).toEqual(5);
expect(countB).toEqual(5);
});
});
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",
},
],
timeline_variables: [{ type: htmlKeyboardResponse }],
},
],
jsPsych
);
expect(getHTML()).toMatch("hello");
await pressKey("a");
});
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
);
await pressKey("a");
await 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
);
await pressKey("a");
await pressKey("a");
await pressKey("a");
await 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");
await 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.evaluateTimelineVariable("x");
return true;
},
},
],
jsPsych
);
await 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.evaluateTimelineVariable("x");
return false;
},
},
],
jsPsych
);
await 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.evaluateTimelineVariable("x");
},
},
],
timeline_variables: [{ x: "foo" }],
},
],
jsPsych
);
await 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.evaluateTimelineVariable("x");
},
},
],
timeline_variables: [{ x: "foo" }],
},
],
jsPsych
);
await 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.evaluateTimelineVariable("x");
},
},
],
timeline_variables: [{ x: "foo" }],
},
],
jsPsych
);
await pressKey("a");
expect(x).toBe("foo");
});
});
test("throws an error when variable doesn't exist", async () => {
const jsPsych = initJsPsych();
const { expectFinished } = await startTimeline(
[
{
timeline: [
{
type: htmlKeyboardResponse,
stimulus: "foo",
on_start: () => {
expect(() => jsPsych.evaluateTimelineVariable("c")).toThrow();
},
},
],
timeline_variables: [
{ a: 1, b: 2 },
{ a: 2, b: 3 },
],
},
],
jsPsych
);
await pressKey("a");
await pressKey("a");
await expectFinished();
});
test("can fetch a variable called 'data'", async () => {
const jsPsych = initJsPsych();
const { expectFinished } = await startTimeline(
[
{
timeline: [
{
type: htmlKeyboardResponse,
stimulus: "foo",
on_start: () => {
expect(() => jsPsych.evaluateTimelineVariable("data")).not.toThrow();
},
},
],
timeline_variables: [
{ data: { a: 1 }, b: 2 },
{ data: { a: 2 }, b: 3 },
],
},
],
jsPsych
);
await pressKey("a");
await pressKey("a");
await expectFinished();
});