ソースを参照

Support Context api (#4427)

The following PR introduces improved support for Context api and other api's which wrap the root view.

## Context api
Navigation.registerComponent('navigation.playground.ContextScreen', () => (props) => (
  <TitleContext.Provider value={'Title from Provider'}>
    <ContextScreen {...props} />
  </TitleContext.Provider>
), () => ContextScreen);

## Redux
Navigation.registerComponent('navigation.playground.ReduxScreen', () => (props) => (
  <Provider store={reduxStore}>
    <ReduxScreen {...props} />
  </Provider>
), () => ReduxScreen);

## Plain Component - not changed
Navigation.registerComponent('navigation.playground.MyScreen', () => MyScreen);

This PR also upgrades the TypeScript version to 3.2.0 and RN version used in the playground app to 0.57.7

* New Android build flavor - `reactNative57_7`
* Unit test coverage is disabled, for some reason it broke after upgrading to RN 0.57.7
Guy Carmeli 6 年 前
コミット
9d365216d9
No account linked to committer's email address
共有33 個のファイルを変更した501 個の追加163 個の削除を含む
  1. 15
    0
      babel.config.js
  2. 0
    1
      index.ios.js
  3. 1
    0
      index.js
  4. 3
    3
      integration/redux/Redux.test.js
  5. 1
    1
      lib/android/app/build.gradle
  6. 28
    0
      lib/android/app/src/reactNative57_5/java/com/reactnativenavigation/react/DevBundleDownloadListenerAdapter.java
  7. 28
    0
      lib/android/app/src/reactNative57_5/java/com/reactnativenavigation/react/JsDevReloadHandlerFacade.java
  8. 107
    0
      lib/android/app/src/reactNative57_5/java/com/reactnativenavigation/react/NavigationReactNativeHost.java
  9. 25
    0
      lib/android/app/src/reactNative57_5/java/com/reactnativenavigation/react/ReloadHandlerFacade.java
  10. 83
    0
      lib/android/app/src/reactNative57_5/java/com/reactnativenavigation/react/SyncUiImplementation.java
  11. 6
    7
      lib/src/Navigation.ts
  12. 2
    3
      lib/src/adapters/Element.tsx
  13. 2
    7
      lib/src/adapters/TouchablePreview.tsx
  14. 6
    7
      lib/src/components/ComponentRegistry.test.tsx
  15. 9
    4
      lib/src/components/ComponentRegistry.ts
  16. 37
    17
      lib/src/components/ComponentWrapper.test.tsx
  17. 5
    10
      lib/src/components/ComponentWrapper.tsx
  18. 10
    0
      metro.config.js
  19. 12
    24
      package.json
  20. 0
    65
      playground/.flowconfig
  21. 2
    2
      playground/android/app/build.gradle
  22. 1
    1
      playground/ios/playground.xcodeproj/project.pbxproj
  23. 7
    0
      playground/src/context/index.js
  24. 59
    0
      playground/src/screens/ContextScreen.js
  25. 6
    3
      playground/src/screens/SearchScreen.js
  26. 9
    0
      playground/src/screens/WelcomeScreen.js
  27. 8
    0
      playground/src/screens/index.js
  28. 1
    0
      playground/src/testIDs.js
  29. 0
    5
      rn-cli.config.js
  30. 1
    1
      scripts/start.js
  31. 1
    1
      scripts/test-unit.js
  32. 25
    0
      scripts/upload_artifacts.sh
  33. 1
    1
      wallaby.js

+ 15
- 0
babel.config.js ファイルの表示

@@ -0,0 +1,15 @@
1
+module.exports = function (api) {
2
+  api && api.cache(false);
3
+  return {
4
+    env: {
5
+      test: {
6
+        presets: [
7
+          "module:metro-react-native-babel-preset"
8
+        ],
9
+        plugins: [
10
+          "@babel/plugin-proposal-class-properties"
11
+        ]
12
+      }
13
+    }
14
+  };
15
+}

+ 0
- 1
index.ios.js ファイルの表示

@@ -1 +0,0 @@
1
-require('./playground/index.ios');

+ 1
- 0
index.js ファイルの表示

@@ -0,0 +1 @@
1
+require('./playground/index');

+ 3
- 3
integration/redux/Redux.test.js ファイルの表示

@@ -18,12 +18,12 @@ describe('redux support', () => {
18 18
       render() {
19 19
         return (
20 20
           <Provider store={store.reduxStore}>
21
-            <MyConnectedComponent />
21
+            <MyConnectedComponent/>
22 22
           </Provider>
23 23
         );
24 24
       }
25 25
     };
26
-    Navigation.registerComponent('ComponentName', () => HOC);
26
+    Navigation.registerComponent('ComponentName', () => (props) => <HOC {...props} />, Provider, store.reduxStore);
27 27
 
28 28
     const tree = renderer.create(<HOC />);
29 29
     expect(tree.toJSON().children).toEqual(['no name']);
@@ -41,7 +41,7 @@ describe('redux support', () => {
41 41
         );
42 42
       }
43 43
     };
44
-    const CompFromNavigation = Navigation.registerComponent('ComponentName', () => HOC)();
44
+    const CompFromNavigation = Navigation.registerComponent('ComponentName', () => (props) => <HOC {...props} />)();
45 45
 
46 46
     const tree = renderer.create(<CompFromNavigation componentId='componentId' renderCountIncrement={renderCountIncrement}/>);
47 47
     expect(tree.toJSON().children).toEqual(['no name']);

+ 1
- 1
lib/android/app/build.gradle ファイルの表示

@@ -64,7 +64,7 @@ android {
64 64
             dimension "RNN.reactNativeVersion"
65 65
             buildConfigField("int", "REACT_NATVE_VERSION_MINOR", "57")
66 66
         }
67
-        reactNative57WixFork {
67
+        reactNative57_5 {
68 68
             dimension "RNN.reactNativeVersion"
69 69
             buildConfigField("int", "REACT_NATVE_VERSION_MINOR", "57")
70 70
         }

+ 28
- 0
lib/android/app/src/reactNative57_5/java/com/reactnativenavigation/react/DevBundleDownloadListenerAdapter.java ファイルの表示

@@ -0,0 +1,28 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.bridge.NativeDeltaClient;
4
+import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
5
+
6
+import javax.annotation.Nullable;
7
+
8
+public class DevBundleDownloadListenerAdapter implements DevBundleDownloadListener, NavigationDevBundleDownloadListener {
9
+    @Override
10
+    public void onSuccess(@Nullable NativeDeltaClient nativeDeltaClient) {
11
+        onSuccess();
12
+    }
13
+
14
+    @Override
15
+    public void onSuccess() {
16
+
17
+    }
18
+
19
+    @Override
20
+    public void onProgress(@Nullable String status, @Nullable Integer done, @Nullable Integer total) {
21
+
22
+    }
23
+
24
+    @Override
25
+    public void onFailure(Exception cause) {
26
+
27
+    }
28
+}

+ 28
- 0
lib/android/app/src/reactNative57_5/java/com/reactnativenavigation/react/JsDevReloadHandlerFacade.java ファイルの表示

@@ -0,0 +1,28 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.bridge.NativeDeltaClient;
4
+import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
5
+
6
+import javax.annotation.Nullable;
7
+
8
+public class JsDevReloadHandlerFacade implements DevBundleDownloadListener, NavigationDevBundleDownloadListener {
9
+    @Override
10
+    public void onSuccess(@Nullable NativeDeltaClient nativeDeltaClient) {
11
+        onSuccess();
12
+    }
13
+
14
+    @Override
15
+    public void onProgress(@Nullable String status, @Nullable Integer done, @Nullable Integer total) {
16
+
17
+    }
18
+
19
+    @Override
20
+    public void onFailure(Exception cause) {
21
+
22
+    }
23
+
24
+    @Override
25
+    public void onSuccess() {
26
+
27
+    }
28
+}

+ 107
- 0
lib/android/app/src/reactNative57_5/java/com/reactnativenavigation/react/NavigationReactNativeHost.java ファイルの表示

@@ -0,0 +1,107 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import android.app.Application;
4
+import android.support.annotation.NonNull;
5
+import android.support.annotation.Nullable;
6
+
7
+import com.facebook.infer.annotation.Assertions;
8
+import com.facebook.react.ReactInstanceManager;
9
+import com.facebook.react.ReactInstanceManagerBuilder;
10
+import com.facebook.react.ReactNativeHost;
11
+import com.facebook.react.ReactPackage;
12
+import com.facebook.react.common.LifecycleState;
13
+import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
14
+import com.facebook.react.shell.MainReactPackage;
15
+import com.reactnativenavigation.NavigationApplication;
16
+
17
+import java.util.ArrayList;
18
+import java.util.List;
19
+
20
+/**
21
+ * Default implementation of {@link ReactNativeHost} that includes {@link NavigationPackage}
22
+ * and user-defined additional packages.
23
+ */
24
+public class NavigationReactNativeHost extends ReactNativeHost implements BundleDownloadListenerProvider {
25
+
26
+    private final boolean isDebug;
27
+    private final List<ReactPackage> additionalReactPackages;
28
+    private @Nullable NavigationDevBundleDownloadListener bundleListener;
29
+    private final DevBundleDownloadListener bundleListenerMediator = new DevBundleDownloadListenerAdapter() {
30
+        @Override
31
+        public void onSuccess() {
32
+            if (bundleListener != null) {
33
+                bundleListener.onSuccess();
34
+            }
35
+        }
36
+    };
37
+
38
+    public NavigationReactNativeHost(NavigationApplication application) {
39
+        this(application, application.isDebug(), application.createAdditionalReactPackages());
40
+    }
41
+
42
+    @SuppressWarnings("WeakerAccess")
43
+    public NavigationReactNativeHost(Application application, boolean isDebug, final List<ReactPackage> additionalReactPackages) {
44
+        super(application);
45
+        this.isDebug = isDebug;
46
+        this.additionalReactPackages = additionalReactPackages;
47
+    }
48
+
49
+    @Override
50
+    public void setBundleLoaderListener(NavigationDevBundleDownloadListener listener) {
51
+        bundleListener = listener;
52
+    }
53
+
54
+    @Override
55
+    public boolean getUseDeveloperSupport() {
56
+        return isDebug;
57
+    }
58
+
59
+    @Override
60
+    protected List<ReactPackage> getPackages() {
61
+        List<ReactPackage> packages = new ArrayList<>();
62
+        boolean hasMainReactPackage = false;
63
+        packages.add(new NavigationPackage(this));
64
+        if (additionalReactPackages != null) {
65
+            for (ReactPackage p : additionalReactPackages) {
66
+                if (!(p instanceof NavigationPackage)) {
67
+                    packages.add(p);
68
+                }
69
+                if (p instanceof MainReactPackage) hasMainReactPackage = true;
70
+            }
71
+        }
72
+        if (!hasMainReactPackage) {
73
+            packages.add(new MainReactPackage());
74
+        }
75
+        return packages;
76
+    }
77
+
78
+    protected ReactInstanceManager createReactInstanceManager() {
79
+        ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
80
+                .setApplication(getApplication())
81
+                .setJSMainModulePath(getJSMainModuleName())
82
+                .setUseDeveloperSupport(getUseDeveloperSupport())
83
+                .setRedBoxHandler(getRedBoxHandler())
84
+                .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
85
+                .setUIImplementationProvider(getUIImplementationProvider())
86
+                .setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
87
+                .setDevBundleDownloadListener(getDevBundleDownloadListener());
88
+
89
+        for (ReactPackage reactPackage : getPackages()) {
90
+            builder.addPackage(reactPackage);
91
+        }
92
+
93
+        String jsBundleFile = getJSBundleFile();
94
+        if (jsBundleFile != null) {
95
+            builder.setJSBundleFile(jsBundleFile);
96
+        } else {
97
+            builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
98
+        }
99
+        return builder.build();
100
+    }
101
+
102
+    @SuppressWarnings("WeakerAccess")
103
+    @NonNull
104
+    protected DevBundleDownloadListener getDevBundleDownloadListener() {
105
+        return bundleListenerMediator;
106
+    }
107
+}

+ 25
- 0
lib/android/app/src/reactNative57_5/java/com/reactnativenavigation/react/ReloadHandlerFacade.java ファイルの表示

@@ -0,0 +1,25 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.bridge.NativeDeltaClient;
4
+import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
5
+
6
+import javax.annotation.Nullable;
7
+
8
+public abstract class ReloadHandlerFacade implements DevBundleDownloadListener {
9
+    @Override
10
+    public void onSuccess(@Nullable NativeDeltaClient nativeDeltaClient) {
11
+
12
+    }
13
+
14
+    @Override
15
+    public void onProgress(@Nullable String status, @Nullable Integer done, @Nullable Integer total) {
16
+
17
+    }
18
+
19
+    @Override
20
+    public void onFailure(Exception cause) {
21
+
22
+    }
23
+
24
+    protected abstract void onSuccess();
25
+}

+ 83
- 0
lib/android/app/src/reactNative57_5/java/com/reactnativenavigation/react/SyncUiImplementation.java ファイルの表示

@@ -0,0 +1,83 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import android.support.annotation.Nullable;
4
+
5
+import com.facebook.react.bridge.ReactApplicationContext;
6
+import com.facebook.react.bridge.ReadableArray;
7
+import com.facebook.react.bridge.ReadableMap;
8
+import com.facebook.react.uimanager.ThemedReactContext;
9
+import com.facebook.react.uimanager.UIImplementation;
10
+import com.facebook.react.uimanager.UIImplementationProvider;
11
+import com.facebook.react.uimanager.UIManagerModule;
12
+import com.facebook.react.uimanager.ViewManager;
13
+import com.facebook.react.uimanager.common.MeasureSpecProvider;
14
+import com.facebook.react.uimanager.common.SizeMonitoringFrameLayout;
15
+import com.facebook.react.uimanager.events.EventDispatcher;
16
+
17
+import java.util.List;
18
+
19
+@SuppressWarnings("WeakerAccess")
20
+public class SyncUiImplementation extends UIImplementation {
21
+    private static final Object lock = new Object();
22
+
23
+    public static class Provider extends UIImplementationProvider {
24
+        @Override
25
+        public UIImplementation createUIImplementation(ReactApplicationContext reactContext, List<ViewManager> viewManagerList, EventDispatcher eventDispatcher, int minTimeLeftInFrameForNonBatchedOperationMs) {
26
+            return new SyncUiImplementation(reactContext, viewManagerList, eventDispatcher, minTimeLeftInFrameForNonBatchedOperationMs);
27
+        }
28
+
29
+        @Override
30
+        public UIImplementation createUIImplementation(ReactApplicationContext reactContext, UIManagerModule.ViewManagerResolver viewManagerResolver, EventDispatcher eventDispatcher, int minTimeLeftInFrameForNonBatchedOperationMs) {
31
+            return new SyncUiImplementation(reactContext, viewManagerResolver, eventDispatcher, minTimeLeftInFrameForNonBatchedOperationMs);
32
+        }
33
+    }
34
+
35
+    public SyncUiImplementation(ReactApplicationContext reactContext, List<ViewManager> viewManagerList, EventDispatcher eventDispatcher, int minTimeLeftInFrameForNonBatchedOperationMs) {
36
+        super(reactContext, viewManagerList, eventDispatcher, minTimeLeftInFrameForNonBatchedOperationMs);
37
+    }
38
+
39
+    public SyncUiImplementation(ReactApplicationContext reactContext, UIManagerModule.ViewManagerResolver viewManagerResolver, EventDispatcher eventDispatcher, int minTimeLeftInFrameForNonBatchedOperationMs) {
40
+        super(reactContext, viewManagerResolver, eventDispatcher, minTimeLeftInFrameForNonBatchedOperationMs);
41
+    }
42
+
43
+    @Override
44
+    public void manageChildren(
45
+            int viewTag,
46
+            @Nullable ReadableArray moveFrom,
47
+            @Nullable ReadableArray moveTo,
48
+            @Nullable ReadableArray addChildTags,
49
+            @Nullable ReadableArray addAtIndices,
50
+            @Nullable ReadableArray removeFrom) {
51
+        synchronized (lock) {
52
+            super.manageChildren(viewTag, moveFrom, moveTo, addChildTags, addAtIndices, removeFrom);
53
+        }
54
+    }
55
+
56
+    @Override
57
+    public void setChildren(int viewTag, ReadableArray childrenTags) {
58
+        synchronized (lock) {
59
+            super.setChildren(viewTag, childrenTags);
60
+        }
61
+    }
62
+
63
+    @Override
64
+    public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
65
+        synchronized (lock) {
66
+            super.createView(tag, className, rootViewTag, props);
67
+        }
68
+    }
69
+
70
+    @Override
71
+    public void removeRootShadowNode(int rootViewTag) {
72
+        synchronized (lock) {
73
+            super.removeRootShadowNode(rootViewTag);
74
+        }
75
+    }
76
+
77
+    @Override
78
+    public <T extends SizeMonitoringFrameLayout & MeasureSpecProvider> void registerRootView(T rootView, int tag, ThemedReactContext context) {
79
+        synchronized (lock) {
80
+            super.registerRootView(rootView, tag, context);
81
+        }
82
+    }
83
+}

+ 6
- 7
lib/src/Navigation.ts ファイルの表示

@@ -31,17 +31,17 @@ export class NavigationRoot {
31 31
   private readonly eventsRegistry: EventsRegistry;
32 32
   private readonly commandsObserver: CommandsObserver;
33 33
   private readonly componentEventsObserver: ComponentEventsObserver;
34
-  private readonly componentWrapper: typeof ComponentWrapper;
34
+  private readonly componentWrapper: ComponentWrapper;
35 35
 
36 36
   constructor() {
37 37
     this.Element = Element;
38 38
     this.TouchablePreview = TouchablePreview;
39
+    this.componentWrapper = new ComponentWrapper();
39 40
     this.store = new Store();
40
-    this.componentWrapper = ComponentWrapper;
41 41
     this.nativeEventsReceiver = new NativeEventsReceiver();
42 42
     this.uniqueIdProvider = new UniqueIdProvider();
43 43
     this.componentEventsObserver = new ComponentEventsObserver(this.nativeEventsReceiver);
44
-    this.componentRegistry = new ComponentRegistry(this.store, this.componentEventsObserver, this.componentWrapper);
44
+    this.componentRegistry = new ComponentRegistry(this.store, this.componentEventsObserver);
45 45
     this.layoutTreeParser = new LayoutTreeParser();
46 46
     this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store);
47 47
     this.nativeCommandsSender = new NativeCommandsSender();
@@ -56,9 +56,8 @@ export class NavigationRoot {
56 56
    * Every navigation component in your app must be registered with a unique name.
57 57
    * The component itself is a traditional React component extending React.Component.
58 58
    */
59
-
60
-  public registerComponent(componentName: string | number, getComponentClassFunc: ComponentProvider): ComponentProvider {
61
-    return this.componentRegistry.registerComponent(componentName, getComponentClassFunc);
59
+  public registerComponent(componentName: string | number, componentProvider: ComponentProvider, concreteComponentProvider?: ComponentProvider): ComponentProvider {
60
+    return this.componentRegistry.registerComponent(componentName, componentProvider, this.componentWrapper, concreteComponentProvider);
62 61
   }
63 62
 
64 63
   /**
@@ -71,7 +70,7 @@ export class NavigationRoot {
71 70
     ReduxProvider: any,
72 71
     reduxStore: any
73 72
   ): ComponentProvider {
74
-    return this.componentRegistry.registerComponent(componentName, getComponentClassFunc, ReduxProvider, reduxStore);
73
+    return this.componentRegistry.registerComponent(componentName, getComponentClassFunc, this.componentWrapper, undefined, ReduxProvider, reduxStore);
75 74
   }
76 75
 
77 76
   /**

+ 2
- 3
lib/src/adapters/Element.tsx ファイルの表示

@@ -1,14 +1,13 @@
1 1
 import * as React from 'react';
2 2
 import * as PropTypes from 'prop-types';
3
-import { View, requireNativeComponent } from 'react-native';
3
+import { requireNativeComponent } from 'react-native';
4 4
 
5 5
 let RNNElement: React.ComponentType<any>;
6 6
 
7 7
 export class Element extends React.Component<{ elementId: any; resizeMode?: any; }, any> {
8 8
   static propTypes = {
9 9
     elementId: PropTypes.string.isRequired,
10
-    resizeMode: PropTypes.string,
11
-    ...View.propTypes
10
+    resizeMode: PropTypes.string
12 11
   };
13 12
 
14 13
   static defaultProps = {

+ 2
- 7
lib/src/adapters/TouchablePreview.tsx ファイルの表示

@@ -47,14 +47,9 @@ export class TouchablePreview extends React.PureComponent<Props, any> {
47 47
 
48 48
   static peeking = false;
49 49
 
50
-  private ref: React.Component<any> | null = null;
51 50
   private timeout: number | undefined;
52 51
   private ts: number = 0;
53
-
54
-  onRef = (ref: React.Component<any>) => {
55
-    this.ref = ref;
56
-  }
57
-
52
+  private onRef = React.createRef<any>();
58 53
   onPress = () => {
59 54
     const { onPress } = this.props;
60 55
 
@@ -73,7 +68,7 @@ export class TouchablePreview extends React.PureComponent<Props, any> {
73 68
         return;
74 69
       }
75 70
 
76
-      const reactTag = findNodeHandle(this.ref);
71
+      const reactTag = findNodeHandle(this.onRef.current);
77 72
 
78 73
       return onPressIn({ reactTag });
79 74
     }

+ 6
- 7
lib/src/components/ComponentRegistry.test.tsx ファイルの表示

@@ -27,28 +27,27 @@ describe('ComponentRegistry', () => {
27 27
     mockRegistry = AppRegistry.registerComponent = jest.fn(AppRegistry.registerComponent);
28 28
     mockWrapper = jest.mock('./ComponentWrapper');
29 29
     mockWrapper.wrap = () => WrappedComponent;
30
-    uut = new ComponentRegistry(store, {} as any, mockWrapper);
30
+    uut = new ComponentRegistry(store, {} as any);
31 31
   });
32 32
 
33 33
   it('registers component by componentName into AppRegistry', () => {
34 34
     expect(mockRegistry).not.toHaveBeenCalled();
35
-    const result = uut.registerComponent('example.MyComponent.name', () => {});
35
+    const result = uut.registerComponent('example.MyComponent.name', () => {}, mockWrapper);
36 36
     expect(mockRegistry).toHaveBeenCalledTimes(1);
37 37
     expect(mockRegistry.mock.calls[0][0]).toEqual('example.MyComponent.name');
38 38
     expect(mockRegistry.mock.calls[0][1]()).toEqual(result());
39 39
   });
40 40
 
41
-  it('saves the wrapper component generator the store', () => {
41
+  it('saves the wrapper component generator to the store', () => {
42 42
     expect(store.getComponentClassForName('example.MyComponent.name')).toBeUndefined();
43
-    uut.registerComponent('example.MyComponent.name', () => {});
43
+    uut.registerComponent('example.MyComponent.name', () => {}, mockWrapper);
44 44
     const Class = store.getComponentClassForName('example.MyComponent.name');
45 45
     expect(Class).not.toBeUndefined();
46 46
     expect(Class()).toEqual(WrappedComponent);
47
-    expect(Object.getPrototypeOf(Class())).toEqual(React.Component);
48 47
   });
49 48
 
50 49
   it('resulting in a normal component', () => {
51
-    uut.registerComponent('example.MyComponent.name', () => {});
50
+    uut.registerComponent('example.MyComponent.name', () => {}, mockWrapper);
52 51
     const Component = mockRegistry.mock.calls[0][1]();
53 52
     const tree = renderer.create(<Component componentId='123' />);
54 53
     expect(tree.toJSON()!.children).toEqual(['Hello, World!']);
@@ -64,7 +63,7 @@ describe('ComponentRegistry', () => {
64 63
     jest.spyOn(store, 'setComponentClassForName');
65 64
     const generator = jest.fn(() => {});
66 65
     const componentName = 'example.MyComponent.name';
67
-    uut.registerComponent(componentName, generator);
66
+    uut.registerComponent(componentName, generator, mockWrapper);
68 67
     expect(store.getComponentClassForName(componentName)()).toEqual(WrappedComponent);
69 68
   });
70 69
 });

+ 9
- 4
lib/src/components/ComponentRegistry.ts ファイルの表示

@@ -1,14 +1,19 @@
1 1
 import { AppRegistry, ComponentProvider } from 'react-native';
2
-import { ComponentWrapper } from './ComponentWrapper';
3 2
 import { Store } from './Store';
4 3
 import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
4
+import { ComponentWrapper } from './ComponentWrapper';
5 5
 
6 6
 export class ComponentRegistry {
7
-  constructor(private readonly store: Store, private readonly componentEventsObserver: ComponentEventsObserver, private readonly ComponentWrapperClass: typeof ComponentWrapper) { }
7
+  constructor(private readonly store: Store, private readonly componentEventsObserver: ComponentEventsObserver) { }
8 8
 
9
-  registerComponent(componentName: string | number, getComponentClassFunc: ComponentProvider, ReduxProvider?: any, reduxStore?: any): ComponentProvider {
9
+  registerComponent(componentName: string | number,
10
+                    componentProvider: ComponentProvider,
11
+                    componentWrapper: ComponentWrapper,
12
+                    concreteComponentProvider?: ComponentProvider,
13
+                    ReduxProvider?: any,
14
+                    reduxStore?: any): ComponentProvider {
10 15
     const NavigationComponent = () => {
11
-      return this.ComponentWrapperClass.wrap(componentName.toString(), getComponentClassFunc, this.store, this.componentEventsObserver, ReduxProvider, reduxStore)
16
+      return componentWrapper.wrap(componentName.toString(), componentProvider, this.store, this.componentEventsObserver, concreteComponentProvider, ReduxProvider, reduxStore);
12 17
     };
13 18
     this.store.setComponentClassForName(componentName.toString(), NavigationComponent);
14 19
     AppRegistry.registerComponent(componentName.toString(), NavigationComponent);

+ 37
- 17
lib/src/components/ComponentWrapper.test.tsx ファイルの表示

@@ -1,5 +1,5 @@
1 1
 import * as React from 'react';
2
-import { Text } from 'react-native';
2
+import { View, Text } from 'react-native';
3 3
 import * as renderer from 'react-test-renderer';
4 4
 import { ComponentWrapper } from './ComponentWrapper';
5 5
 import { Store } from './Store';
@@ -12,6 +12,7 @@ describe('ComponentWrapper', () => {
12 12
   let myComponentProps;
13 13
   let mockedComponentEventsObserver: ComponentEventsObserver;
14 14
   let componentEventsObserver: ComponentEventsObserver;
15
+  let uut: ComponentWrapper;
15 16
 
16 17
   class MyComponent extends React.Component<any, any> {
17 18
     static options = {
@@ -50,10 +51,11 @@ describe('ComponentWrapper', () => {
50 51
     store = new Store();
51 52
     mockedComponentEventsObserver = mock(ComponentEventsObserver);
52 53
     componentEventsObserver = instance(mockedComponentEventsObserver);
54
+    uut = new ComponentWrapper();
53 55
   });
54 56
 
55 57
   it('must have componentId as prop', () => {
56
-    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
58
+    const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);
57 59
     const orig = console.error;
58 60
     console.error = (a) => a;
59 61
     expect(() => {
@@ -63,7 +65,7 @@ describe('ComponentWrapper', () => {
63 65
   });
64 66
 
65 67
   it('wraps the component', () => {
66
-    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
68
+    const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);
67 69
     expect(NavigationComponent).not.toBeInstanceOf(MyComponent);
68 70
     const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
69 71
     expect(tree.toJSON()!.children).toEqual(['Hello, World!']);
@@ -71,14 +73,14 @@ describe('ComponentWrapper', () => {
71 73
 
72 74
   it('injects props from wrapper into original component', () => {
73 75
     const renderCount = jest.fn();
74
-    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
76
+    const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);
75 77
     const tree = renderer.create(<NavigationComponent componentId={'component1'} text={'yo'} renderCount={renderCount} />);
76 78
     expect(tree.toJSON()!.children).toEqual(['yo']);
77 79
     expect(renderCount).toHaveBeenCalledTimes(1);
78 80
   });
79 81
 
80 82
   it('updates props from wrapper into original component on state change', () => {
81
-    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
83
+    const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);
82 84
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
83 85
     expect(myComponentProps.foo).toEqual(undefined);
84 86
     (tree.getInstance() as any).setState({ propsFromState: { foo: 'yo' } });
@@ -87,13 +89,13 @@ describe('ComponentWrapper', () => {
87 89
 
88 90
   it('pulls props from the store and injects them into the inner component', () => {
89 91
     store.setPropsForId('component123', { numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
90
-    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
92
+    const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);
91 93
     renderer.create(<NavigationComponent componentId={'component123'} />);
92 94
     expect(myComponentProps).toEqual({ componentId: 'component123', numberProp: 1, stringProp: 'hello', objectProp: { a: 2 } });
93 95
   });
94 96
 
95 97
   it('updates props from store into inner component', () => {
96
-    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
98
+    const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);
97 99
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
98 100
     store.setPropsForId('component1', { myProp: 'hello' });
99 101
     expect(myComponentProps.foo).toEqual(undefined);
@@ -104,42 +106,57 @@ describe('ComponentWrapper', () => {
104 106
   });
105 107
 
106 108
   it('protects id from change', () => {
107
-    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
109
+    const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);
108 110
     const tree = renderer.create(<TestParent ChildClass={NavigationComponent} />);
109 111
     expect(myComponentProps.componentId).toEqual('component1');
110 112
     (tree.getInstance() as any).setState({ propsFromState: { id: 'ERROR' } });
111 113
     expect(myComponentProps.componentId).toEqual('component1');
112 114
   });
113 115
 
114
-  it('assignes key by id', () => {
115
-    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
116
+  xit('assigns key by componentId', () => {
117
+    const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);
116 118
     const tree = renderer.create(<NavigationComponent componentId={'component1'} />);
117 119
     expect(myComponentProps.componentId).toEqual('component1');
118
-    expect((tree.getInstance() as any)._reactInternalInstance.child.key).toEqual('component1');
120
+    console.log(Object.keys(tree.root.findByType(NavigationComponent).instance._reactInternalFiber));
121
+    console.log(tree.root.findByType(NavigationComponent).instance._reactInternalFiber.child.child.child.return.return.key);
122
+    expect((tree.getInstance() as any)._reactInternalInstance.child.child.Fibernode.key).toEqual('component1');
119 123
   });
120 124
 
121 125
   it('cleans props from store on unMount', () => {
122 126
     store.setPropsForId('component123', { foo: 'bar' });
123
-    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
127
+    const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);
124 128
     const tree = renderer.create(<NavigationComponent componentId={'component123'} />);
125 129
     expect(store.getPropsForId('component123')).toEqual({ foo: 'bar' });
126 130
     tree.unmount();
127 131
     expect(store.getPropsForId('component123')).toEqual({});
128 132
   });
129 133
 
130
-  it(`merges static members from wrapped component when generated`, () => {
131
-    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver) as any;
134
+  it('merges static members from wrapped component when generated', () => {
135
+    const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver) as any;
132 136
     expect(NavigationComponent.options).toEqual({ title: 'MyComponentTitle' });
133 137
   });
134 138
 
135
-  it(`calls unmounted on componentEventsObserver`, () => {
136
-    const NavigationComponent = ComponentWrapper.wrap(componentName, () => MyComponent, store, componentEventsObserver);
139
+  it('calls unmounted on componentEventsObserver', () => {
140
+    const NavigationComponent = uut.wrap(componentName, () => MyComponent, store, componentEventsObserver);
137 141
     const tree = renderer.create(<NavigationComponent componentId={'component123'} />);
138 142
     verify(mockedComponentEventsObserver.unmounted('component123')).never();
139 143
     tree.unmount();
140 144
     verify(mockedComponentEventsObserver.unmounted('component123')).once();
141 145
   });
142 146
 
147
+  it('renders HOC components correctly', () => {
148
+    const generator = () => (props) => (
149
+      <View>
150
+        <MyComponent {...props}/>
151
+      </View>
152
+    );
153
+    uut = new ComponentWrapper();
154
+    const NavigationComponent = uut.wrap(componentName, generator, store, componentEventsObserver);
155
+    const tree = renderer.create(<NavigationComponent componentId={'component123'} />);
156
+    expect(tree.root.findByType(View)).toBeDefined()
157
+    expect(tree.root.findByType(MyComponent).props).toEqual({componentId: 'component123'});
158
+  });
159
+
143 160
   describe(`register with redux store`, () => {
144 161
     class MyReduxComp extends React.Component<any> {
145 162
       static get options() {
@@ -162,10 +179,13 @@ describe('ComponentWrapper', () => {
162 179
     const reduxStore = require('redux').createStore((state = initialState) => state);
163 180
 
164 181
     it(`wraps the component with a react-redux provider with passed store`, () => {
165
-      const NavigationComponent = ComponentWrapper.wrap(componentName, () => ConnectedComp, store, componentEventsObserver, ReduxProvider, reduxStore);
182
+      const NavigationComponent = uut.wrap(componentName, () => ConnectedComp, store, componentEventsObserver, undefined, ReduxProvider, reduxStore);
166 183
       const tree = renderer.create(<NavigationComponent componentId={'theCompId'} />);
167 184
       expect(tree.toJSON()!.children).toEqual(['it just works']);
168 185
       expect((NavigationComponent as any).options).toEqual({ foo: 123 });
169 186
     });
170 187
   });
188
+
171 189
 });
190
+
191
+

+ 5
- 10
lib/src/components/ComponentWrapper.tsx ファイルの表示

@@ -9,11 +9,12 @@ interface HocState { componentId: string; allProps: {}; }
9 9
 interface HocProps { componentId: string; }
10 10
 
11 11
 export class ComponentWrapper {
12
-  static wrap(
12
+  wrap(
13 13
     componentName: string | number,
14 14
     OriginalComponentGenerator: ComponentProvider,
15 15
     store: Store,
16 16
     componentEventsObserver: ComponentEventsObserver,
17
+    concreteComponentProvider: ComponentProvider = OriginalComponentGenerator,
17 18
     ReduxProvider?: any,
18 19
     reduxStore?: any
19 20
   ): React.ComponentClass<any> {
@@ -44,7 +45,6 @@ export class ComponentWrapper {
44 45
           <GeneratedComponentClass
45 46
             {...this.state.allProps}
46 47
             componentId={this.state.componentId}
47
-            key={this.state.componentId}
48 48
           />
49 49
         );
50 50
       }
@@ -57,16 +57,11 @@ export class ComponentWrapper {
57 57
     }
58 58
 
59 59
     ReactLifecyclesCompat.polyfill(WrappedComponent);
60
-    require('hoist-non-react-statics')(WrappedComponent, GeneratedComponentClass);
61
-
62
-    if (reduxStore && ReduxProvider) {
63
-      return ComponentWrapper.wrapWithRedux(WrappedComponent, ReduxProvider, reduxStore);
64
-    } else {
65
-      return WrappedComponent;
66
-    }
60
+    require('hoist-non-react-statics')(WrappedComponent, concreteComponentProvider());
61
+    return ReduxProvider ? this.wrapWithRedux(WrappedComponent, ReduxProvider, reduxStore) : WrappedComponent;
67 62
   }
68 63
 
69
-  static wrapWithRedux(WrappedComponent: React.ComponentClass<any>, ReduxProvider: any, reduxStore: any): React.ComponentClass<any> {
64
+  wrapWithRedux(WrappedComponent: React.ComponentClass<any>, ReduxProvider: any, reduxStore: any): React.ComponentClass<any> {
70 65
     class ReduxWrapper extends React.Component<any, any> {
71 66
       render() {
72 67
         return (

+ 10
- 0
metro.config.js ファイルの表示

@@ -0,0 +1,10 @@
1
+module.exports = {
2
+  projectRoot: `${__dirname}/playground`,
3
+  watchFolders: [
4
+    __dirname
5
+  ],
6
+  transformer: {
7
+    babelTransformerPath: require.resolve('react-native-typescript-transformer')
8
+  }
9
+};
10
+

+ 12
- 24
package.json ファイルの表示

@@ -60,19 +60,21 @@
60 60
     "tslib": "1.9.3"
61 61
   },
62 62
   "devDependencies": {
63
-    "@types/jest": "22.x.x",
63
+    "@types/jest": "23.x.x",
64 64
     "@types/lodash": "4.x.x",
65 65
     "@types/react": "16.x.x",
66
-    "@types/react-native": "0.51.x",
66
+    "@types/react-native": "0.57.7",
67 67
     "@types/react-test-renderer": "16.x.x",
68 68
     "detox": "9.0.6",
69 69
     "handlebars": "4.x.x",
70
-    "jest": "22.x.x",
71
-    "react": "16.2.0",
72
-    "react-native": "0.51.x",
70
+    "jest": "23.x.x",
71
+    "react": "16.6.1",
72
+    "react-native": "0.57.7",
73
+    "react-native-typescript-transformer": "^1.2.10",
73 74
     "react-native-view-overflow": "0.0.3",
74 75
     "react-redux": "5.x.x",
75
-    "react-test-renderer": "16.0.0-alpha.12",
76
+    "react-test-renderer": "16.6.3",
77
+    "metro-react-native-babel-preset": "0.50.0",
76 78
     "redux": "3.x.x",
77 79
     "remx": "2.x.x",
78 80
     "semver": "5.x.x",
@@ -81,19 +83,13 @@
81 83
     "ts-node": "5.x.x",
82 84
     "tslint": "5.x.x",
83 85
     "typedoc": "0.x.x",
84
-    "typescript": "2.9.x"
85
-  },
86
-  "babel": {
87
-    "env": {
88
-      "test": {
89
-        "presets": [
90
-          "react-native"
91
-        ]
92
-      }
93
-    }
86
+    "typescript": "3.2.2"
94 87
   },
95 88
   "jest": {
96 89
     "preset": "react-native",
90
+    "transform": {
91
+      "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
92
+    },
97 93
     "roots": [
98 94
       "<rootDir>/node_modules/",
99 95
       "<rootDir>/lib/dist/",
@@ -112,14 +108,6 @@
112 108
     ],
113 109
     "resetMocks": true,
114 110
     "resetModules": true,
115
-    "coverageThreshold": {
116
-      "global": {
117
-        "branches": 100,
118
-        "functions": 100,
119
-        "lines": 100,
120
-        "statements": 100
121
-      }
122
-    },
123 111
     "coverageReporters": [
124 112
       "json",
125 113
       "lcov",

+ 0
- 65
playground/.flowconfig ファイルの表示

@@ -1,65 +0,0 @@
1
-[ignore]
2
-
3
-# We fork some components by platform.
4
-.*/*.web.js
5
-.*/*.android.js
6
-
7
-# Some modules have their own node_modules with overlap
8
-.*/node_modules/node-haste/.*
9
-
10
-# Ugh
11
-.*/node_modules/babel.*
12
-.*/node_modules/babylon.*
13
-.*/node_modules/invariant.*
14
-
15
-# Ignore react and fbjs where there are overlaps, but don't ignore
16
-# anything that react-native relies on
17
-.*/node_modules/fbjs/lib/Map.js
18
-.*/node_modules/fbjs/lib/Promise.js
19
-.*/node_modules/fbjs/lib/fetch.js
20
-.*/node_modules/fbjs/lib/ExecutionEnvironment.js
21
-.*/node_modules/fbjs/lib/isEmpty.js
22
-.*/node_modules/fbjs/lib/crc32.js
23
-.*/node_modules/fbjs/lib/ErrorUtils.js
24
-
25
-# Flow has a built-in definition for the 'react' module which we prefer to use
26
-# over the currently-untyped source
27
-.*/node_modules/react/react.js
28
-.*/node_modules/react/lib/React.js
29
-.*/node_modules/react/lib/ReactDOM.js
30
-
31
-# Ignore commoner tests
32
-.*/node_modules/commoner/test/.*
33
-
34
-# See https://github.com/facebook/flow/issues/442
35
-.*/react-tools/node_modules/commoner/lib/reader.js
36
-
37
-# Ignore jest
38
-.*/node_modules/jest-cli/.*
39
-
40
-# Ignore Website
41
-.*/website/.*
42
-
43
-[include]
44
-
45
-[libs]
46
-node_modules/react-native/Libraries/react-native/react-native-interface.js
47
-
48
-[options]
49
-module.system=haste
50
-
51
-munge_underscores=true
52
-
53
-module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
54
-module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\)$' -> 'RelativeImageStub'
55
-
56
-suppress_type=$FlowIssue
57
-suppress_type=$FlowFixMe
58
-suppress_type=$FixMe
59
-
60
-suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
61
-suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
62
-suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
63
-
64
-[version]
65
-0.21.0

+ 2
- 2
playground/android/app/build.gradle ファイルの表示

@@ -2,7 +2,7 @@ apply plugin: "com.android.application"
2 2
 
3 3
 project.ext.react = [
4 4
         root     : "../../../",
5
-        entryFile: "playground/index.js"
5
+        entryFile: "index.js"
6 6
 ]
7 7
 
8 8
 apply from: "../../../node_modules/react-native/react.gradle"
@@ -28,7 +28,7 @@ android {
28 28
 
29 29
         testBuildType System.getProperty('testBuildType', 'debug')  //this will later be used to control the test apk build type
30 30
         missingDimensionStrategy "minReactNative", "minReactNative46"
31
-        missingDimensionStrategy "RNN.reactNativeVersion", "reactNative51"
31
+        missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5"
32 32
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
33 33
     }
34 34
     signingConfigs {

+ 1
- 1
playground/ios/playground.xcodeproj/project.pbxproj ファイルの表示

@@ -876,7 +876,7 @@
876 876
 			);
877 877
 			runOnlyForDeploymentPostprocessing = 0;
878 878
 			shellPath = /bin/sh;
879
-			shellScript = "export NODE_BINARY=node\n../../node_modules/react-native/scripts/react-native-xcode.sh ./playground/index.js";
879
+			shellScript = "export NODE_BINARY=node\n../../node_modules/react-native/scripts/react-native-xcode.sh ./index.js";
880 880
 		};
881 881
 /* End PBXShellScriptBuildPhase section */
882 882
 

+ 7
- 0
playground/src/context/index.js ファイルの表示

@@ -0,0 +1,7 @@
1
+const React = require('react');
2
+const titleContext = React.createContext('Default title from Context');
3
+
4
+module.exports = {
5
+  TitleContext: titleContext,
6
+  Context: React.createContext('Default value from Context')
7
+}

+ 59
- 0
playground/src/screens/ContextScreen.js ファイルの表示

@@ -0,0 +1,59 @@
1
+const React = require('react');
2
+const { View, Text } = require('react-native');
3
+const testIDs = require('../testIDs');
4
+const { TitleContext, Context } = require('../context');
5
+
6
+class ContextScreen extends React.Component {
7
+  static contextType = Context;
8
+
9
+  static options() {
10
+    return {
11
+      topBar: {
12
+        title: {
13
+          text: 'My Screen'
14
+        },
15
+        background: {
16
+          color: 'red'
17
+        }
18
+      }
19
+    };
20
+  }
21
+
22
+  render() {
23
+    return (
24
+      <View style={styles.root}>
25
+        <View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 10 }}>
26
+          <Text style={styles.h2}>Default value: </Text>
27
+          <Text style={styles.h2} testID={testIDs.CENTERED_TEXT_HEADER}>{this.context}</Text>
28
+        </View>
29
+        <View style={{ flexDirection: 'row', alignItems: 'center' }}>
30
+          <Text style={styles.h2}>Provider value: </Text>
31
+          <TitleContext.Consumer>
32
+            {title => (
33
+              <Text style={styles.h2} testID={testIDs.CENTERED_TEXT_HEADER}>{title}</Text>
34
+            )}
35
+          </TitleContext.Consumer>
36
+        </View>
37
+      </View>
38
+    );
39
+  }
40
+}
41
+
42
+module.exports = ContextScreen;
43
+
44
+const styles = {
45
+  root: {
46
+    flexGrow: 1,
47
+    justifyContent: 'center',
48
+    alignItems: 'center',
49
+    backgroundColor: '#f5fcff'
50
+  },
51
+  h1: {
52
+    fontSize: 24,
53
+    textAlign: 'center'
54
+  },
55
+  h2: {
56
+    fontSize: 12,
57
+    textAlign: 'center',
58
+  },
59
+};

