mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-02 17:11:38 +08:00
@@ -537,6 +537,20 @@ const getLastInjectedPrompt = (): string => {
|
||||
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 => ({
|
||||
id: 'tab-1',
|
||||
title: 'query.sql',
|
||||
@@ -1610,6 +1624,103 @@ describe('QueryEditor external SQL save', () => {
|
||||
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 () => {
|
||||
storeState.appearance.uiVersion = 'v2';
|
||||
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'),
|
||||
keybindings: [keyBinding.keyMod | keyBinding.keyCode],
|
||||
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 () => {
|
||||
if (!currentQueryIdRef.current) {
|
||||
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.stopPropagation();
|
||||
void handleRun();
|
||||
void handleRunSelectedShortcut();
|
||||
};
|
||||
|
||||
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'),
|
||||
keybindings: [keyBinding.keyMod | keyBinding.keyCode],
|
||||
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]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRunActiveQuery = () => {
|
||||
const handleRunActiveQuery = (event: Event) => {
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
if ((event as CustomEvent<{ requireSelection?: boolean }>).detail?.requireSelection) {
|
||||
void handleRunSelectedShortcut();
|
||||
return;
|
||||
}
|
||||
void handleRun();
|
||||
};
|
||||
|
||||
@@ -4893,7 +4909,7 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
return () => {
|
||||
window.removeEventListener('gonavi:run-active-query', handleRunActiveQuery as EventListener);
|
||||
};
|
||||
}, [isActive, handleRun]);
|
||||
}, [isActive, handleRun, handleRunSelectedShortcut]);
|
||||
|
||||
// 监听由 TabManager 分发的专用注入事件
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user