@@ -1097,3 +1097,184 @@ def test_stash_entry_create_branch(git_repo: GitSync) -> None:
10971097 branch_names = [b .branch_name for b in branches ]
10981098 # Either branch was created or we're on it
10991099 assert "stash-branch" in branch_names or "master" in branch_names
1100+
1101+
1102+ # =============================================================================
1103+ # GitWorktreeManager / GitWorktreeCmd Tests
1104+ # =============================================================================
1105+
1106+
1107+ class WorktreeAddFixture (t .NamedTuple ):
1108+ """Test fixture for GitWorktreeManager.add() operations."""
1109+
1110+ test_id : str
1111+ new_branch : str | None
1112+ detach : bool | None
1113+
1114+
1115+ WORKTREE_ADD_FIXTURES : list [WorktreeAddFixture ] = [
1116+ WorktreeAddFixture (
1117+ test_id = "add-worktree-with-new-branch" ,
1118+ new_branch = "worktree-branch" ,
1119+ detach = None ,
1120+ ),
1121+ WorktreeAddFixture (
1122+ test_id = "add-worktree-detached" ,
1123+ new_branch = None ,
1124+ detach = True ,
1125+ ),
1126+ ]
1127+
1128+
1129+ @pytest .mark .parametrize (
1130+ list (WorktreeAddFixture ._fields ),
1131+ WORKTREE_ADD_FIXTURES ,
1132+ ids = [test .test_id for test in WORKTREE_ADD_FIXTURES ],
1133+ )
1134+ def test_worktree_add (
1135+ git_repo : GitSync ,
1136+ tmp_path : pathlib .Path ,
1137+ test_id : str ,
1138+ new_branch : str | None ,
1139+ detach : bool | None ,
1140+ ) -> None :
1141+ """Test GitWorktreeManager.add() with various scenarios."""
1142+ worktree_path = tmp_path / f"worktree-{ test_id } "
1143+
1144+ result = git_repo .cmd .worktrees .add (
1145+ path = worktree_path ,
1146+ new_branch = new_branch ,
1147+ detach = detach ,
1148+ )
1149+
1150+ # Should succeed (output contains "Preparing worktree" or similar)
1151+ assert "preparing worktree" in result .lower () or worktree_path .exists ()
1152+
1153+ # Verify worktree was created
1154+ worktrees = git_repo .cmd .worktrees .ls ()
1155+ worktree_paths = [wt .worktree_path for wt in worktrees ]
1156+ assert str (worktree_path ) in worktree_paths
1157+
1158+
1159+ def test_worktree_list (git_repo : GitSync , tmp_path : pathlib .Path ) -> None :
1160+ """Test GitWorktreeManager.ls() returns main worktree."""
1161+ worktrees = git_repo .cmd .worktrees .ls ()
1162+
1163+ # Should have at least the main worktree
1164+ assert len (worktrees ) >= 1
1165+
1166+ # Each worktree should have a path
1167+ for wt in worktrees :
1168+ assert wt .worktree_path is not None
1169+
1170+
1171+ def test_worktree_get (git_repo : GitSync , tmp_path : pathlib .Path ) -> None :
1172+ """Test GitWorktreeManager.get() retrieves specific worktree."""
1173+ # Create a worktree first
1174+ worktree_path = tmp_path / "get-test-worktree"
1175+ git_repo .cmd .worktrees .add (path = worktree_path , new_branch = "get-test-branch" )
1176+
1177+ # Get the worktree
1178+ worktree = git_repo .cmd .worktrees .get (worktree_path = str (worktree_path ))
1179+ assert worktree is not None
1180+ assert worktree .worktree_path == str (worktree_path )
1181+
1182+
1183+ def test_worktree_filter (git_repo : GitSync , tmp_path : pathlib .Path ) -> None :
1184+ """Test GitWorktreeManager.filter() with QueryList filtering."""
1185+ # Create a worktree
1186+ worktree_path = tmp_path / "filter-test-worktree"
1187+ git_repo .cmd .worktrees .add (path = worktree_path , new_branch = "filter-test-branch" )
1188+
1189+ # Filter by branch
1190+ worktrees = git_repo .cmd .worktrees .filter (branch__contains = "filter-test" )
1191+ assert len (worktrees ) >= 1
1192+
1193+
1194+ class WorktreeLockUnlockFixture (t .NamedTuple ):
1195+ """Test fixture for GitWorktreeCmd lock/unlock operations."""
1196+
1197+ test_id : str
1198+ reason : str | None
1199+
1200+
1201+ WORKTREE_LOCK_UNLOCK_FIXTURES : list [WorktreeLockUnlockFixture ] = [
1202+ WorktreeLockUnlockFixture (
1203+ test_id = "lock-without-reason" ,
1204+ reason = None ,
1205+ ),
1206+ WorktreeLockUnlockFixture (
1207+ test_id = "lock-with-reason" ,
1208+ reason = "Testing lock functionality" ,
1209+ ),
1210+ ]
1211+
1212+
1213+ @pytest .mark .parametrize (
1214+ list (WorktreeLockUnlockFixture ._fields ),
1215+ WORKTREE_LOCK_UNLOCK_FIXTURES ,
1216+ ids = [test .test_id for test in WORKTREE_LOCK_UNLOCK_FIXTURES ],
1217+ )
1218+ def test_worktree_lock_unlock (
1219+ git_repo : GitSync ,
1220+ tmp_path : pathlib .Path ,
1221+ test_id : str ,
1222+ reason : str | None ,
1223+ ) -> None :
1224+ """Test GitWorktreeCmd.lock() and unlock() operations."""
1225+ # Create a worktree first
1226+ worktree_path = tmp_path / f"lock-{ test_id } -worktree"
1227+ git_repo .cmd .worktrees .add (path = worktree_path , new_branch = f"lock-{ test_id } -branch" )
1228+
1229+ # Get the worktree
1230+ worktree = git_repo .cmd .worktrees .get (worktree_path = str (worktree_path ))
1231+ assert worktree is not None
1232+
1233+ # Lock the worktree
1234+ lock_result = worktree .lock (reason = reason )
1235+ assert lock_result == "" or "locked" not in lock_result .lower ()
1236+
1237+ # Verify it's locked
1238+ worktree_after_lock = git_repo .cmd .worktrees .get (worktree_path = str (worktree_path ))
1239+ assert worktree_after_lock is not None
1240+ assert worktree_after_lock .locked is True
1241+
1242+ # Unlock the worktree
1243+ unlock_result = worktree .unlock ()
1244+ assert unlock_result == ""
1245+
1246+ # Verify it's unlocked
1247+ worktree_after_unlock = git_repo .cmd .worktrees .get (worktree_path = str (worktree_path ))
1248+ assert worktree_after_unlock is not None
1249+ assert worktree_after_unlock .locked is False
1250+
1251+
1252+ def test_worktree_remove (git_repo : GitSync , tmp_path : pathlib .Path ) -> None :
1253+ """Test GitWorktreeCmd.remove()."""
1254+ # Create a worktree first
1255+ worktree_path = tmp_path / "remove-test-worktree"
1256+ git_repo .cmd .worktrees .add (path = worktree_path , new_branch = "remove-test-branch" )
1257+
1258+ # Get the worktree
1259+ worktree = git_repo .cmd .worktrees .get (worktree_path = str (worktree_path ))
1260+ assert worktree is not None
1261+
1262+ # Remove the worktree
1263+ result = worktree .remove ()
1264+ assert result == "" or "error" not in result .lower ()
1265+
1266+ # Verify it's removed
1267+ worktrees_after = git_repo .cmd .worktrees .ls ()
1268+ worktree_paths = [wt .worktree_path for wt in worktrees_after ]
1269+ assert str (worktree_path ) not in worktree_paths
1270+
1271+
1272+ def test_worktree_prune (git_repo : GitSync , tmp_path : pathlib .Path ) -> None :
1273+ """Test GitWorktreeManager.prune()."""
1274+ # Prune should succeed even if nothing to prune
1275+ result = git_repo .cmd .worktrees .prune ()
1276+ assert result == "" or "prune" not in result .lower ()
1277+
1278+ # Dry run should also succeed
1279+ result_dry = git_repo .cmd .worktrees .prune (dry_run = True )
1280+ assert "error" not in result_dry .lower () or result_dry == ""
0 commit comments