前端测试套件 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:
ryan
2026-08-16 19:08:21 +08:00
parent a16be014d4
commit f5c9da03f4
17 changed files with 103 additions and 50 deletions
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
@@ -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 分钟的整数",
+1 -1
View File
@@ -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 分钟的整数",
@@ -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(
<QueryClientProvider client={client}>
<CloudflareGroupDetailPageClient />
</QueryClientProvider>,
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<QueryClientProvider client={client}>
<CloudflareGroupDetailPageClient />
</QueryClientProvider>
</NextIntlClientProvider>,
);
}
@@ -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),
),
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<QueryClientProvider client={client}>
<CloudflarePage />
</QueryClientProvider>
</NextIntlClientProvider>,
);
}
@@ -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(
<MemberAddDialog
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<MemberAddDialog
open
onOpenChange={vi.fn()}
domains={domains}
defaultProxied
pending={false}
onSubmit={onSubmit}
/>,
);
/>
</NextIntlClientProvider>,
);
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(
<MemberAddDialog
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<MemberAddDialog
open
onOpenChange={vi.fn()}
domains={domains}
defaultProxied={false}
pending={false}
onSubmit={vi.fn()}
/>,
);
/>
</NextIntlClientProvider>,
);
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(
<MemberAddDialog
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<MemberAddDialog
open
onOpenChange={vi.fn()}
domains={domains}
defaultProxied
pending={false}
onSubmit={onSubmit}
/>,
);
/>
</NextIntlClientProvider>,
);
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(
<MemberAddDialog
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<MemberAddDialog
open
onOpenChange={vi.fn()}
domains={domains}
defaultProxied={false}
pending={false}
onSubmit={onSubmit}
/>,
);
/>
</NextIntlClientProvider>,
);
const wwwLabel = screen.getByText('www.example.com').closest('label');
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 { 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<typeof import('@/lib/services/admin')>();
@@ -24,9 +26,11 @@ function renderPanel() {
defaultOptions: { queries: { retry: false, gcTime: 0 } },
});
render(
<QueryClientProvider client={client}>
<SyncTasksPanel />
</QueryClientProvider>,
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<QueryClientProvider client={client}>
<SyncTasksPanel />
</QueryClientProvider>
</NextIntlClientProvider>,
);
}
@@ -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(
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>,
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>
</NextIntlClientProvider>,
);
return {
...rendered,
queryClient,
rerenderWithQuery: (nextUI: React.ReactNode) =>
rendered.rerender(
<QueryClientProvider client={queryClient}>
{nextUI}
</QueryClientProvider>,
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<QueryClientProvider client={queryClient}>{nextUI}</QueryClientProvider>
</NextIntlClientProvider>,
),
};
}
@@ -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(
<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('1440 分钟')).toBeVisible();
});
@@ -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 }),
),
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<QueryClientProvider client={client}>
<WafSection route={{ id: 9 } as ProxyRouteItem} />
</QueryClientProvider>
</NextIntlClientProvider>,
);
expect(await screen.findByText('全局规则')).toBeInTheDocument();
@@ -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)),
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<QueryClientProvider client={client}>
<WafPage />
</QueryClientProvider>
</NextIntlClientProvider>,
);
fireEvent.click(await screen.findByRole('button', { name: '新建规则' }));
+7 -3
View File
@@ -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(
<QueryClientProvider client={client}>
<WebsitesPage />
</QueryClientProvider>,
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<QueryClientProvider client={client}>
<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 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(
<QueryClientProvider client={client}>{ui}</QueryClientProvider>,
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<QueryClientProvider client={client}>{ui}</QueryClientProvider>
</NextIntlClientProvider>,
);
}
+8 -4
View File
@@ -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(
<QueryClientProvider client={client}>
<ZonePageClient />
</QueryClientProvider>,
<NextIntlClientProvider locale='zh-CN' messages={zhCN} timeZone='Asia/Shanghai'>
<QueryClientProvider client={client}>
<ZonePageClient />
</QueryClientProvider>
</NextIntlClientProvider>,
);
}
@@ -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();