Skip to content

Commit

Permalink
🧪 [Tests] New approach to mocking AudioContext
Browse files Browse the repository at this point in the history
  • Loading branch information
beefchimi committed Feb 13, 2023
1 parent 5065ae7 commit 70ae277
Show file tree
Hide file tree
Showing 13 changed files with 528 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/tests/mock/MockAudioBuffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {createErrorMessage} from './mock-utils';

function internalMessage(methodName: string, ...args: unknown[]) {
return createErrorMessage('AudioBuffer', methodName, ...args);
}

export class MockAudioBuffer implements AudioBuffer {
readonly duration = 0;

readonly length: number;
readonly numberOfChannels: number;
readonly sampleRate: number;

// channelCountMode
// channelCountMode
// channelInterpretation

constructor(options: AudioBufferOptions) {
this.length = options.length;
this.numberOfChannels = options.numberOfChannels ?? 2;
this.sampleRate = options.sampleRate;
}

copyFromChannel(
destination: Float32Array,
channelNumber: number,
bufferOffset?: number | undefined,
): void {
throw new Error(
internalMessage(
'copyFromChannel',
destination,
channelNumber,
bufferOffset,
),
);
}

copyToChannel(
source: Float32Array,
channelNumber: number,
bufferOffset?: number | undefined,
): void {
throw new Error(
internalMessage('copyToChannel', source, channelNumber, bufferOffset),
);
}

getChannelData(channel: number): Float32Array {
throw new Error(internalMessage('getChannelData', channel));
}
}
31 changes: 31 additions & 0 deletions src/tests/mock/MockAudioBufferSourceNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {MockAudioScheduledSourceNode} from './MockAudioScheduledSourceNode';
import {MockAudioParam} from './MockAudioParam';

export class MockAudioBufferSourceNode
extends MockAudioScheduledSourceNode
implements AudioBufferSourceNode
{
buffer: AudioBuffer | null;
loop: boolean;
loopEnd: number;
loopStart: number;

readonly detune: AudioParam;
readonly playbackRate: AudioParam;

constructor(
readonly context: BaseAudioContext,
options?: AudioBufferSourceOptions,
) {
super();

this.buffer = options?.buffer ?? null;
this.loop = options?.loop ?? false;
this.loopEnd = options?.loopEnd ?? 0;
this.loopStart = options?.loopStart ?? 0;

// Not sure if `option` values need to be passed.
this.detune = new MockAudioParam();
this.playbackRate = new MockAudioParam();
}
}
53 changes: 53 additions & 0 deletions src/tests/mock/MockAudioContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {createErrorMessage} from './mock-utils';
import {MockBaseAudioContext} from './MockBaseAudioContext';

function internalMessage(methodName: string, ...args: unknown[]) {
return createErrorMessage('AudioContext', methodName, ...args);
}

export class MockAudioContext
extends MockBaseAudioContext
implements AudioContext
{
latencyHint: AudioContextOptions['latencyHint'];
readonly baseLatency = 0;
readonly outputLatency = 0.0123;

constructor(options?: AudioContextOptions) {
super();
this.latencyHint = options?.latencyHint ?? 'interactive';
this.sampleRate = options?.sampleRate ?? 44100;
}

createMediaElementSource(
mediaElement: HTMLMediaElement,
): MediaElementAudioSourceNode {
throw new Error(internalMessage('createMediaElementSource', mediaElement));
}

createMediaStreamDestination(): MediaStreamAudioDestinationNode {
throw new Error(internalMessage('createMediaStreamDestination'));
}

createMediaStreamSource(
mediaStream: MediaStream,
): MediaStreamAudioSourceNode {
throw new Error(internalMessage('createMediaStreamSource', mediaStream));
}

getOutputTimestamp(): AudioTimestamp {
throw new Error(internalMessage('getOutputTimestamp'));
}

async close(): Promise<void> {
throw new Error(internalMessage('close'));
}

async resume(): Promise<void> {
throw new Error(internalMessage('resume'));
}

async suspend(): Promise<void> {
throw new Error(internalMessage('suspend'));
}
}
8 changes: 8 additions & 0 deletions src/tests/mock/MockAudioDestinationNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {MockAudioNode} from './MockAudioNode';

export class MockAudioDestinationNode
extends MockAudioNode
implements AudioDestinationNode
{
readonly maxChannelCount = 2;
}
35 changes: 35 additions & 0 deletions src/tests/mock/MockAudioListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {createErrorMessage} from './mock-utils';
import {MockAudioParam} from './MockAudioParam';

function internalMessage(methodName: string, ...args: unknown[]) {
return createErrorMessage('AudioListener', methodName, ...args);
}

const mockParam = new MockAudioParam();

