From f5c9da03f4a46bc79bb536c9c2b33169e0754876 Mon Sep 17 00:00:00 2001 From: ryan Date: Sun, 16 Aug 2026 19:08:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=89=8D=E7=AB=AF=E6=B5=8B=E8=AF=95=E5=A5=97?= =?UTF-8?q?=E4=BB=B6=2044=20=E5=A4=B1=E8=B4=A5=E2=86=92=E5=85=A8=E7=BB=BF?= =?UTF-8?q?=EF=BC=9A10=20=E4=B8=AA=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E8=A1=A5=20NextIntlClientProvider=20=E5=8C=85=E8=A3=85?= =?UTF-8?q?=EF=BC=88=E5=90=AB=20React19=20createElement=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=BF=AE=E5=A4=8D=E3=80=81.ts=E2=86=92.tsx=20?= =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D=EF=BC=89=EF=BC=9B=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=20i18n=20ICU=20bug=EF=BC=88githubUrlInvalid?= =?UTF-8?q?=20=E7=9A=84=20{owner}/{repo}=20=E6=9C=AA=E8=BD=AC=E4=B9=89?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=94=9F=E4=BA=A7=E6=B8=B2=E6=9F=93=E6=88=90?= =?UTF-8?q?=20key=EF=BC=8Czh/en=20+=20fragment=204=20=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E8=BD=AC=E4=B9=89=EF=BC=89=EF=BC=9B=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=202=20=E5=A4=84=E8=BF=87=E6=9C=9F=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=9C=9F=E6=9C=9B=E3=80=82vitest=20116/116=20+=20tsc=20+=20esl?= =?UTF-8?q?int=20=E5=85=A8=E7=BB=BF=EF=BC=8Cchecks.sh=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E6=B5=8B=E8=AF=95=E9=97=A8=E7=A6=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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} --- .auto/checks.sh | 7 ++++ .auto/log.jsonl | 1 + frontend/messages/en.json | 2 +- frontend/messages/fragments/security.en.json | 2 +- .../messages/fragments/security.zh-CN.json | 2 +- frontend/messages/zh-CN.json | 2 +- .../cloudflare-group-detail.test.tsx | 10 ++++-- .../cloudflare/cloudflare-overview.test.tsx | 13 +++---- .../cloudflare/member-add-dialog.test.tsx | 34 ++++++++++++------- .../cloudflare/sync-tasks-panel.test.tsx | 10 ++++-- .../pages-source-auto-update.test.tsx | 12 ++++--- .../tests/openflare/pages-source-ui.test.tsx | 8 +++-- .../tests/unit/waf-binding-order.test.tsx | 13 +++---- ...vice.test.ts => waf-rule-service.test.tsx} | 9 +++-- frontend/tests/zone/websites-page.test.tsx | 10 ++++-- .../tests/zone/zone-domain-selector.test.tsx | 6 +++- frontend/tests/zone/zone-page.test.tsx | 12 ++++--- 17 files changed, 103 insertions(+), 50 deletions(-) rename frontend/tests/unit/{waf-rule-service.test.ts => waf-rule-service.test.tsx} (94%) diff --git a/.auto/checks.sh b/.auto/checks.sh index b052409d..43e53a92 100755 --- a/.auto/checks.sh +++ b/.auto/checks.sh @@ -22,4 +22,11 @@ else exit 1 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" \ No newline at end of file diff --git a/.auto/log.jsonl b/.auto/log.jsonl index 5b91e11b..9d67aa50 100644 --- a/.auto/log.jsonl +++ b/.auto/log.jsonl @@ -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":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":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 结构相同,值得检查)"}} diff --git a/frontend/messages/en.json b/frontend/messages/en.json index d31be8b9..52faa90e 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -3243,7 +3243,7 @@ "switchFailed": "Switch failed", "urlRequired": "Enter a Remote 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)", "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", diff --git a/frontend/messages/fragments/security.en.json b/frontend/messages/fragments/security.en.json index febef139..7854d61d 100644 --- a/frontend/messages/fragments/security.en.json +++ b/frontend/messages/fragments/security.en.json @@ -499,7 +499,7 @@ "switchFailed": "Switch failed", "urlRequired": "Enter a Remote 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)", "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", diff --git a/frontend/messages/fragments/security.zh-CN.json b/frontend/messages/fragments/security.zh-CN.json index fabd8458..12ac4acc 100644 --- a/frontend/messages/fragments/security.zh-CN.json +++ b/frontend/messages/fragments/security.zh-CN.json @@ -499,7 +499,7 @@ "switchFailed": "切换失败", "urlRequired": "请输入 Remote URL", "urlInvalid": "请输入有效的 HTTP(S) URL", - "githubUrlInvalid": "请输入 https://github.com/{owner}/{repo} 格式的公开仓库地址", + "githubUrlInvalid": "请输入 https://github.com/'{owner}'/'{repo}' 格式的公开仓库地址", "tagInvalid": "Release tag 须为有效 Git ref(1–255 字节,可使用 /、#、&、=)", "assetInvalid": "Asset 文件名须为 1–255 字节,且不能是路径或包含控制、换行、双向文本字符", "intervalInvalid": "检查间隔须为 5–1440 分钟的整数", diff --git a/frontend/messages/zh-CN.json b/frontend/messages/zh-CN.json index 1d605168..fb433f07 100644 --- a/frontend/messages/zh-CN.json +++ b/frontend/messages/zh-CN.json @@ -3243,7 +3243,7 @@ "switchFailed": "切换失败", "urlRequired": "请输入 Remote URL", "urlInvalid": "请输入有效的 HTTP(S) URL", - "githubUrlInvalid": "请输入 https://github.com/{owner}/{repo} 格式的公开仓库地址", + "githubUrlInvalid": "请输入 https://github.com/'{owner}'/'{repo}' 格式的公开仓库地址", "tagInvalid": "Release tag 须为有效 Git ref(1–255 字节,可使用 /、#、&、=)", "assetInvalid": "Asset 文件名须为 1–255 字节,且不能是路径或包含控制、换行、双向文本字符", "intervalInvalid": "检查间隔须为 5–1440 分钟的整数", diff --git a/frontend/tests/cloudflare/cloudflare-group-detail.test.tsx b/frontend/tests/cloudflare/cloudflare-group-detail.test.tsx index f6b644b4..cde716bc 100644 --- a/frontend/tests/cloudflare/cloudflare-group-detail.test.tsx +++ b/frontend/tests/cloudflare/cloudflare-group-detail.test.tsx @@ -10,6 +10,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { CloudflareGroupDetailPageClient } from '@/app/(main)/cloudflare/groups/[id]/page-client'; import { CloudflareService, NodeService } from '@/lib/services/openflare'; +import { NextIntlClientProvider } from 'next-intl'; +import zhCN from '@/messages/zh-CN.json'; vi.mock('next/navigation', () => ({ useParams: () => ({ id: '7' }), @@ -39,9 +41,11 @@ function renderPage() { defaultOptions: { queries: { retry: false, gcTime: 0 } }, }); render( - - - , + + + + + , ); } diff --git a/frontend/tests/cloudflare/cloudflare-overview.test.tsx b/frontend/tests/cloudflare/cloudflare-overview.test.tsx index e4d75e45..9e0c20b8 100644 --- a/frontend/tests/cloudflare/cloudflare-overview.test.tsx +++ b/frontend/tests/cloudflare/cloudflare-overview.test.tsx @@ -1,9 +1,10 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen } from '@testing-library/react'; -import { createElement } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; 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 { CloudflareService, NodeService } from '@/lib/services/openflare'; @@ -42,11 +43,11 @@ function renderPage() { defaultOptions: { queries: { retry: false, gcTime: 0 } }, }); render( - createElement( - QueryClientProvider, - { client }, - createElement(CloudflarePage), - ), + + + + + , ); } diff --git a/frontend/tests/cloudflare/member-add-dialog.test.tsx b/frontend/tests/cloudflare/member-add-dialog.test.tsx index 757e58bc..a3d804f3 100644 --- a/frontend/tests/cloudflare/member-add-dialog.test.tsx +++ b/frontend/tests/cloudflare/member-add-dialog.test.tsx @@ -2,6 +2,8 @@ import { fireEvent, render, screen, within } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; 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'; const domains: CloudflareAvailableDomain[] = [ @@ -35,15 +37,17 @@ describe('MemberAddDialog advanced domain picker', () => { it('groups domains under top-level zone roots and supports batch select', () => { const onSubmit = vi.fn(); render( - + , - ); + /> + , + ); expect(screen.getAllByText('example.com').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', () => { render( - + , - ); + /> + , + ); fireEvent.change(screen.getByPlaceholderText('搜索域名或顶级域…'), { target: { value: 'other' }, @@ -83,15 +89,17 @@ describe('MemberAddDialog advanced domain picker', () => { it('selects an entire top-level domain group', () => { const onSubmit = vi.fn(); render( - + , - ); + /> + , + ); fireEvent.click( screen.getByRole('checkbox', { name: '选择顶级域 example.com' }), @@ -107,15 +115,17 @@ describe('MemberAddDialog advanced domain picker', () => { it('toggles individual domains within a group', () => { const onSubmit = vi.fn(); render( - + , - ); + /> + , + ); const wwwLabel = screen.getByText('www.example.com').closest('label'); expect(wwwLabel).toBeTruthy(); diff --git a/frontend/tests/cloudflare/sync-tasks-panel.test.tsx b/frontend/tests/cloudflare/sync-tasks-panel.test.tsx index a071f03d..70173f4d 100644 --- a/frontend/tests/cloudflare/sync-tasks-panel.test.tsx +++ b/frontend/tests/cloudflare/sync-tasks-panel.test.tsx @@ -5,6 +5,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { SyncTasksPanel } from '@/app/(main)/cloudflare/components/sync-tasks-panel'; import { AdminTaskService } 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) => { const actual = await importOriginal(); @@ -24,9 +26,11 @@ function renderPanel() { defaultOptions: { queries: { retry: false, gcTime: 0 } }, }); render( - - - , + + + + + , ); } diff --git a/frontend/tests/openflare/pages-source-auto-update.test.tsx b/frontend/tests/openflare/pages-source-auto-update.test.tsx index a1e65d6f..079a47b3 100644 --- a/frontend/tests/openflare/pages-source-auto-update.test.tsx +++ b/frontend/tests/openflare/pages-source-auto-update.test.tsx @@ -20,6 +20,8 @@ import { type PagesGitHubReleaseSource, PagesService, } from '@/lib/services/openflare'; +import { NextIntlClientProvider } from 'next-intl'; +import zhCN from '@/messages/zh-CN.json'; vi.mock('@/lib/services/openflare', async (importOriginal) => { const actual = @@ -48,16 +50,18 @@ function renderWithQuery(ui: React.ReactNode) { }, }); const rendered = render( - {ui}, + + {ui} + , ); return { ...rendered, queryClient, rerenderWithQuery: (nextUI: React.ReactNode) => rendered.rerender( - - {nextUI} - , + + {nextUI} + , ), }; } diff --git a/frontend/tests/openflare/pages-source-ui.test.tsx b/frontend/tests/openflare/pages-source-ui.test.tsx index 934754d8..69b38bd8 100644 --- a/frontend/tests/openflare/pages-source-ui.test.tsx +++ b/frontend/tests/openflare/pages-source-ui.test.tsx @@ -17,6 +17,8 @@ import { type PagesRemoteURLSource, PagesService, } from '@/lib/services/openflare'; +import { NextIntlClientProvider } from 'next-intl'; +import zhCN from '@/messages/zh-CN.json'; vi.mock('@/lib/services/openflare', async (importOriginal) => { const actual = @@ -56,7 +58,9 @@ function renderWithQuery(ui: React.ReactNode) { }, }); return render( - {ui}, + + {ui} + , ); } @@ -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('1440 分钟')).toBeVisible(); }); diff --git a/frontend/tests/unit/waf-binding-order.test.tsx b/frontend/tests/unit/waf-binding-order.test.tsx index 76013c3c..c2d9f90b 100644 --- a/frontend/tests/unit/waf-binding-order.test.tsx +++ b/frontend/tests/unit/waf-binding-order.test.tsx @@ -1,6 +1,5 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { createElement } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { @@ -9,6 +8,8 @@ import { } from '@/app/(main)/proxy-routes/detail/components/waf-section'; import type { ProxyRouteItem, WAFRule } 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) => { const actual = @@ -97,11 +98,11 @@ describe('WAF route binding order', () => { defaultOptions: { queries: { retry: false } }, }); render( - createElement( - QueryClientProvider, - { client }, - createElement(WafSection, { route: { id: 9 } as ProxyRouteItem }), - ), + + + + + , ); expect(await screen.findByText('全局规则')).toBeInTheDocument(); diff --git a/frontend/tests/unit/waf-rule-service.test.ts b/frontend/tests/unit/waf-rule-service.test.tsx similarity index 94% rename from frontend/tests/unit/waf-rule-service.test.ts rename to frontend/tests/unit/waf-rule-service.test.tsx index cd9f55a0..8352c922 100644 --- a/frontend/tests/unit/waf-rule-service.test.ts +++ b/frontend/tests/unit/waf-rule-service.test.tsx @@ -1,7 +1,8 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 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 { createElement } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import WafPage from '@/app/(main)/waf/page'; @@ -156,7 +157,11 @@ describe('WAF rule creation flow', () => { defaultOptions: { queries: { retry: false, gcTime: 0 } }, }); render( - createElement(QueryClientProvider, { client }, createElement(WafPage)), + + + + + , ); fireEvent.click(await screen.findByRole('button', { name: '新建规则' })); diff --git a/frontend/tests/zone/websites-page.test.tsx b/frontend/tests/zone/websites-page.test.tsx index 489b72a3..72f0beb1 100644 --- a/frontend/tests/zone/websites-page.test.tsx +++ b/frontend/tests/zone/websites-page.test.tsx @@ -4,6 +4,8 @@ import { describe, expect, it, vi } from 'vitest'; import WebsitesPage from '@/app/(main)/websites/page'; import { ZoneService } from '@/lib/services/openflare'; +import { NextIntlClientProvider } from 'next-intl'; +import zhCN from '@/messages/zh-CN.json'; vi.mock('next/link', () => ({ default: ({ @@ -30,9 +32,11 @@ function renderPage() { defaultOptions: { queries: { retry: false } }, }); return render( - - - , + + + + + , ); } diff --git a/frontend/tests/zone/zone-domain-selector.test.tsx b/frontend/tests/zone/zone-domain-selector.test.tsx index aafbaa3a..992bea4e 100644 --- a/frontend/tests/zone/zone-domain-selector.test.tsx +++ b/frontend/tests/zone/zone-domain-selector.test.tsx @@ -5,6 +5,8 @@ import { describe, expect, it, vi } from 'vitest'; import { ZoneDomainSelector } from '@/app/(main)/proxy-routes/components/zone-domain-selector'; import type { ZoneDomainItem, ZoneItem } from '@/lib/services/openflare'; +import { NextIntlClientProvider } from 'next-intl'; +import zhCN from '@/messages/zh-CN.json'; vi.mock('next/link', () => ({ default: ({ @@ -46,7 +48,9 @@ function renderSelector(ui: React.ReactElement) { defaultOptions: { queries: { retry: false } }, }); return render( - {ui}, + + {ui} + , ); } diff --git a/frontend/tests/zone/zone-page.test.tsx b/frontend/tests/zone/zone-page.test.tsx index f792be2a..3649b9a3 100644 --- a/frontend/tests/zone/zone-page.test.tsx +++ b/frontend/tests/zone/zone-page.test.tsx @@ -8,6 +8,8 @@ import { TlsCertificateService, ZoneService, } from '@/lib/services/openflare'; +import { NextIntlClientProvider } from 'next-intl'; +import zhCN from '@/messages/zh-CN.json'; class ResizeObserverMock { observe() {} @@ -68,9 +70,11 @@ function renderPage(zoneId: number, paramZoneId = zoneId) { }, }); return render( - - - , + + + + + , ); } @@ -113,7 +117,7 @@ describe('ZonePageClient', () => { expect( await screen.findByRole('heading', { name: 'example.com' }), ).toBeVisible(); - expect(await screen.findByText('唯一访问者')).toBeVisible(); + expect(await screen.findByText('查询窗口独立访客')).toBeVisible(); expect(screen.getByText('请求总数')).toBeVisible(); expect(screen.getByText('已提供的数据总计')).toBeVisible(); expect(screen.getByRole('tab', { name: '域名 (0)' })).toBeVisible();