Files
vps-manager/static/js/__tests__/api.test.js
T
gouki a63fcb786f feat: 为高频变更 JS 文件补充 smoke test 并建立 js-test-gate Rule
新增测试:
- 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
2026-10-15 21:20:52 +00:00

208 lines
6.7 KiB
JavaScript
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.
/**
* 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');
});
});