@flighthq/midi 0.5.1-next.903.f434bc2 → 0.5.1-next.905.5fbf787
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +4 -4
- package/src/midiAccess.test.ts +25 -19
- package/src/midiPort.test.ts +53 -41
- package/src/midiSubscription.test.ts +22 -0
package/README.md
CHANGED
|
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/midi`. The `@flighth
|
|
|
13
13
|
This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
|
|
14
14
|
|
|
15
15
|
- [Flight project](https://github.com/flighthq/flight)
|
|
16
|
-
- [Source for @flighthq/midi@0.5.1-next.
|
|
17
|
-
- [License](https://github.com/flighthq/flight/blob/
|
|
16
|
+
- [Source for @flighthq/midi@0.5.1-next.905.5fbf787](https://github.com/flighthq/flight/tree/5fbf7874064c384307542d68b4dcb6a895ff0dcf/packages/midi)
|
|
17
|
+
- [License](https://github.com/flighthq/flight/blob/5fbf7874064c384307542d68b4dcb6a895ff0dcf/LICENSE.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/midi",
|
|
3
|
-
"version": "0.5.1-next.
|
|
3
|
+
"version": "0.5.1-next.905.5fbf787",
|
|
4
4
|
"author": "Joshua Granick and other contributors",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@flighthq/entity": "0.5.1-next.
|
|
42
|
-
"@flighthq/signals": "0.5.1-next.
|
|
43
|
-
"@flighthq/types": "0.5.1-next.
|
|
41
|
+
"@flighthq/entity": "0.5.1-next.905.5fbf787",
|
|
42
|
+
"@flighthq/signals": "0.5.1-next.905.5fbf787",
|
|
43
|
+
"@flighthq/types": "0.5.1-next.905.5fbf787"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"typescript": "^5.3.0"
|
package/src/midiAccess.test.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { EntityRuntimeKey } from '@flighthq/types/contract';
|
|
|
3
3
|
import { describe, expect, it, vi } from 'vitest';
|
|
4
4
|
|
|
5
5
|
import * as midi from './contract';
|
|
6
|
+
import { initializeMidiAccessResource } from './midiAccess';
|
|
6
7
|
|
|
7
8
|
describe('createMidiAccessResource', () => {
|
|
8
9
|
it('creates an Entity whose provider operations stay outside its public fields', () => {
|
|
@@ -101,6 +102,30 @@ describe('getMidiAccessOutputPorts', () => {
|
|
|
101
102
|
});
|
|
102
103
|
});
|
|
103
104
|
|
|
105
|
+
describe('initializeMidiAccessResource', () => {
|
|
106
|
+
it('is the construction initializer of createMidiAccessResource', () => {
|
|
107
|
+
expect(typeof initializeMidiAccessResource).toBe('function');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
function createAccess(inputs: readonly object[], outputs: readonly object[]): unknown {
|
|
112
|
+
return requiredFunction('createMidiAccessResource')({
|
|
113
|
+
attachStateChange: vi.fn(),
|
|
114
|
+
getInputPorts: () => inputs,
|
|
115
|
+
getOutputPorts: () => outputs,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createEmptyAccess(): unknown {
|
|
120
|
+
return createAccess([], []);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function requiredFunction(name: string): (...args: unknown[]) => unknown {
|
|
124
|
+
const value: unknown = Reflect.get(midi, name);
|
|
125
|
+
expect(value, `${name} export`).toBeTypeOf('function');
|
|
126
|
+
if (typeof value !== 'function') throw new TypeError(`${name} is not exported`);
|
|
127
|
+
return value as (...args: unknown[]) => unknown;
|
|
128
|
+
}
|
|
104
129
|
describe('requestMidiAccess', () => {
|
|
105
130
|
it('retains accepted access and keeps denial, security restriction, and provider failure distinct', async () => {
|
|
106
131
|
const requestMidiAccess = requiredFunction('requestMidiAccess');
|
|
@@ -126,22 +151,3 @@ describe('requestMidiAccess', () => {
|
|
|
126
151
|
await expect(requestMidiAccess(host)).resolves.toEqual({ reason: 'operation-failed' });
|
|
127
152
|
});
|
|
128
153
|
});
|
|
129
|
-
|
|
130
|
-
function createAccess(inputs: readonly object[], outputs: readonly object[]): unknown {
|
|
131
|
-
return requiredFunction('createMidiAccessResource')({
|
|
132
|
-
attachStateChange: vi.fn(),
|
|
133
|
-
getInputPorts: () => inputs,
|
|
134
|
-
getOutputPorts: () => outputs,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function createEmptyAccess(): unknown {
|
|
139
|
-
return createAccess([], []);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function requiredFunction(name: string): (...args: unknown[]) => unknown {
|
|
143
|
-
const value: unknown = Reflect.get(midi, name);
|
|
144
|
-
expect(value, `${name} export`).toBeTypeOf('function');
|
|
145
|
-
if (typeof value !== 'function') throw new TypeError(`${name} is not exported`);
|
|
146
|
-
return value as (...args: unknown[]) => unknown;
|
|
147
|
-
}
|
package/src/midiPort.test.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { EntityRuntimeKey } from '@flighthq/types/contract';
|
|
|
3
3
|
import { describe, expect, it, vi } from 'vitest';
|
|
4
4
|
|
|
5
5
|
import * as midi from './contract';
|
|
6
|
+
import { initializeMidiInputPortResource, initializeMidiOutputPortResource } from './midiPort';
|
|
6
7
|
|
|
7
8
|
describe('closeMidiPort', () => {
|
|
8
9
|
it('distinguishes a successful close, an already-closed port, disposal, and provider failure', async () => {
|
|
@@ -156,50 +157,15 @@ describe('getMidiPortState', () => {
|
|
|
156
157
|
});
|
|
157
158
|
});
|
|
158
159
|
|
|
159
|
-
describe('
|
|
160
|
-
it('
|
|
161
|
-
|
|
162
|
-
const open = vi.fn(async () => {
|
|
163
|
-
connection.value = 'open';
|
|
164
|
-
});
|
|
165
|
-
const port = createInputPort({ connection, open });
|
|
166
|
-
const openMidiPort = requiredFunction('openMidiPort');
|
|
167
|
-
await expect(openMidiPort(port)).resolves.toEqual({ reason: 'opened' });
|
|
168
|
-
await expect(openMidiPort(port)).resolves.toEqual({ reason: 'already-open' });
|
|
169
|
-
expect(open).toHaveBeenCalledOnce();
|
|
170
|
-
|
|
171
|
-
const disconnected = createInputPort({ state: { value: 'disconnected' } });
|
|
172
|
-
await expect(openMidiPort(disconnected)).resolves.toEqual({ reason: 'disconnected' });
|
|
173
|
-
await requiredFunction('disposeMidiPort')(disconnected);
|
|
174
|
-
await expect(openMidiPort(disconnected)).resolves.toEqual({ reason: 'disposed' });
|
|
175
|
-
|
|
176
|
-
const failed = createInputPort({ open: vi.fn(async () => Promise.reject(new Error('open'))) });
|
|
177
|
-
await expect(openMidiPort(failed)).resolves.toEqual({ reason: 'operation-failed' });
|
|
160
|
+
describe('initializeMidiInputPortResource', () => {
|
|
161
|
+
it('is the construction initializer of createMidiInputPortResource', () => {
|
|
162
|
+
expect(typeof initializeMidiInputPortResource).toBe('function');
|
|
178
163
|
});
|
|
179
164
|
});
|
|
180
165
|
|
|
181
|
-
describe('
|
|
182
|
-
it('
|
|
183
|
-
|
|
184
|
-
const connection = { value: 'closed' };
|
|
185
|
-
const output = createOutputPort({ connection, send });
|
|
186
|
-
const sendMidiMessage = requiredFunction('sendMidiMessage');
|
|
187
|
-
expect(sendMidiMessage(output, new Uint8Array())).toEqual({ reason: 'invalid-message' });
|
|
188
|
-
expect(sendMidiMessage(output, new Uint8Array([0x40]))).toEqual({ reason: 'invalid-message' });
|
|
189
|
-
expect(sendMidiMessage(output, new Uint8Array([0x90, 0x40]))).toEqual({ reason: 'invalid-message' });
|
|
190
|
-
expect(sendMidiMessage(output, new Uint8Array([0x90, 0x90, 0x7f]))).toEqual({ reason: 'invalid-message' });
|
|
191
|
-
expect(sendMidiMessage(output, new Uint8Array([0xf0, 0x01, 0xf7]))).toEqual({
|
|
192
|
-
reason: 'system-exclusive-not-enabled',
|
|
193
|
-
});
|
|
194
|
-
expect(sendMidiMessage(output, new Uint8Array([0x90, 0x40, 0x7f]))).toEqual({ reason: 'not-open' });
|
|
195
|
-
expect(send).not.toHaveBeenCalled();
|
|
196
|
-
|
|
197
|
-
connection.value = 'open';
|
|
198
|
-
expect(sendMidiMessage(output, new Uint8Array([0x90, 0x40, 0x7f]), 12.5)).toEqual({ reason: 'sent' });
|
|
199
|
-
expect(send).toHaveBeenCalledWith([0x90, 0x40, 0x7f], 12.5);
|
|
200
|
-
|
|
201
|
-
const disconnected = createOutputPort({ connection: { value: 'open' }, state: { value: 'disconnected' } });
|
|
202
|
-
expect(sendMidiMessage(disconnected, new Uint8Array([0x80, 0x40, 0]))).toEqual({ reason: 'disconnected' });
|
|
166
|
+
describe('initializeMidiOutputPortResource', () => {
|
|
167
|
+
it('is the construction initializer of createMidiOutputPortResource', () => {
|
|
168
|
+
expect(typeof initializeMidiOutputPortResource).toBe('function');
|
|
203
169
|
});
|
|
204
170
|
});
|
|
205
171
|
|
|
@@ -271,3 +237,49 @@ function requiredFunction(name: string): (...args: unknown[]) => unknown {
|
|
|
271
237
|
if (typeof value !== 'function') throw new TypeError(`${name} is not exported`);
|
|
272
238
|
return value as (...args: unknown[]) => unknown;
|
|
273
239
|
}
|
|
240
|
+
describe('openMidiPort', () => {
|
|
241
|
+
it('distinguishes open, already-open, disconnected, disposed, and provider failure', async () => {
|
|
242
|
+
const connection = { value: 'closed' };
|
|
243
|
+
const open = vi.fn(async () => {
|
|
244
|
+
connection.value = 'open';
|
|
245
|
+
});
|
|
246
|
+
const port = createInputPort({ connection, open });
|
|
247
|
+
const openMidiPort = requiredFunction('openMidiPort');
|
|
248
|
+
await expect(openMidiPort(port)).resolves.toEqual({ reason: 'opened' });
|
|
249
|
+
await expect(openMidiPort(port)).resolves.toEqual({ reason: 'already-open' });
|
|
250
|
+
expect(open).toHaveBeenCalledOnce();
|
|
251
|
+
|
|
252
|
+
const disconnected = createInputPort({ state: { value: 'disconnected' } });
|
|
253
|
+
await expect(openMidiPort(disconnected)).resolves.toEqual({ reason: 'disconnected' });
|
|
254
|
+
await requiredFunction('disposeMidiPort')(disconnected);
|
|
255
|
+
await expect(openMidiPort(disconnected)).resolves.toEqual({ reason: 'disposed' });
|
|
256
|
+
|
|
257
|
+
const failed = createInputPort({ open: vi.fn(async () => Promise.reject(new Error('open'))) });
|
|
258
|
+
await expect(openMidiPort(failed)).resolves.toEqual({ reason: 'operation-failed' });
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('sendMidiMessage', () => {
|
|
263
|
+
it('validates one basic message before the provider and never admits system exclusive data', async () => {
|
|
264
|
+
const send = vi.fn();
|
|
265
|
+
const connection = { value: 'closed' };
|
|
266
|
+
const output = createOutputPort({ connection, send });
|
|
267
|
+
const sendMidiMessage = requiredFunction('sendMidiMessage');
|
|
268
|
+
expect(sendMidiMessage(output, new Uint8Array())).toEqual({ reason: 'invalid-message' });
|
|
269
|
+
expect(sendMidiMessage(output, new Uint8Array([0x40]))).toEqual({ reason: 'invalid-message' });
|
|
270
|
+
expect(sendMidiMessage(output, new Uint8Array([0x90, 0x40]))).toEqual({ reason: 'invalid-message' });
|
|
271
|
+
expect(sendMidiMessage(output, new Uint8Array([0x90, 0x90, 0x7f]))).toEqual({ reason: 'invalid-message' });
|
|
272
|
+
expect(sendMidiMessage(output, new Uint8Array([0xf0, 0x01, 0xf7]))).toEqual({
|
|
273
|
+
reason: 'system-exclusive-not-enabled',
|
|
274
|
+
});
|
|
275
|
+
expect(sendMidiMessage(output, new Uint8Array([0x90, 0x40, 0x7f]))).toEqual({ reason: 'not-open' });
|
|
276
|
+
expect(send).not.toHaveBeenCalled();
|
|
277
|
+
|
|
278
|
+
connection.value = 'open';
|
|
279
|
+
expect(sendMidiMessage(output, new Uint8Array([0x90, 0x40, 0x7f]), 12.5)).toEqual({ reason: 'sent' });
|
|
280
|
+
expect(send).toHaveBeenCalledWith([0x90, 0x40, 0x7f], 12.5);
|
|
281
|
+
|
|
282
|
+
const disconnected = createOutputPort({ connection: { value: 'open' }, state: { value: 'disconnected' } });
|
|
283
|
+
expect(sendMidiMessage(disconnected, new Uint8Array([0x80, 0x40, 0]))).toEqual({ reason: 'disconnected' });
|
|
284
|
+
});
|
|
285
|
+
});
|
|
@@ -4,6 +4,11 @@ import { EntityRuntimeKey } from '@flighthq/types/contract';
|
|
|
4
4
|
import { describe, expect, it, vi } from 'vitest';
|
|
5
5
|
|
|
6
6
|
import * as midi from './contract';
|
|
7
|
+
import {
|
|
8
|
+
initializeMidiAccessStateSubscription,
|
|
9
|
+
initializeMidiInputMessageSubscription,
|
|
10
|
+
initializeMidiPortStateSubscription,
|
|
11
|
+
} from './midiSubscription';
|
|
7
12
|
|
|
8
13
|
describe('attachMidiAccessStateSubscription', () => {
|
|
9
14
|
it('pins the exact access origin and emits stable hotplug port entities', async () => {
|
|
@@ -332,3 +337,20 @@ function successfulAttachment(): object {
|
|
|
332
337
|
out.release = async () => ({ reason: 'ok' });
|
|
333
338
|
return finishEntity(out);
|
|
334
339
|
}
|
|
340
|
+
describe('initializeMidiAccessStateSubscription', () => {
|
|
341
|
+
it('is the construction initializer of createMidiAccessStateSubscription', () => {
|
|
342
|
+
expect(typeof initializeMidiAccessStateSubscription).toBe('function');
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
describe('initializeMidiInputMessageSubscription', () => {
|
|
347
|
+
it('is the construction initializer of createMidiInputMessageSubscription', () => {
|
|
348
|
+
expect(typeof initializeMidiInputMessageSubscription).toBe('function');
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe('initializeMidiPortStateSubscription', () => {
|
|
353
|
+
it('is the construction initializer of createMidiPortStateSubscription', () => {
|
|
354
|
+
expect(typeof initializeMidiPortStateSubscription).toBe('function');
|
|
355
|
+
});
|
|
356
|
+
});
|