Skip to content

Commit 9981ffd

Browse files
committed
Add tests, fix some errors
1 parent a9fbe93 commit 9981ffd

16 files changed

Lines changed: 2006 additions & 57 deletions

.github/workflows/ci.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- '**'
7+
pull_request:
8+
9+
jobs:
10+
test:
11+
name: Test
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout repo
16+
uses: actions/checkout@v4
17+
18+
- name: Install pnpm
19+
uses: pnpm/action-setup@v3
20+
with:
21+
version: 10
22+
23+
- name: Setup Node.js
24+
uses: actions/setup-node@v4
25+
with:
26+
node-version: 20
27+
cache: 'pnpm'
28+
29+
- name: Install dependencies
30+
run: pnpm install --frozen-lockfile
31+
32+
- name: Lint
33+
run: pnpm run lint
34+
35+
- name: Test
36+
run: pnpm run test

.github/workflows/coverage.yml

Lines changed: 0 additions & 32 deletions
This file was deleted.

.github/workflows/publish.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ jobs:
3535
- name: Install dependencies
3636
run: pnpm install --frozen-lockfile
3737

38+
- name: Test
39+
run: pnpm run test
40+
3841
- name: Build project
3942
run: pnpm run build
4043

package.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
"devDependencies": {
3636
"@changesets/cli": "^2.30.0",
3737
"@eslint/js": "^10.0.1",
38-
"@types/lodash-es": "^4.17.12",
3938
"@types/node": "^25.5.0",
4039
"@vitest/coverage-v8": "^4.1.2",
4140
"c8": "^7.14.0",
@@ -50,8 +49,5 @@
5049
"vitepress": "^1.6.4",
5150
"vitest": "^4.1.2",
5251
"vue": "^3.5.31"
53-
},
54-
"dependencies": {
55-
"lodash-es": "^4.17.23"
5652
}
5753
}

