react-native-navigation 7.23.1-snapshot.438 → 7.23.1-snapshot.440

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,10 +6,40 @@ import com.reactnativenavigation.options.parsers.BoolParser
6
6
  import org.json.JSONObject
7
7
 
8
8
 
9
+ sealed class HwBackBottomTabsBehaviour {
10
+ object Undefined : HwBackBottomTabsBehaviour() {
11
+ override fun hasValue(): Boolean = false
12
+ }
13
+
14
+ object Exit : HwBackBottomTabsBehaviour()
15
+ object PrevSelection : HwBackBottomTabsBehaviour()
16
+ object JumpToFirst : HwBackBottomTabsBehaviour()
17
+
18
+ open fun hasValue(): Boolean = true
19
+
20
+ companion object {
21
+ private const val BEHAVIOUR_EXIT = "exit"
22
+ private const val BEHAVIOUR_PREV = "previous"
23
+ private const val BEHAVIOUR_FIRST = "first"
24
+ fun fromString(behaviour: String?): HwBackBottomTabsBehaviour {
25
+ return when (behaviour) {
26
+ BEHAVIOUR_PREV -> PrevSelection
27
+ BEHAVIOUR_FIRST -> JumpToFirst
28
+ BEHAVIOUR_EXIT -> Exit
29
+ else -> Undefined
30
+ }
31
+ }
32
+ }
33
+ }
34
+
9
35
  open class HardwareBackButtonOptions(json: JSONObject? = null) {
10
36
 
11
- @JvmField var dismissModalOnPress: Bool = NullBool()
12
- @JvmField var popStackOnPress: Bool = NullBool()
37
+ @JvmField
38
+ var dismissModalOnPress: Bool = NullBool()
39
+
40
+ @JvmField
41
+ var popStackOnPress: Bool = NullBool()
42
+ var bottomTabOnPress: HwBackBottomTabsBehaviour = HwBackBottomTabsBehaviour.Undefined
13
43
 
14
44
  init {
15
45
  parse(json)
@@ -18,16 +48,19 @@ open class HardwareBackButtonOptions(json: JSONObject? = null) {
18
48
  fun mergeWith(other: HardwareBackButtonOptions) {
19
49
  if (other.dismissModalOnPress.hasValue()) dismissModalOnPress = other.dismissModalOnPress
20
50
  if (other.popStackOnPress.hasValue()) popStackOnPress = other.popStackOnPress
51
+ if (other.bottomTabOnPress.hasValue()) bottomTabOnPress = other.bottomTabOnPress
21
52
  }
22
53
 
23
54
  fun mergeWithDefault(defaultOptions: HardwareBackButtonOptions) {
24
55
  if (!dismissModalOnPress.hasValue()) dismissModalOnPress = defaultOptions.dismissModalOnPress
25
56
  if (!popStackOnPress.hasValue()) popStackOnPress = defaultOptions.popStackOnPress
57
+ if (!bottomTabOnPress.hasValue()) bottomTabOnPress = defaultOptions.bottomTabOnPress
26
58
  }
27
59
 
28
60
  private fun parse(json: JSONObject?) {
29
61
  json ?: return
30
62
  dismissModalOnPress = BoolParser.parse(json, "dismissModalOnPress")
31
63
  popStackOnPress = BoolParser.parse(json, "popStackOnPress")
64
+ bottomTabOnPress = HwBackBottomTabsBehaviour.fromString(json.optString("bottomTabsOnPress"))
32
65
  }
33
66
  }
@@ -13,6 +13,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout;
13
13
  import com.aurelhubert.ahbottomnavigation.AHBottomNavigation;
14
14
  import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem;
15
15
  import com.reactnativenavigation.options.BottomTabOptions;
16
+ import com.reactnativenavigation.options.HwBackBottomTabsBehaviour;
16
17
  import com.reactnativenavigation.options.Options;
17
18
  import com.reactnativenavigation.react.CommandListener;
18
19
  import com.reactnativenavigation.react.CommandListenerAdapter;
@@ -29,6 +30,8 @@ import com.reactnativenavigation.views.bottomtabs.BottomTabsContainer;
29
30
  import com.reactnativenavigation.views.bottomtabs.BottomTabsLayout;
30
31
 
31
32
  import java.util.Collection;
33
+ import java.util.Deque;
34
+ import java.util.LinkedList;
32
35
  import java.util.List;
33
36
 
34
37
  import static com.reactnativenavigation.utils.CollectionUtils.forEach;
@@ -39,6 +42,7 @@ public class BottomTabsController extends ParentController<BottomTabsLayout> imp
39
42
 
40
43
  private BottomTabsContainer bottomTabsContainer;
41
44
  private BottomTabs bottomTabs;
45
+ private final Deque<Integer> selectionStack;
42
46
  private final List<ViewController<?>> tabs;
43
47
  private final EventEmitter eventEmitter;
44
48
  private final ImageLoader imageLoader;
@@ -66,6 +70,7 @@ public class BottomTabsController extends ParentController<BottomTabsLayout> imp
66
70
  this.presenter = bottomTabsPresenter;
67
71
  this.tabPresenter = bottomTabPresenter;
68
72
  forEach(tabs, tab -> tab.setParentController(this));
73
+ selectionStack = new LinkedList<>();
69
74
  }
70
75
 
71
76
  @Override
@@ -156,7 +161,27 @@ public class BottomTabsController extends ParentController<BottomTabsLayout> imp
156
161
 
157
162
  @Override
158
163
  public boolean handleBack(CommandListener listener) {
159
- return !tabs.isEmpty() && tabs.get(bottomTabs.getCurrentItem()).handleBack(listener);
164
+ final boolean childBack = !tabs.isEmpty() && tabs.get(bottomTabs.getCurrentItem()).handleBack(listener);
165
+ final Options options = resolveCurrentOptions();
166
+ if (!childBack) {
167
+ if (options.hardwareBack.getBottomTabOnPress() instanceof HwBackBottomTabsBehaviour.PrevSelection) {
168
+ if (!selectionStack.isEmpty()) {
169
+ final int prevSelectedTabIndex = selectionStack.poll();
170
+ selectTab(prevSelectedTabIndex, false);
171
+ return true;
172
+ }
173
+ } else if (options.hardwareBack.getBottomTabOnPress() instanceof HwBackBottomTabsBehaviour.JumpToFirst) {
174
+ if (getSelectedIndex() != 0) {
175
+ selectTab(0, false);
176
+ return true;
177
+ } else {
178
+ return false;
179
+ }
180
+ } else {
181
+ return false;
182
+ }
183
+ }
184
+ return childBack;
160
185
  }
161
186
 
162
187
  @Override
@@ -203,7 +228,7 @@ public class BottomTabsController extends ParentController<BottomTabsLayout> imp
203
228
  });
204
229
  }
205
230
 