+ 6
- 3
playground/src/screens/SearchScreen.js ファイルの表示

@@ -14,7 +14,10 @@ const {
14 14
 const { Navigation } = require('react-native-navigation');
15 15
 const testIDs = require('../testIDs');
16 16
 
17
-const ITEMS = [...Array(200).keys()].map(key => ({ key: `Item ${key}` }));
17
+const ITEMS = [];
18
+for(let i = 0; i < 200; i++) {
19
+  ITEMS.push({key: `Item ${i}`});
20
+}
18 21
 
19 22
 class SearchControllerScreen extends Component {
20 23
   static get options() {
@@ -113,8 +116,8 @@ module.exports = SearchControllerScreen;
113 116
 const styles = StyleSheet.create({
114 117
   contentContainer: {},
115 118
   row: {
116
-    height: 50,
117
-    padding: 20,
119
+    height: 60,
120
+    padding: 15,
118 121
     justifyContent: 'center'
119 122
   },
120 123
   rowText: {

+ 9
- 0
playground/src/screens/WelcomeScreen.js ファイルの表示

@@ -41,6 +41,7 @@ class WelcomeScreen extends Component {
41 41
           <Button title='Push Lifecycle Screen' testID={testIDs.PUSH_LIFECYCLE_BUTTON} onPress={this.onClickLifecycleScreen} />
42 42
           <Button title='Static Lifecycle Events' testID={testIDs.PUSH_STATIC_LIFECYCLE_BUTTON} onPress={this.onClickShowStaticLifecycleOverlay} />
43 43
           <Button title='Push' testID={testIDs.PUSH_BUTTON} onPress={this.onClickPush} />
44
+          {false && <Button title='Push Context Screen' testID={testIDs.PUSH_CONTEXT_SCREEN_BUTTON} onPress={this.onClickPushContextScreen} />}
44 45
           {Platform.OS === 'ios' && <Button testID={testIDs.SHOW_PREVIEW_BUTTON} onPress={this.onClickPush} onPressIn={this.onClickShowPreview} title='Push Preview' />}
45 46
           <Button title='Push Options Screen' testID={testIDs.PUSH_OPTIONS_BUTTON} onPress={this.onClickPushOptionsScreen} />
46 47
           <Button title='Push External Component' testID={testIDs.PUSH_EXTERNAL_COMPONENT_BUTTON} onPress={this.onClickPushExternalComponent} />
@@ -293,6 +294,14 @@ class WelcomeScreen extends Component {
293 294
     });
294 295
   }
295 296
 
297
+  onClickPushContextScreen = async () => {
298
+    await Navigation.push(this.props.componentId, {
299
+      component: {
300
+        name: 'navigation.playground.ContextScreen',
301
+      }
302
+    })
303
+  }
304
+
296 305
   onClickPushExternalComponent = async () => {
297 306
     await Navigation.push(this.props.componentId, {
298 307
       externalComponent: {

+ 8
- 0
playground/src/screens/index.js ファイルの表示

@@ -1,3 +1,4 @@
1
+const React = require('react');
1 2
 const { Navigation } = require('react-native-navigation');
2 3
 const WelcomeScreen = require('./WelcomeScreen');
3 4
 const TextScreen = require('./TextScreen');
@@ -27,6 +28,8 @@ const SearchScreen = require('./SearchScreen');
27 28
 const KeyboardScreen = require('./KeyboardScreen');
28 29
 const BottomTabSideMenuScreen = require('./complexlayouts/BottomTabSideMenuScreen');
29 30
 const FlatListScreen = require('./FlatListScreen');
31
+const ContextScreen = require('./ContextScreen');
32
+const { TitleContext } = require('../context');
30 33
 
31 34
 function registerScreens() {
32 35
   Navigation.registerComponent(`navigation.playground.CustomTransitionDestination`, () => CustomTransitionDestination);
@@ -38,6 +41,11 @@ function registerScreens() {
38 41
   Navigation.registerComponent(`navigation.playground.StaticLifecycleOverlay`, () => StaticLifecycleOverlay);
39 42
   Navigation.registerComponent(`navigation.playground.TextScreen`, () => TextScreen);
40 43
   Navigation.registerComponent(`navigation.playground.PushedScreen`, () => PushedScreen);
44
+  Navigation.registerComponent('navigation.playground.ContextScreen', () => (props) => (
45
+    <TitleContext.Provider value={'Title from Provider'}>
46
+      <ContextScreen {...props} />
47
+    </TitleContext.Provider>
48
+  ), () => ContextScreen);
41 49
   Navigation.registerComponent(`navigation.playground.OptionsScreen`, () => OptionsScreen);
42 50
   Navigation.registerComponent(`navigation.playground.OrientationSelectScreen`, () => OrientationSelectScreen);
43 51
   Navigation.registerComponent(`navigation.playground.OrientationDetectScreen`, () => OrientationDetectScreen);

+ 1
- 0
playground/src/testIDs.js ファイルの表示

@@ -7,6 +7,7 @@ module.exports = {
7 7
   PUSH_LIFECYCLE_BUTTON: `PUSH_LIFECYCLE_BUTTON`,
8 8
   PUSH_STATIC_LIFECYCLE_BUTTON: `PUSH_STATIC_LIFECYCLE_BUTTON`,
9 9
   PUSH_BUTTON: `PUSH_BUTTON`,
10
+  PUSH_CONTEXT_SCREEN_BUTTON: `PUSH_CONTEXT_SCREEN_BUTTON`,
10 11
   SHOW_YELLOW_BOX: `SHOW_YELLOW_BOX`,
11 12
   SIDE_MENU_PUSH_BUTTON: `SIDE_MENU_PUSH_BUTTON`,
12 13
   LEFT_SIDE_MENU_PUSH_BUTTON: `leftSIDE_MENU_PUSH_BUTTON`,

+ 0
- 5
rn-cli.config.js ファイルの表示

@@ -1,5 +0,0 @@
1
-module.exports = {
2
-  getProjectRoots() {
3
-    return [__dirname, `${__dirname}/playground`];
4
-  }
5
-};

+ 1
- 1
scripts/start.js ファイルの表示

@@ -6,5 +6,5 @@ function run() {
6 6
   exec.killPort(8081);
7 7
   exec.execSync(`watchman watch-del-all || true`);
8 8
   exec.execSync(`adb reverse tcp:8081 tcp:8081 || true`);
9
-  exec.execSync(`node ./node_modules/react-native/local-cli/cli.js start --root ./playground/`);
9
+  exec.execSync(`node ./node_modules/react-native/local-cli/cli.js start`);
10 10
 }

+ 1
- 1
scripts/test-unit.js ファイルの表示

@@ -13,7 +13,7 @@ function run() {
13 13
 }
14 14
 
15 15
 function runAndroidUnitTests() {
16
-  const conf = release ? 'testReactNative51ReleaseUnitTest' : 'testReactNative51DebugUnitTest';
16
+  const conf = release ? 'testReactNative57_5ReleaseUnitTest' : 'testReactNative57_5DebugUnitTest';
17 17
 
18 18
   exec.execSync(`cd lib/android && ./gradlew ${conf}`);
19 19
 }

+ 25
- 0
scripts/upload_artifacts.sh ファイルの表示

@@ -0,0 +1,25 @@
1
+#!/bin/bash -e
2
+
3
+export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY
4
+export AWS_SECRET_ACCESS_KEY=$AWS_ACCESS_SECRET
5
+
6
+DATE=`date '+%Y-%m-%d_%H-%M-%S'`
7
+
8
+if [ $JENKINS_CI ]; then
9
+  ARTIFACTS_NAME="artifacts_${BUILD_ID}_${DATE}.tar.gz"
10
+else
11
+  ARTIFACTS_NAME="artifacts_${TRAVIS_BUILD_ID}_${DATE}.tar.gz"
12
+fi
13
+
14
+if [ -d "detox/test/artifacts" ]; then
15
+  tar cvzf ${ARTIFACTS_NAME} ./detox/test/artifacts/
16
+
17
+  if [ $JENKINS_CI ]; then
18
+      echo "Jenkins has build in plugin"
19
+  else
20
+      aws s3 cp ${ARTIFACTS_NAME} s3://detox-artifacts/ --region=us-east-1
21
+  fi
22
+
23
+  echo "The artifacts archive is available for download at:"
24
+  echo "https://detox-artifacts.s3.amazonaws.com/${ARTIFACTS_NAME}"
25
+fi

+ 1
- 1
wallaby.js ファイルの表示

@@ -1,4 +1,4 @@
1
-const babelOptions = require('./package.json').babel.env.test;
1
+const babelOptions = require('./babel.config')().env.test;
2 2
 
3 3
 module.exports = function (wallaby) {
4 4
   return {