Merge "Fix GestureScope position calculations" into androidx-master-dev
diff --git a/compose/desktop/desktop/build.gradle b/compose/desktop/desktop/build.gradle
index f002278..aa33496 100644
--- a/compose/desktop/desktop/build.gradle
+++ b/compose/desktop/desktop/build.gradle
@@ -49,9 +49,6 @@
api(SKIKO)
implementation(KOTLIN_COROUTINES_SWING)
-
- // TODO: move to jvmTest
- implementation(JUNIT)
}
jvmTest {
@@ -59,9 +56,15 @@
resources.srcDirs += "src/jvmTest/res"
dependencies {
implementation(SKIKO_CURRENT_OS)
+ implementation project(":ui:ui-test")
+ implementation(JUNIT)
implementation(TRUTH)
}
}
+
+ test.dependencies {
+ implementation project(":ui:ui-test")
+ }
}
}
diff --git a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/desktop/InputsTest.kt b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/desktop/InputsTest.kt
new file mode 100644
index 0000000..c6e27ea
--- /dev/null
+++ b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/desktop/InputsTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.desktop
+
+import androidx.compose.material.Slider
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.ui.test.assertValueEquals
+import androidx.ui.test.onNodeWithTag
+import androidx.ui.test.runOnIdle
+
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import androidx.ui.test.createComposeRule
+
+@RunWith(JUnit4::class)
+class InputsTest {
+ private val tag = "slider"
+
+ @get:Rule
+ val composeTestRule = createComposeRule(disableTransitions = true)
+
+ @Test
+ fun sliderPosition_valueCoercion() {
+ val state = mutableStateOf(0f)
+ composeTestRule.setContent {
+ Slider(
+ modifier = Modifier.testTag(tag),
+ value = state.value,
+ onValueChange = { state.value = it },
+ valueRange = 0f..1f
+ )
+ }
+ runOnIdle {
+ state.value = 2f
+ }
+ onNodeWithTag(tag).assertValueEquals("100 percent")
+ runOnIdle {
+ state.value = -123145f
+ }
+ onNodeWithTag(tag).assertValueEquals("0 percent")
+ }
+}
\ No newline at end of file
diff --git a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/desktop/ParagraphTest.kt b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/desktop/ParagraphTest.kt
index 4ce0fc1..d64aa8a 100644
--- a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/desktop/ParagraphTest.kt
+++ b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/desktop/ParagraphTest.kt
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package androidx.compose.desktop
+package androidx.ui.desktop
-import androidx.compose.desktop.test.DesktopScreenshotTestRule
-import androidx.compose.desktop.test.TestSkiaWindow
import androidx.compose.foundation.Text
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -37,6 +35,8 @@
import androidx.compose.ui.text.platform.font
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.ui.test.DesktopScreenshotTestRule
+import androidx.ui.test.TestSkiaWindow
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/DesktopGraphicsTest.kt b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/DesktopGraphicsTest.kt
index 1d73970..d1deb20 100644
--- a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/DesktopGraphicsTest.kt
+++ b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/DesktopGraphicsTest.kt
@@ -16,8 +16,7 @@
package androidx.compose.ui.graphics
-import androidx.compose.desktop.initCompose
-import androidx.compose.desktop.test.DesktopScreenshotTestRule
+import androidx.ui.test.DesktopScreenshotTestRule
import org.jetbrains.skija.Surface
import org.junit.After
import org.junit.Rule
@@ -47,10 +46,4 @@
fun teardown() {
_surface?.close()
}
-
- private companion object {
- init {
- initCompose()
- }
- }
}
\ No newline at end of file
diff --git a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt
index a7b97be..fff340a 100644
--- a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt
+++ b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/DesktopPaintTest.kt
@@ -70,7 +70,7 @@
canvas.drawRect(left = 0f, top = 0f, right = 16f, bottom = 16f, paint = redPaint)
canvas.drawImage(
- image = imageFromResource("androidx.compose.desktop/test.png"),
+ image = imageFromResource("androidx/compose/desktop/test.png"),
topLeftOffset = Offset(2f, 4f),
paint = Paint().apply {
colorFilter = ColorFilter(Color.Blue, BlendMode.Plus)
@@ -83,7 +83,7 @@
@Test
fun filterQuality() {
canvas.drawImageRect(
- image = imageFromResource("androidx.compose.desktop/test.png"),
+ image = imageFromResource("androidx/compose/desktop/test.png"),
srcOffset = IntOffset(0, 2),
srcSize = IntSize(2, 4),
dstOffset = IntOffset(0, 4),
@@ -91,7 +91,7 @@
paint = redPaint
)
canvas.drawImageRect(
- image = imageFromResource("androidx.compose.desktop/test.png"),
+ image = imageFromResource("androidx/compose/desktop/test.png"),
srcOffset = IntOffset(0, 2),
srcSize = IntSize(2, 4),
dstOffset = IntOffset(4, 4),
@@ -101,7 +101,7 @@
}
)
canvas.drawImageRect(
- image = imageFromResource("androidx.compose.desktop/test.png"),
+ image = imageFromResource("androidx/compose/desktop/test.png"),
srcOffset = IntOffset(0, 2),
srcSize = IntSize(2, 4),
dstOffset = IntOffset(8, 4),
diff --git a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/canvas/DesktopCanvasTest.kt b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/canvas/DesktopCanvasTest.kt
index 40ec7ffd..789ee32 100644
--- a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/canvas/DesktopCanvasTest.kt
+++ b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/graphics/canvas/DesktopCanvasTest.kt
@@ -92,12 +92,12 @@
@Test
fun drawImage() {
canvas.drawImage(
- image = imageFromResource("androidx.compose.desktop/test.png"),
+ image = imageFromResource("androidx/compose/desktop/test.png"),
topLeftOffset = Offset(2f, 4f),
paint = redPaint
)
canvas.drawImage(
- image = imageFromResource("androidx.compose.desktop/test.png"),
+ image = imageFromResource("androidx/compose/desktop/test.png"),
topLeftOffset = Offset(-2f, 0f),
paint = redPaint
)
@@ -108,7 +108,7 @@
@Test
fun drawImageRect() {
canvas.drawImageRect(
- image = imageFromResource("androidx.compose.desktop/test.png"),
+ image = imageFromResource("androidx/compose/desktop/test.png"),
srcOffset = IntOffset(0, 2),
srcSize = IntSize(2, 4),
dstOffset = IntOffset(0, 4),
diff --git a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/platform/DrawLayerTest.kt b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/platform/DrawLayerTest.kt
index f91c0c2..24debbf 100644
--- a/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/platform/DrawLayerTest.kt
+++ b/compose/desktop/desktop/src/jvmTest/kotlin/androidx/compose/ui/platform/DrawLayerTest.kt
@@ -16,8 +16,6 @@
package androidx.compose.ui.platform
-import androidx.compose.desktop.test.DesktopScreenshotTestRule
-import androidx.compose.desktop.test.TestSkiaWindow
import androidx.compose.foundation.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -26,6 +24,8 @@
import androidx.compose.ui.drawLayer
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import androidx.ui.test.DesktopScreenshotTestRule
+import androidx.ui.test.TestSkiaWindow
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/ConstraintLayoutTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/ConstraintLayoutTest.kt
index dad4601..e60a42d 100644
--- a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/ConstraintLayoutTest.kt
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/ConstraintLayoutTest.kt
@@ -71,7 +71,7 @@
height = Dimension.wrapContent
}
// Try to be large to make wrap content impossible.
- .preferredWidth((composeTestRule.displayMetrics.widthPixels).toDp())
+ .preferredWidth((composeTestRule.displaySize.width).toDp())
// This could be any (width in height out child) e.g. text
.aspectRatio(2f)
.onPositioned { coordinates ->
@@ -93,12 +93,12 @@
runOnIdle {
// The aspect ratio could not wrap and it is wrap suggested, so it respects constraints.
assertEquals(
- (composeTestRule.displayMetrics.widthPixels / 2),
+ (composeTestRule.displaySize.width / 2),
aspectRatioBoxSize.value!!.width
)
// Aspect ratio is preserved.
assertEquals(
- (composeTestRule.displayMetrics.widthPixels / 2 / 2),
+ (composeTestRule.displaySize.width / 2 / 2),
aspectRatioBoxSize.value!!.height
)
// Divider has fixed width 1.dp in constraint set.
@@ -129,7 +129,7 @@
height = Dimension.preferredWrapContent
}
// Try to be large to make wrap content impossible.
- .preferredWidth((composeTestRule.displayMetrics.widthPixels).toDp())
+ .preferredWidth((composeTestRule.displaySize.width).toDp())
// This could be any (width in height out child) e.g. text
.aspectRatio(2f)
.onPositioned { coordinates ->
@@ -151,12 +151,12 @@
runOnIdle {
// The aspect ratio could not wrap and it is wrap suggested, so it respects constraints.
assertEquals(
- (composeTestRule.displayMetrics.widthPixels / 2),
+ (composeTestRule.displaySize.width / 2),
aspectRatioBoxSize.value!!.width
)
// Aspect ratio is preserved.
assertEquals(
- (composeTestRule.displayMetrics.widthPixels / 2 / 2),
+ (composeTestRule.displaySize.width / 2 / 2),
aspectRatioBoxSize.value!!.height
)
// Divider has fixed width 1.dp in constraint set.
@@ -187,7 +187,7 @@
height = Dimension.wrapContent
}
// Try to be large to make wrap content impossible.
- .preferredWidth((composeTestRule.displayMetrics.widthPixels).toDp())
+ .preferredWidth((composeTestRule.displaySize.width).toDp())
// This could be any (width in height out child) e.g. text
.aspectRatio(2f)
.onPositioned { coordinates ->
@@ -210,12 +210,12 @@
runOnIdle {
// The aspect ratio could not wrap and it is wrap suggested, so it respects constraints.
assertEquals(
- (composeTestRule.displayMetrics.widthPixels / 2),
+ (composeTestRule.displaySize.width / 2),
aspectRatioBoxSize.value!!.width
)
// Aspect ratio is preserved.
assertEquals(
- (composeTestRule.displayMetrics.widthPixels / 2 / 2),
+ (composeTestRule.displaySize.width / 2 / 2),
aspectRatioBoxSize.value!!.height
)
// Divider has fixed width 1.dp in constraint set.
@@ -245,7 +245,7 @@
height = Dimension.wrapContent
}
// Try to be large to make wrap content impossible.
- .preferredWidth((composeTestRule.displayMetrics.widthPixels).toDp())
+ .preferredWidth((composeTestRule.displaySize.width).toDp())
// This could be any (width in height out child) e.g. text
.aspectRatio(2f)
.onPositioned { coordinates ->
@@ -268,12 +268,12 @@
runOnIdle {
// The aspect ratio could not wrap and it is wrap suggested, so it respects constraints.
assertEquals(
- (composeTestRule.displayMetrics.widthPixels / 2),
+ (composeTestRule.displaySize.width / 2),
aspectRatioBoxSize.value!!.width
)
// Aspect ratio is preserved.
assertEquals(
- (composeTestRule.displayMetrics.widthPixels / 2 / 2),
+ (composeTestRule.displaySize.width / 2 / 2),
aspectRatioBoxSize.value!!.height
)
// Divider has fixed width 1.dp in constraint set.
@@ -395,8 +395,8 @@
}
}
- val displayWidth = composeTestRule.displayMetrics.widthPixels
- val displayHeight = composeTestRule.displayMetrics.heightPixels
+ val displayWidth = composeTestRule.displaySize.width
+ val displayHeight = composeTestRule.displaySize.height
runOnIdle {
assertEquals(
@@ -463,8 +463,8 @@
}
}
- val displayWidth = composeTestRule.displayMetrics.widthPixels
- val displayHeight = composeTestRule.displayMetrics.heightPixels
+ val displayWidth = composeTestRule.displaySize.width
+ val displayHeight = composeTestRule.displaySize.height
runOnIdle {
assertEquals(
@@ -536,8 +536,8 @@
}
}
- val displayWidth = composeTestRule.displayMetrics.widthPixels
- val displayHeight = composeTestRule.displayMetrics.heightPixels
+ val displayWidth = composeTestRule.displaySize.width
+ val displayHeight = composeTestRule.displaySize.height
runOnIdle {
assertEquals(
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 364462c..da3d897 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -81,6 +81,7 @@
}
desktopTest.dependencies {
+ implementation project(':ui:ui-test')
implementation(TRUTH)
implementation(JUNIT)
implementation(SKIKO_CURRENT_OS)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt
index 829927f..3a7e4dc 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ZoomableTest.kt
@@ -27,8 +27,8 @@
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import androidx.test.filters.SmallTest
-import androidx.ui.test.AnimationClockTestRule
import androidx.ui.test.center
+import androidx.ui.test.createAnimationClockRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.onNodeWithTag
import androidx.ui.test.performGesture
@@ -53,7 +53,7 @@
val composeTestRule = createComposeRule()
@get:Rule
- val clockRule = AnimationClockTestRule()
+ val clockRule = createAnimationClockRule()
@Test
fun zoomable_zoomIn() {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt
index d801956..1fd124c 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ListItemTest.kt
@@ -210,9 +210,9 @@
assertThat(textPosition.value!!.y).isEqualTo(
((listItemHeight.toIntPx() - textSize.value!!.height) / 2f).roundToInt().toFloat()
)
- val dm = composeTestRule.displayMetrics
+ val ds = composeTestRule.displaySize
assertThat(trailingPosition.value!!.x).isEqualTo(
- dm.widthPixels - trailingSize.value!!.width -
+ ds.width - trailingSize.value!!.width -
expectedRightPadding.toIntPx().toFloat()
)
assertThat(trailingPosition.value!!.y).isEqualTo(
@@ -310,9 +310,9 @@
expectedTextBaseline.toIntPx().toFloat() +
expectedSecondaryTextBaselineOffset.toIntPx().toFloat()
)
- val dm = composeTestRule.displayMetrics
+ val ds = composeTestRule.displaySize
assertThat(trailingPosition.value!!.x).isEqualTo(
- dm.widthPixels - trailingSize.value!!.width -
+ ds.width - trailingSize.value!!.width -
expectedRightPadding.toIntPx().toFloat()
)
assertThat(trailingBaseline.value!!).isEqualTo(
@@ -453,9 +453,9 @@
assertThat(iconPosition.value!!.y).isEqualTo(
expectedIconTopPadding.toIntPx().toFloat()
)
- val dm = composeTestRule.displayMetrics
+ val ds = composeTestRule.displaySize
assertThat(trailingPosition.value!!.x).isEqualTo(
- dm.widthPixels - trailingSize.value!!.width -
+ ds.width - trailingSize.value!!.width -
expectedRightPadding.toIntPx().toFloat()
)
assertThat(trailingPosition.value!!.y).isEqualTo(
@@ -529,9 +529,9 @@
assertThat(iconPosition.value!!.y).isEqualTo(
expectedIconTopPadding.toIntPx().toFloat()
)
- val dm = composeTestRule.displayMetrics
+ val ds = composeTestRule.displaySize
assertThat(trailingPosition.value!!.x).isEqualTo(
- dm.widthPixels - trailingSize.value!!.width.toFloat() -
+ ds.width - trailingSize.value!!.width.toFloat() -
expectedRightPadding.toIntPx().toFloat()
)
assertThat(trailingPosition.value!!.y).isEqualTo(
@@ -643,9 +643,9 @@
assertThat(iconPosition.value!!.y).isEqualTo(
expectedIconTopPadding.toIntPx().toFloat()
)
- val dm = composeTestRule.displayMetrics
+ val ds = composeTestRule.displaySize
assertThat(trailingPosition.value!!.x).isEqualTo(
- dm.widthPixels - trailingSize.value!!.width -
+ ds.width - trailingSize.value!!.width -
expectedRightPadding.toIntPx().toFloat()
)
assertThat(trailingBaseline.value!!).isEqualTo(
diff --git a/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/DesktopUiDispatcher.kt b/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/DesktopUiDispatcher.kt
index 04d74ea..e1922fd 100644
--- a/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/DesktopUiDispatcher.kt
+++ b/compose/runtime/runtime-dispatch/src/desktopMain/kotlin/androidx/compose/runtime/dispatch/DesktopUiDispatcher.kt
@@ -43,7 +43,7 @@
private fun scheduleIfNeeded() {
synchronized(lock) {
- if (!scheduled && (callbacks.isNotEmpty())) {
+ if (!scheduled && hasPendingChanges()) {
invokeLater { tick() }
scheduled = true
}
@@ -70,13 +70,20 @@
}
}
- private fun tick() {
- scheduled = false
+ fun hasPendingChanges() = callbacks.isNotEmpty()
+
+ fun runAllCallbacks() {
val now = System.nanoTime()
runCallbacks(now, callbacks)
scheduleIfNeeded()
}
+ private fun tick() {
+ scheduled = false
+ runAllCallbacks()
+ scheduleIfNeeded()
+ }
+
fun scheduleCallback(action: Action) {
synchronized(lock) {
callbacks.add(action)
@@ -125,4 +132,4 @@
Dispatcher + Dispatcher.frameClock
}
}
-}
\ No newline at end of file
+}
diff --git a/compose/test-utils/src/androidAndroidTest/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunnerTest.kt b/compose/test-utils/src/androidAndroidTest/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunnerTest.kt
index ce794b8..c0e7f9a 100644
--- a/compose/test-utils/src/androidAndroidTest/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunnerTest.kt
+++ b/compose/test-utils/src/androidAndroidTest/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunnerTest.kt
@@ -24,7 +24,7 @@
import androidx.compose.runtime.onCommit
import androidx.compose.runtime.remember
import androidx.test.filters.SmallTest
-import androidx.ui.test.android.AndroidComposeTestRule
+import androidx.ui.test.AndroidComposeTestRule
import androidx.ui.test.android.createAndroidComposeRule
import org.junit.Rule
import org.junit.Test
diff --git a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/TestRuleExtensions.kt b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/TestRuleExtensions.kt
index 81d7ced..73aa388 100644
--- a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/TestRuleExtensions.kt
+++ b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/TestRuleExtensions.kt
@@ -17,7 +17,7 @@
package androidx.compose.testutils
import androidx.activity.ComponentActivity
-import androidx.ui.test.android.AndroidComposeTestRule
+import androidx.ui.test.AndroidComposeTestRule
/**
* Takes the given test case and prepares it for execution-controlled test via
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index 5c28f5b..532afc4 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -1557,6 +1557,20 @@
method public androidx.compose.ui.unit.Uptime? getUptime();
}
+ public final class PointerInputEvent {
+ method public android.view.MotionEvent getMotionEvent();
+ method public java.util.List<androidx.compose.ui.input.pointer.PointerInputEventData> getPointers();
+ method public long getUptime();
+ }
+
+ public final class PointerInputEventData {
+ method public long component1();
+ method public androidx.compose.ui.input.pointer.PointerInputData component2();
+ method public androidx.compose.ui.input.pointer.PointerInputEventData copy-pdufZyI(long id, androidx.compose.ui.input.pointer.PointerInputData pointerInputData);
+ method public long getId();
+ method public androidx.compose.ui.input.pointer.PointerInputData getPointerInputData();
+ }
+
public final class PointerInputEventProcessorKt {
}
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 5c28f5b..532afc4 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -1557,6 +1557,20 @@
method public androidx.compose.ui.unit.Uptime? getUptime();
}
+ public final class PointerInputEvent {
+ method public android.view.MotionEvent getMotionEvent();
+ method public java.util.List<androidx.compose.ui.input.pointer.PointerInputEventData> getPointers();
+ method public long getUptime();
+ }
+
+ public final class PointerInputEventData {
+ method public long component1();
+ method public androidx.compose.ui.input.pointer.PointerInputData component2();
+ method public androidx.compose.ui.input.pointer.PointerInputEventData copy-pdufZyI(long id, androidx.compose.ui.input.pointer.PointerInputData pointerInputData);
+ method public long getId();
+ method public androidx.compose.ui.input.pointer.PointerInputData getPointerInputData();
+ }
+
public final class PointerInputEventProcessorKt {
}
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 55c3aae..375bb15 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -1604,6 +1604,20 @@
method public androidx.compose.ui.unit.Uptime? getUptime();
}
+ public final class PointerInputEvent {
+ method public android.view.MotionEvent getMotionEvent();
+ method public java.util.List<androidx.compose.ui.input.pointer.PointerInputEventData> getPointers();
+ method public long getUptime();
+ }
+
+ public final class PointerInputEventData {
+ method public long component1();
+ method public androidx.compose.ui.input.pointer.PointerInputData component2();
+ method public androidx.compose.ui.input.pointer.PointerInputEventData copy-pdufZyI(long id, androidx.compose.ui.input.pointer.PointerInputData pointerInputData);
+ method public long getId();
+ method public androidx.compose.ui.input.pointer.PointerInputData getPointerInputData();
+ }
+
public final class PointerInputEventProcessorKt {
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogUiTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogUiTest.kt
index 31ee176..cdfdcb3 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogUiTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogUiTest.kt
@@ -115,7 +115,7 @@
// Click outside the dialog to dismiss it
val outsideX = 0
- val outsideY = composeTestRule.displayMetrics.heightPixels / 2
+ val outsideY = composeTestRule.displaySize.height / 2
UiDevice.getInstance(getInstrumentation()).click(outsideX, outsideY)
onNodeWithText(defaultText).assertDoesNotExist()
@@ -137,7 +137,7 @@
// Click outside the dialog to try to dismiss it
val outsideX = 0
- val outsideY = composeTestRule.displayMetrics.heightPixels / 2
+ val outsideY = composeTestRule.displaySize.height / 2
UiDevice.getInstance(getInstrumentation()).click(outsideX, outsideY)
// The Dialog should still be visible
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.kt
index 89e7ab6..aeb3bb5 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.kt
@@ -19,7 +19,7 @@
import android.view.MotionEvent
import androidx.compose.ui.unit.Uptime
-internal actual class PointerInputEvent(
+actual class PointerInputEvent(
actual val uptime: Uptime,
actual val pointers: List<PointerInputEventData>,
val motionEvent: MotionEvent
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
index 590b7f03..a2c7e8d 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
@@ -16,6 +16,7 @@
package androidx.compose.ui.input.pointer
+import androidx.compose.ui.node.InternalCoreApi
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.plus
import androidx.compose.ui.util.annotation.VisibleForTesting
@@ -25,6 +26,7 @@
* Organizes pointers and the [PointerInputFilter]s that they hit into a hierarchy such that
* [PointerInputChange]s can be dispatched to the [PointerInputFilter]s in a hierarchical fashion.
*/
+@OptIn(InternalCoreApi::class)
internal class HitPathTracker {
@VisibleForTesting
@@ -263,6 +265,7 @@
* pointer or [PointerInputFilter] information.
*/
@VisibleForTesting
+@OptIn(InternalCoreApi::class)
internal open class NodeParent {
val children: MutableSet<Node> = mutableSetOf()
@@ -384,6 +387,7 @@
* hit it (tracked as [PointerId]s).
*/
@VisibleForTesting
+@OptIn(InternalCoreApi::class)
internal class Node(val pointerInputFilter: PointerInputFilter) : NodeParent() {
val pointerIds: MutableSet<PointerId> = mutableSetOf()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt
index cf227e8..05f0cc2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/InternalPointerInput.kt
@@ -16,6 +16,7 @@
package androidx.compose.ui.input.pointer
+import androidx.compose.ui.node.InternalCoreApi
import androidx.compose.ui.unit.Uptime
/**
@@ -24,7 +25,8 @@
*
* All pointer locations are relative to the device screen.
*/
-internal expect class PointerInputEvent {
+@InternalCoreApi
+expect class PointerInputEvent {
val uptime: Uptime
val pointers: List<PointerInputEventData>
}
@@ -34,7 +36,7 @@
*
* All pointer locations are relative to the device screen.
*/
-internal data class PointerInputEventData(
+data class PointerInputEventData(
val id: PointerId,
val pointerInputData: PointerInputData
)
@@ -46,6 +48,7 @@
* it is efficient to split the changes between those that are relevant to the sub tree and those
* that are not.
*/
+@OptIn(InternalCoreApi::class)
internal expect class InternalPointerEvent(
changes: MutableMap<PointerId, PointerInputChange>,
pointerInputEvent: PointerInputEvent
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
index c8bb788..056468f 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.kt
@@ -26,6 +26,7 @@
import androidx.compose.ui.input.pointer.PointerEventPass.Initial
import androidx.compose.ui.input.pointer.PointerEventPass.Main
import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.node.InternalCoreApi
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.Uptime
@@ -126,7 +127,7 @@
/**
* Describes a pointer input change event that has occurred at a particular point in time.
*/
-expect class PointerEvent internal constructor(
+expect class PointerEvent @OptIn(InternalCoreApi::class) internal constructor(
changes: List<PointerInputChange>,
internalPointerEvent: InternalPointerEvent?
) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
index dc9da8b..954a2ae 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEventProcessor.kt
@@ -17,13 +17,14 @@
package androidx.compose.ui.input.pointer
import androidx.compose.ui.node.ExperimentalLayoutNodeApi
+import androidx.compose.ui.node.InternalCoreApi
import androidx.compose.ui.node.LayoutNode
import androidx.compose.ui.util.fastForEach
/**
* The core element that receives [PointerInputEvent]s and process them in Compose UI.
*/
-@OptIn(ExperimentalLayoutNodeApi::class)
+@OptIn(ExperimentalLayoutNodeApi::class, InternalCoreApi::class)
internal class PointerInputEventProcessor(val root: LayoutNode) {
private val hitPathTracker = HitPathTracker()
@@ -101,6 +102,7 @@
/**
* Produces [InternalPointerEvent]s by tracking changes between [PointerInputEvent]s
*/
+@OptIn(InternalCoreApi::class)
private class PointerInputChangeEventProducer {
private val previousPointerInputData: MutableMap<PointerId, PointerInputData> = mutableMapOf()
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/TestTag.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/TestTag.kt
similarity index 100%
rename from compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/TestTag.kt
rename to compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/TestTag.kt
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.kt
index d078dc4..31733df 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerInputEvent.kt
@@ -18,7 +18,7 @@
import androidx.compose.ui.unit.Uptime
-internal actual class PointerInputEvent(
+actual class PointerInputEvent(
actual val uptime: Uptime,
actual val pointers: List<PointerInputEventData>
)
\ No newline at end of file
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt
index 4974193..bcfd308 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwner.kt
@@ -218,7 +218,7 @@
root.draw(DesktopCanvas(canvas))
}
- internal fun processPointerInput(event: PointerInputEvent) {
+ fun processPointerInput(event: PointerInputEvent) {
measureAndLayout()
pointerInputEventProcessor.process(event)
}
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
index c96aa083..6c0e93a 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/platform/DesktopOwners.kt
@@ -38,7 +38,7 @@
component: Component,
private val redraw: () -> Unit
) {
- private val list = LinkedHashSet<DesktopOwner>()
+ val list = LinkedHashSet<DesktopOwner>()
// Optimization: we don't need more than one redrawing per tick
private var redrawingScheduled = false
@@ -135,4 +135,4 @@
redrawingScheduled = true
}
}
-}
\ No newline at end of file
+}
diff --git a/ui/ui-test-font/src/main/AndroidManifest.xml b/ui/ui-test-font/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2751a04
--- /dev/null
+++ b/ui/ui-test-font/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.compose.ui.text.font.test">
+</manifest>
diff --git a/ui/ui-test/api/current.txt b/ui/ui-test/api/current.txt
index 1ddc9ad..9302bcf 100644
--- a/ui/ui-test/api/current.txt
+++ b/ui/ui-test/api/current.txt
@@ -9,10 +9,11 @@
method public static void performSemanticsAction(androidx.ui.test.SemanticsNodeInteraction, androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> key);
}
- public final class AndroidAssertionsKt {
+ public final class AndroidAnimationClockTestRuleKt {
+ method public static androidx.ui.test.AnimationClockTestRule createAnimationClockRule();
}
- public final class AndroidBaseInputDispatcherKt {
+ public final class AndroidAssertionsKt {
}
public final class AndroidBitmapHelpersKt {
@@ -26,6 +27,31 @@
method public static boolean contains-ej0GBII(androidx.compose.ui.graphics.Path, long offset);
}
+ public final class AndroidComposeTestRule<T extends androidx.activity.ComponentActivity> implements androidx.ui.test.ComposeTestRule {
+ ctor public AndroidComposeTestRule(androidx.test.ext.junit.rules.ActivityScenarioRule<T> activityRule, boolean disableTransitions, boolean disableBlinkingCursor);
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description? description);
+ method public androidx.test.ext.junit.rules.ActivityScenarioRule<T> getActivityRule();
+ method public androidx.ui.test.AnimationClockTestRule getClockTestRule();
+ method public androidx.compose.ui.unit.Density getDensity();
+ method public long getDisplaySize();
+ method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+ property public androidx.ui.test.AnimationClockTestRule clockTestRule;
+ property public androidx.compose.ui.unit.Density density;
+ property public long displaySize;
+ }
+
+ public final class AndroidComposeTestRule.AndroidComposeStatement extends org.junit.runners.model.Statement {
+ ctor public AndroidComposeTestRule.AndroidComposeStatement(org.junit.runners.model.Statement base);
+ method public void evaluate();
+ }
+
+ public final class AndroidComposeTestRuleKt {
+ method public static androidx.ui.test.ComposeTestRule createComposeRule(boolean disableTransitions, boolean disableBlinkingCursor);
+ }
+
+ public final class AndroidInputDispatcherKt {
+ }
+
public final class AndroidOutputKt {
}
@@ -35,16 +61,14 @@
public final class AndroidSynchronizationKt {
}
- public final class AnimationClockTestRule implements org.junit.rules.TestRule {
- ctor public AnimationClockTestRule();
- method public void advanceClock(long milliseconds);
- method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description? description);
+ public interface AnimationClockTestRule extends org.junit.rules.TestRule {
+ method public default void advanceClock(long milliseconds);
method public androidx.ui.test.TestAnimationClock getClock();
method public boolean isPaused();
method public void pauseClock();
- method public void resumeClock();
- property public final androidx.ui.test.TestAnimationClock clock;
- property public final boolean isPaused;
+ method public default void resumeClock();
+ property public abstract androidx.ui.test.TestAnimationClock clock;
+ property public abstract boolean isPaused;
}
public final class AnimationClocksKt {
@@ -94,15 +118,11 @@
public interface ComposeTestRule extends org.junit.rules.TestRule {
method public androidx.ui.test.AnimationClockTestRule getClockTestRule();
method public androidx.compose.ui.unit.Density getDensity();
- method public android.util.DisplayMetrics getDisplayMetrics();
+ method public long getDisplaySize();
method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
property public abstract androidx.ui.test.AnimationClockTestRule clockTestRule;
property public abstract androidx.compose.ui.unit.Density density;
- property public abstract android.util.DisplayMetrics displayMetrics;
- }
-
- public final class ComposeTestRuleKt {
- method public static androidx.ui.test.ComposeTestRule createComposeRule(boolean disableTransitions = false, boolean disableBlinkingCursor = true);
+ property public abstract long displaySize;
}
public final class CoroutineBuildersKt {
@@ -337,27 +357,9 @@
package androidx.ui.test.android {
- public final class AndroidComposeTestRule<T extends androidx.activity.ComponentActivity> implements androidx.ui.test.ComposeTestRule {
- ctor public AndroidComposeTestRule(androidx.test.ext.junit.rules.ActivityScenarioRule<T> activityRule, boolean disableTransitions, boolean disableBlinkingCursor);
- method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description? description);
- method public androidx.test.ext.junit.rules.ActivityScenarioRule<T> getActivityRule();
- method public androidx.ui.test.AnimationClockTestRule getClockTestRule();
- method public androidx.compose.ui.unit.Density getDensity();
- method public android.util.DisplayMetrics getDisplayMetrics();
- method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
- property public androidx.ui.test.AnimationClockTestRule clockTestRule;
- property public androidx.compose.ui.unit.Density density;
- property public android.util.DisplayMetrics displayMetrics;
- }
-
- public final class AndroidComposeTestRule.AndroidComposeStatement extends org.junit.runners.model.Statement {
- ctor public AndroidComposeTestRule.AndroidComposeStatement(org.junit.runners.model.Statement base);
- method public void evaluate();
- }
-
public final class AndroidComposeTestRuleKt {
- method public static <T extends androidx.activity.ComponentActivity> androidx.ui.test.android.AndroidComposeTestRule<T> createAndroidComposeRule(Class<T> activityClass, boolean disableTransitions = false, boolean disableBlinkingCursor = true);
- method public static inline <reified T extends androidx.activity.ComponentActivity> androidx.ui.test.android.AndroidComposeTestRule<T>! createAndroidComposeRule(boolean disableTransitions = false, boolean disableBlinkingCursor = true);
+ method public static <T extends androidx.activity.ComponentActivity> androidx.ui.test.AndroidComposeTestRule<T> createAndroidComposeRule(Class<T> activityClass, boolean disableTransitions = false, boolean disableBlinkingCursor = true);
+ method public static inline <reified T extends androidx.activity.ComponentActivity> androidx.ui.test.AndroidComposeTestRule<T>! createAndroidComposeRule(boolean disableTransitions = false, boolean disableBlinkingCursor = true);
}
public final class ComposeIdlingResourceKt {
diff --git a/ui/ui-test/api/public_plus_experimental_current.txt b/ui/ui-test/api/public_plus_experimental_current.txt
index 1ddc9ad..9302bcf 100644
--- a/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/ui/ui-test/api/public_plus_experimental_current.txt
@@ -9,10 +9,11 @@
method public static void performSemanticsAction(androidx.ui.test.SemanticsNodeInteraction, androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> key);
}
- public final class AndroidAssertionsKt {
+ public final class AndroidAnimationClockTestRuleKt {
+ method public static androidx.ui.test.AnimationClockTestRule createAnimationClockRule();
}
- public final class AndroidBaseInputDispatcherKt {
+ public final class AndroidAssertionsKt {
}
public final class AndroidBitmapHelpersKt {
@@ -26,6 +27,31 @@
method public static boolean contains-ej0GBII(androidx.compose.ui.graphics.Path, long offset);
}
+ public final class AndroidComposeTestRule<T extends androidx.activity.ComponentActivity> implements androidx.ui.test.ComposeTestRule {
+ ctor public AndroidComposeTestRule(androidx.test.ext.junit.rules.ActivityScenarioRule<T> activityRule, boolean disableTransitions, boolean disableBlinkingCursor);
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description? description);
+ method public androidx.test.ext.junit.rules.ActivityScenarioRule<T> getActivityRule();
+ method public androidx.ui.test.AnimationClockTestRule getClockTestRule();
+ method public androidx.compose.ui.unit.Density getDensity();
+ method public long getDisplaySize();
+ method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+ property public androidx.ui.test.AnimationClockTestRule clockTestRule;
+ property public androidx.compose.ui.unit.Density density;
+ property public long displaySize;
+ }
+
+ public final class AndroidComposeTestRule.AndroidComposeStatement extends org.junit.runners.model.Statement {
+ ctor public AndroidComposeTestRule.AndroidComposeStatement(org.junit.runners.model.Statement base);
+ method public void evaluate();
+ }
+
+ public final class AndroidComposeTestRuleKt {
+ method public static androidx.ui.test.ComposeTestRule createComposeRule(boolean disableTransitions, boolean disableBlinkingCursor);
+ }
+
+ public final class AndroidInputDispatcherKt {
+ }
+
public final class AndroidOutputKt {
}
@@ -35,16 +61,14 @@
public final class AndroidSynchronizationKt {
}
- public final class AnimationClockTestRule implements org.junit.rules.TestRule {
- ctor public AnimationClockTestRule();
- method public void advanceClock(long milliseconds);
- method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description? description);
+ public interface AnimationClockTestRule extends org.junit.rules.TestRule {
+ method public default void advanceClock(long milliseconds);
method public androidx.ui.test.TestAnimationClock getClock();
method public boolean isPaused();
method public void pauseClock();
- method public void resumeClock();
- property public final androidx.ui.test.TestAnimationClock clock;
- property public final boolean isPaused;
+ method public default void resumeClock();
+ property public abstract androidx.ui.test.TestAnimationClock clock;
+ property public abstract boolean isPaused;
}
public final class AnimationClocksKt {
@@ -94,15 +118,11 @@
public interface ComposeTestRule extends org.junit.rules.TestRule {
method public androidx.ui.test.AnimationClockTestRule getClockTestRule();
method public androidx.compose.ui.unit.Density getDensity();
- method public android.util.DisplayMetrics getDisplayMetrics();
+ method public long getDisplaySize();
method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
property public abstract androidx.ui.test.AnimationClockTestRule clockTestRule;
property public abstract androidx.compose.ui.unit.Density density;
- property public abstract android.util.DisplayMetrics displayMetrics;
- }
-
- public final class ComposeTestRuleKt {
- method public static androidx.ui.test.ComposeTestRule createComposeRule(boolean disableTransitions = false, boolean disableBlinkingCursor = true);
+ property public abstract long displaySize;
}
public final class CoroutineBuildersKt {
@@ -337,27 +357,9 @@
package androidx.ui.test.android {
- public final class AndroidComposeTestRule<T extends androidx.activity.ComponentActivity> implements androidx.ui.test.ComposeTestRule {
- ctor public AndroidComposeTestRule(androidx.test.ext.junit.rules.ActivityScenarioRule<T> activityRule, boolean disableTransitions, boolean disableBlinkingCursor);
- method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description? description);
- method public androidx.test.ext.junit.rules.ActivityScenarioRule<T> getActivityRule();
- method public androidx.ui.test.AnimationClockTestRule getClockTestRule();
- method public androidx.compose.ui.unit.Density getDensity();
- method public android.util.DisplayMetrics getDisplayMetrics();
- method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
- property public androidx.ui.test.AnimationClockTestRule clockTestRule;
- property public androidx.compose.ui.unit.Density density;
- property public android.util.DisplayMetrics displayMetrics;
- }
-
- public final class AndroidComposeTestRule.AndroidComposeStatement extends org.junit.runners.model.Statement {
- ctor public AndroidComposeTestRule.AndroidComposeStatement(org.junit.runners.model.Statement base);
- method public void evaluate();
- }
-
public final class AndroidComposeTestRuleKt {
- method public static <T extends androidx.activity.ComponentActivity> androidx.ui.test.android.AndroidComposeTestRule<T> createAndroidComposeRule(Class<T> activityClass, boolean disableTransitions = false, boolean disableBlinkingCursor = true);
- method public static inline <reified T extends androidx.activity.ComponentActivity> androidx.ui.test.android.AndroidComposeTestRule<T>! createAndroidComposeRule(boolean disableTransitions = false, boolean disableBlinkingCursor = true);
+ method public static <T extends androidx.activity.ComponentActivity> androidx.ui.test.AndroidComposeTestRule<T> createAndroidComposeRule(Class<T> activityClass, boolean disableTransitions = false, boolean disableBlinkingCursor = true);
+ method public static inline <reified T extends androidx.activity.ComponentActivity> androidx.ui.test.AndroidComposeTestRule<T>! createAndroidComposeRule(boolean disableTransitions = false, boolean disableBlinkingCursor = true);
}
public final class ComposeIdlingResourceKt {
diff --git a/ui/ui-test/api/restricted_current.txt b/ui/ui-test/api/restricted_current.txt
index 1ddc9ad..9302bcf 100644
--- a/ui/ui-test/api/restricted_current.txt
+++ b/ui/ui-test/api/restricted_current.txt
@@ -9,10 +9,11 @@
method public static void performSemanticsAction(androidx.ui.test.SemanticsNodeInteraction, androidx.compose.ui.semantics.SemanticsPropertyKey<androidx.compose.ui.semantics.AccessibilityAction<kotlin.jvm.functions.Function0<java.lang.Boolean>>> key);
}
- public final class AndroidAssertionsKt {
+ public final class AndroidAnimationClockTestRuleKt {
+ method public static androidx.ui.test.AnimationClockTestRule createAnimationClockRule();
}
- public final class AndroidBaseInputDispatcherKt {
+ public final class AndroidAssertionsKt {
}
public final class AndroidBitmapHelpersKt {
@@ -26,6 +27,31 @@
method public static boolean contains-ej0GBII(androidx.compose.ui.graphics.Path, long offset);
}
+ public final class AndroidComposeTestRule<T extends androidx.activity.ComponentActivity> implements androidx.ui.test.ComposeTestRule {
+ ctor public AndroidComposeTestRule(androidx.test.ext.junit.rules.ActivityScenarioRule<T> activityRule, boolean disableTransitions, boolean disableBlinkingCursor);
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description? description);
+ method public androidx.test.ext.junit.rules.ActivityScenarioRule<T> getActivityRule();
+ method public androidx.ui.test.AnimationClockTestRule getClockTestRule();
+ method public androidx.compose.ui.unit.Density getDensity();
+ method public long getDisplaySize();
+ method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+ property public androidx.ui.test.AnimationClockTestRule clockTestRule;
+ property public androidx.compose.ui.unit.Density density;
+ property public long displaySize;
+ }
+
+ public final class AndroidComposeTestRule.AndroidComposeStatement extends org.junit.runners.model.Statement {
+ ctor public AndroidComposeTestRule.AndroidComposeStatement(org.junit.runners.model.Statement base);
+ method public void evaluate();
+ }
+
+ public final class AndroidComposeTestRuleKt {
+ method public static androidx.ui.test.ComposeTestRule createComposeRule(boolean disableTransitions, boolean disableBlinkingCursor);
+ }
+
+ public final class AndroidInputDispatcherKt {
+ }
+
public final class AndroidOutputKt {
}
@@ -35,16 +61,14 @@
public final class AndroidSynchronizationKt {
}
- public final class AnimationClockTestRule implements org.junit.rules.TestRule {
- ctor public AnimationClockTestRule();
- method public void advanceClock(long milliseconds);
- method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description? description);
+ public interface AnimationClockTestRule extends org.junit.rules.TestRule {
+ method public default void advanceClock(long milliseconds);
method public androidx.ui.test.TestAnimationClock getClock();
method public boolean isPaused();
method public void pauseClock();
- method public void resumeClock();
- property public final androidx.ui.test.TestAnimationClock clock;
- property public final boolean isPaused;
+ method public default void resumeClock();
+ property public abstract androidx.ui.test.TestAnimationClock clock;
+ property public abstract boolean isPaused;
}
public final class AnimationClocksKt {
@@ -94,15 +118,11 @@
public interface ComposeTestRule extends org.junit.rules.TestRule {
method public androidx.ui.test.AnimationClockTestRule getClockTestRule();
method public androidx.compose.ui.unit.Density getDensity();
- method public android.util.DisplayMetrics getDisplayMetrics();
+ method public long getDisplaySize();
method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
property public abstract androidx.ui.test.AnimationClockTestRule clockTestRule;
property public abstract androidx.compose.ui.unit.Density density;
- property public abstract android.util.DisplayMetrics displayMetrics;
- }
-
- public final class ComposeTestRuleKt {
- method public static androidx.ui.test.ComposeTestRule createComposeRule(boolean disableTransitions = false, boolean disableBlinkingCursor = true);
+ property public abstract long displaySize;
}
public final class CoroutineBuildersKt {
@@ -337,27 +357,9 @@
package androidx.ui.test.android {
- public final class AndroidComposeTestRule<T extends androidx.activity.ComponentActivity> implements androidx.ui.test.ComposeTestRule {
- ctor public AndroidComposeTestRule(androidx.test.ext.junit.rules.ActivityScenarioRule<T> activityRule, boolean disableTransitions, boolean disableBlinkingCursor);
- method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description? description);
- method public androidx.test.ext.junit.rules.ActivityScenarioRule<T> getActivityRule();
- method public androidx.ui.test.AnimationClockTestRule getClockTestRule();
- method public androidx.compose.ui.unit.Density getDensity();
- method public android.util.DisplayMetrics getDisplayMetrics();
- method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
- property public androidx.ui.test.AnimationClockTestRule clockTestRule;
- property public androidx.compose.ui.unit.Density density;
- property public android.util.DisplayMetrics displayMetrics;
- }
-
- public final class AndroidComposeTestRule.AndroidComposeStatement extends org.junit.runners.model.Statement {
- ctor public AndroidComposeTestRule.AndroidComposeStatement(org.junit.runners.model.Statement base);
- method public void evaluate();
- }
-
public final class AndroidComposeTestRuleKt {
- method public static <T extends androidx.activity.ComponentActivity> androidx.ui.test.android.AndroidComposeTestRule<T> createAndroidComposeRule(Class<T> activityClass, boolean disableTransitions = false, boolean disableBlinkingCursor = true);
- method public static inline <reified T extends androidx.activity.ComponentActivity> androidx.ui.test.android.AndroidComposeTestRule<T>! createAndroidComposeRule(boolean disableTransitions = false, boolean disableBlinkingCursor = true);
+ method public static <T extends androidx.activity.ComponentActivity> androidx.ui.test.AndroidComposeTestRule<T> createAndroidComposeRule(Class<T> activityClass, boolean disableTransitions = false, boolean disableBlinkingCursor = true);
+ method public static inline <reified T extends androidx.activity.ComponentActivity> androidx.ui.test.AndroidComposeTestRule<T>! createAndroidComposeRule(boolean disableTransitions = false, boolean disableBlinkingCursor = true);
}
public final class ComposeIdlingResourceKt {
diff --git a/ui/ui-test/build.gradle b/ui/ui-test/build.gradle
index ab3e2fc..da620d8 100644
--- a/ui/ui-test/build.gradle
+++ b/ui/ui-test/build.gradle
@@ -34,6 +34,8 @@
kotlin {
android()
+ jvm("desktop")
+
sourceSets {
commonMain.dependencies {
api project(":compose:animation:animation-core")
@@ -53,6 +55,11 @@
implementation project(":compose:runtime:runtime-saved-instance-state")
}
+ jvmMain.dependencies {
+ implementation "androidx.collection:collection:1.1.0"
+ implementation(JUNIT)
+ }
+
androidMain.dependencies {
api(JUNIT)
api(ANDROIDX_TEST_EXT_JUNIT)
@@ -74,6 +81,14 @@
implementation project(':compose:material:material')
implementation project(":compose:ui:ui")
}
+
+ desktopMain.dependencies {
+ implementation(JUNIT)
+ implementation(TRUTH)
+ implementation(SKIKO)
+ }
+
+ desktopMain.dependsOn jvmMain
}
}
diff --git a/ui/ui-test/src/androidAndroidTest/AndroidManifest.xml b/ui/ui-test/src/androidAndroidTest/AndroidManifest.xml
index 6ec2ae6..cad929e 100644
--- a/ui/ui-test/src/androidAndroidTest/AndroidManifest.xml
+++ b/ui/ui-test/src/androidAndroidTest/AndroidManifest.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="androidx.ui.test.test">
+ package="androidx.ui.test">
<application>
<activity android:name="androidx.activity.ComponentActivity"
android:theme="@style/TestTheme"/>
diff --git a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/gesturescope/SendDoubleClickTest.kt b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/gesturescope/SendDoubleClickTest.kt
index 1a6ac80..f87ff98 100644
--- a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/gesturescope/SendDoubleClickTest.kt
+++ b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/gesturescope/SendDoubleClickTest.kt
@@ -21,7 +21,7 @@
import androidx.compose.ui.gesture.doubleTapGestureFilter
import androidx.compose.ui.geometry.Offset
import androidx.ui.test.InputDispatcher.Companion.eventPeriod
-import androidx.ui.test.AndroidBaseInputDispatcher.InputDispatcherTestRule
+import androidx.ui.test.android.AndroidInputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.performGesture
import androidx.ui.test.onNodeWithTag
diff --git a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt
index 1109fe4..f7d13e9 100644
--- a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt
+++ b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/gesturescope/SendSwipeVelocityTest.kt
@@ -23,7 +23,7 @@
import androidx.compose.foundation.layout.Stack
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.wrapContentSize
-import androidx.ui.test.AndroidBaseInputDispatcher.InputDispatcherTestRule
+import androidx.ui.test.android.AndroidInputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.performGesture
import androidx.ui.test.onNodeWithTag
diff --git a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/inputdispatcher/InputDispatcherTest.kt b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/inputdispatcher/InputDispatcherTest.kt
index cbad2f5..09d5d38 100644
--- a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/inputdispatcher/InputDispatcherTest.kt
+++ b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/inputdispatcher/InputDispatcherTest.kt
@@ -17,10 +17,9 @@
package androidx.ui.test.inputdispatcher
import androidx.compose.ui.geometry.Offset
-import androidx.ui.test.AndroidBaseInputDispatcher
-import androidx.ui.test.AndroidBaseInputDispatcher.InputDispatcherTestRule
-import androidx.ui.test.InputDispatcher
import androidx.ui.test.android.AndroidInputDispatcher
+import androidx.ui.test.android.AndroidInputDispatcher.InputDispatcherTestRule
+import androidx.ui.test.InputDispatcher
import androidx.ui.test.util.MotionEventRecorder
import com.google.common.truth.Truth.assertThat
import org.junit.After
@@ -75,5 +74,5 @@
}
internal fun InputDispatcher.verifyNoGestureInProgress() {
- assertThat((this as AndroidBaseInputDispatcher).isGestureInProgress).isFalse()
+ assertThat((this as AndroidInputDispatcher).isGestureInProgress).isFalse()
}
diff --git a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendCancelTest.kt b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendCancelTest.kt
index 5a466d3..5fab8f1 100644
--- a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendCancelTest.kt
+++ b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendCancelTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.MediumTest
import androidx.compose.ui.geometry.Offset
-import androidx.ui.test.AndroidBaseInputDispatcher.InputDispatcherTestRule
+import androidx.ui.test.android.AndroidInputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.inputdispatcher.verifyNoGestureInProgress
import androidx.ui.test.partialgesturescope.Common.partialGesture
diff --git a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendDownTest.kt b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendDownTest.kt
index ad9375b..7a18c79 100644
--- a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendDownTest.kt
+++ b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendDownTest.kt
@@ -19,7 +19,7 @@
import android.os.SystemClock.sleep
import androidx.test.filters.MediumTest
import androidx.compose.ui.geometry.Offset
-import androidx.ui.test.AndroidBaseInputDispatcher.InputDispatcherTestRule
+import androidx.ui.test.android.AndroidInputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.partialgesturescope.Common.partialGesture
import androidx.ui.test.runOnIdle
diff --git a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveByTest.kt b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveByTest.kt
index 451dca3..a585a81 100644
--- a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveByTest.kt
+++ b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveByTest.kt
@@ -19,7 +19,7 @@
import android.os.SystemClock.sleep
import androidx.test.filters.MediumTest
import androidx.compose.ui.geometry.Offset
-import androidx.ui.test.AndroidBaseInputDispatcher.InputDispatcherTestRule
+import androidx.ui.test.android.AndroidInputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.movePointerBy
import androidx.ui.test.partialgesturescope.Common.partialGesture
diff --git a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveTest.kt b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveTest.kt
index 9dc1097..cdf7083 100644
--- a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveTest.kt
+++ b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.MediumTest
import androidx.compose.ui.geometry.Offset
-import androidx.ui.test.AndroidBaseInputDispatcher.InputDispatcherTestRule
+import androidx.ui.test.android.AndroidInputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.partialgesturescope.Common.partialGesture
import androidx.ui.test.cancel
diff --git a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveToTest.kt b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveToTest.kt
index c47d796..6a60568 100644
--- a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveToTest.kt
+++ b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendMoveToTest.kt
@@ -19,7 +19,7 @@
import android.os.SystemClock.sleep
import androidx.test.filters.MediumTest
import androidx.compose.ui.geometry.Offset
-import androidx.ui.test.AndroidBaseInputDispatcher.InputDispatcherTestRule
+import androidx.ui.test.android.AndroidInputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.movePointerTo
import androidx.ui.test.partialgesturescope.Common.partialGesture
diff --git a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendUpTest.kt b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendUpTest.kt
index 91c1ba7..02b7cbb 100644
--- a/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendUpTest.kt
+++ b/ui/ui-test/src/androidAndroidTest/kotlin/androidx/ui/test/partialgesturescope/SendUpTest.kt
@@ -19,7 +19,7 @@
import android.os.SystemClock.sleep
import androidx.test.filters.MediumTest
import androidx.compose.ui.geometry.Offset
-import androidx.ui.test.AndroidBaseInputDispatcher.InputDispatcherTestRule
+import androidx.ui.test.android.AndroidInputDispatcher.InputDispatcherTestRule
import androidx.ui.test.createComposeRule
import androidx.ui.test.inputdispatcher.verifyNoGestureInProgress
import androidx.ui.test.partialgesturescope.Common.partialGesture
diff --git a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AnimationClockTestRule.kt b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidAnimationClockTestRule.kt
similarity index 88%
rename from ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AnimationClockTestRule.kt
rename to ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidAnimationClockTestRule.kt
index d5ac7ab..9ab8c78 100644
--- a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AnimationClockTestRule.kt
+++ b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidAnimationClockTestRule.kt
@@ -41,7 +41,7 @@
* animations. Otherwise, built in steps that make sure the UI is stable when performing actions
* or assertions will fail to work.
*/
-class AnimationClockTestRule : TestRule {
+internal class AndroidAnimationClockTestRule : AnimationClockTestRule {
/** Backing property for [clock] */
private val _clock = AndroidTestAnimationClock()
@@ -52,27 +52,27 @@
* make sure to let it implement [TestAnimationClock] and register it with
* [registerTestClock].
*/
- val clock: TestAnimationClock get() = _clock
+ override val clock: TestAnimationClock get() = _clock
/**
* Convenience property for calling [`clock.isPaused`][TestAnimationClock.isPaused]
*/
- val isPaused: Boolean get() = clock.isPaused
+ override val isPaused: Boolean get() = clock.isPaused
/**
* Convenience method for calling [`clock.pauseClock()`][TestAnimationClock.pauseClock]
*/
- fun pauseClock() = clock.pauseClock()
+ override fun pauseClock() = clock.pauseClock()
/**
* Convenience method for calling [`clock.resumeClock()`][TestAnimationClock.resumeClock]
*/
- fun resumeClock() = clock.resumeClock()
+ override fun resumeClock() = clock.resumeClock()
/**
* Convenience method for calling [`clock.advanceClock()`][TestAnimationClock.advanceClock]
*/
- fun advanceClock(milliseconds: Long) = clock.advanceClock(milliseconds)
+ override fun advanceClock(milliseconds: Long) = clock.advanceClock(milliseconds)
override fun apply(base: Statement, description: Description?): Statement {
return AnimationClockStatement(base)
@@ -97,3 +97,6 @@
}
}
}
+
+actual fun createAnimationClockRule(): AnimationClockTestRule =
+ AndroidAnimationClockTestRule()
\ No newline at end of file
diff --git a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidBaseInputDispatcher.kt b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidBaseInputDispatcher.kt
index dea6057..e69de29 100644
--- a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidBaseInputDispatcher.kt
+++ b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidBaseInputDispatcher.kt
@@ -1,601 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.ui.test
-
-import androidx.collection.SparseArrayCompat
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.lerp
-import androidx.compose.ui.node.Owner
-import androidx.compose.ui.platform.AndroidOwner
-import androidx.compose.ui.unit.Duration
-import androidx.compose.ui.unit.inMilliseconds
-import androidx.compose.ui.unit.milliseconds
-import androidx.ui.test.android.AndroidInputDispatcher
-import androidx.ui.test.android.AndroidOwnerRegistry
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import java.util.WeakHashMap
-import kotlin.math.max
-import kotlin.math.roundToInt
-
-/**
- * Interface for dispatching full and partial gestures.
- *
- * Full gestures:
- * * [enqueueClick]
- * * [enqueueSwipe]
- * * [enqueueSwipes]
- *
- * Partial gestures:
- * * [enqueueDown]
- * * [enqueueMove]
- * * [enqueueUp]
- * * [enqueueCancel]
- * * [movePointer]
- * * [getCurrentPosition]
- *
- * Chaining methods:
- * * [enqueueDelay]
- */
-internal abstract class AndroidBaseInputDispatcher : InputDispatcher() {
- companion object : AndroidOwnerRegistry.OnRegistrationChangedListener {
- /**
- * Indicates that [nextDownTime] is not set
- */
- private const val DownTimeNotSet = -1L
-
- /**
- * Stores the [InputDispatcherState] of each [Owner]. The state will be restored in an
- * [InputDispatcher] when it is created for an owner that has a state stored.
- */
- internal val states = WeakHashMap<Owner, InputDispatcherState>()
-
- init {
- AndroidOwnerRegistry.addOnRegistrationChangedListener(this)
- }
-
- override fun onRegistrationChanged(owner: AndroidOwner, registered: Boolean) {
- if (!registered) {
- states.remove(owner)
- }
- }
- }
-
- override fun saveState(owner: Owner?) {
- if (owner != null && AndroidOwnerRegistry.getUnfilteredOwners().contains(owner)) {
- states[owner] = InputDispatcherState(nextDownTime, gestureLateness, partialGesture)
- }
- }
-
- internal var nextDownTime = DownTimeNotSet
-
- /**
- * The time difference between enqueuing the first event of the gesture and dispatching it.
- *
- * When the first event of a gesture is enqueued, its eventTime is fixed to the current time.
- * However, there is inevitably some time between enqueuing and dispatching of that event.
- * This means that event is going to be "late" by [gestureLateness] milliseconds when it is
- * dispatched. Because the dispatcher wants to align events with the current time, it will
- * dispatch all events that are late immediately and without delay, until it has reached an
- * event whose eventTime is in the future (i.e. an event that is "early").
- *
- * The [gestureLateness] will be used to offset all events, effectively aligning the first
- * event with the dispatch time.
- */
- internal var gestureLateness: Long? = null
-
- internal var partialGesture: PartialGesture? = null
-
- /**
- * Indicates if a gesture is in progress or not. A gesture is in progress if at least one
- * finger is (still) touching the screen.
- */
- val isGestureInProgress: Boolean
- get() = partialGesture != null
-
- abstract override val now: Long
-
- /**
- * Generates the downTime of the next gesture with the given [duration]. The gesture's
- * [duration] is necessary to facilitate chaining of gestures: if another gesture is made
- * after the next one, it will start exactly [duration] after the start of the next gesture.
- * Always use this method to determine the downTime of the [down event][enqueueDown] of a
- * gesture.
- *
- * If the duration is unknown when calling this method, use a duration of zero and update
- * with [moveNextDownTime] when the duration is known, or use [moveNextDownTime]
- * incrementally if the gesture unfolds gradually.
- */
- private fun generateDownTime(duration: Duration): Long {
- val downTime = if (nextDownTime == DownTimeNotSet) {
- now
- } else {
- nextDownTime
- }
- nextDownTime = downTime + duration.inMilliseconds()
- return downTime
- }
-
- /**
- * Moves the start time of the next gesture ahead by the given [duration]. Does not affect
- * any event time from the current gesture. Use this when the expected duration passed to
- * [generateDownTime] has changed.
- */
- private fun moveNextDownTime(duration: Duration) {
- generateDownTime(duration)
- }
-
- /**
- * Increases the eventTime with the given [time]. Also pushes the downTime for the next
- * chained gesture by the same amount to facilitate chaining.
- */
- private fun PartialGesture.increaseEventTime(time: Long = eventPeriod) {
- moveNextDownTime(time.milliseconds)
- lastEventTime += time
- }
-
- /**
- * Adds a delay between the end of the last full or current partial gesture of the given
- * [duration]. Guarantees that the first event time of the next gesture will be exactly
- * [duration] later then if that gesture would be injected without this delay, provided that
- * the next gesture is started using the same [InputDispatcher] instance as the one used to
- * end the last gesture.
- *
- * Note: this does not affect the time of the next event for the _current_ partial gesture,
- * using [enqueueMove], [enqueueUp] and [enqueueCancel], but it will affect the time of the
- * _next_ gesture (including partial gestures started with [enqueueDown]).
- *
- * @param duration The duration of the delay. Must be positive
- */
- override fun enqueueDelay(duration: Duration) {
- require(duration >= Duration.Zero) {
- "duration of a delay can only be positive, not $duration"
- }
- moveNextDownTime(duration)
- }
-
- /**
- * Generates a click event at [position]. There will be 10ms in between the down and the up
- * event. The generated events are enqueued in this [InputDispatcher] and will be sent when
- * [sendAllSynchronous] is called at the end of [performGesture].
- *
- * @param position The coordinate of the click
- */
- override fun enqueueClick(position: Offset) {
- enqueueDown(0, position)
- enqueueMove()
- enqueueUp(0)
- }
-
- /**
- * Generates a swipe gesture from [start] to [end] with the given [duration]. The generated
- * events are enqueued in this [InputDispatcher] and will be sent when [sendAllSynchronous]
- * is called at the end of [performGesture].
- *
- * @param start The start position of the gesture
- * @param end The end position of the gesture
- * @param duration The duration of the gesture
- */
- override fun enqueueSwipe(start: Offset, end: Offset, duration: Duration) {
- val durationFloat = duration.inMilliseconds().toFloat()
- enqueueSwipe(
- curve = { lerp(start, end, it / durationFloat) },
- duration = duration
- )
- }
-
- /**
- * Generates a swipe gesture from [curve](0) to [curve]([duration]), following the
- * route defined by [curve]. Will force sampling of an event at all times defined in
- * [keyTimes]. The number of events sampled between the key times is implementation
- * dependent. The generated events are enqueued in this [InputDispatcher] and will be sent
- * when [sendAllSynchronous] is called at the end of [performGesture].
- *
- * @param curve The function that defines the position of the gesture over time
- * @param duration The duration of the gesture
- * @param keyTimes An optional list of timestamps in milliseconds at which a move event must
- * be sampled
- */
- override fun enqueueSwipe(
- curve: (Long) -> Offset,
- duration: Duration,
- keyTimes: List<Long>
- ) {
- enqueueSwipes(listOf(curve), duration, keyTimes)
- }
-
- /**
- * Generates [curves].size simultaneous swipe gestures, each swipe going from
- * [curves][i](0) to [curves][i]([duration]), following the route defined by
- * [curves][i]. Will force sampling of an event at all times defined in [keyTimes].
- * The number of events sampled between the key times is implementation dependent. The
- * generated events are enqueued in this [InputDispatcher] and will be sent when
- * [sendAllSynchronous] is called at the end of [performGesture].
- *
- * @param curves The functions that define the position of the gesture over time
- * @param duration The duration of the gestures
- * @param keyTimes An optional list of timestamps in milliseconds at which a move event must
- * be sampled
- */
- override fun enqueueSwipes(
- curves: List<(Long) -> Offset>,
- duration: Duration,
- keyTimes: List<Long>
- ) {
- val startTime = 0L
- val endTime = duration.inMilliseconds()
-
- // Validate input
- require(duration >= 1.milliseconds) {
- "duration must be at least 1 millisecond, not $duration"
- }
- val validRange = startTime..endTime
- require(keyTimes.all { it in validRange }) {
- "keyTimes contains timestamps out of range [$startTime..$endTime]: $keyTimes"
- }
- require(keyTimes.asSequence().zipWithNext { a, b -> a <= b }.all { it }) {
- "keyTimes must be sorted: $keyTimes"
- }
-
- // Send down events
- curves.forEachIndexed { i, curve ->
- enqueueDown(i, curve(startTime))
- }
-
- // Send move events between each consecutive pair in [t0, ..keyTimes, tN]
- var currTime = startTime
- var key = 0
- while (currTime < endTime) {
- // advance key
- while (key < keyTimes.size && keyTimes[key] <= currTime) {
- key++
- }
- // send events between t and next keyTime
- val tNext = if (key < keyTimes.size) keyTimes[key] else endTime
- sendPartialSwipes(curves, currTime, tNext)
- currTime = tNext
- }
-
- // And end with up events
- repeat(curves.size) {
- enqueueUp(it)
- }
- }
-
- /**
- * Generates move events between `f([t0])` and `f([tN])` during the time window `(downTime +
- * t0, downTime + tN]`, using [fs] to sample the coordinate of each event. The number of
- * events sent (#numEvents) is such that the time between each event is as close to
- * [InputDispatcher.eventPeriod] as possible, but at least 1. The first event is sent at time
- * `downTime + (tN - t0) / #numEvents`, the last event is sent at time tN.
- *
- * @param fs The functions that define the coordinates of the respective gestures over time
- * @param t0 The start time of this segment of the swipe, in milliseconds relative to downTime
- * @param tN The end time of this segment of the swipe, in milliseconds relative to downTime
- */
- private fun sendPartialSwipes(
- fs: List<(Long) -> Offset>,
- t0: Long,
- tN: Long
- ) {
- var step = 0
- // How many steps will we take between t0 and tN? At least 1, and a number that will
- // bring as as close to eventPeriod as possible
- val steps = max(1, ((tN - t0) / eventPeriod.toFloat()).roundToInt())
-
- var tPrev = t0
- while (step++ < steps) {
- val progress = step / steps.toFloat()
- val t = androidx.compose.ui.util.lerp(t0, tN, progress)
- fs.forEachIndexed { i, f ->
- movePointer(i, f(t))
- }
- enqueueMove(t - tPrev)
- tPrev = t
- }
- }
-
- /**
- * During a partial gesture, returns the position of the last touch event of the given
- * [pointerId]. Returns `null` if no partial gesture is in progress for that [pointerId].
- *
- * @param pointerId The id of the pointer for which to return the current position
- * @return The current position of the pointer with the given [pointerId], or `null` if the
- * pointer is not currently in use
- */
- override fun getCurrentPosition(pointerId: Int): Offset? {
- return partialGesture?.lastPositions?.get(pointerId)
- }
-
- /**
- * Generates a down event at [position] for the pointer with the given [pointerId], starting
- * a new partial gesture. A partial gesture can only be started if none was currently ongoing
- * for that pointer. Pointer ids may be reused during the same gesture. The generated event
- * is enqueued in this [InputDispatcher] and will be sent when [sendAllSynchronous] is called
- * at the end of [performGesture].
- *
- * It is possible to mix partial gestures with full gestures (e.g. generate a [click]
- * [enqueueClick] during a partial gesture), as long as you make sure that the default
- * pointer id (id=0) is free to be used by the full gesture.
- *
- * A full gesture starts with a down event at some position (with this method) that indicates
- * a finger has started touching the screen, followed by zero or more [down][enqueueDown],
- * [move][enqueueMove] and [up][enqueueUp] events that respectively indicate that another
- * finger started touching the screen, a finger moved around or a finger was lifted up from
- * the screen. A gesture is finished when [up][enqueueUp] lifts the last remaining finger
- * from the screen, or when a single [cancel][enqueueCancel] event is generated.
- *
- * Partial gestures don't have to be defined all in the same [performGesture] block, but
- * keep in mind that while the gesture is not complete, all code you execute in between
- * blocks that progress the gesture, will be executed while imaginary fingers are actively
- * touching the screen. All events generated during a single [performGesture] block are sent
- * together at the end of that block.
- *
- * In the context of testing, it is not necessary to complete a gesture with an up or cancel
- * event, if the test ends before it expects the finger to be lifted from the screen.
- *
- * @param pointerId The id of the pointer, can be any number not yet in use by another pointer
- * @param position The coordinate of the down event
- *
- * @see movePointer
- * @see enqueueMove
- * @see enqueueUp
- * @see enqueueCancel
- */
- override fun enqueueDown(pointerId: Int, position: Offset) {
- var gesture = partialGesture
-
- // Check if this pointer is not already down
- require(gesture == null || !gesture.lastPositions.containsKey(pointerId)) {
- "Cannot send DOWN event, a gesture is already in progress for pointer $pointerId"
- }
-
- gesture?.flushPointerUpdates()
-
- // Start a new gesture, or add the pointerId to the existing gesture
- if (gesture == null) {
- gesture = PartialGesture(generateDownTime(0.milliseconds), position, pointerId)
- partialGesture = gesture
- } else {
- gesture.lastPositions.put(pointerId, position)
- }
-
- // Send the DOWN event
- gesture.enqueueDown(pointerId)
- }
-
- /**
- * Updates the position of the pointer with the given [pointerId] to the given [position],
- * but does not generate a move event. Use this to move multiple pointers simultaneously. To
- * generate the next move event, which will contain the current position of _all_ pointers
- * (not just the moved ones), call [enqueueMove] without arguments. If you move one or more
- * pointers and then call [enqueueDown] or [enqueueUp], without calling [enqueueMove] first,
- * a move event will be generated right before that down or up event. See [enqueueDown] for
- * more information on how to make complete gestures from partial gestures.
- *
- * @param pointerId The id of the pointer to move, as supplied in [enqueueDown]
- * @param position The position to move the pointer to
- *
- * @see enqueueDown
- * @see enqueueMove
- * @see enqueueUp
- * @see enqueueCancel
- */
- override fun movePointer(pointerId: Int, position: Offset) {
- val gesture = partialGesture
-
- // Check if this pointer is in the gesture
- check(gesture != null) {
- "Cannot move pointers, no gesture is in progress"
- }
- require(gesture.lastPositions.containsKey(pointerId)) {
- "Cannot move pointer $pointerId, it is not active in the current gesture"
- }
-
- gesture.lastPositions.put(pointerId, position)
- gesture.hasPointerUpdates = true
- }
-
- /**
- * Generates a move event [delay] milliseconds after the previous injected event of this
- * gesture, without moving any of the pointers. The default [delay] is [10 milliseconds]
- * [InputDispatcher.eventPeriod]. Use this to commit all changes in pointer location made
- * with [movePointer]. The generated event will contain the current position of all pointers.
- * It is enqueued in this [InputDispatcher] and will be sent when [sendAllSynchronous] is
- * called at the end of [performGesture]. See [enqueueDown] for more information on how to
- * make complete gestures from partial gestures.
- *
- * @param delay The time in milliseconds between the previously injected event and the move
- * event. [10 milliseconds][InputDispatcher.eventPeriod] by default.
- */
- override fun enqueueMove(delay: Long) {
- val gesture = checkNotNull(partialGesture) {
- "Cannot send MOVE event, no gesture is in progress"
- }
- require(delay >= 0) {
- "Cannot send MOVE event with a delay of $delay ms"
- }
-
- gesture.increaseEventTime(delay)
- gesture.enqueueMove()
- gesture.hasPointerUpdates = false
- }
-
- /**
- * Generates an up event for the given [pointerId] at the current position of that pointer,
- * [delay] milliseconds after the previous injected event of this gesture. The default
- * [delay] is 0 milliseconds. The generated event is enqueued in this [InputDispatcher] and
- * will be sent when [sendAllSynchronous] is called at the end of [performGesture]. See
- * [enqueueDown] for more information on how to make complete gestures from partial gestures.
- *
- * @param pointerId The id of the pointer to lift up, as supplied in [enqueueDown]
- * @param delay The time in milliseconds between the previously injected event and the move
- * event. 0 milliseconds by default.
- *
- * @see enqueueDown
- * @see movePointer
- * @see enqueueMove
- * @see enqueueCancel
- */
- override fun enqueueUp(pointerId: Int, delay: Long) {
- val gesture = partialGesture
-
- // Check if this pointer is in the gesture
- check(gesture != null) {
- "Cannot send UP event, no gesture is in progress"
- }
- require(gesture.lastPositions.containsKey(pointerId)) {
- "Cannot send UP event for pointer $pointerId, it is not active in the current gesture"
- }
- require(delay >= 0) {
- "Cannot send UP event with a delay of $delay ms"
- }
-
- gesture.flushPointerUpdates()
- gesture.increaseEventTime(delay)
-
- // First send the UP event
- gesture.enqueueUp(pointerId)
-
- // Then remove the pointer, and end the gesture if no pointers are left
- gesture.lastPositions.remove(pointerId)
- if (gesture.lastPositions.isEmpty) {
- partialGesture = null
- }
- }
-
- /**
- * Generates a cancel event [delay] milliseconds after the previous injected event of this
- * gesture. The default [delay] is [10 milliseconds][InputDispatcher.eventPeriod]. The
- * generated event is enqueued in this [InputDispatcher] and will be sent when
- * [sendAllSynchronous] is called at the end of [performGesture]. See [enqueueDown] for more
- * information on how to make complete gestures from partial gestures.
- *
- * @param delay The time in milliseconds between the previously injected event and the cancel
- * event. [10 milliseconds][InputDispatcher.eventPeriod] by default.
- *
- * @see enqueueDown
- * @see movePointer
- * @see enqueueMove
- * @see enqueueUp
- */
- override fun enqueueCancel(delay: Long) {
- val gesture = checkNotNull(partialGesture) {
- "Cannot send CANCEL event, no gesture is in progress"
- }
- require(delay >= 0) {
- "Cannot send CANCEL event with a delay of $delay ms"
- }
-
- gesture.increaseEventTime(delay)
- gesture.enqueueCancel()
- partialGesture = null
- }
-
- /**
- * Generates a MOVE event with all pointer locations, if any of the pointers has been moved by
- * [movePointer] since the last MOVE event.
- */
- private fun PartialGesture.flushPointerUpdates() {
- if (hasPointerUpdates) {
- enqueueMove(eventPeriod)
- }
- }
-
- protected abstract fun PartialGesture.enqueueDown(pointerId: Int)
-
- protected abstract fun PartialGesture.enqueueMove()
-
- protected abstract fun PartialGesture.enqueueUp(pointerId: Int)
-
- protected abstract fun PartialGesture.enqueueCancel()
-
- /**
- * A test rule that modifies [InputDispatcher]s behavior. Can be used to disable dispatching
- * of MotionEvents in real time (skips the suspend before injection of an event) or to change
- * the time between consecutive injected events.
- *
- * @param disableDispatchInRealTime If set, controls whether or not events with an eventTime
- * in the future will be dispatched as soon as possible or at that exact eventTime. If
- * `false` or not set, will suspend until the eventTime, if `true`, will send the event
- * immediately without suspending. See also [InputDispatcher.dispatchInRealTime].
- * @param eventPeriodOverride If set, specifies a different period in milliseconds between
- * two consecutive injected motion events injected by this [InputDispatcher]. If not
- * set, the event period of 10 milliseconds is unchanged.
- *
- * @see InputDispatcher.eventPeriod
- */
- internal class InputDispatcherTestRule(
- private val disableDispatchInRealTime: Boolean = false,
- private val eventPeriodOverride: Long? = null
- ) : TestRule {
-
- override fun apply(base: Statement, description: Description?): Statement {
- return ModifyingStatement(base)
- }
-
- inner class ModifyingStatement(private val base: Statement) : Statement() {
- override fun evaluate() {
- if (disableDispatchInRealTime) {
- dispatchInRealTime = false
- }
- if (eventPeriodOverride != null) {
- eventPeriod = eventPeriodOverride
- }
- try {
- base.evaluate()
- } finally {
- if (disableDispatchInRealTime) {
- dispatchInRealTime = true
- }
- if (eventPeriodOverride != null) {
- eventPeriod = 10L
- }
- }
- }
- }
- }
-}
-
-internal actual class PartialGesture actual constructor(
- val downTime: Long,
- startPosition: Offset,
- pointerId: Int
-) {
- var lastEventTime: Long = downTime
- val lastPositions = SparseArrayCompat<Offset>().apply { put(pointerId, startPosition) }
- var hasPointerUpdates: Boolean = false
-}
-
-internal actual fun InputDispatcher(owner: Owner): InputDispatcher {
- require(owner is AndroidOwner) {
- "InputDispatcher currently only supports dispatching to AndroidOwner, not to " +
- owner::class.java.simpleName
- }
- val view = owner.view
- return AndroidInputDispatcher { view.dispatchTouchEvent(it) }.apply {
- AndroidBaseInputDispatcher.states.remove(owner)?.also {
- // TODO(b/157653315): Move restore state to constructor
- if (it.partialGesture != null) {
- nextDownTime = it.nextDownTime
- gestureLateness = it.gestureLateness
- partialGesture = it.partialGesture
- }
- }
- }
-}
diff --git a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidComposeTestRule.kt b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidComposeTestRule.kt
new file mode 100644
index 0000000..d1b4749
--- /dev/null
+++ b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidComposeTestRule.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+import androidx.activity.ComponentActivity
+import androidx.ui.test.android.AndroidOwnerRegistry
+import androidx.ui.test.android.FirstDrawRegistry
+import androidx.ui.test.android.registerComposeWithEspresso
+import androidx.ui.test.android.unregisterComposeFromEspresso
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Recomposer
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.compose.animation.transitionsEnabled
+import androidx.compose.ui.platform.setContent
+import androidx.compose.foundation.InternalFoundationApi
+import androidx.compose.foundation.blinkingCursorEnabled
+import androidx.compose.ui.text.input.textInputServiceFactory
+import androidx.compose.ui.unit.Density
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import androidx.compose.ui.unit.IntSize
+import androidx.ui.test.android.createAndroidComposeRule
+
+actual fun createComposeRule(
+ disableTransitions: Boolean,
+ disableBlinkingCursor: Boolean
+): ComposeTestRule =
+ createAndroidComposeRule<ComponentActivity>(
+ disableTransitions,
+ disableBlinkingCursor
+ )
+
+/**
+ * Android specific implementation of [ComposeTestRule].
+ */
+class AndroidComposeTestRule<T : ComponentActivity>(
+ // TODO(b/153623653): Remove activityRule from arguments when AndroidComposeTestRule can
+ // work with any kind of Activity launcher.
+ val activityRule: ActivityScenarioRule<T>,
+ private val disableTransitions: Boolean = false,
+ private val disableBlinkingCursor: Boolean = true
+) : ComposeTestRule {
+
+ private fun getActivity(): T {
+ var activity: T? = null
+ if (activity == null) {
+ activityRule.scenario.onActivity { activity = it }
+ if (activity == null) {
+ throw IllegalStateException("Activity was not set in the ActivityScenarioRule!")
+ }
+ }
+ return activity!!
+ }
+
+ override val clockTestRule: AnimationClockTestRule = AndroidAnimationClockTestRule()
+
+ internal var disposeContentHook: (() -> Unit)? = null
+
+ override val density: Density get() =
+ Density(getActivity().resources.displayMetrics.density)
+
+ override val displaySize by lazy {
+ getActivity().resources.displayMetrics.let {
+ IntSize(it.widthPixels, it.heightPixels)
+ }
+ }
+
+ override fun apply(base: Statement, description: Description?): Statement {
+ val activityTestRuleStatement = activityRule.apply(base, description)
+ val composeTestRuleStatement = AndroidComposeStatement(activityTestRuleStatement)
+ return clockTestRule.apply(composeTestRuleStatement, description)
+ }
+
+ /**
+ * @throws IllegalStateException if called more than once per test.
+ */
+ @SuppressWarnings("SyntheticAccessor")
+ override fun setContent(composable: @Composable () -> Unit) {
+ check(disposeContentHook == null) {
+ "Cannot call setContent twice per test!"
+ }
+
+ lateinit var activity: T
+ activityRule.scenario.onActivity { activity = it }
+
+ runOnUiThread {
+ val composition = activity.setContent(
+ Recomposer.current(),
+ composable
+ )
+ disposeContentHook = {
+ composition.dispose()
+ }
+ }
+
+ if (!isOnUiThread()) {
+ // Only wait for idleness if not on the UI thread. If we are on the UI thread, the
+ // caller clearly wants to keep tight control over execution order, so don't go
+ // executing future tasks on the main thread.
+ waitForIdle()
+ }
+ }
+
+ inner class AndroidComposeStatement(
+ private val base: Statement
+ ) : Statement() {
+ override fun evaluate() {
+ val oldTextInputFactory = @Suppress("DEPRECATION_ERROR")(textInputServiceFactory)
+ beforeEvaluate()
+ try {
+ base.evaluate()
+ } finally {
+ afterEvaluate()
+ @Suppress("DEPRECATION_ERROR")
+ textInputServiceFactory = oldTextInputFactory
+ }
+ }
+
+ @OptIn(InternalFoundationApi::class)
+ private fun beforeEvaluate() {
+ transitionsEnabled = !disableTransitions
+ blinkingCursorEnabled = !disableBlinkingCursor
+ AndroidOwnerRegistry.setupRegistry()
+ FirstDrawRegistry.setupRegistry()
+ registerComposeWithEspresso()
+ @Suppress("DEPRECATION_ERROR")
+ textInputServiceFactory = {
+ TextInputServiceForTests(it)
+ }
+ }
+
+ @OptIn(InternalFoundationApi::class)
+ private fun afterEvaluate() {
+ transitionsEnabled = true
+ blinkingCursorEnabled = true
+ AndroidOwnerRegistry.tearDownRegistry()
+ FirstDrawRegistry.tearDownRegistry()
+ unregisterComposeFromEspresso()
+ // Dispose the content
+ if (disposeContentHook != null) {
+ runOnUiThread {
+ // NOTE: currently, calling dispose after an exception that happened during
+ // composition is not a safe call. Compose runtime should fix this, and then
+ // this call will be okay. At the moment, however, calling this could
+ // itself produce an exception which will then obscure the original
+ // exception. To fix this, we will just wrap this call in a try/catch of
+ // its own
+ try {
+ disposeContentHook!!()
+ } catch (e: Exception) {
+ // ignore
+ }
+ disposeContentHook = null
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidInputDispatcher.kt b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidInputDispatcher.kt
new file mode 100644
index 0000000..f490787
--- /dev/null
+++ b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/AndroidInputDispatcher.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+import androidx.compose.ui.node.Owner
+import androidx.compose.ui.platform.AndroidOwner
+import androidx.ui.test.android.AndroidInputDispatcher
+
+internal actual fun InputDispatcher(owner: Owner): InputDispatcher {
+ require(owner is AndroidOwner) {
+ "InputDispatcher currently only supports dispatching to AndroidOwner, not to " +
+ owner::class.java.simpleName
+ }
+ val view = owner.view
+ return AndroidInputDispatcher { view.dispatchTouchEvent(it) }.apply {
+ BaseInputDispatcher.states.remove(owner)?.also {
+ // TODO(b/157653315): Move restore state to constructor
+ if (it.partialGesture != null) {
+ nextDownTime = it.nextDownTime
+ gestureLateness = it.gestureLateness
+ partialGesture = it.partialGesture
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidComposeTestRule.kt b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidComposeTestRule.kt
index ae0825d..8886e6e 100644
--- a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidComposeTestRule.kt
+++ b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidComposeTestRule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,26 +16,10 @@
package androidx.ui.test.android
-import android.util.DisplayMetrics
import androidx.activity.ComponentActivity
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Recomposer
import androidx.test.ext.junit.rules.ActivityScenarioRule
-import androidx.compose.animation.transitionsEnabled
-import androidx.compose.ui.platform.setContent
-import androidx.compose.foundation.InternalFoundationApi
-import androidx.compose.foundation.blinkingCursorEnabled
-import androidx.compose.ui.text.input.textInputServiceFactory
-import androidx.ui.test.AnimationClockTestRule
-import androidx.ui.test.ComposeTestRule
-import androidx.ui.test.TextInputServiceForTests
+import androidx.ui.test.AndroidComposeTestRule
import androidx.ui.test.createComposeRule
-import androidx.ui.test.isOnUiThread
-import androidx.ui.test.runOnUiThread
-import androidx.ui.test.waitForIdle
-import androidx.compose.ui.unit.Density
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
/**
* Factory method to provide android specific implementation of [createComposeRule], for a given
@@ -82,127 +66,4 @@
ActivityScenarioRule(activityClass),
disableTransitions,
disableBlinkingCursor
-)
-
-/**
- * Android specific implementation of [ComposeTestRule].
- */
-class AndroidComposeTestRule<T : ComponentActivity>(
- // TODO(b/153623653): Remove activityRule from arguments when AndroidComposeTestRule can
- // work with any kind of Activity launcher.
- val activityRule: ActivityScenarioRule<T>,
- private val disableTransitions: Boolean = false,
- private val disableBlinkingCursor: Boolean = true
-) : ComposeTestRule {
-
- private fun getActivity(): T {
- var activity: T? = null
- if (activity == null) {
- activityRule.scenario.onActivity { activity = it }
- if (activity == null) {
- throw IllegalStateException("Activity was not set in the ActivityScenarioRule!")
- }
- }
- return activity!!
- }
-
- override val clockTestRule = AnimationClockTestRule()
-
- internal var disposeContentHook: (() -> Unit)? = null
-
- override val density: Density get() =
- Density(getActivity().resources.displayMetrics.density)
-
- override val displayMetrics: DisplayMetrics get() = getActivity().resources.displayMetrics
-
- override fun apply(base: Statement, description: Description?): Statement {
- val activityTestRuleStatement = activityRule.apply(base, description)
- val composeTestRuleStatement = AndroidComposeStatement(activityTestRuleStatement)
- return clockTestRule.apply(composeTestRuleStatement, description)
- }
-
- /**
- * @throws IllegalStateException if called more than once per test.
- */
- @SuppressWarnings("SyntheticAccessor")
- override fun setContent(composable: @Composable () -> Unit) {
- check(disposeContentHook == null) {
- "Cannot call setContent twice per test!"
- }
-
- lateinit var activity: T
- activityRule.scenario.onActivity { activity = it }
-
- runOnUiThread {
- val composition = activity.setContent(
- Recomposer.current(),
- composable
- )
- disposeContentHook = {
- composition.dispose()
- }
- }
-
- if (!isOnUiThread()) {
- // Only wait for idleness if not on the UI thread. If we are on the UI thread, the
- // caller clearly wants to keep tight control over execution order, so don't go
- // executing future tasks on the main thread.
- waitForIdle()
- }
- }
-
- inner class AndroidComposeStatement(
- private val base: Statement
- ) : Statement() {
- override fun evaluate() {
- val oldTextInputFactory = @Suppress("DEPRECATION_ERROR")(textInputServiceFactory)
- beforeEvaluate()
- try {
- base.evaluate()
- } finally {
- afterEvaluate()
- @Suppress("DEPRECATION_ERROR")
- textInputServiceFactory = oldTextInputFactory
- }
- }
-
- @OptIn(InternalFoundationApi::class)
- private fun beforeEvaluate() {
- transitionsEnabled = !disableTransitions
- blinkingCursorEnabled = !disableBlinkingCursor
- AndroidOwnerRegistry.setupRegistry()
- FirstDrawRegistry.setupRegistry()
- registerComposeWithEspresso()
- @Suppress("DEPRECATION_ERROR")
- textInputServiceFactory = {
- TextInputServiceForTests(it)
- }
- }
-
- @OptIn(InternalFoundationApi::class)
- private fun afterEvaluate() {
- transitionsEnabled = true
- blinkingCursorEnabled = true
- AndroidOwnerRegistry.tearDownRegistry()
- FirstDrawRegistry.tearDownRegistry()
- unregisterComposeFromEspresso()
- // Dispose the content
- if (disposeContentHook != null) {
- runOnUiThread {
- // NOTE: currently, calling dispose after an exception that happened during
- // composition is not a safe call. Compose runtime should fix this, and then
- // this call will be okay. At the moment, however, calling this could
- // itself produce an exception which will then obscure the original
- // exception. To fix this, we will just wrap this call in a try/catch of
- // its own
- try {
- disposeContentHook!!()
- } catch (e: Exception) {
- // ignore
- }
- disposeContentHook = null
- }
- }
- }
- }
-}
+)
\ No newline at end of file
diff --git a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidInputDispatcher.kt b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidInputDispatcher.kt
index 6261084..739c8cc 100644
--- a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidInputDispatcher.kt
+++ b/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/android/AndroidInputDispatcher.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,17 +27,35 @@
import android.view.MotionEvent.ACTION_UP
import androidx.compose.runtime.dispatch.AndroidUiDispatcher
import androidx.compose.ui.geometry.Offset
-import androidx.ui.test.AndroidBaseInputDispatcher
+import androidx.compose.ui.node.Owner
+import androidx.compose.ui.platform.AndroidOwner
+import androidx.ui.test.BaseInputDispatcher
import androidx.ui.test.InputDispatcher
+import androidx.ui.test.InputDispatcherState
import androidx.ui.test.PartialGesture
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
import kotlin.math.max
internal class AndroidInputDispatcher(
private val sendEvent: (MotionEvent) -> Unit
-) : AndroidBaseInputDispatcher() {
+) : BaseInputDispatcher() {
+
+ companion object : AndroidOwnerRegistry.OnRegistrationChangedListener {
+ init {
+ AndroidOwnerRegistry.addOnRegistrationChangedListener(this)
+ }
+
+ override fun onRegistrationChanged(owner: AndroidOwner, registered: Boolean) {
+ if (!registered) {
+ states.remove(owner)
+ }
+ }
+ }
private val batchLock = Any()
// Batched events are generated just-in-time, given the "lateness" of the dispatching (see
@@ -48,6 +66,12 @@
override val now: Long get() = SystemClock.uptimeMillis()
+ override fun saveState(owner: Owner?) {
+ if (owner != null && AndroidOwnerRegistry.getUnfilteredOwners().contains(owner)) {
+ states[owner] = InputDispatcherState(nextDownTime, gestureLateness, partialGesture)
+ }
+ }
+
override fun PartialGesture.enqueueDown(pointerId: Int) {
batchMotionEvent(
if (lastPositions.size() == 1) ACTION_DOWN else ACTION_POINTER_DOWN,
@@ -184,7 +208,7 @@
*/
private suspend fun sendAndRecycleEvent(event: MotionEvent) {
try {
- if (dispatchInRealTime) {
+ if (InputDispatcher.dispatchInRealTime) {
val delayMs = event.eventTime - now
if (delayMs > 0) {
delay(delayMs)
@@ -195,4 +219,50 @@
event.recycle()
}
}
+
+ /**
+ * A test rule that modifies [InputDispatcher]s behavior. Can be used to disable dispatching
+ * of MotionEvents in real time (skips the suspend before injection of an event) or to change
+ * the time between consecutive injected events.
+ *
+ * @param disableDispatchInRealTime If set, controls whether or not events with an eventTime
+ * in the future will be dispatched as soon as possible or at that exact eventTime. If
+ * `false` or not set, will suspend until the eventTime, if `true`, will send the event
+ * immediately without suspending. See also [InputDispatcher.dispatchInRealTime].
+ * @param eventPeriodOverride If set, specifies a different period in milliseconds between
+ * two consecutive injected motion events injected by this [InputDispatcher]. If not
+ * set, the event period of 10 milliseconds is unchanged.
+ *
+ * @see InputDispatcher.eventPeriod
+ */
+ internal class InputDispatcherTestRule(
+ private val disableDispatchInRealTime: Boolean = false,
+ private val eventPeriodOverride: Long? = null
+ ) : TestRule {
+
+ override fun apply(base: Statement, description: Description?): Statement {
+ return ModifyingStatement(base)
+ }
+
+ inner class ModifyingStatement(private val base: Statement) : Statement() {
+ override fun evaluate() {
+ if (disableDispatchInRealTime) {
+ InputDispatcher.dispatchInRealTime = false
+ }
+ if (eventPeriodOverride != null) {
+ InputDispatcher.eventPeriod = eventPeriodOverride
+ }
+ try {
+ base.evaluate()
+ } finally {
+ if (disableDispatchInRealTime) {
+ InputDispatcher.dispatchInRealTime = true
+ }
+ if (eventPeriodOverride != null) {
+ InputDispatcher.eventPeriod = 10L
+ }
+ }
+ }
+ }
+ }
}
diff --git a/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/InputDispatcher.kt b/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/InputDispatcher.kt
index 2395aa3..80788b7 100644
--- a/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/InputDispatcher.kt
+++ b/ui/ui-test/src/commonMain/kotlin/androidx/ui/test/InputDispatcher.kt
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package androidx.ui.test
import androidx.compose.ui.geometry.Offset
@@ -22,7 +21,26 @@
import androidx.compose.ui.unit.inMilliseconds
import androidx.compose.ui.unit.milliseconds
-internal abstract class InputDispatcher {
+/**
+ * Interface for dispatching full and partial gestures.
+ *
+ * Full gestures:
+ * * [enqueueClick]
+ * * [enqueueSwipe]
+ * * [enqueueSwipes]
+ *
+ * Partial gestures:
+ * * [enqueueDown]
+ * * [enqueueMove]
+ * * [enqueueUp]
+ * * [enqueueCancel]
+ * * [movePointer]
+ * * [getCurrentPosition]
+ *
+ * Chaining methods:
+ * * [enqueueDelay]
+ */
+internal interface InputDispatcher {
companion object {
/**
* Whether or not injection of events should be suspended in between events until [now]
@@ -44,72 +62,207 @@
/**
* The current time, in the time scale used by gesture events.
*/
- protected abstract val now: Long
+ val now: Long
/**
* Sends all enqueued events and blocks while they are dispatched. Will suspend before
* dispatching an event until [now] is at least that event's timestamp. If an exception is
* thrown during the process, all events that haven't yet been dispatched will be dropped.
*/
- internal abstract fun sendAllSynchronous()
+ fun sendAllSynchronous()
- internal abstract fun saveState(owner: Owner?)
-
+ fun saveState(owner: Owner?)
/**
* Called when this [InputDispatcher] is about to be discarded, from [GestureScope.dispose].
*/
- internal abstract fun dispose()
+ fun dispose()
- abstract fun enqueueClick(position: Offset)
+ /**
+ * Generates a click event at [position]. There will be 10ms in between the down and the up
+ * event. The generated events are enqueued in this [InputDispatcher] and will be sent when
+ * [sendAllSynchronous] is called at the end of [performGesture].
+ *
+ * @param position The coordinate of the click
+ */
+ fun enqueueClick(position: Offset)
- abstract fun enqueueSwipe(start: Offset, end: Offset, duration: Duration)
+ /**
+ * Generates a swipe gesture from [start] to [end] with the given [duration]. The generated
+ * events are enqueued in this [InputDispatcher] and will be sent when [sendAllSynchronous]
+ * is called at the end of [performGesture].
+ *
+ * @param start The start position of the gesture
+ * @param end The end position of the gesture
+ * @param duration The duration of the gesture
+ */
+ fun enqueueSwipe(start: Offset, end: Offset, duration: Duration)
- abstract fun enqueueSwipe(
+ /**
+ * Generates a swipe gesture from [curve](0) to [curve]([duration]), following the
+ * route defined by [curve]. Will force sampling of an event at all times defined in
+ * [keyTimes]. The number of events sampled between the key times is implementation
+ * dependent. The generated events are enqueued in this [InputDispatcher] and will be sent
+ * when [sendAllSynchronous] is called at the end of [performGesture].
+ *
+ * @param curve The function that defines the position of the gesture over time
+ * @param duration The duration of the gesture
+ * @param keyTimes An optional list of timestamps in milliseconds at which a move event must
+ * be sampled
+ */
+ fun enqueueSwipe(
curve: (Long) -> Offset,
duration: Duration,
keyTimes: List<Long> = emptyList()
)
- abstract fun enqueueSwipes(
+ /**
+ * Generates [curves].size simultaneous swipe gestures, each swipe going from
+ * [curves][i](0) to [curves][i]([duration]), following the route defined by
+ * [curves][i]. Will force sampling of an event at all times defined in [keyTimes].
+ * The number of events sampled between the key times is implementation dependent. The
+ * generated events are enqueued in this [InputDispatcher] and will be sent when
+ * [sendAllSynchronous] is called at the end of [performGesture].
+ *
+ * @param curves The functions that define the position of the gesture over time
+ * @param duration The duration of the gestures
+ * @param keyTimes An optional list of timestamps in milliseconds at which a move event must
+ * be sampled
+ */
+ fun enqueueSwipes(
curves: List<(Long) -> Offset>,
duration: Duration,
keyTimes: List<Long> = emptyList()
)
- abstract fun enqueueDelay(duration: Duration)
+ /**
+ * Adds a delay between the end of the last full or current partial gesture of the given
+ * [duration]. Guarantees that the first event time of the next gesture will be exactly
+ * [duration] later then if that gesture would be injected without this delay, provided that
+ * the next gesture is started using the same [InputDispatcher] instance as the one used to
+ * end the last gesture.
+ *
+ * Note: this does not affect the time of the next event for the _current_ partial gesture,
+ * using [enqueueMove], [enqueueUp] and [enqueueCancel], but it will affect the time of the
+ * _next_ gesture (including partial gestures started with [enqueueDown]).
+ *
+ * @param duration The duration of the delay. Must be positive
+ */
+ fun enqueueDelay(duration: Duration)
- abstract fun enqueueDown(pointerId: Int, position: Offset)
+ /**
+ * Generates a down event at [position] for the pointer with the given [pointerId], starting
+ * a new partial gesture. A partial gesture can only be started if none was currently ongoing
+ * for that pointer. Pointer ids may be reused during the same gesture. The generated event
+ * is enqueued in this [InputDispatcher] and will be sent when [sendAllSynchronous] is called
+ * at the end of [performGesture].
+ *
+ * It is possible to mix partial gestures with full gestures (e.g. generate a [click]
+ * [enqueueClick] during a partial gesture), as long as you make sure that the default
+ * pointer id (id=0) is free to be used by the full gesture.
+ *
+ * A full gesture starts with a down event at some position (with this method) that indicates
+ * a finger has started touching the screen, followed by zero or more [down][enqueueDown],
+ * [move][enqueueMove] and [up][enqueueUp] events that respectively indicate that another
+ * finger started touching the screen, a finger moved around or a finger was lifted up from
+ * the screen. A gesture is finished when [up][enqueueUp] lifts the last remaining finger
+ * from the screen, or when a single [cancel][enqueueCancel] event is generated.
+ *
+ * Partial gestures don't have to be defined all in the same [performGesture] block, but
+ * keep in mind that while the gesture is not complete, all code you execute in between
+ * blocks that progress the gesture, will be executed while imaginary fingers are actively
+ * touching the screen. All events generated during a single [performGesture] block are sent
+ * together at the end of that block.
+ *
+ * In the context of testing, it is not necessary to complete a gesture with an up or cancel
+ * event, if the test ends before it expects the finger to be lifted from the screen.
+ *
+ * @param pointerId The id of the pointer, can be any number not yet in use by another pointer
+ * @param position The coordinate of the down event
+ *
+ * @see movePointer
+ * @see enqueueMove
+ * @see enqueueUp
+ * @see enqueueCancel
+ */
+ fun enqueueDown(pointerId: Int, position: Offset)
- abstract fun enqueueUp(pointerId: Int, delay: Long = 0)
+ /**
+ * Generates an up event for the given [pointerId] at the current position of that pointer,
+ * [delay] milliseconds after the previous injected event of this gesture. The default
+ * [delay] is 0 milliseconds. The generated event is enqueued in this [InputDispatcher] and
+ * will be sent when [sendAllSynchronous] is called at the end of [performGesture]. See
+ * [enqueueDown] for more information on how to make complete gestures from partial gestures.
+ *
+ * @param pointerId The id of the pointer to lift up, as supplied in [enqueueDown]
+ * @param delay The time in milliseconds between the previously injected event and the move
+ * event. 0 milliseconds by default.
+ *
+ * @see enqueueDown
+ * @see movePointer
+ * @see enqueueMove
+ * @see enqueueCancel
+ */
+ fun enqueueUp(pointerId: Int, delay: Long = 0)
- abstract fun enqueueCancel(delay: Long = eventPeriod)
+ /**
+ * Generates a cancel event [delay] milliseconds after the previous injected event of this
+ * gesture. The default [delay] is [10 milliseconds][InputDispatcher.eventPeriod]. The
+ * generated event is enqueued in this [InputDispatcher] and will be sent when
+ * [sendAllSynchronous] is called at the end of [performGesture]. See [enqueueDown] for more
+ * information on how to make complete gestures from partial gestures.
+ *
+ * @param delay The time in milliseconds between the previously injected event and the cancel
+ * event. [10 milliseconds][InputDispatcher.eventPeriod] by default.
+ *
+ * @see enqueueDown
+ * @see movePointer
+ * @see enqueueMove
+ * @see enqueueUp
+ */
+ fun enqueueCancel(delay: Long = eventPeriod)
- abstract fun movePointer(pointerId: Int, position: Offset)
+ /**
+ * Updates the position of the pointer with the given [pointerId] to the given [position],
+ * but does not generate a move event. Use this to move multiple pointers simultaneously. To
+ * generate the next move event, which will contain the current position of _all_ pointers
+ * (not just the moved ones), call [enqueueMove] without arguments. If you move one or more
+ * pointers and then call [enqueueDown] or [enqueueUp], without calling [enqueueMove] first,
+ * a move event will be generated right before that down or up event. See [enqueueDown] for
+ * more information on how to make complete gestures from partial gestures.
+ *
+ * @param pointerId The id of the pointer to move, as supplied in [enqueueDown]
+ * @param position The position to move the pointer to
+ *
+ * @see enqueueDown
+ * @see enqueueMove
+ * @see enqueueUp
+ * @see enqueueCancel
+ */
+ fun movePointer(pointerId: Int, position: Offset)
- abstract fun enqueueMove(delay: Long = eventPeriod)
+ /**
+ * Generates a move event [delay] milliseconds after the previous injected event of this
+ * gesture, without moving any of the pointers. The default [delay] is [10 milliseconds]
+ * [eventPeriod]. Use this to commit all changes in pointer location made
+ * with [movePointer]. The generated event will contain the current position of all pointers.
+ * It is enqueued in this [InputDispatcher] and will be sent when [sendAllSynchronous] is
+ * called at the end of [performGesture]. See [enqueueDown] for more information on how to
+ * make complete gestures from partial gestures.
+ *
+ * @param delay The time in milliseconds between the previously injected event and the move
+ * event. [10 milliseconds][eventPeriod] by default.
+ */
+ fun enqueueMove(delay: Long = eventPeriod)
- abstract fun getCurrentPosition(pointerId: Int): Offset?
+ /**
+ * During a partial gesture, returns the position of the last touch event of the given
+ * [pointerId]. Returns `null` if no partial gesture is in progress for that [pointerId].
+ *
+ * @param pointerId The id of the pointer for which to return the current position
+ * @return The current position of the pointer with the given [pointerId], or `null` if the
+ * pointer is not currently in use
+ */
+ fun getCurrentPosition(pointerId: Int): Offset?
}
-/**
- * The state of an [InputDispatcher], saved when the [GestureScope] is disposed and restored when
- * the [GestureScope] is recreated.
- *
- * @param nextDownTime The downTime of the start of the next gesture, when chaining gestures.
- * This property will only be restored if an incomplete gesture was in progress when the state of
- * the [InputDispatcher] was saved.
- * @param gestureLateness The time difference in milliseconds between enqueuing the first event
- * of the gesture and dispatching it. Depending on the implementation of [InputDispatcher], this
- * may or may not be used.
- * @param partialGesture The state of an incomplete gesture. If no gesture was in progress
- * when the state of the [InputDispatcher] was saved, this will be `null`.
- */
-internal data class InputDispatcherState(
- val nextDownTime: Long,
- var gestureLateness: Long?,
- val partialGesture: PartialGesture?
-)
-
-internal expect class PartialGesture(downTime: Long, startPosition: Offset, pointerId: Int)
-
-internal expect fun InputDispatcher(owner: Owner): InputDispatcher
\ No newline at end of file
+internal expect fun InputDispatcher(owner: Owner): InputDispatcher
diff --git a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopAnimationClockTestRule.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopAnimationClockTestRule.kt
new file mode 100644
index 0000000..cae407e
--- /dev/null
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopAnimationClockTestRule.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+import androidx.compose.animation.core.AnimationClockObserver
+import androidx.compose.animation.core.InternalAnimationApi
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+internal class DesktopTestAnimationClock : TestAnimationClock {
+ override val isIdle: Boolean
+ get() = TODO("Not yet implemented")
+
+ override fun pauseClock() {
+ TODO("Not yet implemented")
+ }
+
+ override fun resumeClock() {
+ TODO("Not yet implemented")
+ }
+
+ override val isPaused: Boolean
+ get() = TODO("Not yet implemented")
+
+ override fun advanceClock(milliseconds: Long) {
+ TODO("Not yet implemented")
+ }
+
+ override fun subscribe(observer: AnimationClockObserver) {
+ TODO("Not yet implemented")
+ }
+
+ override fun unsubscribe(observer: AnimationClockObserver) {
+ TODO("Not yet implemented")
+ }
+}
+
+internal class DesktopAnimationClockTestRule : AnimationClockTestRule {
+
+ override val clock: TestAnimationClock get() = DesktopTestAnimationClock()
+
+ /**
+ * Convenience property for calling [`clock.isPaused`][TestAnimationClock.isPaused]
+ */
+ override val isPaused: Boolean get() = clock.isPaused
+
+ /**
+ * Convenience method for calling [`clock.pauseClock()`][TestAnimationClock.pauseClock]
+ */
+ override fun pauseClock() = clock.pauseClock()
+
+ /**
+ * Convenience method for calling [`clock.resumeClock()`][TestAnimationClock.resumeClock]
+ */
+ override fun resumeClock() = clock.resumeClock()
+
+ /**
+ * Convenience method for calling [`clock.advanceClock()`][TestAnimationClock.advanceClock]
+ */
+ override fun advanceClock(milliseconds: Long) = clock.advanceClock(milliseconds)
+
+ override fun apply(base: Statement, description: Description?): Statement {
+ return AnimationClockStatement(base)
+ }
+
+ @OptIn(InternalAnimationApi::class)
+ private inner class AnimationClockStatement(private val base: Statement) : Statement() {
+ override fun evaluate() {
+ base.evaluate()
+ }
+ }
+}
+
+actual fun createAnimationClockRule(): AnimationClockTestRule =
+ DesktopAnimationClockTestRule()
\ No newline at end of file
diff --git a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopAssertions.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopAssertions.kt
new file mode 100644
index 0000000..9b23a2e
--- /dev/null
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopAssertions.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.semantics.SemanticsNode
+
+internal actual fun SemanticsNodeInteraction.checkIsDisplayed(): Boolean {
+ TODO()
+}
+
+internal actual fun SemanticsNode.clippedNodeBoundsInWindow(): Rect {
+ TODO()
+}
+
+internal actual fun SemanticsNode.isInScreenBounds(): Boolean {
+ TODO()
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopComposeTestRule.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopComposeTestRule.kt
new file mode 100644
index 0000000..023bab3
--- /dev/null
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopComposeTestRule.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.EmbeddingContext
+import androidx.compose.runtime.EmbeddingContextFactory
+import androidx.compose.runtime.ExperimentalComposeApi
+import androidx.compose.runtime.Recomposer
+import androidx.compose.runtime.dispatch.DesktopUiDispatcher
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.ui.platform.DesktopOwner
+import androidx.compose.ui.platform.DesktopOwners
+import androidx.compose.ui.platform.setContent
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import kotlinx.coroutines.Dispatchers
+import org.jetbrains.skija.Surface
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import java.awt.Component
+import java.util.LinkedList
+
+actual fun createComposeRule(
+ disableTransitions: Boolean,
+ disableBlinkingCursor: Boolean
+): ComposeTestRule {
+ return DesktopComposeTestRule(
+ disableTransitions,
+ disableBlinkingCursor
+ )
+}
+
+class DesktopComposeTestRule(
+ private val disableTransitions: Boolean = false,
+ private val disableBlinkingCursor: Boolean = true
+) : ComposeTestRule, EmbeddingContext {
+
+ companion object {
+ init {
+ initCompose()
+ }
+
+ var current: DesktopComposeTestRule? = null
+ }
+
+ var owners: DesktopOwners? = null
+
+ override val clockTestRule: AnimationClockTestRule = DesktopAnimationClockTestRule()
+
+ override val density: Density
+ get() = TODO()
+
+ override val displaySize: IntSize get() = IntSize(1024, 768)
+
+ val executionQueue = LinkedList<() -> Unit>()
+
+ override fun apply(base: Statement, description: Description?): Statement {
+ current = this
+ return object : Statement() {
+ override fun evaluate() {
+ EmbeddingContextFactory = fun() = this@DesktopComposeTestRule
+ base.evaluate()
+ runExecutionQueue()
+ }
+ }
+ }
+
+ private fun runExecutionQueue() {
+ while (executionQueue.isNotEmpty()) {
+ executionQueue.removeFirst()()
+ }
+ }
+
+ @OptIn(ExperimentalComposeApi::class)
+ private fun isIdle() =
+ !DesktopUiDispatcher.Dispatcher.hasPendingChanges() &&
+ !Snapshot.current.hasPendingChanges() &&
+ !Recomposer.current().hasPendingChanges()
+
+ internal fun waitForIdle() {
+ while (!isIdle()) {
+ DesktopUiDispatcher.Dispatcher.runAllCallbacks()
+ runExecutionQueue()
+ Thread.sleep(10)
+ }
+ }
+
+ override fun setContent(composable: @Composable () -> Unit) {
+ val surface = Surface.makeRasterN32Premul(displaySize.width, displaySize.height)
+ val canvas = surface.canvas
+ val component = object : Component() {}
+ val owners = DesktopOwners(component = component, redraw = {}).also {
+ owners = it
+ }
+ val owner = DesktopOwner(owners)
+ owner.setContent(composable)
+ owner.setSize(displaySize.width, displaySize.height)
+ owner.draw(canvas)
+ }
+
+ override fun isMainThread() = true
+ override fun mainThreadCompositionContext() = Dispatchers.Default
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopInputDispatcher.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopInputDispatcher.kt
new file mode 100644
index 0000000..f181b8b
--- /dev/null
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopInputDispatcher.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+import androidx.compose.ui.input.pointer.PointerId
+import androidx.compose.ui.input.pointer.PointerInputData
+import androidx.compose.ui.input.pointer.PointerInputEvent
+import androidx.compose.ui.input.pointer.PointerInputEventData
+import androidx.compose.ui.node.Owner
+import androidx.compose.ui.platform.DesktopOwner
+import androidx.compose.ui.unit.Uptime
+
+internal actual fun InputDispatcher(owner: Owner): InputDispatcher {
+ return DesktopInputDispatcher(owner as DesktopOwner).apply {
+ BaseInputDispatcher.states.remove(owner)?.also {
+ // TODO(b/157653315): Move restore state to constructor
+ if (it.partialGesture != null) {
+ nextDownTime = it.nextDownTime
+ partialGesture = it.partialGesture
+ }
+ }
+ }
+}
+
+internal class DesktopInputDispatcher(val owner: DesktopOwner) : BaseInputDispatcher() {
+ companion object {
+ var gesturePointerId = 0L
+ }
+
+ override val now: Long get() = System.nanoTime() / 1_000_000
+
+ override fun saveState(owner: Owner?) {
+ if (owner != null) {
+ states[owner] = InputDispatcherState(nextDownTime, gestureLateness, partialGesture)
+ }
+ }
+
+ private var isMousePressed = false
+
+ private var batchedEvents = mutableListOf<PointerInputEvent>()
+
+ override fun PartialGesture.enqueueDown(pointerId: Int) {
+ isMousePressed = true
+ enqueueEvent(pointerInputEvent(isMousePressed))
+ }
+ override fun PartialGesture.enqueueMove() {
+ enqueueEvent(pointerInputEvent(isMousePressed))
+ }
+
+ override fun PartialGesture.enqueueUp(pointerId: Int) {
+ isMousePressed = false
+ enqueueEvent(pointerInputEvent(isMousePressed))
+ gesturePointerId += 1
+ }
+
+ override fun PartialGesture.enqueueCancel() {
+ println("PartialGesture.sendCancel")
+ }
+
+ private fun enqueueEvent(event: PointerInputEvent) {
+ batchedEvents.add(event)
+ }
+
+ private fun PartialGesture.pointerInputEvent(down: Boolean): PointerInputEvent {
+ val time = Uptime(lastEventTime * 1_000_000)
+ val offset = lastPositions.valueAt(0)
+ val event = PointerInputEvent(
+ time,
+ listOf(
+ PointerInputEventData(
+ PointerId(gesturePointerId),
+ PointerInputData(
+ time,
+ offset,
+ down
+ )
+ )
+ )
+ )
+ return event
+ }
+
+ override fun sendAllSynchronous() {
+ val copy = batchedEvents.toList()
+ batchedEvents.clear()
+ copy.forEach {
+ if (InputDispatcher.dispatchInRealTime) {
+ val delayMs = (it.uptime.nanoseconds / 1_000_000) - now
+ if (delayMs > 0) {
+ Thread.sleep(delayMs)
+ }
+ }
+ owner.processPointerInput(it)
+ }
+ }
+
+ override fun dispose() {
+ batchedEvents.clear()
+ }
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopOutput.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopOutput.kt
new file mode 100644
index 0000000..6c3edbb
--- /dev/null
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopOutput.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+internal actual fun printToLog(tag: String, message: String) {
+ TODO()
+}
diff --git a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopSemanticsNodeInteractions.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopSemanticsNodeInteractions.kt
new file mode 100644
index 0000000..57bbffc
--- /dev/null
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopSemanticsNodeInteractions.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+import androidx.compose.ui.semantics.SemanticsNode
+import androidx.compose.ui.semantics.getAllSemanticsNodes
+
+internal actual fun getAllSemanticsNodes(mergingEnabled: Boolean): List<SemanticsNode> {
+ return DesktopComposeTestRule.current!!.owners!!.list.flatMap {
+ it.semanticsOwner.getAllSemanticsNodes(mergingEnabled) }
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopSynchronization.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopSynchronization.kt
new file mode 100644
index 0000000..6f2c327
--- /dev/null
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopSynchronization.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+internal actual fun <T> actualRunOnUiThread(action: () -> T): T {
+ val result = action()
+ actualWaitForIdle()
+ return result
+}
+
+internal actual fun actualWaitForIdle() {
+ DesktopComposeTestRule.current!!.waitForIdle()
+}
\ No newline at end of file
diff --git a/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopTextActions.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopTextActions.kt
new file mode 100644
index 0000000..5461ce6
--- /dev/null
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/DesktopTextActions.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+import androidx.compose.ui.semantics.SemanticsNode
+import androidx.compose.ui.text.input.EditOperation
+import androidx.compose.ui.text.input.ImeAction
+
+internal actual fun SemanticsNodeInteraction.actualPerformImeAction(
+ node: SemanticsNode,
+ actionSpecified: ImeAction
+) {
+ TODO()
+}
+
+internal actual fun SemanticsNodeInteraction.actualSendTextInputCommand(
+ node: SemanticsNode,
+ command: List<EditOperation>
+) {
+ TODO()
+}
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/test/SkijaTest.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/SkijaTest.kt
similarity index 87%
rename from compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/test/SkijaTest.kt
rename to ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/SkijaTest.kt
index 746f833..eac5449 100644
--- a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/test/SkijaTest.kt
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/SkijaTest.kt
@@ -1,14 +1,30 @@
-package androidx.compose.desktop.test
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.ui.test
import androidx.compose.runtime.EmbeddingContext
import androidx.compose.runtime.EmbeddingContextFactory
import kotlinx.coroutines.Dispatchers
import org.jetbrains.skija.Surface
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
+import org.jetbrains.skiko.Library
import java.io.File
import java.security.MessageDigest
+import org.junit.rules.TestRule
+import org.junit.runners.model.Statement
+import org.junit.runner.Description
import java.util.LinkedList
// TODO: replace with androidx.test.screenshot.proto.ScreenshotResultProto after MPP
@@ -150,11 +166,28 @@
return ScreenshotTestRule(GoldenConfig(fsGoldenPath, repoGoldenPath, modulePath))
}
+fun initCompose() {
+ ComposeInit
+}
+
+private object ComposeInit {
+ init {
+ Library.load("/", "skiko")
+ System.getProperties().setProperty("kotlinx.coroutines.fast.service.loader", "false")
+ }
+}
+
class ScreenshotTestRule internal constructor(val config: GoldenConfig) : TestRule,
EmbeddingContext {
private lateinit var testIdentifier: String
private lateinit var album: SkijaTestAlbum
+ companion object {
+ init {
+ initCompose()
+ }
+ }
+
val executionQueue = LinkedList<() -> Unit>()
override fun apply(base: Statement, description: Description?): Statement {
diff --git a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/test/TestSkiaWindow.kt b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/TestSkiaWindow.kt
similarity index 81%
rename from compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/test/TestSkiaWindow.kt
rename to ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/TestSkiaWindow.kt
index fddf599..6e1680b 100644
--- a/compose/desktop/desktop/src/jvmMain/kotlin/androidx/compose/desktop/test/TestSkiaWindow.kt
+++ b/ui/ui-test/src/desktopMain/kotlin/androidx/ui/test/TestSkiaWindow.kt
@@ -13,15 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.compose.desktop.test
-import androidx.compose.desktop.initCompose
+package androidx.ui.test
+
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.DesktopOwner
import androidx.compose.ui.platform.DesktopOwners
import androidx.compose.ui.platform.setContent
import org.jetbrains.skija.Canvas
import org.jetbrains.skija.Surface
+import org.jetbrains.skiko.Library
import java.awt.Component
// TODO(demin): replace by androidx.compose.ui.test.TestComposeWindow when it will be
@@ -39,7 +40,10 @@
companion object {
init {
- initCompose()
+ Library.load("/", "skiko")
+ // Until https://github.com/Kotlin/kotlinx.coroutines/issues/2039 is resolved
+ // we have to set this property manually for coroutines to work.
+ System.getProperties().setProperty("kotlinx.coroutines.fast.service.loader", "false")
}
}
diff --git a/ui/ui-test/src/jvmMain/kotlin/androidx/ui/test/AnimationClockTestRule.kt b/ui/ui-test/src/jvmMain/kotlin/androidx/ui/test/AnimationClockTestRule.kt
new file mode 100644
index 0000000..1ae658b
--- /dev/null
+++ b/ui/ui-test/src/jvmMain/kotlin/androidx/ui/test/AnimationClockTestRule.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+import org.junit.rules.TestRule
+
+interface AnimationClockTestRule : TestRule {
+ /**
+ * The ambient animation clock that is provided at the root of the composition tree.
+ */
+ val clock: TestAnimationClock
+
+ /**
+ * Convenience property for calling [`clock.isPaused`][TestAnimationClock.isPaused]
+ */
+ val isPaused: Boolean
+
+ /**
+ * Convenience method for calling [`clock.pauseClock()`][TestAnimationClock.pauseClock]
+ */
+ fun pauseClock()
+
+ /**
+ * Convenience method for calling [`clock.resumeClock()`][TestAnimationClock.resumeClock]
+ */
+ fun resumeClock() = clock.resumeClock()
+
+ /**
+ * Convenience method for calling [`clock.advanceClock()`][TestAnimationClock.advanceClock]
+ */
+ fun advanceClock(milliseconds: Long) = clock.advanceClock(milliseconds)
+}
+
+expect fun createAnimationClockRule(): AnimationClockTestRule
\ No newline at end of file
diff --git a/ui/ui-test/src/jvmMain/kotlin/androidx/ui/test/BaseInputDispatcher.kt b/ui/ui-test/src/jvmMain/kotlin/androidx/ui/test/BaseInputDispatcher.kt
new file mode 100644
index 0000000..81a2e69
--- /dev/null
+++ b/ui/ui-test/src/jvmMain/kotlin/androidx/ui/test/BaseInputDispatcher.kt
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ui.test
+
+import androidx.collection.SparseArrayCompat
+import java.util.WeakHashMap
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.lerp
+import androidx.compose.ui.node.Owner
+import androidx.compose.ui.unit.Duration
+import androidx.compose.ui.unit.inMilliseconds
+import androidx.compose.ui.unit.milliseconds
+import kotlin.math.max
+import kotlin.math.roundToInt
+
+internal abstract class BaseInputDispatcher : InputDispatcher {
+ companion object {
+ /**
+ * Indicates that [nextDownTime] is not set
+ */
+ private const val DownTimeNotSet = -1L
+
+ /**
+ * Stores the [InputDispatcherState] of each [Owner]. The state will be restored in an
+ * [InputDispatcher] when it is created for an owner that has a state stored.
+ */
+ internal val states = WeakHashMap<Owner, InputDispatcherState>()
+ }
+
+ internal var nextDownTime = DownTimeNotSet
+
+ /**
+ * The time difference between enqueuing the first event of the gesture and dispatching it.
+ *
+ * When the first event of a gesture is enqueued, its eventTime is fixed to the current time.
+ * However, there is inevitably some time between enqueuing and dispatching of that event.
+ * This means that event is going to be "late" by [gestureLateness] milliseconds when it is
+ * dispatched. Because the dispatcher wants to align events with the current time, it will
+ * dispatch all events that are late immediately and without delay, until it has reached an
+ * event whose eventTime is in the future (i.e. an event that is "early").
+ *
+ * The [gestureLateness] will be used to offset all events, effectively aligning the first
+ * event with the dispatch time.
+ */
+ internal var gestureLateness: Long? = null
+
+ internal var partialGesture: PartialGesture? = null
+
+ /**
+ * Indicates if a gesture is in progress or not. A gesture is in progress if at least one
+ * finger is (still) touching the screen.
+ */
+ val isGestureInProgress: Boolean
+ get() = partialGesture != null
+
+ abstract override val now: Long
+
+ /**
+ * Generates the downTime of the next gesture with the given [duration]. The gesture's
+ * [duration] is necessary to facilitate chaining of gestures: if another gesture is made
+ * after the next one, it will start exactly [duration] after the start of the next gesture.
+ * Always use this method to determine the downTime of the [down event][enqueueDown] of a
+ * gesture.
+ *
+ * If the duration is unknown when calling this method, use a duration of zero and update
+ * with [moveNextDownTime] when the duration is known, or use [moveNextDownTime]
+ * incrementally if the gesture unfolds gradually.
+ */
+ private fun generateDownTime(duration: Duration): Long {
+ val downTime = if (nextDownTime == DownTimeNotSet) {
+ now
+ } else {
+ nextDownTime
+ }
+ nextDownTime = downTime + duration.inMilliseconds()
+ return downTime
+ }
+
+ /**
+ * Moves the start time of the next gesture ahead by the given [duration]. Does not affect
+ * any event time from the current gesture. Use this when the expected duration passed to
+ * [generateDownTime] has changed.
+ */
+ private fun moveNextDownTime(duration: Duration) {
+ generateDownTime(duration)
+ }
+
+ /**
+ * Increases the eventTime with the given [time]. Also pushes the downTime for the next
+ * chained gesture by the same amount to facilitate chaining.
+ */
+ private fun PartialGesture.increaseEventTime(time: Long = InputDispatcher.eventPeriod) {
+ moveNextDownTime(time.milliseconds)
+ lastEventTime += time
+ }
+
+ override fun enqueueDelay(duration: Duration) {
+ require(duration >= Duration.Zero) {
+ "duration of a delay can only be positive, not $duration"
+ }
+ moveNextDownTime(duration)
+ }
+
+ override fun enqueueClick(position: Offset) {
+ enqueueDown(0, position)
+ enqueueMove()
+ enqueueUp(0)
+ }
+
+ override fun enqueueSwipe(start: Offset, end: Offset, duration: Duration) {
+ val durationFloat = duration.inMilliseconds().toFloat()
+ enqueueSwipe(
+ curve = { lerp(start, end, it / durationFloat) },
+ duration = duration
+ )
+ }
+
+ override fun enqueueSwipe(
+ curve: (Long) -> Offset,
+ duration: Duration,
+ keyTimes: List<Long>
+ ) {
+ enqueueSwipes(listOf(curve), duration, keyTimes)
+ }
+
+ override fun enqueueSwipes(
+ curves: List<(Long) -> Offset>,
+ duration: Duration,
+ keyTimes: List<Long>
+ ) {
+ val startTime = 0L
+ val endTime = duration.inMilliseconds()
+ // Validate input
+ require(duration >= 1.milliseconds) {
+ "duration must be at least 1 millisecond, not $duration"
+ }
+ val validRange = startTime..endTime
+ require(keyTimes.all { it in validRange }) {
+ "keyTimes contains timestamps out of range [$startTime..$endTime]: $keyTimes"
+ }
+ require(keyTimes.asSequence().zipWithNext { a, b -> a <= b }.all { it }) {
+ "keyTimes must be sorted: $keyTimes"
+ }
+ // Send down events
+ curves.forEachIndexed { i, curve ->
+ enqueueDown(i, curve(startTime))
+ }
+ // Send move events between each consecutive pair in [t0, ..keyTimes, tN]
+ var currTime = startTime
+ var key = 0
+ while (currTime < endTime) {
+ // advance key
+ while (key < keyTimes.size && keyTimes[key] <= currTime) {
+ key++
+ }
+ // send events between t and next keyTime
+ val tNext = if (key < keyTimes.size) keyTimes[key] else endTime
+ sendPartialSwipes(curves, currTime, tNext)
+ currTime = tNext
+ }
+ // And end with up events
+ repeat(curves.size) {
+ enqueueUp(it)
+ }
+ }
+
+ /**
+ * Generates move events between `f([t0])` and `f([tN])` during the time window `(downTime +
+ * t0, downTime + tN]`, using [fs] to sample the coordinate of each event. The number of
+ * events sent (#numEvents) is such that the time between each event is as close to
+ * [InputDispatcher.eventPeriod] as possible, but at least 1. The first event is sent at time
+ * `downTime + (tN - t0) / #numEvents`, the last event is sent at time tN.
+ *
+ * @param fs The functions that define the coordinates of the respective gestures over time
+ * @param t0 The start time of this segment of the swipe, in milliseconds relative to downTime
+ * @param tN The end time of this segment of the swipe, in milliseconds relative to downTime
+ */
+ private fun sendPartialSwipes(
+ fs: List<(Long) -> Offset>,
+ t0: Long,
+ tN: Long
+ ) {
+ var step = 0
+ // How many steps will we take between t0 and tN? At least 1, and a number that will
+ // bring as as close to eventPeriod as possible
+ val steps = max(1, ((tN - t0) / InputDispatcher.eventPeriod.toFloat()).roundToInt())
+ var tPrev = t0
+ while (step++ < steps) {
+ val progress = step / steps.toFloat()
+ val t = androidx.compose.ui.util.lerp(t0, tN, progress)
+ fs.forEachIndexed { i, f ->
+ movePointer(i, f(t))
+ }
+ enqueueMove(t - tPrev)
+ tPrev = t
+ }
+ }
+
+ override fun getCurrentPosition(pointerId: Int): Offset? {
+ return partialGesture?.lastPositions?.get(pointerId)
+ }
+
+ override fun enqueueDown(pointerId: Int, position: Offset) {
+ var gesture = partialGesture
+ // Check if this pointer is not already down
+ require(gesture == null || !gesture.lastPositions.containsKey(pointerId)) {
+ "Cannot send DOWN event, a gesture is already in progress for pointer $pointerId"
+ }
+ gesture?.flushPointerUpdates()
+ // Start a new gesture, or add the pointerId to the existing gesture
+ if (gesture == null) {
+ gesture = PartialGesture(generateDownTime(0.milliseconds), position, pointerId)
+ partialGesture = gesture
+ } else {
+ gesture.lastPositions.put(pointerId, position)
+ }
+ // Send the DOWN event
+ gesture.enqueueDown(pointerId)
+ }
+
+ override fun movePointer(pointerId: Int, position: Offset) {
+ val gesture = partialGesture
+ // Check if this pointer is in the gesture
+ check(gesture != null) {
+ "Cannot move pointers, no gesture is in progress"
+ }
+ require(gesture.lastPositions.containsKey(pointerId)) {
+ "Cannot move pointer $pointerId, it is not active in the current gesture"
+ }
+ gesture.lastPositions.put(pointerId, position)
+ gesture.hasPointerUpdates = true
+ }
+
+ override fun enqueueMove(delay: Long) {
+ val gesture = checkNotNull(partialGesture) {
+ "Cannot send MOVE event, no gesture is in progress"
+ }
+ require(delay >= 0) {
+ "Cannot send MOVE event with a delay of $delay ms"
+ }
+ gesture.increaseEventTime(delay)
+ gesture.enqueueMove()
+ gesture.hasPointerUpdates = false
+ }
+
+ override fun enqueueUp(pointerId: Int, delay: Long) {
+ val gesture = partialGesture
+ // Check if this pointer is in the gesture
+ check(gesture != null) {
+ "Cannot send UP event, no gesture is in progress"
+ }
+ require(gesture.lastPositions.containsKey(pointerId)) {
+ "Cannot send UP event for pointer $pointerId, it is not active in the current gesture"
+ }
+ require(delay >= 0) {
+ "Cannot send UP event with a delay of $delay ms"
+ }
+ gesture.flushPointerUpdates()
+ gesture.increaseEventTime(delay)
+ // First send the UP event
+ gesture.enqueueUp(pointerId)
+ // Then remove the pointer, and end the gesture if no pointers are left
+ gesture.lastPositions.remove(pointerId)
+ if (gesture.lastPositions.isEmpty) {
+ partialGesture = null
+ }
+ }
+
+ override fun enqueueCancel(delay: Long) {
+ val gesture = checkNotNull(partialGesture) {
+ "Cannot send CANCEL event, no gesture is in progress"
+ }
+ require(delay >= 0) {
+ "Cannot send CANCEL event with a delay of $delay ms"
+ }
+ gesture.increaseEventTime(delay)
+ gesture.enqueueCancel()
+ partialGesture = null
+ }
+
+ /**
+ * Generates a MOVE event with all pointer locations, if any of the pointers has been moved by
+ * [movePointer] since the last MOVE event.
+ */
+ private fun PartialGesture.flushPointerUpdates() {
+ if (hasPointerUpdates) {
+ enqueueMove(InputDispatcher.eventPeriod)
+ }
+ }
+
+ protected abstract fun PartialGesture.enqueueDown(pointerId: Int)
+
+ protected abstract fun PartialGesture.enqueueMove()
+
+ protected abstract fun PartialGesture.enqueueUp(pointerId: Int)
+
+ protected abstract fun PartialGesture.enqueueCancel()
+}
+
+/**
+ * The state of an [InputDispatcher], saved when the [GestureScope] is disposed and restored when
+ * the [GestureScope] is recreated.
+ *
+ * @param nextDownTime The downTime of the start of the next gesture, when chaining gestures.
+ * This property will only be restored if an incomplete gesture was in progress when the state of
+ * the [InputDispatcher] was saved.
+ * @param gestureLateness The time difference in milliseconds between enqueuing the first event
+ * of the gesture and dispatching it. Depending on the implementation of [InputDispatcher], this
+ * may or may not be used.
+ * @param partialGesture The state of an incomplete gesture. If no gesture was in progress
+ * when the state of the [InputDispatcher] was saved, this will be `null`.
+ */
+internal data class InputDispatcherState(
+ val nextDownTime: Long,
+ var gestureLateness: Long?,
+ val partialGesture: PartialGesture?
+)
+
+internal class PartialGesture constructor(
+ val downTime: Long,
+ startPosition: Offset,
+ pointerId: Int
+) {
+ var lastEventTime: Long = downTime
+ var hasPointerUpdates: Boolean = false
+ val lastPositions = SparseArrayCompat<Offset>().apply { put(pointerId, startPosition) }
+}
diff --git a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/ComposeTestRule.kt b/ui/ui-test/src/jvmMain/kotlin/androidx/ui/test/ComposeTestRule.kt
similarity index 86%
rename from ui/ui-test/src/androidMain/kotlin/androidx/ui/test/ComposeTestRule.kt
rename to ui/ui-test/src/jvmMain/kotlin/androidx/ui/test/ComposeTestRule.kt
index 03b81d9..9f007b5 100644
--- a/ui/ui-test/src/androidMain/kotlin/androidx/ui/test/ComposeTestRule.kt
+++ b/ui/ui-test/src/jvmMain/kotlin/androidx/ui/test/ComposeTestRule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,9 @@
package androidx.ui.test
-import android.util.DisplayMetrics
-import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
-import androidx.ui.test.android.createAndroidComposeRule
import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
import org.junit.rules.TestRule
/**
@@ -53,7 +51,7 @@
fun setContent(composable: @Composable () -> Unit)
// TODO(pavlis): Provide better abstraction for host side reusability
- val displayMetrics: DisplayMetrics get
+ val displaySize: IntSize get
}
/**
@@ -66,10 +64,7 @@
* reference to this activity into the manifest file of the corresponding tests (usually in
* androidTest/AndroidManifest.xml).
*/
-fun createComposeRule(
+expect fun createComposeRule(
disableTransitions: Boolean = false,
disableBlinkingCursor: Boolean = true
-): ComposeTestRule = createAndroidComposeRule<ComponentActivity>(
- disableTransitions,
- disableBlinkingCursor
-)
+): ComposeTestRule