Ran Greenberg преди 8 години
родител
ревизия
15705fba96
променени са 30 файла, в които са добавени 610 реда и са изтрити 90 реда
  1. 1
    0
      android/app/build.gradle
  2. 7
    7
      android/app/deprecated/java/com/reactnativenavigation/utils/ViewUtils.java
  3. 0
    2
      android/app/src/main/AndroidManifest.xml
  4. 37
    11
      android/app/src/main/java/com/reactnativenavigation/layout/LayoutFactory.java
  5. 89
    0
      android/app/src/main/java/com/reactnativenavigation/layout/bottomtabs/BottomTabs.java
  6. 63
    0
      android/app/src/main/java/com/reactnativenavigation/layout/bottomtabs/BottomTabsContainer.java
  7. 8
    0
      android/app/src/main/java/com/reactnativenavigation/layout/bottomtabs/BottomTabsCreator.java
  8. 4
    0
      android/app/src/main/java/com/reactnativenavigation/layout/bottomtabs/TooManyTabsException.java
  9. 2
    11
      android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java
  10. 2
    4
      android/app/src/main/java/com/reactnativenavigation/react/ReactDevPermission.java
  11. 2
    1
      android/app/src/main/res/values/styles.xml
  12. 6
    0
      android/app/src/test/RoboManifest.xml
  13. 128
    0
      android/app/src/test/java/com/reactnativenavigation/BottomTabsContainerTest.java
  14. 96
    42
      android/app/src/test/java/com/reactnativenavigation/LayoutFactoryTest.java
  15. 27
    0
      android/app/src/test/java/com/reactnativenavigation/TestUtils.java
  16. 4
    0
      playground/android/app/build.gradle
  17. 9
    0
      playground/android/app/src/androidTest/AndroidManifest.xml
  18. 32
    5
      playground/android/app/src/androidTest/java/com/reactnativenavigation/playground/ApplicationTest.java
  19. 2
    1
      playground/android/app/src/main/AndroidManifest.xml
  20. 4
    0
      playground/android/app/src/main/res/values/styles.xml
  21. 5
    1
      playground/e2e/app.test.js
  22. 1
    0
      playground/scripts/e2e.android.js
  23. 1
    0
      playground/scripts/e2e.ios.js
  24. 9
    4
      playground/src/containers/WelcomeScreen.js
  25. 4
    0
      src/Navigation.js
  26. 9
    0
      src/Navigation.test.js
  27. 5
    0
      src/adapters/NativeCommandsSender.js
  28. 8
    1
      src/adapters/NativeCommandsSender.test.js
  29. 7
    0
      src/commands/AppCommands.js
  30. 38
    0
      src/commands/AppCommands.test.js

+ 1
- 0
android/app/build.gradle Целия файл

48
     compile fileTree(include: ['*.jar'], dir: 'libs')
48
     compile fileTree(include: ['*.jar'], dir: 'libs')
49
     compile 'com.android.support:design:25.1.1'
49
     compile 'com.android.support:design:25.1.1'
50
     compile 'com.android.support:appcompat-v7:25.1.1'
50
     compile 'com.android.support:appcompat-v7:25.1.1'
51
+    compile "com.android.support:support-v4:25.1.1"
51
 
52
 
52
     // node_modules
53
     // node_modules
53
     compile 'com.facebook.react:react-native:+'
54
     compile 'com.facebook.react:react-native:+'

+ 7
- 7
android/app/deprecated/java/com/reactnativenavigation/utils/ViewUtils.java Целия файл

57
         return pixels * scaledDensity;
57
         return pixels * scaledDensity;
58
     }
58
     }
59
 
59
 
60
+    public static float getScreenHeight() {
61
+        WindowManager wm = (WindowManager) NavigationApplication.instance.getSystemService(Context.WINDOW_SERVICE);
62
+        DisplayMetrics metrics = new DisplayMetrics();
63
+        wm.getDefaultDisplay().getMetrics(metrics);
64
+        return metrics.heightPixels;
65
+    }
66
+
60
     public static int generateViewId() {
67
     public static int generateViewId() {
61
         if (Build.VERSION.SDK_INT >= 17) {
68
         if (Build.VERSION.SDK_INT >= 17) {
62
             return View.generateViewId();
69
             return View.generateViewId();
65
         }
72
         }
66
     }
73
     }
67
 
74
 
