196 lines
7.5 KiB
TypeScript
196 lines
7.5 KiB
TypeScript
import { changeField } from '@/test-utils/select'
|
||
import { fireEvent, render, screen } from '@testing-library/react'
|
||
import type { OnTimeDeliveryWorkflowItem } from '@/lib/ding-workflow-sync'
|
||
import OnTimeDeliveryInsightsView from './OnTimeDeliveryInsights'
|
||
|
||
const mockPush = jest.fn()
|
||
|
||
jest.mock('next/navigation', () => ({
|
||
usePathname: () => '/workspaces/dashboard/core-metrics/on-time-delivery',
|
||
useRouter: () => ({ push: mockPush }),
|
||
}))
|
||
|
||
const ART_PROCESS_CODE = 'PROC-3571C915-0506-4490-A91A-8AA7EA33F838'
|
||
const CONTENT_PROCESS_CODE = 'PROC-AE9FF3E1-A442-4E6A-85CF-0F2B86409673'
|
||
|
||
function buildItem(
|
||
id: string,
|
||
processCode: string,
|
||
assessmentStatus: 'on_time' | 'overtime' | 'pending',
|
||
options: { reviewFinishedAt?: string; assessmentFinishedAt?: string | null } = {},
|
||
): OnTimeDeliveryWorkflowItem {
|
||
const isContent = processCode === CONTENT_PROCESS_CODE
|
||
return {
|
||
id,
|
||
businessId: `BIZ-${id}`,
|
||
title: isContent ? `内容流程-${id}` : `美术流程-${id}`,
|
||
status: 'COMPLETED',
|
||
originatorUserId: `user-${id}`,
|
||
originatorDeptName: '品牌中心',
|
||
sourceCreatedAt: '2026-08-01T00:00:00.000Z',
|
||
sourceUpdatedAt: '2026-08-03T00:00:00.000Z',
|
||
activeTaskCount: 0,
|
||
taskCount: 2,
|
||
schemeFieldCount: 3,
|
||
latestTaskStatus: 'COMPLETED',
|
||
processCode,
|
||
projectType: isContent ? '内容视频和图片拍摄' : '美术设计流程',
|
||
brand: '亿家达',
|
||
requesterName: '测试需求人',
|
||
requesterDepartment: '品牌中心',
|
||
initiatorName: '测试发起人',
|
||
initiatorDepartment: '运营中心A',
|
||
formFields: {
|
||
platform: null,
|
||
brand: null,
|
||
category: null,
|
||
spu: null,
|
||
demandType: null,
|
||
standardOutputDeadline: null,
|
||
sourceType: null,
|
||
taskType: null,
|
||
installVideo: null,
|
||
quantity: null,
|
||
cameraQuantity: null,
|
||
planningTaskStats: null,
|
||
graphicTaskStats: null,
|
||
imageTaskStats: null,
|
||
},
|
||
assignmentNodeLabel: isContent ? '内容主管分配任务' : '品牌策划主管分配任务',
|
||
assignmentStartedAt: '2026-08-01T00:00:00.000Z',
|
||
reviewNodeLabel: isContent ? '编导审核' : '品牌策划主管审核',
|
||
reviewFinishedAt: options.reviewFinishedAt || '2026-08-03T00:00:00.000Z',
|
||
activeTasks: [],
|
||
result: 'AGREE',
|
||
sourceFinishedAt: '2026-08-03T00:00:00.000Z',
|
||
assessment: {
|
||
status: assessmentStatus,
|
||
standardDuration: 3,
|
||
durationUnit: isContent ? 'hour' : 'day',
|
||
actualDuration: assessmentStatus === 'pending' ? null : 2,
|
||
startedAt: '2026-08-01T00:00:00.000Z',
|
||
finishedAt: options.assessmentFinishedAt === undefined
|
||
? '2026-08-03T00:00:00.000Z'
|
||
: options.assessmentFinishedAt,
|
||
startNodeLabel: isContent ? '内容主管分配任务' : '品牌策划主管分配任务',
|
||
endNodeLabel: isContent ? '编导审核' : '品牌策划主管审核',
|
||
},
|
||
}
|
||
}
|
||
|
||
describe('OnTimeDeliveryInsightsView', () => {
|
||
beforeEach(() => mockPush.mockClear())
|
||
|
||
it('按全部、美术和内容流程切换公式、数量与明细', () => {
|
||
render(
|
||
<OnTimeDeliveryInsightsView
|
||
insights={{
|
||
items: [
|
||
buildItem('art-on-time', ART_PROCESS_CODE, 'on_time'),
|
||
buildItem('art-overtime', ART_PROCESS_CODE, 'overtime'),
|
||
buildItem('content-on-time', CONTENT_PROCESS_CODE, 'on_time'),
|
||
],
|
||
summary: {
|
||
onTime: 2,
|
||
overtime: 1,
|
||
pending: 0,
|
||
total: 3,
|
||
rate: 66.7,
|
||
monthStart: '2026-07-31T16:00:00.000Z',
|
||
monthEnd: '2026-08-31T16:00:00.000Z',
|
||
lastSyncedAt: '2026-08-14T02:30:00.000Z',
|
||
syncStatus: 'ready',
|
||
},
|
||
}}
|
||
/>,
|
||
)
|
||
|
||
expect(screen.getByText('当前口径:2 ÷ 3 = 66.7% · 超时 1')).toBeInTheDocument()
|
||
expect(screen.getByRole('tab', { name: '筛选全部流程,共3条' })).toHaveAttribute('aria-selected', 'true')
|
||
|
||
fireEvent.click(screen.getByRole('tab', { name: '筛选内容视频流程,共1条' }))
|
||
expect(screen.getByText('当前口径:1 ÷ 1 = 100% · 超时 0')).toBeInTheDocument()
|
||
expect(screen.getByRole('tab', { name: '筛选内容视频流程,共1条' })).toHaveClass('bg-[#e7f3ff]')
|
||
expect(screen.getByText('内容流程-content-on-time')).toBeInTheDocument()
|
||
expect(screen.queryByText('美术流程-art-on-time')).not.toBeInTheDocument()
|
||
|
||
fireEvent.click(screen.getByRole('tab', { name: '筛选美术设计流程,共2条' }))
|
||
expect(screen.getByText('当前口径:1 ÷ 2 = 50% · 超时 1')).toBeInTheDocument()
|
||
expect(screen.getByText('美术流程-art-overtime')).toBeInTheDocument()
|
||
expect(screen.queryByText('内容流程-content-on-time')).not.toBeInTheDocument()
|
||
})
|
||
|
||
it('排除待计算流程,并只纳入本月审核节点完成的已判定流程', () => {
|
||
render(
|
||
<OnTimeDeliveryInsightsView
|
||
insights={{
|
||
items: [
|
||
buildItem('eligible-on-time', ART_PROCESS_CODE, 'on_time'),
|
||
buildItem('eligible-overtime', ART_PROCESS_CODE, 'overtime'),
|
||
buildItem('pending', CONTENT_PROCESS_CODE, 'pending'),
|
||
buildItem('reviewed-last-month', CONTENT_PROCESS_CODE, 'on_time', {
|
||
reviewFinishedAt: '2026-07-31T15:59:59.000Z',
|
||
assessmentFinishedAt: '2026-07-31T15:59:59.000Z',
|
||
}),
|
||
buildItem('missing-review-time', CONTENT_PROCESS_CODE, 'on_time', {
|
||
assessmentFinishedAt: null,
|
||
}),
|
||
],
|
||
// Deliberately stale to ensure the view derives the metric from the
|
||
// review-node timestamps rather than trusting the upstream summary.
|
||
summary: {
|
||
onTime: 4,
|
||
overtime: 1,
|
||
pending: 1,
|
||
total: 5,
|
||
rate: 80,
|
||
monthStart: '2026-07-31T16:00:00.000Z',
|
||
monthEnd: '2026-08-31T16:00:00.000Z',
|
||
lastSyncedAt: '2026-08-14T02:30:00.000Z',
|
||
syncStatus: 'ready',
|
||
},
|
||
}}
|
||
/>,
|
||
)
|
||
|
||
expect(screen.getByText('当前口径:1 ÷ 2 = 50% · 超时 1')).toBeInTheDocument()
|
||
expect(screen.getByRole('tab', { name: '筛选全部流程,共2条' })).toBeInTheDocument()
|
||
expect(screen.queryByText('内容流程-pending')).not.toBeInTheDocument()
|
||
expect(screen.queryByText('内容流程-reviewed-last-month')).not.toBeInTheDocument()
|
||
expect(screen.queryByText('内容流程-missing-review-time')).not.toBeInTheDocument()
|
||
})
|
||
|
||
it('按月份筛选并更新按时交付明细期间', () => {
|
||
render(
|
||
<OnTimeDeliveryInsightsView
|
||
insights={{
|
||
items: [],
|
||
summary: {
|
||
onTime: 0,
|
||
overtime: 0,
|
||
pending: 0,
|
||
total: 0,
|
||
rate: null,
|
||
monthStart: '2026-07-31T16:00:00.000Z',
|
||
monthEnd: '2026-08-31T16:00:00.000Z',
|
||
lastSyncedAt: null,
|
||
syncStatus: 'ready',
|
||
},
|
||
availableMonths: [
|
||
{ value: '2026-08', label: '本月', startDate: '2026-08-01', endDate: '2026-08-31' },
|
||
{ value: '2026-07', label: '上月', startDate: '2026-07-01', endDate: '2026-07-31' },
|
||
],
|
||
}}
|
||
/>,
|
||
)
|
||
|
||
expect(screen.getByRole('form', { name: '按时交付日期筛选' })).toBeInTheDocument()
|
||
changeField(screen.getByRole('combobox', { name: '选择统计月份' }), {
|
||
target: { value: '2026-07' },
|
||
})
|
||
expect(mockPush).toHaveBeenCalledWith(
|
||
'/workspaces/dashboard/core-metrics/on-time-delivery?month=2026-07',
|
||
)
|
||
})
|
||
})
|