Skip to content

Commit ebc8a70

Browse files
committed
added source unit tests
1 parent 6980b81 commit ebc8a70

File tree

8 files changed

+379
-2
lines changed

8 files changed

+379
-2
lines changed

src/sources/audio.spec.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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, vi, beforeEach, expect } from 'vitest';
9+
import { AudioSource } from './audio'; // Import the AudioSource class
10+
11+
// Mocking the OfflineAudioContext class
12+
class MockOfflineAudioContext {
13+
constructor(public numberOfChannels: number, public length: number, public sampleRate: number) { }
14+
15+
decodeAudioData(arrayBuffer: ArrayBuffer): Promise<AudioBuffer> {
16+
const audioBuffer = {
17+
duration: 5, // Mock duration
18+
sampleRate: this.sampleRate,
19+
getChannelData: () => new Float32Array(5000), // Return a dummy Float32Array
20+
} as any as AudioBuffer;
21+
return Promise.resolve(audioBuffer);
22+
}
23+
}
24+
25+
vi.stubGlobal('OfflineAudioContext', MockOfflineAudioContext); // Stub the global OfflineAudioContext
26+
27+
describe('AudioSource', () => {
28+
let audioSource: AudioSource;
29+
30+
beforeEach(() => {
31+
audioSource = new AudioSource();
32+
audioSource.file = new File([], 'audio.mp3', { type: 'audio/mp3' });
33+
});
34+
35+
it('should decode an audio buffer correctly', async () => {
36+
const buffer = await audioSource.decode(2, 44100);
37+
expect(buffer.duration).toBe(5); // Mock duration
38+
expect(buffer.sampleRate).toBe(44100);
39+
expect(audioSource.audioBuffer).toBe(buffer);
40+
expect(audioSource.duration.seconds).toBe(5); // Ensure duration is set
41+
});
42+
43+
it('should create a thumbnail with correct DOM elements', async () => {
44+
const thumbnail = await audioSource.thumbnail(60, 50, 0);
45+
46+
// Check if the thumbnail is a div
47+
expect(thumbnail.tagName).toBe('DIV');
48+
expect(thumbnail.className).toContain('audio-samples');
49+
50+
// Check if it has the right number of children
51+
expect(thumbnail.children.length).toBe(60);
52+
53+
// Check if each child has the correct class
54+
for (const child of thumbnail.children) {
55+
expect(child.className).toContain('audio-sample-item');
56+
}
57+
});
58+
});

src/sources/html.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import { describe, expect, it, vi } from 'vitest';
99
import { HtmlSource } from './html';
10+
import { setFetchMockReturnValue } from '../../vitest.mocks';
11+
import { sleep } from '../utils';
1012

