Editor settings, SSH/SSL runtime, data import + table-menu loose ends (#7)

* feat: editor settings model + typed clamped defaults (Task 1)

* feat: carry SSH config + ssh_password to backend DbConfig (Task 2)

* feat: Change enum bulk/drop/empty + type-specific change payload builder (Task 3)

* feat: csv parser + shared export util (Task 4)

* feat: rustls TLS connector factory with modes + client auth (Task 5)

* feat: real SSH tunnel manager (testable backend) + pool eviction hook (Task 6)

* feat: table DDL fetch (sqlite + pg_dump arg builder) (Task 7)

* feat: keychain SSH secrets + connection delete purge + tunnel lifecycle (Task 8)

* feat: real SSH tunnel + TLS connect path for postgres/mysql (Task 9)

* feat: execute_change bulk/drop/empty + get_table_ddl command (Task 10)

* feat: fetch SSH secrets into dbConnect + save on connection form (Task 11)

* feat: Editor settings tab UI (Task 12)

* feat: QueryEditor applies editor settings live (Task 13)

* feat: ImportDialog with CSV/JSON preview + column mapping (Task 14)

* feat: table-menu export/empty/delete/import + queue labels + payload builder (Task 15)

* fix: error sanitization, encrypted-key guard, row-indexed import errors, caps (Task 16)

* docs: mark Editor Settings, SSH/SSL runtime, Data Import, table-menu loose ends shipped (Task 17)

* feat: auto-refresh schema tree after schema-modifying SQL (query + queue drop)

* fix: use theme-consistent red classes for danger menu items (text-error was undefined)

* feat: changes queue as tab-bar popover + amber pending border

* feat: redesign changes popover (visual/SQL toggle, cards, footer actions, Cmd+S)

* refactor: drop per-card status label from changes popover cards

* feat: green completion indicator on committed cards + auto-close tabs of dropped tables

* docs: changes queue popover UX + auto schema refresh statuses
This commit is contained in:
2026-08-02 20:38:23 +08:00
committed by GitHub
parent e32fe7967c
commit e0c0db8352
68 changed files with 3885 additions and 553 deletions
@@ -202,6 +202,62 @@ describe("DbViewerScreen", () => {
);
});
it("refreshes the schema tree after a DDL query runs", async () => {
vi.spyOn(commands, "executeQuery").mockResolvedValue(
mockQueryResult as any,
);
const getSchemas = vi
.spyOn(commands, "getSchemas")
.mockResolvedValue(["public"]);
vi.spyOn(commands, "getDatabases").mockResolvedValue(["mydb"]);
vi.spyOn(commands, "getTables").mockResolvedValue([] as any);
render(
<DbViewerScreen
connectionId="c1"
onHome={() => {}}
onSettings={() => {}}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /new query/i }));
const textarea = await waitFor(() =>
screen.getByTestId("monaco-textarea"),
);
fireEvent.change(textarea, {
target: { value: "CREATE TABLE users_new (id INTEGER)" },
});
fireEvent.click(screen.getByRole("button", { name: /run query/i }));
// CREATE is destructive -> confirm dialog appears -> click Execute
await waitFor(() =>
screen.getByRole("button", { name: /execute/i }),
);
fireEvent.click(screen.getByRole("button", { name: /execute/i }));
await waitFor(() => expect(getSchemas).toHaveBeenCalledWith("c1"));
});
it("does not refresh the schema tree after a SELECT", async () => {
vi.spyOn(commands, "executeQuery").mockResolvedValue(
mockQueryResult as any,
);
const getSchemas = vi
.spyOn(commands, "getSchemas")
.mockResolvedValue(["public"]);
render(
<DbViewerScreen
connectionId="c1"
onHome={() => {}}
onSettings={() => {}}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /new query/i }));
const textarea = await waitFor(() =>
screen.getByTestId("monaco-textarea"),
);
fireEvent.change(textarea, { target: { value: "SELECT 1" } });
fireEvent.click(screen.getByRole("button", { name: /run query/i }));
await waitFor(() => expect(commands.executeQuery).toHaveBeenCalled());
expect(getSchemas).not.toHaveBeenCalled();
});
it("formats the query SQL when Auto format is clicked", async () => {
render(
<DbViewerScreen