Defer level validation while editors are open

This commit is contained in:
Joe Tretter
2026-06-26 13:26:56 -05:00
parent 781b5090f7
commit 91913a9de8
3 changed files with 48 additions and 8 deletions

View File

@@ -20,8 +20,8 @@ android {
applicationId = "solutions.tretter.githugandroid"
minSdk = 26
targetSdk = 35
versionCode = 179
versionName = "0.1.178"
versionCode = 180
versionName = "0.1.179"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true

View File

@@ -3,6 +3,7 @@ package solutions.tretter.githugandroid
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.test.performClick
@@ -84,6 +85,25 @@ class GitRepositoryRuntimeInstrumentedTest {
}
}
@Test
fun squashLevelWaitsForFinalEditorBeforeCompleting() {
launchFreshApp().use {
selectLevel(squashLevel())
submitTerminalCommand("git rebase -i HEAD~4")
waitForGitEditorPath(".git/rebase-merge/git-rebase-todo")
replaceGitEditorContent(squashRebaseTodo(currentGitEditorContent()))
composeRule.onNodeWithTag("git-message-editor-save").performClick()
waitForGitMessageEditor()
composeRule.onNodeWithTag("exercise-title-${squashLevel().id}").assertIsDisplayed()
composeRule.onNodeWithTag("git-message-editor-save").performClick()
waitForLevelCompletion(nextLevel = initLevel())
}
}
@Test
fun configLevelCompletesThroughUiWithArbitraryValues() {
launchFreshApp().use {
@@ -182,12 +202,28 @@ class GitRepositoryRuntimeInstrumentedTest {
composeRule.onNodeWithTag("git-message-editor-path").assertIsDisplayed()
}
private fun waitForGitMessageEditor() {
composeRule.waitUntil(timeoutMillis = 30_000) {
composeRule.onAllNodesWithTag("git-message-editor-content").fetchSemanticsNodes().isNotEmpty()
}
composeRule.onNodeWithTag("git-message-editor-content").assertIsDisplayed()
}
private fun currentGitEditorContent(): String {
val node = composeRule.onNodeWithTag("git-message-editor-content").fetchSemanticsNode()
return node.config.getOrNull(SemanticsProperties.EditableText)?.text
?: error("Expected Git editor content semantics")
}
private fun squashRebaseTodo(content: String): String {
return content
.lineSequence()
.mapIndexed { index, line ->
if (index > 0 && line.startsWith("pick ")) line.replaceFirst("pick ", "squash ") else line
}
.joinToString("\n")
}
private fun replaceGitEditorContent(content: String) {
composeRule.onNodeWithTag("git-message-editor-content")
.performSemanticsAction(SemanticsActions.SetText) { setText ->

View File

@@ -388,11 +388,16 @@ fun GitHugApp() {
fun applyCommandResult(raw: String, newRepo: RepoState, lines: List<String>, echoCommand: Boolean) {
val startedAt = System.nanoTime()
val levelForResult = currentLevel
val solvedAfterCommand = levelForResult.validator(newRepo, raw)
val validationBlockedByEditor = editorState != null || gitMessageEditorState != null
val solvedAfterCommand = if (validationBlockedByEditor) {
false
} else {
levelForResult.validator(newRepo, raw)
}
val wasAlreadyCompleted = currentLevel.id in completedLevels
AppLog.d(
"GitHugApp",
"Command='$raw' level=${levelForResult.id} solved=$solvedAfterCommand alreadyCompleted=$wasAlreadyCompleted " +
"Command='$raw' level=${levelForResult.id} solved=$solvedAfterCommand validationBlockedByEditor=$validationBlockedByEditor alreadyCompleted=$wasAlreadyCompleted " +
"completedBefore=${completedLevels.sorted()} outputLineCount=${lines.size} repo=${newRepo.diagnosticSnapshot()}",
)
val newOutput = buildList {
@@ -519,14 +524,13 @@ fun GitHugApp() {
invocation = state.invocation,
message = state.content,
)
gitMessageEditorState = null
applyCommandResult(state.invocation.command, result.repo, result.outputLines, echoCommand = false)
result.nextEditor?.let { nextEditor ->
gitMessageEditorState = GitMessageEditorState(
gitMessageEditorState = result.nextEditor?.let { nextEditor ->
GitMessageEditorState(
invocation = nextEditor.invocation,
content = nextEditor.content,
)
}
applyCommandResult(state.invocation.command, result.repo, result.outputLines, echoCommand = false)
}
fun runCommand() {