Skip to content
Merged
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
123 changes: 123 additions & 0 deletions app/e2e/widget-lab.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -729,4 +729,127 @@ test.describe("Widget Lab", () => {
).not.toBeVisible();
});
});

// ── Widget Lab consumption: duplicate, filter, search ───────────────

test.describe("Widget Lab consumption", () => {
let templateIds: string[] = [];

test.beforeEach(async ({ page }) => {
// Create two templates via API for filter/search tests
const neo4jBar = await page.request.post("/api/widget-templates", {
data: {
name: "Neo4j Bar Template",
chartType: "bar",
connectorType: "neo4j",
query: "MATCH (m:Movie) RETURN m.title AS label, count(*) AS value",
},
});
const pgTable = await page.request.post("/api/widget-templates", {
data: {
name: "PostgreSQL Table Template",
chartType: "table",
connectorType: "postgresql",
query: "SELECT title FROM movies LIMIT 5",
},
});
const t1 = (await neo4jBar.json()).data;
const t2 = (await pgTable.json()).data;
templateIds = [t1.id, t2.id];
});

test.afterEach(async ({ page }) => {
for (const id of templateIds) {
await page.request.delete(`/api/widget-templates/${id}`);
}
templateIds = [];
});
Comment on lines +738 to +766

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make fixture data unique and assert API setup/cleanup responses.

Line 632 and Line 640 use fixed names, which can collide with stale/retry data and make duplicate/filter assertions ambiguous. Also, setup/teardown API calls (Line 630-Line 645, Line 653) should assert success to fail fast when fixtures don’t load/clean correctly.

Suggested hardening diff
 test.describe("Widget Lab consumption", () => {
   let templateIds: string[] = [];
+  let neo4jTemplateName = "";
+  let pgTemplateName = "";

   test.beforeEach(async ({ page }) => {
+    const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
+    neo4jTemplateName = `Neo4j Bar Template ${suffix}`;
+    pgTemplateName = `PostgreSQL Table Template ${suffix}`;
+
     // Create two templates via API for filter/search tests
     const neo4jBar = await page.request.post("/api/widget-templates", {
       data: {
-        name: "Neo4j Bar Template",
+        name: neo4jTemplateName,
         chartType: "bar",
         connectorType: "neo4j",
         query: "MATCH (m:Movie) RETURN m.title AS label, count(*) AS value",
       },
     });
+    expect(neo4jBar.ok()).toBeTruthy();

     const pgTable = await page.request.post("/api/widget-templates", {
       data: {
-        name: "PostgreSQL Table Template",
+        name: pgTemplateName,
         chartType: "table",
         connectorType: "postgresql",
         query: "SELECT title FROM movies LIMIT 5",
       },
     });
+    expect(pgTable.ok()).toBeTruthy();

     const t1 = (await neo4jBar.json()).data;
     const t2 = (await pgTable.json()).data;
     templateIds = [t1.id, t2.id];
   });

   test.afterEach(async ({ page }) => {
     for (const id of templateIds) {
-      await page.request.delete(`/api/widget-templates/${id}`);
+      const delRes = await page.request.delete(`/api/widget-templates/${id}`);
+      expect(delRes.ok()).toBeTruthy();
     }
     templateIds = [];
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/e2e/widget-lab.spec.ts` around lines 628 - 656, The test setup in
test.beforeEach uses fixed template names and does not assert API responses;
update the POST calls (page.request.post in test.beforeEach) to create unique
names (e.g., append a timestamp or random suffix) so fixtures cannot collide
across runs, capture and assert the response status/body for both neo4jBar and
pgTable (verify success and extract IDs into templateIds), and in test.afterEach
assert each DELETE (page.request.delete) returns a successful status before
clearing templateIds; reference the test.beforeEach/test.afterEach blocks, the
templateIds variable, and the page.request.post/page.request.delete calls when
making these changes.


test("can duplicate a template", async ({ page }) => {
await page.goto("/widget-lab");
const card = page
.locator("[data-testid='template-card']")
.filter({ hasText: "Neo4j Bar Template" })
.first();
await expect(card).toBeVisible({ timeout: 10_000 });

await card.getByLabel("Duplicate").click();

// Duplicate should appear with "(copy)" suffix
await expect(
page.getByText("Neo4j Bar Template (copy)", { exact: true }),
).toBeVisible({ timeout: 10_000 });

// Clean up the duplicate
const res = await page.request.get("/api/widget-templates");
const templates = (await res.json()).data;
const copy = templates.find(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(t: any) => t.name === "Neo4j Bar Template (copy)",
);
Comment on lines +786 to +789

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Check explicit any usage in the changed test file:"
rg -nP '\bany\b' app/e2e/widget-lab.spec.ts

echo
echo "Check TS strict/noImplicitAny config under app/:"
fd -i '^tsconfig.*\.json$' app --exec rg -n '"strict"\s*:\s*true|"noImplicitAny"\s*:\s*true' {}

Repository: alfredo1996/neoboard

Length of output: 683


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check full context of the template lookup at lines 676-679 ==="
sed -n '670,690p' app/e2e/widget-lab.spec.ts

echo
echo "=== Check the /api/widget-templates endpoint to understand response structure ==="
fd -t f '\.(ts|tsx)$' app --exec grep -l 'widget-templates' {} \;

echo
echo "=== Check all occurrences of the template pattern to see if they're all identical ==="
sed -n '126,140p' app/e2e/widget-lab.spec.ts
echo "---"
sed -n '350,365p' app/e2e/widget-lab.spec.ts

Repository: alfredo1996/neoboard

Length of output: 1537


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check the widget-templates API route ==="
cat -n app/src/app/api/widget-templates/route.ts | head -80

echo
echo "=== Check hook that uses widget-templates ==="
cat -n app/src/hooks/use-widget-templates.ts

echo
echo "=== Check if there's a types file for templates ==="
fd -t f 'template' app/src --type f | grep -E '\.(ts|tsx)$' | head -20

Repository: alfredo1996/neoboard

Length of output: 7305


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check apiList response structure ==="
grep -A 20 'export function apiList' app/src/lib/api/api-response.ts

echo
echo "=== Check unwrapResponse ==="
grep -A 15 'export.*unwrapResponse' app/src/lib/api/api-client.ts

echo
echo "=== Check WidgetTemplate type ==="
grep -B 5 -A 20 'export.*WidgetTemplate' app/src/lib/db/schema.ts | head -50

Repository: alfredo1996/neoboard

Length of output: 2372


Remove any and properly type the API response to maintain strict TypeScript.

Line 678 uses any, which violates strict TS mode. The /api/widget-templates endpoint returns a structured envelope with { data, error, meta }, so the response type can be declared explicitly:

type WidgetTemplateListResponse = {
  data: Array<{ id: string; name: string }>;
};
const templates = ((await res.json()) as WidgetTemplateListResponse).data;
const copy = templates.find(
  (t) => t.name === "Neo4j Bar Template (copy)",
);

This eliminates the need for @typescript-eslint/no-explicit-any and strengthens type safety.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/e2e/widget-lab.spec.ts` around lines 676 - 679, Replace the use of any by
declaring an explicit response type for the API result: introduce a type (e.g.,
WidgetTemplateListResponse with data: Array<{ id: string; name: string }>) and
cast the result of res.json() to that type when assigning templates, then use
templates.find((t) => t.name === "Neo4j Bar Template (copy)") to locate copy;
update references to res.json(), templates and copy so the eslint disable and
any are removed and strict TypeScript is preserved.

if (copy) templateIds.push(copy.id);
});

test("can filter templates by chart type", async ({ page }) => {
await page.goto("/widget-lab");
await expect(
page.getByText("Neo4j Bar Template", { exact: true }),
).toBeVisible({ timeout: 10_000 });
await expect(
page.getByText("PostgreSQL Table Template", { exact: true }),
).toBeVisible();

// Filter to bar charts only — shadcn Select uses combobox role
await page.locator("button[role='combobox']").nth(0).click();
await page.getByRole("option", { name: "Bar Chart" }).click();

await expect(
page.getByText("Neo4j Bar Template", { exact: true }),
).toBeVisible();
await expect(
page.getByText("PostgreSQL Table Template", { exact: true }),
).not.toBeVisible();
});

test("can filter templates by connector type", async ({ page }) => {
await page.goto("/widget-lab");
await expect(
page.getByText("Neo4j Bar Template", { exact: true }),
).toBeVisible({ timeout: 10_000 });

// Filter to PostgreSQL only — connector select is the second combobox
await page.locator("button[role='combobox']").nth(1).click();
await page.getByRole("option", { name: /PostgreSQL/i }).click();

await expect(
page.getByText("PostgreSQL Table Template", { exact: true }),
).toBeVisible();
await expect(
page.getByText("Neo4j Bar Template", { exact: true }),
).not.toBeVisible();
});

test("can search templates by name", async ({ page }) => {
await page.goto("/widget-lab");
await expect(
page.getByText("Neo4j Bar Template", { exact: true }),
).toBeVisible({ timeout: 10_000 });

// Search for "PostgreSQL"
await page.getByPlaceholder("Search templates...").fill("PostgreSQL");

await expect(
page.getByText("PostgreSQL Table Template", { exact: true }),
).toBeVisible();
await expect(
page.getByText("Neo4j Bar Template", { exact: true }),
).not.toBeVisible();

// Clear search
await page.getByPlaceholder("Search templates...").clear();
await expect(
page.getByText("Neo4j Bar Template", { exact: true }),
).toBeVisible();
});
});
});
Loading