1113
describe('The Html Source Object', () => {
1214
it('should create an object url when the iframe loads', async () => {
@@ -43,6 +45,52 @@ describe('The Html Source Object', () => {
4345
await expect(() => source.from(file)).rejects.toThrowError();
4446
expect(evtMock).toHaveBeenCalledTimes(1);
4547
});
48+
49+
it('should have a valid document getter', async () => {
50+
const source = new HtmlSource();
51+
52+
expect(source.document).toBeTruthy();
53+
});
54+
55+
it('should create an object url after the fetch has been completed', async () => {
56+
const resetFetch = setFetchMockReturnValue({
57+
ok: true,
58+
blob: async () => {
59+
await sleep(10);
60+
return new Blob([], { type: 'text/html' });
61+
},
62+
});
63+
64+
const source = new HtmlSource();
65+
66+
mockIframeValid(source);
67+
68+
source.from('https://external.html');
69+
70+
expect(source.objectURL).toBeUndefined();
71+
72+
const url = await source.createObjectURL();
73+
74+
expect(url).toBe("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E");
75+
76+
expect(source.objectURL).toBeDefined();
77+
78+
resetFetch();
79+
});
80+
81+
it('should retrun a video as thumbnail', async () => {
82+
const file = new File([], 'test.html', { type: 'text/html' });
83+
const source = new HtmlSource();
84+
85+
mockIframeValid(source);
86+
mockDocumentValid(source);
87+
88+
await source.from(file);
89+
90+
const thumbnail = await source.thumbnail();
91+
92+
expect(thumbnail).toBeInstanceOf(Image);
93+
});
4694
});
4795

4896
function mockIframeValid(source: HtmlSource) {

src/sources/html.utils.spec.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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, expect, it, vi } from 'vitest';
9+
import { documentToSvgImageUrl, fontToBas64Url } from './html.utils';
10+
import { setFetchMockReturnValue } from '../../vitest.mocks';
11+
12+
describe('documentToSvgImageUrl', () => {
13+
it('should return empty SVG if document is not provided', () => {
14+
const result = documentToSvgImageUrl();
15+
expect(result).toBe("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E");
16+
});
17+
18+
it('should return empty SVG if document has no body', () => {
19+
const mockDocument = document.implementation.createDocument('', '', null);
20+
const result = documentToSvgImageUrl(mockDocument);
21+
expect(result).toBe("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E");
22+
});
23+
24+
it('should return valid SVG when document has body and style', () => {
25+
const mockDocument = document.implementation.createHTMLDocument('Test Document');
26+
const body = mockDocument.body;
27+
const style = mockDocument.createElement('style');
28+
style.textContent = 'body { background: red; }';
29+
mockDocument.head.appendChild(style);
30+
body.innerHTML = '<div>Hello World</div>';
31+
32+
const result = documentToSvgImageUrl(mockDocument);
33+
34+
// Check if result starts with valid data URI and contains parts of the body and style
35+
expect(result).toContain('data:image/svg+xml;base64,');
36+
const decodedSvg = atob(result.split(',')[1]);
37+
expect(decodedSvg).toContain('Hello World');
38+
expect(decodedSvg).toContain('body { background: red; }');
39+
});
40+
41+
it('should return valid SVG when document has body but no style', () => {
42+
const mockDocument = document.implementation.createHTMLDocument('Test Document');
43+
const body = mockDocument.body;
44+
body.innerHTML = '<div>Hello World</div>';
45+
46+
const result = documentToSvgImageUrl(mockDocument);
47+
48+
// Check if result starts with valid data URI and contains parts of the body
49+
expect(result).toContain('data:image/svg+xml;base64,');
50+
const decodedSvg = atob(result.split(',')[1]);
51+
expect(decodedSvg).toContain('Hello World');
52+
expect(decodedSvg).not.toContain('<style>'); // There should be no style element
53+
});
54+
55+
it('should handle documents with nested elements', () => {
56+
const mockDocument = document.implementation.createHTMLDocument('Test Document');
57+
const body = mockDocument.body;
58+
body.innerHTML = '<div><p>Nested</p></div>';
59+
60+
const result = documentToSvgImageUrl(mockDocument);
61+
62+
// Check if result starts with valid data URI and contains nested elements
63+
expect(result).toContain('data:image/svg+xml;base64,');
64+
const decodedSvg = atob(result.split(',')[1]);
65+
expect(decodedSvg).toContain('<div><p>Nested</p></div>');
66+
});
67+
});
68+
69+
describe('fontToBas64Url', () => {
70+
it('should return base64 font URL with format woff2 by default', async () => {
71+
// Mock the response from fetch to return a Blob
72+
const resetFetch = setFetchMockReturnValue({
73+
ok: true,
74+
blob: async () => new Blob(['font-data'], { type: 'font/woff2' }),
75+
});
76+
77+
// Mock FileReader and its behavior
78+
vi.stubGlobal('FileReader', class {
79+
readAsDataURL = vi.fn();
80+
set onloadend(fn: () => void) {
81+
fn();
82+
}
83+
result = 'data:font/woff2;base64,Zm9udC1kYXRh';
84+
});
85+
86+
const result = await fontToBas64Url('https://example.com/font.woff2');
87+
88+
expect(result).toBe(`url(data:font/woff2;base64,Zm9udC1kYXRh) format('woff2')`);
89+
90+
resetFetch()
91+
vi.unstubAllGlobals(); // Cleanup
92+
});
93+
94+
it('should return base64 font URL with format ttf', async () => {
95+
// Mock the response from fetch to return a Blob
96+
const resetFetch = setFetchMockReturnValue({
97+
ok: true,
98+
blob: async () => new Blob(['font-data'], { type: 'font/ttf' }),
99+
});
100+
101+
// Mock FileReader and its behavior
102+
vi.stubGlobal('FileReader', class {
103+
readAsDataURL = vi.fn();
104+
set onloadend(fn: () => void) {
105+
fn();
106+
}
107+
result = 'data:font/ttf;base64,Zm9udC1kYXRh';
108+
});
109+
110+
const result = await fontToBas64Url('https://example.com/font.ttf');
111+
112+
expect(result).toBe(`url(data:font/ttf;base64,Zm9udC1kYXRh) format('truetype')`);
113+
114+
resetFetch()
115+
vi.unstubAllGlobals(); // Cleanup
116+
});
117+
});

src/sources/html.utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export function utf8ToBase64(str: string) {
1919
}
2020

2121
export function documentToSvgImageUrl(doc?: Document) {
22-
if (!doc) return emptySvg;
22+
if (!doc || !doc.body) return emptySvg;
23+
2324
const width = doc.body.scrollWidth;
2425
const height = doc.body.scrollHeight;
2526

src/sources/image.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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, expect, it } from 'vitest';
9+
import { ImageSource } from './image';
10+
11+
describe('The Image Source Object', () => {
12+
it('should retrun a video as thumbnail', async () => {
13+
const file = new File([], 'file.png', { type: 'image/png' });
14+
const source = new ImageSource();
15+
16+
await source.from(file);
17+
18+
const thumbnail = await source.thumbnail();
19+
20+
expect(thumbnail).toBeInstanceOf(Image);
21+
});
22+
});

src/sources/source.spec.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { setFetchMockReturnValue } from '../../vitest.mocks';
99
import { describe, expect, it, vi } from 'vitest';
1010
import { Source } from './source';
11+
import { sleep } from '../utils';
1112

1213
describe('The Source Object', () => {
1314
it('should be createable from a file', async () => {
@@ -63,6 +64,14 @@ describe('The Source Object', () => {
6364
resetFetch();
6465
});
6566

67+
it('should not load from a http address when the response in not ok', async () => {
68+
const resetFetch = setFetchMockReturnValue({ ok: false });
69+
70+
await expect(() => new Source().from('https://external.url')).rejects.toThrowError();
71+
72+
resetFetch();
73+
});
74+
6675
it('should get a file after the asset has been fetched', async () => {
6776
const file = new File([], 'file.mp4', { type: 'video/mp4' });
6877

@@ -73,11 +82,47 @@ describe('The Source Object', () => {
7382
expect((await source.getFile()).name).toBe('file.mp4');
7483
});
7584

85+
it('should extract the array buffer or the file', async () => {
86+
const file = new File([], 'file.mp4', { type: 'video/mp4' });
87+
88+
const source = new Source();
89+
await source.from(file);
90+
91+
const buffer = await source.arrayBuffer();
92+
93+
expect(buffer).toBeInstanceOf(ArrayBuffer);
94+
});
95+
96+
it('should download the file', async () => {
97+
const a = document.createElement('a');
98+
99+
const clickSpy = vi.spyOn(a, 'click');
100+
const createSpy = vi.spyOn(document, 'createElement').mockImplementation(() => a);
101+
102+
const file = new File([], 'file.mp4', { type: 'video/mp4' });
103+
104+
const source = new Source();
105+
await source.from(file);
106+
await source.download();
107+
108+
expect(clickSpy).toHaveBeenCalledTimes(1);
109+
expect(createSpy).toHaveBeenCalledTimes(1);
110+
expect(a.download).toBe('file.mp4');
111+
createSpy.mockRestore();
112+
});
113+
76114
it('should not get the file if no asset has been added yet', async () => {
77115
const source = new Source();
78116
await expect(() => source.getFile()).rejects.toThrowError();
79117
});
80118

119+
it('should return an html element as thumbnail', async () => {
120+
const source = new Source();
121+
const el = await source.thumbnail();
122+
123+
expect(el).toBeInstanceOf(HTMLDivElement);
124+
});
125+
81126
it('should create a object url', async () => {
82127
const file = new File([], 'file.mp4', { type: 'video/mp4' });
83128

@@ -105,4 +150,34 @@ describe('The Source Object', () => {
105150
expect(source.file).toBeUndefined();
106151
expect(source.objectURL).toBeUndefined();
107152
});
153+
154+
it('should get a file after the fetch has been completed', async () => {
155+
const resetFetch = setFetchMockReturnValue({
156+
ok: true,
157+
blob: async () => {
158+
await sleep(10);
159+
return new Blob([], { type: 'video/mp4' });
160+
},
161+
});
162+
163+
const source = new Source();
164+
165+
source.from('https://external.url');
166+
167+
expect(source.file).toBeUndefined();
168+
169+
const file = await source.getFile();
170+
171+
expect(file).toBeInstanceOf(File);
172+
173+
resetFetch();
174+
});
175+
176+
it('should initialize using the static method', async () => {
177+
const file = new File([], 'file.mp4', { type: 'video/mp4' });
178+
const source = await Source.from(file);
179+
180+
expect(source).toBeInstanceOf(Source);
181+
expect(source.name).toBe('file.mp4');
182+
});
108183
});

src/sources/source.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export class Source extends EventEmitterMixin<Events, typeof Serializer>(Seriali
190190
* as an html element
191191
*/
192192
public async thumbnail(): Promise<HTMLElement> {
193-
return new HTMLElement();
193+
return document.createElement('div');
194194
}
195195

196196
/**

0 commit comments

Comments
 (0)