feat(editor): add table structure peek and dblclick insert
This commit is contained in:
1 parent
da95bd435f
commit
12ed7a49b3
19 files changed
+1021
-8
No files matched your search
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import { EditorState } from "@codemirror/state";
|
||||
import { EditorView } from "@codemirror/view";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
STRUCTURE_PEEK_PANEL_SELECTOR,
|
||||
clearRememberedFocusedQueryEditorView,
|
||||
focusedQueryEditorView,
|
||||
queryEditorInsertContext,
|
||||
registerQueryEditorInsertContext,
|
||||
rememberFocusedQueryEditorView,
|
||||
shouldPreserveEditorFocusOnPeekPointerDown,
|
||||
unregisterQueryEditorInsertContext,
|
||||
} from "@/lib/editor/focusedQueryEditorView";
|
||||
|
||||
function mountEditor(): { root: HTMLElement; view: EditorView } {
|
||||
const root = document.createElement("div");
|
||||
root.setAttribute("data-query-editor-root", "");
|
||||
document.body.appendChild(root);
|
||||
const view = new EditorView({
|
||||
state: EditorState.create({ doc: "select 1" }),
|
||||
parent: root,
|
||||
});
|
||||
return { root, view };
|
||||
}
|
||||
|
||||
describe("shouldPreserveEditorFocusOnPeekPointerDown", () => {
|
||||
it("preserves focus for table / chrome clicks", () => {
|
||||
const td = document.createElement("td");
|
||||
expect(shouldPreserveEditorFocusOnPeekPointerDown(td)).toBe(true);
|
||||
});
|
||||
|
||||
it("allows native focus for editable controls", () => {
|
||||
const input = document.createElement("input");
|
||||
expect(shouldPreserveEditorFocusOnPeekPointerDown(input)).toBe(false);
|
||||
|
||||
const button = document.createElement("button");
|
||||
expect(shouldPreserveEditorFocusOnPeekPointerDown(button)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("focusedQueryEditorView", () => {
|
||||
afterEach(() => {
|
||||
clearRememberedFocusedQueryEditorView();
|
||||
document.body.replaceChildren();
|
||||
});
|
||||
|
||||
it("returns the focused query editor under activeElement", () => {
|
||||
const { root, view } = mountEditor();
|
||||
view.focus();
|
||||
expect(focusedQueryEditorView(view.contentDOM)).toBe(view);
|
||||
expect(focusedQueryEditorView(root)).toBe(view);
|
||||
});
|
||||
|
||||
it("falls back to the last focused editor when activeElement is inside a peek panel", () => {
|
||||
const { view } = mountEditor();
|
||||
rememberFocusedQueryEditorView(view);
|
||||
|
||||
const peek = document.createElement("div");
|
||||
peek.setAttribute("data-structure-peek-panel", "");
|
||||
const input = document.createElement("input");
|
||||
peek.appendChild(input);
|
||||
document.body.appendChild(peek);
|
||||
|
||||
expect(input.closest(STRUCTURE_PEEK_PANEL_SELECTOR)).toBe(peek);
|
||||
expect(focusedQueryEditorView(input)).toBe(view);
|
||||
});
|
||||
|
||||
it("returns null when focus is outside editors and peek panels", () => {
|
||||
const { view } = mountEditor();
|
||||
rememberFocusedQueryEditorView(view);
|
||||
|
||||
const sidebar = document.createElement("button");
|
||||
document.body.appendChild(sidebar);
|
||||
expect(focusedQueryEditorView(sidebar)).toBeNull();
|
||||
});
|
||||
|
||||
it("ignores a remembered editor that was disconnected", () => {
|
||||
const { root, view } = mountEditor();
|
||||
rememberFocusedQueryEditorView(view);
|
||||
root.remove();
|
||||
|
||||
const peek = document.createElement("div");
|
||||
peek.setAttribute("data-structure-peek-panel", "");
|
||||
document.body.appendChild(peek);
|
||||
expect(focusedQueryEditorView(peek)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("queryEditorInsertContext", () => {
|
||||
afterEach(() => {
|
||||
document.body.replaceChildren();
|
||||
});
|
||||
|
||||
it("stores and clears per-view insert context", () => {
|
||||
const { view } = mountEditor();
|
||||
registerQueryEditorInsertContext(view, { connectionId: "c1", database: "db", databaseType: "postgres" });
|
||||
expect(queryEditorInsertContext(view)).toEqual({ connectionId: "c1", database: "db", databaseType: "postgres" });
|
||||
unregisterQueryEditorInsertContext(view);
|
||||
expect(queryEditorInsertContext(view)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { STRUCTURE_PEEK_MIN_HEIGHT, STRUCTURE_PEEK_MIN_WIDTH, clampStructurePeekRect, resizeStructurePeekRect, structurePeekPanelId } from "@/lib/editor/structurePeekPanel";
|
||||
|
||||
describe("structurePeekPanel", () => {
|
||||
it("builds a stable case-insensitive identity including catalog", () => {
|
||||
expect(structurePeekPanelId("Conn", "Db", "Public", "Orders", "Cat")).toBe(structurePeekPanelId("conn", "db", "public", "orders", "cat"));
|
||||
expect(structurePeekPanelId("c", "db", "a", "t")).not.toBe(structurePeekPanelId("c", "db", "b", "t"));
|
||||
expect(structurePeekPanelId("c", "db", "s", "t", "cat-a")).not.toBe(structurePeekPanelId("c", "db", "s", "t", "cat-b"));
|
||||
});
|
||||
|
||||
it("clamps rects inside the viewport with a margin", () => {
|
||||
expect(clampStructurePeekRect({ left: -40, top: -20, width: 500, height: 400 }, 800, 600)).toEqual({
|
||||
left: 8,
|
||||
top: 8,
|
||||
width: 500,
|
||||
height: 400,
|
||||
});
|
||||
expect(clampStructurePeekRect({ left: 700, top: 500, width: 500, height: 400 }, 800, 600)).toEqual({
|
||||
left: 800 - 8 - 500,
|
||||
top: 600 - 8 - 400,
|
||||
width: 500,
|
||||
height: 400,
|
||||
});
|
||||
});
|
||||
|
||||
it("enforces minimum size while resizing from the west/north edges", () => {
|
||||
const origin = { left: 100, top: 100, width: 400, height: 300 };
|
||||
const shrunkWest = resizeStructurePeekRect(origin, "w", 200, 0, 1200, 800);
|
||||
expect(shrunkWest.width).toBe(STRUCTURE_PEEK_MIN_WIDTH);
|
||||
expect(shrunkWest.left).toBe(100 + 400 - STRUCTURE_PEEK_MIN_WIDTH);
|
||||
|
||||
const shrunkNorth = resizeStructurePeekRect(origin, "n", 0, 200, 1200, 800);
|
||||
expect(shrunkNorth.height).toBe(STRUCTURE_PEEK_MIN_HEIGHT);
|
||||
expect(shrunkNorth.top).toBe(100 + 300 - STRUCTURE_PEEK_MIN_HEIGHT);
|
||||
});
|
||||
|
||||
it("resizes northeast corners without treating n as containing e", () => {
|
||||
const origin = { left: 100, top: 100, width: 400, height: 300 };
|
||||
const grown = resizeStructurePeekRect(origin, "ne", 50, -40, 1200, 800);
|
||||
expect(grown.width).toBe(450);
|
||||
expect(grown.height).toBe(340);
|
||||
expect(grown.left).toBe(100);
|
||||
expect(grown.top).toBe(60);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user