Browse Source

Refactor options processor v2 (#4492)

## Goal of this PR
The goal of this PR is to make `OptionsProcessor.ts` great again 🇺🇸 . To me it was total mess including tests. 

## What was wrong or funky?
1. the tests tested if React Native's `processColor` color works as it should even though react native has [multiple tests for this](https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/__tests__/processColor-test.js). Aka we don't have to test React Native's functions :D
2. `LayoutTreeCrawler.test.ts` was testing `OptionsProcessor`s functionality so it was removed now
3. Lots of small things since this was done before putting `noImplicitAny` to true so it wasn't made TypeScript in mind

## Result
Now `OptionsProcessor.ts` should be
1. code way more clear
2. tests are testing only things that they should
3. tests are written in clear way
4. it is easy to see from the tests what the whole function does
5. type safe for TypeScript `noImplicitAny` (when we actually turn it on)
Henrik Raitasola 5 years ago
parent
commit
ee04610f6a

+ 13
- 2
lib/src/Navigation.ts View File

17
 import { LayoutRoot, Layout } from './interfaces/Layout';
17
 import { LayoutRoot, Layout } from './interfaces/Layout';
18
 import { Options } from './interfaces/Options';
18
 import { Options } from './interfaces/Options';
19
 import { ComponentWrapper } from './components/ComponentWrapper';
19
 import { ComponentWrapper } from './components/ComponentWrapper';
20
+import { OptionsProcessor } from './commands/OptionsProcessor';
21
+import { ColorService } from './adapters/ColorService';
22
+import { AssetService } from './adapters/AssetResolver';
20
 
23
 
21
 export class NavigationRoot {
24
 export class NavigationRoot {
22
   public readonly Element: React.ComponentType<{ elementId: any; resizeMode?: any; }>;
25
   public readonly Element: React.ComponentType<{ elementId: any; resizeMode?: any; }>;
44
     this.componentEventsObserver = new ComponentEventsObserver(this.nativeEventsReceiver);
47
     this.componentEventsObserver = new ComponentEventsObserver(this.nativeEventsReceiver);
45
     this.componentRegistry = new ComponentRegistry(this.store, this.componentEventsObserver);
48
     this.componentRegistry = new ComponentRegistry(this.store, this.componentEventsObserver);
46
     this.layoutTreeParser = new LayoutTreeParser();
49
     this.layoutTreeParser = new LayoutTreeParser();
47
-    this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store);
50
+    const optionsProcessor = new OptionsProcessor(this.store, this.uniqueIdProvider, new ColorService(), new AssetService());
51
+    this.layoutTreeCrawler = new LayoutTreeCrawler(this.uniqueIdProvider, this.store, optionsProcessor);
48
     this.nativeCommandsSender = new NativeCommandsSender();
52
     this.nativeCommandsSender = new NativeCommandsSender();
49
     this.commandsObserver = new CommandsObserver();
53
     this.commandsObserver = new CommandsObserver();
50
-    this.commands = new Commands(this.nativeCommandsSender, this.layoutTreeParser, this.layoutTreeCrawler, this.commandsObserver, this.uniqueIdProvider);
54
+    this.commands = new Commands(
55
+      this.nativeCommandsSender,
56
+      this.layoutTreeParser,
57
+      this.layoutTreeCrawler,
58
+      this.commandsObserver,
59
+      this.uniqueIdProvider,
60
+      optionsProcessor
61
+    );
51
     this.eventsRegistry = new EventsRegistry(this.nativeEventsReceiver, this.commandsObserver, this.componentEventsObserver);
62
     this.eventsRegistry = new EventsRegistry(this.nativeEventsReceiver, this.commandsObserver, this.componentEventsObserver);
52
 
63
 
53
     this.componentEventsObserver.registerOnceForAllComponentEvents();
64
     this.componentEventsObserver.registerOnceForAllComponentEvents();

+ 8
- 0
lib/src/adapters/AssetResolver.ts View File

1
+import * as resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
2
+import { ImageRequireSource } from 'react-native';
3
+
4
+export class AssetService {
5
+  resolveFromRequire(value: ImageRequireSource) {
6
+    return resolveAssetSource(value);
7
+  }
8
+}

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

1
+import { processColor } from 'react-native';
2
+
3
+export class ColorService {
4
+  toNativeColor(inputColor: string) {
5
+    return processColor(inputColor);
6
+  }
7
+}

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

8
 import { Commands } from './Commands';
8
 import { Commands } from './Commands';
9
 import { CommandsObserver } from '../events/CommandsObserver';
9
 import { CommandsObserver } from '../events/CommandsObserver';
10
 import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
10
 import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
11
+import { OptionsProcessor } from './OptionsProcessor';
11
 
12
 
12
 describe('Commands', () => {
13
 describe('Commands', () => {
13
   let uut: Commands;
14
   let uut: Commands;
22
     mockedNativeCommandsSender = mock(NativeCommandsSender);
23
     mockedNativeCommandsSender = mock(NativeCommandsSender);
23
     nativeCommandsSender = instance(mockedNativeCommandsSender);
24
     nativeCommandsSender = instance(mockedNativeCommandsSender);
24
 
25
 
26
+    const mockedOptionsProcessor = mock(OptionsProcessor);
27
+    const optionsProcessor = instance(mockedOptionsProcessor);
28
+
25
     uut = new Commands(
29
     uut = new Commands(
26
       nativeCommandsSender,
30
       nativeCommandsSender,
27
       new LayoutTreeParser(),
31
       new LayoutTreeParser(),
28
-      new LayoutTreeCrawler(new UniqueIdProvider(), store),
32
+      new LayoutTreeCrawler(new UniqueIdProvider(), store, optionsProcessor),
29
       commandsObserver,
33
       commandsObserver,
30
-      new UniqueIdProvider()
34
+      new UniqueIdProvider(),
35
+      optionsProcessor
31
     );
36
     );
32
   });
37
   });
33
 
38
 
353
       const mockParser = { parse: () => 'parsed' };
358
       const mockParser = { parse: () => 'parsed' };
354
       const mockCrawler = { crawl: (x: any) => x, processOptions: (x: any) => x };
359
       const mockCrawler = { crawl: (x: any) => x, processOptions: (x: any) => x };
355
       commandsObserver.register(cb);
360
       commandsObserver.register(cb);
356
-      uut = new Commands(mockedNativeCommandsSender, mockParser as any, mockCrawler as any, commandsObserver, new UniqueIdProvider());
361
+      const mockedOptionsProcessor = mock(OptionsProcessor);
362
+      const optionsProcessor = instance(mockedOptionsProcessor);
363
+      uut = new Commands(
364
+        mockedNativeCommandsSender,
365
+        mockParser as any,
366
+        mockCrawler as any,
367
+        commandsObserver,
368
+        new UniqueIdProvider(),
369
+        optionsProcessor
370
+      );
357
     });
