Initial Commit
This commit is contained in:
136
packages/ui/react/react-midi/src/test-utils.tsx
Normal file
136
packages/ui/react/react-midi/src/test-utils.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { type ReactNode, useMemo } from "react";
|
||||
import type {
|
||||
WebMIDIAccess,
|
||||
WebMIDIConnectionEvent,
|
||||
WebMIDIInput,
|
||||
WebMIDIMessageEvent,
|
||||
WebMIDIOutput,
|
||||
WebMIDIRequestFn,
|
||||
} from "./port/web-midi.types.js";
|
||||
import { MidiRequestContext } from "./provider/midi-context.js";
|
||||
import { MidiProvider } from "./provider/midi-provider.js";
|
||||
|
||||
export interface FakeInput extends WebMIDIInput {
|
||||
fire(bytes: number[]): void;
|
||||
}
|
||||
|
||||
export interface FakeOutput extends WebMIDIOutput {
|
||||
readonly sent: number[][];
|
||||
}
|
||||
|
||||
export interface FakeAccess extends WebMIDIAccess {
|
||||
addInput(input: FakeInput): void;
|
||||
addOutput(output: FakeOutput): void;
|
||||
removeInput(id: string): void;
|
||||
}
|
||||
|
||||
export function makeFakeInput(id: string, name = `input-${id}`): FakeInput {
|
||||
let handler: ((ev: WebMIDIMessageEvent) => void) | null = null;
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
manufacturer: "Suaveplan",
|
||||
state: "connected",
|
||||
type: "input",
|
||||
get onmidimessage() {
|
||||
return handler;
|
||||
},
|
||||
set onmidimessage(fn) {
|
||||
handler = fn;
|
||||
},
|
||||
fire(bytes) {
|
||||
handler?.({ data: new Uint8Array(bytes), timeStamp: 0 });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function makeFakeOutput(id: string, name = `output-${id}`): FakeOutput {
|
||||
const sent: number[][] = [];
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
manufacturer: "Suaveplan",
|
||||
state: "connected",
|
||||
type: "output",
|
||||
send(bytes) {
|
||||
sent.push(Array.from(bytes));
|
||||
},
|
||||
sent,
|
||||
};
|
||||
}
|
||||
|
||||
export function makeFakeAccess(opts?: {
|
||||
inputs?: FakeInput[];
|
||||
outputs?: FakeOutput[];
|
||||
sysex?: boolean;
|
||||
}): FakeAccess {
|
||||
const inputs = new Map<string, FakeInput>();
|
||||
const outputs = new Map<string, FakeOutput>();
|
||||
let stateChange: ((ev: WebMIDIConnectionEvent) => void) | null = null;
|
||||
for (const i of opts?.inputs ?? []) inputs.set(i.id, i);
|
||||
for (const o of opts?.outputs ?? []) outputs.set(o.id, o);
|
||||
const access: FakeAccess = {
|
||||
get inputs() {
|
||||
return inputs as ReadonlyMap<string, WebMIDIInput>;
|
||||
},
|
||||
get outputs() {
|
||||
return outputs as ReadonlyMap<string, WebMIDIOutput>;
|
||||
},
|
||||
sysexEnabled: opts?.sysex ?? false,
|
||||
get onstatechange() {
|
||||
return stateChange;
|
||||
},
|
||||
set onstatechange(fn) {
|
||||
stateChange = fn;
|
||||
},
|
||||
addInput(input) {
|
||||
inputs.set(input.id, input);
|
||||
stateChange?.({ port: input });
|
||||
},
|
||||
addOutput(output) {
|
||||
outputs.set(output.id, output);
|
||||
stateChange?.({ port: output });
|
||||
},
|
||||
removeInput(id) {
|
||||
const port = inputs.get(id);
|
||||
if (port !== undefined) {
|
||||
inputs.delete(id);
|
||||
stateChange?.({
|
||||
port: { ...port, state: "disconnected" },
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
return access;
|
||||
}
|
||||
|
||||
export interface FakeProviderProps {
|
||||
readonly access: FakeAccess;
|
||||
readonly requestImpl?: WebMIDIRequestFn;
|
||||
readonly sysex?: boolean;
|
||||
readonly children?: ReactNode;
|
||||
readonly requestSpy?: (opts?: { sysex?: boolean }) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders `<MidiProvider>` with a deterministic fake access injected
|
||||
* through `MidiRequestContext`. The `requestSpy` callback is invoked
|
||||
* once per request, allowing tests to assert call counts (e.g.
|
||||
* exactly-once under StrictMode).
|
||||
*/
|
||||
export function FakeMidiProvider(props: FakeProviderProps): ReactNode {
|
||||
const request: WebMIDIRequestFn = useMemo(() => {
|
||||
if (props.requestImpl !== undefined) {
|
||||
return props.requestImpl;
|
||||
}
|
||||
return (opts) => {
|
||||
props.requestSpy?.(opts);
|
||||
return Promise.resolve(props.access);
|
||||
};
|
||||
}, [props.access, props.requestImpl, props.requestSpy]);
|
||||
return (
|
||||
<MidiRequestContext.Provider value={request}>
|
||||
<MidiProvider sysex={props.sysex}>{props.children}</MidiProvider>
|
||||
</MidiRequestContext.Provider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user