🐛 fix(sql-editor): 修复补全提示下连续输入光标跳转

- 调整 SQL 补全触发策略,避免普通字母输入高频触发 provider
- 支持 Monaco 补全取消 token,丢弃连续输入时的过期异步请求
- 将 SQL snippet provider 纳入统一 disposable 管理,避免重复注册残留
- 补充 QueryEditor 回归测试覆盖补全触发、取消和释放链路
Refs #504
This commit is contained in:
Syngnat
2026-06-12 16:25:23 +08:00
parent 77a306beb2
commit 5fcc04a200
2 changed files with 87 additions and 7 deletions

View File

@@ -71,6 +71,7 @@ const storeState = vi.hoisted(() => ({
activeTabId: 'tab-1',
aiPanelVisible: false,
setAIPanelVisible: vi.fn(),
sqlSnippets: [] as any[],
}));
const backendApp = vi.hoisted(() => ({
@@ -827,6 +828,73 @@ describe('QueryEditor external SQL save', () => {
renderer.unmount();
});
it('registers all SQL completion providers in the disposable singleton state', async () => {
let renderer!: ReactTestRenderer;
await act(async () => {
renderer = create(<QueryEditor tab={createTab({ query: '' })} />);
});
const completionState = (globalThis as any).__gonaviSqlCompletionState;
expect(editorState.hoverProviders).toHaveLength(1);
expect(editorState.providers).toHaveLength(3);
expect(completionState.disposables).toHaveLength(4);
await act(async () => {
renderer.unmount();
});
});
it('keeps plain typing out of SQL completion trigger characters', async () => {
let renderer!: ReactTestRenderer;
await act(async () => {
renderer = create(<QueryEditor tab={createTab({ query: '' })} />);
});
const sqlProvider = editorState.providers.find((provider) => Array.isArray(provider.triggerCharacters) && provider.triggerCharacters.includes('.'));
expect(sqlProvider).toBeTruthy();
expect(sqlProvider.triggerCharacters).toEqual(['.']);
expect(sqlProvider.triggerCharacters).not.toContain('s');
await act(async () => {
renderer.unmount();
});
});
it('drops cancelled SQL completion requests while the user keeps typing', async () => {
let renderer!: ReactTestRenderer;
backendApp.DBGetTables.mockResolvedValueOnce({
success: true,
data: [{ Table: 'session_log' }],
});
await act(async () => {
renderer = create(<QueryEditor tab={createTab({ query: '', dbName: 'main' })} />);
});
const sqlProvider = editorState.providers.find((provider) => Array.isArray(provider.triggerCharacters) && provider.triggerCharacters.includes('.'));
expect(sqlProvider).toBeTruthy();
editorState.value = 'SELECT * FROM ss';
editorState.position = { lineNumber: 1, column: editorState.value.length + 1 };
editorState.latestOnChange?.(editorState.value);
const result = await sqlProvider.provideCompletionItems(
editorState.editor.getModel(),
editorState.position,
undefined,
{ isCancellationRequested: true },
);
expect(result.suggestions).toEqual([]);
expect(backendApp.DBGetTables).not.toHaveBeenCalled();
await act(async () => {
renderer.unmount();
});
});
it('keeps table name completion available after typing in a fresh query tab', async () => {
let renderer!: ReactTestRenderer;
autoFetchState.visible = true;