180 lines
5.7 KiB
TypeScript
180 lines
5.7 KiB
TypeScript
/** @jest-environment jsdom */
|
|
|
|
import { act, render, screen, waitFor } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { useSession } from 'next-auth/react'
|
|
import EditMaterialPage from '@/app/edit/[id]/page'
|
|
|
|
const mockPush = jest.fn()
|
|
const mockBack = jest.fn()
|
|
const mockRouter = {
|
|
push: mockPush,
|
|
back: mockBack,
|
|
}
|
|
|
|
jest.mock('next-auth/react', () => ({
|
|
useSession: jest.fn(),
|
|
}))
|
|
|
|
jest.mock('next/navigation', () => ({
|
|
useRouter: () => mockRouter,
|
|
}))
|
|
|
|
jest.mock('@/components/Header', () => ({
|
|
__esModule: true,
|
|
default: () => <a href="/">返回素材库</a>,
|
|
}))
|
|
|
|
jest.mock('@/components/upload/TaskReferenceFields', () => ({
|
|
__esModule: true,
|
|
default: () => null,
|
|
}))
|
|
|
|
const material = {
|
|
id: 'material-1',
|
|
userId: 'user-1',
|
|
productModel: 'SPU-001',
|
|
reportTitle: null,
|
|
categoryName: '店铺A',
|
|
sceneName: '场景A',
|
|
platformName: 'Amazon',
|
|
productCategoryName: '',
|
|
materialType: '3D场景',
|
|
renderer: 'CR',
|
|
authorName: '测试作者',
|
|
previewUrl: null,
|
|
taskNo: null,
|
|
dingtalkApprovalNo: null,
|
|
taskStatus: null,
|
|
taskFetchedAt: null,
|
|
isFullSet: false,
|
|
taskDetailSourceType: null,
|
|
taskDetailRequesterUserId: null,
|
|
taskDetailRequesterName: null,
|
|
canEdit: true,
|
|
}
|
|
|
|
function jsonResponse(payload: unknown, status = 200) {
|
|
return Promise.resolve({
|
|
ok: status >= 200 && status < 300,
|
|
status,
|
|
json: async () => payload,
|
|
} as Response)
|
|
}
|
|
|
|
function referencePayload(url: string) {
|
|
if (url.endsWith('/api/categories')) {
|
|
return [{ id: 'category-1', name: '店铺A' }]
|
|
}
|
|
if (url.endsWith('/api/scenes')) {
|
|
return [{ id: 'scene-1', name: '场景A' }]
|
|
}
|
|
if (url.endsWith('/api/platforms')) {
|
|
return [{ id: 'platform-1', name: 'Amazon', count: 1 }]
|
|
}
|
|
if (url.endsWith('/api/product-categories')) {
|
|
return []
|
|
}
|
|
return [{ id: 'user-1', username: 'author', displayName: '测试作者' }]
|
|
}
|
|
|
|
describe('EditMaterialPage', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
;(useSession as jest.Mock).mockReturnValue({
|
|
data: { user: { id: 'user-1', role: 'USER' } },
|
|
status: 'authenticated',
|
|
})
|
|
Element.prototype.scrollIntoView = jest.fn()
|
|
})
|
|
|
|
test('素材本体先于可选项显示,选项完成前禁止保存', async () => {
|
|
let releaseReferences: (() => void) | undefined
|
|
const referenceGate = new Promise<void>((resolve) => {
|
|
releaseReferences = resolve
|
|
})
|
|
global.fetch = jest.fn((input: RequestInfo | URL) => {
|
|
const url = String(input)
|
|
if (url.includes('/api/materials/material-1')) {
|
|
return jsonResponse(material)
|
|
}
|
|
return referenceGate.then(() => jsonResponse(referencePayload(url)))
|
|
}) as jest.Mock
|
|
|
|
render(<EditMaterialPage params={{ id: 'material-1' }} />)
|
|
|
|
expect(await screen.findByLabelText('产品型号 *')).toHaveValue('SPU-001')
|
|
expect(screen.getByText('正在加载店铺、场景和人员选项…')).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: '保存修改' })).toBeDisabled()
|
|
|
|
await act(async () => {
|
|
releaseReferences?.()
|
|
await referenceGate
|
|
})
|
|
|
|
await waitFor(() => {
|
|
expect(screen.queryByText('正在加载店铺、场景和人员选项…')).not.toBeInTheDocument()
|
|
})
|
|
expect(screen.getByRole('button', { name: '保存修改' })).toBeEnabled()
|
|
})
|
|
|
|
test('部分选项失败时保留当前值、显示重试并禁止保存', async () => {
|
|
global.fetch = jest.fn((input: RequestInfo | URL) => {
|
|
const url = String(input)
|
|
if (url.includes('/api/materials/material-1')) {
|
|
return jsonResponse(material)
|
|
}
|
|
if (url.endsWith('/api/categories')) {
|
|
return jsonResponse({ error: '店铺读取失败' }, 500)
|
|
}
|
|
return jsonResponse(referencePayload(url))
|
|
}) as jest.Mock
|
|
|
|
render(<EditMaterialPage params={{ id: 'material-1' }} />)
|
|
|
|
const categorySelect = await screen.findByLabelText('店铺 *')
|
|
await screen.findByText('店铺加载失败,当前值已保留。')
|
|
|
|
expect(categorySelect).toHaveValue('店铺A')
|
|
expect(categorySelect).toBeDisabled()
|
|
expect(screen.getByRole('button', { name: '重新加载' })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: '保存修改' })).toBeDisabled()
|
|
})
|
|
|
|
test('修改后取消会使用统一弹窗,刷新保护与移动端粘性操作区同时生效', async () => {
|
|
const user = userEvent.setup()
|
|
global.fetch = jest.fn((input: RequestInfo | URL) => {
|
|
const url = String(input)
|
|
if (url.includes('/api/materials/material-1')) {
|
|
return jsonResponse(material)
|
|
}
|
|
return jsonResponse(referencePayload(url))
|
|
}) as jest.Mock
|
|
|
|
render(<EditMaterialPage params={{ id: 'material-1' }} />)
|
|
|
|
const productModelInput = await screen.findByLabelText('产品型号 *')
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('button', { name: '保存修改' })).toBeEnabled()
|
|
})
|
|
await user.clear(productModelInput)
|
|
await user.type(productModelInput, 'SPU-002')
|
|
|
|
const beforeUnloadEvent = new Event('beforeunload', { cancelable: true })
|
|
window.dispatchEvent(beforeUnloadEvent)
|
|
expect(beforeUnloadEvent.defaultPrevented).toBe(true)
|
|
|
|
const saveButton = screen.getByRole('button', { name: '保存修改' })
|
|
expect(saveButton.closest('.sticky')).not.toBeNull()
|
|
|
|
await user.click(screen.getByRole('button', { name: '取消' }))
|
|
expect(await screen.findByText('放弃未保存的修改?')).toBeInTheDocument()
|
|
await user.click(screen.getByRole('button', { name: '继续编辑' }))
|
|
expect(mockBack).not.toHaveBeenCalled()
|
|
|
|
await user.click(screen.getByRole('button', { name: '取消' }))
|
|
await user.click(await screen.findByRole('button', { name: '放弃修改' }))
|
|
expect(mockBack).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|