v0.7.6: PostgreSQL object management, keychain toggle, tabbed Objects workspace (#12)
* [P1-T1] Change::Ddl Rust variant + execute_change arm * [P1-T2] Frontend ddl change type + objectCrud capability * [P1-T3] use_keychain data model + migration v8 * [P2-T1] object_crud skeleton + validators + build_ddl dispatch * [P2-T2] Sequence builders * [P2-T3] Enum builders (no value removal) * [P2-T4] View / matview / extension builders * [P2-T5] Index + constraint builders * [P2-T6] Function / procedure + trigger builders * [P2-T7] build_object_ddl + get_available_extensions commands + wrappers * [P3-T1] conditional keychain + session passwords * [P3-T2] keychain-off password prompt on connect * [P3-T3] default-ON keychain opt-out + tooltip + modal conditional * [P3-T4] ddl queue card + after-commit refetch * [P4-T1] ObjectCrudDialog shell * [P4-T2] SequenceForm * [P4-T3] EnumForm with no-removal note * [P4-T4] ExtensionForm with available-extensions picker * [P4-T5] ViewForm (view + materialized view) * [P5-T1] IndexForm with column picker * [P5-T2] ConstraintForm (check/unique/pk/fk) + ColumnPicker * [P5-T3] FunctionForm (function + procedure) * [P5-T4] TriggerForm with trigger-function picker * [P5-T5] ObjectContextMenu + Explorer/TableOverflowMenu CRUD wiring * [P6-T1] object tab type + openObjectTab dedup * [P6-T2] extract ObjectDetail for object tabs * [P6-T3] Objects view two-pane sidebar + workspace * [P6-T4] object-tab content + per-type tab icons * [P7-T1] Version bump 0.7.5 -> 0.7.6 * [P7-T2] docs sync README/ROADMAP/AGENTS for v0.7.6 * [P7-T3] chore: Cargo.lock version sync 0.7.5 -> 0.7.6 * fix(ui): object tab icon stacks above name (preflight svg block) * fix(ui): optically center object tab icon with name * fix(ui): object tab icon matches query/table icon handling * [UI-POLISH-1] objectForm tab type + openFormTab store action * [UI-POLISH-2] ObjectFormTab + KindForm with Visual/SQL toggle * [UI-POLISH-3] route create/edit through form tabs; remove modal * docs: create/edit now open as form tabs (AGENTS sync) * [FB-1] follow app styling patterns + schema dropdown in forms * [FB-2] Monaco editor for function body + view definition * [FB-3] form tabs styled like viewers + in-cell editing * [FB-5] focus outline scoped to input area (label excluded) * [FB-6] no amber focus outline on Monaco body/definition rows * [FB-7] header dedupe + schema default + full edit prefill * [FB-8] SQL view in read-only Monaco editor * docs: roadmap — table create/edit + relationships (next) * docs: roadmap — Admin follow-up is 0.7.7 (next after 0.7.6)
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||
import { ObjectContextMenu } from "./ObjectContextMenu";
|
||||
import * as objectCrud from "../../../lib/objectCrud";
|
||||
import { useDbViewerStore } from "../../../stores/dbViewerStore";
|
||||
|
||||
vi.mock("../../../lib/objectCrud", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../../lib/objectCrud")>();
|
||||
return {
|
||||
...actual,
|
||||
buildObjectDdl: vi.fn(),
|
||||
getAvailableExtensions: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("ObjectContextMenu", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
useDbViewerStore.getState().reset();
|
||||
});
|
||||
|
||||
it("Edit on a sequence opens an objectForm tab with prefilled edit params", async () => {
|
||||
render(
|
||||
<ObjectContextMenu
|
||||
connectionId="c1"
|
||||
objectType="sequence"
|
||||
item={{ schema: "public", name: "s" }}
|
||||
onRefresh={() => {}}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /actions/i }));
|
||||
fireEvent.click(screen.getByText("Edit…"));
|
||||
const st = useDbViewerStore.getState();
|
||||
expect(st.tabs).toHaveLength(1);
|
||||
expect(st.tabs[0].tabType).toBe("objectForm");
|
||||
expect(st.tabs[0].form?.mode).toBe("edit");
|
||||
expect(st.tabs[0].form?.kind).toBe("sequence");
|
||||
expect(st.tabs[0].form?.params?.name).toBe("s");
|
||||
expect(st.tabs[0].form?.params?.schema).toBe("public");
|
||||
});
|
||||
|
||||
it("Create on a sequence opens an objectForm tab in create mode", async () => {
|
||||
render(
|
||||
<ObjectContextMenu
|
||||
connectionId="c1"
|
||||
objectType="sequence"
|
||||
item={{ schema: "public", name: "s" }}
|
||||
onRefresh={() => {}}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /actions/i }));
|
||||
fireEvent.click(screen.getByText("Create…"));
|
||||
const st = useDbViewerStore.getState();
|
||||
expect(st.tabs).toHaveLength(1);
|
||||
expect(st.tabs[0].tabType).toBe("objectForm");
|
||||
expect(st.tabs[0].form?.mode).toBe("create");
|
||||
expect(st.tabs[0].form?.kind).toBe("sequence");
|
||||
expect(st.tabs[0].form?.params?.name).toBe("");
|
||||
});
|
||||
|
||||
it("Drop fetches dependencies then stages the drop", async () => {
|
||||
vi.mocked(objectCrud.buildObjectDdl).mockResolvedValue([
|
||||
'DROP SEQUENCE "public"."s"',
|
||||
]);
|
||||
const addChange = vi.spyOn(useDbViewerStore.getState(), "addChange");
|
||||
render(
|
||||
<ObjectContextMenu
|
||||
connectionId="c1"
|
||||
objectType="sequence"
|
||||
item={{ schema: "public", name: "s" }}
|
||||
onRefresh={() => {}}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /actions/i }));
|
||||
fireEvent.click(screen.getByText("Drop…"));
|
||||
// DependencyDialog with no deps → Proceed is enabled
|
||||
expect(
|
||||
await screen.findByRole("button", { name: /proceed/i }),
|
||||
).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: /proceed/i }));
|
||||
await waitFor(() =>
|
||||
expect(addChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "ddl",
|
||||
sql: 'DROP SEQUENCE "public"."s"',
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user