131 lines
3.6 KiB
TypeScript
131 lines
3.6 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import fc from "fast-check";
|
|
import { decodeMessage } from "../src/message/decode.js";
|
|
import { encodeMessage } from "../src/message/encode.js";
|
|
import type { MidiMessage } from "../src/message/message.types.js";
|
|
|
|
const channel = fc.integer({ min: 0, max: 15 });
|
|
const data7 = fc.integer({ min: 0, max: 127 });
|
|
const data14 = fc.integer({ min: 0, max: 16383 });
|
|
|
|
const channelVoiceArbs: fc.Arbitrary<MidiMessage>[] = [
|
|
fc.record({
|
|
type: fc.constant("noteOff" as const),
|
|
channel,
|
|
note: data7,
|
|
velocity: data7,
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("noteOn" as const),
|
|
channel,
|
|
note: data7,
|
|
velocity: data7,
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("polyPressure" as const),
|
|
channel,
|
|
note: data7,
|
|
pressure: data7,
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("controlChange" as const),
|
|
channel,
|
|
controller: data7,
|
|
value: data7,
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("programChange" as const),
|
|
channel,
|
|
program: data7,
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("channelPressure" as const),
|
|
channel,
|
|
pressure: data7,
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("pitchBend" as const),
|
|
channel,
|
|
value: data14,
|
|
}),
|
|
];
|
|
|
|
const systemArbs: fc.Arbitrary<MidiMessage>[] = [
|
|
fc.record({
|
|
type: fc.constant("timeCode" as const),
|
|
channel: fc.constant(null),
|
|
value: data7,
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("songPosition" as const),
|
|
channel: fc.constant(null),
|
|
position: data14,
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("songSelect" as const),
|
|
channel: fc.constant(null),
|
|
song: data7,
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("tuneRequest" as const),
|
|
channel: fc.constant(null),
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("clock" as const),
|
|
channel: fc.constant(null),
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("start" as const),
|
|
channel: fc.constant(null),
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("continue" as const),
|
|
channel: fc.constant(null),
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("stop" as const),
|
|
channel: fc.constant(null),
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("activeSensing" as const),
|
|
channel: fc.constant(null),
|
|
}),
|
|
fc.record({
|
|
type: fc.constant("reset" as const),
|
|
channel: fc.constant(null),
|
|
}),
|
|
];
|
|
|
|
const sysexArb: fc.Arbitrary<MidiMessage> = fc
|
|
.uint8Array({ minLength: 0, maxLength: 32, min: 0, max: 0x7f })
|
|
.map((payload) => ({
|
|
type: "systemExclusive" as const,
|
|
channel: null,
|
|
payload,
|
|
}));
|
|
|
|
const anyMessageArb = fc.oneof(...channelVoiceArbs, ...systemArbs, sysexArb);
|
|
|
|
describe("property: encode/decode round-trip", () => {
|
|
it("every variant round-trips", { timeout: 30000 }, () => {
|
|
fc.assert(
|
|
fc.property(anyMessageArb, (msg) => {
|
|
const bytes = encodeMessage(msg);
|
|
const { msg: decoded, next } = decodeMessage(bytes, 0);
|
|
expect(next).toBe(bytes.length);
|
|
if (
|
|
msg.type === "systemExclusive" &&
|
|
decoded.type === "systemExclusive"
|
|
) {
|
|
expect(Array.from(decoded.payload)).toEqual(
|
|
Array.from(msg.payload)
|
|
);
|
|
} else {
|
|
expect(decoded).toEqual(msg);
|
|
}
|
|
}),
|
|
{ numRuns: 1000 }
|
|
);
|
|
});
|
|
});
|