mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-09 22:42:55 +08:00
@@ -537,6 +537,20 @@ const getLastInjectedPrompt = (): string => {
|
|||||||
return event?.detail?.prompt;
|
return event?.detail?.prompt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createRunShortcutEvent = () => {
|
||||||
|
const isMacRuntime = /(Mac|iPhone|iPad|iPod)/i.test(`${navigator.platform || ''} ${navigator.userAgent || ''}`);
|
||||||
|
return {
|
||||||
|
ctrlKey: !isMacRuntime,
|
||||||
|
metaKey: isMacRuntime,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: false,
|
||||||
|
key: 'Enter',
|
||||||
|
target: null,
|
||||||
|
preventDefault: vi.fn(),
|
||||||
|
stopPropagation: vi.fn(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const createTab = (overrides: Partial<TabData> = {}): TabData => ({
|
const createTab = (overrides: Partial<TabData> = {}): TabData => ({
|
||||||
id: 'tab-1',
|
id: 'tab-1',
|
||||||
title: 'query.sql',
|
title: 'query.sql',
|
||||||
@@ -1610,6 +1624,103 @@ describe('QueryEditor external SQL save', () => {
|
|||||||
expect(String(backendApp.DBQueryMulti.mock.calls[0][2])).not.toContain('select 3');
|
expect(String(backendApp.DBQueryMulti.mock.calls[0][2])).not.toContain('select 3');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not run SQL from the run shortcut when nothing is selected', async () => {
|
||||||
|
storeState.shortcutOptions.runQuery.mac = { enabled: true, combo: 'Meta+Enter' };
|
||||||
|
storeState.shortcutOptions.runQuery.windows = { enabled: true, combo: 'Ctrl+Enter' };
|
||||||
|
const windowListeners: Record<string, ((event?: any) => void)[]> = {};
|
||||||
|
vi.stubGlobal('window', {
|
||||||
|
addEventListener: vi.fn((type: string, listener: (event?: any) => void) => {
|
||||||
|
windowListeners[type] ||= [];
|
||||||
|
windowListeners[type].push(listener);
|
||||||
|
}),
|
||||||
|
removeEventListener: vi.fn(),
|
||||||
|
dispatchEvent: vi.fn(),
|
||||||
|
requestAnimationFrame: vi.fn((callback: FrameRequestCallback) => {
|
||||||
|
callback(0);
|
||||||
|
return 1;
|
||||||
|
}),
|
||||||
|
cancelAnimationFrame: vi.fn(),
|
||||||
|
innerHeight: 900,
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
create(<QueryEditor tab={createTab({
|
||||||
|
dbName: 'main',
|
||||||
|
query: 'select 1;\nselect 2 as two;\nselect 3;',
|
||||||
|
})} />);
|
||||||
|
});
|
||||||
|
editorState.position = { lineNumber: 2, column: 8 };
|
||||||
|
editorState.selection = null;
|
||||||
|
backendApp.DBQueryMulti.mockClear();
|
||||||
|
|
||||||
|
const event = createRunShortcutEvent();
|
||||||
|
await act(async () => {
|
||||||
|
windowListeners.keydown?.forEach((listener) => listener(event));
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
await Promise.resolve();
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(event.preventDefault).toHaveBeenCalled();
|
||||||
|
expect(event.stopPropagation).toHaveBeenCalled();
|
||||||
|
expect(backendApp.DBQueryMulti).not.toHaveBeenCalled();
|
||||||
|
expect(messageApi.info).toHaveBeenCalledWith('没有可选择的 SQL 语句。');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs selected SQL from the run shortcut', async () => {
|
||||||
|
storeState.shortcutOptions.runQuery.mac = { enabled: true, combo: 'Meta+Enter' };
|
||||||
|
storeState.shortcutOptions.runQuery.windows = { enabled: true, combo: 'Ctrl+Enter' };
|
||||||
|
const windowListeners: Record<string, ((event?: any) => void)[]> = {};
|
||||||
|
vi.stubGlobal('window', {
|
||||||
|
addEventListener: vi.fn((type: string, listener: (event?: any) => void) => {
|
||||||
|
windowListeners[type] ||= [];
|
||||||
|
windowListeners[type].push(listener);
|
||||||
|
}),
|
||||||
|
removeEventListener: vi.fn(),
|
||||||
|
dispatchEvent: vi.fn(),
|
||||||
|
requestAnimationFrame: vi.fn((callback: FrameRequestCallback) => {
|
||||||
|
callback(0);
|
||||||
|
return 1;
|
||||||
|
}),
|
||||||
|
cancelAnimationFrame: vi.fn(),
|
||||||
|
innerHeight: 900,
|
||||||
|
});
|
||||||
|
backendApp.DBQueryMulti.mockResolvedValueOnce({
|
||||||
|
success: true,
|
||||||
|
data: [{ columns: ['two'], rows: [{ two: 2 }] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
create(<QueryEditor tab={createTab({
|
||||||
|
dbName: 'main',
|
||||||
|
query: 'select 1;\nselect 2 as two;\nselect 3;',
|
||||||
|
})} />);
|
||||||
|
});
|
||||||
|
editorState.position = { lineNumber: 1, column: 4 };
|
||||||
|
editorState.selection = {
|
||||||
|
startLineNumber: 2,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 2,
|
||||||
|
endColumn: 'select 2 as two'.length + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const event = createRunShortcutEvent();
|
||||||
|
await act(async () => {
|
||||||
|
windowListeners.keydown?.forEach((listener) => listener(event));
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
await Promise.resolve();
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(event.preventDefault).toHaveBeenCalled();
|
||||||
|
expect(event.stopPropagation).toHaveBeenCalled();
|
||||||
|
expect(backendApp.DBQueryMulti).toHaveBeenCalledWith(expect.anything(), 'main', expect.stringContaining('select 2 as two'), 'query-1');
|
||||||
|
expect(String(backendApp.DBQueryMulti.mock.calls[0][2])).not.toContain('select 1');
|
||||||
|
expect(String(backendApp.DBQueryMulti.mock.calls[0][2])).not.toContain('select 3');
|
||||||
|
});
|
||||||
|
|
||||||
it('renders V2 empty state copy for the active non-Chinese language', async () => {
|
it('renders V2 empty state copy for the active non-Chinese language', async () => {
|
||||||
storeState.appearance.uiVersion = 'v2';
|
storeState.appearance.uiVersion = 'v2';
|
||||||
storeState.languagePreference = 'en-US';
|
storeState.languagePreference = 'en-US';
|
||||||
|
|||||||
@@ -2573,7 +2573,9 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
|||||||
label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.runQuery.label'),
|
label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.runQuery.label'),
|
||||||
keybindings: [keyBinding.keyMod | keyBinding.keyCode],
|
keybindings: [keyBinding.keyMod | keyBinding.keyCode],
|
||||||
run: () => {
|
run: () => {
|
||||||
window.dispatchEvent(new CustomEvent('gonavi:run-active-query'));
|
window.dispatchEvent(new CustomEvent('gonavi:run-active-query', {
|
||||||
|
detail: { requireSelection: true },
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -4629,6 +4631,14 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRunSelectedShortcut = async () => {
|
||||||
|
if (!getSelectedSQL().trim()) {
|
||||||
|
message.info(translate('query_editor.message.no_selectable_sql'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await handleRun();
|
||||||
|
};
|
||||||
|
|
||||||
const handleCancel = async () => {
|
const handleCancel = async () => {
|
||||||
if (!currentQueryIdRef.current) {
|
if (!currentQueryIdRef.current) {
|
||||||
message.warning(translate('query_editor.message.cancel_no_running'));
|
message.warning(translate('query_editor.message.cancel_no_running'));
|
||||||
@@ -4710,7 +4720,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
|||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
void handleRun();
|
void handleRunSelectedShortcut();
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', handleRunShortcut, true);
|
window.addEventListener('keydown', handleRunShortcut, true);
|
||||||
@@ -4758,7 +4768,9 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
|||||||
label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.runQuery.label'),
|
label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.runQuery.label'),
|
||||||
keybindings: [keyBinding.keyMod | keyBinding.keyCode],
|
keybindings: [keyBinding.keyMod | keyBinding.keyCode],
|
||||||
run: () => {
|
run: () => {
|
||||||
window.dispatchEvent(new CustomEvent('gonavi:run-active-query'));
|
window.dispatchEvent(new CustomEvent('gonavi:run-active-query', {
|
||||||
|
detail: { requireSelection: true },
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -4882,10 +4894,14 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
|||||||
}, [languagePreference, toggleQueryResultsPanelShortcutBinding, toggleResultPanelVisibility]);
|
}, [languagePreference, toggleQueryResultsPanelShortcutBinding, toggleResultPanelVisibility]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleRunActiveQuery = () => {
|
const handleRunActiveQuery = (event: Event) => {
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ((event as CustomEvent<{ requireSelection?: boolean }>).detail?.requireSelection) {
|
||||||
|
void handleRunSelectedShortcut();
|
||||||
|
return;
|
||||||
|
}
|
||||||
void handleRun();
|
void handleRun();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4893,7 +4909,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('gonavi:run-active-query', handleRunActiveQuery as EventListener);
|
window.removeEventListener('gonavi:run-active-query', handleRunActiveQuery as EventListener);
|
||||||
};
|
};
|
||||||
}, [isActive, handleRun]);
|
}, [isActive, handleRun, handleRunSelectedShortcut]);
|
||||||
|
|
||||||
// 监听由 TabManager 分发的专用注入事件
|
// 监听由 TabManager 分发的专用注入事件
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user