From d296962c7ea761ed64e7ed7e35974b8151109b7d Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Wed, 8 Nov 2023 09:55:02 -0500 Subject: [PATCH] mock the audioplayer module and add tests --- .../plugin-api/__mocks__/AudioPlayer.ts | 38 ++++++ .../src/index.spec.ts | 126 +++++++++++++++++- 2 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 packages/jspsych/src/modules/plugin-api/__mocks__/AudioPlayer.ts diff --git a/packages/jspsych/src/modules/plugin-api/__mocks__/AudioPlayer.ts b/packages/jspsych/src/modules/plugin-api/__mocks__/AudioPlayer.ts new file mode 100644 index 0000000000..0f8484efea --- /dev/null +++ b/packages/jspsych/src/modules/plugin-api/__mocks__/AudioPlayer.ts @@ -0,0 +1,38 @@ +import { AudioPlayerOptions } from "../AudioPlayer"; + +const actual = jest.requireActual("../AudioPlayer"); + +export const mockStop = jest.fn(); + +export const AudioPlayer = jest + .fn() + .mockImplementation((src: string, options: AudioPlayerOptions = { useWebAudio: false }) => { + let eventHandlers = {}; + + const mockInstance = Object.create(actual.AudioPlayer.prototype); + + return Object.assign(mockInstance, { + load: jest.fn(), + play: jest.fn(() => { + setTimeout(() => { + if (eventHandlers["ended"]) { + for (const handler of eventHandlers["ended"]) { + handler(); + } + } + }, 1000); + }), + stop: mockStop, + addEventListener: jest.fn((event, handler) => { + if (!eventHandlers[event]) { + eventHandlers[event] = []; + } + eventHandlers[event].push(handler); + }), + removeEventListener: jest.fn((event, handler) => { + if (eventHandlers[event] === handler) { + eventHandlers[event] = eventHandlers[event].filter((h) => h !== handler); + } + }), + }); + }); diff --git a/packages/plugin-audio-keyboard-response/src/index.spec.ts b/packages/plugin-audio-keyboard-response/src/index.spec.ts index cd5e073745..1ffa62fede 100644 --- a/packages/plugin-audio-keyboard-response/src/index.spec.ts +++ b/packages/plugin-audio-keyboard-response/src/index.spec.ts @@ -1,10 +1,131 @@ -import { pressKey, simulateTimeline, startTimeline } from "@jspsych/test-utils"; +jest.mock("../../jspsych/src/modules/plugin-api/AudioPlayer"); + +import { flushPromises, pressKey, simulateTimeline, startTimeline } from "@jspsych/test-utils"; import { initJsPsych } from "jspsych"; +//@ts-expect-error mock +import { mockStop } from "../../jspsych/src/modules/plugin-api/AudioPlayer"; import audioKeyboardResponse from "."; jest.useFakeTimers(); +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe("audio-keyboard-response", () => { + // this relies on AudioContext, which we haven't mocked yet + it.skip("works with all defaults", async () => { + const { expectFinished, expectRunning } = await startTimeline([ + { + type: audioKeyboardResponse, + stimulus: "foo.mp3", + }, + ]); + + expectRunning(); + + pressKey("a"); + + expectFinished(); + + await flushPromises(); + }); + + it("works with use_webaudio:false", async () => { + const jsPsych = initJsPsych({ use_webaudio: false }); + + const { expectFinished, expectRunning } = await startTimeline( + [ + { + type: audioKeyboardResponse, + stimulus: "foo.mp3", + }, + ], + jsPsych + ); + + await expectRunning(); + pressKey("a"); + await expectFinished(); + }); + + it("ends when trial_ends_after_audio is true and audio finishes", async () => { + const jsPsych = initJsPsych({ use_webaudio: false }); + + const { expectFinished, expectRunning } = await startTimeline( + [ + { + type: audioKeyboardResponse, + stimulus: "foo.mp3", + trial_ends_after_audio: true, + }, + ], + jsPsych + ); + + await expectRunning(); + + jest.runAllTimers(); + + await expectFinished(); + }); + + it("prevents responses when response_allowed_while_playing is false", async () => { + const jsPsych = initJsPsych({ use_webaudio: false }); + + const { expectFinished, expectRunning } = await startTimeline( + [ + { + type: audioKeyboardResponse, + stimulus: "foo.mp3", + response_allowed_while_playing: false, + }, + ], + jsPsych + ); + + await expectRunning(); + + pressKey("a"); + + await expectRunning(); + + jest.runAllTimers(); + + await expectRunning(); + + pressKey("a"); + + await expectFinished(); + }); + + it("ends when trial_duration is shorter than the audio duration, stopping the audio", async () => { + const jsPsych = initJsPsych({ use_webaudio: false }); + + const { expectFinished, expectRunning } = await startTimeline( + [ + { + type: audioKeyboardResponse, + stimulus: "foo.mp3", + trial_duration: 500, + }, + ], + jsPsych + ); + + await expectRunning(); + + expect(mockStop).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(500); + + expect(mockStop).toHaveBeenCalled(); + + await expectFinished(); + }); +}); + describe("audio-keyboard-response simulation", () => { test("data mode works", async () => { const timeline = [ @@ -22,8 +143,7 @@ describe("audio-keyboard-response simulation", () => { expect(typeof getData().values()[0].response).toBe("string"); }); - // can't run this until we mock Audio elements. - test.skip("visual mode works", async () => { + test("visual mode works", async () => { const jsPsych = initJsPsych({ use_webaudio: false }); const timeline = [