From feeac6c3db4012d6158f09127982cede5dcc667c Mon Sep 17 00:00:00 2001 From: Laimonas Turauskas Date: Fri, 20 Sep 2024 15:11:12 -0400 Subject: [PATCH] Refactoring android tests to use utils/android (pt4). (#396) * Refactoring android tests to use utils/android (pt4). * Fix coverage. --- .../formula/test/TestFragmentActivity.kt | 45 ------- .../instacart/formula/FormulaFragmentTest.kt | 30 ++--- .../formula/FragmentLifecycleStateTest.kt | 51 ++++---- .../formula/FragmentLifecycleTest.kt | 112 ++++++++---------- .../instacart/formula/TestFeatureFactory.kt | 14 +-- .../formula/android/FragmentStoreTest.kt | 10 +- .../instacart/formula/android/TestUtils.kt | 4 +- .../formula/android/ViewFactoryTest.kt | 45 +++++++ .../formula/android/views/ViewInstanceTest.kt | 47 ++++++++ gradle/jacoco.gradle | 1 - .../testutils/android/HeadlessFragment.kt | 5 + .../testutils/android/NoOpFeatureFactory.kt | 7 +- .../testutils/android/NoOpViewFactory.kt | 11 -- .../testutils/android/TestExtensions.kt | 1 - .../android/TestFragmentExtensions.kt | 47 ++++++++ .../android}/TestFragmentLifecycleCallback.kt | 2 +- .../instacart/testutils/android}/TestKey.kt | 2 +- .../testutils/android/TestViewFactory.kt | 18 +++ 18 files changed, 264 insertions(+), 188 deletions(-) create mode 100644 formula-android/src/test/java/com/instacart/formula/android/ViewFactoryTest.kt create mode 100644 formula-android/src/test/java/com/instacart/formula/android/views/ViewInstanceTest.kt create mode 100644 test-utils/android/src/main/java/com/instacart/testutils/android/HeadlessFragment.kt delete mode 100644 test-utils/android/src/main/java/com/instacart/testutils/android/NoOpViewFactory.kt create mode 100644 test-utils/android/src/main/java/com/instacart/testutils/android/TestFragmentExtensions.kt rename {formula-android-tests/src/main/java/com/instacart/formula/test => test-utils/android/src/main/java/com/instacart/testutils/android}/TestFragmentLifecycleCallback.kt (97%) rename {formula-android-tests/src/main/java/com/instacart/formula/test => test-utils/android/src/main/java/com/instacart/testutils/android}/TestKey.kt (81%) create mode 100644 test-utils/android/src/main/java/com/instacart/testutils/android/TestViewFactory.kt diff --git a/formula-android-tests/src/main/java/com/instacart/formula/test/TestFragmentActivity.kt b/formula-android-tests/src/main/java/com/instacart/formula/test/TestFragmentActivity.kt index 5bdc09d2..eb17abf9 100644 --- a/formula-android-tests/src/main/java/com/instacart/formula/test/TestFragmentActivity.kt +++ b/formula-android-tests/src/main/java/com/instacart/formula/test/TestFragmentActivity.kt @@ -2,60 +2,15 @@ package com.instacart.formula.test import android.os.Bundle import androidx.annotation.VisibleForTesting -import com.instacart.formula.FormulaAndroid -import com.instacart.formula.android.FormulaFragment import com.instacart.formula.android.FragmentKey import com.instacart.formula.android.FormulaAppCompatActivity import com.instacart.testutils.android.R class TestFragmentActivity : FormulaAppCompatActivity() { - @VisibleForTesting lateinit var initialKey: FragmentKey @VisibleForTesting val renderCalls = mutableListOf>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.test_activity) - - if (savedInstanceState == null) { - val fragment = FormulaFragment.newInstance(initialKey) - supportFragmentManager.beginTransaction() - .add(R.id.activity_content, fragment, initialKey.tag) - .addToBackStack(initialKey.tag) - .commit() - } - } - - fun navigateTo(key: FragmentKey, allowStateLoss: Boolean = false) { - val entryIndex = supportFragmentManager.backStackEntryCount - 1 - val fragment = if (entryIndex >= 0) { - val entry = supportFragmentManager.getBackStackEntryAt(entryIndex) - supportFragmentManager.findFragmentByTag(entry.name) - } else { - null - } - - supportFragmentManager.beginTransaction().apply { - if (fragment != null) { - remove(fragment) - } - add(R.id.activity_content, FormulaFragment.newInstance(key), key.tag) - addToBackStack(key.tag) - }.apply { - if (allowStateLoss) { - commitAllowingStateLoss() - } else { - commit() - } - } - } - - override fun onBackPressed() { - if (!FormulaAndroid.onBackPressed(this)) { - if (supportFragmentManager.backStackEntryCount > 1) { - supportFragmentManager.popBackStackImmediate() - } else { - finish() - } - } } } diff --git a/formula-android-tests/src/test/java/com/instacart/formula/FormulaFragmentTest.kt b/formula-android-tests/src/test/java/com/instacart/formula/FormulaFragmentTest.kt index 662c6a41..f1c0dced 100644 --- a/formula-android-tests/src/test/java/com/instacart/formula/FormulaFragmentTest.kt +++ b/formula-android-tests/src/test/java/com/instacart/formula/FormulaFragmentTest.kt @@ -1,7 +1,6 @@ package com.instacart.formula import android.os.Looper -import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.rules.ActivityScenarioRule @@ -16,12 +15,14 @@ import com.instacart.formula.android.FragmentEnvironment import com.instacart.formula.android.FragmentStore import com.instacart.formula.android.events.FragmentLifecycleEvent import com.instacart.formula.test.TestBackCallbackRenderModel -import com.instacart.formula.test.TestKey +import com.instacart.testutils.android.TestKey import com.instacart.formula.test.TestKeyWithId import com.instacart.formula.test.TestFragmentActivity import com.instacart.formula.test.TestLifecycleKey +import com.instacart.testutils.android.HeadlessFragment import com.instacart.testutils.android.activity import com.instacart.testutils.android.get +import com.instacart.testutils.android.showFragment import com.jakewharton.rxrelay3.PublishRelay import io.reactivex.rxjava3.core.Observable import org.junit.Before @@ -38,11 +39,8 @@ import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) class FormulaFragmentTest { - class HeadlessFragment : Fragment() - private var lastState: FragmentState? = null private val stateChangeRelay = PublishRelay.create>() - private var onPreCreated: (TestFragmentActivity) -> Unit = {} private var updateThreads = linkedSetOf() private val errors = mutableListOf() private val fragmentLifecycleEvents = mutableListOf() @@ -56,10 +54,6 @@ class FormulaFragmentTest { FormulaAndroid.init(app, environment) { activity { ActivityStore( - configureActivity = { activity -> - activity.initialKey = TestKey() - onPreCreated(activity) - }, onRenderFragmentState = { a, state -> lastState = state @@ -100,6 +94,7 @@ class FormulaFragmentTest { @Before fun setup() { scenario = activityRule.scenario + scenario.showFragment(TestKey()) } @Test fun `add fragment lifecycle event`() { @@ -257,13 +252,13 @@ class FormulaFragmentTest { // Pass feature updates on a background thread executor.execute { - stateChangeRelay.accept(initial to "main-state-1") - stateChangeRelay.accept(initial to "main-state-2") - stateChangeRelay.accept(initial to "main-state-3") + sendStateUpdate(initial, "main-state-1") + sendStateUpdate(initial, "main-state-2") + sendStateUpdate(initial, "main-state-3") - stateChangeRelay.accept(keyWithId to "detail-state-1") - stateChangeRelay.accept(keyWithId to "detail-state-2") - stateChangeRelay.accept(keyWithId to "detail-state-3") + sendStateUpdate(keyWithId, "detail-state-1") + sendStateUpdate(keyWithId, "detail-state-2") + sendStateUpdate(keyWithId, "detail-state-3") latch.countDown() } @@ -344,9 +339,8 @@ class FormulaFragmentTest { } private fun navigateToTaskDetail(id: Int = 1, allowStateLoss: Boolean = false) { - scenario.onActivity { - it.navigateTo(TestKeyWithId(id), allowStateLoss = allowStateLoss) - } + val fragmentKey = TestKeyWithId(id) + scenario.showFragment(fragmentKey, allowStateLoss) } private fun assertFragmentViewIsCreated(key: FragmentKey) { diff --git a/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleStateTest.kt b/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleStateTest.kt index a61134a6..db710ce7 100644 --- a/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleStateTest.kt +++ b/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleStateTest.kt @@ -3,20 +3,18 @@ package com.instacart.formula import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 import com.instacart.formula.android.ActivityStore -import com.instacart.formula.android.FormulaFragment import com.instacart.formula.android.FragmentStore import com.instacart.formula.android.FragmentKey -import com.instacart.formula.test.TestKey +import com.instacart.testutils.android.TestKey import com.instacart.formula.test.TestKeyWithId -import com.instacart.formula.test.TestFragmentActivity import com.instacart.testutils.android.FormulaAndroidInteractor import com.instacart.testutils.android.NoOpFeatureFactory +import com.instacart.testutils.android.TestFormulaActivity +import com.instacart.testutils.android.showFragment import com.instacart.testutils.android.withFormulaAndroid import io.reactivex.rxjava3.observers.TestObserver import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.annotation.LooperMode -import com.instacart.testutils.android.R as TestR @RunWith(AndroidJUnit4::class) class FragmentLifecycleStateTest { @@ -24,11 +22,8 @@ class FragmentLifecycleStateTest { private fun runTest(continuation: (FormulaAndroidInteractor) -> Unit) { withFormulaAndroid( configure = { - activity { + activity { ActivityStore( - configureActivity = { - it.initialKey = TestKey() - }, fragmentStore = FragmentStore.init { bind(NoOpFeatureFactory()) bind(NoOpFeatureFactory()) @@ -44,8 +39,12 @@ class FragmentLifecycleStateTest { @Test fun `is fragment started`() { runTest { interactor -> - val startedEvents = interactor.startedEvents(TestKey()) - ActivityScenario.launch(TestFragmentActivity::class.java) + val fragmentKey = TestKey() + val startedEvents = interactor.startedEvents(fragmentKey) + + val scenario = ActivityScenario.launch(TestFormulaActivity::class.java) + scenario.showFragment(fragmentKey) + startedEvents.assertValues(false, true) } } @@ -53,25 +52,28 @@ class FragmentLifecycleStateTest { @Test fun `is fragment resumed`() { runTest { interactor -> - val resumedEvents = interactor.resumedEvents(TestKey()) - ActivityScenario.launch(TestFragmentActivity::class.java) + val fragmentKey = TestKey() + val resumedEvents = interactor.resumedEvents(fragmentKey) + val scenario = ActivityScenario.launch(TestFormulaActivity::class.java) + scenario.showFragment(fragmentKey) resumedEvents.assertValues(false, true) } } - @LooperMode(LooperMode.Mode.LEGACY) @Test fun `navigate forward`() { runTest { interactor -> - val initialKeyStartedEvents = interactor.startedEvents(TestKey()) - val initialKeyResumedEvents = interactor.resumedEvents(TestKey()) + val initialKey = TestKey() + val initialKeyStartedEvents = interactor.startedEvents(initialKey) + val initialKeyResumedEvents = interactor.resumedEvents(initialKey) val detailKey = TestKeyWithId(1) val detailKeyStartedEvents = interactor.startedEvents(detailKey) val detailKeyResumedEvents = interactor.resumedEvents(detailKey) - val scenario = ActivityScenario.launch(TestFragmentActivity::class.java) - navigateToTaskDetail(scenario, detailKey) + val scenario = ActivityScenario.launch(TestFormulaActivity::class.java) + scenario.showFragment(initialKey) + scenario.showFragment(detailKey) initialKeyStartedEvents.assertValues(false, true, false) initialKeyResumedEvents.assertValues(false, true, false) @@ -88,17 +90,4 @@ class FragmentLifecycleStateTest { private fun FormulaAndroidInteractor.resumedEvents(key: FragmentKey): TestObserver { return selectEvents { it.isFragmentResumed(key) }.test() } - - private fun navigateToTaskDetail( - scenario: ActivityScenario, - key: TestKeyWithId, - ) { - scenario.onActivity { - it.supportFragmentManager.beginTransaction() - .remove(it.supportFragmentManager.findFragmentByTag(TestKey().tag)!!) - .add(TestR.id.activity_content, FormulaFragment.newInstance(key), key.tag) - .addToBackStack(null) - .commit() - } - } } diff --git a/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleTest.kt b/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleTest.kt index 344ae4dd..bc7dfa9f 100644 --- a/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleTest.kt +++ b/formula-android-tests/src/test/java/com/instacart/formula/FragmentLifecycleTest.kt @@ -1,93 +1,85 @@ package com.instacart.formula -import android.content.Context -import android.content.Intent -import android.os.Bundle import androidx.test.core.app.ActivityScenario -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import com.instacart.formula.android.ActivityStore -import com.instacart.formula.android.Feature -import com.instacart.formula.android.FeatureFactory import com.instacart.formula.android.FormulaFragment import com.instacart.formula.android.FragmentStore -import com.instacart.formula.android.ViewFactory -import com.instacart.formula.test.TestFragmentActivity -import com.instacart.formula.test.TestFragmentLifecycleCallback +import com.instacart.testutils.android.TestFragmentLifecycleCallback import com.instacart.formula.test.TestLifecycleKey -import com.instacart.testutils.android.R as TestR -import io.reactivex.rxjava3.core.Observable -import org.junit.Rule +import com.instacart.testutils.android.NoOpFeatureFactory +import com.instacart.testutils.android.TestFormulaActivity +import com.instacart.testutils.android.TestViewFactory +import com.instacart.testutils.android.showFragment +import com.instacart.testutils.android.withFormulaAndroid import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class FragmentLifecycleTest { - private lateinit var lifecycleCallback: TestFragmentLifecycleCallback - - @get:Rule val formulaRule = TestFormulaRule(initFormula = { app -> - FormulaAndroid.init(app) { - activity { - lifecycleCallback = TestFragmentLifecycleCallback() - ActivityStore( - configureActivity = { activity -> - activity.initialKey = TestLifecycleKey() - }, - fragmentStore = FragmentStore.init { - val featureFactory = object : FeatureFactory { - override fun initialize(dependencies: Unit, key: TestLifecycleKey): Feature { - return Feature( - state = Observable.empty(), - viewFactory = ViewFactory.fromLayout(TestR.layout.test_fragment_layout) { - featureView(lifecycleCallback) {} - } - ) - } - } - bind(featureFactory) - }, - ) + private fun runTest( + continuation: (ActivityScenario, TestFragmentLifecycleCallback) -> Unit + ) { + val lifecycleCallback = TestFragmentLifecycleCallback() + withFormulaAndroid( + configure = { + activity { + ActivityStore( + fragmentStore = FragmentStore.init { + val featureFactory = NoOpFeatureFactory( + viewFactory = TestViewFactory(lifecycleCallback) + ) + bind(featureFactory) + }, + ) + } } + ) { + val scenario = ActivityScenario.launch(TestFormulaActivity::class.java) + scenario.showFragment(TestLifecycleKey()) + continuation(scenario, lifecycleCallback) } - }) + } @Test fun `creation callbacks`() { - ActivityScenario.launch(TestFragmentActivity::class.java) - - assertThat(lifecycleCallback.hasOnViewCreated).isTrue() - assertThat(lifecycleCallback.hasOnActivityCreated).isTrue() - assertThat(lifecycleCallback.hasOnStart).isTrue() - assertThat(lifecycleCallback.hasOnResume).isTrue() + runTest { _, lifecycleCallback -> + assertThat(lifecycleCallback.hasOnViewCreated).isTrue() + assertThat(lifecycleCallback.hasOnActivityCreated).isTrue() + assertThat(lifecycleCallback.hasOnStart).isTrue() + assertThat(lifecycleCallback.hasOnResume).isTrue() + } } @Test fun `destroy callbacks`() { - val scenario = ActivityScenario.launch(TestFragmentActivity::class.java) - scenario.close() + runTest { scenario, lifecycleCallback -> + scenario.close() - assertThat(lifecycleCallback.hasOnPauseEvent).isTrue() - assertThat(lifecycleCallback.hasOnStop).isTrue() - assertThat(lifecycleCallback.hasOnDestroyView).isTrue() + assertThat(lifecycleCallback.hasOnPauseEvent).isTrue() + assertThat(lifecycleCallback.hasOnStop).isTrue() + assertThat(lifecycleCallback.hasOnDestroyView).isTrue() + } } @Test fun `save instance state callback`() { - val scenario = ActivityScenario.launch(TestFragmentActivity::class.java) - - assertThat(lifecycleCallback.hasOnSaveInstanceState).isFalse() - scenario.recreate() - assertThat(lifecycleCallback.hasOnSaveInstanceState).isTrue() + runTest { scenario, lifecycleCallback -> + assertThat(lifecycleCallback.hasOnSaveInstanceState).isFalse() + scenario.recreate() + assertThat(lifecycleCallback.hasOnSaveInstanceState).isTrue() + } } @Test fun `low memory`() { - val scenario = ActivityScenario.launch(TestFragmentActivity::class.java) - scenario.onActivity { - val fragment = it.supportFragmentManager.fragments - .filterIsInstance() - .first() + runTest { scenario, lifecycleCallback -> + scenario.onActivity { + val fragment = it.supportFragmentManager.fragments + .filterIsInstance() + .first() - fragment.onLowMemory() + fragment.onLowMemory() + } + assertThat(lifecycleCallback.hasCalledLowMemory).isTrue() } - assertThat(lifecycleCallback.hasCalledLowMemory).isTrue() } } diff --git a/formula-android-tests/src/test/java/com/instacart/formula/TestFeatureFactory.kt b/formula-android-tests/src/test/java/com/instacart/formula/TestFeatureFactory.kt index a3277329..b771c670 100644 --- a/formula-android-tests/src/test/java/com/instacart/formula/TestFeatureFactory.kt +++ b/formula-android-tests/src/test/java/com/instacart/formula/TestFeatureFactory.kt @@ -3,10 +3,9 @@ package com.instacart.formula import com.instacart.formula.android.Feature import com.instacart.formula.android.FeatureFactory import com.instacart.formula.android.FragmentKey -import com.instacart.formula.android.ViewFactory import com.instacart.formula.test.TestFragmentActivity +import com.instacart.testutils.android.TestViewFactory import io.reactivex.rxjava3.core.Observable -import com.instacart.testutils.android.R as TestR class TestFeatureFactory( private val applyOutput: (Any) -> Unit = {}, @@ -15,14 +14,9 @@ class TestFeatureFactory( override fun initialize(dependencies: Unit, key: Key): Feature { return Feature( state = state(key), - viewFactory = ViewFactory.fromLayout(TestR.layout.test_fragment_layout) { - val renderView = object : RenderView { - override val render: Renderer = Renderer { value -> - (view.context as TestFragmentActivity).renderCalls.add(Pair(key, value)) - applyOutput(value) - } - } - featureView(renderView) + viewFactory = TestViewFactory { view, value -> + (view.context as TestFragmentActivity).renderCalls.add(Pair(key, value)) + applyOutput(value) } ) } diff --git a/formula-android/src/test/java/com/instacart/formula/android/FragmentStoreTest.kt b/formula-android/src/test/java/com/instacart/formula/android/FragmentStoreTest.kt index 051aa67f..87bd3b09 100644 --- a/formula-android/src/test/java/com/instacart/formula/android/FragmentStoreTest.kt +++ b/formula-android/src/test/java/com/instacart/formula/android/FragmentStoreTest.kt @@ -7,7 +7,7 @@ import com.instacart.formula.android.fakes.DetailKey import com.instacart.formula.android.fakes.FakeComponent import com.instacart.formula.android.fakes.MainKey import com.instacart.formula.android.events.FragmentLifecycleEvent -import com.instacart.testutils.android.NoOpViewFactory +import com.instacart.testutils.android.TestViewFactory import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.observers.TestObserver import io.reactivex.rxjava3.subjects.PublishSubject @@ -213,7 +213,7 @@ class FragmentStoreTest { override fun initialize(dependencies: Any, key: MainKey): Feature { return Feature( state = stateSubject, - viewFactory = NoOpViewFactory(), + viewFactory = TestViewFactory(), ) } } @@ -252,7 +252,7 @@ class FragmentStoreTest { override fun initialize(dependencies: Any, key: MainKey): Feature { return Feature( state = stateSubject, - viewFactory = NoOpViewFactory(), + viewFactory = TestViewFactory(), ) } } @@ -297,7 +297,7 @@ class FragmentStoreTest { override fun initialize(dependencies: Any, key: MainKey): Feature { return Feature( state = Observable.just("value"), - viewFactory = NoOpViewFactory(), + viewFactory = TestViewFactory(), ) } } @@ -364,7 +364,7 @@ class FragmentStoreTest { override fun initialize(dependencies: FakeComponent, key: FragmentKeyT): Feature { return Feature( state = dependencies.state(key), - viewFactory = NoOpViewFactory() + viewFactory = TestViewFactory() ) } } diff --git a/formula-android/src/test/java/com/instacart/formula/android/TestUtils.kt b/formula-android/src/test/java/com/instacart/formula/android/TestUtils.kt index 967d5bdb..41fe6ad1 100644 --- a/formula-android/src/test/java/com/instacart/formula/android/TestUtils.kt +++ b/formula-android/src/test/java/com/instacart/formula/android/TestUtils.kt @@ -1,13 +1,13 @@ package com.instacart.formula.android -import com.instacart.testutils.android.NoOpViewFactory +import com.instacart.testutils.android.TestViewFactory import io.reactivex.rxjava3.core.Observable object TestUtils { fun feature(stateValue: Value): Feature { return Feature( state = Observable.just(stateValue), - viewFactory = NoOpViewFactory() + viewFactory = TestViewFactory() ) } } \ No newline at end of file diff --git a/formula-android/src/test/java/com/instacart/formula/android/ViewFactoryTest.kt b/formula-android/src/test/java/com/instacart/formula/android/ViewFactoryTest.kt new file mode 100644 index 00000000..5efddcc8 --- /dev/null +++ b/formula-android/src/test/java/com/instacart/formula/android/ViewFactoryTest.kt @@ -0,0 +1,45 @@ +package com.instacart.formula.android + +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import com.instacart.testutils.android.NoOpFeatureFactory +import com.instacart.testutils.android.TestFormulaActivity +import com.instacart.testutils.android.TestKey +import com.instacart.testutils.android.showFragment +import com.instacart.testutils.android.withFormulaAndroid +import org.junit.Test +import org.junit.runner.RunWith +import com.instacart.testutils.android.R as TestR + +@RunWith(AndroidJUnit4::class) +class ViewFactoryTest { + + @Test fun fromLayout() { + withFormulaAndroid( + configure = { + activity { + ActivityStore( + fragmentStore = FragmentStore.init { + val featureFactory = NoOpFeatureFactory( + viewFactory = ViewFactory.fromLayout(TestR.layout.test_fragment_layout) { + featureView { } + } + ) + bind(featureFactory) + } + ) + } + } + ) { + val fragmentKey = TestKey() + + val scenario = ActivityScenario.launch(TestFormulaActivity::class.java) + scenario.showFragment(fragmentKey) + scenario.onActivity { + val fragment = it.supportFragmentManager.findFragmentByTag(fragmentKey.tag) + assertThat(fragment).isNotNull() + } + } + } +} \ No newline at end of file diff --git a/formula-android/src/test/java/com/instacart/formula/android/views/ViewInstanceTest.kt b/formula-android/src/test/java/com/instacart/formula/android/views/ViewInstanceTest.kt new file mode 100644 index 00000000..c0afeb4b --- /dev/null +++ b/formula-android/src/test/java/com/instacart/formula/android/views/ViewInstanceTest.kt @@ -0,0 +1,47 @@ +package com.instacart.formula.android.views + +import android.view.View +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import com.instacart.formula.RenderView +import com.instacart.formula.Renderer +import com.instacart.formula.android.ViewInstance +import com.instacart.testutils.android.TestFragmentActivity +import com.instacart.testutils.android.TestFragmentLifecycleCallback +import com.instacart.testutils.android.activity +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ViewInstanceTest { + + @Test + fun `feature view with render view`() { + val renderView = object : RenderView { + override val render: Renderer = Renderer.create {} + } + val viewInstance = viewInstance() + val featureView = viewInstance.featureView(renderView) + assertThat(featureView.view).isEqualTo(viewInstance.view) + assertThat(featureView.setOutput).isEqualTo(renderView.render) + assertThat(featureView.lifecycleCallbacks).isEqualTo(null) + } + + @Test + fun `feature view with render view and lifecycle callbacks`() { + val renderView = object : RenderView { + override val render: Renderer = Renderer.create {} + } + val viewInstance = viewInstance() + val callback = TestFragmentLifecycleCallback() + val featureView = viewInstance.featureView(renderView, callback) + assertThat(featureView.lifecycleCallbacks).isEqualTo(callback) + } + + private fun viewInstance(): ViewInstance { + val scenario = ActivityScenario.launch(TestFragmentActivity::class.java) + val view = View(scenario.activity()) + return InflatedViewInstance(view) + } +} \ No newline at end of file diff --git a/gradle/jacoco.gradle b/gradle/jacoco.gradle index bec402a1..f749a3a2 100644 --- a/gradle/jacoco.gradle +++ b/gradle/jacoco.gradle @@ -15,7 +15,6 @@ junitJacoco { ] ignoreProjects = [ "samples/*", - "test-utils/*" ] includeNoLocationClasses = true includeInstrumentationCoverageInMergedReport = true diff --git a/test-utils/android/src/main/java/com/instacart/testutils/android/HeadlessFragment.kt b/test-utils/android/src/main/java/com/instacart/testutils/android/HeadlessFragment.kt new file mode 100644 index 00000000..d3d2fd93 --- /dev/null +++ b/test-utils/android/src/main/java/com/instacart/testutils/android/HeadlessFragment.kt @@ -0,0 +1,5 @@ +package com.instacart.testutils.android + +import androidx.fragment.app.Fragment + +class HeadlessFragment : Fragment() \ No newline at end of file diff --git a/test-utils/android/src/main/java/com/instacart/testutils/android/NoOpFeatureFactory.kt b/test-utils/android/src/main/java/com/instacart/testutils/android/NoOpFeatureFactory.kt index 578f9de8..eeafd912 100644 --- a/test-utils/android/src/main/java/com/instacart/testutils/android/NoOpFeatureFactory.kt +++ b/test-utils/android/src/main/java/com/instacart/testutils/android/NoOpFeatureFactory.kt @@ -3,13 +3,16 @@ package com.instacart.testutils.android import com.instacart.formula.android.Feature import com.instacart.formula.android.FeatureFactory import com.instacart.formula.android.FragmentKey +import com.instacart.formula.android.ViewFactory import io.reactivex.rxjava3.core.Observable -class NoOpFeatureFactory : FeatureFactory { +class NoOpFeatureFactory( + private val viewFactory: ViewFactory = TestViewFactory(), +) : FeatureFactory { override fun initialize(dependencies: Unit, key: FragmentKeyT): Feature { return Feature( state = Observable.empty(), - viewFactory = NoOpViewFactory() + viewFactory = viewFactory, ) } } \ No newline at end of file diff --git a/test-utils/android/src/main/java/com/instacart/testutils/android/NoOpViewFactory.kt b/test-utils/android/src/main/java/com/instacart/testutils/android/NoOpViewFactory.kt deleted file mode 100644 index 127371f7..00000000 --- a/test-utils/android/src/main/java/com/instacart/testutils/android/NoOpViewFactory.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.instacart.testutils.android - -import com.instacart.formula.android.FeatureView -import com.instacart.formula.android.LayoutViewFactory -import com.instacart.formula.android.ViewInstance - -class NoOpViewFactory : LayoutViewFactory(R.layout.test_fragment_layout) { - override fun ViewInstance.create(): FeatureView { - return featureView { } - } -} \ No newline at end of file diff --git a/test-utils/android/src/main/java/com/instacart/testutils/android/TestExtensions.kt b/test-utils/android/src/main/java/com/instacart/testutils/android/TestExtensions.kt index 134bf9ef..3fbcfd53 100644 --- a/test-utils/android/src/main/java/com/instacart/testutils/android/TestExtensions.kt +++ b/test-utils/android/src/main/java/com/instacart/testutils/android/TestExtensions.kt @@ -7,7 +7,6 @@ import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth.assertThat import com.instacart.formula.FormulaAndroid -import com.instacart.formula.android.ActivityConfigurator import com.instacart.formula.android.FragmentEnvironment import io.reactivex.rxjava3.plugins.RxJavaPlugins import org.robolectric.Shadows diff --git a/test-utils/android/src/main/java/com/instacart/testutils/android/TestFragmentExtensions.kt b/test-utils/android/src/main/java/com/instacart/testutils/android/TestFragmentExtensions.kt new file mode 100644 index 00000000..37320ea4 --- /dev/null +++ b/test-utils/android/src/main/java/com/instacart/testutils/android/TestFragmentExtensions.kt @@ -0,0 +1,47 @@ +package com.instacart.testutils.android + +import android.os.Looper +import androidx.fragment.app.FragmentActivity +import androidx.test.core.app.ActivityScenario +import com.instacart.formula.android.FormulaFragment +import com.instacart.formula.android.FragmentKey +import org.robolectric.Shadows.shadowOf + +fun FragmentActivity.showFragment( + fragmentKey: FragmentKey, + allowStateLoss: Boolean = false, +) { + val entryIndex = supportFragmentManager.backStackEntryCount - 1 + val fragment = if (entryIndex >= 0) { + val entry = supportFragmentManager.getBackStackEntryAt(entryIndex) + supportFragmentManager.findFragmentByTag(entry.name) + } else { + null + } + + supportFragmentManager.beginTransaction().apply { + if (fragment != null) { + remove(fragment) + } + + val tag = fragmentKey.tag + add(R.id.activity_content, FormulaFragment.newInstance(fragmentKey), tag) + addToBackStack(tag) + }.apply { + if (allowStateLoss) { + commitAllowingStateLoss() + } else { + commit() + } + } +} + +fun ActivityScenario.showFragment( + fragmentKey: FragmentKey, + allowStateLoss: Boolean = false +) { + onActivity { + it.showFragment(fragmentKey, allowStateLoss) + shadowOf(Looper.getMainLooper()).idle() + } +} diff --git a/formula-android-tests/src/main/java/com/instacart/formula/test/TestFragmentLifecycleCallback.kt b/test-utils/android/src/main/java/com/instacart/testutils/android/TestFragmentLifecycleCallback.kt similarity index 97% rename from formula-android-tests/src/main/java/com/instacart/formula/test/TestFragmentLifecycleCallback.kt rename to test-utils/android/src/main/java/com/instacart/testutils/android/TestFragmentLifecycleCallback.kt index ec6ebab3..e2ea60de 100644 --- a/formula-android-tests/src/main/java/com/instacart/formula/test/TestFragmentLifecycleCallback.kt +++ b/test-utils/android/src/main/java/com/instacart/testutils/android/TestFragmentLifecycleCallback.kt @@ -1,4 +1,4 @@ -package com.instacart.formula.test +package com.instacart.testutils.android import android.os.Bundle import android.view.View diff --git a/formula-android-tests/src/main/java/com/instacart/formula/test/TestKey.kt b/test-utils/android/src/main/java/com/instacart/testutils/android/TestKey.kt similarity index 81% rename from formula-android-tests/src/main/java/com/instacart/formula/test/TestKey.kt rename to test-utils/android/src/main/java/com/instacart/testutils/android/TestKey.kt index 479ee230..2fc4e0e9 100644 --- a/formula-android-tests/src/main/java/com/instacart/formula/test/TestKey.kt +++ b/test-utils/android/src/main/java/com/instacart/testutils/android/TestKey.kt @@ -1,4 +1,4 @@ -package com.instacart.formula.test +package com.instacart.testutils.android import com.instacart.formula.android.FragmentKey import kotlinx.parcelize.Parcelize diff --git a/test-utils/android/src/main/java/com/instacart/testutils/android/TestViewFactory.kt b/test-utils/android/src/main/java/com/instacart/testutils/android/TestViewFactory.kt new file mode 100644 index 00000000..9670c96e --- /dev/null +++ b/test-utils/android/src/main/java/com/instacart/testutils/android/TestViewFactory.kt @@ -0,0 +1,18 @@ +package com.instacart.testutils.android + +import android.view.View +import com.instacart.formula.android.FeatureView +import com.instacart.formula.android.FragmentLifecycleCallback +import com.instacart.formula.android.LayoutViewFactory +import com.instacart.formula.android.ViewInstance + +class TestViewFactory( + private val fragmentLifecycleCallbacks: FragmentLifecycleCallback? = null, + private val render: (View, RenderModel) -> Unit = { _, _ -> }, +) : LayoutViewFactory(R.layout.test_fragment_layout) { + override fun ViewInstance.create(): FeatureView { + return featureView(fragmentLifecycleCallbacks) { + render(view, it) + } + } +} \ No newline at end of file