import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import defaultI18n from '../src/locale/index.ts'; const adminRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const handoverPagePath = path.join( adminRoot, 'src/pages/team/handover/index.tsx' ); const handoverLocalePath = path.join( adminRoot, 'src/pages/team/handover/locale/index.ts' ); function readPage(): string { assert.ok(fs.existsSync(handoverPagePath), '离职交接页面必须存在'); return fs.readFileSync(handoverPagePath, 'utf-8'); } test('1. team/handover 必须请求 handover/transfer,并使用 Form 与 Popconfirm', () => { const content = readPage(); assert.ok( content.includes('admin/team/handover/transfer'), '必须请求 POST /api/admin/team/handover/transfer' ); assert.ok(content.includes('Form'), '必须使用 Arco Form'); assert.ok(content.includes('Popconfirm'), '必须使用 Arco Popconfirm'); assert.ok( content.includes("from '@arco-design/web-react'"), '必须从 @arco-design/web-react 引入组件' ); assert.ok( content.includes('code === 1000') || content.includes('code ===1000'), '必须按 Cool-Admin code === 1000 判断成功' ); assert.ok(content.includes('Message.success'), '成功必须使用 Arco Message.success'); assert.ok(content.includes('Message.error'), '失败必须使用 Arco Message.error'); assert.ok( !content.includes('Mock.js') && !content.includes("from 'mockjs'"), '禁止用 Mock.js 假数据冒充成功' ); assert.ok(!/\bany\b/.test(content), '禁止使用 any'); }); test('2. team/handover 必须包含必填字段、Alert 说明与单一主 CTA', () => { const content = readPage(); assert.ok(content.includes("from '@/types/omni'"), '必须复用 admin/src/types/omni.ts'); assert.ok(content.includes('HandoverRequest'), '必须使用 HandoverRequest 类型'); assert.ok(content.includes('Alert'), '必须使用 Arco Alert'); assert.ok(content.includes('cardId'), '表单必须包含 cardId'); assert.ok(content.includes('fromUserId'), '表单必须包含 fromUserId'); assert.ok(content.includes('toUserId'), '表单必须包含 toUserId'); assert.ok(content.includes('required: true'), 'cardId / fromUserId / toUserId 必须必填'); assert.ok(content.includes('lightColors'), '颜色必须引用 lightColors token'); assert.ok(content.includes('type="primary"'), '必须有一个主 CTA'); assert.ok( !/#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})\b/.test(content), '禁止硬编码颜色' ); assert.ok( !content.includes('#000') && !content.includes('#000000'), '禁止纯黑' ); assert.ok( !content.includes('Mock.js') && !content.includes("from 'mockjs'"), '禁止用 Mock.js' ); assert.ok(!/\bany\b/.test(content), '禁止使用 any'); }); test('3. team/handover locale 必须包含 menu.team.handover 与中英文案', () => { assert.ok(fs.existsSync(handoverLocalePath), '离职交接语言包必须存在'); const content = fs.readFileSync(handoverLocalePath, 'utf-8'); assert.ok( content.includes("'menu.team.handover'"), '页面语言包必须包含 menu.team.handover' ); assert.ok(content.includes("'en-US'"), '必须包含英文案'); assert.ok(content.includes("'zh-CN'"), '必须包含中文案'); assert.ok(content.includes("'ar-SA'"), '必须包含阿语文案'); assert.ok(content.includes('teamHandover.form.cardId'), '必须包含 cardId 文案'); assert.ok(content.includes('teamHandover.form.fromUserId'), '必须包含 fromUserId 文案'); assert.ok(content.includes('teamHandover.form.toUserId'), '必须包含 toUserId 文案'); assert.ok(content.includes('teamHandover.alert.keepSlug'), '必须包含短链保留说明'); assert.ok(content.includes('teamHandover.form.submit'), '必须包含交接主 CTA 文案'); assert.ok(content.includes('teamHandover.request.success'), '必须包含成功提示文案'); assert.ok(content.includes('teamHandover.request.fail'), '必须包含失败提示文案'); assert.ok(!/\bany\b/.test(content), 'locale 禁止使用 any'); assert.ok( !/[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u.test(content), 'locale 禁止使用 emoji' ); assert.equal(defaultI18n['en-US']['menu.team.handover'], 'Handover'); assert.equal(defaultI18n['zh-CN']['menu.team.handover'], '离职交接'); }); test('4. 离职人/接替人必须用 Select(成员列表),提交字段仍是用户 ID', () => { const content = readPage(); assert.ok(content.includes('Select'), '必须使用 Arco Select'); assert.ok( content.includes('admin/team/members/getMemberPage'), '离职人/接替人选项必须来自现有 getMemberPage 接口' ); assert.ok( content.includes('field="fromUserId"'), '表单字段名仍必须是 fromUserId' ); assert.ok( content.includes('field="toUserId"'), '表单字段名仍必须是 toUserId' ); assert.ok( /fromUserId[\s\S]*toUserId/.test(content) && content.includes('HandoverRequest'), '提交体必须仍按 HandoverRequest 携带 fromUserId / toUserId' ); assert.ok( content.includes('body') || content.includes('HandoverRequest'), '必须构造交接请求体' ); // 离职人、接替人表单项不得再用裸 Input 手填用户 ID const fromBlock = content.slice( content.indexOf('field="fromUserId"'), content.indexOf('field="toUserId"') ); const toStart = content.indexOf('field="toUserId"'); const toBlock = content.slice( toStart, content.indexOf('Form.Item', content.indexOf('', toStart) + 1) ); assert.ok(fromBlock.includes(']/.test(fromBlock), 'fromUserId 禁止裸 Input 手填用户 ID'); assert.ok(toBlock.includes(']/.test(toBlock), 'toUserId 禁止裸 Input 手填用户 ID'); assert.ok( !content.includes('modules/team/service/handover') && !content.includes('backend/src/modules'), '禁止改动或引用后端 handover service' ); });