371
     });
358
 
372
 
359
     function getAllMethodsOfUut() {
373
     function getAllMethodsOfUut() {

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

6
 import { Layout, LayoutRoot } from '../interfaces/Layout';
6
 import { Layout, LayoutRoot } from '../interfaces/Layout';
7
 import { LayoutTreeParser } from './LayoutTreeParser';
7
 import { LayoutTreeParser } from './LayoutTreeParser';
8
 import { LayoutTreeCrawler } from './LayoutTreeCrawler';
8
 import { LayoutTreeCrawler } from './LayoutTreeCrawler';
9
+import { OptionsProcessor } from './OptionsProcessor';
9
 
10
 
10
 export class Commands {
11
 export class Commands {
11
   constructor(
12
   constructor(
13
     private readonly layoutTreeParser: LayoutTreeParser,
14
     private readonly layoutTreeParser: LayoutTreeParser,
14
     private readonly layoutTreeCrawler: LayoutTreeCrawler,
15
     private readonly layoutTreeCrawler: LayoutTreeCrawler,
15
     private readonly commandsObserver: CommandsObserver,
16
     private readonly commandsObserver: CommandsObserver,
16
-    private readonly uniqueIdProvider: UniqueIdProvider) {
17
-  }
17
+    private readonly uniqueIdProvider: UniqueIdProvider,
18
+    private readonly optionsProcessor: OptionsProcessor
19
+  ) {}
18
 
20
 
19
   public setRoot(simpleApi: LayoutRoot) {
21
   public setRoot(simpleApi: LayoutRoot) {
20
     const input = _.cloneDeep(simpleApi);
22
     const input = _.cloneDeep(simpleApi);
41
 
43
 
42
   public setDefaultOptions(options: Options) {
44
   public setDefaultOptions(options: Options) {
43
     const input = _.cloneDeep(options);
45
     const input = _.cloneDeep(options);
44
-    this.layoutTreeCrawler.processOptions(input);
46
+    this.optionsProcessor.processOptions(input);
45
 
47
 
46
     this.nativeCommandsSender.setDefaultOptions(input);
48
     this.nativeCommandsSender.setDefaultOptions(input);
47
     this.commandsObserver.notify('setDefaultOptions', { options });
49
     this.commandsObserver.notify('setDefaultOptions', { options });
49
 
51
 
50
   public mergeOptions(componentId: string, options: Options) {
52
   public mergeOptions(componentId: string, options: Options) {
51
     const input = _.cloneDeep(options);
53
     const input = _.cloneDeep(options);
52
-    this.layoutTreeCrawler.processOptions(input);
54
+    this.optionsProcessor.processOptions(input);
53
 
55
 
54
     this.nativeCommandsSender.mergeOptions(componentId, input);
56
     this.nativeCommandsSender.mergeOptions(componentId, input);
55
     this.commandsObserver.notify('mergeOptions', { componentId, options });
57
     this.commandsObserver.notify('mergeOptions', { componentId, options });

+ 5
- 83
lib/src/commands/LayoutTreeCrawler.test.ts View File

4
 import { LayoutTreeCrawler, LayoutNode } from './LayoutTreeCrawler';
4
 import { LayoutTreeCrawler, LayoutNode } from './LayoutTreeCrawler';
5
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider.mock';
5
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider.mock';
6
 import { Store } from '../components/Store';
6
 import { Store } from '../components/Store';
7
+import { mock, instance } from 'ts-mockito';
8
+import { OptionsProcessor } from './OptionsProcessor';
7
 
9
 
8
 describe('LayoutTreeCrawler', () => {
10
 describe('LayoutTreeCrawler', () => {
9
   let uut: LayoutTreeCrawler;
11
   let uut: LayoutTreeCrawler;
11
 
13
 
12
   beforeEach(() => {
14
   beforeEach(() => {
13
     store = new Store();
15
     store = new Store();
14
-    uut = new LayoutTreeCrawler(new UniqueIdProvider(), store);
16
+    const mockedOptionsProcessor = mock(OptionsProcessor);
17
+    const optionsProcessor = instance(mockedOptionsProcessor);
18
+    uut = new LayoutTreeCrawler(new UniqueIdProvider(), store, optionsProcessor);
15
   });
19
   });
16
 
20
 
17
   it('crawls a layout tree and adds unique id to each node', () => {
21
   it('crawls a layout tree and adds unique id to each node', () => {
202
     expect(node.data.passProps).toBeUndefined();
206
     expect(node.data.passProps).toBeUndefined();
203
   });
207
   });
204
 
208
 
205
-  describe('navigation options', () => {
206
-    let options: Record<string, any>;
207
-    let node: LayoutNode;
208
-
209
-    beforeEach(() => {
210
-      options = {};
211
-      node = { type: LayoutType.Component, data: { name: 'theComponentName', options }, children: [] };
212
-    });
213
-
214
-    it('processes colors into numeric AARRGGBB', () => {
215
-      options.someKeyColor = 'red';
216
-      uut.crawl(node);
217
-      expect(node.data.options.someKeyColor).toEqual(0xffff0000);
218
-    });
219
-
220
-    it('processes colors into numeric AARRGGBB', () => {
221
-      options.someKeyColor = 'yellow';
222
-      uut.crawl(node);
223
-      expect(node.data.options.someKeyColor).toEqual(0xffffff00);
224
-    });
225
-
226
-    it('processes numeric colors', () => {
227
-      options.someKeyColor = '#123456';
228
-      uut.crawl(node);
229
-      expect(node.data.options.someKeyColor).toEqual(0xff123456);
230
-    });
231
-
232
-    it('processes numeric colors with rrggbbAA', () => {
233
-      options.someKeyColor = 0x123456ff; // wut
234
-      uut.crawl(node);
235
-      expect(node.data.options.someKeyColor).toEqual(0xff123456);
236
-    });
237
-
238
-    it('process colors with rgb functions', () => {
239
-      options.someKeyColor = 'rgb(255, 0, 255)';
240
-      uut.crawl(node);
241
-      expect(node.data.options.someKeyColor).toEqual(0xffff00ff);
242
-    });
243
-
244
-    it('process colors with special words', () => {
245
-      options.someKeyColor = 'fuchsia';
246
-      uut.crawl(node);
247
-      expect(node.data.options.someKeyColor).toEqual(0xffff00ff);
248
-    });
249
-
250
-    it('process colors with hsla functions', () => {
251
-      options.someKeyColor = 'hsla(360, 100%, 100%, 1.0)';
252
-      uut.crawl(node);
253
-      expect(node.data.options.someKeyColor).toEqual(0xffffffff);
254
-    });
255
-
256
-    it('unknown colors return undefined', () => {
257
-      options.someKeyColor = 'wut';
258
-      uut.crawl(node);
259
-      expect(node.data.options.someKeyColor).toEqual(undefined);
260
-    });
261
-
262
-    it('any keys ending with Color', () => {
263
-      options.otherKeyColor = 'red';
264
-      options.yetAnotherColor = 'blue';
265
-      options.andAnotherColor = 'rgb(0, 255, 0)';
266
-      uut.crawl(node);
267
-      expect(node.data.options.otherKeyColor).toEqual(0xffff0000);
268
-      expect(node.data.options.yetAnotherColor).toEqual(0xff0000ff);
269
-      expect(node.data.options.andAnotherColor).toEqual(0xff00ff00);
270
-    });
271
-
272
-    it('keys ending with Color case sensitive', () => {
273
-      options.otherKey_color = 'red'; // eslint-disable-line camelcase
274
-      uut.crawl(node);
275
-      expect(node.data.options.otherKey_color).toEqual('red');
276
-    });
277
-
278
-    it('any nested recursive keys ending with Color', () => {
279
-      options.innerObj = { theKeyColor: 'red' };
280
-      options.innerObj.innerMostObj = { anotherColor: 'yellow' };
281
-      uut.crawl(node);
282
-      expect(node.data.options.innerObj.theKeyColor).toEqual(0xffff0000);
283
-      expect(node.data.options.innerObj.innerMostObj.anotherColor).toEqual(0xffffff00);
284
-    });
285
-  });
286
-
287
   describe('LayoutNode', () => {
209
   describe('LayoutNode', () => {
288
     it('convertable from same data structure', () => {
210
     it('convertable from same data structure', () => {
289
       const x = {
211
       const x = {

+ 7
- 11
lib/src/commands/LayoutTreeCrawler.ts View File

1
 import * as _ from 'lodash';
1
 import * as _ from 'lodash';
2
-import { OptionsProcessor } from './OptionsProcessor';
3
 import { LayoutType } from './LayoutType';
2
 import { LayoutType } from './LayoutType';
3
+import { OptionsProcessor } from './OptionsProcessor';
4
+import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
4
 
5
 
5
 export interface Data {
6
 export interface Data {
6
   name?: string;
7
   name?: string;
15
 }
16
 }
16
 
17
 
17
 export class LayoutTreeCrawler {
18
 export class LayoutTreeCrawler {
18
-  private optionsProcessor: OptionsProcessor;
19
   constructor(
19
   constructor(
20
-    private readonly uniqueIdProvider: any,
21
-    public readonly store: any) {
20
+    private readonly uniqueIdProvider: UniqueIdProvider,
21
+    public readonly store: any,
22
+    private readonly optionsProcessor: OptionsProcessor
23
+  ) {
22
     this.crawl = this.crawl.bind(this);
24
     this.crawl = this.crawl.bind(this);
23
-    this.processOptions = this.processOptions.bind(this);
24
-    this.optionsProcessor = new OptionsProcessor(store, uniqueIdProvider);
25
   }
25
   }
26
 
26
 
27
   crawl(node: LayoutNode): void {
27
   crawl(node: LayoutNode): void {
29
     if (node.type === LayoutType.Component) {
29
     if (node.type === LayoutType.Component) {
30
       this._handleComponent(node);
30
       this._handleComponent(node);
31
     }
31
     }
32
-    this.processOptions(node.data.options);
32
+    this.optionsProcessor.processOptions(node.data.options);
33
     _.forEach(node.children, this.crawl);
33
     _.forEach(node.children, this.crawl);
34
   }
34
   }
35
 
35
 
36
-  processOptions(options) {
37
-    this.optionsProcessor.processOptions(options);
38
-  }
39
-
40
   _handleComponent(node) {
36
   _handleComponent(node) {
41
     this._assertComponentDataName(node);
37
     this._assertComponentDataName(node);
42
     this._savePropsToStore(node);
38
     this._savePropsToStore(node);

+ 81
- 160
lib/src/commands/OptionsProcessor.test.ts View File

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';
4
 import * as _ from 'lodash';
5
+import { Options, OptionsModalPresentationStyle } from '../interfaces/Options';
6
+import { mock, when, anyString, instance, anyNumber, verify } from 'ts-mockito';
7
+import { ColorService } from '../adapters/ColorService';
8
+import { AssetService } from '../adapters/AssetResolver';
5
 
9
 
6
 describe('navigation options', () => {
10
 describe('navigation options', () => {
7
   let uut: OptionsProcessor;
11
   let uut: OptionsProcessor;
8
-  let options: Record<string, any>;
9
-  let store: Store;
12
+  const mockedStore = mock(Store);
13
+  const store = instance(mockedStore);
14
+
10
   beforeEach(() => {
15
   beforeEach(() => {
11
-    options = {};
12
-    store = new Store();
13
-    uut = new OptionsProcessor(store, new UniqueIdProvider());
14
-  });
16
+    const mockedAssetService = mock(AssetService);
17
+    when(mockedAssetService.resolveFromRequire(anyNumber())).thenReturn('lol');
18
+    const assetService = instance(mockedAssetService);
15
 
19
 
16
-  it('processes colors into numeric AARRGGBB', () => {
17
-    options.someKeyColor = 'red';
18
-    options.color = 'blue';
19
-    uut.processOptions(options);
20
-    expect(options.someKeyColor).toEqual(0xffff0000);
21
-    expect(options.color).toEqual(0xff0000ff);
20
+    const mockedColorService = mock(ColorService);
21
+    when(mockedColorService.toNativeColor(anyString())).thenReturn(666);
22
+    const colorService = instance(mockedColorService);
22
 
23
 
23
-    options.someKeyColor = 'yellow';
24
-    uut.processOptions(options);
25
-    expect(options.someKeyColor).toEqual(0xffffff00);
24
+    uut = new OptionsProcessor(store, new UniqueIdProvider(), colorService, assetService);
26
   });
25
   });
27
 
26
 
28
-  it('processes numeric colors', () => {
29
-    options.someKeyColor = '#123456';
27
+  it('keeps original values if values were not processed', () => {
28
+    const options: Options = {
29
+      blurOnUnmount: false,
30
+      popGesture: false,
31
+      modalPresentationStyle: OptionsModalPresentationStyle.fullScreen,
32
+      animations: { dismissModal: { alpha: { from: 0, to: 1 } } },
33
+    };
30
     uut.processOptions(options);
34
     uut.processOptions(options);
31
-    expect(options.someKeyColor).toEqual(0xff123456);
32
-
33
-    options.someKeyColor = 0x123456ff; // wut
35
+    expect(options).toEqual({
36
+      blurOnUnmount: false,
37
+      popGesture: false,
38
+      modalPresentationStyle: OptionsModalPresentationStyle.fullScreen,
39
+      animations: { dismissModal: { alpha: { from: 0, to: 1 } } },
40
+    });
41
+  });
42
+
43
+  it('processes color keys', () => {
44
+    const options: Options = {
45
+      statusBar: { backgroundColor: 'red' },
46
+      topBar: { background: { color: 'blue' } },
47
+    };
34
     uut.processOptions(options);
48
     uut.processOptions(options);
35
-    expect(options.someKeyColor).toEqual(0xff123456);
49
+    expect(options).toEqual({
50
+      statusBar: { backgroundColor: 666 },
51
+      topBar: { background: { color: 666 } },
52
+    });
36
   });
53
   });
37
 
54
 
38
-  it('process colors with rgb functions', () => {
39
-    options.someKeyColor = 'rgb(255, 0, 255)';
55
+  it('processes image keys', () => {
56
+    const options: Options = {
57
+      backgroundImage: 123,
58
+      rootBackgroundImage: 234,
59
+      bottomTab: { icon: 345, selectedIcon: 345 },
60
+    };
40
     uut.processOptions(options);
61
     uut.processOptions(options);
41
-    expect(options.someKeyColor).toEqual(0xffff00ff);
62
+    expect(options).toEqual({
63
+      backgroundImage: 'lol',
64
+      rootBackgroundImage: 'lol',
65
+      bottomTab: { icon: 'lol', selectedIcon: 'lol' },
66
+    });
42
   });
67
   });
43
 
68
 
44
-  it('process colors with special words', () => {
45
-    options.someKeyColor = 'fuchsia';
46
-    uut.processOptions(options);
47
-    expect(options.someKeyColor).toEqual(0xffff00ff);
48
-  });
69
+  it('calls store if component has passProps', () => {
70
+    const passProps = { some: 'thing' };
71
+    const options = { topBar: { title: { component: { passProps, name: 'a' } } } };
49
 
72
 
50
-  it('process colors with hsla functions', () => {
51
-    options.someKeyColor = 'hsla(360, 100%, 100%, 1.0)';
52
     uut.processOptions(options);
73
     uut.processOptions(options);
53
 
74
 
54
-    expect(options.someKeyColor).toEqual(0xffffffff);
75
+    verify(mockedStore.setPropsForId('CustomComponent1', passProps)).called();
55
   });
76
   });
56
 
77
 
57
-  it('unknown colors return undefined', () => {
58
-    options.someKeyColor = 'wut';
59
-    uut.processOptions(options);
60
-    expect(options.someKeyColor).toEqual(undefined);
61
-  });
78
+  it('generates componentId for component id was not passed', () => {
79
+    const options = { topBar: { title: { component: { name: 'a' } } } };
62
 
80
 
63
-  it('any keys ending with Color', () => {
64
-    options.otherKeyColor = 'red';
65
-    options.yetAnotherColor = 'blue';
66
-    options.andAnotherColor = 'rgb(0, 255, 0)';
67
     uut.processOptions(options);
81
     uut.processOptions(options);
68
-    expect(options.otherKeyColor).toEqual(0xffff0000);
69
-    expect(options.yetAnotherColor).toEqual(0xff0000ff);
70
-    expect(options.andAnotherColor).toEqual(0xff00ff00);
71
-  });
72
 
82
 
73
-  it('keys ending with Color case sensitive', () => {
74
-    options.otherKey_color = 'red'; // eslint-disable-line camelcase
75
-    uut.processOptions(options);
76
-    expect(options.otherKey_color).toEqual('red');
83
+    expect(options).toEqual({
84
+      topBar: { title: { component: { name: 'a', componentId: 'CustomComponent1' } } },
85
+    });
77
   });
86
   });
78
 
87
 
79
-  it('any nested recursive keys ending with Color', () => {
80
-    options.topBar = { textColor: 'red' };
81
-    options.topBar.innerMostObj = { anotherColor: 'yellow' };
82
-    uut.processOptions(options);
83
-    expect(options.topBar.textColor).toEqual(0xffff0000);
84
-    expect(options.topBar.innerMostObj.anotherColor).toEqual(0xffffff00);
85
-  });
88
+  it('copies passed id to componentId key', () => {
89
+    const options = { topBar: { title: { component: { name: 'a', id: 'Component1' } } } };
86
 
90
 
87
-  it('resolve image sources with name/ending with icon', () => {
88
-    options.icon = 'require("https://wix.github.io/react-native-navigation/_images/logo.png");';
89
-    options.image = 'require("https://wix.github.io/react-native-navigation/_images/logo.png");';
90
-    options.myImage = 'require("https://wix.github.io/react-native-navigation/_images/logo.png");';
91
-    options.topBar = {
92
-      myIcon: 'require("https://wix.github.io/react-native-navigation/_images/logo.png");',
93
-      myOtherValue: 'value'
94
-    };
95
     uut.processOptions(options);
91
     uut.processOptions(options);
96
 
92
 
97
-    // As we can't import external images and we don't want to add an image here
98
-    // I assign the icons to strings (what the require would generally look like)
99
-    // and expect the value to be resolved, in this case it doesn't find anything and returns null
100
-    expect(options.icon).toEqual(null);
101
-    expect(options.topBar.myIcon).toEqual(null);
102
-    expect(options.image).toEqual(null);
103
-    expect(options.myImage).toEqual(null);
104
-    expect(options.topBar.myOtherValue).toEqual('value');
93
+    expect(options).toEqual({
94
+      topBar: { title: { component: { name: 'a', id: 'Component1', componentId: 'Component1' } } },
95
+    });
105
   });
96
   });
106
 
97
 
107
-  it('passProps for Buttons options', () => {
98
+  it('calls store when button has passProps and id', () => {
108
     const passProps = { prop: 'prop' };
99
     const passProps = { prop: 'prop' };
109
-    options.rightButtons = [{ passProps, id: '1' }];
110
-
111
-    uut.processOptions({ o: options });
112
-
113
-    expect(store.getPropsForId('1')).toEqual(passProps);
114
-  });
115
-
116
-  it('passProps for custom component', () => {
117
-    const passProps = { color: '#ff0000', some: 'thing' };
118
-    options.component = { passProps, name: 'a' };
119
-
120
-    uut.processOptions({ o: options });
121
-
122
-    expect(store.getPropsForId(options.component.componentId)).toEqual(passProps);
123
-    expect(Object.keys(options.component)).not.toContain('passProps');
124
-  });
125
-
126
-  it('generate component id for component in options', () => {
127
-    options.component = { name: 'a' };
128
-
129
-    uut.processOptions({ o: options });
130
-
131
-    expect(options.component.componentId).toBeDefined();
132
-  });
133
-
134
-  it('passProps from options are not processed', () => {
135
-    const passProps = { color: '#ff0000', some: 'thing' };
136
-    const clonedProps = _.cloneDeep(passProps);
137
-    options.component = { passProps, name: 'a' };
100
+    const options = { topBar: { rightButtons: [{ passProps, id: '1' }] } };
138
 
101
 
139
     uut.processOptions(options);
102
     uut.processOptions(options);
140
-    expect(store.getPropsForId(options.component.componentId)).toEqual(clonedProps);
141
-  });
142
 
103
 
143
-  it('pass supplied componentId for component in options', () => {
144
-    options.component = { name: 'a', id: 'Component1' };
145
-
146
-    uut.processOptions({ o: options });
147
-
148
-    expect(options.component.componentId).toEqual('Component1');
104
+    verify(mockedStore.setPropsForId('1', passProps)).called();
149
   });
105
   });
150
 
106
 
151
-  it('passProps must be with id next to it', () => {
107
+  it('do not touch passProps when id for button is missing', () => {
152
     const passProps = { prop: 'prop' };
108
     const passProps = { prop: 'prop' };
153
-    options.rightButtons = [{ passProps }];
154
-
155
-    uut.processOptions({ o: options });
156
-
157
-    expect(store.getPropsForId('1')).toEqual({});
158
-  });
109
+    const options = { topBar: { rightButtons: [{ passProps } as any] } };
159
 
110
 
160
-  it('processes Options object', () => {
161
-    options.someKeyColor = 'rgb(255, 0, 255)';
162
-    options.topBar = { textColor: 'red' };
163
-    options.topBar.innerMostObj = { anotherColor: 'yellow' };
164
-
165
-    uut.processOptions({ o: options });
166
-
167
-    expect(options.topBar.textColor).toEqual(0xffff0000);
168
-  });
169
-
170
-  it('undefined value return undefined ', () => {
171
-    options.someImage = undefined;
172
     uut.processOptions(options);
111
     uut.processOptions(options);
173
 
112
 
174
-    expect(options.someImage).toEqual(undefined);
113
+    expect(options).toEqual({ topBar: { rightButtons: [{ passProps }] } });
175
   });
114
   });
176
 
115
 
177
-  it('omits passProps when processing options', () => {
178
-    const passProps = {
116
+  it('omits passProps when processing buttons or components', () => {
117
+    const options = {
179
       topBar: {
118
       topBar: {
180
-        rightButtons: [
181
-          {
182
-            passProps: {},
183
-            id: 'btn1'
184
-          },
185
-        ],
186
-        leftButtons: [
187
-          {
188
-            passProps: {},
189
-            id: 'btn2'
190
-          }
191
-        ],
192
-        title: {
193
-          component: {
194
-            passProps: {}
195
-          }
196
-        },
197
-        background: {
198
-          component: {
199
-            passProps: {}
200
-          }
201
-        }
202
-      }
119
+        rightButtons: [{ passProps: {}, id: 'btn1' }],
120
+        leftButtons: [{ passProps: {}, id: 'btn2' }],
121
+        title: { component: { name: 'helloThere1', passProps: {} } },
122
+        background: { component: { name: 'helloThere2', passProps: {} } },
123
+      },
203
     };
124
     };
204
-    uut.processOptions(passProps);
205
-    expect(passProps.topBar.rightButtons[0].passProps).toBeUndefined();
206
-    expect(passProps.topBar.leftButtons[0].passProps).toBeUndefined();
207
-    expect(passProps.topBar.title.component.passProps).toBeUndefined();
208
-    expect(passProps.topBar.background.component.passProps).toBeUndefined();
125
+    uut.processOptions(options);
126
+    expect(options.topBar.rightButtons[0].passProps).toBeUndefined();
127
+    expect(options.topBar.leftButtons[0].passProps).toBeUndefined();
128
+    expect(options.topBar.title.component.passProps).toBeUndefined();
129
+    expect(options.topBar.background.component.passProps).toBeUndefined();
209
   });
130
   });
210
 });
131
 });

+ 31
- 14
lib/src/commands/OptionsProcessor.ts View File

1
 import * as _ from 'lodash';
1
 import * as _ from 'lodash';
2
-import { processColor } from 'react-native';
3
-import * as resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
4
 
2
 
5
 import { Store } from '../components/Store';
3
 import { Store } from '../components/Store';
6
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
4
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
5
+import { ColorService } from '../adapters/ColorService';
6
+import { AssetService } from '../adapters/AssetResolver';
7
+import { Options } from '../interfaces/Options';
7
 
8
 
8
 export class OptionsProcessor {
9
 export class OptionsProcessor {
9
-  constructor(public store: Store, public uniqueIdProvider: UniqueIdProvider) { }
10
+  constructor(
11
+    private store: Store,
12
+    private uniqueIdProvider: UniqueIdProvider,
13
+    private colorService: ColorService,
14
+    private assetService: AssetService,
15
+  ) {}
10
 
16
 
11
-  public processOptions(options: Record<string, any>) {
12
-    _.forEach(options, (value, key) => {
13
-      if (!value) { return; }
17
+  public processOptions(options: Options) {
18
+    this.processObject(options);
19
+  }
20
+
21
+  private processObject(objectToProcess: object) {
22
+    _.forEach(objectToProcess, (value, key) => {
23
+      if (!value) {
24
+        return;
25
+      }
14
 
26
 
15
-      this.processComponent(key, value, options);
16
-      this.processColor(key, value, options);
17
-      this.processImage(key, value, options);
27
+      this.processComponent(key, value, objectToProcess);
28
+      this.processColor(key, value, objectToProcess);
29
+      this.processImage(key, value, objectToProcess);
18
       this.processButtonsPassProps(key, value);
30
       this.processButtonsPassProps(key, value);
19
 
31
 
20
       if (!_.isEqual(key, 'passProps') && (_.isObject(value) || _.isArray(value))) {
32
       if (!_.isEqual(key, 'passProps') && (_.isObject(value) || _.isArray(value))) {
21
-        this.processOptions(value);
33
+        this.processObject(value);
22
       }
34
       }
23
     });
35
     });
24
   }
36
   }
25
 
37
 
26
   private processColor(key: string, value: any, options: Record<string, any>) {
38
   private processColor(key: string, value: any, options: Record<string, any>) {
27
     if (_.isEqual(key, 'color') || _.endsWith(key, 'Color')) {
39
     if (_.isEqual(key, 'color') || _.endsWith(key, 'Color')) {
28
-      options[key] = processColor(value);
40
+      options[key] = this.colorService.toNativeColor(value);
29
     }
41
     }
30
   }
42
   }
31
 
43
 
32
   private processImage(key: string, value: any, options: Record<string, any>) {
44
   private processImage(key: string, value: any, options: Record<string, any>) {
33
-    if (_.isEqual(key, 'icon') || _.isEqual(key, 'image') || _.endsWith(key, 'Icon') || _.endsWith(key, 'Image')) {
34
-      options[key] = resolveAssetSource(value);
45
+    if (
46
+      _.isEqual(key, 'icon') ||
47
+      _.isEqual(key, 'image') ||
48
+      _.endsWith(key, 'Icon') ||
49
+      _.endsWith(key, 'Image')
50
+    ) {
51
+      options[key] = this.assetService.resolveFromRequire(value);
35
     }
52
     }
36
   }
53
   }
37
 
54
 
52
       if (value.passProps) {
69
       if (value.passProps) {
53
         this.store.setPropsForId(value.componentId, value.passProps);
70
         this.store.setPropsForId(value.componentId, value.passProps);
54
       }
71
       }
55
-      options[key] = _.omit(value, 'passProps');
72
+      options[key].passProps = undefined;
56
     }
73
     }
57
   }
74
   }
58
 }
75
 }