新增测试: - api.test.js: 28 个用例覆盖 VpsFmt 纯工具函数(OAuth 归一化、倒计时文案、费用文案、徽章样式) - app.test.js: 15 个用例覆盖 NAVS/VIEW_MAP/VALID_VIEWS 常量和 _viewFromHash 路由解析 - modals.test.js: 3 个用例验证 9 个模态框组件定义完整性 规则与钩子: - 新建 .qoder/rules/js-test-gate.md:编辑 static/js/ 下 JS 文件后运行 vitest - 更新 post-edit-check.sh:JS 编辑后 node --check + vitest(排除 vendored 文件) 部署闸门 check_js_tests(update.sh)已有 vitest run 步骤,现在测试覆盖从 1/7 文件提升到 4/7
208 lines
6.7 KiB
JavaScript
208 lines
6.7 KiB
JavaScript
/**
|
||
* api.js smoke test
|
||
*
|
||
* 验证 VpsFmt 纯工具函数:OAuth 归一化、倒计时文案、费用文案、徽章样式等。
|
||
* api.js 是 IIFE,会立即创建 window.VpsApi / window.VpsFmt,
|
||
* 用 vm.runInNewContext 在沙箱内加载,注入最小 mock。
|
||
*/
|
||
import { describe, it, expect, beforeAll } from 'vitest';
|
||
import { readFileSync } from 'node:fs';
|
||
import { resolve } from 'node:path';
|
||
import vm from 'node:vm';
|
||
|
||
let ctx;
|
||
|
||
beforeAll(() => {
|
||
const _store = {};
|
||
const localStorage = {
|
||
getItem: (k) => (k in _store ? _store[k] : null),
|
||
setItem: (k, v) => { _store[k] = String(v); },
|
||
removeItem: (k) => { delete _store[k]; },
|
||
clear: () => { for (const k of Object.keys(_store)) delete _store[k]; },
|
||
get length() { return Object.keys(_store).length; },
|
||
key: (i) => Object.keys(_store)[i] ?? null,
|
||
};
|
||
|
||
ctx = vm.createContext({
|
||
APP_CONFIG: { appName: 'Test', version: '0.0.0', commit: 'test' },
|
||
window: {},
|
||
localStorage,
|
||
fetch: async () => ({ ok: true, status: 200, json: async () => ({}) }),
|
||
console,
|
||
});
|
||
ctx.window = ctx;
|
||
|
||
const code = readFileSync(
|
||
resolve(import.meta.dirname, '..', 'api.js'),
|
||
'utf-8',
|
||
);
|
||
vm.runInContext(code, ctx);
|
||
});
|
||
|
||
/* ---------- VpsApi 基础 ---------- */
|
||
|
||
describe('VpsApi 初始化', () => {
|
||
it('暴露 get/post/put/del 方法', () => {
|
||
expect(typeof ctx.VpsApi.get).toBe('function');
|
||
expect(typeof ctx.VpsApi.post).toBe('function');
|
||
expect(typeof ctx.VpsApi.put).toBe('function');
|
||
expect(typeof ctx.VpsApi.del).toBe('function');
|
||
});
|
||
|
||
it('CFG 来自 window.APP_CONFIG', () => {
|
||
expect(ctx.VpsApi.CFG.appName).toBe('Test');
|
||
});
|
||
});
|
||
|
||
/* ---------- OAuth 归一化 ---------- */
|
||
|
||
describe('VpsFmt OAuth 函数', () => {
|
||
it('oauthLabel 预设 key 返回中文标签', () => {
|
||
expect(ctx.VpsFmt.oauthLabel('alipay')).toBe('支付宝');
|
||
expect(ctx.VpsFmt.oauthLabel('github')).toBe('GitHub');
|
||
expect(ctx.VpsFmt.oauthLabel('wechat')).toBe('微信');
|
||
});
|
||
|
||
it('oauthLabel 未知 key 原样返回', () => {
|
||
expect(ctx.VpsFmt.oauthLabel('custom_provider')).toBe('custom_provider');
|
||
expect(ctx.VpsFmt.oauthLabel('')).toBe('');
|
||
expect(ctx.VpsFmt.oauthLabel(null)).toBe('');
|
||
});
|
||
|
||
it('oauthDetail 预设返回「标签(key)」格式', () => {
|
||
expect(ctx.VpsFmt.oauthDetail('alipay')).toBe('支付宝(alipay)');
|
||
expect(ctx.VpsFmt.oauthDetail('github')).toBe('GitHub(github)');
|
||
});
|
||
|
||
it('oauthDetail 未知 key 只返回原文', () => {
|
||
expect(ctx.VpsFmt.oauthDetail('my_custom')).toBe('my_custom');
|
||
});
|
||
|
||
it('normalizeOauth 小写 key 直通', () => {
|
||
expect(ctx.VpsFmt.normalizeOauth('github')).toBe('github');
|
||
expect(ctx.VpsFmt.normalizeOauth(' GitHub ')).toBe('github');
|
||
});
|
||
|
||
it('normalizeOauth 中文标签归一为 key', () => {
|
||
expect(ctx.VpsFmt.normalizeOauth('支付宝')).toBe('alipay');
|
||
expect(ctx.VpsFmt.normalizeOauth('微信')).toBe('wechat');
|
||
});
|
||
|
||
it('normalizeOauth 空值返回空字符串', () => {
|
||
expect(ctx.VpsFmt.normalizeOauth('')).toBe('');
|
||
expect(ctx.VpsFmt.normalizeOauth(null)).toBe('');
|
||
});
|
||
});
|
||
|
||
/* ---------- 倒计时文案 ---------- */
|
||
|
||
describe('VpsFmt.countdownText', () => {
|
||
it('正数返回「N 天后」', () => {
|
||
expect(ctx.VpsFmt.countdownText(30)).toBe('30 天后');
|
||
expect(ctx.VpsFmt.countdownText(1)).toBe('1 天后');
|
||
});
|
||
|
||
it('负数返回「已过期 N 天」', () => {
|
||
expect(ctx.VpsFmt.countdownText(-5)).toBe('已过期 5 天');
|
||
});
|
||
|
||
it('零返回「今天到期」', () => {
|
||
expect(ctx.VpsFmt.countdownText(0)).toBe('今天到期');
|
||
});
|
||
|
||
it('null/undefined 返回默认占位', () => {
|
||
expect(ctx.VpsFmt.countdownText(null)).toBe('未设到期日');
|
||
expect(ctx.VpsFmt.countdownText(undefined)).toBe('未设到期日');
|
||
});
|
||
|
||
it('opts.empty 自定义占位', () => {
|
||
expect(ctx.VpsFmt.countdownText(null, { empty: '—' })).toBe('—');
|
||
});
|
||
|
||
it('opts.unit="" 只返回「N 天」', () => {
|
||
expect(ctx.VpsFmt.countdownText(7, { unit: '' })).toBe('7 天');
|
||
});
|
||
});
|
||
|
||
/* ---------- 费用文案 ---------- */
|
||
|
||
describe('VpsFmt.costText', () => {
|
||
it('零返回「未填」', () => {
|
||
expect(ctx.VpsFmt.costText(0, 'USD')).toBe('未填');
|
||
});
|
||
|
||
it('有值有币种拼接', () => {
|
||
expect(ctx.VpsFmt.costText(9.99, 'USD')).toBe('9.99 USD');
|
||
});
|
||
|
||
it('有值无币种只返回数字字符串', () => {
|
||
expect(ctx.VpsFmt.costText(100, '')).toBe('100');
|
||
expect(ctx.VpsFmt.costText(50, null)).toBe('50');
|
||
});
|
||
});
|
||
|
||
/* ---------- 到期日样式 ---------- */
|
||
|
||
describe('VpsFmt 到期日样式函数', () => {
|
||
it('expiryText 按天数区间返回不同样式类', () => {
|
||
expect(ctx.VpsFmt.expiryText(-1)).toContain('red');
|
||
expect(ctx.VpsFmt.expiryText(3)).toContain('red');
|
||
expect(ctx.VpsFmt.expiryText(15)).toContain('amber');
|
||
expect(ctx.VpsFmt.expiryText(60)).toContain('slate');
|
||
expect(ctx.VpsFmt.expiryText(null)).toContain('slate');
|
||
});
|
||
|
||
it('expiryBadge 按天数区间返回不同背景色', () => {
|
||
expect(ctx.VpsFmt.expiryBadge(3)).toContain('red');
|
||
expect(ctx.VpsFmt.expiryBadge(15)).toContain('amber');
|
||
expect(ctx.VpsFmt.expiryBadge(60)).toContain('slate');
|
||
});
|
||
});
|
||
|
||
/* ---------- 标签映射表 ---------- */
|
||
|
||
describe('VpsFmt 标签映射表', () => {
|
||
it('TYPE_LABELS 包含核心资产类型', () => {
|
||
expect(ctx.VpsFmt.TYPE_LABELS.vps).toBe('VPS');
|
||
expect(ctx.VpsFmt.TYPE_LABELS.domain).toBe('域名');
|
||
expect(ctx.VpsFmt.TYPE_LABELS.ai_agent).toBe('AI账号');
|
||
});
|
||
|
||
it('STATUS_LABELS 包含全部状态', () => {
|
||
expect(ctx.VpsFmt.STATUS_LABELS.active).toBe('使用中');
|
||
expect(ctx.VpsFmt.STATUS_LABELS.expired).toBe('已过期');
|
||
});
|
||
|
||
it('LOGIN_TYPE_LABELS 包含登录方式', () => {
|
||
expect(ctx.VpsFmt.LOGIN_TYPE_LABELS.password).toBe('密码登录');
|
||
expect(ctx.VpsFmt.LOGIN_TYPE_LABELS.oauth).toBe('授权登录');
|
||
});
|
||
|
||
it('OAUTH_PRESETS 是非空数组', () => {
|
||
expect(Array.isArray(ctx.VpsFmt.OAUTH_PRESETS)).toBe(true);
|
||
expect(ctx.VpsFmt.OAUTH_PRESETS.length).toBeGreaterThan(10);
|
||
});
|
||
});
|
||
|
||
/* ---------- 徽章函数兜底 ---------- */
|
||
|
||
describe('VpsFmt 徽章函数兜底', () => {
|
||
it('typeBadge 未知类型返回默认样式', () => {
|
||
expect(ctx.VpsFmt.typeBadge('nonexistent')).toContain('slate');
|
||
});
|
||
|
||
it('statusBadge 未知状态返回默认样式', () => {
|
||
expect(ctx.VpsFmt.statusBadge('unknown_status')).toContain('slate');
|
||
});
|
||
|
||
it('loginTypeBadge 未知类型返回默认样式', () => {
|
||
expect(ctx.VpsFmt.loginTypeBadge('xxx')).toContain('slate');
|
||
});
|
||
|
||
it('checkBadge 各状态返回对应颜色', () => {
|
||
expect(ctx.VpsFmt.checkBadge('pass')).toContain('emerald');
|
||
expect(ctx.VpsFmt.checkBadge('warn')).toContain('amber');
|
||
expect(ctx.VpsFmt.checkBadge('fail')).toContain('red');
|
||
});
|
||
});
|