[go: nahoru, domu]

blob: 3f9735ca5ed5c101f3bcca18637586065d36027b [file] [log] [blame]
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -07001/*
2 * 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
Diego Perez40a18732020-11-04 11:09:49 +000017package androidx.compose.ui.tooling.inspector
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -070018
19import android.view.View
20import android.view.ViewGroup
Jens Ole Lauridsen9528c972020-10-22 08:25:27 -070021import androidx.compose.foundation.Image
Jens Ole Lauridsen27c564b2020-07-21 14:04:15 -070022import androidx.compose.foundation.layout.Column
Jens Ole Lauridsen9528c972020-10-22 08:25:27 -070023import androidx.compose.foundation.layout.Spacer
24import androidx.compose.foundation.layout.preferredHeight
Chuck Jazdzewskia6a0b3d2020-12-04 13:16:18 -080025import androidx.compose.foundation.text.BasicText
Louis Pullen-Freilichc52e5612020-07-23 15:00:18 +010026import androidx.compose.material.Button
Jens Ole Lauridsen2353bf62020-12-30 16:52:09 -080027import androidx.compose.material.Icon
Jens Ole Lauridsen76baf942021-01-08 08:09:19 -080028import androidx.compose.material.MaterialTheme
Louis Pullen-Freilichfb860452021-02-03 20:55:50 +000029import androidx.compose.material.ModalDrawer
Louis Pullen-Freilichc52e5612020-07-23 15:00:18 +010030import androidx.compose.material.Surface
Louis Pullen-Freilich051800b2020-10-30 19:26:32 +000031import androidx.compose.material.Text
Jens Ole Lauridsen9528c972020-10-22 08:25:27 -070032import androidx.compose.material.icons.Icons
33import androidx.compose.material.icons.filled.Call
Jens Ole Lauridsen2353bf62020-12-30 16:52:09 -080034import androidx.compose.material.icons.filled.FavoriteBorder
Jens Ole Lauridsene295d242020-09-22 15:39:42 -070035import androidx.compose.runtime.InternalComposeApi
36import androidx.compose.runtime.resetSourceInfo
Jens Ole Lauridsen9528c972020-10-22 08:25:27 -070037import androidx.compose.ui.Modifier
Jens Ole Lauridsene295d242020-09-22 15:39:42 -070038import androidx.compose.ui.graphics.Color
Jens Ole Lauridsen76baf942021-01-08 08:09:19 -080039import androidx.compose.ui.graphics.graphicsLayer
Andrey Kulikov554f7362021-01-28 15:35:39 +000040import androidx.compose.ui.layout.GraphicLayerInfo
Jens Ole Lauridsen2353bf62020-12-30 16:52:09 -080041import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
Chuck Jazdzewskia6a0b3d2020-12-04 13:16:18 -080042import androidx.compose.ui.text.TextStyle
43import androidx.compose.ui.text.style.TextDecoration
Jens Ole Lauridsend1ef5d62021-01-08 09:13:33 -080044import androidx.compose.ui.tooling.CompositionDataRecord
Diego Perez40a18732020-11-04 11:09:49 +000045import androidx.compose.ui.tooling.Inspectable
46import androidx.compose.ui.tooling.R
Diego Perez40a18732020-11-04 11:09:49 +000047import androidx.compose.ui.tooling.ToolingTest
Sergey Vasilinets7ce0a3a2021-01-29 02:45:53 +000048import androidx.compose.ui.tooling.data.Group
49import androidx.compose.ui.tooling.data.UiToolingDataApi
50import androidx.compose.ui.tooling.data.asTree
51import androidx.compose.ui.tooling.data.position
Jens Ole Lauridsene295d242020-09-22 15:39:42 -070052import androidx.compose.ui.unit.Density
53import androidx.compose.ui.unit.Dp
54import androidx.compose.ui.unit.dp
Jelle Fresen53dd7b72020-09-25 10:02:27 +010055import androidx.test.ext.junit.runners.AndroidJUnit4
Jelle Fresen53dd7b72020-09-25 10:02:27 +010056import androidx.test.filters.LargeTest
Louis Pullen-Freilich051800b2020-10-30 19:26:32 +000057import androidx.test.filters.SdkSuppress
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -070058import com.google.common.truth.Truth.assertThat
Jens Ole Lauridsene295d242020-09-22 15:39:42 -070059import com.google.common.truth.Truth.assertWithMessage
Jens Ole Lauridsen2353bf62020-12-30 16:52:09 -080060import org.junit.After
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -070061import org.junit.Before
62import org.junit.Test
63import org.junit.runner.RunWith
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -070064import kotlin.math.roundToInt
65
66private const val DEBUG = false
67
Jelle Fresen53dd7b72020-09-25 10:02:27 +010068@LargeTest
Jelle Fresen17628d72020-09-24 16:23:51 +010069@RunWith(AndroidJUnit4::class)
Jens Ole Lauridsen76baf942021-01-08 08:09:19 -080070@SdkSuppress(minSdkVersion = 29) // Render id is not returned for api < 29
Sergey Vasilinets7ce0a3a2021-01-29 02:45:53 +000071@OptIn(UiToolingDataApi::class)
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -070072class LayoutInspectorTreeTest : ToolingTest() {
73 private lateinit var density: Density
74 private lateinit var view: View
75
76 @Before
Jens Ole Lauridsen2353bf62020-12-30 16:52:09 -080077 fun before() {
Chuck Jazdzewski4fab4b92020-06-15 09:51:37 -070078 @OptIn(InternalComposeApi::class)
79 resetSourceInfo()
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -070080 density = Density(activity)
81 view = activityTestRule.activity.findViewById<ViewGroup>(android.R.id.content)
Jens Ole Lauridsen2353bf62020-12-30 16:52:09 -080082 isDebugInspectorInfoEnabled = true
83 }
84
85 @After
86 fun after() {
87 isDebugInspectorInfoEnabled = false
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -070088 }
89
90 @Test
91 fun buildTree() {
Chuck Jazdzewski9fcdd6f2020-12-04 09:29:42 -080092 val slotTableRecord = CompositionDataRecord.create()
Jens Ole Lauridsenabd03ef2020-07-08 16:08:59 -070093
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -070094 show {
95 Inspectable(slotTableRecord) {
96 Column {
Jens Ole Lauridsenabd03ef2020-07-08 16:08:59 -070097 Text(text = "Hello World", color = Color.Green)
Anastasia Soboleva0b8299e2021-01-11 16:37:11 +000098 Icon(Icons.Filled.FavoriteBorder, null)
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -070099 Surface {
100 Button(onClick = {}) { Text(text = "OK") }
101 }
102 }
103 }
104 }
105
106 // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
107 view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
108 val viewWidth = with(density) { view.width.toDp() }
109 val viewHeight = with(density) { view.height.toDp() }
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700110 val builder = LayoutInspectorTree()
111 val nodes = builder.convert(view)
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800112 dumpNodes(nodes, builder)
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700113
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800114 validate(nodes, builder, checkParameters = false) {
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700115 node(
Amaury Medeiros5f276e22021-01-22 13:48:04 +0000116 name = "Content",
117 fileName = "",
118 left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
119 children = listOf("Box")
120 )
121 node(
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700122 name = "Box",
Jens Ole Lauridsen76baf942021-01-08 08:09:19 -0800123 isRenderNode = true,
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800124 fileName = "",
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700125 left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
126 children = listOf("Column")
127 )
128 node(
129 name = "Column",
130 fileName = "LayoutInspectorTreeTest.kt",
Jens Ole Lauridsen2353bf62020-12-30 16:52:09 -0800131 left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 78.9.dp,
132 children = listOf("Text", "Icon", "Surface")
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800133 )
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700134 node(
135 name = "Text",
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700136 isRenderNode = true,
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800137 fileName = "LayoutInspectorTreeTest.kt",
138 left = 0.0.dp, top = 0.0.dp, width = 72.0.dp, height = 18.9.dp,
139 )
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700140 node(
Jens Ole Lauridsen2353bf62020-12-30 16:52:09 -0800141 name = "Icon",
142 isRenderNode = true,
143 fileName = "LayoutInspectorTreeTest.kt",
144 left = 0.0.dp, top = 18.9.dp, width = 24.0.dp, height = 24.0.dp,
145 )
146 node(
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700147 name = "Surface",
148 fileName = "LayoutInspectorTreeTest.kt",
149 isRenderNode = true,
150 left = 0.0.dp,
Jens Ole Lauridsen2353bf62020-12-30 16:52:09 -0800151 top = 42.9.dp, width = 64.0.dp, height = 36.0.dp,
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800152 children = listOf("Button")
153 )
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700154 node(
155 name = "Button",
156 fileName = "LayoutInspectorTreeTest.kt",
Jens Ole Lauridsen76baf942021-01-08 08:09:19 -0800157 isRenderNode = true,
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700158 left = 0.0.dp,
Jens Ole Lauridsen2353bf62020-12-30 16:52:09 -0800159 top = 42.9.dp, width = 64.0.dp, height = 36.0.dp,
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800160 children = listOf("Text")
161 )
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700162 node(
163 name = "Text",
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800164 isRenderNode = true,
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700165 fileName = "LayoutInspectorTreeTest.kt",
Jens Ole Lauridsen2353bf62020-12-30 16:52:09 -0800166 left = 21.7.dp, top = 51.6.dp, width = 20.9.dp, height = 18.9.dp,
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800167 )
Jens Ole Lauridsenabd03ef2020-07-08 16:08:59 -0700168 }
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700169 }
170
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700171 @Test
Jens Ole Lauridsen76baf942021-01-08 08:09:19 -0800172 fun buildTreeWithTransformedText() {
173 val slotTableRecord = CompositionDataRecord.create()
174
175 show {
176 Inspectable(slotTableRecord) {
177 MaterialTheme {
178 Text(
179 text = "Hello World",
180 modifier = Modifier.graphicsLayer(rotationZ = 225f)
181 )
182 }
183 }
184 }
185
186 // TODO: Find out if we can set "settings put global debug_view_attributes 1" in tests
187 view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
188 val viewWidth = with(density) { view.width.toDp() }
189 val viewHeight = with(density) { view.height.toDp() }
190 val builder = LayoutInspectorTree()
191 val nodes = builder.convert(view)
192 dumpNodes(nodes, builder)
193
194 validate(nodes, builder, checkParameters = false) {
195 node(
Amaury Medeiros5f276e22021-01-22 13:48:04 +0000196 name = "Content",
197 fileName = "",
198 left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
199 children = listOf("Box")
200 )
201 node(
Jens Ole Lauridsen76baf942021-01-08 08:09:19 -0800202 name = "Box",
203 isRenderNode = true,
204 fileName = "",
205 left = 0.0.dp, top = 0.0.dp, width = viewWidth, height = viewHeight,
206 children = listOf("MaterialTheme")
207 )
208 node(
209 name = "MaterialTheme",
210 hasTransformations = true,
211 fileName = "LayoutInspectorTreeTest.kt",
Amaury Medeiros5f276e22021-01-22 13:48:04 +0000212 left = 68.0.dp, top = 49.7.dp, width = 88.5.dp, height = 21.7.dp,
Jens Ole Lauridsen76baf942021-01-08 08:09:19 -0800213 children = listOf("Text")
214 )
215 node(
216 name = "Text",
217 isRenderNode = true,
218 hasTransformations = true,
219 fileName = "LayoutInspectorTreeTest.kt",
Amaury Medeiros5f276e22021-01-22 13:48:04 +0000220 left = 68.0.dp, top = 49.7.dp, width = 88.5.dp, height = 21.7.dp,
Jens Ole Lauridsen76baf942021-01-08 08:09:19 -0800221 )
222 }
223 }
224
225 @Test
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700226 fun testStitchTreeFromModelDrawerLayout() {
Chuck Jazdzewski9fcdd6f2020-12-04 09:29:42 -0800227 val slotTableRecord = CompositionDataRecord.create()
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700228
229 show {
230 Inspectable(slotTableRecord) {
Louis Pullen-Freilichfb860452021-02-03 20:55:50 +0000231 ModalDrawer(
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700232 drawerContent = { Text("Something") },
Louis Pullen-Freilichfb860452021-02-03 20:55:50 +0000233 content = {
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700234 Column {
235 Text(text = "Hello World", color = Color.Green)
236 Button(onClick = {}) { Text(text = "OK") }
237 }
238 }
239 )
240 }
241 }
242 view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
243 dumpSlotTableSet(slotTableRecord)
244 val builder = LayoutInspectorTree()
245 val nodes = builder.convert(view)
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800246 dumpNodes(nodes, builder)
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700247
248 if (DEBUG) {
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800249 validate(nodes, builder, checkParameters = false) {
Louis Pullen-Freilichfb860452021-02-03 20:55:50 +0000250 node("Box", children = listOf("ModalDrawer"))
251 node("ModalDrawer", children = listOf("Column", "Text"))
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800252 node("Column", children = listOf("Text", "Button"))
253 node("Text")
254 node("Button", children = listOf("Text"))
255 node("Text")
256 node("Text")
257 }
258 }
259 assertThat(nodes.size).isEqualTo(1)
260 }
261
262 @Test
263 fun testStitchTreeFromModelDrawerLayoutWithSystemNodes() {
264 val slotTableRecord = CompositionDataRecord.create()
265
266 show {
267 Inspectable(slotTableRecord) {
Louis Pullen-Freilichfb860452021-02-03 20:55:50 +0000268 ModalDrawer(
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800269 drawerContent = { Text("Something") },
Louis Pullen-Freilichfb860452021-02-03 20:55:50 +0000270 content = {
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800271 Column {
272 Text(text = "Hello World", color = Color.Green)
273 Button(onClick = {}) { Text(text = "OK") }
274 }
275 }
276 )
277 }
278 }
279 view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
280 dumpSlotTableSet(slotTableRecord)
281 val builder = LayoutInspectorTree()
282 builder.hideSystemNodes = false
283 val nodes = builder.convert(view)
284 dumpNodes(nodes, builder)
285
286 if (DEBUG) {
287 validate(nodes, builder, checkParameters = false) {
Louis Pullen-Freilichfb860452021-02-03 20:55:50 +0000288 node("Box", children = listOf("ModalDrawer"))
289 node("ModalDrawer", children = listOf("WithConstraints"))
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700290 node("WithConstraints", children = listOf("SubcomposeLayout"))
291 node("SubcomposeLayout", children = listOf("Box"))
292 node("Box", children = listOf("Box", "Canvas", "Surface"))
293 node("Box", children = listOf("Column"))
294 node("Column", children = listOf("Text", "Button"))
295 node("Text", children = listOf("Text"))
296 node("Text", children = listOf("CoreText"))
297 node("CoreText", children = listOf())
298 node("Button", children = listOf("Surface"))
299 node("Surface", children = listOf("ProvideTextStyle"))
300 node("ProvideTextStyle", children = listOf("Row"))
301 node("Row", children = listOf("Text"))
302 node("Text", children = listOf("Text"))
303 node("Text", children = listOf("CoreText"))
304 node("CoreText", children = listOf())
305 node("Canvas", children = listOf("Spacer"))
306 node("Spacer", children = listOf())
307 node("Surface", children = listOf("Column"))
308 node("Column", children = listOf("Text"))
309 node("Text", children = listOf("Text"))
310 node("Text", children = listOf("CoreText"))
311 node("CoreText", children = listOf())
312 }
313 }
314 assertThat(nodes.size).isEqualTo(1)
315 }
316
Jens Ole Lauridsen9528c972020-10-22 08:25:27 -0700317 @Test
318 fun testSpacer() {
Chuck Jazdzewski9fcdd6f2020-12-04 09:29:42 -0800319 val slotTableRecord = CompositionDataRecord.create()
Jens Ole Lauridsen9528c972020-10-22 08:25:27 -0700320
321 show {
322 Inspectable(slotTableRecord) {
323 Column {
324 Text(text = "Hello World", color = Color.Green)
325 Spacer(Modifier.preferredHeight(16.dp))
Anastasia Soboleva0b8299e2021-01-11 16:37:11 +0000326 Image(Icons.Filled.Call, null)
Jens Ole Lauridsen9528c972020-10-22 08:25:27 -0700327 }
328 }
329 }
330
331 view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
332 val builder = LayoutInspectorTree()
333 val node = builder.convert(view)
334 .flatMap { flatten(it) }
335 .firstOrNull { it.name == "Spacer" }
Jens Ole Lauridsen0a1c9662020-10-22 13:58:53 -0700336
337 // Spacer should show up in the Compose tree:
Jens Ole Lauridsen9528c972020-10-22 08:25:27 -0700338 assertThat(node).isNotNull()
339 }
340
Chuck Jazdzewskia6a0b3d2020-12-04 13:16:18 -0800341 @Test // regression test b/174855322
342 fun testBasicText() {
Chuck Jazdzewski9fcdd6f2020-12-04 09:29:42 -0800343 val slotTableRecord = CompositionDataRecord.create()
Chuck Jazdzewskia6a0b3d2020-12-04 13:16:18 -0800344
345 view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
346 show {
Amaury Medeiros5f276e22021-01-22 13:48:04 +0000347 Inspectable(slotTableRecord) {
348 Column {
349 BasicText(
350 text = "Some text",
351 style = TextStyle(textDecoration = TextDecoration.Underline)
352 )
353 }
Chuck Jazdzewskia6a0b3d2020-12-04 13:16:18 -0800354 }
355 }
356
357 val builder = LayoutInspectorTree()
358 val node = builder.convert(view)
359 .flatMap { flatten(it) }
360 .firstOrNull { it.name == "BasicText" }
361
362 assertThat(node).isNotNull()
363
364 assertThat(node?.parameters).isNotEmpty()
365 }
366
Jens Ole Lauridsen0a1c9662020-10-22 13:58:53 -0700367 @Test
368 fun testTextId() {
Chuck Jazdzewski9fcdd6f2020-12-04 09:29:42 -0800369 val slotTableRecord = CompositionDataRecord.create()
Jens Ole Lauridsen0a1c9662020-10-22 13:58:53 -0700370
371 show {
372 Inspectable(slotTableRecord) {
373 Text(text = "Hello World")
374 }
375 }
376
377 view.setTag(R.id.inspection_slot_table_set, slotTableRecord.store)
378 val builder = LayoutInspectorTree()
379 val node = builder.convert(view)
380 .flatMap { flatten(it) }
Jens Ole Lauridsend1ef5d62021-01-08 09:13:33 -0800381 .firstOrNull { it.name == "Text" }
Jens Ole Lauridsen0a1c9662020-10-22 13:58:53 -0700382
Jens Ole Lauridsend1ef5d62021-01-08 09:13:33 -0800383 // LayoutNode id should be captured by the Text node:
Jens Ole Lauridsen0a1c9662020-10-22 13:58:53 -0700384 assertThat(node?.id).isGreaterThan(0)
385 }
386
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800387 @Suppress("SameParameterValue")
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700388 private fun validate(
389 result: List<InspectorNode>,
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800390 builder: LayoutInspectorTree,
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700391 checkParameters: Boolean,
392 block: TreeValidationReceiver.() -> Unit = {}
393 ) {
394 val nodes = result.flatMap { flatten(it) }.iterator()
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800395 val tree = TreeValidationReceiver(nodes, density, checkParameters, builder)
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700396 tree.block()
397 }
398
399 private class TreeValidationReceiver(
400 val nodeIterator: Iterator<InspectorNode>,
401 val density: Density,
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800402 val checkParameters: Boolean,
403 val builder: LayoutInspectorTree
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700404 ) {
405 fun node(
406 name: String,
407 fileName: String? = null,
408 lineNumber: Int = -1,
Jens Ole Lauridsen76baf942021-01-08 08:09:19 -0800409 isRenderNode: Boolean = false,
410 hasTransformations: Boolean = false,
411
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700412 left: Dp = Dp.Unspecified,
413 top: Dp = Dp.Unspecified,
414 width: Dp = Dp.Unspecified,
415 height: Dp = Dp.Unspecified,
416 children: List<String> = listOf(),
417 block: ParameterValidationReceiver.() -> Unit = {}
418 ) {
419 assertWithMessage("No such node found: $name").that(nodeIterator.hasNext()).isTrue()
420 val node = nodeIterator.next()
421 assertThat(node.name).isEqualTo(name)
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800422 val message = "Node: $name"
423 assertWithMessage(message).that(node.children.map { it.name })
424 .containsExactlyElementsIn(children).inOrder()
425 fileName?.let { assertWithMessage(message).that(node.fileName).isEqualTo(fileName) }
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700426 if (lineNumber != -1) {
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800427 assertWithMessage(message).that(node.lineNumber).isEqualTo(lineNumber)
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700428 }
Jens Ole Lauridsen76baf942021-01-08 08:09:19 -0800429 if (isRenderNode) {
430 assertWithMessage(message).that(node.id).isGreaterThan(0L)
431 } else {
432 assertWithMessage(message).that(node.id).isLessThan(0L)
433 }
434 if (hasTransformations) {
435 assertWithMessage(message).that(node.bounds).isNotEmpty()
436 } else {
437 assertWithMessage(message).that(node.bounds).isEmpty()
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700438 }
439 if (left != Dp.Unspecified) {
440 with(density) {
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800441 assertWithMessage(message).that(node.left.toDp().value)
442 .isWithin(2.0f).of(left.value)
443 assertWithMessage(message).that(node.top.toDp().value)
444 .isWithin(2.0f).of(top.value)
445 assertWithMessage(message).that(node.width.toDp().value)
446 .isWithin(2.0f).of(width.value)
447 assertWithMessage(message).that(node.height.toDp().value)
448 .isWithin(2.0f).of(height.value)
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700449 }
450 }
451
452 if (checkParameters) {
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800453 val params = builder.convertParameters(node)
454 val receiver = ParameterValidationReceiver(params.listIterator())
455 receiver.block()
456 if (receiver.parameterIterator.hasNext()) {
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700457 val elementNames = mutableListOf<String>()
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800458 receiver.parameterIterator.forEachRemaining { elementNames.add(it.name) }
Jens Ole Lauridsene295d242020-09-22 15:39:42 -0700459 error("$name: has more parameters like: ${elementNames.joinToString()}")
460 }
461 }
462 }
Jens Ole Lauridsen27c564b2020-07-21 14:04:15 -0700463 }
464
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700465 private fun flatten(node: InspectorNode): List<InspectorNode> =
466 listOf(node).plus(node.children.flatMap { flatten(it) })
467
468 // region DEBUG print methods
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800469 private fun dumpNodes(nodes: List<InspectorNode>, builder: LayoutInspectorTree) {
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700470 @Suppress("ConstantConditionIf")
471 if (!DEBUG) {
472 return
473 }
474 println()
475 println("=================== Nodes ==========================")
476 nodes.forEach { dumpNode(it, indent = 0) }
477 println()
478 println("=================== validate statements ==========================")
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800479 nodes.forEach { generateValidate(it, builder) }
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700480 }
481
482 private fun dumpNode(node: InspectorNode, indent: Int) {
483 println(
484 "\"${" ".repeat(indent * 2)}\", \"${node.name}\", \"${node.fileName}\", " +
Jeff Gaston0292fc22020-09-18 15:10:46 -0400485 "${node.lineNumber}, ${node.left}, ${node.top}, " +
486 "${node.width}, ${node.height}"
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700487 )
488 node.children.forEach { dumpNode(it, indent + 1) }
489 }
490
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800491 private fun generateValidate(
492 node: InspectorNode,
493 builder: LayoutInspectorTree,
494 generateParameters: Boolean = false
495 ) {
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700496 with(density) {
497 val left = round(node.left.toDp())
498 val top = round(node.top.toDp())
499 val width = if (node.width == view.width) "viewWidth" else round(node.width.toDp())
500 val height = if (node.height == view.height) "viewHeight" else round(node.height.toDp())
Jens Ole Lauridsen2abda602020-06-12 10:19:48 -0700501
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700502 print(
503 """
504 validate(
505 name = "${node.name}",
506 fileName = "${node.fileName}",
Jeff Gaston0292fc22020-09-18 15:10:46 -0400507 left = $left, top = $top, width = $width, height = $height
508 """.trimIndent()
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700509 )
510 }
Jens Ole Lauridsenabd03ef2020-07-08 16:08:59 -0700511 if (node.id > 0L) {
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700512 println(",")
513 print(" isRenderNode = true")
514 }
515 if (node.children.isNotEmpty()) {
516 println(",")
517 val children = node.children.joinToString { "\"${it.name}\"" }
518 print(" children = listOf($children)")
519 }
520 println()
Jens Ole Lauridsenabd03ef2020-07-08 16:08:59 -0700521 print(")")
Jens Ole Lauridsen9e35be52020-12-23 15:12:29 -0800522 if (generateParameters && node.parameters.isNotEmpty()) {
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800523 generateParameters(builder.convertParameters(node), 0)
Jens Ole Lauridsenabd03ef2020-07-08 16:08:59 -0700524 }
525 println()
Jens Ole Lauridsen50d7f442020-11-04 08:09:40 -0800526 node.children.forEach { generateValidate(it, builder) }
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700527 }
528
Jens Ole Lauridsenabd03ef2020-07-08 16:08:59 -0700529 private fun generateParameters(parameters: List<NodeParameter>, indent: Int) {
530 val indentation = " ".repeat(indent * 2)
531 println(" {")
532 for (param in parameters) {
533 val name = param.name
534 val type = param.type
535 val value = toDisplayValue(type, param.value)
536 print("$indentation parameter(name = \"$name\", type = $type, value = $value)")
537 if (param.elements.isNotEmpty()) {
538 generateParameters(param.elements, indent + 1)
539 }
540 println()
541 }
542 print("$indentation}")
543 }
544
545 private fun toDisplayValue(type: ParameterType, value: Any?): String =
546 when (type) {
547 ParameterType.Boolean -> value.toString()
548 ParameterType.Color ->
549 "0x${Integer.toHexString(value as Int)}${if (value < 0) ".toInt()" else ""}"
550 ParameterType.DimensionSp,
551 ParameterType.DimensionDp -> "${value}f"
552 ParameterType.Int32 -> value.toString()
553 ParameterType.String -> "\"$value\""
554 else -> value?.toString() ?: "null"
555 }
556
Chuck Jazdzewski9fcdd6f2020-12-04 09:29:42 -0800557 private fun dumpSlotTableSet(slotTableRecord: CompositionDataRecord) {
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700558 @Suppress("ConstantConditionIf")
559 if (!DEBUG) {
560 return
561 }
562 println()
563 println("=================== Groups ==========================")
564 slotTableRecord.store.forEach { dumpGroup(it.asTree(), indent = 0) }
565 }
566
567 private fun dumpGroup(group: Group, indent: Int) {
568 val position = group.position?.let { "\"$it\"" } ?: "null"
569 val box = group.box
Andrey Kulikov554f7362021-01-28 15:35:39 +0000570 val id = group.modifierInfo.mapNotNull { (it.extra as? GraphicLayerInfo)?.layerId }
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700571 .singleOrNull() ?: 0
572 println(
Jens Ole Lauridsen27c564b2020-07-21 14:04:15 -0700573 "\"${" ".repeat(indent)}\", ${group.javaClass.simpleName}, \"${group.name}\", " +
Jeff Gaston0292fc22020-09-18 15:10:46 -0400574 "params: ${group.parameters.size}, children: ${group.children.size}, " +
575 "$id, $position, " +
576 "${box.left}, ${box.right}, ${box.right - box.left}, ${box.bottom - box.top}"
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700577 )
Jens Ole Lauridsenabd03ef2020-07-08 16:08:59 -0700578 for (parameter in group.parameters) {
579 println("\"${" ".repeat(indent + 4)}\"- ${parameter.name}")
580 }
Jens Ole Lauridsen222fa0a2020-06-09 13:55:49 -0700581 group.children.forEach { dumpGroup(it, indent + 1) }
582 }
583
584 private fun round(dp: Dp): Dp = Dp((dp.value * 10.0f).roundToInt() / 10.0f)
585
586 //endregion
587}