gongxiangp2/components/department-dashboard/BrandKnowledgeDashboard.test.tsx

550 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { changeField, openSelect } from '@/test-utils/select'
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import BrandKnowledgeDashboard from './BrandKnowledgeDashboard'
import { BRAND_KNOWLEDGE_TYPE_OPTIONS } from '@/lib/brand-knowledge-file-types'
const mockPrepareBrandKnowledgePreviewInBrowser = jest.fn()
const mockChunkUpload = jest.fn()
jest.mock('@/lib/brand-knowledge-image-browser', () => ({
prepareBrandKnowledgePreviewInBrowser: (...args: unknown[]) => mockPrepareBrandKnowledgePreviewInBrowser(...args),
canUseBrandKnowledgeLegacyServerPreview: jest.fn(async () => false),
}))
jest.mock('@/lib/chunk-upload', () => ({
chunkUpload: (...args: unknown[]) => mockChunkUpload(...args),
generateFileId: (file: File) => `file-${file.name}-${file.size}-${file.lastModified}`,
}))
jest.mock('next-auth/react', () => ({
useSession: () => ({ data: { user: { name: '本地超级管理员' } } }),
signOut: jest.fn(),
}))
describe('BrandKnowledgeDashboard', () => {
beforeEach(() => {
localStorage.clear()
Object.defineProperty(URL, 'createObjectURL', { configurable: true, value: jest.fn(() => 'blob:test-preview') })
Object.defineProperty(URL, 'revokeObjectURL', { configurable: true, value: jest.fn() })
Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText: jest.fn().mockResolvedValue(undefined) } })
mockChunkUpload.mockReset()
mockChunkUpload.mockResolvedValue({
uploadId: 'upload-knowledge-1',
existingFilePath: '/uploads/temp/upload-knowledge-1/merged-brand-guide.pdf',
fileName: 'brand-guide.pdf',
fileSize: 12,
})
mockPrepareBrandKnowledgePreviewInBrowser.mockResolvedValue([
{
file: new File(['slice one'], '预览图-001.webp', { type: 'image/webp' }),
mimeType: 'image/webp',
width: 1600,
height: 1500,
kind: 'slice',
sourceName: '预览图.png',
sourceIndex: 0,
sliceIndex: 0,
sliceCount: 2,
},
{
file: new File(['slice two'], '预览图-002.webp', { type: 'image/webp' }),
mimeType: 'image/webp',
width: 1600,
height: 900,
kind: 'slice',
sourceName: '预览图.png',
sourceIndex: 0,
sliceIndex: 1,
sliceCount: 2,
},
])
global.fetch = jest.fn().mockImplementation((input: RequestInfo | URL) => {
if (String(input) === '/api/product-categories') {
return Promise.resolve({ ok: true, json: async () => [{ id: 'category-1', name: '床类' }] } as Response)
}
return Promise.resolve({ ok: true, json: async () => ({ success: true, data: [] }) } as Response)
})
})
afterEach(() => { jest.restoreAllMocks() })
it('shows the real upload entry and empty state', async () => {
render(<BrandKnowledgeDashboard />)
const main = screen.getByRole('main')
expect(within(main).queryByRole('heading', { level: 1, name: '品牌知识库' })).not.toBeInTheDocument()
expect(within(main).queryByText('可上传')).not.toBeInTheDocument()
expect(screen.getByRole('searchbox', { name: '搜索品牌知识' })).toBeInTheDocument()
expect(screen.getByRole('navigation', { name: '知识类型' })).toBeInTheDocument()
expect(screen.getAllByRole('button').map((button) => button.textContent)).toEqual(expect.arrayContaining([...BRAND_KNOWLEDGE_TYPE_OPTIONS]))
expect(screen.getByRole('button', { name: '上传知识' })).toBeEnabled()
await waitFor(() => expect(screen.getByText('品牌知识库暂未上传内容')).toBeInTheDocument())
expect(screen.getByLabelText('共 0 条')).toHaveTextContent('共 0 条')
expect(screen.queryByRole('heading', { name: '知识内容' })).not.toBeInTheDocument()
expect(screen.queryByText('人员上传的知识文件将按类型保存,并可在此下载')).not.toBeInTheDocument()
})
it('filters the empty state by type and search', async () => {
render(<BrandKnowledgeDashboard />)
await waitFor(() => expect(screen.getByText('品牌知识库暂未上传内容')).toBeInTheDocument())
fireEvent.click(screen.getByRole('button', { name: '主图规范' }))
expect(screen.getByText('主图规范暂未上传内容')).toBeInTheDocument()
changeField(screen.getByRole('searchbox', { name: '搜索品牌知识' }), { target: { value: '索菲亚' } })
expect(screen.getByText('没有找到与“索菲亚”匹配的知识内容')).toBeInTheDocument()
})
it('searches by knowledge type when the title uses a different template spelling', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: true,
json: async () => ({
success: true,
total: 1,
data: [{
id: 'knowledge-template-1',
title: '升降桌主图模版-索菲亚',
knowledgeType: '主图模板',
brand: '索菲亚',
productCategoryName: '书桌',
fileName: '升降桌主图模板.psd',
mimeType: 'image/vnd.adobe.photoshop',
fileSize: 1024,
downloadCount: 0,
createdAt: '2026-09-21T00:00:00.000Z',
uploadedBy: '无邪',
downloadUrl: '/api/department-dashboard/brand-knowledge/knowledge-template-1/file',
previewUrl: null,
previewImageName: null,
likeCount: 0,
isLiked: false,
isFavorited: false,
}],
}),
} as Response)
render(<BrandKnowledgeDashboard />)
expect(await screen.findByRole('heading', { level: 3, name: '升降桌主图模版-索菲亚' })).toBeInTheDocument()
changeField(screen.getByRole('searchbox', { name: '搜索品牌知识' }), { target: { value: '主图模板' } })
expect(screen.getByRole('heading', { level: 3, name: '升降桌主图模版-索菲亚' })).toBeInTheDocument()
})
it('shows uploaded knowledge with the material-library card hierarchy', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: true,
json: async () => ({
success: true,
total: 23_081,
data: [{
id: 'knowledge-1',
title: '索菲亚图像影像规范',
knowledgeType: '影像规范',
brand: '索菲亚',
productCategoryName: '衣柜',
fileName: '索菲亚图像影像规范.rar',
mimeType: 'application/vnd.rar',
fileSize: 668886630,
downloadCount: 4,
createdAt: '2026-09-12T00:00:00.000Z',
uploadedBy: '无邪',
downloadUrl: '/api/department-dashboard/brand-knowledge/knowledge-1/file',
previewUrl: '/api/department-dashboard/brand-knowledge/knowledge-1/preview',
previewImageName: 'preview.png',
likeCount: 0,
isLiked: false,
isFavorited: false,
}],
}),
} as Response)
render(<BrandKnowledgeDashboard />)
const preview = await screen.findByRole('img', { name: '预览图:索菲亚图像影像规范' })
expect(screen.getByLabelText('共 23081 条')).toHaveTextContent('共 23,081 条')
expect(preview).toHaveClass('object-cover', 'object-center')
expect(screen.getByRole('link', { name: '查看索菲亚图像影像规范详情' })).toHaveAttribute(
'href',
'/workspaces/assets/brand-knowledge/knowledge-1',
)
expect(screen.getByRole('heading', { level: 3, name: '索菲亚图像影像规范' })).toBeInTheDocument()
expect(screen.getByText(/637\.90 MB/)).toBeInTheDocument()
expect(screen.getByText(/无邪/)).toBeInTheDocument()
expect(screen.getByText(/2026\.09\.12/)).toBeInTheDocument()
const knowledgeList = screen.getByRole('list', { name: '品牌知识内容列表' })
expect(knowledgeList).toHaveClass('grid-cols-1', 'sm:grid-cols-2', 'lg:grid-cols-4')
expect(within(knowledgeList).getByText('影像规范')).toBeInTheDocument()
expect(within(knowledgeList).getByText('索菲亚')).toBeInTheDocument()
expect(within(knowledgeList).getByText('衣柜')).toBeInTheDocument()
expect(screen.queryByText('索菲亚图像影像规范.rar')).not.toBeInTheDocument()
const copyIdButton = screen.getByRole('button', { name: '复制作品ID knowledge-1' })
expect(copyIdButton).toHaveTextContent('作品ID knowledge-1')
fireEvent.click(copyIdButton)
await waitFor(() => expect(navigator.clipboard.writeText).toHaveBeenCalledWith('knowledge-1'))
await waitFor(() => expect(screen.getByRole('status')).toHaveTextContent('作品ID已复制'))
expect(screen.getByRole('link', { name: '下载索菲亚图像影像规范' })).toHaveAttribute(
'href',
'/api/department-dashboard/brand-knowledge/knowledge-1/file',
)
expect(screen.getByRole('button', { name: '分享索菲亚图像影像规范' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '点赞索菲亚图像影像规范' })).toHaveAttribute('aria-pressed', 'false')
expect(screen.getByRole('button', { name: '收藏索菲亚图像影像规范' })).toHaveAttribute('aria-pressed', 'false')
expect(screen.queryByRole('button', { name: '编辑索菲亚图像影像规范' })).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: '删除索菲亚图像影像规范' })).not.toBeInTheDocument()
})
it('opens the material-library share dialog, copies the deep link, and persists engagement state', async () => {
const item = {
id: 'knowledge-1',
title: '索菲亚图像影像规范',
knowledgeType: '影像规范',
brand: '索菲亚',
productCategoryName: '衣柜',
fileName: '索菲亚图像影像规范.rar',
mimeType: 'application/vnd.rar',
fileSize: 668886630,
downloadCount: 4,
createdAt: '2026-09-12T00:00:00.000Z',
uploadedBy: '无邪',
downloadUrl: '/api/department-dashboard/brand-knowledge/knowledge-1/file',
previewUrl: '/api/department-dashboard/brand-knowledge/knowledge-1/preview',
previewImageName: 'preview.png',
likeCount: 0,
isLiked: false,
isFavorited: false,
}
global.fetch = jest.fn().mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
if (String(input).endsWith('/like') && init?.method === 'PUT') {
return Promise.resolve({ ok: true, json: async () => ({ success: true, liked: true, likeCount: 1 }) } as Response)
}
if (String(input).endsWith('/favorite') && init?.method === 'PUT') {
return Promise.resolve({ ok: true, json: async () => ({ success: true, isFavorited: true }) } as Response)
}
return Promise.resolve({ ok: true, json: async () => ({ success: true, data: [item] }) } as Response)
})
render(<BrandKnowledgeDashboard />)
fireEvent.click(await screen.findByRole('button', { name: '分享索菲亚图像影像规范' }))
const shareDialog = screen.getByRole('dialog', { name: '分享链接' })
expect(within(shareDialog).getByTestId('brand-knowledge-share-url')).toHaveTextContent(
'/workspaces/assets/brand-knowledge/knowledge-1',
)
expect(navigator.clipboard.writeText).not.toHaveBeenCalled()
fireEvent.click(within(shareDialog).getByRole('button', { name: '复制链接' }))
await waitFor(() => expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
expect.stringMatching(/\/workspaces\/assets\/brand-knowledge\/knowledge-1$/),
))
await waitFor(() => expect(screen.getByRole('status')).toHaveTextContent('分享链接已复制到剪贴板!'))
expect(screen.queryByRole('dialog', { name: '分享链接' })).not.toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: '点赞索菲亚图像影像规范' }))
await waitFor(() => expect(screen.getByRole('button', { name: '取消点赞索菲亚图像影像规范' })).toHaveTextContent('点赞 1'))
expect(global.fetch).toHaveBeenCalledWith(
'/api/department-dashboard/brand-knowledge/knowledge-1/like',
expect.objectContaining({ method: 'PUT', body: JSON.stringify({ active: true }) }),
)
fireEvent.click(screen.getByRole('button', { name: '收藏索菲亚图像影像规范' }))
await waitFor(() => expect(screen.getByRole('button', { name: '取消收藏索菲亚图像影像规范' })).toHaveTextContent('已收藏'))
expect(global.fetch).toHaveBeenCalledWith(
'/api/department-dashboard/brand-knowledge/knowledge-1/favorite',
expect.objectContaining({ method: 'PUT', body: JSON.stringify({ active: true }) }),
)
})
it('shows edit and delete only for super administrators and persists both actions', async () => {
const item = {
id: 'knowledge-1', title: '索菲亚图像影像规范', knowledgeType: '影像规范', brand: '索菲亚',
productCategoryName: '衣柜', fileName: 'guide.pdf', mimeType: 'application/pdf', fileSize: 10,
downloadCount: 0, createdAt: '2026-09-12T00:00:00.000Z', uploadedBy: '无邪',
downloadUrl: '/download', previewUrl: null, previewImageName: null, likeCount: 0,
isLiked: false, isFavorited: false,
}
global.fetch = jest.fn().mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
if (String(input) === '/api/product-categories') {
return Promise.resolve({ ok: true, json: async () => [{ id: 'category-1', name: '床类' }] } as Response)
}
if (init?.method === 'PATCH') {
const metadata = JSON.parse(String(init.body))
return Promise.resolve({ ok: true, json: async () => ({ success: true, data: { id: item.id, ...metadata } }) } as Response)
}
if (init?.method === 'DELETE') {
return Promise.resolve({ ok: true, json: async () => ({ success: true }) } as Response)
}
return Promise.resolve({ ok: true, json: async () => ({ success: true, data: [item], permissions: { canManage: true } }) } as Response)
})
render(<BrandKnowledgeDashboard />)
fireEvent.click(await screen.findByRole('button', { name: '编辑索菲亚图像影像规范' }))
const editDialog = screen.getByRole('dialog', { name: '编辑品牌知识' })
changeField(within(editDialog).getByLabelText('知识标题'), { target: { value: '更新后的规范' } })
changeField(within(editDialog).getByLabelText('知识类型'), { target: { value: '主图模板' } })
changeField(within(editDialog).getByLabelText('关联品牌'), { target: { value: '亿家达' } })
openSelect(within(editDialog).getByLabelText('品类'))
await screen.findByRole('option', { name: '床类' })
changeField(within(editDialog).getByLabelText('品类'), { target: { value: '床类' } })
fireEvent.click(within(editDialog).getByRole('button', { name: '保存修改' }))
await waitFor(() => expect(global.fetch).toHaveBeenCalledWith(
'/api/department-dashboard/brand-knowledge/knowledge-1',
expect.objectContaining({
method: 'PATCH',
body: JSON.stringify({
title: '更新后的规范',
knowledgeType: '主图模板',
brand: '亿家达',
productCategoryName: '床类',
}),
}),
))
expect(await screen.findByRole('heading', { level: 3, name: '更新后的规范' })).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: '删除更新后的规范' }))
const deleteDialog = screen.getByRole('dialog', { name: '删除品牌知识' })
fireEvent.click(within(deleteDialog).getByRole('button', { name: '确认删除' }))
await waitFor(() => expect(screen.queryByRole('heading', { level: 3, name: '更新后的规范' })).not.toBeInTheDocument())
expect(global.fetch).toHaveBeenCalledWith(
'/api/department-dashboard/brand-knowledge/knowledge-1',
{ method: 'DELETE' },
)
})
it('submits a selected knowledge file to the independent API', async () => {
const fetchMock = global.fetch as jest.Mock
fetchMock.mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
if (String(input) === '/api/product-categories') {
return Promise.resolve({ ok: true, json: async () => [{ id: 'category-1', name: '床类' }] } as Response)
}
if (init?.method === 'POST') {
return Promise.resolve({ ok: true, json: async () => ({ success: true, data: {} }) } as Response)
}
return Promise.resolve({ ok: true, json: async () => ({ success: true, data: [] }) } as Response)
})
render(<BrandKnowledgeDashboard />)
const uploadButton = screen.getByRole('button', { name: '上传知识' })
fireEvent.click(uploadButton)
expect(screen.getByRole('dialog', { name: '上传知识' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: '提交知识' })).toBeDisabled()
const knowledgeTypeSelect = screen.getByRole('dialog').querySelector('select') as HTMLSelectElement
expect(Array.from(knowledgeTypeSelect.querySelectorAll('option')).map((option) => option.textContent)).toEqual([...BRAND_KNOWLEDGE_TYPE_OPTIONS])
expect(Array.from(knowledgeTypeSelect.options).map((option) => option.value)).toEqual([
'主图规范', '主图模板', '详情页规范', '详情页模板', 'sku规范',
'sku模板', '主图视频规范', '主图视频模板', '影像规范', '品牌VI',
])
changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
openSelect(screen.getByLabelText(/品类/))
await waitFor(() => expect(screen.getByRole('option', { name: '床类' })).toBeInTheDocument())
changeField(screen.getByRole('combobox', { name: /品类/ }), { target: { value: '床类' } })
const knowledgeFile = new File(['品牌资料'], '品牌手册.pdf', { type: 'application/pdf' })
const previewFile = new File(['fake image'], '预览图.png', { type: 'image/png' })
changeField(screen.getByLabelText('选择知识文件'), { target: { files: [knowledgeFile] } })
changeField(screen.getByLabelText('选择预览图片'), { target: { files: [previewFile] } })
expect(screen.getByText('品牌手册.pdf')).toBeInTheDocument()
expect(await screen.findByText('预览图.png')).toBeInTheDocument()
expect(await screen.findByRole('img', { name: '预览图:预览图.png' })).toHaveClass('object-contain', 'object-center')
expect(screen.getByText(/已切为 2 片/)).toBeInTheDocument()
expect(screen.getByRole('button', { name: '提交知识' })).toBeEnabled()
fireEvent.click(screen.getByRole('button', { name: '提交知识' }))
await waitFor(() => expect(fetchMock).toHaveBeenCalledWith('/api/department-dashboard/brand-knowledge', expect.objectContaining({ method: 'POST', body: expect.any(FormData) })))
expect(mockChunkUpload).toHaveBeenCalledWith(
knowledgeFile,
{ uploadPurpose: 'brand-knowledge' },
expect.objectContaining({
maxRetries: 5,
onProgress: expect.any(Function),
onStageChange: expect.any(Function),
isCancelled: expect.any(Function),
onActiveXhrChange: expect.any(Function),
}),
)
const postCall = fetchMock.mock.calls.find(([, init]) => init?.method === 'POST')
expect(postCall).toBeDefined()
const submittedFormData = postCall?.[1]?.body as FormData
expect(submittedFormData.get('productCategoryName')).toBe('床类')
expect(submittedFormData.get('file')).toBeNull()
expect(submittedFormData.get('uploadId')).toBe('upload-knowledge-1')
expect(submittedFormData.get('existingFilePath')).toBe('/uploads/temp/upload-knowledge-1/merged-brand-guide.pdf')
expect(submittedFormData.get('originalFileName')).toBe('品牌手册.pdf')
expect(submittedFormData.get('fileSize')).toBe(String(knowledgeFile.size))
expect(submittedFormData.get('previewImage')).toBeNull()
expect(submittedFormData.get('previewImageName')).toBe('预览图.png')
expect(submittedFormData.get('previewImageSliceCount')).toBe('2')
expect(submittedFormData.getAll('previewImageSlices').map((entry) => (entry as File).name)).toEqual([
'预览图-001.webp',
'预览图-002.webp',
])
await waitFor(() => expect(screen.queryByRole('dialog', { name: '上传知识' })).not.toBeInTheDocument())
expect(uploadButton).toHaveFocus()
})
it('labels a processed preview with one output as a single image', async () => {
mockPrepareBrandKnowledgePreviewInBrowser.mockResolvedValueOnce([{
file: new File(['single preview'], '预览图.webp', { type: 'image/webp' }),
mimeType: 'image/webp',
width: 1_000,
height: 3_000,
kind: 'slice',
sourceName: '预览图.png',
sourceIndex: 0,
sliceIndex: 0,
sliceCount: 1,
}])
render(<BrandKnowledgeDashboard />)
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
const previewFile = new File(['fake image'], '预览图.png', { type: 'image/png' })
changeField(screen.getByLabelText('选择预览图片'), { target: { files: [previewFile] } })
expect(await screen.findByText('10 B · 单张预览')).toBeInTheDocument()
expect(screen.getByRole('status')).toHaveTextContent('图片高度不超过 3000px,已保留为单张预览')
expect(screen.queryByText(/已切为 1 片/)).not.toBeInTheDocument()
})
it.each([
[413, '上传内容超过服务器允许大小(413)'],
[429, '当前上传任务较多(429)'],
[502, '上传服务网关异常(502)'],
[503, '上传服务暂不可用(503)'],
[504, '服务器处理超时(504)'],
])('shows an actionable message for a non-JSON %i upload response', async (status, expectedMessage) => {
const fetchMock = global.fetch as jest.Mock
fetchMock.mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
if (String(input) === '/api/product-categories') {
return Promise.resolve({ ok: true, json: async () => [] } as Response)
}
if (init?.method === 'POST') {
return Promise.resolve({
ok: false,
status,
json: async () => { throw new Error('non-JSON upstream response') },
} as unknown as Response)
}
return Promise.resolve({ ok: true, json: async () => ({ success: true, data: [] }) } as Response)
})
render(<BrandKnowledgeDashboard />)
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
changeField(screen.getByLabelText('选择知识文件'), {
target: { files: [new File(['guide'], '品牌手册.pdf', { type: 'application/pdf' })] },
})
fireEvent.click(screen.getByRole('button', { name: '提交知识' }))
expect(await screen.findByRole('alert')).toHaveTextContent(expectedMessage)
expect(screen.getByRole('button', { name: '重新上传' })).toBeEnabled()
})
it('retries finalize without uploading the completed main file again', async () => {
const fetchMock = global.fetch as jest.Mock
let postCount = 0
fetchMock.mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
if (String(input) === '/api/product-categories') {
return Promise.resolve({ ok: true, json: async () => [] } as Response)
}
if (init?.method === 'POST') {
postCount += 1
if (postCount === 1) {
return Promise.resolve({
ok: false,
status: 504,
json: async () => { throw new Error('gateway timeout') },
} as unknown as Response)
}
return Promise.resolve({ ok: true, status: 201, json: async () => ({ success: true, data: {} }) } as Response)
}
return Promise.resolve({ ok: true, json: async () => ({ success: true, data: [] }) } as Response)
})
render(<BrandKnowledgeDashboard />)
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
changeField(screen.getByLabelText('选择知识文件'), {
target: { files: [new File(['guide'], '品牌手册.pdf', { type: 'application/pdf' })] },
})
fireEvent.click(screen.getByRole('button', { name: '提交知识' }))
expect(await screen.findByText(/服务器处理超时(504)/)).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: '重新上传' }))
await waitFor(() => expect(screen.queryByRole('dialog', { name: '上传知识' })).not.toBeInTheDocument())
expect(mockChunkUpload).toHaveBeenCalledTimes(1)
expect(postCount).toBe(2)
})
it('restores a completed main-file upload after remount and removes it after successful creation', async () => {
const fetchMock = global.fetch as jest.Mock
let finalizeCount = 0
fetchMock.mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
if (String(input) === '/api/product-categories') {
return Promise.resolve({ ok: true, json: async () => [] } as Response)
}
if (init?.method === 'POST') {
finalizeCount += 1
if (finalizeCount === 1) {
return Promise.resolve({
ok: false,
status: 504,
json: async () => { throw new Error('gateway timeout') },
} as unknown as Response)
}
return Promise.resolve({ ok: true, status: 201, json: async () => ({ success: true, data: {} }) } as Response)
}
return Promise.resolve({ ok: true, json: async () => ({ success: true, data: [] }) } as Response)
})
const file = new File(['guide'], '品牌手册.pdf', {
type: 'application/pdf',
lastModified: 123456,
})
const firstRender = render(<BrandKnowledgeDashboard />)
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
changeField(screen.getByLabelText('选择知识文件'), { target: { files: [file] } })
fireEvent.click(screen.getByRole('button', { name: '提交知识' }))
expect(await screen.findByText(/服务器处理超时(504)/)).toBeInTheDocument()
expect(localStorage.length).toBe(1)
firstRender.unmount()
render(<BrandKnowledgeDashboard />)
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
changeField(screen.getByLabelText('选择知识文件'), { target: { files: [file] } })
expect(within(screen.getByRole('dialog', { name: '上传知识' })).getByRole('status')).toHaveTextContent('已恢复这个文件完成的分片上传')
fireEvent.click(screen.getByRole('button', { name: '提交知识' }))
await waitFor(() => expect(screen.queryByRole('dialog', { name: '上传知识' })).not.toBeInTheDocument())
expect(mockChunkUpload).toHaveBeenCalledTimes(1)
expect(finalizeCount).toBe(2)
expect(localStorage.length).toBe(0)
})
it('shows chunk progress and aborts active requests when the user cancels', async () => {
const abort = jest.fn()
mockChunkUpload.mockImplementation((_file, _metadata, options) => new Promise((_resolve, reject) => {
abort.mockImplementation(() => reject(new Error('上传已取消')))
options.onProgress(37, 2 * 1024 * 1024)
options.onActiveXhrChange(new Set([{ abort }]))
}))
render(<BrandKnowledgeDashboard />)
fireEvent.click(screen.getByRole('button', { name: '上传知识' }))
changeField(screen.getByLabelText('知识标题'), { target: { value: '品牌手册' } })
changeField(screen.getByLabelText('选择知识文件'), {
target: { files: [new File(['guide'], '品牌手册.pdf', { type: 'application/pdf' })] },
})
fireEvent.click(screen.getByRole('button', { name: '提交知识' }))
const progress = await screen.findByRole('progressbar', { name: '主文件上传进度' })
expect(progress).toHaveAttribute('aria-valuenow', '37')
expect(progress).toHaveTextContent('2.0 MB/s')
fireEvent.click(screen.getByRole('button', { name: '取消上传' }))
expect(abort).toHaveBeenCalledTimes(1)
expect(await screen.findByRole('alert')).toHaveTextContent('上传已取消')
expect(screen.getByRole('button', { name: '重新上传' })).toBeEnabled()
})
})