[go: nahoru, domu]

blob: 107854354c9a25f5b94002fb7f4ffad4ac6bffa9 [file] [log] [blame]
import androidx.ui.VoidCallback
import androidx.ui.bindings.EnginePhase
import androidx.ui.engine.geometry.Rect
import androidx.ui.engine.text.TextDirection
import androidx.ui.foundation.diagnostics.DiagnosticLevel
import androidx.ui.rendering.box.RenderBox
import androidx.ui.rendering.layout
import androidx.ui.rendering.obj.RenderObject
import androidx.ui.rendering.proxybox.RenderProxyBox
import androidx.ui.rendering.pumpFrame
import androidx.ui.rendering.renderer
import androidx.ui.semantics.DebugSemanticsDumpOrder
import androidx.ui.semantics.MoveCursorHandler
import androidx.ui.semantics.OrdinalSortKey
import androidx.ui.semantics.SemanticsAction
import androidx.ui.semantics.SemanticsConfiguration
import androidx.ui.semantics.SemanticsNode
import androidx.ui.semantics.SemanticsSortKey
import androidx.ui.semantics.SemanticsTag
import androidx.ui.semantics.debugResetSemanticsIdCounter
import androidx.ui.test.util.assertThrows
import androidx.ui.test.util.normalizeHashCodes
import androidx.ui.vectormath64.Matrix4
import androidx.ui.vectormath64.Vector3
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class SemanticsNodeTests {
val tag1: SemanticsTag = SemanticsTag("Tag One")
val tag2: SemanticsTag = SemanticsTag("Tag Two")
val tag3: SemanticsTag = SemanticsTag("Tag Three")
@Before
fun setUp() {
debugResetSemanticsIdCounter()
}
@Test
fun tagging() {
val node = SemanticsNode()
assertThat(node.isTagged(tag1)).isFalse()
assertThat(node.isTagged(tag2)).isFalse()
node.tags = setOf(tag1)
assertThat(node.isTagged(tag1)).isTrue()
assertThat(node.isTagged(tag2)).isFalse()
node.tags = setOf(tag1, tag2)
assertThat(node.isTagged(tag1)).isTrue()
assertThat(node.isTagged(tag2)).isTrue()
}
@Test
fun `getSemanticsData includes tags`() {
val tags = mutableSetOf(tag1, tag2)
val node: SemanticsNode = SemanticsNode().also {
it.rect = Rect.fromLTRB(0.0f, 0.0f, 10.0f, 10.0f)
it.tags = tags
}
assertThat(tags).isEqualTo(node.getSemanticsData().tags)
tags.add(tag3)
val config: SemanticsConfiguration = SemanticsConfiguration().also {
it.isSemanticBoundary = true
it.isMergingSemanticsOfDescendants = true
}
node.updateWith(
config = config,
childrenInInversePaintOrder = listOf(
SemanticsNode().also {
it.isMergedIntoParent = true
it.rect = Rect.fromLTRB(5.0f, 5.0f, 10.0f, 10.0f)
it.tags = tags
}
)
)
assertThat(tags).isEqualTo(node.getSemanticsData().tags)
}
/**
* after markNeedsSemanticsUpdate() all render objects between two semantic boundaries are
* asked for annotations
*/
@Test
@Ignore("TODO(Migration/ryanmentley): Needs native code implemented")
fun `after markNeedsSemanticsUpdate() queries annotations for objects between boundaries`() {
renderer.pipelineOwner!!.ensureSemantics()
var middle: TestRender? = null
val root = TestRender(
hasTapAction = true,
isSemanticBoundary = true,
child = TestRender(
hasLongPressAction = true,
isSemanticBoundary = false,
child = TestRender(
hasScrollLeftAction = true,
isSemanticBoundary = false,
child = TestRender(
hasScrollRightAction = true,
isSemanticBoundary = false,
child = TestRender(
hasScrollUpAction = true,
isSemanticBoundary = true
)
)
).also { middle = it }
)
)
layout(root)
pumpFrame(phase = EnginePhase.flushSemantics)
var expectedActions = SemanticsAction.tap.index or
SemanticsAction.longPress.index or
SemanticsAction.scrollLeft.index or
SemanticsAction.scrollRight.index
assertThat(expectedActions).isEqualTo(root.debugSemantics!!.getSemanticsData().actions)
// Guaranteed non-null by the also block above
middle!!.apply {
hasScrollLeftAction = false
hasScrollDownAction = true
}
middle!!.markNeedsSemanticsUpdate()
pumpFrame(phase = EnginePhase.flushSemantics)
expectedActions = SemanticsAction.tap.index or
SemanticsAction.longPress.index or
SemanticsAction.scrollDown.index or
SemanticsAction.scrollRight.index
assertThat(expectedActions).isEqualTo(root.debugSemantics!!.getSemanticsData().actions)
}
@Test
fun `toStringDeep() does not throw with transform == null`() {
val child1: SemanticsNode = SemanticsNode().also {
it.rect = Rect.fromLTRB(0.0f, 0.0f, 5.0f, 5.0f)
}
val child2: SemanticsNode = SemanticsNode().also {
it.rect = Rect.fromLTRB(5.0f, 0.0f, 10.0f, 5.0f)
}
val root: SemanticsNode = SemanticsNode().also {
it.rect = Rect.fromLTRB(0.0f, 0.0f, 10.0f, 5.0f)
}
root.updateWith(
config = null,
childrenInInversePaintOrder = listOf(child1, child2)
)
assertThat(root.transform).isNull()
assertThat(child1.transform).isNull()
assertThat(child2.transform).isNull()
assertThat(root.toStringDeep(childOrder = DebugSemanticsDumpOrder.traversalOrder))
.isEqualTo(
"SemanticsNode#3\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(0.0, 0.0, 10.0, 5.0)\n" +
" │\n" +
" ├─SemanticsNode#1\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(0.0, 0.0, 5.0, 5.0)\n" +
" │\n" +
" └─SemanticsNode#2\n" +
" STALE\n" +
" owner: null\n" +
" Rect.fromLTRB(5.0, 0.0, 10.0, 5.0)\n"
)
}
@Test
fun `Incompatible OrdinalSortKey throw AssertionError when compared`() {
// Different types.
assertThrows(AssertionError::class) {
OrdinalSortKey(0.0f).compareTo(CustomSortKey(0.0f))
}
// Different names.
assertThrows(AssertionError::class) {
OrdinalSortKey(0.0f, name = "a").compareTo(OrdinalSortKey(0.0f, name = "b"))
}
}
@Test
fun `OrdinalSortKey compares correctly`() {
val tests: List<List<SemanticsSortKey>> = listOf(
listOf(OrdinalSortKey(0.0f), OrdinalSortKey(0.0f)),
listOf(OrdinalSortKey(0.0f), OrdinalSortKey(1.0f)),
listOf(OrdinalSortKey(1.0f), OrdinalSortKey(0.0f)),
listOf(OrdinalSortKey(1.0f), OrdinalSortKey(1.0f))
)
val expectedResults: List<Int> = listOf(0, -1, 1, 0)
val results: MutableList<Int> = mutableListOf()
for (tuple in tests) {
results.add(tuple[0].compareTo(tuple[1]))
}
assertThat(results).containsExactlyElementsIn(expectedResults)
}
@Test
fun `toStringDeep respects childOrder parameter`() {
val child1: SemanticsNode = SemanticsNode().also {
it.rect = Rect.fromLTRB(15.0f, 0.0f, 20.0f, 5.0f)
}
val child2: SemanticsNode = SemanticsNode().also {
it.rect = Rect.fromLTRB(10.0f, 0.0f, 15.0f, 5.0f)
}
val root: SemanticsNode = SemanticsNode().also {
it.rect = Rect.fromLTRB(0.0f, 0.0f, 20.0f, 5.0f)
}
root.updateWith(
config = null,
childrenInInversePaintOrder = listOf(child1, child2)
)
assertThat(
root.toStringDeep(childOrder = DebugSemanticsDumpOrder.traversalOrder)
).isEqualTo(
"SemanticsNode#3\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(0.0, 0.0, 20.0, 5.0)\n" +
" │\n" +
" ├─SemanticsNode#1\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n" +
" │\n" +
" └─SemanticsNode#2\n" +
" STALE\n" +
" owner: null\n" +
" Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n"
)
assertThat(
root.toStringDeep(childOrder = DebugSemanticsDumpOrder.inverseHitTest)
).isEqualTo(
"SemanticsNode#3\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(0.0, 0.0, 20.0, 5.0)\n" +
" │\n" +
" ├─SemanticsNode#1\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n" +
" │\n" +
" └─SemanticsNode#2\n" +
" STALE\n" +
" owner: null\n" +
" Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n"
)
val child3: SemanticsNode = SemanticsNode().also {
it.rect = Rect.fromLTRB(0.0f, 0.0f, 10.0f, 5.0f)
}
child3.updateWith(
config = null,
childrenInInversePaintOrder = listOf(
SemanticsNode().also {
it.rect = Rect.fromLTRB(5.0f, 0.0f, 10.0f, 5.0f)
},
SemanticsNode().also {
it.rect = Rect.fromLTRB(0.0f, 0.0f, 5.0f, 5.0f)
}
)
)
val rootComplex: SemanticsNode = SemanticsNode().also {
it.rect = Rect.fromLTRB(0.0f, 0.0f, 25.0f, 5.0f)
}
rootComplex.updateWith(
config = null,
childrenInInversePaintOrder = listOf(child1, child2, child3)
)
assertThat(
rootComplex.toStringDeep(childOrder = DebugSemanticsDumpOrder.traversalOrder)
).isEqualTo(
"SemanticsNode#7\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(0.0, 0.0, 25.0, 5.0)\n" +
" │\n" +
" ├─SemanticsNode#1\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n" +
" │\n" +
" ├─SemanticsNode#2\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n" +
" │\n" +
" └─SemanticsNode#4\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(0.0, 0.0, 10.0, 5.0)\n" +
" │\n" +
" ├─SemanticsNode#5\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(5.0, 0.0, 10.0, 5.0)\n" +
" │\n" +
" └─SemanticsNode#6\n" +
" STALE\n" +
" owner: null\n" +
" Rect.fromLTRB(0.0, 0.0, 5.0, 5.0)\n"
)
assertThat(
rootComplex.toStringDeep(childOrder = DebugSemanticsDumpOrder.inverseHitTest)
).isEqualTo(
"SemanticsNode#7\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(0.0, 0.0, 25.0, 5.0)\n" +
" │\n" +
" ├─SemanticsNode#1\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(15.0, 0.0, 20.0, 5.0)\n" +
" │\n" +
" ├─SemanticsNode#2\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(10.0, 0.0, 15.0, 5.0)\n" +
" │\n" +
" └─SemanticsNode#4\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(0.0, 0.0, 10.0, 5.0)\n" +
" │\n" +
" ├─SemanticsNode#5\n" +
" │ STALE\n" +
" │ owner: null\n" +
" │ Rect.fromLTRB(5.0, 0.0, 10.0, 5.0)\n" +
" │\n" +
" └─SemanticsNode#6\n" +
" STALE\n" +
" owner: null\n" +
" Rect.fromLTRB(0.0, 0.0, 5.0, 5.0)\n"
)
}
@Test
fun `debug properties`() {
val minimalProperties: SemanticsNode = SemanticsNode()
assertThat(
minimalProperties.toStringDeep()
).isEqualTo(
"SemanticsNode#1\n" +
" Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)\n" +
" invisible\n"
)
assertThat(
minimalProperties.toStringDeep(minLevel = DiagnosticLevel.hidden)
).isEqualTo(
"SemanticsNode#1\n" +
" owner: null\n" +
" isMergedIntoParent: false\n" +
" mergeAllDescendantsIntoThisNode: false\n" +
" Rect.fromLTRB(0.0, 0.0, 0.0, 0.0)\n" +
" actions: []\n" +
" flags: []\n" +
" invisible\n" +
" isHidden: false\n" +
" label: \"\"\n" +
" value: \"\"\n" +
" increasedValue: \"\"\n" +
" decreasedValue: \"\"\n" +
" hint: \"\"\n" +
" textDirection: null\n" +
" sortKey: null\n" +
" scrollExtentMin: null\n" +
" scrollPosition: null\n" +
" scrollExtentMax: null\n"
)
val config: SemanticsConfiguration = SemanticsConfiguration().also {
it.isSemanticBoundary = true
it.isMergingSemanticsOfDescendants = true
it.onScrollUp = { }
it.onLongPress = { }
it.onShowOnScreen = { }
it.isChecked = false
it.isSelected = true
it.isButton = true
it.label = "Use all the properties"
it.textDirection = TextDirection.RTL
it.sortKey = OrdinalSortKey(1.0f)
}
val allProperties: SemanticsNode = SemanticsNode().also {
it.rect = Rect.fromLTWH(50.0f, 10.0f, 20.0f, 30.0f)
it.transform = Matrix4.translation(Vector3(10.0f, 10.0f, 0.0f))
it.updateWith(config = config, childrenInInversePaintOrder = null)
}
assertThat(
allProperties.toStringDeep().normalizeHashCodes()
).isEqualTo(
("SemanticsNode#2\n" +
" STALE\n" +
" owner: null\n" +
" merge boundary ⛔️\n" +
" Rect.fromLTRB(60.0, 20.0, 80.0, 50.0)\n" +
" actions: longPress, scrollUp, showOnScreen\n" +
" flags: hasCheckedState, isSelected, isButton\n" +
" label: \"Use all the properties\"\n" +
" textDirection: RTL\n" +
" sortKey: OrdinalSortKey#19df5(order: 1.0)\n").normalizeHashCodes()
)
assertThat(
allProperties.getSemanticsData().toString()
).isEqualTo(
"SemanticsData(Rect.fromLTRB(50.0, 10.0, 70.0, 40.0), " +
"[1.0,0.0,0.0,10.0; 0.0,1.0,0.0,10.0; 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0], " +
"actions: [longPress, scrollUp, showOnScreen], " +
"flags: [hasCheckedState, isSelected, isButton], " +
"label: \"Use all the properties\", textDirection: RTL)"
)
val scaled: SemanticsNode = SemanticsNode().also {
it.rect = Rect.fromLTWH(50.0f, 10.0f, 20.0f, 30.0f)
it.transform = Matrix4.diagonal3(Vector3(10.0f, 10.0f, 1.0f))
}
assertThat(
scaled.toStringDeep()
).isEqualTo(
"SemanticsNode#3\n" +
" STALE\n" +
" owner: null\n" +
" Rect.fromLTRB(50.0, 10.0, 70.0, 40.0) scaled by 10.0x\n"
)
assertThat(
scaled.getSemanticsData().toString()
).isEqualTo(
"SemanticsData(Rect.fromLTRB(50.0, 10.0, 70.0, 40.0), " +
"[10.0,0.0,0.0,0.0; 0.0,10.0,0.0,0.0; 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0])"
)
}
@Test
fun `SemanticsConfiguration getter and setter`() {
val config = SemanticsConfiguration()
assertThat(config.isSemanticBoundary).isFalse()
assertThat(config.isButton).isFalse()
assertThat(config.isMergingSemanticsOfDescendants).isFalse()
assertThat(config.isEnabled).isNull()
assertThat(config.isChecked).isNull()
assertThat(config.isSelected).isFalse()
assertThat(config.isBlockingSemanticsOfPreviouslyPaintedNodes).isFalse()
assertThat(config.isFocused).isFalse()
assertThat(config.isTextField).isFalse()
assertThat(config.onShowOnScreen).isNull()
assertThat(config.onScrollDown).isNull()
assertThat(config.onScrollUp).isNull()
assertThat(config.onScrollLeft).isNull()
assertThat(config.onScrollRight).isNull()
assertThat(config.onLongPress).isNull()
assertThat(config.onDecrease).isNull()
assertThat(config.onIncrease).isNull()
assertThat(config.onMoveCursorForwardByCharacter).isNull()
assertThat(config.onMoveCursorBackwardByCharacter).isNull()
assertThat(config.onTap).isNull()
config.isSemanticBoundary = true
config.isButton = true
config.isMergingSemanticsOfDescendants = true
config.isEnabled = true
config.isChecked = true
config.isSelected = true
config.isBlockingSemanticsOfPreviouslyPaintedNodes = true
config.isFocused = true
config.isTextField = true
val onShowOnScreen: VoidCallback = { }
val onScrollDown: VoidCallback = { }
val onScrollUp: VoidCallback = { }
val onScrollLeft: VoidCallback = { }
val onScrollRight: VoidCallback = { }
val onLongPress: VoidCallback = { }
val onDecrease: VoidCallback = { }
val onIncrease: VoidCallback = { }
val onMoveCursorForwardByCharacter: MoveCursorHandler = { }
val onMoveCursorBackwardByCharacter: MoveCursorHandler = { }
val onTap: VoidCallback = { }
config.onShowOnScreen = onShowOnScreen
config.onScrollDown = onScrollDown
config.onScrollUp = onScrollUp
config.onScrollLeft = onScrollLeft
config.onScrollRight = onScrollRight
config.onLongPress = onLongPress
config.onDecrease = onDecrease
config.onIncrease = onIncrease
config.onMoveCursorForwardByCharacter = onMoveCursorForwardByCharacter
config.onMoveCursorBackwardByCharacter = onMoveCursorBackwardByCharacter
config.onTap = onTap
assertThat(config.isSemanticBoundary).isTrue()
assertThat(config.isButton).isTrue()
assertThat(config.isMergingSemanticsOfDescendants).isTrue()
assertThat(config.isEnabled).isTrue()
assertThat(config.isChecked).isTrue()
assertThat(config.isSelected).isTrue()
assertThat(config.isBlockingSemanticsOfPreviouslyPaintedNodes).isTrue()
assertThat(config.isFocused).isTrue()
assertThat(config.isTextField).isTrue()
assertThat(onShowOnScreen).isSameAs(config.onShowOnScreen)
assertThat(onScrollDown).isSameAs(config.onScrollDown)
assertThat(onScrollUp).isSameAs(config.onScrollUp)
assertThat(onScrollLeft).isSameAs(config.onScrollLeft)
assertThat(onScrollRight).isSameAs(config.onScrollRight)
assertThat(onLongPress).isSameAs(config.onLongPress)
assertThat(onDecrease).isSameAs(config.onDecrease)
assertThat(onIncrease).isSameAs(config.onIncrease)
assertThat(onMoveCursorForwardByCharacter).isSameAs(config.onMoveCursorForwardByCharacter)
assertThat(onMoveCursorBackwardByCharacter).isSameAs(config.onMoveCursorBackwardByCharacter)
assertThat(onTap).isSameAs(config.onTap)
}
}
private class TestRender(
var hasTapAction: Boolean = false,
var hasLongPressAction: Boolean = false,
var hasScrollLeftAction: Boolean = false,
var hasScrollRightAction: Boolean = false,
var hasScrollUpAction: Boolean = false,
var hasScrollDownAction: Boolean = false,
var isSemanticBoundary: Boolean,
// TODO(Migration/ryanmentley): How does this compile in Dart?!
child: RenderObject? = null
) : RenderProxyBox(child as RenderBox?) {
override fun describeSemanticsConfiguration(config: SemanticsConfiguration) {
super.describeSemanticsConfiguration(config)
config.isSemanticBoundary = isSemanticBoundary
if (hasTapAction) {
config.onTap = { }
}
if (hasLongPressAction) {
config.onLongPress = { }
}
if (hasScrollLeftAction) {
config.onScrollLeft = { }
}
if (hasScrollRightAction) {
config.onScrollRight = { }
}
if (hasScrollUpAction) {
config.onScrollUp = { }
}
if (hasScrollDownAction) {
config.onScrollDown = { }
}
}
}
private class CustomSortKey(
order: Float,
name: String? = null
) : OrdinalSortKey(order, name)