68
-    public static float getScreenHeight() {
69
-        WindowManager wm = (WindowManager) NavigationApplication.instance.getSystemService(Context.WINDOW_SERVICE);
70
-        DisplayMetrics metrics = new DisplayMetrics();
71
-        wm.getDefaultDisplay().getMetrics(metrics);
72
-        return metrics.heightPixels;
73
-    }
74
-
75
     private static int compatGenerateViewId() {
75
     private static int compatGenerateViewId() {
76
         for (; ; ) {
76
         for (; ; ) {
77
             final int result = viewId.get();
77
             final int result = viewId.get();

+ 0
- 2
android/app/src/main/AndroidManifest.xml Целия файл

2
     package="com.reactnativenavigation">
2
     package="com.reactnativenavigation">
3
 
3
 
4
     <application>
4
     <application>
5
-        <activity android:name=".controllers.NavigationActivity" />
6
-
7
         <activity
5
         <activity
8
             android:name="com.facebook.react.devsupport.DevSettingsActivity"
6
             android:name="com.facebook.react.devsupport.DevSettingsActivity"
9
             android:exported="false" />
7
             android:exported="false" />

+ 37
- 11
android/app/src/main/java/com/reactnativenavigation/layout/LayoutFactory.java Целия файл

3
 import android.app.Activity;
3
 import android.app.Activity;
4
 import android.view.View;
4
 import android.view.View;
5
 
5
 
6
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsContainer;
7
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsCreator;
8
+
6
 import java.util.List;
9
 import java.util.List;
7
 
10
 
8
 public class LayoutFactory {
11
 public class LayoutFactory {
12
+
9
     public interface RootViewCreator {
13
     public interface RootViewCreator {
10
         View createRootView(String id, String name);
14
         View createRootView(String id, String name);
11
     }
15
     }
12
 
16
 
13
-    private Activity activity;
14
-    private RootViewCreator rootViewCreator;
17
+    private final Activity activity;
18
+    private final RootViewCreator rootViewCreator;
19
+    private final BottomTabsCreator bottomTabsCreator; // TODO: revisit this, may not be needed
15
 
20
 
16
-    public LayoutFactory(Activity activity, RootViewCreator rootViewCreator) {
21
+    public LayoutFactory(Activity activity, RootViewCreator rootViewCreator, BottomTabsCreator bottomTabsCreator) {
17
         this.activity = activity;
22
         this.activity = activity;
18
         this.rootViewCreator = rootViewCreator;
23
         this.rootViewCreator = rootViewCreator;
24
+        this.bottomTabsCreator = bottomTabsCreator;
19
     }
25
     }
20
 
26
 
21
     public View create(LayoutNode node) {
27
     public View create(LayoutNode node) {
22
         switch (node.type) {
28
         switch (node.type) {
23
             case "Container":
29
             case "Container":
24
-                String name = (String) node.data.get("name");
25
-                return new Container(activity, rootViewCreator, node.id, name);
26
-
30
+                return createContainerView(node);
27
             case "ContainerStack":
31
             case "ContainerStack":
28
-                ContainerStack containerStack = new ContainerStack(activity);
29
-                List<LayoutNode> children = node.children;
30
-                addChildrenNodes(containerStack, children);
31
-                return containerStack;
32
+                return createContainerStackView(node);
33
+            case "BottomTabs":
34
+                return createBottomTabs(node);
35
+            default:
36
+                throw new IllegalArgumentException("Invalid node type: "+node.type);
32
         }
37
         }
38
+    }
39
+
40
+    private View createContainerView(LayoutNode node) {
41
+        final String name = (String) node.data.get("name");
42
+        return new Container(activity, rootViewCreator, node.id, name);
43
+    }
44
+
45
+    private View createContainerStackView(LayoutNode node) {
46
+        final ContainerStack containerStack = new ContainerStack(activity);
47
+        addChildrenNodes(containerStack, node.children);
48
+        return containerStack;
49
+    }
33
 
50
 
34
-        return null;
51
+    private View createBottomTabs(LayoutNode node) {
52
+        final BottomTabsContainer tabsContainer = new BottomTabsContainer(activity, bottomTabsCreator.create());
53
+
54
+        int i = 0;
55
+        for (LayoutNode child : node.children) {
56
+            final View tabContent = create(child);
57
+            tabsContainer.addTabContent("#" + i, tabContent);
58
+            i++;
59
+        }
60
+        return tabsContainer;
35
     }
61
     }
36
 
62
 
37
     private void addChildrenNodes(ContainerStack containerStack, List<LayoutNode> children) {
63
     private void addChildrenNodes(ContainerStack containerStack, List<LayoutNode> children) {

+ 89
- 0
android/app/src/main/java/com/reactnativenavigation/layout/bottomtabs/BottomTabs.java Целия файл

1
+package com.reactnativenavigation.layout.bottomtabs;
2
+
3
+import android.graphics.Color;
4
+import android.os.Build;
5
+import android.support.annotation.NonNull;
6
+import android.support.design.widget.BottomNavigationView;
7
+import android.view.Menu;
8
+import android.view.MenuItem;
9
+import android.view.View;
10
+import android.widget.RelativeLayout;
11
+import android.widget.RelativeLayout.LayoutParams;
12
+
13
+import java.util.concurrent.atomic.AtomicInteger;
14
+
15
+import static android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM;
16
+
17
+public class BottomTabs implements BottomNavigationView.OnNavigationItemSelectedListener {
18
+
19
+    interface BottomTabsSelectionListener {
20
+        void onTabSelected(int index);
21
+    }
22
+
23
+    private static final AtomicInteger viewId = new AtomicInteger(1);
24
+
25
+    private BottomNavigationView bottomNavigationView;
26
+    private BottomTabsSelectionListener listener;
27
+
28
+    public void attach(RelativeLayout parentLayout) {
29
+        createBottomNavigation(parentLayout);
30
+        addButtomNavigationToParent(parentLayout);
31
+    }
32
+
33
+    public void setSelectionListener(BottomTabsSelectionListener listener) {
34
+        this.listener = listener;
35
+    }
36
+
37
+    public void add(String label) {
38
+        int tabId = size();
39
+        bottomNavigationView.getMenu().add(0, tabId, Menu.NONE, label);
40
+    }
41
+
42
+    public int size() {
43
+        return bottomNavigationView.getMenu().size();
44
+    }
45
+
46
+    int getViewId() {
47
+        return bottomNavigationView.getId();
48
+    }
49
+
50
+    @Override
51
+    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
52
+        listener.onTabSelected(item.getItemId());
53
+        return true;
54
+    }
55
+
56
+    private void createBottomNavigation(RelativeLayout parentLayout) {
57
+        bottomNavigationView = new BottomNavigationView(parentLayout.getContext());
58
+        bottomNavigationView.setId(generateViewId());
59
+        bottomNavigationView.setBackgroundColor(Color.DKGRAY);
60
+        bottomNavigationView.setOnNavigationItemSelectedListener(this);
61
+    }
62
+
63
+    private void addButtomNavigationToParent(RelativeLayout parentLayout) {
64
+        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
65
+        lp.addRule(ALIGN_PARENT_BOTTOM);
66
+        bottomNavigationView.setLayoutParams(lp);
67
+        parentLayout.addView(bottomNavigationView, lp);
68
+    }
69
+
70
+    private int generateViewId() {
71
+        if (Build.VERSION.SDK_INT >= 17) {
72
+            return View.generateViewId();
73
+        } else {
74
+            return compatGenerateViewId();
75
+        }
76
+    }
77
+
78
+    private int compatGenerateViewId() {
79
+        while(true) {
80
+            final int result = viewId.get();
81
+            // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
82
+            int newValue = result + 1;
83
+            if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
84
+            if (viewId.compareAndSet(result, newValue)) {
85
+                return result;
86
+            }
87
+        }
88
+    }
89
+}

+ 63
- 0
android/app/src/main/java/com/reactnativenavigation/layout/bottomtabs/BottomTabsContainer.java Целия файл

1
+package com.reactnativenavigation.layout.bottomtabs;
2
+
3
+import android.app.Activity;
4
+import android.view.View;
5
+import android.widget.RelativeLayout;
6
+
7
+import java.util.ArrayList;
8
+import java.util.List;
9
+
10
+public class BottomTabsContainer extends RelativeLayout implements BottomTabs.BottomTabsSelectionListener {
11
+
12
+    private List<View> tabsContent;
13
+    private BottomTabs bottomTabs;
14
+    private int currentTab;
15
+
16
+    public BottomTabsContainer(Activity activity, BottomTabs bottomTabs) {
17
+        super(activity);
18
+        initBottomTabs(bottomTabs);
19
+    }
20
+
21
+    public void addTabContent(String label, View tabContent) {
22
+        if (tabsContent.size() > 5) {
23
+            throw new TooManyTabsException();
24
+        }
25
+        bottomTabs.add(label);
26
+        attachTabContent(tabContent);
27
+        tabsContent.add(tabContent);
28
+
29
+        if (tabsContent.size() > 1) {
30
+            tabContent.setVisibility(View.GONE);
31
+        }
32
+    }
33
+
34
+    @Override
35
+    public void onTabSelected(int index) {
36
+        hideTab(currentTab);
37
+        currentTab = index;
38
+        showTab(currentTab);
39
+    }
40
+
41
+    private void initBottomTabs(BottomTabs bottomTabs) {
42
+        this.bottomTabs = bottomTabs;
43
+        this.bottomTabs.attach(this);
44
+        this.bottomTabs.setSelectionListener(this);
45
+
46
+        tabsContent = new ArrayList<>();
47
+    }
48
+
49
+    private void attachTabContent(View tabContent) {
50
+        LayoutParams tabParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
51
+        tabParams.addRule(ABOVE, bottomTabs.getViewId());
52
+        addView(tabContent, tabParams);
53
+    }
54
+
55
+    private void showTab(int tabId) {
56
+        tabsContent.get(tabId).setVisibility(View.VISIBLE);
57
+    }
58
+
59
+    private void hideTab(int tabId) {
60
+        tabsContent.get(tabId).setVisibility(View.GONE);
61
+    }
62
+
63
+}

+ 8
- 0
android/app/src/main/java/com/reactnativenavigation/layout/bottomtabs/BottomTabsCreator.java Целия файл

1
+package com.reactnativenavigation.layout.bottomtabs;
2
+
3
+public class BottomTabsCreator {
4
+
5
+    public BottomTabs create() {
6
+        return new BottomTabs();
7
+    }
8
+}

+ 4
- 0
android/app/src/main/java/com/reactnativenavigation/layout/bottomtabs/TooManyTabsException.java Целия файл

1
+package com.reactnativenavigation.layout.bottomtabs;
2
+
3
+public class TooManyTabsException extends RuntimeException {
4
+}

+ 2
- 11
android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java Целия файл

13
 import com.reactnativenavigation.controllers.NavigationActivity;
13
 import com.reactnativenavigation.controllers.NavigationActivity;
14
 import com.reactnativenavigation.layout.LayoutFactory;
14
 import com.reactnativenavigation.layout.LayoutFactory;
15
 import com.reactnativenavigation.layout.LayoutNode;
15
 import com.reactnativenavigation.layout.LayoutNode;
16
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsCreator;
16
 
17
 
17
 import java.util.ArrayList;
18
 import java.util.ArrayList;
18
 import java.util.HashMap;
19
 import java.util.HashMap;
44
                         rootView.startReactApplication(NavigationActivity.instance.getHost().getReactInstanceManager(), name, opts);
45
                         rootView.startReactApplication(NavigationActivity.instance.getHost().getReactInstanceManager(), name, opts);
45
                         return rootView;
46
                         return rootView;
46
                     }
47
                     }
47
-                });
48
+                }, new BottomTabsCreator());
48
 
49
 
49
                 final LayoutNode layoutTreeRoot = readableMapToLayoutNode(layoutTree);
50
                 final LayoutNode layoutTreeRoot = readableMapToLayoutNode(layoutTree);
50
                 final View rootView = factory.create(layoutTreeRoot);
51
                 final View rootView = factory.create(layoutTreeRoot);
51
                 NavigationActivity.instance.setContentView(rootView);
52
                 NavigationActivity.instance.setContentView(rootView);
52
-
53
-//                Map<String, Object> node = new HashMap<String, Object>();
54
-//                node.put("id", container.getString("id"));
55
-//                HashMap<String, Object> data = new HashMap<>();
56
-//                data.put("name", container.getMap("data").getString("name"));
57
-//                node.put("data", data);
58
-//                View rootView = factory.create(node);
59
-//                NavigationActivity.instance.setContentView(rootView);
60
             }
