diff --git a/android/app/src/androidTest/java/com/testapp/IsolatedTest.kt b/android/app/src/androidTest/java/com/testapp/IsolatedTest.kt index 8ac5384..bc9a05a 100644 --- a/android/app/src/androidTest/java/com/testapp/IsolatedTest.kt +++ b/android/app/src/androidTest/java/com/testapp/IsolatedTest.kt @@ -1,7 +1,7 @@ package com.rnstorybookautoscreenshots -import android.graphics.Color import android.view.View +import android.view.ViewGroup import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.testapp.MainApplication @@ -9,11 +9,12 @@ import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertTrue import org.junit.Test import org.junit.runner.RunWith -import com.facebook.testing.screenshot.ViewHelpers import com.facebook.testing.screenshot.Screenshot import com.facebook.testing.screenshot.WindowAttachment -import org.junit.Assert.*; +import org.junit.Assert.* import com.facebook.react.interfaces.* +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) class IsolatedTest { @@ -29,19 +30,16 @@ class IsolatedTest { val surface = app.reactHost.createSurface(context, "SimpleTestComponent", null) assertEquals("SimpleTestComponent", surface.moduleName) - // TODO: we aren't 100% sure if prerender() and start() are being called the way we want it to. - // We probably want to create a ReactHost directly instead of taking it from the MainApplication... probably - // Also look up bridge-less mode. assertGoodTask(surface.prerender()) - assertNotNull(surface.view) - ViewHelpers.setupView(surface.view!!) - .setExactHeightPx(1000) - .setExactWidthPx(1000) - .layout() - + val view = surface.view!! + view.measure( + View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY) + ) + view.layout(0, 0, view.measuredWidth, view.measuredHeight) val ti = surface.start() assertGoodTask(ti) @@ -51,72 +49,115 @@ class IsolatedTest { } @Test - fun childCountTest() { + fun childCountScreenshotTest() { val instrumentation = InstrumentationRegistry.getInstrumentation() val context = instrumentation.targetContext val app = context.applicationContext as MainApplication val surface = app.reactHost.createSurface(context, "SimpleTestComponent", null) - val view = surface.view!! + assertGoodTask(surface.prerender()) + + val view = surface.view!! as ViewGroup var detacher: WindowAttachment.Detacher? = null + var startTask: TaskInterface? = null try { instrumentation.runOnMainSync { view.setLayerType(View.LAYER_TYPE_SOFTWARE, null) - view.setBackgroundColor(Color.WHITE) detacher = WindowAttachment.dispatchAttach(view) app.reactHost.onHostResume(null) - ViewHelpers.setupView(view).setExactWidthPx(1080).setExactHeightPx(1920).layout() - surface.start() + view.measure( + View.MeasureSpec.makeMeasureSpec(1080, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(1920, View.MeasureSpec.EXACTLY) + ) + view.layout(0, 0, view.measuredWidth, view.measuredHeight) + startTask = surface.start() } - val deadline = System.currentTimeMillis() + 30_000 - var hasChildren = false - while (!hasChildren && System.currentTimeMillis() < deadline) { - Thread.sleep(50) - instrumentation.runOnMainSync { hasChildren = view.childCount > 0 } - } - assertTrue("Timed out waiting for children", hasChildren) + assertGoodTask(startTask!!) + waitUntil { view.childCount > 0 } + + Screenshot.snap(view).record() } finally { - instrumentation.runOnMainSync { - surface.stop() - detacher?.detach() - } + instrumentation.runOnMainSync { detacher?.detach() } } } @Test - fun childCountScreenshotTest() { + fun childCountSyncTest() { val instrumentation = InstrumentationRegistry.getInstrumentation() val context = instrumentation.targetContext val app = context.applicationContext as MainApplication val surface = app.reactHost.createSurface(context, "SimpleTestComponent", null) - val view = surface.view!! + assertGoodTask(surface.prerender()) + + val view = surface.view!! as ViewGroup var detacher: WindowAttachment.Detacher? = null + var startTask: TaskInterface? = null + try { instrumentation.runOnMainSync { view.setLayerType(View.LAYER_TYPE_SOFTWARE, null) - view.setBackgroundColor(Color.WHITE) detacher = WindowAttachment.dispatchAttach(view) app.reactHost.onHostResume(null) - ViewHelpers.setupView(view).setExactWidthPx(1080).setExactHeightPx(1920).layout() - surface.start() + view.measure( + View.MeasureSpec.makeMeasureSpec(1080, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(1920, View.MeasureSpec.EXACTLY) + ) + view.layout(0, 0, view.measuredWidth, view.measuredHeight) + startTask = surface.start() } - val deadline = System.currentTimeMillis() + 30_000 - var hasChildren = false - while (!hasChildren && System.currentTimeMillis() < deadline) { - Thread.sleep(50) - instrumentation.runOnMainSync { hasChildren = view.childCount > 0 } - } - assertTrue("Timed out waiting for children", hasChildren) + assertGoodTask(startTask!!) + waitUntil { view.childCount > 0 } + assertTrue("Expected childCount > 0, but was ${view.childCount}", view.childCount > 0) + } finally { + instrumentation.runOnMainSync { detacher?.detach() } + } + } + + @Test + fun childCountTest() { + val instrumentation = InstrumentationRegistry.getInstrumentation() + val context = instrumentation.targetContext + val app = context.applicationContext as MainApplication + val surface = app.reactHost.createSurface(context, "SimpleTestComponent", null) + val view = surface.view!! as ViewGroup + var detacher: WindowAttachment.Detacher? = null + var startTask: TaskInterface? = null + val childrenMounted = CompletableFuture() + + try { instrumentation.runOnMainSync { - ViewHelpers.setupView(view).setExactWidthPx(1080).setExactHeightPx(1920).layout() - Screenshot.snap(view).setName("childCountReadyTest").record() + view.setLayerType(View.LAYER_TYPE_SOFTWARE, null) + detacher = WindowAttachment.dispatchAttach(view) + app.reactHost.onHostResume(null) + view.measure( + View.MeasureSpec.makeMeasureSpec(1080, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(1920, View.MeasureSpec.EXACTLY) + ) + view.layout(0, 0, view.measuredWidth, view.measuredHeight) + startTask = surface.start() + // dispatchAttach gives the view a real Handler on the main Looper via AttachInfo, + // so view.post() routes to the main MessageQueue. Poll here (on the main thread, + // between Choreographer frames) until Fabric mounts the first child. + val check = object : Runnable { + override fun run() { + if (view.childCount > 0) childrenMounted.complete(view.childCount) + else view.postDelayed(this, 50) + } + } + view.post(check) } + + // start() fires on the bg executor. assertGoodTask blocks the test thread until + // it completes, keeping the main thread free for Choreographer and our check loop. + assertGoodTask(startTask!!) + val count = childrenMounted.get(5, TimeUnit.SECONDS) + assertTrue("Expected childCount > 0, but was $count", count > 0) } finally { instrumentation.runOnMainSync { surface.stop() @@ -126,7 +167,15 @@ class IsolatedTest { } } -fun assertGoodTask(ti : TaskInterface) { +fun waitUntil(timeoutMs: Long = 5000, condition: () -> Boolean) { + val deadline = System.currentTimeMillis() + timeoutMs + while (!condition()) { + check(System.currentTimeMillis() < deadline) { "Condition not met within ${timeoutMs}ms" } + Thread.sleep(16) + } +} + +fun assertGoodTask(ti: TaskInterface) { ti.waitForCompletion() assertFalse(ti.isFaulted()) assertTrue(ti.isCompleted()) diff --git a/android/app/src/androidTest/java/com/testapp/ScreenshotTest.kt b/android/app/src/androidTest/java/com/testapp/ScreenshotTest.kt index 1dffb5d..72e7754 100644 --- a/android/app/src/androidTest/java/com/testapp/ScreenshotTest.kt +++ b/android/app/src/androidTest/java/com/testapp/ScreenshotTest.kt @@ -10,7 +10,6 @@ import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.GrantPermissionRule import com.facebook.testing.screenshot.Screenshot -import com.facebook.testing.screenshot.ViewHelpers import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -53,11 +52,12 @@ class ScreenshotTest { view.setBackgroundColor(Color.WHITE) view.setPadding(20, 20, 20, 20) - // Measure and layout the view - ViewHelpers.setupView(view) - .setExactWidthDp(300) - .setExactHeightDp(100) - .layout() + val density = view.resources.displayMetrics.density + view.measure( + android.view.View.MeasureSpec.makeMeasureSpec((300 * density).toInt(), android.view.View.MeasureSpec.EXACTLY), + android.view.View.MeasureSpec.makeMeasureSpec((100 * density).toInt(), android.view.View.MeasureSpec.EXACTLY) + ) + view.layout(0, 0, view.measuredWidth, view.measuredHeight) // Take screenshot Screenshot.snap(view)