mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-04 03:01:42 +08:00
feat: 增加退群监控功能
This commit is contained in:
+50
-1
@@ -117,7 +117,7 @@ test('KEY-03 changing one key does not invalidate archive data or unrelated sett
|
||||
|
||||
test('NAV-01 NAV-02 every top-level page is unique and switchable', async () => {
|
||||
const fixture = await launchTestApp()
|
||||
const labels = ['档案', '问问微信', '日报', 'Agent', '导出', 'API', '设置']
|
||||
const labels = ['档案', '问问微信', '日报', '退群监控', 'Agent', '导出', 'API', '设置']
|
||||
try {
|
||||
const navigation = fixture.page.getByRole('navigation', { name: '一级导航' })
|
||||
await expect(navigation).toBeVisible()
|
||||
@@ -131,6 +131,55 @@ test('NAV-01 NAV-02 every top-level page is unique and switchable', async () =>
|
||||
}
|
||||
})
|
||||
|
||||
test('NAV-03 exit monitor page shows a member departure event and stays within the viewport', async () => {
|
||||
const fixture = await launchTestApp()
|
||||
const pageErrors: Error[] = []
|
||||
fixture.page.on('pageerror', (error) => pageErrors.push(error))
|
||||
try {
|
||||
await fixture.setWindowContentSize({ width: 820, height: 600 })
|
||||
await fixture.page.getByRole('button', { name: '退群监控' }).click()
|
||||
await expect(fixture.page.getByRole('heading', { name: '退群监控', exact: true })).toBeVisible()
|
||||
await expect(fixture.page.getByText('测试成员退出了产品测试群', { exact: true })).toBeVisible()
|
||||
await expect(fixture.page.getByText(/240 人 → 239 人/)).toBeVisible()
|
||||
await expect(fixture.page.getByText('实时监听已启用')).toBeVisible()
|
||||
await expect(fixture.page.getByRole('button', { name: '立即检查' })).toBeEnabled()
|
||||
await expect(fixture.page.getByRole('button', { name: '清空记录' })).toBeEnabled()
|
||||
expect(
|
||||
await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)
|
||||
).toBe(true)
|
||||
expect(pageErrors).toEqual([])
|
||||
} finally {
|
||||
await fixture.close()
|
||||
}
|
||||
})
|
||||
|
||||
test('NAV-04 exit monitor management can save an empty scope and show the setup state', async () => {
|
||||
const fixture = await launchTestApp()
|
||||
try {
|
||||
await fixture.page.getByRole('button', { name: '退群监控' }).click()
|
||||
await fixture.page.getByRole('button', { name: '管理群聊' }).click()
|
||||
await expect(fixture.page.getByRole('heading', { name: '管理群聊', exact: true })).toBeVisible()
|
||||
await expect(fixture.page.getByText('发送能力已就绪', { exact: true })).toBeVisible()
|
||||
await expect(fixture.page.getByRole('checkbox', { name: '监控产品测试群' })).toBeChecked()
|
||||
await fixture.page.getByRole('button', { name: '查看退群监测模板' }).click()
|
||||
await expect(fixture.page.getByLabel('退群监测模板内容')).toHaveValue(/用户: \{user\}/)
|
||||
await expect(fixture.page.getByLabel('退群监测模板内容')).toHaveValue(/群备注: \{groupRemark\}/)
|
||||
const customTemplate = '[退群监测]\n用户: {user}\n群备注: {groupRemark}'
|
||||
await fixture.page.getByLabel('退群监测模板内容').fill(customTemplate)
|
||||
await fixture.page.getByRole('button', { name: '保存模板' }).click()
|
||||
await fixture.page.getByRole('button', { name: '查看退群监测模板' }).click()
|
||||
await expect(fixture.page.getByLabel('退群监测模板内容')).toHaveValue(customTemplate)
|
||||
await fixture.page.keyboard.press('Escape')
|
||||
await fixture.page.getByRole('checkbox', { name: '监控产品测试群' }).click()
|
||||
await fixture.page.getByRole('checkbox', { name: '监控折叠群聊样本' }).click()
|
||||
await fixture.page.getByRole('button', { name: '保存监控群聊' }).click()
|
||||
await expect(fixture.page.getByText('还没有设置监控群聊', { exact: true })).toBeVisible()
|
||||
await expect(fixture.page.getByRole('button', { name: '选择群聊' })).toBeVisible()
|
||||
} finally {
|
||||
await fixture.close()
|
||||
}
|
||||
})
|
||||
|
||||
test('CHAT-01 archive More menu is keyboard-safe and keeps the page usable', async () => {
|
||||
const fixture = await launchTestApp()
|
||||
const pageErrors: Error[] = []
|
||||
|
||||
@@ -234,6 +234,98 @@ const handle = (channel, fn) => {
|
||||
ipcMain.handle(channel, async (event, ...args) => fn(...args))
|
||||
}
|
||||
|
||||
const groupExitEvent = {
|
||||
id: 'fixture-exit-event',
|
||||
contactId: 'group-regular-md5',
|
||||
roomId: 'group_regular@chatroom',
|
||||
groupName: '产品测试群',
|
||||
memberWxid: 'wxid_fixture_member',
|
||||
memberName: '测试成员',
|
||||
wechatName: '测试成员微信名',
|
||||
groupRemark: '测试成员群备注',
|
||||
contactRemark: '测试成员通讯录备注',
|
||||
previousCount: 240,
|
||||
currentCount: 239,
|
||||
delta: -1,
|
||||
message: '测试成员退出了产品测试群',
|
||||
detectedAt: fixtureNowMs - 60 * 1000
|
||||
}
|
||||
let groupExitMonitorState = {
|
||||
events: [groupExitEvent],
|
||||
running: connected,
|
||||
nativeMonitorActive: connected,
|
||||
monitoredGroupCount: 2,
|
||||
monitorSelectionConfigured: true,
|
||||
monitoredRoomIds: ['group_regular@chatroom', 'group_folded@chatroom'],
|
||||
notificationRoomIds: [],
|
||||
notificationTemplate:
|
||||
'[退群监测]\n\n用户: {user}\n\n群备注: {groupRemark}\n\n微信号: {wxid}\n\n退群时间: {time}',
|
||||
lastCheckedAt: fixtureNowMs - 30 * 1000,
|
||||
lastReadAt: 0,
|
||||
unreadCount: 1
|
||||
}
|
||||
const cloneGroupExitMonitorState = () => ({
|
||||
...groupExitMonitorState,
|
||||
events: groupExitMonitorState.events.map((event) => ({ ...event })),
|
||||
unreadCount: groupExitMonitorState.events.filter(
|
||||
(event) => event.detectedAt > groupExitMonitorState.lastReadAt
|
||||
).length
|
||||
})
|
||||
handle('group-exit-monitor:getState', () => cloneGroupExitMonitorState())
|
||||
handle('group-exit-monitor:setGroups', (roomIds, notificationRoomIds) => {
|
||||
const selected = Array.isArray(roomIds)
|
||||
? roomIds.filter((roomId) => typeof roomId === 'string' && roomId.endsWith('@chatroom'))
|
||||
: []
|
||||
const notifications = Array.isArray(notificationRoomIds)
|
||||
? notificationRoomIds.filter(
|
||||
(roomId) => typeof roomId === 'string' && selected.includes(roomId)
|
||||
)
|
||||
: []
|
||||
groupExitMonitorState = {
|
||||
...groupExitMonitorState,
|
||||
monitorSelectionConfigured: true,
|
||||
monitoredRoomIds: [...new Set(selected)],
|
||||
notificationRoomIds: [...new Set(notifications)],
|
||||
monitoredGroupCount: selected.length,
|
||||
lastCheckedAt: fixtureNowMs
|
||||
}
|
||||
return cloneGroupExitMonitorState()
|
||||
})
|
||||
handle('group-exit-monitor:setTemplate', (template) => {
|
||||
if (typeof template !== 'string' || !template.trim()) throw new Error('模板不能为空')
|
||||
groupExitMonitorState = {
|
||||
...groupExitMonitorState,
|
||||
notificationTemplate: template.trim()
|
||||
}
|
||||
return cloneGroupExitMonitorState()
|
||||
})
|
||||
handle('group-exit-monitor:checkNow', () => {
|
||||
groupExitMonitorState = {
|
||||
...groupExitMonitorState,
|
||||
lastCheckedAt: fixtureNowMs
|
||||
}
|
||||
return cloneGroupExitMonitorState()
|
||||
})
|
||||
handle('group-exit-monitor:clearEvents', () => {
|
||||
groupExitMonitorState = {
|
||||
...groupExitMonitorState,
|
||||
events: [],
|
||||
lastReadAt: fixtureNowMs
|
||||
}
|
||||
return cloneGroupExitMonitorState()
|
||||
})
|
||||
handle('group-exit-monitor:markRead', (readAt) => {
|
||||
const requestedAt = Number(readAt)
|
||||
groupExitMonitorState = {
|
||||
...groupExitMonitorState,
|
||||
lastReadAt: Math.max(
|
||||
groupExitMonitorState.lastReadAt,
|
||||
Number.isFinite(requestedAt) && requestedAt > 0 ? requestedAt : fixtureNowMs
|
||||
)
|
||||
}
|
||||
return cloneGroupExitMonitorState()
|
||||
})
|
||||
|
||||
const scheduledReportTasks = []
|
||||
const scheduledReportExecutions = []
|
||||
const scheduledReportNextRun = (scheduleTime) => {
|
||||
|
||||
@@ -284,6 +284,92 @@ test('API-02 Agent target segmented control visual @visual', async () => {
|
||||
})
|
||||
|
||||
for (const appearanceTheme of ['light', 'dark'] as const) {
|
||||
test(`EXIT-01 group exit monitor page ${appearanceTheme} visual @visual`, async () => {
|
||||
const fixture = await launchTestApp({ now: visualNow, appearanceTheme })
|
||||
const pageErrors: Error[] = []
|
||||
fixture.page.on('pageerror', (error) => pageErrors.push(error))
|
||||
try {
|
||||
await fixture.setWindowContentSize(visualViewport)
|
||||
await fixture.page.getByRole('button', { name: '退群监控' }).click()
|
||||
await expect(
|
||||
fixture.page.getByRole('heading', { name: '退群监控', exact: true })
|
||||
).toBeVisible()
|
||||
await expect(
|
||||
fixture.page.getByText('测试成员退出了产品测试群', { exact: true })
|
||||
).toBeVisible()
|
||||
await expect(fixture.page.getByText(/240 人 → 239 人/)).toBeVisible()
|
||||
expect(
|
||||
await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)
|
||||
).toBe(true)
|
||||
expect(pageErrors).toEqual([])
|
||||
await clearScreenshotFocus(fixture.page)
|
||||
await expect(fixture.page).toHaveScreenshot(`group-exit-monitor-${appearanceTheme}.png`, {
|
||||
animations: 'disabled',
|
||||
caret: 'hide'
|
||||
})
|
||||
} finally {
|
||||
await fixture.close()
|
||||
}
|
||||
})
|
||||
|
||||
test(`EXIT-02 group exit monitor management ${appearanceTheme} visual @visual`, async () => {
|
||||
const fixture = await launchTestApp({ now: visualNow, appearanceTheme })
|
||||
const pageErrors: Error[] = []
|
||||
fixture.page.on('pageerror', (error) => pageErrors.push(error))
|
||||
try {
|
||||
await fixture.setWindowContentSize(visualViewport)
|
||||
await fixture.page.getByRole('button', { name: '退群监控' }).click()
|
||||
await fixture.page.getByRole('button', { name: '管理群聊' }).click()
|
||||
await expect(
|
||||
fixture.page.getByRole('heading', { name: '管理群聊', exact: true })
|
||||
).toBeVisible()
|
||||
await expect(fixture.page.getByRole('checkbox', { name: '监控产品测试群' })).toBeChecked()
|
||||
expect(
|
||||
await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)
|
||||
).toBe(true)
|
||||
expect(pageErrors).toEqual([])
|
||||
await clearScreenshotFocus(fixture.page)
|
||||
await expect(fixture.page).toHaveScreenshot(
|
||||
`group-exit-monitor-manage-${appearanceTheme}.png`,
|
||||
{
|
||||
animations: 'disabled',
|
||||
caret: 'hide'
|
||||
}
|
||||
)
|
||||
} finally {
|
||||
await fixture.close()
|
||||
}
|
||||
})
|
||||
|
||||
test(`EXIT-03 group exit monitor setup empty ${appearanceTheme} visual @visual`, async () => {
|
||||
const fixture = await launchTestApp({ now: visualNow, appearanceTheme })
|
||||
const pageErrors: Error[] = []
|
||||
fixture.page.on('pageerror', (error) => pageErrors.push(error))
|
||||
try {
|
||||
await fixture.setWindowContentSize(visualViewport)
|
||||
await fixture.page.getByRole('button', { name: '退群监控' }).click()
|
||||
await fixture.page.getByRole('button', { name: '管理群聊' }).click()
|
||||
await fixture.page.getByRole('checkbox', { name: '监控产品测试群' }).click()
|
||||
await fixture.page.getByRole('checkbox', { name: '监控折叠群聊样本' }).click()
|
||||
await fixture.page.getByRole('button', { name: '保存监控群聊' }).click()
|
||||
await expect(fixture.page.getByText('还没有设置监控群聊', { exact: true })).toBeVisible()
|
||||
expect(
|
||||
await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)
|
||||
).toBe(true)
|
||||
expect(pageErrors).toEqual([])
|
||||
await clearScreenshotFocus(fixture.page)
|
||||
await expect(fixture.page).toHaveScreenshot(
|
||||
`group-exit-monitor-empty-${appearanceTheme}.png`,
|
||||
{
|
||||
animations: 'disabled',
|
||||
caret: 'hide'
|
||||
}
|
||||
)
|
||||
} finally {
|
||||
await fixture.close()
|
||||
}
|
||||
})
|
||||
|
||||
test(`REPORT-00 report configuration controls ${appearanceTheme} visual @visual`, async () => {
|
||||
const fixture = await launchTestApp({ now: visualNow, appearanceTheme })
|
||||
const pageErrors: Error[] = []
|
||||
|
||||
Reference in New Issue
Block a user