Browse Source

Road to noImplicitAny part 6 (FINAL part) (#4508)

## What this PR is about?
Finally all strict rules are enables for TypeScript compiler! 🎉 Basically previously `noImplicitAny` was turned off but now it plus all strict rules are turned ON! 💋 

## Some highlights of the changes
- `Element` renamed internally to `SharedElement` to make more sense what is it
- `LayoutTreeParser`'s responsibility is now to do all `id` related stuff. Previously it was spread between `LayoutTreeCrawler` and `LayoutTreeParse` so it is more simple now
- clean up a lot of tests because they were testing duplicate stuff that was covered by other tests already
- removed all usages of `static get options` and replaces them with `static options`. This is how it is in the docs plus you cannot have `static get options(passProps)` because it is impossible to have getter with parameters.
Henrik Raitasola 6 years ago
parent
commit
08f8581b3f
31 changed files with 450 additions and 504 deletions
  1. 7
    8
      lib/src/Navigation.ts
  2. 8
    3
      lib/src/adapters/SharedElement.tsx
  3. 0
    5
      lib/src/adapters/UniqueIdProvider.mock.ts
  4. 228
    232
      lib/src/commands/Commands.test.ts
  5. 88
    157
      lib/src/commands/LayoutTreeCrawler.test.ts
  6. 28
    21
      lib/src/commands/LayoutTreeCrawler.ts
  7. 39
    29
      lib/src/commands/LayoutTreeParser.test.ts
  8. 18
    11
      lib/src/commands/LayoutTreeParser.ts
  9. 0
    1
      lib/src/commands/OptionsProcessor.test.ts
  10. 2
    2
      lib/src/components/ComponentWrapper.test.tsx
  11. 8
    8
      lib/src/components/Store.ts
  12. 2
    2
      lib/src/events/CommandsObserver.ts
  13. 4
    2
      lib/src/events/ComponentEventsObserver.ts
  14. 1
    1
      lib/src/events/EventsRegistry.test.tsx
  15. 1
    1
      playground/src/screens/BackHandlerModalScreen.js
  16. 1
    1
      playground/src/screens/BackHandlerScreen.js
  17. 1
    1
      playground/src/screens/CustomDialog.js
  18. 1
    1
      playground/src/screens/CustomTransitionDestination.js
  19. 1
    1
      playground/src/screens/CustomTransitionOrigin.js
  20. 1
    1
      playground/src/screens/KeyboardScreen.js
  21. 1
    1
      playground/src/screens/ModalScreen.js
  22. 1
    1
      playground/src/screens/OptionsScreen.js
  23. 1
    1
      playground/src/screens/PushedScreen.js
  24. 1
    1
      playground/src/screens/ScrollViewScreen.js
  25. 1
    1
      playground/src/screens/SearchScreen.js
  26. 1
    1
      playground/src/screens/TextScreen.js
  27. 1
    1
      playground/src/screens/TopTabOptionsScreen.js
  28. 1
    1
      playground/src/screens/TopTabScreen.js
  29. 1
    1
      playground/src/screens/WelcomeScreen.js
  30. 1
    1
      playground/src/screens/complexlayouts/BottomTabSideMenuScreen.js
  31. 1
    6
      tsconfig-strict.json

+ 7
- 8
lib/src/Navigation.ts View File

9
 import { LayoutTreeCrawler } from './commands/LayoutTreeCrawler';
9
 import { LayoutTreeCrawler } from './commands/LayoutTreeCrawler';
10
 import { EventsRegistry } from './events/EventsRegistry';
10
 import { EventsRegistry } from './events/EventsRegistry';
11
 import { ComponentProvider } from 'react-native';
11
 import { ComponentProvider } from 'react-native';
12
-import { Element } from './adapters/Element';
12
+import { SharedElement } from './adapters/SharedElement';
13
 import { CommandsObserver } from './events/CommandsObserver';
13
 import { CommandsObserver } from './events/CommandsObserver';
14
 import { Constants } from './adapters/Constants';
14
 import { Constants } from './adapters/Constants';
15
 import { ComponentEventsObserver } from './events/ComponentEventsObserver';
15
 import { ComponentEventsObserver } from './events/ComponentEventsObserver';
23
 import { AppRegistryService } from './adapters/AppRegistryService';
23
 import { AppRegistryService } from './adapters/AppRegistryService';
24
 
24
 
25
 export class NavigationRoot {
25
 export class NavigationRoot {
26
-  public readonly Element: React.ComponentType<{ elementId: any; resizeMode?: any; }>;
27
-  public readonly TouchablePreview: React.ComponentType<any>;
28
-  public readonly store: Store;
26
+  public readonly Element = SharedElement;
27
+  public readonly TouchablePreview = TouchablePreview;
28
+
29
+  private readonly store: Store;
29
   private readonly nativeEventsReceiver: NativeEventsReceiver;
30
   private readonly nativeEventsReceiver: NativeEventsReceiver;
30
   private readonly uniqueIdProvider: UniqueIdProvider;
31
   private readonly uniqueIdProvider: UniqueIdProvider;
31
   private readonly componentRegistry: ComponentRegistry;
32
   private readonly componentRegistry: ComponentRegistry;
39
   private readonly componentWrapper: ComponentWrapper;
40
   private readonly componentWrapper: ComponentWrapper;
40
 
41
 
41
   constructor() {
42
   constructor() {
42
-    this.Element = Element;
43
-    this.TouchablePreview = TouchablePreview;
44
     this.componentWrapper = new ComponentWrapper();
43
     this.componentWrapper = new ComponentWrapper();
45
     this.store = new Store();
44
     this.store = new Store();
46
     this.nativeEventsReceiver = new NativeEventsReceiver();
45
     this.nativeEventsReceiver = new NativeEventsReceiver();
53
       this.componentWrapper,
52
       this.componentWrapper,
54
       appRegistryService
53
       appRegistryService
55
     );
54
     );
56
-    this.layoutTreeParser = new LayoutTreeParser();
55
+    this.layoutTreeParser = new LayoutTreeParser(this.uniqueIdProvider);
57
     const optionsProcessor = new OptionsProcessor(this.store, this.uniqueIdProvider, new ColorService(), new AssetService());
56
     const optionsProcessor = new OptionsProcessor(this.store, this.uniqueIdProvider, new ColorService(), new AssetService());
58
-    this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store, optionsProcessor);
57
+    this.layoutTreeCrawler = new LayoutTreeCrawler(this.store, optionsProcessor);
59
     this.nativeCommandsSender = new NativeCommandsSender();
58
     this.nativeCommandsSender = new NativeCommandsSender();
60
     this.commandsObserver = new CommandsObserver(this.uniqueIdProvider);
59
     this.commandsObserver = new CommandsObserver(this.uniqueIdProvider);
61
     this.commands = new Commands(
60
     this.commands = new Commands(

lib/src/adapters/Element.tsx → lib/src/adapters/SharedElement.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
-export class Element extends React.Component<{ elementId: string; resizeMode?: string }> {
5
+export interface SharedElementProps {
6
+  elementId: string;
7
+  resizeMode: string;
8
+}
9
+
10
+export class SharedElement extends React.Component<SharedElementProps> {
6
   static propTypes = {
11
   static propTypes = {
7
     elementId: PropTypes.string.isRequired,
12
     elementId: PropTypes.string.isRequired,
8
     resizeMode: PropTypes.string
13
     resizeMode: PropTypes.string
13
   };
18
   };
14
 
19
 
15
   render() {
20
   render() {
16
-    return <RNNElement {...this.props} />;
21
+    return <RnnSharedElement {...this.props} />;
17
   }
22
   }
18
 }
23
 }
19
 
24
 
20
-const RNNElement = requireNativeComponent('RNNElement', Element, {
25
+const RnnSharedElement = requireNativeComponent('RNNElement', SharedElement, {
21
   nativeOnly: { nativeID: true }
26
   nativeOnly: { nativeID: true }
22
 });
27
 });

+ 0
- 5
lib/src/adapters/UniqueIdProvider.mock.ts View File

1
-export class UniqueIdProvider {
2
-  generate(prefix: string): string {
3
-    return `${prefix}+UNIQUE_ID`;
4
-  }
5
-}

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

4
 import { LayoutTreeParser } from './LayoutTreeParser';
4
 import { LayoutTreeParser } from './LayoutTreeParser';
5
 import { LayoutTreeCrawler } from './LayoutTreeCrawler';
5
 import { LayoutTreeCrawler } from './LayoutTreeCrawler';
6
 import { Store } from '../components/Store';
6
 import { Store } from '../components/Store';
7
-import { UniqueIdProvider } from '../adapters/UniqueIdProvider.mock';
8
 import { Commands } from './Commands';
7
 import { Commands } from './Commands';
9
 import { CommandsObserver } from '../events/CommandsObserver';
8
 import { CommandsObserver } from '../events/CommandsObserver';
10
 import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
9
 import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
11
 import { OptionsProcessor } from './OptionsProcessor';
10
 import { OptionsProcessor } from './OptionsProcessor';
11
+import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
12
+import { Options } from '../interfaces/Options';
12
 
13
 
13
 describe('Commands', () => {
14
 describe('Commands', () => {
14
   let uut: Commands;
15
   let uut: Commands;
15
   let mockedNativeCommandsSender: NativeCommandsSender;
16
   let mockedNativeCommandsSender: NativeCommandsSender;
16
-  let nativeCommandsSender: NativeCommandsSender;
17
-  let store: Store;
17
+  let mockedStore: Store;
18
   let commandsObserver: CommandsObserver;
18
   let commandsObserver: CommandsObserver;
19
+  let mockedUniqueIdProvider: UniqueIdProvider;
19
 
20
 
20
   beforeEach(() => {
21
   beforeEach(() => {
21
-    store = new Store();
22
-    commandsObserver = new CommandsObserver(new UniqueIdProvider());
23
     mockedNativeCommandsSender = mock(NativeCommandsSender);
22
     mockedNativeCommandsSender = mock(NativeCommandsSender);
24
-    nativeCommandsSender = instance(mockedNativeCommandsSender);
23
+    mockedUniqueIdProvider = mock(UniqueIdProvider);
24
+    when(mockedUniqueIdProvider.generate(anything())).thenCall((prefix) => `${prefix}+UNIQUE_ID`);
25
+    const uniqueIdProvider = instance(mockedUniqueIdProvider);
26
+    mockedStore = mock(Store);
27
+    commandsObserver = new CommandsObserver(uniqueIdProvider);
25
 
28
 
26
     const mockedOptionsProcessor = mock(OptionsProcessor);
29
     const mockedOptionsProcessor = mock(OptionsProcessor);
27
     const optionsProcessor = instance(mockedOptionsProcessor);
30
     const optionsProcessor = instance(mockedOptionsProcessor);
28
 
31
 
29
     uut = new Commands(
32
     uut = new Commands(
30
-      nativeCommandsSender,
31
-      new LayoutTreeParser(),
32
-      new LayoutTreeCrawler(new UniqueIdProvider(), store, optionsProcessor),
33
+      instance(mockedNativeCommandsSender),
34
+      new LayoutTreeParser(uniqueIdProvider),
35
+      new LayoutTreeCrawler(instance(mockedStore), optionsProcessor),
33
       commandsObserver,
36
       commandsObserver,
34
-      new UniqueIdProvider(),
37
+      uniqueIdProvider,
35
       optionsProcessor
38
       optionsProcessor
36
     );
39
     );
37
   });
40
   });
39
   describe('setRoot', () => {
42
   describe('setRoot', () => {
40
     it('sends setRoot to native after parsing into a correct layout tree', () => {
43
     it('sends setRoot to native after parsing into a correct layout tree', () => {
41
       uut.setRoot({
44
       uut.setRoot({
42
-        root: {
43
-          component: {
44
-            name: 'com.example.MyScreen'
45
-          }
46
-        }
45
+        root: { component: { name: 'com.example.MyScreen' } }
47
       });
46
       });
48
-      verify(mockedNativeCommandsSender.setRoot('setRoot+UNIQUE_ID', deepEqual({
49
-        root: {
50
-          type: 'Component',
51
-          id: 'Component+UNIQUE_ID',
52
-          children: [],
53
-          data: {
54
-            name: 'com.example.MyScreen',
55
-            options: {},
56
-            passProps: undefined
57
-          }
58
-        },
59
-        modals: [],
60
-        overlays: []
61
-      }))).called();
62
-    });
63
-
64
-    it('passProps into components', () => {
65
-      const passProps = {
66
-        fn: () => 'Hello'
67
-      };
68
-      expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual({});
69
-      uut.setRoot({ root: { component: { name: 'asd', passProps } } });
70
-      expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual(passProps);
71
-      expect(store.getPropsForId('Component+UNIQUE_ID').fn()).toEqual('Hello');
47
+      verify(
48
+        mockedNativeCommandsSender.setRoot(
49
+          'setRoot+UNIQUE_ID',
50
+          deepEqual({
51
+            root: {
52
+              type: 'Component',
53
+              id: 'Component+UNIQUE_ID',
54
+              children: [],
55
+              data: { name: 'com.example.MyScreen', options: {}, passProps: undefined }
56
+            },
57
+            modals: [],
58
+            overlays: []
59
+          })
60
+        )
61
+      ).called();
72
     });
62
     });
73
 
63
 
74
     it('returns a promise with the resolved layout', async () => {
64
     it('returns a promise with the resolved layout', async () => {
75
-      when(mockedNativeCommandsSender.setRoot(anything(), anything())).thenResolve('the resolved layout' as any);
65
+      when(mockedNativeCommandsSender.setRoot(anything(), anything())).thenResolve(
66
+        'the resolved layout'
67
+      );
76
       const result = await uut.setRoot({ root: { component: { name: 'com.example.MyScreen' } } });
68
       const result = await uut.setRoot({ root: { component: { name: 'com.example.MyScreen' } } });
77
       expect(result).toEqual('the resolved layout');
69
       expect(result).toEqual('the resolved layout');
78
     });
70
     });
79
 
71
 
80
     it('inputs modals and overlays', () => {
72
     it('inputs modals and overlays', () => {
81
       uut.setRoot({
73
       uut.setRoot({
82
-        root: {
83
-          component: {
84
-            name: 'com.example.MyScreen'
85
-          }
86
-        },
87
-        modals: [
88
-          {
89
-            component: {
90
-              name: 'com.example.MyModal'
91
-            }
92
-          }
93
-        ],
94
-        overlays: [
95
-          {
96
-            component: {
97
-              name: 'com.example.MyOverlay'
98
-            }
99
-          }
100
-        ]
74
+        root: { component: { name: 'com.example.MyScreen' } },
75
+        modals: [{ component: { name: 'com.example.MyModal' } }],
76
+        overlays: [{ component: { name: 'com.example.MyOverlay' } }]
101
       });
77
       });
102
-      verify(mockedNativeCommandsSender.setRoot('setRoot+UNIQUE_ID', deepEqual({
103
-        root:
104
-          {
105
-            type: 'Component',
106
-            id: 'Component+UNIQUE_ID',
107
-            children: [],
108
-            data: {
109
-              name: 'com.example.MyScreen',
110
-              options: {},
111
-              passProps: undefined
112
-            }
113
-          },
114
-        modals: [
115
-          {
116
-            type: 'Component',
117
-            id: 'Component+UNIQUE_ID',
118
-            children: [],
119
-            data: {
120
-              name: 'com.example.MyModal',
121
-              options: {},
122
-              passProps: undefined
123
-            }
124
-          }
125
-        ],
126
-        overlays: [
127
-          {
128
-            type: 'Component',
129
-            id: 'Component+UNIQUE_ID',
130
-            children: [],
131
-            data: {
132
-              name: 'com.example.MyOverlay',
133
-              options: {},
134
-              passProps: undefined
135
-            }
136
-          }
137
-        ]
138
-      }))).called();
78
+      verify(
79
+        mockedNativeCommandsSender.setRoot(
80
+          'setRoot+UNIQUE_ID',
81
+          deepEqual({
82
+            root: {
83
+              type: 'Component',
84
+              id: 'Component+UNIQUE_ID',
85
+              children: [],
86
+              data: {
87
+                name: 'com.example.MyScreen',
88
+                options: {},
89
+                passProps: undefined
90
+              }
91
+            },
92
+            modals: [
93
+              {
94
+                type: 'Component',
95
+                id: 'Component+UNIQUE_ID',
96
+                children: [],
97
+                data: {
98
+                  name: 'com.example.MyModal',
99
+                  options: {},
100
+                  passProps: undefined
101
+                }
102
+              }
103
+            ],
104
+            overlays: [
105
+              {
106
+                type: 'Component',
107
+                id: 'Component+UNIQUE_ID',
108
+                children: [],
109
+                data: {
110
+                  name: 'com.example.MyOverlay',
111
+                  options: {},
112
+                  passProps: undefined
113
+                }
114
+              }
115
+            ]
116
+          })
117
+        )
118
+      ).called();
139
     });
119
     });
140
   });
120
   });
141
 
121
 
142
   describe('mergeOptions', () => {
122
   describe('mergeOptions', () => {
143
     it('passes options for component', () => {
123
     it('passes options for component', () => {
144
-      uut.mergeOptions('theComponentId', { title: '1' } as any);
145
-      verify(mockedNativeCommandsSender.mergeOptions('theComponentId', deepEqual({title: '1'}))).called();
124
+      uut.mergeOptions('theComponentId', { blurOnUnmount: true });
125
+      verify(
126
+        mockedNativeCommandsSender.mergeOptions(
127
+          'theComponentId',
128
+          deepEqual({ blurOnUnmount: true })
129
+        )
130
+      ).called();
146
     });
131
     });
147
   });
132
   });
148
 
133
 
149
   describe('showModal', () => {
134
   describe('showModal', () => {
150
     it('sends command to native after parsing into a correct layout tree', () => {
135
     it('sends command to native after parsing into a correct layout tree', () => {
151
-      uut.showModal({
152
-        component: {
153
-          name: 'com.example.MyScreen'
154
-        }
155
-      });
156
-      verify(mockedNativeCommandsSender.showModal('showModal+UNIQUE_ID', deepEqual({
157
-        type: 'Component',
158
-        id: 'Component+UNIQUE_ID',
159
-        data: {
160
-          name: 'com.example.MyScreen',
161
-          options: {},
162
-          passProps: undefined
163
-        },
164
-        children: []
165
-      }))).called();
166
-    });
167
-
168
-    it('passProps into components', () => {
169
-      const passProps = {};
170
-      expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual({});
171
-      uut.showModal({
172
-        component: {
173
-          name: 'com.example.MyScreen',
174
-          passProps
175
-        }
176
-      });
177
-      expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual(passProps);
136
+      uut.showModal({ component: { name: 'com.example.MyScreen' } });
137
+      verify(
138
+        mockedNativeCommandsSender.showModal(
139
+          'showModal+UNIQUE_ID',
140
+          deepEqual({
141
+            type: 'Component',
142
+            id: 'Component+UNIQUE_ID',
143
+            data: {
144
+              name: 'com.example.MyScreen',
145
+              options: {},
146
+              passProps: undefined
147
+            },
148
+            children: []
149
+          })
150
+        )
151
+      ).called();
178
     });
152
     });
179
 
153
 
180
     it('returns a promise with the resolved layout', async () => {
154
     it('returns a promise with the resolved layout', async () => {
181
-      when(mockedNativeCommandsSender.showModal(anything(), anything())).thenResolve('the resolved layout' as any);
155
+      when(mockedNativeCommandsSender.showModal(anything(), anything())).thenResolve(
156
+        'the resolved layout'
157
+      );
182
       const result = await uut.showModal({ component: { name: 'com.example.MyScreen' } });
158
       const result = await uut.showModal({ component: { name: 'com.example.MyScreen' } });
183
       expect(result).toEqual('the resolved layout');
159
       expect(result).toEqual('the resolved layout');
184
     });
160
     });
187
   describe('dismissModal', () => {
163
   describe('dismissModal', () => {
188
     it('sends command to native', () => {
164
     it('sends command to native', () => {
189
       uut.dismissModal('myUniqueId', {});
165
       uut.dismissModal('myUniqueId', {});
190
-      verify(mockedNativeCommandsSender.dismissModal('dismissModal+UNIQUE_ID', 'myUniqueId', deepEqual({}))).called();
166
+      verify(
167
+        mockedNativeCommandsSender.dismissModal(
168
+          'dismissModal+UNIQUE_ID',
169
+          'myUniqueId',
170
+          deepEqual({})
171
+        )
172
+      ).called();
191
     });
173
     });
192
 
174
 
193
     it('returns a promise with the id', async () => {
175
     it('returns a promise with the id', async () => {
194
-      when(mockedNativeCommandsSender.dismissModal(anyString(), anything(), anything())).thenResolve('the id' as any);
176
+      when(
177
+        mockedNativeCommandsSender.dismissModal(anyString(), anything(), anything())
178
+      ).thenResolve('the id');
195
       const result = await uut.dismissModal('myUniqueId');
179
       const result = await uut.dismissModal('myUniqueId');
196
       expect(result).toEqual('the id');
180
       expect(result).toEqual('the id');
197
     });
181
     });
200
   describe('dismissAllModals', () => {
184
   describe('dismissAllModals', () => {
201
     it('sends command to native', () => {
185
     it('sends command to native', () => {
202
       uut.dismissAllModals({});
186
       uut.dismissAllModals({});
203
-      verify(mockedNativeCommandsSender.dismissAllModals('dismissAllModals+UNIQUE_ID', deepEqual({}))).called();
187
+      verify(
188
+        mockedNativeCommandsSender.dismissAllModals('dismissAllModals+UNIQUE_ID', deepEqual({}))
189
+      ).called();
204
     });
190
     });
205
 
191
 
206
     it('returns a promise with the id', async () => {
192
     it('returns a promise with the id', async () => {
207
-      when(mockedNativeCommandsSender.dismissAllModals(anyString(), anything())).thenResolve('the id' as any);
193
+      when(mockedNativeCommandsSender.dismissAllModals(anyString(), anything())).thenResolve(
194
+        'the id'
195
+      );
208
       const result = await uut.dismissAllModals();
196
       const result = await uut.dismissAllModals();
209
       expect(result).toEqual('the id');
197
       expect(result).toEqual('the id');
210
     });
198
     });
212
 
200
 
213
   describe('push', () => {
201
   describe('push', () => {
214
     it('resolves with the parsed layout', async () => {
202
     it('resolves with the parsed layout', async () => {
215
-      when(mockedNativeCommandsSender.push(anyString(), anyString(), anything())).thenResolve('the resolved layout' as any);
216
-      const result = await uut.push('theComponentId', { component: { name: 'com.example.MyScreen' } });
203
+      when(mockedNativeCommandsSender.push(anyString(), anyString(), anything())).thenResolve(
204
+        'the resolved layout'
205
+      );
206
+      const result = await uut.push('theComponentId', {
207
+        component: { name: 'com.example.MyScreen' }
208
+      });
217
       expect(result).toEqual('the resolved layout');
209
       expect(result).toEqual('the resolved layout');
218
     });
210
     });
219
 
211
 
220
     it('parses into correct layout node and sends to native', () => {
212
     it('parses into correct layout node and sends to native', () => {
221
       uut.push('theComponentId', { component: { name: 'com.example.MyScreen' } });
213
       uut.push('theComponentId', { component: { name: 'com.example.MyScreen' } });
222
-      verify(mockedNativeCommandsSender.push('push+UNIQUE_ID', 'theComponentId', deepEqual({
223
-        type: 'Component',
224
-        id: 'Component+UNIQUE_ID',
225
-        data: {
226
-          name: 'com.example.MyScreen',
227
-          options: {},
228
-          passProps: undefined
229
-        },
230
-        children: []
231
-      }))).called();
232
-    });
233
-
234
-    it('calls component generator once', async () => {
235
-      const generator = jest.fn(() => {
236
-        return {};
237
-      });
238
-      store.setComponentClassForName('theComponentName', generator);
239
-      await uut.push('theComponentId', { component: { name: 'theComponentName' } });
240
-      expect(generator).toHaveBeenCalledTimes(1);
214
+      verify(
215
+        mockedNativeCommandsSender.push(
216
+          'push+UNIQUE_ID',
217
+          'theComponentId',
218
+          deepEqual({
219
+            type: 'Component',
220
+            id: 'Component+UNIQUE_ID',
221
+            data: {
222
+              name: 'com.example.MyScreen',
223
+              options: {},
224
+              passProps: undefined
225
+            },
226
+            children: []
227
+          })
228
+        )
229
+      ).called();
241
     });
230
     });
242
   });
231
   });
243
 
232
 
244
   describe('pop', () => {
233
   describe('pop', () => {
245
     it('pops a component, passing componentId', () => {
234
     it('pops a component, passing componentId', () => {
246
       uut.pop('theComponentId', {});
235
       uut.pop('theComponentId', {});
247
-      verify(mockedNativeCommandsSender.pop('pop+UNIQUE_ID', 'theComponentId', deepEqual({}))).called();
236
+      verify(
237
+        mockedNativeCommandsSender.pop('pop+UNIQUE_ID', 'theComponentId', deepEqual({}))
238
+      ).called();
248
     });
239
     });
249
     it('pops a component, passing componentId and options', () => {
240
     it('pops a component, passing componentId and options', () => {
250
-      const options = {
251
-        customTransition: {
252
-          animations: [
253
-            { type: 'sharedElement', fromId: 'title2', toId: 'title1', startDelay: 0, springVelocity: 0.2, duration: 0.5 }
254
-          ],
255
-          duration: 0.8
256
-        }
257
-      };
258
-      uut.pop('theComponentId', options as any);
241
+      const options: Options = { popGesture: true };
242
+      uut.pop('theComponentId', options);
259
       verify(mockedNativeCommandsSender.pop('pop+UNIQUE_ID', 'theComponentId', options)).called();
243
       verify(mockedNativeCommandsSender.pop('pop+UNIQUE_ID', 'theComponentId', options)).called();
260
     });
244
     });
261
 
245
 
262
     it('pop returns a promise that resolves to componentId', async () => {
246
     it('pop returns a promise that resolves to componentId', async () => {
263
-      when(mockedNativeCommandsSender.pop(anyString(), anyString(), anything())).thenResolve('theComponentId' as any);
247
+      when(mockedNativeCommandsSender.pop(anyString(), anyString(), anything())).thenResolve(
248
+        'theComponentId'
249
+      );
264
       const result = await uut.pop('theComponentId', {});
250
       const result = await uut.pop('theComponentId', {});
265
       expect(result).toEqual('theComponentId');
251
       expect(result).toEqual('theComponentId');
266
     });
252
     });
269
   describe('popTo', () => {
255
   describe('popTo', () => {
270
     it('pops all components until the passed Id is top', () => {
256
     it('pops all components until the passed Id is top', () => {
271
       uut.popTo('theComponentId', {});
257
       uut.popTo('theComponentId', {});
272
-      verify(mockedNativeCommandsSender.popTo('popTo+UNIQUE_ID', 'theComponentId', deepEqual({}))).called();
258
+      verify(
259
+        mockedNativeCommandsSender.popTo('popTo+UNIQUE_ID', 'theComponentId', deepEqual({}))
260
+      ).called();
273
     });
261
     });
274
 
262
 
275
     it('returns a promise that resolves to targetId', async () => {
263
     it('returns a promise that resolves to targetId', async () => {
276
-      when(mockedNativeCommandsSender.popTo(anyString(), anyString(), anything())).thenResolve('theComponentId' as any);
264
+      when(mockedNativeCommandsSender.popTo(anyString(), anyString(), anything())).thenResolve(
265
+        'theComponentId'
266
+      );
277
       const result = await uut.popTo('theComponentId');
267
       const result = await uut.popTo('theComponentId');
278
       expect(result).toEqual('theComponentId');
268
       expect(result).toEqual('theComponentId');
279
     });
269
     });
282
   describe('popToRoot', () => {
272
   describe('popToRoot', () => {
283
     it('pops all components to root', () => {
273
     it('pops all components to root', () => {
284
       uut.popToRoot('theComponentId', {});
274
       uut.popToRoot('theComponentId', {});
285
-      verify(mockedNativeCommandsSender.popToRoot('popToRoot+UNIQUE_ID', 'theComponentId', deepEqual({}))).called();
275
+      verify(
276
+        mockedNativeCommandsSender.popToRoot('popToRoot+UNIQUE_ID', 'theComponentId', deepEqual({}))
277
+      ).called();
286
     });
278
     });
287
 
279
 
288
     it('returns a promise that resolves to targetId', async () => {
280
     it('returns a promise that resolves to targetId', async () => {
289
-      when(mockedNativeCommandsSender.popToRoot(anyString(), anyString(), anything())).thenResolve('theComponentId' as any);
281
+      when(mockedNativeCommandsSender.popToRoot(anyString(), anyString(), anything())).thenResolve(
282
+        'theComponentId'
283
+      );
290
       const result = await uut.popToRoot('theComponentId');
284
       const result = await uut.popToRoot('theComponentId');
291
       expect(result).toEqual('theComponentId');
285
       expect(result).toEqual('theComponentId');
292
     });
286
     });
295
   describe('setStackRoot', () => {
289
   describe('setStackRoot', () => {
296
     it('parses into correct layout node and sends to native', () => {
290
     it('parses into correct layout node and sends to native', () => {
297
       uut.setStackRoot('theComponentId', [{ component: { name: 'com.example.MyScreen' } }]);
291
       uut.setStackRoot('theComponentId', [{ component: { name: 'com.example.MyScreen' } }]);
298
-      verify(mockedNativeCommandsSender.setStackRoot('setStackRoot+UNIQUE_ID', 'theComponentId', deepEqual([
299
-        {
300
-          type: 'Component',
301
-          id: 'Component+UNIQUE_ID',
302
-          data: {
303
-            name: 'com.example.MyScreen',
304
-            options: {},
305
-            passProps: undefined
306
-          },
307
-          children: []
308
-        }
309
-      ]))).called();
292
+      verify(
293
+        mockedNativeCommandsSender.setStackRoot(
294
+          'setStackRoot+UNIQUE_ID',
295
+          'theComponentId',
296
+          deepEqual([
297
+            {
298
+              type: 'Component',
299
+              id: 'Component+UNIQUE_ID',
300
+              data: {
301
+                name: 'com.example.MyScreen',
302
+                options: {},
303
+                passProps: undefined
304
+              },
305
+              children: []
306
+            }
307
+          ])
308
+        )
309
+      ).called();
310
     });
310
     });
311
   });
311
   });
312
 
312
 
313
   describe('showOverlay', () => {
313
   describe('showOverlay', () => {
314
     it('sends command to native after parsing into a correct layout tree', () => {
314
     it('sends command to native after parsing into a correct layout tree', () => {
315
-      uut.showOverlay({
316
-        component: {
317
-          name: 'com.example.MyScreen'
318
-        }
319
-      });
320
-      verify(mockedNativeCommandsSender.showOverlay('showOverlay+UNIQUE_ID', deepEqual({
321
-        type: 'Component',
322
-        id: 'Component+UNIQUE_ID',
323
-        data: {
324
-          name: 'com.example.MyScreen',
325
-          options: {},
326
-          passProps: undefined
327
-        },
328
-        children: []
329
-      }))).called();
315
+      uut.showOverlay({ component: { name: 'com.example.MyScreen' } });
316
+      verify(
317
+        mockedNativeCommandsSender.showOverlay(
318
+          'showOverlay+UNIQUE_ID',
319
+          deepEqual({
320
+            type: 'Component',
321
+            id: 'Component+UNIQUE_ID',
322
+            data: {
323
+              name: 'com.example.MyScreen',
324
+              options: {},
325
+              passProps: undefined
326
+            },
327
+            children: []
328
+          })
329
+        )
330
+      ).called();
330
     });
331
     });
331
 
332
 
332
     it('resolves with the component id', async () => {
333
     it('resolves with the component id', async () => {
333
-      when(mockedNativeCommandsSender.showOverlay(anyString(), anything())).thenResolve('Component1' as any);
334
+      when(mockedNativeCommandsSender.showOverlay(anyString(), anything())).thenResolve(
335
+        'Component1'
336
+      );
334
       const result = await uut.showOverlay({ component: { name: 'com.example.MyScreen' } });
337
       const result = await uut.showOverlay({ component: { name: 'com.example.MyScreen' } });
335
       expect(result).toEqual('Component1');
338
       expect(result).toEqual('Component1');
336
     });
339
     });
338
 
341
 
339
   describe('dismissOverlay', () => {
342
   describe('dismissOverlay', () => {
340
     it('check promise returns true', async () => {
343
     it('check promise returns true', async () => {
341
-      when(mockedNativeCommandsSender.dismissOverlay(anyString(), anyString())).thenResolve(true as any);
344
+      when(mockedNativeCommandsSender.dismissOverlay(anyString(), anyString())).thenResolve(true);
342
       const result = await uut.dismissOverlay('Component1');
345
       const result = await uut.dismissOverlay('Component1');
343
       verify(mockedNativeCommandsSender.dismissOverlay(anyString(), anyString())).called();
346
       verify(mockedNativeCommandsSender.dismissOverlay(anyString(), anyString())).called();
344
       expect(result).toEqual(true);
347
       expect(result).toEqual(true);
346
 
349
 
347
     it('send command to native with componentId', () => {
350
     it('send command to native with componentId', () => {
348
       uut.dismissOverlay('Component1');
351
       uut.dismissOverlay('Component1');
349
-      verify(mockedNativeCommandsSender.dismissOverlay('dismissOverlay+UNIQUE_ID', 'Component1')).called();
352
+      verify(
353
+        mockedNativeCommandsSender.dismissOverlay('dismissOverlay+UNIQUE_ID', 'Component1')
354
+      ).called();
350
     });
355
     });
351
   });
356
   });
352
 
357
 
353
   describe('notifies commandsObserver', () => {
358
   describe('notifies commandsObserver', () => {
354
     let cb: any;
359
     let cb: any;
360
+    let mockedLayoutTreeParser: LayoutTreeParser;
361
+    let mockedLayoutTreeCrawler: LayoutTreeCrawler;
362
+    let anotherMockedUniqueIdProvider: UniqueIdProvider;
355
 
363
 
356
     beforeEach(() => {
364
     beforeEach(() => {
357
       cb = jest.fn();
365
       cb = jest.fn();
358
-      const mockParser = { parse: () => 'parsed' };
359
-      const mockCrawler = { crawl: (x: any) => x, processOptions: (x: any) => x };
366
+      mockedLayoutTreeParser = mock(LayoutTreeParser);
367
+      mockedLayoutTreeCrawler = mock(LayoutTreeCrawler);
360
       commandsObserver.register(cb);
368
       commandsObserver.register(cb);
361
       const mockedOptionsProcessor = mock(OptionsProcessor);
369
       const mockedOptionsProcessor = mock(OptionsProcessor);
362
-      const optionsProcessor = instance(mockedOptionsProcessor);
370
+      anotherMockedUniqueIdProvider = mock(UniqueIdProvider);
371
+      when(anotherMockedUniqueIdProvider.generate(anything())).thenCall(
372
+        (prefix) => `${prefix}+UNIQUE_ID`
373
+      );
374
+
363
       uut = new Commands(
375
       uut = new Commands(
364
         mockedNativeCommandsSender,
376
         mockedNativeCommandsSender,
365
-        mockParser as any,
366
-        mockCrawler as any,
377
+        instance(mockedLayoutTreeParser),
378
+        instance(mockedLayoutTreeCrawler),
367
         commandsObserver,
379
         commandsObserver,
368
-        new UniqueIdProvider(),
369
-        optionsProcessor
380
+        instance(anotherMockedUniqueIdProvider),
381
+        instance(mockedOptionsProcessor)
370
       );
382
       );
371
     });
383
     });
372
 
384
 
377
       return methods;
389
       return methods;
378
     }
390
     }
379
 
391
 
380
-    // function getAllMethodsOfNativeCommandsSender() {
381
-    //   const nativeCommandsSenderFns = _.functions(mockedNativeCommandsSender);
382
-    //   expect(nativeCommandsSenderFns.length).toBeGreaterThan(1);
383
-    //   return nativeCommandsSenderFns;
384
-    // }
385
-
386
-    // it('always call last, when nativeCommand fails, dont notify listeners', () => {
387
-    //   // expect(getAllMethodsOfUut().sort()).toEqual(getAllMethodsOfNativeCommandsSender().sort());
388
-
389
-    //   // call all commands on uut, all should throw, no commandObservers called
390
-    //   _.forEach(getAllMethodsOfUut(), (m) => {
391
-    //     expect(() => uut[m]()).toThrow();
392
-    //     expect(cb).not.toHaveBeenCalled();
393
-    //   });
394
-    // });
395
-
396
-    // it('notify on all commands', () => {
397
-    //   _.forEach(getAllMethodsOfUut(), (m) => {
398
-    //     uut[m]({});
399
-    //   });
400
-    //   expect(cb).toHaveBeenCalledTimes(getAllMethodsOfUut().length);
401
-    // });
402
-
403
     describe('passes correct params', () => {
392
     describe('passes correct params', () => {
404
       const argsForMethodName: Record<string, any[]> = {
393
       const argsForMethodName: Record<string, any[]> = {
405
         setRoot: [{}],
394
         setRoot: [{}],
418
         getLaunchArgs: ['id']
407
         getLaunchArgs: ['id']
419
       };
408
       };
420
       const paramsForMethodName: Record<string, object> = {
409
       const paramsForMethodName: Record<string, object> = {
421
-        setRoot: { commandId: 'setRoot+UNIQUE_ID', layout: { root: 'parsed', modals: [], overlays: [] } },
410
+        setRoot: {
411
+          commandId: 'setRoot+UNIQUE_ID',
412
+          layout: { root: null, modals: [], overlays: [] }
413
+        },
422
         setDefaultOptions: { options: {} },
414
         setDefaultOptions: { options: {} },
423
         mergeOptions: { componentId: 'id', options: {} },
415
         mergeOptions: { componentId: 'id', options: {} },
424
-        showModal: { commandId: 'showModal+UNIQUE_ID', layout: 'parsed' },
416
+        showModal: { commandId: 'showModal+UNIQUE_ID', layout: null },
425
         dismissModal: { commandId: 'dismissModal+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
417
         dismissModal: { commandId: 'dismissModal+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
426
         dismissAllModals: { commandId: 'dismissAllModals+UNIQUE_ID', mergeOptions: {} },
418
         dismissAllModals: { commandId: 'dismissAllModals+UNIQUE_ID', mergeOptions: {} },
427
-        push: { commandId: 'push+UNIQUE_ID', componentId: 'id', layout: 'parsed' },
419
+        push: { commandId: 'push+UNIQUE_ID', componentId: 'id', layout: null },
428
         pop: { commandId: 'pop+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
420
         pop: { commandId: 'pop+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
429
         popTo: { commandId: 'popTo+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
421
         popTo: { commandId: 'popTo+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
430
         popToRoot: { commandId: 'popToRoot+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
422
         popToRoot: { commandId: 'popToRoot+UNIQUE_ID', componentId: 'id', mergeOptions: {} },
431
-        setStackRoot: { commandId: 'setStackRoot+UNIQUE_ID', componentId: 'id', layout: ['parsed'] },
432
-        showOverlay: { commandId: 'showOverlay+UNIQUE_ID', layout: 'parsed' },
423
+        setStackRoot: {
424
+          commandId: 'setStackRoot+UNIQUE_ID',
425
+          componentId: 'id',
426
+          layout: [null]
427
+        },
428
+        showOverlay: { commandId: 'showOverlay+UNIQUE_ID', layout: null },
433
         dismissOverlay: { commandId: 'dismissOverlay+UNIQUE_ID', componentId: 'id' },
429
         dismissOverlay: { commandId: 'dismissOverlay+UNIQUE_ID', componentId: 'id' },
434
-        getLaunchArgs: { commandId: 'getLaunchArgs+UNIQUE_ID' },
430
+        getLaunchArgs: { commandId: 'getLaunchArgs+UNIQUE_ID' }
435
       };
431
       };
436
       _.forEach(getAllMethodsOfUut(), (m) => {
432
       _.forEach(getAllMethodsOfUut(), (m) => {
437
         it(`for ${m}`, () => {
433
         it(`for ${m}`, () => {

+ 88
- 157
lib/src/commands/LayoutTreeCrawler.test.ts View File

1
 import * as React from 'react';
1
 import * as React from 'react';
2
 
2
 
3
 import { LayoutType } from './LayoutType';
3
 import { LayoutType } from './LayoutType';
4
-import { LayoutTreeCrawler, LayoutNode } from './LayoutTreeCrawler';
5
-import { UniqueIdProvider } from '../adapters/UniqueIdProvider.mock';
4
+import { LayoutTreeCrawler } from './LayoutTreeCrawler';
6
 import { Store } from '../components/Store';
5
 import { Store } from '../components/Store';
7
-import { mock, instance } from 'ts-mockito';
6
+import { mock, instance, verify, deepEqual, when } from 'ts-mockito';
8
 import { OptionsProcessor } from './OptionsProcessor';
7
 import { OptionsProcessor } from './OptionsProcessor';
8
+import { Options } from '../interfaces/Options';
9
 
9
 
10
 describe('LayoutTreeCrawler', () => {
10
 describe('LayoutTreeCrawler', () => {
11
   let uut: LayoutTreeCrawler;
11
   let uut: LayoutTreeCrawler;
12
-  let store: Store;
12
+  let mockedStore: Store;
13
+  let mockedOptionsProcessor: OptionsProcessor;
13
 
14
 
14
   beforeEach(() => {
15
   beforeEach(() => {
15
-    store = new Store();
16
-    const mockedOptionsProcessor = mock(OptionsProcessor);
17
-    const optionsProcessor = instance(mockedOptionsProcessor);
18
-    uut = new LayoutTreeCrawler(new UniqueIdProvider(), store, optionsProcessor);
19
-  });
16
+    mockedStore = mock(Store);
17
+    mockedOptionsProcessor = mock(OptionsProcessor);
20
 
18
 
21
-  it('crawls a layout tree and adds unique id to each node', () => {
22
-    const node = { type: LayoutType.Stack, id: 'Stack+UNIQUE_ID', children: [{ id: 'BottomTabs+UNIQUE_ID', type: LayoutType.BottomTabs, data: {}, children: [] }], data: {} };
23
-    uut.crawl(node);
24
-    expect(node.id).toEqual('Stack+UNIQUE_ID');
25
-    expect(node.children[0].id).toEqual('BottomTabs+UNIQUE_ID');
26
-  });
27
-
28
-  it('does not generate unique id when already provided', () => {
29
-    const node = { id: 'user defined id', type: LayoutType.Stack, data: {}, children: [] };
30
-    uut.crawl(node);
31
-    expect(node.id).toEqual('user defined id');
32
-  });
33
-
34
-  it('crawls a layout tree and ensures data exists', () => {
35
-    const node = { type: LayoutType.Stack, children: [{ type: LayoutType.BottomTabs, data: {}, children: [] }], data: {} };
36
-    uut.crawl(node);
37
-    expect(node.data).toEqual({});
38
-    expect(node.children[0].data).toEqual({});
39
-  });
40
-
41
-  it('crawls a layout tree and ensures children exists', () => {
42
-    const node = { type: LayoutType.Stack, children: [{ type: LayoutType.BottomTabs, data: {}, children: [] }], data: {} };
43
-    uut.crawl(node);
44
-    expect(node.children[0].children).toEqual([]);
19
+    uut = new LayoutTreeCrawler(instance(mockedStore), instance(mockedOptionsProcessor));
45
   });
20
   });
46
 
21
 
47
   it('saves passProps into store for Component nodes', () => {
22
   it('saves passProps into store for Component nodes', () => {
48
     const node = {
23
     const node = {
24
+      id: 'testId',
49
       type: LayoutType.BottomTabs,
25
       type: LayoutType.BottomTabs,
50
-      children: [{ type: LayoutType.Component, data: { name: 'the name', passProps: { myProp: 123 } }, children: [] }],
26
+      children: [
27
+        {
28
+          id: 'testId',
29
+          type: LayoutType.Component,
30
+          data: { name: 'the name', passProps: { myProp: 123 } },
31
+          children: []
32
+        }
33
+      ],
51
       data: {}
34
       data: {}
52
     };
35
     };
53
-    expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual({});
54
     uut.crawl(node);
36
     uut.crawl(node);
55
-    expect(store.getPropsForId('Component+UNIQUE_ID')).toEqual({ myProp: 123 });
37
+    verify(mockedStore.setPropsForId('testId', deepEqual({ myProp: 123 }))).called();
56
   });
38
   });
57
 
39
 
58
   it('Components: injects options from original component class static property', () => {
40
   it('Components: injects options from original component class static property', () => {
59
-    const theStyle = {};
60
-    const MyComponent = class CoolComponent extends React.Component {
61
-      static get options() {
62
-        return theStyle;
63
-      }
41
+    when(mockedStore.getComponentClassForName('theComponentName')).thenReturn(
42
+      () =>
43
+        class extends React.Component {
44
+          static options(): Options {
45
+            return { popGesture: true };
46
+          }
47
+        }
48
+    );
49
+    const node = {
50
+      id: 'testId',
51
+      type: LayoutType.Component,
52
+      data: { name: 'theComponentName', options: {} },
53
+      children: []
64
     };
54
     };
65
-
66
-    const node = { type: LayoutType.Component, data: { name: 'theComponentName', options: {} }, children: [] };
67
-    store.setComponentClassForName('theComponentName', () => MyComponent);
68
     uut.crawl(node);
55
     uut.crawl(node);
69
-    expect(node.data.options).toEqual(theStyle);
56
+    expect(node.data.options).toEqual({ popGesture: true });
70
   });
57
   });
71
 
58
 
72
   it('Components: crawl does not cache options', () => {
59
   it('Components: crawl does not cache options', () => {
73
-    const optionsWithTitle = (title?: string) => {
74
-      return {
75
-        topBar: {
76
-          title: {
77
-            text: title
60
+    when(mockedStore.getComponentClassForName('theComponentName')).thenReturn(
61
+      () =>
62
+        class extends React.Component {
63
+          static options(props: { title?: string }) {
64
+            return { topBar: { title: { text: props.title } } };
78
           }
65
           }
79
         }
66
         }
80
-      };
81
-    };
82
-
83
-    const MyComponent = class CoolComponent extends React.Component {
84
-      static options(props: {title: string}) {
85
-        return {
86
-          topBar: {
87
-            title: {
88
-              text: props.title
89
-            }
90
-          }
91
-        };
92
-      }
93
-    };
94
-
95
-    const node = { type: LayoutType.Component, data: { name: 'theComponentName', options: {}, passProps: { title: 'title' } }, children: [] };
96
-    store.setComponentClassForName('theComponentName', () => MyComponent);
97
-    uut.crawl(node);
98
-    expect(node.data.options).toEqual(optionsWithTitle('title'));
99
-
100
-    const node2 = { type: LayoutType.Component, data: { name: 'theComponentName', options: {} }, children: [] };
101
-    uut.crawl(node2);
102
-    expect(node2.data.options).toEqual(optionsWithTitle(undefined));
103
-  });
104
-
105
-  it('Components: passes passProps to the static options function to be used by the user', () => {
106
-    const MyComponent = class CoolComponent extends React.Component {
107
-      static options(passProps: {bar: {baz: {value: string}}}) {
108
-        return { foo: passProps.bar.baz.value };
109
-      }
67
+    );
68
+    const node = {
69
+      id: 'testId',
70
+      type: LayoutType.Component,
71
+      data: { name: 'theComponentName', options: {}, passProps: { title: 'title' } },
72
+      children: []
110
     };
73
     };
111
-
112
-    const node = { type: LayoutType.Component, data: { name: 'theComponentName', passProps: { bar: { baz: { value: 'hello' } } }, options: {} }, children: [] };
113
-    store.setComponentClassForName('theComponentName', () => MyComponent);
114
     uut.crawl(node);
74
     uut.crawl(node);
115
-    expect(node.data.options).toEqual({ foo: 'hello' });
116
-  });
75
+    expect(node.data.options).toEqual({ topBar: { title: { text: 'title' } } });
117
 
76
 
118
-  it('Components: passProps in the static options is optional', () => {
119
-    const MyComponent = class CoolComponent extends React.Component {
120
-      static options(passProps: string) {
121
-        return { foo: passProps };
122
-      }
77
+    const node2 = {
78
+      id: 'testId',
79
+      type: LayoutType.Component,
80
+      data: { name: 'theComponentName', options: {} },
81
+      children: []
123
     };
82
     };
124
-
125
-    const node = { type: LayoutType.Component, data: { name: 'theComponentName', options: {} }, children: [] };
126
-    store.setComponentClassForName('theComponentName', () => MyComponent);
127
-    uut.crawl(node);
128
-    expect(node.data.options).toEqual({ foo: {} });
83
+    uut.crawl(node2);
84
+    expect(node2.data.options).toEqual({ topBar: { title: {} } });
129
   });
85
   });
130
 
86
 
131
   it('Components: merges options from component class static property with passed options, favoring passed options', () => {
87
   it('Components: merges options from component class static property with passed options, favoring passed options', () => {
132
-    const theStyle = {
133
-      bazz: 123,
134
-      inner: {
135
-        foo: 'bar'
136
-      },
137
-      opt: 'exists only in static'
138
-    };
139
-    const MyComponent = class CoolComponent extends React.Component {
140
-      static get options() {
141
-        return theStyle;
142
-      }
143
-    };
88
+    when(mockedStore.getComponentClassForName('theComponentName')).thenReturn(
89
+      () =>
90
+        class extends React.Component {
91
+          static options() {
92
+            return {
93
+              bazz: 123,
94
+              inner: { foo: 'this gets overriden' },
95
+              opt: 'exists only in static'
96
+            };
97
+          }
98
+        }
99
+    );
144
 
100
 
145
-    const passedOptions = {
146
-      aaa: 'exists only in passed',
147
-      bazz: 789,
148
-      inner: {
149
-        foo: 'this is overriden'
150
-      }
101
+    const node = {
102
+      id: 'testId',
103
+      type: LayoutType.Component,
104
+      data: {
105
+        name: 'theComponentName',
106
+        options: {
107
+          aaa: 'exists only in passed',
108
+          bazz: 789,
109
+          inner: { foo: 'this should override same keys' }
110
+        }
111
+      },
112
+      children: []
151
     };
113
     };
152
 
114
 
153
-    const node = { type: LayoutType.Component, data: { name: 'theComponentName', options: passedOptions }, children: [] };
154
-    store.setComponentClassForName('theComponentName', () => MyComponent);
155
-
156
     uut.crawl(node);
115
     uut.crawl(node);
157
 
116
 
158
     expect(node.data.options).toEqual({
117
     expect(node.data.options).toEqual({
159
       aaa: 'exists only in passed',
118
       aaa: 'exists only in passed',
160
       bazz: 789,
119
       bazz: 789,
161
-      inner: {
162
-        foo: 'this is overriden'
163
-      },
120
+      inner: { foo: 'this should override same keys' },
164
       opt: 'exists only in static'
121
       opt: 'exists only in static'
165
     });
122
     });
166
   });
123
   });
167
 
124
 
168
-  it('Component: deepClones options', () => {
169
-    const theStyle = {};
170
-    const MyComponent = class CoolComponent extends React.Component {
171
-      static get options() {
172
-        return theStyle;
173
-      }
174
-    };
175
-
176
-    const node = { type: LayoutType.Component, data: { name: 'theComponentName', options: {} }, children: [] };
177
-    store.setComponentClassForName('theComponentName', () => MyComponent);
178
-    uut.crawl(node);
179
-    expect(node.data.options).not.toBe(theStyle);
180
-  });
181
-
182
   it('Components: must contain data name', () => {
125
   it('Components: must contain data name', () => {
183
-    const node = { type: LayoutType.Component, data: {}, children: [] };
126
+    const node = { type: LayoutType.Component, data: {}, children: [], id: 'testId' };
184
     expect(() => uut.crawl(node)).toThrowError('Missing component data.name');
127
     expect(() => uut.crawl(node)).toThrowError('Missing component data.name');
185
   });
128
   });
186
 
129
 
187
   it('Components: options default obj', () => {
130
   it('Components: options default obj', () => {
188
-    const MyComponent = class extends React.Component { };
131
+    when(mockedStore.getComponentClassForName('theComponentName')).thenReturn(
132
+      () => class extends React.Component {}
133
+    );
189
 
134
 
190
-    const node = { type: LayoutType.Component, data: { name: 'theComponentName', options: {} }, children: [] };
191
-    store.setComponentClassForName('theComponentName', () => MyComponent);
135
+    const node = {
136
+      id: 'testId',
137
+      type: LayoutType.Component,
138
+      data: { name: 'theComponentName', options: {} },
139
+      children: []
140
+    };
192
     uut.crawl(node);
141
     uut.crawl(node);
193
     expect(node.data.options).toEqual({});
142
     expect(node.data.options).toEqual({});
194
   });
143
   });
195
 
144
 
196
   it('Components: omits passProps after processing so they are not passed over the bridge', () => {
145
   it('Components: omits passProps after processing so they are not passed over the bridge', () => {
197
     const node = {
146
     const node = {
147
+      id: 'testId',
198
       type: LayoutType.Component,
148
       type: LayoutType.Component,
199
       data: {
149
       data: {
200
         name: 'compName',
150
         name: 'compName',
201
-        passProps: {}
151
+        passProps: { someProp: 'here' }
202
       },
152
       },
203
       children: []
153
       children: []
204
     };
154
     };
205
     uut.crawl(node);
155
     uut.crawl(node);
206
     expect(node.data.passProps).toBeUndefined();
156
     expect(node.data.passProps).toBeUndefined();
207
   });
157
   });
208
-
209
-  describe('LayoutNode', () => {
210
-    it('convertable from same data structure', () => {
211
-      const x = {
212
-        id: 'theId',
213
-        type: LayoutType.Component,
214
-        data: {},
215
-        children: []
216
-      };
217
-
218
-      let got;
219
-      function expectingLayoutNode(param: LayoutNode) {
220
-        got = param;
221
-      }
222
-      expectingLayoutNode(x);
223
-
224
-      expect(got).toBe(x);
225
-    });
226
-  });
227
 });
158
 });

+ 28
- 21
lib/src/commands/LayoutTreeCrawler.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 { OptionsProcessor } from './OptionsProcessor';
3
 import { OptionsProcessor } from './OptionsProcessor';
4
-import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
4
+import { Store } from '../components/Store';
5
+import { Options } from '../interfaces/Options';
5
 
6
 
6
 export interface Data {
7
 export interface Data {
7
   name?: string;
8
   name?: string;
9
   passProps?: any;
10
   passProps?: any;
10
 }
11
 }
11
 export interface LayoutNode {
12
 export interface LayoutNode {
12
-  id?: string;
13
+  id: string;
13
   type: LayoutType;
14
   type: LayoutType;
14
   data: Data;
15
   data: Data;
15
   children: LayoutNode[];
16
   children: LayoutNode[];
16
 }
17
 }
17
 
18
 
19
+type ComponentWithOptions = React.ComponentType<any> & { options(passProps: any): Options };
20
+
18
 export class LayoutTreeCrawler {
21
 export class LayoutTreeCrawler {
19
-  constructor(
20
-    private readonly uniqueIdProvider: UniqueIdProvider,
21
-    public readonly store: any,
22
-    private readonly optionsProcessor: OptionsProcessor
23
-  ) {
22
+  constructor(public readonly store: Store, private readonly optionsProcessor: OptionsProcessor) {
24
     this.crawl = this.crawl.bind(this);
23
     this.crawl = this.crawl.bind(this);
25
   }
24
   }
26
 
25
 
27
   crawl(node: LayoutNode): void {
26
   crawl(node: LayoutNode): void {
28
-    node.id = node.id || this.uniqueIdProvider.generate(node.type);
29
     if (node.type === LayoutType.Component) {
27
     if (node.type === LayoutType.Component) {
30
-      this._handleComponent(node);
28
+      this.handleComponent(node);
31
     }
29
     }
32
     this.optionsProcessor.processOptions(node.data.options);
30
     this.optionsProcessor.processOptions(node.data.options);
33
-    _.forEach(node.children, this.crawl);
31
+    node.children.forEach(this.crawl);
34
   }
32
   }
35
 
33
 
36
-  _handleComponent(node) {
37
-    this._assertComponentDataName(node);
38
-    this._savePropsToStore(node);
39
-    this._applyStaticOptions(node);
34
+  private handleComponent(node: LayoutNode) {
35
+    this.assertComponentDataName(node);
36
+    this.savePropsToStore(node);
37
+    this.applyStaticOptions(node);
40
     node.data.passProps = undefined;
38
     node.data.passProps = undefined;
41
   }
39
   }
42
 
40
 
43
-  _savePropsToStore(node) {
41
+  private savePropsToStore(node: LayoutNode) {
44
     this.store.setPropsForId(node.id, node.data.passProps);
42
     this.store.setPropsForId(node.id, node.data.passProps);
45
   }
43
   }
46
 
44
 
47
-  _applyStaticOptions(node) {
48
-    const clazz = this.store.getComponentClassForName(node.data.name) ? this.store.getComponentClassForName(node.data.name)() : {};
49
-    const staticOptions = _.isFunction(clazz.options) ? clazz.options(node.data.passProps || {}) : (_.cloneDeep(clazz.options) || {});
50
-    const passedOptions = node.data.options || {};
51
-    node.data.options = _.merge({}, staticOptions, passedOptions);
45
+  private isComponentWithOptions(component: any): component is ComponentWithOptions {
46
+    return (component as ComponentWithOptions).options !== undefined;
47
+  }
48
+
49
+  private staticOptionsIfPossible(node: LayoutNode) {
50
+    const foundReactGenerator = this.store.getComponentClassForName(node.data.name!);
51
+    const reactComponent = foundReactGenerator ? foundReactGenerator() : undefined;
52
+    return reactComponent && this.isComponentWithOptions(reactComponent)
53
+      ? reactComponent.options(node.data.passProps || {})
54
+      : {};
55
+  }
56
+
57
+  private applyStaticOptions(node: LayoutNode) {
58
+    node.data.options = _.merge({}, this.staticOptionsIfPossible(node), node.data.options);
52
   }
59
   }
53
 
60
 
54
-  _assertComponentDataName(component) {
61
+  private assertComponentDataName(component: LayoutNode) {
55
     if (!component.data.name) {
62
     if (!component.data.name) {
56
       throw new Error('Missing component data.name');
63
       throw new Error('Missing component data.name');
57
     }
64
     }

+ 39
- 29
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';
4
 import { Layout } from '../interfaces/Layout';
5
 import { OptionsSplitView } from '../interfaces/Options';
5
 import { OptionsSplitView } from '../interfaces/Options';
6
+import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
7
+import { mock, instance, when, anything } from 'ts-mockito';
6
 
8
 
7
 describe('LayoutTreeParser', () => {
9
 describe('LayoutTreeParser', () => {
8
   let uut: LayoutTreeParser;
10
   let uut: LayoutTreeParser;
11
+  let mockedUniqueIdProvider: UniqueIdProvider;
9
 
12
 
10
   beforeEach(() => {
13
   beforeEach(() => {
11
-    uut = new LayoutTreeParser();
14
+    mockedUniqueIdProvider = mock(UniqueIdProvider);
15
+    when(mockedUniqueIdProvider.generate(anything())).thenReturn('myUniqueId');
16
+    uut = new LayoutTreeParser(instance(mockedUniqueIdProvider));
12
   });
17
   });
13
 
18
 
14
   describe('parses into { type, data, children }', () => {
19
   describe('parses into { type, data, children }', () => {
18
 
23
 
19
     it('single component', () => {
24
     it('single component', () => {
20
       expect(uut.parse(LayoutExamples.singleComponent)).toEqual({
25
       expect(uut.parse(LayoutExamples.singleComponent)).toEqual({
26
+        id: 'myUniqueId',
21
         type: LayoutType.Component,
27
         type: LayoutType.Component,
22
-        data: { name: 'MyReactComponent', options: LayoutExamples.options, passProps: LayoutExamples.passProps },
28
+        data: {
29
+          name: 'MyReactComponent',
30
+          options: LayoutExamples.options,
31
+          passProps: LayoutExamples.passProps
32
+        },
23
         children: []
33
         children: []
24
       });
34
       });
25
     });
35
     });
26
 
36
 
27
     it('external component', () => {
37
     it('external component', () => {
28
       expect(uut.parse(LayoutExamples.externalComponent)).toEqual({
38
       expect(uut.parse(LayoutExamples.externalComponent)).toEqual({
39
+        id: 'myUniqueId',
29
         type: LayoutType.ExternalComponent,
40
         type: LayoutType.ExternalComponent,
30
-        data: { name: 'MyReactComponent', options: LayoutExamples.options, passProps: LayoutExamples.passProps },
41
+        data: {
42
+          name: 'MyReactComponent',
43
+          options: LayoutExamples.options,
44
+          passProps: LayoutExamples.passProps
45
+        },
31
         children: []
46
         children: []
32
       });
47
       });
33
     });
48
     });
40
         }
55
         }
41
       });
56
       });
42
       expect(result).toEqual({
57
       expect(result).toEqual({
58
+        id: 'myUniqueId',
43
         type: LayoutType.Component,
59
         type: LayoutType.Component,
44
         data: { name: 'MyScreen', passProps: LayoutExamples.passProps },
60
         data: { name: 'MyScreen', passProps: LayoutExamples.passProps },
45
         children: []
61
         children: []
49
 
65
 
50
     it('stack of components with top bar', () => {
66
     it('stack of components with top bar', () => {
51
       expect(uut.parse(LayoutExamples.stackWithTopBar)).toEqual({
67
       expect(uut.parse(LayoutExamples.stackWithTopBar)).toEqual({
68
+        id: 'myUniqueId',
52
         type: LayoutType.Stack,
69
         type: LayoutType.Stack,
53
         data: {
70
         data: {
54
           options: LayoutExamples.options
71
           options: LayoutExamples.options
55
         },
72
         },
56
         children: [
73
         children: [
57
           {
74
           {
75
+            id: 'myUniqueId',
58
             type: LayoutType.Component,
76
             type: LayoutType.Component,
59
             data: { name: 'MyReactComponent1' },
77
             data: { name: 'MyReactComponent1' },
60
             children: []
78
             children: []
61
           },
79
           },
62
           {
80
           {
81
+            id: 'myUniqueId',
63
             type: LayoutType.Component,
82
             type: LayoutType.Component,
64
             data: { name: 'MyReactComponent2', options: LayoutExamples.options },
83
             data: { name: 'MyReactComponent2', options: LayoutExamples.options },
65
             children: []
84
             children: []
71
     it('bottom tabs', () => {
90
     it('bottom tabs', () => {
72
       const result = uut.parse(LayoutExamples.bottomTabs);
91
       const result = uut.parse(LayoutExamples.bottomTabs);
73
       expect(_.keys(result)).toEqual(['id', 'type', 'data', 'children']);
92
       expect(_.keys(result)).toEqual(['id', 'type', 'data', 'children']);
74
-      expect(result.id).toEqual(undefined);
93
+      expect(result.id).toEqual('myUniqueId');
75
       expect(result.type).toEqual(LayoutType.BottomTabs);
94
       expect(result.type).toEqual(LayoutType.BottomTabs);
76
       expect(result.data).toEqual({});
95
       expect(result.data).toEqual({});
77
       expect(result.children.length).toEqual(3);
96
       expect(result.children.length).toEqual(3);
83
     it('side menus', () => {
102
     it('side menus', () => {
84
       const result = uut.parse(LayoutExamples.sideMenu);
103
       const result = uut.parse(LayoutExamples.sideMenu);
85
       expect(_.keys(result)).toEqual(['id', 'type', 'data', 'children']);
104
       expect(_.keys(result)).toEqual(['id', 'type', 'data', 'children']);
86
-      expect(result.id).toEqual(undefined);
105
+      expect(result.id).toEqual('myUniqueId');
87
       expect(result.type).toEqual(LayoutType.SideMenuRoot);
106
       expect(result.type).toEqual(LayoutType.SideMenuRoot);
88
       expect(result.data).toEqual({});
107
       expect(result.data).toEqual({});
89
       expect(result.children.length).toEqual(3);
108
       expect(result.children.length).toEqual(3);
101
     it('top tabs', () => {
120
     it('top tabs', () => {
102
       const result = uut.parse(LayoutExamples.topTabs);
121
       const result = uut.parse(LayoutExamples.topTabs);
103
       expect(_.keys(result)).toEqual(['id', 'type', 'data', 'children']);
122
       expect(_.keys(result)).toEqual(['id', 'type', 'data', 'children']);
104
-      expect(result.id).toEqual(undefined);
123
+      expect(result.id).toEqual('myUniqueId');
105
       expect(result.type).toEqual(LayoutType.TopTabs);
124
       expect(result.type).toEqual(LayoutType.TopTabs);
106
       expect(result.data).toEqual({ options: LayoutExamples.options });
125
       expect(result.data).toEqual({ options: LayoutExamples.options });
107
       expect(result.children.length).toEqual(5);
126
       expect(result.children.length).toEqual(5);
136
     expect(uut.parse({ stack: { options } }).data.options).toBe(options);
155
     expect(uut.parse({ stack: { options } }).data.options).toBe(options);
137
     expect(uut.parse({ bottomTabs: { options } }).data.options).toBe(options);
156
     expect(uut.parse({ bottomTabs: { options } }).data.options).toBe(options);
138
     expect(uut.parse({ topTabs: { options } }).data.options).toBe(options);
157
     expect(uut.parse({ topTabs: { options } }).data.options).toBe(options);
139
-    expect(uut.parse({ sideMenu: { options, center: { component: {name: 'lool'} } } }).data.options).toBe(options);
158
+    expect(
159
+      uut.parse({ sideMenu: { options, center: { component: { name: 'lool' } } } }).data.options
160
+    ).toBe(options);
140
     expect(uut.parse(LayoutExamples.splitView).data.options).toBe(optionsSplitView);
161
     expect(uut.parse(LayoutExamples.splitView).data.options).toBe(optionsSplitView);
141
   });
162
   });
142
 
163
 
146
     expect(uut.parse({ stack: { id: 'stackId' } }).id).toEqual('stackId');
167
     expect(uut.parse({ stack: { id: 'stackId' } }).id).toEqual('stackId');
147
     expect(uut.parse({ stack: { children: [{ component }] } }).children[0].id).toEqual('compId');
168
     expect(uut.parse({ stack: { children: [{ component }] } }).children[0].id).toEqual('compId');
148
     expect(uut.parse({ bottomTabs: { id: 'myId' } }).id).toEqual('myId');
169
     expect(uut.parse({ bottomTabs: { id: 'myId' } }).id).toEqual('myId');
149
-    expect(uut.parse({ bottomTabs: { children: [{ component }] } }).children[0].id).toEqual('compId');
170
+    expect(uut.parse({ bottomTabs: { children: [{ component }] } }).children[0].id).toEqual(
171
+      'compId'
172
+    );
150
     expect(uut.parse({ topTabs: { id: 'myId' } }).id).toEqual('myId');
173
     expect(uut.parse({ topTabs: { id: 'myId' } }).id).toEqual('myId');
151
     expect(uut.parse({ topTabs: { children: [{ component }] } }).children[0].id).toEqual('compId');
174
     expect(uut.parse({ topTabs: { children: [{ component }] } }).children[0].id).toEqual('compId');
152
     expect(uut.parse({ sideMenu: { id: 'myId', center: { component } } }).id).toEqual('myId');
175
     expect(uut.parse({ sideMenu: { id: 'myId', center: { component } } }).id).toEqual('myId');
174
   displayMode: 'auto',
197
   displayMode: 'auto',
175
   primaryEdge: 'leading',
198
   primaryEdge: 'leading',
176
   minWidth: 150,
199
   minWidth: 150,
177
-  maxWidth: 300,
200
+  maxWidth: 300
178
 };
201
 };
179
 
202
 
180
 const singleComponent = {
203
 const singleComponent = {
236
 
259
 
237
 const topTabs = {
260
 const topTabs = {
238
   topTabs: {
261
   topTabs: {
239
-    children: [
240
-      singleComponent,
241
-      singleComponent,
242
-      singleComponent,
243
-      singleComponent,
244
-      stackWithTopBar
245
-    ],
262
+    children: [singleComponent, singleComponent, singleComponent, singleComponent, stackWithTopBar],
246
     options
263
     options
247
   }
264
   }
248
 };
265
 };
257
           stackWithTopBar,
274
           stackWithTopBar,
258
           {
275
           {
259
             stack: {
276
             stack: {
260
-              children: [
261
-                singleComponent,
262
-                singleComponent,
263
-                singleComponent,
264
-                singleComponent,
265
-              ]
277
+              children: [singleComponent, singleComponent, singleComponent, singleComponent]
266
             }
278
             }
267
           }
279
           }
268
         ]
280
         ]
275
   splitView: {
287
   splitView: {
276
     master: {
288
     master: {
277
       stack: {
289
       stack: {
278
-        children: [
279
-          singleComponent,
280
-        ],
290
+        children: [singleComponent],
281
         options
291
         options
282
-      },
292
+      }
283
     },
293
     },
284
     detail: stackWithTopBar,
294
     detail: stackWithTopBar,
285
-    options: optionsSplitView,
286
-  },
295
+    options: optionsSplitView
296
+  }
287
 };
297
 };
288
 
298
 
289
 const LayoutExamples = {
299
 const LayoutExamples = {

+ 18
- 11
lib/src/commands/LayoutTreeParser.ts View File

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

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

1
 import { OptionsProcessor } from './OptionsProcessor';
1
 import { OptionsProcessor } from './OptionsProcessor';
2
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
2
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
3
 import { Store } from '../components/Store';
3
 import { Store } from '../components/Store';
4
-import * as _ from 'lodash';
5
 import { Options, OptionsModalPresentationStyle } from '../interfaces/Options';
4
 import { Options, OptionsModalPresentationStyle } from '../interfaces/Options';
6
 import { mock, when, anyString, instance, anyNumber, verify } from 'ts-mockito';
5
 import { mock, when, anyString, instance, anyNumber, verify } from 'ts-mockito';
7
 import { ColorService } from '../adapters/ColorService';
6
 import { ColorService } from '../adapters/ColorService';

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

159
 
159
 
160
   describe(`register with redux store`, () => {
160
   describe(`register with redux store`, () => {
161
     class MyReduxComp extends React.Component<any> {
161
     class MyReduxComp extends React.Component<any> {
162
-      static get options() {
162
+      static options() {
163
         return { foo: 123 };
163
         return { foo: 123 };
164
       }
164
       }
165
       render() {
165
       render() {
185
       const NavigationComponent = uut.wrap(componentName, () => ConnectedComp, store, componentEventsObserver, undefined, ReduxProvider, reduxStore);
185
       const NavigationComponent = uut.wrap(componentName, () => ConnectedComp, store, componentEventsObserver, undefined, ReduxProvider, reduxStore);
186
       const tree = renderer.create(<NavigationComponent componentId={'theCompId'} />);
186
       const tree = renderer.create(<NavigationComponent componentId={'theCompId'} />);
187
       expect(tree.toJSON()!.children).toEqual(['it just works']);
187
       expect(tree.toJSON()!.children).toEqual(['it just works']);
188
-      expect((NavigationComponent as any).options).toEqual({ foo: 123 });
188
+      expect((NavigationComponent as any).options()).toEqual({ foo: 123 });
189
     });
189
     });
190
   });
190
   });
191
 
191
 

+ 8
- 8
lib/src/components/Store.ts View File

1
-import * as React from 'react';
2
 import * as _ from 'lodash';
1
 import * as _ from 'lodash';
2
+import { ComponentProvider } from 'react-native';
3
 
3
 
4
 export class Store {
4
 export class Store {
5
-  private componentsByName: Record<string, () => React.ComponentClass<any, any>> = {};
5
+  private componentsByName: Record<string, ComponentProvider> = {};
6
   private propsById: Record<string, any> = {};
6
   private propsById: Record<string, any> = {};
7
 
7
 
8
   setPropsForId(componentId: string, props: any) {
8
   setPropsForId(componentId: string, props: any) {
13
     return _.get(this.propsById, componentId, {});
13
     return _.get(this.propsById, componentId, {});
14
   }
14
   }
15
 
15
 
16
-  setComponentClassForName(componentName: string | number, ComponentClass: () => React.ComponentClass<any, any>) {
17
-    _.set(this.componentsByName, componentName.toString(), ComponentClass);
16
+  cleanId(componentId: string) {
17
+    _.unset(this.propsById, componentId);
18
   }
18
   }
19
 
19
 
20
-  getComponentClassForName(componentName: string | number) {
21
-    return _.get(this.componentsByName, componentName.toString());
20
+  setComponentClassForName(componentName: string | number, ComponentClass: ComponentProvider) {
21
+    _.set(this.componentsByName, componentName.toString(), ComponentClass);
22
   }
22
   }
23
 
23
 
24
-  cleanId(componentId: string) {
25
-    _.unset(this.propsById, componentId);
24
+  getComponentClassForName(componentName: string | number): ComponentProvider | undefined {
25
+    return _.get(this.componentsByName, componentName.toString());
26
   }
26
   }
27
 }
27
 }

+ 2
- 2
lib/src/events/CommandsObserver.ts View File

1
 import { EventSubscription } from '../interfaces/EventSubscription';
1
 import { EventSubscription } from '../interfaces/EventSubscription';
2
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
2
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
3
 
3
 
4
-export type CommandsListener = (name: string, params: any) => void;
4
+export type CommandsListener = (name: string, params: Record<string, any>) => void;
5
 
5
 
6
 export class CommandsObserver {
6
 export class CommandsObserver {
7
   private listeners: Record<string, CommandsListener> = {};
7
   private listeners: Record<string, CommandsListener> = {};
15
     };
15
     };
16
   }
16
   }
17
 
17
 
18
-  public notify(commandName: string, params: any): void {
18
+  public notify(commandName: string, params: Record<string, any>): void {
19
     Object.values(this.listeners).forEach((listener) => listener(commandName, params));
19
     Object.values(this.listeners).forEach((listener) => listener(commandName, params));
20
   }
20
   }
21
 }
21
 }

+ 4
- 2
lib/src/events/ComponentEventsObserver.ts View File

12
 } from '../interfaces/ComponentEvents';
12
 } from '../interfaces/ComponentEvents';
13
 import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver';
13
 import { NativeEventsReceiver } from '../adapters/NativeEventsReceiver';
14
 
14
 
15
+type ReactComponentWithIndexing = React.Component<any> & Record<string, any>;
16
+
15
 export class ComponentEventsObserver {
17
 export class ComponentEventsObserver {
16
-  private readonly listeners = {};
18
+  private listeners: Record<string, Record<string, ReactComponentWithIndexing>> = {};
17
   private alreadyRegistered = false;
19
   private alreadyRegistered = false;
18
 
20
 
19
   constructor(private readonly nativeEventsReceiver: NativeEventsReceiver) {
21
   constructor(private readonly nativeEventsReceiver: NativeEventsReceiver) {
87
 
89
 
88
   private triggerOnAllListenersByComponentId(event: ComponentEvent, method: string) {
90
   private triggerOnAllListenersByComponentId(event: ComponentEvent, method: string) {
89
     _.forEach(this.listeners[event.componentId], (component) => {
91
     _.forEach(this.listeners[event.componentId], (component) => {
90
-      if (_.isObject(component) && _.isFunction(component[method])) {
92
+      if (component[method]) {
91
         component[method](event);
93
         component[method](event);
92
       }
94
       }
93
     });
95
     });

+ 1
- 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
+import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
5
 
5
 
6
 describe('EventsRegistry', () => {
6
 describe('EventsRegistry', () => {
7
   let uut: EventsRegistry;
7
   let uut: EventsRegistry;

+ 1
- 1
playground/src/screens/BackHandlerModalScreen.js View File

4
 const { View, Text, Button, BackHandler } = require('react-native');
4
 const { View, Text, Button, BackHandler } = require('react-native');
5
 
5
 
6
 class BackHandlerModalScreen extends Component {
6
 class BackHandlerModalScreen extends Component {
7
-  static get options() {
7
+  static options() {
8
     return {
8
     return {
9
       topBar: {
9
       topBar: {
10
         title: {
10
         title: {

+ 1
- 1
playground/src/screens/BackHandlerScreen.js View File

4
 const { View, Text, Button, BackHandler } = require('react-native');
4
 const { View, Text, Button, BackHandler } = require('react-native');
5
 
5
 
6
 class BackHandlerScreen extends Component {
6
 class BackHandlerScreen extends Component {
7
-  static get options() {
7
+  static options() {
8
     return {
8
     return {
9
       topBar: {
9
       topBar: {
10
         title: {
10
         title: {

+ 1
- 1
playground/src/screens/CustomDialog.js View File

7
 const testIDs = require('../testIDs');
7
 const testIDs = require('../testIDs');
8
 
8
 
9
 class CustomDialog extends PureComponent {
9
 class CustomDialog extends PureComponent {
10
-  static get options() {
10
+  static options() {
11
     return {
11
     return {
12
       statusBarBackgroundColor: 'green'
12
       statusBarBackgroundColor: 'green'
13
     };
13
     };

+ 1
- 1
playground/src/screens/CustomTransitionDestination.js View File

11
     this.push = this.push.bind(this);
11
     this.push = this.push.bind(this);
12
   }
12
   }
13
 
13
 
14
-  static get options() {
14
+  static options() {
15
     return {
15
     return {
16
       topBar: {
16
       topBar: {
17
         title: {
17
         title: {

+ 1
- 1
playground/src/screens/CustomTransitionOrigin.js View File

8
     super(props);
8
     super(props);
9
     this.onClickNavigationIcon = this.onClickNavigationIcon.bind(this);
9
     this.onClickNavigationIcon = this.onClickNavigationIcon.bind(this);
10
   }
10
   }
11
-  static get options() {
11
+  static options() {
12
     return {
12
     return {
13
       topBar: {
13
       topBar: {
14
         title: {
14
         title: {

+ 1
- 1
playground/src/screens/KeyboardScreen.js View File

22
                     'dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
22
                     'dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
23
 
23
 
24
 class KeyboardScreen extends Component {
24
 class KeyboardScreen extends Component {
25
-  static get options() {
25
+  static options() {
26
     return {
26
     return {
27
       bottomTabs: {
27
       bottomTabs: {
28
         drawBehind: true,
28
         drawBehind: true,

+ 1
- 1
playground/src/screens/ModalScreen.js View File

9
 const testIDs = require('../testIDs');
9
 const testIDs = require('../testIDs');
10
 
10
 
11
 class ModalScreen extends Component {
11
 class ModalScreen extends Component {
12
-  static get options() {
12
+  static options() {
13
     return {
13
     return {
14
       statusBar: {
14
       statusBar: {
15
         visible: false,
15
         visible: false,

+ 1
- 1
playground/src/screens/OptionsScreen.js View File

20
     Navigation.events().bindComponent(this);
20
     Navigation.events().bindComponent(this);
21
   }
21
   }
22
 
22
 
23
-  static get options() {
23
+  static options() {
24
     return {
24
     return {
25
       statusBar: {
25
       statusBar: {
26
         style: 'dark',
26
         style: 'dark',

+ 1
- 1
playground/src/screens/PushedScreen.js View File

7
 const testIDs = require('../testIDs');
7
 const testIDs = require('../testIDs');
8
 
8
 
9
 class PushedScreen extends Component {
9
 class PushedScreen extends Component {
10
-  static get options() {
10
+  static options() {
11
     return {
11
     return {
12
       _statusBar: {
12
       _statusBar: {
13
         visible: false,
13
         visible: false,

+ 1
- 1
playground/src/screens/ScrollViewScreen.js View File

9
 const FAB = 'fab';
9
 const FAB = 'fab';
10
 
10
 
11
 class ScrollViewScreen extends Component {
11
 class ScrollViewScreen extends Component {
12
-  static get options() {
12
+  static options() {
13
     return {
13
     return {
14
       topBar: {
14
       topBar: {
15
         title: {
15
         title: {

+ 1
- 1
playground/src/screens/SearchScreen.js View File

20
 }
20
 }
21
 
21
 
22
 class SearchControllerScreen extends Component {
22
 class SearchControllerScreen extends Component {
23
-  static get options() {
23
+  static options() {
24
     return {
24
     return {
25
       topBar: {
25
       topBar: {
26
         title: {
26
         title: {

+ 1
- 1
playground/src/screens/TextScreen.js View File

7
 const testIDs = require('../testIDs');
7
 const testIDs = require('../testIDs');
8
 const Bounds = require('../components/Bounds');
8
 const Bounds = require('../components/Bounds');
9
 class TextScreen extends Component {
9
 class TextScreen extends Component {
10
-  static get options() {
10
+  static options() {
11
     return {
11
     return {
12
       bottomTabs: {
12
       bottomTabs: {
13
         drawBehind: true,
13
         drawBehind: true,

+ 1
- 1
playground/src/screens/TopTabOptionsScreen.js View File

5
 const { Navigation } = require('react-native-navigation');
5
 const { Navigation } = require('react-native-navigation');
6
 
6
 
7
 class TopTabOptionsScreen extends PureComponent {
7
 class TopTabOptionsScreen extends PureComponent {
8
-  static get options() {
8
+  static options() {
9
     return {
9
     return {
10
       topBar: {
10
       topBar: {
11
         title: {
11
         title: {

+ 1
- 1
playground/src/screens/TopTabScreen.js View File

5
 const FAB = 'fab';
5
 const FAB = 'fab';
6
 
6
 
7
 class TopTabScreen extends PureComponent {
7
 class TopTabScreen extends PureComponent {
8
-  static get options() {
8
+  static options() {
9
     return {
9
     return {
10
       topBar: {
10
       topBar: {
11
         title: {
11
         title: {

+ 1
- 1
playground/src/screens/WelcomeScreen.js View File

8
 const { Navigation } = require('react-native-navigation');
8
 const { Navigation } = require('react-native-navigation');
9
 
9
 
10
 class WelcomeScreen extends Component {
10
 class WelcomeScreen extends Component {
11
-  static get options() {
11
+  static options() {
12
     return {
12
     return {
13
       _statusBar: {
13
       _statusBar: {
14
         backgroundColor: 'transparent',
14
         backgroundColor: 'transparent',

+ 1
- 1
playground/src/screens/complexlayouts/BottomTabSideMenuScreen.js View File

5
 const testIDs = require('../../testIDs');
5
 const testIDs = require('../../testIDs');
6
 
6
 
7
 class BottomTabSideMenuScreen extends Component {
7
 class BottomTabSideMenuScreen extends Component {
8
-  static get options() {
8
+  static options() {
9
     return {
9
     return {
10
       topBar: {
10
       topBar: {
11
         title: {
11
         title: {

+ 1
- 6
tsconfig-strict.json View File

4
     "allowSyntheticDefaultImports": false,
4
     "allowSyntheticDefaultImports": false,
5
     "allowUnreachableCode": false,
5
     "allowUnreachableCode": false,
6
     "allowUnusedLabels": false,
6
     "allowUnusedLabels": false,
7
-    "alwaysStrict": true,
8
     "diagnostics": true,
7
     "diagnostics": true,
9
     "forceConsistentCasingInFileNames": true,
8
     "forceConsistentCasingInFileNames": true,
10
     "importHelpers": true,
9
     "importHelpers": true,
11
     "noEmitOnError": true,
10
     "noEmitOnError": true,
12
     "noFallthroughCasesInSwitch": true,
11
     "noFallthroughCasesInSwitch": true,
13
     "noImplicitReturns": true,
12
     "noImplicitReturns": true,
14
-    "noImplicitThis": true,
15
     "noUnusedLocals": true,
13
     "noUnusedLocals": true,
16
     "noUnusedParameters": true,
14
     "noUnusedParameters": true,
17
     "pretty": true,
15
     "pretty": true,
18
-    "strictFunctionTypes": true,
19
-    "strictNullChecks": true,
20
-    "strict": true,
21
-    "noImplicitAny": false
16
+    "strict": true
22
   }
17
   }
23
 }
18
 }