Skip to content

Commit 79f07f5

Browse files
authored
Merge pull request #17 from diffusionstudio/konstantin/testing/increase-test-coverage
Konstantin/testing/increase test coverage
2 parents 80f6a09 + a9ec63b commit 79f07f5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2926
-478
lines changed

package-lock.json

+273-416
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+14-14
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@diffusionstudio/core",
33
"private": false,
4-
"version": "1.0.0-rc.7",
4+
"version": "1.0.0-rc.8",
55
"type": "module",
66
"description": "Build bleeding edge video processing applications",
77
"files": [
@@ -27,26 +27,26 @@
2727
"docs": "typedoc src/index.ts --plugin typedoc-plugin-markdown --out ./docs"
2828
},
2929
"devDependencies": {
30-
"@biomejs/biome": "1.8.3",
30+
"@biomejs/biome": "1.9.2",
3131
"@types/dom-webcodecs": "^0.1.11",
32-
"@types/node": "^22.5.2",
32+
"@types/node": "^22.5.5",
3333
"@types/wicg-file-system-access": "^2023.10.5",
34-
"@vitest/coverage-v8": "^2.0.5",
35-
"@vitest/web-worker": "^2.0.5",
36-
"@webgpu/types": "^0.1.44",
37-
"jsdom": "^25.0.0",
34+
"@vitest/coverage-v8": "^2.1.1",
35+
"@vitest/web-worker": "^2.1.1",
36+
"@webgpu/types": "^0.1.46",
37+
"jsdom": "^25.0.1",
3838
"rollup-plugin-node-externals": "^7.1.3",
39-
"typedoc": "^0.26.6",
40-
"typedoc-plugin-markdown": "^4.2.6",
41-
"typescript": "^5.5.4",
39+
"typedoc": "^0.26.7",
40+
"typedoc-plugin-markdown": "^4.2.8",
41+
"typescript": "^5.6.2",
4242
"user-agent-data-types": "^0.4.2",
43-
"vite": "^5.4.2",
44-
"vite-plugin-dts": "^4.1.0",
45-
"vitest": "^2.0.5",
43+
"vite": "^5.4.7",
44+
"vite-plugin-dts": "^4.2.1",
45+
"vitest": "^2.1.1",
4646
"vitest-canvas-mock": "^0.3.3"
4747
},
4848
"dependencies": {
49-
"mp4-muxer": "^5.1.1"
49+
"mp4-muxer": "^5.1.3"
5050
},
5151
"peerDependencies": {
5252
"pixi-filters": ">=6.0.0",
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { ClipDeserializer } from './clip.desierializer';
3+
import {
4+
AudioClip,
5+
VideoClip,
6+
HtmlClip,
7+
ImageClip,
8+
TextClip,
9+
ComplexTextClip,
10+
Clip
11+
} from '..';
12+
import { AudioSource, HtmlSource, ImageSource, VideoSource } from '../../sources';
13+
import type { Source } from '../../sources';
14+
15+
describe('ClipDeserializer', () => {
16+
it('should return correct clip based on type', () => {
17+
expect(ClipDeserializer.fromType({ type: 'video' })).toBeInstanceOf(VideoClip);
18+
expect(ClipDeserializer.fromType({ type: 'audio' })).toBeInstanceOf(AudioClip);
19+
expect(ClipDeserializer.fromType({ type: 'html' })).toBeInstanceOf(HtmlClip);
20+
expect(ClipDeserializer.fromType({ type: 'image' })).toBeInstanceOf(ImageClip);
21+
expect(ClipDeserializer.fromType({ type: 'text' })).toBeInstanceOf(TextClip);
22+
expect(ClipDeserializer.fromType({ type: 'complex_text' })).toBeInstanceOf(ComplexTextClip);
23+
expect(ClipDeserializer.fromType({ type: 'unknown' as any })).toBeInstanceOf(Clip); // Default case
24+
});
25+
26+
it('should return correct clip based on source', () => {
27+
// Mock instances for different source types
28+
const audioSource = new AudioSource();
29+
const videoSource = new VideoSource();
30+
const imageSource = new ImageSource();
31+
const htmlSource = new HtmlSource();
32+
33+
const res = ClipDeserializer.fromSource(audioSource)
34+
35+
// Ensure proper class instantiation based on source type
36+
expect(res).toBeInstanceOf(AudioClip);
37+
expect(ClipDeserializer.fromSource(videoSource)).toBeInstanceOf(VideoClip);
38+
expect(ClipDeserializer.fromSource(imageSource)).toBeInstanceOf(ImageClip);
39+
expect(ClipDeserializer.fromSource(htmlSource)).toBeInstanceOf(HtmlClip);
40+
});
41+
42+
it('should return undefined if source type does not match', () => {
43+
const invalidSourceMock = { type: 'unknown' } as any as Source;
44+
expect(ClipDeserializer.fromSource(invalidSourceMock)).toBeUndefined();
45+
});
46+
});

src/clips/video/buffer.spec.ts

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Copyright (c) 2024 The Diffusion Studio Authors
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla
5+
* Public License, v. 2.0 that can be found in the LICENSE file.
6+
*/
7+
8+
import { describe, it, expect, vi, beforeEach } from 'vitest';
9+
import { FrameBuffer } from './buffer';
10+
11+
describe('FrameBuffer', () => {
12+
let frameBuffer: FrameBuffer;
13+
let mockVideoFrame: any;
14+
15+
beforeEach(() => {
16+
// Mock VideoFrame
17+
mockVideoFrame = {
18+
close: vi.fn(),
19+
};
20+
21+
frameBuffer = new FrameBuffer();
22+
});
23+
24+
it('should enqueue frames and trigger onenqueue callback', () => {
25+
const mockOnEnqueue = vi.fn();
26+
frameBuffer.onenqueue = mockOnEnqueue;
27+
28+
frameBuffer.enqueue(mockVideoFrame);
29+
30+
expect(frameBuffer['buffer'].length).toBe(1);
31+
expect(frameBuffer['buffer'][0]).toBe(mockVideoFrame);
32+
expect(mockOnEnqueue).toHaveBeenCalled();
33+
});
34+
35+
it('should dequeue frames in FIFO order', async () => {
36+
const frame1 = { ...mockVideoFrame };
37+
const frame2 = { ...mockVideoFrame };
38+
39+
frameBuffer.enqueue(frame1);
40+
frameBuffer.enqueue(frame2);
41+
42+
const dequeuedFrame1 = await frameBuffer.dequeue();
43+
const dequeuedFrame2 = await frameBuffer.dequeue();
44+
45+
expect(dequeuedFrame1).toBe(frame1);
46+
expect(dequeuedFrame2).toBe(frame2);
47+
expect(frameBuffer['buffer'].length).toBe(0);
48+
});
49+
50+
it('should wait for a frame to be enqueued if buffer is empty and state is active', async () => {
51+
const mockOnEnqueue = vi.fn();
52+
const mockWaitFor = vi.spyOn(frameBuffer as any, 'waitFor');
53+
54+
frameBuffer.onenqueue = mockOnEnqueue;
55+
const dequeuePromise = frameBuffer.dequeue();
56+
57+
// Simulate enqueuing a frame after some delay
58+
setTimeout(() => {
59+
frameBuffer.enqueue(mockVideoFrame);
60+
}, 100);
61+
62+
const result = await dequeuePromise;
63+
64+
expect(result).toBe(mockVideoFrame);
65+
expect(mockWaitFor).toHaveBeenCalledWith(20000); // 20s timeout
66+
});
67+
68+
it('should resolve immediately if buffer is closed and empty', async () => {
69+
frameBuffer.close();
70+
71+
const result = await frameBuffer.dequeue();
72+
expect(result).toBeUndefined();
73+
});
74+
75+
it('should call onclose callback when buffer is closed', () => {
76+
const mockOnClose = vi.fn();
77+
frameBuffer.onclose = mockOnClose;
78+
79+
frameBuffer.close();
80+
81+
expect(frameBuffer['state']).toBe('closed');
82+
expect(mockOnClose).toHaveBeenCalled();
83+
});
84+
85+
it('should close all frames when terminate is called', () => {
86+
const frame1 = { ...mockVideoFrame, close: vi.fn() };
87+
const frame2 = { ...mockVideoFrame, close: vi.fn() };
88+
89+
frameBuffer.enqueue(frame1);
90+
frameBuffer.enqueue(frame2);
91+
92+
frameBuffer.terminate();
93+
94+
expect(frame1.close).toHaveBeenCalled();
95+
expect(frame2.close).toHaveBeenCalled();
96+
});
97+
98+
it('should reject after timeout if no enqueue or close happens', async () => {
99+
await expect((frameBuffer as any).waitFor(50)).rejects.toThrow('Promise timed out after 50 ms');
100+
});
101+
});

src/clips/video/decoder.spec.ts

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* Copyright (c) 2024 The Diffusion Studio Authors
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla
5+
* Public License, v. 2.0 that can be found in the LICENSE file.
6+
*/
7+
8+
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
9+
import { Decoder } from './decoder';
10+
11+
describe('Decoder', () => {
12+
let mockPostMessage: Mock<any>
13+
let mockVideoDecoder: Mock<any>
14+
let mockVideoFrame: any;
15+
let decoder: Decoder;
16+
17+
beforeEach(() => {
18+
// Mock postMessage for 'self'
19+
mockPostMessage = vi.fn();
20+
(global as any).self = {
21+
postMessage: mockPostMessage,
22+
close: vi.fn(),
23+
};
24+
25+
// Mock VideoDecoder
26+
mockVideoDecoder = vi.fn().mockImplementation(({ output, error }) => {
27+
return {
28+
output,
29+
error,
30+
decode: vi.fn(),
31+
close: vi.fn(),
32+
};
33+
});
34+
(global as any).VideoDecoder = mockVideoDecoder;
35+
36+
// Mock VideoFrame
37+
mockVideoFrame = {
38+
timestamp: 0,
39+
duration: 1000000, // 1 second in nanoseconds
40+
close: vi.fn(),
41+
};
42+
});
43+
44+
it('should initialize with correct properties', () => {
45+
const range = [0, 5] satisfies [number, number]; // 5 seconds range
46+
const fps = 30;
47+
48+
decoder = new Decoder(range, fps);
49+
50+
expect(decoder.video).toBeDefined();
51+
expect(mockVideoDecoder).toHaveBeenCalled();
52+
expect(decoder['currentTime']).toBe(range[0] * 1e6);
53+
expect(decoder['firstTimestamp']).toBe(range[0] * 1e6);
54+
expect(decoder['totalFrames']).toBe(((range[1] - range[0]) * fps) + 1);
55+
expect(decoder['fps']).toBe(fps);
56+
});
57+
58+
it('should post a frame and update current time and count', () => {
59+
const range = [0, 5] satisfies [number, number];
60+
const fps = 30;
61+
62+
decoder = new Decoder(range, fps);
63+
64+
decoder['postFrame'](mockVideoFrame);
65+
66+
expect(mockPostMessage).toHaveBeenCalledWith({ type: 'frame', frame: mockVideoFrame });
67+
expect(decoder['currentTime']).toBeGreaterThan(range[0] * 1e6); // Time should increase
68+
expect(decoder['currentFrames']).toBe(1);
69+
});
70+
71+
it('should handle frame output within range and post frames', () => {
72+
const range = [0, 5] satisfies [number, number];
73+
const fps = 30;
74+
75+
decoder = new Decoder(range, fps);
76+
mockVideoFrame.timestamp = range[0] * 1e6; // Start time
77+
78+
decoder['handleFrameOutput'](mockVideoFrame);
79+
80+
expect(mockPostMessage).toHaveBeenCalledWith({ type: 'frame', frame: mockVideoFrame });
81+
expect(mockVideoFrame.close).toHaveBeenCalled();
82+
});
83+
84+
it('should handle errors and post error messages', () => {
85+
const range = [0, 5] satisfies [number, number];
86+
const fps = 30;
87+
const mockError = new DOMException('Test Error');
88+
89+
decoder = new Decoder(range, fps);
90+
91+
decoder['handleError'](mockError);
92+
93+
expect(mockPostMessage).toHaveBeenCalledWith({
94+
type: 'error',
95+
message: 'Test Error',
96+
});
97+
expect(self.close).toHaveBeenCalled();
98+
});
99+
});

src/clips/video/demuxer/ffmpeg.worker.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* c8 ignore start */
12
import type { WebAVPacket, WebAVStream } from './types';
23

34
let Module: any; // TODO: rm any

src/clips/video/demuxer/types/avutil.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* c8 ignore start */
12
/**
23
* sync with ffmpeg libavutil/avutil.h
34
*/

src/clips/video/demuxer/types/demuxer.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* c8 ignore start */
12
/**
23
* sync with web-demuxer.h
34
*/

src/clips/video/demuxer/types/ffmpeg-worker-message.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* c8 ignore start */
12
import type { AVMediaType } from './avutil';
23

34
export enum FFMpegWorkerMessageType {

src/clips/video/demuxer/web-demuxer.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* c8 ignore start */
12
import { AVMediaType, FFMpegWorkerMessageType } from './types';
23
import FFmpegWorker from './ffmpeg.worker.ts?worker&inline';
34

0 commit comments

Comments
 (0)