mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-03 18:33:14 +08:00
- 群员统计:档案中按群统计发言排行与未发言成员,可复制纯文本、按平台能力发送 - 退群事件永久保存,并以本地推断条目并入档案消息流 - 设置新增本地索引页,集中管理聊天记录索引与图片文字索引
173 lines
5.8 KiB
TypeScript
173 lines
5.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import {
|
||
formatActiveMemberStats,
|
||
formatSilentMemberStats,
|
||
formatStatsDate,
|
||
formatStatsDateTime,
|
||
resolveGroupStatsRangeStart,
|
||
type GroupMemberStatsResult
|
||
} from '../../src/shared/group-stats'
|
||
|
||
/** 用本地时区构造时间,避免测试受运行机器时区影响。 */
|
||
const at = (year: number, month: number, day: number, hour = 0): number =>
|
||
new Date(year, month - 1, day, hour).getTime()
|
||
|
||
const base = (overrides: Partial<GroupMemberStatsResult> = {}): GroupMemberStatsResult => ({
|
||
conversationId: 'c'.repeat(32),
|
||
startTime: at(2026, 6, 15),
|
||
endTime: at(2026, 9, 4),
|
||
freshness: 'fresh',
|
||
complete: true,
|
||
memberCount: 48,
|
||
activeMemberCount: 45,
|
||
silentMemberCount: 3,
|
||
activeMembers: [],
|
||
silentMembers: [],
|
||
unattributedMessages: 0,
|
||
excludedSystemMessages: 0,
|
||
firstMessageTime: at(2026, 6, 15),
|
||
limitations: [],
|
||
...overrides
|
||
})
|
||
|
||
describe('GroupStats formatter', () => {
|
||
it('未发言模板按编号列出,并带区间与人数前缀', () => {
|
||
const text = formatSilentMemberStats(
|
||
base({
|
||
silentMembers: [
|
||
{ senderId: 'wxid_1', displayName: '轩轩', groupNickname: '' },
|
||
{ senderId: 'wxid_2', displayName: '十二', groupNickname: '' },
|
||
{ senderId: 'wxid_3', displayName: '湘江中路', groupNickname: '' }
|
||
]
|
||
})
|
||
)
|
||
|
||
expect(text).toContain('未发言统计')
|
||
expect(text).toContain('2026-06-15 至 2026-09-04')
|
||
expect(text).toContain('1. 轩轩')
|
||
expect(text).toContain('2. 十二')
|
||
expect(text).toContain('3. 湘江中路')
|
||
// 纯文本:不能出现 Markdown 标记
|
||
expect(text).not.toContain('**')
|
||
expect(text).not.toContain('##')
|
||
})
|
||
|
||
it('未发言模板保留 limitation 文案', () => {
|
||
const text = formatSilentMemberStats(
|
||
base({
|
||
silentMembers: [{ senderId: 'wxid_1', displayName: '轩轩', groupNickname: '' }],
|
||
limitations: ['统计基于当前群成员名单与本地已归档消息。']
|
||
})
|
||
)
|
||
expect(text).toContain('注:统计基于当前群成员名单与本地已归档消息。')
|
||
})
|
||
|
||
it('未发言为空时给出明确文案而不是空白', () => {
|
||
const text = formatSilentMemberStats(base())
|
||
expect(text).toContain('(无)')
|
||
})
|
||
|
||
it('未发言模板支持 limit 并说明省略人数', () => {
|
||
const silentMembers = Array.from({ length: 5 }, (_, index) => ({
|
||
senderId: `wxid_${index}`,
|
||
displayName: `成员${index}`,
|
||
groupNickname: ''
|
||
}))
|
||
const text = formatSilentMemberStats(base({ silentMembers }), { limit: 2 })
|
||
expect(text).toContain('1. 成员0')
|
||
expect(text).toContain('2. 成员1')
|
||
expect(text).not.toContain('3. 成员2')
|
||
expect(text).toContain('其余 3 人未列出')
|
||
})
|
||
|
||
it('活跃模板带条数', () => {
|
||
const text = formatActiveMemberStats(
|
||
base({
|
||
activeMembers: [
|
||
{
|
||
senderId: 'wxid_1',
|
||
displayName: '张三',
|
||
groupNickname: '',
|
||
messageCount: 328,
|
||
lastMessageTime: at(2026, 9, 20, 12)
|
||
},
|
||
{
|
||
senderId: 'wxid_2',
|
||
displayName: '李四',
|
||
groupNickname: '',
|
||
messageCount: 217,
|
||
lastMessageTime: at(2026, 9, 19, 12)
|
||
}
|
||
]
|
||
})
|
||
)
|
||
|
||
expect(text).toContain('群聊活跃统计')
|
||
expect(text).toContain('统计时间:2026-06-15 至 2026-09-04')
|
||
expect(text).toContain('1. 张三 328 条')
|
||
expect(text).toContain('2. 李四 217 条')
|
||
})
|
||
|
||
it('群昵称与主名不同时显示成「微信名(群昵称)」', () => {
|
||
const text = formatSilentMemberStats(
|
||
base({
|
||
silentMembers: [
|
||
{ senderId: 'wxid_1', displayName: '张三', groupNickname: '老张' },
|
||
// 两者相同时不重复展示,避免噪声
|
||
{ senderId: 'wxid_2', displayName: '李四', groupNickname: '李四' },
|
||
// 没有群昵称时只显示主名
|
||
{ senderId: 'wxid_3', displayName: '王五', groupNickname: '' }
|
||
]
|
||
})
|
||
)
|
||
|
||
expect(text).toContain('1. 张三(老张)')
|
||
expect(text).toContain('2. 李四')
|
||
expect(text).not.toContain('李四(李四)')
|
||
expect(text).toContain('3. 王五')
|
||
})
|
||
|
||
it('日期格式化使用本地时区且补零', () => {
|
||
expect(formatStatsDate(at(2026, 1, 5))).toBe('2026-01-05')
|
||
expect(formatStatsDateTime(at(2026, 1, 5, 9))).toBe('2026-01-05 09:00')
|
||
})
|
||
|
||
it('快捷范围按滑动窗口折算起点', () => {
|
||
const end = at(2026, 9, 20)
|
||
expect(resolveGroupStatsRangeStart('7d', end)).toBe(end - 7 * 24 * 60 * 60 * 1000)
|
||
expect(resolveGroupStatsRangeStart('30d', end)).toBe(end - 30 * 24 * 60 * 60 * 1000)
|
||
expect(resolveGroupStatsRangeStart('90d', end)).toBe(end - 90 * 24 * 60 * 60 * 1000)
|
||
})
|
||
|
||
it('「全部」不放下界(startTime = 0)', () => {
|
||
expect(resolveGroupStatsRangeStart('all', at(2026, 9, 20))).toBe(0)
|
||
})
|
||
|
||
it('选「全部」时区间文案用本机第一条消息的真实日期,而不是 1970-01-01', () => {
|
||
const text = formatSilentMemberStats(
|
||
base({
|
||
startTime: 0,
|
||
firstMessageTime: at(2020, 2, 2),
|
||
endTime: at(2026, 9, 20),
|
||
silentMembers: [{ senderId: 'wxid_1', displayName: '轩轩', groupNickname: '' }]
|
||
})
|
||
)
|
||
expect(text).toContain('2020-02-02 至 2026-09-20')
|
||
expect(text).not.toContain('1970')
|
||
expect(text).not.toContain('全部历史')
|
||
})
|
||
|
||
it('选「全部」但取不到第一条消息时,退化成「全部历史」且不假装从 1970 开始', () => {
|
||
const text = formatSilentMemberStats(
|
||
base({
|
||
startTime: 0,
|
||
firstMessageTime: null,
|
||
endTime: at(2026, 9, 20),
|
||
silentMembers: [{ senderId: 'wxid_1', displayName: '轩轩', groupNickname: '' }]
|
||
})
|
||
)
|
||
expect(text).toContain('全部历史')
|
||
expect(text).not.toContain('1970')
|
||
})
|
||
})
|