test(web): cover integrated note runtime
This commit is contained in:
parent
b4634e9367
commit
5f3b32bb93
@ -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"
|
||||
);
|
||||
|
||||
@ -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"
|
||||
);
|
||||
|
||||
161
web/src/lib/notes-client.test.ts
Normal file
161
web/src/lib/notes-client.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user