mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-28 01:09:17 +08:00
✨ feat(i18n): 推进多语言剩余切片闭环
- 补齐 DataGrid、DataViewer、DefinitionViewer、JVM 等模块多语言文案与回归测试 - 收口 JVM 前后端展示、诊断、监控和资源呈现相关多语言路径 - 更新六语言共享词典并保留 raw 边界
This commit is contained in:
205
frontend/src/components/FindInDatabaseModal.test.tsx
Normal file
205
frontend/src/components/FindInDatabaseModal.test.tsx
Normal file
@@ -0,0 +1,205 @@
|
||||
import React from "react";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { act, create, type ReactTestRenderer } from "react-test-renderer";
|
||||
|
||||
import { I18nProvider } from "../i18n/provider";
|
||||
import FindInDatabaseModal from "./FindInDatabaseModal";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
dbQuery: vi.fn(),
|
||||
dbGetTables: vi.fn(),
|
||||
dbGetAllColumns: vi.fn(),
|
||||
message: {
|
||||
warning: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
},
|
||||
storeState: {
|
||||
connections: [
|
||||
{
|
||||
id: "conn-1",
|
||||
config: {
|
||||
type: "mysql",
|
||||
host: "localhost",
|
||||
port: 3306,
|
||||
user: "root",
|
||||
password: "",
|
||||
database: "app",
|
||||
},
|
||||
},
|
||||
],
|
||||
theme: "light",
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../store", () => ({
|
||||
useStore: (selector: (state: typeof mocks.storeState) => unknown) => selector(mocks.storeState),
|
||||
}));
|
||||
|
||||
vi.mock("../i18n/runtime", () => ({
|
||||
applyDayjsLocale: vi.fn(),
|
||||
syncLanguageRuntime: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../wailsjs/go/app/App", () => ({
|
||||
DBQuery: mocks.dbQuery,
|
||||
DBGetTables: mocks.dbGetTables,
|
||||
DBGetAllColumns: mocks.dbGetAllColumns,
|
||||
}));
|
||||
|
||||
vi.mock("antd", async () => {
|
||||
const React = await import("react");
|
||||
return {
|
||||
Modal: ({
|
||||
children,
|
||||
open,
|
||||
title,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
open?: boolean;
|
||||
title?: React.ReactNode;
|
||||
}) => (open ? React.createElement("section", null, title, children) : null),
|
||||
Input: ({
|
||||
placeholder,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
onChange?: (event: { target: { value: string } }) => void;
|
||||
}) =>
|
||||
React.createElement("input", {
|
||||
placeholder,
|
||||
value,
|
||||
onChange: (event: any) => onChange?.({ target: { value: event.target.value } }),
|
||||
}),
|
||||
Button: ({
|
||||
children,
|
||||
disabled,
|
||||
icon,
|
||||
onClick,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
icon?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}) => React.createElement("button", { disabled, onClick }, icon, children),
|
||||
Select: ({ options }: { options?: Array<{ label: React.ReactNode; value: string }> }) =>
|
||||
React.createElement(
|
||||
"div",
|
||||
null,
|
||||
options?.map((option) => React.createElement("span", { key: option.value }, option.label)),
|
||||
),
|
||||
Table: ({ columns, dataSource }: { columns?: any[]; dataSource?: any[] }) =>
|
||||
React.createElement(
|
||||
"div",
|
||||
null,
|
||||
columns?.map((column) => React.createElement("span", { key: column.key || column.dataIndex }, column.title)),
|
||||
dataSource?.map((row, index) =>
|
||||
React.createElement(
|
||||
"div",
|
||||
{ key: index },
|
||||
Object.values(row).map((value, valueIndex) =>
|
||||
React.createElement("span", { key: valueIndex }, String(value)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Progress: ({ percent }: { percent: number }) => React.createElement("div", null, `${percent}%`),
|
||||
Space: ({ children }: { children?: React.ReactNode }) => React.createElement("div", null, children),
|
||||
Tag: ({ children }: { children?: React.ReactNode }) => React.createElement("span", null, children),
|
||||
Tooltip: ({ children, title }: { children?: React.ReactNode; title?: React.ReactNode }) =>
|
||||
React.createElement("span", { title }, children),
|
||||
Empty: ({ description }: { description?: React.ReactNode }) => React.createElement("div", null, description),
|
||||
message: mocks.message,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@ant-design/icons", async () => {
|
||||
const React = await import("react");
|
||||
const Icon = () => React.createElement("span", null);
|
||||
return {
|
||||
SearchOutlined: Icon,
|
||||
StopOutlined: Icon,
|
||||
EyeOutlined: Icon,
|
||||
DatabaseOutlined: Icon,
|
||||
};
|
||||
});
|
||||
|
||||
const textContent = (node: any): string => {
|
||||
if (node === null || node === undefined) return "";
|
||||
if (typeof node === "string" || typeof node === "number") return String(node);
|
||||
if (Array.isArray(node)) return node.map((item) => textContent(item)).join("");
|
||||
return textContent(node.children || []);
|
||||
};
|
||||
|
||||
const renderFindModal = () => {
|
||||
let renderer!: ReactTestRenderer;
|
||||
act(() => {
|
||||
renderer = create(
|
||||
<I18nProvider preference="en-US" onPreferenceChange={() => undefined}>
|
||||
<FindInDatabaseModal open connectionId="conn-1" dbName="app_db" onClose={vi.fn()} />
|
||||
</I18nProvider>,
|
||||
);
|
||||
});
|
||||
return renderer;
|
||||
};
|
||||
|
||||
describe("FindInDatabaseModal i18n", () => {
|
||||
beforeEach(() => {
|
||||
mocks.dbQuery.mockReset();
|
||||
mocks.dbGetTables.mockReset();
|
||||
mocks.dbGetAllColumns.mockReset();
|
||||
mocks.message.warning.mockClear();
|
||||
mocks.message.error.mockClear();
|
||||
mocks.message.info.mockClear();
|
||||
});
|
||||
|
||||
it("renders search chrome in the active language while preserving raw database name", () => {
|
||||
const renderer = renderFindModal();
|
||||
const renderedText = textContent(renderer.toJSON());
|
||||
const input = renderer.root.findByType("input");
|
||||
|
||||
expect(renderedText).toContain("Search in database - app_db");
|
||||
expect(input.props.placeholder).toBe("Enter the string to search for...");
|
||||
expect(renderedText).toContain("Contains");
|
||||
expect(renderedText).toContain("Exact match");
|
||||
expect(renderedText).toContain("Search");
|
||||
});
|
||||
|
||||
it("localizes controlled error wrappers while preserving backend detail", async () => {
|
||||
mocks.dbGetTables.mockResolvedValue({ success: false, message: "driver raw detail" });
|
||||
const renderer = renderFindModal();
|
||||
|
||||
const input = renderer.root.findByType("input");
|
||||
await act(async () => {
|
||||
input.props.onChange({ target: { value: "alice" } });
|
||||
});
|
||||
|
||||
const searchButton = renderer.root.findAllByType("button").find((button) => textContent(button).includes("Search"));
|
||||
expect(searchButton).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
searchButton?.props.onClick();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(mocks.message.error).toHaveBeenCalledWith("Failed to get table list: driver raw detail");
|
||||
});
|
||||
|
||||
it("does not keep migrated Chinese UI literals in FindInDatabaseModal source", () => {
|
||||
const source = readFileSync(new URL("./FindInDatabaseModal.tsx", import.meta.url), "utf8");
|
||||
|
||||
expect(source).not.toContain("请输入搜索关键字");
|
||||
expect(source).not.toContain("未找到连接配置");
|
||||
expect(source).not.toContain("获取表列表失败");
|
||||
expect(source).not.toContain("当前数据库没有表");
|
||||
expect(source).not.toContain("未找到匹配的数据");
|
||||
expect(source).not.toContain("搜索出错");
|
||||
expect(source).not.toContain("在数据库中搜索");
|
||||
expect(source).not.toContain("输入要搜索的字符串");
|
||||
expect(source).not.toContain("精确匹配");
|
||||
expect(source).not.toContain("匹配行详情");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user