fix(grid): preserve viewport during gutter execution
This commit is contained in:
1 parent
99ded5c8bb
commit
a6945a19f2
8 files changed
+131
-14
No files matched your search
@@ -1,8 +1,12 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createQueryEditorExecutionViewportOwnership } from "@/lib/editor/queryEditorExecutionViewport";
|
||||
|
||||
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
|
||||
const contentAreaSource = readFileSync(new URL("../../../components/layout/ContentArea.vue", import.meta.url), "utf8");
|
||||
const appSource = readFileSync(new URL("../../../App.vue", import.meta.url), "utf8");
|
||||
const sqlExecutionSource = readFileSync(new URL("../../../composables/useSqlExecution.ts", import.meta.url), "utf8");
|
||||
const queryStoreSource = readFileSync(new URL("../../../stores/queryStore.ts", import.meta.url), "utf8");
|
||||
|
||||
describe("QueryEditor execution routing", () => {
|
||||
it("routes the execution shortcut through the shared execution-mode contract while bypassing the picker", () => {
|
||||
@@ -46,10 +50,18 @@ describe("QueryEditor execution routing", () => {
|
||||
});
|
||||
|
||||
it("preserves the source range when executing from the statement gutter", () => {
|
||||
expect(queryEditorSource).toContain("emitExecutionRequest(sqlExecutionSnapshotForRange(currentView, statementRange))");
|
||||
expect(queryEditorSource).toContain("const editorViewportRequestId = gutterExecutionViewport.beginRequest()");
|
||||
expect(queryEditorSource).toContain("emitExecutionRequest({ ...sqlExecutionSnapshotForRange(currentView, statementRange), editorViewportRequestId })");
|
||||
expect(queryEditorSource).not.toContain('emit("execute", statementRange.sql)');
|
||||
});
|
||||
|
||||
it("claims gutter viewport ownership only after the matching execution starts", () => {
|
||||
expect(appSource).toContain("acceptQueryEditorExecutionViewport(editorViewportRequestId)");
|
||||
expect(contentAreaSource).toContain("acceptGutterExecutionViewport(requestId)");
|
||||
expect(sqlExecutionSource).toContain("onExecutionStarted: () => deps.onExecutionStarted?.(options.editorViewportRequestId!)");
|
||||
expect(queryStoreSource.indexOf("tab.isExecuting = true")).toBeLessThan(queryStoreSource.indexOf("options?.onExecutionStarted?.()"));
|
||||
});
|
||||
|
||||
it("lets the shortcut skip the picker without affecting other execution entry points", () => {
|
||||
// The picker guard must also honor the shortcut's bypass flag, otherwise Ctrl+Enter would keep popping the dialog.
|
||||
expect(queryEditorSource).toContain("if (options.bypassPicker || !settingsStore.editorSettings.showExecutionTargetPicker");
|
||||
@@ -63,6 +75,41 @@ describe("QueryEditor execution routing", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("QueryEditor gutter execution viewport ownership", () => {
|
||||
it("does not let a cancelled or early-returned gutter request affect the next ordinary execution", () => {
|
||||
const ownership = createQueryEditorExecutionViewportOwnership();
|
||||
const cancelledRequestId = ownership.beginRequest();
|
||||
|
||||
ownership.cancelPendingRequest();
|
||||
|
||||
expect(ownership.acceptRequest(cancelledRequestId)).toBe(false);
|
||||
expect(ownership.consumeAcceptedRequest()).toBe(false);
|
||||
});
|
||||
|
||||
it("preserves the viewport once for the matching accepted execution", () => {
|
||||
const ownership = createQueryEditorExecutionViewportOwnership();
|
||||
const requestId = ownership.beginRequest();
|
||||
|
||||
expect(ownership.acceptRequest(requestId)).toBe(true);
|
||||
expect(ownership.consumeAcceptedRequest()).toBe(true);
|
||||
expect(ownership.consumeAcceptedRequest()).toBe(false);
|
||||
});
|
||||
|
||||
it("clears pending and accepted ownership when the editor becomes inactive", () => {
|
||||
const ownership = createQueryEditorExecutionViewportOwnership();
|
||||
const pendingRequestId = ownership.beginRequest();
|
||||
ownership.reset();
|
||||
|
||||
expect(ownership.acceptRequest(pendingRequestId)).toBe(false);
|
||||
|
||||
const acceptedRequestId = ownership.beginRequest();
|
||||
expect(ownership.acceptRequest(acceptedRequestId)).toBe(true);
|
||||
ownership.reset();
|
||||
|
||||
expect(ownership.consumeAcceptedRequest()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContentArea execution summary errors", () => {
|
||||
it("keeps batch errors selectable and copyable without triggering statement navigation", () => {
|
||||
expect(contentAreaSource).toContain('class="absolute inset-0 z-0 cursor-pointer');
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
export function createQueryEditorExecutionViewportOwnership() {
|
||||
let nextRequestId = 0;
|
||||
let pendingRequestId: number | undefined;
|
||||
let acceptedRequestId: number | undefined;
|
||||
|
||||
return {
|
||||
beginRequest(): number {
|
||||
nextRequestId += 1;
|
||||
pendingRequestId = nextRequestId;
|
||||
return nextRequestId;
|
||||
},
|
||||
cancelPendingRequest() {
|
||||
pendingRequestId = undefined;
|
||||
},
|
||||
acceptRequest(requestId: number): boolean {
|
||||
if (pendingRequestId !== requestId) return false;
|
||||
pendingRequestId = undefined;
|
||||
acceptedRequestId = requestId;
|
||||
return true;
|
||||
},
|
||||
consumeAcceptedRequest(): boolean {
|
||||
if (acceptedRequestId === undefined) return false;
|
||||
acceptedRequestId = undefined;
|
||||
return true;
|
||||
},
|
||||
reset() {
|
||||
pendingRequestId = undefined;
|
||||
acceptedRequestId = undefined;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export interface SqlExecutionSnapshot {
|
||||
cursorPos: number;
|
||||
selectionFrom: number;
|
||||
selectionTo: number;
|
||||
editorViewportRequestId?: number;
|
||||
}
|
||||
|
||||
export type SqlExecutionOverride = string | SqlExecutionSnapshot;
|
||||
|
||||
Reference in new issue
Block a user