206
- int getSelectedIndex() {
231
+ public int getSelectedIndex() {
207
232
  return bottomTabs.getCurrentItem();
208
233
  }
209
234
 
@@ -239,6 +264,12 @@ public class BottomTabsController extends ParentController<BottomTabsLayout> imp
239
264
 
240
265
  @Override
241
266
  public void selectTab(final int newIndex) {
267
+ final boolean enableSelectionHistory = resolveCurrentOptions().hardwareBack.getBottomTabOnPress() instanceof HwBackBottomTabsBehaviour.PrevSelection;
268
+ selectTab(newIndex, enableSelectionHistory);
269
+ }
270
+
271
+ private void selectTab(int newIndex, boolean enableSelectionHistory) {
272
+ saveTabSelection(newIndex, enableSelectionHistory);
242
273
  tabsAttacher.onTabSelected(tabs.get(newIndex));
243
274
  getCurrentView().setVisibility(View.INVISIBLE);
244
275
  bottomTabs.setCurrentItem(newIndex, false);
@@ -246,6 +277,15 @@ public class BottomTabsController extends ParentController<BottomTabsLayout> imp
246
277
  getCurrentChild().onViewDidAppear();
247
278
  }
248
279
 
280
+ private void saveTabSelection(int newIndex, boolean enableSelectionHistory) {
281
+ if (enableSelectionHistory) {
282
+ if (selectionStack.isEmpty()
283
+ || selectionStack.peek() != newIndex
284
+ || bottomTabs.getCurrentItem() != newIndex)
285
+ selectionStack.offerFirst(bottomTabs.getCurrentItem());
286
+ }
287
+ }
288
+
249
289
  @NonNull
250
290
  private ViewGroup getCurrentView() {
251
291
  return tabs.get(bottomTabs.getCurrentItem()).getView();
@@ -0,0 +1,572 @@
1
+ package com.reactnativenavigation.viewcontrollers.bottomtabs
2
+
3
+ import android.app.Activity
4
+ import android.graphics.Color
5
+ import android.view.Gravity
6
+ import android.view.View
7
+ import android.view.ViewGroup.MarginLayoutParams
8
+ import androidx.coordinatorlayout.widget.CoordinatorLayout
9
+ import com.aurelhubert.ahbottomnavigation.AHBottomNavigation.TitleState
10
+ import com.reactnativenavigation.BaseTest
11
+ import com.reactnativenavigation.TestUtils
12
+ import com.reactnativenavigation.mocks.ImageLoaderMock.mock
13
+ import com.reactnativenavigation.mocks.SimpleViewController
14
+ import com.reactnativenavigation.mocks.TypefaceLoaderMock
15
+ import com.reactnativenavigation.options.BottomTabsOptions
16
+ import com.reactnativenavigation.options.HwBackBottomTabsBehaviour
17
+ import com.reactnativenavigation.options.Options
18
+ import com.reactnativenavigation.options.params.*
19
+ import com.reactnativenavigation.react.CommandListenerAdapter
20
+ import com.reactnativenavigation.react.events.EventEmitter
21
+ import com.reactnativenavigation.utils.OptionHelper
22
+ import com.reactnativenavigation.utils.SystemUiUtils.getStatusBarHeight
23
+ import com.reactnativenavigation.utils.SystemUiUtils.saveStatusBarHeight
24
+ import com.reactnativenavigation.viewcontrollers.bottomtabs.attacher.BottomTabsAttacher
25
+ import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry
26
+ import com.reactnativenavigation.viewcontrollers.fakes.FakeParentController
27
+ import com.reactnativenavigation.viewcontrollers.parent.ParentController
28
+ import com.reactnativenavigation.viewcontrollers.stack.StackController
29
+ import com.reactnativenavigation.viewcontrollers.viewcontroller.Presenter
30
+ import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController
31
+ import com.reactnativenavigation.views.bottomtabs.BottomTabs
32
+ import com.reactnativenavigation.views.bottomtabs.BottomTabsContainer
33
+ import com.reactnativenavigation.views.bottomtabs.BottomTabsLayout
34
+ import org.assertj.core.api.Java6Assertions
35
+ import org.junit.Test
36
+ import org.mockito.ArgumentCaptor
37
+ import org.mockito.ArgumentMatchers
38
+ import org.mockito.Mockito
39
+ import org.mockito.kotlin.any
40
+ import org.mockito.kotlin.eq
41
+ import org.mockito.kotlin.times
42
+ import java.util.*
43
+
44
+ class BottomTabsControllerTest : BaseTest() {
45
+ private lateinit var activity: Activity
46
+ private lateinit var bottomTabs: BottomTabs
47
+ private lateinit var bottomTabsContainer: BottomTabsContainer
48
+ private lateinit var uut: BottomTabsController
49
+ private val initialOptions = Options()
50
+ private lateinit var child1: ViewController<*>
51
+ private lateinit var child2: ViewController<*>
52
+ private lateinit var child3: ViewController<*>
53
+ private lateinit var stackChild: ViewController<*>
54
+ private lateinit var child4: StackController
55
+ private lateinit var child5: ViewController<*>
56
+ private val tabOptions = OptionHelper.createBottomTabOptions()
57
+ private val imageLoaderMock = mock()
58
+ private lateinit var eventEmitter: EventEmitter
59
+ private lateinit var childRegistry: ChildControllersRegistry
60
+ private lateinit var tabs: MutableList<ViewController<*>>
61
+ private lateinit var presenter: BottomTabsPresenter
62
+ private lateinit var bottomTabPresenter: BottomTabPresenter
63
+ private lateinit var tabsAttacher: BottomTabsAttacher
64
+ override fun beforeEach() {
65
+ super.beforeEach()
66
+ activity = newActivity()
67
+ childRegistry = ChildControllersRegistry()
68
+ eventEmitter = Mockito.mock(EventEmitter::class.java)
69
+ prepareViewsForTests()
70
+ saveStatusBarHeight(63)
71
+ }
72
+
73
+ @Test
74
+ fun createView_checkProperStructure() {
75
+ idleMainLooper()
76
+ Java6Assertions.assertThat(uut.view).isInstanceOf(CoordinatorLayout::class.java)
77
+ Java6Assertions.assertThat(uut.view.getChildAt(uut.view.childCount - 1)).isInstanceOf(
78
+ BottomTabsContainer::class.java
79
+ )
80
+ Java6Assertions.assertThat((uut.bottomTabsContainer.layoutParams as CoordinatorLayout.LayoutParams).gravity)
81
+ .isEqualTo(Gravity.BOTTOM)
82
+ }
83
+
84
+ @Test
85
+ fun createView_tabsWithoutIconsAreAccepted() {
86
+ tabOptions.bottomTabOptions.icon = NullText()
87
+ prepareViewsForTests()
88
+ Java6Assertions.assertThat(uut.bottomTabs.itemsCount).isEqualTo(tabs.size)
89
+ }
90
+
91
+ @Test
92
+ fun createView_showTitlesWhenAllTabsDontHaveIcons() {
93
+ tabOptions.bottomTabOptions.icon = NullText()
94
+ Java6Assertions.assertThat(tabOptions.bottomTabsOptions.titleDisplayMode.hasValue()).isFalse
95
+ prepareViewsForTests()
96
+ presenter.applyOptions(Options.EMPTY)
97
+ Java6Assertions.assertThat(bottomTabsContainer.bottomTabs.titleState).isEqualTo(TitleState.ALWAYS_SHOW)
98
+ }
99
+
100
+ @Test(expected = RuntimeException::class)
101
+ fun setTabs_ThrowWhenMoreThan5() {
102
+ tabs.add(SimpleViewController(activity, childRegistry, "6", tabOptions))
103
+ createBottomTabs()
104
+ idleMainLooper()
105
+ }
106
+
107
+ @Test
108
+ fun parentControllerIsSet() {
109
+ uut = createBottomTabs()
110
+ for (tab in tabs) {
111
+ Java6Assertions.assertThat(tab.parentController).isEqualTo(uut)
112
+ }
113
+ }
114
+
115
+ @Test
116
+ fun setTabs_allChildViewsAreAttachedToHierarchy() {
117
+ uut.onViewWillAppear()
118
+ Java6Assertions.assertThat(uut.view.childCount).isEqualTo(6)
119
+ for (child in uut.childControllers) {
120
+ Java6Assertions.assertThat(child.view.parent).isNotNull
121
+ }
122
+ }
123
+
124
+ @Test
125
+ fun setTabs_firstChildIsVisibleOtherAreGone() {
126
+ uut.onViewWillAppear()
127
+ for (i in uut.childControllers.indices) {
128
+ Java6Assertions.assertThat(uut.view.getChildAt(i)).isEqualTo(tabs[i].view)
129
+ Java6Assertions.assertThat(uut.view.getChildAt(i).visibility)
130
+ .isEqualTo(if (i == 0) View.VISIBLE else View.INVISIBLE)
131
+ }
132
+ }
133
+
134
+ @Test
135
+ fun onTabSelected() {
136
+ uut.ensureViewIsCreated()
137
+ Java6Assertions.assertThat(uut.selectedIndex).isZero
138
+ Java6Assertions.assertThat(((uut.childControllers as List<*>)[0] as ViewController<*>).view.visibility)
139
+ .isEqualTo(
140
+ View.VISIBLE
141
+ )
142
+ uut.onTabSelected(3, false)
143
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(3)
144
+ Java6Assertions.assertThat(((uut.childControllers as List<*>)[0] as ViewController<*>).view.visibility)
145
+ .isEqualTo(
146
+ View.INVISIBLE
147
+ )
148
+ Java6Assertions.assertThat(((uut.childControllers as List<*>)[3] as ViewController<*>).view.visibility)
149
+ .isEqualTo(
150
+ View.VISIBLE
151
+ )
152
+ Mockito.verify(eventEmitter).emitBottomTabSelected(0, 3)
153
+ }
154
+
155
+ @Test
156
+ fun onTabReSelected() {
157
+ uut.ensureViewIsCreated()
158
+ Java6Assertions.assertThat(uut.selectedIndex).isZero
159
+ uut.onTabSelected(0, true)
160
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(0)
161
+ Java6Assertions.assertThat(((uut.childControllers as List<*>)[0] as ViewController<*>).view.parent).isNotNull
162
+ Mockito.verify(eventEmitter).emitBottomTabSelected(0, 0)
163
+ }
164
+
165
+ @Test
166
+ fun handleBack_DelegatesToSelectedChild() {
167
+ uut.ensureViewIsCreated()
168
+ Java6Assertions.assertThat(uut.handleBack(CommandListenerAdapter())).isFalse
169
+ uut.selectTab(4)
170
+ Java6Assertions.assertThat(uut.handleBack(CommandListenerAdapter())).isTrue
171
+ Mockito.verify(child5).handleBack(ArgumentMatchers.any())
172
+ }
173
+
174
+ @Test
175
+ fun `handleBack - PrevSelection - reselect tab selection history of navigation when root has bottom tabs`() {
176
+ val options = Options().apply {
177
+ hardwareBack.bottomTabOnPress = HwBackBottomTabsBehaviour.PrevSelection
178
+ }
179
+ prepareViewsForTests(options = options)
180
+ idleMainLooper()
181
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(0)
182
+
183
+ uut.selectTab(1)
184
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(1)
185
+
186
+ uut.selectTab(3)
187
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(3)
188
+
189
+ uut.selectTab(2)
190
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(2)
191
+
192
+ Java6Assertions.assertThat(uut.handleBack(CommandListenerAdapter())).isTrue
193
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(3)
194
+
195
+ Java6Assertions.assertThat(uut.handleBack(CommandListenerAdapter())).isTrue
196
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(1)
197
+
198
+ Java6Assertions.assertThat(uut.handleBack(CommandListenerAdapter())).isTrue
199
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(0)
200
+
201
+ Java6Assertions.assertThat(uut.handleBack(CommandListenerAdapter())).isFalse
202
+ }
203
+
204
+ @Test
205
+ fun `handleBack - JumpToFirst - reselect first tab`() {
206
+ val options = Options().apply {
207
+ hardwareBack.bottomTabOnPress = HwBackBottomTabsBehaviour.JumpToFirst
208
+ }
209
+ prepareViewsForTests(options = options)
210
+ idleMainLooper()
211
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(0)
212
+
213
+ uut.selectTab(1)
214
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(1)
215
+
216
+ uut.selectTab(3)
217
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(3)
218
+
219
+ uut.selectTab(2)
220
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(2)
221
+
222
+ Java6Assertions.assertThat(uut.handleBack(CommandListenerAdapter())).isTrue
223
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(0)
224
+
225
+ Java6Assertions.assertThat(uut.handleBack(CommandListenerAdapter())).isFalse
226
+ }
227
+
228
+ @Test
229
+ fun `handleBack - Default - should exit app with no reselection`() {
230
+
231
+ prepareViewsForTests()
232
+ idleMainLooper()
233
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(0)
234
+
235
+ uut.selectTab(1)
236
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(1)
237
+
238
+ uut.selectTab(3)
239
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(3)
240
+
241
+ uut.selectTab(2)
242
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(2)
243
+
244
+ Java6Assertions.assertThat(uut.handleBack(CommandListenerAdapter())).isFalse
245
+ }
246
+
247
+ @Test
248
+ fun `handleBack - Exit - reselect first tab`() {
249
+ val options = Options().apply {
250
+ hardwareBack.bottomTabOnPress = HwBackBottomTabsBehaviour.Exit
251
+ }
252
+ prepareViewsForTests(options = options)
253
+ idleMainLooper()
254
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(0)
255
+
256
+ uut.selectTab(1)
257
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(1)
258
+
259
+ uut.selectTab(3)
260
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(3)
261
+
262
+ uut.selectTab(2)
263
+ Java6Assertions.assertThat(uut.selectedIndex).isEqualTo(2)
264
+
265
+ Java6Assertions.assertThat(uut.handleBack(CommandListenerAdapter())).isFalse
266
+ }
267
+
268
+ @Test
269
+ fun applyChildOptions_bottomTabsOptionsAreClearedAfterApply() {
270
+ val parent = Mockito.mock(ParentController::class.java)
271
+ uut.parentController = parent
272
+ child1.options.bottomTabsOptions.backgroundColor = ThemeColour(Colour(Color.RED))
273
+ child1.onViewWillAppear()
274
+ val optionsCaptor = ArgumentCaptor.forClass(
275
+ Options::class.java
276
+ )
277
+ Mockito.verify(parent).applyChildOptions(optionsCaptor.capture(), ArgumentMatchers.any())
278
+ Java6Assertions.assertThat(optionsCaptor.value.bottomTabsOptions.backgroundColor.hasValue()).isFalse
279
+ }
280
+
281
+ @Test
282
+ fun applyOptions_bottomTabsCreateViewOnlyOnce() {
283
+ idleMainLooper()
284
+ Mockito.verify(presenter).applyOptions(any())
285
+ Mockito.verify(bottomTabsContainer.bottomTabs, times(2))
286
+ .superCreateItems() // first time when view is created, second time when options are applied
287
+ }
288
+
289
+ @Test
290
+ fun onSizeChanged_recreateItemsIfSizeHasChanged() {
291
+ val numberOfPreviousInvocations = 1
292
+ bottomTabs.onSizeChanged(0, 0, 0, 0)
293
+ Mockito.verify(bottomTabs, Mockito.times(numberOfPreviousInvocations)).superCreateItems()
294
+ bottomTabs.onSizeChanged(100, 0, 0, 0)
295
+ Mockito.verify(bottomTabs, Mockito.times(numberOfPreviousInvocations)).superCreateItems()
296
+ bottomTabs.onSizeChanged(1080, 147, 0, 0)
297
+ Mockito.verify(bottomTabs, Mockito.times(numberOfPreviousInvocations + 1)).superCreateItems()
298
+ bottomTabs.onSizeChanged(1920, 147, 0, 0)
299
+ Mockito.verify(bottomTabs, Mockito.times(numberOfPreviousInvocations + 2)).superCreateItems()
300
+ Mockito.`when`(bottomTabs.itemsCount).thenReturn(0)
301
+ bottomTabs.onSizeChanged(1080, 147, 0, 0)
302
+ Mockito.verify(bottomTabs, Mockito.times(numberOfPreviousInvocations + 2)).superCreateItems()
303
+ }
304
+
305
+ @Test
306
+ fun mergeOptions_currentTabIndex() {
307
+ uut.ensureViewIsCreated()
308
+ Java6Assertions.assertThat(uut.selectedIndex).isZero
309
+ val options = Options()
310
+ options.bottomTabsOptions.currentTabIndex = Number(1)
311
+ uut.mergeOptions(options)
312
+ Java6Assertions.assertThat(uut.selectedIndex).isOne
313
+ Mockito.verify(eventEmitter, Mockito.times(0)).emitBottomTabSelected(
314
+ ArgumentMatchers.any(
315
+ Int::class.java
316
+ ), ArgumentMatchers.any(Int::class.java)
317
+ )
318
+ }
319
+
320
+ @Test
321
+ fun mergeOptions_drawBehind() {
322
+ Java6Assertions.assertThat(uut.getBottomInset(child1)).isEqualTo(uut.bottomTabs.height)
323
+ val o1 = Options()
324
+ o1.bottomTabsOptions.drawBehind = Bool(true)
325
+ child1.mergeOptions(o1)
326
+ Java6Assertions.assertThat(uut.getBottomInset(child1)).isEqualTo(0)
327
+ val o2 = Options()
328
+ o2.topBar.title.text = Text("Some text")
329
+ child1.mergeOptions(o1)
330
+ Java6Assertions.assertThat(uut.getBottomInset(child1)).isEqualTo(0)
331
+ }
332
+
333
+ @Test
334
+ fun mergeOptions_drawBehind_stack() {
335
+ uut.ensureViewIsCreated()
336
+ uut.selectTab(3)
337
+ Java6Assertions.assertThat((stackChild.view.layoutParams as MarginLayoutParams).bottomMargin).isEqualTo(
338
+ bottomTabs.height
339
+ )
340
+ val o1 = Options()
341
+ o1.bottomTabsOptions.drawBehind = Bool(true)
342
+ stackChild.mergeOptions(o1)
343
+ Java6Assertions.assertThat((stackChild.view.layoutParams as MarginLayoutParams).bottomMargin).isEqualTo(0)
344
+ }
345
+
346
+ @Test
347
+ fun mergeOptions_mergesBottomTabOptions() {
348
+ val options = Options()
349
+ uut.mergeOptions(options)
350
+ Mockito.verify(bottomTabPresenter).mergeOptions(options)
351
+ }
352
+
353
+ @Test
354
+ fun applyChildOptions_resolvedOptionsAreUsed() {
355
+ val childOptions = Options()
356
+ val pushedScreen = SimpleViewController(activity, childRegistry, "child4.1", childOptions)
357
+ disablePushAnimation(pushedScreen)
358
+ child4 = spyOnStack(pushedScreen)
359
+ tabs = ArrayList(listOf(child4))
360
+ tabsAttacher = BottomTabsAttacher(tabs, presenter, Options.EMPTY)
361
+ initialOptions.bottomTabsOptions.currentTabIndex = Number(0)
362
+ val resolvedOptions = Options()
363
+ uut = object : BottomTabsController(
364
+ activity,
365
+ tabs,
366
+ childRegistry,
367
+ eventEmitter,
368
+ imageLoaderMock,
369
+ "uut",
370
+ initialOptions,
371
+ Presenter(activity, Options()),
372
+ tabsAttacher,
373
+ presenter,
374
+ BottomTabPresenter(activity, tabs, mock(), TypefaceLoaderMock(), Options())
375
+ ) {
376
+ override fun resolveCurrentOptions(): Options {
377
+ return resolvedOptions
378
+ }
379
+
380
+ override fun createBottomTabs(): BottomTabs {
381
+ return object : BottomTabs(activity) {
382
+ override fun createItems() {}
383
+ }
384
+ }
385
+ }
386
+ activity.setContentView(uut.view)
387
+ idleMainLooper()
388
+ Mockito.verify(presenter, Mockito.times(2))
389
+ .applyChildOptions(eq(resolvedOptions), any())
390
+ }
391
+
392
+ @Test
393
+ fun child_mergeOptions_currentTabIndex() {
394
+ uut.ensureViewIsCreated()
395
+ Java6Assertions.assertThat(uut.selectedIndex).isZero
396
+ val options = Options()
397
+ options.bottomTabsOptions.currentTabIndex = Number(1)
398
+ child1.mergeOptions(options)
399
+ Java6Assertions.assertThat(uut.selectedIndex).isOne
400
+ }
401
+
402
+ @Test
403
+ fun resolveCurrentOptions_returnsFirstTabIfInvokedBeforeViewIsCreated() {
404
+ uut = createBottomTabs()
405
+ Java6Assertions.assertThat(uut.currentChild).isEqualTo(tabs[0])
406
+ }
407
+
408
+ @Test
409
+ fun buttonPressInvokedOnCurrentTab() {
410
+ uut.ensureViewIsCreated()
411
+ uut.selectTab(4)
412
+ uut.sendOnNavigationButtonPressed("btn1")
413
+ Mockito.verify(child5, Mockito.times(1)).sendOnNavigationButtonPressed("btn1")
414
+ }
415
+
416
+ @Test
417
+ fun push() {
418
+ uut.selectTab(3)
419
+ val stackChild2 = SimpleViewController(activity, childRegistry, "stackChild2", Options())
420
+ disablePushAnimation(stackChild2)
421
+ TestUtils.hideBackButton(stackChild2)
422
+ Java6Assertions.assertThat(child4.size()).isEqualTo(1)
423
+ child4.push(stackChild2, CommandListenerAdapter())
424
+ Java6Assertions.assertThat(child4.size()).isEqualTo(2)
425
+ }
426
+
427
+ @Test
428
+ fun oneTimeOptionsAreAppliedOnce() {
429
+ val options = Options()
430
+ options.bottomTabsOptions.currentTabIndex = Number(1)
431
+ Java6Assertions.assertThat(uut.selectedIndex).isZero
432
+ uut.mergeOptions(options)
433
+ Java6Assertions.assertThat(uut.selectedIndex).isOne
434
+ Java6Assertions.assertThat(uut.options.bottomTabsOptions.currentTabIndex.hasValue()).isFalse
435
+ Java6Assertions.assertThat(uut.initialOptions.bottomTabsOptions.currentTabIndex.hasValue()).isFalse
436
+ }
437
+
438
+ @Test
439
+ fun selectTab() {
440
+ uut.selectTab(1)
441
+ Mockito.verify(tabsAttacher).onTabSelected(tabs[1])
442
+ }
443
+
444
+ @Test
445
+ fun selectTab_onViewDidAppearIsInvokedAfterSelection() {
446
+ uut.selectTab(1)
447
+ Mockito.verify(child2).onViewDidAppear()
448
+ }
449
+
450
+ @Test
451
+ fun creatingTabs_onViewDidAppearInvokedAfterInitialTabIndexSet() {
452
+ val options = Options.EMPTY.copy()
453
+ options.bottomTabsOptions.currentTabIndex = Number(1)
454
+ prepareViewsForTests(options.bottomTabsOptions)
455
+ idleMainLooper()
456
+ Mockito.verify(tabs[0], Mockito.times(0)).onViewDidAppear()
457
+ Mockito.verify(tabs[1], Mockito.times(1)).onViewDidAppear()
458
+ Mockito.verify(tabs[2], Mockito.times(0)).onViewDidAppear()
459
+ Mockito.verify(tabs[3], Mockito.times(0)).onViewDidAppear()
460
+ Mockito.verify(tabs[4], Mockito.times(0)).onViewDidAppear()
461
+ }
462
+
463
+ @Test
464
+ fun topInset() {
465
+ Java6Assertions.assertThat(child1.topInset).isEqualTo(statusBarHeight)
466
+ Java6Assertions.assertThat(child2.topInset).isEqualTo(statusBarHeight)
467
+ child1.options.statusBar.drawBehind = Bool(true)
468
+ Java6Assertions.assertThat(child1.topInset).isEqualTo(0)
469
+ Java6Assertions.assertThat(child2.topInset).isEqualTo(statusBarHeight)
470
+ Java6Assertions.assertThat(stackChild.topInset).isEqualTo(statusBarHeight + child4.topBar.height)
471
+ }
472
+
473
+ @Test
474
+ fun bottomInset_defaultOptionsAreTakenIntoAccount() {
475
+ val defaultOptions = Options()
476
+ defaultOptions.bottomTabsOptions.visible = Bool(false)
477
+ Java6Assertions.assertThat(uut.getBottomInset(child1)).isEqualTo(bottomTabs.height)
478
+ uut.setDefaultOptions(defaultOptions)
479
+ Java6Assertions.assertThat(uut.getBottomInset(child1)).isZero
480
+ }
481
+
482
+ @Test
483
+ fun destroy() {
484
+ uut.destroy()
485
+ Mockito.verify(tabsAttacher).destroy()
486
+ }
487
+
488
+ private fun prepareViewsForTests(
489
+ bottomTabsOptions: BottomTabsOptions = initialOptions.bottomTabsOptions,
490
+ options: Options = initialOptions, defaultOptions: Options = initialOptions
491
+ ) {
492
+ if(::uut.isInitialized){
493
+ uut.destroy()
494
+ }
495
+ // ObjectUtils.perform(uut, { obj: BottomTabsController -> obj.destroy() })
496
+ bottomTabs = Mockito.spy(object : BottomTabs(activity) {
497
+ override fun superCreateItems() {}
498
+ })
499
+ bottomTabsContainer = Mockito.spy(BottomTabsContainer(activity, bottomTabs))
500
+ createChildren()
501
+ tabs = mutableListOf(child1, child2, child3, child4, child5)
502
+ defaultOptions.bottomTabsOptions = bottomTabsOptions
503
+ presenter = Mockito.spy(BottomTabsPresenter(tabs, defaultOptions, BottomTabsAnimator()))
504
+ bottomTabPresenter =
505
+ Mockito.spy(BottomTabPresenter(activity, tabs, mock(), TypefaceLoaderMock(), defaultOptions))
506
+ tabsAttacher = Mockito.spy(BottomTabsAttacher(tabs, presenter, defaultOptions))
507
+ uut = createBottomTabs(options = options, defaultOptions = defaultOptions)
508
+ activity.setContentView(FakeParentController(activity, childRegistry, uut).view)
509
+ }
510
+
511
+ private fun createChildren() {
512
+ child1 = Mockito.spy(SimpleViewController(activity, childRegistry, "child1", tabOptions))
513
+ child2 = Mockito.spy(SimpleViewController(activity, childRegistry, "child2", tabOptions))
514
+ child3 = Mockito.spy(SimpleViewController(activity, childRegistry, "child3", tabOptions))
515
+ stackChild = Mockito.spy(SimpleViewController(activity, childRegistry, "stackChild", tabOptions))
516
+ child4 = spyOnStack(stackChild)
517
+ child5 = Mockito.spy(SimpleViewController(activity, childRegistry, "child5", tabOptions))
518
+ Mockito.`when`(child5.handleBack(any())).thenReturn(true)
519
+ }
520
+
521
+ private fun spyOnStack(initialChild: ViewController<*>?): StackController {
522
+ val build = TestUtils.newStackController(activity)
523
+ .setInitialOptions(tabOptions)
524
+ .build()
525
+ val stack = Mockito.spy(build)
526
+ disablePushAnimation(initialChild)
527
+ stack.ensureViewIsCreated()
528
+ stack.push(initialChild, CommandListenerAdapter())
529
+ return stack
530
+ }
531
+
532
+ private fun createBottomTabs(
533
+ options: Options = initialOptions,
534
+ defaultOptions: Options = initialOptions
535
+ ): BottomTabsController {
536
+ return object : BottomTabsController(
537
+ activity,
538
+ tabs,
539
+ childRegistry,
540
+ eventEmitter,
541
+ imageLoaderMock,
542
+ "uut",
543
+ options,
544
+ Presenter(activity, defaultOptions),
545
+ tabsAttacher,
546
+ presenter,
547
+ bottomTabPresenter
548
+ ) {
549
+ override fun ensureViewIsCreated() {
550
+ super.ensureViewIsCreated()
551
+ uut.view.layout(0, 0, 1000, 1000)
552
+ }
553
+
554
+ override fun createView(): BottomTabsLayout {
555
+ val view = super.createView()
556
+ this@BottomTabsControllerTest.bottomTabs.layoutParams.height = 100
557
+ return view
558
+ }
559
+
560
+ override fun createBottomTabsContainer(): BottomTabsContainer {
561
+ return this@BottomTabsControllerTest.bottomTabsContainer
562
+ }
563
+
564
+ override fun createBottomTabs(): BottomTabs {
565
+ return this@BottomTabsControllerTest.bottomTabs
566
+ }
567
+ }
568
+ }
569
+
570
+ private val statusBarHeight: Int
571
+ get() = getStatusBarHeight(activity)
572
+ }
@@ -360,6 +360,10 @@ export interface HardwareBackButtonOptions {
360
360
  * @default true
361
361
  */
362
362
  popStackOnPress?: boolean;
363
+ /**
364
+ * Controls hardware back button bottom tab selection behaviour
365
+ */
366
+ bottomTabsOnPress?: 'exit' | 'first' | 'previous';
363
367
  }
364
368
  export interface OptionsTopBarScrollEdgeAppearanceBackground {
365
369
  /**
@@ -414,6 +414,11 @@ export interface HardwareBackButtonOptions {
414
414
  * @default true
415
415
  */
416
416
  popStackOnPress?: boolean;
417
+
418
+ /**
419
+ * Controls hardware back button bottom tab selection behaviour
420
+ */
421
+ bottomTabsOnPress?: 'exit' | 'first' | 'previous';
417
422
  }
418
423
 
419
424
  export interface OptionsTopBarScrollEdgeAppearanceBackground {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-navigation",
3
- "version": "7.23.1-snapshot.438",
3
+ "version": "7.23.1-snapshot.440",
4
4
  "description": "React Native Navigation - truly native navigation for iOS and Android",
5
5
  "license": "MIT",
6
6
  "nativePackage": true,
@@ -1,514 +0,0 @@
1
- package com.reactnativenavigation.viewcontrollers.bottomtabs;
2
-
3
- import android.app.Activity;
4
- import android.graphics.Color;
5
- import android.view.Gravity;
6
- import android.view.View;
7
- import android.view.ViewGroup.MarginLayoutParams;
8
-
9
- import com.aurelhubert.ahbottomnavigation.AHBottomNavigation;
10
- import com.reactnativenavigation.BaseTest;
11
- import com.reactnativenavigation.TestUtils;
12
- import com.reactnativenavigation.mocks.ImageLoaderMock;
13
- import com.reactnativenavigation.mocks.SimpleViewController;
14
- import com.reactnativenavigation.mocks.TypefaceLoaderMock;
15
- import com.reactnativenavigation.options.BottomTabsOptions;
16
- import com.reactnativenavigation.options.Options;
17
- import com.reactnativenavigation.options.params.Bool;
18
- import com.reactnativenavigation.options.params.Colour;
19
- import com.reactnativenavigation.options.params.NullText;
20
- import com.reactnativenavigation.options.params.Number;
21
- import com.reactnativenavigation.options.params.ThemeColour;
22
- import com.reactnativenavigation.options.params.Text;
23
- import com.reactnativenavigation.react.CommandListenerAdapter;
24
- import com.reactnativenavigation.react.events.EventEmitter;
25
- import com.reactnativenavigation.utils.ImageLoader;
26
- import com.reactnativenavigation.utils.OptionHelper;
27
- import com.reactnativenavigation.utils.SystemUiUtils;
28
- import com.reactnativenavigation.viewcontrollers.bottomtabs.attacher.BottomTabsAttacher;
29
- import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry;
30
- import com.reactnativenavigation.viewcontrollers.fakes.FakeParentController;
31
- import com.reactnativenavigation.viewcontrollers.parent.ParentController;
32
- import com.reactnativenavigation.viewcontrollers.stack.StackController;
33
- import com.reactnativenavigation.viewcontrollers.viewcontroller.Presenter;
34
- import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController;
35
- import com.reactnativenavigation.views.bottomtabs.BottomTabs;
36
- import com.reactnativenavigation.views.bottomtabs.BottomTabsContainer;
37
- import com.reactnativenavigation.views.bottomtabs.BottomTabsLayout;
38
-
39
- import org.junit.Test;
40
- import org.mockito.ArgumentCaptor;
41
- import org.mockito.Mockito;
42
-
43
- import java.util.ArrayList;
44
- import java.util.Arrays;
45
- import java.util.Collections;
46
- import java.util.List;
47
-
48
- import androidx.annotation.NonNull;
49
- import androidx.coordinatorlayout.widget.CoordinatorLayout;
50
-
51
-
52
- import static com.reactnativenavigation.TestUtils.hideBackButton;
53
- import static com.reactnativenavigation.utils.ObjectUtils.perform;
54
- import static org.assertj.core.api.Java6Assertions.assertThat;
55
- import static org.mockito.ArgumentMatchers.any;
56
- import static org.mockito.ArgumentMatchers.eq;
57
- import static org.mockito.Mockito.spy;
58
- import static org.mockito.Mockito.times;
59
- import static org.mockito.Mockito.verify;
60
- import static org.mockito.Mockito.when;
61
-
62
- public class BottomTabsControllerTest extends BaseTest {
63
-
64
- private Activity activity;
65
- private BottomTabs bottomTabs;
66
- private BottomTabsContainer bottomTabsContainer;
67
- private BottomTabsController uut;
68
- private final Options initialOptions = new Options();
69
- private ViewController<?> child1;
70
- private ViewController<?> child2;
71
- private ViewController<?> child3;
72
- private ViewController<?> stackChild;
73
- private StackController child4;
74
- private ViewController<?> child5;
75
- private final Options tabOptions = OptionHelper.createBottomTabOptions();
76
- private final ImageLoader imageLoaderMock = ImageLoaderMock.mock();
77
- private EventEmitter eventEmitter;
78
- private ChildControllersRegistry childRegistry;
79
- private List<ViewController<?>> tabs;
80
- private BottomTabsPresenter presenter;
81
- private BottomTabPresenter bottomTabPresenter;
82
- private BottomTabsAttacher tabsAttacher;
83
-
84
- @Override
85
- public void beforeEach() {
86
- activity = newActivity();
87
- childRegistry = new ChildControllersRegistry();
88
- eventEmitter = Mockito.mock(EventEmitter.class);
89
- prepareViewsForTests();
90
- SystemUiUtils.saveStatusBarHeight(63);
91
- }
92
-
93
- @Test
94
- public void createView_checkProperStructure() {
95
- idleMainLooper();
96
- assertThat(uut.getView()).isInstanceOf(CoordinatorLayout.class);
97
- assertThat(uut.getView().getChildAt(uut.getView().getChildCount() - 1)).isInstanceOf(BottomTabsContainer.class);
98
- assertThat(((CoordinatorLayout.LayoutParams) uut.getBottomTabsContainer().getLayoutParams()).gravity).isEqualTo(Gravity.BOTTOM);
99
- }
100
-
101
- @Test
102
- public void createView_tabsWithoutIconsAreAccepted() {
103
- tabOptions.bottomTabOptions.icon = new NullText();
104
- prepareViewsForTests();
105
- assertThat(uut.getBottomTabs().getItemsCount()).isEqualTo(tabs.size());
106
- }
107
-
108
- @Test
109
- public void createView_showTitlesWhenAllTabsDontHaveIcons() {
110
- tabOptions.bottomTabOptions.icon = new NullText();
111
- assertThat(tabOptions.bottomTabsOptions.titleDisplayMode.hasValue()).isFalse();
112
- prepareViewsForTests();
113
- presenter.applyOptions(Options.EMPTY);
114
- assertThat(bottomTabsContainer.getBottomTabs().getTitleState()).isEqualTo(AHBottomNavigation.TitleState.ALWAYS_SHOW);
115
- }
116
-
117
- @Test(expected = RuntimeException.class)
118
- public void setTabs_ThrowWhenMoreThan5() {
119
- tabs.add(new SimpleViewController(activity, childRegistry, "6", tabOptions));
120
- createBottomTabs();
121
- }
122
-
123
- @Test
124
- public void parentControllerIsSet() {
125
- uut = createBottomTabs();
126
- for (ViewController<?> tab : tabs) {
127
- assertThat(tab.getParentController()).isEqualTo(uut);
128
- }
129
- }
130
-
131
- @Test
132
- public void setTabs_allChildViewsAreAttachedToHierarchy() {
133
- uut.onViewWillAppear();
134
- assertThat(uut.getView().getChildCount()).isEqualTo(6);
135
- for (ViewController<?> child : uut.getChildControllers()) {
136
- assertThat(child.getView().getParent()).isNotNull();
137
- }
138
- }
139
-
140
- @Test
141
- public void setTabs_firstChildIsVisibleOtherAreGone() {
142
- uut.onViewWillAppear();
143
- for (int i = 0; i < uut.getChildControllers().size(); i++) {
144
- assertThat(uut.getView().getChildAt(i)).isEqualTo(tabs.get(i).getView());
145
- assertThat(uut.getView().getChildAt(i).getVisibility()).isEqualTo(i == 0 ? View.VISIBLE : View.INVISIBLE);
146
- }
147
- }
148
-
149
- @Test
150
- public void onTabSelected() {
151
- uut.ensureViewIsCreated();
152
- assertThat(uut.getSelectedIndex()).isZero();
153
- assertThat(((ViewController<?>) ((List<?>) uut.getChildControllers()).get(0)).getView().getVisibility()).isEqualTo(View.VISIBLE);
154
-
155
- uut.onTabSelected(3, false);
156
-
157
- assertThat(uut.getSelectedIndex()).isEqualTo(3);
158
- assertThat(((ViewController<?>) ((List<?>) uut.getChildControllers()).get(0)).getView().getVisibility()).isEqualTo(View.INVISIBLE);
159
- assertThat(((ViewController<?>) ((List<?>) uut.getChildControllers()).get(3)).getView().getVisibility()).isEqualTo(View.VISIBLE);
160
- verify(eventEmitter).emitBottomTabSelected(0, 3);
161
- }
162
-
163
- @Test
164
- public void onTabReSelected() {
165
- uut.ensureViewIsCreated();
166
- assertThat(uut.getSelectedIndex()).isZero();
167
-
168
- uut.onTabSelected(0, true);
169
-
170
- assertThat(uut.getSelectedIndex()).isEqualTo(0);
171
- assertThat(((ViewController<?>) ((List<?>) uut.getChildControllers()).get(0)).getView().getParent()).isNotNull();
172
- verify(eventEmitter).emitBottomTabSelected(0, 0);
173
- }
174
-
175
- @Test
176
- public void handleBack_DelegatesToSelectedChild() {
177
- uut.ensureViewIsCreated();
178
- assertThat(uut.handleBack(new CommandListenerAdapter())).isFalse();
179
- uut.selectTab(4);
180
- assertThat(uut.handleBack(new CommandListenerAdapter())).isTrue();
181
- verify(child5).handleBack(any());
182
- }
183
-
184
- @Test
185
- public void applyChildOptions_bottomTabsOptionsAreClearedAfterApply() {
186
- ParentController<?> parent = Mockito.mock(ParentController.class);
187
- Mockito.when(parent.resolveChildOptions(uut)).thenReturn(Options.EMPTY);
188
- Mockito.when(parent.resolveChildOptions(child1)).thenReturn(Options.EMPTY);
189
-
190
- uut.setParentController(parent);
191
-
192
- child1.options.bottomTabsOptions.backgroundColor = new ThemeColour(new Colour(Color.RED));
193
- child1.onViewWillAppear();
194
-
195
- ArgumentCaptor<Options> optionsCaptor = ArgumentCaptor.forClass(Options.class);
196
- verify(parent).applyChildOptions(optionsCaptor.capture(), any());
197
- assertThat(optionsCaptor.getValue().bottomTabsOptions.backgroundColor.hasValue()).isFalse();
198
- }
199
-
200
- @Test
201
- public void applyOptions_bottomTabsCreateViewOnlyOnce() {
202
- idleMainLooper();
203
- verify(presenter).applyOptions(any());
204
- verify(bottomTabsContainer.getBottomTabs(), times(2)).superCreateItems(); // first time when view is created, second time when options are applied
205
- }
206
-
207
- @Test
208
- public void onSizeChanged_recreateItemsIfSizeHasChanged() {
209
- int numberOfPreviousInvocations = 1;
210
- bottomTabs.onSizeChanged(0, 0, 0, 0);
211
- verify(bottomTabs, times(numberOfPreviousInvocations)).superCreateItems();
212
-
213
- bottomTabs.onSizeChanged(100, 0, 0, 0);
214
- verify(bottomTabs, times(numberOfPreviousInvocations)).superCreateItems();
215
-
216
- bottomTabs.onSizeChanged(1080, 147, 0, 0);
217
- verify(bottomTabs, times(numberOfPreviousInvocations + 1)).superCreateItems();
218
-
219
- bottomTabs.onSizeChanged(1920, 147, 0, 0);
220
- verify(bottomTabs, times(numberOfPreviousInvocations + 2)).superCreateItems();
221
-
222
- when(bottomTabs.getItemsCount()).thenReturn(0);
223
- bottomTabs.onSizeChanged(1080, 147, 0, 0);
224
- verify(bottomTabs, times(numberOfPreviousInvocations + 2)).superCreateItems();
225
- }
226
-
227
- @Test
228
- public void mergeOptions_currentTabIndex() {
229
- uut.ensureViewIsCreated();
230
- assertThat(uut.getSelectedIndex()).isZero();
231
-
232
- Options options = new Options();
233
- options.bottomTabsOptions.currentTabIndex = new Number(1);
234
- uut.mergeOptions(options);
235
- assertThat(uut.getSelectedIndex()).isOne();
236
- verify(eventEmitter, times(0)).emitBottomTabSelected(any(Integer.class), any(Integer.class));
237
- }
238
-
239
- @Test
240
- public void mergeOptions_drawBehind() {
241
- assertThat(uut.getBottomInset(child1)).isEqualTo(uut.getBottomTabs().getHeight());
242
-
243
- Options o1 = new Options();
244
- o1.bottomTabsOptions.drawBehind = new Bool(true);
245
- child1.mergeOptions(o1);
246
- assertThat(uut.getBottomInset(child1)).isEqualTo(0);
247
-
248
- Options o2 = new Options();
249
- o2.topBar.title.text = new Text("Some text");
250
- child1.mergeOptions(o1);
251
- assertThat(uut.getBottomInset(child1)).isEqualTo(0);
252
- }
253
-
254
- @Test
255
- public void mergeOptions_drawBehind_stack() {
256
- uut.ensureViewIsCreated();
257
- uut.selectTab(3);
258
-
259
- assertThat(((MarginLayoutParams) stackChild.getView().getLayoutParams()).bottomMargin).isEqualTo(bottomTabs.getHeight());
260
-
261
- Options o1 = new Options();
262
- o1.bottomTabsOptions.drawBehind = new Bool(true);
263
- stackChild.mergeOptions(o1);
264
-
265
- assertThat(((MarginLayoutParams) stackChild.getView().getLayoutParams()).bottomMargin).isEqualTo(0);
266
- }
267
-
268
- @Test
269
- public void mergeOptions_mergesBottomTabOptions() {
270
- Options options = new Options();
271
- uut.mergeOptions(options);
272
- verify(bottomTabPresenter).mergeOptions(options);
273
- }
274
-
275
- @Test
276
- public void applyChildOptions_resolvedOptionsAreUsed() {
277
- Options childOptions = new Options();
278
- SimpleViewController pushedScreen = new SimpleViewController(activity, childRegistry, "child4.1", childOptions);
279
- disablePushAnimation(pushedScreen);
280
- child4 = spyOnStack(pushedScreen);
281
-
282
- tabs = new ArrayList<>(Collections.singletonList(child4));
283
- tabsAttacher = new BottomTabsAttacher(tabs, presenter, Options.EMPTY);
284
-
285
- initialOptions.bottomTabsOptions.currentTabIndex = new Number(0);
286
- Options resolvedOptions = new Options();
287
- uut = new BottomTabsController(activity,
288
- tabs,
289
- childRegistry,
290
- eventEmitter,
291
- imageLoaderMock,
292
- "uut",
293
- initialOptions,
294
- new Presenter(activity, new Options()),
295
- tabsAttacher,
296
- presenter,
297
- new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new TypefaceLoaderMock(), new Options())) {
298
- @Override
299
- public Options resolveCurrentOptions() {
300
- return resolvedOptions;
301
- }
302
-
303
- @NonNull
304
- @Override
305
- protected BottomTabs createBottomTabs() {
306
- return new BottomTabs(activity) {
307
- @Override
308
- protected void createItems() {
309
-
310
- }
311
- };
312
- }
313
- };
314
-
315
- activity.setContentView(uut.getView());
316
- idleMainLooper();
317
- verify(presenter, times(2)).applyChildOptions(eq(resolvedOptions), any());
318
- }
319
-
320
- @Test
321
- public void child_mergeOptions_currentTabIndex() {
322
- uut.ensureViewIsCreated();
323
-
324
- assertThat(uut.getSelectedIndex()).isZero();
325
-
326
- Options options = new Options();
327
- options.bottomTabsOptions.currentTabIndex = new Number(1);
328
- child1.mergeOptions(options);
329
-
330
- assertThat(uut.getSelectedIndex()).isOne();
331
- }
332
-
333
- @Test
334
- public void resolveCurrentOptions_returnsFirstTabIfInvokedBeforeViewIsCreated() {
335
- uut = createBottomTabs();
336
- assertThat(uut.getCurrentChild()).isEqualTo(tabs.get(0));
337
- }
338
-
339
- @Test
340
- public void buttonPressInvokedOnCurrentTab() {
341
- uut.ensureViewIsCreated();
342
- uut.selectTab(4);
343
-
344
- uut.sendOnNavigationButtonPressed("btn1");
345
- verify(child5, times(1)).sendOnNavigationButtonPressed("btn1");
346
- }
347
-
348
- @Test
349
- public void push() {
350
- uut.selectTab(3);
351
-
352
- SimpleViewController stackChild2 = new SimpleViewController(activity, childRegistry, "stackChild2", new Options());
353
- disablePushAnimation(stackChild2);
354
- hideBackButton(stackChild2);
355
-
356
- assertThat(child4.size()).isEqualTo(1);
357
- child4.push(stackChild2, new CommandListenerAdapter());
358
- assertThat(child4.size()).isEqualTo(2);
359
- }
360
-
361
- @Test
362
- public void oneTimeOptionsAreAppliedOnce() {
363
- Options options = new Options();
364
- options.bottomTabsOptions.currentTabIndex = new Number(1);
365
-
366
- assertThat(uut.getSelectedIndex()).isZero();
367
- uut.mergeOptions(options);
368
- assertThat(uut.getSelectedIndex()).isOne();
369
- assertThat(uut.options.bottomTabsOptions.currentTabIndex.hasValue()).isFalse();
370
- assertThat(uut.initialOptions.bottomTabsOptions.currentTabIndex.hasValue()).isFalse();
371
- }
372
-
373
- @Test
374
- public void selectTab() {
375
- uut.selectTab(1);
376
- verify(tabsAttacher).onTabSelected(tabs.get(1));
377
- }
378
-
379
- @Test
380
- public void selectTab_onViewDidAppearIsInvokedAfterSelection() {
381
- uut.selectTab(1);
382
- verify(child2).onViewDidAppear();
383
- }
384
-
385
- @Test
386
- public void creatingTabs_onViewDidAppearInvokedAfterInitialTabIndexSet() {
387
- Options options = Options.EMPTY.copy();
388
- options.bottomTabsOptions.currentTabIndex = new Number(1);
389
- prepareViewsForTests(options.bottomTabsOptions);
390
- idleMainLooper();
391
- verify(tabs.get(0), times(0)).onViewDidAppear();
392
- verify(tabs.get(1), times(1)).onViewDidAppear();
393
- verify(tabs.get(2), times(0)).onViewDidAppear();
394
- verify(tabs.get(3), times(0)).onViewDidAppear();
395
- verify(tabs.get(4), times(0)).onViewDidAppear();
396
- }
397
-
398
- @Test
399
- public void getTopInset() {
400
- assertThat(child1.getTopInset()).isEqualTo(getStatusBarHeight());
401
- assertThat(child2.getTopInset()).isEqualTo(getStatusBarHeight());
402
-
403
- child1.options.statusBar.drawBehind = new Bool(true);
404
- assertThat(child1.getTopInset()).isEqualTo(0);
405
- assertThat(child2.getTopInset()).isEqualTo(getStatusBarHeight());
406
-
407
- assertThat(stackChild.getTopInset()).isEqualTo(getStatusBarHeight() + child4.getTopBar().getHeight());
408
- }
409
-
410
- @Test
411
- public void getBottomInset_defaultOptionsAreTakenIntoAccount() {
412
- Options defaultOptions = new Options();
413
- defaultOptions.bottomTabsOptions.visible = new Bool(false);
414
-
415
- assertThat(uut.getBottomInset(child1)).isEqualTo(bottomTabs.getHeight());
416
- uut.setDefaultOptions(defaultOptions);
417
- assertThat(uut.getBottomInset(child1)).isZero();
418
- }
419
-
420
- @Test
421
- public void destroy() {
422
- uut.destroy();
423
- verify(tabsAttacher).destroy();
424
- }
425
-
426
- private void prepareViewsForTests() {
427
- prepareViewsForTests(initialOptions.bottomTabsOptions);
428
- }
429
-
430
- private void prepareViewsForTests(BottomTabsOptions bottomTabsOptions) {
431
- perform(uut, ViewController::destroy);
432
- bottomTabs = spy(new BottomTabs(activity) {
433
- @Override
434
- public void superCreateItems() {
435
-
436
- }
437
- });
438
- bottomTabsContainer = spy(new BottomTabsContainer(activity, bottomTabs));
439
-
440
- createChildren();
441
- tabs = Arrays.asList(child1, child2, child3, child4, child5);
442
- initialOptions.bottomTabsOptions = bottomTabsOptions;
443
- presenter = spy(new BottomTabsPresenter(tabs, initialOptions, new BottomTabsAnimator()));
444
- bottomTabPresenter = spy(new BottomTabPresenter(activity, tabs, ImageLoaderMock.mock(), new TypefaceLoaderMock(), initialOptions));
445
- tabsAttacher = spy(new BottomTabsAttacher(tabs, presenter, initialOptions));
446
- uut = createBottomTabs();
447
- activity.setContentView(new FakeParentController(activity, childRegistry, uut).getView());
448
- }
449
-
450
- private void createChildren() {
451
- child1 = spy(new SimpleViewController(activity, childRegistry, "child1", tabOptions));
452
- child2 = spy(new SimpleViewController(activity, childRegistry, "child2", tabOptions));
453
- child3 = spy(new SimpleViewController(activity, childRegistry, "child3", tabOptions));
454
- stackChild = spy(new SimpleViewController(activity, childRegistry, "stackChild", tabOptions));
455
- child4 = spyOnStack(stackChild);
456
- child5 = spy(new SimpleViewController(activity, childRegistry, "child5", tabOptions));
457
- when(child5.handleBack(any())).thenReturn(true);
458
- }
459
-
460
- private StackController spyOnStack(ViewController<?> initialChild) {
461
- StackController build = TestUtils.newStackController(activity)
462
- .setInitialOptions(tabOptions)
463
- .build();
464
- StackController stack = spy(build);
465
- disablePushAnimation(initialChild);
466
- stack.ensureViewIsCreated();
467
- stack.push(initialChild, new CommandListenerAdapter());
468
- return stack;
469
- }
470
-
471
- private BottomTabsController createBottomTabs() {
472
- return new BottomTabsController(activity,
473
- tabs,
474
- childRegistry,
475
- eventEmitter,
476
- imageLoaderMock,
477
- "uut",
478
- initialOptions,
479
- new Presenter(activity, initialOptions),
480
- tabsAttacher,
481
- presenter,
482
- bottomTabPresenter) {
483
- @Override
484
- public void ensureViewIsCreated() {
485
- super.ensureViewIsCreated();
486
- uut.getView().layout(0, 0, 1000, 1000);
487
- }
488
-
489
- @NonNull
490
- @Override
491
- public BottomTabsLayout createView() {
492
- BottomTabsLayout view = super.createView();
493
- bottomTabs.getLayoutParams().height = 100;
494
- return view;
495
- }
496
-
497
- @NonNull
498
- @Override
499
- protected BottomTabsContainer createBottomTabsContainer() {
500
- return bottomTabsContainer;
501
- }
502
-
503
- @NonNull
504
- @Override
505
- protected BottomTabs createBottomTabs() {
506
- return bottomTabs;
507
- }
508
- };
509
- }
510
-
511
- private int getStatusBarHeight() {
512
- return SystemUiUtils.getStatusBarHeight(activity);
513
- }
514
- }