@@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test
1111import java.lang.reflect.Method
1212
1313/* *
14- * Tests for the [LayoutNodeUtils.getMethod ] reflection helper that resolves
14+ * Tests for the [LayoutNodeUtils.invokeInternalAccessor ] reflection helper that resolves
1515 * Kotlin-internal method names regardless of module-suffix mangling.
1616 *
1717 * Compose internal properties are compiled to JVM methods whose names are mangled
@@ -21,50 +21,51 @@ import java.lang.reflect.Method
2121 */
2222internal class LayoutNodeGetMethodTest {
2323
24- private val testedLayoutNodeUtils = LayoutNodeUtils ()
25-
26- // getMethod is private fun Any.getMethod(prefix: String): Any? compiled as an
27- // instance method with signature getMethod(Object, String) in the JVM.
28- private val getMethodFn: Method = LayoutNodeUtils ::class .java
29- .getDeclaredMethod(" getMethod" , Any ::class .java, String ::class .java)
24+ // invokeInternalAccessor is private fun Any.invokeInternalAccessor(prefix: String): Any? compiled
25+ // as an instance method with signature invokeInternalAccessor(Object, String) in the JVM.
26+ private val invokeInternalAccessorFn: Method = LayoutNodeUtils ::class .java
27+ .getDeclaredMethod(" invokeInternalAccessor" , Any ::class .java, String ::class .java)
3028 .also { it.isAccessible = true }
3129
3230 // region $ui suffix
3331
3432 @Test
35- fun `M find method W getMethod {method has dollar ui suffix}` () {
33+ fun `M find method W invokeInternalAccessor {method has dollar ui suffix}` () {
3634 // Given
35+ val tested = LayoutNodeUtils ()
3736 val expected = Any ()
3837 val fakeNode = FakeLayoutNodeUi (expected)
3938
4039 // When
41- val result = getMethodFn .invoke(testedLayoutNodeUtils , fakeNode, " getLayoutDelegate" )
40+ val result = invokeInternalAccessorFn .invoke(tested , fakeNode, " getLayoutDelegate" )
4241
4342 // Then
4443 assertThat(result).isSameAs(expected)
4544 }
4645
4746 @Test
48- fun `M find method W getMethod {outer coordinator method has dollar ui suffix}` () {
47+ fun `M find method W invokeInternalAccessor {outer coordinator method has dollar ui suffix}` () {
4948 // Given
49+ val tested = LayoutNodeUtils ()
5050 val expected = Any ()
5151 val fakeNode = FakeLayoutNodeUi (expected)
5252
5353 // When
54- val result = getMethodFn .invoke(testedLayoutNodeUtils , fakeNode, " getOuterCoordinator" )
54+ val result = invokeInternalAccessorFn .invoke(tested , fakeNode, " getOuterCoordinator" )
5555
5656 // Then
5757 assertThat(result).isSameAs(expected)
5858 }
5959
6060 @Test
61- fun `M find method W getMethod {coordinates method has dollar ui suffix}` () {
61+ fun `M find method W invokeInternalAccessor {coordinates method has dollar ui suffix}` () {
6262 // Given
63+ val tested = LayoutNodeUtils ()
6364 val expected = Any ()
6465 val fakeNode = FakeLayoutNodeUi (expected)
6566
6667 // When
67- val result = getMethodFn .invoke(testedLayoutNodeUtils , fakeNode, " getCoordinates" )
68+ val result = invokeInternalAccessorFn .invoke(tested , fakeNode, " getCoordinates" )
6869
6970 // Then
7071 assertThat(result).isSameAs(expected)
@@ -75,55 +76,86 @@ internal class LayoutNodeGetMethodTest {
7576 // region $ui_release suffix
7677
7778 @Test
78- fun `M find method W getMethod {method has dollar ui_release suffix}` () {
79+ fun `M find method W invokeInternalAccessor {method has dollar ui_release suffix}` () {
7980 // Given
81+ val tested = LayoutNodeUtils ()
8082 val expected = Any ()
8183 val fakeNode = FakeLayoutNodeUiRelease (expected)
8284
8385 // When
84- val result = getMethodFn .invoke(testedLayoutNodeUtils , fakeNode, " getLayoutDelegate" )
86+ val result = invokeInternalAccessorFn .invoke(tested , fakeNode, " getLayoutDelegate" )
8587
8688 // Then
8789 assertThat(result).isSameAs(expected)
8890 }
8991
9092 @Test
91- fun `M find method W getMethod {outer coordinator method has dollar ui_release suffix}` () {
93+ fun `M find method W invokeInternalAccessor {outer coordinator method has dollar ui_release suffix}` () {
9294 // Given
95+ val tested = LayoutNodeUtils ()
9396 val expected = Any ()
9497 val fakeNode = FakeLayoutNodeUiRelease (expected)
9598
9699 // When
97- val result = getMethodFn .invoke(testedLayoutNodeUtils , fakeNode, " getOuterCoordinator" )
100+ val result = invokeInternalAccessorFn .invoke(tested , fakeNode, " getOuterCoordinator" )
98101
99102 // Then
100103 assertThat(result).isSameAs(expected)
101104 }
102105
103106 @Test
104- fun `M find method W getMethod {coordinates method has dollar ui_release suffix}` () {
107+ fun `M find method W invokeInternalAccessor {coordinates method has dollar ui_release suffix}` () {
105108 // Given
109+ val tested = LayoutNodeUtils ()
106110 val expected = Any ()
107111 val fakeNode = FakeLayoutNodeUiRelease (expected)
108112
109113 // When
110- val result = getMethodFn .invoke(testedLayoutNodeUtils , fakeNode, " getCoordinates" )
114+ val result = invokeInternalAccessorFn .invoke(tested , fakeNode, " getCoordinates" )
111115
112116 // Then
113117 assertThat(result).isSameAs(expected)
114118 }
115119
116120 // endregion
117121
122+ // region suffix reuse
123+
124+ @Test
125+ fun `M reuse resolved suffix W invokeInternalAccessor {subsequent prefixes on same module}` () {
126+ // Given — once the first lookup resolves a suffix, later prefixes must reuse it
127+ // without re-probing, because all accessors in the same compose-ui module share it.
128+ val tested = LayoutNodeUtils ()
129+ val expected = Any ()
130+ val fakeNode = FakeLayoutNodeUi (expected)
131+ val suffixField = LayoutNodeUtils ::class .java
132+ .getDeclaredField(" internalMethodSuffix" )
133+ .also { it.isAccessible = true }
134+
135+ // When
136+ invokeInternalAccessorFn.invoke(tested, fakeNode, " getLayoutDelegate" )
137+ val suffixAfterFirst = suffixField.get(tested)
138+ invokeInternalAccessorFn.invoke(tested, fakeNode, " getOuterCoordinator" )
139+ invokeInternalAccessorFn.invoke(tested, fakeNode, " getCoordinates" )
140+ val suffixAfterThird = suffixField.get(tested)
141+
142+ // Then
143+ assertThat(suffixAfterFirst).isEqualTo(" \$ ui" )
144+ assertThat(suffixAfterThird).isEqualTo(" \$ ui" )
145+ }
146+
147+ // endregion
148+
118149 // region no match
119150
120151 @Test
121- fun `M return null W getMethod {no matching method}` () {
152+ fun `M return null W invokeInternalAccessor {no matching method}` () {
122153 // Given
154+ val tested = LayoutNodeUtils ()
123155 val fakeNode = Any ()
124156
125157 // When
126- val result = getMethodFn .invoke(testedLayoutNodeUtils , fakeNode, " getLayoutDelegate" )
158+ val result = invokeInternalAccessorFn .invoke(tested , fakeNode, " getLayoutDelegate" )
127159
128160 // Then
129161 assertThat(result).isNull()
0 commit comments