v0.5.0 — Grid Interactivity, Home Polish, Deeper PostgreSQL (#8)
* chore: bump version to 0.5.0 (Task 1) * feat(store): v7 migration — favorites + recent_connections (Task 2) * feat(models): add favorite to Connection + Store CRUD (Task 3) * feat(types): ColumnInfo editability + IndexInfo/ConstraintInfo/RecentConnection (Task 4) * chore: bump version to 0.5.0 (Task 1) — lockfile * feat(commands): typed wrappers for favorites/recents/indexes/constraints (Task 5) * feat(db): PG indexes/constraints queries + matview UNION in tables (Task 6) * feat(db): get_table_data editability flags + ctid/rowid locator (Task 7) * feat(db): execute_change no-PK locator guard + affected-count check (Task 8) * feat(db): preserve bigint precision as string on PG read path (Task 9) * feat(db): get_indexes / get_constraints commands (Task 10) * feat(store): favorites + recents Store methods (Task 11) * feat(commands): favorites/recents IPC + register indexes/constraints (Task 12) * feat(store): connectionStore favorites/recents/move-selection (Task 13) * feat(lib): recent-connections pure helpers (Task 14) * feat(store): dbViewerStore indexes/constraints + stageCellEdit (Task 15) * feat(grid): pure editability + filter-operator + cell transform (Task 16) * feat(grid): pure keyboard-nav helper (Task 17) * feat(grid): CellEditor inline editor (Task 18) * feat(grid): CellContextMenu + RowDetailDrawer (Task 19) * feat(grid): focus model + keyboard nav + inline edit + copy + context menu (Task 20) * feat(db-viewer): FilterBuilder drag-and-drop + type-aware operators (Task 21) * feat(db-viewer): ObjectExplorer indexes/constraints/procedures + matview icon (Task 22) * feat(home): ConnectionCard favorite star + on-demand StatusDot (Task 23) * feat(home): move-to-folder + recents strip + status wiring (Task 24) * feat(db-viewer): grid wiring + matview read-only + post-commit refetch (Task 25) * docs: v0.5.0 status + roadmap updates (Task 26) * polish: empty/error/loading states for v0.5.0 surfaces (Task 28) * feat(home): duplicateConnection + useConnectionStatus hook, drop StatusDot (FEAT-A) * feat(home): connection card kebab menu — favorite/test/manage (FEAT-B) * fix(home): populate server_version/latency_ms in test_connection + clean online display * style(home): swap grab handle and kebab positions on connection card * style(home): nudge kebab menu to right-1 * style(home): nudge kebab menu to right-0.5 * feat(home): Escape clears + exits focused search * feat(grid): context-menu View/Select Row, outside-click close, Esc cancels edit, FK reference (GRID-A) * feat(grid): smart CellEditor — enum select, FK searchable dropdown, textarea height (GRID-B) * feat(grid): enums + FK options fed into CellEditor (GRID-C) * feat(grid): FK dropdown display-column labels + placeholder + empty state * fix(grid): FK dropdown renders as fixed overlay to avoid clipping * fix(grid): portal FK dropdown to body + FK reference icon instead of click-to-open * style(grid): move FK reference icon to the start of the cell * feat(grid): optimistic staged cell values + pending dot, cleared on refetch * fix(grid): queue is source of truth for staged values — value diff, Clear All clears dots, same-cell edits replace * fix(db-viewer): type getLocator for staged-value matching * test(db-viewer): unit-test deriveStagedValues; fix activeTab null guard * fix(db-viewer): pass table prop to VirtualDataGrid — staged edits now carry the table name * test(db-viewer): use index access instead of .at() for TS lib target * feat(grid): FK dropdown options as one-row column cells (FK-reference style, cap 5) * style(grid): FK dropdown — values only, fixed 360px width, FK-viewer surface styling * style(grid): harden FK dropdown minWidth to 360px * style(grid): cap FK dropdown cells at 3 * style(grid): cap FK dropdown cells at 4 * fix(grid): pending dot clears on commit — values stay until refetch * fix(db): deserialize pg_attribute char columns as i8 — no more panic on get_table_data * docs: reflect grid interactivity, smart editors, optimistic queue, FK reference, kebab status
This commit is contained in:
+47
-4
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { describe, it, expect, expectTypeOf } from "vitest";
|
||||
import type {
|
||||
Connection,
|
||||
ConnectionInput,
|
||||
@@ -16,6 +16,9 @@ import type {
|
||||
GraphColumn,
|
||||
Relationship,
|
||||
Settings,
|
||||
IndexInfo,
|
||||
ConstraintInfo,
|
||||
RecentConnection,
|
||||
} from "./types";
|
||||
|
||||
describe("ActiveView", () => {
|
||||
@@ -37,6 +40,7 @@ describe("Connection", () => {
|
||||
folder_id: null,
|
||||
keychain_ref: null,
|
||||
tag_ids: [],
|
||||
favorite: false,
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
updated_at: "2024-01-01T00:00:00Z",
|
||||
// new SSH/SSL fields
|
||||
@@ -68,6 +72,7 @@ describe("Connection", () => {
|
||||
folder_id: null,
|
||||
keychain_ref: null,
|
||||
tag_ids: [],
|
||||
favorite: false,
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
updated_at: "2024-01-01T00:00:00Z",
|
||||
};
|
||||
@@ -89,6 +94,7 @@ describe("Connection", () => {
|
||||
folder_id: null,
|
||||
keychain_ref: null,
|
||||
tag_ids: [],
|
||||
favorite: false,
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
updated_at: "2024-01-01T00:00:00Z",
|
||||
};
|
||||
@@ -184,6 +190,8 @@ describe("ColumnInfo", () => {
|
||||
is_fk: false,
|
||||
fk_ref: null,
|
||||
default_value: null,
|
||||
editable: true,
|
||||
is_generated: false,
|
||||
};
|
||||
expect(col.name).toBe("id");
|
||||
expect(col.is_pk).toBe(true);
|
||||
@@ -198,6 +206,8 @@ describe("ColumnInfo", () => {
|
||||
is_fk: true,
|
||||
fk_ref: ["users", "id"],
|
||||
default_value: null,
|
||||
editable: true,
|
||||
is_generated: false,
|
||||
};
|
||||
expect(col.fk_ref?.[0]).toBe("users");
|
||||
});
|
||||
@@ -206,7 +216,7 @@ describe("ColumnInfo", () => {
|
||||
describe("QueryResult", () => {
|
||||
it("is well-typed with columns and rows", () => {
|
||||
const result: QueryResult = {
|
||||
columns: [{name:"id",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null},{name:"name",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null},{name:"email",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null}],
|
||||
columns: [{name:"id",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false},{name:"name",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false},{name:"email",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false}],
|
||||
rows: [
|
||||
[1, "Alice"],
|
||||
[2, "Bob"],
|
||||
@@ -231,7 +241,7 @@ describe("QueryResult", () => {
|
||||
|
||||
it("can have null execution_time", () => {
|
||||
const result: QueryResult = {
|
||||
columns: [{name:"id",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null}],
|
||||
columns: [{name:"id",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false}],
|
||||
rows: [],
|
||||
total_rows: 0, page: 1, page_size: 50,
|
||||
execution_time_ms: null,
|
||||
@@ -324,7 +334,7 @@ describe("DbViewerTab", () => {
|
||||
title: "SELECT * FROM users",
|
||||
query: "SELECT * FROM users",
|
||||
result: {
|
||||
columns: [{name:"id",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null},{name:"name",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null}],
|
||||
columns: [{name:"id",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false},{name:"name",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false}],
|
||||
rows: [],
|
||||
total_rows: 0, page: 1, page_size: 50,
|
||||
},
|
||||
@@ -452,4 +462,37 @@ describe("Settings", () => {
|
||||
expect(s.editor_font_size).toBe(13);
|
||||
expect(s.editor_word_wrap).toBe("off");
|
||||
});
|
||||
});
|
||||
|
||||
describe("v0.5.0 types", () => {
|
||||
it("ColumnInfo carries editability metadata", () => {
|
||||
const c: ColumnInfo = {
|
||||
name: "id", data_type: "integer", is_nullable: false, is_pk: true,
|
||||
is_fk: false, fk_ref: null, default_value: null, editable: false, is_generated: false,
|
||||
};
|
||||
expectTypeOf(c.editable).toEqualTypeOf<boolean>();
|
||||
expectTypeOf(c.is_generated).toEqualTypeOf<boolean>();
|
||||
});
|
||||
it("IndexInfo has the documented fields", () => {
|
||||
const i: IndexInfo = {
|
||||
name: "idx", schema: "public", table: "users", definition: "CREATE INDEX ...",
|
||||
is_unique: true, method: "btree", columns: ["id"], size_bytes: 4096, tablespace: null,
|
||||
};
|
||||
expectTypeOf(i).toMatchTypeOf<IndexInfo>();
|
||||
});
|
||||
it("ConstraintInfo has the documented fields", () => {
|
||||
const c: ConstraintInfo = {
|
||||
name: "ck", schema: "public", table: "users", contype: "CHECK",
|
||||
definition: "CHECK (x > 0)", deferrable: false, validated: true, columns: ["x"],
|
||||
};
|
||||
expectTypeOf(c).toMatchTypeOf<ConstraintInfo>();
|
||||
});
|
||||
it("RecentConnection pairs a connection id with an opened_at timestamp", () => {
|
||||
const r: RecentConnection = { connection_id: "c1", opened_at: "2026-08-02T00:00:00Z" };
|
||||
expectTypeOf(r.connection_id).toEqualTypeOf<string>();
|
||||
});
|
||||
it("Connection carries favorite", () => {
|
||||
const c = { favorite: true } as Connection;
|
||||
expectTypeOf(c.favorite).toEqualTypeOf<boolean>();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user