53
             }
61
         });
54
         });
62
     }
55
     }
88
                 case Map:
81
                 case Map:
89
                     map.put(key, readableMapToJavaMap(readableMap.getMap(key)));
82
                     map.put(key, readableMapToJavaMap(readableMap.getMap(key)));
90
                     break;
83
                     break;
91
-                default:
92
-                    throw new IllegalArgumentException("WTF?!");
93
             }
84
             }
94
         }
85
         }
95
         return map;
86
         return map;

+ 2
- 4
android/app/src/main/java/com/reactnativenavigation/react/ReactDevPermission.java Целия файл

16
     public static boolean shouldAskPermission() {
16
     public static boolean shouldAskPermission() {
17
         return NavigationApplication.instance.isDebug() &&
17
         return NavigationApplication.instance.isDebug() &&
18
                 Build.VERSION.SDK_INT >= 23 &&
18
                 Build.VERSION.SDK_INT >= 23 &&
19
-               !Settings.canDrawOverlays(NavigationApplication.instance);
19
+                !Settings.canDrawOverlays(NavigationApplication.instance);
20
     }
20
     }
21
 
21
 
22
     @TargetApi(23)
22
     @TargetApi(23)
28
             Log.w(ReactConstants.TAG, "======================================\n\n");
28
             Log.w(ReactConstants.TAG, "======================================\n\n");
29
             Log.w(ReactConstants.TAG, msg);
29
             Log.w(ReactConstants.TAG, msg);
30
             Log.w(ReactConstants.TAG, "\n\n======================================");
30
             Log.w(ReactConstants.TAG, "\n\n======================================");
31
-            for (int i = 0; i < 5; i++) {
32
-                Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
33
-            }
31
+            Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
34
         }
32
         }
35
     }
33
     }
36
 }
34
 }

+ 2
- 1
android/app/src/main/res/values/styles.xml Целия файл

1
 <?xml version="1.0" encoding="utf-8"?>
1
 <?xml version="1.0" encoding="utf-8"?>
2
 <resources>
2
 <resources>
3
+
3
     <style name="Modal" parent="@android:style/Theme.Translucent.NoTitleBar">
4
     <style name="Modal" parent="@android:style/Theme.Translucent.NoTitleBar">
4
         <item name="android:windowAnimationStyle">@style/modalAnimations</item>
5
         <item name="android:windowAnimationStyle">@style/modalAnimations</item>
5
     </style>
6
     </style>
7
     <style name="modalAnimations">
8
     <style name="modalAnimations">
8
         <item name="android:windowExitAnimation">@anim/slide_down</item>
9
         <item name="android:windowExitAnimation">@anim/slide_down</item>
9
     </style>
10
     </style>
10
-</resources>
11
+</resources>

+ 6
- 0
android/app/src/test/RoboManifest.xml Целия файл

1
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+          package="com.reactnativenavigation">
3
+    <application android:theme="@style/Theme.AppCompat.Light">
4
+        <activity android:name=".RoboActivity" android:theme="@style/Theme.AppCompat.Light"/>
5
+    </application>
6
+</manifest>

+ 128
- 0
android/app/src/test/java/com/reactnativenavigation/BottomTabsContainerTest.java Целия файл

