feat: publish av-tools 0.1.0

This commit is contained in:
2026-07-24 14:58:03 +02:00
commit fcc537b024
254 changed files with 63270 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
import { describe, expect, it, vi } from 'vitest';
import {
aggregatePcmPeaks,
describeWaveform,
renderWaveformToCanvas,
} from '../../src/waveform';
describe('waveform canvas rendering', () => {
it('renders bounded columns and supplies a text alternative', () => {
const peaks = aggregatePcmPeaks(
{
container: 'raw',
data: new Int16Array([-32_768, 0, 16_384, 32_767]),
encoding: 's16le',
sampleRate: 2,
},
{ bucketCount: 4 }
);
const { canvas, context, attributes } = createCanvas(200, 80);
const rendered = renderWaveformToCanvas(canvas, peaks, {
devicePixelRatio: 2,
startBucket: 1,
endBucket: 4,
selection: { startFraction: 0.25, endFraction: 0.75 },
});
expect(canvas.width).toBe(400);
expect(canvas.height).toBe(160);
expect(rendered.startBucket).toBe(1);
expect(rendered.endBucket).toBe(4);
expect(rendered.accessibleLabel).toContain('Audio waveform');
expect(attributes.get('role')).toBe('img');
expect(attributes.get('aria-label')).toBe(rendered.accessibleLabel);
expect(context.moveTo).toHaveBeenCalled();
expect(context.lineTo).toHaveBeenCalled();
expect(context.fillRect).toHaveBeenCalledWith(50, 0, 100, 80);
});
it('describes empty waveforms and respects an explicit accessible label', () => {
const empty = aggregatePcmPeaks(
{
container: 'raw',
data: new Uint8Array(),
encoding: 's16le',
sampleRate: 8_000,
},
{ bucketCount: 1 }
);
const { canvas, attributes } = createCanvas(100, 40);
const result = renderWaveformToCanvas(canvas, empty, {
accessibleLabel: 'No audible content',
});
expect(describeWaveform(empty)).toBe('Empty audio waveform.');
expect(result.accessibleLabel).toBe('No audible content');
expect(attributes.get('aria-label')).toBe('No audible content');
});
it('rejects unsafe dimensions, ranges, and selections', () => {
const peaks = aggregatePcmPeaks(
{
container: 'raw',
data: new Int16Array([1, 2]),
encoding: 's16le',
sampleRate: 8_000,
},
{ bucketCount: 2 }
);
const { canvas } = createCanvas(100, 40);
expect(() =>
renderWaveformToCanvas(canvas, peaks, { width: 100_000 })
).toThrow(/safe limits/u);
expect(() =>
renderWaveformToCanvas(canvas, peaks, {
startBucket: 2,
endBucket: 1,
})
).toThrow(/bucket range/u);
expect(() =>
renderWaveformToCanvas(canvas, peaks, {
selection: { startFraction: -1, endFraction: 0.5 },
})
).toThrow(/selection/u);
});
});
function createCanvas(width: number, height: number) {
const attributes = new Map<string, string>();
const context = {
setTransform: vi.fn(),
clearRect: vi.fn(),
fillRect: vi.fn(),
beginPath: vi.fn(),
moveTo: vi.fn(),
lineTo: vi.fn(),
stroke: vi.fn(),
fillStyle: '',
strokeStyle: '',
lineWidth: 1,
};
const canvas = {
width,
height,
clientWidth: width,
clientHeight: height,
getContext: vi.fn(() => context),
setAttribute: vi.fn((name: string, value: string) => {
attributes.set(name, value);
}),
} as unknown as HTMLCanvasElement;
return { canvas, context, attributes };
}