chore: update build config, linting, and add test infrastructure
Replace install -D with mkdir -p + install for macOS portability, add vitest with jsdom and testing-library, configure eslint for react-hooks v7 warnings, and add tokio test deps for Rust.
This commit is contained in:
217
src/stores/__tests__/app-store.test.ts
Normal file
217
src/stores/__tests__/app-store.test.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
import { useAppStore } from "../app-store";
|
||||
import type { Tab } from "@/types";
|
||||
|
||||
function resetStore() {
|
||||
useAppStore.setState({
|
||||
connections: [],
|
||||
activeConnectionId: null,
|
||||
currentDatabase: null,
|
||||
connectedIds: new Set(),
|
||||
readOnlyMap: {},
|
||||
dbFlavors: {},
|
||||
tabs: [],
|
||||
activeTabId: null,
|
||||
sidebarWidth: 260,
|
||||
pgVersion: null,
|
||||
});
|
||||
}
|
||||
|
||||
const makeTab = (id: string, type: Tab["type"] = "query"): Tab => ({
|
||||
id,
|
||||
type,
|
||||
title: `Tab ${id}`,
|
||||
connectionId: "conn-1",
|
||||
});
|
||||
|
||||
describe("AppStore", () => {
|
||||
beforeEach(() => {
|
||||
resetStore();
|
||||
});
|
||||
|
||||
// ── Connections ────────────────────────────────────────────
|
||||
|
||||
describe("connections", () => {
|
||||
it("should set active connection id", () => {
|
||||
useAppStore.getState().setActiveConnectionId("conn-1");
|
||||
expect(useAppStore.getState().activeConnectionId).toBe("conn-1");
|
||||
});
|
||||
|
||||
it("should clear active connection id", () => {
|
||||
useAppStore.getState().setActiveConnectionId("conn-1");
|
||||
useAppStore.getState().setActiveConnectionId(null);
|
||||
expect(useAppStore.getState().activeConnectionId).toBeNull();
|
||||
});
|
||||
|
||||
it("should add connected id and default to read-only", () => {
|
||||
useAppStore.getState().addConnectedId("conn-1");
|
||||
const state = useAppStore.getState();
|
||||
expect(state.connectedIds.has("conn-1")).toBe(true);
|
||||
expect(state.readOnlyMap["conn-1"]).toBe(true);
|
||||
});
|
||||
|
||||
it("should remove connected id and clean up maps", () => {
|
||||
const s = useAppStore.getState();
|
||||
s.addConnectedId("conn-1");
|
||||
s.setDbFlavor("conn-1", "postgresql");
|
||||
s.setReadOnly("conn-1", false);
|
||||
|
||||
useAppStore.getState().removeConnectedId("conn-1");
|
||||
|
||||
const state = useAppStore.getState();
|
||||
expect(state.connectedIds.has("conn-1")).toBe(false);
|
||||
expect(state.readOnlyMap["conn-1"]).toBeUndefined();
|
||||
expect(state.dbFlavors["conn-1"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not affect other connections on remove", () => {
|
||||
const s = useAppStore.getState();
|
||||
s.addConnectedId("conn-1");
|
||||
s.addConnectedId("conn-2");
|
||||
s.setDbFlavor("conn-1", "postgresql");
|
||||
s.setDbFlavor("conn-2", "greenplum");
|
||||
|
||||
useAppStore.getState().removeConnectedId("conn-1");
|
||||
|
||||
const state = useAppStore.getState();
|
||||
expect(state.connectedIds.has("conn-2")).toBe(true);
|
||||
expect(state.readOnlyMap["conn-2"]).toBe(true);
|
||||
expect(state.dbFlavors["conn-2"]).toBe("greenplum");
|
||||
});
|
||||
|
||||
it("should toggle read-only mode", () => {
|
||||
useAppStore.getState().addConnectedId("conn-1");
|
||||
expect(useAppStore.getState().readOnlyMap["conn-1"]).toBe(true);
|
||||
|
||||
useAppStore.getState().setReadOnly("conn-1", false);
|
||||
expect(useAppStore.getState().readOnlyMap["conn-1"]).toBe(false);
|
||||
|
||||
useAppStore.getState().setReadOnly("conn-1", true);
|
||||
expect(useAppStore.getState().readOnlyMap["conn-1"]).toBe(true);
|
||||
});
|
||||
|
||||
it("addConnectedId forces read-only true on reconnect", () => {
|
||||
useAppStore.getState().addConnectedId("conn-1");
|
||||
useAppStore.getState().setReadOnly("conn-1", false);
|
||||
// Reconnect resets to read-only
|
||||
useAppStore.getState().addConnectedId("conn-1");
|
||||
expect(useAppStore.getState().readOnlyMap["conn-1"]).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Tabs ──────────────────────────────────────────────────
|
||||
|
||||
describe("tabs", () => {
|
||||
it("should add tab and set it as active", () => {
|
||||
useAppStore.getState().addTab(makeTab("tab-1"));
|
||||
|
||||
const state = useAppStore.getState();
|
||||
expect(state.tabs).toHaveLength(1);
|
||||
expect(state.tabs[0].id).toBe("tab-1");
|
||||
expect(state.activeTabId).toBe("tab-1");
|
||||
});
|
||||
|
||||
it("should activate the most recently added tab", () => {
|
||||
useAppStore.getState().addTab(makeTab("tab-1"));
|
||||
useAppStore.getState().addTab(makeTab("tab-2"));
|
||||
expect(useAppStore.getState().activeTabId).toBe("tab-2");
|
||||
});
|
||||
|
||||
it("should close tab and activate last remaining", () => {
|
||||
useAppStore.getState().addTab(makeTab("tab-1"));
|
||||
useAppStore.getState().addTab(makeTab("tab-2"));
|
||||
useAppStore.getState().addTab(makeTab("tab-3"));
|
||||
useAppStore.getState().setActiveTabId("tab-2");
|
||||
|
||||
useAppStore.getState().closeTab("tab-2");
|
||||
|
||||
const state = useAppStore.getState();
|
||||
expect(state.tabs).toHaveLength(2);
|
||||
// When closing the active tab, last remaining tab becomes active
|
||||
expect(state.activeTabId).toBe("tab-3");
|
||||
});
|
||||
|
||||
it("should set activeTabId to null when closing the only tab", () => {
|
||||
useAppStore.getState().addTab(makeTab("tab-1"));
|
||||
useAppStore.getState().closeTab("tab-1");
|
||||
|
||||
expect(useAppStore.getState().tabs).toHaveLength(0);
|
||||
expect(useAppStore.getState().activeTabId).toBeNull();
|
||||
});
|
||||
|
||||
it("should preserve activeTabId when closing a non-active tab", () => {
|
||||
useAppStore.getState().addTab(makeTab("tab-1"));
|
||||
useAppStore.getState().addTab(makeTab("tab-2"));
|
||||
useAppStore.getState().setActiveTabId("tab-1");
|
||||
|
||||
useAppStore.getState().closeTab("tab-2");
|
||||
|
||||
expect(useAppStore.getState().activeTabId).toBe("tab-1");
|
||||
});
|
||||
|
||||
it("should update tab fields without affecting others", () => {
|
||||
useAppStore.getState().addTab(makeTab("tab-1"));
|
||||
useAppStore.getState().addTab(makeTab("tab-2"));
|
||||
|
||||
useAppStore.getState().updateTab("tab-1", { title: "Updated" });
|
||||
|
||||
expect(useAppStore.getState().tabs[0].title).toBe("Updated");
|
||||
expect(useAppStore.getState().tabs[1].title).toBe("Tab tab-2");
|
||||
});
|
||||
|
||||
it("should handle closing non-existent tab gracefully", () => {
|
||||
useAppStore.getState().addTab(makeTab("tab-1"));
|
||||
useAppStore.getState().closeTab("non-existent");
|
||||
|
||||
expect(useAppStore.getState().tabs).toHaveLength(1);
|
||||
expect(useAppStore.getState().activeTabId).toBe("tab-1");
|
||||
});
|
||||
|
||||
it("should handle different tab types", () => {
|
||||
useAppStore.getState().addTab(makeTab("t1", "query"));
|
||||
useAppStore.getState().addTab(makeTab("t2", "table"));
|
||||
useAppStore.getState().addTab(makeTab("t3", "erd"));
|
||||
|
||||
expect(useAppStore.getState().tabs.map((t) => t.type)).toEqual([
|
||||
"query",
|
||||
"table",
|
||||
"erd",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Database state ────────────────────────────────────────
|
||||
|
||||
describe("database state", () => {
|
||||
it("should set and clear current database", () => {
|
||||
useAppStore.getState().setCurrentDatabase("mydb");
|
||||
expect(useAppStore.getState().currentDatabase).toBe("mydb");
|
||||
|
||||
useAppStore.getState().setCurrentDatabase(null);
|
||||
expect(useAppStore.getState().currentDatabase).toBeNull();
|
||||
});
|
||||
|
||||
it("should set pg version", () => {
|
||||
useAppStore.getState().setPgVersion("16.2");
|
||||
expect(useAppStore.getState().pgVersion).toBe("16.2");
|
||||
});
|
||||
|
||||
it("should set db flavor", () => {
|
||||
useAppStore.getState().setDbFlavor("conn-1", "greenplum");
|
||||
expect(useAppStore.getState().dbFlavors["conn-1"]).toBe("greenplum");
|
||||
});
|
||||
});
|
||||
|
||||
// ── Sidebar ───────────────────────────────────────────────
|
||||
|
||||
describe("sidebar", () => {
|
||||
it("should set sidebar width", () => {
|
||||
useAppStore.getState().setSidebarWidth(400);
|
||||
expect(useAppStore.getState().sidebarWidth).toBe(400);
|
||||
});
|
||||
|
||||
it("should have default sidebar width of 260", () => {
|
||||
expect(useAppStore.getState().sidebarWidth).toBe(260);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user