文件
OmniCard/admin/tests/team-brand-kit.test.ts
wikiboss 62e0ae4435 feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 18:01:47 +08:00

243 行
9.5 KiB
TypeScript

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 brandKitPagePath = path.join(
adminRoot,
'src/pages/team/brand-kit/index.tsx'
);
const brandKitLocalePath = path.join(
adminRoot,
'src/pages/team/brand-kit/locale/index.ts'
);
function readPage(): string {
assert.ok(fs.existsSync(brandKitPagePath), 'Brand Kit 页面必须存在');
return fs.readFileSync(brandKitPagePath, 'utf-8');
}
test('1. team/brand-kit 必须请求 brand/page 与 add 或 update,并使用 Arco Form', () => {
const content = readPage();
assert.ok(
content.includes('admin/team/brand/page'),
'必须请求 POST /api/admin/team/brand/page'
);
assert.ok(
content.includes('admin/team/brand/add') ||
content.includes('admin/team/brand/update'),
'必须请求 POST /api/admin/team/brand/add 或 update'
);
assert.ok(content.includes('Form'), '必须使用 Arco Form');
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.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/brand-kit 必须拉取 size=1、保存按 id 分流,并预览 token 主色', () => {
const content = readPage();
assert.ok(content.includes("from '@/types/omni'"), '必须复用 admin/src/types/omni.ts');
assert.ok(content.includes('BrandKit'), '必须使用 BrandKit 类型');
assert.ok(content.includes('TenantProfile'), '必须使用 TenantProfile 类型');
assert.ok(
content.includes('lightColors') || content.includes('#0B5CFF'),
'必须引用 lightColors.primary 或默认 #0B5CFF'
);
assert.ok(content.includes('lightColors.primary') || content.includes('#0B5CFF'));
assert.ok(content.includes('companyName'), '表单必须包含 companyName');
assert.ok(content.includes('logoUrl'), '表单必须包含 logoUrl');
assert.ok(content.includes('primaryColor'), '表单必须包含 primaryColor');
assert.ok(
content.includes('size: 1') || content.includes('size:1'),
'page 必须 size=1 取当前租户品牌'
);
assert.ok(
content.includes('admin/team/brand/add'),
'无 id 保存必须请求 add'
);
assert.ok(
content.includes('admin/team/brand/update'),
'有 id 保存必须请求 update'
);
assert.ok(content.includes('type="primary"'), '必须有一个主 CTA');
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/brand-kit locale 必须包含 menu.team.brandKit 与中英文案', () => {
assert.ok(fs.existsSync(brandKitLocalePath), 'Brand Kit 语言包必须存在');
const content = fs.readFileSync(brandKitLocalePath, 'utf-8');
assert.ok(
content.includes("'menu.team.brandKit'"),
'页面语言包必须包含 menu.team.brandKit'
);
assert.ok(content.includes("'en-US'"), '必须包含英文案');
assert.ok(content.includes("'zh-CN'"), '必须包含中文案');
assert.ok(content.includes('teamBrandKit.request.fail'), '必须包含失败提示文案');
assert.ok(content.includes('teamBrandKit.form.save'), '必须包含保存主 CTA 文案');
assert.ok(content.includes('companyName'), '必须包含 companyName 文案');
assert.ok(content.includes('logoUrl'), '必须包含 logoUrl 文案');
assert.ok(content.includes('primaryColor'), '必须包含 primaryColor 文案');
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.brandKit'], 'Brand Kit');
assert.equal(defaultI18n['zh-CN']['menu.team.brandKit'], '品牌规范');
});
test('4. 版权页脚:无真实保存字段时不得提交/假装已生效,须禁用或提示暂不可保存', () => {
const content = readPage();
const localeContent = fs.readFileSync(brandKitLocalePath, 'utf-8');
// 当前 brand 保存契约仅有 companyName / logoUrl / primaryColor,无 footer|copyright|legalNotice
const savePayloadMatch = content.match(
/interface BrandSavePayload\s*\{[\s\S]*?\}/
);
const savePayloadBlock = savePayloadMatch?.[0] ?? '';
const hasFooterInSavePayload = /\b(footer|copyright|legalNotice)\b/.test(
savePayloadBlock
);
const hasFooterAssignedToBody =
/body\.(footer|copyright|legalNotice)\s*=/.test(content) ||
/(?:const|let)\s+body\s*[:=][^{]*\{[\s\S]*?\b(footer|copyright|legalNotice)\s*:/.test(
content
);
assert.ok(
localeContent.includes("'teamBrandKit.footer.unavailable'") ||
localeContent.includes("'teamBrandKit.copyright.unavailable'") ||
localeContent.includes("'teamBrandKit.legalNotice.unavailable'"),
'locale 必须包含页脚/版权暂不可保存词条'
);
assert.ok(
localeContent.includes("'teamBrandKit.form.footer'") ||
localeContent.includes("'teamBrandKit.form.copyright'") ||
localeContent.includes("'teamBrandKit.form.legalNotice'"),
'locale 必须包含页脚输入标签文案'
);
assert.ok(
content.includes("t['teamBrandKit.footer.unavailable']") ||
content.includes("t['teamBrandKit.copyright.unavailable']") ||
content.includes("t['teamBrandKit.legalNotice.unavailable']") ||
content.includes('teamBrandKit.footer.unavailable') ||
content.includes('teamBrandKit.copyright.unavailable') ||
content.includes('teamBrandKit.legalNotice.unavailable'),
'页面必须引用页脚暂不可保存文案'
);
// 禁止把写死版权句当成已保存生效内容
assert.ok(
!/©\s*20\d{2}|All rights reserved|版权所有|保留所有权利/i.test(content),
'禁止页面写死版权声明并当成已生效'
);
assert.ok(
!hasFooterInSavePayload && !hasFooterAssignedToBody,
'无后端页脚字段时禁止把 footer/copyright/legalNotice 写入保存 payload'
);
// 空值不得提交假声明(保存体不得硬编码版权句子)
assert.ok(
!/BrandSavePayload[\s\S]{0,400}(©|All rights reserved|版权所有)/i.test(
content
),
'空值不得向保存接口提交假版权声明'
);
const hasFooterSaveField = hasFooterInSavePayload || hasFooterAssignedToBody;
if (!hasFooterSaveField) {
const footerInputShown =
/field=["'](footer|copyright|legalNotice)["']/.test(content) ||
content.includes('teamBrandKit.form.footer') ||
content.includes('teamBrandKit.form.copyright') ||
content.includes('teamBrandKit.form.legalNotice');
assert.ok(footerInputShown, '可展示页脚输入,但不得当作已保存字段');
const footerDisabled =
/disabled[\s\S]{0,160}(footer|copyright|legalNotice)|(footer|copyright|legalNotice)[\s\S]{0,200}disabled/i.test(
content
) ||
content.includes('disabled={!FOOTER_SAVE_SUPPORTED}') ||
content.includes('disabled={true}');
const footerUnavailableHint =
content.includes("t['teamBrandKit.footer.unavailable']") ||
content.includes("t['teamBrandKit.copyright.unavailable']") ||
content.includes("t['teamBrandKit.legalNotice.unavailable']") ||
content.includes('teamBrandKit.footer.unavailable') ||
content.includes('teamBrandKit.copyright.unavailable') ||
content.includes('teamBrandKit.legalNotice.unavailable');
assert.ok(
footerDisabled || footerUnavailableHint,
'无页脚保存字段时必须禁用输入/保存或提示暂不可保存'
);
}
// Message.success 仅允许出现在 code === 1000 成功分支
const successMatches = [
...content.matchAll(/Message\.success\s*\([^)]*\)/g),
];
for (const match of successMatches) {
const idx = match.index ?? 0;
const windowBefore = content.slice(Math.max(0, idx - 280), idx);
assert.ok(
/code\s*===\s*1000/.test(windowBefore),
'保存失败不得 Message.success;成功提示必须在 code === 1000 分支内'
);
}
});
test('5. locale 页脚词条须说明暂不可保存,且中英文齐全', () => {
const localeContent = fs.readFileSync(brandKitLocalePath, 'utf-8');
const unavailableKey =
localeContent.includes("'teamBrandKit.footer.unavailable'")
? 'teamBrandKit.footer.unavailable'
: localeContent.includes("'teamBrandKit.copyright.unavailable'")
? 'teamBrandKit.copyright.unavailable'
: 'teamBrandKit.legalNotice.unavailable';
assert.ok(
localeContent.includes(`'${unavailableKey}'`),
'必须包含暂不可保存词条'
);
assert.ok(
/暂不可保存|暂不可用|not available|cannot be saved|unavailable/i.test(
localeContent
),
'暂不可保存文案须明确不可落库'
);
assert.ok(!/\bany\b/.test(localeContent), 'locale 禁止使用 any');
assert.ok(
!/[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u.test(
localeContent
),
'locale 禁止使用 emoji'
);
});