refactor: 清理测试代码 eslint-disable 指令,消除文件级和重复局部禁用
This commit is contained in:
@@ -1157,6 +1157,15 @@ bun run check # 一键运行 schema:check + typecheck + lint + test
|
|||||||
| `eslint-plugin-import` | 导入路径验证、循环依赖检测、重复导入合并 |
|
| `eslint-plugin-import` | 导入路径验证、循环依赖检测、重复导入合并 |
|
||||||
| `eslint-plugin-prettier` recommended + `eslint-config-prettier` | 将 Prettier 格式集成为 ESLint 规则,禁用冲突规则 |
|
| `eslint-plugin-prettier` recommended + `eslint-config-prettier` | 将 Prettier 格式集成为 ESLint 规则,禁用冲突规则 |
|
||||||
|
|
||||||
|
### 测试代码 ESLint 规范
|
||||||
|
|
||||||
|
测试代码与业务代码使用相同的 ESLint 规则集,应优先通过类型化 helper、类型化 mock、显式 no-op 和受控断言模式满足已启用的类型感知规则,最小化 `eslint-disable` 的使用。具体约定:
|
||||||
|
|
||||||
|
- 使用类型化 mock 变量(`vi.fn()`)替代动态 `require` 获取 mocked module
|
||||||
|
- 异步错误断言使用 helper 或显式 try/catch,避免依赖 Bun `expect(...).rejects` 与 `await-thenable` 规则的类型不匹配
|
||||||
|
- polyfill 中的 intentional no-op 使用显式可解释写法(如 `() => undefined` 或共享 `noop` 函数)
|
||||||
|
- 对 `process.exit` 等系统 API 使用 `spyOn`(从 `bun:test` 导入)受控 mock 而非手动 monkey patch
|
||||||
|
|
||||||
### Prettier 配置
|
### Prettier 配置
|
||||||
|
|
||||||
配置文件:`.prettierrc.json`,通过 `eslint-plugin-prettier` 集成为 ESLint 规则(`lint` 命令同时检查格式),也可通过 `format` 命令独立运行。
|
配置文件:`.prettierrc.json`,通过 `eslint-plugin-prettier` 集成为 ESLint 规则(`lint` 命令同时检查格式),也可通过 `format` 命令独立运行。
|
||||||
|
|||||||
@@ -124,3 +124,26 @@
|
|||||||
#### Scenario: 完整验证失败
|
#### Scenario: 完整验证失败
|
||||||
- **WHEN** `verify` 中任一阶段失败
|
- **WHEN** `verify` 中任一阶段失败
|
||||||
- **THEN** `verify` MUST 以非零状态退出且不能继续声明验证成功
|
- **THEN** `verify` MUST 以非零状态退出且不能继续声明验证成功
|
||||||
|
|
||||||
|
### Requirement: 测试代码 ESLint 禁用最小化
|
||||||
|
项目测试代码 SHALL 优先通过类型化 helper、类型化 mock、显式 no-op 和受控断言模式满足已启用的 ESLint 类型感知规则。受本变更审计的项目自有测试文件 MUST NOT 保留用于压制可通过代码结构解决的 `eslint-disable` 指令。
|
||||||
|
|
||||||
|
#### Scenario: 消除组件测试文件级禁用
|
||||||
|
- **WHEN** ESLint 检查 `tests/web/components/App.test.tsx`
|
||||||
|
- **THEN** 该文件 MUST 不使用文件级 `eslint-disable` 关闭 `@typescript-eslint/no-require-imports` 或 `@typescript-eslint/no-unsafe-*` 规则,并且测试中的 hook mock SHALL 使用类型化引用或等价方式访问 mock API
|
||||||
|
|
||||||
|
#### Scenario: 消除配置加载测试重复 await 禁用
|
||||||
|
- **WHEN** `tests/server/checker/config-loader.test.ts` 断言 `loadConfig()` 异步失败
|
||||||
|
- **THEN** 测试 SHALL 使用 helper 或显式 try/catch 断言错误实例与消息,MUST 不通过逐行 `eslint-disable-next-line @typescript-eslint/await-thenable` 压制 Bun `expect(...).rejects` 类型不匹配
|
||||||
|
|
||||||
|
#### Scenario: 测试环境 no-op polyfill 保持可解释
|
||||||
|
- **WHEN** `tests/setup.ts` 为 jsdom 测试环境定义浏览器 API polyfill
|
||||||
|
- **THEN** intentional no-op SHALL 使用显式可解释写法表达,MUST 不通过文件级 `eslint-disable @typescript-eslint/no-empty-function` 关闭空函数检查
|
||||||
|
|
||||||
|
#### Scenario: release 测试拦截 process.exit 保持窄作用域
|
||||||
|
- **WHEN** `tests/scripts/release.test.ts` 验证无效 release target 会触发 `process.exit(1)`
|
||||||
|
- **THEN** 测试 SHALL 使用受控 mock 或等价窄作用域替换并在断言后恢复,MUST 不通过 `eslint-disable-next-line @typescript-eslint/unbound-method` 保存未绑定方法
|
||||||
|
|
||||||
|
#### Scenario: 质量门禁验证禁用清理
|
||||||
|
- **WHEN** 开发者运行 `bun run lint`
|
||||||
|
- **THEN** ESLint MUST 检查项目自有测试代码并在无上述 `eslint-disable` 指令的情况下通过
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
||||||
import { mkdir, rm } from "node:fs/promises";
|
import { mkdir, rm } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
@@ -45,17 +45,12 @@ describe("parseTargets", () => {
|
|||||||
|
|
||||||
test("无效 target 导致进程退出", () => {
|
test("无效 target 导致进程退出", () => {
|
||||||
const exitCalls: number[] = [];
|
const exitCalls: number[] = [];
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
const exitSpy = spyOn(process, "exit").mockImplementation(((code: number) => {
|
||||||
const originalExit = process.exit;
|
|
||||||
process.exit = ((code: number) => {
|
|
||||||
exitCalls.push(code);
|
exitCalls.push(code);
|
||||||
}) as never;
|
}) as typeof process.exit);
|
||||||
|
|
||||||
try {
|
parseTargets(["--target", "invalid-target"]);
|
||||||
parseTargets(["--target", "invalid-target"]);
|
exitSpy.mockRestore();
|
||||||
} finally {
|
|
||||||
process.exit = originalExit;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(exitCalls).toEqual([1]);
|
expect(exitCalls).toEqual([1]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -104,6 +104,17 @@ describe("loadConfig", () => {
|
|||||||
expect((error as Error).message).toContain(message);
|
expect((error as Error).message).toContain(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function expectConfigLoadError(configPath: string, message: string): Promise<void> {
|
||||||
|
let error: unknown;
|
||||||
|
try {
|
||||||
|
await loadConfig(configPath);
|
||||||
|
} catch (caught) {
|
||||||
|
error = caught;
|
||||||
|
}
|
||||||
|
expect(error).toBeInstanceOf(Error);
|
||||||
|
expect((error as Error).message).toContain(message);
|
||||||
|
}
|
||||||
|
|
||||||
test("解析最简 HTTP 配置", async () => {
|
test("解析最简 HTTP 配置", async () => {
|
||||||
const configPath = join(tempDir, "minimal-http.yaml");
|
const configPath = join(tempDir, "minimal-http.yaml");
|
||||||
await writeFile(
|
await writeFile(
|
||||||
@@ -327,8 +338,7 @@ targets:
|
|||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "name 不能为空白");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("name 不能为空白");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("name 仅包含空白字符抛出错误", async () => {
|
test("name 仅包含空白字符抛出错误", async () => {
|
||||||
@@ -343,8 +353,7 @@ targets:
|
|||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "name 不能为空白");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("name 不能为空白");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("description 显式 null 保留为 null", async () => {
|
test("description 显式 null 保留为 null", async () => {
|
||||||
@@ -449,8 +458,7 @@ targets:
|
|||||||
maxRedirects: "\${max_redirects}"
|
maxRedirects: "\${max_redirects}"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "maxRedirects");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("maxRedirects");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("变量替换后通过 schema 校验", async () => {
|
test("变量替换后通过 schema 校验", async () => {
|
||||||
@@ -491,8 +499,7 @@ targets:
|
|||||||
url: "\${MISSING_BASE_URL}/health"
|
url: "\${MISSING_BASE_URL}/health"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "未定义的变量");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("未定义的变量");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("绝对 dataDir 保持不变", async () => {
|
test("绝对 dataDir 保持不变", async () => {
|
||||||
@@ -546,8 +553,7 @@ targets:
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("配置文件不存在抛出错误", async () => {
|
test("配置文件不存在抛出错误", async () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError("/nonexistent/file.yaml", "配置文件不存在");
|
||||||
await expect(loadConfig("/nonexistent/file.yaml")).rejects.toThrow("配置文件不存在");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("target 缺少 id 抛出错误", async () => {
|
test("target 缺少 id 抛出错误", async () => {
|
||||||
@@ -560,8 +566,7 @@ targets:
|
|||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "缺少 id 字段");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("缺少 id 字段");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("target 缺少 type 抛出错误", async () => {
|
test("target 缺少 type 抛出错误", async () => {
|
||||||
@@ -575,8 +580,7 @@ targets:
|
|||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "缺少 type 字段");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("缺少 type 字段");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("HTTP target 缺少 url 抛出错误", async () => {
|
test("HTTP target 缺少 url 抛出错误", async () => {
|
||||||
@@ -590,8 +594,7 @@ targets:
|
|||||||
http: {}
|
http: {}
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "缺少 http.url 字段");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("缺少 http.url 字段");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("HTTP target 缺少 http 分组抛出清晰错误", async () => {
|
test("HTTP target 缺少 http 分组抛出清晰错误", async () => {
|
||||||
@@ -604,8 +607,7 @@ targets:
|
|||||||
type: http
|
type: http
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "缺少 http.url 字段");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("缺少 http.url 字段");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("HTTP target ignoreSSL 非布尔值抛出错误", async () => {
|
test("HTTP target ignoreSSL 非布尔值抛出错误", async () => {
|
||||||
@@ -621,8 +623,7 @@ targets:
|
|||||||
ignoreSSL: "true"
|
ignoreSSL: "true"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "http.ignoreSSL 类型不合法");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("http.ignoreSSL 类型不合法");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("HTTP target maxRedirects 非负整数校验", async () => {
|
test("HTTP target maxRedirects 非负整数校验", async () => {
|
||||||
@@ -638,8 +639,7 @@ targets:
|
|||||||
maxRedirects: 1.5
|
maxRedirects: 1.5
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "http.maxRedirects 类型不合法");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("http.maxRedirects 类型不合法");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("HTTP target status 模式非法抛出错误", async () => {
|
test("HTTP target status 模式非法抛出错误", async () => {
|
||||||
@@ -656,8 +656,7 @@ targets:
|
|||||||
status: ["abc"]
|
status: ["abc"]
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "status 模式");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("status 模式");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("cmd target 缺少 exec 抛出错误", async () => {
|
test("cmd target 缺少 exec 抛出错误", async () => {
|
||||||
@@ -671,8 +670,7 @@ targets:
|
|||||||
cmd: {}
|
cmd: {}
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "缺少 cmd.exec 字段");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("缺少 cmd.exec 字段");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("非法 target type 抛出错误", async () => {
|
test("非法 target type 抛出错误", async () => {
|
||||||
@@ -685,8 +683,7 @@ targets:
|
|||||||
type: dns
|
type: dns
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "不支持的 type");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("不支持的 type");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("target id 重复抛出错误", async () => {
|
test("target id 重复抛出错误", async () => {
|
||||||
@@ -706,8 +703,7 @@ targets:
|
|||||||
url: "http://b.com"
|
url: "http://b.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "target id 重复");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("target id 重复");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("target id 为空字符串抛出错误", async () => {
|
test("target id 为空字符串抛出错误", async () => {
|
||||||
@@ -721,8 +717,7 @@ targets:
|
|||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "缺少 id 字段");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("缺少 id 字段");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("target id 命名不合法抛出错误", async () => {
|
test("target id 命名不合法抛出错误", async () => {
|
||||||
@@ -736,8 +731,7 @@ targets:
|
|||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "id 不符合命名规则");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("id 不符合命名规则");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("target id 包含下划线和连字符通过", async () => {
|
test("target id 包含下划线和连字符通过", async () => {
|
||||||
@@ -758,8 +752,7 @@ targets:
|
|||||||
test("targets 为空数组抛出错误", async () => {
|
test("targets 为空数组抛出错误", async () => {
|
||||||
const configPath = join(tempDir, "empty-targets.yaml");
|
const configPath = join(tempDir, "empty-targets.yaml");
|
||||||
await writeFile(configPath, `targets: []`);
|
await writeFile(configPath, `targets: []`);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "至少一个 target");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("至少一个 target");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("无效端口号抛出错误", async () => {
|
test("无效端口号抛出错误", async () => {
|
||||||
@@ -776,8 +769,7 @@ targets:
|
|||||||
url: "http://a.com"
|
url: "http://a.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "server.port 数值范围不合法");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("server.port 数值范围不合法");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("非法 maxConcurrentChecks 抛出错误", async () => {
|
test("非法 maxConcurrentChecks 抛出错误", async () => {
|
||||||
@@ -794,8 +786,7 @@ targets:
|
|||||||
url: "http://a.com"
|
url: "http://a.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "runtime.maxConcurrentChecks 数值范围不合法");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("runtime.maxConcurrentChecks 数值范围不合法");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("非法 size 格式抛出错误", async () => {
|
test("非法 size 格式抛出错误", async () => {
|
||||||
@@ -813,8 +804,7 @@ targets:
|
|||||||
url: "http://a.com"
|
url: "http://a.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "无效的 size 格式");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("无效的 size 格式");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("非法 interval 格式抛出错误", async () => {
|
test("非法 interval 格式抛出错误", async () => {
|
||||||
@@ -830,8 +820,7 @@ targets:
|
|||||||
url: "http://a.com"
|
url: "http://a.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "无效的时长格式");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("无效的时长格式");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("解析 expect 配置", async () => {
|
test("解析 expect 配置", async () => {
|
||||||
@@ -1011,8 +1000,7 @@ targets:
|
|||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "group 必须为字符串");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("group 必须为字符串");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("HTTP headers 非字符串值抛出错误", async () => {
|
test("HTTP headers 非字符串值抛出错误", async () => {
|
||||||
@@ -1029,8 +1017,7 @@ targets:
|
|||||||
X-Custom: 123
|
X-Custom: 123
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "http.headers");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("http.headers");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("HTTP body 非字符串抛出错误", async () => {
|
test("HTTP body 非字符串抛出错误", async () => {
|
||||||
@@ -1046,8 +1033,7 @@ targets:
|
|||||||
body: 123
|
body: 123
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "http.body 类型不合法");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("http.body 类型不合法");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("maxBodyBytes 负数抛出错误", async () => {
|
test("maxBodyBytes 负数抛出错误", async () => {
|
||||||
@@ -1063,8 +1049,7 @@ targets:
|
|||||||
maxBodyBytes: -1
|
maxBodyBytes: -1
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "非负安全整数");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("非负安全整数");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("maxBodyBytes 非整数抛出错误", async () => {
|
test("maxBodyBytes 非整数抛出错误", async () => {
|
||||||
@@ -1080,8 +1065,7 @@ targets:
|
|||||||
maxBodyBytes: 1.5
|
maxBodyBytes: 1.5
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "非负安全整数");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("非负安全整数");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("expect.status 数字不在 100-599 范围抛出错误", async () => {
|
test("expect.status 数字不在 100-599 范围抛出错误", async () => {
|
||||||
@@ -1098,8 +1082,7 @@ targets:
|
|||||||
status: [999]
|
status: [999]
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "100-599");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("100-599");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("expect.status 范围 6xx 抛出错误", async () => {
|
test("expect.status 范围 6xx 抛出错误", async () => {
|
||||||
@@ -1116,8 +1099,7 @@ targets:
|
|||||||
status: ["6xx"]
|
status: ["6xx"]
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "5xx");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("5xx");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("expect.durationMs 对象简写抛出错误", async () => {
|
test("expect.durationMs 对象简写抛出错误", async () => {
|
||||||
@@ -1135,8 +1117,7 @@ targets:
|
|||||||
foo: "bar"
|
foo: "bar"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "expect.durationMs.foo 是未知 matcher");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("expect.durationMs.foo 是未知 matcher");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("expect.body 非数组抛出错误", async () => {
|
test("expect.body 非数组抛出错误", async () => {
|
||||||
@@ -1153,8 +1134,7 @@ targets:
|
|||||||
body: "not-array"
|
body: "not-array"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "expect.body 必须为数组");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("expect.body 必须为数组");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("body rule 缺少支持字段抛出错误", async () => {
|
test("body rule 缺少支持字段抛出错误", async () => {
|
||||||
@@ -1172,8 +1152,7 @@ targets:
|
|||||||
- foo: "bar"
|
- foo: "bar"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "foo 是未知字段");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("foo 是未知字段");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("body rule 使用 match 字段(非支持)抛出错误", async () => {
|
test("body rule 使用 match 字段(非支持)抛出错误", async () => {
|
||||||
@@ -1191,8 +1170,7 @@ targets:
|
|||||||
- match: "ok"
|
- match: "ok"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "match 是未知字段");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("match 是未知字段");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("body rule 直接 matcher 混入 extractor 抛出错误", async () => {
|
test("body rule 直接 matcher 混入 extractor 抛出错误", async () => {
|
||||||
@@ -1212,8 +1190,7 @@ targets:
|
|||||||
path: "$.status"
|
path: "$.status"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "直接 matcher 不能与 extractor 混用");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("直接 matcher 不能与 extractor 混用");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("body regex 非法正则抛出错误", async () => {
|
test("body regex 非法正则抛出错误", async () => {
|
||||||
@@ -1231,8 +1208,7 @@ targets:
|
|||||||
- regex: "[invalid"
|
- regex: "[invalid"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "regex 正则不合法");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("regex 正则不合法");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("body json path 不以 $. 开头抛出错误", async () => {
|
test("body json path 不以 $. 开头抛出错误", async () => {
|
||||||
@@ -1252,8 +1228,7 @@ targets:
|
|||||||
equals: "ok"
|
equals: "ok"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "json.path");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("json.path");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("body css selector 为空抛出错误", async () => {
|
test("body css selector 为空抛出错误", async () => {
|
||||||
@@ -1272,8 +1247,7 @@ targets:
|
|||||||
selector: ""
|
selector: ""
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "css.selector 必须为非空字符串");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("css.selector 必须为非空字符串");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("旧 match matcher 抛出错误", async () => {
|
test("旧 match matcher 抛出错误", async () => {
|
||||||
@@ -1292,8 +1266,7 @@ targets:
|
|||||||
match: "[invalid"
|
match: "[invalid"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "match 是未知 matcher");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("match 是未知 matcher");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("operator gte 非数字抛出错误", async () => {
|
test("operator gte 非数字抛出错误", async () => {
|
||||||
@@ -1313,8 +1286,7 @@ targets:
|
|||||||
gte: "abc"
|
gte: "abc"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "gte 必须为有限数字");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("gte 必须为有限数字");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("operator exists 非布尔值抛出错误", async () => {
|
test("operator exists 非布尔值抛出错误", async () => {
|
||||||
@@ -1334,8 +1306,7 @@ targets:
|
|||||||
exists: "yes"
|
exists: "yes"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "exists 必须为布尔值");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("exists 必须为布尔值");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("未知字段导致启动失败", async () => {
|
test("未知字段导致启动失败", async () => {
|
||||||
@@ -1357,8 +1328,7 @@ targets:
|
|||||||
note: "ignored"
|
note: "ignored"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "unknownHttpField 是未知字段");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("unknownHttpField 是未知字段");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("xpath path 非空字符串校验", async () => {
|
test("xpath path 非空字符串校验", async () => {
|
||||||
@@ -1377,8 +1347,7 @@ targets:
|
|||||||
path: ""
|
path: ""
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "xpath.path 必须为非空字符串");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("xpath.path 必须为非空字符串");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("expect headers 非对象抛出错误", async () => {
|
test("expect headers 非对象抛出错误", async () => {
|
||||||
@@ -1395,8 +1364,7 @@ targets:
|
|||||||
headers: "invalid"
|
headers: "invalid"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "expect.headers 类型不合法");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("expect.headers 类型不合法");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("HTTP method 小写输入失败", async () => {
|
test("HTTP method 小写输入失败", async () => {
|
||||||
@@ -1760,8 +1728,7 @@ targets:
|
|||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "description");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("description");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("description 超过 500 字符抛出错误", async () => {
|
test("description 超过 500 字符抛出错误", async () => {
|
||||||
@@ -1776,8 +1743,7 @@ targets:
|
|||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "description");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("description");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("变量替换后 description 超长抛出错误", async () => {
|
test("变量替换后 description 超长抛出错误", async () => {
|
||||||
@@ -1794,8 +1760,7 @@ targets:
|
|||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "description");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("description");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("id 超过 30 字符抛出错误", async () => {
|
test("id 超过 30 字符抛出错误", async () => {
|
||||||
@@ -1809,8 +1774,7 @@ targets:
|
|||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "id");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("id");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("name 超过 30 字符抛出错误", async () => {
|
test("name 超过 30 字符抛出错误", async () => {
|
||||||
@@ -1825,8 +1789,7 @@ targets:
|
|||||||
url: "http://example.com"
|
url: "http://example.com"
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
await expectConfigLoadError(configPath, "name");
|
||||||
await expect(loadConfig(configPath)).rejects.toThrow("name");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("解析最简 tcp 配置", async () => {
|
test("解析最简 tcp 配置", async () => {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
* 组件测试使用各自的 test-utils.tsx
|
* 组件测试使用各自的 test-utils.tsx
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
// Set up jsdom for ALL tests (both backend and frontend)
|
// Set up jsdom for ALL tests (both backend and frontend)
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
|
|
||||||
@@ -34,8 +33,10 @@ const nodeProto = dom.window.Node.prototype;
|
|||||||
const elementProto = dom.window.Element.prototype;
|
const elementProto = dom.window.Element.prototype;
|
||||||
const htmlElementProto = dom.window.HTMLElement.prototype;
|
const htmlElementProto = dom.window.HTMLElement.prototype;
|
||||||
|
|
||||||
const attachEventFn = () => {};
|
const noop = () => undefined;
|
||||||
const detachEventFn = () => {};
|
|
||||||
|
const attachEventFn = noop;
|
||||||
|
const detachEventFn = noop;
|
||||||
|
|
||||||
Object.defineProperty(nodeProto, "attachEvent", { configurable: true, value: attachEventFn, writable: true });
|
Object.defineProperty(nodeProto, "attachEvent", { configurable: true, value: attachEventFn, writable: true });
|
||||||
Object.defineProperty(nodeProto, "detachEvent", { configurable: true, value: detachEventFn, writable: true });
|
Object.defineProperty(nodeProto, "detachEvent", { configurable: true, value: detachEventFn, writable: true });
|
||||||
@@ -46,27 +47,53 @@ Object.defineProperty(htmlElementProto, "detachEvent", { configurable: true, val
|
|||||||
|
|
||||||
// Other polyfills
|
// Other polyfills
|
||||||
globalThis.ResizeObserver = class {
|
globalThis.ResizeObserver = class {
|
||||||
disconnect() {}
|
disconnect() {
|
||||||
observe() {}
|
return undefined;
|
||||||
unobserve() {}
|
}
|
||||||
|
|
||||||
|
observe() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
unobserve() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
globalThis.MutationObserver = class {
|
globalThis.MutationObserver = class {
|
||||||
disconnect() {}
|
disconnect() {
|
||||||
observe() {}
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
observe() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
takeRecords() {
|
takeRecords() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
unobserve() {}
|
|
||||||
|
unobserve() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
globalThis.IntersectionObserver = class {
|
globalThis.IntersectionObserver = class {
|
||||||
disconnect() {}
|
disconnect() {
|
||||||
observe() {}
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
observe() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
takeRecords() {
|
takeRecords() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
unobserve() {}
|
|
||||||
|
unobserve() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
} as unknown as typeof IntersectionObserver;
|
} as unknown as typeof IntersectionObserver;
|
||||||
|
|
||||||
globalThis.requestAnimationFrame = (cb: FrameRequestCallback) => setTimeout(cb, 16);
|
globalThis.requestAnimationFrame = (cb: FrameRequestCallback) => setTimeout(cb, 16);
|
||||||
@@ -74,24 +101,24 @@ globalThis.cancelAnimationFrame = (id: number) => clearTimeout(id);
|
|||||||
|
|
||||||
Object.defineProperty(dom.window, "matchMedia", {
|
Object.defineProperty(dom.window, "matchMedia", {
|
||||||
value: (query: string) => ({
|
value: (query: string) => ({
|
||||||
addEventListener: () => {},
|
addEventListener: noop,
|
||||||
addListener: () => {},
|
addListener: noop,
|
||||||
dispatchEvent: () => true,
|
dispatchEvent: () => true,
|
||||||
matches: false,
|
matches: false,
|
||||||
media: query,
|
media: query,
|
||||||
onchange: null,
|
onchange: null,
|
||||||
removeEventListener: () => {},
|
removeEventListener: noop,
|
||||||
removeListener: () => {},
|
removeListener: noop,
|
||||||
}),
|
}),
|
||||||
writable: true,
|
writable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
dom.window.Element.prototype.scrollTo = () => {};
|
dom.window.Element.prototype.scrollTo = noop;
|
||||||
dom.window.Element.prototype.scrollIntoView = () => {};
|
dom.window.Element.prototype.scrollIntoView = noop;
|
||||||
|
|
||||||
Object.defineProperty(dom.window, "customElements", {
|
Object.defineProperty(dom.window, "customElements", {
|
||||||
value: {
|
value: {
|
||||||
define: () => {},
|
define: noop,
|
||||||
get: () => undefined,
|
get: () => undefined,
|
||||||
},
|
},
|
||||||
writable: true,
|
writable: true,
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
||||||
import "../../../tests/web/test-utils";
|
import "../../../tests/web/test-utils";
|
||||||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||||
import { afterEach, beforeEach, describe, expect, test, vi } from "bun:test";
|
import { afterEach, beforeEach, describe, expect, test, vi } from "bun:test";
|
||||||
@@ -9,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "bun:test";
|
|||||||
import { App } from "../../../src/web/app";
|
import { App } from "../../../src/web/app";
|
||||||
import { THEME_MEDIA_QUERY, THEME_PREFERENCE_STORAGE_KEY } from "../../../src/web/hooks/use-theme-preference";
|
import { THEME_MEDIA_QUERY, THEME_PREFERENCE_STORAGE_KEY } from "../../../src/web/hooks/use-theme-preference";
|
||||||
|
|
||||||
function createDashboardResult(overrides = {}) {
|
function createDashboardResult(overrides: Record<string, unknown> = {}) {
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
summary: {
|
summary: {
|
||||||
@@ -35,6 +31,40 @@ function createDashboardResult(overrides = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const useDashboardMock = vi.fn(() => createDashboardResult());
|
||||||
|
const useMetaMock = vi.fn(() => ({
|
||||||
|
data: { checkerTypes: ["http", "cmd"] as string[], version: "0.1.0" },
|
||||||
|
}));
|
||||||
|
const useTargetDetailMock = vi.fn(() => ({
|
||||||
|
activeTab: "overview",
|
||||||
|
closeDrawer: vi.fn(),
|
||||||
|
handlePageChange: vi.fn(),
|
||||||
|
handleTabChange: vi.fn(),
|
||||||
|
handleTimeChange: vi.fn(),
|
||||||
|
historyData: {
|
||||||
|
items: [],
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
historyLoading: false,
|
||||||
|
metricsData: null,
|
||||||
|
metricsLoading: false,
|
||||||
|
openDrawer: vi.fn(),
|
||||||
|
selectedTarget: null,
|
||||||
|
timeFrom: "",
|
||||||
|
timeTo: "",
|
||||||
|
}));
|
||||||
|
|
||||||
|
void vi.mock("../../../src/web/hooks/use-queries", () => ({
|
||||||
|
useDashboard: useDashboardMock,
|
||||||
|
useMeta: useMetaMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
void vi.mock("../../../src/web/hooks/use-target-detail", () => ({
|
||||||
|
useTargetDetail: useTargetDetailMock,
|
||||||
|
}));
|
||||||
|
|
||||||
function installMatchMedia(initialMatches: boolean) {
|
function installMatchMedia(initialMatches: boolean) {
|
||||||
const originalMatchMedia = window.matchMedia;
|
const originalMatchMedia = window.matchMedia;
|
||||||
let matches = initialMatches;
|
let matches = initialMatches;
|
||||||
@@ -65,43 +95,11 @@ function installMatchMedia(initialMatches: boolean) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock hooks
|
|
||||||
void vi.mock("../../../src/web/hooks/use-queries", () => ({
|
|
||||||
useDashboard: vi.fn(() => createDashboardResult()),
|
|
||||||
useMeta: vi.fn(() => ({
|
|
||||||
data: { checkerTypes: ["http", "cmd"], version: "0.1.0" },
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
void vi.mock("../../../src/web/hooks/use-target-detail", () => ({
|
|
||||||
useTargetDetail: vi.fn(() => ({
|
|
||||||
activeTab: "overview",
|
|
||||||
closeDrawer: vi.fn(),
|
|
||||||
handlePageChange: vi.fn(),
|
|
||||||
handleTabChange: vi.fn(),
|
|
||||||
handleTimeChange: vi.fn(),
|
|
||||||
historyData: {
|
|
||||||
items: [],
|
|
||||||
page: 1,
|
|
||||||
pageSize: 20,
|
|
||||||
total: 0,
|
|
||||||
},
|
|
||||||
historyLoading: false,
|
|
||||||
metricsData: null,
|
|
||||||
metricsLoading: false,
|
|
||||||
openDrawer: vi.fn(),
|
|
||||||
selectedTarget: null,
|
|
||||||
timeFrom: "",
|
|
||||||
timeTo: "",
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe("App", () => {
|
describe("App", () => {
|
||||||
let matchMediaController: ReturnType<typeof installMatchMedia>;
|
let matchMediaController: ReturnType<typeof installMatchMedia>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const { useDashboard } = require("../../../src/web/hooks/use-queries");
|
useDashboardMock.mockReturnValue(createDashboardResult());
|
||||||
useDashboard.mockReturnValue(createDashboardResult());
|
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
document.documentElement.removeAttribute("theme-mode");
|
document.documentElement.removeAttribute("theme-mode");
|
||||||
matchMediaController = installMatchMedia(false);
|
matchMediaController = installMatchMedia(false);
|
||||||
@@ -117,8 +115,7 @@ describe("App", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("loading 状态不崩溃", () => {
|
test("loading 状态不崩溃", () => {
|
||||||
const { useDashboard } = require("../../../src/web/hooks/use-queries");
|
useDashboardMock.mockReturnValue(
|
||||||
useDashboard.mockReturnValue(
|
|
||||||
createDashboardResult({
|
createDashboardResult({
|
||||||
data: null,
|
data: null,
|
||||||
dataUpdatedAt: 0,
|
dataUpdatedAt: 0,
|
||||||
@@ -134,12 +131,11 @@ describe("App", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("错误状态不崩溃", () => {
|
test("错误状态不崩溃", () => {
|
||||||
const { useDashboard } = require("../../../src/web/hooks/use-queries");
|
useDashboardMock.mockReturnValue(
|
||||||
useDashboard.mockReturnValue(
|
|
||||||
createDashboardResult({
|
createDashboardResult({
|
||||||
data: null,
|
data: null,
|
||||||
dataUpdatedAt: 0,
|
dataUpdatedAt: 0,
|
||||||
error: { message: "Network error" },
|
error: new Error("Network error"),
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
refetch: vi.fn(),
|
refetch: vi.fn(),
|
||||||
@@ -151,8 +147,7 @@ describe("App", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("有数据状态不崩溃", () => {
|
test("有数据状态不崩溃", () => {
|
||||||
const { useDashboard } = require("../../../src/web/hooks/use-queries");
|
useDashboardMock.mockReturnValue(
|
||||||
useDashboard.mockReturnValue(
|
|
||||||
createDashboardResult({
|
createDashboardResult({
|
||||||
data: {
|
data: {
|
||||||
summary: {
|
summary: {
|
||||||
@@ -215,9 +210,8 @@ describe("App", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("缺失版本时不展示版本占位", () => {
|
test("缺失版本时不展示版本占位", () => {
|
||||||
const { useMeta } = require("../../../src/web/hooks/use-queries");
|
useMetaMock.mockReturnValue({
|
||||||
useMeta.mockReturnValue({
|
data: { checkerTypes: ["http", "cmd"], version: undefined as unknown as string },
|
||||||
data: { checkerTypes: ["http", "cmd"] },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
render(<App />);
|
render(<App />);
|
||||||
@@ -225,8 +219,7 @@ describe("App", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("复用 useMeta 查询结果", () => {
|
test("复用 useMeta 查询结果", () => {
|
||||||
const { useMeta } = require("../../../src/web/hooks/use-queries");
|
|
||||||
render(<App />);
|
render(<App />);
|
||||||
expect(useMeta).toHaveBeenCalled();
|
expect(useMetaMock).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user