1
+package com.reactnativenavigation;
2
+
3
+import android.app.Activity;
4
+import android.view.View;
5
+
6
+import com.reactnativenavigation.layout.bottomtabs.BottomTabs;
7
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsContainer;
8
+import com.reactnativenavigation.layout.bottomtabs.TooManyTabsException;
9
+
10
+import org.junit.Before;
11
+import org.junit.Test;
12
+import org.junit.runner.RunWith;
13
+import org.robolectric.Robolectric;
14
+import org.robolectric.RobolectricTestRunner;
15
+
16
+import static org.assertj.core.api.Java6Assertions.assertThat;
17
+import static org.mockito.Mockito.mock;
18
+import static org.mockito.Mockito.verify;
19
+
20
+@RunWith(RobolectricTestRunner.class)
21
+public class BottomTabsContainerTest {
22
+
23
+    private static final String TAB_NAME = "myTab";
24
+    private static final String OTHER_TAB_NAME = "myOtherTab";
25
+
26
+    private BottomTabs bottomTabs;
27
+    private Activity activity;
28
+
29
+    @Before
30
+    public void setUp() throws Exception {
31
+        bottomTabs = mock(BottomTabs.class);
32
+        activity = Robolectric.buildActivity(Activity.class).get();
33
+    }
34
+
35
+    @Test
36
+    public void addsTabToBottomTabs() throws Exception {
37
+        View tabContent = new View(activity);
38
+
39
+        BottomTabsContainer bottomTabsContainer = createBottomTabsContainer();
40
+        bottomTabsContainer.addTabContent(TAB_NAME, tabContent);
41
+
42
+        verify(bottomTabs).add(TAB_NAME);
43
+    }
44
+
45
+    @Test
46
+    public void addsTabContentToLayout() throws Exception {
47
+        View tabContent = new View(activity);
48
+
49
+        BottomTabsContainer bottomTabsContainer = createBottomTabsContainer();
50
+        bottomTabsContainer.addTabContent(TAB_NAME, tabContent);
51
+
52
+        verify(bottomTabs).attach(bottomTabsContainer);
53
+        TestUtils.assertViewChildren(bottomTabsContainer, tabContent);
54
+    }
55
+
56
+    @Test
57
+    public void addsTwoTabsContentToLayout() throws Exception {
58
+        View tabContent = new View(activity);
59
+        View otherTabContent = new View(activity);
60
+
61
+        BottomTabsContainer bottomTabsContainer = createBottomTabsContainer();
62
+        bottomTabsContainer.addTabContent(TAB_NAME, tabContent);
63
+        bottomTabsContainer.addTabContent(OTHER_TAB_NAME, otherTabContent);
64
+
65
+        TestUtils.assertViewChildren(bottomTabsContainer, tabContent, otherTabContent);
66
+    }
67
+
68
+    @Test (expected = TooManyTabsException.class)
69
+    public void throwsExceptionWhenMoreThenFiveTabs() throws Exception {
70
+        BottomTabsContainer bottomTabsContainer = createBottomTabsContainer();
71
+        for (int i = 0; i <= 6; i++) {
72
+            View content = new View(activity);
73
+            bottomTabsContainer.addTabContent("#" + i, content);
74
+        }
75
+    }
76
+
77
+    @Test
78
+    public void onlyFirstTabShouldBeVisible() throws Exception {
79
+        View tabContent = new View(activity);
80
+        View otherTabContent = new View(activity);
81
+
82
+        BottomTabsContainer bottomTabsContainer = createBottomTabsContainer();
83
+        bottomTabsContainer.addTabContent(TAB_NAME, tabContent);
84
+        bottomTabsContainer.addTabContent(OTHER_TAB_NAME, otherTabContent);
85
+
86
+        assertThat(tabContent.getVisibility()).isEqualTo(View.VISIBLE);
87
+        assertThat(otherTabContent.getVisibility()).isEqualTo(View.GONE);
88
+    }
89
+
90
+    @Test
91
+    public void listensToTabsSwitchingEvents() throws Exception {
92
+        BottomTabsContainer bottomTabsContainer = createBottomTabsContainer();
93
+        verify(bottomTabs).setSelectionListener(bottomTabsContainer);
94
+    }
95
+
96
+    @Test
97
+    public void switchesBetweenFirstAndSecondTabs() throws Exception {
98
+        View tabContent = new View(activity);
99
+        View otherTabContent = new View(activity);
100
+
101
+        BottomTabsContainer bottomTabsContainer = createBottomTabsContainer();
102
+        bottomTabsContainer.addTabContent(TAB_NAME, tabContent);
103
+        bottomTabsContainer.addTabContent(OTHER_TAB_NAME, otherTabContent);
104
+        bottomTabsContainer.onTabSelected(1);
105
+
106
+        assertThat(tabContent.getVisibility()).isEqualTo(View.GONE);
107
+        assertThat(otherTabContent.getVisibility()).isEqualTo(View.VISIBLE);
108
+    }
109
+
110
+    @Test
111
+    public void switchesBetweenSecondAndFirstTabs() throws Exception {
112
+        View tabContent = new View(activity);
113
+        View otherTabContent = new View(activity);
114
+
115
+        BottomTabsContainer bottomTabsContainer = createBottomTabsContainer();
116
+        bottomTabsContainer.addTabContent(TAB_NAME, tabContent);
117
+        bottomTabsContainer.addTabContent(OTHER_TAB_NAME, otherTabContent);
118
+        bottomTabsContainer.onTabSelected(1);
119
+        bottomTabsContainer.onTabSelected(0);
120
+
121
+        assertThat(tabContent.getVisibility()).isEqualTo(View.VISIBLE);
122
+        assertThat(otherTabContent.getVisibility()).isEqualTo(View.GONE);
123
+    }
124
+
125
+    private BottomTabsContainer createBottomTabsContainer() {
126
+        return new BottomTabsContainer(activity, bottomTabs);
127
+    }
128
+}

+ 96
- 42
android/app/src/test/java/com/reactnativenavigation/LayoutFactoryTest.java Целия файл

1
 package com.reactnativenavigation;
1
 package com.reactnativenavigation;
2
 
2
 
3
 import android.app.Activity;
3
 import android.app.Activity;
4
+import android.support.v7.app.AppCompatActivity;
4
 import android.view.View;
5
 import android.view.View;
5
 import android.view.ViewGroup;
6
 import android.view.ViewGroup;
6
 
7
 
8
 import com.reactnativenavigation.layout.ContainerStack;
9
 import com.reactnativenavigation.layout.ContainerStack;
9
 import com.reactnativenavigation.layout.LayoutFactory;
10
 import com.reactnativenavigation.layout.LayoutFactory;
10
 import com.reactnativenavigation.layout.LayoutNode;
11
 import com.reactnativenavigation.layout.LayoutNode;
12
+import com.reactnativenavigation.layout.bottomtabs.BottomTabs;
13
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsContainer;
14
+import com.reactnativenavigation.layout.bottomtabs.BottomTabsCreator;
11
 
15
 
12
 import org.junit.Before;
16
 import org.junit.Before;
13
 import org.junit.Test;
17
 import org.junit.Test;
15
 import org.robolectric.Robolectric;
19
 import org.robolectric.Robolectric;
16
 import org.robolectric.RobolectricTestRunner;
20
 import org.robolectric.RobolectricTestRunner;
17
 
21
 
18
-import java.util.ArrayList;
19
 import java.util.Arrays;
22
 import java.util.Arrays;
23
+import java.util.Collections;
20
 import java.util.HashMap;
24
 import java.util.HashMap;
