fix(grid): improve filter condition expansion
This commit is contained in:
parent
52d5c99a92
commit
1a6e00acbb
|
|
@ -1976,7 +1976,9 @@ async function initApp() {
|
|||
try {
|
||||
await settingsStore.initEditorSettings();
|
||||
console.log(`[STARTUP] settingsStore.initEditorSettings: ${(performance.now() - t0).toFixed(0)}ms`);
|
||||
await queryStore.initOpenTabs();
|
||||
await connectionStore.initFromDisk();
|
||||
console.log(`[STARTUP] connectionStore.initFromDisk: ${(performance.now() - t0).toFixed(0)}ms`);
|
||||
await queryStore.initOpenTabs({ validConnectionIds: connectionStore.connections.map((connection) => connection.id) });
|
||||
console.log(`[STARTUP] queryStore.initOpenTabs: ${(performance.now() - t0).toFixed(0)}ms`);
|
||||
await settingsStore.initDesktopSettings().catch(() => {});
|
||||
|
||||
|
|
@ -1991,8 +1993,6 @@ async function initApp() {
|
|||
toast(t("connection.loadFailed", { message: e?.message || String(e) }), 5000);
|
||||
});
|
||||
|
||||
await connectionStore.initFromDisk();
|
||||
console.log(`[STARTUP] connectionStore.initFromDisk: ${(performance.now() - t0).toFixed(0)}ms`);
|
||||
restoreActiveConnectionContext();
|
||||
} catch (e: any) {
|
||||
toast(t("connection.loadFailed", { message: e?.message || String(e) }), 5000);
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ function createTextProbe(input: HTMLTextAreaElement, wrap: boolean) {
|
|||
const probe = document.createElement(wrap ? "div" : "span");
|
||||
const style = window.getComputedStyle(input);
|
||||
probe.textContent = input.value || input.placeholder || "";
|
||||
probe.style.cssText = `position:fixed;left:-9999px;top:-9999px;visibility:hidden;box-sizing:border-box;${wrap ? `width:${input.clientWidth}px;white-space:pre-wrap;overflow-wrap:anywhere;padding:${style.paddingTop} ${style.paddingRight} ${style.paddingBottom} ${style.paddingLeft};` : "white-space:pre;"}font:${style.font};font-size:${style.fontSize};font-family:${style.fontFamily};font-weight:${style.fontWeight};line-height:${style.lineHeight};letter-spacing:${style.letterSpacing};`;
|
||||
probe.style.cssText = `position:fixed;left:-9999px;top:-9999px;visibility:hidden;box-sizing:border-box;${wrap ? `width:${input.clientWidth}px;white-space:pre-wrap;overflow-wrap:normal;padding:${style.paddingTop} ${style.paddingRight} ${style.paddingBottom} ${style.paddingLeft};` : "white-space:pre;"}font:${style.font};font-size:${style.fontSize};font-family:${style.fontFamily};font-weight:${style.fontWeight};line-height:${style.lineHeight};letter-spacing:${style.letterSpacing};`;
|
||||
document.body.appendChild(probe);
|
||||
return probe;
|
||||
}
|
||||
|
|
@ -142,7 +142,15 @@ function updateSuggestionPosition() {
|
|||
void nextTick(() => {
|
||||
const target = activeEditor.value;
|
||||
if (!target) return;
|
||||
suggestionPosition.value = getDataGridConditionSuggestionPosition(target.getBoundingClientRect(), {
|
||||
const targetRect = target.getBoundingClientRect();
|
||||
const suggestionAnchor = expanded.value
|
||||
? {
|
||||
left: targetRect.left,
|
||||
bottom: expandedRect.value.top + expandedHeight.value,
|
||||
width: targetRect.width,
|
||||
}
|
||||
: targetRect;
|
||||
suggestionPosition.value = getDataGridConditionSuggestionPosition(suggestionAnchor, {
|
||||
viewportWidth: window.innerWidth,
|
||||
preferredWidth: suggestionPreferredWidth.value,
|
||||
maxWidth: suggestionPreferredWidth.value === undefined ? undefined : 520,
|
||||
|
|
@ -160,7 +168,8 @@ function resizeEditor(forceExpand = false) {
|
|||
expandAfterComposition = true;
|
||||
return;
|
||||
}
|
||||
const focused = document.activeElement === input || document.activeElement === overlayRef.value;
|
||||
const overlayFocused = document.activeElement === overlayRef.value;
|
||||
const focused = document.activeElement === input || overlayFocused;
|
||||
const nextExpanded = focused && shouldExpand(input) && (forceExpand || expanded.value);
|
||||
if (nextExpanded) {
|
||||
expandedRect.value = measureExpandedRect(input);
|
||||
|
|
@ -177,6 +186,12 @@ function resizeEditor(forceExpand = false) {
|
|||
overlay.setSelectionRange(selectionStart.value, selectionEnd.value);
|
||||
});
|
||||
}
|
||||
if (!nextExpanded && overlayFocused && !composing.value) {
|
||||
void nextTick(() => {
|
||||
input.focus();
|
||||
input.setSelectionRange(selectionStart.value, selectionEnd.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -203,6 +218,36 @@ function focus(select = false) {
|
|||
resizeEditor(true);
|
||||
}
|
||||
|
||||
function scrollCaretIntoView() {
|
||||
const target = activeEditor.value;
|
||||
if (!target || target.scrollHeight <= target.clientHeight) return;
|
||||
const style = window.getComputedStyle(target);
|
||||
const probe = document.createElement("div");
|
||||
const caretMarker = document.createElement("span");
|
||||
const caret = Math.min(Math.max(selectionStart.value, 0), target.value.length);
|
||||
probe.textContent = target.value.slice(0, caret) || " ";
|
||||
caretMarker.textContent = "\u200b";
|
||||
probe.appendChild(caretMarker);
|
||||
probe.style.cssText = `position:fixed;left:-9999px;top:-9999px;visibility:hidden;box-sizing:border-box;width:${target.clientWidth}px;white-space:${style.whiteSpace};overflow-wrap:${style.overflowWrap};padding:${style.paddingTop} ${style.paddingRight} ${style.paddingBottom} ${style.paddingLeft};font:${style.font};font-size:${style.fontSize};font-family:${style.fontFamily};font-weight:${style.fontWeight};line-height:${style.lineHeight};letter-spacing:${style.letterSpacing};text-indent:${style.textIndent};`;
|
||||
document.body.appendChild(probe);
|
||||
const lineHeight = Number.parseFloat(style.lineHeight) || 24;
|
||||
const caretTop = caretMarker.offsetTop;
|
||||
const topPadding = Number.parseFloat(style.paddingTop) || 0;
|
||||
const bottomPadding = Number.parseFloat(style.paddingBottom) || 0;
|
||||
const visibleTop = target.scrollTop + topPadding;
|
||||
const visibleBottom = target.scrollTop + target.clientHeight - bottomPadding;
|
||||
if (caretTop < visibleTop) target.scrollTop = Math.max(0, caretTop - topPadding);
|
||||
else if (caretTop + lineHeight > visibleBottom) target.scrollTop = caretTop + lineHeight + bottomPadding - target.clientHeight;
|
||||
probe.remove();
|
||||
}
|
||||
|
||||
function focusAfterAccept() {
|
||||
void nextTick(() => {
|
||||
focus();
|
||||
void nextTick(scrollCaretIntoView);
|
||||
});
|
||||
}
|
||||
|
||||
function syncSelection(target: HTMLTextAreaElement) {
|
||||
selectionStart.value = target.selectionStart;
|
||||
selectionEnd.value = target.selectionEnd;
|
||||
|
|
@ -255,7 +300,7 @@ function onKeydown(event: KeyboardEvent) {
|
|||
if (completeQuote(event)) return;
|
||||
const action = editor.handleKeydown(event);
|
||||
if (action === "apply") void applyCondition();
|
||||
if (action === "accept") void nextTick(() => focus());
|
||||
if (action === "accept") focusAfterAccept();
|
||||
}
|
||||
|
||||
function completeQuote(event: KeyboardEvent) {
|
||||
|
|
@ -283,7 +328,7 @@ function openHistory() {
|
|||
|
||||
function acceptSuggestion(index: number) {
|
||||
editor.accept(index);
|
||||
void nextTick(() => focus());
|
||||
focusAfterAccept();
|
||||
}
|
||||
|
||||
function eventInside(event: Event, element?: HTMLElement) {
|
||||
|
|
@ -433,8 +478,8 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH
|
|||
@input="onInput"
|
||||
@keydown="onKeydown"
|
||||
/>
|
||||
<div class="data-grid-topbar-condition-floating-controls pointer-events-none absolute inset-x-2 z-[1] flex h-6 min-w-0 items-center gap-1">
|
||||
<span class="data-grid-topbar-condition-label" :class="[props.kind === 'where' ? 'data-grid-topbar-condition-label--where' : 'data-grid-topbar-condition-label--order', { 'data-grid-topbar-condition-label--compact': props.compact }]">
|
||||
<div class="data-grid-topbar-condition-floating-controls pointer-events-none absolute inset-x-2 z-[2] flex h-6 min-w-0 items-center gap-1">
|
||||
<span class="data-grid-topbar-condition-label data-grid-topbar-condition-label--floating" :class="[props.kind === 'where' ? 'data-grid-topbar-condition-label--where' : 'data-grid-topbar-condition-label--order', { 'data-grid-topbar-condition-label--compact': props.compact }]">
|
||||
{{ props.kind === "where" ? "WHERE" : "ORDER BY" }}
|
||||
</span>
|
||||
<div class="min-w-0 flex-1" />
|
||||
|
|
@ -515,6 +560,16 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH
|
|||
color: rgb(234 88 12);
|
||||
}
|
||||
|
||||
.data-grid-topbar-condition-label--floating {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
text-shadow:
|
||||
-1px 0 color-mix(in oklab, var(--background) 96%, var(--muted) 4%),
|
||||
1px 0 color-mix(in oklab, var(--background) 96%, var(--muted) 4%),
|
||||
0 -1px color-mix(in oklab, var(--background) 96%, var(--muted) 4%),
|
||||
0 1px color-mix(in oklab, var(--background) 96%, var(--muted) 4%);
|
||||
}
|
||||
|
||||
:global(.dark) .data-grid-topbar-condition-label--where {
|
||||
color: rgb(96 165 250);
|
||||
}
|
||||
|
|
@ -523,6 +578,14 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH
|
|||
color: rgb(251 146 60);
|
||||
}
|
||||
|
||||
:global(.dark) .data-grid-topbar-condition-label--floating {
|
||||
text-shadow:
|
||||
-1px 0 rgb(24, 24, 27),
|
||||
1px 0 rgb(24, 24, 27),
|
||||
0 -1px rgb(24, 24, 27),
|
||||
0 1px rgb(24, 24, 27);
|
||||
}
|
||||
|
||||
.data-grid-topbar-condition-label--compact {
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
|
|
@ -586,9 +649,7 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH
|
|||
box-shadow:
|
||||
inset 0 -1px 0 var(--border),
|
||||
0 8px 16px rgb(15 23 42 / 8%);
|
||||
transition:
|
||||
height 150ms ease,
|
||||
box-shadow 150ms ease;
|
||||
transition: box-shadow 150ms ease;
|
||||
--data-grid-expanded-scrollbar-offset: 8px;
|
||||
--data-grid-condition-controls-top: 0.125rem;
|
||||
--data-grid-condition-input-top: 0.125rem;
|
||||
|
|
@ -620,7 +681,7 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH
|
|||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
overflow-wrap: normal;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// @vitest-environment happy-dom
|
||||
|
||||
import { createApp, defineComponent, h, nextTick, ref, type App } from "vue";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import DataGridConditionEditor from "@/components/grid/DataGridConditionEditor.vue";
|
||||
import type { DataGridConditionHistoryKind } from "@/lib/dataGrid/dataGridConditionHistory";
|
||||
|
|
@ -99,4 +101,44 @@ describe("DataGridConditionEditor quote completion", () => {
|
|||
expect(input.selectionStart).toBe(20);
|
||||
expect(input.selectionEnd).toBe(20);
|
||||
});
|
||||
|
||||
it("keeps expanded input first-line indent without forced word breaks", () => {
|
||||
const source = readFileSync(resolve(process.cwd(), "apps/desktop/src/components/grid/DataGridConditionEditor.vue"), "utf8");
|
||||
const expandedInputCss = source.match(/\.data-grid-topbar-condition-input--expanded\s*\{(?<body>[\s\S]*?)\n\}/)?.groups?.body;
|
||||
|
||||
expect(expandedInputCss).toContain("padding:");
|
||||
expect(expandedInputCss).toContain("text-indent: var(--data-grid-condition-prefix-indent)");
|
||||
expect(expandedInputCss).toContain("overflow-wrap: normal");
|
||||
expect(source).toContain("white-space:pre-wrap;overflow-wrap:normal;");
|
||||
});
|
||||
|
||||
it("keeps the expanded condition label readable over wrapped content", () => {
|
||||
const source = readFileSync(resolve(process.cwd(), "apps/desktop/src/components/grid/DataGridConditionEditor.vue"), "utf8");
|
||||
const floatingControls = source.match(/data-grid-topbar-condition-floating-controls[^"]*/)?.[0];
|
||||
const floatingLabelCss = source.match(/\.data-grid-topbar-condition-label--floating\s*\{(?<body>[\s\S]*?)\n\}/)?.groups?.body;
|
||||
|
||||
expect(floatingControls).toContain("z-[2]");
|
||||
expect(source).toContain("data-grid-topbar-condition-label--floating");
|
||||
expect(floatingLabelCss).toContain("text-shadow:");
|
||||
expect(floatingLabelCss).not.toContain("padding-right:");
|
||||
expect(floatingLabelCss).not.toContain("box-shadow:");
|
||||
});
|
||||
|
||||
it("scrolls the caret into view after accepting a long completion", () => {
|
||||
const source = readFileSync(resolve(process.cwd(), "apps/desktop/src/components/grid/DataGridConditionEditor.vue"), "utf8");
|
||||
|
||||
expect(source).toContain("function scrollCaretIntoView()");
|
||||
expect(source).toContain("function focusAfterAccept()");
|
||||
expect(source).toContain("void nextTick(scrollCaretIntoView)");
|
||||
expect(source).toContain('if (action === "accept") focusAfterAccept()');
|
||||
});
|
||||
|
||||
it("positions suggestions below the measured expanded editor height", () => {
|
||||
const source = readFileSync(resolve(process.cwd(), "apps/desktop/src/components/grid/DataGridConditionEditor.vue"), "utf8");
|
||||
const expandedPaneCss = source.match(/\.data-grid-topbar-condition-pane--expanded\s*\{(?<body>[\s\S]*?)\n\}/)?.groups?.body;
|
||||
|
||||
expect(source).toContain("bottom: expandedRect.value.top + expandedHeight.value");
|
||||
expect(expandedPaneCss).toContain("transition: box-shadow 150ms ease");
|
||||
expect(expandedPaneCss).not.toContain("height 150ms");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ export interface RestoredOpenTabs {
|
|||
}
|
||||
|
||||
export type OpenTabsRestoreFilter = "all" | "pinned";
|
||||
export interface OpenTabsRestoreOptions {
|
||||
queryOnly?: boolean;
|
||||
filter?: OpenTabsRestoreFilter;
|
||||
validConnectionIds?: Iterable<string>;
|
||||
}
|
||||
|
||||
function shouldPersistTabSql(tab: QueryTab) {
|
||||
if (!tab.savedSqlId) return true;
|
||||
|
|
@ -143,14 +148,17 @@ function isSavedOpenTab(value: unknown): value is SavedOpenTab {
|
|||
return typeof tab.id === "string" && typeof tab.title === "string" && typeof tab.connectionId === "string" && typeof tab.database === "string" && (typeof tab.sql === "string" || typeof tab.savedSqlId === "string");
|
||||
}
|
||||
|
||||
function restoreOpenTabsArray(parsed: unknown, rawActiveTabId: string | null, options: { queryOnly?: boolean; filter?: OpenTabsRestoreFilter } = {}): RestoredOpenTabs {
|
||||
function restoreOpenTabsArray(parsed: unknown, rawActiveTabId: string | null, options: OpenTabsRestoreOptions = {}): RestoredOpenTabs {
|
||||
if (!Array.isArray(parsed)) return { tabs: [], activeTabId: null };
|
||||
|
||||
try {
|
||||
const validConnectionIds = options.validConnectionIds ? new Set(options.validConnectionIds) : undefined;
|
||||
const saved = parsed.filter(isSavedOpenTab);
|
||||
const filtered = saved.filter((tab) => {
|
||||
if (options.queryOnly && (tab.mode ?? "query") !== "query") return false;
|
||||
const mode = tab.mode ?? "query";
|
||||
if (options.queryOnly && mode !== "query") return false;
|
||||
if (options.filter === "pinned" && !tab.pinned) return false;
|
||||
if (mode !== "query" && validConnectionIds && !validConnectionIds.has(tab.connectionId)) return false;
|
||||
return true;
|
||||
});
|
||||
const tabs: QueryTab[] = filtered.map((tab) => {
|
||||
|
|
@ -194,12 +202,12 @@ function restoreOpenTabsArray(parsed: unknown, rawActiveTabId: string | null, op
|
|||
}
|
||||
}
|
||||
|
||||
export function restoreOpenTabsPayload(payload: { tabs?: unknown; activeTabId?: unknown } | null | undefined, options: { queryOnly?: boolean; filter?: OpenTabsRestoreFilter } = {}): RestoredOpenTabs {
|
||||
export function restoreOpenTabsPayload(payload: { tabs?: unknown; activeTabId?: unknown } | null | undefined, options: OpenTabsRestoreOptions = {}): RestoredOpenTabs {
|
||||
if (!payload) return { tabs: [], activeTabId: null };
|
||||
return restoreOpenTabsArray(payload.tabs, typeof payload.activeTabId === "string" ? payload.activeTabId : null, options);
|
||||
}
|
||||
|
||||
export function restoreOpenTabsState(rawTabs: string | null, rawActiveTabId: string | null, options: { queryOnly?: boolean; filter?: OpenTabsRestoreFilter } = {}): RestoredOpenTabs {
|
||||
export function restoreOpenTabsState(rawTabs: string | null, rawActiveTabId: string | null, options: OpenTabsRestoreOptions = {}): RestoredOpenTabs {
|
||||
if (!rawTabs) return { tabs: [], activeTabId: null };
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export const DATA_GRID_COMPACT_TOPBAR_WIDTH = 900;
|
||||
export const DATA_GRID_COMPACT_TOPBAR_WIDTH = 1050;
|
||||
|
||||
export type DataGridReloadIntent = "refresh";
|
||||
|
||||
|
|
|
|||
|
|
@ -448,20 +448,22 @@ function clearLegacySavedTabs() {
|
|||
safeLocalStorageRemove(ACTIVE_TAB_STORAGE_KEY);
|
||||
}
|
||||
|
||||
function restoreSavedTabsFromPayload(payload: { tabs?: unknown; activeTabId?: unknown } | null | undefined): { tabs: QueryTab[]; activeTabId: string | null } {
|
||||
function restoreSavedTabsFromPayload(payload: { tabs?: unknown; activeTabId?: unknown } | null | undefined, options: { validConnectionIds?: Iterable<string> } = {}): { tabs: QueryTab[]; activeTabId: string | null } {
|
||||
const restoreMode = useSettingsStore().editorSettings.openTabsRestoreMode;
|
||||
if (restoreMode === "none") return { tabs: [], activeTabId: null };
|
||||
return restoreOpenTabsPayload(payload, {
|
||||
filter: restoreMode === "pinned" ? "pinned" : "all",
|
||||
validConnectionIds: options.validConnectionIds,
|
||||
});
|
||||
}
|
||||
|
||||
function restoreLegacySavedTabs(): { tabs: QueryTab[]; activeTabId: string | null } {
|
||||
function restoreLegacySavedTabs(options: { validConnectionIds?: Iterable<string> } = {}): { tabs: QueryTab[]; activeTabId: string | null } {
|
||||
const restoreMode = useSettingsStore().editorSettings.openTabsRestoreMode;
|
||||
if (restoreMode === "none") return { tabs: [], activeTabId: null };
|
||||
const legacy = loadLegacySavedTabs();
|
||||
return restoreOpenTabsState(legacy.rawTabs, legacy.rawActiveTabId, {
|
||||
filter: restoreMode === "pinned" ? "pinned" : "all",
|
||||
validConnectionIds: options.validConnectionIds,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1043,11 +1045,11 @@ export const useQueryStore = defineStore("query", () => {
|
|||
else setTimeout(maintain, 0);
|
||||
}
|
||||
|
||||
async function initOpenTabs() {
|
||||
async function initOpenTabs(options: { validConnectionIds?: Iterable<string> } = {}) {
|
||||
if (isOpenTabsLoaded.value) return;
|
||||
const saved = await api.loadOpenTabsState().catch(() => null);
|
||||
if (saved?.tabs && Array.isArray(saved.tabs)) {
|
||||
const restored = restoreSavedTabsFromPayload(saved);
|
||||
const restored = restoreSavedTabsFromPayload(saved, options);
|
||||
applyRestoredOpenTabs(restored);
|
||||
if (useSettingsStore().editorSettings.openTabsRestoreMode === "none") {
|
||||
// Restore is explicitly disabled, so stale saved payloads should not
|
||||
|
|
@ -1062,7 +1064,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
|
||||
const legacy = loadLegacySavedTabs();
|
||||
if (legacy.rawTabs || legacy.rawActiveTabId) {
|
||||
const restored = restoreLegacySavedTabs();
|
||||
const restored = restoreLegacySavedTabs(options);
|
||||
applyRestoredOpenTabs(restored);
|
||||
if (useSettingsStore().editorSettings.openTabsRestoreMode === "none") {
|
||||
// Restore is explicitly disabled, so keeping the legacy startup payload
|
||||
|
|
|
|||
|
|
@ -342,6 +342,49 @@ test("restores data and structure tabs with table state", () => {
|
|||
assert.equal(restored.tabs[1]?.structureTableName, "users");
|
||||
});
|
||||
|
||||
test("drops restored non-query tabs for missing connections but keeps query drafts", () => {
|
||||
const raw = serializeOpenTabs([
|
||||
queryTab({
|
||||
id: "query",
|
||||
title: "Draft",
|
||||
connectionId: "missing-conn",
|
||||
database: "app",
|
||||
sql: "select * from users",
|
||||
}),
|
||||
queryTab({
|
||||
id: "data",
|
||||
title: "users",
|
||||
connectionId: "missing-conn",
|
||||
database: "app",
|
||||
mode: "data",
|
||||
sql: "",
|
||||
tableMeta: {
|
||||
schema: "public",
|
||||
tableName: "users",
|
||||
columns: [],
|
||||
primaryKeys: [],
|
||||
},
|
||||
}),
|
||||
queryTab({
|
||||
id: "structure",
|
||||
title: "Edit users",
|
||||
connectionId: "missing-conn",
|
||||
database: "app",
|
||||
mode: "structure",
|
||||
sql: "",
|
||||
structureTableName: "users",
|
||||
}),
|
||||
]);
|
||||
|
||||
const restored = restoreOpenTabsState(JSON.stringify(raw), "data", { validConnectionIds: ["live-conn"] });
|
||||
|
||||
assert.deepEqual(
|
||||
restored.tabs.map((tab) => ({ id: tab.id, mode: tab.mode })),
|
||||
[{ id: "query", mode: "query" }],
|
||||
);
|
||||
assert.equal(restored.activeTabId, "query");
|
||||
});
|
||||
|
||||
test("restores MQ tabs with selected tenant context", () => {
|
||||
const raw = JSON.stringify([
|
||||
queryTab({
|
||||
|
|
|
|||
Loading…
Reference in New Issue