export class MockAudioListener implements AudioListener {
readonly forwardX = mockParam;
readonly forwardY = mockParam;
readonly forwardZ = mockParam;
readonly positionX = mockParam;
readonly positionY = mockParam;
readonly positionZ = mockParam;
readonly upX = mockParam;
readonly upY = mockParam;
readonly upZ = mockParam;

setOrientation(
x: number,
y: number,
z: number,
xUp: number,
yUp: number,
zUp: number,
): void {
throw new Error(internalMessage('setOrientation', x, y, z, xUp, yUp, zUp));
}

setPosition(x: number, y: number, z: number): void {
throw new Error(internalMessage('setOrientation', x, y, z));
}
}
58 changes: 58 additions & 0 deletions src/tests/mock/MockAudioNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {createErrorMessage} from './mock-utils';

function internalMessage(methodName: string, ...args: unknown[]) {
return createErrorMessage('AudioNode', methodName, ...args);
}

// We cannot import `MockBaseAudioContext` and instantiate it.
// Doing so would create a circular dependency that creates
// a recursive call between `MockAudioNode` and `MockBaseAudioContext`.
const typecastContext = {};

export class MockAudioNode extends EventTarget implements AudioNode {
channelCount = 2;
channelCountMode: ChannelCountMode = 'explicit';
channelInterpretation: ChannelInterpretation = 'speakers';

readonly context: BaseAudioContext = typecastContext as BaseAudioContext;
readonly numberOfInputs: number;
readonly numberOfOutputs: number;

constructor() {
super();
this.numberOfInputs = 1;
this.numberOfOutputs = 0;
}

connect(
destinationNode: AudioNode,
output?: number | undefined,
input?: number | undefined,
): AudioNode;
connect(destinationParam: AudioParam, output?: number | undefined): void;
connect(
destinationNode: unknown,
output?: unknown,
input?: unknown,
): AudioNode {
// eslint-disable-next-line no-console
console.log(internalMessage('connect', destinationNode, output, input));
return this;
}

disconnect(): void;
disconnect(output: number): void;
disconnect(destinationNode: AudioNode): void;
disconnect(destinationNode: AudioNode, output: number): void;
disconnect(destinationNode: AudioNode, output: number, input: number): void;
disconnect(destinationParam: AudioParam): void;
disconnect(destinationParam: AudioParam, output: number): void;
disconnect(
destinationNode?: unknown,
output?: unknown,
input?: unknown,
): void {
// eslint-disable-next-line no-console
console.log(internalMessage('disconnect', destinationNode, output, input));
}
}
56 changes: 56 additions & 0 deletions src/tests/mock/MockAudioParam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {createErrorMessage} from './mock-utils';

function internalMessage(methodName: string, ...args: unknown[]) {
return createErrorMessage('AudioParam', methodName, ...args);
}

export class MockAudioParam implements AudioParam {
automationRate: AutomationRate = 'a-rate';
value = 1;

readonly defaultValue = 1;
readonly maxValue = 2;
readonly minValue = -2;

cancelAndHoldAtTime(cancelTime: number): AudioParam {
throw new Error(internalMessage('cancelAndHoldAtTime', cancelTime));
}

cancelScheduledValues(cancelTime: number): AudioParam {
throw new Error(internalMessage('cancelScheduledValues', cancelTime));
}

exponentialRampToValueAtTime(value: number, endTime: number): AudioParam {
throw new Error(
internalMessage('exponentialRampToValueAtTime', value, endTime),
);
}

linearRampToValueAtTime(value: number, endTime: number): AudioParam {
throw new Error(internalMessage('linearRampToValueAtTime', value, endTime));
}

setTargetAtTime(
target: number,
startTime: number,
timeConstant: number,
): AudioParam {
throw new Error(
internalMessage('setTargetAtTime', target, startTime, timeConstant),
);
}

setValueAtTime(value: number, startTime: number): AudioParam {
throw new Error(internalMessage('setValueAtTime', value, startTime));
}

setValueCurveAtTime(
values: number[] | Float32Array,
startTime: number,
duration: number,
): AudioParam {
throw new Error(
internalMessage('setValueCurveAtTime', ...values, startTime, duration),
);
}
}
23 changes: 23 additions & 0 deletions src/tests/mock/MockAudioScheduledSourceNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {createErrorMessage} from './mock-utils';
import {MockAudioNode} from './MockAudioNode';

function internalMessage(methodName: string, ...args: unknown[]) {
return createErrorMessage('AudioScheduledSourceNode', methodName, ...args);
}

export class MockAudioScheduledSourceNode
extends MockAudioNode
implements AudioScheduledSourceNode
{
onended: AudioScheduledSourceNode['onended'] = null;

start(when?: number | undefined): void {
// eslint-disable-next-line no-console
console.log(internalMessage('start', when));
}

stop(when?: number | undefined): void {
// eslint-disable-next-line no-console
console.log(internalMessage('stop', when));
}
}
3 changes: 3 additions & 0 deletions src/tests/mock/MockAudioWorklet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {MockWorklet} from './MockWorklet';

export class MockAudioWorklet extends MockWorklet implements AudioWorklet {}
Loading

0 comments on commit 70ae277

Please sign in to comment.