refactor: convert to bun-workspaces monorepo (desktop/ + www/)
- Move the Tauri app (React frontend + Rust backend) into desktop/ via git mv — configs unchanged (relative paths: tauri.conf.json, vite.config.ts) - Root package.json becomes a private bun workspace container with orchestration scripts; desktop package renamed gridline-desktop - Scaffold www/ with Astro 7 + Tailwind v4 (hand-wired vite plugin, static output, ready for Dokploy) - Update release.yml for the new layout: projectPath: desktop, desktop/src-tauri resource paths, rust-cache workspace path - Update README + AGENTS.md structure trees and dev commands - Verified: vitest (1074), cargo test (354), desktop build, tauri dev (window launches), tauri build (dmg + app bundles), Astro build
This commit is contained in:
@@ -0,0 +1,491 @@
|
||||
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||
|
||||
vi.mock("@tauri-apps/api/core", () => ({
|
||||
invoke: vi.fn(),
|
||||
}));
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import {
|
||||
buildObjectDdl,
|
||||
dropCrudParams,
|
||||
getAvailableExtensions,
|
||||
initialCrudParams,
|
||||
} from "./objectCrud";
|
||||
import type { CrudItem } from "./objectCrud";
|
||||
|
||||
describe("buildObjectDdl", () => {
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
it("invokes build_object_ddl with connectionId, kind, params", async () => {
|
||||
vi.mocked(invoke).mockResolvedValueOnce([
|
||||
"CREATE TYPE public.role AS ENUM ('admin')",
|
||||
]);
|
||||
const sql = await buildObjectDdl("c1", "enum", {
|
||||
schema: "public",
|
||||
name: "role",
|
||||
action: { op: "create", labels: ["admin"] },
|
||||
});
|
||||
expect(invoke).toHaveBeenCalledWith("build_object_ddl", {
|
||||
connectionId: "c1",
|
||||
kind: "enum",
|
||||
params: {
|
||||
schema: "public",
|
||||
name: "role",
|
||||
action: { op: "create", labels: ["admin"] },
|
||||
},
|
||||
});
|
||||
expect(sql).toEqual(["CREATE TYPE public.role AS ENUM ('admin')"]);
|
||||
});
|
||||
|
||||
it("passes through drop params for sequences", async () => {
|
||||
vi.mocked(invoke).mockResolvedValueOnce(['DROP SEQUENCE "public"."s"']);
|
||||
const sql = await buildObjectDdl("c1", "sequence", {
|
||||
schema: "public",
|
||||
name: "s",
|
||||
action: { op: "drop" },
|
||||
});
|
||||
expect(invoke).toHaveBeenCalledWith("build_object_ddl", {
|
||||
connectionId: "c1",
|
||||
kind: "sequence",
|
||||
params: { schema: "public", name: "s", action: { op: "drop" } },
|
||||
});
|
||||
expect(sql).toEqual(['DROP SEQUENCE "public"."s"']);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAvailableExtensions", () => {
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
it("invokes get_available_extensions with connectionId", async () => {
|
||||
vi.mocked(invoke).mockResolvedValueOnce([
|
||||
{ name: "pgcrypto", version: "1.3", comment: null },
|
||||
]);
|
||||
const exts = await getAvailableExtensions("c1");
|
||||
expect(invoke).toHaveBeenCalledWith("get_available_extensions", {
|
||||
connectionId: "c1",
|
||||
});
|
||||
expect(exts).toEqual([{ name: "pgcrypto", version: "1.3", comment: null }]);
|
||||
});
|
||||
|
||||
it("returns empty array when no extensions available", async () => {
|
||||
vi.mocked(invoke).mockResolvedValueOnce([]);
|
||||
const exts = await getAvailableExtensions("c2");
|
||||
expect(exts).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("initialCrudParams", () => {
|
||||
it("create shape starts empty with sensible defaults", () => {
|
||||
const p = initialCrudParams(
|
||||
"sequence",
|
||||
{ schema: "public", name: "" },
|
||||
"create",
|
||||
);
|
||||
expect(p).toEqual({
|
||||
schema: "public",
|
||||
name: "",
|
||||
action: {
|
||||
op: "create",
|
||||
increment: "1",
|
||||
min_value: "1",
|
||||
max_value: "9223372036854775807",
|
||||
start: "1",
|
||||
cycle: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("edit prefills the name from the item", () => {
|
||||
const p = initialCrudParams(
|
||||
"sequence",
|
||||
{ schema: "public", name: "s" },
|
||||
"edit",
|
||||
);
|
||||
expect(p).toMatchObject({
|
||||
schema: "public",
|
||||
name: "s",
|
||||
action: { op: "create", increment: "1" },
|
||||
});
|
||||
});
|
||||
|
||||
it("enum edit carries labels through", () => {
|
||||
const p = initialCrudParams(
|
||||
"enum",
|
||||
{ schema: "public", name: "role", labels: ["admin"] },
|
||||
"edit",
|
||||
);
|
||||
expect(p.action).toMatchObject({ op: "create", labels: ["admin"] });
|
||||
});
|
||||
|
||||
it("index create keeps the owning table and an empty column list", () => {
|
||||
const p = initialCrudParams(
|
||||
"index",
|
||||
{ schema: "public", table: "users", name: "" },
|
||||
"create",
|
||||
);
|
||||
expect(p).toEqual({
|
||||
schema: "public",
|
||||
table: "users",
|
||||
name: "",
|
||||
action: { op: "create", unique: false, method: "", columns: [], predicate: null },
|
||||
});
|
||||
});
|
||||
|
||||
it("constraint create starts as a CHECK on the owning table", () => {
|
||||
const p = initialCrudParams(
|
||||
"constraint",
|
||||
{ schema: "public", table: "users", name: "" },
|
||||
"create",
|
||||
);
|
||||
expect(p).toEqual({
|
||||
schema: "public",
|
||||
table: "users",
|
||||
name: "",
|
||||
action: { op: "check", expression: "" },
|
||||
});
|
||||
});
|
||||
|
||||
it("procedure edit prefills is_procedure from kind", () => {
|
||||
const p = initialCrudParams(
|
||||
"procedure",
|
||||
{ schema: "public", name: "do_thing" },
|
||||
"edit",
|
||||
);
|
||||
expect(p).toMatchObject({
|
||||
schema: "public",
|
||||
name: "do_thing",
|
||||
is_procedure: true,
|
||||
action: { op: "create_or_replace", args: [] },
|
||||
});
|
||||
});
|
||||
|
||||
it("view edit prefills the definition and marks non-materialized", () => {
|
||||
const p = initialCrudParams(
|
||||
"view",
|
||||
{ schema: "public", name: "v", definition: "SELECT 1" },
|
||||
"edit",
|
||||
);
|
||||
expect(p).toMatchObject({
|
||||
schema: "public",
|
||||
name: "v",
|
||||
materialized: false,
|
||||
action: { op: "create", definition: "SELECT 1" },
|
||||
});
|
||||
});
|
||||
|
||||
it("function edit prefills body, zipped args, return_type, and language", () => {
|
||||
const p = initialCrudParams(
|
||||
"function",
|
||||
{
|
||||
schema: "public",
|
||||
name: "add_one",
|
||||
return_type: "integer",
|
||||
argument_types: ["integer"],
|
||||
argument_names: ["x"],
|
||||
argument_modes: ["IN"],
|
||||
language: "plpgsql",
|
||||
source: "BEGIN RETURN x + 1; END",
|
||||
kind: "f",
|
||||
},
|
||||
"edit",
|
||||
);
|
||||
expect(p).toMatchObject({
|
||||
schema: "public",
|
||||
name: "add_one",
|
||||
is_procedure: false,
|
||||
action: {
|
||||
op: "create_or_replace",
|
||||
args: [{ mode: "IN", name: "x", type: "integer" }],
|
||||
return_type: "integer",
|
||||
language: "plpgsql",
|
||||
body: "BEGIN RETURN x + 1; END",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("procedure edit prefills source and zips multiple args", () => {
|
||||
const p = initialCrudParams(
|
||||
"procedure",
|
||||
{
|
||||
schema: "public",
|
||||
name: "do_thing",
|
||||
return_type: "void",
|
||||
argument_types: ["int", "text"],
|
||||
argument_names: ["a", "b"],
|
||||
argument_modes: ["IN", "OUT"],
|
||||
language: "plpgsql",
|
||||
source: "BEGIN PERFORM a; END",
|
||||
kind: "p",
|
||||
},
|
||||
"edit",
|
||||
);
|
||||
expect(p).toMatchObject({
|
||||
is_procedure: true,
|
||||
action: {
|
||||
op: "create_or_replace",
|
||||
args: [
|
||||
{ mode: "IN", name: "a", type: "int" },
|
||||
{ mode: "OUT", name: "b", type: "text" },
|
||||
],
|
||||
return_type: "void",
|
||||
body: "BEGIN PERFORM a; END",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("function create keeps empty args/body and the default language", () => {
|
||||
const p = initialCrudParams(
|
||||
"function",
|
||||
{ schema: "public", name: "add_one" },
|
||||
"create",
|
||||
);
|
||||
expect(p.action).toMatchObject({
|
||||
op: "create_or_replace",
|
||||
args: [],
|
||||
return_type: null,
|
||||
language: "plpgsql",
|
||||
body: "",
|
||||
});
|
||||
});
|
||||
|
||||
it("sequence edit prefills increment/min/max/start/cycle from the item", () => {
|
||||
const p = initialCrudParams(
|
||||
"sequence",
|
||||
{
|
||||
schema: "public",
|
||||
name: "s",
|
||||
start_value: "5",
|
||||
min_value: "1",
|
||||
max_value: "999",
|
||||
increment: "2",
|
||||
cycle: true,
|
||||
},
|
||||
"edit",
|
||||
);
|
||||
expect(p.action).toMatchObject({
|
||||
op: "create",
|
||||
increment: "2",
|
||||
min_value: "1",
|
||||
max_value: "999",
|
||||
start: "5",
|
||||
cycle: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("trigger edit prefills table, timing, events, and orientation", () => {
|
||||
const p = initialCrudParams(
|
||||
"trigger",
|
||||
{
|
||||
schema: "public",
|
||||
name: "trg",
|
||||
table_name: "users",
|
||||
event_manipulation: "INSERT",
|
||||
action_timing: "AFTER",
|
||||
action_orientation: "row",
|
||||
},
|
||||
"edit",
|
||||
);
|
||||
expect(p.action).toMatchObject({
|
||||
op: "create",
|
||||
table: "users",
|
||||
timing: "AFTER",
|
||||
events: ["INSERT"],
|
||||
orientation: "ROW",
|
||||
});
|
||||
});
|
||||
|
||||
it("trigger edit splits OR-joined event_manipulation into separate events", () => {
|
||||
const p = initialCrudParams(
|
||||
"trigger",
|
||||
{
|
||||
schema: "public",
|
||||
name: "trg",
|
||||
table_name: "orders",
|
||||
event_manipulation: "INSERT OR UPDATE",
|
||||
action_timing: "BEFORE",
|
||||
action_orientation: "STATEMENT",
|
||||
},
|
||||
"edit",
|
||||
);
|
||||
expect(p.action).toMatchObject({
|
||||
events: ["INSERT", "UPDATE"],
|
||||
orientation: "STATEMENT",
|
||||
});
|
||||
});
|
||||
|
||||
it("index edit prefills name, unique, method, and columns", () => {
|
||||
const p = initialCrudParams(
|
||||
"index",
|
||||
{
|
||||
schema: "public",
|
||||
table: "users",
|
||||
name: "idx_users_email",
|
||||
is_unique: true,
|
||||
method: "btree",
|
||||
columns: ["email"],
|
||||
},
|
||||
"edit",
|
||||
);
|
||||
expect(p).toMatchObject({
|
||||
schema: "public",
|
||||
table: "users",
|
||||
name: "idx_users_email",
|
||||
action: {
|
||||
op: "create",
|
||||
unique: true,
|
||||
method: "btree",
|
||||
columns: ["email"],
|
||||
predicate: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("constraint edit maps CHECK contype to a check action with the definition", () => {
|
||||
const p = initialCrudParams(
|
||||
"constraint",
|
||||
{
|
||||
schema: "public",
|
||||
table: "users",
|
||||
name: "chk_age",
|
||||
contype: "CHECK",
|
||||
definition: "CHECK (age > 0)",
|
||||
columns: ["age"],
|
||||
},
|
||||
"edit",
|
||||
);
|
||||
expect(p).toMatchObject({
|
||||
schema: "public",
|
||||
table: "users",
|
||||
name: "chk_age",
|
||||
action: { op: "check", expression: "CHECK (age > 0)" },
|
||||
});
|
||||
});
|
||||
|
||||
it("constraint edit maps UNIQUE contype to a unique action with columns", () => {
|
||||
const p = initialCrudParams(
|
||||
"constraint",
|
||||
{
|
||||
schema: "public",
|
||||
table: "users",
|
||||
name: "uniq_email",
|
||||
contype: "UNIQUE",
|
||||
definition: "UNIQUE (email)",
|
||||
columns: ["email"],
|
||||
},
|
||||
"edit",
|
||||
);
|
||||
expect(p).toMatchObject({
|
||||
name: "uniq_email",
|
||||
action: { op: "unique", columns: ["email"] },
|
||||
});
|
||||
});
|
||||
|
||||
it("extension edit prefills the version from the item", () => {
|
||||
const p = initialCrudParams(
|
||||
"extension",
|
||||
{ schema: "public", name: "pgcrypto", version: "1.3" },
|
||||
"edit",
|
||||
);
|
||||
expect(p).toMatchObject({
|
||||
name: "pgcrypto",
|
||||
action: { op: "create", version: "1.3" },
|
||||
});
|
||||
});
|
||||
|
||||
it("view edit marks materialized when the item is a materialized view", () => {
|
||||
const p = initialCrudParams(
|
||||
"view",
|
||||
{
|
||||
schema: "public",
|
||||
name: "mv",
|
||||
table_type: "MATERIALIZED VIEW",
|
||||
definition: "SELECT 1",
|
||||
},
|
||||
"edit",
|
||||
);
|
||||
expect(p).toMatchObject({
|
||||
name: "mv",
|
||||
materialized: true,
|
||||
action: { op: "create", definition: "SELECT 1" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("dropCrudParams", () => {
|
||||
it("plain kinds build a base drop action", () => {
|
||||
expect(
|
||||
dropCrudParams("sequence", { schema: "public", name: "s" }),
|
||||
).toEqual({ schema: "public", name: "s", action: { op: "drop" } });
|
||||
});
|
||||
|
||||
it("index and constraint carry the owning table", () => {
|
||||
expect(
|
||||
dropCrudParams("index", { schema: "public", name: "i", table: "users" }),
|
||||
).toEqual({
|
||||
schema: "public",
|
||||
name: "i",
|
||||
table: "users",
|
||||
action: { op: "drop" },
|
||||
});
|
||||
});
|
||||
|
||||
it("trigger uses table_name as a fallback for the table", () => {
|
||||
expect(
|
||||
dropCrudParams("trigger", {
|
||||
schema: "public",
|
||||
name: "trg",
|
||||
table_name: "users",
|
||||
}),
|
||||
).toEqual({
|
||||
schema: "public",
|
||||
name: "trg",
|
||||
action: { op: "drop", table: "users" },
|
||||
});
|
||||
});
|
||||
|
||||
it("view marks materialized via table_type", () => {
|
||||
expect(
|
||||
dropCrudParams("view", { schema: "public", name: "m", table_type: "m" }),
|
||||
).toMatchObject({ materialized: true, action: { op: "drop" } });
|
||||
});
|
||||
|
||||
it("function carries argument types and is_procedure false", () => {
|
||||
expect(
|
||||
dropCrudParams("function", {
|
||||
schema: "public",
|
||||
name: "f",
|
||||
argument_types: ["int"],
|
||||
}),
|
||||
).toMatchObject({
|
||||
is_procedure: false,
|
||||
action: { op: "drop", arg_types: ["int"] },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("table/role crud params", () => {
|
||||
it("initialCrudParams(table, create) starts empty with columns", () => {
|
||||
const p = initialCrudParams("table", { schema: "public", name: "" }, "create");
|
||||
expect(p).toEqual({ schema: "public", name: "", action: { op: "create", columns: [] } });
|
||||
});
|
||||
it("initialCrudParams(table, edit) prefills columns", () => {
|
||||
const item: CrudItem = { schema: "public", name: "users", columns: ["id"], columnMeta: [
|
||||
{ name: "id", type: "integer", nullable: false, is_pk: true, default: null },
|
||||
] };
|
||||
const p = initialCrudParams("table", item, "edit");
|
||||
expect((p as any).action.op).toBe("edit");
|
||||
expect((p as any).action.old_columns).toEqual(item.columnMeta);
|
||||
});
|
||||
it("initialCrudParams(role, create) starts empty with options", () => {
|
||||
const p = initialCrudParams("role", { schema: "", name: "" }, "create");
|
||||
expect(p).toEqual({ schema: "", name: "", action: { op: "create", login: false, superuser: false, createdb: false, createrole: false, inherit: true, replication: false, bypassrls: false, connection_limit: -1, valid_until: "", password: "", members: [] } });
|
||||
});
|
||||
it("dropCrudParams(role) drops by name", () => {
|
||||
const p = dropCrudParams("role", { schema: "", name: "app" });
|
||||
expect(p).toEqual({ schema: "", name: "app", action: { op: "drop" } });
|
||||
});
|
||||
it("dropCrudParams(table) drops the table", () => {
|
||||
const p = dropCrudParams("table", { schema: "public", name: "users" });
|
||||
expect(p).toEqual({ schema: "public", name: "users", action: { op: "drop" } });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user