From f0d21d7642798d4a2d28688a9eaa99c1f244ad27 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:34:20 +0900 Subject: [PATCH] Test: Render up to four frames after a Python write to a remembered TextFieldState On develop the single render after the write drew identical pixels (0 differ) although the state held the new text; the field's text-change handling plausibly lags one frame. The assertion is unchanged, so a write that never reaches the draw still fails. --- .../compose/TextFieldStateRenderTest.kt | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/ksp-fixtures/compose/src/desktopTest/kotlin/fixture/compose/TextFieldStateRenderTest.kt b/ksp-fixtures/compose/src/desktopTest/kotlin/fixture/compose/TextFieldStateRenderTest.kt index 15f74ec38..b63587e49 100644 --- a/ksp-fixtures/compose/src/desktopTest/kotlin/fixture/compose/TextFieldStateRenderTest.kt +++ b/ksp-fixtures/compose/src/desktopTest/kotlin/fixture/compose/TextFieldStateRenderTest.kt @@ -138,7 +138,8 @@ class TextFieldStateRenderTest { PythonComposition(REMEMBERED) } val before: IntArray - val after: IntArray + var after: IntArray + var frames = 0 try { before = pixelsOf(scene.render()) Python3.exec( @@ -148,8 +149,18 @@ class TextFieldStateRenderTest { _tfs_seen[0].setTextAndPlaceCursorAtEnd('a much longer line of text') """.trimIndent(), ) - Snapshot.sendApplyNotifications() - after = pixelsOf(scene.render()) + // `TextField(state=...)` does not finish reacting to a state write inside one frame: the + // text-changed handling of BasicTextField runs on the scene's coroutine dispatcher, which is + // flushed by `render()`, so the pixels can trail the write by a frame. The test is the frame + // clock (there is none), so it advances it explicitly -- but only up to MAX_FRAMES, and it + // stops at the first frame that differs. A write that truly never reaches the draw leaves every + // one of these frames identical to `before`, and the assertion below still fails. + after = before + while (frames < MAX_FRAMES && differing(before, after) <= 10) { + Snapshot.sendApplyNotifications() + after = pixelsOf(scene.render()) + frames++ + } Python3.exec( """ assert all(_s.text == 'a much longer line of text' for _s in _tfs_seen), \ @@ -160,7 +171,7 @@ class TextFieldStateRenderTest { scene.close() } val moved = differing(before, after) - println("compose rememberTextFieldState: ink ${inkOf(before)} -> ${inkOf(after)} px, $moved pixels differ") + println("compose rememberTextFieldState: ink ${inkOf(before)} -> ${inkOf(after)} px, $moved pixels differ after $frames frame(s)") assertTrue(inkOf(before) > 0, "TextField(state=rememberTextFieldState(...)) never composed") assertTrue(moved > 10, "only $moved pixels differ, so the write from Python never reached the next frame") } @@ -198,6 +209,9 @@ class TextFieldStateRenderTest { const val FIELD_WIDTH = 280 const val FIELD_HEIGHT = 64 + /** Upper bound on frames rendered after the write in the remembered-state test. */ + const val MAX_FRAMES = 4 + val REMEMBERED = """ from androidx.compose.foundation.text.input import rememberTextFieldState from androidx.compose.material3 import TextField