Split oversized runtime, sandbox, and interactive add files into focused helpers

This commit is contained in:
Joe Tretter
2026-05-18 21:49:43 -05:00
parent ed0b81e587
commit bea901acfe
13 changed files with 1743 additions and 1622 deletions

View File

@@ -325,280 +325,6 @@ class GitSandboxEngineTest {
assertFalse(updatedRepo.files.any { it.name == "deleteme.txt" })
}
@Test
fun interactiveStageShowsMenuWithoutStagingOrAutoCommands() {
val repo = RepoState(initialized = true, files = listOf(GitFile("README")))
val (updatedRepo, output) = GitSandboxEngine.execute(repo, "git stage -i")
assertFalse(updatedRepo.files.single { it.name == "README" }.staged)
assertTrue(output.any { it.contains("What now>") })
assertFalse(output.any { it.contains("What now> update") })
assertFalse(output.any { it.contains("What now> quit") })
assertFalse(output.any { it.contains("GitHug Android") })
}
@Test
fun interactiveAddShowsMenuWithoutStagingOrAutoCommands() {
val repo = RepoState(initialized = true, files = listOf(GitFile("README")))
val (updatedRepo, output) = GitSandboxEngine.execute(repo, "git add -i")
assertFalse(updatedRepo.files.single { it.name == "README" }.staged)
assertTrue(updatedRepo.interactiveAddSession != null)
assertTrue(output.any { it.contains("What now>") })
assertFalse(output.any { it.contains("What now> update") })
assertFalse(output.any { it.contains("What now> quit") })
}
@Test
fun interactiveAddAcceptsUpdateSelectionFromNextInput() {
val repo = RepoState(initialized = true, files = listOf(GitFile("README")))
val (menuRepo, _) = GitSandboxEngine.execute(repo, "git stage -i")
val (updateRepo, updateOutput) = GitSandboxEngine.execute(menuRepo, "2")
val (selectedRepo, selectionOutput) = GitSandboxEngine.execute(updateRepo, "1")
val (quitRepo, quitOutput) = GitSandboxEngine.execute(selectedRepo, "7")
assertTrue(updateRepo.interactiveAddSession?.awaitingUpdateSelection == true)
assertTrue(updateOutput.any { it.contains("Update>>") })
assertTrue(selectedRepo.files.single { it.name == "README" }.staged)
assertTrue(selectedRepo.interactiveAddSession?.awaitingUpdateSelection == false)
assertTrue(selectionOutput.any { it.contains("updated 1 path(s)") })
assertTrue(quitRepo.interactiveAddSession == null)
assertTrue(quitOutput.any { it.contains("Bye.") })
}
@Test
fun interactiveAddHandlesEveryDisplayedMenuCommand() {
val menuCommands = listOf(
"1" to "What now> 1",
"2" to "Update>>",
"3" to "Revert>>",
"4" to "Add untracked>>",
"5" to "Patch update>>",
"6" to "Diff>>",
"7" to "Bye.",
"8" to "What now> 8",
)
menuCommands.forEach { (command, expectedOutput) ->
val repo = RepoState(initialized = true, files = listOf(GitFile("README")))
val (menuRepo, _) = GitSandboxEngine.execute(repo, "git add -i")
val (updatedRepo, output) = GitSandboxEngine.execute(menuRepo, command)
assertFalse("$command should not be rejected", output.any { it.contains("Huh ($command)?") })
assertTrue("$command should produce $expectedOutput", output.any { it.contains(expectedOutput) })
if (command in listOf("2", "3", "4", "5", "6")) {
assertTrue(updatedRepo.interactiveAddSession?.awaitingUpdateSelection == true)
}
}
}
@Test
fun interactiveAddHandlesMenuCommandAliases() {
val aliases = listOf(
"status" to "What now> status",
"update" to "Update>>",
"revert" to "Revert>>",
"add untracked" to "Add untracked>>",
"patch" to "Patch update>>",
"diff" to "Diff>>",
"quit" to "Bye.",
"help" to "What now> help",
)
aliases.forEach { (command, expectedOutput) ->
val repo = RepoState(initialized = true, files = listOf(GitFile("README")))
val (menuRepo, _) = GitSandboxEngine.execute(repo, "git add -i")
val (updatedRepo, output) = GitSandboxEngine.execute(menuRepo, command)
assertFalse("$command should not be rejected", output.any { it.contains("Huh ($command)?") })
assertTrue("$command should produce $expectedOutput", output.any { it.contains(expectedOutput) })
if (command in listOf("update", "revert", "add untracked", "patch", "diff")) {
assertTrue(updatedRepo.interactiveAddSession?.awaitingUpdateSelection == true)
}
}
}
@Test
fun interactiveAddPatchSelectionStagesSelectedPath() {
val repo = RepoState(initialized = true, files = listOf(GitFile("README")))
val (menuRepo, _) = GitSandboxEngine.execute(repo, "git add -i")
val (patchRepo, patchOutput) = GitSandboxEngine.execute(menuRepo, "patch")
val (hunkRepo, hunkOutput) = GitSandboxEngine.execute(patchRepo, "1")
val (selectedRepo, selectionOutput) = GitSandboxEngine.execute(hunkRepo, "y")
assertTrue(patchRepo.interactiveAddSession?.awaitingUpdateSelection == true)
assertTrue(patchOutput.any { it.contains("Patch update>>") })
assertTrue(hunkOutput.any { it.contains("Stage this hunk") })
assertTrue(selectedRepo.files.single { it.name == "README" }.staged)
assertTrue(selectionOutput.any { it.contains("Stage this hunk") && it.contains("y") })
}
@Test
fun patchAddStartsPatchHunkDialogWithoutStagingImmediately() {
listOf("git add -p README", "git add --patch README").forEach { command ->
val repo = RepoState(initialized = true, files = listOf(GitFile("README")))
val (patchRepo, output) = GitSandboxEngine.execute(repo, command)
assertFalse("$command should not stage before a selection", patchRepo.files.single { it.name == "README" }.staged)
assertEquals("patch-hunk", patchRepo.interactiveAddSession?.selectionAction)
assertTrue(output.any { it.startsWith("diff --git a/README b/README") })
assertTrue(output.any { it.contains("Stage this hunk") })
}
}
@Test
fun patchAddSelectionStagesSelectedPath() {
val repo = RepoState(initialized = true, files = listOf(GitFile("README")))
val (patchRepo, _) = GitSandboxEngine.execute(repo, "git add -p README")
val (selectedRepo, output) = GitSandboxEngine.execute(patchRepo, "y")
assertTrue(selectedRepo.files.single { it.name == "README" }.staged)
assertTrue(output.any { it.contains("Stage this hunk") && it.contains("y") })
assertTrue(selectedRepo.interactiveAddSession == null)
}
@Test
fun patchAddHunkEditOpensPatchEditorInvocation() {
val repo = RepoState(initialized = true, files = listOf(GitFile("README", "A\nB\n", tracked = true)))
val (patchRepo, _) = GitSandboxEngine.execute(repo, "git add -p README")
val invocation = GitSandboxEngine.parsePatchHunkEditorInvocation(patchRepo, "e")
assertEquals(GitEditorCommandKind.PATCH_HUNK, invocation?.kind)
assertEquals("Edit Patch Hunk", invocation?.title)
assertTrue(invocation?.initialContent.orEmpty().contains("diff --git a/README b/README"))
assertTrue(invocation?.initialContent.orEmpty().contains("+A"))
assertFalse(invocation?.initialContent.orEmpty().contains("Stage this hunk"))
}
@Test
fun editedPatchHunkStagesCurrentPatchTarget() {
val repo = RepoState(initialized = true, files = listOf(GitFile("README", "A\nB\n", tracked = true)))
val (patchRepo, _) = GitSandboxEngine.execute(repo, "git add -p README")
val invocation = GitSandboxEngine.parsePatchHunkEditorInvocation(patchRepo, "e")
?: error("Expected patch editor invocation")
val (selectedRepo, output) = GitSandboxEngine.applyPatchHunkEdit(patchRepo, invocation.initialContent)
assertTrue(selectedRepo.files.single { it.name == "README" }.staged)
assertTrue(selectedRepo.interactiveAddSession == null)
assertTrue(output.any { it.contains("Applied edited hunk.") })
}
@Test
fun patchAddHunkDialogHandlesAdvertisedCommands() {
val commands = listOf("y", "n", "q", "a", "d", "s", "e", "p", "P", "?")
commands.forEach { command ->
val repo = RepoState(initialized = true, files = listOf(GitFile("README", "A\nB\n", tracked = true)))
val (patchRepo, _) = GitSandboxEngine.execute(repo, "git add -p README")
val (updatedRepo, output) = GitSandboxEngine.execute(patchRepo, command)
assertFalse("$command should not be rejected", output.any { it.contains("Unknown command '$command'") })
assertTrue("$command should echo hunk prompt", output.any { it.contains("Stage this hunk") })
if (command == "e") {
assertTrue(output.any { it.contains("Opening patch editor") })
}
if (command in listOf("y", "a")) {
assertTrue(updatedRepo.files.single { it.name == "README" }.staged)
assertTrue(updatedRepo.interactiveAddSession == null)
}
}
}
@Test
fun nativeInteractiveAddSelectionUpdatesGitIndex() {
val git = testGitBinary()
assumeTrue(git.exists() && git.canExecute())
val root = Files.createTempDirectory("githug-interactive-add").toFile()
try {
val runtime = GitRepositoryRuntime(root, git)
val level = level(
id = "interactive-add-test",
title = "Interactive Add Test",
description = "",
hints = emptyList(),
commandSuggestions = emptyList(),
setup = { RepoState(initialized = true, files = listOf(GitFile("README")), branches = mapOf("master" to 0)) },
validator = { _, _ -> false },
)
val repo = runtime.prepareLevel(level)
val (menuRepo, _) = runtime.execute(level, repo, "git stage -i")
val (updateRepo, _) = runtime.execute(level, menuRepo, "2")
val (selectedRepo, _) = runtime.execute(level, updateRepo, "1")
val (quitRepo, _) = runtime.execute(level, selectedRepo, "7")
val (_, statusOutput) = runtime.execute(level, quitRepo, "git status")
assertTrue(selectedRepo.files.single { it.name == "README" }.staged)
assertTrue(quitRepo.interactiveAddSession == null)
assertTrue(statusOutput.any { it.contains("new file:") && it.contains("README") })
} finally {
root.deleteRecursively()
}
}
@Test
fun nativePatchAddSelectionUpdatesGitIndex() {
val git = testGitBinary()
assumeTrue(git.exists() && git.canExecute())
val root = Files.createTempDirectory("githug-patch-add").toFile()
try {
val runtime = GitRepositoryRuntime(root, git)
val level = level(
id = "patch-add-test",
title = "Patch Add Test",
description = "",
hints = emptyList(),
commandSuggestions = emptyList(),
setup = { RepoState(initialized = true, files = listOf(GitFile("README")), branches = mapOf("master" to 0)) },
validator = { _, _ -> false },
)
val repo = runtime.prepareLevel(level)
val (patchRepo, patchOutput) = runtime.execute(level, repo, "git add -p README")
val (selectedRepo, _) = runtime.execute(level, patchRepo, "y")
assertTrue(patchOutput.any { it.contains("Stage this hunk") })
assertTrue(selectedRepo.files.single { it.name == "README" }.staged)
} finally {
root.deleteRecursively()
}
}
@Test
fun nativePatchHunkEditorSaveUpdatesGitIndex() {
val git = testGitBinary()
assumeTrue(git.exists() && git.canExecute())
val root = Files.createTempDirectory("githug-patch-edit").toFile()
try {
val runtime = GitRepositoryRuntime(root, git)
val level = level(
id = "patch-edit-test",
title = "Patch Edit Test",
description = "",
hints = emptyList(),
commandSuggestions = emptyList(),
setup = { RepoState(initialized = true, files = listOf(GitFile("README")), branches = mapOf("master" to 0)) },
validator = { _, _ -> false },
)
val repo = runtime.prepareLevel(level)
val (patchRepo, _) = runtime.execute(level, repo, "git add -p README")
val invocation = GitSandboxEngine.parsePatchHunkEditorInvocation(patchRepo, "e")
?: error("Expected patch editor invocation")
val (selectedRepo, output) = runtime.executeGitEditorCommand(level, patchRepo, invocation, invocation.initialContent)
assertTrue(output.any { it.contains("Applied edited hunk.") })
assertTrue(selectedRepo.files.single { it.name == "README" }.staged)
} finally {
root.deleteRecursively()
}
}
@Test
fun nativeInteractiveRebaseUsesAppSequenceEditorContent() {
val git = testGitBinary()