Skip to content

Commit d20054f

Browse files
committed
test(pwa): M4 Task12 - PWA 分支单测(isDouyinUrl/resampleToMono/computeRms)
1 parent 2dc1e8b commit d20054f

2 files changed

Lines changed: 95 additions & 0 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* WebCaptureAdapter 纯函数测试(PWA 麦克风应急通道,M4 Task12 补测)
3+
*
4+
* @ai-context: 覆盖 resampleToMono(48k→16k 降采样 + 多声道混单)与
5+
* computeRms(能量计算)。MediaRecorder/getUserMedia 依赖真实浏览器,
6+
* jsdom 无法实例化,故仅测纯函数层。
7+
* @ai-context EN: unit tests for resampleToMono (48k→16k downsampling +
8+
* channel mixing) and computeRms. MediaRecorder/getUserMedia need a real
9+
* browser, so only pure functions are covered under jsdom.
10+
*/
11+
import { describe, it, expect } from 'vitest';
12+
import { resampleToMono, computeRms } from './WebCaptureAdapter';
13+
14+
/** 构造最小 AudioBuffer 假对象(仅覆盖 resampleToMono 用到的字段) */
15+
function fakeAudioBuffer(sampleRate: number, channelData: Float32Array[]): AudioBuffer {
16+
return {
17+
sampleRate,
18+
numberOfChannels: channelData.length,
19+
length: channelData[0]?.length ?? 0,
20+
duration: 0,
21+
getChannelData: (c: number) => channelData[c] ?? new Float32Array(0),
22+
} as unknown as AudioBuffer;
23+
}
24+
25+
describe('resampleToMono 重采样混单', () => {
26+
it('48kHz 单声道降采样到 16kHz,长度约为 1/3', () => {
27+
const src = new Float32Array(48000);
28+
src.fill(0.5);
29+
const out = resampleToMono(fakeAudioBuffer(48000, [src]), 16000);
30+
expect(out.length).toBe(Math.floor(48000 / 3));
31+
expect(out[0]).toBeCloseTo(0.5, 3);
32+
});
33+
34+
it('双声道取平均', () => {
35+
const left = new Float32Array(16000).fill(0.4);
36+
const right = new Float32Array(16000).fill(0.8);
37+
const out = resampleToMono(fakeAudioBuffer(16000, [left, right]), 16000);
38+
expect(out.length).toBe(16000);
39+
expect(out[0]).toBeCloseTo(0.6, 3);
40+
});
41+
42+
it('空缓冲返回空数组', () => {
43+
const out = resampleToMono(fakeAudioBuffer(48000, [new Float32Array(0)]), 16000);
44+
expect(out.length).toBe(0);
45+
});
46+
});
47+
48+
describe('computeRms 能量计算', () => {
49+
it('全零样本能量为 0', () => {
50+
expect(computeRms(new Float32Array(100))).toBe(0);
51+
});
52+
53+
it('满幅样本能量约为 1', () => {
54+
const pcm = new Float32Array(100).fill(1.0);
55+
expect(computeRms(pcm)).toBeCloseTo(1, 5);
56+
});
57+
58+
it('空数组能量为 0', () => {
59+
expect(computeRms(new Float32Array(0))).toBe(0);
60+
});
61+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* 视频上传分析工具纯函数测试(PWA 视频转笔记,M4 Task12 补测)
3+
*
4+
* @ai-context: 覆盖 isDouyinUrl 抖音链接检测(短链/长链/非抖音/非法 URL)。
5+
* analyzeVideoFile 依赖真实 fetch + supabase,jsdom 覆盖收益低,留真机验证。
6+
* @ai-context EN: unit tests for isDouyinUrl detection. analyzeVideoFile needs
7+
* real fetch + supabase, left for device verification.
8+
*/
9+
import { describe, it, expect } from 'vitest';
10+
import { isDouyinUrl } from './uploadVideoAnalysis';
11+
12+
describe('isDouyinUrl 抖音链接检测', () => {
13+
it('识别 v.douyin.com 短链', () => {
14+
expect(isDouyinUrl('https://v.douyin.com/AbCdEf/')).toBe(true);
15+
});
16+
17+
it('识别 www.douyin.com 长链', () => {
18+
expect(isDouyinUrl('https://www.douyin.com/video/123456789')).toBe(true);
19+
});
20+
21+
it('无协议前缀返回 false(URL 解析失败不抛错)', () => {
22+
expect(isDouyinUrl('v.douyin.com/xyz')).toBe(false);
23+
});
24+
25+
it('拒绝非抖音域名', () => {
26+
expect(isDouyinUrl('https://www.bilibili.com/video/123')).toBe(false);
27+
expect(isDouyinUrl('https://douyin.evil.com/')).toBe(false);
28+
});
29+
30+
it('拒绝非法 URL(不抛错)', () => {
31+
expect(isDouyinUrl('')).toBe(false);
32+
expect(isDouyinUrl('not a url')).toBe(false);
33+
});
34+
});

0 commit comments

Comments
 (0)