Files
WechatExplorer/tests/unit/group-exit-event-message.test.ts
T
Wxw-Gu 766cd94788 feat: 新增群员统计与退群事件,并把本地索引集中到设置页
- 群员统计:档案中按群统计发言排行与未发言成员,可复制纯文本、按平台能力发送
- 退群事件永久保存,并以本地推断条目并入档案消息流
- 设置新增本地索引页,集中管理聊天记录索引与图片文字索引
2026-09-20 21:46:37 +08:00

121 lines
4.0 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
GROUP_EXIT_EVENT_MESSAGE_TYPE,
isGroupExitEventMessage,
mergeGroupExitEvents,
toGroupExitEventMessage
} from '../../src/shared/group-exit-event-message'
import type { GroupExitMonitorEvent } from '../../src/shared/group-exit-monitor'
import type { Message } from '../../src/shared/types'
const at = (year: number, month: number, day: number, hour = 0, minute = 0): number =>
new Date(year, month - 1, day, hour, minute).getTime()
const makeEvent = (overrides: Partial<GroupExitMonitorEvent> = {}): GroupExitMonitorEvent => ({
id: 'evt-1',
contactId: 'g'.repeat(32),
roomId: 'room@chatroom',
groupName: '研发群',
memberWxid: 'wxid_a',
memberName: '老张',
previousCount: 10,
currentCount: 9,
delta: -1,
message: '老张退出了研发群',
detectedAt: at(2026, 9, 20, 14, 30),
...overrides
})
const makeMessage = (id: string, createTimeSec: number): Message => ({
id,
from: 'user',
type: '文本',
datetime: '2026-09-20 14:00',
content: 'hello',
isSender: false,
createTime: createTimeSec
})
describe('toGroupExitEventMessage', () => {
it('把事件映射成系统分支的消息,并带专用 type 标记', () => {
const message = toGroupExitEventMessage(makeEvent())
expect(message.id).toBe('group-exit:evt-1')
expect(message.from).toBe('system')
expect(message.type).toBe(GROUP_EXIT_EVENT_MESSAGE_TYPE)
expect(message.content).toBe('老张退出了研发群')
expect(message.isSender).toBe(false)
expect(message.senderId).toBe('wxid_a')
expect(message.sessionId).toBe('room@chatroom')
expect(isGroupExitEventMessage(message)).toBe(true)
})
it('⚠️ createTime 必须从毫秒换算成秒(全项目 Message.createTime 都是秒)', () => {
const detectedAt = at(2026, 9, 20, 14, 30)
const message = toGroupExitEventMessage(makeEvent({ detectedAt }))
// 直接塞毫秒会让事件被排到几万年之后,或者从时间轴上消失。
expect(message.createTime).toBe(Math.floor(detectedAt / 1000))
expect(message.createTime).toBeLessThan(1e11)
})
it('真实微信系统消息不会被误判为退群事件', () => {
const systemMessage: Message = {
id: 'm1',
from: 'system',
type: '系统消息',
datetime: '2026-09-20 14:00',
content: '某某加入了群聊',
isSender: false
}
expect(isGroupExitEventMessage(systemMessage)).toBe(false)
})
})
describe('mergeGroupExitEvents', () => {
it('按时间升序并入,事件落在正确位置', () => {
const messages = [
makeMessage('m1', Math.floor(at(2026, 9, 20, 10) / 1000)),
makeMessage('m2', Math.floor(at(2026, 9, 20, 16) / 1000))
]
const events = [makeEvent({ detectedAt: at(2026, 9, 20, 14, 30) })]
const merged = mergeGroupExitEvents(messages, events)
expect(merged.map((message) => message.id)).toEqual(['m1', 'group-exit:evt-1', 'm2'])
})
it('重复合并是幂等的(广播会反复刷新,不能越插越多)', () => {
const messages = [makeMessage('m1', Math.floor(at(2026, 9, 20, 10) / 1000))]
const events = [makeEvent()]
const once = mergeGroupExitEvents(messages, events)
const twice = mergeGroupExitEvents(once, events)
expect(twice).toHaveLength(2)
expect(twice.map((message) => message.id)).toEqual(once.map((message) => message.id))
})
it('没有事件时原样返回同一个引用(避免无谓的重渲染)', () => {
const messages = [makeMessage('m1', 1_700_000_000)]
expect(mergeGroupExitEvents(messages, [])).toBe(messages)
})
it('多个事件各自成组、保持时间顺序', () => {
const messages = [makeMessage('m1', Math.floor(at(2026, 9, 20, 8) / 1000))]
const events = [
makeEvent({ id: 'e1', detectedAt: at(2026, 9, 20, 12) }),
makeEvent({ id: 'e2', detectedAt: at(2026, 9, 20, 15) })
]
const merged = mergeGroupExitEvents(messages, events)
expect(merged.map((message) => message.id)).toEqual([
'm1',
'group-exit:e1',
'group-exit:e2'
])
})
})