21
 import java.util.List;
25
 import java.util.List;
22
 
26
 
23
 import static org.assertj.core.api.Java6Assertions.assertThat;
27
 import static org.assertj.core.api.Java6Assertions.assertThat;
24
 import static org.mockito.ArgumentMatchers.eq;
28
 import static org.mockito.ArgumentMatchers.eq;
25
 import static org.mockito.Mockito.mock;
29
 import static org.mockito.Mockito.mock;
30
+import static org.mockito.Mockito.verify;
26
 import static org.mockito.Mockito.when;
31
 import static org.mockito.Mockito.when;
27
 
32
 
28
-
29
 @RunWith(RobolectricTestRunner.class)
33
 @RunWith(RobolectricTestRunner.class)
30
 public class LayoutFactoryTest {
34
 public class LayoutFactoryTest {
31
 
35
 
35
     private final static String OTHER_VIEW_ID = "anotherUniqueId";
39
     private final static String OTHER_VIEW_ID = "anotherUniqueId";
36
     private final static String OTHER_VIEW_NAME = "anotherName";
40
     private final static String OTHER_VIEW_NAME = "anotherName";
37
 
41
 
42
+    private Activity activity;
38
     private View mockView;
43
     private View mockView;
44
+    private View otherMockView;
39
     private LayoutFactory.RootViewCreator rootViewCreator;
45
     private LayoutFactory.RootViewCreator rootViewCreator;
40
 
46
 
41
     @Before
47
     @Before
42
     public void setUp() {
48
     public void setUp() {
43
-        mockView = new View(Robolectric.setupActivity(Activity.class));
49
+        activity = Robolectric.buildActivity(AppCompatActivity.class).get();
50
+        mockView = new View(activity);
51
+        otherMockView = new View(activity);
44
         rootViewCreator = mock(LayoutFactory.RootViewCreator.class);
52
         rootViewCreator = mock(LayoutFactory.RootViewCreator.class);
45
     }
53
     }
46
 
54
 
47
     @Test
55
     @Test
48
-    public void returnsContainerThatHoldsTheRootView() {
56
+    public void returnsContainerThatHoldsTheRootView() throws Exception {
49
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
57
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
50
         final LayoutNode node = createContainerNode();
58
         final LayoutNode node = createContainerNode();
51
 
59
 
52
         final ViewGroup result = (ViewGroup) createLayoutFactory().create(node);
60
         final ViewGroup result = (ViewGroup) createLayoutFactory().create(node);
53
 
61
 
54
         assertThat(result).isInstanceOf(Container.class);
62
         assertThat(result).isInstanceOf(Container.class);
55
-        assertViewChildren(result, mockView);
63
+        TestUtils.assertViewChildren(result, mockView);
56
     }
64
     }
57
 
65
 
58
     @Test
66
     @Test
59
-    public void returnsContainerStack() {
67
+    public void returnsContainerStack() throws Exception {
60
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
68
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
61
-        final LayoutNode node = createContainerNode();
62
-        final LayoutNode outerNode = getContainerStackNode(node);
69
+        final LayoutNode containerNode = createContainerNode();
70
+        final LayoutNode stackNode = createContainerStackNode(containerNode);
63
 
71
 
64
-        final ViewGroup result = (ViewGroup) createLayoutFactory().create(outerNode);
72
+        final ViewGroup result = (ViewGroup) createLayoutFactory().create(stackNode);
65
 
73
 
66
         assertThat(result).isInstanceOf(ContainerStack.class);
74
         assertThat(result).isInstanceOf(ContainerStack.class);
67
-        ViewGroup container = (ViewGroup) assertViewChildrenCount(result, 1).get(0);
68
-        assertViewChildren(container, mockView);
75
+        ViewGroup container = (ViewGroup) TestUtils.assertViewChildrenCount(result, 1).get(0);
76
+        TestUtils.assertViewChildren(container, mockView);
69
     }
77
     }
70
 
78
 
71
     @Test
79
     @Test
72
-    public void returnsContainerStackWithMultipleViews() {
80
+    public void returnsContainerStackWithMultipleViews() throws Exception {
73
         final View mockView1 = mock(View.class);
81
         final View mockView1 = mock(View.class);
74
         final View mockView2 = mock(View.class);
82
         final View mockView2 = mock(View.class);
75
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView1);
83
         when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView1);
76
         when(rootViewCreator.createRootView(eq(OTHER_VIEW_ID), eq(OTHER_VIEW_NAME))).thenReturn(mockView2);
84
         when(rootViewCreator.createRootView(eq(OTHER_VIEW_ID), eq(OTHER_VIEW_NAME))).thenReturn(mockView2);
77
 
85
 
78
-        final LayoutNode node1 = createContainerNode(VIEW_ID, VIEW_NAME);
79
-        final LayoutNode node2 = createContainerNode(OTHER_VIEW_ID, OTHER_VIEW_NAME);
80
-        final LayoutNode outerNode = getContainerStackNode(Arrays.asList(node1, node2));
86
+        final LayoutNode containerNode1 = createContainerNode(VIEW_ID, VIEW_NAME);
87
+        final LayoutNode containerNode2 = createContainerNode(OTHER_VIEW_ID, OTHER_VIEW_NAME);
88
+        final LayoutNode stackNode = createContainerStackNode(containerNode1, containerNode2);
81
 
89
 
82
-        final ViewGroup result = (ViewGroup) createLayoutFactory().create(outerNode);
90
+        final ViewGroup result = (ViewGroup) createLayoutFactory().create(stackNode);
83
 
91
 
84
         assertThat(result).isInstanceOf(ContainerStack.class);
92
         assertThat(result).isInstanceOf(ContainerStack.class);
85
-        List<View> containers = assertViewChildrenCount(result, 2);
93
+        List<View> containers = TestUtils.assertViewChildrenCount(result, 2);
86
         ViewGroup container1 = (ViewGroup) containers.get(0);
94
         ViewGroup container1 = (ViewGroup) containers.get(0);
87
         ViewGroup container2 = (ViewGroup) containers.get(1);
95
         ViewGroup container2 = (ViewGroup) containers.get(1);
88
-        assertViewChildren(container1, mockView1);
89
-        assertViewChildren(container2, mockView2);
96
+        TestUtils.assertViewChildren(container1, mockView1);
97
+        TestUtils.assertViewChildren(container2, mockView2);
90
     }
98
     }
91
 
99
 
92
-    private LayoutFactory createLayoutFactory() {
93
-        return new LayoutFactory(Robolectric.buildActivity(Activity.class).get(), rootViewCreator);
100
+    @Test
101
+    public void returnsSingleTabContent() throws Exception {
102
+        BottomTabs bottomTabsMock = mock(BottomTabs.class);
103
+        when(bottomTabsMock.size()).thenReturn(0);
104
+
105
+        when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
106
+        final LayoutNode containerNode = createContainerNode();
107
+        final LayoutNode tabNode = createTabNode(containerNode);
108
+
109
+        final View result = createLayoutFactory(bottomTabsMock).create(tabNode);
110
+
111
+        verify(bottomTabsMock).add("#0");
112
+
113
+        assertThat(result).isInstanceOf(BottomTabsContainer.class);
114
+        Container container = (Container) TestUtils.assertViewChildrenCount((BottomTabsContainer) result, 1).get(0);
115
+        View view = TestUtils.assertViewChildrenCount(container, 1).get(0);
116
+        assertThat(view).isEqualTo(mockView);
94
     }
117
     }
