flnxi-Wu/components/__tests__/UploadModal.test.tsx

248 lines
6.5 KiB
TypeScript

/**
* UploadModal 条件字段测试
* @jest-environment jsdom
*/
import React from 'react'
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'
import '@testing-library/jest-dom'
jest.mock('next/image', () => ({
__esModule: true,
default: (props: any) => {
// eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text
return <img {...props} />
},
}))
jest.mock('@/lib/audio-parser-browser', () => ({
parseAudioFile: jest.fn(),
isAudioFile: jest.fn(() => false),
AUDIO_ACCEPT_TYPES: 'audio/*',
}))
import UploadModal from '../UploadModal'
const mockFetch = jest.fn()
global.fetch = mockFetch
global.URL.createObjectURL = jest.fn(() => 'blob:test-url')
global.URL.revokeObjectURL = jest.fn()
const materialTemplate = {
id: 'template-1',
name: '条件材质模板',
description: null,
materialType: '材质',
materialKind: 'IMAGE',
status: 'active',
isBuiltin: false,
fields: [
{
id: 'field-product',
key: 'productModel',
label: '材质名称',
type: 'text',
required: true,
},
{
id: 'field-conditional',
key: 'conditionalCode',
label: '条件编码',
type: 'text',
required: true,
conditions: JSON.stringify({
logic: 'AND',
conditions: [{ fieldKey: 'productModel', operator: 'equals', value: '需要编码' }],
}),
},
],
}
const checkboxTemplate = {
...materialTemplate,
fields: [
{
id: 'field-product',
key: 'productModel',
label: '材质名称',
type: 'text',
required: true,
},
{
id: 'field-checkbox',
key: 'allowUse',
label: '允许使用',
type: 'checkbox',
required: true,
},
],
}
class MockXMLHttpRequest {
static instances: MockXMLHttpRequest[] = []
upload = {
addEventListener: jest.fn(),
}
timeout = 0
status = 201
responseText = '{"success":true}'
method = ''
url = ''
body: FormData | null = null
private listeners: Record<string, Array<() => void>> = {}
constructor() {
MockXMLHttpRequest.instances.push(this)
}
open(method: string, url: string) {
this.method = method
this.url = url
}
send(body: FormData) {
this.body = body
this.listeners.load?.forEach((listener) => listener())
}
abort() {
this.listeners.abort?.forEach((listener) => listener())
}
addEventListener(event: string, listener: () => void) {
this.listeners[event] = this.listeners[event] || []
this.listeners[event].push(listener)
}
}
describe('UploadModal 条件字段', () => {
const onClose = jest.fn()
const onSuccess = jest.fn()
beforeEach(() => {
jest.clearAllMocks()
MockXMLHttpRequest.instances = []
;(global as any).XMLHttpRequest = MockXMLHttpRequest
window.localStorage.clear()
mockFetch.mockImplementation((url: string) => {
if (url.includes('/api/upload-templates')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve([materialTemplate]),
})
}
if (
url.includes('/api/template-categories') ||
url.includes('/api/categories') ||
url.includes('/api/scenes') ||
url.includes('/api/platforms')
) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve([]),
})
}
return Promise.resolve({
ok: false,
json: () => Promise.resolve({ error: 'Not found' }),
})
})
})
it('条件不满足时应隐藏字段且不提交隐藏字段', async () => {
render(<UploadModal onClose={onClose} onSuccess={onSuccess} />)
await waitFor(() => {
expect(screen.getByLabelText('材质名称')).toBeInTheDocument()
})
expect(screen.queryByLabelText('条件编码')).not.toBeInTheDocument()
fireEvent.change(screen.getByLabelText('材质名称'), {
target: { value: '普通材质' },
})
await act(async () => {
fireEvent.click(screen.getByText('上传'))
})
await waitFor(() => {
expect(MockXMLHttpRequest.instances.length).toBe(1)
})
const sentBody = MockXMLHttpRequest.instances[0].body
expect(sentBody?.get('templateId')).toBe('template-1')
const templateFields = JSON.parse(String(sentBody?.get('templateFields')))
expect(templateFields.productModel).toBe('普通材质')
expect(templateFields.conditionalCode).toBeUndefined()
})
it('依赖字段变化后应显示条件字段并参与必填校验', async () => {
render(<UploadModal onClose={onClose} onSuccess={onSuccess} />)
const productInput = await screen.findByLabelText('材质名称')
fireEvent.change(productInput, { target: { value: '需要编码' } })
await waitFor(() => {
expect(screen.getByLabelText('条件编码')).toBeInTheDocument()
})
await act(async () => {
fireEvent.click(screen.getByText('上传'))
})
expect(await screen.findByText('请填写必填字段:条件编码')).toBeInTheDocument()
expect(MockXMLHttpRequest.instances.length).toBe(0)
})
it('必填 checkbox 未勾选也应视为已填写并允许提交 false', async () => {
mockFetch.mockImplementation((url: string) => {
if (url.includes('/api/upload-templates')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve([checkboxTemplate]),
})
}
if (
url.includes('/api/template-categories') ||
url.includes('/api/categories') ||
url.includes('/api/scenes') ||
url.includes('/api/platforms')
) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve([]),
})
}
return Promise.resolve({
ok: false,
json: () => Promise.resolve({ error: 'Not found' }),
})
})
render(<UploadModal onClose={onClose} onSuccess={onSuccess} />)
const productInput = await screen.findByLabelText('材质名称')
fireEvent.change(productInput, { target: { value: '普通材质' } })
expect(screen.getByLabelText('允许使用')).not.toBeChecked()
await act(async () => {
fireEvent.click(screen.getByText('上传'))
})
await waitFor(() => {
expect(MockXMLHttpRequest.instances.length).toBe(1)
})
expect(screen.queryByText('请填写必填字段:允许使用')).not.toBeInTheDocument()
const sentBody = MockXMLHttpRequest.instances[0].body
const templateFields = JSON.parse(String(sentBody?.get('templateFields')))
expect(templateFields.allowUse).toBe(false)
})
})