feat: 完善日报语音转写与全部聊天分目录导出

This commit is contained in:
电摇小子
2026-08-11 02:59:54 +08:00
parent d1a090bb6b
commit 7a5499f093
28 changed files with 1909 additions and 193 deletions
@@ -9,6 +9,7 @@ const baseProps = {
previewBytes: 0,
selfInfo: null,
selectedCount: 1,
allExport: false,
jobId: 'fixture-job',
onCancel: vi.fn(),
onReveal: vi.fn()
@@ -70,4 +71,34 @@ describe('export progress panel', () => {
expect(progressbar).toHaveAttribute('aria-valuenow', '31')
expect(progressbar.querySelector('span')).toHaveStyle({ width: '31%' })
})
it('shows the current conversation and overall position for all export', () => {
render(
<ExportPreviewPanel
{...baseProps}
allExport
selectedCount={30}
progress={{
jobId: 'fixture-job',
phase: 'media',
processed: 5,
total: 12,
percent: 36,
currentTargetIndex: 11,
currentTargetCount: 30,
currentTargetName: '项目讨论群',
currentTargetType: 'group'
}}
includeVoiceTranscripts={false}
zip={false}
/>
)
expect(screen.getByText('群聊')).toBeVisible()
expect(screen.getByText('第 11/30 个:项目讨论群')).toBeVisible()
expect(screen.getByRole('progressbar', { name: '导出进度' })).toHaveAttribute(
'aria-valuenow',
'36'
)
})
})
@@ -52,4 +52,42 @@ describe('export task center', () => {
expect(writeText.mock.calls[0][0]).toContain('EPERM: operation not permitted, copyfile')
expect(screen.getByRole('button', { name: '已复制' })).toBeInTheDocument()
})
it('shows the current conversation for a background all-export task', () => {
render(
<ExportTaskCenter
open
taskCount={1}
tasks={[
{
jobId: 'all-export',
scope: 'all',
allContactTypes: ['group', 'user'],
targetIds: [],
targetNames: [],
targetLabel: '全部 20 个聊天',
format: 'html',
status: 'running',
progress: {
jobId: 'all-export',
phase: 'media',
processed: 3,
total: 8,
percent: 42,
currentTargetIndex: 9,
currentTargetCount: 20,
currentTargetName: '项目群',
currentTargetType: 'group'
},
createdAt: Date.now()
}
]}
onToggle={vi.fn()}
onCancel={vi.fn()}
/>
)
expect(screen.getByText('第 9/20 个:项目群')).toBeVisible()
expect(screen.getByText('42%')).toBeVisible()
})
})
+87 -2
View File
@@ -38,7 +38,8 @@ describe('ExportWorkspace multi-chat selection', () => {
})
const renderWorkspace = (
onStartExport = vi.fn(async () => ({ success: false }))
onStartExport = vi.fn(async () => ({ success: false })),
exportTasks: ExportTaskRecord[] = []
): { loadPreviewMessages: ReturnType<typeof vi.fn> } => {
const loadPreviewMessages = vi.fn(async (contact: Contact) => [previewMessage(contact)])
render(
@@ -49,7 +50,7 @@ describe('ExportWorkspace multi-chat selection', () => {
dbReady
loadPreviewMessages={loadPreviewMessages}
onOpenSettings={vi.fn()}
exportTasks={[]}
exportTasks={exportTasks}
onStartExport={onStartExport}
onCancelExport={vi.fn(async () => undefined)}
/>
@@ -112,6 +113,90 @@ describe('ExportWorkspace multi-chat selection', () => {
expect(screen.getByText('已选 4 / 5 个')).toBeVisible()
expect(screen.getByRole('button', { name: /聊天 F/ })).toBeEnabled()
})
it('exports every chat into its own format folder without loading every preview', async () => {
const onStartExport = vi.fn(async () => ({ success: false }))
const { loadPreviewMessages } = renderWorkspace(onStartExport)
await screen.findByText('聊天 A 的预览')
await userEvent.click(screen.getByRole('button', { name: /全部导出/ }))
expect(screen.getByText(/全部群聊 1 个和全部联系人 5 个/)).toBeVisible()
expect(screen.getByRole('button', { name: 'CSV' })).toBeEnabled()
expect(screen.getByRole('button', { name: 'CSV' })).toHaveClass('active')
expect(screen.getByRole('button', { name: 'JSON' })).toBeEnabled()
expect(screen.getByRole('button', { name: 'Markdown' })).toBeEnabled()
expect(loadPreviewMessages).toHaveBeenCalledTimes(1)
await userEvent.click(screen.getByRole('button', { name: '开始导出' }))
await waitFor(() => expect(onStartExport).toHaveBeenCalledOnce())
expect(onStartExport.mock.calls[0][0]).toMatchObject({
scope: 'all',
allContactTypes: ['group', 'user'],
format: 'csv',
outputName: '全部聊天记录'
})
expect(onStartExport.mock.calls[0][0].targets).toHaveLength(contacts.length)
expect(window.api.getGroupSnapshot).not.toHaveBeenCalled()
})
it('allows all export to include only groups and replaces the single-chat avatars', async () => {
const onStartExport = vi.fn(async () => ({ success: false }))
renderWorkspace(onStartExport)
await userEvent.click(screen.getByRole('button', { name: /全部导出/ }))
expect(document.querySelector('.export-all-chat-avatar.group')).toHaveTextContent('群')
expect(document.querySelector('.export-all-chat-avatar.user')).toHaveTextContent('联')
await userEvent.click(screen.getByRole('checkbox', { name: '导出全部联系人' }))
expect(document.querySelector('.export-all-chat-avatar.user')).not.toBeInTheDocument()
expect(screen.getAllByText(/全部群聊 1 个/)).toHaveLength(2)
expect(screen.getByRole('button', { name: /聊天 A/ })).toHaveAttribute('aria-pressed', 'false')
await userEvent.click(screen.getByRole('button', { name: '开始导出' }))
await waitFor(() => expect(onStartExport).toHaveBeenCalledOnce())
expect(onStartExport.mock.calls[0][0]).toMatchObject({
scope: 'all',
allContactTypes: ['group'],
startTime: undefined,
endTime: undefined
})
expect(onStartExport.mock.calls[0][0].targets).toEqual([
expect.objectContaining({ userMd5: 'contact-3', type: 'group' })
])
})
it('restores a running all-export task after returning to the page', async () => {
const runningTask: ExportTaskRecord = {
jobId: 'background-all',
scope: 'all',
allContactTypes: ['group'],
targetIds: [],
targetNames: [],
targetLabel: '全部 1 个聊天',
format: 'html',
status: 'running',
progress: {
jobId: 'background-all',
phase: 'media',
processed: 4,
total: 10,
percent: 48,
currentTargetIndex: 1,
currentTargetCount: 1,
currentTargetName: '聊天 C',
currentTargetType: 'group'
},
createdAt: Date.now()
}
const { loadPreviewMessages } = renderWorkspace(undefined, [runningTask])
expect(await screen.findByText('第 1/1 个:聊天 C')).toBeVisible()
expect(screen.getByRole('checkbox', { name: '导出全部群聊' })).toBeChecked()
expect(screen.getByRole('checkbox', { name: '导出全部联系人' })).not.toBeChecked()
expect(document.querySelector('.export-all-chat-avatar.group')).toHaveTextContent('群')
expect(document.querySelector('.export-all-chat-avatar.user')).not.toBeInTheDocument()
expect(loadPreviewMessages).not.toHaveBeenCalled()
})
})
describe('ExportTaskCenter details', () => {
+81
View File
@@ -0,0 +1,81 @@
import { render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ReportGroupMemberSelector } from '../../src/renderer/src/components/reports/ReportGroupMemberSelector'
import { ReportTaskStatusPanel } from '../../src/renderer/src/components/reports/ReportTaskStatusPanel'
import type { Contact } from '../../src/shared/types'
const groupContact: Contact = {
md5: 'group-md5',
m_nsUsrName: 'group@chatroom',
m_nsNickName: '测试群',
type: 'group'
}
describe('daily report controls', () => {
beforeEach(() => {
Object.defineProperty(window, 'api', {
configurable: true,
value: {
getAppLogPath: vi.fn(async () => ''),
revealAppLog: vi.fn(async () => undefined),
getGroupSnapshot: vi.fn(async () => ({
members: [
{
wxid: 'wxid-one',
nickname: '兼容名称一',
groupNickname: '群内昵称一',
wechatNickname: '微信昵称一',
remark: '通讯录备注一',
avatar: ''
},
{
wxid: 'wxid-two',
nickname: '兼容名称二',
groupNickname: '群内昵称二',
wechatNickname: '微信昵称二',
remark: '通讯录备注二',
avatar: ''
}
]
}))
}
})
})
it('shows a separate voice progress bar only when voice is selected', () => {
const progress = { processed: 2, total: 3, succeeded: 2, failed: 0 }
const { rerender } = render(
<ReportTaskStatusPanel
phase="transcribingVoice"
error=""
voiceTranscriptionProgress={progress}
voiceTranscriptionEnabled
onRetry={vi.fn()}
/>
)
expect(screen.getByText('2/5')).toBeVisible()
expect(screen.getByRole('progressbar', { name: '语音转写进度' })).toHaveAttribute('value', '2')
rerender(
<ReportTaskStatusPanel
phase="preparingInput"
error=""
voiceTranscriptionProgress={null}
voiceTranscriptionEnabled={false}
onRetry={vi.fn()}
/>
)
expect(screen.queryByText('转写语音消息')).not.toBeInTheDocument()
expect(screen.getByText('2/4')).toBeVisible()
})
it('loads and displays group nickname, WeChat nickname, and remark separately', async () => {
render(<ReportGroupMemberSelector sourceContact={groupContact} />)
await waitFor(() => expect(screen.getAllByText('群内昵称一')).toHaveLength(2))
expect(screen.getByText('微信昵称一')).toBeVisible()
expect(screen.getByText('通讯录备注一')).toBeVisible()
expect(screen.getByText('wxid-one')).toBeVisible()
})
})