RolandJansen/intermix.js

View on GitHub
src/registry/__tests__/MasterRegistry.test.ts

Summary

Maintainability
F
3 days
Test Coverage
/// <reference path="../../../typings/web-audio-test-api.d.ts" />
import "web-audio-test-api";
import MasterRegistry from "../MasterRegistry";
import TestInstrument from "../../plugins/TestInstrument";
import { ICoreAction, IOscAction } from "../../interfaces/IActions";
import { IPlugin } from "../../interfaces/IRegistryItems";
import { store } from "../../store/store";
import SeqPart from "../../seqpart/SeqPart";

// instruct Jest to use the mock class
// instead of the real one globaly.
// jest.mock("../../store/store");

let registry: MasterRegistry;
let ac: AudioContext;

beforeEach(() => {
    ac = new AudioContext();
    registry = new MasterRegistry(ac);
});

/**
 * In the following tests we use the real store. It will be
 * polluted by every test but it doesn't
 * matter as long as we just examine the
 * sub-state for the current test.
 */

describe("PluginRegistry", () => {
    let plugin: IPlugin;
    let pluginId: string;

    beforeEach(() => {
        pluginId = registry.addPlugin(TestInstrument);
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        plugin = registry["plugins"]["itemList"].get(pluginId)!;
    });

    describe("addPlugin", () => {
        test("creates a new plugin with a unique id", () => {
            expect(plugin.uid).toMatch(pluginId);
        });

        test("adds an entry to the store", () => {
            const state = store.getState();
            expect(state[pluginId]).toBeDefined();
        });

        test("store entry contains the properties of the plugin", () => {
            const state = store.getState();
            expect(state[pluginId].ACTION1).toBe(0);
            expect(state[pluginId].ACTION2).toBe(1);
        });

        test("adds reducers for the plugin to the store", () => {
            // STOP: Why does this work? It should NOT work!!!
            // or is it the middleware? -> Investigate!!!
            const action1: ICoreAction = {
                listener: pluginId,
                type: "ACTION1",
                payload: 23,
            };
            const action2: ICoreAction = {
                listener: pluginId,
                type: "ACTION2",
                payload: 42,
            };

            store.dispatch(action1);
            store.dispatch(action2);

            const state = store.getState();
            expect(state[pluginId].ACTION1).toBe(23);
            expect(state[pluginId].ACTION2).toBe(42);
        });

        test("adds action creators to the plugin", () => {
            plugin.actionCreators.ACTION1(5);

            const state = store.getState();
            expect(state[pluginId].ACTION1).toBe(5);
        });

        test("subscribes the plugin to dispatch", () => {
            // we cannot spy on plugin.onChange as the
            // dispatcher would use the original method anyway.
            // Instead, testValue in the plugin can be checked.
            plugin.actionCreators.ACTION1(2342);
            expect(plugin.testValue[1]).toBe(2342);
        });
    });

    describe("removePlugin", () => {
        beforeEach(() => {
            jest.spyOn(plugin, "unsubscribe");
            registry.removePlugin(pluginId);
        });

        test("removes the plugin from the plugin registry", () => {
            const allPluginIds = registry["plugins"].getUidList();
            expect(allPluginIds).not.toContain(pluginId);
        });

        test("unsubscribes from dispatch", () => {
            // this is also tested in PluginRegistry.test but anyway
            expect(plugin.unsubscribe).toHaveBeenCalled();
        });

        test("removes the plugins reducers", () => {
            // we dispatch an action and assume that when
            // it doesn't alter the state, there is no
            // appropriate reducer present.
            const oldState = store.getState();
            plugin.actionCreators.ACTION1(237);
            const nextState = store.getState();
            expect(oldState).toEqual(nextState);
        });

        test("removes the plugins state from the store", () => {
            const state = store.getState();
            expect(state[pluginId]).not.toBeDefined();
        });
    });
});

