- DataBuffer: 离线持久化缓冲(分桶存储 + 去重 + 容量管理) - GenericBleAdapter: 基于 Bluetooth SIG 标准 Health Profile 的通用适配器 (Heart Rate 0x180D / Health Thermometer 0x1809 / Blood Pressure 0x1810) - DataSyncScheduler: 定时自动同步调度(基于时间间隔判断是否需要同步) - BLEManager: 集成 DataBuffer 替换简单 Storage 缓存 - device-sync 页面: 注册 CustomBandAdapter + 自动同步 + 状态显示 - 新增 vitest 单元测试配置,30 个测试全部通过
90 lines
2.8 KiB
TypeScript
90 lines
2.8 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { DataBuffer } from '@/services/ble/DataBuffer';
|
|
import type { NormalizedReading } from '@/services/ble/types';
|
|
|
|
// Mock Taro Storage
|
|
const storage = new Map<string, string>();
|
|
vi.mock('@tarojs/taro', () => ({
|
|
default: {
|
|
getStorageSync: vi.fn((key: string) => storage.get(key) || ''),
|
|
setStorageSync: vi.fn((key: string, value: string) => { storage.set(key, value); }),
|
|
removeStorageSync: vi.fn((key: string) => { storage.delete(key); }),
|
|
getStorageInfoSync: vi.fn(() => ({ keys: Array.from(storage.keys()), limitSize: 10240, currentSize: storage.size })),
|
|
},
|
|
}));
|
|
|
|
function makeReading(overrides: Partial<NormalizedReading> = {}): NormalizedReading {
|
|
return {
|
|
device_type: 'heart_rate',
|
|
values: { heart_rate: 72 },
|
|
measured_at: new Date().toISOString(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('DataBuffer', () => {
|
|
let buffer: DataBuffer;
|
|
|
|
beforeEach(() => {
|
|
storage.clear();
|
|
buffer = new DataBuffer({ bucketSize: 100 });
|
|
});
|
|
|
|
it('push 添加读数并持久化', () => {
|
|
const reading = makeReading();
|
|
buffer.push(reading);
|
|
expect(buffer.size()).toBe(1);
|
|
});
|
|
|
|
it('push 批量添加读数', () => {
|
|
const readings = Array.from({ length: 10 }, (_, i) =>
|
|
makeReading({ measured_at: new Date(Date.now() + i * 1000).toISOString() }),
|
|
);
|
|
buffer.push(readings);
|
|
expect(buffer.size()).toBe(10);
|
|
});
|
|
|
|
it('flush 返回并清空缓冲区', () => {
|
|
buffer.push([
|
|
makeReading({ measured_at: '2026-05-04T10:00:00.000Z' }),
|
|
makeReading({ measured_at: '2026-05-04T10:00:01.000Z' }),
|
|
]);
|
|
const flushed = buffer.flush();
|
|
expect(flushed.length).toBe(2);
|
|
expect(buffer.size()).toBe(0);
|
|
});
|
|
|
|
it('超过 maxTotal 时丢弃最旧数据', () => {
|
|
const smallBuffer = new DataBuffer({ bucketSize: 5, maxTotal: 10 });
|
|
for (let i = 0; i < 15; i++) {
|
|
smallBuffer.push(makeReading({ measured_at: new Date(i * 1000).toISOString() }));
|
|
}
|
|
expect(smallBuffer.size()).toBe(10);
|
|
});
|
|
|
|
it('去重:相同 measured_at + device_type 不重复存储', () => {
|
|
const ts = '2026-05-04T10:00:00.000Z';
|
|
buffer.push(makeReading({ measured_at: ts }));
|
|
buffer.push(makeReading({ measured_at: ts }));
|
|
expect(buffer.size()).toBe(1);
|
|
});
|
|
|
|
it('restore 从 Storage 恢复未上传数据', () => {
|
|
buffer.push([
|
|
makeReading({ measured_at: '2026-05-04T10:00:00.000Z' }),
|
|
makeReading({ measured_at: '2026-05-04T10:00:01.000Z' }),
|
|
]);
|
|
// 模拟重启:新建 DataBuffer 并 restore
|
|
const restored = new DataBuffer({ bucketSize: 100 });
|
|
const count = restored.restore();
|
|
expect(count).toBe(2);
|
|
expect(restored.size()).toBe(2);
|
|
});
|
|
|
|
it('clear 清空缓冲区和 Storage', () => {
|
|
buffer.push(makeReading());
|
|
buffer.clear();
|
|
expect(buffer.size()).toBe(0);
|
|
});
|
|
});
|