Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changes/unreleased/groupware-contacts-scene-test-flake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
category: internal
issue: none
pull: none
platforms: desktop
user-facing: no

The desktop contacts mutation scene tests now wait for the rendered contact list and the reconciled end state instead of a fixed number of frames, so a slow machine no longer fails them.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dev.obiente.nextcloudnative.app
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.text.AnnotatedString
import java.lang.reflect.Proxy
Expand All @@ -24,10 +25,15 @@ class GroupwareContactsMutationSceneTest {
NativeGroupwareContactsScreen(fixture.services, fixture.session, "alice", {},
onMutationInProgressChanged = { blocked = it })
}) {
// The list appears only after the recovery read and the DAV discovery and listing requests.
settleUntil(SCENE_TIMEOUT_MILLIS) { has("Synthetic contact") }
click("Synthetic contact")
click("Delete")
click("Delete")
repeat(4) { settle() }
settleUntil(SCENE_TIMEOUT_MILLIS) {
fixture.deletes > 0 && fixture.recovery == null && !blocked && !has("Synthetic contact")
}
settle()
assertEquals(1, fixture.deletes)
assertTrue(fixture.verifications >= 1, "Successful deletion must verify authoritative absence")
assertNull(fixture.recovery)
Expand All @@ -52,21 +58,30 @@ class GroupwareContactsMutationSceneTest {
navigationEnabled = !blocked.value, content = workspace,
)
}) {
// Creation is enabled only after the recovery read and the address book discovery complete.
settleUntil(SCENE_TIMEOUT_MILLIS) { enabled("Create contact") }
assertTrue(enabled("Create contact"), "Contacts did not finish loading a writable address book")
click("Create contact")
val name = generateSequence(requireNotNull(node("Name"))) { it.parent }
.first { it.config.getOrNull(SemanticsActions.SetText)?.action != null }
assertTrue(name.config[SemanticsActions.SetText].action!!.invoke(AnnotatedString("Synthetic contact")))
settle()
click("Save")
repeat(4) { settle() }
settleUntil(SCENE_TIMEOUT_MILLIS) {
fixture.creates > 0 && fixture.recovery == null && !blocked.value && has("Synthetic contact")
}
settle()
assertEquals(1, fixture.creates)
assertNull(fixture.recovery)
assertFalse(blocked.value)
assertFalse(has("Resolve contact recovery"))
click("Synthetic contact")
click("Delete")
click("Delete")
repeat(4) { settle() }
settleUntil(SCENE_TIMEOUT_MILLIS) {
fixture.deletes > 0 && fixture.recovery == null && !blocked.value && !has("Synthetic contact")
}
settle()
assertEquals(1, fixture.deletes)
assertTrue(fixture.verifications >= 2)
assertNull(fixture.recovery)
Expand All @@ -76,6 +91,9 @@ class GroupwareContactsMutationSceneTest {
}
}

private fun NativeSceneTestDriver.enabled(label: String): Boolean =
node(label)?.let { SemanticsProperties.Disabled !in it.config } == true

private class Fixture {
val session = NextcloudSession("https://contacts-scene.invalid", "alice", "synthetic")
val home = "/remote.php/dav/addressbooks/users/alice/"
Expand Down Expand Up @@ -130,4 +148,9 @@ class GroupwareContactsMutationSceneTest {
private fun item(href: String, props: String) = "<d:response><d:href>$href</d:href><d:propstat><d:prop>$props</d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response>"
private fun response(status: Int, body: String = "") = NextcloudApiResponse(status, body.encodeToByteArray(), "application/xml", "etag")
}

private companion object {
/** Bounds each wait for the delayed synthetic store and transport, which takes several frames. */
const val SCENE_TIMEOUT_MILLIS = 5_000L
}
}
Loading