feat: publish av-tools 0.1.0
This commit is contained in:
216
tests/unit/components/waveform-editor.test.tsx
Normal file
216
tests/unit/components/waveform-editor.test.tsx
Normal file
@@ -0,0 +1,216 @@
|
||||
import {
|
||||
cleanup,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { WaveformEditor } from '../../../src/components/WaveformEditor';
|
||||
import { aggregatePcmPeaks } from '../../../src/waveform';
|
||||
|
||||
describe('WaveformEditor', () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(
|
||||
createCanvasContext()
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a bounded canvas with accessible trim and marker controls', async () => {
|
||||
render(
|
||||
<WaveformEditor
|
||||
peaks={createPeaks()}
|
||||
inSeconds={1}
|
||||
outSeconds={9}
|
||||
markers={[3]}
|
||||
currentTimeSeconds={5}
|
||||
onChange={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
const canvas = document.querySelector('canvas');
|
||||
if (!(canvas instanceof HTMLCanvasElement)) {
|
||||
throw new Error('Missing waveform canvas');
|
||||
}
|
||||
await waitFor(() =>
|
||||
expect(canvas).toHaveAttribute(
|
||||
'aria-label',
|
||||
expect.stringContaining('Selected 00:00:01.000–00:00:09.000')
|
||||
)
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('slider', { name: 'In trim handle' })
|
||||
).toHaveAttribute('aria-valuenow', '1');
|
||||
expect(
|
||||
screen.getByRole('slider', { name: 'Out trim handle' })
|
||||
).toHaveAttribute('aria-valuenow', '9');
|
||||
expect(
|
||||
screen.getByRole('slider', { name: 'Split marker 1' })
|
||||
).toHaveAttribute('aria-valuenow', '3');
|
||||
expect(screen.getByText('Single sequential audio track')).toBeVisible();
|
||||
});
|
||||
|
||||
it('emits validated keyboard edits and current-time actions', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
render(
|
||||
<WaveformEditor
|
||||
peaks={createPeaks()}
|
||||
inSeconds={1}
|
||||
outSeconds={9}
|
||||
markers={[3]}
|
||||
currentTimeSeconds={4}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.keyDown(screen.getByRole('slider', { name: 'In trim handle' }), {
|
||||
key: 'ArrowRight',
|
||||
});
|
||||
expect(onChange).toHaveBeenLastCalledWith({
|
||||
inSeconds: 1.1,
|
||||
outSeconds: 9,
|
||||
markers: [3],
|
||||
});
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'Add split at current time' })
|
||||
);
|
||||
expect(onChange).toHaveBeenLastCalledWith({
|
||||
inSeconds: 1,
|
||||
outSeconds: 9,
|
||||
markers: [3, 4],
|
||||
});
|
||||
|
||||
fireEvent.keyDown(screen.getByRole('slider', { name: 'Split marker 1' }), {
|
||||
key: 'Delete',
|
||||
});
|
||||
expect(onChange).toHaveBeenLastCalledWith({
|
||||
inSeconds: 1,
|
||||
outSeconds: 9,
|
||||
markers: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('drags split markers and seeks using the visible zoomed range', async () => {
|
||||
const onChange = vi.fn();
|
||||
const onSeek = vi.fn();
|
||||
const { container } = render(
|
||||
<WaveformEditor
|
||||
peaks={createPeaks()}
|
||||
inSeconds={0}
|
||||
outSeconds={10}
|
||||
markers={[2]}
|
||||
onChange={onChange}
|
||||
onSeek={onSeek}
|
||||
/>
|
||||
);
|
||||
const stage = container.querySelector('.waveform-editor__stage');
|
||||
if (!(stage instanceof HTMLElement)) {
|
||||
throw new Error('Missing waveform stage');
|
||||
}
|
||||
vi.spyOn(stage, 'getBoundingClientRect').mockReturnValue({
|
||||
x: 0,
|
||||
y: 0,
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 100,
|
||||
bottom: 168,
|
||||
width: 100,
|
||||
height: 168,
|
||||
toJSON: () => ({}),
|
||||
});
|
||||
const marker = screen.getByRole('slider', { name: 'Split marker 1' });
|
||||
|
||||
fireEvent.pointerDown(marker, {
|
||||
pointerId: 11,
|
||||
clientX: 20,
|
||||
clientY: 20,
|
||||
});
|
||||
fireEvent.pointerMove(marker, {
|
||||
pointerId: 11,
|
||||
clientX: 50,
|
||||
clientY: 20,
|
||||
});
|
||||
fireEvent.pointerUp(marker, { pointerId: 11 });
|
||||
expect(onChange).toHaveBeenLastCalledWith({
|
||||
inSeconds: 0,
|
||||
outSeconds: 10,
|
||||
markers: [5],
|
||||
});
|
||||
|
||||
fireEvent.pointerDown(stage, {
|
||||
button: 0,
|
||||
clientX: 75,
|
||||
clientY: 30,
|
||||
});
|
||||
expect(onSeek).toHaveBeenLastCalledWith(7.5);
|
||||
|
||||
fireEvent.change(screen.getByRole('slider', { name: 'Zoom waveform' }), {
|
||||
target: { value: '2' },
|
||||
});
|
||||
expect(
|
||||
screen.getByRole('slider', { name: 'Scroll waveform' })
|
||||
).toBeEnabled();
|
||||
});
|
||||
|
||||
it('normalizes untrusted selection values and caps marker count', () => {
|
||||
render(
|
||||
<WaveformEditor
|
||||
peaks={createPeaks()}
|
||||
inSeconds={Number.NaN}
|
||||
outSeconds={999}
|
||||
markers={[
|
||||
Number.NaN,
|
||||
-1,
|
||||
2,
|
||||
2.0004,
|
||||
...Array.from({ length: 510 }, (_, index) => 2.01 + index * 0.01),
|
||||
]}
|
||||
onChange={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText(/Selected 00:00:00.000–00:00:10.000/u)
|
||||
).toBeVisible();
|
||||
expect(screen.getByText('254/254')).toBeVisible();
|
||||
expect(
|
||||
screen.getAllByRole('slider', { name: /Split marker/u })
|
||||
).toHaveLength(254);
|
||||
});
|
||||
});
|
||||
|
||||
function createPeaks() {
|
||||
return aggregatePcmPeaks(
|
||||
{
|
||||
container: 'raw',
|
||||
data: new Int16Array(
|
||||
Array.from({ length: 100 }, (_, index) =>
|
||||
Math.round(Math.sin(index / 5) * 20_000)
|
||||
)
|
||||
),
|
||||
encoding: 's16le',
|
||||
sampleRate: 10,
|
||||
},
|
||||
{ bucketCount: 100 }
|
||||
);
|
||||
}
|
||||
|
||||
function createCanvasContext(): CanvasRenderingContext2D {
|
||||
return {
|
||||
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,
|
||||
} as unknown as CanvasRenderingContext2D;
|
||||
}
|
||||
Reference in New Issue
Block a user