111 lines
3.9 KiB
TypeScript
111 lines
3.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import type { Command, Device } from './api';
|
|
import { describeCommand, describeDevice, durationText, initial, relativeTime } from './format';
|
|
|
|
const now = new Date(2026, 8, 28, 15, 30, 0);
|
|
|
|
function device(overrides: Partial<Device> = {}): Device {
|
|
return {
|
|
device_id: 'dev-1',
|
|
device_name: 'PC',
|
|
app_version: '1.5.0',
|
|
online: true,
|
|
session_active: true,
|
|
last_seen_at: '',
|
|
last_seen_seconds: 3,
|
|
remote_capable: true,
|
|
blacklist_sync: true,
|
|
status: {
|
|
listening: true,
|
|
state: 'running',
|
|
state_label: '监听中',
|
|
transport: 'visual',
|
|
transport_label: '视觉通道',
|
|
wecom_connected: true,
|
|
pending_count: 0,
|
|
reported_at: '',
|
|
reported_seconds: 4,
|
|
fresh: true,
|
|
},
|
|
last_command: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('relativeTime', () => {
|
|
it('uses friendly buckets', () => {
|
|
expect(relativeTime(new Date(2026, 8, 28, 15, 29, 40).toISOString(), now)).toBe('刚刚');
|
|
expect(relativeTime(new Date(2026, 8, 28, 15, 10, 0).toISOString(), now)).toBe('20 分钟前');
|
|
expect(relativeTime(new Date(2026, 8, 28, 9, 5, 0).toISOString(), now)).toBe('今天 09:05');
|
|
expect(relativeTime(new Date(2026, 8, 27, 21, 0, 0).toISOString(), now)).toBe('昨天 21:00');
|
|
expect(relativeTime(new Date(2026, 1, 3, 8, 0, 0).toISOString(), now)).toBe('02-03 08:00');
|
|
expect(relativeTime(new Date(2025, 11, 31, 8, 0, 0).toISOString(), now)).toBe('2025-12-31');
|
|
});
|
|
|
|
it('accepts backend offsets and tolerates junk', () => {
|
|
expect(relativeTime('2026-09-28T15:29:30+08:00', new Date('2026-09-28T15:30:00+08:00'))).toBe('刚刚');
|
|
expect(relativeTime('', now)).toBe('—');
|
|
expect(relativeTime('not a date', now)).toBe('—');
|
|
});
|
|
});
|
|
|
|
describe('durationText', () => {
|
|
it('rounds to the largest sensible unit', () => {
|
|
expect(durationText(12)).toBe('12 秒');
|
|
expect(durationText(150)).toBe('3 分钟');
|
|
expect(durationText(7200)).toBe('2 小时');
|
|
expect(durationText(3 * 86400)).toBe('3 天');
|
|
expect(durationText(null)).toBe('—');
|
|
});
|
|
});
|
|
|
|
describe('describeDevice', () => {
|
|
it('prioritises offline, then outdated, then status', () => {
|
|
expect(describeDevice(device({ online: false, last_seen_seconds: 600 })).label).toBe('离线');
|
|
expect(describeDevice(device({ remote_capable: false })).label).toBe('版本过低');
|
|
expect(describeDevice(device()).tone).toBe('success');
|
|
const stopped = device();
|
|
stopped.status = { ...stopped.status!, listening: false, state: 'stopped', state_label: '已停止' };
|
|
expect(describeDevice(stopped)).toMatchObject({ tone: 'muted', label: '已停止' });
|
|
const failed = device();
|
|
failed.status = { ...failed.status!, state: 'error', state_label: '连接失败' };
|
|
expect(describeDevice(failed).tone).toBe('danger');
|
|
});
|
|
});
|
|
|
|
describe('describeCommand', () => {
|
|
const base: Command = {
|
|
id: 'c1',
|
|
type: 'stop_listening',
|
|
type_label: '停止监听',
|
|
device_id: 'dev-1',
|
|
status: 'pending',
|
|
status_label: '',
|
|
final: false,
|
|
created_at: '',
|
|
created_by: '',
|
|
delivered_at: '',
|
|
acked_at: '',
|
|
expires_at: 0,
|
|
result: {},
|
|
error: '',
|
|
};
|
|
|
|
it('describes progress and outcomes', () => {
|
|
expect(describeCommand(base)).toMatchObject({ busy: true, tone: 'primary' });
|
|
expect(describeCommand({ ...base, status: 'succeeded', result: { message: '监听已停止' } }).text).toBe('监听已停止');
|
|
expect(describeCommand({ ...base, status: 'failed', error: '企业微信未登录' }).text).toContain('企业微信未登录');
|
|
expect(describeCommand({ ...base, status: 'expired' }).tone).toBe('warning');
|
|
});
|
|
});
|
|
|
|
describe('initial', () => {
|
|
it('takes the first visible character', () => {
|
|
expect(initial('张三')).toBe('张');
|
|
expect(initial('alice')).toBe('A');
|
|
expect(initial(' ')).toBe('?');
|
|
expect(initial('😀客户')).toBe('😀');
|
|
});
|
|
});
|