2026-04-17 12:50:06 +08:00

362 lines
11 KiB
TypeScript

import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import type { ReactNode } from 'react'
import AssistedAssemblyClient from './AssistedAssemblyClient'
import { createDraftGroup } from '@/lib/open-product/platform-api'
import type { AssemblyWorkspaceSnapshot } from '@/lib/open-product/assembly-workspace'
let mockSearchParams = new URLSearchParams()
jest.mock('next/link', () => ({
__esModule: true,
default: ({ href, children }: { href: string; children: ReactNode }) => <a href={href}>{children}</a>,
}))
const push = jest.fn()
jest.mock('next/navigation', () => ({
useRouter: () => ({ push }),
useSearchParams: () => mockSearchParams,
}))
jest.mock('@/components/platform/AssemblyWorkbenchV2', () => ({
__esModule: true,
AssemblyWorkbenchV2: ({
value,
onChange,
rightDrawerContent,
}: {
value: AssemblyWorkspaceSnapshot
onChange: (next: AssemblyWorkspaceSnapshot) => void
rightDrawerContent?: ReactNode
}) => (
<div>
<div>{rightDrawerContent}</div>
<div>辅助拼装工作台</div>
<div>库存:{value.inventory.length}</div>
<div>节点:{value.nodes.length}</div>
<div>首个规格款:{value.inventory[0]?.selectedVariantId ?? 'none'}</div>
<button
type="button"
onClick={() =>
onChange({
...value,
nodes: [
{
nodeId: 'node-desktop-1',
role: 'desktop',
label: '桌面板',
assetFamilyId: 'family-board-1',
variantId: 'variant-board-1',
code: 'BOARD-A-001',
name: '桌面A',
modelUrl: '/board.glb',
thumbnailUrl: null,
position: [0, 720, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
zone: 'main',
source: 'desktop',
inventoryId: value.inventory[0]?.inventoryId ?? 'desktop:family-board-1:1',
slotKey: 'desktop-1',
roleRecommendation: 'desktop',
roleConfirmed: 'desktop',
weakSlot: { role: 'desktop', parentRolePath: null, instanceIndex: 1 },
targetPosition: [0, 720, 0],
targetRotation: [0, 0, 0],
isSnapped: true,
subassemblyId: null,
mountPoints: [],
assemblyBasis: { anchor: 'desktop-center' },
compatibilityRules: null,
materialConfig: null,
},
],
validation: {
level: 'PASSED',
blockingIssues: [],
warnings: [],
},
})
}
>
准备可保存工作台
</button>
</div>
),
}))
jest.mock('@/lib/open-product/platform-api', () => ({
__esModule: true,
createDraftGroup: jest.fn(),
}))
describe('AssistedAssemblyClient', () => {
beforeEach(() => {
push.mockReset()
jest.mocked(createDraftGroup).mockReset()
mockSearchParams = new URLSearchParams()
})
it('默认会预选当前品类下的最新资产族,且调整选择后会同步到辅助拼装工作台', async () => {
const actor = userEvent.setup()
render(
<AssistedAssemblyClient
families={[
{
id: 'family-board-1',
type: 'BOARD',
code: 'BOARD-A',
name: '桌面A',
productCategoryId: 'category-desk',
productCategoryName: '书桌',
} as never,
{
id: 'family-leg-1',
type: 'COMPONENT',
code: 'LEG-A',
name: '桌腿A',
productCategoryId: 'category-desk',
productCategoryName: '书桌',
} as never,
]}
variants={[
{
id: 'variant-board-1',
assetFamilyId: 'family-board-1',
assetType: 'BOARD',
code: 'BOARD-A-001',
name: '桌面A',
status: 'AVAILABLE',
modelUrl: '/board.glb',
thumbnailUrl: null,
dimensions: {},
mountPoints: [],
assemblyBasis: { anchor: 'desktop-center' },
compatibilityRules: null,
} as never,
{
id: 'variant-leg-1',
assetFamilyId: 'family-leg-1',
assetType: 'COMPONENT',
code: 'LEG-A-001',
name: '桌腿A',
status: 'AVAILABLE',
modelUrl: '/leg.glb',
thumbnailUrl: null,
dimensions: {},
mountPoints: [{ name: 'origin', position: [0, 0, 0] }],
assemblyBasis: { attachTo: 'desktop' },
compatibilityRules: { allowedSlots: ['frontLeftLeg'] },
} as never,
]}
metrics={[]}
/>,
)
expect(screen.getByText('辅助拼装工作台')).toBeInTheDocument()
expect(screen.getByText('库存:2')).toBeInTheDocument()
await actor.click(screen.getByRole('button', { name: '支撑组件' }))
await actor.click(screen.getByRole('button', { name: /桌腿A/ }))
await waitFor(() => {
expect(screen.getByText('库存:1')).toBeInTheDocument()
})
await actor.click(screen.getByRole('button', { name: /桌腿A/ }))
await waitFor(() => {
expect(screen.getByText('库存:2')).toBeInTheDocument()
})
})
it('保存草稿组时会发送去模板化后的辅助拼装输入', async () => {
const actor = userEvent.setup()
jest.mocked(createDraftGroup).mockResolvedValue({
draftGroup: { id: 'draft-group-1' },
} as never)
render(
<AssistedAssemblyClient
families={[
{
id: 'family-board-1',
type: 'BOARD',
code: 'BOARD-A',
name: '桌面A',
productCategoryId: 'category-desk',
productCategoryName: '书桌',
} as never,
]}
variants={[
{
id: 'variant-board-1',
assetFamilyId: 'family-board-1',
assetType: 'BOARD',
code: 'BOARD-A-001',
name: '桌面A',
status: 'AVAILABLE',
modelUrl: '/board.glb',
thumbnailUrl: null,
dimensions: {},
mountPoints: [],
assemblyBasis: { anchor: 'desktop-center' },
compatibilityRules: null,
} as never,
]}
metrics={[]}
/>,
)
await actor.click(screen.getByRole('button', { name: /准备可保存工作台/ }))
await actor.clear(screen.getByLabelText('草稿组名称'))
await actor.type(screen.getByLabelText('草稿组名称'), '测试草稿组')
await actor.click(screen.getByRole('button', { name: /仅保存草稿/ }))
await waitFor(() => {
expect(createDraftGroup).toHaveBeenCalledWith(
expect.objectContaining({
categoryId: 'category-desk',
assemblies: [
expect.objectContaining({
source: 'assisted-assembly',
}),
],
}),
)
})
expect(jest.mocked(createDraftGroup).mock.calls[0]?.[0]).not.toHaveProperty('seriesTemplateId')
})
it('资产中心带料进入时,会保留来源资产族并保留草稿规格款作为默认选择', async () => {
const actor = userEvent.setup()
mockSearchParams = new URLSearchParams(
'entryMode=asset-center&source=asset-center&desktopFamilyIds=family-board-draft',
)
render(
<AssistedAssemblyClient
families={[
{
id: 'family-board-draft',
type: 'BOARD',
code: 'BOARD-DRAFT',
name: '草稿桌面',
status: 'DRAFT',
productCategoryId: 'category-desk',
productCategoryName: '书桌',
} as never,
]}
variants={[
{
id: 'variant-board-draft',
assetFamilyId: 'family-board-draft',
assetType: 'BOARD',
code: 'BOARD-DRAFT-001',
name: '草稿桌面规格款',
status: 'DRAFT',
modelUrl: '/board-draft.glb',
thumbnailUrl: null,
dimensions: {},
mountPoints: [],
assemblyBasis: { anchor: 'desktop-center' },
compatibilityRules: null,
} as never,
]}
metrics={[]}
/>,
)
await waitFor(() => {
expect(screen.getByText('辅助拼装工作台')).toBeInTheDocument()
expect(screen.getByText('库存:1')).toBeInTheDocument()
expect(screen.getByText('首个规格款:variant-board-draft')).toBeInTheDocument()
})
await actor.click(screen.getByRole('button', { name: '板件资产' }))
await waitFor(() => {
expect(screen.getByRole('button', { name: /草稿桌面/ })).toHaveAttribute('aria-pressed', 'true')
})
})
it('源 SKU 新增进入时,只预选来源装配涉及的资产族,不会回退成全量默认选中', async () => {
const actor = userEvent.setup()
mockSearchParams = new URLSearchParams(
'entryMode=source-sku&source=assisted-assembly&sourceSkuId=sku-source-1&desktopFamilyIds=family-board-source',
)
render(
<AssistedAssemblyClient
families={[
{
id: 'family-board-source',
type: 'BOARD',
code: 'BOARD-SOURCE',
name: '源桌面',
status: 'AVAILABLE',
productCategoryId: 'category-desk',
productCategoryName: '书桌',
} as never,
{
id: 'family-board-extra',
type: 'BOARD',
code: 'BOARD-EXTRA',
name: '额外桌面',
status: 'AVAILABLE',
productCategoryId: 'category-desk',
productCategoryName: '书桌',
} as never,
]}
variants={[
{
id: 'variant-board-source',
assetFamilyId: 'family-board-source',
assetType: 'BOARD',
code: 'BOARD-SOURCE-001',
name: '源桌面规格款',
status: 'AVAILABLE',
modelUrl: '/board-source.glb',
thumbnailUrl: null,
dimensions: {},
mountPoints: [],
assemblyBasis: { anchor: 'desktop-center' },
compatibilityRules: null,
} as never,
{
id: 'variant-board-extra',
assetFamilyId: 'family-board-extra',
assetType: 'BOARD',
code: 'BOARD-EXTRA-001',
name: '额外桌面规格款',
status: 'AVAILABLE',
modelUrl: '/board-extra.glb',
thumbnailUrl: null,
dimensions: {},
mountPoints: [],
assemblyBasis: { anchor: 'desktop-center' },
compatibilityRules: null,
} as never,
]}
metrics={[]}
/>,
)
await waitFor(() => {
expect(screen.getByText('库存:1')).toBeInTheDocument()
expect(screen.getByText('首个规格款:variant-board-source')).toBeInTheDocument()
})
await actor.click(screen.getByRole('button', { name: '板件资产' }))
await waitFor(() => {
expect(screen.getByRole('button', { name: /源桌面/ })).toHaveAttribute('aria-pressed', 'true')
expect(screen.getByRole('button', { name: /额外桌面/ })).toHaveAttribute('aria-pressed', 'false')
})
})
})