mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(editor): execute manually selected sql from the toolbar
This commit is contained in:
+21
-20
@@ -776,15 +776,17 @@ const executableSql = computed(() => {
|
||||
: "";
|
||||
});
|
||||
|
||||
async function resolveActiveExecutableSql(snapshot?: SqlExecutionSnapshot) {
|
||||
const tab = activeTab.value;
|
||||
return tab
|
||||
? await resolveExecutableSqlWithBackend(snapshot?.fullSql ?? tab.sql, snapshot?.selectedSql ?? selectedSql.value, {
|
||||
mode: settingsStore.editorSettings.executeMode,
|
||||
cursorPos: snapshot?.cursorPos ?? cursorPos.value,
|
||||
databaseType: activeConnection.value?.db_type,
|
||||
})
|
||||
: "";
|
||||
async function resolveActiveExecutableSql(snapshot?: SqlExecutionSnapshot, executionTab?: QueryTab) {
|
||||
const tab = executionTab ?? activeTab.value;
|
||||
if (!tab) return "";
|
||||
const connection = connectionStore.getConfig(tab.connectionId) ?? activeConnection.value;
|
||||
const selection = tab.editorSelection;
|
||||
const fallbackSelectedSql = tab.id === activeTab.value?.id ? selectedSql.value : selection && selection.anchor !== selection.head ? tab.sql.slice(Math.min(selection.anchor, selection.head), Math.max(selection.anchor, selection.head)) : "";
|
||||
return await resolveExecutableSqlWithBackend(snapshot?.fullSql ?? tab.sql, snapshot?.selectedSql ?? fallbackSelectedSql, {
|
||||
mode: settingsStore.editorSettings.executeMode,
|
||||
cursorPos: snapshot?.cursorPos ?? (tab.id === activeTab.value?.id ? cursorPos.value : (selection?.head ?? 0)),
|
||||
databaseType: connection?.db_type,
|
||||
});
|
||||
}
|
||||
|
||||
const blockDangerousRedisCommands = ref(true);
|
||||
@@ -835,22 +837,21 @@ const {
|
||||
onExecutionCancelled: (editorViewportRequestId) => contentAreaRef.value?.cancelQueryEditorExecutionViewport(editorViewportRequestId),
|
||||
});
|
||||
|
||||
function captureActiveEditorExecutionSnapshot() {
|
||||
const snapshot = contentAreaRef.value?.captureQueryEditorExecutionSnapshot?.();
|
||||
pendingToolbarExecutionSnapshot.value = snapshot ? { ...snapshot, tabId: activeTab.value?.id } : undefined;
|
||||
function captureActiveEditorExecutionSnapshot(tabId: string) {
|
||||
const snapshot = contentAreaRef.value?.captureQueryEditorExecutionSnapshot?.(tabId);
|
||||
pendingToolbarExecutionSnapshot.value = snapshot ? { ...snapshot, tabId } : undefined;
|
||||
}
|
||||
|
||||
function requestActiveEditorExecute(source?: "pointer" | "keyboard") {
|
||||
function requestActiveEditorExecute(source?: "pointer" | "keyboard", tabId?: string) {
|
||||
const snapshot = pendingToolbarExecutionSnapshot.value;
|
||||
pendingToolbarExecutionSnapshot.value = undefined;
|
||||
if (source === "pointer") {
|
||||
if (snapshot) {
|
||||
void tryExecute(snapshot, { tabId: snapshot.tabId ?? activeTab.value?.id });
|
||||
return;
|
||||
}
|
||||
const targetTabId = tabId ?? (source === "pointer" ? snapshot?.tabId : undefined);
|
||||
if (source === "pointer" && snapshot && snapshot.tabId === targetTabId) {
|
||||
void tryExecute(snapshot, { tabId: targetTabId });
|
||||
return;
|
||||
}
|
||||
if (contentAreaRef.value?.requestQueryEditorExecute?.()) return;
|
||||
void tryExecute();
|
||||
if (contentAreaRef.value?.requestQueryEditorExecute?.(targetTabId)) return;
|
||||
void tryExecute(undefined, targetTabId ? { tabId: targetTabId } : undefined);
|
||||
}
|
||||
|
||||
function requestActiveEditorExecuteInNewResultTab() {
|
||||
|
||||
@@ -192,8 +192,8 @@ const groupExecutableSql = computed(() => {
|
||||
@commit="activeTab && queryStore.commitTransaction(activeTab.id)"
|
||||
@rollback="activeTab && queryStore.rollbackTransaction(activeTab.id)"
|
||||
@dismiss-txn-rolled-back="activeTab && (activeTab.txnAutoRolledBack = false)"
|
||||
@execute-pointer-down="toolbar.captureExecutionSnapshot()"
|
||||
@toolbar-execute="toolbar.toolbarExecute($event)"
|
||||
@execute-pointer-down="toolbar.captureExecutionSnapshot(activeTab.id)"
|
||||
@toolbar-execute="toolbar.toolbarExecute($event, activeTab.id)"
|
||||
@multi-execute="toolbar.multiExecute()"
|
||||
@preview-changes="activeTab && toolbar.previewChanges(activeTab.id)"
|
||||
@cancel="activeTab && toolbar.cancelExecution(activeTab.id)"
|
||||
|
||||
@@ -92,9 +92,9 @@ defineExpose({
|
||||
const group = groupForElement(target) ?? activeEditorGroup();
|
||||
return group?.handleModRTarget(target) ?? false;
|
||||
},
|
||||
requestQueryEditorExecute: () => activeEditorGroup()?.requestQueryEditorExecute() ?? false,
|
||||
captureQueryEditorExecutionSnapshot: () => activeEditorGroup()?.captureQueryEditorExecutionSnapshot(),
|
||||
requestQueryEditorExecuteInNewResultTab: () => activeEditorGroup()?.requestQueryEditorExecuteInNewResultTab() ?? false,
|
||||
requestQueryEditorExecute: (tabId?: string) => editorGroupForTab(tabId)?.requestQueryEditorExecute() ?? false,
|
||||
captureQueryEditorExecutionSnapshot: (tabId?: string) => editorGroupForTab(tabId)?.captureQueryEditorExecutionSnapshot(),
|
||||
requestQueryEditorExecuteInNewResultTab: (tabId?: string) => editorGroupForTab(tabId)?.requestQueryEditorExecuteInNewResultTab() ?? false,
|
||||
requestQueryEditorPreviewChanges: (stackSql?: string) => activeEditorGroup()?.requestQueryEditorPreviewChanges(stackSql) ?? false,
|
||||
shouldBlockQueryEditorExecutionShortcut: (event: KeyboardEvent) => activeEditorGroup()?.shouldBlockQueryEditorExecutionShortcut(event) ?? false,
|
||||
cancelQueryEditorExecutionViewport: (requestId: number) => activeEditorGroup()?.cancelQueryEditorExecutionViewport(requestId) ?? false,
|
||||
@@ -214,6 +214,11 @@ function activeEditorGroup() {
|
||||
const group = queryStore.groups.find((item) => item.id === queryStore.focusedGroupId) ?? queryStore.groups[0];
|
||||
return group ? (groupRefs.get(group.id) ?? null) : null;
|
||||
}
|
||||
function editorGroupForTab(tabId?: string): InstanceType<typeof EditorGroup> | null {
|
||||
if (!tabId) return activeEditorGroup();
|
||||
const group = queryStore.groups.find((item) => item.activeTabId === tabId);
|
||||
return group ? (groupRefs.get(group.id) ?? null) : null;
|
||||
}
|
||||
function groupForElement(element: Element | null): InstanceType<typeof EditorGroup> | null {
|
||||
const groupElement = element?.closest<HTMLElement>("[data-group-id]");
|
||||
const groupId = groupElement?.dataset.groupId;
|
||||
|
||||
@@ -198,6 +198,8 @@ describe("EditorGroup mount contract", () => {
|
||||
|
||||
const actions: EditorToolbarActions = {
|
||||
...createNoopEditorToolbarActions(),
|
||||
captureExecutionSnapshot: vi.fn(),
|
||||
toolbarExecute: vi.fn(),
|
||||
cancelExecution: vi.fn(),
|
||||
explain: vi.fn(),
|
||||
saveSql: vi.fn(),
|
||||
@@ -229,10 +231,14 @@ describe("EditorGroup mount contract", () => {
|
||||
expect(toolbar?.textContent).toBe(queryId);
|
||||
|
||||
// Toolbar events flow to the injected actions with the group's tab id.
|
||||
(toolbar as any).__vueParentComponent.emit("executePointerDown");
|
||||
(toolbar as any).__vueParentComponent.emit("toolbarExecute", "pointer");
|
||||
(toolbar as any).__vueParentComponent.emit("cancel");
|
||||
(toolbar as any).__vueParentComponent.emit("explain");
|
||||
(toolbar as any).__vueParentComponent.emit("saveSql", queryId);
|
||||
await nextTick();
|
||||
expect(actions.captureExecutionSnapshot).toHaveBeenCalledWith(queryId);
|
||||
expect(actions.toolbarExecute).toHaveBeenCalledWith("pointer", queryId);
|
||||
expect(actions.cancelExecution).toHaveBeenCalledWith(queryId);
|
||||
expect(actions.explain).toHaveBeenCalledWith(queryId);
|
||||
expect(actions.saveSql).toHaveBeenCalledWith(queryId);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { createApp, nextTick } from "vue";
|
||||
import { createApp, h, nextTick, reactive, ref } from "vue";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { createI18n } from "vue-i18n";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -63,6 +63,9 @@ vi.mock("@/components/common/ProductionContextBadge.vue", () => ({
|
||||
|
||||
import EditorToolbar from "../EditorToolbar.vue";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { resolveExecutableSql, type SqlExecutionSnapshot } from "@/lib/sql/sqlExecutionTarget";
|
||||
import QueryEditor from "@/components/editor/QueryEditor.vue";
|
||||
import { EditorView } from "@codemirror/view";
|
||||
|
||||
function createHost(): HTMLDivElement {
|
||||
const host = document.createElement("div");
|
||||
@@ -141,6 +144,169 @@ describe("EditorToolbar mount contract", () => {
|
||||
host.remove();
|
||||
});
|
||||
|
||||
it("keeps the manually selected SQL from pointer-down through toolbar execution", async () => {
|
||||
const connectionStore = useConnectionStore();
|
||||
connectionStore.connections = [
|
||||
{
|
||||
id: "conn-1",
|
||||
name: "conn",
|
||||
db_type: "postgres",
|
||||
color: "",
|
||||
} as never,
|
||||
];
|
||||
|
||||
const fullSql = "SELECT first_value;\nSELECT selected_value;";
|
||||
const selectedSql = "SELECT selected_value;";
|
||||
const snapshot = {
|
||||
fullSql,
|
||||
selectedSql,
|
||||
cursorPos: fullSql.length,
|
||||
selectionFrom: fullSql.indexOf(selectedSql),
|
||||
selectionTo: fullSql.length,
|
||||
};
|
||||
let pendingSnapshot: typeof snapshot | undefined;
|
||||
let executedSql = "";
|
||||
const onExecutePointerDown = vi.fn(() => {
|
||||
pendingSnapshot = snapshot;
|
||||
});
|
||||
const onToolbarExecute = vi.fn((source: "pointer" | "keyboard") => {
|
||||
executedSql = source === "pointer" && pendingSnapshot ? resolveExecutableSql(pendingSnapshot.fullSql, pendingSnapshot.selectedSql, { mode: "current", cursorPos: pendingSnapshot.cursorPos }) : fullSql;
|
||||
});
|
||||
|
||||
const host = createHost();
|
||||
const app = createApp(EditorToolbar, {
|
||||
activeTab: {
|
||||
id: "tab-1",
|
||||
title: "SQL",
|
||||
connectionId: "conn-1",
|
||||
database: "db",
|
||||
sql: fullSql,
|
||||
mode: "query",
|
||||
isExecuting: false,
|
||||
isCancelling: false,
|
||||
isExplaining: false,
|
||||
},
|
||||
activeConnection: connectionStore.getConfig("conn-1"),
|
||||
executableSql: selectedSql,
|
||||
explainMode: "explain",
|
||||
blockDangerousRedisCommands: false,
|
||||
sqlKeywordCase: "preserve",
|
||||
databaseRequiredSignal: 0,
|
||||
autoCommit: true,
|
||||
txnSessionId: undefined,
|
||||
txnAutoRolledBack: false,
|
||||
oracleTxnPossiblyDirty: false,
|
||||
isOracleManualTransaction: false,
|
||||
onExecutePointerDown,
|
||||
onToolbarExecute,
|
||||
});
|
||||
app.use(pinia);
|
||||
app.use(i18n);
|
||||
app.mount(host);
|
||||
await nextTick();
|
||||
|
||||
const runButton = host.querySelectorAll("button")[0];
|
||||
runButton?.dispatchEvent(new MouseEvent("mousedown", { button: 0, bubbles: true, cancelable: true }));
|
||||
runButton?.dispatchEvent(new MouseEvent("click", { detail: 1, bubbles: true }));
|
||||
await nextTick();
|
||||
|
||||
expect(onExecutePointerDown).toHaveBeenCalledTimes(1);
|
||||
expect(onToolbarExecute).toHaveBeenCalledWith("pointer");
|
||||
expect(onExecutePointerDown.mock.invocationCallOrder[0]).toBeLessThan(onToolbarExecute.mock.invocationCallOrder[0]!);
|
||||
expect(executedSql).toBe(selectedSql);
|
||||
|
||||
app.unmount();
|
||||
host.remove();
|
||||
});
|
||||
|
||||
it("executes the exact CodeMirror selection after the toolbar click moves focus", async () => {
|
||||
const connectionStore = useConnectionStore();
|
||||
const connection = {
|
||||
id: "conn-1",
|
||||
name: "conn",
|
||||
db_type: "postgres",
|
||||
color: "",
|
||||
} as never;
|
||||
connectionStore.connections = [connection];
|
||||
|
||||
const fullSql = "SELECT first_value;\nSELECT selected_value;";
|
||||
const selectedSql = "SELECT selected_value;";
|
||||
const selectionFrom = fullSql.indexOf(selectedSql);
|
||||
const editorRef = ref<{ captureExecutionSnapshot?: () => SqlExecutionSnapshot | undefined } | null>(null);
|
||||
const state = reactive({ sql: fullSql });
|
||||
let pendingSnapshot: SqlExecutionSnapshot | undefined;
|
||||
let executedSql = "";
|
||||
|
||||
const host = createHost();
|
||||
const app = createApp({
|
||||
setup() {
|
||||
return () =>
|
||||
h("div", [
|
||||
h(QueryEditor, {
|
||||
ref: editorRef,
|
||||
modelValue: state.sql,
|
||||
tabId: "tab-1",
|
||||
connectionId: "conn-1",
|
||||
database: "db",
|
||||
databaseType: "postgres",
|
||||
dialect: "postgres",
|
||||
autoFocus: false,
|
||||
"onUpdate:modelValue": (value: string) => (state.sql = value),
|
||||
}),
|
||||
h(EditorToolbar, {
|
||||
activeTab: {
|
||||
id: "tab-1",
|
||||
title: "SQL",
|
||||
connectionId: "conn-1",
|
||||
database: "db",
|
||||
sql: fullSql,
|
||||
mode: "query",
|
||||
isExecuting: false,
|
||||
isCancelling: false,
|
||||
isExplaining: false,
|
||||
},
|
||||
activeConnection: connection,
|
||||
executableSql: fullSql,
|
||||
explainMode: "explain",
|
||||
blockDangerousRedisCommands: false,
|
||||
sqlKeywordCase: "preserve",
|
||||
databaseRequiredSignal: 0,
|
||||
autoCommit: true,
|
||||
txnSessionId: undefined,
|
||||
txnAutoRolledBack: false,
|
||||
oracleTxnPossiblyDirty: false,
|
||||
isOracleManualTransaction: false,
|
||||
onExecutePointerDown: () => {
|
||||
pendingSnapshot = editorRef.value?.captureExecutionSnapshot?.();
|
||||
},
|
||||
onToolbarExecute: (source: "pointer" | "keyboard") => {
|
||||
executedSql = source === "pointer" && pendingSnapshot ? resolveExecutableSql(pendingSnapshot.fullSql, pendingSnapshot.selectedSql, { mode: "current", cursorPos: pendingSnapshot.cursorPos }) : fullSql;
|
||||
},
|
||||
}),
|
||||
]);
|
||||
},
|
||||
});
|
||||
app.use(pinia);
|
||||
app.use(i18n);
|
||||
app.mount(host);
|
||||
await vi.waitFor(() => expect(host.querySelector(".cm-editor")).not.toBeNull(), { timeout: 5000 });
|
||||
|
||||
const view = EditorView.findFromDOM(host.querySelector(".cm-editor") as HTMLElement)!;
|
||||
view.dispatch({ selection: { anchor: selectionFrom, head: fullSql.length } });
|
||||
expect(view.state.sliceDoc(view.state.selection.main.from, view.state.selection.main.to)).toBe(selectedSql);
|
||||
|
||||
const runButton = host.querySelector<HTMLElement>(".app-editor-toolbar button");
|
||||
runButton?.dispatchEvent(new MouseEvent("mousedown", { button: 0, bubbles: true, cancelable: true }));
|
||||
runButton?.dispatchEvent(new MouseEvent("click", { detail: 1, bubbles: true }));
|
||||
await nextTick();
|
||||
|
||||
expect(pendingSnapshot?.selectedSql).toBe(selectedSql);
|
||||
expect(executedSql).toBe(selectedSql);
|
||||
|
||||
app.unmount();
|
||||
host.remove();
|
||||
});
|
||||
|
||||
it("does not expose SQL formatting for Redis connections", async () => {
|
||||
const connectionStore = useConnectionStore();
|
||||
connectionStore.connections = [
|
||||
|
||||
+52
@@ -21,6 +21,7 @@ const groupHandleModRCalls = vi.hoisted(() => [] as Element[]);
|
||||
const groupFocusSearchCalls = vi.hoisted(() => [] as Array<Element | null>);
|
||||
const resultHandleModRCalls = vi.hoisted(() => [] as Element[]);
|
||||
const resultFocusSearchCalls = vi.hoisted(() => [] as boolean[]);
|
||||
const groupExecutionCalls = vi.hoisted(() => ({ capture: [] as string[], request: [] as string[] }));
|
||||
|
||||
vi.mock("@/components/layout/EditorGroup.vue", () => ({
|
||||
default: {
|
||||
@@ -44,6 +45,14 @@ vi.mock("@/components/layout/EditorGroup.vue", () => ({
|
||||
groupFocusSearchCalls.push(target ?? null);
|
||||
return true;
|
||||
},
|
||||
captureQueryEditorExecutionSnapshot(this: { groupId: string }) {
|
||||
groupExecutionCalls.capture.push(this.groupId);
|
||||
return { fullSql: `SELECT ${this.groupId}`, selectedSql: `SELECT ${this.groupId}`, cursorPos: 0, selectionFrom: 0, selectionTo: 13 };
|
||||
},
|
||||
requestQueryEditorExecute(this: { groupId: string }) {
|
||||
groupExecutionCalls.request.push(this.groupId);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
template: `<div data-test="editor-group" :data-group-id="groupId" :data-tab-ids="tabIds.join(',')" :data-active-tab-id="activeTabId"><button data-test="emit-execute" @click="$emit('execute', { fullSql: 'SELECT 1', selectedSql: 'SELECT 1', cursorPos: 0, selectionFrom: 0, selectionTo: 8 })">execute</button></div>`,
|
||||
},
|
||||
@@ -149,6 +158,8 @@ describe("SqlEditorWorkspace mount contract", () => {
|
||||
groupFocusSearchCalls.length = 0;
|
||||
resultHandleModRCalls.length = 0;
|
||||
resultFocusSearchCalls.length = 0;
|
||||
groupExecutionCalls.capture.length = 0;
|
||||
groupExecutionCalls.request.length = 0;
|
||||
pinia = createPinia();
|
||||
setActivePinia(pinia);
|
||||
i18n = createI18n({
|
||||
@@ -158,6 +169,47 @@ describe("SqlEditorWorkspace mount contract", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("routes toolbar execution to the requested tab's editor group", async () => {
|
||||
const store = useQueryStore();
|
||||
const tabs = [tab("tab-a"), tab("tab-b")];
|
||||
store.tabs = tabs;
|
||||
store.activeTabId = "tab-a";
|
||||
store.groups = [
|
||||
{ id: "g1", tabIds: ["tab-a"], activeTabId: "tab-a" },
|
||||
{ id: "g2", tabIds: ["tab-b"], activeTabId: "tab-b" },
|
||||
];
|
||||
store.focusedGroupId = "g1";
|
||||
store.sizes = [50, 50];
|
||||
const host = createHost();
|
||||
const app = createApp(SqlEditorWorkspace, {
|
||||
activeTab: tabs[0]!,
|
||||
activeConnection: undefined,
|
||||
executableSql: "SELECT 1",
|
||||
activeOutputView: "result",
|
||||
formatSqlRequest: null,
|
||||
compressSqlRequest: null,
|
||||
selectedSql: "",
|
||||
cursorPos: 0,
|
||||
blockDangerousRedisCommands: false,
|
||||
});
|
||||
app.use(pinia);
|
||||
app.use(i18n);
|
||||
const vm = app.mount(host) as unknown as {
|
||||
captureQueryEditorExecutionSnapshot: (tabId?: string) => { selectedSql: string } | undefined;
|
||||
requestQueryEditorExecute: (tabId?: string) => boolean;
|
||||
};
|
||||
await nextTick();
|
||||
|
||||
const snapshot = vm.captureQueryEditorExecutionSnapshot("tab-b");
|
||||
expect(snapshot?.selectedSql).toBe("SELECT g2");
|
||||
expect(groupExecutionCalls.capture).toEqual(["g2"]);
|
||||
expect(vm.requestQueryEditorExecute("tab-b")).toBe(true);
|
||||
expect(groupExecutionCalls.request).toEqual(["g2"]);
|
||||
|
||||
app.unmount();
|
||||
host.remove();
|
||||
});
|
||||
|
||||
it("gives an idle query the full editor pane without a result surface or re-show button", async () => {
|
||||
const { host, vm, cleanup } = mountOutputWorkspace([{ ...tab("idle"), result: undefined }]);
|
||||
await nextTick();
|
||||
|
||||
@@ -26,8 +26,8 @@ export interface EditorToolbarActions {
|
||||
/** Highlights the database selector of the tab that needs a database choice. */
|
||||
databaseRequiredSignalFor(tabId: string): number;
|
||||
/** Captures the acting editor's execution snapshot before a toolbar click. */
|
||||
captureExecutionSnapshot(): void;
|
||||
toolbarExecute(source: "pointer" | "keyboard"): void;
|
||||
captureExecutionSnapshot(tabId: string): void;
|
||||
toolbarExecute(source: "pointer" | "keyboard", tabId: string): void;
|
||||
cancelExecution(tabId: string): void;
|
||||
explain(tabId: string): void;
|
||||
formatSql(tabId: string): void;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useProductionSafetyStore } from "@/stores/productionSafetyStore";
|
||||
import { createQueryEditorExecutionViewportOwnership } from "@/lib/editor/queryEditorExecutionViewport";
|
||||
import * as objectMetadataCache from "@/lib/metadata/objectMetadataCache";
|
||||
import type { ConnectionConfig, QueryTab } from "@/types/database";
|
||||
import type { SqlExecutionSnapshot } from "@/lib/sql/sqlExecutionTarget";
|
||||
|
||||
vi.mock("vue-i18n", () => ({
|
||||
createI18n: () => ({ global: { locale: { value: "en" }, setLocaleMessage: vi.fn() } }),
|
||||
@@ -248,6 +249,49 @@ describe("useSqlExecution", () => {
|
||||
expect(executeCurrentSql).toHaveBeenCalledWith(selectedSql, { tabId: "tab-1", sourceOffset: selectionFrom });
|
||||
});
|
||||
|
||||
it("keeps a toolbar snapshot bound to its owning tab when focus changes", async () => {
|
||||
const fullSql = "SELECT active_value;\nSELECT selected_value;";
|
||||
const selectedSql = "SELECT selected_value;";
|
||||
const selectionFrom = fullSql.indexOf(selectedSql);
|
||||
const activeTabValue = { ...queryTab("app"), id: "tab-a", sql: "SELECT active_value;" };
|
||||
const executionTabValue = { ...queryTab("app"), id: "tab-b", sql: fullSql };
|
||||
const activeTab = ref<QueryTab | undefined>(activeTabValue);
|
||||
const activeConnection = ref<ConnectionConfig | undefined>(connection("mysql"));
|
||||
const activeOutputView = ref<"result" | "summary" | "explain" | "chart">("result");
|
||||
const queryStore = useQueryStore();
|
||||
const connectionStore = useConnectionStore();
|
||||
connectionStore.connections = [connection("mysql")];
|
||||
queryStore.tabs = [activeTabValue, executionTabValue];
|
||||
queryStore.activeTabId = activeTabValue.id;
|
||||
const executeCurrentSql = vi.spyOn(queryStore, "executeCurrentSql").mockImplementation(async () => undefined);
|
||||
vi.spyOn(useHistoryStore(), "add").mockResolvedValue(undefined);
|
||||
const resolveExecutableSql = vi.fn(async (snapshot: SqlExecutionSnapshot | undefined, tab?: QueryTab) => {
|
||||
expect(tab?.id).toBe("tab-b");
|
||||
return snapshot?.selectedSql ?? "";
|
||||
});
|
||||
const execution = useSqlExecution({
|
||||
activeTab: computed(() => activeTab.value),
|
||||
activeConnection: computed(() => activeConnection.value),
|
||||
executableSql: computed(() => activeTab.value?.sql ?? ""),
|
||||
resolveExecutableSql,
|
||||
activeOutputView,
|
||||
});
|
||||
|
||||
await execution.tryExecute(
|
||||
{
|
||||
fullSql,
|
||||
selectedSql,
|
||||
cursorPos: selectionFrom,
|
||||
selectionFrom,
|
||||
selectionTo: fullSql.length,
|
||||
},
|
||||
{ tabId: executionTabValue.id },
|
||||
);
|
||||
|
||||
expect(resolveExecutableSql).toHaveBeenCalledWith(expect.objectContaining({ selectedSql }), executionTabValue);
|
||||
expect(executeCurrentSql).toHaveBeenCalledWith(selectedSql, { tabId: executionTabValue.id, sourceOffset: selectionFrom });
|
||||
});
|
||||
|
||||
it("expands preceding @set values in a selected shell-style statement", async () => {
|
||||
const selectedSql = "SELECT * FROM patrol WHERE post_id = ${postid};";
|
||||
const fullSql = ["-- saved query", "@set postid='224';", selectedSql, "@set postid = 'future';"].join("\n");
|
||||
|
||||
@@ -58,17 +58,20 @@ describe("QueryEditor execution routing", () => {
|
||||
expect(contentAreaSource).toContain("function captureQueryEditorExecutionSnapshot()");
|
||||
expect(contentAreaSource).toContain("queryEditorRef.value?.captureExecutionSnapshot();");
|
||||
expect(appSource).toContain("const pendingToolbarExecutionSnapshot = ref<SqlExecutionSnapshot & { tabId?: string }>();");
|
||||
expect(appSource).toContain("pendingToolbarExecutionSnapshot.value = snapshot ? { ...snapshot, tabId: activeTab.value?.id } : undefined;");
|
||||
expect(appSource).toContain('if (source === "pointer")');
|
||||
expect(appSource).toContain("contentAreaRef.value?.captureQueryEditorExecutionSnapshot?.(tabId)");
|
||||
expect(appSource).toContain("pendingToolbarExecutionSnapshot.value = snapshot ? { ...snapshot, tabId } : undefined;");
|
||||
expect(appSource).toContain('if (source === "pointer" && snapshot && snapshot.tabId === targetTabId)');
|
||||
expect(appSource).toContain("pendingToolbarExecutionSnapshot.value = undefined;");
|
||||
expect(appSource).toContain("void tryExecute(snapshot, { tabId: snapshot.tabId ?? activeTab.value?.id });");
|
||||
expect(appSource).toContain("void tryExecute(snapshot, { tabId: targetTabId });");
|
||||
expect(appSource).toContain('@execute="(tabId: string, override?: SqlExecutionOverride) => tryExecute(override, { tabId })"');
|
||||
expect(appSource).toContain("async function resolveActiveExecutableSql(snapshot?: SqlExecutionSnapshot, executionTab?: QueryTab)");
|
||||
expect(appSource).toContain("const connection = connectionStore.getConfig(tab.connectionId) ?? activeConnection.value;");
|
||||
// Per-group toolbars call back into App-owned orchestration via injection.
|
||||
expect(appSource).toContain("provide(EDITOR_TOOLBAR_ACTIONS, {");
|
||||
expect(appSource).toContain("captureExecutionSnapshot: captureActiveEditorExecutionSnapshot,");
|
||||
expect(appSource).toContain("toolbarExecute: requestActiveEditorExecute,");
|
||||
expect(editorGroupSource).toContain('@execute-pointer-down="toolbar.captureExecutionSnapshot()"');
|
||||
expect(editorGroupSource).toContain('@toolbar-execute="toolbar.toolbarExecute($event)"');
|
||||
expect(editorGroupSource).toContain('@execute-pointer-down="toolbar.captureExecutionSnapshot(activeTab.id)"');
|
||||
expect(editorGroupSource).toContain('@toolbar-execute="toolbar.toolbarExecute($event, activeTab.id)"');
|
||||
expect(editorToolbarSource).toContain("function onExecutePointerDown(event: MouseEvent)");
|
||||
expect(editorToolbarSource).toContain('emit("toolbarExecute", event.detail > 0 ? "pointer" : "keyboard")');
|
||||
expect(editorToolbarSource).not.toContain('emit("execute", event.detail > 0 ? "pointer" : "keyboard")');
|
||||
|
||||
Reference in New Issue
Block a user