feat(FN-4011): align room input keyboard handlers
Completes Step 1 of mobile touch-focus parity by aligning room input keyboard handlers in ChatView, with a regression test covering mobile room and direct composer touch-focus behavior. Fusion-Task-Id: FN-4011
This commit is contained in:
5
.changeset/fn-3893-run-now-header.md
Normal file
5
.changeset/fn-3893-run-now-header.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Move agent Run Now control into the agent detail header next to lifecycle buttons.
|
||||
@@ -1612,7 +1612,6 @@ function RunsTab({
|
||||
}
|
||||
}, [initialRunId, preferActiveRun, runs, isLoadingRuns, handleRunClick]);
|
||||
|
||||
|
||||
const handleStopRun = async () => {
|
||||
const shouldStop = await confirm({
|
||||
title: "Stop Active Run",
|
||||
@@ -1633,7 +1632,6 @@ function RunsTab({
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (isLoadingRuns && runs.length === 0) {
|
||||
return (
|
||||
<div className="runs-tab">
|
||||
|
||||
@@ -2164,6 +2164,17 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
|
||||
value={messageInput}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onKeyUp={handleInputKeyUp}
|
||||
onClick={handleInputSelectionChange}
|
||||
onBlur={handleInputBlur}
|
||||
onFocus={handleInputFocus}
|
||||
onTouchStart={(event) => {
|
||||
if (typeof window === "undefined") return;
|
||||
if (window.innerWidth > 768) return;
|
||||
if (document.activeElement === event.currentTarget) return;
|
||||
event.preventDefault();
|
||||
event.currentTarget.focus({ preventScroll: true });
|
||||
}}
|
||||
rows={1}
|
||||
data-testid="chat-input"
|
||||
/>
|
||||
|
||||
@@ -283,6 +283,7 @@ describe("AgentDetailView", () => {
|
||||
createdAt: "2024-01-01T00:00:00.000Z",
|
||||
updatedAt: "2024-01-01T00:00:00.000Z",
|
||||
} as any);
|
||||
mockStartAgentRun.mockResolvedValue({ id: "run-003" } as any);
|
||||
// Default: no budget limit configured
|
||||
mockFetchAgentBudgetStatus.mockResolvedValue({
|
||||
agentId: "agent-001",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { act, render, screen, waitFor, within } from "@testing-library/react";
|
||||
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
||||
import { userEvent } from "@testing-library/user-event";
|
||||
import { ChatView } from "../ChatView";
|
||||
import * as useChatModule from "../../hooks/useChat";
|
||||
@@ -231,6 +231,40 @@ describe("ChatView — rooms (FN-3805..FN-3811 contract)", () => {
|
||||
mediaSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("keeps room composer touch-focus behavior in parity with direct chat on mobile", async () => {
|
||||
const mediaSpy = mockMobileViewport();
|
||||
setup(
|
||||
{
|
||||
activeSession,
|
||||
messages: [{ id: "msg-1", sessionId: activeSession.id, role: "assistant", content: "Direct hello", createdAt: "2026-04-08T00:00:00.000Z" }],
|
||||
},
|
||||
{
|
||||
activeRoom: roomA,
|
||||
messages: [{ id: "rmsg-1", roomId: roomA.id, role: "assistant", content: "Room hello", createdAt: "2026-04-08T00:00:00.000Z", senderAgentId: "agent-1", mentions: [] }],
|
||||
},
|
||||
);
|
||||
|
||||
render(<ChatView projectId="proj-123" addToast={vi.fn()} experimentalFeatures={{ chatRooms: true }} />);
|
||||
|
||||
const roomInput = screen.getByTestId("chat-input") as HTMLTextAreaElement;
|
||||
const roomFocusSpy = vi.spyOn(roomInput, "focus");
|
||||
await act(async () => {
|
||||
fireEvent.touchStart(roomInput);
|
||||
});
|
||||
expect(roomFocusSpy).toHaveBeenCalledWith({ preventScroll: true });
|
||||
|
||||
await userEvent.click(screen.getByTestId("chat-sidebar-scope-direct"));
|
||||
|
||||
const directInput = screen.getByTestId("chat-input") as HTMLTextAreaElement;
|
||||
const directFocusSpy = vi.spyOn(directInput, "focus");
|
||||
await act(async () => {
|
||||
fireEvent.touchStart(directInput);
|
||||
});
|
||||
expect(directFocusSpy).toHaveBeenCalledWith({ preventScroll: true });
|
||||
|
||||
mediaSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("applies keyboard-active thread layout in room mode on mobile and preserves direct-chat parity", async () => {
|
||||
const mediaSpy = mockMobileViewport();
|
||||
const { listeners, mockVV } = mockMobileVisualViewport({ innerHeight: 800, vvHeight: 800 });
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { LocalRuntimeManager } from "../local-runtime";
|
||||
|
||||
describe("desktop runtime package resolution", () => {
|
||||
it("resolves @fusion/core and @fusion/dashboard from desktop vitest project", async () => {
|
||||
const core = await import("@fusion/core");
|
||||
const dashboard = await import("@fusion/dashboard");
|
||||
|
||||
expect(core.TaskStore).toBeTypeOf("function");
|
||||
expect(dashboard.createServer).toBeTypeOf("function");
|
||||
});
|
||||
|
||||
it("can instantiate LocalRuntimeManager without relying on built dist artifacts", () => {
|
||||
const manager = new LocalRuntimeManager({ rootDir: process.cwd() });
|
||||
expect(manager.getStatus().state).toBe("stopped");
|
||||
});
|
||||
});
|
||||
@@ -3,13 +3,15 @@ import { resolve } from "node:path";
|
||||
import { computeMaxWorkers } from "../core/src/__test-utils__/vitest-workers";
|
||||
|
||||
const maxWorkers = computeMaxWorkers();
|
||||
const fusionAliases = {
|
||||
"@fusion/core": resolve(__dirname, "../core/src/index.ts"),
|
||||
"@fusion/dashboard": resolve(__dirname, "../dashboard/src/index.ts"),
|
||||
"@fusion/engine": resolve(__dirname, "../engine/src/index.ts"),
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@fusion/core": resolve(__dirname, "../core/src/index.ts"),
|
||||
"@fusion/dashboard": resolve(__dirname, "../dashboard/src/index.ts"),
|
||||
},
|
||||
alias: fusionAliases,
|
||||
},
|
||||
test: {
|
||||
setupFiles: [resolve(__dirname, "../core/src/__test-utils__/vitest-setup.ts")],
|
||||
@@ -23,6 +25,9 @@ export default defineConfig({
|
||||
passWithNoTests: true,
|
||||
projects: [
|
||||
{
|
||||
resolve: {
|
||||
alias: fusionAliases,
|
||||
},
|
||||
test: {
|
||||
name: "desktop",
|
||||
include: ["src/__tests__/**/*.test.ts"],
|
||||
@@ -31,6 +36,9 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: {
|
||||
alias: fusionAliases,
|
||||
},
|
||||
test: {
|
||||
name: "desktop-renderer",
|
||||
include: ["src/renderer/**/*.test.ts", "src/renderer/**/*.test.tsx"],
|
||||
|
||||
Reference in New Issue
Block a user