3ad4bf2
commit 3ad4bf2
1 file changed
+246 -0
Showing 1 changed file+246-0
▼packages/subtitle/src/index.test.ts
+246-0
| @@ -0,0 +1,246 @@ | ||
1 | +import { describe, it, expect, vi } from 'vitest'; | |
2 | +import { SRTParser, VTTParser, ASSParser, SubtitleRenderer } from './index.js'; | |
3 | + | |
4 | +describe('SRTParser', () => { | |
5 | + it('should parse valid SRT content', () => { | |
6 | + const srt = `1 | |
7 | +00:00:01,000 --> 00:00:04,000 | |
8 | +Hello World | |
9 | + | |
10 | +2 | |
11 | +00:00:05,000 --> 00:00:08,000 | |
12 | +Second subtitle`; | |
13 | + | |
14 | + const packets = SRTParser.parse(srt); | |
15 | + expect(packets.length).toBe(2); | |
16 | + expect(packets[0].text).toBe('Hello World'); | |
17 | + expect(packets[0].startTime).toBe(1); | |
18 | + expect(packets[0].endTime).toBe(4); | |
19 | + expect(packets[1].text).toBe('Second subtitle'); | |
20 | + }); | |
21 | + | |
22 | + it('should handle empty SRT', () => { | |
23 | + const packets = SRTParser.parse(''); | |
24 | + expect(packets.length).toBe(0); | |
25 | + }); | |
26 | + | |
27 | + it('should handle malformed SRT', () => { | |
28 | + const srt = `Invalid SRT content`; | |
29 | + const packets = SRTParser.parse(srt); | |
30 | + expect(packets.length).toBe(0); | |
31 | + }); | |
32 | + | |
33 | + it('should parse timestamps correctly', () => { | |
34 | + const ts = SRTParser.parseTimestamp('01:30:45,500'); | |
35 | + expect(ts).toBe(5445.5); | |
36 | + }); | |
37 | +}); | |
38 | + | |
39 | +describe('VTTParser', () => { | |
40 | + it('should parse valid VTT content', () => { | |
41 | + const vtt = `WEBVTT | |
42 | + | |
43 | +1 | |
44 | +00:00:01.000 --> 00:00:04.000 | |
45 | +Hello World | |
46 | + | |
47 | +2 | |
48 | +00:00:05.000 --> 00:00:08.000 | |
49 | +Second subtitle`; | |
50 | + | |
51 | + const packets = VTTParser.parse(vtt); | |
52 | + expect(packets.length).toBe(2); | |
53 | + expect(packets[0].text).toBe('Hello World'); | |
54 | + }); | |
55 | + | |
56 | + it('should handle VTT with NOTE blocks', () => { | |
57 | + const vtt = `WEBVTT | |
58 | + | |
59 | +NOTE This is a comment | |
60 | + | |
61 | +1 | |
62 | +00:00:01.000 --> 00:00:04.000 | |
63 | +Hello World`; | |
64 | + | |
65 | + const packets = VTTParser.parse(vtt); | |
66 | + expect(packets.length).toBe(1); | |
67 | + }); | |
68 | + | |
69 | + it('should handle empty VTT', () => { | |
70 | + const packets = VTTParser.parse(''); | |
71 | + expect(packets.length).toBe(0); | |
72 | + }); | |
73 | +}); | |
74 | + | |
75 | +describe('ASSParser', () => { | |
76 | + it('should parse valid ASS content', () => { | |
77 | + const ass = `[Script Info] | |
78 | +Title: Test | |
79 | +PlayResX: 1920 | |
80 | +PlayResY: 1080 | |
81 | + | |
82 | +[V4+ Styles] | |
83 | +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding | |
84 | +Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H80000000,0,0,0,0,100,100,0,0,1,1,0,2,10,10,10,1 | |
85 | + | |
86 | +[Events] | |
87 | +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text | |
88 | +Dialogue: 0,0:00:01.00,0:00:04.00,Default,,0,0,0,,Hello World`; | |
89 | + | |
90 | + const packets = ASSParser.parse(ass); | |
91 | + expect(packets.length).toBe(1); | |
92 | + expect(packets[0].text).toBe('Hello World'); | |
93 | + expect(packets[0].startTime).toBe(1); | |
94 | + expect(packets[0].endTime).toBe(4); | |
95 | + }); | |
96 | + | |
97 | + it('should parse Script Info', () => { | |
98 | + const ass = `[Script Info] | |
99 | +PlayResX: 1920 | |
100 | +PlayResY: 1080`; | |
101 | + | |
102 | + const info = ASSParser.getASSInfo(ass); | |
103 | + expect(info.playResX).toBe(1920); | |
104 | + expect(info.playResY).toBe(1080); | |
105 | + }); | |
106 | + | |
107 | + it('should handle empty ASS', () => { | |
108 | + const packets = ASSParser.parse(''); | |
109 | + expect(packets.length).toBe(0); | |
110 | + }); | |
111 | + | |
112 | + it('should strip override tags', () => { | |
113 | + const ass = `[Events] | |
114 | +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text | |
115 | +Dialogue: 0,0:00:01.00,0:00:04.00,Default,,0,0,0,,{\\b1}Bold{\\b0} text`; | |
116 | + | |
117 | + const packets = ASSParser.parse(ass); | |
118 | + expect(packets.length).toBe(1); | |
119 | + expect(packets[0].text).toBe('Bold text'); | |
120 | + }); | |
121 | +}); | |
122 | + | |
123 | +describe('SubtitleRenderer', () => { | |
124 | + it('should initialize with canvas', () => { | |
125 | + const canvas = { | |
126 | + width: 1920, | |
127 | + height: 1080, | |
128 | + getContext: vi.fn(() => ({ | |
129 | + clearRect: vi.fn(), | |
130 | + fillText: vi.fn(), | |
131 | + strokeText: vi.fn(), | |
132 | + measureText: vi.fn(() => ({ width: 100 })), | |
133 | + fillStyle: '', | |
134 | + strokeStyle: '', | |
135 | + lineWidth: 1, | |
136 | + font: '', | |
137 | + textAlign: 'center', | |
138 | + textBaseline: 'bottom' | |
139 | + })), | |
140 | + style: {} | |
141 | + } as unknown as HTMLCanvasElement; | |
142 | + | |
143 | + const renderer = new SubtitleRenderer(canvas); | |
144 | + expect(renderer).toBeDefined(); | |
145 | + }); | |
146 | + | |
147 | + it('should render active subtitles', () => { | |
148 | + const canvasEl = { width: 1920, height: 1080 }; | |
149 | + const mockCtx = { | |
150 | + canvas: canvasEl, | |
151 | + clearRect: vi.fn(), | |
152 | + fillText: vi.fn(), | |
153 | + strokeText: vi.fn(), | |
154 | + measureText: vi.fn(() => ({ width: 100 })), | |
155 | + fillStyle: '', | |
156 | + strokeStyle: '', | |
157 | + lineWidth: 1, | |
158 | + font: '', | |
159 | + textAlign: 'center', | |
160 | + textBaseline: 'bottom' | |
161 | + }; | |
162 | + | |
163 | + const canvas = { | |
164 | + width: 1920, | |
165 | + height: 1080, | |
166 | + getContext: vi.fn(() => mockCtx), | |
167 | + style: {} | |
168 | + } as unknown as HTMLCanvasElement; | |
169 | + | |
170 | + const renderer = new SubtitleRenderer(canvas); | |
171 | + | |
172 | + const packets = [ | |
173 | + { startTime: 1, endTime: 4, text: 'Hello World' }, | |
174 | + { startTime: 5, endTime: 8, text: 'Second subtitle' } | |
175 | + ]; | |
176 | + | |
177 | + renderer.renderSubtitles(packets, 2); | |
178 | + expect(mockCtx.clearRect).toHaveBeenCalled(); | |
179 | + expect(mockCtx.fillText).toHaveBeenCalled(); | |
180 | + }); | |
181 | + | |
182 | + it('should not render inactive subtitles', () => { | |
183 | + const canvasEl = { width: 1920, height: 1080 }; | |
184 | + const mockCtx = { | |
185 | + canvas: canvasEl, | |
186 | + clearRect: vi.fn(), | |
187 | + fillText: vi.fn(), | |
188 | + strokeText: vi.fn(), | |
189 | + measureText: vi.fn(() => ({ width: 100 })), | |
190 | + fillStyle: '', | |
191 | + strokeStyle: '', | |
192 | + lineWidth: 1, | |
193 | + font: '', | |
194 | + textAlign: 'center', | |
195 | + textBaseline: 'bottom' | |
196 | + }; | |
197 | + | |
198 | + const canvas = { | |
199 | + width: 1920, | |
200 | + height: 1080, | |
201 | + getContext: vi.fn(() => mockCtx), | |
202 | + style: {} | |
203 | + } as unknown as HTMLCanvasElement; | |
204 | + | |
205 | + const renderer = new SubtitleRenderer(canvas); | |
206 | + | |
207 | + const packets = [ | |
208 | + { startTime: 1, endTime: 4, text: 'Hello World' } | |
209 | + ]; | |
210 | + | |
211 | + renderer.renderSubtitles(packets, 10); // Time outside subtitle range | |
212 | + expect(mockCtx.fillText).not.toHaveBeenCalled(); | |
213 | + }); | |
214 | + | |
215 | + it('should set custom style', () => { | |
216 | + const canvas = { | |
217 | + width: 1920, | |
218 | + height: 1080, | |
219 | + getContext: vi.fn(() => ({ | |
220 | + clearRect: vi.fn(), | |
221 | + fillText: vi.fn(), | |
222 | + strokeText: vi.fn(), | |
223 | + measureText: vi.fn(() => ({ width: 100 })), | |
224 | + fillStyle: '', | |
225 | + strokeStyle: '', | |
226 | + lineWidth: 1, | |
227 | + font: '', | |
228 | + textAlign: 'center', | |
229 | + textBaseline: 'bottom' | |
230 | + })), | |
231 | + style: {} | |
232 | + } as unknown as HTMLCanvasElement; | |
233 | + | |
234 | + const renderer = new SubtitleRenderer(canvas); | |
235 | + | |
236 | + renderer.setStyle({ | |
237 | + fontSize: 32, | |
238 | + fontFamily: 'Arial', | |
239 | + color: '#FF0000', | |
240 | + outlineColor: '#000000' | |
241 | + }); | |
242 | + | |
243 | + // Should not throw | |
244 | + expect(true).toBe(true); | |
245 | + }); | |
246 | +}); | |
0 | 247 | |