• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.communal.ui.viewmodel
18 
19 import androidx.compose.foundation.gestures.DraggableAnchors
20 import androidx.compose.runtime.snapshots.Snapshot
21 import androidx.test.ext.junit.runners.AndroidJUnit4
22 import androidx.test.filters.SmallTest
23 import com.android.systemui.SysuiTestCase
24 import com.android.systemui.coroutines.collectLastValue
25 import com.android.systemui.kosmos.testScope
26 import com.android.systemui.lifecycle.activateIn
27 import com.android.systemui.testKosmos
28 import com.google.common.truth.Truth.assertThat
29 import kotlinx.coroutines.test.TestScope
30 import kotlinx.coroutines.test.runCurrent
31 import kotlinx.coroutines.test.runTest
32 import org.junit.Before
33 import org.junit.Test
34 import org.junit.runner.RunWith
35 
36 @SmallTest
37 @RunWith(AndroidJUnit4::class)
38 class ResizeableItemFrameViewModelTest : SysuiTestCase() {
39     private val kosmos = testKosmos()
40     private val testScope = kosmos.testScope
41     private val underTest = kosmos.resizeableItemFrameViewModel
42 
43     /** Total viewport height of the entire grid */
44     private val viewportHeightPx = 100
45     /** Total amount of vertical padding around the viewport */
46     private val verticalContentPaddingPx = 20f
47 
48     private val singleSpanGrid =
49         GridLayout(
50             verticalItemSpacingPx = 10f,
51             verticalContentPaddingPx = verticalContentPaddingPx,
52             viewportHeightPx = viewportHeightPx,
53             currentRow = 0,
54             currentSpan = 1,
55             maxHeightPx = Int.MAX_VALUE,
56             minHeightPx = 0,
57             resizeMultiple = 1,
58             totalSpans = 1,
59         )
60 
61     @Before
setUpnull62     fun setUp() {
63         underTest.activateIn(testScope)
64     }
65 
66     @Test
testDefaultStatenull67     fun testDefaultState() {
68         val topState = underTest.topDragState
69         assertThat(topState.currentValue).isEqualTo(0)
70         assertThat(topState.offset).isEqualTo(0f)
71         assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
72 
73         val bottomState = underTest.bottomDragState
74         assertThat(bottomState.currentValue).isEqualTo(0)
75         assertThat(bottomState.offset).isEqualTo(0f)
76         assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
77     }
78 
79     @Test
testSingleSpanGridnull80     fun testSingleSpanGrid() =
81         testScope.runTest {
82             updateGridLayout(singleSpanGrid)
83 
84             val topState = underTest.topDragState
85             assertThat(topState.currentValue).isEqualTo(0)
86             assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
87 
88             val bottomState = underTest.bottomDragState
89             assertThat(bottomState.currentValue).isEqualTo(0)
90             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
91         }
92 
93     /**
94      * Verifies element in first row which is already at the minimum size can only be expanded
95      * downwards.
96      */
97     @Test
testTwoSpanGrid_elementInFirstRow_sizeSingleSpannull98     fun testTwoSpanGrid_elementInFirstRow_sizeSingleSpan() =
99         testScope.runTest {
100             updateGridLayout(singleSpanGrid.copy(currentRow = 0, totalSpans = 2))
101             val topState = underTest.topDragState
102             assertThat(topState.currentValue).isEqualTo(0)
103             assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
104 
105             val bottomState = underTest.bottomDragState
106             assertThat(bottomState.currentValue).isEqualTo(0)
107             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 45f)
108         }
109 
110     /**
111      * Verifies element in second row which is already at the minimum size can only be expanded
112      * upwards.
113      */
114     @Test
testTwoSpanGrid_elementInSecondRow_sizeSingleSpannull115     fun testTwoSpanGrid_elementInSecondRow_sizeSingleSpan() =
116         testScope.runTest {
117             updateGridLayout(singleSpanGrid.copy(currentRow = 1, totalSpans = 2))
118             val topState = underTest.topDragState
119             assertThat(topState.currentValue).isEqualTo(0)
120             assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f)
121 
122             val bottomState = underTest.bottomDragState
123             assertThat(bottomState.currentValue).isEqualTo(0)
124             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
125         }
126 
127     /**
128      * Verifies element in first row which is already at full size (2 span) can only be shrunk from
129      * the bottom.
130      */
131     @Test
testTwoSpanGrid_elementInFirstRow_sizeTwoSpannull132     fun testTwoSpanGrid_elementInFirstRow_sizeTwoSpan() =
133         testScope.runTest {
134             val adjustedGridLayout = singleSpanGrid.copy(currentSpan = 2, totalSpans = 2)
135 
136             updateGridLayout(adjustedGridLayout)
137 
138             val topState = underTest.topDragState
139             assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
140             assertThat(topState.currentValue).isEqualTo(0)
141 
142             val bottomState = underTest.bottomDragState
143             assertThat(bottomState.anchors.toList()).containsExactly(-1 to -45f, 0 to 0f)
144             assertThat(bottomState.currentValue).isEqualTo(0)
145         }
146 
147     /**
148      * Verifies element in a middle row at minimum size can be expanded from either top or bottom.
149      */
150     @Test
testThreeSpanGrid_elementInMiddleRow_sizeOneSpannull151     fun testThreeSpanGrid_elementInMiddleRow_sizeOneSpan() =
152         testScope.runTest {
153             val adjustedGridLayout =
154                 singleSpanGrid.copy(currentRow = 1, currentSpan = 1, totalSpans = 3)
155 
156             updateGridLayout(adjustedGridLayout)
157 
158             val topState = underTest.topDragState
159             assertThat(topState.currentValue).isEqualTo(0)
160             assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -30f)
161 
162             val bottomState = underTest.bottomDragState
163             assertThat(bottomState.currentValue).isEqualTo(0)
164             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 30f)
165         }
166 
167     @Test
testThreeSpanGrid_elementInTopRow_sizeOneSpannull168     fun testThreeSpanGrid_elementInTopRow_sizeOneSpan() =
169         testScope.runTest {
170             val adjustedGridLayout =
171                 singleSpanGrid.copy(currentRow = 0, currentSpan = 1, totalSpans = 3)
172 
173             updateGridLayout(adjustedGridLayout)
174 
175             val topState = underTest.topDragState
176             assertThat(topState.currentValue).isEqualTo(0)
177             assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
178 
179             val bottomState = underTest.bottomDragState
180             assertThat(bottomState.currentValue).isEqualTo(0)
181             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 30f, 2 to 60f)
182         }
183 
184     @Test
testSixSpanGrid_minSpanThree_itemInFourthRow_sizeThreeSpansnull185     fun testSixSpanGrid_minSpanThree_itemInFourthRow_sizeThreeSpans() =
186         testScope.runTest {
187             val adjustedGridLayout =
188                 singleSpanGrid.copy(
189                     currentRow = 3,
190                     currentSpan = 3,
191                     resizeMultiple = 3,
192                     totalSpans = 6,
193                 )
194 
195             updateGridLayout(adjustedGridLayout)
196 
197             val topState = underTest.topDragState
198             assertThat(topState.currentValue).isEqualTo(0)
199             assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -3 to -45f)
200 
201             val bottomState = underTest.bottomDragState
202             assertThat(bottomState.currentValue).isEqualTo(0)
203             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
204         }
205 
206     @Test
testTwoSpanGrid_elementMovesFromFirstRowToSecondRownull207     fun testTwoSpanGrid_elementMovesFromFirstRowToSecondRow() =
208         testScope.runTest {
209             val firstRowLayout =
210                 singleSpanGrid.copy(
211                     currentRow = 0,
212                     currentSpan = 1,
213                     resizeMultiple = 1,
214                     totalSpans = 2,
215                 )
216             updateGridLayout(firstRowLayout)
217 
218             val topState = underTest.topDragState
219             val bottomState = underTest.bottomDragState
220 
221             assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
222             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 45f)
223 
224             val secondRowLayout = firstRowLayout.copy(currentRow = 1)
225             updateGridLayout(secondRowLayout)
226 
227             assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f)
228             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
229         }
230 
231     @Test
<lambda>null232     fun testTwoSpanGrid_expandElementFromBottom() = runTestWithSnapshots {
233         val resizeInfo by collectLastValue(underTest.resizeInfo)
234 
235         val adjustedGridLayout = singleSpanGrid.copy(resizeMultiple = 1, totalSpans = 2)
236 
237         updateGridLayout(adjustedGridLayout)
238 
239         underTest.bottomDragState.anchoredDrag { dragTo(45f) }
240 
241         assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.BOTTOM))
242     }
243 
244     @Test
<lambda>null245     fun testThreeSpanGrid_expandMiddleElementUpwards() = runTestWithSnapshots {
246         val resizeInfo by collectLastValue(underTest.resizeInfo)
247         updateGridLayout(singleSpanGrid.copy(currentRow = 1, totalSpans = 3))
248 
249         underTest.topDragState.anchoredDrag { dragTo(-30f) }
250         assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.TOP))
251     }
252 
253     @Test
<lambda>null254     fun testThreeSpanGrid_expandTopElementDownBy2Spans() = runTestWithSnapshots {
255         val resizeInfo by collectLastValue(underTest.resizeInfo)
256         updateGridLayout(singleSpanGrid.copy(totalSpans = 3))
257 
258         assertThat(resizeInfo).isNull()
259         underTest.bottomDragState.anchoredDrag { dragTo(60f) }
260         assertThat(resizeInfo).isEqualTo(ResizeInfo(2, DragHandle.BOTTOM))
261     }
262 
263     @Test
<lambda>null264     fun testTwoSpanGrid_shrinkElementFromBottom() = runTestWithSnapshots {
265         val resizeInfo by collectLastValue(underTest.resizeInfo)
266         updateGridLayout(singleSpanGrid.copy(totalSpans = 2, currentSpan = 2))
267 
268         assertThat(resizeInfo).isNull()
269         underTest.bottomDragState.anchoredDrag { dragTo(-45f) }
270         assertThat(resizeInfo).isEqualTo(ResizeInfo(-1, DragHandle.BOTTOM))
271     }
272 
273     @Test
testRowInfoBecomesNull_revertsBackToDefaultnull274     fun testRowInfoBecomesNull_revertsBackToDefault() =
275         testScope.runTest {
276             val gridLayout = singleSpanGrid.copy(currentRow = 1, resizeMultiple = 1, totalSpans = 3)
277             updateGridLayout(gridLayout)
278 
279             val topState = underTest.topDragState
280             assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -30f)
281 
282             val bottomState = underTest.bottomDragState
283             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 30f)
284 
285             // Set currentRow to null to simulate the row info becoming null
286             updateGridLayout(gridLayout.copy(currentRow = null))
287             assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
288             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
289         }
290 
291     @Test
testEqualMaxAndMinHeight_cannotResizenull292     fun testEqualMaxAndMinHeight_cannotResize() =
293         testScope.runTest {
294             val heightPx = 20
295             updateGridLayout(
296                 singleSpanGrid.copy(maxHeightPx = heightPx, minHeightPx = heightPx, totalSpans = 2)
297             )
298 
299             val topState = underTest.topDragState
300             val bottomState = underTest.bottomDragState
301 
302             assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
303             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
304         }
305 
306     @Test
testMinHeightTwoRows_canExpandButNotShrinknull307     fun testMinHeightTwoRows_canExpandButNotShrink() =
308         testScope.runTest {
309             val threeRowGrid =
310                 singleSpanGrid.copy(
311                     maxHeightPx = 80,
312                     minHeightPx = 50,
313                     totalSpans = 3,
314                     currentSpan = 2,
315                     currentRow = 0,
316                 )
317 
318             updateGridLayout(threeRowGrid)
319 
320             val topState = underTest.topDragState
321             val bottomState = underTest.bottomDragState
322             assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
323             assertThat(bottomState.anchors.toList()).containsAtLeast(0 to 0f, 1 to 30f)
324         }
325 
326     @Test
testMaxHeightTwoRows_canShrinkButNotExpandnull327     fun testMaxHeightTwoRows_canShrinkButNotExpand() =
328         testScope.runTest {
329             val threeRowGrid =
330                 singleSpanGrid.copy(
331                     maxHeightPx = 50,
332                     minHeightPx = 20,
333                     totalSpans = 3,
334                     currentSpan = 2,
335                     currentRow = 0,
336                 )
337 
338             updateGridLayout(threeRowGrid)
339 
340             val topState = underTest.topDragState
341             val bottomState = underTest.bottomDragState
342 
343             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, -1 to -30f)
344 
345             assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
346         }
347 
348     @Test
testMinHeightEqualToAvailableSpan_cannotResizenull349     fun testMinHeightEqualToAvailableSpan_cannotResize() =
350         testScope.runTest {
351             val twoRowGrid =
352                 singleSpanGrid.copy(
353                     minHeightPx = (viewportHeightPx - verticalContentPaddingPx.toInt()),
354                     totalSpans = 2,
355                     currentSpan = 2,
356                 )
357 
358             updateGridLayout(twoRowGrid)
359 
360             val topState = underTest.topDragState
361             val bottomState = underTest.bottomDragState
362 
363             assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
364             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
365         }
366 
367     @Test
testMaxSpansLessThanCurrentSpannull368     fun testMaxSpansLessThanCurrentSpan() =
369         testScope.runTest {
370             // heightPerSpan =
371             // (viewportHeightPx - verticalContentPaddingPx - (totalSpans - 1)
372             // * verticalItemSpacingPx) / totalSpans
373             // = 145.3333
374             // maxSpans = (maxHeightPx + verticalItemSpacing) /
375             // (heightPerSpanPx + verticalItemSpacingPx)
376             // = 4.72
377             // This is invalid because the max span calculation comes out to be less than
378             // the current span. Ensure we handle this case correctly.
379             val layout =
380                 GridLayout(
381                     verticalItemSpacingPx = 100f,
382                     currentRow = 0,
383                     minHeightPx = 480,
384                     maxHeightPx = 1060,
385                     currentSpan = 6,
386                     resizeMultiple = 3,
387                     totalSpans = 6,
388                     viewportHeightPx = 1600,
389                     verticalContentPaddingPx = 228f,
390                 )
391             updateGridLayout(layout)
392 
393             val topState = underTest.topDragState
394             val bottomState = underTest.bottomDragState
395 
396             assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
397             assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, -3 to -736f)
398         }
399 
400     @Test
testCanExpand_atTopPosition_withMultipleAnchors_returnsTruenull401     fun testCanExpand_atTopPosition_withMultipleAnchors_returnsTrue() =
402         testScope.runTest {
403             val twoRowGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 0)
404 
405             updateGridLayout(twoRowGrid)
406             assertThat(underTest.canExpand()).isTrue()
407             assertThat(underTest.bottomDragState.anchors.toList())
408                 .containsAtLeast(0 to 0f, 1 to 45f)
409         }
410 
411     @Test
testCanExpand_atTopPosition_withSingleAnchors_returnsFalsenull412     fun testCanExpand_atTopPosition_withSingleAnchors_returnsFalse() =
413         testScope.runTest {
414             val oneRowGrid = singleSpanGrid.copy(totalSpans = 1, currentSpan = 1, currentRow = 0)
415             updateGridLayout(oneRowGrid)
416             assertThat(underTest.canExpand()).isFalse()
417         }
418 
419     @Test
testCanExpand_atBottomPosition_withMultipleAnchors_returnsTruenull420     fun testCanExpand_atBottomPosition_withMultipleAnchors_returnsTrue() =
421         testScope.runTest {
422             val twoRowGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 1)
423             updateGridLayout(twoRowGrid)
424             assertThat(underTest.canExpand()).isTrue()
425             assertThat(underTest.topDragState.anchors.toList()).containsAtLeast(0 to 0f, -1 to -45f)
426         }
427 
428     @Test
testCanShrink_atMinimumHeight_returnsFalsenull429     fun testCanShrink_atMinimumHeight_returnsFalse() =
430         testScope.runTest {
431             val oneRowGrid = singleSpanGrid.copy(totalSpans = 1, currentSpan = 1, currentRow = 0)
432             updateGridLayout(oneRowGrid)
433             assertThat(underTest.canShrink()).isFalse()
434         }
435 
436     @Test
<lambda>null437     fun testCanShrink_atFullSize_checksBottomDragState() = runTestWithSnapshots {
438         val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 2, currentRow = 0)
439         updateGridLayout(twoSpanGrid)
440 
441         assertThat(underTest.canShrink()).isTrue()
442         assertThat(underTest.bottomDragState.anchors.toList()).containsAtLeast(0 to 0f, -1 to -45f)
443     }
444 
445     @Test
<lambda>null446     fun testResizeByAccessibility_expandFromBottom_usesTopDragState() = runTestWithSnapshots {
447         val resizeInfo by collectLastValue(underTest.resizeInfo)
448 
449         val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 1)
450         updateGridLayout(twoSpanGrid)
451 
452         underTest.expandToNextAnchor()
453 
454         assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.TOP))
455     }
456 
457     @Test
<lambda>null458     fun testResizeByAccessibility_expandFromTop_usesBottomDragState() = runTestWithSnapshots {
459         val resizeInfo by collectLastValue(underTest.resizeInfo)
460 
461         val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 0)
462         updateGridLayout(twoSpanGrid)
463 
464         underTest.expandToNextAnchor()
465 
466         assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.BOTTOM))
467     }
468 
469     @Test
<lambda>null470     fun testResizeByAccessibility_shrinkFromFull_usesBottomDragState() = runTestWithSnapshots {
471         val resizeInfo by collectLastValue(underTest.resizeInfo)
472 
473         val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 2, currentRow = 0)
474         updateGridLayout(twoSpanGrid)
475 
476         underTest.shrinkToNextAnchor()
477 
478         assertThat(resizeInfo).isEqualTo(ResizeInfo(-1, DragHandle.BOTTOM))
479     }
480 
481     @Test
<lambda>null482     fun testResizeByAccessibility_cannotResizeAtMinSize() = runTestWithSnapshots {
483         val resizeInfo by collectLastValue(underTest.resizeInfo)
484 
485         // Set up grid at minimum size
486         val minSizeGrid =
487             singleSpanGrid.copy(
488                 totalSpans = 2,
489                 currentSpan = 1,
490                 minHeightPx = singleSpanGrid.minHeightPx,
491                 currentRow = 0,
492             )
493         updateGridLayout(minSizeGrid)
494 
495         underTest.shrinkToNextAnchor()
496 
497         assertThat(resizeInfo).isNull()
498     }
499 
500     @Test(expected = IllegalArgumentException::class)
testIllegalState_maxHeightLessThanMinHeightnull501     fun testIllegalState_maxHeightLessThanMinHeight() =
502         testScope.runTest {
503             updateGridLayout(singleSpanGrid.copy(maxHeightPx = 50, minHeightPx = 100))
504         }
505 
506     @Test(expected = IllegalArgumentException::class)
testIllegalState_currentSpanExceedsTotalSpansnull507     fun testIllegalState_currentSpanExceedsTotalSpans() =
508         testScope.runTest { updateGridLayout(singleSpanGrid.copy(currentSpan = 3, totalSpans = 2)) }
509 
510     @Test(expected = IllegalArgumentException::class)
testIllegalState_resizeMultipleZeroOrNegativenull511     fun testIllegalState_resizeMultipleZeroOrNegative() =
512         testScope.runTest { updateGridLayout(singleSpanGrid.copy(resizeMultiple = 0)) }
513 
514     @Test
<lambda>null515     fun testZeroHeights_cannotResize() = runTestWithSnapshots {
516         val zeroHeightGrid =
517             singleSpanGrid.copy(
518                 totalSpans = 2,
519                 currentSpan = 1,
520                 currentRow = 0,
521                 minHeightPx = 0,
522                 maxHeightPx = 0,
523             )
524         updateGridLayout(zeroHeightGrid)
525 
526         val topState = underTest.topDragState
527         val bottomState = underTest.bottomDragState
528         assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
529         assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
530     }
531 
updateGridLayoutnull532     private fun TestScope.updateGridLayout(gridLayout: GridLayout) {
533         underTest.setGridLayoutInfo(
534             verticalItemSpacingPx = gridLayout.verticalItemSpacingPx,
535             currentRow = gridLayout.currentRow,
536             maxHeightPx = gridLayout.maxHeightPx,
537             minHeightPx = gridLayout.minHeightPx,
538             currentSpan = gridLayout.currentSpan,
539             resizeMultiple = gridLayout.resizeMultiple,
540             totalSpans = gridLayout.totalSpans,
541             viewportHeightPx = gridLayout.viewportHeightPx,
542             verticalContentPaddingPx = gridLayout.verticalContentPaddingPx,
543         )
544         runCurrent()
545     }
546 
<lambda>null547     private fun DraggableAnchors<Int>.toList() = buildList {
548         for (index in 0 until this@toList.size) {
549             add(anchorAt(index) to positionAt(index))
550         }
551     }
552 
runTestWithSnapshotsnull553     private fun runTestWithSnapshots(testBody: suspend TestScope.() -> Unit) {
554         val globalWriteObserverHandle =
555             Snapshot.registerGlobalWriteObserver {
556                 // This is normally done by the compose runtime.
557                 Snapshot.sendApplyNotifications()
558             }
559 
560         try {
561             testScope.runTest(testBody = testBody)
562         } finally {
563             globalWriteObserverHandle.dispose()
564         }
565     }
566 
567     private data class GridLayout(
568         val verticalItemSpacingPx: Float,
569         val verticalContentPaddingPx: Float,
570         val viewportHeightPx: Int,
571         val currentRow: Int?,
572         val currentSpan: Int,
573         val maxHeightPx: Int,
574         val minHeightPx: Int,
575         val resizeMultiple: Int,
576         val totalSpans: Int,
577     )
578 }
579