95
 
118
 
96
-    private LayoutNode createContainerNode() {
97
-        return createContainerNode(VIEW_ID, VIEW_NAME);
119
+    @Test
120
+    public void returnsTwoTabContent() throws Exception {
121
+        BottomTabs bottomTabsMock = mock(BottomTabs.class);
122
+        when(bottomTabsMock.size()).thenReturn(0, 1);
123
+
124
+        when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
125
+        final LayoutNode firstTabRootNode = createContainerNode(VIEW_ID, VIEW_NAME);
126
+
127
+        when(rootViewCreator.createRootView(eq(OTHER_VIEW_ID), eq(OTHER_VIEW_NAME))).thenReturn(otherMockView);
128
+        final LayoutNode secondTabRootNode = createContainerStackNode(createContainerNode(OTHER_VIEW_ID, OTHER_VIEW_NAME));
129
+
130
+        final LayoutNode tabNode = createTabNode(firstTabRootNode, secondTabRootNode);
131
+
132
+        final View result = createLayoutFactory(bottomTabsMock).create(tabNode);
133
+
134
+        assertThat(result).isInstanceOf(BottomTabsContainer.class);
135
+        verify(bottomTabsMock).add(eq("#0"));
136
+        verify(bottomTabsMock).add(eq("#1"));
98
     }
137
     }
99
 
138
 
100
-    private LayoutNode createContainerNode(final String id, final String name) {
101
-        return new LayoutNode(id, "Container", new HashMap<String, Object>() {{ put("name", name); }});
139
+
140
+    @Test(expected = IllegalArgumentException.class)
141
+    public void throwsExceptionForUnknownType() throws Exception {
142
+        when(rootViewCreator.createRootView(eq(VIEW_ID), eq(VIEW_NAME))).thenReturn(mockView);
143
+        final LayoutNode node = new LayoutNode(VIEW_ID, "***unknownType***", Collections.<String, Object>emptyMap());
144
+
145
+        createLayoutFactory().create(node);
102
     }
146
     }
103
 
147
 
104
-    private LayoutNode getContainerStackNode(LayoutNode innerNode) {
105
-        return getContainerStackNode(Arrays.asList(innerNode));
148
+    private LayoutFactory createLayoutFactory() {
149
+        return createLayoutFactory(null);
106
     }
150
     }
107
 
151
 
108
-    private LayoutNode getContainerStackNode(List<LayoutNode> children) {
109
-        LayoutNode outerNode = new LayoutNode();
110
-        outerNode.type = "ContainerStack";
111
-        outerNode.children = children;
112
-        return outerNode;
152
+    private LayoutFactory createLayoutFactory(BottomTabs bottomTabs) {
153
+        BottomTabsCreator bottomTabsCreator = null;
154
+        if (bottomTabs != null) {
155
+            bottomTabsCreator = mock(BottomTabsCreator.class);
156
+            when(bottomTabsCreator.create()).thenReturn(bottomTabs);
157
+        }
158
+
159
+        return new LayoutFactory(activity, rootViewCreator, bottomTabsCreator);
113
     }
160
     }
114
 
161
 
115
-    private List<View> assertViewChildrenCount(ViewGroup view, int count) {
116
-        assertThat(view.getChildCount()).isEqualTo(count);
162
+    private LayoutNode createContainerNode() {
163
+        return createContainerNode(VIEW_ID, VIEW_NAME);
164
+    }
117
 
165
 
118
-        final List<View> children = new ArrayList<>(count);
119
-        for (int i = 0; i < count; i++) {
120
-            children.add(view.getChildAt(i));
121
-        }
122
-        return children;
166
+    private LayoutNode createContainerNode(final String id, final String name) {
167
+        return new LayoutNode(id, "Container", new HashMap<String, Object>() {{ put("name", name); }});
168
+    }
169
+
170
+    private LayoutNode createContainerStackNode(LayoutNode... children) {
171
+        LayoutNode node = new LayoutNode();
172
+        node.type = "ContainerStack";
173
+        node.children = Arrays.asList(children);
174
+        return node;
123
     }
175
     }
124
 
176
 
125
-    private void assertViewChildren(ViewGroup view, View... children) {
126
-        final List<View> childViews = assertViewChildrenCount(view, children.length);
127
-        assertThat(childViews).isEqualTo(Arrays.asList(children));
177
+    private LayoutNode createTabNode(LayoutNode... children) {
178
+        LayoutNode node = new LayoutNode();
179
+        node.type = "BottomTabs";
180
+        node.children = Arrays.asList(children);
181
+        return node;
128
     }
182
     }
129
 }
183
 }

+ 27
- 0
android/app/src/test/java/com/reactnativenavigation/TestUtils.java Целия файл

1
+package com.reactnativenavigation;
2
+
3
+import android.view.View;
4
+import android.view.ViewGroup;
5
+
6
+import java.util.ArrayList;
7
+import java.util.Arrays;
8
+import java.util.List;
9
+
10
+import static org.assertj.core.api.Java6Assertions.assertThat;
11
+
12
+public class TestUtils {
13
+    public static List<View> assertViewChildrenCount(ViewGroup view, int count) {
14
+        assertThat(view.getChildCount()).isEqualTo(count);
15
+
16
+        final List<View> children = new ArrayList<>(count);
17
+        for (int i = 0; i < count; i++) {
18
+            children.add(view.getChildAt(i));
19
+        }
20
+        return children;
21
+    }
22
+
23
+    public static void assertViewChildren(ViewGroup view, View... children) {
24
+        final List<View> childViews = assertViewChildrenCount(view, children.length);
25
+        assertThat(childViews).isEqualTo(Arrays.asList(children));
26
+    }
27
+}

+ 4
- 0
playground/android/app/build.gradle Целия файл

27
     androidTestCompile 'com.android.support:support-annotations:25.1.1'
27
     androidTestCompile 'com.android.support:support-annotations:25.1.1'
28
     androidTestCompile 'com.android.support.test:runner:0.5'
28
     androidTestCompile 'com.android.support.test:runner:0.5'
29
     androidTestCompile 'com.android.support.test:rules:0.5'
29
     androidTestCompile 'com.android.support.test:rules:0.5'
30
+    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
30
     androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
31
     androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
31
         exclude module: 'jsr305'
32
         exclude module: 'jsr305'
32
     }
33
     }
34
+    androidTestCompile('com.android.support.test.espresso:espresso-intents:2.2.2') {
35
+        exclude module: 'jsr305'
36
+    }
33
     androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') {
