Added strongly typed tree for sidebar inspector

Summary:
Introduced a JSON like tree structure for the sidebar insepector. Descriptors can provide their data as well as  if its mutable or not.

Enum mapping was simplified

Reviewed By: lblasa

Differential Revision: D38947238

fbshipit-source-id: cd8a6a8a752c5f626582ab8ac5efae6e9ff6a2ad
This commit is contained in:
Luke De Feo
2022-09-07 04:37:17 -07:00
committed by Facebook GitHub Bot
parent c69e737f19
commit f123e65e8f
20 changed files with 418 additions and 336 deletions

View File

@@ -52,16 +52,6 @@ public class EnumMapping<T> {
return mMapping.get(mDefaultKey); return mMapping.get(mDefaultKey);
} }
public InspectorValue<?> toPicker() {
return toPicker(true);
}
public InspectorValue<?> toPicker(final boolean mutable) {
return mutable
? InspectorValue.mutable(Picker, new InspectorValue.Picker(mMapping.keySet(), mDefaultKey))
: InspectorValue.immutable(Enum, mDefaultKey);
}
public InspectorValue<?> toPicker(final T currentValue) { public InspectorValue<?> toPicker(final T currentValue) {
return toPicker(currentValue, true); return toPicker(currentValue, true);
} }

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.uidebugger
import com.facebook.flipper.plugins.uidebugger.common.Node
@kotlinx.serialization.Serializable
data class InitEvent(val rootId: String) {
companion object {
val name = "init"
}
}
// TODO flatten the tree into normalised list
@kotlinx.serialization.Serializable
data class NativeScanEvent(val root: Node) {
companion object {
val name = "nativeScan"
}
}

View File

