Files
goodbuddy/src/renderer/src/DocumentParsingSettingsSection.test.tsx
T

384 lines
10 KiB
TypeScript

import {
cleanup,
fireEvent,
render,
screen,
waitFor
} from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type {
DocumentParsingSettings,
DocumentParsingSnapshot
} from '../../shared/document-parsing-contracts'
import { changeUiLocale } from './i18n'
import { DocumentParsingSettingsSection } from './DocumentParsingSettingsSection'
const settings: DocumentParsingSettings = {
chatWorkflow: 'auto',
knowledgeWorkflow: 'complete-index',
pdfOcrMode: 'auto',
ocrProvider: 'local',
localOcrEnabled: true,
localOcrModelId: 'pp-ocrv6-tiny',
maximumPages: 100,
ocrConcurrency: 1,
pageTimeoutSeconds: 60
}
const modelEntry = {
id: 'pp-ocrv6-tiny' as const,
displayName: 'PP-OCRv6 Tiny',
description: '轻量中文 OCR 模型',
languages: ['中文', '英语'],
runtime: 'onnxruntime-web-wasm' as const,
quality: 'basic' as const,
speed: 'fast' as const,
recommended: false,
repositoryUrl:
'https://modelscope.cn/models/PaddlePaddle/PP-OCRv6_tiny_rec_onnx',
license: {
name: 'Apache License 2.0',
notice: '使用前请阅读模型许可证。',
url: 'https://example.com/license'
},
files: [
{
name: 'detection.onnx',
role: 'detection' as const,
download: {
url: 'https://modelscope.cn/models/example/detection.onnx',
size: 1_000,
sha256: 'a'.repeat(64)
}
},
{
name: 'recognition.onnx',
role: 'recognition' as const,
download: {
url: 'https://modelscope.cn/models/example/recognition.onnx',
size: 2_000,
sha256: 'b'.repeat(64)
}
},
{
name: 'dictionary.yml',
role: 'dictionary' as const,
download: {
url: 'https://modelscope.cn/models/example/dictionary.yml',
size: 500,
sha256: 'c'.repeat(64)
}
}
]
}
const secondModelEntry = {
...modelEntry,
id: 'pp-ocrv6-small',
displayName: 'PP-OCRv6 Small',
quality: 'balanced' as const,
speed: 'balanced' as const,
recommended: true
}
const thirdModelEntry = {
...modelEntry,
id: 'pp-ocrv6-medium',
displayName: 'PP-OCRv6 Medium',
quality: 'high' as const,
speed: 'slow' as const,
recommended: false
}
const snapshot: DocumentParsingSnapshot = {
settings,
status: {
nativeParsingAvailable: true,
conversionAvailable: false,
localOcr: {
id: 'pp-ocrv6-tiny',
displayName: 'PP-OCRv6 Tiny',
available: false,
verified: false,
runtime: 'onnxruntime-web-wasm',
detail: '模型尚未安装'
}
},
ocrModels: {
rootDirectory: 'C:\\Users\\test\\models\\document-ocr',
catalog: [modelEntry, secondModelEntry, thirdModelEntry],
installed: [
{
id: 'pp-ocrv6-small',
displayName: 'PP-OCRv6 Small',
source: 'download',
installedAt: '2026-08-11T00:00:00.000Z',
files: secondModelEntry.files.map((file) => ({
name: file.name,
role: file.role,
size: file.download.size,
sha256: file.download.sha256
}))
}
],
operations: []
}
}
const getSnapshot = vi.fn(async () => snapshot)
const update = vi.fn(async (input: DocumentParsingSettings) => ({
...snapshot,
settings: input
}))
const test = vi.fn(async () => ({
fileName: 'scan.pdf',
sourceFormat: 'PDF',
pageCount: 2,
ocrPageCount: 2,
characterCount: 120,
method: 'ocr' as const,
durationMs: 1_250,
preview: '扫描件识别正文',
warnings: []
}))
const installOcrModel = vi.fn(async () => ({
...snapshot,
status: {
...snapshot.status,
localOcr: {
...snapshot.status.localOcr,
available: true,
verified: true,
detail: '模型已安装并校验'
}
},
ocrModels: {
...snapshot.ocrModels,
installed: [
{
id: 'pp-ocrv6-tiny' as const,
displayName: 'PP-OCRv6 Tiny',
source: 'download' as const,
installedAt: '2026-08-11T00:00:00.000Z',
files: modelEntry.files.map((file) => ({
name: file.name,
role: file.role,
size: file.download.size,
sha256: file.download.sha256
}))
}
]
}
}))
const importOcrModelArchive = vi.fn(async () => snapshot)
const exportOcrModelArchive = vi.fn(async () => snapshot)
const openOcrModelRepository = vi.fn(async () => undefined)
describe('DocumentParsingSettingsSection', () => {
beforeEach(async () => {
await changeUiLocale('zh-CN')
vi.clearAllMocks()
Object.defineProperty(window, 'goodbuddy', {
configurable: true,
value: {
documentParsing: {
getSnapshot,
update,
test,
installOcrModel,
cancelOcrModelOperation: vi.fn(async () => true),
removeOcrModel: vi.fn(async () => snapshot),
importOcrModelArchive,
exportOcrModelArchive,
openOcrModelRepository,
openOcrModelsDirectory: vi.fn(),
getOcrAssets: vi.fn(),
respondOcr: vi.fn(),
onOcrRequest: vi.fn(() => () => undefined),
onOcrCancel: vi.fn(() => () => undefined)
}
}
})
})
afterEach(() => cleanup())
it('shows actual capability status and saves workflow settings', async () => {
const onNotify = vi.fn()
render(
<DocumentParsingSettingsSection onNotify={onNotify} />
)
expect(await screen.findByText('PP-OCRv6 Tiny')).toBeInTheDocument()
expect(screen.getByText('ModelScope')).toBeInTheDocument()
expect(screen.getByText('质量:基础')).toBeInTheDocument()
expect(screen.getByText('速度:快')).toBeInTheDocument()
expect(screen.getByText('旧版 Office 转换')).toBeInTheDocument()
expect(
screen.getByRole('switch', { name: /启用本地 OCR/u })
).toBeChecked()
expect(
screen.getByRole('button', { name: '本地模型' })
).toHaveAttribute('aria-pressed', 'true')
expect(
screen.getByRole('button', {
name: '远程服务(即将支持)'
})
).toBeDisabled()
expect(screen.queryByText('隐私与云端处理')).not.toBeInTheDocument()
expect(
screen.queryByText('模型详情与手动导入')
).not.toBeInTheDocument()
expect(
screen.queryByText('可从 ModelScope 下载')
).not.toBeInTheDocument()
fireEvent.click(
screen.getByRole('button', {
name: '打开 PP-OCRv6 Tiny 的 ModelScope 页面'
})
)
expect(openOcrModelRepository).toHaveBeenCalledWith('pp-ocrv6-tiny')
fireEvent.change(screen.getByLabelText('聊天附件'), {
target: { value: 'fast-text' }
})
fireEvent.click(
screen.getByRole('button', { name: '保存设置' })
)
await waitFor(() =>
expect(update).toHaveBeenCalledWith(
expect.objectContaining({ chatWorkflow: 'fast-text' })
)
)
expect(onNotify).toHaveBeenCalledWith(
expect.objectContaining({
message: '文档解析设置已保存'
})
)
})
it('downloads the verified OCR model from the model catalog', async () => {
const onNotify = vi.fn()
render(
<DocumentParsingSettingsSection onNotify={onNotify} />
)
fireEvent.click(
await screen.findByRole('button', {
name: '下载 PP-OCRv6 Tiny'
})
)
await waitFor(() =>
expect(installOcrModel).toHaveBeenCalledWith('pp-ocrv6-tiny')
)
expect(onNotify).toHaveBeenCalledWith(
expect.objectContaining({
message: 'PP-OCRv6 Tiny 已安装'
})
)
})
it('imports and exports verified OCR model ZIP archives', async () => {
const onNotify = vi.fn()
render(
<DocumentParsingSettingsSection onNotify={onNotify} />
)
fireEvent.click(
await screen.findByRole('button', {
name: '从 ZIP 导入 PP-OCRv6 Tiny'
})
)
await waitFor(() =>
expect(importOcrModelArchive).toHaveBeenCalledWith(
'pp-ocrv6-tiny'
)
)
expect(onNotify).toHaveBeenCalledWith(
expect.objectContaining({
message: 'PP-OCRv6 Tiny 已从 ZIP 导入'
})
)
fireEvent.change(screen.getByLabelText('当前 OCR 模型'), {
target: { value: 'pp-ocrv6-small' }
})
fireEvent.click(
screen.getByRole('button', {
name: '将 PP-OCRv6 Small 导出为 ZIP'
})
)
await waitFor(() =>
expect(exportOcrModelArchive).toHaveBeenCalledWith(
'pp-ocrv6-small'
)
)
expect(update).not.toHaveBeenCalled()
expect(onNotify).toHaveBeenCalledWith(
expect.objectContaining({
message: 'PP-OCRv6 Small 已导出为 ZIP'
})
)
})
it('switches the selected OCR model only when settings are saved', async () => {
render(<DocumentParsingSettingsSection />)
const selector = await screen.findByLabelText('当前 OCR 模型')
expect(
screen.getByRole('option', {
name: 'PP-OCRv6 Tiny · 可下载'
})
).toBeInTheDocument()
expect(
screen.getByRole('option', {
name: 'PP-OCRv6 Small · 已安装'
})
).toBeInTheDocument()
expect(
screen.getByRole('option', {
name: 'PP-OCRv6 Medium · 可下载'
})
).toBeInTheDocument()
fireEvent.change(selector, {
target: { value: 'pp-ocrv6-small' }
})
expect(
screen.getByText('模型选择尚未生效,点击“保存设置”后切换。')
).toBeInTheDocument()
expect(screen.getByText('PP-OCRv6 Small')).toBeInTheDocument()
expect(screen.getByText('质量:均衡')).toBeInTheDocument()
expect(screen.getByText('速度:均衡')).toBeInTheDocument()
expect(update).not.toHaveBeenCalled()
fireEvent.click(
screen.getByRole('button', { name: '保存设置' })
)
await waitFor(() =>
expect(update).toHaveBeenCalledWith(
expect.objectContaining({
localOcrModelId: 'pp-ocrv6-small'
})
)
)
})
it('runs a real-file diagnostic flow and displays its result', async () => {
render(<DocumentParsingSettingsSection />)
await screen.findByText('PP-OCRv6 Tiny')
fireEvent.click(
screen.getByRole('button', { name: '测试解析' })
)
expect(
await screen.findByRole('dialog', {
name: '解析测试结果'
})
).toHaveTextContent('扫描件识别正文')
expect(test).toHaveBeenCalledOnce()
})
})