src/helpers.test.ts

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { isHeader, isStyle, isNote, isCue, isRegion, isComment } from './helpers';
3+
4+
describe('isHeader', () => {
5+
it('matches bare WEBVTT', () => {
6+
expect(isHeader('WEBVTT')).toBe(true);
7+
});
8+
9+
it('matches WEBVTT with a description', () => {
10+
expect(isHeader('WEBVTT - English captions')).toBe(true);
11+
});
12+
13+
it('matches WEBVTT with metadata lines below', () => {
14+
expect(isHeader('WEBVTT\nKind: captions\nLanguage: en')).toBe(true);
15+
});
16+
17+
it('matches WEBVTT with UTF-8 BOM', () => {
18+
expect(isHeader('\uFEFFWEBVTT')).toBe(true);
19+
});
20+
21+
it('matches WEBVTT BOM with description', () => {
22+
expect(isHeader('\uFEFFWEBVTT Live captions')).toBe(true);
23+
});
24+
25+
it('does not match an empty string', () => {
26+
expect(isHeader('')).toBe(false);
27+
});
28+
29+
it('does not match a cue timing line', () => {
30+
expect(isHeader('00:00:01.000 --> 00:00:04.000')).toBe(false);
31+
});
32+
33+
it('does not match lowercase webvtt', () => {
34+
expect(isHeader('webvtt')).toBe(false);
35+
});
36+
37+
it('does not match a mid-string WEBVTT occurrence', () => {
38+
expect(isHeader('NOTE WEBVTT')).toBe(false);
39+
});
40+
41+
it('does not match WEBVTTx (7th char must be space/tab/LF or end)', () => {
42+
expect(isHeader('WEBVTTx rest of content')).toBe(false);
43+
});
44+
45+
it('matches WEBVTT followed by tab', () => {
46+
expect(isHeader('WEBVTT\tThis is a description')).toBe(true);
47+
});
48+
49+
it('matches WEBVTT followed by LF', () => {
50+
expect(isHeader('WEBVTT\nKind: captions')).toBe(true);
51+
});
52+
});
53+
54+
describe('isStyle', () => {
55+
it('matches STYLE', () => {
56+
expect(isStyle('STYLE')).toBe(true);
57+
});
58+
59+
it('matches STYLE with content', () => {
60+
expect(isStyle('STYLE\n::cue { color: white; }')).toBe(true);
61+
});
62+
63+
it('does not match lowercase style', () => {
64+
expect(isStyle('style')).toBe(false);
65+
});
66+
67+
it('does not match other segment types', () => {
68+
expect(isStyle('REGION')).toBe(false);
69+
});
70+
});
71+
72+
describe('isNote', () => {
73+
it('matches bare NOTE', () => {
74+
expect(isNote('NOTE')).toBe(true);
75+
});
76+
77+
it('matches NOTE with inline text', () => {
78+
expect(isNote('NOTE This is a comment')).toBe(true);
79+
});
80+
81+
it('matches NOTE with a block body', () => {
82+
expect(isNote('NOTE\nThis is a comment')).toBe(true);
83+
});
84+
85+
it('does not match lowercase note', () => {
86+
expect(isNote('note')).toBe(false);
87+
});
88+
});
89+
90+
describe('isCue', () => {
91+
it('matches a simple cue without identifier', () => {
92+
const cue = '00:00:01.000 --> 00:00:04.000\nHello world';
93+
expect(isCue(cue)).toBe(true);
94+
});
95+
96+
it('matches a cue with a string identifier', () => {
97+
const cue = 'intro\n00:00:00.000 --> 00:00:05.000\nWelcome';
98+
expect(isCue(cue)).toBe(true);
99+
});
100+
101+
it('matches a cue with a numeric identifier', () => {
102+
const cue = '1\n00:00:01.000 --> 00:00:04.000\nHello';
103+
expect(isCue(cue)).toBe(true);
104+
});
105+
106+
it('matches a cue with cue settings on the timing line', () => {
107+
const cue = '00:00:01.000 --> 00:00:04.000 align:center size:80%\nHello';
108+
expect(isCue(cue)).toBe(true);
109+
});
110+
111+
it('matches short mm:ss.mmm timestamps', () => {
112+
const cue = '01:23.456 --> 01:26.789\nShort form';
113+
expect(isCue(cue)).toBe(true);
114+
});
115+
116+
it('does not match a WEBVTT header', () => {
117+
expect(isCue('WEBVTT')).toBe(false);
118+
});
119+
120+
it('does not match a NOTE block', () => {
121+
expect(isCue('NOTE\nA comment')).toBe(false);
122+
});
123+
124+
it('does not match a REGION block', () => {
125+
expect(isCue('REGION\nid:fred')).toBe(false);
126+
});
127+
128+
it('does not match a STYLE block', () => {
129+
expect(isCue('STYLE\n::cue { color: white; }')).toBe(false);
130+
});
131+
});
132+
133+
describe('isRegion', () => {
134+
it('matches bare REGION', () => {
135+
expect(isRegion('REGION')).toBe(true);
136+
});
137+
138+
it('matches REGION with settings', () => {
139+
expect(isRegion('REGION\nid:fred\nwidth:40%')).toBe(true);
140+
});
141+
142+
it('does not match REGIONS (plural)', () => {
143+
expect(isRegion('REGIONS')).toBe(false);
144+
});
145+
146+
it('does not match lowercase region', () => {
147+
expect(isRegion('region')).toBe(false);
148+
});
149+
});
150+
151+
describe('isComment', () => {
152+
it('matches bare NOTE', () => {
153+
expect(isComment('NOTE')).toBe(true);
154+
});
155+
156+
it('matches NOTE with block body', () => {
157+
expect(isComment('NOTE\nThis is a comment')).toBe(true);
158+
});
159+
160+
it('does not match NOTE with inline text (uses isNote for that)', () => {
161+
// isComment checks ^NOTE(\n|$) so "NOTE text" should NOT match
162+
expect(isComment('NOTE This is inline')).toBe(false);
163+
});
164+
165+
it('does not match lowercase note', () => {
166+
expect(isComment('note')).toBe(false);
167+
});
168+
});

src/helpers.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const isHeader = (line: string) => {
2-
return /^WEBVTT/.test(line) || /^\uFEFFWEBVTT/.test(line);
2+
return /^(?:\uFEFF)?WEBVTT(?:[\t \n]|$)/.test(line);
33
};
44

55
export const isStyle = (line: string) => {
@@ -11,7 +11,9 @@ export const isNote = (line: string) => {
1111
};
1212

1313
export const isCue = (str: string) => {
14-
return /^(?:[^\n]+\n)?\d{2}:\d{2}:\d{2}\.\d{3}\s+-->\s+\d{2}:\d{2}:\d{2}\.\d{3}/.test(str);
14+
return /^(?:[^\n]+\n)?(?:\d+:)?\d{2}:\d{2}\.\d{3}\s+-->\s+(?:\d+:)?\d{2}:\d{2}\.\d{3}/.test(
15+
str,
16+
);
1517
};
1618

1719
export const isRegion = (str: string) => {

0 commit comments

Comments
 (0)