/** * AI Software Factory — E2E: Confluence P2 T-153/T-154 (⌘K home) * * Covers the ⌘K search dialog's two new empty-input sections (pure-web on * the done + integration-tested users API): * * - T-153 "Recently viewed": opening a page (or database) records the view * (fire-and-forget PUT /users/me/recent); the ⌘K home lists it with a * resolved title; clicking navigates (database → /databases/:id, page → * /page/:id). * - T-154 "Saved searches": the "Save" button persists the committed query * (POST /users/me/saved-searches); the home lists the chip; clicking a * chip re-runs its query (fills the input). * * Selector convention (additive to search.spec.ts's pinned testids): * data-testid="search-recent-viewed" the section (empty input) * data-testid="search-recent-viewed-item" a recently-viewed row * data-testid="search-save-current" the Save button (query set) * data-testid="search-saved" the saved-searches section * data-testid="search-saved-item" a saved-search chip * data-testid="search-saved-delete" a chip's delete button */ import { test, expect } from "../fixtures"; import { createPage } from "../utils/api"; import { waitForCondition } from "../utils/wait"; const unique = (prefix: string) => `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; /** True once PUT /users/me/recent has persisted (itemType, refId) for the * demo user (the record is fire-and-forget from the page open). */ async function recentViewedRecorded( api: { get: (u: string) => Promise<{ json: () => Promise }> }, itemType: string, refId: string, ): Promise { const res = await api.get("/api/v1/users/me/recent"); const body = (await res.json()) as { data: Array<{ itemType: string; refId: string }> }; return (body.data ?? []).some((it) => it.itemType === itemType && it.refId === refId); } test.describe("Confluence P2 — ⌘K home (T-153/T-154)", () => { test("opening a page records it; the ⌘K home lists + resolves + navigates it", async ({ authPage, api, freshWorkspace, }) => { const ws = await freshWorkspace("Recent Viewed Space"); const token = unique("RECENTVIEW"); const page = await createPage(api, ws.id, { title: `${token} Launch Plan` }); // Open the page in the browser → PageCanvas records the view (page kind). await authPage.goto(`/w/${ws.id}/page/${page.id}`); await authPage.getByText(`${token} Launch Plan`).first().waitFor({ timeout: 15_000 }); // Wait for the fire-and-forget record to land server-side. await waitForCondition( authPage, () => recentViewedRecorded(api, "page", page.id), { timeoutMs: 10_000, message: "recent-viewed record did not persist" }, ); // ⌘K home (empty input) → the "Recently viewed" section with the RESOLVED // title; clicking it navigates to the page route. await authPage.getByTestId("search-button").click(); await expect(authPage.getByTestId("search-dialog")).toBeVisible(); await expect(authPage.getByTestId("search-recent-viewed")).toBeVisible(); const item = authPage.getByTestId("search-recent-viewed-item").filter({ hasText: token }).first(); await expect(item).toBeVisible({ timeout: 10_000 }); await item.click(); await authPage.waitForURL(/\/page\//, { timeout: 15_000 }); expect(authPage.url()).toContain(`/page/${page.id}`); }); test("a database open is recorded as a database; the home navigates to /databases/:id", async ({ authPage, api, freshWorkspace, }) => { const ws = await freshWorkspace("Recent DB Space"); const token = unique("RECENTDB"); // Create the database through the NORMAL page route (POST // /workspaces/:id/databases does not exist — a recorded conflict, see // ai-database.spec.ts). The page carries the database's `databaseId`. const dbPage = (await ( await api.post(`/api/v1/workspaces/${ws.id}/pages`, { data: { title: `${token} Inventory`, type: "database" }, }) ).json()) as { id: string; databaseId?: string }; if (!dbPage.databaseId) throw new Error(`no databaseId: ${JSON.stringify(dbPage)}`); const dbId = dbPage.databaseId; // Open the database via its dedicated route → DatabasePageView records the // view (database kind). await authPage.goto(`/w/${ws.id}/databases/${dbId}`); await waitForCondition( authPage, () => recentViewedRecorded(api, "database", dbId), { timeoutMs: 10_000, message: "recent-viewed database record did not persist" }, ); await authPage.getByTestId("search-button").click(); const item = authPage .getByTestId("search-recent-viewed-item") .filter({ hasText: token }) .first(); await expect(item).toBeVisible({ timeout: 10_000 }); await item.click(); await authPage.waitForURL(/\/databases\//, { timeout: 15_000 }); expect(authPage.url()).toContain(`/databases/${dbId}`); }); test("Save persists the committed query; the home chip re-runs it", async ({ authPage, api, freshWorkspace, }) => { const ws = await freshWorkspace("Saved Search Space"); const token = unique("SAVEDSRCH"); await createPage(api, ws.id, { title: `${token} Budget` }); await authPage.goto(`/w/${ws.id}`); await authPage.getByTestId("search-button").click(); await expect(authPage.getByTestId("search-dialog")).toBeVisible(); // Commit the query (fill + Enter) → results, then the Save button. const input = authPage.getByTestId("search-input"); await input.fill(token); await expect(authPage.getByTestId("search-result").first()).toBeVisible({ timeout: 10_000, }); const save = authPage.getByTestId("search-save-current"); await expect(save).toBeVisible(); await save.click(); // The save persisted (per-user, server-side). await waitForCondition( authPage, async () => { const res = await api.get("/api/v1/users/me/saved-searches"); const body = (await res.json()) as { data: Array<{ name: string; queryJson: { q: string } }> }; return (body.data ?? []).some((s) => s.queryJson?.q === token); }, { timeoutMs: 10_000, message: "saved search did not persist" }, ); // Back to the ⌘K home (clear the input) → the chip is listed. await input.fill(""); const chip = authPage.getByTestId("search-saved-item").filter({ hasText: token }).first(); await expect(chip).toBeVisible({ timeout: 10_000 }); // Clicking the chip re-runs the saved query (fills the input). await chip.click(); await expect(input).toHaveValue(token); }); });