@@ -222,6 +222,30 @@ def test_cmd_project_list_empty(tmp_path, capsys):
222222 assert rc == 0
223223
224224
225+ def test_cmd_project_list_non_tty_falls_back_to_table (tmp_path , monkeypatch , capsys ):
226+ """interactive=True (デフォルト) でも非 TTY では一覧表示にフォールバックする。"""
227+ from devbase .commands import project as project_mod
228+ from devbase .commands import status as status_mod
229+ from devbase .commands import container as container_mod
230+
231+ _make_plugin_project (tmp_path , "repos/o--r/alpha" , "alpha-proj" )
232+ _link_project (tmp_path , "alpha-proj" , "repos/o--r/alpha" , "alpha-proj" )
233+ monkeypatch .setattr (status_mod , "_container_status_for" ,
234+ lambda entry : {"name" : entry .name , "status" : "stopped" , "count" : 0 })
235+ monkeypatch .setattr (project_mod .sys .stdin , "isatty" , lambda : False )
236+
237+ called = []
238+ monkeypatch .setattr (container_mod , "cmd_project" , lambda args : called .append (1 ) or 0 )
239+
240+ args = types .SimpleNamespace (interactive = True )
241+ rc = project_mod .cmd_project_list (tmp_path , args )
242+ out = capsys .readouterr ().out
243+
244+ assert rc == 0
245+ assert called == [], "非 TTY では対話起動しない"
246+ assert "alpha-proj" in out
247+
248+
225249# ---------------------------------------------------------------------------
226250# cmd_project_list: --interactive
227251# ---------------------------------------------------------------------------
@@ -237,6 +261,8 @@ def test_cmd_project_list_interactive_selects_and_ups(tmp_path, monkeypatch):
237261 _link_project (tmp_path , "beta-proj" , "plugins/beta" , "beta-proj" )
238262 monkeypatch .setattr (status_mod , "_container_status_for" , lambda entry : None )
239263
264+ # 対話選択は TTY 環境でのみ起動するため isatty を True に固定する。
265+ monkeypatch .setattr (project_mod .sys .stdin , "isatty" , lambda : True )
240266 # 番号 "2" を選択 (sorted: alpha-proj=1, beta-proj=2)
241267 monkeypatch .setattr ("builtins.input" , lambda * a , ** k : "2" )
242268
@@ -261,6 +287,7 @@ def test_cmd_project_list_interactive_empty_input_aborts(tmp_path, monkeypatch):
261287 _make_plugin_project (tmp_path , "repos/o--r/alpha" , "alpha-proj" )
262288 _link_project (tmp_path , "alpha-proj" , "repos/o--r/alpha" , "alpha-proj" )
263289 monkeypatch .setattr (status_mod , "_container_status_for" , lambda entry : None )
290+ monkeypatch .setattr (project_mod .sys .stdin , "isatty" , lambda : True )
264291 monkeypatch .setattr ("builtins.input" , lambda * a , ** k : "" )
265292
266293 called = []
@@ -285,6 +312,7 @@ def test_cmd_project_list_interactive_non_tty_eof(tmp_path, monkeypatch):
285312 def raise_eof (* a , ** k ):
286313 raise EOFError
287314
315+ monkeypatch .setattr (project_mod .sys .stdin , "isatty" , lambda : True )
288316 monkeypatch .setattr ("builtins.input" , raise_eof )
289317 called = []
290318 monkeypatch .setattr (container_mod , "cmd_project" , lambda args : called .append (1 ) or 0 )
@@ -308,6 +336,7 @@ def test_cmd_project_list_interactive_keyboard_interrupt_aborts(tmp_path, monkey
308336 def raise_interrupt (* a , ** k ):
309337 raise KeyboardInterrupt
310338
339+ monkeypatch .setattr (project_mod .sys .stdin , "isatty" , lambda : True )
311340 monkeypatch .setattr ("builtins.input" , raise_interrupt )
312341 called = []
313342 monkeypatch .setattr (container_mod , "cmd_project" , lambda args : called .append (1 ) or 0 )
@@ -328,6 +357,7 @@ def test_cmd_project_list_interactive_out_of_range_reprompts(tmp_path, monkeypat
328357 _link_project (tmp_path , "alpha-proj" , "repos/o--r/alpha" , "alpha-proj" )
329358 monkeypatch .setattr (status_mod , "_container_status_for" , lambda entry : None )
330359
360+ monkeypatch .setattr (project_mod .sys .stdin , "isatty" , lambda : True )
331361 # "99" (範囲外) → "1" (有効) の順に入力 → 再入力後に up が起動する
332362 inputs = iter (["99" , "1" ])
333363 monkeypatch .setattr ("builtins.input" , lambda * a , ** k : next (inputs ))
@@ -351,6 +381,7 @@ def test_cmd_project_list_interactive_non_numeric_reprompts(tmp_path, monkeypatc
351381 _link_project (tmp_path , "alpha-proj" , "repos/o--r/alpha" , "alpha-proj" )
352382 monkeypatch .setattr (status_mod , "_container_status_for" , lambda entry : None )
353383
384+ monkeypatch .setattr (project_mod .sys .stdin , "isatty" , lambda : True )
354385 # "abc" (数値以外) → "1" (有効)
355386 inputs = iter (["abc" , "1" ])
356387 monkeypatch .setattr ("builtins.input" , lambda * a , ** k : next (inputs ))
@@ -369,20 +400,30 @@ def test_cmd_project_list_interactive_non_numeric_reprompts(tmp_path, monkeypatc
369400# ---------------------------------------------------------------------------
370401
371402def test_parser_project_list ():
403+ # 対話選択はデフォルト ON (フラグ無しで interactive=True)。
372404 parser = cli ._create_parser ()
373405 args = parser .parse_args (["project" , "list" ])
374406 assert args .command == "project"
375407 assert args .subcommand == "list"
376- assert args .interactive is False
408+ assert args .interactive is True
377409
378410
379411def test_parser_project_list_interactive_flag ():
412+ # `-i` / `--interactive` は後方互換で受け付ける (実質 no-op、True のまま)。
380413 parser = cli ._create_parser ()
381414 for flag in ("--interactive" , "-i" ):
382415 args = parser .parse_args (["project" , "list" , flag ])
383416 assert args .interactive is True
384417
385418
419+ def test_parser_project_list_no_interactive_flag ():
420+ # `--no-interactive` / `--plain` / `-P` で一覧表示のみ (interactive=False)。
421+ parser = cli ._create_parser ()
422+ for flag in ("--no-interactive" , "--plain" , "-P" ):
423+ args = parser .parse_args (["project" , "list" , flag ])
424+ assert args .interactive is False
425+
426+
386427def test_parser_top_level_list_synonym ():
387428 parser = cli ._create_parser ()
388429 args = parser .parse_args (["list" , "-i" ])
0 commit comments