1- import { render , screen , waitFor } from "@/utils/test-utils"
1+ // npx vitest src/components/modes/__tests__/ModesView.import-switch.spec.tsx
2+
3+ import { render , waitFor } from "@/utils/test-utils"
24import ModesView from "../ModesView"
35import { ExtensionStateContext } from "@src/context/ExtensionStateContext"
46import { vscode } from "@src/utils/vscode"
57
8+ // Mock vscode API
69vitest . mock ( "@src/utils/vscode" , ( ) => ( {
710 vscode : {
811 postMessage : vitest . fn ( ) ,
912 } ,
1013} ) )
1114
12- const baseState = {
15+ const mockExtensionState = {
1316 customModePrompts : { } ,
14- listApiConfigMeta : [ ] ,
17+ listApiConfigMeta : [
18+ { id : "config1" , name : "Config 1" } ,
19+ { id : "config2" , name : "Config 2" } ,
20+ ] ,
1521 enhancementApiConfigId : "" ,
1622 setEnhancementApiConfigId : vitest . fn ( ) ,
1723 mode : "code" ,
@@ -22,97 +28,128 @@ const baseState = {
2228 setCustomInstructions : vitest . fn ( ) ,
2329}
2430
25- describe ( "ModesView - auto switch after import" , ( ) => {
31+ const renderModesView = ( props = { } ) => {
32+ const mockOnDone = vitest . fn ( )
33+ return render (
34+ < ExtensionStateContext . Provider value = { { ...mockExtensionState , ...props } as any } >
35+ < ModesView onDone = { mockOnDone } />
36+ </ ExtensionStateContext . Provider > ,
37+ )
38+ }
39+
40+ Element . prototype . scrollIntoView = vitest . fn ( )
41+
42+ describe ( "ModesView Import Auto-Switch" , ( ) => {
2643 beforeEach ( ( ) => {
2744 vitest . clearAllMocks ( )
2845 } )
2946
30- it ( "switches to imported mode when import succeeds and slug is provided" , async ( ) => {
31- const importedMode = {
32- slug : "imported-mode" ,
33- name : "Imported Mode" ,
34- roleDefinition : "Role" ,
35- groups : [ "read" ] as const ,
36- source : "global" as const ,
47+ it ( "should auto-switch to imported mode when found in current state" , async ( ) => {
48+ const importedModeSlug = "custom-test-mode"
49+ const customModes = [
50+ {
51+ slug : importedModeSlug ,
52+ name : "Custom Test Mode" ,
53+ roleDefinition : "Test role" ,
54+ groups : [ ] ,
55+ } ,
56+ ]
57+
58+ renderModesView ( { customModes } )
59+
60+ // Simulate successful import message with the mode already in state
61+ const importMessage = {
62+ data : {
63+ type : "importModeResult" ,
64+ success : true ,
65+ slug : importedModeSlug ,
66+ } ,
3767 }
3868
39- render (
40- < ExtensionStateContext . Provider value = { { ...baseState , customModes : [ importedMode ] } as any } >
41- < ModesView onDone = { vitest . fn ( ) } />
42- </ ExtensionStateContext . Provider > ,
43- )
44-
45- const trigger = screen . getByTestId ( "mode-select-trigger" )
46- expect ( trigger ) . toHaveTextContent ( "Code" )
47-
48- // Simulate extension sending successful import result with slug
49- window . dispatchEvent (
50- new MessageEvent ( "message" , {
51- data : { type : "importModeResult" , success : true , slug : "imported-mode" } ,
52- } ) ,
53- )
54-
55- // Backend switch message sent
56- await waitFor ( ( ) => {
57- expect ( vscode . postMessage ) . toHaveBeenCalledWith ( { type : "mode" , text : "imported-mode" } )
58- } )
69+ window . dispatchEvent ( new MessageEvent ( "message" , importMessage ) )
5970
60- // UI reflects new mode selection
71+ // Wait for the mode switch message to be sent
6172 await waitFor ( ( ) => {
62- expect ( trigger ) . toHaveTextContent ( "Imported Mode" )
73+ expect ( vscode . postMessage ) . toHaveBeenCalledWith ( {
74+ type : "mode" ,
75+ text : importedModeSlug ,
76+ } )
6377 } )
6478 } )
6579
66- it ( "does not switch when import fails or slug missing" , async ( ) => {
67- render (
68- < ExtensionStateContext . Provider value = { { ...baseState } as any } >
69- < ModesView onDone = { vitest . fn ( ) } />
70- </ ExtensionStateContext . Provider > ,
71- )
72-
73- const trigger = screen . getByTestId ( "mode-select-trigger" )
74- expect ( trigger ) . toHaveTextContent ( "Code" )
80+ it ( "should fallback to architect mode when imported slug not yet in state (race condition)" , async ( ) => {
81+ const importedModeSlug = "custom-new-mode"
7582
76- // Import failure
77- window . dispatchEvent (
78- new MessageEvent ( "message" , { data : { type : "importModeResult" , success : false , error : "x" } } ) ,
79- )
83+ // Render without the imported mode in customModes (simulating race condition)
84+ renderModesView ( { customModes : [ ] } )
8085
81- await waitFor ( ( ) => {
82- expect ( vscode . postMessage ) . not . toHaveBeenCalledWith ( { type : "mode" , text : expect . any ( String ) } )
83- } )
84- expect ( trigger ) . toHaveTextContent ( "Code" )
86+ // Simulate successful import message but mode not yet in state
87+ const importMessage = {
88+ data : {
89+ type : "importModeResult" ,
90+ success : true ,
91+ slug : importedModeSlug ,
92+ } ,
93+ }
8594
86- // Success but no slug provided
87- window . dispatchEvent ( new MessageEvent ( "message" , { data : { type : "importModeResult" , success : true } } ) )
95+ window . dispatchEvent ( new MessageEvent ( "message" , importMessage ) )
8896
97+ // Wait for the fallback to architect mode
8998 await waitFor ( ( ) => {
90- expect ( vscode . postMessage ) . not . toHaveBeenCalledWith ( { type : "mode" , text : expect . any ( String ) } )
99+ expect ( vscode . postMessage ) . toHaveBeenCalledWith ( {
100+ type : "mode" ,
101+ text : "architect" ,
102+ } )
91103 } )
92- expect ( trigger ) . toHaveTextContent ( "Code" )
93104 } )
94105
95- it ( "uses fallback branch when imported slug not yet present in customModes" , async ( ) => {
96- // Render with empty customModes - imported mode hasn't been added to state yet
97- render (
98- < ExtensionStateContext . Provider value = { { ...baseState , customModes : [ ] } as any } >
99- < ModesView onDone = { vitest . fn ( ) } />
100- </ ExtensionStateContext . Provider > ,
101- )
106+ it ( "should not switch modes on import failure" , async ( ) => {
107+ renderModesView ( )
108+
109+ // Simulate failed import message
110+ const importMessage = {
111+ data : {
112+ type : "importModeResult" ,
113+ success : false ,
114+ error : "Import failed" ,
115+ } ,
116+ }
117+
118+ window . dispatchEvent ( new MessageEvent ( "message" , importMessage ) )
102119
103- const trigger = screen . getByTestId ( " mode-select-trigger" )
104- expect ( trigger ) . toHaveTextContent ( "Code" )
120+ // Wait a bit to ensure no mode switch happens
121+ await new Promise ( ( resolve ) => setTimeout ( resolve , 100 ) )
105122
106- // Simulate successful import for a slug not yet in customModes (timing race condition)
107- window . dispatchEvent (
108- new MessageEvent ( "message" , {
109- data : { type : "importModeResult" , success : true , slug : "not-yet-loaded- mode" } ,
123+ // Verify no mode switch message was sent
124+ expect ( vscode . postMessage ) . not . toHaveBeenCalledWith (
125+ expect . objectContaining ( {
126+ type : "mode" ,
110127 } ) ,
111128 )
129+ } )
112130
113- // Fallback branch should send backend switch message
114- await waitFor ( ( ) => {
115- expect ( vscode . postMessage ) . toHaveBeenCalledWith ( { type : "mode" , text : "not-yet-loaded-mode" } )
116- } )
131+ it ( "should not switch modes on cancelled import" , async ( ) => {
132+ renderModesView ( )
133+
134+ // Simulate cancelled import message
135+ const importMessage = {
136+ data : {
137+ type : "importModeResult" ,
138+ success : false ,
139+ error : "cancelled" ,
140+ } ,
141+ }
142+
143+ window . dispatchEvent ( new MessageEvent ( "message" , importMessage ) )
144+
145+ // Wait a bit to ensure no mode switch happens
146+ await new Promise ( ( resolve ) => setTimeout ( resolve , 100 ) )
147+
148+ // Verify no mode switch message was sent
149+ expect ( vscode . postMessage ) . not . toHaveBeenCalledWith (
150+ expect . objectContaining ( {
151+ type : "mode" ,
152+ } ) ,
153+ )
117154 } )
118155} )
0 commit comments