@@ -72,6 +72,13 @@ const DEFAULT_VIEWPORT: ViewportSpec = {
7272 textTolerancePx : 44 ,
7373 attachmentTolerancePx : 56 ,
7474} ;
75+ const WIDE_VIEWPORT : ViewportSpec = {
76+ name : "wide" ,
77+ width : 1_440 ,
78+ height : 1_100 ,
79+ textTolerancePx : 44 ,
80+ attachmentTolerancePx : 56 ,
81+ } ;
7582const TEXT_VIEWPORT_MATRIX = [
7683 DEFAULT_VIEWPORT ,
7784 { name : "tablet" , width : 720 , height : 1_024 , textTolerancePx : 44 , attachmentTolerancePx : 56 } ,
@@ -592,18 +599,45 @@ async function waitForSendButton(): Promise<HTMLButtonElement> {
592599 ) ;
593600}
594601
595- async function waitForInteractionModeButton (
596- expectedLabel : "Chat" | "Plan" ,
597- ) : Promise < HTMLButtonElement > {
598- return waitForElement (
599- ( ) =>
600- Array . from ( document . querySelectorAll ( "button" ) ) . find (
601- ( button ) => button . textContent ?. trim ( ) === expectedLabel ,
602- ) as HTMLButtonElement | null ,
603- `Unable to find ${ expectedLabel } interaction mode button.` ,
602+ function isVisibleElement ( element : Element | null ) : element is HTMLElement {
603+ return (
604+ element instanceof HTMLElement &&
605+ element . getBoundingClientRect ( ) . width > 0 &&
606+ element . getBoundingClientRect ( ) . height > 0
604607 ) ;
605608}
606609
610+ async function readCurrentInteractionModeLabel ( ) : Promise < "Chat" | "Code" | "Plan" > {
611+ const inlineButton = Array . from ( document . querySelectorAll ( "button" ) ) . find ( ( button ) => {
612+ const label = button . textContent ?. trim ( ) ;
613+ return (
614+ button . getAttribute ( "title" ) === "Cycle interaction mode: Chat → Code → Plan" &&
615+ ( label === "Chat" || label === "Code" || label === "Plan" )
616+ ) ;
617+ } ) ;
618+ const inlineLabel = inlineButton ?. textContent ?. trim ( ) ;
619+ if ( inlineLabel === "Chat" || inlineLabel === "Code" || inlineLabel === "Plan" ) {
620+ return inlineLabel ;
621+ }
622+
623+ const compactMenuTrigger = document . querySelector < HTMLButtonElement > (
624+ 'button[aria-label="More composer controls"]' ,
625+ ) ;
626+ if ( compactMenuTrigger && isVisibleElement ( compactMenuTrigger ) ) {
627+ compactMenuTrigger . click ( ) ;
628+ await waitForLayout ( ) ;
629+ const selectedRadio = document . querySelector < HTMLElement > (
630+ '[role="menuitemradio"][aria-checked="true"]' ,
631+ ) ;
632+ const radioLabel = selectedRadio ?. textContent ?. trim ( ) ;
633+ if ( radioLabel === "Chat" || radioLabel === "Code" || radioLabel === "Plan" ) {
634+ return radioLabel ;
635+ }
636+ }
637+
638+ throw new Error ( "Unable to determine current interaction mode." ) ;
639+ }
640+
607641async function waitForServerConfigToApply ( ) : Promise < void > {
608642 await vi . waitFor (
609643 ( ) => {
@@ -1005,64 +1039,6 @@ describe("ChatView timeline estimator parity (full app)", () => {
10051039 } ,
10061040 ) ;
10071041
1008- it ( "opens the project cwd for draft threads without a worktree path" , async ( ) => {
1009- useComposerDraftStore . setState ( {
1010- draftThreadsByThreadId : {
1011- [ THREAD_ID ] : {
1012- projectId : PROJECT_ID ,
1013- createdAt : NOW_ISO ,
1014- title : "New thread" ,
1015- runtimeMode : "full-access" ,
1016- interactionMode : "chat" ,
1017- branch : null ,
1018- worktreePath : null ,
1019- envMode : "local" ,
1020- } ,
1021- } ,
1022- projectDraftThreadIdByProjectId : {
1023- [ PROJECT_ID ] : THREAD_ID ,
1024- } ,
1025- } ) ;
1026-
1027- const mounted = await mountChatView ( {
1028- viewport : DEFAULT_VIEWPORT ,
1029- snapshot : createDraftOnlySnapshot ( ) ,
1030- configureFixture : ( nextFixture ) => {
1031- nextFixture . serverConfig = {
1032- ...nextFixture . serverConfig ,
1033- availableEditors : [ "vscode" ] ,
1034- } ;
1035- } ,
1036- } ) ;
1037-
1038- try {
1039- const openButton = await waitForElement (
1040- ( ) =>
1041- Array . from ( document . querySelectorAll ( "button" ) ) . find (
1042- ( button ) => button . textContent ?. trim ( ) === "Open" ,
1043- ) as HTMLButtonElement | null ,
1044- "Unable to find Open button." ,
1045- ) ;
1046- openButton . click ( ) ;
1047-
1048- await vi . waitFor (
1049- ( ) => {
1050- const openRequest = wsRequests . find (
1051- ( request ) => request . _tag === WS_METHODS . shellOpenInEditor ,
1052- ) ;
1053- expect ( openRequest ) . toMatchObject ( {
1054- _tag : WS_METHODS . shellOpenInEditor ,
1055- cwd : "/repo/project" ,
1056- editor : "vscode" ,
1057- } ) ;
1058- } ,
1059- { timeout : 8_000 , interval : 16 } ,
1060- ) ;
1061- } finally {
1062- await mounted . cleanup ( ) ;
1063- }
1064- } ) ;
1065-
10661042 it ( "runs project scripts from local draft threads at the project cwd" , async ( ) => {
10671043 useComposerDraftStore . setState ( {
10681044 draftThreadsByThreadId : {
@@ -1263,16 +1239,20 @@ describe("ChatView timeline estimator parity (full app)", () => {
12631239
12641240 it ( "toggles plan mode with Shift+Tab only while the composer is focused" , async ( ) => {
12651241 const mounted = await mountChatView ( {
1266- viewport : DEFAULT_VIEWPORT ,
1242+ viewport : WIDE_VIEWPORT ,
12671243 snapshot : createSnapshotForTargetUser ( {
12681244 targetMessageId : "msg-user-target-hotkey" as MessageId ,
12691245 targetText : "hotkey target" ,
12701246 } ) ,
12711247 } ) ;
12721248
12731249 try {
1274- const initialModeButton = await waitForInteractionModeButton ( "Chat" ) ;
1275- expect ( initialModeButton . title ) . toContain ( "enter plan mode" ) ;
1250+ await vi . waitFor (
1251+ async ( ) => {
1252+ expect ( await readCurrentInteractionModeLabel ( ) ) . toBe ( "Chat" ) ;
1253+ } ,
1254+ { timeout : 8_000 , interval : 16 } ,
1255+ ) ;
12761256
12771257 window . dispatchEvent (
12781258 new KeyboardEvent ( "keydown" , {
@@ -1284,7 +1264,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
12841264 ) ;
12851265 await waitForLayout ( ) ;
12861266
1287- expect ( ( await waitForInteractionModeButton ( "Chat" ) ) . title ) . toContain ( "enter plan mode ") ;
1267+ expect ( await readCurrentInteractionModeLabel ( ) ) . toBe ( "Chat ") ;
12881268
12891269 const composerEditor = await waitForComposerEditor ( ) ;
12901270 composerEditor . focus ( ) ;
@@ -1299,9 +1279,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
12991279
13001280 await vi . waitFor (
13011281 async ( ) => {
1302- expect ( ( await waitForInteractionModeButton ( "Plan" ) ) . title ) . toContain (
1303- "return to normal chat mode" ,
1304- ) ;
1282+ expect ( await readCurrentInteractionModeLabel ( ) ) . toBe ( "Plan" ) ;
13051283 } ,
13061284 { timeout : 8_000 , interval : 16 } ,
13071285 ) ;
@@ -1317,7 +1295,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
13171295
13181296 await vi . waitFor (
13191297 async ( ) => {
1320- expect ( ( await waitForInteractionModeButton ( "Chat" ) ) . title ) . toContain ( "enter plan mode ") ;
1298+ expect ( await readCurrentInteractionModeLabel ( ) ) . toBe ( "Chat ") ;
13211299 } ,
13221300 { timeout : 8_000 , interval : 16 } ,
13231301 ) ;
0 commit comments