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,113 @@
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { useState } from "react";
|
||||
import { SequenceForm } from "./SequenceForm";
|
||||
import type { DdlParams } from "../../../lib/objectCrud";
|
||||
|
||||
function StatefulSequenceForm({ initialParams }: { initialParams: DdlParams }) {
|
||||
const [params, setParams] = useState(initialParams);
|
||||
return <SequenceForm params={params} onChange={setParams} />;
|
||||
}
|
||||
|
||||
describe("SequenceForm", () => {
|
||||
it("renders create fields and emits params on change", () => {
|
||||
const onChange = vi.fn();
|
||||
render(
|
||||
<SequenceForm
|
||||
params={{
|
||||
schema: "public",
|
||||
name: "s",
|
||||
action: {
|
||||
op: "create",
|
||||
increment: "1",
|
||||
min_value: "1",
|
||||
max_value: "9",
|
||||
start: "1",
|
||||
cycle: false,
|
||||
},
|
||||
}}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByPlaceholderText("Sequence name")).toHaveValue("s");
|
||||
fireEvent.change(screen.getByPlaceholderText("Sequence name"), {
|
||||
target: { value: "s2" },
|
||||
});
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ name: "s2" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("renders a schema dropdown when schemas are provided", () => {
|
||||
const onChange = vi.fn();
|
||||
render(
|
||||
<SequenceForm
|
||||
params={{ schema: "public", name: "s", action: { op: "create" } }}
|
||||
schemas={["public", "utils"]}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
const select = screen.getByLabelText("Schema");
|
||||
expect(select).toBeInTheDocument();
|
||||
expect(screen.getByRole("option", { name: "public" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("option", { name: "utils" })).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(select, { target: { value: "utils" } });
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ schema: "utils" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("the editing outline wraps only the value area, not the label cell", () => {
|
||||
const onChange = vi.fn();
|
||||
render(
|
||||
<SequenceForm
|
||||
params={{ schema: "public", name: "s", action: { op: "create" } }}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// The children wrapper (direct parent of the input) carries the amber
|
||||
// focus-within editing outline, exactly like a grid editing cell.
|
||||
const input = screen.getByPlaceholderText("Sequence name");
|
||||
const valueArea = input.parentElement;
|
||||
expect(valueArea).not.toBeNull();
|
||||
expect(valueArea!.className).toContain("focus-within:outline");
|
||||
expect(valueArea!.className).toContain("focus-within:outline-amber-400");
|
||||
expect(valueArea!.className).toContain("focus-within:outline-offset-[-2px]");
|
||||
|
||||
// The label cell must stay clean: no ancestor of the label may carry
|
||||
// the editing outline (regression: the old row-level outline lit up the
|
||||
// whole row, label cell included).
|
||||
const label = screen.getByText("Name");
|
||||
expect(label.closest('[class*="focus-within:outline"]')).toBeNull();
|
||||
});
|
||||
|
||||
it("switching to restart shows only the with-field", () => {
|
||||
render(
|
||||
<StatefulSequenceForm
|
||||
initialParams={{
|
||||
schema: "public",
|
||||
name: "s",
|
||||
action: {
|
||||
op: "create",
|
||||
increment: "1",
|
||||
min_value: "1",
|
||||
max_value: "9",
|
||||
start: "1",
|
||||
cycle: false,
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const opSelect = screen.getByLabelText("Operation");
|
||||
fireEvent.change(opSelect, { target: { value: "restart" } });
|
||||
expect(screen.getByPlaceholderText("Restart with")).toBeInTheDocument();
|
||||
expect(screen.queryByPlaceholderText("Increment")).not.toBeInTheDocument();
|
||||
expect(screen.queryByPlaceholderText("Start")).not.toBeInTheDocument();
|
||||
expect(screen.queryByLabelText("CYCLE")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user