describe("SeqPart Registry", () => {
    const payload = ["note", 0, 0, 0, 0];

    let part: SeqPart;
    let partId: string;

    beforeEach(() => {
        partId = registry.addSeqPart();
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        part = registry["seqParts"]["itemList"].get(partId)!;
    });

    describe("addSeqPart", () => {
        let action1: IOscAction;
        let action2: IOscAction;

        beforeEach(() => {
            // auch mit "konventionellen actions testen und mit type"
            action1 = {
                address: `/intermix/seqpart/${partId}/addNote`,
                typeTag: ",iiff",
                type: "addNote",
                payload,
            };
            action2 = {
                address: `/intermix/seqpart/${partId}/removeNote`,
                typeTag: ",iiff",
                type: ",removeNote",
                payload,
            };
        });

        test("returns the instance id", () => {
            expect(partId).toMatch(part.uid);
        });

        test("adds an entry to the store", () => {
            const state = store.getState();
            expect(state[partId]).toBeDefined();
        });

        test("adds reducers for the seqpart to the store", () => {
            store.dispatch(action1);
            store.dispatch(action2);

            const state = store.getState();
            expect(state[partId].addNote).toEqual(payload);
            expect(state[partId].removeNote).toEqual(payload);
        });

        test("adds action creators to the part", () => {
            part.actionCreators.addNote(payload);

            const state = store.getState();
            expect(state[partId].addNote).toBe(payload);
        });

        test("subscribes the part to dispatch", () => {
            // there's no way to test this currently.
            // in the future we can send something
            // to the SeqPart but that's not implemented yet (in SeqPart).
        });
    });

    describe("removeSeqPart", () => {
        beforeEach(() => {
            jest.spyOn(part, "unsubscribe");
            registry.removeSeqPart(partId);
        });

        test("removes the part from the seqpart registry", () => {
            const allPartsIds = registry["seqParts"].getUidList();
            expect(allPartsIds).not.toContain(partId);
        });

        test("unsubscribes from dispatch", () => {
            // this is also tested in SeqPartRegistry.test but anyway
            expect(part.unsubscribe).toHaveBeenCalled();
        });

        test("removes the parts reducers", () => {
            // we dispatch an action and assume that when
            // it doesn't alter the state, there is no
            // appropriate reducer present.
            const oldState = store.getState();
            part.actionCreators.addNote(237);
            const nextState = store.getState();
            expect(oldState).toEqual(nextState);
        });

        test("removes the parts state from the store", () => {
            const state = store.getState();
            expect(state[partId]).not.toBeDefined();
        });
    });

    describe("getActionCreators", () => {
        let pluginId: string;
        let partId: string;

        beforeEach(() => {
            pluginId = registry.addPlugin(TestInstrument);
            partId = registry.addSeqPart();
        });

        test("returns an empty object if id was not found", () => {
            const ac = registry.getActionCreators("wasdas");
            expect(ac).toEqual({});
        });

        test("takes a plugin id and returns its action creators", () => {
            const ac = registry.getActionCreators(pluginId);
            const oldState = store.getState();
            expect(oldState[pluginId].ACTION1).toBe(0);
            expect(oldState[pluginId].ACTION2).toBe(1);
            ac.ACTION1(23);
            ac.ACTION2(42);
            const nextState = store.getState();
            expect(nextState[pluginId].ACTION1).toBe(23);
            expect(nextState[pluginId].ACTION2).toBe(42);
        });

        test("takes a plugin id and returns its unbound action creators", () => {
            const ac = registry.getActionCreators(pluginId, "unbound");
            const oldState = store.getState();
            ac.ACTION1(23);
            const nextState = store.getState();
            expect(oldState).toEqual(nextState); // nothing changed = action was not dispatched
        });

        test("takes a seqPart id and returns its action creators", () => {
            const expected = ["note", 0, 0, 0.0, 0.0];
            const ac = registry.getActionCreators(partId);
            const oldState = store.getState();
            expect(oldState[partId].addNote).toEqual(expected);
            expect(oldState[partId].removeNote).toEqual(expected);
            ac.addNote(payload);
            ac.removeNote(payload);
            const nextState = store.getState();
            expect(nextState[partId].addNote).toBe(payload);
            expect(nextState[partId].removeNote).toBe(payload);
        });

        test("takes a seqPart id and returns its unbound action creators", () => {
            const ac = registry.getActionCreators(partId, "unbound");
            const oldState = store.getState();
            ac.addNote(23);
            const nextState = store.getState();
            expect(oldState).toEqual(nextState); // nothing changed = action was not dispatched
        });
    });
});