gongxiangp2/app/performance-survey/PerformanceSurveyDashboardNavigation.test.tsx

782 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** @jest-environment jsdom */
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import PerformanceSurveyPage from './[[...path]]/page'
const mockRouterReplace = jest.fn()
const mockRouterPush = jest.fn()
const mockChoosePerformanceSurveyExportDestination = jest.fn()
const mockSavePerformanceSurveyDashboardWorkbook = jest.fn()
let mockSearchParams = new URLSearchParams()
jest.mock('next/navigation', () => ({
useParams: () => ({ path: [] }),
useRouter: () => ({
push: mockRouterPush,
replace: mockRouterReplace,
}),
useSearchParams: () => mockSearchParams,
}))
jest.mock('next-auth/react', () => ({
useSession: () => ({
data: {
user: {
id: 'user-admin',
name: '管理员',
email: 'admin@example.com',
},
expires: '2099-01-01T00:00:00.000Z',
},
status: 'authenticated',
}),
}))
jest.mock('@/lib/performance-survey-dashboard-export', () => ({
choosePerformanceSurveyExportDestination: (...args: unknown[]) => (
mockChoosePerformanceSurveyExportDestination(...args)
),
sanitizePerformanceSurveyExportFileName: (value: string) => `${value}.xlsx`,
savePerformanceSurveyDashboardWorkbook: (...args: unknown[]) => (
mockSavePerformanceSurveyDashboardWorkbook(...args)
),
}))
const templateDefinition = {
code: 'visual_review',
name: '视觉案例评分',
shortName: '视觉评分',
description: '视觉案例评分',
surveyType: 'visual_case',
weight: 1,
maxTotal: 60,
questionMaxScore: 20,
requiredWorkItemCount: 3,
workItemImageRequired: true,
questions: [{ id: 'quality', title: '作品质量', maxScore: 20 }],
scoreBands: [],
workflow: {
stages: [{
key: 'review',
label: '终评',
order: 0,
reviewers: [{ source: 'direct_manager', label: '直属主管' }],
}],
scoring: { mode: 'final_reviewer', stageKey: 'review', reviewerSource: 'direct_manager' },
},
}
const adminConfig = {
month: '2026-07',
groups: [{
id: 'group-1',
code: 'brand_designer',
name: '品牌设计师',
sortOrder: 10,
isActive: true,
status: 'ready',
memberCount: 2,
rules: [],
effectivePlan: {
id: 'plan-1',
version: 1,
effectiveMonth: '2026-07',
templateVersionIds: ['version-1'],
},
draftPlan: null,
}],
templates: [{
id: 'template-1',
code: 'visual_review',
name: '视觉案例评分',
description: '视觉案例评分',
surveyType: 'visual_case',
isActive: true,
versions: [{
id: 'version-1',
version: 1,
status: 'published',
effectiveMonth: '2026-07',
definition: templateDefinition,
}],
}],
issues: {
unmatched: [],
conflicts: [],
},
}
function jsonResponse(data: unknown) {
return {
ok: true,
status: 200,
headers: { get: () => 'application/json' },
json: async () => ({ success: true, data }),
text: async () => '',
} as unknown as Response
}
function jsonErrorResponse(error: string, status = 409) {
return {
ok: false,
status,
headers: { get: () => 'application/json' },
json: async () => ({ success: false, error }),
text: async () => '',
} as unknown as Response
}
describe('考核调研顶层导航保护', () => {
const originalFetch = global.fetch
beforeEach(() => {
mockRouterReplace.mockReset()
mockRouterPush.mockReset()
mockChoosePerformanceSurveyExportDestination.mockReset()
mockChoosePerformanceSurveyExportDestination.mockResolvedValue({ kind: 'download' })
mockSavePerformanceSurveyDashboardWorkbook.mockReset()
mockSavePerformanceSurveyDashboardWorkbook.mockResolvedValue(undefined)
mockSearchParams = new URLSearchParams('tab=manage&adminWorkspace=positions&adminMonth=2026-07')
global.fetch = jest.fn(async (input) => {
const url = String(input)
if (url.includes('/admin/config-audit-logs')) return jsonResponse({ items: [] })
if (url.includes('/admin/config?')) return jsonResponse(adminConfig)
if (url.endsWith('/me')) {
return jsonResponse({
user: { id: 'user-admin', name: '管理员', email: 'admin@example.com' },
employee: { id: 'employee-admin', displayName: '管理员' },
permissions: {
canCreate: true,
isSystemAdmin: true,
isDepartmentAdmin: false,
isLeadership: false,
},
})
}
if (url.includes('/departments?purpose=evaluator')) return jsonResponse([])
if (url.endsWith('/surveys')) return jsonResponse([])
return jsonResponse([])
}) as jest.Mock
Object.defineProperty(window, 'matchMedia', {
configurable: true,
value: jest.fn().mockReturnValue({ matches: false }),
})
})
afterEach(() => {
global.fetch = originalFetch
jest.restoreAllMocks()
})
test('岗位存在未保存修改时切换个人看板会使用统一弹窗保护', async () => {
render(<PerformanceSurveyPage />)
const nameInput = await screen.findByLabelText('岗位名称')
const requestedUrls = (global.fetch as jest.Mock).mock.calls.map(([input]) => String(input))
expect(requestedUrls).toContain(
'/api/performance-survey/admin/config?month=2026-07&scope=positions',
)
expect(requestedUrls.some((url) => url.includes('/departments?purpose=evaluator'))).toBe(false)
expect(requestedUrls.some((url) => url.endsWith('/surveys'))).toBe(false)
fireEvent.change(nameInput, { target: { value: '品牌设计师(修改中)' } })
expect(await screen.findByText('未保存')).toBeInTheDocument()
fireEvent.click(screen.getAllByRole('button', { name: '个人看板' })[0])
const firstDialog = await screen.findByRole('alertdialog', { name: '放弃尚未保存的修改?' })
fireEvent.click(within(firstDialog).getByRole('button', { name: '取消' }))
await waitFor(() => expect(screen.queryByRole('alertdialog', { name: '放弃尚未保存的修改?' })).not.toBeInTheDocument())
expect(mockRouterReplace).not.toHaveBeenCalledWith(
expect.stringContaining('tab=personal'),
expect.anything(),
)
fireEvent.click(screen.getAllByRole('button', { name: '个人看板' })[0])
const secondDialog = await screen.findByRole('alertdialog', { name: '放弃尚未保存的修改?' })
fireEvent.click(within(secondDialog).getByRole('button', { name: '放弃并离开' }))
await waitFor(() => expect(mockRouterReplace).toHaveBeenCalledWith(
'/performance-survey?tab=personal&adminWorkspace=positions',
{ scroll: false },
))
})
test('发起调研按当前被考核人的人员分配自动关联岗位', async () => {
mockSearchParams = new URLSearchParams('tab=manage&adminWorkspace=launch&adminMonth=2026-07')
global.fetch = jest.fn(async (input) => {
const url = String(input)
if (url.endsWith('/me')) {
return jsonResponse({
user: { id: 'user-admin', name: '管理员', email: 'admin@example.com' },
employee: {
id: 'employee-admin',
displayName: '管理员',
departmentName: '品牌创意部',
positionLevel: 'M1',
},
permissions: {
canCreate: true,
isSystemAdmin: true,
isDepartmentAdmin: true,
isLeadership: true,
},
})
}
if (url.includes('/position-groups?month=2026-07')) {
return jsonResponse({
month: '2026-07',
groups: [{
id: 'group-1',
code: 'brand_designer',
name: '品牌设计师',
status: 'ready',
templateCount: 1,
}],
membership: {
status: 'matched',
groupCode: 'brand_designer',
groupName: '品牌设计师',
conflictGroupNames: [],
},
})
}
if (url.includes('/templates?month=2026-07&positionGroupCode=brand_designer')) {
return jsonResponse([{
...templateDefinition,
name: '品牌调性视觉评分(综合主管)',
shortName: '视觉评分',
templateVersionId: 'version-1',
templateOptionKey: 'visual_review:version-1',
templateVariantName: '视觉评分(综合主管)',
}])
}
if (url.includes('/departments?purpose=evaluator')) return jsonResponse([])
if (url.endsWith('/surveys')) return jsonResponse([])
return jsonResponse([])
}) as jest.Mock
render(<PerformanceSurveyPage />)
expect(await screen.findByText('品牌设计师')).toBeInTheDocument()
expect(screen.queryByRole('combobox', { name: '考核岗位' })).not.toBeInTheDocument()
expect(screen.getByText('已按被考核人的人员分配自动关联,发起时无需选择。')).toBeInTheDocument()
expect(await screen.findByRole('option', { name: '品牌调性视觉评分(综合主管)' })).toBeInTheDocument()
expect(screen.queryByRole('option', { name: '视觉评分(综合主管)' })).not.toBeInTheDocument()
await waitFor(() => expect(global.fetch).toHaveBeenCalledWith(
'/api/performance-survey/templates?month=2026-07&positionGroupCode=brand_designer',
expect.anything(),
))
})
test('公共看板可选择全部岗位并请求跨岗位数据', async () => {
mockSearchParams = new URLSearchParams('tab=public')
const positionGroups = [
{
id: 'group-brand',
code: 'brand_designer',
name: '品牌设计主管',
status: 'ready',
templateCount: 1,
},
{
id: 'group-general',
code: 'ai_planning_general',
name: 'AI策划-综合部',
status: 'ready',
templateCount: 1,
},
]
global.fetch = jest.fn(async (input) => {
const url = String(input)
if (url.endsWith('/me')) {
return jsonResponse({
user: { id: 'user-admin', name: '管理员', email: 'admin@example.com' },
employee: {
id: 'employee-admin',
displayName: '管理员',
departmentName: '品牌设计部',
positionLevel: 'M1',
},
permissions: {
canCreate: true,
isSystemAdmin: true,
isDepartmentAdmin: true,
isLeadership: true,
},
})
}
if (url.includes('/departments?purpose=dashboard')) return jsonResponse([])
if (url.includes('/dashboard?')) {
const allSelected = url.includes('positionGroupCode=all')
return jsonResponse({
month: '2026-07',
filters: {
view: 'public',
month: '2026-07',
positionGroupCode: allSelected ? 'all' : 'brand_designer',
itemPage: 1,
},
permissions: { canOpenPublicDetail: true },
positionGroups,
positionMembership: {
status: 'matched',
groupCode: 'brand_designer',
groupName: '品牌设计主管',
conflictGroupNames: [],
},
selectedPositionGroup: allSelected ? null : positionGroups[0],
templatePager: {
page: 1,
totalPages: 1,
totalItems: allSelected ? 2 : 1,
start: 1,
end: allSelected ? 2 : 1,
},
templates: allSelected
? [
{ code: 'brand_review', name: '品牌调性视觉评分', maxTotal: 120 },
{ code: 'general_review', name: '品牌调性视觉评分(无文案)', maxTotal: 120 },
]
: [{ code: 'brand_review', name: '品牌调性视觉评分', maxTotal: 120 }],
personal: { rows: [] },
public: { rows: [] },
})
}
return jsonResponse([])
}) as jest.Mock
render(<PerformanceSurveyPage />)
const positionSelect = await screen.findByRole('combobox', { name: '考核岗位' })
expect(within(positionSelect).getByRole('option', { name: '全部岗位' })).toBeInTheDocument()
fireEvent.change(positionSelect, { target: { value: 'all' } })
expect(await screen.findByText('2 个已生效岗位 · 2 个考核项')).toBeInTheDocument()
await waitFor(() => expect(
(global.fetch as jest.Mock).mock.calls
.map(([input]) => String(input))
.some((url) => url.includes('/dashboard?') && url.includes('positionGroupCode=all')),
).toBe(true))
expect(
(global.fetch as jest.Mock).mock.calls
.map(([input]) => String(input))
.some((url) => url.includes('/departments?purpose=dashboard') && url.includes('positionGroupCode=all')),
).toBe(false)
})
test('公共看板按当前岗位和月份选择保存位置并导出全部考核项', async () => {
mockSearchParams = new URLSearchParams('tab=public&month=2026-07')
const dashboardData = {
month: '2026-07',
filters: {
view: 'public',
month: '2026-07',
positionGroupCode: 'brand_creative_manager',
itemPage: 1,
},
permissions: { canOpenPublicDetail: true },
positionGroups: [{
id: 'group-creative-manager',
code: 'brand_creative_manager',
name: '品牌创意主管',
status: 'ready',
templateCount: 4,
}],
positionMembership: {
status: 'matched',
groupCode: 'brand_creative_manager',
groupName: '品牌创意主管',
conflictGroupNames: [],
},
selectedPositionGroup: {
id: 'group-creative-manager',
code: 'brand_creative_manager',
name: '品牌创意主管',
status: 'ready',
},
templatePager: {
page: 1,
totalPages: 1,
totalItems: 4,
start: 1,
end: 4,
},
templates: [{ code: 'coordination', name: '跨部门协同', maxTotal: 120 }],
personal: { rows: [] },
public: {
rows: [{
month: '2026-07',
employee: {
id: 'employee-boyan',
displayName: '伯言',
jobNumber: 'JS15040602',
departmentName: '品牌创意A部',
positionLevel: 'M2',
},
cells: {
coordination: {
score: 114.5,
surveyCount: 1,
positionGroupCode: 'brand_creative_manager',
},
},
}],
},
}
global.fetch = jest.fn(async (input) => {
const url = String(input)
if (url.endsWith('/me')) {
return jsonResponse({
user: { id: 'user-admin', name: '管理员', email: 'admin@example.com' },
employee: { id: 'employee-admin', displayName: '管理员' },
permissions: { canCreate: true, isSystemAdmin: true, isDepartmentAdmin: true, isLeadership: true },
})
}
if (url.includes('/departments?purpose=dashboard')) return jsonResponse([])
if (url.includes('/dashboard/export?')) return jsonResponse(dashboardData)
if (url.includes('/dashboard?')) return jsonResponse(dashboardData)
return jsonResponse([])
}) as jest.Mock
render(<PerformanceSurveyPage />)
fireEvent.click(await screen.findByRole('button', { name: '导出' }))
const dialog = await screen.findByRole('dialog', { name: '导出公共看板' })
expect(within(dialog).getByText('品牌创意主管')).toBeInTheDocument()
expect(within(dialog).getByText('2026年7月')).toBeInTheDocument()
expect(within(dialog).getByText('1 人')).toBeInTheDocument()
expect(within(dialog).getByText('4 项')).toBeInTheDocument()
fireEvent.click(within(dialog).getByRole('button', { name: '选择保存位置并导出' }))
await waitFor(() => expect(mockChoosePerformanceSurveyExportDestination).toHaveBeenCalledWith(
'考核调研_品牌创意主管_2026-07.xlsx',
))
await waitFor(() => expect(global.fetch).toHaveBeenCalledWith(
'/api/performance-survey/dashboard/export?month=2026-07&positionGroupCode=brand_creative_manager',
expect.anything(),
))
await waitFor(() => expect(mockSavePerformanceSurveyDashboardWorkbook).toHaveBeenCalledWith(
dashboardData,
'考核调研_品牌创意主管_2026-07.xlsx',
{ kind: 'download' },
))
expect(await screen.findByRole('status')).toHaveTextContent('请在浏览器下载记录中查看')
})
test('我发起的草稿发布失败时展示发布进度和后端原因', async () => {
mockSearchParams = new URLSearchParams('tab=personal&workspace=created')
let resolvePublish: (response: Response) => void = () => {}
const publishResponse = new Promise<Response>((resolve) => {
resolvePublish = resolve
})
global.fetch = jest.fn(async (input, init) => {
const url = String(input)
if (url.endsWith('/me')) {
return jsonResponse({
user: { id: 'user-admin', name: '管理员', email: 'admin@example.com' },
employee: { id: 'employee-admin', displayName: '管理员' },
permissions: {
canCreate: true,
isSystemAdmin: true,
isDepartmentAdmin: true,
isLeadership: true,
},
})
}
if (url.endsWith('/surveys') && (!init?.method || init.method === 'GET')) {
return jsonResponse({
summary: {
pendingCount: 0,
submittedCount: 0,
createdCount: 1,
openCreatedCount: 0,
},
pending: [],
submitted: [],
created: [{
id: 'survey-draft',
title: '2026年7月 · AI作品评审',
status: 'draft',
departmentName: '综合支持部',
dueAt: null,
counts: { targets: 1, assignments: 3, responses: 0 },
}],
})
}
if (url.endsWith('/surveys/survey-draft') && init?.method === 'PATCH') {
return publishResponse
}
return jsonResponse([])
}) as jest.Mock
render(<PerformanceSurveyPage />)
const publishButtons = await screen.findAllByRole('button', { name: '发布调研:2026年7月 · AI作品评审' })
expect(screen.getAllByRole('link', { name: '继续编辑草稿:2026年7月 · AI作品评审' })[0]).toHaveAttribute(
'href',
'/performance-survey?tab=manage&adminWorkspace=launch&draftId=survey-draft',
)
fireEvent.click(publishButtons[0])
expect(await screen.findAllByText('发布中...')).not.toHaveLength(0)
resolvePublish(jsonErrorResponse('视觉评分作品尚未上传完整:无邪(0/3)'))
expect(await screen.findAllByRole('alert')).not.toHaveLength(0)
expect(await screen.findAllByText('发布失败:视觉评分作品尚未上传完整:无邪(0/3)')).not.toHaveLength(0)
expect(screen.getAllByRole('link', { name: '继续上传作品' })[0]).toHaveAttribute(
'href',
'/performance-survey/survey/survey-draft',
)
expect(screen.getAllByRole('button', { name: '发布调研:2026年7月 · AI作品评审' })[0]).toBeEnabled()
})
test('继续编辑草稿时恢复原模板和已上传作品', async () => {
mockSearchParams = new URLSearchParams('tab=manage&adminWorkspace=launch&adminMonth=2026-07&draftId=survey-draft')
global.fetch = jest.fn(async (input) => {
const url = String(input)
if (url.endsWith('/me')) {
return jsonResponse({
user: { id: 'user-admin', name: '管理员', email: 'admin@example.com' },
employee: {
id: 'employee-admin',
displayName: '管理员',
departmentName: '品牌创意部',
positionLevel: 'M1',
},
permissions: {
canCreate: true,
isSystemAdmin: true,
isDepartmentAdmin: true,
isLeadership: true,
},
})
}
if (url.endsWith('/surveys/survey-draft')) {
return jsonResponse({
id: 'survey-draft',
title: '2026年7月 · AI作品评审',
description: '草稿说明',
templateCode: 'visual_review',
template: {
...templateDefinition,
name: 'AI作品评审',
shortName: 'AI作品评审',
requiredWorkItemCount: 3,
},
status: 'draft',
canManage: true,
assessmentMonth: '2026-07',
dueAt: null,
targets: [{
id: 'target-1',
targetType: 'employee',
targetName: '管理员',
targetSnapshot: {
id: 'employee-admin',
displayName: '管理员',
departmentName: '品牌创意部',
positionLevel: 'M1',
},
canCreateWorkItem: true,
canUploadWorkItems: true,
workItemUploadRemaining: 2,
workItems: [{
id: 'work-item-1',
title: '已上传作品',
description: '第一版',
imageUrls: ['https://s3.jiansun.vip/example.webp'],
}],
assignments: [{
id: 'assignment-1',
evaluatorEmployeeId: 'evaluator-1',
evaluatorName: '元泽',
evaluatorSnapshot: { id: 'evaluator-1', displayName: '元泽' },
reviewerSource: 'fixed_employee',
status: 'pending',
}],
}],
})
}
if (url.includes('/position-groups?month=2026-07')) {
return jsonResponse({
month: '2026-07',
groups: [{
id: 'group-1',
code: 'brand_designer',
name: '品牌设计师',
status: 'ready',
templateCount: 1,
}],
membership: {
status: 'matched',
groupCode: 'brand_designer',
groupName: '品牌设计师',
conflictGroupNames: [],
},
})
}
if (url.includes('/templates?month=2026-07&positionGroupCode=brand_designer')) {
return jsonResponse([{
...templateDefinition,
name: 'AI作品评审',
shortName: 'AI作品评审',
templateVersionId: 'version-1',
templateOptionKey: 'visual_review:version-1',
requiredWorkItemCount: 3,
}])
}
if (url.includes('/departments?purpose=evaluator')) return jsonResponse([])
if (url.endsWith('/surveys')) {
return jsonResponse({
summary: {},
pending: [],
submitted: [],
created: [],
})
}
return jsonResponse([])
}) as jest.Mock
render(<PerformanceSurveyPage />)
expect(await screen.findByText('继续编辑未发布草稿')).toBeInTheDocument()
expect(screen.getByText('已恢复 1/3 个作品;补齐内容后可直接发布。被考核人与评分流程沿用原草稿。')).toBeInTheDocument()
expect(screen.getByText('已上传作品')).toBeInTheDocument()
expect(screen.getByLabelText('考评模板')).toBeDisabled()
expect(screen.getByLabelText('案例 2 名称')).toBeInTheDocument()
expect(screen.getByLabelText('案例 3 名称')).toBeInTheDocument()
expect(screen.getByRole('button', { name: '上传剩余作品并发布' })).toBeDisabled()
})
test('公共看板仅按后端权限在分数下显示删除按钮并彻底删除单项调研', async () => {
mockSearchParams = new URLSearchParams('tab=public&workspace=pending&month=2026-07')
let dashboardRequestCount = 0
const deleteRequests: Array<{ url: string; init?: RequestInit }> = []
global.fetch = jest.fn(async (input, init) => {
const url = String(input)
if (url.endsWith('/me')) {
return jsonResponse({
user: { id: 'user-wuxie', name: '无邪', email: 'wuxie@example.com' },
employee: { id: 'employee-wuxie', displayName: '无邪' },
permissions: {
canCreate: true,
isSystemAdmin: true,
isDepartmentAdmin: true,
isLeadership: true,
},
})
}
if (url.includes('/departments?purpose=dashboard')) return jsonResponse([])
if (url.includes('/dashboard/public/assessments/employee-ruolan') && init?.method === 'DELETE') {
deleteRequests.push({ url, init })
return jsonResponse({
employeeProfileId: 'employee-ruolan',
employeeName: '若兰',
templateCode: 'work_coordination',
deletedTargetCount: 1,
})
}
if (url.includes('/dashboard?')) {
dashboardRequestCount += 1
return jsonResponse({
month: '2026-07',
filters: {
view: 'public',
month: '2026-07',
positionGroupCode: 'ai_planning_creative',
itemPage: 1,
},
permissions: {
canOpenPublicDetail: true,
canDeletePublicDashboardAssessment: true,
},
positionGroups: [{
id: 'group-creative',
code: 'ai_planning_creative',
name: 'AI策划-创意部',
status: 'ready',
templateCount: 1,
}],
positionMembership: {
status: 'matched',
groupCode: 'ai_planning_creative',
groupName: 'AI策划-创意部',
conflictGroupNames: [],
},
selectedPositionGroup: {
id: 'group-creative',
code: 'ai_planning_creative',
name: 'AI策划-创意部',
status: 'ready',
},
templatePager: {
page: 1,
totalPages: 1,
totalItems: 1,
start: 1,
end: 1,
},
templates: [{
code: 'work_coordination',
name: '工作配合度',
maxTotal: 120,
}],
personal: { rows: [] },
public: {
rows: dashboardRequestCount > 1 ? [] : [{
month: '2026-07',
employee: {
id: 'employee-ruolan',
displayName: '若兰',
jobNumber: 'JS18102201',
departmentName: '品牌创意部',
positionLevel: 'M0',
},
cells: {
work_coordination: {
surveyCount: 1,
score: 113,
maxTotal: 120,
positionGroupCode: 'ai_planning_creative',
},
},
}],
},
})
}
return jsonResponse([])
}) as jest.Mock
render(<PerformanceSurveyPage />)
const removeButtons = await screen.findAllByRole('button', { name: '删除若兰的工作配合度调研' })
expect(removeButtons).toHaveLength(2)
fireEvent.click(removeButtons[0])
expect(await screen.findByRole('alertdialog')).toHaveTextContent('彻底删除“若兰”的工作配合度?')
expect(screen.getByRole('alertdialog')).toHaveTextContent('删除后不可恢复,但可以重新发起该月该项调研')
const confirmButton = screen.getByRole('button', { name: '确认彻底删除' })
expect(confirmButton).toBeDisabled()
fireEvent.change(screen.getByRole('textbox', { name: /删除原因/ }), {
target: { value: '岗位归属错误' },
})
expect(confirmButton).toBeEnabled()
fireEvent.click(confirmButton)
expect(await screen.findByRole('status')).toHaveTextContent(
'已彻底删除若兰在2026年7月的“工作配合度”调研,现在可以重新发起。',
)
expect(deleteRequests).toHaveLength(1)
expect(JSON.parse(String(deleteRequests[0].init?.body))).toEqual({
month: '2026-07',
positionGroupCode: 'ai_planning_creative',
templateCode: 'work_coordination',
reason: '岗位归属错误',
})
expect(dashboardRequestCount).toBeGreaterThan(1)
})
})