Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions sensibledb-explorer/src/frontend/e2e/explorer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { test, expect } from "@playwright/test";

test.describe("SensibleDB Explorer E2E Flow", () => {
test("complete user journey: load demo, view graph, chat with AI", async ({ page }) => {
await page.goto("/");
await expect(page.locator("text=Welcome to SensibleDB")).toBeVisible({ timeout: 30000 });

await page.locator("button:has-text('Explore')").first().click();
await expect(page.locator("svg")).toBeVisible({ timeout: 15000 });
await expect(page.locator(".node-card").first()).toBeVisible({ timeout: 10000 });

await page.keyboard.press("3");
await expect(page.locator("textarea[placeholder*='Ask']")).toBeVisible({ timeout: 5000 });

await page.locator("textarea").first().fill("Show me all nodes");
await page.keyboard.press("Enter");
await page.waitForTimeout(3000);

await page.keyboard.press("4");
await expect(page.locator("text=Summary Report")).toBeVisible({ timeout: 5000 });
await expect(page.locator("text=Total Items:")).toBeVisible();
await expect(page.locator("text=Total Connections:")).toBeVisible();

console.log("✅ E2E flow completed successfully");
});

test("sidebar navigation works", async ({ page }) => {
await page.goto("/");
await expect(page.locator("text=Welcome to SensibleDB")).toBeVisible({ timeout: 30000 });

await page.keyboard.press("1");
await expect(page.locator("text=Welcome to SensibleDB")).toBeVisible();

await page.keyboard.press("2");
await page.waitForTimeout(500);

await page.keyboard.press("3");
await page.waitForTimeout(500);

await page.keyboard.press("4");
await page.waitForTimeout(500);

await page.keyboard.press("8");
await page.waitForTimeout(500);

console.log("✅ Sidebar navigation test passed");
});

test("can load demo database", async ({ page }) => {
await page.goto("/");
await page.locator("button:has-text('Explore')").first().click();
await expect(page.locator("svg")).toBeVisible({ timeout: 15000 });
await expect(page.locator(".node-card").first()).toBeVisible({ timeout: 10000 });
await expect(page.locator(".header-db-selector")).toContainText("health-patterns");

console.log("✅ Demo database loading works");
});
});
6 changes: 5 additions & 1 deletion sensibledb-explorer/src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"test": "vitest",
"test:ui": "vitest --ui",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
"test:coverage": "vitest run --coverage",
"e2e": "playwright test",
"e2e:headed": "playwright test --headed"
},
"dependencies": {
"@codemirror/lang-javascript": "^6.2",
Expand All @@ -26,6 +28,7 @@
"solid-js": "^1.9"
},
"devDependencies": {
"@playwright/test": "^1.59.1",
"@solidjs/testing-library": "^0.8.10",
"@tauri-apps/cli": "^2",
"@testing-library/jest-dom": "^6.9.1",
Expand All @@ -34,6 +37,7 @@
"@vitest/coverage-v8": "^4.1.2",
"happy-dom": "^20.8.9",
"jsdom": "^29.0.1",
"playwright": "^1.59.1",
"typescript": "^5.6",
"vite": "^6",
"vite-plugin-solid": "^2.10",
Expand Down
29 changes: 29 additions & 0 deletions sensibledb-explorer/src/frontend/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
testDir: "./e2e",
fullyParallel: true,
retries: 0,
workers: 1,
reporter: "list",

use: {
baseURL: "http://localhost:1420",
trace: "on-first-retry",
screenshot: "only-on-failure",
},

projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],

webServer: {
command: "npm run tauri dev",
url: "http://localhost:1420",
reuseExistingServer: !process.env.CI,
timeout: 180000,
},
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Component, createSignal, onMount, Show, For } from "solid-js";
import { EditorView, basicSetup } from "codemirror";
import { EditorState } from "@codemirror/state";
import { Component, Show, For, createSignal } from "solid-js";
import { sensibleqlExecute } from "../../lib/api";
import { activeDb } from "../../stores/app";
import { useCodeMirror } from "../../lib/useCodeMirror";
import type { SensibleqlResult } from "../../types";
import "./SensibleQLEditor.css";

Expand All @@ -27,40 +26,21 @@ const sampleQueries: SampleQuery[] = [
];

const SensibleQLEditor: Component = () => {
let editorRef: HTMLDivElement | undefined;
const [query, setQuery] = createSignal("");
const [result, setResult] = createSignal<SensibleqlResult | null>(null);
const [isRunning, setIsRunning] = createSignal(false);
let editor: EditorView | undefined;

onMount(() => {
if (!editorRef) return;
editor = new EditorView({
state: EditorState.create({
doc: "// Select a sample query below or write your own\n// Supported: MATCH, GET, FIND, COUNT\n",
extensions: [
basicSetup,
EditorView.theme({
"&": { fontSize: "14px" },
".cm-editor": { background: "#f8fafc" },
".cm-gutters": { background: "#f1f5f9", border: "none" },
}),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
setQuery(update.state.doc.toString());
}
}),
],
}),
parent: editorRef,
});
const { ref: editorRef, setDoc: setEditorDoc, getDoc } = useCodeMirror({
initialDoc: "// Select a sample query below or write your own\n// Supported: MATCH, GET, FIND, COUNT\n",
onChange: (doc) => setQuery(doc),
});

