Files
openidea/src/features/database/table-editor.test.tsx
T
l0ng-aiandClaude Opus 5.5 425801be34 feat(database): submit and revert table edits from the keyboard (#1589)
The table editor's Submit and Revert were toolbar buttons only. They are now the db.submitChanges and db.revertChanges actions, enabled while a table editor has focus and pending edits, bound to ⌘↩ / Ctrl+Enter and ⌥⌘Z / Ctrl+Alt+Z in the IntelliJ keymaps (unbound in Eclipse, where ⌥⌘Z is Surround With). A text field keeps ⌘↩ for its own edit (Value panel, large-value dialog), and query consoles keep Execute.

Closes #1549

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 00:08:37 +08:00

129 lines
5.1 KiB
TypeScript

import { act, fireEvent, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { DbDriver, DbTablePage } from "@/platform/ipc";
import { getContext } from "@/platform/context";
import { TableEditor } from "./TableEditor";
import { activeTableEditor, installTableEditorFocus } from "./tableEditorFocus";
import { tableInput } from "./tableInput";
const backend = vi.hoisted(() => ({
driver: "clickhouse" as DbDriver,
page: vi.fn(),
applyEdits: vi.fn(),
}));
vi.mock("@/platform/ipc", async (importOriginal) => {
const actual = await importOriginal<typeof import("@/platform/ipc")>();
return { ...actual, commands: { ...actual.commands, dbTablePage: backend.page, dbApplyEdits: backend.applyEdits } };
});
vi.mock("./store", () => ({
sourceById: () => ({ driver: backend.driver, readOnly: false }),
withConnection: (_id: string, run: () => unknown) => run(),
openDialog: vi.fn(),
}));
vi.mock("@tanstack/react-virtual", () => ({
useVirtualizer: ({ count }: { count: number }) => ({
getTotalSize: () => count * 24,
getVirtualItems: () => Array.from({ length: count }, (_, index) => ({ index, start: index * 24, key: index })),
scrollToIndex: () => {},
}),
}));
const page: DbTablePage = {
result: {
statement: "select * from users",
columns: [
{ name: "id", typeName: "UInt64" },
{ name: "name", typeName: "String" },
],
rows: [["1", "Ann"]],
affected: null,
hasMore: false,
elapsedMs: 1,
executionMs: 1,
},
details: {
columns: [
{ name: "id", dataType: "UInt64", primaryKey: true, nullable: false, default: null },
{ name: "name", dataType: "String", primaryKey: false, nullable: false, default: null },
],
indexes: [],
foreignKeys: [],
triggers: [],
},
offset: 0,
};
beforeEach(() => {
backend.driver = "clickhouse";
backend.page.mockResolvedValue({ status: "ok", data: page });
backend.applyEdits.mockClear();
});
describe("table editor capabilities", () => {
it("keeps ClickHouse rows read-only even when metadata marks a primary key", async () => {
await act(async () => {
render(<TableEditor input={tableInput("source", "default", "users")} />);
});
const cell = await screen.findByRole("gridcell", { name: "Ann" });
fireEvent.doubleClick(cell);
expect(screen.queryByRole("textbox", { name: "Edit name" })).toBeNull();
expect(screen.queryByRole("button", { name: "Add Row" })).toBeNull();
expect(screen.queryByRole("button", { name: "Submit" })).toBeNull();
expect(screen.getByText("Use the query console to change ClickHouse data.")).toBeTruthy();
fireEvent.mouseDown(cell, { button: 0 });
fireEvent.paste(screen.getByRole("grid"), { clipboardData: { getData: () => "Changed" } });
expect(screen.getByRole("gridcell", { name: "Ann" })).toBeTruthy();
expect(backend.applyEdits).not.toHaveBeenCalled();
});
it("keeps keyed PostgreSQL rows editable", async () => {
backend.driver = "postgres";
await act(async () => {
render(<TableEditor input={tableInput("source", "public", "users")} />);
});
fireEvent.doubleClick(await screen.findByRole("gridcell", { name: "Ann" }));
expect(screen.getByRole("textbox", { name: "Edit name" })).toBeTruthy();
expect(screen.getByRole("button", { name: "Add Row" })).toBeTruthy();
expect(screen.getByRole("button", { name: "Submit" })).toBeTruthy();
});
it("offers Submit and Revert to the keyboard while the grid has focus and edits are pending (#1549)", async () => {
backend.driver = "postgres";
backend.applyEdits.mockResolvedValue({ status: "ok", data: 1 });
const uninstall = installTableEditorFocus();
try {
await act(async () => {
render(<TableEditor input={tableInput("source", "public", "users")} />);
});
const grid = screen.getByRole("grid");
act(() => grid.focus());
expect(getContext()).toMatchObject({ dbTableEditorFocus: true, dbTableEditorChanges: false });
fireEvent.doubleClick(await screen.findByRole("gridcell", { name: "Ann" }));
const input = screen.getByRole("textbox", { name: "Edit name" });
// A text field keeps ⌘↩ for its own edit.
expect(getContext().dbTableEditorFocus).toBe(false);
fireEvent.change(input, { target: { value: "Bob" } });
fireEvent.keyDown(input, { key: "Enter" });
act(() => grid.focus());
expect(getContext()).toMatchObject({ dbTableEditorFocus: true, dbTableEditorChanges: true });
act(() => activeTableEditor()!.revert());
expect(screen.getByRole("gridcell", { name: "Ann" })).toBeTruthy();
expect(getContext().dbTableEditorChanges).toBe(false);
fireEvent.doubleClick(screen.getByRole("gridcell", { name: "Ann" }));
fireEvent.change(screen.getByRole("textbox", { name: "Edit name" }), { target: { value: "Bob" } });
fireEvent.keyDown(screen.getByRole("textbox", { name: "Edit name" }), { key: "Enter" });
act(() => grid.focus());
await act(async () => activeTableEditor()!.submit());
expect(backend.applyEdits).toHaveBeenCalledTimes(1);
} finally {
uninstall();
}
});
});