1 /*
<lambda>null2  * Copyright 2020 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 androidx.compose.ui.layout
18 
19 import androidx.activity.ComponentActivity
20 import androidx.compose.foundation.layout.Box
21 import androidx.compose.foundation.layout.offset
22 import androidx.compose.foundation.layout.requiredSize
23 import androidx.compose.foundation.layout.size
24 import androidx.compose.runtime.Composable
25 import androidx.compose.runtime.getValue
26 import androidx.compose.runtime.mutableStateMapOf
27 import androidx.compose.runtime.mutableStateOf
28 import androidx.compose.runtime.setValue
29 import androidx.compose.ui.Modifier
30 import androidx.compose.ui.test.junit4.createAndroidComposeRule
31 import androidx.compose.ui.unit.Constraints
32 import androidx.compose.ui.unit.IntOffset
33 import androidx.compose.ui.unit.dp
34 import androidx.test.ext.junit.runners.AndroidJUnit4
35 import androidx.test.filters.MediumTest
36 import kotlin.math.min
37 import org.junit.Assert.assertEquals
38 import org.junit.Rule
39 import org.junit.Test
40 import org.junit.runner.RunWith
41 
42 @MediumTest
43 @RunWith(AndroidJUnit4::class)
44 class AlignmentLineTest {
45     @get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
46 
47     @Test
48     fun queryingLinesOfUnmeasuredChild() {
49         val root = root {
50             queryAlignmentLineDuringMeasure()
51             add(
52                 node {
53                     doNotMeasure()
54                     add(node())
55                 }
56             )
57         }
58 
59         createDelegate(root)
60         assertMeasuredAndLaidOut(root)
61     }
62 
63     @Test
64     fun alignmentLinesPositionInCooperation_whenModifierDisobeys() {
65         val hLine = HorizontalAlignmentLine(::min)
66         val vLine = VerticalAlignmentLine(::min)
67         val hLinePosition = 50
68         val vLinePosition = 150
69         val constrainedSize = 100
70         val actualSize = 200
71         rule.setContent {
72             val contentWithAlignmentLines =
73                 @Composable {
74                     Box(Modifier.requiredSize(with(rule.density) { actualSize.toDp() })) {
75                         Layout({}, Modifier) { _, _ ->
76                             layout(0, 0, mapOf(hLine to hLinePosition, vLine to vLinePosition)) {}
77                         }
78                     }
79                 }
80             Layout(contentWithAlignmentLines) { measurables, _ ->
81                 val placeable =
82                     measurables
83                         .first()
84                         .measure(
85                             Constraints(maxWidth = constrainedSize, maxHeight = constrainedSize)
86                         )
87                 val obtainedHLinePosition = placeable[hLine]
88                 val obtainedVLinePosition = placeable[vLine]
89                 assertEquals(
90                     hLinePosition - (actualSize - constrainedSize) / 2,
91                     obtainedHLinePosition
92                 )
93                 assertEquals(
94                     vLinePosition - (actualSize - constrainedSize) / 2,
95                     obtainedVLinePosition
96                 )
97                 layout(0, 0) {}
98             }
99         }
100         rule.waitForIdle()
101     }
102 
103     @Test
104     fun alignmentLinesPositionInCooperation_whenLayoutDisobeys() {
105         val hLine = HorizontalAlignmentLine(::min)
106         val vLine = VerticalAlignmentLine(::min)
107         val hLinePosition = 50
108         val vLinePosition = 150
109         val constrainedSize = 100
110         val actualSize = 200
111         rule.setContent {
112             val contentWithAlignmentLines =
113                 @Composable {
114                     Layout({}, Modifier) { _, _ ->
115                         layout(
116                             actualSize,
117                             actualSize,
118                             mapOf(hLine to hLinePosition, vLine to vLinePosition)
119                         ) {}
120                     }
121                 }
122             Layout(contentWithAlignmentLines) { measurables, _ ->
123                 val placeable =
124                     measurables
125                         .first()
126                         .measure(
127                             Constraints(maxWidth = constrainedSize, maxHeight = constrainedSize)
128                         )
129                 val obtainedHLinePosition = placeable[hLine]
130                 val obtainedVLinePosition = placeable[vLine]
131                 assertEquals(
132                     hLinePosition - (actualSize - constrainedSize) / 2,
133                     obtainedHLinePosition
134                 )
135                 assertEquals(
136                     vLinePosition - (actualSize - constrainedSize) / 2,
137                     obtainedVLinePosition
138                 )
139                 layout(0, 0) {}
140             }
141         }
142         rule.waitForIdle()
143     }
144 
145     @Test
146     fun alignmentLinesArePropagated_whenSuppliedViaModifier() {
147         val size = 50
148         val sizeDp = with(rule.density) { size.toDp() }
149         val linePosition = 25
150         val hLine = HorizontalAlignmentLine(::min)
151         val vLine = VerticalAlignmentLine(::min)
152         rule.setContent {
153             val content =
154                 @Composable {
155                     Box(Modifier.size(sizeDp)) {
156                         Box(
157                             Modifier.supplyAlignmentLines {
158                                     mapOf(hLine to linePosition, vLine to linePosition)
159                                 }
160                                 .size(sizeDp)
161                         )
162                     }
163                 }
164 
165             Layout(content) { measurables, constraints ->
166                 val placeable = measurables.first().measure(constraints)
167                 assertEquals(linePosition, placeable[hLine])
168                 assertEquals(linePosition, placeable[vLine])
169                 layout(0, 0) {}
170             }
171         }
172     }
173 
174     @Test
175     fun alignmentLinesArePropagated_whenSuppliedViaModifier_withCorrectPosition() {
176         val size = 50
177         val sizeDp = with(rule.density) { size.toDp() }
178         val offset = 10
179         val offsetDp = with(rule.density) { offset.toDp() }
180         val linePosition = 25
181         val hLine = HorizontalAlignmentLine(::min)
182         val vLine = VerticalAlignmentLine(::min)
183         rule.setContent {
184             val content =
185                 @Composable {
186                     Box(Modifier.size(sizeDp)) {
187                         Box(
188                             Modifier.offset(offsetDp, offsetDp)
189                                 .supplyAlignmentLines {
190                                     mapOf(hLine to linePosition, vLine to linePosition)
191                                 }
192                                 .size(sizeDp)
193                                 .offset(offsetDp, offsetDp)
194                         )
195                     }
196                 }
197 
198             Layout(content) { measurables, constraints ->
199                 val placeable = measurables.first().measure(constraints)
200                 assertEquals(linePosition + offset, placeable[hLine])
201                 assertEquals(linePosition + offset, placeable[vLine])
202                 layout(0, 0) {}
203             }
204         }
205     }
206 
207     @Test
208     fun alignmentLinesChangeCausesRemeasure_whenSuppliedViaModifier() {
209         val size = 50
210         val sizeDp = with(rule.density) { size.toDp() }
211         val offset = 10
212         val linePosition = 25
213         val hLine = HorizontalAlignmentLine(::min)
214         val vLine = VerticalAlignmentLine(::min)
215         val alignmentLines = mutableStateMapOf(hLine to linePosition, vLine to linePosition)
216         var obtainedHLinePosition = -1
217         var obtainedVLinePosition = -1
218         rule.setContent {
219             val content =
220                 @Composable {
221                     Box(Modifier.size(sizeDp)) {
222                         Box(Modifier.supplyAlignmentLines { alignmentLines.toMap() }.size(sizeDp))
223                     }
224                 }
225 
226             Layout(content) { measurables, constraints ->
227                 val placeable = measurables.first().measure(constraints)
228                 obtainedHLinePosition = placeable[hLine]
229                 obtainedVLinePosition = placeable[vLine]
230                 layout(0, 0) {}
231             }
232         }
233 
234         rule.runOnIdle {
235             assertEquals(linePosition, obtainedHLinePosition)
236             assertEquals(linePosition, obtainedVLinePosition)
237             alignmentLines[hLine] = linePosition + offset
238             alignmentLines[vLine] = linePosition + offset
239         }
240 
241         rule.runOnIdle {
242             assertEquals(linePosition + offset, obtainedHLinePosition)
243             assertEquals(linePosition + offset, obtainedVLinePosition)
244         }
245     }
246 
247     @Test
248     fun alignmentLinesChangeCausesRemeasure_whenSuppliedViaLayout() {
249         val size = 50
250         val sizeDp = with(rule.density) { size.toDp() }
251         val offset = 10
252         val linePosition = 25
253         val hLine = HorizontalAlignmentLine(::min)
254         val vLine = VerticalAlignmentLine(::min)
255         val alignmentLines = mutableStateMapOf(hLine to linePosition, vLine to linePosition)
256         var obtainedHLinePosition = -1
257         var obtainedVLinePosition = -1
258         rule.setContent {
259             val content =
260                 @Composable {
261                     val innerContent =
262                         @Composable { Layout({}) { _, _ -> layout(size, size, alignmentLines) {} } }
263                     Layout(content = innerContent, Modifier.size(sizeDp)) { measurables, constraints
264                         ->
265                         val placeable = measurables.first().measure(constraints)
266                         layout(constraints.maxWidth, constraints.maxHeight) {
267                             placeable.place(0, 0)
268                         }
269                     }
270                 }
271 
272             Layout(content) { measurables, constraints ->
273                 val placeable = measurables.first().measure(constraints)
274                 obtainedHLinePosition = placeable[hLine]
275                 obtainedVLinePosition = placeable[vLine]
276                 layout(0, 0) {}
277             }
278         }
279 
280         rule.runOnIdle {
281             assertEquals(linePosition, obtainedHLinePosition)
282             assertEquals(linePosition, obtainedVLinePosition)
283             alignmentLines[hLine] = linePosition + offset
284             alignmentLines[vLine] = linePosition + offset
285         }
286 
287         rule.runOnIdle {
288             assertEquals(linePosition + offset, obtainedHLinePosition)
289             assertEquals(linePosition + offset, obtainedVLinePosition)
290         }
291     }
292 
293     @Test
294     fun scenario1() {
295         var parentMeasures = 0
296         var measures = 0
297         rule.setContent {
298             Parent(onMeasure = { ++parentMeasures }) {
299                 Parent(onMeasure = { ++measures }, readDuringMeasure = true) {
300                     Parent { Provider() }
301                 }
302             }
303         }
304         rule.runOnIdle {
305             assertEquals(1, parentMeasures)
306             assertEquals(1, measures)
307             changeLinePosition()
308         }
309 
310         rule.runOnIdle {
311             assertEquals(1, parentMeasures)
312             assertEquals(2, measures)
313         }
314     }
315 
316     @Test
317     fun scenario2() {
318         var parentLayouts = 0
319         var measures = 0
320         var layouts = 0
321         rule.setContent {
322             Parent(onLayout = { ++parentLayouts }) {
323                 Parent(
324                     onMeasure = { ++measures },
325                     onLayout = { ++layouts },
326                     readDuringLayoutBeforePlacing = true
327                 ) {
328                     Parent { Provider() }
329                 }
330             }
331         }
332         rule.runOnIdle {
333             assertEquals(1, parentLayouts)
334             assertEquals(1, measures)
335             assertEquals(1, layouts)
336             changeLinePosition()
337         }
338 
339         rule.runOnIdle {
340             assertEquals(1, parentLayouts)
341             assertEquals(1, measures)
342             assertEquals(2, layouts)
343         }
344     }
345 
346     @Test
347     fun scenario3() {
348         var parentLayouts = 0
349         var measures = 0
350         var layouts = 0
351         rule.setContent {
352             Parent(onLayout = { ++parentLayouts }) {
353                 Parent(
354                     onMeasure = { ++measures },
355                     onLayout = { ++layouts },
356                     readDuringLayoutAfterPlacing = true
357                 ) {
358                     Parent { Provider() }
359                 }
360             }
361         }
362         rule.runOnIdle {
363             assertEquals(1, parentLayouts)
364             assertEquals(1, measures)
365             assertEquals(1, layouts)
366             changeLinePosition()
367         }
368 
369         rule.runOnIdle {
370             assertEquals(1, parentLayouts)
371             assertEquals(1, measures)
372             assertEquals(2, layouts)
373         }
374     }
375 
376     @Test
377     fun scenario4() {
378         var parentMeasures = 0
379         var parentLayouts = 0
380         var measures = 0
381         var layouts = 0
382         rule.setContent {
383             Parent(
384                 onMeasure = { ++parentMeasures },
385                 onLayout = { ++parentLayouts },
386                 readDuringMeasure = true
387             ) {
388                 Parent(
389                     onMeasure = { ++measures },
390                     onLayout = { ++layouts },
391                     readDuringLayoutBeforePlacing = true
392                 ) {
393                     Parent { Provider() }
394                 }
395             }
396         }
397         rule.runOnIdle {
398             assertEquals(1, parentMeasures)
399             assertEquals(1, parentLayouts)
400             assertEquals(1, measures)
401             assertEquals(1, layouts)
402             changeLinePosition()
403         }
404 
405         rule.runOnIdle {
406             assertEquals(2, parentMeasures)
407             assertEquals(2, parentLayouts)
408             assertEquals(1, measures)
409             assertEquals(2, layouts)
410         }
411     }
412 
413     @Test
414     fun scenario5() {
415         var parentMeasures = 0
416         var parentLayouts = 0
417         var measures = 0
418         var layouts = 0
419         rule.setContent {
420             Parent(
421                 onMeasure = { ++parentMeasures },
422                 onLayout = { ++parentLayouts },
423                 readDuringMeasure = true
424             ) {
425                 Parent(
426                     onMeasure = { ++measures },
427                     onLayout = { ++layouts },
428                     readDuringLayoutAfterPlacing = true
429                 ) {
430                     Parent { Provider() }
431                 }
432             }
433         }
434         rule.runOnIdle {
435             assertEquals(1, parentMeasures)
436             assertEquals(1, parentLayouts)
437             assertEquals(1, measures)
438             assertEquals(1, layouts)
439             changeLinePosition()
440         }
441 
442         rule.runOnIdle {
443             assertEquals(2, parentMeasures)
444             assertEquals(2, parentLayouts)
445             assertEquals(1, measures)
446             assertEquals(2, layouts)
447         }
448     }
449 
450     @Test
451     fun scenario6() {
452         var parentMeasures = 0
453         var parentLayouts = 0
454         var measures = 0
455         var layouts = 0
456         rule.setContent {
457             Parent(
458                 onMeasure = { ++parentMeasures },
459                 onLayout = { ++parentLayouts },
460                 readDuringLayoutAfterPlacing = true
461             ) {
462                 Parent(
463                     onMeasure = { ++measures },
464                     onLayout = { ++layouts },
465                     readDuringMeasure = true
466                 ) {
467                     Parent { Provider() }
468                 }
469             }
470         }
471         rule.runOnIdle {
472             assertEquals(1, parentMeasures)
473             assertEquals(1, parentLayouts)
474             assertEquals(1, measures)
475             assertEquals(1, layouts)
476             changeLinePosition()
477         }
478 
479         rule.runOnIdle {
480             assertEquals(1, parentMeasures)
481             assertEquals(2, parentLayouts)
482             assertEquals(2, measures)
483             assertEquals(2, layouts)
484         }
485     }
486 
487     @Test
488     fun scenario7() {
489         var parentMeasures = 0
490         var measures = 0
491         var layouts = 0
492         var childMeasures = 0
493         rule.setContent {
494             Parent(onMeasure = { ++parentMeasures }) {
495                 Parent(
496                     onMeasure = { ++measures },
497                     onLayout = { ++layouts },
498                     readDuringMeasure = true
499                 ) {
500                     Parent(modifier = Modifier.provider(), onMeasure = { ++childMeasures }) {
501                         Parent()
502                     }
503                 }
504             }
505         }
506         rule.runOnIdle {
507             assertEquals(1, parentMeasures)
508             assertEquals(1, measures)
509             assertEquals(1, layouts)
510             assertEquals(1, childMeasures)
511             changeLinePosition()
512         }
513 
514         rule.runOnIdle {
515             assertEquals(1, parentMeasures)
516             assertEquals(2, measures)
517             assertEquals(2, layouts)
518             assertEquals(2, childMeasures)
519         }
520     }
521 
522     @Test
523     fun scenario8() {
524         var parentMeasures = 0
525         var measures = 0
526         var layouts = 0
527         var childMeasures = 0
528         rule.setContent {
529             Parent(onMeasure = { ++parentMeasures }) {
530                 Parent(
531                     onMeasure = { ++measures },
532                     onLayout = { ++layouts },
533                     readDuringLayoutBeforePlacing = true
534                 ) {
535                     Parent(modifier = Modifier.provider(), onMeasure = { ++childMeasures }) {
536                         Parent()
537                     }
538                 }
539             }
540         }
541         rule.runOnIdle {
542             assertEquals(1, parentMeasures)
543             assertEquals(1, measures)
544             assertEquals(1, layouts)
545             assertEquals(1, childMeasures)
546             changeLinePosition()
547         }
548 
549         rule.runOnIdle {
550             assertEquals(1, parentMeasures)
551             assertEquals(1, measures)
552             assertEquals(2, layouts)
553             assertEquals(2, childMeasures)
554         }
555     }
556 
557     @Test
558     fun scenario9() {
559         var parentMeasures = 0
560         var measures = 0
561         var layouts = 0
562         var childMeasures = 0
563         rule.setContent {
564             Parent(onMeasure = { ++parentMeasures }) {
565                 Parent(
566                     onMeasure = { ++measures },
567                     onLayout = { ++layouts },
568                     readDuringLayoutAfterPlacing = true
569                 ) {
570                     Parent(modifier = Modifier.provider(), onMeasure = { ++childMeasures }) {
571                         Parent()
572                     }
573                 }
574             }
575         }
576         rule.runOnIdle {
577             assertEquals(1, parentMeasures)
578             assertEquals(1, measures)
579             assertEquals(1, layouts)
580             assertEquals(1, childMeasures)
581             changeLinePosition()
582         }
583 
584         rule.runOnIdle {
585             assertEquals(1, parentMeasures)
586             assertEquals(1, measures)
587             assertEquals(2, layouts)
588             assertEquals(2, childMeasures)
589         }
590     }
591 
592     @Test
593     fun scenario10() {
594         var parentMeasures = 0
595         var measures = 0
596         var layouts = 0
597         var childMeasures = 0
598         rule.setContent {
599             Parent(onMeasure = { ++parentMeasures }, readDuringMeasure = true) {
600                 Parent(
601                     onMeasure = { ++measures },
602                     onLayout = { ++layouts },
603                     readDuringLayoutAfterPlacing = true
604                 ) {
605                     Parent(modifier = Modifier.provider(), onMeasure = { ++childMeasures }) {
606                         Parent()
607                     }
608                 }
609             }
610         }
611         rule.runOnIdle {
612             assertEquals(1, parentMeasures)
613             assertEquals(1, measures)
614             assertEquals(1, layouts)
615             assertEquals(1, childMeasures)
616             changeLinePosition()
617         }
618 
619         rule.runOnIdle {
620             assertEquals(2, parentMeasures)
621             assertEquals(1, measures)
622             assertEquals(2, layouts)
623             assertEquals(2, childMeasures)
624         }
625     }
626 
627     @Test
628     fun scenario11() {
629         var measures = 0
630         var layouts = 0
631         var childMeasures = 0
632         rule.setContent {
633             Parent {
634                 Parent(
635                     onMeasure = { ++measures },
636                     onLayout = { ++layouts },
637                     readDuringLayoutAfterPlacing = true
638                 ) {
639                     Parent(
640                         modifier = Modifier.reader(readDuringMeasure = true),
641                         onMeasure = { ++childMeasures }
642                     ) {
643                         Provider()
644                     }
645                 }
646             }
647         }
648         rule.runOnIdle {
649             assertEquals(1, measures)
650             assertEquals(1, layouts)
651             assertEquals(1, childMeasures)
652             changeLinePosition()
653         }
654 
655         rule.runOnIdle {
656             assertEquals(1, measures)
657             assertEquals(2, layouts)
658             assertEquals(2, childMeasures)
659         }
660     }
661 
662     @Test
663     fun scenario12() {
664         var childMeasures = 0
665         rule.setContent {
666             Parent {
667                 Provider(
668                     modifier = Modifier.reader(readDuringMeasure = true),
669                     onMeasure = { ++childMeasures }
670                 )
671             }
672         }
673         rule.runOnIdle {
674             assertEquals(1, childMeasures)
675             changeLinePosition()
676         }
677 
678         rule.runOnIdle { assertEquals(2, childMeasures) }
679     }
680 
681     @Test
682     fun scenario13() {
683         var measures = 0
684         var childMeasures = 0
685         var childLayouts = 0
686         rule.setContent {
687             Parent(onMeasure = { ++measures }) {
688                 Provider(
689                     modifier = Modifier.reader(readDuringLayoutBeforePlacing = true),
690                     onMeasure = { ++childMeasures },
691                     onLayout = { ++childLayouts }
692                 )
693             }
694         }
695         rule.runOnIdle {
696             assertEquals(1, measures)
697             assertEquals(1, childMeasures)
698             assertEquals(1, childLayouts)
699             changeLinePosition()
700         }
701 
702         rule.runOnIdle {
703             assertEquals(1, measures)
704             assertEquals(2, childMeasures)
705             assertEquals(2, childLayouts)
706         }
707     }
708 
709     @Test
710     fun scenario14() {
711         var measures = 0
712         var childMeasures = 0
713         var childLayouts = 0
714         rule.setContent {
715             Parent(onMeasure = { ++measures }) {
716                 Provider(
717                     modifier = Modifier.reader(readDuringLayoutAfterPlacing = true),
718                     onMeasure = { ++childMeasures },
719                     onLayout = { ++childLayouts }
720                 )
721             }
722         }
723         rule.runOnIdle {
724             assertEquals(1, measures)
725             assertEquals(1, childMeasures)
726             assertEquals(1, childLayouts)
727             changeLinePosition()
728         }
729 
730         rule.runOnIdle {
731             assertEquals(1, measures)
732             assertEquals(2, childMeasures)
733             assertEquals(2, childLayouts)
734         }
735     }
736 
737     @Test
738     fun scenario15() {
739         var parentMeasures = 0
740         var measures = 0
741         var childMeasures = 0
742         rule.setContent {
743             Parent(onMeasure = { ++parentMeasures }) {
744                 Parent(
745                     modifier = Modifier.reader(readDuringMeasure = true).provider(),
746                     onMeasure = { ++measures }
747                 ) {
748                     Parent(onMeasure = { ++childMeasures })
749                 }
750             }
751         }
752         rule.runOnIdle {
753             assertEquals(1, parentMeasures)
754             assertEquals(1, measures)
755             assertEquals(1, childMeasures)
756             changeLinePosition()
757         }
758 
759         rule.runOnIdle {
760             assertEquals(1, parentMeasures)
761             assertEquals(2, measures)
762             assertEquals(1, childMeasures)
763         }
764     }
765 
766     @Test
767     fun scenario16() {
768         var parentMeasures = 0
769         var measures = 0
770         var childMeasures = 0
771         rule.setContent {
772             Parent(onMeasure = { ++parentMeasures }, readDuringMeasure = true) {
773                 Parent(
774                     modifier = Modifier.reader(readDuringMeasure = true).provider(),
775                     onMeasure = { ++measures }
776                 ) {
777                     Parent(onMeasure = { ++childMeasures })
778                 }
779             }
780         }
781         rule.runOnIdle {
782             assertEquals(1, parentMeasures)
783             assertEquals(1, measures)
784             assertEquals(1, childMeasures)
785             changeLinePosition()
786         }
787 
788         rule.runOnIdle {
789             assertEquals(2, parentMeasures)
790             assertEquals(2, measures)
791             assertEquals(1, childMeasures)
792         }
793     }
794 
795     @Test
796     fun scenario17() {
797         var parentMeasures = 0
798         var measures = 0
799         var childMeasures = 0
800         var read by mutableStateOf(true)
801         rule.setContent {
802             Parent(onMeasure = { ++parentMeasures }, readDuringMeasure = true) {
803                 ChangingParent(onMeasure = { ++measures }, readDuringMeasure = { read }) {
804                     Parent(onMeasure = { ++childMeasures }) { Provider() }
805                 }
806             }
807         }
808         rule.runOnIdle {
809             assertEquals(1, parentMeasures)
810             assertEquals(1, measures)
811             assertEquals(1, childMeasures)
812             changeLinePosition()
813             read = false
814         }
815 
816         rule.runOnIdle {
817             assertEquals(2, parentMeasures)
818             assertEquals(2, measures)
819             assertEquals(1, childMeasures)
820             changeLinePosition()
821         }
822 
823         rule.runOnIdle {
824             assertEquals(3, parentMeasures)
825             assertEquals(2, measures)
826             assertEquals(1, childMeasures)
827         }
828     }
829 
830     @Test
831     fun scenario18() {
832         var parentLayouts = 0
833         var parentMeasures = 0
834         var measures = 0
835         var childMeasures = 0
836         var read by mutableStateOf(true)
837         rule.setContent {
838             Parent(
839                 onMeasure = { ++parentMeasures },
840                 onLayout = { ++parentLayouts },
841                 readDuringLayoutAfterPlacing = true
842             ) {
843                 ChangingParent(onMeasure = { ++measures }, readDuringMeasure = { read }) {
844                     Parent(onMeasure = { ++childMeasures }) { Provider() }
845                 }
846             }
847         }
848         rule.runOnIdle {
849             assertEquals(1, parentMeasures)
850             assertEquals(1, parentLayouts)
851             assertEquals(1, measures)
852             assertEquals(1, childMeasures)
853             changeLinePosition()
854             read = false
855         }
856 
857         rule.runOnIdle {
858             assertEquals(1, parentMeasures)
859             assertEquals(2, parentLayouts)
860             assertEquals(2, measures)
861             assertEquals(1, childMeasures)
862             changeLinePosition()
863         }
864 
865         rule.runOnIdle {
866             assertEquals(1, parentMeasures)
867             assertEquals(3, parentLayouts)
868             assertEquals(2, measures)
869             assertEquals(1, childMeasures)
870         }
871     }
872 
873     @Test
874     fun scenario19() {
875         var offset by mutableStateOf(IntOffset.Zero)
876         rule.setContent {
877             Parent(readDuringLayoutBeforePlacing = true) {
878                 Provider(modifier = Modifier.offset { offset })
879             }
880         }
881         rule.runOnIdle {
882             offset = IntOffset(10, 10)
883             linePosition += 10
884         }
885 
886         rule.waitForIdle()
887     }
888 
889     @Test
890     fun scenario20() {
891         var parentLayouts = 0
892         var offset by mutableStateOf(IntOffset.Zero)
893         rule.setContent {
894             Parent(readDuringLayoutBeforePlacing = true, onLayout = { ++parentLayouts }) {
895                 Parent { Provider(modifier = Modifier.offset { offset }) }
896             }
897         }
898         rule.runOnIdle {
899             offset = IntOffset(10, 10)
900             linePosition += 10
901         }
902 
903         rule.runOnIdle { assertEquals(2, parentLayouts) }
904 
905         rule.waitForIdle()
906     }
907 
908     @Test
909     fun scenario21() {
910         var parentMeasures = 0
911         var read by mutableStateOf(false)
912         rule.setContent {
913             ChangingParent(readDuringMeasure = { read }, onMeasure = { ++parentMeasures }) {
914                 Parent { Provider() }
915             }
916         }
917         rule.runOnIdle { read = true }
918 
919         rule.runOnIdle { assertEquals(2, parentMeasures) }
920     }
921 
922     @Test
923     fun scenario22() {
924         var parentLayouts = 0
925         var read by mutableStateOf(false)
926         rule.setContent {
927             ChangingParent(
928                 readDuringLayoutBeforePlacing = { read },
929                 onLayout = { ++parentLayouts }
930             ) {
931                 Parent { Provider() }
932             }
933         }
934         rule.runOnIdle { read = true }
935 
936         rule.runOnIdle { assertEquals(2, parentLayouts) }
937     }
938 
939     @Test
940     fun scenario23() {
941         var obtainedPosition = 0
942         var changingState by mutableStateOf(false)
943         rule.setContent {
944             Layout(content = { Parent { Provider() } }) { measurables, constraints ->
945                 val placeable = measurables.first().measure(constraints)
946                 layout(constraints.maxWidth, constraints.maxHeight) {
947                     if (changingState) require(true)
948                     obtainedPosition = placeable[TestLine]
949                     placeable.place(0, 0)
950                 }
951             }
952         }
953         rule.runOnIdle {
954             assertEquals(linePosition, obtainedPosition)
955             changeLinePosition()
956             changingState = true
957         }
958 
959         rule.runOnIdle { assertEquals(linePosition, obtainedPosition) }
960     }
961 
962     @Test
963     fun scenario24() {
964         var obtainedPosition = 0
965         var changingState by mutableStateOf(false)
966         rule.setContent {
967             Layout(content = { Parent { Provider() } }) { measurables, constraints ->
968                 val placeable = measurables.first().measure(constraints)
969                 layout(constraints.maxWidth, constraints.maxHeight) {
970                     if (changingState) require(true)
971                     placeable.place(0, 0)
972                     obtainedPosition = placeable[TestLine]
973                 }
974             }
975         }
976         rule.runOnIdle {
977             assertEquals(linePosition, obtainedPosition)
978             changeLinePosition()
979             changingState = true
980         }
981 
982         rule.runOnIdle { assertEquals(linePosition, obtainedPosition) }
983     }
984 
985     @Test
986     fun scenario25() {
987         var obtainedPosition = 0
988         rule.setContent {
989             Parent(modifier = Modifier.onGloballyPositioned { obtainedPosition = it[TestLine] }) {
990                 Parent { Provider() }
991             }
992         }
993         rule.runOnIdle {
994             assertEquals(linePosition, obtainedPosition)
995             changeLinePosition()
996         }
997 
998         rule.runOnIdle { assertEquals(linePosition, obtainedPosition) }
999     }
1000 
1001     @Test
1002     fun scenario26() {
1003         var measures = 0
1004         var layouts = 0
1005         rule.setContent {
1006             Parent(
1007                 modifier =
1008                     Modifier.reader(
1009                         readDuringMeasure = true,
1010                         readDuringLayoutBeforePlacing = true,
1011                         onMeasure = { ++measures },
1012                         onLayout = { ++layouts }
1013                     )
1014             ) {
1015                 Parent { Provider() }
1016             }
1017         }
1018         rule.runOnIdle {
1019             assertEquals(1, measures)
1020             assertEquals(1, layouts)
1021             changeLinePosition()
1022         }
1023 
1024         rule.runOnIdle {
1025             assertEquals(2, measures)
1026             assertEquals(2, layouts)
1027         }
1028     }
1029 
1030     @Test
1031     fun scenario27() {
1032         var measures = 0
1033         var layouts = 0
1034         rule.setContent {
1035             Parent(
1036                 modifier =
1037                     Modifier.reader(
1038                         readDuringLayoutBeforePlacing = true,
1039                         onMeasure = { ++measures },
1040                         onLayout = { ++layouts }
1041                     )
1042             ) {
1043                 Parent { Provider() }
1044             }
1045         }
1046         rule.runOnIdle {
1047             assertEquals(1, measures)
1048             assertEquals(1, layouts)
1049             changeLinePosition()
1050         }
1051 
1052         rule.runOnIdle {
1053             assertEquals(1, measures)
1054             assertEquals(2, layouts)
1055         }
1056     }
1057 
1058     @Test
1059     fun scenario28() {
1060         var measures = 0
1061         var layouts = 0
1062         rule.setContent {
1063             Parent(
1064                 modifier =
1065                     Modifier.reader(
1066                         readDuringLayoutAfterPlacing = true,
1067                         onMeasure = { ++measures },
1068                         onLayout = { ++layouts }
1069                     )
1070             ) {
1071                 Parent { Provider() }
1072             }
1073         }
1074         rule.runOnIdle {
1075             assertEquals(1, measures)
1076             assertEquals(1, layouts)
1077             changeLinePosition()
1078         }
1079 
1080         rule.runOnIdle {
1081             assertEquals(1, measures)
1082             assertEquals(2, layouts)
1083         }
1084     }
1085 
1086     @Test
1087     fun notMeasuredChildIsNotCrashingWhenGrandParentQueriesAlignments() {
1088         var emit by mutableStateOf(false)
1089 
1090         rule.setContent {
1091             Layout(
1092                 content = {
1093                     Layout(
1094                         content = {
1095                             if (emit) {
1096                                 Box(Modifier.size(10.dp))
1097                             }
1098                         }
1099                     ) { _, constraints ->
1100                         layout(constraints.maxWidth, constraints.maxHeight) {}
1101                     }
1102                 }
1103             ) { measurables, constraints ->
1104                 val placeable = measurables.first().measure(constraints)
1105                 placeable[FirstBaseline]
1106                 layout(placeable.width, placeable.height) { placeable.place(0, 0) }
1107             }
1108         }
1109 
1110         rule.runOnIdle { emit = true }
1111 
1112         rule.runOnIdle {}
1113     }
1114 
1115     private var linePosition = 10
1116     private var linePositionState by mutableStateOf(10)
1117 
1118     private fun changeLinePosition() {
1119         linePosition += 10
1120         linePositionState += 10
1121     }
1122 
1123     private val TestLine = HorizontalAlignmentLine(::min)
1124 
1125     @Composable
1126     private fun Parent(
1127         modifier: Modifier = Modifier,
1128         onMeasure: () -> Unit = {},
1129         onLayout: () -> Unit = {},
1130         readDuringMeasure: Boolean = false,
1131         readDuringLayoutBeforePlacing: Boolean = false,
1132         readDuringLayoutAfterPlacing: Boolean = false,
1133         content: @Composable () -> Unit = {}
1134     ) {
1135         ChangingParent(
1136             modifier,
1137             onMeasure,
1138             onLayout,
1139             { readDuringMeasure },
1140             { readDuringLayoutBeforePlacing },
1141             { readDuringLayoutAfterPlacing },
1142             content
1143         )
1144     }
1145 
1146     @Composable
1147     private fun ChangingParent(
1148         modifier: Modifier = Modifier,
1149         onMeasure: () -> Unit = {},
1150         onLayout: () -> Unit = {},
1151         readDuringMeasure: () -> Boolean = { false },
1152         readDuringLayoutBeforePlacing: () -> Boolean = { false },
1153         readDuringLayoutAfterPlacing: () -> Boolean = { false },
1154         content: @Composable () -> Unit = {}
1155     ) {
1156         Layout(content, modifier) { measurables, constraints ->
1157             onMeasure()
1158             val placeables =
1159                 measurables.map {
1160                     it.measure(constraints).also {
1161                         if (readDuringMeasure()) assertEquals(linePosition, it[TestLine])
1162                     }
1163                 }
1164             layout(constraints.maxWidth, constraints.maxHeight) {
1165                 onLayout()
1166                 placeables.forEach { placeable ->
1167                     if (readDuringLayoutBeforePlacing()) {
1168                         // placeable[TestLine]
1169                         assertEquals(linePosition, placeable[TestLine])
1170                     }
1171                     placeable.place(0, 0)
1172                     if (readDuringLayoutAfterPlacing()) {
1173                         // placeable[TestLine]
1174                         assertEquals(linePosition, placeable[TestLine])
1175                     }
1176                 }
1177             }
1178         }
1179     }
1180 
1181     @Composable
1182     private fun Provider(
1183         modifier: Modifier = Modifier,
1184         onMeasure: () -> Unit = {},
1185         onLayout: () -> Unit = {},
1186         content: @Composable () -> Unit = {}
1187     ) {
1188         Layout(content, modifier) { _, constraints ->
1189             onMeasure()
1190             layout(
1191                 constraints.maxWidth,
1192                 constraints.maxHeight,
1193                 mapOf(TestLine to linePositionState)
1194             ) {
1195                 onLayout()
1196             }
1197         }
1198     }
1199 
1200     private fun Modifier.reader(
1201         onMeasure: () -> Unit = {},
1202         onLayout: () -> Unit = {},
1203         readDuringMeasure: Boolean = false,
1204         readDuringLayoutBeforePlacing: Boolean = false,
1205         readDuringLayoutAfterPlacing: Boolean = false
1206     ) =
1207         this.then(
1208             ReaderModifier(
1209                 onMeasure,
1210                 onLayout,
1211                 readDuringMeasure,
1212                 readDuringLayoutBeforePlacing,
1213                 readDuringLayoutAfterPlacing
1214             )
1215         )
1216 
1217     private inner class ReaderModifier(
1218         val onMeasure: () -> Unit,
1219         val onLayout: () -> Unit,
1220         val readDuringMeasure: Boolean,
1221         val readDuringLayoutBeforePlacing: Boolean,
1222         val readDuringLayoutAfterPlacing: Boolean
1223     ) : LayoutModifier {
1224         override fun MeasureScope.measure(
1225             measurable: Measurable,
1226             constraints: Constraints
1227         ): MeasureResult {
1228             onMeasure()
1229             val placeable = measurable.measure(constraints)
1230             if (readDuringMeasure) assertEquals(linePosition, placeable[TestLine])
1231             return layout(constraints.maxWidth, constraints.maxHeight) {
1232                 onLayout()
1233                 if (readDuringLayoutBeforePlacing) assertEquals(linePosition, placeable[TestLine])
1234                 placeable.place(0, 0)
1235                 if (readDuringLayoutAfterPlacing) assertEquals(linePosition, placeable[TestLine])
1236             }
1237         }
1238     }
1239 
1240     private fun Modifier.provider() = this.then(ProviderModifier())
1241 
1242     private inner class ProviderModifier : LayoutModifier {
1243         override fun MeasureScope.measure(
1244             measurable: Measurable,
1245             constraints: Constraints
1246         ): MeasureResult {
1247             val placeable = measurable.measure(constraints)
1248             return layout(
1249                 constraints.maxWidth,
1250                 constraints.maxHeight,
1251                 mapOf(TestLine to linePositionState)
1252             ) {
1253                 placeable.place(0, 0)
1254             }
1255         }
1256 
1257         override fun hashCode(): Int {
1258             return 0
1259         }
1260 
1261         override fun equals(other: Any?): Boolean {
1262             return other is ProviderModifier
1263         }
1264     }
1265 
1266     private fun Modifier.supplyAlignmentLines(alignmentLines: () -> Map<AlignmentLine, Int>) =
1267         layout { measurable, constraints ->
1268             val placeable = measurable.measure(constraints)
1269             layout(placeable.width, placeable.height, alignmentLines()) { placeable.place(0, 0) }
1270         }
1271 }
1272