247 lines
5.6 KiB
JavaScript
247 lines
5.6 KiB
JavaScript
/**
|
|
* utils.js 单元测试
|
|
* 覆盖所有工具函数
|
|
*/
|
|
|
|
import {
|
|
generateId,
|
|
escapeHTML,
|
|
prefersDark,
|
|
debounce,
|
|
throttle,
|
|
deepMerge,
|
|
isBrowser,
|
|
formatFileSize,
|
|
sleep,
|
|
} from '../src/utils.js';
|
|
|
|
describe('generateId', () => {
|
|
test('返回字符串', () => {
|
|
expect(typeof generateId()).toBe('string');
|
|
});
|
|
|
|
test('以 me- 前缀开头', () => {
|
|
expect(generateId()).toMatch(/^me-/);
|
|
});
|
|
|
|
test('多次调用产生不同值', () => {
|
|
const ids = new Set();
|
|
for (let i = 0; i < 100; i++) ids.add(generateId());
|
|
expect(ids.size).toBe(100);
|
|
});
|
|
});
|
|
|
|
describe('escapeHTML', () => {
|
|
test('转义 < > &', () => {
|
|
const out = escapeHTML('<a>&b</a>');
|
|
expect(out).toContain('<');
|
|
expect(out).toContain('>');
|
|
expect(out).toContain('&');
|
|
expect(out).not.toContain('<a>');
|
|
});
|
|
|
|
test('null/undefined 转空字符串', () => {
|
|
expect(escapeHTML(null)).toBe('');
|
|
expect(escapeHTML(undefined)).toBe('');
|
|
});
|
|
|
|
test('数字被转为字符串', () => {
|
|
expect(escapeHTML(123)).toBe('123');
|
|
});
|
|
|
|
test('无特殊字符原样返回', () => {
|
|
expect(escapeHTML('hello world')).toBe('hello world');
|
|
});
|
|
});
|
|
|
|
describe('prefersDark', () => {
|
|
test('返回布尔值', () => {
|
|
expect(typeof prefersDark()).toBe('boolean');
|
|
});
|
|
});
|
|
|
|
describe('debounce', () => {
|
|
test('延迟执行', (done) => {
|
|
let called = 0;
|
|
const fn = debounce(() => { called++; }, 50);
|
|
fn();
|
|
expect(called).toBe(0);
|
|
setTimeout(() => {
|
|
expect(called).toBe(1);
|
|
done();
|
|
}, 100);
|
|
});
|
|
|
|
test('多次调用只执行最后一次', (done) => {
|
|
let result = 0;
|
|
const fn = debounce((v) => { result = v; }, 50);
|
|
fn(1);
|
|
fn(2);
|
|
fn(3);
|
|
setTimeout(() => {
|
|
expect(result).toBe(3);
|
|
done();
|
|
}, 100);
|
|
});
|
|
|
|
test('immediate 模式立即执行', () => {
|
|
let called = 0;
|
|
const fn = debounce(() => { called++; }, 50, true);
|
|
fn();
|
|
expect(called).toBe(1);
|
|
});
|
|
|
|
test('保留 this 上下文', (done) => {
|
|
const obj = {
|
|
val: 42,
|
|
getVal: debounce(function () { return this.val; }, 30),
|
|
};
|
|
const p = obj.getVal();
|
|
// immediate=false 时首次不返回,但 this 应正确
|
|
setTimeout(() => {
|
|
expect(typeof p).toBe('undefined'); // debounce 返回的是 undefined(异步执行)
|
|
done();
|
|
}, 60);
|
|
});
|
|
|
|
test('传递参数', (done) => {
|
|
let captured = null;
|
|
const fn = debounce((a, b) => { captured = a + b; }, 30);
|
|
fn(1, 2);
|
|
setTimeout(() => {
|
|
expect(captured).toBe(3);
|
|
done();
|
|
}, 60);
|
|
});
|
|
});
|
|
|
|
describe('throttle', () => {
|
|
test('首次立即执行', () => {
|
|
let called = 0;
|
|
const fn = throttle(() => { called++; }, 50);
|
|
fn();
|
|
expect(called).toBe(1);
|
|
});
|
|
|
|
test('限流期内不重复执行', () => {
|
|
let called = 0;
|
|
const fn = throttle(() => { called++; }, 50);
|
|
fn();
|
|
fn();
|
|
fn();
|
|
expect(called).toBe(1);
|
|
});
|
|
|
|
test('限流期后可再次执行', (done) => {
|
|
let called = 0;
|
|
const fn = throttle(() => { called++; }, 40);
|
|
fn();
|
|
setTimeout(() => {
|
|
fn();
|
|
expect(called).toBe(2);
|
|
done();
|
|
}, 60);
|
|
});
|
|
|
|
test('保留 this 与参数', (done) => {
|
|
const obj = {
|
|
n: 10,
|
|
run: throttle(function (x) { this.result = this.n + x; }, 30),
|
|
};
|
|
obj.run(5);
|
|
expect(obj.result).toBe(15);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('deepMerge', () => {
|
|
test('浅层合并', () => {
|
|
const out = deepMerge({ a: 1, b: 2 }, { b: 3, c: 4 });
|
|
expect(out).toEqual({ a: 1, b: 3, c: 4 });
|
|
});
|
|
|
|
test('深层对象合并', () => {
|
|
const out = deepMerge({ obj: { x: 1, y: 2 } }, { obj: { y: 3, z: 4 } });
|
|
expect(out).toEqual({ obj: { x: 1, y: 3, z: 4 } });
|
|
});
|
|
|
|
test('source 覆盖 target 非对象值', () => {
|
|
const out = deepMerge({ a: { x: 1 } }, { a: 'str' });
|
|
expect(out.a).toBe('str');
|
|
});
|
|
|
|
test('不修改原始 target', () => {
|
|
const target = { a: { x: 1 } };
|
|
const source = { a: { y: 2 } };
|
|
deepMerge(target, source);
|
|
expect(target.a).toEqual({ x: 1 });
|
|
});
|
|
|
|
test('多层嵌套合并', () => {
|
|
const out = deepMerge(
|
|
{ a: { b: { c: 1, d: 2 } } },
|
|
{ a: { b: { d: 3, e: 4 } } }
|
|
);
|
|
expect(out).toEqual({ a: { b: { c: 1, d: 3, e: 4 } } });
|
|
});
|
|
});
|
|
|
|
describe('isBrowser', () => {
|
|
test('jsdom 环境下返回 true', () => {
|
|
expect(isBrowser()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('formatFileSize', () => {
|
|
test('0 返回 0 Bytes', () => {
|
|
expect(formatFileSize(0)).toBe('0 Bytes');
|
|
});
|
|
|
|
test('Bytes 档位', () => {
|
|
expect(formatFileSize(512)).toBe('512 Bytes');
|
|
});
|
|
|
|
test('KB 档位', () => {
|
|
expect(formatFileSize(1024)).toBe('1 KB');
|
|
expect(formatFileSize(1536)).toBe('1.5 KB');
|
|
});
|
|
|
|
test('MB 档位', () => {
|
|
expect(formatFileSize(1048576)).toBe('1 MB');
|
|
});
|
|
|
|
test('GB 档位', () => {
|
|
expect(formatFileSize(1073741824)).toBe('1 GB');
|
|
});
|
|
|
|
test('小数位数控制', () => {
|
|
expect(formatFileSize(1536, 0)).toBe('2 KB');
|
|
expect(formatFileSize(1536, 3)).toBe('1.5 KB');
|
|
});
|
|
|
|
test('负 decimals 当作 0', () => {
|
|
expect(formatFileSize(1536, -1)).toBe('2 KB');
|
|
});
|
|
});
|
|
|
|
describe('sleep', () => {
|
|
test('返回 Promise', () => {
|
|
expect(sleep(10)).toBeInstanceOf(Promise);
|
|
});
|
|
|
|
test('延迟后 resolve', (done) => {
|
|
const start = Date.now();
|
|
sleep(50).then(() => {
|
|
const elapsed = Date.now() - start;
|
|
expect(elapsed).toBeGreaterThanOrEqual(40);
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('async/await 用法', async () => {
|
|
const start = Date.now();
|
|
await sleep(30);
|
|
expect(Date.now() - start).toBeGreaterThanOrEqual(20);
|
|
});
|
|
});
|