v0.7.0: New Connection screen revamp + full MySQL DB viewer (#10)
* docs: correct competitor comparison for DB Pro, Beekeeper, TablePlus Research-verified the 'Why Gridline vs the alternatives' claims against vendor docs, pricing pages, GitHub, and release notes (May 2026): - DB Pro is an Electron app (founder-confirmed), not native; add TablePlus column to the comparison table - Fix wrong cells: DB Pro has query/dashboard folders + table tags and CSV/JSON export on the free tier; object-explorer depth corrected for DB Pro (tables/views/indexes/enums) and Beekeeper (tables/views/routines/triggers) - Reframe differentiators: unlimited-everything framing dropped for Beekeeper (free tier is already unlimited on tabs/connections/queries); keep DB-to-DB sync as the genuinely unique feature - Add a dated 'Competitor reality check' section to AGENTS.md so future edits don't re-assert inaccurate claims * docs: add project roadmap, link it from README and AGENTS New ROADMAP.md is the source of truth for planned work, reflecting the in-flight v0.7.0 connection-screen-revamp spec (new-connection flow, full MySQL DB viewer, capability gating, Supabase/Neon presets, SQLite path mode, tag overflow scroll, styling sweep). Next-up scope: PostgreSQL object management CRUD with companion features (schema CRUD, global object search, copy-as-DDL, object dependencies) and an admin follow-up (users/roles/grants, VACUUM/ANALYZE/REINDEX). MySQL Objects view explicitly deferred. Queue: Redis browsing, MariaDB/TimescaleDB, PlanetScale/Turso, query workbench upgrades (multiple result sets, query cancel, result streaming, visual query builder), schema/data tooling, SQLite .dump, schema diff, more export formats. Planned: BYOK AI, website & docs, rolling UI/UX polish (incl. onboarding tour, settings import/export, SSH key management). README roadmap section now links to ROADMAP.md; AGENTS.md Related Documents + Implementation Status reference it and the v0.7.0 spec. * docs: release notes reference prod as the production branch The repo's production branch is prod (feature branches merge back to prod), not main. Update the release-cut instructions in the README and the trigger comment in release.yml. * docs: add robust bug report issue template Structured .github/ISSUE_TEMPLATE/bug_report.md covering environment (OS, Gridline version, install type, DB type/version, hosted provider, connection method incl. SSH/TLS/socket), steps to reproduce, expected vs actual, screenshots, logs, impact, and workarounds — plus a duplicate checklist and secrets-redaction note. Referenced from the README Contributing section. * docs: drop in-flight branch mention from roadmap; remove unused starter assets - ROADMAP.md no longer references the in-flight feature branch/spec (removed at the end anyway when the branch PRs into prod) - Remove unused Vite/Tauri starter SVGs from public/ (no favicon or asset references anywhere in the app) * test: fix stale README comparison-table regex in docs-coverage (5-col table) * feat: shared INPUT_ROUNDING constant + bump to v0.7.0 (Task 1.1) * fix: map SQLite file path to host field + provider host detection (Task 1.2) * test: bump version expectation to 0.7.0 (Task 1.1 follow-up) * feat: db capability matrix for DB viewer gating (Task 1.3) * feat: provider tab definitions, Supabase/Neon icons + setup guides (Task 1.4) * feat(rust): MySQL SQL builders + identifier quoting (Task 2.1) * chore(rust): sync Cargo.lock to gridline 0.7.0 * feat(rust): MySQL db_connect (SSL + SSH tunnel) + pool variant (Tasks 2.2-2.3) * feat(rust): MySQL execute_query with wrapped pagination + raw fallback (Task 2.4) * feat(rust): MySQL introspection + changes-queue editing + DDL (Task 2.5) * fix(ui): show table toolbar immediately while tab is still loading first data * feat: gate DB viewer sidebar nav by db capabilities (Task 3.1) * feat: guard DB viewer views by capability + Redis unsupported state (Task 3.2) * feat(ui): 2-column provider tab grid (Task 4.1) * feat(ui): collapsible Supabase/Neon setup guide (Task 4.2) * feat(ui): SQLite file-path input with Browse (Task 4.3) * feat(ui): connection metadata row (label + tags/env/folder) (Task 4.4) * feat(ui): rework GeneralTab (URI + OR + manual) + reduce Detailed form tabs (Task 4.5) * feat(ui): NewConnectionScreen two-stage flow; remove SimpleConnectionForm (Task 5.1) * feat(ui): scroll connection-card tag row past 3 tags (Task 5.2) * style: sweep form controls from rounded-full to rounded-lg (Task 5.3) * feat(ui): EditConnectionModal parity + managed-preset SSL hint (Task 5.4) * fix(rust): decode MySQL VARBINARY metadata columns (information_schema/SHOW) as strings * test: full suite green for v0.7.0 connection revamp (Task 5.5) * feat(ui): schema dropdown + tables tree loading state while schema tree fetches * docs: update AGENTS/README/ROADMAP for v0.7.0 (connection revamp, MySQL viewer, gating)
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { useState } from "react";
|
||||
import { render, screen, within } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { ConnectionMetadataRow } from "./ConnectionMetadataRow";
|
||||
import type { ConnectionFormData } from "./connectionFormData";
|
||||
import type { Folder, Tag } from "../../lib/types";
|
||||
|
||||
const BASE_FORM: ConnectionFormData = {
|
||||
name: "", environment: null, folder_id: null, tag_ids: [],
|
||||
connection_string: "", db_type: "postgresql", host: "", port: 5432,
|
||||
username: null, password: null, database: null, use_keychain: false, ssh_password: null,
|
||||
};
|
||||
const folders: Folder[] = [{ id: "f1", name: "Work", parent_id: null, tag_ids: [], created_at: "", updated_at: "" }];
|
||||
const tags: Tag[] = [{ id: "t1", name: "prod", color: "#f00", created_at: "" }];
|
||||
|
||||
describe("ConnectionMetadataRow", () => {
|
||||
it("renders a Connection Label input and emits name changes", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
function Wrapper() {
|
||||
const [form, setForm] = useState(BASE_FORM);
|
||||
return (
|
||||
<ConnectionMetadataRow
|
||||
form={form}
|
||||
folders={folders}
|
||||
tags={tags}
|
||||
onChange={(updates) => {
|
||||
onChange(updates);
|
||||
setForm((prev) => ({ ...prev, ...updates }));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
render(<Wrapper />);
|
||||
const label = screen.getByLabelText(/connection label/i);
|
||||
await user.type(label, "My DB");
|
||||
expect(onChange).toHaveBeenLastCalledWith({ name: "My DB" });
|
||||
});
|
||||
|
||||
it("toggles the tag picker via + Add Tags and selects a tag", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
render(<ConnectionMetadataRow form={BASE_FORM} folders={folders} tags={tags} onChange={onChange} />);
|
||||
await user.click(screen.getByRole("button", { name: /add tags/i }));
|
||||
await user.click(screen.getByRole("button", { name: /prod/i }));
|
||||
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ tag_ids: ["t1"] }));
|
||||
});
|
||||
|
||||
it("changes the environment via Set Env", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
render(<ConnectionMetadataRow form={BASE_FORM} folders={folders} tags={tags} onChange={onChange} />);
|
||||
await user.click(screen.getByRole("button", { name: /set env/i }));
|
||||
const envSection = screen.getByTestId("environment-section");
|
||||
await user.click(within(envSection).getByRole("button"));
|
||||
await user.click(within(envSection).getByRole("button", { name: "Production" }));
|
||||
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ environment: "production" }));
|
||||
});
|
||||
|
||||
it("changes the folder via the folder select", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
render(<ConnectionMetadataRow form={BASE_FORM} folders={folders} tags={tags} onChange={onChange} />);
|
||||
const folderSection = screen.getByTestId("folder-section");
|
||||
await user.click(within(folderSection).getByRole("button"));
|
||||
await user.click(within(folderSection).getByRole("button", { name: "Work" }));
|
||||
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ folder_id: "f1" }));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user