feat: 优化档案搜索与头像

新增退群监控开关
发送限速
This commit is contained in:
Wxw-Gu
2026-09-03 18:12:29 +08:00
parent 3462d49516
commit 719ffa0a24
33 changed files with 1025 additions and 64 deletions
@@ -0,0 +1,49 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { ConversationItem } from '../../src/renderer/src/components/conversation/ConversationItem'
import type { Contact } from '../../src/shared/types'
const contact: Contact = {
m_nsUsrName: 'wxid_fixture',
m_nsNickName: '联系人',
md5: 'fixture',
type: 'user',
avatar: 'https://old.example/avatar.jpg'
}
describe('ConversationItem avatar recovery', () => {
it('refreshes only the failed contact and lets a newer prop replace the repaired source', async () => {
const getContactAvatars = vi
.fn()
.mockResolvedValue({ wxid_fixture: 'data:image/png;base64,repaired' })
Object.defineProperty(window, 'api', {
configurable: true,
value: { getContactAvatars } as unknown as typeof window.api
})
const { rerender } = render(
<ConversationItem contact={contact} active={false} onSelect={vi.fn()} />
)
fireEvent.error(screen.getByRole('img', { name: '联系人' }))
await waitFor(() =>
expect(screen.getByRole('img', { name: '联系人' })).toHaveAttribute(
'src',
'data:image/png;base64,repaired'
)
)
expect(getContactAvatars).toHaveBeenCalledWith(['wxid_fixture'], { refresh: true })
rerender(
<ConversationItem
contact={{ ...contact, avatar: 'https://new.example/avatar.jpg' }}
active={false}
onSelect={vi.fn()}
/>
)
expect(screen.getByRole('img', { name: '联系人' })).toHaveAttribute(
'src',
'https://new.example/avatar.jpg'
)
})
})
@@ -41,6 +41,7 @@ const state: GroupExitMonitorState = {
detectedAt: 1_756_600_100_000
}
],
enabled: true,
running: true,
nativeMonitorActive: true,
monitoredGroupCount: 3,
@@ -93,6 +94,28 @@ describe('GroupExitMonitorWorkspace', () => {
expect(screen.getByText('实时监听已启用')).toBeVisible()
})
it('persists the master switch and shows the paused state', async () => {
const user = userEvent.setup()
const pausedState = { ...state, enabled: false, running: false }
const setEnabled = vi.fn().mockResolvedValue(pausedState)
window.api = {
getGroupExitMonitorState: vi.fn().mockResolvedValue(state),
onGroupExitMonitorState: vi.fn(() => () => undefined),
setGroupExitMonitorEnabled: setEnabled,
checkGroupExitMonitorNow: vi.fn().mockResolvedValue(state),
clearGroupExitMonitorEvents: vi.fn().mockResolvedValue(state)
} as typeof window.api
renderWorkspace(<GroupExitMonitorWorkspace dbReady />)
const masterSwitch = await screen.findByRole('switch', { name: '退群监控总开关' })
expect(masterSwitch).toBeChecked()
await user.click(masterSwitch)
await waitFor(() => expect(setEnabled).toHaveBeenCalledWith(false))
expect(screen.getByText('监控已暂停')).toBeVisible()
expect(screen.getByRole('button', { name: '立即检查' })).toBeDisabled()
})
it('filters to groups that have exit events and clears the history', async () => {
const user = userEvent.setup()
renderWorkspace(<GroupExitMonitorWorkspace dbReady />)
+68
View File
@@ -0,0 +1,68 @@
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'
import { LogsWorkspace } from '../../src/renderer/src/features/logs/LogsWorkspace'
import type { ActionLogEntry } from '../../src/shared/action-log'
const entries: ActionLogEntry[] = [
{
id: 'member-log',
category: 'wechat_send',
source: 'member_monitor',
purpose: 'member_left_notification',
timestamp: '2026-09-03T08:30:21.000Z',
recipientType: 'group',
recipientId: '123456@chatroom',
recipientName: '测试 q1',
contentType: 'text',
contentPreview: 'Shinven 已退出群聊',
status: 'sent'
},
{
id: 'report-log',
category: 'wechat_send',
source: 'scheduled_report',
purpose: 'scheduled_report',
timestamp: '2026-09-03T07:00:00.000Z',
recipientType: 'group',
recipientId: 'report@chatroom',
recipientName: '日报群',
contentType: 'image',
contentPreview: 'daily-report.png',
status: 'failed',
errorCode: 'SEND_CAPABILITY_UNAVAILABLE',
reason: '个人微信发送能力不可用'
}
]
describe('LogsWorkspace', () => {
it('shows audit entries and filters by search, status, and purpose', async () => {
const user = userEvent.setup()
Object.defineProperty(window, 'api', {
configurable: true,
value: {
listWechatActionLogs: vi.fn().mockResolvedValue(entries)
} as unknown as typeof window.api
})
render(<LogsWorkspace />)
await waitFor(() => expect(screen.getByText(/Shinven 已退出群聊/)).toBeVisible())
expect(screen.getByText(/daily-report.png/)).toBeVisible()
expect(screen.getByText('发送能力不可用')).toBeVisible()
const search = screen.getByRole('searchbox', { name: '搜索日志' })
await user.type(search, '123456')
expect(screen.getByText(/Shinven 已退出群聊/)).toBeVisible()
expect(screen.queryByText(/daily-report.png/)).not.toBeInTheDocument()
await user.clear(search)
await user.click(screen.getByRole('combobox', { name: '筛选日志状态' }))
await user.click(await screen.findByRole('option', { name: '失败' }))
expect(screen.queryByText(/Shinven 已退出群聊/)).not.toBeInTheDocument()
expect(screen.getByText(/daily-report.png/)).toBeVisible()
await user.click(screen.getByRole('combobox', { name: '筛选日志类型' }))
await user.click(await screen.findByRole('option', { name: '退群通知' }))
expect(screen.getByText('没有匹配的日志')).toBeVisible()
})
})