mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
前端测试套件 44 失败→全绿:10 个测试文件补 NextIntlClientProvider 包装(含 React19 createElement 类型修复、.ts→.tsx 重命名);修复真实 i18n ICU bug(githubUrlInvalid 的 {owner}/{repo} 未转义导致生产渲染成 key,zh/en + fragment 4 文件同步转义);更新 2 处过期测试期望。vitest 116/116 + tsc + eslint 全绿,checks.sh 增加前端测试门禁。
Result: {"status":"keep","total_issues":8,"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"golint_vetx_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"measure_s":39}
This commit is contained in:
@@ -22,4 +22,11 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 前端测试(vitest;2026-08-16 起全绿)
|
||||||
|
echo "==> pnpm exec vitest run (frontend)"
|
||||||
|
(cd frontend && pnpm exec vitest run --reporter=dot > /tmp/auto_vitest.log 2>&1) || {
|
||||||
|
tail -30 /tmp/auto_vitest.log
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
echo "OK: checks passed"
|
echo "OK: checks passed"
|
||||||
@@ -15,3 +15,4 @@
|
|||||||
{"run":14,"commit":"65c02ef","metric":8,"metrics":{"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"measure_s":36},"status":"keep","description":"基准扩展 exhaustive(文档化)+ 12→0:枚举 switch 补显式 case(全部与现有 default 行为等价,fail-explicit 防未来枚举静默落入 default);source_tasks.go 为控制复杂度合并两个等价校验条件。","timestamp":1786875548060,"segment":0,"confidence":4.25531914893617,"asi":{"hypothesis":"基准扩展 exhaustive(12 处枚举 switch 显式化)+ 全量修复","insight":"12 处全部是 default 已正确处理、缺显式 case 的类型;补显式 case 仅为 fail-explicit(未来枚举新增不会静默落入 default)。source_tasks 补 case 后 Execute 复杂度 20→21 触发 cyclop,合并两个 ActionInvalid 条件(逻辑等价)降回 19。cyclop 与 exhaustive 的张力:显式 case 也计入复杂度","next_action_hint":"剩余 8 全为刻意保留。可再深化:sloglint 全量、govet 附加分析器、或前端 jsx-a11y/next 规则已有覆盖。也可将剩余 8 处文档化后收尾总结"}}
|
{"run":14,"commit":"65c02ef","metric":8,"metrics":{"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"measure_s":36},"status":"keep","description":"基准扩展 exhaustive(文档化)+ 12→0:枚举 switch 补显式 case(全部与现有 default 行为等价,fail-explicit 防未来枚举静默落入 default);source_tasks.go 为控制复杂度合并两个等价校验条件。","timestamp":1786875548060,"segment":0,"confidence":4.25531914893617,"asi":{"hypothesis":"基准扩展 exhaustive(12 处枚举 switch 显式化)+ 全量修复","insight":"12 处全部是 default 已正确处理、缺显式 case 的类型;补显式 case 仅为 fail-explicit(未来枚举新增不会静默落入 default)。source_tasks 补 case 后 Execute 复杂度 20→21 触发 cyclop,合并两个 ActionInvalid 条件(逻辑等价)降回 19。cyclop 与 exhaustive 的张力:显式 case 也计入复杂度","next_action_hint":"剩余 8 全为刻意保留。可再深化:sloglint 全量、govet 附加分析器、或前端 jsx-a11y/next 规则已有覆盖。也可将剩余 8 处文档化后收尾总结"}}
|
||||||
{"run":15,"commit":"d7b8f44","metric":8,"metrics":{"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"golint_vetx_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"measure_s":37},"status":"keep","description":"修复 geoip/runtime.go 真死代码:ensureServerMMDB 的 os.Stat 错误被 if-init 遮蔽,`err != nil && !os.IsNotExist(err)` 恒为 false(外层 err 恒 nil),防御检查从未生效;改为显式捕获 statErr,stat 非 not-exist 错误现在正确返回。基准新增第 4 维度 govet nilness+unusedwrite(文档化扩展),当前 0。","timestamp":1786875949461,"segment":0,"confidence":4.166666666666667,"asi":{"hypothesis":"govet nilness 真实死代码 bug:ensureServerMMDB 的 stat 错误被 if-init 遮蔽,!os.IsNotExist(err) 恒为死条件(外层 err 恒 nil)","insight":"修复:显式捕获 statErr,使防御检查生效(stat 权限错误现在立即返回,不再静默吞掉后走 WriteFile 失败)。顺带基准扩展第 4 维度 govet nilness+unusedwrite(文档化,survey 过 fatcontext/containedctx/unparam/gocritic+29 检查:unparam 有 6+ 处真实死结果但需签名改动,留待下轮)","next_action_hint":"下轮候选:unparam(6+ 处 always-nil/never-used 结果,含 getSQLiteOverview/getPostgresOverview/getStatus 等,需改签名+调用方,churn 中等但都是真实死代码);或 fatcontext/containedctx(3+3 处,需逐处判断是否真反模式)"}}
|
{"run":15,"commit":"d7b8f44","metric":8,"metrics":{"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"golint_vetx_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"measure_s":37},"status":"keep","description":"修复 geoip/runtime.go 真死代码:ensureServerMMDB 的 os.Stat 错误被 if-init 遮蔽,`err != nil && !os.IsNotExist(err)` 恒为 false(外层 err 恒 nil),防御检查从未生效;改为显式捕获 statErr,stat 非 not-exist 错误现在正确返回。基准新增第 4 维度 govet nilness+unusedwrite(文档化扩展),当前 0。","timestamp":1786875949461,"segment":0,"confidence":4.166666666666667,"asi":{"hypothesis":"govet nilness 真实死代码 bug:ensureServerMMDB 的 stat 错误被 if-init 遮蔽,!os.IsNotExist(err) 恒为死条件(外层 err 恒 nil)","insight":"修复:显式捕获 statErr,使防御检查生效(stat 权限错误现在立即返回,不再静默吞掉后走 WriteFile 失败)。顺带基准扩展第 4 维度 govet nilness+unusedwrite(文档化,survey 过 fatcontext/containedctx/unparam/gocritic+29 检查:unparam 有 6+ 处真实死结果但需签名改动,留待下轮)","next_action_hint":"下轮候选:unparam(6+ 处 always-nil/never-used 结果,含 getSQLiteOverview/getPostgresOverview/getStatus 等,需改签名+调用方,churn 中等但都是真实死代码);或 fatcontext/containedctx(3+3 处,需逐处判断是否真反模式)"}}
|
||||||
{"run":16,"commit":"c85373f","metric":8,"metrics":{"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"golint_vetx_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"measure_s":43},"status":"keep","description":"unparam 死代码清理 12→2(保留 2 处 objectstore 构造函数统一签名):移除 10 处恒 nil error / 从未使用的结果(getPoWConfigForRoute 的恒 nil *PoWConfig、getSQLiteOverview/getPostgresOverview/getStatus/loadKumaConfig/filterExpectedRoutes 的恒 nil error、rawJSONString/parsePositiveInt 的弃用 bool、buildProxyRoute 的弃用 []ZoneDomain、getLocked 的恒 nil error),同步简化 12+ 处调用方与死错误检查。9 个受影响包测试通过。metric 持平 8(改进在基准之外)。","timestamp":1786876191447,"segment":0,"confidence":5.128205128205129,"asi":{"hypothesis":"unparam 死代码清理:10 处 always-nil error / never-used 结果从签名移除","insight":"移除后调用方同步简化(db_manage 的 err 检查、option routers 的 AbortBadRequestOnError 成为死代码一并删)。getPoWConfigForRoute 的 *PoWConfig 结果恒 nil 且从未被用 —— 真死代码。保留 2 处 objectstore 构造函数 (X, error):factory switch 统一签名(newS3Backend/newLocalBackend 等可能真实报错),unparam 在此为接口一致性误报。全部 9 个受影响包测试通过。metric 持平 8(改进在基准之外,诚实记录)","next_action_hint":"下一候选:fatcontext(3 处嵌套 context 闭包,多为 slog/otel ctx 传递,需逐处判断是否真反模式) 或 containedctx(3 处 struct 含 ctx 字段,含 webdav/uptimekuma client —— 重构风险中等);或收尾把 unparam 加入基准(2 处已知保留)"}}
|
{"run":16,"commit":"c85373f","metric":8,"metrics":{"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"golint_vetx_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"measure_s":43},"status":"keep","description":"unparam 死代码清理 12→2(保留 2 处 objectstore 构造函数统一签名):移除 10 处恒 nil error / 从未使用的结果(getPoWConfigForRoute 的恒 nil *PoWConfig、getSQLiteOverview/getPostgresOverview/getStatus/loadKumaConfig/filterExpectedRoutes 的恒 nil error、rawJSONString/parsePositiveInt 的弃用 bool、buildProxyRoute 的弃用 []ZoneDomain、getLocked 的恒 nil error),同步简化 12+ 处调用方与死错误检查。9 个受影响包测试通过。metric 持平 8(改进在基准之外)。","timestamp":1786876191447,"segment":0,"confidence":5.128205128205129,"asi":{"hypothesis":"unparam 死代码清理:10 处 always-nil error / never-used 结果从签名移除","insight":"移除后调用方同步简化(db_manage 的 err 检查、option routers 的 AbortBadRequestOnError 成为死代码一并删)。getPoWConfigForRoute 的 *PoWConfig 结果恒 nil 且从未被用 —— 真死代码。保留 2 处 objectstore 构造函数 (X, error):factory switch 统一签名(newS3Backend/newLocalBackend 等可能真实报错),unparam 在此为接口一致性误报。全部 9 个受影响包测试通过。metric 持平 8(改进在基准之外,诚实记录)","next_action_hint":"下一候选:fatcontext(3 处嵌套 context 闭包,多为 slog/otel ctx 传递,需逐处判断是否真反模式) 或 containedctx(3 处 struct 含 ctx 字段,含 webdav/uptimekuma client —— 重构风险中等);或收尾把 unparam 加入基准(2 处已知保留)"}}
|
||||||
|
{"run":17,"commit":"a16be01","metric":8,"metrics":{"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"golint_vetx_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"measure_s":40},"status":"keep","description":"修复 frpc 进程生命周期真 bug(agent 生产代码):exec.CommandContext 默认只杀直接子进程,被杀 shell 的孤儿 sleep 继续持有 stderr 管道,cmd.Wait() 阻塞到其自然退出(Stop/重启可挂起秒级)。改 Setpgid 进程组 + Kill(-pid) 整组击杀。连带修复两个测试 bug(Manager 拥有 Cmd 的并发 Wait 竞态 → Signal(0) 探测;ssl_renew 用 miniredis 替代 init() 创建的真实 redis 客户端)。go test ./internal/... ./pkg/... 全绿,checks.sh 升级为真实测试门禁。","timestamp":1786877266517,"segment":0,"confidence":7.142857142857143,"asi":{"hypothesis":"frpc 进程生命周期真 bug:exec.CommandContext 只杀直接子进程,孤儿孙进程持有 stderr 管道导致 cmd.Wait 阻塞到其自然退出(实测脚本 sleep 5 时 Stop 挂起 5s)","insight":"修复:Setpgid 独立进程组 + cmd.Cancel 覆盖为 Kill(-pid,SIGKILL) 整组击杀(经隔离复现 + 临时插桩定位,4 次假设检验收敛)。连带修复两个测试 bug:TestStopCancelsRunningProcesses 对 Manager 拥有的 Cmd 并发 Wait(与 os/exec ctxResult 通道竞争永久挂起)改为 Signal(0) 探测;ssl_renew 测试改用 miniredis(task 包 init() 创建真实 redis 客户端,违反 repo 无 init 装配约束)。成果:go test ./internal/... ./pkg/... 从 3 个失败→全绿(81+13 包),checks.sh 升级为真实测试门禁。metric 持平 8(改进在基准之外,但价值最高的一轮)","next_action_hint":"测试全绿后可解锁:paralleltest/tparallel 维度(t.Parallel 提速)——需先评估共享状态(miniredis/sqlite 每测试独立,风险低);或探索 relay/frps 同构代码是否有同样的 group-kill 问题(frps/manager 结构相同,值得检查)"}}
|
||||||
|
|||||||
@@ -3243,7 +3243,7 @@
|
|||||||
"switchFailed": "Switch failed",
|
"switchFailed": "Switch failed",
|
||||||
"urlRequired": "Enter a Remote URL",
|
"urlRequired": "Enter a Remote URL",
|
||||||
"urlInvalid": "Enter a valid HTTP(S) URL",
|
"urlInvalid": "Enter a valid HTTP(S) URL",
|
||||||
"githubUrlInvalid": "Enter a public repository URL in https://github.com/{owner}/{repo} format",
|
"githubUrlInvalid": "Enter a public repository URL in https://github.com/'{owner}'/'{repo}' format",
|
||||||
"tagInvalid": "Release tag must be a valid Git ref (1–255 bytes; / # & = are allowed)",
|
"tagInvalid": "Release tag must be a valid Git ref (1–255 bytes; / # & = are allowed)",
|
||||||
"assetInvalid": "Asset name must be 1–255 bytes and cannot be a path or contain control, newline, or bidi characters",
|
"assetInvalid": "Asset name must be 1–255 bytes and cannot be a path or contain control, newline, or bidi characters",
|
||||||
"intervalInvalid": "Check interval must be an integer between 5 and 1440 minutes",
|
"intervalInvalid": "Check interval must be an integer between 5 and 1440 minutes",
|
||||||
|
|||||||
@@ -499,7 +499,7 @@
|
|||||||
"switchFailed": "Switch failed",
|
"switchFailed": "Switch failed",
|
||||||
"urlRequired": "Enter a Remote URL",
|
"urlRequired": "Enter a Remote URL",
|
||||||
"urlInvalid": "Enter a valid HTTP(S) URL",
|
"urlInvalid": "Enter a valid HTTP(S) URL",
|
||||||
"githubUrlInvalid": "Enter a public repository URL in https://github.com/{owner}/{repo} format",
|
"githubUrlInvalid": "Enter a public repository URL in https://github.com/'{owner}'/'{repo}' format",
|
||||||
"tagInvalid": "Release tag must be a valid Git ref (1–255 bytes; / # & = are allowed)",
|
"tagInvalid": "Release tag must be a valid Git ref (1–255 bytes; / # & = are allowed)",
|
||||||
"assetInvalid": "Asset name must be 1–255 bytes and cannot be a path or contain control, newline, or bidi characters",
|
"assetInvalid": "Asset name must be 1–255 bytes and cannot be a path or contain control, newline, or bidi characters",
|
||||||
"intervalInvalid": "Check interval must be an integer between 5 and 1440 minutes",
|
"intervalInvalid": "Check interval must be an integer between 5 and 1440 minutes",
|
||||||
|
|||||||
@@ -499,7 +499,7 @@
|
|||||||
"switchFailed": "切换失败",
|
"switchFailed": "切换失败",
|
||||||
"urlRequired": "请输入 Remote URL",
|
"urlRequired": "请输入 Remote URL",
|
||||||
"urlInvalid": "请输入有效的 HTTP(S) URL",
|
"urlInvalid": "请输入有效的 HTTP(S) URL",
|
||||||
"githubUrlInvalid": "请输入 https://github.com/{owner}/{repo} 格式的公开仓库地址",
|
"githubUrlInvalid": "请输入 https://github.com/'{owner}'/'{repo}' 格式的公开仓库地址",
|
||||||
"tagInvalid": "Release tag 须为有效 Git ref(1–255 字节,可使用 /、#、&、=)",
|
"tagInvalid": "Release tag 须为有效 Git ref(1–255 字节,可使用 /、#、&、=)",
|
||||||
"assetInvalid": "Asset 文件名须为 1–255 字节,且不能是路径或包含控制、换行、双向文本字符",
|
"assetInvalid": "Asset 文件名须为 1–255 字节,且不能是路径或包含控制、换行、双向文本字符",
|
||||||
"intervalInvalid": "检查间隔须为 5–1440 分钟的整数",
|
"intervalInvalid": "检查间隔须为 5–1440 分钟的整数",
|
||||||
|
|||||||
@@ -3243,7 +3243,7 @@
|
|||||||
"switchFailed": "切换失败",
|
"switchFailed": "切换失败",
|
||||||
"urlRequired": "请输入 Remote URL",
|
"urlRequired": "请输入 Remote URL",
|
||||||
"urlInvalid": "请输入有效的 HTTP(S) URL",
|
"urlInvalid": "请输入有效的 HTTP(S) URL",
|
||||||
"githubUrlInvalid": "请输入 https://github.com/{owner}/{repo} 格式的公开仓库地址",
|
"githubUrlInvalid": "请输入 https://github.com/'{owner}'/'{repo}' 格式的公开仓库地址",
|
||||||
"tagInvalid": "Release tag 须为有效 Git ref(1–255 字节,可使用 /、#、&、=)",
|
"tagInvalid": "Release tag 须为有效 Git ref(1–255 字节,可使用 /、#、&、=)",
|
||||||
"assetInvalid": "Asset 文件名须为 1–255 字节,且不能是路径或包含控制、换行、双向文本字符",
|
"assetInvalid": "Asset 文件名须为 1–255 字节,且不能是路径或包含控制、换行、双向文本字符",
|
||||||
"intervalInvalid": "检查间隔须为 5–1440 分钟的整数",
|
"intervalInvalid": "检查间隔须为 5–1440 分钟的整数",
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||||||
|
|
||||||
import { CloudflareGroupDetailPageClient } from '@/app/(main)/cloudflare/groups/[id]/page-client';
|
import { CloudflareGroupDetailPageClient } from '@/app/(main)/cloudflare/groups/[id]/page-client';
|
||||||
import { CloudflareService, NodeService } from '@/lib/services/openflare';
|
import { CloudflareService, NodeService } from '@/lib/services/openflare';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import zhCN from '@/messages/zh-CN.json';
|
||||||
|
|
||||||
vi.mock('next/navigation', () => ({
|
vi.mock('next/navigation', () => ({
|
||||||
useParams: () => ({ id: '7' }),
|
useParams: () => ({ id: '7' }),
|
||||||
@@ -39,9 +41,11 @@ function renderPage() {
|
|||||||
defaultOptions: { queries: { retry: false, gcTime: 0 } },
|
defaultOptions: { queries: { retry: false, gcTime: 0 } },
|
||||||
});
|
});
|
||||||
render(
|
render(
|
||||||
<QueryClientProvider client={client}>
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
<CloudflareGroupDetailPageClient />
|
<QueryClientProvider client={client}>
|
||||||
</QueryClientProvider>,
|
<CloudflareGroupDetailPageClient />
|
||||||
|
</QueryClientProvider>
|
||||||
|
</NextIntlClientProvider>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { createElement } from 'react';
|
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import CloudflarePage from '@/app/(main)/cloudflare/page';
|
import CloudflarePage from '@/app/(main)/cloudflare/page';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import zhCN from '@/messages/zh-CN.json';
|
||||||
import { AdminTaskService } from '@/lib/services/admin';
|
import { AdminTaskService } from '@/lib/services/admin';
|
||||||
import { CloudflareService, NodeService } from '@/lib/services/openflare';
|
import { CloudflareService, NodeService } from '@/lib/services/openflare';
|
||||||
|
|
||||||
@@ -42,11 +43,11 @@ function renderPage() {
|
|||||||
defaultOptions: { queries: { retry: false, gcTime: 0 } },
|
defaultOptions: { queries: { retry: false, gcTime: 0 } },
|
||||||
});
|
});
|
||||||
render(
|
render(
|
||||||
createElement(
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
QueryClientProvider,
|
<QueryClientProvider client={client}>
|
||||||
{ client },
|
<CloudflarePage />
|
||||||
createElement(CloudflarePage),
|
</QueryClientProvider>
|
||||||
),
|
</NextIntlClientProvider>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { fireEvent, render, screen, within } from '@testing-library/react';
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { MemberAddDialog } from '@/app/(main)/cloudflare/components/member-add-dialog';
|
import { MemberAddDialog } from '@/app/(main)/cloudflare/components/member-add-dialog';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import zhCN from '@/messages/zh-CN.json';
|
||||||
import type { CloudflareAvailableDomain } from '@/lib/services/openflare';
|
import type { CloudflareAvailableDomain } from '@/lib/services/openflare';
|
||||||
|
|
||||||
const domains: CloudflareAvailableDomain[] = [
|
const domains: CloudflareAvailableDomain[] = [
|
||||||
@@ -35,15 +37,17 @@ describe('MemberAddDialog advanced domain picker', () => {
|
|||||||
it('groups domains under top-level zone roots and supports batch select', () => {
|
it('groups domains under top-level zone roots and supports batch select', () => {
|
||||||
const onSubmit = vi.fn();
|
const onSubmit = vi.fn();
|
||||||
render(
|
render(
|
||||||
<MemberAddDialog
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
|
<MemberAddDialog
|
||||||
open
|
open
|
||||||
onOpenChange={vi.fn()}
|
onOpenChange={vi.fn()}
|
||||||
domains={domains}
|
domains={domains}
|
||||||
defaultProxied
|
defaultProxied
|
||||||
pending={false}
|
pending={false}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
/>,
|
/>
|
||||||
);
|
</NextIntlClientProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
expect(screen.getAllByText('example.com').length).toBeGreaterThan(0);
|
expect(screen.getAllByText('example.com').length).toBeGreaterThan(0);
|
||||||
expect(screen.getAllByText('other.io').length).toBeGreaterThan(0);
|
expect(screen.getAllByText('other.io').length).toBeGreaterThan(0);
|
||||||
@@ -61,15 +65,17 @@ describe('MemberAddDialog advanced domain picker', () => {
|
|||||||
|
|
||||||
it('filters by keyword across domain and zone root', () => {
|
it('filters by keyword across domain and zone root', () => {
|
||||||
render(
|
render(
|
||||||
<MemberAddDialog
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
|
<MemberAddDialog
|
||||||
open
|
open
|
||||||
onOpenChange={vi.fn()}
|
onOpenChange={vi.fn()}
|
||||||
domains={domains}
|
domains={domains}
|
||||||
defaultProxied={false}
|
defaultProxied={false}
|
||||||
pending={false}
|
pending={false}
|
||||||
onSubmit={vi.fn()}
|
onSubmit={vi.fn()}
|
||||||
/>,
|
/>
|
||||||
);
|
</NextIntlClientProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
fireEvent.change(screen.getByPlaceholderText('搜索域名或顶级域…'), {
|
fireEvent.change(screen.getByPlaceholderText('搜索域名或顶级域…'), {
|
||||||
target: { value: 'other' },
|
target: { value: 'other' },
|
||||||
@@ -83,15 +89,17 @@ describe('MemberAddDialog advanced domain picker', () => {
|
|||||||
it('selects an entire top-level domain group', () => {
|
it('selects an entire top-level domain group', () => {
|
||||||
const onSubmit = vi.fn();
|
const onSubmit = vi.fn();
|
||||||
render(
|
render(
|
||||||
<MemberAddDialog
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
|
<MemberAddDialog
|
||||||
open
|
open
|
||||||
onOpenChange={vi.fn()}
|
onOpenChange={vi.fn()}
|
||||||
domains={domains}
|
domains={domains}
|
||||||
defaultProxied
|
defaultProxied
|
||||||
pending={false}
|
pending={false}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
/>,
|
/>
|
||||||
);
|
</NextIntlClientProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
screen.getByRole('checkbox', { name: '选择顶级域 example.com' }),
|
screen.getByRole('checkbox', { name: '选择顶级域 example.com' }),
|
||||||
@@ -107,15 +115,17 @@ describe('MemberAddDialog advanced domain picker', () => {
|
|||||||
it('toggles individual domains within a group', () => {
|
it('toggles individual domains within a group', () => {
|
||||||
const onSubmit = vi.fn();
|
const onSubmit = vi.fn();
|
||||||
render(
|
render(
|
||||||
<MemberAddDialog
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
|
<MemberAddDialog
|
||||||
open
|
open
|
||||||
onOpenChange={vi.fn()}
|
onOpenChange={vi.fn()}
|
||||||
domains={domains}
|
domains={domains}
|
||||||
defaultProxied={false}
|
defaultProxied={false}
|
||||||
pending={false}
|
pending={false}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
/>,
|
/>
|
||||||
);
|
</NextIntlClientProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
const wwwLabel = screen.getByText('www.example.com').closest('label');
|
const wwwLabel = screen.getByText('www.example.com').closest('label');
|
||||||
expect(wwwLabel).toBeTruthy();
|
expect(wwwLabel).toBeTruthy();
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|||||||
import { SyncTasksPanel } from '@/app/(main)/cloudflare/components/sync-tasks-panel';
|
import { SyncTasksPanel } from '@/app/(main)/cloudflare/components/sync-tasks-panel';
|
||||||
import { AdminTaskService } from '@/lib/services/admin';
|
import { AdminTaskService } from '@/lib/services/admin';
|
||||||
import type { TaskExecution } from '@/lib/services/admin';
|
import type { TaskExecution } from '@/lib/services/admin';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import zhCN from '@/messages/zh-CN.json';
|
||||||
|
|
||||||
vi.mock('@/lib/services/admin', async (importOriginal) => {
|
vi.mock('@/lib/services/admin', async (importOriginal) => {
|
||||||
const actual = await importOriginal<typeof import('@/lib/services/admin')>();
|
const actual = await importOriginal<typeof import('@/lib/services/admin')>();
|
||||||
@@ -24,9 +26,11 @@ function renderPanel() {
|
|||||||
defaultOptions: { queries: { retry: false, gcTime: 0 } },
|
defaultOptions: { queries: { retry: false, gcTime: 0 } },
|
||||||
});
|
});
|
||||||
render(
|
render(
|
||||||
<QueryClientProvider client={client}>
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
<SyncTasksPanel />
|
<QueryClientProvider client={client}>
|
||||||
</QueryClientProvider>,
|
<SyncTasksPanel />
|
||||||
|
</QueryClientProvider>
|
||||||
|
</NextIntlClientProvider>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import {
|
|||||||
type PagesGitHubReleaseSource,
|
type PagesGitHubReleaseSource,
|
||||||
PagesService,
|
PagesService,
|
||||||
} from '@/lib/services/openflare';
|
} from '@/lib/services/openflare';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import zhCN from '@/messages/zh-CN.json';
|
||||||
|
|
||||||
vi.mock('@/lib/services/openflare', async (importOriginal) => {
|
vi.mock('@/lib/services/openflare', async (importOriginal) => {
|
||||||
const actual =
|
const actual =
|
||||||
@@ -48,16 +50,18 @@ function renderWithQuery(ui: React.ReactNode) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const rendered = render(
|
const rendered = render(
|
||||||
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>,
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
|
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>
|
||||||
|
</NextIntlClientProvider>,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
...rendered,
|
...rendered,
|
||||||
queryClient,
|
queryClient,
|
||||||
rerenderWithQuery: (nextUI: React.ReactNode) =>
|
rerenderWithQuery: (nextUI: React.ReactNode) =>
|
||||||
rendered.rerender(
|
rendered.rerender(
|
||||||
<QueryClientProvider client={queryClient}>
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
{nextUI}
|
<QueryClientProvider client={queryClient}>{nextUI}</QueryClientProvider>
|
||||||
</QueryClientProvider>,
|
</NextIntlClientProvider>,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import {
|
|||||||
type PagesRemoteURLSource,
|
type PagesRemoteURLSource,
|
||||||
PagesService,
|
PagesService,
|
||||||
} from '@/lib/services/openflare';
|
} from '@/lib/services/openflare';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import zhCN from '@/messages/zh-CN.json';
|
||||||
|
|
||||||
vi.mock('@/lib/services/openflare', async (importOriginal) => {
|
vi.mock('@/lib/services/openflare', async (importOriginal) => {
|
||||||
const actual =
|
const actual =
|
||||||
@@ -56,7 +58,9 @@ function renderWithQuery(ui: React.ReactNode) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
return render(
|
return render(
|
||||||
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>,
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
|
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>
|
||||||
|
</NextIntlClientProvider>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +394,7 @@ describe('Pages source UI', () => {
|
|||||||
expect(screen.getByText('下次检查')).toBeVisible();
|
expect(screen.getByText('下次检查')).toBeVisible();
|
||||||
expect(screen.getByText('自动更新')).toBeVisible();
|
expect(screen.getByText('自动更新')).toBeVisible();
|
||||||
expect(screen.getByText('已关闭')).toBeVisible();
|
expect(screen.getByText('已关闭')).toBeVisible();
|
||||||
expect(screen.getByText('检查间隔')).toBeVisible();
|
expect(screen.getByText('检查间隔(分钟)')).toBeVisible();
|
||||||
expect(screen.getByText('1440 分钟')).toBeVisible();
|
expect(screen.getByText('1440 分钟')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
import { createElement } from 'react';
|
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -9,6 +8,8 @@ import {
|
|||||||
} from '@/app/(main)/proxy-routes/detail/components/waf-section';
|
} from '@/app/(main)/proxy-routes/detail/components/waf-section';
|
||||||
import type { ProxyRouteItem, WAFRule } from '@/lib/services/openflare';
|
import type { ProxyRouteItem, WAFRule } from '@/lib/services/openflare';
|
||||||
import { WafService } from '@/lib/services/openflare';
|
import { WafService } from '@/lib/services/openflare';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import zhCN from '@/messages/zh-CN.json';
|
||||||
|
|
||||||
vi.mock('@/lib/services/openflare', async (importOriginal) => {
|
vi.mock('@/lib/services/openflare', async (importOriginal) => {
|
||||||
const actual =
|
const actual =
|
||||||
@@ -97,11 +98,11 @@ describe('WAF route binding order', () => {
|
|||||||
defaultOptions: { queries: { retry: false } },
|
defaultOptions: { queries: { retry: false } },
|
||||||
});
|
});
|
||||||
render(
|
render(
|
||||||
createElement(
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
QueryClientProvider,
|
<QueryClientProvider client={client}>
|
||||||
{ client },
|
<WafSection route={{ id: 9 } as ProxyRouteItem} />
|
||||||
createElement(WafSection, { route: { id: 9 } as ProxyRouteItem }),
|
</QueryClientProvider>
|
||||||
),
|
</NextIntlClientProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(await screen.findByText('全局规则')).toBeInTheDocument();
|
expect(await screen.findByText('全局规则')).toBeInTheDocument();
|
||||||
|
|||||||
+7
-2
@@ -1,7 +1,8 @@
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import zhCN from '@/messages/zh-CN.json';
|
||||||
import type { AxiosResponse } from 'axios';
|
import type { AxiosResponse } from 'axios';
|
||||||
import { createElement } from 'react';
|
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import WafPage from '@/app/(main)/waf/page';
|
import WafPage from '@/app/(main)/waf/page';
|
||||||
@@ -156,7 +157,11 @@ describe('WAF rule creation flow', () => {
|
|||||||
defaultOptions: { queries: { retry: false, gcTime: 0 } },
|
defaultOptions: { queries: { retry: false, gcTime: 0 } },
|
||||||
});
|
});
|
||||||
render(
|
render(
|
||||||
createElement(QueryClientProvider, { client }, createElement(WafPage)),
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
|
<QueryClientProvider client={client}>
|
||||||
|
<WafPage />
|
||||||
|
</QueryClientProvider>
|
||||||
|
</NextIntlClientProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole('button', { name: '新建规则' }));
|
fireEvent.click(await screen.findByRole('button', { name: '新建规则' }));
|
||||||
@@ -4,6 +4,8 @@ import { describe, expect, it, vi } from 'vitest';
|
|||||||
|
|
||||||
import WebsitesPage from '@/app/(main)/websites/page';
|
import WebsitesPage from '@/app/(main)/websites/page';
|
||||||
import { ZoneService } from '@/lib/services/openflare';
|
import { ZoneService } from '@/lib/services/openflare';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import zhCN from '@/messages/zh-CN.json';
|
||||||
|
|
||||||
vi.mock('next/link', () => ({
|
vi.mock('next/link', () => ({
|
||||||
default: ({
|
default: ({
|
||||||
@@ -30,9 +32,11 @@ function renderPage() {
|
|||||||
defaultOptions: { queries: { retry: false } },
|
defaultOptions: { queries: { retry: false } },
|
||||||
});
|
});
|
||||||
return render(
|
return render(
|
||||||
<QueryClientProvider client={client}>
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
<WebsitesPage />
|
<QueryClientProvider client={client}>
|
||||||
</QueryClientProvider>,
|
<WebsitesPage />
|
||||||
|
</QueryClientProvider>
|
||||||
|
</NextIntlClientProvider>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { describe, expect, it, vi } from 'vitest';
|
|||||||
|
|
||||||
import { ZoneDomainSelector } from '@/app/(main)/proxy-routes/components/zone-domain-selector';
|
import { ZoneDomainSelector } from '@/app/(main)/proxy-routes/components/zone-domain-selector';
|
||||||
import type { ZoneDomainItem, ZoneItem } from '@/lib/services/openflare';
|
import type { ZoneDomainItem, ZoneItem } from '@/lib/services/openflare';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import zhCN from '@/messages/zh-CN.json';
|
||||||
|
|
||||||
vi.mock('next/link', () => ({
|
vi.mock('next/link', () => ({
|
||||||
default: ({
|
default: ({
|
||||||
@@ -46,7 +48,9 @@ function renderSelector(ui: React.ReactElement) {
|
|||||||
defaultOptions: { queries: { retry: false } },
|
defaultOptions: { queries: { retry: false } },
|
||||||
});
|
});
|
||||||
return render(
|
return render(
|
||||||
<QueryClientProvider client={client}>{ui}</QueryClientProvider>,
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
|
<QueryClientProvider client={client}>{ui}</QueryClientProvider>
|
||||||
|
</NextIntlClientProvider>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
TlsCertificateService,
|
TlsCertificateService,
|
||||||
ZoneService,
|
ZoneService,
|
||||||
} from '@/lib/services/openflare';
|
} from '@/lib/services/openflare';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import zhCN from '@/messages/zh-CN.json';
|
||||||
|
|
||||||
class ResizeObserverMock {
|
class ResizeObserverMock {
|
||||||
observe() {}
|
observe() {}
|
||||||
@@ -68,9 +70,11 @@ function renderPage(zoneId: number, paramZoneId = zoneId) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
return render(
|
return render(
|
||||||
<QueryClientProvider client={client}>
|
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
|
||||||
<ZonePageClient />
|
<QueryClientProvider client={client}>
|
||||||
</QueryClientProvider>,
|
<ZonePageClient />
|
||||||
|
</QueryClientProvider>
|
||||||
|
</NextIntlClientProvider>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +117,7 @@ describe('ZonePageClient', () => {
|
|||||||
expect(
|
expect(
|
||||||
await screen.findByRole('heading', { name: 'example.com' }),
|
await screen.findByRole('heading', { name: 'example.com' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
expect(await screen.findByText('唯一访问者')).toBeVisible();
|
expect(await screen.findByText('查询窗口独立访客')).toBeVisible();
|
||||||
expect(screen.getByText('请求总数')).toBeVisible();
|
expect(screen.getByText('请求总数')).toBeVisible();
|
||||||
expect(screen.getByText('已提供的数据总计')).toBeVisible();
|
expect(screen.getByText('已提供的数据总计')).toBeVisible();
|
||||||
expect(screen.getByRole('tab', { name: '域名 (0)' })).toBeVisible();
|
expect(screen.getByRole('tab', { name: '域名 (0)' })).toBeVisible();
|
||||||
|
|||||||
Reference in New Issue
Block a user