import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Workbench } from "../../src/components/Workbench"; let closeCount = 0; class FakeAudioContext { currentTime = 0; destination = {} as AudioDestinationNode; async resume(): Promise {} createGain() { return { gain: { value: 0, setValueAtTime: vi.fn(), linearRampToValueAtTime: vi.fn(), }, connect: vi.fn(), }; } createOscillator() { return { type: "sine", frequency: { value: 0 }, connect: vi.fn(), start: vi.fn(), stop: vi.fn(), }; } async close(): Promise { closeCount += 1; } } describe("MIDI Workbench", () => { beforeEach(() => { closeCount = 0; vi.stubGlobal("AudioContext", FakeAudioContext); }); afterEach(() => vi.unstubAllGlobals()); it("shows the example and applies and undoes an edit", async () => { const user = userEvent.setup(); render(); expect( screen.getByRole("img", { name: /Piano roll with 3 notes/u }), ).toBeVisible(); await user.clear(screen.getByLabelText("Semitones")); await user.type(screen.getByLabelText("Semitones"), "12"); await user.click(screen.getByRole("button", { name: "Apply transpose" })); expect(screen.getByRole("status")).toHaveTextContent("transposed"); await user.click(screen.getByRole("button", { name: "Undo edit" })); expect(screen.getByRole("status")).toHaveTextContent("undone"); }); it("closes the active audio context on immediate stop", async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole("button", { name: "Play from start" })); await waitFor(() => expect(screen.getByRole("status")).toHaveTextContent("Playing 3"), ); await user.click(screen.getByRole("button", { name: "Stop now" })); expect(closeCount).toBe(1); expect(screen.getByRole("status")).toHaveTextContent("stopped immediately"); }); });