mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-22 05:11:44 +08:00
feat(i18n): 收口多语言功能业务代码
This commit is contained in:
@@ -4,8 +4,10 @@ import { act, create, type ReactTestRenderer } from 'react-test-renderer';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { setCurrentLanguage } from '../i18n';
|
||||
import { catalogs } from '../i18n/catalog';
|
||||
import type { SavedQuery, TabData } from '../types';
|
||||
import { ORACLE_ROWID_LOCATOR_COLUMN } from '../utils/rowLocator';
|
||||
import { formatSqlExecutionError } from '../utils/sqlErrorSemantics';
|
||||
import { clearQueryTabDraft, clearSQLFileTabDraft, getQueryTabDraft, getSQLFileTabDraft } from '../utils/sqlFileTabDrafts';
|
||||
import QueryEditor, {
|
||||
collectQueryEditorObjectDecorationCandidates,
|
||||
@@ -618,6 +620,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
editorState.position = { lineNumber: 1, column: 1 };
|
||||
editorState.selection = null;
|
||||
editorState.domNode.style.cursor = '';
|
||||
editorState.providers = [];
|
||||
editorState.hoverProviders = [];
|
||||
editorState.contentChangeListeners = [];
|
||||
editorState.cursorPositionListeners = [];
|
||||
@@ -2099,7 +2102,9 @@ describe('QueryEditor external SQL save', () => {
|
||||
|
||||
editorState.value = '__AI_SQL__\nselect 1;';
|
||||
await act(async () => {
|
||||
editorState.contentChangeListeners.forEach((listener) => listener());
|
||||
editorState.contentChangeListeners.forEach((listener) => (listener as any)({
|
||||
changes: [{ text: '__AI_SQL__' }],
|
||||
}));
|
||||
vi.runAllTimers();
|
||||
});
|
||||
|
||||
@@ -2318,7 +2323,9 @@ describe('QueryEditor external SQL save', () => {
|
||||
editorState.value = '__AI_SQL__\nselect 1;';
|
||||
|
||||
await act(async () => {
|
||||
editorState.contentChangeListeners.forEach((listener) => listener());
|
||||
editorState.contentChangeListeners.forEach((listener) => (listener as any)({
|
||||
changes: [{ text: '__AI_SQL__' }],
|
||||
}));
|
||||
vi.runAllTimers();
|
||||
});
|
||||
|
||||
@@ -2382,7 +2389,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
});
|
||||
|
||||
expect(getLastInjectedPrompt()).toBe(
|
||||
'I got an error while executing this SQL:\n```sql\nselect 1;\n```\n\nThe database returned this error:\n```text\ndriver exploded\n```\n\nAnalyze the cause and suggest a fix.',
|
||||
`I got an error while executing this SQL:\n\`\`\`sql\nselect 1;\n\`\`\`\n\nThe database returned this error:\n\`\`\`text\n${formatSqlExecutionError('driver exploded')}\n\`\`\`\n\nAnalyze the cause and suggest a fix.`,
|
||||
);
|
||||
expect(getLastInjectedPrompt()).not.toContain('我在执行以下 SQL 时遇到了错误');
|
||||
} finally {
|
||||
@@ -2479,17 +2486,12 @@ describe('QueryEditor external SQL save', () => {
|
||||
backendApp.DBQuery.mock.calls.some((call: any[]) => /table_comment|information_schema\.tables/i.test(String(call[2]))),
|
||||
).toBe(true);
|
||||
|
||||
await act(async () => {
|
||||
editorState.mouseMoveListeners[0]?.({
|
||||
target: { position: { lineNumber: 1, column: 27 } },
|
||||
event: {
|
||||
ctrlKey: true,
|
||||
metaKey: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const hoverMarkdown = editorState.editor.deltaDecorations.mock.calls.at(-1)?.[1]?.[0]?.options?.hoverMessage?.value;
|
||||
const hoverProvider = editorState.hoverProviders[editorState.hoverProviders.length - 1];
|
||||
const hover = hoverProvider?.provideHover(
|
||||
editorState.editor.getModel(),
|
||||
{ lineNumber: 1, column: 27 },
|
||||
);
|
||||
const hoverMarkdown = hover?.contents?.[0]?.value;
|
||||
expect(hoverMarkdown).toContain('**Table** `reporting.events`');
|
||||
expect(hoverMarkdown).toContain('Database: `main`');
|
||||
expect(hoverMarkdown).toContain('Schema: `reporting`');
|
||||
@@ -3002,6 +3004,13 @@ describe('QueryEditor external SQL save', () => {
|
||||
const completionProvider = editorState.providers[0];
|
||||
expect(completionProvider).toBeTruthy();
|
||||
|
||||
editorState.value = 'select * from analytics.';
|
||||
await completionProvider.provideCompletionItems(
|
||||
editorState.editor.getModel(),
|
||||
{ lineNumber: 1, column: editorState.value.length + 1 },
|
||||
);
|
||||
|
||||
editorState.value = 'select * from ';
|
||||
const completionItems = await completionProvider.provideCompletionItems(
|
||||
editorState.editor.getModel(),
|
||||
{ lineNumber: 1, column: editorState.value.length + 1 },
|
||||
@@ -3634,7 +3643,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
query: 'select 2;',
|
||||
savedQueryId: undefined,
|
||||
}));
|
||||
expect(messageApi.success).toHaveBeenCalledWith('SQL 文件已保存!');
|
||||
expect(messageApi.success).toHaveBeenCalledWith('SQL 文件已保存。');
|
||||
});
|
||||
|
||||
it('keeps external SQL file typing out of persisted tab drafts to avoid input freezes', async () => {
|
||||
@@ -3844,7 +3853,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
dbName: 'main',
|
||||
createdAt: 100,
|
||||
}));
|
||||
expect(messageApi.success).toHaveBeenCalledWith('查询已保存!');
|
||||
expect(messageApi.success).toHaveBeenCalledWith('查询已保存。');
|
||||
});
|
||||
|
||||
it('does not create saved queries when external SQL file writes fail', async () => {
|
||||
@@ -3865,7 +3874,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(backendApp.WriteSQLFile).toHaveBeenCalledWith(filePath, 'select 4;');
|
||||
expect(storeState.saveQuery).not.toHaveBeenCalled();
|
||||
expect(storeState.addTab).not.toHaveBeenCalled();
|
||||
expect(messageApi.error).toHaveBeenCalledWith('保存 SQL 文件失败: 磁盘只读');
|
||||
expect(messageApi.error).toHaveBeenCalledWith('保存 SQL 文件失败:磁盘只读');
|
||||
});
|
||||
|
||||
it('keeps saved query quick-save behavior for non-file tabs', async () => {
|
||||
@@ -3913,7 +3922,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
editorState.value = 'select 8;';
|
||||
|
||||
await act(async () => {
|
||||
findButton(renderer!, '保存').props.onClick();
|
||||
findButton(renderer!, 'Save').props.onClick();
|
||||
});
|
||||
|
||||
expect(storeState.saveQuery).toHaveBeenCalledWith(expect.objectContaining({
|
||||
@@ -4008,7 +4017,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
title: '查询',
|
||||
savedQueryId: 'saved-1',
|
||||
}));
|
||||
expect(messageApi.success).toHaveBeenCalledWith('查询已重命名!');
|
||||
expect(messageApi.success).toHaveBeenCalledWith('查询已重命名。');
|
||||
});
|
||||
|
||||
it('exports the current editor SQL without changing saved query state', async () => {
|
||||
@@ -5536,9 +5545,13 @@ describe('QueryEditor external SQL save', () => {
|
||||
renderer = create(<QueryEditor tab={createTab()} />);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
findButton(renderer!, 'Show results panel').props.onClick();
|
||||
});
|
||||
|
||||
const rendered = textContent(renderer!.toJSON());
|
||||
expect(rendered).toContain('Awaiting SQL execution');
|
||||
expect(rendered).toContain('Run a query to display results below in the new data grid.');
|
||||
expect(rendered).toContain(catalogs['en-US']['query_editor.empty_state.title']);
|
||||
expect(rendered).toContain(catalogs['en-US']['query_editor.empty_state.description']);
|
||||
expect(rendered).not.toContain('等待执行 SQL');
|
||||
expect(rendered).not.toContain('运行查询后,结果会在下方以新版数据网格展示。');
|
||||
});
|
||||
@@ -5634,7 +5647,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
editorState.position = { lineNumber: 3, column: 1 };
|
||||
|
||||
await act(async () => {
|
||||
const runButton = findButton(renderer!, 'Run');
|
||||
const runButton = findButton(renderer!, '运行');
|
||||
runButton.props.onMouseDown?.({ preventDefault: vi.fn() });
|
||||
await runButton.props.onClick();
|
||||
});
|
||||
@@ -5674,7 +5687,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
const runButton = findButton(renderer!, 'Run');
|
||||
const runButton = findButton(renderer!, '运行');
|
||||
runButton.props.onMouseDown?.({ preventDefault: vi.fn() });
|
||||
await runButton.props.onClick();
|
||||
});
|
||||
@@ -5716,7 +5729,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
const runButton = findButton(renderer!, '运行');
|
||||
const runButton = findButton(renderer!, 'Run');
|
||||
runButton.props.onMouseDown?.({ preventDefault: vi.fn() });
|
||||
await runButton.props.onClick();
|
||||
});
|
||||
@@ -5725,7 +5738,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(textContent(renderer!.toJSON())).toContain('结果 1');
|
||||
expect(textContent(renderer!.toJSON())).toContain('Result 1');
|
||||
backendApp.DBQueryMulti.mockClear();
|
||||
messageApi.info.mockClear();
|
||||
|
||||
@@ -5743,7 +5756,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const runButton = findButton(renderer!, '运行');
|
||||
const runButton = findButton(renderer!, 'Run');
|
||||
runButton.props.onMouseDown?.({ preventDefault: vi.fn() });
|
||||
await runButton.props.onClick();
|
||||
});
|
||||
@@ -5879,7 +5892,8 @@ describe('QueryEditor external SQL save', () => {
|
||||
});
|
||||
|
||||
const rendered = textContent(renderer.toJSON());
|
||||
expect(rendered).toContain('Statement 2 failed: driver exploded');
|
||||
expect(rendered).toContain('Statement 2 failed:');
|
||||
expect(rendered).toContain('Raw error: driver exploded');
|
||||
expect(rendered).not.toContain('第 2 条语句执行失败:driver exploded');
|
||||
});
|
||||
|
||||
@@ -5951,11 +5965,16 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(messageApi.success).not.toHaveBeenCalledWith('已执行完成,生成 2 个结果集。');
|
||||
});
|
||||
|
||||
it('shows the non-Mongo zero-result success toast in English', async () => {
|
||||
it('renders the non-Mongo zero-row transactional result in English', async () => {
|
||||
storeState.languagePreference = 'en-US';
|
||||
setCurrentLanguage('en-US');
|
||||
const query = 'update users set active = 1 where 1 = 0;';
|
||||
backendApp.DBQueryMulti.mockResolvedValueOnce({ success: true, data: [] });
|
||||
backendApp.DBQueryMultiTransactional.mockResolvedValueOnce({
|
||||
success: true,
|
||||
transactionId: 'tx-zero-rows',
|
||||
transactionPending: true,
|
||||
data: [{ columns: ['affectedRows'], rows: [{ affectedRows: 0 }], statementIndex: 1 }],
|
||||
});
|
||||
|
||||
let renderer!: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
@@ -5980,11 +5999,16 @@ describe('QueryEditor external SQL save', () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const rendered = textContent(renderer.toJSON());
|
||||
expect(backendApp.DBQueryWithCancel).not.toHaveBeenCalled();
|
||||
expect(backendApp.DBQueryMulti).toHaveBeenCalledTimes(1);
|
||||
expect(String(backendApp.DBQueryMulti.mock.calls[0][2])).toContain('update users set active = 1 where 1 = 0');
|
||||
expect(messageApi.success).toHaveBeenCalledWith('Execution succeeded.');
|
||||
expect(messageApi.success).not.toHaveBeenCalledWith('执行成功。');
|
||||
expect(backendApp.DBQueryMulti).not.toHaveBeenCalled();
|
||||
expect(backendApp.DBQueryMultiTransactional).toHaveBeenCalledTimes(1);
|
||||
expect(String(backendApp.DBQueryMultiTransactional.mock.calls[0][2])).toContain('update users set active = 1 where 1 = 0');
|
||||
expect(rendered).toContain(catalogs['en-US']['query_editor.result.execution_success']);
|
||||
expect(rendered).toContain(catalogs['en-US']['query_editor.result.affected_rows'].replace('{{count}}', '0'));
|
||||
expect(rendered).not.toContain('执行成功');
|
||||
expect(rendered).not.toContain('影响行数:0');
|
||||
expect(messageApi.success).not.toHaveBeenCalledWith('Execution succeeded.');
|
||||
});
|
||||
|
||||
it('shows the wrapped execution failure toast in English while preserving raw error detail', async () => {
|
||||
@@ -6019,7 +6043,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(backendApp.DBQueryWithCancel).not.toHaveBeenCalled();
|
||||
expect(backendApp.DBQueryMulti).toHaveBeenCalledTimes(1);
|
||||
expect(String(backendApp.DBQueryMulti.mock.calls[0][2])).toContain('select 1');
|
||||
expect(messageApi.error).toHaveBeenCalledWith('Query execution failed: driver exploded');
|
||||
expect(messageApi.error).toHaveBeenCalledWith(`Query execution failed: ${formatSqlExecutionError('driver exploded')}`);
|
||||
expect(messageApi.error).not.toHaveBeenCalledWith('Error executing query: driver exploded');
|
||||
});
|
||||
});
|
||||
@@ -6074,7 +6098,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(messageApi.error).toHaveBeenCalledWith('Refresh failed: network down');
|
||||
expect(messageApi.error).toHaveBeenCalledWith(`Refresh failed: ${formatSqlExecutionError('network down')}`);
|
||||
expect(messageApi.error).not.toHaveBeenCalledWith('刷新失败: network down');
|
||||
});
|
||||
|
||||
@@ -6100,7 +6124,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(messageApi.error).toHaveBeenCalledWith('Refresh failed: socket closed');
|
||||
expect(messageApi.error).toHaveBeenCalledWith(`Refresh failed: ${formatSqlExecutionError('socket closed')}`);
|
||||
expect(messageApi.error).not.toHaveBeenCalledWith('刷新失败: socket closed');
|
||||
});
|
||||
});
|
||||
@@ -6651,7 +6675,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
const modalSource = readFileSync(new URL('./SnippetSettingsModal.tsx', import.meta.url), 'utf8');
|
||||
const source = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8');
|
||||
|
||||
expect(modalSource).toContain('片段语法说明(可编辑)');
|
||||
expect(modalSource).toContain("t('snippet_settings.syntax_help.label')");
|
||||
expect(modalSource).toContain('data-sql-snippet-syntax-help-editor="true"');
|
||||
expect(modalSource).toContain("defaultActiveKey={['snippet-help']}");
|
||||
expect(modalSource).toContain('footer={null}');
|
||||
@@ -6660,7 +6684,7 @@ describe('QueryEditor external SQL save', () => {
|
||||
expect(modalSource).toContain("size=\"large\"");
|
||||
expect(modalSource).toContain('minWidth: 96');
|
||||
expect(modalSource).toContain('syntaxHelp');
|
||||
expect(modalSource).toContain('占位符语法参考');
|
||||
expect(modalSource).toContain("t('snippet_settings.syntax_reference.label')");
|
||||
expect(source).toContain('s.syntaxHelp || s.description || s.body');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user