From 74dc224cc03fd28974c47bcb5c225cd7963a5875 Mon Sep 17 00:00:00 2001 From: Ward Abbass Date: Tue, 19 Oct 2021 16:51:15 +0300 Subject: [PATCH 01/35] mergeButtonOptions Updates the button that already added to a menu without removing and adding all the menu items --- .../stack/topbar/button/ButtonController.kt | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonController.kt b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonController.kt index b18cb11b987..4a7ca853b7c 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonController.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonController.kt @@ -71,6 +71,10 @@ open class ButtonController(activity: Activity, return if (other.id != id) false else button.equals(other.button) } + fun areButtonOptionsChanged(otherOptions:ButtonOptions):Boolean{ + return otherOptions.id == id && !button.equals(otherOptions) + } + fun applyNavigationIcon(toolbar: Toolbar) { presenter.applyNavigationIcon(toolbar) { onPressListener.onPress(it) @@ -83,13 +87,22 @@ open class ButtonController(activity: Activity, fun addToMenu(buttonBar: ButtonBar, order: Int) { if (button.component.hasValue() && buttonBar.containsButton(menuItem, order)) return - buttonBar.menu.removeItem(button.intId) - menuItem = buttonBar.addButton(Menu.NONE, + buttonBar.menu.removeItem(button.intId) + menuItem = buttonBar.addButton(Menu.NONE, button.intId, order, presenter.styledText)?.also { menuItem -> - menuItem.setOnMenuItemClickListener(this@ButtonController) - presenter.applyOptions(buttonBar, menuItem, this@ButtonController::getView) + menuItem.setOnMenuItemClickListener(this@ButtonController) + presenter.applyOptions(buttonBar, menuItem, this@ButtonController::getView) + } + } + + fun mergeButtonOptions(optionsToMerge: ButtonOptions,buttonBar: ButtonBar) { + button.mergeWith(optionsToMerge) + presenter.button = this.button + buttonBar.getButtonById(button.intId)?.let { + menuItem-> + presenter.applyOptions(buttonBar,menuItem,this::getView) } } } \ No newline at end of file From d31e002058bb7d505977d7e1a4d7e4ad3b70c757 Mon Sep 17 00:00:00 2001 From: Ward Abbass Date: Tue, 19 Oct 2021 16:53:36 +0300 Subject: [PATCH 02/35] apply text Add apply text to menu item --- .../stack/topbar/button/ButtonPresenter.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonPresenter.kt b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonPresenter.kt index e1520a4cceb..5be22e2047e 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonPresenter.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/button/ButtonPresenter.kt @@ -25,7 +25,8 @@ import com.reactnativenavigation.utils.ViewUtils import com.reactnativenavigation.views.stack.topbar.titlebar.IconBackgroundDrawable import kotlin.math.max -open class ButtonPresenter(private val context: Context, private val button: ButtonOptions, private val iconResolver: IconResolver) { +open class ButtonPresenter(private val context: Context,var button: ButtonOptions, private val iconResolver: +IconResolver) { companion object { const val DISABLED_COLOR = Color.LTGRAY } @@ -47,6 +48,8 @@ open class ButtonPresenter(private val context: Context, private val button: But applyComponent(menuItem, viewCreator) applyAccessibilityLabel(menuItem) applyIcon(menuItem) + applyText(menuItem) + applyOptionsDirectlyOnView(toolbar, menuItem) { applyTestId(it) @@ -55,6 +58,11 @@ open class ButtonPresenter(private val context: Context, private val button: But } } + private fun applyText(menuItem: MenuItem) { + if (button.text.hasValue()) + menuItem.title = button.text.get() + } + fun applyColor(toolbar: Toolbar, menuItem: MenuItem, color: ThemeColour) { button.color = color applyIcon(menuItem) From 4b65a7819b32febcdb75cc96fb49c43760081a2a Mon Sep 17 00:00:00 2001 From: Ward Abbass Date: Tue, 19 Oct 2021 16:54:42 +0300 Subject: [PATCH 03/35] add new usecases test and check modifying buttons, including clear flows where we rebuild the menu --- .../stack/StackPresenterTest.kt | 88 +++++------ .../stack/TopBarControllerTest.kt | 139 +++++++++++++++++- 2 files changed, 177 insertions(+), 50 deletions(-) diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt index 779d9b3b621..2df8d06cf93 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenterTest.kt @@ -17,7 +17,10 @@ import com.reactnativenavigation.options.params.* import com.reactnativenavigation.options.params.Number import com.reactnativenavigation.options.parsers.TypefaceLoader import com.reactnativenavigation.react.CommandListenerAdapter -import com.reactnativenavigation.utils.* +import com.reactnativenavigation.utils.CollectionUtils +import com.reactnativenavigation.utils.RenderChecker +import com.reactnativenavigation.utils.TitleBarHelper +import com.reactnativenavigation.utils.UiUtils import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry import com.reactnativenavigation.viewcontrollers.stack.topbar.TopBarController import com.reactnativenavigation.viewcontrollers.stack.topbar.button.ButtonController @@ -150,12 +153,14 @@ class StackPresenterTest : BaseTest() { val options = Options.EMPTY.copy() options.topBar.buttons.left = arrayListOf(ButtonOptions()) options.topBar.buttons.right = arrayListOf(ButtonOptions()) + uut.applyChildOptions(options,parent,child) + verify(topBarController, times(1)).applyRightButtonsOptions(any(),any(),any()) + verify(topBarController, times(1)).applyLeftButtonsOptions(any(),any(),any()) uut.onConfigurationChanged(options) - - verify(topBarController, times(2)).applyRightButtons(any()) - verify(topBarController, times(2)).applyLeftButtons(any()) + verify(topBarController, times(1)).applyRightButtons(any()) + verify(topBarController, times(1)).applyLeftButtons(any()) } @Test @@ -260,11 +265,11 @@ class StackPresenterTest : BaseTest() { button.text = Text("btn") options.topBar.buttons.right = ArrayList(setOf(button)) uut.mergeChildOptions(options, EMPTY_OPTIONS, parent, child) - verify(topBarController).mergeRightButtons(any(), any()) + verify(topBarController).mergeRightButtonsOptions(any(), any(),any()) options.topBar.buttons.left = ArrayList(setOf(button)) uut.mergeChildOptions(options, EMPTY_OPTIONS, parent, child) - verify(topBarController).mergeLeftButtons(any(), any()) + verify(topBarController).mergeLeftButtonsOptions(any(), any(), any()) } @Test @@ -313,8 +318,8 @@ class StackPresenterTest : BaseTest() { toApply.topBar.buttons.right = arrayListOf(textBtn1, componentBtn1) uut.applyChildOptions(toApply, parent, child) - val captor1 = argumentCaptor>() - verify(topBarController).applyRightButtons(captor1.capture()) + val captor1 = argumentCaptor>() + verify(topBarController).applyRightButtonsOptions(any(),captor1.capture(), any()) assertThat(topBar.rightButtonBar.menu.size()).isEqualTo(2) val appliedButtons = captor1.firstValue @@ -325,12 +330,12 @@ class StackPresenterTest : BaseTest() { uut.mergeChildOptions(toMerge, Options.EMPTY, parent, child) assertThat(topBar.rightButtonBar.menu.size()).isEqualTo(3) - val captor2 = argumentCaptor>() - verify(topBarController).mergeRightButtons(captor2.capture(), any()) + val captor2 = argumentCaptor>() + verify(topBarController).mergeRightButtonsOptions(any(),captor2.capture(), any()) val mergedButtons = captor2.firstValue assertThat(mergedButtons).hasSize(3) - assertThat(appliedButtons[0]).isNotEqualTo(mergedButtons[0]) - assertThat(appliedButtons[1]).isEqualTo(mergedButtons[2]) + assertThat(appliedButtons[0].id).isNotEqualTo(mergedButtons[1].id) + assertThat(appliedButtons[1].id).isEqualTo(mergedButtons[2].id) } @Test @@ -357,15 +362,14 @@ class StackPresenterTest : BaseTest() { options.topBar.buttons.left = ArrayList(listOf(textBtn2)) uut.applyChildOptions(options, parent, child) ShadowLooper.idleMainLooper() - verify(topBar, times(1)).clearLeftButtons() - verify(topBar, times(1)).clearBackButton() + verify(topBarController, times(1)).applyLeftButtonsOptions(any(), any(), any()) + verify(topBar, never()).setBackButton(any()) val backButtonHidden = Options() backButtonHidden.topBar.buttons.back.setHidden() uut.mergeChildOptions(backButtonHidden, options, parent, child) ShadowLooper.idleMainLooper() - verify(topBar, times(1)).clearLeftButtons() - verify(topBar, times(2)).clearBackButton() + verify(topBar, times(1)).clearBackButton() } @Test @@ -400,7 +404,7 @@ class StackPresenterTest : BaseTest() { assertThat(toMerge.topBar.buttons.back.hasValue()).isTrue() uut.mergeChildOptions(toMerge, Options.EMPTY, parent, child) - verify(topBarController).mergeLeftButtons(any(), any()) + verify(topBarController).mergeLeftButtonsOptions(any(), any(),any()) verify(topBar, never()).clearLeftButtons() } @@ -662,15 +666,15 @@ class StackPresenterTest : BaseTest() { options.topBar.buttons.left = ArrayList() options.topBar.buttons.left!!.add(leftButton) uut.applyChildOptions(options, parent, child) - val rightCaptor = argumentCaptor>() - verify(topBarController).applyRightButtons(rightCaptor.capture()) - assertThat(rightCaptor.firstValue[0].button.color.get()).isEqualTo(options.topBar.rightButtonColor.get()) - assertThat(rightCaptor.firstValue[1].button.color.get()).isEqualTo(options.topBar.rightButtonColor.get()) + val rightCaptor = argumentCaptor>() + verify(topBarController).applyRightButtonsOptions(any(),rightCaptor.capture(), any()) + assertThat(rightCaptor.firstValue[0].color.get()).isEqualTo(options.topBar.rightButtonColor.get()) + assertThat(rightCaptor.firstValue[1].color.get()).isEqualTo(options.topBar.rightButtonColor.get()) assertThat(rightCaptor.firstValue[0]).isNotEqualTo(rightButton1) assertThat(rightCaptor.firstValue[1]).isNotEqualTo(rightButton2) - val leftCaptor = argumentCaptor>() - verify(topBarController).applyLeftButtons(leftCaptor.capture()) - assertThat(leftCaptor.firstValue[0].button.color).isEqualTo(options.topBar.leftButtonColor) + val leftCaptor = argumentCaptor>() + verify(topBarController).applyLeftButtonsOptions(any(),leftCaptor.capture(),any()) + assertThat(leftCaptor.firstValue[0].color).isEqualTo(options.topBar.leftButtonColor) assertThat(leftCaptor.firstValue[0]).isNotEqualTo(leftButton) } @@ -790,15 +794,15 @@ class StackPresenterTest : BaseTest() { options2.topBar.buttons.left = ArrayList(listOf(leftButton)) uut.mergeChildOptions(options2, appliedOptions, parent, child) - val rightCaptor = argumentCaptor>() - verify(topBarController).mergeRightButtons(rightCaptor.capture(), any()) - assertThat(rightCaptor.firstValue[0].button.color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get()) - assertThat(rightCaptor.firstValue[1].button.color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get()) + val rightCaptor = argumentCaptor>() + verify(topBarController).mergeRightButtonsOptions(any(),rightCaptor.capture(), any()) + assertThat(rightCaptor.firstValue[0].color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get()) + assertThat(rightCaptor.firstValue[1].color.get()).isEqualTo(appliedOptions.topBar.rightButtonColor.get()) assertThat(rightCaptor.firstValue[0]).isNotEqualTo(rightButton1) assertThat(rightCaptor.firstValue[1]).isNotEqualTo(rightButton2) - val leftCaptor = argumentCaptor>() - verify(topBarController).mergeLeftButtons(leftCaptor.capture(), any()) - assertThat(leftCaptor.firstValue[0].button.color.get()).isEqualTo(appliedOptions.topBar.leftButtonColor.get()) + val leftCaptor = argumentCaptor>() + verify(topBarController).mergeLeftButtonsOptions(any(),leftCaptor.capture(), any()) + assertThat(leftCaptor.firstValue[0].color.get()).isEqualTo(appliedOptions.topBar.leftButtonColor.get()) assertThat(leftCaptor.firstValue[0]).isNotEqualTo(leftButton) } @@ -816,15 +820,15 @@ class StackPresenterTest : BaseTest() { options2.topBar.buttons.left = ArrayList(listOf(leftButton)) uut.mergeChildOptions(options2, resolvedOptions, parent, child) - val rightCaptor = argumentCaptor>() - verify(topBarController).mergeRightButtons(rightCaptor.capture(), any()) - assertThat(rightCaptor.firstValue[0].button.color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get()) - assertThat(rightCaptor.firstValue[1].button.color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get()) + val rightCaptor = argumentCaptor>() + verify(topBarController).mergeRightButtonsOptions(any(),rightCaptor.capture(), any()) + assertThat(rightCaptor.firstValue[0].color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get()) + assertThat(rightCaptor.firstValue[1].color.get()).isEqualTo(resolvedOptions.topBar.rightButtonColor.get()) assertThat(rightCaptor.firstValue[0]).isNotEqualTo(rightButton1) assertThat(rightCaptor.firstValue[1]).isNotEqualTo(rightButton2) - val leftCaptor = argumentCaptor>() - verify(topBarController).mergeLeftButtons(leftCaptor.capture(), any()) - assertThat(leftCaptor.firstValue[0].button.color.get()).isEqualTo(resolvedOptions.topBar.leftButtonColor.get()) + val leftCaptor = argumentCaptor>() + verify(topBarController).mergeLeftButtonsOptions(any(),leftCaptor.capture(), any()) + assertThat(leftCaptor.firstValue[0].color.get()).isEqualTo(resolvedOptions.topBar.leftButtonColor.get()) assertThat(leftCaptor.firstValue[0]).isNotEqualTo(leftButton) } @@ -834,10 +838,10 @@ class StackPresenterTest : BaseTest() { options.topBar.buttons.right = ArrayList(listOf(textBtn1)) options.topBar.buttons.left = ArrayList(listOf(textBtn1)) uut.applyChildOptions(options, parent, child) - val rightCaptor = argumentCaptor>() - val leftCaptor = argumentCaptor>() - verify(topBarController).applyRightButtons(rightCaptor.capture()) - verify(topBarController).applyLeftButtons(leftCaptor.capture()) + val rightCaptor = argumentCaptor>() + val leftCaptor = argumentCaptor>() + verify(topBarController).applyRightButtonsOptions(any(),rightCaptor.capture(),any()) + verify(topBarController).applyLeftButtonsOptions(any(),leftCaptor.capture(),any()) assertThat(rightCaptor.firstValue.size).isOne() assertThat(leftCaptor.firstValue.size).isOne() } diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt index fc0baeb6fa4..cb874dee469 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt @@ -6,6 +6,8 @@ import android.content.Context import android.view.View import com.nhaarman.mockitokotlin2.* import com.reactnativenavigation.BaseTest +import com.reactnativenavigation.fakes.IconResolverFake +import com.reactnativenavigation.mocks.TitleBarButtonCreatorMock import com.reactnativenavigation.options.BackButton import com.reactnativenavigation.options.ButtonOptions import com.reactnativenavigation.options.Options @@ -19,11 +21,13 @@ import com.reactnativenavigation.utils.resetViewProperties import com.reactnativenavigation.viewcontrollers.stack.topbar.TopBarAnimator import com.reactnativenavigation.viewcontrollers.stack.topbar.TopBarController import com.reactnativenavigation.viewcontrollers.stack.topbar.button.ButtonController +import com.reactnativenavigation.viewcontrollers.stack.topbar.button.ButtonPresenter import com.reactnativenavigation.views.stack.StackLayout import com.reactnativenavigation.views.stack.topbar.TopBar import org.assertj.core.api.Java6Assertions.assertThat import org.junit.Test import java.util.* +import kotlin.collections.LinkedHashMap class TopBarControllerTest : BaseTest() { private lateinit var uut: TopBarController @@ -97,10 +101,16 @@ class TopBarControllerTest : BaseTest() { } @Test - fun mergeRightButtons_componentButtonIsNotAddedIfAlreadyAddedToMenu() { - val initialButtons = rightButtons(componentButton) - uut.applyRightButtons(initialButtons!!) - uut.mergeRightButtons(initialButtons, emptyList()) + fun mergeRightButtonsOptions_componentButtonIsNotAddedIfAlreadyAddedToMenu() { + val controllers = mutableMapOf() + uut.applyRightButtonsOptions(controllers, listOf(componentButton)){ + createButtonController(it) + } + verify(controllers[componentButton.id]!!, times(1)).addToMenu(any(), any()) + uut.mergeRightButtonsOptions(controllers, listOf(componentButton.copy())){ + createButtonController(it) + } + verify(controllers[componentButton.id]!!, times(1)).addToMenu(any(), any()) } @Test @@ -131,19 +141,27 @@ class TopBarControllerTest : BaseTest() { @Test fun mergeLeftButtons_clearsBackButton() { + val controllers = mutableMapOf() uut.view.setBackButton(TitleBarHelper.createButtonController(activity, backButton)) assertThat(uut.view.navigationIcon).isNotNull() - uut.mergeLeftButtons(emptyList(), leftButton(leftButton)) + uut.mergeLeftButtonsOptions(controllers, listOf(leftButton)){ + createButtonController(it) + } assertThat(uut.view.navigationIcon).isNull() } @Test fun mergeLeftButtons_emptyButtonsListClearsBackButton() { + val controllers = mutableMapOf() + uut.view.setBackButton(TitleBarHelper.createButtonController(activity, backButton)) assertThat(uut.view.navigationIcon).isNotNull() - val initialButtons = leftButton(leftButton) - uut.applyLeftButtons(initialButtons) - uut.mergeLeftButtons(initialButtons, emptyList()) + uut.applyLeftButtonsOptions(controllers, listOf(leftButton)){ + createButtonController(it) + } + uut.mergeLeftButtonsOptions(controllers, emptyList()){ + createButtonController(it) + } assertThat(uut.view.navigationIcon).isNull() } @@ -219,6 +237,110 @@ class TopBarControllerTest : BaseTest() { assertThat(result).isEqualTo(someAnimator) } + @Test + fun `mergeRightButtons - should add buttons`(){ + val controllers = spy(LinkedHashMap()) + val controller = spy(ButtonController(activity, ButtonPresenter(activity, textButton1, IconResolverFake(activity)), + textButton1, TitleBarButtonCreatorMock(), object : ButtonController.OnClickListener { + override fun onPress(button: ButtonOptions) { + + } + + })) + uut.mergeRightButtonsOptions(controllers, listOf(textButton1)) { + controller + } + assertThat(uut.rightButtonCount).isEqualTo(1) + verify(controllers, never()).remove(any()) + assertThat(controllers[textButton1.id]).isEqualTo(controller) + } + @Test + fun `mergeRightButtons - should remove all and re-add buttons in case of reorder`(){ + val controllers = mutableMapOf() + uut.mergeRightButtonsOptions(controllers, listOf(textButton1, textButton2)) { + createButtonController(it) + } + assertThat(uut.getRightButton(1).itemId ).isEqualTo(textButton1.intId) + assertThat(uut.getRightButton(0).itemId ).isEqualTo(textButton2.intId) + val removedControllers = mutableMapOf().apply { putAll(controllers) } + uut.mergeRightButtonsOptions(controllers, listOf(textButton2.copy(), textButton1.copy())) { + createButtonController(it) + } + assertThat(uut.getRightButton(1).itemId ).isEqualTo(textButton2.intId) + assertThat(uut.getRightButton(0).itemId ).isEqualTo(textButton1.intId) + + verify(removedControllers[textButton1.id]!!, times(1)).destroy() + verify(removedControllers[textButton2.id]!!, times(1)).destroy() + + verify(controllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) + verify(controllers[textButton2.id]!!, times(1)).addToMenu(any(), any()) + } + @Test + fun `mergeRightButtons - should rebuild menu when adding menu items`(){ + val controllers = spy(LinkedHashMap()) + uut.mergeRightButtonsOptions(controllers, listOf(textButton1)) { + createButtonController(it) + } + assertThat(uut.rightButtonCount).isEqualTo(1) + + uut.mergeRightButtonsOptions(controllers, listOf(textButton1, textButton2)) { + createButtonController(it) + } + assertThat(uut.rightButtonCount).isEqualTo(2) + verify(controllers, times(1)).remove(any()) + verify(controllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) + } + @Test + fun `mergeRightButtons - should modify changed buttons`(){ + val controllers = spy(LinkedHashMap()) + uut.mergeRightButtonsOptions(controllers, listOf(textButton1.apply { + this.enabled = Bool(true) + })) { + createButtonController(it) + } + assertThat(uut.rightButtonCount).isEqualTo(1) + verify(controllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) + + uut.mergeRightButtonsOptions(controllers, listOf(textButton1.copy().apply { this.enabled= Bool(false) })) { + createButtonController(it) + } + verify(controllers, never()).remove(any()) + verify(controllers[textButton1.id]!!, times(1)).mergeButtonOptions(any(), any()) + verify(controllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) + verify(controllers[textButton1.id]!!, never()).destroy() + } + + fun `mergeRightButtons - reorder of same menu items should rebuild menu`(){ + val controllers = spy(LinkedHashMap()) + uut.mergeRightButtonsOptions(controllers, listOf(textButton1, textButton2)) { + createButtonController(it) + } + assertThat(uut.rightButtonCount).isEqualTo(1) + verify(controllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) + verify(controllers[textButton2.id]!!, times(1)).addToMenu(any(), any()) + + uut.mergeRightButtonsOptions(controllers, listOf(textButton2.copy(), textButton1.copy())) { + createButtonController(it) + } + verify(controllers[textButton1.id]!!, never()).mergeButtonOptions(any(), any()) + verify(controllers[textButton2.id]!!, never()).mergeButtonOptions(any(), any()) + verify(controllers[textButton1.id]!!, times(2)).addToMenu(any(), any()) + verify(controllers[textButton2.id]!!, times(2)).addToMenu(any(), any()) + verify(controllers[textButton1.id]!!, times(1)).destroy() + verify(controllers[textButton2.id]!!, times(1)).destroy() + } + + + + private fun createButtonController(it: ButtonOptions) = + spy(ButtonController(activity, ButtonPresenter(activity, it, IconResolverFake(activity)), + it, TitleBarButtonCreatorMock(), object : ButtonController.OnClickListener { + override fun onPress(button: ButtonOptions) { + + } + + })) + private fun createButtons() { leftButton = ButtonOptions() leftButton.id = Constants.BACK_BUTTON_ID @@ -251,4 +373,5 @@ class TopBarControllerTest : BaseTest() { return spy(super.createTopBar(context, stackLayout)) } }) + } \ No newline at end of file From 08ffdf5e72e8e813a3e529ccc4615ea0b170b960 Mon Sep 17 00:00:00 2001 From: Ward Abbass Date: Tue, 19 Oct 2021 16:55:57 +0300 Subject: [PATCH 04/35] refactor apply&merge buttons Rewrite apply buttons flows and merge options flows with a clear flows without removing and rebuilding the whole menu each time! --- .../viewcontrollers/stack/StackPresenter.java | 181 +++++++++--------- .../stack/topbar/TopBarController.kt | 166 ++++++++++++++-- 2 files changed, 239 insertions(+), 108 deletions(-) diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java index 313a9c3912c..09d2fbe8eb4 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackPresenter.java @@ -1,9 +1,16 @@ package com.reactnativenavigation.viewcontrollers.stack; +import static com.reactnativenavigation.utils.CollectionUtils.filter; +import static com.reactnativenavigation.utils.CollectionUtils.forEach; +import static com.reactnativenavigation.utils.CollectionUtils.isNullOrEmpty; +import static com.reactnativenavigation.utils.CollectionUtils.merge; +import static com.reactnativenavigation.utils.ObjectUtils.perform; + import android.animation.Animator; import android.app.Activity; import android.graphics.Color; import android.view.View; +import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.widget.FrameLayout; @@ -47,22 +54,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import static com.reactnativenavigation.utils.CollectionUtils.difference; -import static com.reactnativenavigation.utils.CollectionUtils.filter; -import static com.reactnativenavigation.utils.CollectionUtils.first; -import static com.reactnativenavigation.utils.CollectionUtils.forEach; -import static com.reactnativenavigation.utils.CollectionUtils.getOrDefault; -import static com.reactnativenavigation.utils.CollectionUtils.isNullOrEmpty; -import static com.reactnativenavigation.utils.CollectionUtils.keyBy; -import static com.reactnativenavigation.utils.CollectionUtils.merge; -import static com.reactnativenavigation.utils.ObjectUtils.perform; -import static com.reactnativenavigation.utils.ObjectUtils.take; - public class StackPresenter { private static final int DEFAULT_BORDER_COLOR = Color.BLACK; private static final double DEFAULT_ELEVATION = 4d; @@ -150,7 +145,7 @@ public void onConfigurationChanged(Options options) { if (currentLeftButtons != null && !currentLeftButtons.isEmpty()) topBarController.applyLeftButtons(currentLeftButtons); if (withDefault.topBar.buttons.back.visible.isTrue()) { - topBar.setBackButton(createButtonController(withDefault.topBar.buttons.back)); + topBarController.setBackButton(createButtonController(withDefault.topBar.buttons.back)); } topBar.setOverflowButtonColor(withDefault.topBar.rightButtonColor.get(Color.BLACK)); topBar.applyTopTabsColors(withDefault.topTabs.selectedTabColor, @@ -258,7 +253,7 @@ private void applyTopBarOptions(Options options, StackController stack, ViewCont } private void applyStatusBarDrawBehindOptions(TopBarOptions topBarOptions, Options withDefault) { - if(withDefault.statusBar.visible.isTrueOrUndefined() && withDefault.statusBar.drawBehind.isTrue()){ + if (withDefault.statusBar.visible.isTrueOrUndefined() && withDefault.statusBar.drawBehind.isTrue()) { topBar.setTopPadding(StatusBarUtils.getStatusBarHeight(activity)); topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)) + StatusBarUtils.getStatusBarHeightDp(activity)); } else { @@ -268,8 +263,8 @@ private void applyStatusBarDrawBehindOptions(TopBarOptions topBarOptions, Option } private void mergeStatusBarDrawBehindOptions(TopBarOptions topBarOptions, Options toMerge) { - if(toMerge.statusBar.drawBehind.hasValue()){ - if(toMerge.statusBar.visible.isTrueOrUndefined() && toMerge.statusBar.drawBehind.isTrue()){ + if (toMerge.statusBar.drawBehind.hasValue()) { + if (toMerge.statusBar.visible.isTrueOrUndefined() && toMerge.statusBar.drawBehind.isTrue()) { topBar.setTopPadding(StatusBarUtils.getStatusBarHeight(activity)); topBar.setHeight(topBarOptions.height.get(UiUtils.getTopBarHeightDp(activity)) + StatusBarUtils.getStatusBarHeightDp(activity)); } else { @@ -304,59 +299,21 @@ private void applyTopBarVisibility(TopBarOptions options) { } private void applyButtons(TopBarOptions options, ViewController child) { - if (options.buttons.right != null) { - List rightButtons = mergeButtonsWithColor(options.buttons.right, - options.rightButtonColor - , options.rightButtonDisabledColor); - List rightButtonControllers = getOrCreateButtonControllersByInstanceId(componentRightButtons.get(child.getView()), rightButtons); - componentRightButtons.put(child.getView(), keyBy(rightButtonControllers, ButtonController::getButtonInstanceId)); - if (!CollectionUtils.equals(currentRightButtons, rightButtonControllers)) { - currentRightButtons = rightButtonControllers; - topBarController.applyRightButtons(currentRightButtons); - } - } else { - currentRightButtons = null; - topBar.clearRightButtons(); - } - - if (options.buttons.left != null) { - List leftButtons = mergeButtonsWithColor(options.buttons.left, - options.leftButtonColor, - options.leftButtonDisabledColor); - List leftButtonControllers = getOrCreateButtonControllersByInstanceId(componentLeftButtons.get(child.getView()), leftButtons); - componentLeftButtons.put(child.getView(), keyBy(leftButtonControllers, ButtonController::getButtonInstanceId)); - if (!CollectionUtils.equals(currentLeftButtons, leftButtonControllers)) { - currentLeftButtons = leftButtonControllers; - topBarController.applyLeftButtons(currentLeftButtons); - } - } else { - currentLeftButtons = null; - topBar.clearLeftButtons(); - } - - if (options.buttons.back.visible.isTrue() && !options.buttons.hasLeftButtons()) { - topBar.setBackButton(createButtonController(options.buttons.back)); - } + //should be at first in order for next actions to be animated if (options.animateRightButtons.hasValue()) - topBar.animateRightButtons(options.animateRightButtons.isTrue()); + topBarController.animateRightButtons(options.animateRightButtons.isTrue()); if (options.animateLeftButtons.hasValue()) - topBar.animateLeftButtons(options.animateLeftButtons.isTrue()); - topBar.setOverflowButtonColor(options.rightButtonColor.get(Color.BLACK)); - } + topBarController.animateLeftButtons(options.animateLeftButtons.isTrue()); - private List getOrCreateButtonControllersByInstanceId(@Nullable Map currentButtons, @Nullable List buttons) { - if (buttons == null) return null; - Map result = new LinkedHashMap<>(); - forEach(buttons, b -> result.put(b.instanceId, getOrDefault(currentButtons, b.instanceId, () -> createButtonController(b)))); - return new ArrayList<>(result.values()); - } + applyRightButtonsOptions(options, child); - private List getOrCreateButtonControllers(@Nullable Map currentButtons, @NonNull List buttons) { - ArrayList result = new ArrayList<>(); - for (ButtonOptions b : buttons) { - result.add(take(first(perform(currentButtons, null, Map::values), button -> button.getButton().equals(b)), createButtonController(b))); + applyLeftButtonsOptions(options, child); + + if (options.buttons.back.visible.isTrue() && !options.buttons.hasLeftButtons()) { + topBarController.setBackButton(createButtonController(options.buttons.back)); } - return result; + + topBar.setOverflowButtonColor(options.rightButtonColor.get(Color.BLACK)); } private ButtonController createButtonController(ButtonOptions button) { @@ -419,8 +376,8 @@ private void mergeOrientation(OrientationOptions orientationOptions) { } private void mergeButtons(TopBarOptions options, TopBarOptions optionsToMerge, View child, StackController stack) { - mergeRightButtons(options, optionsToMerge.buttons, child); - mergeLeftButton(options, optionsToMerge.buttons, child); + mergeRightButtonsOptions(options, optionsToMerge.buttons, child); + mergeLeftButtonsOptions(options, optionsToMerge.buttons, child); mergeLeftButtonsColor(child, optionsToMerge.leftButtonColor, optionsToMerge.leftButtonDisabledColor); mergeRightButtonsColor(child, optionsToMerge.rightButtonColor, optionsToMerge.rightButtonDisabledColor); mergeBackButton(optionsToMerge.buttons, stack); @@ -458,40 +415,80 @@ private void mergeRightButtonsColor(View child, ThemeColour color, ThemeColour d } } - private void mergeRightButtons(TopBarOptions options, TopBarButtons buttons, View child) { - if (buttons.right == null) return; - List rightButtons = mergeButtonsWithColor(buttons.right, options.rightButtonColor, options.rightButtonDisabledColor); - List toMerge = getOrCreateButtonControllers(componentRightButtons.get(child), rightButtons); - List toRemove = difference(currentRightButtons, toMerge, ButtonController::areButtonsEqual); - forEach(toRemove, ButtonController::destroy); + private void applyLeftButtonsOptions(TopBarOptions options, ViewController child) { + if (options.buttons.left != null) { + List leftButtons = mergeButtonsWithColor(options.buttons.left, + options.leftButtonColor + , options.leftButtonDisabledColor); + final ViewGroup childView = child.getView(); + final Map controllerMap = componentLeftButtons.get(childView); + final Map btnControllers = controllerMap != null ? controllerMap : + new HashMap<>(); + if (controllerMap == null) + componentLeftButtons.put(childView, btnControllers); + topBarController.applyLeftButtonsOptions(btnControllers, leftButtons, this::createButtonController); + currentLeftButtons = new ArrayList<>(btnControllers.values()); + } else { + currentLeftButtons = null; + topBarController.clearLeftButtons(); + } + } - if (!CollectionUtils.equals(currentRightButtons, toMerge)) { - componentRightButtons.put(child, keyBy(toMerge, ButtonController::getButtonInstanceId)); - topBarController.mergeRightButtons(toMerge, toRemove); - currentRightButtons = toMerge; + private void applyRightButtonsOptions(TopBarOptions options, ViewController child) { + if (options.buttons.right != null) { + List rightButtons = mergeButtonsWithColor(options.buttons.right, + options.rightButtonColor + , options.rightButtonDisabledColor); + final ViewGroup childView = child.getView(); + final Map controllerMap = componentRightButtons.get(childView); + final Map btnControllers = controllerMap != null ? controllerMap : + new HashMap<>(); + if (controllerMap == null) + componentRightButtons.put(childView, btnControllers); + topBarController.applyRightButtonsOptions(btnControllers, rightButtons, this::createButtonController); + currentRightButtons = new ArrayList<>(btnControllers.values()); + } else { + currentRightButtons = null; + topBarController.clearRightButtons(); } + } + + private void mergeRightButtonsOptions(TopBarOptions options, TopBarButtons buttons, View child) { + if (buttons.right == null) return; + List rightButtons = mergeButtonsWithColor(buttons.right, options.rightButtonColor, + options.rightButtonDisabledColor); + final Map controllerMap = componentRightButtons.get(child); + final Map btnControllers = controllerMap != null ? controllerMap : + new HashMap<>(); + if (controllerMap == null) + componentRightButtons.put(child, btnControllers); + topBarController.mergeRightButtonsOptions(btnControllers, rightButtons, this::createButtonController); + if (options.rightButtonColor.hasValue()) topBar.setOverflowButtonColor(options.rightButtonColor.get()); + } - private void mergeLeftButton(TopBarOptions options, TopBarButtons buttons, View child) { + private void mergeLeftButtonsOptions(TopBarOptions options, TopBarButtons buttons, View child) { if (buttons.left == null) return; - List leftButtons = mergeButtonsWithColor(buttons.left, options.leftButtonColor, options.leftButtonDisabledColor); - List toMerge = getOrCreateButtonControllers(componentLeftButtons.get(child), leftButtons); - List toRemove = difference(currentLeftButtons, toMerge, ButtonController::areButtonsEqual); - forEach(toRemove, ButtonController::destroy); - if (!CollectionUtils.equals(currentLeftButtons, toMerge)) { - componentLeftButtons.put(child, keyBy(toMerge, ButtonController::getButtonInstanceId)); - topBarController.mergeLeftButtons(toMerge, toRemove); - currentLeftButtons = toMerge; - } + List leftButtons = mergeButtonsWithColor(buttons.left, options.leftButtonColor, + options.leftButtonDisabledColor); + final Map controllerMap = componentLeftButtons.get(child); + final Map btnControllers = controllerMap != null ? controllerMap : + new HashMap<>(); + if (controllerMap == null) + componentLeftButtons.put(child, btnControllers); + topBarController.mergeLeftButtonsOptions(btnControllers, leftButtons, this::createButtonController); + + if (options.leftButtonColor.hasValue()) topBar.setOverflowButtonColor(options.leftButtonColor.get()); + } private void mergeBackButton(TopBarButtons buttons, StackController stack) { if (buttons.back.hasValue() && isNullOrEmpty(buttons.left)) { if (buttons.back.visible.isFalse()) { - topBar.clearBackButton(); + topBarController.clearBackButton(); } else if (stack.size() > 1) { - topBar.setBackButton(createButtonController(buttons.back)); + topBarController.setBackButton(createButtonController(buttons.back)); } } } @@ -517,13 +514,15 @@ private void mergeTopBarOptions(TopBarOptions resolveOptions, Options options, S if (topBarOptions.topMargin.hasValue() && topBar.getLayoutParams() instanceof MarginLayoutParams) { ((MarginLayoutParams) topBar.getLayoutParams()).topMargin = UiUtils.dpToPx(activity, topBarOptions.topMargin.get()); } - mergeStatusBarDrawBehindOptions(resolveOptions,options); + mergeStatusBarDrawBehindOptions(resolveOptions, options); if (topBarOptions.title.height.hasValue()) topBar.setTitleHeight(topBarOptions.title.height.get()); if (topBarOptions.title.topMargin.hasValue()) topBar.setTitleTopMargin(topBarOptions.title.topMargin.get()); + if (topBarOptions.animateLeftButtons.hasValue()) - topBar.animateLeftButtons(topBarOptions.animateLeftButtons.isTrue()); + topBarController.animateLeftButtons(topBarOptions.animateLeftButtons.isTrue()); if (topBarOptions.animateRightButtons.hasValue()) - topBar.animateRightButtons(topBarOptions.animateRightButtons.isTrue()); + topBarController.animateRightButtons(topBarOptions.animateRightButtons.isTrue()); + if (topBarOptions.title.component.hasValue()) { TitleBarReactViewController controller = findTitleComponent(topBarOptions.title.component); if (controller == null) { diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/TopBarController.kt b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/TopBarController.kt index f0ac09d20b7..9994f126189 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/TopBarController.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/topbar/TopBarController.kt @@ -4,12 +4,16 @@ import android.animation.Animator import android.content.Context import android.view.MenuItem import android.view.View +import androidx.transition.AutoTransition +import androidx.transition.TransitionManager import androidx.viewpager.widget.ViewPager import com.reactnativenavigation.options.Alignment import com.reactnativenavigation.options.AnimationOptions +import com.reactnativenavigation.options.ButtonOptions import com.reactnativenavigation.options.Options import com.reactnativenavigation.utils.CollectionUtils.forEachIndexed import com.reactnativenavigation.utils.ViewUtils +import com.reactnativenavigation.utils.logd import com.reactnativenavigation.utils.resetViewProperties import com.reactnativenavigation.viewcontrollers.stack.topbar.button.ButtonController import com.reactnativenavigation.viewcontrollers.stack.topbar.title.TitleBarReactViewController @@ -22,7 +26,7 @@ open class TopBarController(private val animator: TopBarAnimator = TopBarAnimato lateinit var view: TopBar private lateinit var leftButtonBar: ButtonBar private lateinit var rightButtonBar: ButtonBar - + private val buttonsTransition = AutoTransition().setDuration(350) val height: Int get() = view.height @@ -54,26 +58,26 @@ open class TopBarController(private val animator: TopBarAnimator = TopBarAnimato fun getPushAnimation(appearingOptions: Options, additionalDy: Float = 0f): Animator? { if (appearingOptions.topBar.animate.isFalse) return null return animator.getPushAnimation( - appearingOptions.animations.push.topBar, - appearingOptions.topBar.visible, - additionalDy + appearingOptions.animations.push.topBar, + appearingOptions.topBar.visible, + additionalDy ) } fun getPopAnimation(appearingOptions: Options, disappearingOptions: Options): Animator? { if (appearingOptions.topBar.animate.isFalse) return null return animator.getPopAnimation( - disappearingOptions.animations.pop.topBar, - appearingOptions.topBar.visible + disappearingOptions.animations.pop.topBar, + appearingOptions.topBar.visible ) } fun getSetStackRootAnimation(appearingOptions: Options, additionalDy: Float = 0f): Animator? { if (appearingOptions.topBar.animate.isFalse) return null return animator.getSetStackRootAnimation( - appearingOptions.animations.setStackRoot.topBar, - appearingOptions.topBar.visible, - additionalDy + appearingOptions.animations.setStackRoot.topBar, + appearingOptions.topBar.visible, + additionalDy ) } @@ -110,20 +114,148 @@ open class TopBarController(private val animator: TopBarAnimator = TopBarAnimato toAdd.reversed().forEachIndexed { i, b -> b.addToMenu(rightButtonBar, i * 10) } } - fun mergeRightButtons(toAdd: List, toRemove: List) { - toRemove.forEach { view.removeRightButton(it) } - toAdd.reversed().forEachIndexed { i, b -> b.addToMenu(rightButtonBar, i * 10) } - } - open fun applyLeftButtons(toAdd: List) { view.clearBackButton() view.clearLeftButtons() forEachIndexed(toAdd) { b: ButtonController, i: Int -> b.addToMenu(leftButtonBar, i * 10) } } - open fun mergeLeftButtons(toAdd: List, toRemove: List) { + fun clearRightButtons() { + view.clearRightButtons() + } + + fun clearLeftButtons() { + view.clearLeftButtons() + } + + fun clearBackButton() { view.clearBackButton() - toRemove.forEach { view.removeLeftButton(it) } - forEachIndexed(toAdd) { b: ButtonController, i: Int -> b.addToMenu(leftButtonBar, i * 10) } + } + + fun setBackButton(backButton: ButtonController?) { + backButton?.let { view.setBackButton(it) } + } + + fun animateRightButtons(shouldAnimate: Boolean) { + view.animateRightButtons(shouldAnimate) + } + + fun animateLeftButtons(shouldAnimate: Boolean) { + view.animateLeftButtons(shouldAnimate) + } + + fun mergeRightButtonsOptions( + btnControllers: MutableMap, + rightButtons: List, + controllerCreator: (ButtonOptions) -> ButtonController + ) { + mergeButtonOptions(btnControllers, rightButtons.reversed(), controllerCreator, rightButtonBar) + } + + fun mergeLeftButtonsOptions( + btnControllers: MutableMap, + leftButtons: List, + controllerCreator: (ButtonOptions) -> ButtonController + ) { + clearBackButton() + mergeButtonOptions(btnControllers, leftButtons, controllerCreator, leftButtonBar) + } + + fun applyRightButtonsOptions( + btnControllers: MutableMap, + rightButtons: List, + controllerCreator: (ButtonOptions) -> ButtonController + ) { + applyButtonsOptions(btnControllers, rightButtons.reversed(), controllerCreator, rightButtonBar) + } + + fun applyLeftButtonsOptions( + btnControllers: MutableMap, + leftButtons: List, + controllerCreator: (ButtonOptions) -> ButtonController + ) { + applyButtonsOptions(btnControllers, leftButtons, controllerCreator, leftButtonBar) + } + + private fun applyButtonsOptions( + btnControllers: MutableMap, + buttons: List, + controllerCreator: (ButtonOptions) -> ButtonController, + buttonBar: ButtonBar + ) { + val intersect = buttons.map { it.id }.intersect(btnControllers.values.map { it.button.id }) + logd( + "Apply ButtonsOptions ${if (rightButtonBar == buttonBar) "Right" else "Left"} intersect ${intersect.size}", + "NewBarMerge" + ) + + if (intersect.size != buttons.size) { + if (buttonBar.shouldAnimate) + TransitionManager.beginDelayedTransition(buttonBar, buttonsTransition) + buttonBar.clearButtons() + btnControllers.map { it.key }.forEach { + btnControllers.remove(it)?.destroy() + } + buttons.forEachIndexed { index, it -> + val order = (btnControllers.size + index) * 10 + val newController = controllerCreator(it) + newController.addToMenu(buttonBar, order) + btnControllers[it.id] = newController + } + } + } + + private fun mergeButtonOptions( + btnControllers: MutableMap, + buttons: List, + controllerCreator: (ButtonOptions) -> ButtonController, + buttonBar: ButtonBar + ) { + + var updatedButtons = buttons.filter { btnControllers[it.id]?.areButtonOptionsChanged(it) ?: false } + var added = buttons.filterNot { btnControllers.containsKey(it.id) } + var removed = btnControllers.filterNot { ctrl -> + buttons.any { + ctrl.key == it.id + } + }.map { it.key to it.value.buttonIntId } + + val rebuildMenu = if (updatedButtons.size == buttons.size) { + val values = btnControllers.values + buttons.filterIndexed { index, buttonOptions -> + val buttonController = btnControllers[buttonOptions.id] + values.indexOf(buttonController) == index + }.size != buttons.size + } else added.isNotEmpty() || removed.isNotEmpty() + + if (rebuildMenu) { + updatedButtons = emptyList() + added = buttons + removed = btnControllers.map { it.key to it.value.buttonIntId } + if (buttonBar.shouldAnimate) + TransitionManager.beginDelayedTransition(buttonBar, buttonsTransition) + } + logd("rebuildMenu:$rebuildMenu ${if (rightButtonBar == buttonBar) "Right" else "Left"}", "NewBarMerge") + + updatedButtons.forEach { + logd( + "Updated ${if (rightButtonBar == buttonBar) "Right" else "Left"} Button ${it.id} with $it", + "NewBarMerge" + ) + btnControllers[it.id]?.mergeButtonOptions(it, buttonBar) + } + removed.forEach { + val (key, buttonIntId) = it + logd("Remove ${if (rightButtonBar == buttonBar) "Right" else "Left"} Button ${key}", "NewBarMerge") + buttonBar.removeButton(buttonIntId) + btnControllers.remove(key)?.destroy() + } + added.forEach { + logd("Add ${if (rightButtonBar == buttonBar) "Right" else "Left"} Button ${it.id}", "NewBarMerge") + val order = buttons.indexOf(it) * 10 + val newController = controllerCreator(it) + newController.addToMenu(buttonBar, order) + btnControllers[it.id] = newController + } } } \ No newline at end of file From d1e37c50b8ef620d48234adfb3d6660dd5fb81f5 Mon Sep 17 00:00:00 2001 From: Ward Abbass Date: Mon, 25 Oct 2021 16:50:39 +0300 Subject: [PATCH 05/35] add more test cases --- .../stack/TopBarControllerTest.kt | 142 ++++++++++++------ 1 file changed, 98 insertions(+), 44 deletions(-) diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt index cb874dee469..00c337608fd 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/TopBarControllerTest.kt @@ -10,12 +10,12 @@ import com.reactnativenavigation.fakes.IconResolverFake import com.reactnativenavigation.mocks.TitleBarButtonCreatorMock import com.reactnativenavigation.options.BackButton import com.reactnativenavigation.options.ButtonOptions +import com.reactnativenavigation.options.ComponentOptions import com.reactnativenavigation.options.Options import com.reactnativenavigation.options.params.Bool import com.reactnativenavigation.options.params.Text import com.reactnativenavigation.react.Constants import com.reactnativenavigation.react.ReactView -import com.reactnativenavigation.utils.CollectionUtils import com.reactnativenavigation.utils.TitleBarHelper import com.reactnativenavigation.utils.resetViewProperties import com.reactnativenavigation.viewcontrollers.stack.topbar.TopBarAnimator @@ -26,8 +26,6 @@ import com.reactnativenavigation.views.stack.StackLayout import com.reactnativenavigation.views.stack.topbar.TopBar import org.assertj.core.api.Java6Assertions.assertThat import org.junit.Test -import java.util.* -import kotlin.collections.LinkedHashMap class TopBarControllerTest : BaseTest() { private lateinit var uut: TopBarController @@ -38,10 +36,16 @@ class TopBarControllerTest : BaseTest() { private lateinit var textButton2: ButtonOptions private lateinit var componentButton: ButtonOptions private lateinit var animator: TopBarAnimator + private lateinit var leftButtonControllers: MutableMap + private lateinit var rightButtonControllers: MutableMap + + private val topBar: View get() = uut.view override fun beforeEach() { + leftButtonControllers= mutableMapOf() + rightButtonControllers= mutableMapOf() activity = newActivity() animator = spy(TopBarAnimator()) uut = createTopBarController() @@ -52,51 +56,80 @@ class TopBarControllerTest : BaseTest() { @Test fun setButton_setsTextButton() { - uut.applyRightButtons(rightButtons(textButton1)!!) - uut.applyLeftButtons(leftButton(leftButton)) + uut.applyRightButtonsOptions(rightButtonControllers, listOf(textButton1)){ + createButtonController(it) + } + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } assertThat(uut.getRightButton(0).title.toString()).isEqualTo(textButton1.text.get()) } @Test fun setButton_setsCustomButton() { - uut.applyLeftButtons(leftButton(leftButton)) - uut.applyRightButtons(rightButtons(componentButton)!!) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf(componentButton)){ + createButtonController(it) + } val btnView = uut.getRightButton(0).actionView as ReactView assertThat(btnView.componentName).isEqualTo(componentButton.component.name.get()) } @Test fun applyRightButtons_emptyButtonsListClearsRightButtons() { - uut.applyLeftButtons(leftButton(leftButton)) - uut.applyRightButtons(rightButtons(componentButton, textButton1)!!) - uut.applyLeftButtons(leftButton(leftButton)) - uut.applyRightButtons(ArrayList()) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf(componentButton, textButton1)){ + createButtonController(it) + } + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf()){ + createButtonController(it) + } assertThat(uut.rightButtonCount).isEqualTo(0) } @Test fun applyRightButtons_previousButtonsAreCleared() { - uut.applyRightButtons(rightButtons(textButton1, componentButton)!!) + uut.applyRightButtonsOptions(rightButtonControllers, listOf(textButton1, componentButton)){ + createButtonController(it) + } assertThat(uut.rightButtonCount).isEqualTo(2) - uut.applyRightButtons(rightButtons(textButton2)!!) + uut.applyRightButtonsOptions(rightButtonControllers, listOf(textButton2)){ + createButtonController(it) + } assertThat(uut.rightButtonCount).isEqualTo(1) } @Test fun applyRightButtons_buttonsAreAddedInReversedOrderToMatchOrderOnIOs() { - uut.applyLeftButtons(leftButton(leftButton)) - uut.applyRightButtons(rightButtons(textButton1, componentButton)!!) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf(textButton1, componentButton)){ + createButtonController(it) + } assertThat(uut.getRightButton(1).title.toString()).isEqualTo(textButton1.text.get()) } @Test fun applyRightButtons_componentButtonIsReapplied() { - val initialButtons = rightButtons(componentButton) - uut.applyRightButtons(initialButtons!!) + uut.applyRightButtonsOptions(rightButtonControllers, listOf( componentButton)){ + createButtonController(it) + } assertThat(uut.getRightButton(0).itemId).isEqualTo(componentButton.intId) - uut.applyRightButtons(rightButtons(textButton1)!!) + uut.applyRightButtonsOptions(rightButtonControllers, listOf( textButton1)){ + createButtonController(it) + } assertThat(uut.getRightButton(0).itemId).isEqualTo(textButton1.intId) - uut.applyRightButtons(initialButtons) + uut.applyRightButtonsOptions(rightButtonControllers, listOf( componentButton)){ + createButtonController(it) + } assertThat(uut.getRightButton(0).itemId).isEqualTo(componentButton.intId) } @@ -115,11 +148,19 @@ class TopBarControllerTest : BaseTest() { @Test fun setLeftButtons_emptyButtonsListClearsLeftButton() { - uut.applyLeftButtons(leftButton(leftButton)) - uut.applyRightButtons(rightButtons(componentButton)!!) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf( componentButton)){ + createButtonController(it) + } assertThat(uut.leftButtonCount).isNotZero() - uut.applyLeftButtons(emptyList()) - uut.applyRightButtons(rightButtons(textButton1)!!) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf()){ + createButtonController(it) + } + uut.applyRightButtonsOptions(rightButtonControllers, listOf( textButton1)){ + createButtonController(it) + } assertThat(uut.leftButtonCount).isZero() } @@ -127,7 +168,9 @@ class TopBarControllerTest : BaseTest() { fun setLeftButtons_clearsBackButton() { uut.view.setBackButton(TitleBarHelper.createButtonController(activity, backButton)) assertThat(uut.view.navigationIcon).isNotNull() - uut.applyLeftButtons(leftButton(leftButton)) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf(leftButton)){ + createButtonController(it) + } assertThat(uut.view.navigationIcon).isNull() } @@ -135,7 +178,9 @@ class TopBarControllerTest : BaseTest() { fun setLeftButtons_emptyButtonsListClearsBackButton() { uut.view.setBackButton(TitleBarHelper.createButtonController(activity, backButton)) assertThat(uut.view.navigationIcon).isNotNull() - uut.applyLeftButtons(emptyList()) + uut.applyLeftButtonsOptions(leftButtonControllers, listOf()){ + createButtonController(it) + } assertThat(uut.view.navigationIcon).isNull() } @@ -255,25 +300,44 @@ class TopBarControllerTest : BaseTest() { assertThat(controllers[textButton1.id]).isEqualTo(controller) } @Test - fun `mergeRightButtons - should remove all and re-add buttons in case of reorder`(){ - val controllers = mutableMapOf() - uut.mergeRightButtonsOptions(controllers, listOf(textButton1, textButton2)) { + fun `mergeRightOptions - should destroy all buttons that was removed`(){ + val componentButton2 = componentButton.copy() + componentButton2.component = ComponentOptions().apply { + this.name = componentButton.component.name + this.componentId = Text("CustomNewComponent") + } + uut.mergeRightButtonsOptions(rightButtonControllers, listOf(textButton1, textButton2, componentButton)) { + createButtonController(it) + } + val removedControllers = mutableMapOf().apply { + putAll(rightButtonControllers) + } + uut.mergeRightButtonsOptions(rightButtonControllers, listOf(componentButton2)) { + createButtonController(it) + } + verify(removedControllers[textButton1.id]!!, times(1)).destroy() + verify(removedControllers[textButton2.id]!!, times(1)).destroy() + verify(removedControllers[componentButton.id]!!, times(1)).destroy() + } + @Test + fun `mergeRightButtons - should remove all and re-add buttons in case of reorder, without destroy`(){ + uut.mergeRightButtonsOptions(rightButtonControllers, listOf(textButton1, textButton2)) { createButtonController(it) } assertThat(uut.getRightButton(1).itemId ).isEqualTo(textButton1.intId) assertThat(uut.getRightButton(0).itemId ).isEqualTo(textButton2.intId) - val removedControllers = mutableMapOf().apply { putAll(controllers) } - uut.mergeRightButtonsOptions(controllers, listOf(textButton2.copy(), textButton1.copy())) { + val removedControllers = mutableMapOf().apply { putAll(rightButtonControllers) } + uut.mergeRightButtonsOptions(rightButtonControllers, listOf(textButton2.copy(), textButton1.copy())) { createButtonController(it) } assertThat(uut.getRightButton(1).itemId ).isEqualTo(textButton2.intId) assertThat(uut.getRightButton(0).itemId ).isEqualTo(textButton1.intId) - verify(removedControllers[textButton1.id]!!, times(1)).destroy() - verify(removedControllers[textButton2.id]!!, times(1)).destroy() + verify(removedControllers[textButton1.id]!!, never()).destroy() + verify(removedControllers[textButton2.id]!!, never()).destroy() - verify(controllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) - verify(controllers[textButton2.id]!!, times(1)).addToMenu(any(), any()) + verify(rightButtonControllers[textButton1.id]!!, times(1)).addToMenu(any(), any()) + verify(rightButtonControllers[textButton2.id]!!, times(1)).addToMenu(any(), any()) } @Test fun `mergeRightButtons - should rebuild menu when adding menu items`(){ @@ -330,8 +394,6 @@ class TopBarControllerTest : BaseTest() { verify(controllers[textButton2.id]!!, times(1)).destroy() } - - private fun createButtonController(it: ButtonOptions) = spy(ButtonController(activity, ButtonPresenter(activity, it, IconResolverFake(activity)), it, TitleBarButtonCreatorMock(), object : ButtonController.OnClickListener { @@ -360,14 +422,6 @@ class TopBarControllerTest : BaseTest() { return button } - private fun leftButton(leftButton: ButtonOptions): List { - return listOf(TitleBarHelper.createButtonController(activity, leftButton)) - } - - private fun rightButtons(vararg buttons: ButtonOptions): List? { - return CollectionUtils.map(listOf(*buttons)) { button: ButtonOptions? -> TitleBarHelper.createButtonController(activity, button) } - } - private fun createTopBarController() = spy(object : TopBarController(animator) { override fun createTopBar(context: Context, stackLayout: StackLayout): TopBar { return spy(super.createTopBar(context, stackLayout)) From 26361f916279d574206d43a8cd5bfc79aca02f12 Mon Sep 17 00:00:00 2001 From: Ward Abbass Date: Mon, 25 Oct 2021 16:50:56 +0300 Subject: [PATCH 06/35] rename measurer test --- ...urer.kt => TitleAndButtonsMeasurerTest.kt} | 4 +- playground/src/screens/StressMergeOptions.tsx | 121 ++++++++++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) rename lib/android/app/src/test/java/com/reactnativenavigation/utils/{TitleAndButtonsMeasurer.kt => TitleAndButtonsMeasurerTest.kt} (99%) create mode 100644 playground/src/screens/StressMergeOptions.tsx diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurer.kt b/lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurerTest.kt similarity index 99% rename from lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurer.kt rename to lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurerTest.kt index d8509ee5089..3908a776979 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurer.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/utils/TitleAndButtonsMeasurerTest.kt @@ -2,13 +2,13 @@ package com.reactnativenavigation.utils import com.reactnativenavigation.BaseTest import com.reactnativenavigation.views.stack.topbar.titlebar.DEFAULT_LEFT_MARGIN_PX +import com.reactnativenavigation.views.stack.topbar.titlebar.resolveHorizontalTitleBoundsLimit import com.reactnativenavigation.views.stack.topbar.titlebar.resolveLeftButtonsBounds import com.reactnativenavigation.views.stack.topbar.titlebar.resolveRightButtonsBounds -import com.reactnativenavigation.views.stack.topbar.titlebar.resolveHorizontalTitleBoundsLimit import org.junit.Test import kotlin.test.assertEquals -class TitleAndButtonsMeasurer : BaseTest() { +class TitleAndButtonsMeasurerTest : BaseTest() { private val parentWidth = 1080 @Test diff --git a/playground/src/screens/StressMergeOptions.tsx b/playground/src/screens/StressMergeOptions.tsx new file mode 100644 index 00000000000..b2728859c77 --- /dev/null +++ b/playground/src/screens/StressMergeOptions.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import { Button, View } from 'react-native-ui-lib'; +import { + Navigation, + Options, + NavigationComponent, + NavigationComponentProps, +} from 'react-native-navigation'; +import Root from '../components/Root'; + +interface Props { + flag: boolean; + componentId: string; +} + +class StressedComponent extends React.Component { + constructor(props: Props) { + super(props); + } + + componentDidUpdate() { + console.log('mergeOptiosn called form componentDidUpdate'); + let buttons = []; + if (this.props.flag) { + buttons.push({ + text: 'Save', + id: 'save', + testID: 'nav-save', + disabledColor: 'gray', + enabled: this.props.flag, + }); + } else { + buttons = []; + } + Navigation.mergeOptions(this.props.componentId, { + topBar: { + animateLeftButtons: true, + animateRightButtons: true, + title: { + text: `MergeOptions Stress Test ${this.props.flag ? ' Looooooooooooooooooong' : 'Short'}`, + }, + rightButtons: buttons, + leftButtons: buttons, + }, + }); + } + render() { + return ( + +