37
     androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') {
34
         exclude module: 'support-annotations'
38
         exclude module: 'support-annotations'
35
         exclude module: 'support-v4'
39
         exclude module: 'support-v4'

+ 9
- 0
playground/android/app/src/androidTest/AndroidManifest.xml Целия файл

1
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+    xmlns:tools="http://schemas.android.com/tools"
3
+    package="com.reactnativenavigation.playground">
4
+
5
+    <uses-sdk
6
+        android:minSdkVersion="18"
7
+        tools:overrideLibrary="android.support.test.uiautomator.v18" />
8
+
9
+</manifest>

+ 32
- 5
playground/android/app/src/androidTest/java/com/reactnativenavigation/playground/ApplicationTest.java Целия файл

1
 package com.reactnativenavigation.playground;
1
 package com.reactnativenavigation.playground;
2
 
2
 
3
-import android.support.test.espresso.action.ViewActions;
3
+import android.app.Activity;
4
+import android.app.Instrumentation;
5
+import android.provider.Settings;
6
+import android.support.test.espresso.intent.Intents;
4
 import android.support.test.rule.ActivityTestRule;
7
 import android.support.test.rule.ActivityTestRule;
5
 import android.support.test.runner.AndroidJUnit4;
8
 import android.support.test.runner.AndroidJUnit4;
9
+import android.support.test.uiautomator.UiDevice;
10
+import android.support.test.uiautomator.UiObjectNotFoundException;
11
+import android.support.test.uiautomator.UiSelector;
6
 
12
 
7
 import org.junit.Rule;
13
 import org.junit.Rule;
8
 import org.junit.Test;
14
 import org.junit.Test;
9
 import org.junit.runner.RunWith;
15
 import org.junit.runner.RunWith;
10
 
16
 
17
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
11
 import static android.support.test.espresso.Espresso.onView;
18
 import static android.support.test.espresso.Espresso.onView;
19
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
20
+import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction;
21
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
12
 import static android.support.test.espresso.matcher.ViewMatchers.withText;
22
 import static android.support.test.espresso.matcher.ViewMatchers.withText;
13
 
23
 
14
 @RunWith(AndroidJUnit4.class)
24
 @RunWith(AndroidJUnit4.class)
15
 public class ApplicationTest {
25
 public class ApplicationTest {
16
 
26
 
17
     @Rule
27
     @Rule
18
-    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
28
+    public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class, false, false);
19
 
29
 
20
     @Test
30
     @Test
21
-    public void startsTheApp() {
22
-        
23
-        onView(withText("Playground")).perform(ViewActions.click());
31
+    public void startAppShowsTheSplash() throws InterruptedException {
32
+        Intents.init();
33
+        Intents.intending(hasAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)).respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null));
34
+
35
+        rule.launchActivity(null);
36
+
37
+        onView(withText("Splash :)")).check(matches(isDisplayed()));
38
+        Intents.release();
39
+    }
40
+
41
+    @Test
42
+    public void startAppLoadsBridgeShowsWelcomeScreen() throws UiObjectNotFoundException {
43
+        rule.launchActivity(null);
44
+
45
+        UiDevice.getInstance(getInstrumentation()).findObject(new UiSelector().text("Playground")).click();
46
+        UiDevice.getInstance(getInstrumentation()).findObject(new UiSelector().text("Permit drawing over other apps")).click();
47
+        UiDevice.getInstance(getInstrumentation()).pressBack();
48
+        UiDevice.getInstance(getInstrumentation()).pressBack();
49
+
50
+        onView(withText("React Native Navigation!")).check(matches(isDisplayed()));
24
     }
51
     }
25
 }
52
 }

+ 2
- 1
playground/android/app/src/main/AndroidManifest.xml Целия файл

8
         android:allowBackup="false"
8
         android:allowBackup="false"
9
         android:icon="@mipmap/ic_launcher"
9
         android:icon="@mipmap/ic_launcher"
10
         android:label="@string/app_name"
10
         android:label="@string/app_name"
11
-        android:theme="@style/Theme.AppCompat.Light.NoActionBar">
11
+        android:theme="@style/AppTheme"
12
+        >
12
         <activity
13
         <activity
13
             android:name=".MainActivity"
14
             android:name=".MainActivity"
14
             android:label="@string/app_name">
15
             android:label="@string/app_name">

+ 4
- 0
playground/android/app/src/main/res/values/styles.xml Целия файл

1
+<?xml version="1.0" encoding="utf-8"?>
2
+<resources>
3
+    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"/>
4
+</resources>

+ 5
- 1
playground/e2e/app.test.js Целия файл

38
     elementByLabel('Pop').tap();
38
     elementByLabel('Pop').tap();
39
     expect(elementByLabel('React Native Navigation!')).toBeVisible();
39
     expect(elementByLabel('React Native Navigation!')).toBeVisible();
40
   });
40
   });
41
-  
41
+
42
+  xit('show modal', () => {
43
+    elementByLabel('Show Modal').tap();
44
+    expect(elementByLabel('Modal screen')).toBeVisible();
45
+  });
42
 });
46
 });
43
 
47
 
44
 describe('reload app', () => {
48
 describe('reload app', () => {

+ 1
- 0
playground/scripts/e2e.android.js Целия файл

7
 function e2e() { //eslint-disable-line
7
 function e2e() { //eslint-disable-line
8
   try {
8
   try {
9
     shellUtils.exec.execSync(`echo 'travis_fold:start:android-espresso'`);
9
     shellUtils.exec.execSync(`echo 'travis_fold:start:android-espresso'`);
10
+    shellUtils.exec.execSync(`cd android && ./gradlew --stop`);
10
     shellUtils.exec.execSync(`cd android && ./gradlew connectedDebugAndroidTest`);
11
     shellUtils.exec.execSync(`cd android && ./gradlew connectedDebugAndroidTest`);
11
   } finally {
12
   } finally {
12
     shellUtils.exec.execSync(`echo 'travis_fold:end:android-espresso'`);
13
     shellUtils.exec.execSync(`echo 'travis_fold:end:android-espresso'`);

+ 1
- 0
playground/scripts/e2e.ios.js Целия файл

60
   buildProjForDetox();
60
   buildProjForDetox();
61
   if (process.env.TRAVIS) {
61
   if (process.env.TRAVIS) {
62
     console.warn('skipping e2e temporarily due to fbsimctl is currently broken'); //eslint-disable-line
62
     console.warn('skipping e2e temporarily due to fbsimctl is currently broken'); //eslint-disable-line
63
+    return;
63
   }
64
   }
64
   e2e();
65
   e2e();
65
 }
66
 }

+ 9
- 4
playground/src/containers/WelcomeScreen.js Целия файл

18
         <Button title="Switch to app with side menus" onPress={this.onClickSwitchToSideMenus} />
18
         <Button title="Switch to app with side menus" onPress={this.onClickSwitchToSideMenus} />
19
         <Button title="Switch to lifecycle screen" onPress={this.onClickLifecycleScreen} />
19
         <Button title="Switch to lifecycle screen" onPress={this.onClickLifecycleScreen} />
20
         <Button title="Push" onPress={this.onClickPush} />
20
         <Button title="Push" onPress={this.onClickPush} />
21
-        <Button title="Show Modal" onPress={this.onClickPush} />
21
+        <Button title="Show Modal" onPress={this.onClickShowModal} />
22
         <Text style={styles.footer}>{`this.props.id = ${this.props.id}`}</Text>
22
         <Text style={styles.footer}>{`this.props.id = ${this.props.id}`}</Text>
23
       </View>
23
       </View>
24
     );
24
     );
105
       }
105
       }
106
     });