@@ -9,11 +9,14 @@ package com.facebook.flipper.plugins.uidebugger
import android.app.Application import android.app.Application
import com.facebook.flipper.core.FlipperConnection import com.facebook.flipper.core.FlipperConnection
import com.facebook.flipper.core.FlipperObject
import com.facebook.flipper.core.FlipperPlugin import com.facebook.flipper.core.FlipperPlugin
import com.facebook.flipper.plugins.uidebugger.common.Node
import com.facebook.flipper.plugins.uidebugger.core.ApplicationInspector import com.facebook.flipper.plugins.uidebugger.core.ApplicationInspector
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.Context import com.facebook.flipper.plugins.uidebugger.core.Context
import kotlinx.serialization.json.Json
val LogTag = "FlipperUIDebugger"
class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin { class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin {
@@ -28,14 +31,18 @@ class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin {
override fun onConnect(connection: FlipperConnection) { override fun onConnect(connection: FlipperConnection) {
this.connection = connection this.connection = connection
// temp solution, get from descriptor // temp solution, get from descriptor
connection.send(
"init",
FlipperObject.Builder()
.put("rootId", System.identityHashCode(application).toString())
.build())
val inspector = ApplicationInspector(context) val inspector = ApplicationInspector(context)
val root = inspector.inspect() val root: Node = inspector.inspect()!!
val initEvent = InitEvent(System.identityHashCode(application).toString())
connection.send(
InitEvent.name,
Json.encodeToString(
InitEvent.serializer(), InitEvent(System.identityHashCode(application).toString())))
connection.send(
NativeScanEvent.name,
Json.encodeToString(NativeScanEvent.serializer(), NativeScanEvent(root)))
} }
@Throws(Exception::class) @Throws(Exception::class)

View File

@@ -7,60 +7,36 @@
package com.facebook.flipper.plugins.uidebugger.common package com.facebook.flipper.plugins.uidebugger.common
import androidx.collection.ArrayMap import android.util.Log
import androidx.collection.SimpleArrayMap import com.facebook.flipper.plugins.uidebugger.LogTag
open class EnumMapping<T>(private val defaultKey: String) { // Maintains 2 way mapping between some enum value and a readable string representation
private val map = ArrayMap<String, T>() open class EnumMapping<T>(val mapping: Map<String, T>) {
fun put(key: String, value: T) { fun getStringRepresentation(enumValue: T): String {
map.put(key, value) val entry = mapping.entries.find { (_, value) -> value == enumValue }
} if (entry != null) {
return entry.key
fun get(value: T): InspectableValue<String> { } else {
return get(value, true) Log.w(
} LogTag,
"Could not convert enum value ${enumValue.toString()} to string, known values ${mapping.entries}")
fun get(value: T, mutable: Boolean = true): InspectableValue<String> { return NoMapping
val key = findKeyForValue(map, defaultKey, value)
return if (mutable) InspectableValue.mutable(InspectableValue.Type.Enum, key)
else InspectableValue.immutable(InspectableValue.Type.Enum, key)
}
fun get(s: String): T? {
return if (map.containsKey(s)) {
map[s]
} else map[defaultKey]
}
fun toPicker(mutable: Boolean = true): InspectableValue<*> {
return if (mutable)
InspectableValue.mutable(
InspectableValue.Type.Picker, InspectableValue.Picker(map.keys, defaultKey))
else InspectableValue.immutable(InspectableValue.Type.Enum, defaultKey)
}
fun toPicker(currentValue: T, mutable: Boolean = true): InspectableValue<*> {
val value = findKeyForValue(map, defaultKey, currentValue)
return if (mutable)
InspectableValue.mutable(
InspectableValue.Type.Picker, InspectableValue.Picker(map.keys, value))
else InspectableValue.immutable(InspectableValue.Type.Enum, value)
}
companion object {
fun <T> findKeyForValue(
mapping: SimpleArrayMap<String, T>,
defaultKey: String,
currentValue: T
): String {
val count = mapping.size() - 1
for (i in 0..count) {
if (mapping.valueAt(i) == currentValue) {
return mapping.keyAt(i)
}
}
return defaultKey
} }
} }
fun getEnumValue(key: String): T {
val value =
mapping[key]
?: throw UIDebuggerException(
"Could not convert string ${key} to enum value, possible values ${mapping.entries} ")
return value
}
fun toInspectable(value: T, mutable: Boolean): InspectableValue.Enum {
return InspectableValue.Enum(EnumData(mapping.keys, getStringRepresentation(value)), mutable)
}
companion object {
val NoMapping = "__UNKNOWN_ENUM_VALUE__"
}
} }

View File

@@ -0,0 +1,10 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.uidebugger.common
class UIDebuggerException(message: String) : Exception(message)

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.uidebugger.common
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@kotlinx.serialization.Serializable
sealed class Inspectable {
abstract val mutable: kotlin.Boolean
}
// mutable here means you can add/remove items, for native android this should probably be false
@SerialName("array")
@Serializable
data class InspectableArray(val items: List<Inspectable>, override val mutable: Boolean = false) :
Inspectable()
// mutable here means you can add / remove keys, for native android this should probably be false
@SerialName("object")
@Serializable
data class InspectableObject(
val fields: Map<String, Inspectable>,
override val mutable: Boolean = false
) : Inspectable()
@kotlinx.serialization.Serializable
sealed class InspectableValue : Inspectable() {
@kotlinx.serialization.Serializable
@SerialName("text")
class Text(val value: String, override val mutable: kotlin.Boolean) : InspectableValue()
@kotlinx.serialization.Serializable
@SerialName("boolean")
class Boolean(val value: kotlin.Boolean, override val mutable: kotlin.Boolean) :
InspectableValue()
@SerialName("number")
@kotlinx.serialization.Serializable
data class Number(
@Serializable(with = NumberSerializer::class) val value: kotlin.Number,
override val mutable: kotlin.Boolean
) : InspectableValue()
@SerialName("color")
@kotlinx.serialization.Serializable
data class Color(val value: Int, override val mutable: kotlin.Boolean) : InspectableValue()
@SerialName("enum")
@kotlinx.serialization.Serializable
data class Enum(val value: EnumData, override val mutable: kotlin.Boolean) : InspectableValue()
companion object {
/**
* Will attempt to convert Any ref to a suitable primitive inspectable value. Only use if you
* are dealing with an Any / object type. Prefer the specific contructors
*/
fun fromAny(any: Any, mutable: kotlin.Boolean): Inspectable? {
return when (any) {
is kotlin.Number -> InspectableValue.Number(any, mutable)
is kotlin.Boolean -> InspectableValue.Boolean(any, mutable)
is kotlin.String -> InspectableValue.Text(any, mutable)
else -> null
}
}
}
}
@kotlinx.serialization.Serializable data class EnumData(val values: Set<String>, val value: String)
object NumberSerializer : KSerializer<Number> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("com.meta.NumberSerializer", PrimitiveKind.DOUBLE)
override fun serialize(encoder: Encoder, value: Number) {
when (value) {
is Double -> encoder.encodeDouble(value.toDouble())
is Float -> encoder.encodeFloat(value.toFloat())
is Long -> encoder.encodeLong(value.toLong())
is Int -> encoder.encodeInt(value.toInt())
}
}
override fun deserialize(decoder: Decoder): Number {
return decoder.decodeFloat()
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.uidebugger.common
class InspectableValue<T>
private constructor(val type: Type<T>, val value: T, val mutable: Boolean) {
/**
* Describe the type of data this value contains. This will influence how values are parsed and
* displayed by the Flipper desktop app. For example colors will be parse as integers and
* displayed using hex values and be editable using a color picker.
*
* Do not extends this list of types without adding support for the type in the desktop Inspector.
*/
class Type<T> internal constructor(private val name: String) {
override fun toString(): String {
return name
}
companion object {
val Auto: Type<Any> = Type<Any>("auto")
val Text = Type<String>("text")
val Number = Type<Number>("number")
val Boolean = Type<Boolean>("boolean")
val Enum = Type<String>("enum")
val Color = Type<Int>("color")
val Picker = Type<Picker>("picker")
}
}
class Picker(val values: Set<String>, val selected: String) {}
companion object {
fun <T> mutable(type: Type<T>, value: T): InspectableValue<T> {
return InspectableValue(type, value, true)
}
fun <T> immutable(type: Type<T>, value: T): InspectableValue<T> {
return InspectableValue(type, value, false)
}
fun mutable(value: Any): InspectableValue<*> {
return InspectableValue<Any>(Type.Auto, value, true)
}
fun immutable(value: Any): InspectableValue<*> {
return InspectableValue<Any>(Type.Auto, value, false)
}
}
}

View File

@@ -7,11 +7,10 @@
package com.facebook.flipper.plugins.uidebugger.common package com.facebook.flipper.plugins.uidebugger.common
import java.lang.ref.WeakReference @kotlinx.serialization.Serializable
class Node() {
class Node(val ref: WeakReference<Any>) {
var id: String? = null var id: String? = null
var name: String? = null var name: String? = null
var attributes: Map<String, Any?>? = null var attributes: Map<String, InspectableObject> = mapOf()
var children: List<Node>? = null var children: List<Node>? = null
} }

View File

@@ -7,10 +7,10 @@
package com.facebook.flipper.plugins.uidebugger.core package com.facebook.flipper.plugins.uidebugger.core
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.common.Node import com.facebook.flipper.plugins.uidebugger.common.Node
import com.facebook.flipper.plugins.uidebugger.descriptors.Descriptor import com.facebook.flipper.plugins.uidebugger.descriptors.Descriptor
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import java.lang.ref.WeakReference
class LayoutTraversal(private val descriptorRegister: DescriptorRegister) { class LayoutTraversal(private val descriptorRegister: DescriptorRegister) {
class IntermediateNode(val node: Node) { class IntermediateNode(val node: Node) {
@@ -20,7 +20,7 @@ class LayoutTraversal(private val descriptorRegister: DescriptorRegister) {
internal inline fun Descriptor<*>.asAny(): Descriptor<Any> = this as Descriptor<Any> internal inline fun Descriptor<*>.asAny(): Descriptor<Any> = this as Descriptor<Any>
private fun describe(obj: Any): IntermediateNode { private fun describe(obj: Any): IntermediateNode {
var intermediate = IntermediateNode(Node(WeakReference(obj))) var intermediate = IntermediateNode(Node())
val descriptor = descriptorRegister.descriptorForClass(obj::class.java) val descriptor = descriptorRegister.descriptorForClass(obj::class.java)
descriptor?.let { descriptor -> descriptor?.let { descriptor ->
@@ -29,7 +29,7 @@ class LayoutTraversal(private val descriptorRegister: DescriptorRegister) {
intermediate.node.id = anyDescriptor.getId(obj) intermediate.node.id = anyDescriptor.getId(obj)
intermediate.node.name = anyDescriptor.getName(obj) intermediate.node.name = anyDescriptor.getName(obj)
val attributes = mutableMapOf<String, Any?>() val attributes = mutableMapOf<String, InspectableObject>()
anyDescriptor.getData(obj, attributes) anyDescriptor.getData(obj, attributes)
intermediate.node.attributes = attributes intermediate.node.attributes = attributes

View File

@@ -7,13 +7,15 @@
package com.facebook.flipper.plugins.uidebugger.descriptors package com.facebook.flipper.plugins.uidebugger.descriptors
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
/** /**
* This class derives from Descriptor and provides a canonical implementation of ChainedDescriptor}. * This class derives from Descriptor and provides a canonical implementation of ChainedDescriptor}.
*/ */
abstract class AbstractChainedDescriptor<T> : Descriptor<T>(), ChainedDescriptor<T> { abstract class AbstractChainedDescriptor<T> : Descriptor<T>(), ChainedDescriptor<T> {
private var mSuper: Descriptor<T>? = null private var mSuper: Descriptor<T>? = null
override fun setSuper(superDescriptor: Descriptor<T>) { final override fun setSuper(superDescriptor: Descriptor<T>) {
if (superDescriptor !== mSuper) { if (superDescriptor !== mSuper) {
check(mSuper == null) check(mSuper == null)
mSuper = superDescriptor mSuper = superDescriptor
@@ -25,7 +27,7 @@ abstract class AbstractChainedDescriptor<T> : Descriptor<T>(), ChainedDescriptor
} }
/** Initialize a descriptor. */ /** Initialize a descriptor. */
override fun init() { final override fun init() {
mSuper?.init() mSuper?.init()
onInit() onInit()
} }
@@ -36,7 +38,7 @@ abstract class AbstractChainedDescriptor<T> : Descriptor<T>(), ChainedDescriptor
* A globally unique ID used to identify a node in a hierarchy. If your node does not have a * A globally unique ID used to identify a node in a hierarchy. If your node does not have a
* globally unique ID it is fine to rely on [System.identityHashCode]. * globally unique ID it is fine to rely on [System.identityHashCode].
*/ */
override fun getId(node: T): String { final override fun getId(node: T): String {
return onGetId(node) return onGetId(node)
} }
@@ -46,28 +48,28 @@ abstract class AbstractChainedDescriptor<T> : Descriptor<T>(), ChainedDescriptor
* The name used to identify this node in the inspector. Does not need to be unique. A good * The name used to identify this node in the inspector. Does not need to be unique. A good
* default is to use the class name of the node. * default is to use the class name of the node.
*/ */
override fun getName(node: T): String { final override fun getName(node: T): String {
return onGetName(node) return onGetName(node)
} }
abstract fun onGetName(node: T): String abstract fun onGetName(node: T): String
/** The children this node exposes in the inspector. */ /** The children this node exposes in the inspector. */
override fun getChildren(node: T, children: MutableList<Any>) { final override fun getChildren(node: T, children: MutableList<Any>) {
mSuper?.getChildren(node, children) mSuper?.getChildren(node, children)
onGetChildren(node, children) onGetChildren(node, children)
} }
open fun onGetChildren(node: T, children: MutableList<Any>) {} open fun onGetChildren(node: T, children: MutableList<Any>) {}
/** final override fun getData(node: T, builder: MutableMap<String, InspectableObject>) {
* Get the data to show for this node in the sidebar of the inspector. The object will be showen
* in order and with a header matching the given name.
*/
override fun getData(node: T, builder: MutableMap<String, Any?>) {
mSuper?.getData(node, builder) mSuper?.getData(node, builder)
onGetData(node, builder) onGetData(node, builder)
} }
open fun onGetData(node: T, builder: MutableMap<String, Any?>) {} /**
* Get the data to show for this node in the sidebar of the inspector. Each key will be a have its
* own section
*/
open fun onGetData(node: T, attributeSections: MutableMap<String, InspectableObject>) {}
} }

View File

@@ -8,6 +8,7 @@
package com.facebook.flipper.plugins.uidebugger.descriptors package com.facebook.flipper.plugins.uidebugger.descriptors
import android.app.Activity import android.app.Activity
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat
class ActivityDescriptor : AbstractChainedDescriptor<Activity>() { class ActivityDescriptor : AbstractChainedDescriptor<Activity>() {
@@ -35,7 +36,10 @@ class ActivityDescriptor : AbstractChainedDescriptor<Activity>() {
} }
} }
override fun onGetData(activity: Activity, builder: MutableMap<String, Any?>) {} override fun onGetData(
activity: Activity,
attributeSections: MutableMap<String, InspectableObject>
) {}
private fun getFragments(compat: FragmentCompat<*, *, *, *>?, activity: Activity): List<Any> { private fun getFragments(compat: FragmentCompat<*, *, *, *>?, activity: Activity): List<Any> {
if (compat == null) { if (compat == null) {

View File

@@ -8,6 +8,7 @@
package com.facebook.flipper.plugins.uidebugger.descriptors package com.facebook.flipper.plugins.uidebugger.descriptors
import android.app.Activity import android.app.Activity
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver
@@ -48,5 +49,8 @@ class ApplicationDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
} }
} }
override fun onGetData(applicationRef: ApplicationRef, builder: MutableMap<String, Any?>) {} override fun onGetData(
applicationRef: ApplicationRef,
attributeSections: MutableMap<String, InspectableObject>
) {}
} }

View File

@@ -8,9 +8,9 @@
package com.facebook.flipper.plugins.uidebugger.descriptors package com.facebook.flipper.plugins.uidebugger.descriptors
import android.widget.Button import android.widget.Button
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
class ButtonDescriptor : AbstractChainedDescriptor<Button>() { class ButtonDescriptor : AbstractChainedDescriptor<Button>() {
override fun init() {}
override fun onGetId(button: Button): String { override fun onGetId(button: Button): String {
return Integer.toString(System.identityHashCode(button)) return Integer.toString(System.identityHashCode(button))
@@ -20,7 +20,10 @@ class ButtonDescriptor : AbstractChainedDescriptor<Button>() {
return button.javaClass.simpleName return button.javaClass.simpleName
} }
override fun onGetData(button: Button, builder: MutableMap<String, Any?>) {} override fun onGetData(
button: Button,
attributeSections: MutableMap<String, InspectableObject>
) {}
override fun onGetChildren(button: Button, children: MutableList<Any>) {} override fun onGetChildren(button: Button, children: MutableList<Any>) {}
} }

View File

@@ -7,6 +7,8 @@
package com.facebook.flipper.plugins.uidebugger.descriptors package com.facebook.flipper.plugins.uidebugger.descriptors
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
interface NodeDescriptor<T> { interface NodeDescriptor<T> {
/** Initialize a descriptor. */ /** Initialize a descriptor. */
fun init() fun init()
@@ -27,8 +29,8 @@ interface NodeDescriptor<T> {
fun getChildren(node: T, children: MutableList<Any>) fun getChildren(node: T, children: MutableList<Any>)
/** /**
* Get the data to show for this node in the sidebar of the inspector. The object will be showen * Get the data to show for this node in the sidebar of the inspector. The object will be shown in
* in order and with a header matching the given name. * order and with a header matching the given name.
*/ */
fun getData(node: T, builder: MutableMap<String, Any?>) fun getData(node: T, builder: MutableMap<String, InspectableObject>)
} }

View File

@@ -7,6 +7,8 @@
package com.facebook.flipper.plugins.uidebugger.descriptors package com.facebook.flipper.plugins.uidebugger.descriptors
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
class ObjectDescriptor : Descriptor<Any>() { class ObjectDescriptor : Descriptor<Any>() {
override fun init() {} override fun init() {}
@@ -20,5 +22,5 @@ class ObjectDescriptor : Descriptor<Any>() {
override fun getChildren(node: Any, children: MutableList<Any>) {} override fun getChildren(node: Any, children: MutableList<Any>) {}
override fun getData(obj: Any, builder: MutableMap<String, Any?>) {} override fun getData(obj: Any, builder: MutableMap<String, InspectableObject>) {}
} }

View File

@@ -8,9 +8,9 @@
package com.facebook.flipper.plugins.uidebugger.descriptors package com.facebook.flipper.plugins.uidebugger.descriptors
import android.widget.TextView import android.widget.TextView
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
class TextViewDescriptor : AbstractChainedDescriptor<TextView>() { class TextViewDescriptor : AbstractChainedDescriptor<TextView>() {
override fun init() {}
override fun onGetId(textView: TextView): String { override fun onGetId(textView: TextView): String {
return Integer.toString(System.identityHashCode(textView)) return Integer.toString(System.identityHashCode(textView))
@@ -22,5 +22,8 @@ class TextViewDescriptor : AbstractChainedDescriptor<TextView>() {
override fun onGetChildren(textView: TextView, children: MutableList<Any>) {} override fun onGetChildren(textView: TextView, children: MutableList<Any>) {}
override fun onGetData(textView: TextView, builder: MutableMap<String, Any?>) {} override fun onGetData(
textView: TextView,
attributeSections: MutableMap<String, InspectableObject>
) {}
} }

View File

@@ -13,16 +13,16 @@ import android.util.SparseArray
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout import android.widget.LinearLayout
import com.facebook.flipper.plugins.uidebugger.common.EnumMapping import com.facebook.flipper.plugins.uidebugger.common.EnumMapping
import com.facebook.flipper.plugins.uidebugger.common.Inspectable
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.common.InspectableValue import com.facebook.flipper.plugins.uidebugger.common.InspectableValue
import com.facebook.flipper.plugins.uidebugger.stetho.ResourcesUtil import com.facebook.flipper.plugins.uidebugger.stetho.ResourcesUtil
import java.lang.reflect.Field import java.lang.reflect.Field
class ViewDescriptor : AbstractChainedDescriptor<View>() { class ViewDescriptor : AbstractChainedDescriptor<View>() {
override fun init() {}
override fun onGetId(view: View): String { override fun onGetId(view: View): String {
return Integer.toBinaryString(System.identityHashCode(view)) return Integer.toBinaryString(System.identityHashCode(view))
@@ -34,119 +34,133 @@ class ViewDescriptor : AbstractChainedDescriptor<View>() {
override fun onGetChildren(view: View, children: MutableList<Any>) {} override fun onGetChildren(view: View, children: MutableList<Any>) {}
override fun onGetData(view: View, builder: MutableMap<String, Any?>) { override fun onGetData(view: View, attributeSections: MutableMap<String, InspectableObject>) {
val positionOnScreen = IntArray(2) val positionOnScreen = IntArray(2)
view.getLocationOnScreen(positionOnScreen) view.getLocationOnScreen(positionOnScreen)
val props = mutableMapOf<String, Any?>() val props = mutableMapOf<String, Inspectable>()
props.put("height", InspectableValue.mutable(view.height)) props.put("height", InspectableValue.Number(view.height, mutable = true))
props.put("width", InspectableValue.mutable(view.width)) props.put("width", InspectableValue.Number(view.width, mutable = true))
props.put("alpha", InspectableValue.mutable(view.alpha)) props.put("alpha", InspectableValue.Number(view.alpha, mutable = true))
props.put("visibility", VisibilityMapping.toPicker(view.visibility)) props.put("visibility", VisibilityMapping.toInspectable(view.visibility, mutable = false))
props.put("background", fromDrawable(view.background))
// props.put("tag", InspectableValue.mutable(view.tag)) fromDrawable(view.background)?.let { props.put("background", it) }
// props.put("keyedTags", getTags(view))
view.tag?.let { InspectableValue.fromAny(it, mutable = false) }?.let { props.put("tag", it) }
props.put("keyedTags", InspectableObject(getTags(view)))
props.put("layoutParams", getLayoutParams(view)) props.put("layoutParams", getLayoutParams(view))
props.put( props.put(
"state", "state",
mapOf<String, Any?>( InspectableObject(
"enabled" to InspectableValue.mutable(view.isEnabled), mapOf(
"activated" to InspectableValue.mutable(view.isActivated), "enabled" to InspectableValue.Boolean(view.isEnabled, mutable = false),
"focused" to view.isFocused, "activated" to InspectableValue.Boolean(view.isActivated, mutable = false),
"selected" to InspectableValue.mutable(view.isSelected))) "focused" to InspectableValue.Boolean(view.isFocused, mutable = false),
"selected" to InspectableValue.Boolean(view.isSelected, mutable = false))))
props.put( props.put(
"bounds", "bounds",
mapOf<String, Any?>( InspectableObject(
"left" to InspectableValue.mutable(view.left), mapOf<String, Inspectable>(
"right" to InspectableValue.mutable(view.right), "left" to InspectableValue.Number(view.left, mutable = true),
"top" to InspectableValue.mutable(view.top), "right" to InspectableValue.Number(view.right, mutable = true),
"bottom" to InspectableValue.mutable(view.bottom))) "top" to InspectableValue.Number(view.top, mutable = true),
"bottom" to InspectableValue.Number(view.bottom, mutable = true))))
props.put( props.put(
"padding", "padding",
mapOf<String, Any?>( InspectableObject(
"left" to InspectableValue.mutable(view.paddingLeft), mapOf<String, Inspectable>(
"top" to InspectableValue.mutable(view.paddingTop), "left" to InspectableValue.Number(view.paddingLeft, mutable = true),
"right" to InspectableValue.mutable(view.paddingRight), "right" to InspectableValue.Number(view.paddingRight, mutable = true),
"bottom" to InspectableValue.mutable(view.paddingBottom))) "top" to InspectableValue.Number(view.paddingTop, mutable = true),
"bottom" to InspectableValue.Number(view.paddingBottom, mutable = true))))
props.put( props.put(
"rotation", "rotation",
mapOf<String, Any?>( InspectableObject(
"x" to InspectableValue.mutable(view.rotationX), mapOf<String, Inspectable>(
"y" to InspectableValue.mutable(view.rotationY), "x" to InspectableValue.Number(view.rotationX, mutable = true),
"z" to InspectableValue.mutable(view.rotation))) "y" to InspectableValue.Number(view.rotationY, mutable = true),
"z" to InspectableValue.Number(view.rotation, mutable = true))))
props.put( props.put(
"scale", "scale",
mapOf<String, Any?>( InspectableObject(
"x" to InspectableValue.mutable(view.scaleX), mapOf(
"y" to InspectableValue.mutable(view.scaleY))) "x" to InspectableValue.Number(view.scaleX, mutable = true),
"y" to InspectableValue.Number(view.scaleY, mutable = true))))
props.put( props.put(
"pivot", "pivot",
mapOf<String, Any?>( InspectableObject(
"x" to InspectableValue.mutable(view.pivotX), mapOf(
"y" to InspectableValue.mutable(view.pivotY))) "x" to InspectableValue.Number(view.pivotX, mutable = true),
props.put("positionOnScreenX", positionOnScreen[0]) "y" to InspectableValue.Number(view.pivotY, mutable = true))))
props.put("positionOnScreenY", positionOnScreen[1])
builder.put("View", props) props.put(
"globalPosition",
InspectableObject(
mapOf(
"x" to InspectableValue.Number(positionOnScreen[0], mutable = false),
"y" to InspectableValue.Number(positionOnScreen[1], mutable = false))))
attributeSections.put("View", InspectableObject(props.toMap()))
} }
fun fromDrawable(d: Drawable?): InspectableValue<*> { fun fromDrawable(d: Drawable?): Inspectable? {
return if (d is ColorDrawable) { return if (d is ColorDrawable) {
InspectableValue.mutable(InspectableValue.Type.Color, d.color) InspectableValue.Color(d.color, mutable = false)
} else InspectableValue.mutable(InspectableValue.Type.Color, 0) } else null
} }
fun fromSize(size: Int): InspectableValue<*> { fun getLayoutParams(node: View): InspectableObject {
return when (size) {
ViewGroup.LayoutParams.WRAP_CONTENT ->
InspectableValue.mutable(InspectableValue.Type.Enum, "WRAP_CONTENT")
ViewGroup.LayoutParams.MATCH_PARENT ->
InspectableValue.mutable(InspectableValue.Type.Enum, "MATCH_PARENT")
else -> InspectableValue.mutable(InspectableValue.Type.Enum, Integer.toString(size))
}
}
fun getLayoutParams(node: View): MutableMap<String, Any> {
val layoutParams = node.layoutParams val layoutParams = node.layoutParams
val params = mutableMapOf<String, Any>() val params = mutableMapOf<String, Inspectable>()
params.put("width", fromSize(layoutParams.width)) params.put("width", LayoutParamsMapping.toInspectable(layoutParams.width, mutable = true))
params.put("height", fromSize(layoutParams.height)) params.put("height", LayoutParamsMapping.toInspectable(layoutParams.height, mutable = true))
if (layoutParams is MarginLayoutParams) {
if (layoutParams is ViewGroup.MarginLayoutParams) {
val marginLayoutParams = layoutParams val marginLayoutParams = layoutParams
val margin = val margin =
mapOf<String, Any>( InspectableObject(
"left" to InspectableValue.mutable(marginLayoutParams.leftMargin), mapOf<String, Inspectable>(
"top" to InspectableValue.mutable(marginLayoutParams.topMargin), "left" to InspectableValue.Number(marginLayoutParams.leftMargin, mutable = true),
"right" to InspectableValue.mutable(marginLayoutParams.rightMargin), "top" to InspectableValue.Number(marginLayoutParams.topMargin, mutable = true),
"bottom" to InspectableValue.mutable(marginLayoutParams.bottomMargin)) "right" to
InspectableValue.Number(marginLayoutParams.rightMargin, mutable = true),
"bottom" to
InspectableValue.Number(marginLayoutParams.bottomMargin, mutable = true)))
params.put("margin", margin) params.put("margin", margin)
} }
if (layoutParams is FrameLayout.LayoutParams) { if (layoutParams is FrameLayout.LayoutParams) {
params.put("gravity", GravityMapping.toPicker(layoutParams.gravity)) params.put("gravity", GravityMapping.toInspectable(layoutParams.gravity, mutable = true))
} }
if (layoutParams is LinearLayout.LayoutParams) { if (layoutParams is LinearLayout.LayoutParams) {
val linearLayoutParams = layoutParams val linearLayoutParams = layoutParams
params.put("weight", InspectableValue.mutable(linearLayoutParams.weight)) params.put("weight", InspectableValue.Number(linearLayoutParams.weight, mutable = true))
params.put("gravity", GravityMapping.toPicker(linearLayoutParams.gravity)) params.put(
"gravity", GravityMapping.toInspectable(linearLayoutParams.gravity, mutable = true))
} }
return params return InspectableObject(params)
} }
fun getTags(node: View): MutableMap<String, Any?> { fun getTags(node: View): MutableMap<String, Inspectable> {
val tags = mutableMapOf<String, Any?>() val tags = mutableMapOf<String, Inspectable>()
KeyedTagsField?.let { field -> KeyedTagsField?.let { field ->
val keyedTags = field[node] as SparseArray<*> val keyedTags = field.get(node) as SparseArray<*>?
if (keyedTags != null) { if (keyedTags != null) {
var i = 0 var i = 0
val count = keyedTags.size() val count = keyedTags.size()
while (i < count) { while (i < count) {
val id = val id =
ResourcesUtil.getIdStringQuietly(node.context, node.resources, keyedTags.keyAt(i)) ResourcesUtil.getIdStringQuietly(node.context, node.resources, keyedTags.keyAt(i))
tags.put(id, keyedTags.valueAt(i)) keyedTags
.valueAt(i)
?.let { InspectableValue.fromAny(it, false) }
?.let { tags.put(id, it) }
i++ i++
} }
} }
@@ -155,67 +169,75 @@ class ViewDescriptor : AbstractChainedDescriptor<View>() {
return tags return tags
} }
private val LayoutParamsMapping: EnumMapping<Int> =
object :
EnumMapping<Int>(
mapOf(
"WRAP_CONTENT" to ViewGroup.LayoutParams.WRAP_CONTENT,
"MATCH_PARENT" to ViewGroup.LayoutParams.MATCH_PARENT,
"FILL_PARENT" to ViewGroup.LayoutParams.FILL_PARENT,
)) {}
private val VisibilityMapping: EnumMapping<Int> = private val VisibilityMapping: EnumMapping<Int> =
object : EnumMapping<Int>("VISIBLE") { object :
init { EnumMapping<Int>(
put("VISIBLE", View.VISIBLE) mapOf(
put("INVISIBLE", View.INVISIBLE) "VISIBLE" to View.VISIBLE,
put("GONE", View.GONE) "INVISIBLE" to View.INVISIBLE,
} "GONE" to View.GONE,
} )) {}
private val LayoutDirectionMapping: EnumMapping<Int> = private val LayoutDirectionMapping: EnumMapping<Int> =
object : EnumMapping<Int>("LAYOUT_DIRECTION_INHERIT") { object :
init { EnumMapping<Int>(
put("LAYOUT_DIRECTION_INHERIT", View.LAYOUT_DIRECTION_INHERIT) mapOf(
put("LAYOUT_DIRECTION_LOCALE", View.LAYOUT_DIRECTION_LOCALE) "LAYOUT_DIRECTION_INHERIT" to View.LAYOUT_DIRECTION_INHERIT,
put("LAYOUT_DIRECTION_LTR", View.LAYOUT_DIRECTION_LTR) "LAYOUT_DIRECTION_LOCALE" to View.LAYOUT_DIRECTION_LOCALE,
put("LAYOUT_DIRECTION_RTL", View.LAYOUT_DIRECTION_RTL) "LAYOUT_DIRECTION_LTR" to View.LAYOUT_DIRECTION_LTR,
} "LAYOUT_DIRECTION_RTL" to View.LAYOUT_DIRECTION_RTL,
} )) {}
private val TextDirectionMapping: EnumMapping<Int> = private val TextDirectionMapping: EnumMapping<Int> =
object : EnumMapping<Int>("TEXT_DIRECTION_INHERIT") { object :
init { EnumMapping<Int>(
put("TEXT_DIRECTION_INHERIT", View.TEXT_DIRECTION_INHERIT) mapOf(
put("TEXT_DIRECTION_FIRST_STRONG", View.TEXT_DIRECTION_FIRST_STRONG) "TEXT_DIRECTION_INHERIT" to View.TEXT_DIRECTION_INHERIT,
put("TEXT_DIRECTION_ANY_RTL", View.TEXT_DIRECTION_ANY_RTL) "TEXT_DIRECTION_FIRST_STRONG" to View.TEXT_DIRECTION_FIRST_STRONG,
put("TEXT_DIRECTION_LTR", View.TEXT_DIRECTION_LTR) "TEXT_DIRECTION_ANY_RTL" to View.TEXT_DIRECTION_ANY_RTL,
put("TEXT_DIRECTION_RTL", View.TEXT_DIRECTION_RTL) "TEXT_DIRECTION_LTR" to View.TEXT_DIRECTION_LTR,
put("TEXT_DIRECTION_LOCALE", View.TEXT_DIRECTION_LOCALE) "TEXT_DIRECTION_RTL" to View.TEXT_DIRECTION_RTL,
put("TEXT_DIRECTION_FIRST_STRONG_LTR", View.TEXT_DIRECTION_FIRST_STRONG_LTR) "TEXT_DIRECTION_LOCALE" to View.TEXT_DIRECTION_LOCALE,
put("TEXT_DIRECTION_FIRST_STRONG_RTL", View.TEXT_DIRECTION_FIRST_STRONG_RTL) "TEXT_DIRECTION_FIRST_STRONG_LTR" to View.TEXT_DIRECTION_FIRST_STRONG_LTR,
} "TEXT_DIRECTION_FIRST_STRONG_RTL" to View.TEXT_DIRECTION_FIRST_STRONG_RTL,
} )) {}
private val TextAlignmentMapping: EnumMapping<Int> = private val TextAlignmentMapping: EnumMapping<Int> =
object : EnumMapping<Int>("TEXT_ALIGNMENT_INHERIT") { object :
init { EnumMapping<Int>(
put("TEXT_ALIGNMENT_INHERIT", View.TEXT_ALIGNMENT_INHERIT) mapOf(
put("TEXT_ALIGNMENT_GRAVITY", View.TEXT_ALIGNMENT_GRAVITY) "TEXT_ALIGNMENT_INHERIT" to View.TEXT_ALIGNMENT_INHERIT,
put("TEXT_ALIGNMENT_TEXT_START", View.TEXT_ALIGNMENT_TEXT_START) "TEXT_ALIGNMENT_GRAVITY" to View.TEXT_ALIGNMENT_GRAVITY,
put("TEXT_ALIGNMENT_TEXT_END", View.TEXT_ALIGNMENT_TEXT_END) "TEXT_ALIGNMENT_TEXT_START" to View.TEXT_ALIGNMENT_TEXT_START,
put("TEXT_ALIGNMENT_CENTER", View.TEXT_ALIGNMENT_CENTER) "TEXT_ALIGNMENT_TEXT_END" to View.TEXT_ALIGNMENT_TEXT_END,
put("TEXT_ALIGNMENT_VIEW_START", View.TEXT_ALIGNMENT_VIEW_START) "TEXT_ALIGNMENT_CENTER" to View.TEXT_ALIGNMENT_CENTER,
put("TEXT_ALIGNMENT_VIEW_END", View.TEXT_ALIGNMENT_VIEW_END) "TEXT_ALIGNMENT_VIEW_START" to View.TEXT_ALIGNMENT_VIEW_START,
} "TEXT_ALIGNMENT_VIEW_END" to View.TEXT_ALIGNMENT_VIEW_END,
} )) {}
private val GravityMapping: EnumMapping<Int> = private val GravityMapping =
object : EnumMapping<Int>("NO_GRAVITY") { object :
init { EnumMapping<Int>(
put("NO_GRAVITY", Gravity.NO_GRAVITY) mapOf(
put("LEFT", Gravity.LEFT) "NO_GRAVITY" to Gravity.NO_GRAVITY,
put("TOP", Gravity.TOP) "LEFT" to Gravity.LEFT,
put("RIGHT", Gravity.RIGHT) "TOP" to Gravity.TOP,
put("BOTTOM", Gravity.BOTTOM) "RIGHT" to Gravity.RIGHT,
put("CENTER", Gravity.CENTER) "BOTTOM" to Gravity.BOTTOM,
put("CENTER_VERTICAL", Gravity.CENTER_VERTICAL) "CENTER" to Gravity.CENTER,
put("FILL_VERTICAL", Gravity.FILL_VERTICAL) "CENTER_VERTICAL" to Gravity.CENTER_VERTICAL,
put("CENTER_HORIZONTAL", Gravity.CENTER_HORIZONTAL) "FILL_VERTICAL" to Gravity.FILL_VERTICAL,
put("FILL_HORIZONTAL", Gravity.FILL_HORIZONTAL) "CENTER_HORIZONTAL" to Gravity.CENTER_HORIZONTAL,
} "FILL_HORIZONTAL" to Gravity.FILL_HORIZONTAL,
} )) {}
companion object { companion object {
private var KeyedTagsField: Field? = null private var KeyedTagsField: Field? = null

View File

@@ -9,15 +9,16 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
import android.app.Fragment import android.app.Fragment
import android.os.Build import android.os.Build
import android.util.Log
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.ViewGroupCompat import androidx.core.view.ViewGroupCompat
import com.facebook.flipper.plugins.uidebugger.common.EnumMapping
import com.facebook.flipper.plugins.uidebugger.common.Inspectable
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
import com.facebook.flipper.plugins.uidebugger.common.InspectableValue import com.facebook.flipper.plugins.uidebugger.common.InspectableValue
import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat
class ViewGroupDescriptor : AbstractChainedDescriptor<ViewGroup>() { class ViewGroupDescriptor : AbstractChainedDescriptor<ViewGroup>() {
override fun init() {}
override fun onGetId(viewGroup: ViewGroup): String { override fun onGetId(viewGroup: ViewGroup): String {
return Integer.toString(System.identityHashCode(viewGroup)) return Integer.toString(System.identityHashCode(viewGroup))
@@ -38,31 +39,36 @@ class ViewGroupDescriptor : AbstractChainedDescriptor<ViewGroup>() {
} }
} }
override fun onGetData(viewGroup: ViewGroup, builder: MutableMap<String, Any?>) { override fun onGetData(
Log.d("FLIPPER_LAYOUT", "[viewgroup] onGetData") viewGroup: ViewGroup,
val groupBuilder = mutableMapOf<String, Any?>() attributeSections: MutableMap<String, InspectableObject>
) {
val viewGroupAttrs = mutableMapOf<String, Inspectable>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
groupBuilder.put( viewGroupAttrs.put(
"LayoutMode", "LayoutMode", LayoutModeMapping.toInspectable(viewGroup.getLayoutMode(), true))
InspectableValue.mutable( viewGroupAttrs.put(
InspectableValue.Type.Enum,
if (viewGroup.getLayoutMode() == ViewGroupCompat.LAYOUT_MODE_CLIP_BOUNDS)
"LAYOUT_MODE_CLIP_BOUNDS"
else "LAYOUT_MODE_OPTICAL_BOUNDS"))
groupBuilder.put(
"ClipChildren", "ClipChildren",
InspectableValue.mutable(InspectableValue.Type.Boolean, viewGroup.getClipChildren())) InspectableValue.Boolean(viewGroup.getClipChildren(), true),
)
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
groupBuilder.put( viewGroupAttrs.put(
"ClipToPadding", "ClipToPadding", InspectableValue.Boolean(viewGroup.getClipToPadding(), true))
InspectableValue.mutable(InspectableValue.Type.Boolean, viewGroup.getClipToPadding()))
} }
builder.put("ViewGroup", groupBuilder) attributeSections.put("ViewGroup", InspectableObject(viewGroupAttrs))
} }
private val LayoutModeMapping: EnumMapping<Int> =
object :
EnumMapping<Int>(
mapOf(
"LAYOUT_MODE_CLIP_BOUNDS" to ViewGroupCompat.LAYOUT_MODE_CLIP_BOUNDS,
"LAYOUT_MODE_OPTICAL_BOUNDS" to ViewGroupCompat.LAYOUT_MODE_OPTICAL_BOUNDS,
)) {}
companion object { companion object {
private fun getAttachedFragmentForView(v: View): Any? { private fun getAttachedFragmentForView(v: View): Any? {
return try { return try {

View File

@@ -8,9 +8,9 @@
package com.facebook.flipper.plugins.uidebugger.descriptors package com.facebook.flipper.plugins.uidebugger.descriptors
import android.view.Window import android.view.Window
import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
class WindowDescriptor : AbstractChainedDescriptor<Window>() { class WindowDescriptor : AbstractChainedDescriptor<Window>() {
override fun init() {}
override fun onGetId(window: Window): String { override fun onGetId(window: Window): String {
return Integer.toString(System.identityHashCode(window)) return Integer.toString(System.identityHashCode(window))
@@ -24,5 +24,8 @@ class WindowDescriptor : AbstractChainedDescriptor<Window>() {
children.add(window.decorView) children.add(window.decorView)
} }
override fun onGetData(window: Window, builder: MutableMap<String, Any?>) {} override fun onGetData(
window: Window,
attributeSections: MutableMap<String, InspectableObject>
) {}
} }

View File

@@ -8,9 +8,9 @@
package com.facebook.flipper.plugins.uidebugger package com.facebook.flipper.plugins.uidebugger
import android.view.View import android.view.View
import com.facebook.flipper.plugins.uidebugger.common.EnumData
import com.facebook.flipper.plugins.uidebugger.common.EnumMapping import com.facebook.flipper.plugins.uidebugger.common.EnumMapping
import com.facebook.flipper.plugins.uidebugger.common.InspectableValue import com.facebook.flipper.plugins.uidebugger.common.InspectableValue
import org.hamcrest.CoreMatchers
import org.hamcrest.CoreMatchers.* import org.hamcrest.CoreMatchers.*
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test import org.junit.Test
@@ -19,50 +19,29 @@ import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
class EnumMappingTest { class EnumMappingTest {
@Throws(Exception::class)
val visibility: EnumMapping<Int> =
object :
EnumMapping<Int>(
mapOf(
"VISIBLE" to View.VISIBLE, "INVISIBLE" to View.INVISIBLE, "GONE" to View.GONE)) {}
@Test @Test
fun emptyMapping() { fun testTurnsEnumToString() {
val e: EnumMapping<Int> = object : EnumMapping<Int>("k") {} assertThat(visibility.getEnumValue("VISIBLE"), equalTo(View.VISIBLE))
assertThat(e.get("j"), CoreMatchers.`is`(nullValue()))
var inspectable = e.get(0)
assertThat(inspectable.mutable, equalTo(true))
assertThat(inspectable.type, equalTo(InspectableValue.Type.Enum))
assertThat(inspectable.value, equalTo("k"))
inspectable = e.get(0, true)
assertThat(inspectable.mutable, equalTo(true))
assertThat(inspectable.type, equalTo(InspectableValue.Type.Enum))
assertThat(inspectable.value, equalTo("k"))
inspectable = e.get(0, false)
assertThat(inspectable.mutable, equalTo(false))
assertThat(inspectable.type, equalTo(InspectableValue.Type.Enum))
assertThat(inspectable.value, equalTo("k"))
var picker = e.toPicker()
assertThat(picker.mutable, equalTo(true))
assertThat(picker.type, equalTo(InspectableValue.Type.Picker))
assertThat(picker.value, CoreMatchers.`is`(notNullValue()))
val value: InspectableValue.Picker = picker.value as InspectableValue.Picker
assertThat(value.selected, equalTo("k"))
assertThat(value.values.size, equalTo(0))
} }
@Throws(Exception::class)
@Test @Test
fun putGet() { fun testTurnsStringToEnum() {
val visibility: EnumMapping<Int> = assertThat(visibility.getStringRepresentation(View.VISIBLE), equalTo("VISIBLE"))
object : EnumMapping<Int>("VISIBLE") { }
init {
put("VISIBLE", View.VISIBLE)
put("INVISIBLE", View.INVISIBLE)
put("GONE", View.GONE)
}
}
assertThat(visibility.get("VISIBLE"), equalTo(View.VISIBLE)) @Test
fun testTurnsIntoEnumInspectable() {
assertThat(
visibility.toInspectable(View.GONE, true),
equalTo(
InspectableValue.Enum(
EnumData(setOf("VISIBLE", "INVISIBLE", "GONE"), "GONE"), mutable = true)))
} }
} }