@@ -48,6 +48,11 @@ describe('terminal module', function()
4848 return 42
4949 end
5050
51+ -- Mock vim.fn.getcwd
52+ _G .vim .fn .getcwd = function ()
53+ return ' /test/current/dir'
54+ end
55+
5156 -- Mock vim.api.nvim_win_close
5257 _G .vim .api .nvim_win_close = function (win_id , force )
5358 -- Remove the window from win_ids
@@ -78,12 +83,14 @@ describe('terminal module', function()
7883 },
7984 git = {
8085 use_git_root = true ,
86+ multi_instance = true ,
8187 },
8288 }
8389
8490 claude_code = {
8591 claude_code = {
86- bufnr = nil ,
92+ instances = {},
93+ current_instance = nil ,
8794 saved_updatetime = nil ,
8895 },
8996 }
@@ -95,10 +102,11 @@ describe('terminal module', function()
95102 }
96103 end )
97104
98- describe (' toggle' , function ()
99- it (' should open terminal window when Claude Code is not running' , function ()
100- -- Claude Code is not running (bufnr is nil)
101- claude_code .claude_code .bufnr = nil
105+ describe (' toggle with multi-instance enabled' , function ()
106+ it (' should create new instance when none exists' , function ()
107+ -- No instances exist
108+ claude_code .claude_code .instances = {}
109+ claude_code .claude_code .current_instance = nil
102110
103111 -- Call toggle
104112 terminal .toggle (claude_code , config , git )
@@ -122,36 +130,43 @@ describe('terminal module', function()
122130 assert .is_true (resize_cmd_found , ' Resize command should be called' )
123131 assert .is_true (terminal_cmd_found , ' Terminal command should be called' )
124132
125- -- Buffer number should be set
126- assert .is_not_nil (claude_code .claude_code .bufnr , ' Claude Code buffer number should be set' )
133+ -- Current instance should be set
134+ assert .is_not_nil (claude_code .claude_code .current_instance , ' Current instance should be set' )
135+
136+ -- Instance should be created in instances table
137+ local current_instance = claude_code .claude_code .current_instance
138+ assert .is_not_nil (claude_code .claude_code .instances [current_instance ], ' Instance buffer should be set' )
127139 end )
128140
129- it (' should use git root when configured' , function ()
130- -- Claude Code is not running (bufnr is nil)
131- claude_code .claude_code .bufnr = nil
132-
133- -- Set git config to use root
141+ it (' should use git root as instance identifier when use_git_root is true' , function ()
142+ -- Configure to use git root
134143 config .git .use_git_root = true
144+ config .git .multi_instance = true
135145
136146 -- Call toggle
137147 terminal .toggle (claude_code , config , git )
138148
139- -- Check that git root was used in terminal command
140- local git_root_cmd_found = false
149+ -- Current instance should be git root
150+ assert .are .equal (' /test/git/root' , claude_code .claude_code .current_instance )
151+ end )
141152
142- for _ , cmd in ipairs (vim_cmd_calls ) do
143- if cmd :match (' terminal pushd /test/git/root && ' .. config .command .. ' && popd' ) then
144- git_root_cmd_found = true
145- break
146- end
147- end
153+ it (' should use current directory as instance identifier when use_git_root is false' , function ()
154+ -- Configure to use current directory
155+ config .git .use_git_root = false
156+ config .git .multi_instance = true
148157
149- assert .is_true (git_root_cmd_found , ' Terminal command should include git root' )
158+ -- Call toggle
159+ terminal .toggle (claude_code , config , git )
160+
161+ -- Current instance should be current directory
162+ assert .are .equal (' /test/current/dir' , claude_code .claude_code .current_instance )
150163 end )
151164
152- it (' should close window when Claude Code is visible' , function ()
153- -- Claude Code is running and visible
154- claude_code .claude_code .bufnr = 42
165+ it (' should close window when instance is visible' , function ()
166+ -- Setup existing instance
167+ local instance_id = ' /test/git/root'
168+ claude_code .claude_code .instances [instance_id ] = 42
169+ claude_code .claude_code .current_instance = instance_id
155170 win_ids = { 100 , 101 } -- Windows displaying the buffer
156171
157172 -- Create a function to clear the win_ids array
@@ -168,9 +183,11 @@ describe('terminal module', function()
168183 assert .are .equal (0 , # win_ids , ' Windows should be closed' )
169184 end )
170185
171- it (' should reopen window when Claude Code exists but is hidden' , function ()
172- -- Claude Code is running but not visible
173- claude_code .claude_code .bufnr = 42
186+ it (' should reopen window when instance exists but is hidden' , function ()
187+ -- Setup existing instance that's not visible
188+ local instance_id = ' /test/git/root'
189+ claude_code .claude_code .instances [instance_id ] = 42
190+ claude_code .claude_code .current_instance = instance_id
174191 win_ids = {} -- No windows displaying the buffer
175192
176193 -- Call toggle
@@ -195,13 +212,98 @@ describe('terminal module', function()
195212 assert .is_true (resize_cmd_found , ' Resize command should be called' )
196213 assert .is_true (buffer_cmd_found , ' Buffer command should be called with correct buffer number' )
197214 end )
215+
216+ it (' should create buffer with sanitized name for multi-instance' , function ()
217+ -- Use an instance ID with special characters
218+ config .git .use_git_root = false
219+ config .git .multi_instance = true
220+
221+ -- Mock getcwd to return path with special characters
222+ _G .vim .fn .getcwd = function ()
223+ return ' /test/path with spaces/and-symbols!'
224+ end
225+
226+ -- Call toggle
227+ terminal .toggle (claude_code , config , git )
228+
229+ -- Check that file command was called with sanitized name
230+ local file_cmd_found = false
231+ for _ , cmd in ipairs (vim_cmd_calls ) do
232+ if cmd :match (' file claude%-code%-.*' ) then
233+ file_cmd_found = true
234+ -- Ensure no special characters remain
235+ assert .is_false (cmd :match (' [^%w%-_]' ), ' Buffer name should not contain special characters' )
236+ break
237+ end
238+ end
239+
240+ assert .is_true (file_cmd_found , ' File command should be called with sanitized buffer name' )
241+ end )
242+
243+ it (' should clean up invalid buffers from instances table' , function ()
244+ -- Setup invalid buffer in instances
245+ local instance_id = ' /test/git/root'
246+ claude_code .claude_code .instances [instance_id ] = 999 -- Invalid buffer number
247+
248+ -- Mock nvim_buf_is_valid to return false for this buffer
249+ _G .vim .api .nvim_buf_is_valid = function (bufnr )
250+ return bufnr ~= 999
251+ end
252+
253+ -- Call toggle
254+ terminal .toggle (claude_code , config , git )
255+
256+ -- Invalid buffer should be cleaned up
257+ assert .is_nil (claude_code .claude_code .instances [instance_id ], ' Invalid buffer should be cleaned up' )
258+ end )
259+ end )
260+
261+ describe (' toggle with multi-instance disabled' , function ()
262+ before_each (function ()
263+ config .git .multi_instance = false
264+ end )
265+
266+ it (' should use global instance when multi-instance is disabled' , function ()
267+ -- Call toggle
268+ terminal .toggle (claude_code , config , git )
269+
270+ -- Current instance should be "global"
271+ assert .are .equal (' global' , claude_code .claude_code .current_instance )
272+ end )
273+
274+ it (' should create single global instance' , function ()
275+ -- Call toggle
276+ terminal .toggle (claude_code , config , git )
277+
278+ -- Check that global instance is created
279+ assert .is_not_nil (claude_code .claude_code .instances [' global' ], ' Global instance should be created' )
280+ end )
281+ end )
282+
283+ describe (' git root usage' , function ()
284+ it (' should use git root when configured' , function ()
285+ -- Set git config to use root
286+ config .git .use_git_root = true
287+
288+ -- Call toggle
289+ terminal .toggle (claude_code , config , git )
290+
291+ -- Check that git root was used in terminal command
292+ local git_root_cmd_found = false
293+
294+ for _ , cmd in ipairs (vim_cmd_calls ) do
295+ if cmd :match (' terminal pushd /test/git/root && ' .. config .command .. ' && popd' ) then
296+ git_root_cmd_found = true
297+ break
298+ end
299+ end
300+
301+ assert .is_true (git_root_cmd_found , ' Terminal command should include git root' )
302+ end )
198303 end )
199304
200305 describe (' start_in_normal_mode option' , function ()
201306 it (' should not enter insert mode when start_in_normal_mode is true' , function ()
202- -- Claude Code is not running (bufnr is nil)
203- claude_code .claude_code .bufnr = nil
204-
205307 -- Set start_in_normal_mode to true
206308 config .window .start_in_normal_mode = true
207309
@@ -224,9 +326,6 @@ describe('terminal module', function()
224326 end )
225327
226328 it (' should enter insert mode when start_in_normal_mode is false' , function ()
227- -- Claude Code is not running (bufnr is nil)
228- claude_code .claude_code .bufnr = nil
229-
230329 -- Set start_in_normal_mode to false
231330 config .window .start_in_normal_mode = false
232331
@@ -251,43 +350,59 @@ describe('terminal module', function()
251350
252351 describe (' force_insert_mode' , function ()
253352 it (' should check insert mode conditions in terminal buffer' , function ()
353+ -- Setup mock with instances table
354+ local mock_claude_code = {
355+ claude_code = {
356+ instances = {
357+ [' /test/instance' ] = 1 ,
358+ },
359+ current_instance = ' /test/instance' ,
360+ },
361+ }
362+ local mock_config = {
363+ window = {
364+ start_in_normal_mode = false ,
365+ },
366+ }
367+
254368 -- For this test, we'll just verify that the function can be called without error
255369 local success , _ = pcall (function ()
256- -- Setup minimal mock
257- local mock_claude_code = {
258- claude_code = {
259- bufnr = 1 ,
260- },
261- }
262- local mock_config = {
263- window = {
264- start_in_normal_mode = false ,
265- },
266- }
267370 terminal .force_insert_mode (mock_claude_code , mock_config )
268371 end )
269372
270373 assert .is_true (success , ' Force insert mode function should run without error' )
271374 end )
272375
273376 it (' should handle non-terminal buffers correctly' , function ()
377+ -- Setup mock with instances table but different current buffer
378+ local mock_claude_code = {
379+ claude_code = {
380+ instances = {
381+ [' /test/instance' ] = 2 ,
382+ },
383+ current_instance = ' /test/instance' ,
384+ },
385+ }
386+ local mock_config = {
387+ window = {
388+ start_in_normal_mode = false ,
389+ },
390+ }
391+
392+ -- Mock bufnr to return different buffer
393+ _G .vim .fn .bufnr = function (pattern )
394+ if pattern == ' %' then
395+ return 1 -- Different from instances buffer
396+ end
397+ return 1
398+ end
399+
274400 -- For this test, we'll just verify that the function can be called without error
275401 local success , _ = pcall (function ()
276- -- Setup minimal mock that's different from terminal buffer
277- local mock_claude_code = {
278- claude_code = {
279- bufnr = 2 ,
280- },
281- }
282- local mock_config = {
283- window = {
284- start_in_normal_mode = false ,
285- },
286- }
287402 terminal .force_insert_mode (mock_claude_code , mock_config )
288403 end )
289404
290405 assert .is_true (success , ' Force insert mode function should run without error' )
291406 end )
292407 end )
293- end )
408+ end )
0 commit comments