106
     });
107
   }
107
   }
108
-  
109
-  onShowModal() {
110
-    Navigation.on(this.props.id).show
108
+
109
+  onClickShowModal() {
110
+    Navigation.showModal({
111
+      name: 'navigation.playground.SimpleScreen',
112
+      passProps: {
113
+        text: 'Modal screen'
114
+      }
115
+    });
111
   }
116
   }
112
 }
117
 }
113
 
118
 

+ 4
- 0
src/Navigation.js Целия файл

34
     return this.appCommands.setRoot(params);
34
     return this.appCommands.setRoot(params);
35
   }
35
   }
36
 
36
 
37
+  showModal(params) {
38
+    return this.appCommands.showModal(params);
39
+  }
40
+
37
   events() {
41
   events() {
38
     return this.publicEventsRegistry;
42
     return this.publicEventsRegistry;
39
   }
43
   }

+ 9
- 0
src/Navigation.test.js Целия файл

30
     expect(Navigation.appCommands.setRoot).toHaveBeenCalledWith(params);
30
     expect(Navigation.appCommands.setRoot).toHaveBeenCalledWith(params);
31
   });
31
   });
32
 
32
 
33
+  it('showModal delegates to AppCommands', async () => {
34
+    Navigation.appCommands.showModal.mockReturnValue(Promise.resolve('result'));
35
+    const params = {};
36
+    const result = await Navigation.showModal(params);
37
+    expect(result).toEqual('result');
38
+    expect(Navigation.appCommands.showModal).toHaveBeenCalledTimes(1);
39
+    expect(Navigation.appCommands.showModal).toHaveBeenCalledWith(params);
40
+  });
41
+
33
   it('events return public events', () => {
42
   it('events return public events', () => {
34
     const cb = jest.fn();
43
     const cb = jest.fn();
35
     Navigation.events().onAppLaunched(cb);
44
     Navigation.events().onAppLaunched(cb);

+ 5
- 0
src/adapters/NativeCommandsSender.js Целия файл

19
     this.nativeCommandsModule.pop(containerId);
19
     this.nativeCommandsModule.pop(containerId);
20
     return Promise.resolve(containerId);
20
     return Promise.resolve(containerId);
21
   }
21
   }
22
+
23
+  showModal(layout) {
24
+    this.nativeCommandsModule.showModal(layout);
25
+    return Promise.resolve(layout);
26
+  }
22
 }
27
 }
23
 
28
 

+ 8
- 1
src/adapters/NativeCommandsSender.test.js Целия файл

8
     mockNativeModule = {
8
     mockNativeModule = {
9
       setRoot: jest.fn(),
9
       setRoot: jest.fn(),
10
       push: jest.fn(),
10
       push: jest.fn(),
11
-      pop: jest.fn()
11
+      pop: jest.fn(),
12
+      showModal: jest.fn()
12
     };
13
     };
13
     NativeModules.RNNBridgeModule = mockNativeModule;
14
     NativeModules.RNNBridgeModule = mockNativeModule;
14
     uut = new NativeCommandsSender();
15
     uut = new NativeCommandsSender();
36
     expect(mockNativeModule.pop).toHaveBeenCalledTimes(1);
37
     expect(mockNativeModule.pop).toHaveBeenCalledTimes(1);
37
     expect(result).toBeDefined();
38
     expect(result).toBeDefined();
38
   });
39
   });
40
+
41
+  it('showModal sends to native', async () => {
42
+    const result = await uut.showModal({});
43
+    expect(mockNativeModule.showModal).toHaveBeenCalledTimes(1);
44
+    expect(result).toBeDefined();
45
+  });
39
 });
46
 });

+ 7
- 0
src/commands/AppCommands.js Целия файл

13
     this.layoutTreeCrawler.crawl(layout);
13
     this.layoutTreeCrawler.crawl(layout);
14
     return this.nativeCommandsSender.setRoot(layout);
14
     return this.nativeCommandsSender.setRoot(layout);
15
   }
15
   }
16
+
17
+  showModal(simpleApi) {
18
+    const input = _.cloneDeep(simpleApi);
19
+    const layout = this.layoutTreeParser.createContainer(input);
20
+    this.layoutTreeCrawler.crawl(layout);
21
+    return this.nativeCommandsSender.showModal(layout);
22
+  }
16
 }
23
 }
17
 
24
 

+ 38
- 0
src/commands/AppCommands.test.js Целия файл

62
       expect(result).toEqual('the resolved layout');
62
       expect(result).toEqual('the resolved layout');
63
     });
63
     });
64
   });
64
   });
65
+
66
+  describe('showModal', () => {
67
+    it('sends command to native after parsing into a correct layout tree', () => {
68
+      uut.showModal({
69
+        name: 'com.example.MyScreen'
70
+      });
71
+      expect(mockCommandsSender.showModal).toHaveBeenCalledTimes(1);
72
+      expect(mockCommandsSender.showModal).toHaveBeenCalledWith({
73
+        type: 'Container',
74
+        id: 'Container+UNIQUE_ID',
75
+        children: [],
76
+        data: {
77
+          name: 'com.example.MyScreen'
78
+        }
79
+      });
80
+    });
81
+
82
+    it('deep clones input to avoid mutation errors', () => {
83
+      const obj = {};
84
+      uut.showModal({ inner: obj });
85
+      expect(mockCommandsSender.showModal.mock.calls[0][0].data.inner).not.toBe(obj);
86
+    });
87
+
88
+    it('passProps into containers', () => {
89
+      expect(store.getPropsForContainerId('Container+UNIQUE_ID')).toEqual({});
90
+      uut.showModal({
91
+        name: 'com.example.MyScreen',
92
+        passProps: SimpleLayouts.passProps
93
+      });
94
+      expect(store.getPropsForContainerId('Container+UNIQUE_ID')).toEqual(SimpleLayouts.passProps);
95
+    });
96
+
97
+    it('returns a promise with the resolved layout', async () => {
98
+      mockCommandsSender.showModal.mockReturnValue(Promise.resolve('the resolved layout'));
99
+      const result = await uut.showModal({ container: { name: 'com.example.MyScreen' } });
100
+      expect(result).toEqual('the resolved layout');
101
+    });
102
+  });
65
 });
103
 });