Browse Source

Road to implicit any part5 (#4500)

- `registerCommandListener` documentation was wrong so that is fixed
- use `uniqueIdProvider` much as possible so we are not duplicating logic
- add `appRegistryService` which makes `componentRegistry` cleaner and also makes testing easier
- type return type of `NativeEventsReceiver.ts` correctly
- add types to `LayoutTreeParser`
- `ComponentRegistry.test.tsx` refactor so it tests only things that is should and not implementation of React Native functions
- fix type `center` prop to be required on `LayoutSideMenu`
- add missing layout props `topTabs` and `externalComponent`
- lots of minor cleaning
Henrik Raitasola 5 years ago
parent
commit
ee6dc78802

+ 8
- 8
docs/docs/events.md View File

27
   componentDidMount() {
27
   componentDidMount() {
28
     this.navigationEventListener = Navigation.events().bindComponent(this);
28
     this.navigationEventListener = Navigation.events().bindComponent(this);
29
   }
29
   }
30
-  
30
+
31
   componentWillUnmount() {
31
   componentWillUnmount() {
32
     // Not mandatory
32
     // Not mandatory
33
     if (this.navigationEventListener) {
33
     if (this.navigationEventListener) {
34
       this.navigationEventListener.remove();
34
       this.navigationEventListener.remove();
35
     }
35
     }
36
   }
36
   }
37
-  
37
+
38
   componentDidAppear() {
38
   componentDidAppear() {
39
 
39
 
40
   }
40
   }
66
   componentDidMount() {
66
   componentDidMount() {
67
     this.navigationEventListener = Navigation.events().bindComponent(this);
67
     this.navigationEventListener = Navigation.events().bindComponent(this);
68
   }
68
   }
69
-  
69
+
70
   componentWillUnmount() {
70
   componentWillUnmount() {
71
     // Not mandatory
71
     // Not mandatory
72
     if (this.navigationEventListener) {
72
     if (this.navigationEventListener) {
101
 
101
 
102
 ```js
102
 ```js
103
 // Subscribe
103
 // Subscribe
104
-const commandListener = Navigation.events().registerCommandListener(({ name, params }) => {
104
+const commandListener = Navigation.events().registerCommandListener((name, params) => {
105
 
105
 
106
 });
106
 });
107
 ...
107
 ...
158
   componentDidMount() {
158
   componentDidMount() {
159
     this.navigationEventListener = Navigation.events().bindComponent(this);
159
     this.navigationEventListener = Navigation.events().bindComponent(this);
160
   }
160
   }
161
-  
161
+
162
   componentWillUnmount() {
162
   componentWillUnmount() {
163
     // Not mandatory
163
     // Not mandatory
164
     if (this.navigationEventListener) {
164
     if (this.navigationEventListener) {
197
   componentDidMount() {
197
   componentDidMount() {
198
     this.navigationEventListener = Navigation.events().bindComponent(this);
198
     this.navigationEventListener = Navigation.events().bindComponent(this);
199
   }
199
   }
200
-  
200
+
201
   componentWillUnmount() {
201
   componentWillUnmount() {
202
     // Not mandatory
202
     // Not mandatory
203
     if (this.navigationEventListener) {
203
     if (this.navigationEventListener) {
220
   componentDidMount() {
220
   componentDidMount() {
221
     this.navigationEventListener = Navigation.events().bindComponent(this);
221
     this.navigationEventListener = Navigation.events().bindComponent(this);
222
   }
222
   }
223
-  
223
+
224
   componentWillUnmount() {
224
   componentWillUnmount() {
225
     // Not mandatory
225
     // Not mandatory
226
     if (this.navigationEventListener) {
226
     if (this.navigationEventListener) {
243
   componentDidMount() {
243
   componentDidMount() {
244
     this.navigationEventListener = Navigation.events().bindComponent(this);
244
     this.navigationEventListener = Navigation.events().bindComponent(this);
245
   }
245
   }
246
-  
246
+
247
   componentWillUnmount() {
247
   componentWillUnmount() {
248
     // Not mandatory
248
     // Not mandatory
249
     if (this.navigationEventListener) {
249
     if (this.navigationEventListener) {

+ 11
- 4
lib/src/Navigation.ts View File

20
 import { OptionsProcessor } from './commands/OptionsProcessor';
20
 import { OptionsProcessor } from './commands/OptionsProcessor';
21
 import { ColorService } from './adapters/ColorService';
21
 import { ColorService } from './adapters/ColorService';
22
 import { AssetService } from './adapters/AssetResolver';
22
 import { AssetService } from './adapters/AssetResolver';
23
+import { AppRegistryService } from './adapters/AppRegistryService';
23
 
24
 
24
 export class NavigationRoot {
25
 export class NavigationRoot {
25
   public readonly Element: React.ComponentType<{ elementId: any; resizeMode?: any; }>;
26
   public readonly Element: React.ComponentType<{ elementId: any; resizeMode?: any; }>;
45
     this.nativeEventsReceiver = new NativeEventsReceiver();
46
     this.nativeEventsReceiver = new NativeEventsReceiver();
46
     this.uniqueIdProvider = new UniqueIdProvider();
47
     this.uniqueIdProvider = new UniqueIdProvider();
47
     this.componentEventsObserver = new ComponentEventsObserver(this.nativeEventsReceiver);
48
     this.componentEventsObserver = new ComponentEventsObserver(this.nativeEventsReceiver);
48
-    this.componentRegistry = new ComponentRegistry(this.store, this.componentEventsObserver);
49
+    const appRegistryService = new AppRegistryService();
50
+    this.componentRegistry = new ComponentRegistry(
51
+      this.store,
52
+      this.componentEventsObserver,
53
+      this.componentWrapper,
54
+      appRegistryService
55
+    );
49
     this.layoutTreeParser = new LayoutTreeParser();
56
     this.layoutTreeParser = new LayoutTreeParser();
50
     const optionsProcessor = new OptionsProcessor(this.store, this.uniqueIdProvider, new ColorService(), new AssetService());
57
     const optionsProcessor = new OptionsProcessor(this.store, this.uniqueIdProvider, new ColorService(), new AssetService());
51
     this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store, optionsProcessor);
58
     this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store, optionsProcessor);
52
     this.nativeCommandsSender = new NativeCommandsSender();
59
     this.nativeCommandsSender = new NativeCommandsSender();
53
-    this.commandsObserver = new CommandsObserver();
60
+    this.commandsObserver = new CommandsObserver(this.uniqueIdProvider);
54
     this.commands = new Commands(
61
     this.commands = new Commands(
55
       this.nativeCommandsSender,
62
       this.nativeCommandsSender,
56
       this.layoutTreeParser,
63
       this.layoutTreeParser,
69
    * The component itself is a traditional React component extending React.Component.
76
    * The component itself is a traditional React component extending React.Component.
70
    */
77
    */
71
   public registerComponent(componentName: string | number, componentProvider: ComponentProvider, concreteComponentProvider?: ComponentProvider): ComponentProvider {
78
   public registerComponent(componentName: string | number, componentProvider: ComponentProvider, concreteComponentProvider?: ComponentProvider): ComponentProvider {
72
-    return this.componentRegistry.registerComponent(componentName, componentProvider, this.componentWrapper, concreteComponentProvider);
79
+    return this.componentRegistry.registerComponent(componentName, componentProvider, concreteComponentProvider);
73
   }
80
   }
74
 
81
 
75
   /**
82
   /**
82
     ReduxProvider: any,
89
     ReduxProvider: any,
83
     reduxStore: any
90
     reduxStore: any
84
   ): ComponentProvider {
91
   ): ComponentProvider {
85
-    return this.componentRegistry.registerComponent(componentName, getComponentClassFunc, this.componentWrapper, undefined, ReduxProvider, reduxStore);
92
+    return this.componentRegistry.registerComponent(componentName, getComponentClassFunc, undefined, ReduxProvider, reduxStore);
86
   }
93
   }
87
 
94
 
88
   /**
95
   /**

+ 7
- 0
lib/src/adapters/AppRegistryService.ts View File

1
+import { ComponentProvider, AppRegistry } from 'react-native';
2
+
3
+export class AppRegistryService {
4
+  registerComponent(appKey: string, getComponentFunc: ComponentProvider) {
5
+    AppRegistry.registerComponent(appKey, getComponentFunc);
6
+  }
7
+}

+ 3
- 7
lib/src/adapters/Element.tsx View File

2
 import * as PropTypes from 'prop-types';
2
 import * as PropTypes from 'prop-types';
3
 import { requireNativeComponent } from 'react-native';
3
 import { requireNativeComponent } from 'react-native';
4
 
4
 
5
-let RNNElement: React.ComponentType<any>;
6
-
7
-export class Element extends React.Component<{ elementId: any; resizeMode?: any }> {
5
+export class Element extends React.Component<{ elementId: string; resizeMode?: string }> {
8
   static propTypes = {
6
   static propTypes = {
9
     elementId: PropTypes.string.isRequired,
7
     elementId: PropTypes.string.isRequired,
10
     resizeMode: PropTypes.string
8
     resizeMode: PropTypes.string
19
   }
17
   }
20
 }
18
 }
21
 
19
 
22
-RNNElement = requireNativeComponent('RNNElement', Element, {
23
-  nativeOnly: {
24
-    nativeID: true
25
-  }
20
+const RNNElement = requireNativeComponent('RNNElement', Element, {
21
+  nativeOnly: { nativeID: true }
26
 });
22
 });

+ 13
- 14
lib/src/adapters/NativeEventsReceiver.ts View File

1
-import { NativeModules, NativeEventEmitter } from 'react-native';
2
-import { EventSubscription } from '../interfaces/EventSubscription';
1
+import { NativeModules, NativeEventEmitter, EventEmitter, EmitterSubscription } from 'react-native';
3
 import {
2
 import {
4
   ComponentDidAppearEvent,
3
   ComponentDidAppearEvent,
5
   ComponentDidDisappearEvent,
4
   ComponentDidDisappearEvent,
12
 import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events';
11
 import { CommandCompletedEvent, BottomTabSelectedEvent } from '../interfaces/Events';
13
 
12
 
14
 export class NativeEventsReceiver {
13
 export class NativeEventsReceiver {
15
-  private emitter: { addListener(event: string, callback: any): EventSubscription };
14
+  private emitter: EventEmitter;
16
   constructor() {
15
   constructor() {
17
     // NOTE: This try catch is workaround for integration tests
16
     // NOTE: This try catch is workaround for integration tests
18
     // TODO: mock NativeEventEmitter in integration tests rather done adding try catch in source code
17
     // TODO: mock NativeEventEmitter in integration tests rather done adding try catch in source code
25
             remove: () => undefined
24
             remove: () => undefined
26
           };
25
           };
27
         }
26
         }
28
-      };
27
+      } as any as EventEmitter;
29
     }
28
     }
30
   }
29
   }
31
 
30
 
32
-  public registerAppLaunchedListener(callback: () => void): EventSubscription {
31
+  public registerAppLaunchedListener(callback: () => void): EmitterSubscription {
33
     return this.emitter.addListener('RNN.AppLaunched', callback);
32
     return this.emitter.addListener('RNN.AppLaunched', callback);
34
   }
33
   }
35
 
34
 
36
-  public registerComponentDidAppearListener(callback: (event: ComponentDidAppearEvent) => void): EventSubscription {
35
+  public registerComponentDidAppearListener(callback: (event: ComponentDidAppearEvent) => void): EmitterSubscription {
37
     return this.emitter.addListener('RNN.ComponentDidAppear', callback);
36
     return this.emitter.addListener('RNN.ComponentDidAppear', callback);
38
   }
37
   }
39
 
38
 
40
-  public registerComponentDidDisappearListener(callback: (event: ComponentDidDisappearEvent) => void): EventSubscription {
39
+  public registerComponentDidDisappearListener(callback: (event: ComponentDidDisappearEvent) => void): EmitterSubscription {
41
     return this.emitter.addListener('RNN.ComponentDidDisappear', callback);
40
     return this.emitter.addListener('RNN.ComponentDidDisappear', callback);
42
   }
41
   }
43
 
42
 
44
-  public registerNavigationButtonPressedListener(callback: (event: NavigationButtonPressedEvent) => void): EventSubscription {
43
+  public registerNavigationButtonPressedListener(callback: (event: NavigationButtonPressedEvent) => void): EmitterSubscription {
45
     return this.emitter.addListener('RNN.NavigationButtonPressed', callback);
44
     return this.emitter.addListener('RNN.NavigationButtonPressed', callback);
46
   }
45
   }
47
 
46
 
48
-  public registerModalDismissedListener(callback: (event: ModalDismissedEvent) => void): EventSubscription {
47
+  public registerModalDismissedListener(callback: (event: ModalDismissedEvent) => void): EmitterSubscription {
49
     return this.emitter.addListener('RNN.ModalDismissed', callback);
48
     return this.emitter.addListener('RNN.ModalDismissed', callback);
50
   }
49
   }
51
 
50
 
52
-  public registerSearchBarUpdatedListener(callback: (event: SearchBarUpdatedEvent) => void): EventSubscription {
51
+  public registerSearchBarUpdatedListener(callback: (event: SearchBarUpdatedEvent) => void): EmitterSubscription {
53
     return this.emitter.addListener('RNN.SearchBarUpdated', callback);
52
     return this.emitter.addListener('RNN.SearchBarUpdated', callback);
54
   }
53
   }
55
 
54
 
56
-  public registerSearchBarCancelPressedListener(callback: (event: SearchBarCancelPressedEvent) => void): EventSubscription {
55
+  public registerSearchBarCancelPressedListener(callback: (event: SearchBarCancelPressedEvent) => void): EmitterSubscription {
57
     return this.emitter.addListener('RNN.SearchBarCancelPressed', callback);
56
     return this.emitter.addListener('RNN.SearchBarCancelPressed', callback);
58
   }
57
   }
59
 
58
 
60
-  public registerPreviewCompletedListener(callback: (event: PreviewCompletedEvent) => void): EventSubscription {
59
+  public registerPreviewCompletedListener(callback: (event: PreviewCompletedEvent) => void): EmitterSubscription {
61
     return this.emitter.addListener('RNN.PreviewCompleted', callback);
60
     return this.emitter.addListener('RNN.PreviewCompleted', callback);
62
   }
61
   }
63
 
62
 
64
-  public registerCommandCompletedListener(callback: (data: CommandCompletedEvent) => void): EventSubscription {
63
+  public registerCommandCompletedListener(callback: (data: CommandCompletedEvent) => void): EmitterSubscription {
65
     return this.emitter.addListener('RNN.CommandCompleted', callback);
64
     return this.emitter.addListener('RNN.CommandCompleted', callback);
66
   }
65
   }
67
 
66
 
68
-  public registerBottomTabSelectedListener(callback: (data: BottomTabSelectedEvent) => void): EventSubscription {
67
+  public registerBottomTabSelectedListener(callback: (data: BottomTabSelectedEvent) => void): EmitterSubscription {
69
     return this.emitter.addListener('RNN.BottomTabSelected', callback);
68
     return this.emitter.addListener('RNN.BottomTabSelected', callback);
70
   }
69
   }
71
 }
70
 }

+ 1
- 1
lib/src/adapters/UniqueIdProvider.ts View File

1
 import * as _ from 'lodash';
1
 import * as _ from 'lodash';
2
 
2
 
3
 export class UniqueIdProvider {
3
 export class UniqueIdProvider {
4
-  generate(prefix: string): string {
4
+  generate(prefix?: string): string {
5
     return _.uniqueId(prefix);
5
     return _.uniqueId(prefix);
6
   }
6
   }
7
 }
7
 }

+ 1
- 1
lib/src/commands/Commands.test.ts View File

19
 
19
 
20
   beforeEach(() => {
20
   beforeEach(() => {
21
     store = new Store();
21
     store = new Store();
22
-    commandsObserver = new CommandsObserver();
22
+    commandsObserver = new CommandsObserver(new UniqueIdProvider());
23
     mockedNativeCommandsSender = mock(NativeCommandsSender);
23
     mockedNativeCommandsSender = mock(NativeCommandsSender);
24
     nativeCommandsSender = instance(mockedNativeCommandsSender);
24
     nativeCommandsSender = instance(mockedNativeCommandsSender);
25
 
25
 

+ 6
- 6
lib/src/commands/Commands.ts View File

57
     this.commandsObserver.notify('mergeOptions', { componentId, options });
57
     this.commandsObserver.notify('mergeOptions', { componentId, options });
58
   }
58
   }
59
 
59
 
60
-  public showModal(simpleApi: Layout) {
61
-    const input = _.cloneDeep(simpleApi);
62
-    const layout = this.layoutTreeParser.parse(input);
63
-    this.layoutTreeCrawler.crawl(layout);
60
+  public showModal(layout: Layout) {
61
+    const layoutCloned = _.cloneDeep(layout);
62
+    const layoutNode = this.layoutTreeParser.parse(layoutCloned);
63
+    this.layoutTreeCrawler.crawl(layoutNode);
64
 
64
 
65
     const commandId = this.uniqueIdProvider.generate('showModal');
65
     const commandId = this.uniqueIdProvider.generate('showModal');
66
-    const result = this.nativeCommandsSender.showModal(commandId, layout);
67
-    this.commandsObserver.notify('showModal', { commandId, layout });
66
+    const result = this.nativeCommandsSender.showModal(commandId, layoutNode);
67
+    this.commandsObserver.notify('showModal', { commandId, layout: layoutNode });
68
     return result;
68
     return result;
69
   }
69
   }
70
 
70
 

+ 15
- 41
lib/src/commands/LayoutTreeParser.test.ts View File

1
 import * as  _ from 'lodash';
1
 import * as  _ from 'lodash';
2
 import { LayoutTreeParser } from './LayoutTreeParser';
2
 import { LayoutTreeParser } from './LayoutTreeParser';
3
 import { LayoutType } from './LayoutType';
3
 import { LayoutType } from './LayoutType';
4
+import { Layout } from '../interfaces/Layout';
5
+import { OptionsSplitView } from '../interfaces/Options';
4
 
6
 
5
 describe('LayoutTreeParser', () => {
7
 describe('LayoutTreeParser', () => {
6
   let uut: LayoutTreeParser;
8
   let uut: LayoutTreeParser;
11
 
13
 
12
   describe('parses into { type, data, children }', () => {
14
   describe('parses into { type, data, children }', () => {
13
     it('unknown type', () => {
15
     it('unknown type', () => {
14
-      expect(() => uut.parse({ wut: {} })).toThrowError('unknown LayoutType "wut"');
16
+      expect(() => uut.parse({ wut: {} } as Layout)).toThrowError('unknown LayoutType "wut"');
15
     });
17
     });
16
 
18
 
17
     it('single component', () => {
19
     it('single component', () => {
43
         children: []
45
         children: []
44
       });
46
       });
45
       expect(result.data.passProps).toBe(LayoutExamples.passProps);
47
       expect(result.data.passProps).toBe(LayoutExamples.passProps);
46
-      expect(result.data.passProps.fnProp()).toEqual('Hello from a function');
47
     });
48
     });
48
 
49
 
49
     it('stack of components with top bar', () => {
50
     it('stack of components with top bar', () => {
97
       expect(result.children[2].children[0].type).toEqual(LayoutType.Component);
98
       expect(result.children[2].children[0].type).toEqual(LayoutType.Component);
98
     });
99
     });
99
 
100
 
100
-    it('side menu center is require', () => {
101
-      expect(() => uut.parse({ sideMenu: {} })).toThrowError('sideMenu.center is required');
102
-    });
103
-
104
     it('top tabs', () => {
101
     it('top tabs', () => {
105
       const result = uut.parse(LayoutExamples.topTabs);
102
       const result = uut.parse(LayoutExamples.topTabs);
106
       expect(_.keys(result)).toEqual(['id', 'type', 'data', 'children']);
103
       expect(_.keys(result)).toEqual(['id', 'type', 'data', 'children']);
121
       expect(result.children[1].type).toEqual('SideMenuCenter');
118
       expect(result.children[1].type).toEqual('SideMenuCenter');
122
       expect(result.children[1].children[0].type).toEqual('BottomTabs');
119
       expect(result.children[1].children[0].type).toEqual('BottomTabs');
123
       expect(result.children[1].children[0].children[2].type).toEqual('Stack');
120
       expect(result.children[1].children[0].children[2].type).toEqual('Stack');
124
-      expect(result.children[1].children[0].children[2].children[0].type).toEqual('TopTabs');
125
-      expect(result.children[1].children[0].children[2].children[0].children[2].type).toEqual('TopTabs');
126
-      expect(result.children[1].children[0].children[2].children[0].children[2].children[4].type).toEqual('Stack');
127
-      expect(result.children[1].children[0].children[2].children[0].children[2].data).toEqual({ options: { topBar: { title: { text: 'Hello1'} } } });
128
     });
121
     });
129
 
122
 
130
     it('split view', () => {
123
     it('split view', () => {
131
       const result = uut.parse(LayoutExamples.splitView);
124
       const result = uut.parse(LayoutExamples.splitView);
132
-      const master = uut.parse(LayoutExamples.splitView.splitView.master);
133
-      const detail = uut.parse(LayoutExamples.splitView.splitView.detail);
125
+      const master = uut.parse(LayoutExamples.splitView.splitView!.master!);
126
+      const detail = uut.parse(LayoutExamples.splitView.splitView!.detail!);
134
 
127
 
135
       expect(result.type).toEqual('SplitView');
128
       expect(result.type).toEqual('SplitView');
136
       expect(result.children[0]).toEqual(master);
129
       expect(result.children[0]).toEqual(master);
139
   });
132
   });
140
 
133
 
141
   it('options for all containing types', () => {
134
   it('options for all containing types', () => {
142
-    expect(uut.parse({ component: { options } }).data.options).toBe(options);
135
+    expect(uut.parse({ component: { name: 'lol', options } }).data.options).toBe(options);
143
     expect(uut.parse({ stack: { options } }).data.options).toBe(options);
136
     expect(uut.parse({ stack: { options } }).data.options).toBe(options);
144
     expect(uut.parse({ bottomTabs: { options } }).data.options).toBe(options);
137
     expect(uut.parse({ bottomTabs: { options } }).data.options).toBe(options);
145
     expect(uut.parse({ topTabs: { options } }).data.options).toBe(options);
138
     expect(uut.parse({ topTabs: { options } }).data.options).toBe(options);
146
-    expect(uut.parse({ sideMenu: { options, center: { component: {} } } }).data.options).toBe(options);
139
+    expect(uut.parse({ sideMenu: { options, center: { component: {name: 'lool'} } } }).data.options).toBe(options);
147
     expect(uut.parse(LayoutExamples.splitView).data.options).toBe(optionsSplitView);
140
     expect(uut.parse(LayoutExamples.splitView).data.options).toBe(optionsSplitView);
148
   });
141
   });
149
 
142
 
150
   it('pass user provided id as is', () => {
143
   it('pass user provided id as is', () => {
151
-    const component = { id: 'compId' };
144
+    const component = { id: 'compId', name: 'loool' };
152
     expect(uut.parse({ component }).id).toEqual('compId');
145
     expect(uut.parse({ component }).id).toEqual('compId');
153
     expect(uut.parse({ stack: { id: 'stackId' } }).id).toEqual('stackId');
146
     expect(uut.parse({ stack: { id: 'stackId' } }).id).toEqual('stackId');
154
     expect(uut.parse({ stack: { children: [{ component }] } }).children[0].id).toEqual('compId');
147
     expect(uut.parse({ stack: { children: [{ component }] } }).children[0].id).toEqual('compId');
157
     expect(uut.parse({ topTabs: { id: 'myId' } }).id).toEqual('myId');
150
     expect(uut.parse({ topTabs: { id: 'myId' } }).id).toEqual('myId');
158
     expect(uut.parse({ topTabs: { children: [{ component }] } }).children[0].id).toEqual('compId');
151
     expect(uut.parse({ topTabs: { children: [{ component }] } }).children[0].id).toEqual('compId');
159
     expect(uut.parse({ sideMenu: { id: 'myId', center: { component } } }).id).toEqual('myId');
152
     expect(uut.parse({ sideMenu: { id: 'myId', center: { component } } }).id).toEqual('myId');
160
-    expect(uut.parse({ sideMenu: { center: { id: 'myId', component } } }).children[0].id).toEqual('myId');
161
-    expect(uut.parse({ sideMenu: { center: { component }, left: { id: 'theId', component } } }).children[0].id).toEqual('theId');
162
-    expect(uut.parse({ sideMenu: { center: { component }, right: { id: 'theId', component } } }).children[1].id).toEqual('theId');
163
   });
153
   });
164
 });
154
 });
165
 
155
 
180
   }
170
   }
181
 };
171
 };
182
 
172
 
183
-const optionsSplitView = {
173
+const optionsSplitView: OptionsSplitView = {
184
   displayMode: 'auto',
174
   displayMode: 'auto',
185
   primaryEdge: 'leading',
175
   primaryEdge: 'leading',
186
   minWidth: 150,
176
   minWidth: 150,
257
   }
247
   }
258
 };
248
 };
259
 
249
 
260
-const complexLayout = {
250
+const complexLayout: Layout = {
261
   sideMenu: {
251
   sideMenu: {
262
     left: singleComponent,
252
     left: singleComponent,
263
     center: {
253
     center: {
268
           {
258
           {
269
             stack: {
259
             stack: {
270
               children: [
260
               children: [
271
-                {
272
-                  topTabs: {
273
-                    children: [
274
-                      stackWithTopBar,
275
-                      stackWithTopBar,
276
-                      {
277
-                        topTabs: {
278
-                          options,
279
-                          children: [
280
-                            singleComponent,
281
-                            singleComponent,
282
-                            singleComponent,
283
-                            singleComponent,
284
-                            stackWithTopBar
285
-                          ]
286
-                        }
287
-                      }
288
-                    ]
289
-                  }
290
-                }
261
+                singleComponent,
262
+                singleComponent,
263
+                singleComponent,
264
+                singleComponent,
291
               ]
265
               ]
292
             }
266
             }
293
           }
267
           }
297
   }
271
   }
298
 };
272
 };
299
 
273
 
300
-const splitView = {
274
+const splitView: Layout = {
301
   splitView: {
275
   splitView: {
302
     master: {
276
     master: {
303
       stack: {
277
       stack: {

+ 38
- 41
lib/src/commands/LayoutTreeParser.ts View File

1
 import * as _ from 'lodash';
1
 import * as _ from 'lodash';
2
 import { LayoutType } from './LayoutType';
2
 import { LayoutType } from './LayoutType';
3
 import { LayoutNode } from './LayoutTreeCrawler';
3
 import { LayoutNode } from './LayoutTreeCrawler';
4
+import {
5
+  Layout,
6
+  TopTabs,
7
+  LayoutComponent,
8
+  LayoutStack,
9
+  LayoutBottomTabs,
10
+  LayoutSideMenu,
11
+  LayoutSplitView,
12
+  ExternalComponent
13
+} from '../interfaces/Layout';
4
 
14
 
5
 export class LayoutTreeParser {
15
 export class LayoutTreeParser {
6
   constructor() {
16
   constructor() {
7
     this.parse = this.parse.bind(this);
17
     this.parse = this.parse.bind(this);
8
   }
18
   }
9
 
19
 
10
-  parse(api): LayoutNode {
20
+  public parse(api: Layout): LayoutNode {
11
     if (api.topTabs) {
21
     if (api.topTabs) {
12
-      return this._topTabs(api.topTabs);
22
+      return this.topTabs(api.topTabs);
13
     } else if (api.sideMenu) {
23
     } else if (api.sideMenu) {
14
-      return this._sideMenu(api.sideMenu);
24
+      return this.sideMenu(api.sideMenu);
15
     } else if (api.bottomTabs) {
25
     } else if (api.bottomTabs) {
16
-      return this._bottomTabs(api.bottomTabs);
26
+      return this.bottomTabs(api.bottomTabs);
17
     } else if (api.stack) {
27
     } else if (api.stack) {
18
-      return this._stack(api.stack);
28
+      return this.stack(api.stack);
19
     } else if (api.component) {
29
     } else if (api.component) {
20
-      return this._component(api.component);
30
+      return this.component(api.component);
21
     } else if (api.externalComponent) {
31
     } else if (api.externalComponent) {
22
-      return this._externalComponent(api.externalComponent);
32
+      return this.externalComponent(api.externalComponent);
23
     } else if (api.splitView) {
33
     } else if (api.splitView) {
24
-      return this._splitView(api.splitView);
34
+      return this.splitView(api.splitView);
25
     }
35
     }
26
     throw new Error(`unknown LayoutType "${_.keys(api)}"`);
36
     throw new Error(`unknown LayoutType "${_.keys(api)}"`);
27
   }
37
   }
28
 
38
 
29
-  _topTabs(api): LayoutNode {
39
+  private topTabs(api: TopTabs): LayoutNode {
30
     return {
40
     return {
31
       id: api.id,
41
       id: api.id,
32
       type: LayoutType.TopTabs,
42
       type: LayoutType.TopTabs,
33
       data: { options: api.options },
43
       data: { options: api.options },
34
-      children: _.map(api.children, this.parse)
44
+      children: api.children ? api.children.map(this.parse) : []
35
     };
45
     };
36
   }
46
   }
37
 
47
 
38
-  _sideMenu(api): LayoutNode {
48
+  private sideMenu(api: LayoutSideMenu): LayoutNode {
39
     return {
49
     return {
40
       id: api.id,
50
       id: api.id,
41
       type: LayoutType.SideMenuRoot,
51
       type: LayoutType.SideMenuRoot,
42
       data: { options: api.options },
52
       data: { options: api.options },
43
-      children: this._sideMenuChildren(api)
53
+      children: this.sideMenuChildren(api)
44
     };
54
     };
45
   }
55
   }
46
 
56
 
47
-  _sideMenuChildren(api): LayoutNode[] {
48
-    if (!api.center) {
49
-      throw new Error(`sideMenu.center is required`);
50
-    }
57
+  private sideMenuChildren(api: LayoutSideMenu): LayoutNode[] {
51
     const children: LayoutNode[] = [];
58
     const children: LayoutNode[] = [];
52
     if (api.left) {
59
     if (api.left) {
53
-      children.push({
54
-        id: api.left.id,
55
-        type: LayoutType.SideMenuLeft,
56
-        data: {},
57
-        children: [this.parse(api.left)]
58
-      });
60
+      children.push({ type: LayoutType.SideMenuLeft, data: {}, children: [this.parse(api.left)] });
59
     }
61
     }
60
     children.push({
62
     children.push({
61
-      id: api.center.id,
62
       type: LayoutType.SideMenuCenter,
63
       type: LayoutType.SideMenuCenter,
63
       data: {},
64
       data: {},
64
       children: [this.parse(api.center)]
65
       children: [this.parse(api.center)]
65
     });
66
     });
66
     if (api.right) {
67
     if (api.right) {
67
       children.push({
68
       children.push({
68
-        id: api.right.id,
69
         type: LayoutType.SideMenuRight,
69
         type: LayoutType.SideMenuRight,
70
         data: {},
70
         data: {},
71
         children: [this.parse(api.right)]
71
         children: [this.parse(api.right)]
74
     return children;
74
     return children;
75
   }
75
   }
76
 
76
 
77
-  _bottomTabs(api): LayoutNode {
77
+  private bottomTabs(api: LayoutBottomTabs): LayoutNode {
78
     return {
78
     return {
79
       id: api.id,
79
       id: api.id,
80
       type: LayoutType.BottomTabs,
80
       type: LayoutType.BottomTabs,
81
       data: { options: api.options },
81
       data: { options: api.options },
82
-      children: _.map(api.children, this.parse)
82
+      children: api.children ? api.children.map(this.parse) : []
83
     };
83
     };
84
   }
84
   }
85
 
85
 
86
-  _stack(api): LayoutNode {
86
+  private stack(api: LayoutStack): LayoutNode {
87
     return {
87
     return {
88
       id: api.id,
88
       id: api.id,
89
       type: LayoutType.Stack,
89
       type: LayoutType.Stack,
90
-      data: { name: api.name, options: api.options },
91
-      children: _.map(api.children, this.parse)
90
+      data: { options: api.options },
91
+      children: api.children ? api.children.map(this.parse) : []
92
     };
92
     };
93
   }
93
   }
94
 
94
 
95
-  _component(api): LayoutNode {
95
+  private component(api: LayoutComponent): LayoutNode {
96
     return {
96
     return {
97
       id: api.id,
97
       id: api.id,
98
       type: LayoutType.Component,
98
       type: LayoutType.Component,
99
-      data: { name: api.name, options: api.options, passProps: api.passProps },
99
+      data: { name: api.name.toString(), options: api.options, passProps: api.passProps },
100
       children: []
100
       children: []
101
     };
101
     };
102
   }
102
   }
103
 
103
 
104
-  _externalComponent(api): LayoutNode {
104
+  private externalComponent(api: ExternalComponent): LayoutNode {
105
     return {
105
     return {
106
       id: api.id,
106
       id: api.id,
107
       type: LayoutType.ExternalComponent,
107
       type: LayoutType.ExternalComponent,
108
-      data: { name: api.name, options: api.options, passProps: api.passProps },
108
+      data: { name: api.name.toString(), options: api.options, passProps: api.passProps },
109
       children: []
109
       children: []
110
     };
110
     };
111
   }
111
   }
112
 
112
 
113
-  _splitView(api): LayoutNode {
114
-    const master = this.parse(api.master);
115
-    const detail = this.parse(api.detail);
113
+  private splitView(api: LayoutSplitView): LayoutNode {
114
+    const master = api.master ? this.parse(api.master) : undefined;
115
+    const detail = api.detail ? this.parse(api.detail) : undefined;
116
 
116
 
117
     return {
117
     return {
118
       id: api.id,
118
       id: api.id,
119
       type: LayoutType.SplitView,
119
       type: LayoutType.SplitView,
120
-      data: { name: api.name, options: api.options },
121
-      children: [
122
-        master,
123
-        detail,
124
-      ],
120
+      data: { options: api.options },
121
+      children: master && detail ? [master, detail] : []
125
     };
122
     };
126
   }
123
   }
127
 }
124
 }

+ 30
- 49
lib/src/components/ComponentRegistry.test.tsx View File

1
-import * as React from 'react';
2
-import { AppRegistry, Text } from 'react-native';
3
-import * as renderer from 'react-test-renderer';
4
 import { ComponentRegistry } from './ComponentRegistry';
1
 import { ComponentRegistry } from './ComponentRegistry';
5
 import { Store } from './Store';
2
 import { Store } from './Store';
3
+import { mock, instance, verify, anyFunction } from 'ts-mockito';
4
+import { ComponentWrapper } from './ComponentWrapper';
5
+import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
6
+import { AppRegistryService } from '../adapters/AppRegistryService';
6
 
7
 
7
-describe('ComponentRegistry', () => {
8
-  let uut;
9
-  let store;
10
-  let mockRegistry: any;
11
-  let mockWrapper: any;
12
-
8
+const DummyComponent = () => null;
13
 
9
 
14
-  class WrappedComponent extends React.Component {
15
-    render() {
16
-      return (
17
-        <Text>
18
-          {
19
-            'Hello, World!'
20
-          }
21
-        </Text>);
22
-    }
23
-  }
10
+describe('ComponentRegistry', () => {
11
+  let mockedStore: Store;
12
+  let mockedComponentEventsObserver: ComponentEventsObserver;
13
+  let mockedComponentWrapper: ComponentWrapper;
14
+  let mockedAppRegistryService: AppRegistryService;
15
+  let uut: ComponentRegistry;
24
 
16
 
25
   beforeEach(() => {
17
   beforeEach(() => {
26
-    store = new Store();
27
-    mockRegistry = AppRegistry.registerComponent = jest.fn(AppRegistry.registerComponent);
28
-    mockWrapper = jest.mock('./ComponentWrapper');
29
-    mockWrapper.wrap = () => WrappedComponent;
30
-    uut = new ComponentRegistry(store, {} as any);
18
+    mockedStore = mock(Store);
19
+    mockedComponentEventsObserver = mock(ComponentEventsObserver);
20
+    mockedComponentWrapper = mock(ComponentWrapper);
21
+    mockedAppRegistryService = mock(AppRegistryService);
22
+
23
+    uut = new ComponentRegistry(
24
+      instance(mockedStore),
25
+      instance(mockedComponentEventsObserver),
26
+      instance(mockedComponentWrapper),
27
+      instance(mockedAppRegistryService)
28
+    );
31
   });
29
   });
32
 
30
 
33
   it('registers component by componentName into AppRegistry', () => {
31
   it('registers component by componentName into AppRegistry', () => {
34
-    expect(mockRegistry).not.toHaveBeenCalled();
35
-    const result = uut.registerComponent('example.MyComponent.name', () => {}, mockWrapper);
36
-    expect(mockRegistry).toHaveBeenCalledTimes(1);
37
-    expect(mockRegistry.mock.calls[0][0]).toEqual('example.MyComponent.name');
38
-    expect(mockRegistry.mock.calls[0][1]()).toEqual(result());
32
+    uut.registerComponent('example.MyComponent.name', () => DummyComponent);
33
+    verify(
34
+      mockedAppRegistryService.registerComponent('example.MyComponent.name', anyFunction())
35
+    ).called();
39
   });
36
   });
40
 
37
 
41
   it('saves the wrapper component generator to the store', () => {
38
   it('saves the wrapper component generator to the store', () => {
42
-    expect(store.getComponentClassForName('example.MyComponent.name')).toBeUndefined();
43
-    uut.registerComponent('example.MyComponent.name', () => {}, mockWrapper);
44
-    const Class = store.getComponentClassForName('example.MyComponent.name');
45
-    expect(Class).not.toBeUndefined();
46
-    expect(Class()).toEqual(WrappedComponent);
47
-  });
48
-
49
-  it('resulting in a normal component', () => {
50
-    uut.registerComponent('example.MyComponent.name', () => {}, mockWrapper);
51
-    const Component = mockRegistry.mock.calls[0][1]();
52
-    const tree = renderer.create(<Component componentId='123' />);
53
-    expect(tree.toJSON()!.children).toEqual(['Hello, World!']);
39
+    uut.registerComponent('example.MyComponent.name', () => DummyComponent);
40
+    verify(
41
+      mockedStore.setComponentClassForName('example.MyComponent.name', anyFunction())
42
+    ).called();
54
   });
43
   });
55
 
44
 
56
   it('should not invoke generator', () => {
45
   it('should not invoke generator', () => {
58
     uut.registerComponent('example.MyComponent.name', generator);
47
     uut.registerComponent('example.MyComponent.name', generator);
59
     expect(generator).toHaveBeenCalledTimes(0);
48
     expect(generator).toHaveBeenCalledTimes(0);
60
   });
49
   });
61
-
62
-  it('saves wrapped component to store', () => {
63
-    jest.spyOn(store, 'setComponentClassForName');
64
-    const generator = jest.fn(() => {});
65
-    const componentName = 'example.MyComponent.name';
66
-    uut.registerComponent(componentName, generator, mockWrapper);
67
-    expect(store.getComponentClassForName(componentName)()).toEqual(WrappedComponent);
68
-  });
69
 });
50
 });

+ 25
- 10
lib/src/components/ComponentRegistry.ts View File

1
-import { AppRegistry, ComponentProvider } from 'react-native';
1
+import { ComponentProvider } from 'react-native';
2
 import { Store } from './Store';
2
 import { Store } from './Store';
3
 import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
3
 import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
4
 import { ComponentWrapper } from './ComponentWrapper';
4
 import { ComponentWrapper } from './ComponentWrapper';
5
+import { AppRegistryService } from '../adapters/AppRegistryService';
5
 
6
 
6
 export class ComponentRegistry {
7
 export class ComponentRegistry {
7
-  constructor(private readonly store: Store, private readonly componentEventsObserver: ComponentEventsObserver) { }
8
+  constructor(
9
+    private store: Store,
10
+    private componentEventsObserver: ComponentEventsObserver,
11
+    private componentWrapper: ComponentWrapper,
12
+    private appRegistryService: AppRegistryService
13
+  ) {}
8
 
14
 
9
-  registerComponent(componentName: string | number,
10
-                    componentProvider: ComponentProvider,
11
-                    componentWrapper: ComponentWrapper,
12
-                    concreteComponentProvider?: ComponentProvider,
13
-                    ReduxProvider?: any,
14
-                    reduxStore?: any): ComponentProvider {
15
+  registerComponent(
16
+    componentName: string | number,
17
+    componentProvider: ComponentProvider,
18
+    concreteComponentProvider?: ComponentProvider,
19
+    ReduxProvider?: any,
20
+    reduxStore?: any
21
+  ): ComponentProvider {
15
     const NavigationComponent = () => {
22
     const NavigationComponent = () => {
16
-      return componentWrapper.wrap(componentName.toString(), componentProvider, this.store, this.componentEventsObserver, concreteComponentProvider, ReduxProvider, reduxStore);
23
+      return this.componentWrapper.wrap(
24
+        componentName.toString(),
25
+        componentProvider,
26
+        this.store,
27
+        this.componentEventsObserver,
28
+        concreteComponentProvider,
29
+        ReduxProvider,
30
+        reduxStore
31
+      );
17
     };
32
     };
18
     this.store.setComponentClassForName(componentName.toString(), NavigationComponent);
33
     this.store.setComponentClassForName(componentName.toString(), NavigationComponent);
19
-    AppRegistry.registerComponent(componentName.toString(), NavigationComponent);
34
+    this.appRegistryService.registerComponent(componentName.toString(), NavigationComponent);
20
     return NavigationComponent;
35
     return NavigationComponent;
21
   }
36
   }
22
 }
37
 }

+ 1
- 1
lib/src/components/ComponentWrapper.test.tsx View File

145
   });
145
   });
146
 
146
 
147
   it('renders HOC components correctly', () => {
147
   it('renders HOC components correctly', () => {
148
-    const generator = () => (props) => (
148
+    const generator = () => (props: any) => (
149
       <View>
149
       <View>
150
         <MyComponent {...props}/>
150
         <MyComponent {...props}/>
151
       </View>
151
       </View>

+ 6
- 4
lib/src/components/ComponentWrapper.tsx View File

1
 import * as React from 'react';
1
 import * as React from 'react';
2
 import { ComponentProvider } from 'react-native';
2
 import { ComponentProvider } from 'react-native';
3
 import * as  _ from 'lodash';
3
 import * as  _ from 'lodash';
4
-import * as ReactLifecyclesCompat from 'react-lifecycles-compat';
4
+import { polyfill } from 'react-lifecycles-compat';
5
+import hoistNonReactStatics = require('hoist-non-react-statics');
6
+
5
 import { Store } from './Store';
7
 import { Store } from './Store';
6
 import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
8
 import { ComponentEventsObserver } from '../events/ComponentEventsObserver';
7
 
9
 
56
       }
58
       }
57
     }
59
     }
58
 
60
 
59
-    ReactLifecyclesCompat.polyfill(WrappedComponent);
60
-    require('hoist-non-react-statics')(WrappedComponent, concreteComponentProvider());
61
+    polyfill(WrappedComponent);
62
+    hoistNonReactStatics(WrappedComponent, concreteComponentProvider());
61
     return ReduxProvider ? this.wrapWithRedux(WrappedComponent, ReduxProvider, reduxStore) : WrappedComponent;
63
     return ReduxProvider ? this.wrapWithRedux(WrappedComponent, ReduxProvider, reduxStore) : WrappedComponent;
62
   }
64
   }
63
 
65
 
71
         );
73
         );
72
       }
74
       }
73
     }
75
     }
74
-    require('hoist-non-react-statics')(ReduxWrapper, WrappedComponent);
76
+    hoistNonReactStatics(ReduxWrapper, WrappedComponent);
75
     return ReduxWrapper;
77
     return ReduxWrapper;
76
   }
78
   }
77
 }
79
 }

+ 10
- 7
lib/src/events/CommandsObserver.test.ts View File

1
-import { CommandsObserver } from './CommandsObserver';
1
+import { CommandsObserver, CommandsListener } from './CommandsObserver';
2
+import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
2
 
3
 
3
 describe('CommandsObserver', () => {
4
 describe('CommandsObserver', () => {
4
   let uut: CommandsObserver;
5
   let uut: CommandsObserver;
5
-  let cb1;
6
-  let cb2;
6
+  let cb1: CommandsListener;
7
+  let cb2: CommandsListener;
7
 
8
 
8
   beforeEach(() => {
9
   beforeEach(() => {
9
     cb1 = jest.fn();
10
     cb1 = jest.fn();
10
     cb2 = jest.fn();
11
     cb2 = jest.fn();
11
-    uut = new CommandsObserver();
12
+    uut = new CommandsObserver(new UniqueIdProvider());
12
   });
13
   });
13
 
14
 
14
   it('register and notify listener', () => {
15
   it('register and notify listener', () => {
30
   });
31
   });
31
 
32
 
32
   it('remove listener', () => {
33
   it('remove listener', () => {
34
+    expect(cb1).toHaveBeenCalledTimes(0);
35
+    expect(cb2).toHaveBeenCalledTimes(0);
36
+
33
     uut.register(cb1);
37
     uut.register(cb1);
34
-    const result = uut.register(cb2);
35
-    expect(result).toBeDefined();
38
+    const cb2Subscription = uut.register(cb2);
36
 
39
 
37
     uut.notify('commandName', {});
40
     uut.notify('commandName', {});
38
     expect(cb1).toHaveBeenCalledTimes(1);
41
     expect(cb1).toHaveBeenCalledTimes(1);
39
     expect(cb2).toHaveBeenCalledTimes(1);
42
     expect(cb2).toHaveBeenCalledTimes(1);
40
 
43
 
41
-    result.remove();
44
+    cb2Subscription.remove();
42
 
45
 
43
     uut.notify('commandName', {});
46
     uut.notify('commandName', {});
44
     expect(cb1).toHaveBeenCalledTimes(2);
47
     expect(cb1).toHaveBeenCalledTimes(2);

+ 9
- 8
lib/src/events/CommandsObserver.ts View File

1
-import * as _ from 'lodash';
2
 import { EventSubscription } from '../interfaces/EventSubscription';
1
 import { EventSubscription } from '../interfaces/EventSubscription';
2
+import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
3
 
3
 
4
-export type CommandsListener = (name: string, params: {}) => void;
4
+export type CommandsListener = (name: string, params: any) => void;
5
 
5
 
6
 export class CommandsObserver {
6
 export class CommandsObserver {
7
-  private readonly listeners = {};
7
+  private listeners: Record<string, CommandsListener> = {};
8
+  constructor(private uniqueIdProvider: UniqueIdProvider) {}
8
 
9
 
9
   public register(listener: CommandsListener): EventSubscription {
10
   public register(listener: CommandsListener): EventSubscription {
10
-    const id = _.uniqueId();
11
-    _.set(this.listeners, id, listener);
11
+    const id = this.uniqueIdProvider.generate();
12
+    this.listeners[id] = listener;
12
     return {
13
     return {
13
-      remove: () => _.unset(this.listeners, id)
14
+      remove: () => delete this.listeners[id]
14
     };
15
     };
15
   }
16
   }
16
 
17
 
17
-  public notify(commandName: string, params: {}): void {
18
-    _.forEach(this.listeners, (listener: CommandsListener) => listener(commandName, params));
18
+  public notify(commandName: string, params: any): void {
19
+    Object.values(this.listeners).forEach((listener) => listener(commandName, params));
19
   }
20
   }
20
 }
21
 }

+ 2
- 1
lib/src/events/EventsRegistry.test.tsx View File

1
 import { EventsRegistry } from './EventsRegistry';
1
 import { EventsRegistry } from './EventsRegistry';
2
 import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver.mock';
2
 import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver.mock';
3
 import { CommandsObserver } from './CommandsObserver';
3
 import { CommandsObserver } from './CommandsObserver';
4
+import { UniqueIdProvider } from '../adapters/UniqueIdProvider.mock';
4
 
5
 
5
 describe('EventsRegistry', () => {
6
 describe('EventsRegistry', () => {
6
   let uut: EventsRegistry;
7
   let uut: EventsRegistry;
9
   const mockScreenEventsRegistry = {} as any;
10
   const mockScreenEventsRegistry = {} as any;
10
 
11
 
11
   beforeEach(() => {
12
   beforeEach(() => {
12
-    commandsObserver = new CommandsObserver();
13
+    commandsObserver = new CommandsObserver(new UniqueIdProvider());
13
     uut = new EventsRegistry(mockNativeEventsReceiver, commandsObserver, mockScreenEventsRegistry);
14
     uut = new EventsRegistry(mockNativeEventsReceiver, commandsObserver, mockScreenEventsRegistry);
14
   });
15
   });
15
 
16
 

+ 12
- 10
lib/src/events/EventsRegistry.ts View File

1
+import { EmitterSubscription } from 'react-native';
2
+
1
 import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver';
3
 import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver';
2
 import { CommandsObserver } from './CommandsObserver';
4
 import { CommandsObserver } from './CommandsObserver';
3
 import { EventSubscription } from '../interfaces/EventSubscription';
5
 import { EventSubscription } from '../interfaces/EventSubscription';
16
 export class EventsRegistry {
18
 export class EventsRegistry {
17
   constructor(private nativeEventsReceiver: NativeEventsReceiver, private commandsObserver: CommandsObserver, private componentEventsObserver: ComponentEventsObserver) { }
19
   constructor(private nativeEventsReceiver: NativeEventsReceiver, private commandsObserver: CommandsObserver, private componentEventsObserver: ComponentEventsObserver) { }
18
 
20
 
19
-  public registerAppLaunchedListener(callback: () => void): EventSubscription {
21
+  public registerAppLaunchedListener(callback: () => void): EmitterSubscription {
20
     return this.nativeEventsReceiver.registerAppLaunchedListener(callback);
22
     return this.nativeEventsReceiver.registerAppLaunchedListener(callback);
21
   }
23
   }
22
 
24
 
23
-  public registerComponentDidAppearListener(callback: (event: ComponentDidAppearEvent) => void): EventSubscription {
25
+  public registerComponentDidAppearListener(callback: (event: ComponentDidAppearEvent) => void): EmitterSubscription {
24
     return this.nativeEventsReceiver.registerComponentDidAppearListener(callback);
26
     return this.nativeEventsReceiver.registerComponentDidAppearListener(callback);
25
   }
27
   }
26
 
28
 
27
-  public registerComponentDidDisappearListener(callback: (event: ComponentDidDisappearEvent) => void): EventSubscription {
29
+  public registerComponentDidDisappearListener(callback: (event: ComponentDidDisappearEvent) => void): EmitterSubscription {
28
     return this.nativeEventsReceiver.registerComponentDidDisappearListener(callback);
30
     return this.nativeEventsReceiver.registerComponentDidDisappearListener(callback);
29
   }
31
   }
30
 
32
 
31
-  public registerCommandCompletedListener(callback: (event: CommandCompletedEvent) => void): EventSubscription {
33
+  public registerCommandCompletedListener(callback: (event: CommandCompletedEvent) => void): EmitterSubscription {
32
     return this.nativeEventsReceiver.registerCommandCompletedListener(callback);
34
     return this.nativeEventsReceiver.registerCommandCompletedListener(callback);
33
   }
35
   }
34
 
36
 
35
-  public registerBottomTabSelectedListener(callback: (event: BottomTabSelectedEvent) => void): EventSubscription {
37
+  public registerBottomTabSelectedListener(callback: (event: BottomTabSelectedEvent) => void): EmitterSubscription {
36
     return this.nativeEventsReceiver.registerBottomTabSelectedListener(callback);
38
     return this.nativeEventsReceiver.registerBottomTabSelectedListener(callback);
37
   }
39
   }
38
 
40
 
39
-  public registerNavigationButtonPressedListener(callback: (event: NavigationButtonPressedEvent) => void): EventSubscription {
41
+  public registerNavigationButtonPressedListener(callback: (event: NavigationButtonPressedEvent) => void): EmitterSubscription {
40
     return this.nativeEventsReceiver.registerNavigationButtonPressedListener(callback);
42
     return this.nativeEventsReceiver.registerNavigationButtonPressedListener(callback);
41
   }
43
   }
42
 
44
 
43
-  public registerModalDismissedListener(callback: (event: ModalDismissedEvent) => void): EventSubscription {
45
+  public registerModalDismissedListener(callback: (event: ModalDismissedEvent) => void): EmitterSubscription {
44
     return this.nativeEventsReceiver.registerModalDismissedListener(callback);
46
     return this.nativeEventsReceiver.registerModalDismissedListener(callback);
45
   }
47
   }
46
 
48
 
47
-  public registerSearchBarUpdatedListener(callback: (event: SearchBarUpdatedEvent) => void): EventSubscription {
49
+  public registerSearchBarUpdatedListener(callback: (event: SearchBarUpdatedEvent) => void): EmitterSubscription {
48
     return this.nativeEventsReceiver.registerSearchBarUpdatedListener(callback);
50
     return this.nativeEventsReceiver.registerSearchBarUpdatedListener(callback);
49
   }
51
   }
50
 
52
 
51
-  public registerSearchBarCancelPressedListener(callback: (event: SearchBarCancelPressedEvent) => void): EventSubscription {
53
+  public registerSearchBarCancelPressedListener(callback: (event: SearchBarCancelPressedEvent) => void): EmitterSubscription {
52
     return this.nativeEventsReceiver.registerSearchBarCancelPressedListener(callback);
54
     return this.nativeEventsReceiver.registerSearchBarCancelPressedListener(callback);
53
   }
55
   }
54
 
56
 
55
-  public registerPreviewCompletedListener(callback: (event: PreviewCompletedEvent) => void): EventSubscription {
57
+  public registerPreviewCompletedListener(callback: (event: PreviewCompletedEvent) => void): EmitterSubscription {
56
     return this.nativeEventsReceiver.registerPreviewCompletedListener(callback);
58
     return this.nativeEventsReceiver.registerPreviewCompletedListener(callback);
57
   }
59
   }
58
 
60
 

+ 44
- 2
lib/src/interfaces/Layout.ts View File

82
   /**
82
   /**
83
    * Set the center view
83
    * Set the center view
84
    */
84
    */
85
-  center?: Layout;
85
+  center: Layout;
86
   /**
86
   /**
87
    * Set the right side bar
87
    * Set the right side bar
88
    */
88
    */
113
   options?: OptionsSplitView;
113
   options?: OptionsSplitView;
114
 }
114
 }
115
 
115
 
116
+export interface TopTabs {
117
+  /**
118
+   * Set the layout's id so Navigation.mergeOptions can be used to update options
119
+   */
120
+  id?: string;
121
+  /**
122
+   * Set the children screens
123
+   */
124
+  children?: any[];
125
+  /**
126
+   * Configure top tabs
127
+   */
128
+  options?: Options;
129
+}
130
+
116
 export interface LayoutRoot {
131
 export interface LayoutRoot {
117
   /**
132
   /**
118
    * Set the root
133
    * Set the root
119
    */
134
    */
120
-  root?: Layout;
135
+  root: Layout;
121
   modals?: any;
136
   modals?: any;
122
   overlays?: any;
137
   overlays?: any;
123
 }
138
 }
124
 
139
 
140
+export interface ExternalComponent {
141
+  /**
142
+   * Set the screen's id so Navigation.mergeOptions can be used to update options
143
+   */
144
+  id?: string;
145
+  /**
146
+   * Name of your component
147
+   */
148
+  name: string | number;
149
+  /**
150
+   * Configure component options
151
+   */
152
+  options?: Options;
153
+  /**
154
+   * Properties to pass down to the component
155
+   */
156
+  passProps?: object;
157
+}
158
+
125
 export interface Layout<P = {}> {
159
 export interface Layout<P = {}> {
126
   /**
160
   /**
127
    * Set the component
161
    * Set the component
143
    * Set the split view
177
    * Set the split view
144
    */
178
    */
145
   splitView?: LayoutSplitView;
179
   splitView?: LayoutSplitView;
180
+  /**
181
+   * Set the top tabs
182
+   */
183
+  topTabs?: TopTabs;
184
+  /**
185
+   * Set the external component
186
+   */
187
+  externalComponent?: ExternalComponent;
146
 }
188
 }

+ 5
- 0
lib/src/types.ts View File

1
+declare module 'react-lifecycles-compat' {
2
+  import * as React from 'react';
3
+
4
+  export function polyfill(component: React.ComponentClass<any>): void;
5
+}

+ 3
- 2
package.json View File

60
     "tslib": "1.9.3"
60
     "tslib": "1.9.3"
61
   },
61
   },
62
   "devDependencies": {
62
   "devDependencies": {
63
+    "@types/hoist-non-react-statics": "^3.0.1",
63
     "@types/jest": "23.x.x",
64
     "@types/jest": "23.x.x",
64
     "@types/lodash": "4.x.x",
65
     "@types/lodash": "4.x.x",
65
     "@types/react": "16.x.x",
66
     "@types/react": "16.x.x",
68
     "detox": "9.0.6",
69
     "detox": "9.0.6",
69
     "handlebars": "4.x.x",
70
     "handlebars": "4.x.x",
70
     "jest": "23.x.x",
71
     "jest": "23.x.x",
72
+    "metro-react-native-babel-preset": "0.50.0",
71
     "react": "16.6.1",
73
     "react": "16.6.1",
72
     "react-native": "0.57.7",
74
     "react-native": "0.57.7",
73
     "react-native-typescript-transformer": "^1.2.10",
75
     "react-native-typescript-transformer": "^1.2.10",
74
     "react-native-view-overflow": "0.0.3",
76
     "react-native-view-overflow": "0.0.3",
75
     "react-redux": "5.x.x",
77
     "react-redux": "5.x.x",
76
     "react-test-renderer": "16.6.3",
78
     "react-test-renderer": "16.6.3",
77
-    "metro-react-native-babel-preset": "0.50.0",
78
     "redux": "3.x.x",
79
     "redux": "3.x.x",
79
     "remx": "2.x.x",
80
     "remx": "2.x.x",
80
     "semver": "5.x.x",
81
     "semver": "5.x.x",
154
       }
155
       }
155
     }
156
     }
156
   }
157
   }
157
-}
158
+}