gongxiangp2/components/upload/ProductPackageUploader.layout.test.tsx
2026-08-31 18:19:26 +08:00

194 lines
7.1 KiB
TypeScript

import { render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import ProductPackageUploader from '@/components/upload/ProductPackageUploader'
const mockAddCategory = jest.fn()
const mockAddScene = jest.fn()
jest.mock('@/components/upload/category-handlers', () => ({
addCategory: (...args: unknown[]) => mockAddCategory(...args),
addScene: (...args: unknown[]) => mockAddScene(...args),
}))
jest.mock('@/components/upload/DetailImageSorter', () => ({
__esModule: true,
default: () => <div data-testid="detail-image-sorter" />,
}))
const defaultProps = {
onSuccess: jest.fn(),
onCancel: jest.fn(),
categories: [
{ id: 'cat-1', name: '店铺A' },
{ id: 'cat-2', name: '店铺B' },
],
scenes: [
{ id: 'scene-1', name: '场景A' },
{ id: 'scene-2', name: '场景B' },
],
platforms: [
{ id: 'platform-1', name: '淘宝' },
{ id: 'platform-2', name: '天猫' },
],
productCategories: [
{ id: 'pc-1', name: '沙发' },
{ id: 'pc-2', name: '餐桌' },
],
onRefreshCategories: jest.fn().mockResolvedValue(undefined),
onRefreshScenes: jest.fn().mockResolvedValue(undefined),
}
function installUsersFetch() {
const fetchMock = jest.fn((input: RequestInfo | URL) => {
const url = String(input)
if (url === '/api/users/list') {
return Promise.resolve({
ok: true,
json: async () => [
{ id: 'requester-1', username: 'operator-a', displayName: '运营甲' },
{ id: 'requester-2', username: 'operator-b', displayName: '运营乙' },
],
})
}
return Promise.reject(new Error(`测试未处理请求:${url}`))
})
global.fetch = fetchMock as unknown as typeof fetch
return fetchMock
}
async function renderUploader() {
render(<ProductPackageUploader {...defaultProps} />)
await screen.findByPlaceholderText('搜索发起本次出图需求的运营人员')
}
describe('ProductPackageUploader compact layout interactions', () => {
const originalFetch = global.fetch
beforeEach(() => {
jest.clearAllMocks()
installUsersFetch()
mockAddCategory.mockImplementation(async (
name: string,
onSuccess?: (category: { id: string; name: string }) => Promise<void> | void,
) => {
await onSuccess?.({ id: 'cat-new', name })
})
mockAddScene.mockImplementation(async (
name: string,
onSuccess?: (scene: { id: string; name: string }) => Promise<void> | void,
) => {
await onSuccess?.({ id: 'scene-new', name })
})
})
afterAll(() => {
global.fetch = originalFetch
})
it('需求运营标签可访问,真实 UploadForm 的 SPU 品类 店铺 场景 平台字段仍可用', async () => {
const user = userEvent.setup()
await renderUploader()
const requester = screen.getByLabelText(/需求运营/) as HTMLInputElement
expect(requester).toBeInTheDocument()
await user.click(requester)
await user.type(requester, '运营甲')
await user.click(screen.getByRole('option', { name: /运营甲/ }))
expect(requester).toHaveValue('运营甲 (operator-a)')
const productModel = screen.getByLabelText(/SPU型号/) as HTMLInputElement
await user.type(productModel, 'df-a3dz0008')
expect(productModel).toHaveValue('DF-A3DZ0008')
await user.selectOptions(screen.getByLabelText(/品类/), '沙发')
expect(screen.getByLabelText(/品类/)).toHaveValue('沙发')
await user.selectOptions(screen.getByLabelText(/使用店铺/), '店铺B')
expect(screen.getByLabelText(/使用店铺/)).toHaveValue('店铺B')
await user.selectOptions(screen.getByLabelText(/场景类别/), '场景A')
expect(screen.getByLabelText(/场景类别/)).toHaveValue('场景A')
await user.selectOptions(screen.getByLabelText(/平台分类/), '淘宝')
expect(screen.getByLabelText(/平台分类/)).toHaveValue('淘宝')
})
it('内联新建店铺和场景输入可打开、添加、取消', async () => {
const user = userEvent.setup()
await renderUploader()
await user.click(screen.getByRole('button', { name: '新建店铺' }))
const categoryInput = screen.getByPlaceholderText('输入新店铺名称')
await user.type(categoryInput, '新店铺C')
await user.click(screen.getByRole('button', { name: '添加' }))
await waitFor(() => expect(mockAddCategory).toHaveBeenCalled())
expect(mockAddCategory).toHaveBeenCalledWith(
'新店铺C',
expect.any(Function),
expect.any(Function),
)
expect(defaultProps.onRefreshCategories).toHaveBeenCalled()
expect(screen.queryByPlaceholderText('输入新店铺名称')).not.toBeInTheDocument()
await user.click(screen.getByRole('button', { name: '新建场景' }))
const sceneInput = screen.getByPlaceholderText('输入新场景类别')
await user.type(sceneInput, '新场景C')
await user.click(within(sceneInput.parentElement as HTMLElement).getByRole('button', { name: '取消' }))
expect(screen.queryByPlaceholderText('输入新场景类别')).not.toBeInTheDocument()
expect(mockAddScene).not.toHaveBeenCalled()
})
it('新品只能公开,优化时才可选私密,切回新品会恢复公开', async () => {
const user = userEvent.setup()
await renderUploader()
const demandType = screen.getByLabelText(/需求类型/) as HTMLSelectElement
const visibility = screen.getByLabelText(/可见性/) as HTMLSelectElement
const privateOption = screen.getByRole('option', { name: '私密' }) as HTMLOptionElement
expect(demandType.value).toBe('新品')
expect(visibility.value).toBe('public')
expect(privateOption.disabled).toBe(true)
expect(screen.getByText(/新品固定公开,仅优化需求支持私密/)).toBeInTheDocument()
await user.selectOptions(demandType, '优化')
expect(privateOption.disabled).toBe(false)
expect(screen.getByText('私密仅所选需求运营及已授权权限组可访问,上传人不自动放行。')).toBeInTheDocument()
await user.selectOptions(visibility, 'private')
expect(visibility.value).toBe('private')
await user.selectOptions(demandType, '新品')
expect(visibility.value).toBe('public')
expect(privateOption.disabled).toBe(true)
})
it('四类文件选择入口都存在', async () => {
await renderUploader()
expect(screen.getByLabelText('主图')).toBeInTheDocument()
expect(screen.getByLabelText('SKU图')).toBeInTheDocument()
expect(screen.getByLabelText('详情图')).toBeInTheDocument()
expect(screen.getByLabelText(/PSD源文件/)).toBeInTheDocument()
})
it('校验失败时将错误提示滚动到可见位置', async () => {
const original = HTMLElement.prototype.scrollIntoView
const scrollIntoView = jest.fn()
HTMLElement.prototype.scrollIntoView = scrollIntoView
try {
const user = userEvent.setup()
await renderUploader()
await user.click(screen.getByRole('button', { name: '提交', exact: true }))
expect(screen.getByRole('alert')).toHaveTextContent('请选择需求运营人员')
expect(scrollIntoView).toHaveBeenCalledWith({ block: 'nearest' })
} finally {
HTMLElement.prototype.scrollIntoView = original
}
})
})