test(FN-5151): complete Step 5 — add autosize regression coverage

Fusion-Task-Id: FN-5151
Fusion-Task-Lineage: 17ebfc3f-6b6d-44a6-8243-7e8b8b140057
This commit is contained in:
Fusion (runfusion.ai)
2026-05-19 10:26:17 -07:00
committed by gsxdsm
parent a34914ede6
commit e0b82fda6b
4 changed files with 144 additions and 12 deletions

View File

@@ -1,9 +1,11 @@
import { describe, it, expect, vi } from "vitest";
import { describe, it, expect, vi, afterEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
import { AgentOnboardingModal } from "../AgentOnboardingModal";
let streamHandlers: any;
let respondCount = 0;
const originalScrollHeightDescriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "scrollHeight");
vi.mock("../../api", () => ({
startAgentOnboardingStreaming: vi.fn().mockResolvedValue({ sessionId: "onb-1" }),
@@ -34,6 +36,16 @@ vi.mock("../../api", () => ({
createAgent: vi.fn().mockResolvedValue({ id: "agent-1" }),
}));
afterEach(() => {
respondCount = 0;
streamHandlers = undefined;
if (originalScrollHeightDescriptor) {
Object.defineProperty(HTMLTextAreaElement.prototype, "scrollHeight", originalScrollHeightDescriptor);
} else {
Reflect.deleteProperty(HTMLTextAreaElement.prototype, "scrollHeight");
}
});
describe("AgentOnboardingModal", () => {
it("walks onboarding flow through summary and create", async () => {
const onCreated = vi.fn();
@@ -65,4 +77,40 @@ describe("AgentOnboardingModal", () => {
expect(onCreated).toHaveBeenCalled();
});
});
describe("autosize", () => {
it("grows the intent textarea up to the 640px cap", async () => {
Object.defineProperty(HTMLTextAreaElement.prototype, "scrollHeight", {
configurable: true,
get() {
const value = (this as HTMLTextAreaElement).value;
if (!value) return 24;
if (value.includes("cap")) return 800;
return 200;
},
});
render(
<AgentOnboardingModal
isOpen={true}
onClose={vi.fn()}
onCreated={vi.fn()}
addToast={vi.fn()}
existingAgents={[]}
/>,
);
const textarea = screen.getByLabelText("What do you want this agent to do?") as HTMLTextAreaElement;
await userEvent.type(textarea, "Draft onboarding intent");
await waitFor(() => {
expect(textarea.style.height).toBe("200px");
});
await userEvent.type(textarea, " cap");
await waitFor(() => {
expect(textarea.style.height).toBe("640px");
});
});
});
});

View File

@@ -1,4 +1,4 @@
import { describe, it, expect, vi } from "vitest";
import { describe, it, expect, vi, afterEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
import { MessageComposer } from "../MessageComposer";
@@ -13,6 +13,16 @@ const defaultProps = {
addToast: vi.fn(),
};
const originalScrollHeightDescriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "scrollHeight");
afterEach(() => {
if (originalScrollHeightDescriptor) {
Object.defineProperty(HTMLTextAreaElement.prototype, "scrollHeight", originalScrollHeightDescriptor);
} else {
Reflect.deleteProperty(HTMLTextAreaElement.prototype, "scrollHeight");
}
});
describe("MessageComposer autosize", () => {
it("grows and caps height as content wraps, then resets for short content", async () => {
render(<MessageComposer {...defaultProps} />);
@@ -23,7 +33,8 @@ describe("MessageComposer autosize", () => {
get() {
const value = (this as HTMLTextAreaElement).value;
if (!value || value.length <= 4) return 24;
if (value.includes("\n\n")) return 900;
if (value.includes("\n\nline three")) return 900;
if (value.includes("line medium")) return 500;
return 180;
},
});
@@ -31,12 +42,17 @@ describe("MessageComposer autosize", () => {
await userEvent.type(textarea, "line one\nline two");
await waitFor(() => {
expect(Number.parseInt(textarea.style.height, 10)).toBeGreaterThanOrEqual(68);
expect(Number.parseInt(textarea.style.height, 10)).toBeLessThanOrEqual(320);
expect(Number.parseInt(textarea.style.height, 10)).toBeLessThanOrEqual(640);
});
await userEvent.type(textarea, "\nline medium");
await waitFor(() => {
expect(textarea.style.height).toBe("500px");
});
await userEvent.type(textarea, "\n\nline three");
await waitFor(() => {
expect(textarea.style.height).toBe("320px");
expect(textarea.style.height).toBe("640px");
});
await userEvent.clear(textarea);

View File

@@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
import { PlanningModeModal } from "../PlanningModeModal";
import {
@@ -79,7 +79,17 @@ vi.mock("../../hooks/useSessionLock", () => ({
useSessionLock: () => ({ isLockedByOther: false, takeControl: vi.fn(), isLoading: false }),
}));
const originalScrollHeightDescriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "scrollHeight");
describe("PlanningModeModal autosize", () => {
afterEach(() => {
if (originalScrollHeightDescriptor) {
Object.defineProperty(HTMLTextAreaElement.prototype, "scrollHeight", originalScrollHeightDescriptor);
} else {
Reflect.deleteProperty(HTMLTextAreaElement.prototype, "scrollHeight");
}
});
beforeEach(() => {
vi.clearAllMocks();
mockConfirm.mockResolvedValue(true);
@@ -115,7 +125,8 @@ describe("PlanningModeModal autosize", () => {
get() {
const value = (this as HTMLTextAreaElement).value;
if (!value) return 24;
if (value.split("\n").length > 5) return 800;
if (value.includes("line 7")) return 900;
if (value.includes("line 5")) return 500;
return 180;
},
});
@@ -123,12 +134,69 @@ describe("PlanningModeModal autosize", () => {
await userEvent.type(textarea, "line 1\nline 2");
await waitFor(() => {
expect(Number.parseInt(textarea.style.height, 10)).toBeGreaterThanOrEqual(120);
expect(Number.parseInt(textarea.style.height, 10)).toBeLessThanOrEqual(320);
expect(Number.parseInt(textarea.style.height, 10)).toBeLessThanOrEqual(640);
});
await userEvent.type(textarea, "\nline 3\nline 4\nline 5\nline 6");
await userEvent.type(textarea, "\nline 3\nline 4\nline 5");
await waitFor(() => {
expect(textarea.style.height).toBe("320px");
expect(textarea.style.height).toBe("500px");
});
await userEvent.type(textarea, "\nline 6\nline 7");
await waitFor(() => {
expect(textarea.style.height).toBe("640px");
});
});
it("keeps SummaryView collapsed and expanded autosize caps distinct", async () => {
Object.defineProperty(HTMLTextAreaElement.prototype, "scrollHeight", {
configurable: true,
get() {
return 900;
},
});
mockFetchAiSession.mockResolvedValueOnce({
id: "session-complete-1",
type: "planning",
status: "complete",
title: "Resume-ready planning output",
inputPayload: JSON.stringify({ initialPlan: "Build resilient planning resume" }),
conversationHistory: "[]",
currentQuestion: null,
result: JSON.stringify({
title: "Resume-ready planning output",
description: "Recovered summary description from persisted session",
suggestedSize: "L",
suggestedDependencies: ["FN-001"],
keyDeliverables: ["Deliverable A", "Deliverable B"],
}),
thinkingOutput: "",
error: null,
projectId: null,
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
});
render(
<PlanningModeModal
isOpen={true}
onClose={vi.fn()}
onTaskCreated={vi.fn()}
onTasksCreated={vi.fn()}
tasks={mockTasks}
resumeSessionId="session-complete-1"
/>
);
const description = await screen.findByDisplayValue("Recovered summary description from persisted session") as HTMLTextAreaElement;
await waitFor(() => {
expect(description.style.height).toBe("640px");
});
fireEvent.click(screen.getByText("Expand"));
await waitFor(() => {
expect(description.style.height).toBe("800px");
});
});
});

View File

@@ -12,7 +12,7 @@ const qualityAppTests = [
"app/api/**/*.test.ts",
// Representative workflow/component coverage. Exhaustive modal/view suites
// stay available in the full `dashboard-app` project.
"app/components/__tests__/{ActiveAgentsPanel,ActivityLogModal,AgentMentionPopup,AgentMetricsBar,AgentReflectionsTab,AgentTokenStatsPanel,App,AuthTokenRecoveryDialog,Board,board-mobile-view-switch,ChatView,ChatView.autosize,ChatView.chat-input-autosize,ChatView.default-model-icon,ChatView.draft,ChatView.rooms,ChatView.swipe-back,Column,ConfirmDialog,ConversationHistory,DashboardLoader,DevServerView.mobile,DirectoryPicker,DuplicateWarningModal,ErrorBoundary,ExecutorStatusBar,FileBrowser,FileEditor,GitHubBadge,InlineCreateCard,LoginInstructions,MemoryView,MessageComposer,MobileNavBar,NewTaskModal,NodeCard,NodeHealthDot,NodeStatusIndicator,PrChecksList,PrCreateModal,PrCreateModal.layout,ProjectCard,ProjectSelector,ProviderIcon,PrPanel,PrPanel.merge,PrPanel.reviews,QuickChatFAB,ReliabilityView,ResearchView,SecretsView,SettingsModal,SettingsModal.worktrunk,StashRecoveryView,TaskCard,TaskCard.badge-height,TaskChangesTab,TaskComments,TaskDetailModal,TaskDetailModal.github-tracking-header,TaskDetailModal.github-tracking-stale,TaskDetailModal.rebind-banner,TaskDocumentsTab,TaskForm,TaskIdIntegrityBanner,TrackingRepoSelect,WorkflowResultsTab,WorktrunkInstallApprovalDetails}.test.tsx",
"app/components/__tests__/{ActiveAgentsPanel,ActivityLogModal,AgentMentionPopup,AgentMetricsBar,AgentOnboardingModal,AgentReflectionsTab,AgentTokenStatsPanel,App,AuthTokenRecoveryDialog,Board,board-mobile-view-switch,ChatView,ChatView.autosize,ChatView.chat-input-autosize,ChatView.default-model-icon,ChatView.draft,ChatView.rooms,ChatView.swipe-back,Column,ConfirmDialog,ConversationHistory,DashboardLoader,DevServerView.mobile,DirectoryPicker,DuplicateWarningModal,ErrorBoundary,ExecutorStatusBar,FileBrowser,FileEditor,GitHubBadge,InlineCreateCard,LoginInstructions,MemoryView,MessageComposer,MessageComposer.autosize,MobileNavBar,NewTaskModal,NodeCard,NodeHealthDot,NodeStatusIndicator,PlanningModeModal.autosize,PrChecksList,PrCreateModal,PrCreateModal.layout,ProjectCard,ProjectSelector,ProviderIcon,PrPanel,PrPanel.merge,PrPanel.reviews,QuickChatFAB,ReliabilityView,ResearchView,SecretsView,SettingsModal,SettingsModal.worktrunk,StashRecoveryView,TaskCard,TaskCard.badge-height,TaskChangesTab,TaskComments,TaskDetailModal,TaskDetailModal.github-tracking-header,TaskDetailModal.github-tracking-stale,TaskDetailModal.rebind-banner,TaskDocumentsTab,TaskForm,TaskIdIntegrityBanner,TrackingRepoSelect,WorkflowResultsTab,WorktrunkInstallApprovalDetails}.test.tsx",
// Hooks and utilities are fast, user-visible state/formatting behavior.
"app/context/**/*.test.tsx",
"app/hooks/__tests__/{useAgents,useAgentLogs,useAppSettings,useAuthOnboarding,useConfirm,useCurrentProject,useNodes,useNodeSettingsSync,useProjects,useQuickChat,useTasks,useTerminalSessions,useTheme,useToast,useUsageData,useViewState}.test.{ts,tsx}",