diff --git a/apps/web/src/components/chat/ComposerPrimaryActions.test.ts b/apps/web/src/components/chat/ComposerPrimaryActions.test.ts index b7624db0a8f..ba416e9fce3 100644 --- a/apps/web/src/components/chat/ComposerPrimaryActions.test.ts +++ b/apps/web/src/components/chat/ComposerPrimaryActions.test.ts @@ -1,6 +1,64 @@ -import { describe, expect, it } from "vite-plus/test"; +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it, vi } from "vite-plus/test"; -import { formatPendingPrimaryActionLabel } from "./ComposerPrimaryActions"; +vi.mock("~/hooks/useSettings", () => ({ + useEnvironmentIdentificationMode: () => "none", +})); +vi.mock("../SidebarStageBackdrop", () => ({ + StageBackdropButtonArt: () => null, + useSidebarStageBackdropVariant: () => null, +})); + +import { ComposerPrimaryActions, formatPendingPrimaryActionLabel } from "./ComposerPrimaryActions"; + +function renderPendingActions(isRunning: boolean) { + return renderToStaticMarkup( + createElement(ComposerPrimaryActions, { + compact: true, + pendingAction: { + questionIndex: 0, + isLastQuestion: true, + canAdvance: true, + isResponding: false, + isComplete: true, + }, + isRunning, + showPlanFollowUpPrompt: false, + promptHasText: false, + isSendBusy: false, + sendDisabledReason: null, + isConnecting: false, + isEnvironmentUnavailable: false, + isPreparingWorktree: false, + hasSendableContent: false, + onPreviousPendingQuestion: () => {}, + onInterrupt: () => {}, + onImplementPlanInNewThread: () => {}, + }), + ); +} + +function renderStandaloneStop() { + return renderToStaticMarkup( + createElement(ComposerPrimaryActions, { + compact: true, + pendingAction: null, + isRunning: true, + showPlanFollowUpPrompt: false, + promptHasText: false, + isSendBusy: false, + sendDisabledReason: null, + isConnecting: false, + isEnvironmentUnavailable: false, + isPreparingWorktree: false, + hasSendableContent: false, + onPreviousPendingQuestion: () => {}, + onInterrupt: () => {}, + onImplementPlanInNewThread: () => {}, + }), + ); +} describe("formatPendingPrimaryActionLabel", () => { it("returns 'Submitting...' while responding", () => { @@ -91,3 +149,19 @@ describe("formatPendingPrimaryActionLabel", () => { ).toBe("Submit answers"); }); }); + +describe("ComposerPrimaryActions", () => { + it("offers Stop generation while a running turn is waiting for user input", () => { + expect(renderPendingActions(true)).toContain('aria-label="Stop generation"'); + }); + + it("does not offer Stop generation for a pending request without a running turn", () => { + expect(renderPendingActions(false)).not.toContain('aria-label="Stop generation"'); + }); + + it("matches the small pending action size without changing the standalone size", () => { + expect(renderPendingActions(true)).toContain("size-8 sm:size-7"); + expect(renderStandaloneStop()).toContain("size-8 sm:h-8 sm:w-8"); + expect(renderStandaloneStop()).not.toContain("sm:size-7"); + }); +}); diff --git a/apps/web/src/components/chat/ComposerPrimaryActions.tsx b/apps/web/src/components/chat/ComposerPrimaryActions.tsx index 504b7e1cc44..516d9267090 100644 --- a/apps/web/src/components/chat/ComposerPrimaryActions.tsx +++ b/apps/web/src/components/chat/ComposerPrimaryActions.tsx @@ -80,10 +80,27 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ const stageBackdropVariant = useSidebarStageBackdropVariant( environmentIdentificationMode === "artwork", ); + const renderStopGenerationButton = (insidePendingAction: boolean) => ( + + ); if (pendingAction) { return (