/** * 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 }, })) 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 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() 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() 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() 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) }) })