test(web): cover integrated note runtime

This commit is contained in:
saravanakumardb1 2026-03-10 13:03:43 -07:00
parent b4634e9367
commit 5f3b32bb93
3 changed files with 217 additions and 4 deletions

View File

@ -2,6 +2,8 @@ import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import SearchPage from "./page";
const listNoteSummariesMock = vi.fn();
vi.mock("next/link", () => ({
default: ({ href, children, ...props }: React.ComponentProps<"a"> & { href: string }) => (
<a href={href} {...props}>
@ -10,15 +12,32 @@ vi.mock("next/link", () => ({
),
}));
vi.mock("@/lib/notes-client", () => ({
listNoteSummaries: () => listNoteSummariesMock(),
}));
describe("SearchPage", () => {
it("renders an accessible search field, saved searches, and note links", () => {
it("renders an accessible search field, saved searches, and note links", async () => {
listNoteSummariesMock.mockResolvedValue([
{
id: "note-prd-cutline",
workspaceId: "workspace-product",
title: "MVP cut line for agentic notes launch",
excerpt: "Define which note, task, search, and approval flows must exist before wider rollout.",
status: "active",
tags: ["mvp", "launch", "scope"],
updatedAt: "2026-03-10T14:30:00.000Z",
updatedBy: "Product Lead",
},
]);
render(<SearchPage />);
expect(screen.getByRole("heading", { level: 1, name: "Search" })).toBeInTheDocument();
expect(screen.getByRole("textbox", { name: "Search notes" })).toBeInTheDocument();
expect(screen.getByText("Saved searches")).toBeInTheDocument();
expect(screen.getByText("Launch readiness")).toBeInTheDocument();
expect(screen.getByRole("link", { name: /MVP cut line for agentic notes launch/i })).toHaveAttribute(
expect(await screen.findByRole("link", { name: /MVP cut line for agentic notes launch/i })).toHaveAttribute(
"href",
"/notes/note-prd-cutline"
);

View File

@ -2,6 +2,9 @@ import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import WorkspacesPage from "./page";
const listNoteSummariesMock = vi.fn();
const listWorkspaceSummariesMock = vi.fn();
vi.mock("next/link", () => ({
default: ({ href, children, ...props }: React.ComponentProps<"a"> & { href: string }) => (
<a href={href} {...props}>
@ -10,15 +13,45 @@ vi.mock("next/link", () => ({
),
}));
vi.mock("@/lib/notes-client", () => ({
listNoteSummaries: () => listNoteSummariesMock(),
listWorkspaceSummaries: () => listWorkspaceSummariesMock(),
}));
describe("WorkspacesPage", () => {
it("renders an accessible workspace filter and saved workspace views", () => {
it("renders an accessible workspace filter and saved workspace views", async () => {
listNoteSummariesMock.mockResolvedValue([
{
id: "note-prd-cutline",
workspaceId: "workspace-product",
title: "MVP cut line for agentic notes launch",
excerpt: "Define which note, task, search, and approval flows must exist before wider rollout.",
status: "active",
tags: ["mvp", "launch", "scope"],
updatedAt: "2026-03-10T14:30:00.000Z",
updatedBy: "Product Lead",
},
]);
listWorkspaceSummariesMock.mockResolvedValue([
{
id: "workspace-product",
name: "Product Strategy",
description: "PRDs, roadmap cuts, launch tradeoffs, and operating decisions.",
owner: "Product Lead",
noteCount: 1,
visibility: "shared",
updatedAt: "2026-03-10T14:35:00.000Z",
tags: ["strategy", "launch", "roadmap"],
},
]);
render(<WorkspacesPage />);
expect(screen.getByRole("heading", { level: 1, name: "Workspaces" })).toBeInTheDocument();
expect(screen.getByRole("textbox", { name: "Filter workspaces" })).toBeInTheDocument();
expect(screen.getByText("Saved views")).toBeInTheDocument();
expect(screen.getByText("All workspaces")).toBeInTheDocument();
expect(screen.getByRole("link", { name: /MVP cut line for agentic notes launch/i })).toHaveAttribute(
expect(await screen.findByRole("link", { name: /MVP cut line for agentic notes launch/i })).toHaveAttribute(
"href",
"/notes/note-prd-cutline"
);

View File

@ -0,0 +1,161 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
const fetchMock = vi.fn();
const extractSuggestedTasksMock = vi.fn();
vi.mock("@bytelyst/api-client", () => ({
createApiClient: () => ({
fetch: fetchMock,
}),
}));
vi.mock("@/lib/extraction-client", () => ({
extractSuggestedTasks: (...args: unknown[]) => extractSuggestedTasksMock(...args),
}));
import { getNoteDetail } from "@/lib/notes-client";
describe("getNoteDetail", () => {
beforeEach(() => {
fetchMock.mockReset();
extractSuggestedTasksMock.mockReset();
});
it("merges backend tasks with extracted suggestions, preserves artifact blob metadata, and normalizes review state", async () => {
fetchMock.mockResolvedValueOnce({
items: [
{
id: "workspace-1",
name: "Product",
members: [{ userId: "owner-1", role: "owner" }],
updatedAt: "2026-03-10T12:00:00.000Z",
updatedBy: "owner-1",
},
],
});
fetchMock.mockResolvedValueOnce({
items: [
{
id: "note-1",
workspaceId: "workspace-1",
title: "Launch note",
body: "Sarah agreed to handle the testing by Friday.",
status: "active",
tags: ["launch"],
updatedAt: "2026-03-10T12:01:00.000Z",
updatedBy: "editor-1",
createdBy: "editor-1",
sourceType: "manual",
},
],
});
fetchMock.mockResolvedValueOnce({
items: [
{
id: "task-1",
noteId: "note-1",
title: "Review approval UX cut line",
status: "open",
source: "manual",
},
],
});
fetchMock.mockResolvedValueOnce({
items: [
{
id: "artifact-1",
noteId: "note-1",
artifactType: "file",
title: "Launch brief.pdf",
description: "Ready for review",
blobPath: "bytelyst-notes/user-1/launch-brief.pdf",
contentType: "application/pdf",
sizeBytes: 2048,
},
],
});
fetchMock.mockResolvedValueOnce({
items: [
{
id: "action-1",
noteId: "note-1",
actorId: "agent-1",
actorType: "agent",
actionType: "summarize",
state: "draft",
afterSummary: "Drafted a summary update.",
updatedAt: "2026-03-10T12:03:00.000Z",
},
{
id: "action-2",
noteId: "note-1",
actorId: "agent-2",
actorType: "agent",
actionType: "extract_tasks",
state: "approved",
afterSummary: "Approved task extraction.",
updatedAt: "2026-03-10T12:02:00.000Z",
},
],
});
extractSuggestedTasksMock.mockResolvedValue([
{
id: "extract-review-0",
title: "Review approval UX cut line",
status: "todo",
source: "agent",
},
{
id: "extract-test-1",
title: "Sarah agreed to handle the testing",
status: "todo",
source: "agent",
},
]);
const note = await getNoteDetail("note-1");
expect(note).not.toBeNull();
expect(extractSuggestedTasksMock).toHaveBeenCalledWith(
"Sarah agreed to handle the testing by Friday."
);
expect(note?.metadata.reviewState).toBe("none");
expect(note?.tasks).toEqual([
{
id: "task-1",
title: "Review approval UX cut line",
status: "todo",
source: "manual",
},
{
id: "extract-test-1",
title: "Sarah agreed to handle the testing",
status: "todo",
source: "agent",
},
]);
expect(note?.artifacts).toEqual([
{
id: "artifact-1",
name: "Launch brief.pdf",
type: "file",
status: "ready",
blobPath: "bytelyst-notes/user-1/launch-brief.pdf",
contentType: "application/pdf",
sizeBytes: 2048,
},
]);
expect(note?.timeline[0]?.status).toBe("draft");
});
it("returns null when the note is missing", async () => {
fetchMock.mockResolvedValueOnce({ items: [] });
fetchMock.mockResolvedValueOnce({ items: [] });
const note = await getNoteDetail("missing-note");
expect(note).toBeNull();
expect(extractSuggestedTasksMock).not.toHaveBeenCalled();
});
});