const handleRun = async () => {
if (!activeDb() || !query()) return;
const currentQuery = getDoc();
if (!activeDb() || !currentQuery) return;
setIsRunning(true);
try {
const res = await sensibleqlExecute(activeDb()!, query());
const res = await sensibleqlExecute(activeDb()!, currentQuery);
setResult(res);
} catch (e: any) {
setResult({ success: false, message: String(e), data: null });
Expand All @@ -70,11 +50,7 @@ const SensibleQLEditor: Component = () => {
};

const loadSample = (sample: SampleQuery) => {
if (editor) {
editor.dispatch({
changes: { from: 0, to: editor.state.doc.length, insert: sample.query },
});
}
setEditorDoc(sample.query);
setQuery(sample.query);
setResult(null);
};
Expand Down Expand Up @@ -105,7 +81,7 @@ const SensibleQLEditor: Component = () => {
</div>
</div>

<div ref={editorRef!} class="editor-container" />
<div ref={editorRef} class="editor-container" />
<Show when={result()}>
{(r) => (
<div classList={{ "result-panel": true, error: !r().success }}>
Expand All @@ -122,4 +98,4 @@ const SensibleQLEditor: Component = () => {
);
};

export default SensibleQLEditor;
export default SensibleQLEditor;
100 changes: 100 additions & 0 deletions sensibledb-explorer/src/frontend/src/components/home/HomeView.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { render, screen } from "@solidjs/testing-library";
import HomeView from "../home/HomeView";
import { setActiveView, setActiveDb, databases, setDatabases, setNodes, setEdges, setSchema } from "../../stores/app";

vi.mock("../../lib/api", () => ({
nodeList: vi.fn().mockResolvedValue([]),
edgeList: vi.fn().mockResolvedValue([]),
schemaGet: vi.fn().mockResolvedValue({ node_labels: [], edge_types: [], indexes: [], vector_indexes: [] }),
}));

vi.mock("../onboarding/GuidedTour", () => ({
showTour: vi.fn(),
}));

vi.mock("../onboarding/ConnectionWizard", () => ({
sourceOptions: [
{ value: "demo", label: "Load Demo Data", icon: "🧪" },
],
default: () => <div>ConnectionWizard</div>,
}));

describe("HomeView", () => {
beforeEach(() => {
vi.clearAllMocks();
setActiveDb(null);
setDatabases([]);
setNodes([]);
setEdges([]);
setSchema(null);
});

it("renders welcome section", () => {
const { container } = render(() => <HomeView />);
expect(container.querySelector(".welcome-section")).toBeInTheDocument();
});

it("displays welcome title", () => {
render(() => <HomeView />);
expect(screen.getByText(/welcome to sensibledb/i)).toBeInTheDocument();
});

it("shows connect your data section", () => {
render(() => <HomeView />);
expect(screen.getAllByText(/connect your data/i).length).toBeGreaterThan(0);
});

it("shows import your data card", () => {
render(() => <HomeView />);
expect(screen.getAllByText(/import your data/i).length).toBeGreaterThan(0);
});

it("has tour button", () => {
render(() => <HomeView />);
expect(screen.getByRole("button", { name: /take a tour/i })).toBeInTheDocument();
});

it("has connect data button", () => {
render(() => <HomeView />);
expect(screen.getByRole("button", { name: /connect your data/i })).toBeInTheDocument();
});

it("shows demo section", () => {
render(() => <HomeView />);
expect(screen.getByText(/try a demo database/i)).toBeInTheDocument();
});

it("displays demo card titles", () => {
render(() => <HomeView />);
expect(screen.getByText(/health patterns/i)).toBeInTheDocument();
expect(screen.getByText(/project management/i)).toBeInTheDocument();
});

it("displays demo card descriptions", () => {
render(() => <HomeView />);
expect(screen.getByText(/track symptoms, triggers/i)).toBeInTheDocument();
expect(screen.getByText(/see how team members/i)).toBeInTheDocument();
});

it("shows demo item counts", () => {
render(() => <HomeView />);
expect(screen.getAllByText(/items/i).length).toBeGreaterThan(0);
});

it("shows demo connection counts", () => {
render(() => <HomeView />);
expect(screen.getAllByText(/connections/i).length).toBeGreaterThan(0);
});

it("has explore button on demo cards", () => {
render(() => <HomeView />);
expect(screen.getAllByRole("button", { name: /explore/i }).length).toBeGreaterThan(0);
});

it("shows question suggestions on demo cards", () => {
render(() => <HomeView />);
expect(screen.getByText(/what triggers fatigue?/i)).toBeInTheDocument();
expect(screen.getByText(/show me all symptoms/i)).toBeInTheDocument();
});
});
38 changes: 38 additions & 0 deletions sensibledb-explorer/src/frontend/src/lib/useAppInit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, it, expect, vi } from "vitest";
import { useAppInitialization, useKeyboardShortcuts } from "./useAppInit";

vi.mock("../lib/api", () => ({
dbList: vi.fn().mockResolvedValue([]),
nodeList: vi.fn().mockResolvedValue([]),
edgeList: vi.fn().mockResolvedValue([]),
schemaGet: vi.fn().mockResolvedValue({ node_labels: [], edge_types: [], indexes: [], vector_indexes: [] }),
logError: vi.fn().mockResolvedValue(undefined),
}));

vi.mock("../components/onboarding/GuidedTour", () => ({
isTourCompleted: vi.fn().mockReturnValue(true),
}));

describe("useAppInitialization", () => {
it("exports useAppInitialization function", () => {
expect(typeof useAppInitialization).toBe("function");
});

it("returns loadDbData function", () => {
const setDatabases = vi.fn();
const setActiveDb = vi.fn();
const setNodes = vi.fn();
const setEdges = vi.fn();
const setSchema = vi.fn();

const result = useAppInitialization(setDatabases, setActiveDb, setNodes, setEdges, setSchema);
expect(result).toHaveProperty("loadDbData");
expect(typeof result.loadDbData).toBe("function");
});
});

describe("useKeyboardShortcuts", () => {
it("exports useKeyboardShortcuts function", () => {
expect(typeof useKeyboardShortcuts).toBe("function");
});
});
Loading
Loading