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,6 +17,9 @@ import { TouchablePreview } from './adapters/TouchablePreview';
17 17
 import { LayoutRoot, Layout } from './interfaces/Layout';
18 18
 import { Options } from './interfaces/Options';
19 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 24
 export class NavigationRoot {
22 25
   public readonly Element: React.ComponentType<{ elementId: any; resizeMode?: any; }>;
@@ -44,10 +47,18 @@ export class NavigationRoot {
44 47
     this.componentEventsObserver = new ComponentEventsObserver(this.nativeEventsReceiver);
45 48
     this.componentRegistry = new ComponentRegistry(this.store, this.componentEventsObserver);
46 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 52
     this.nativeCommandsSender = new NativeCommandsSender();
49 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 62
     this.eventsRegistry = new EventsRegistry(this.nativeEventsReceiver, this.commandsObserver, this.componentEventsObserver);
52 63
 
53 64
     this.componentEventsObserver.registerOnceForAllComponentEvents();

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

@@ -0,0 +1,8 @@
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

@@ -0,0 +1,7 @@
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,6 +8,7 @@ import { UniqueIdProvider } from '../adapters/UniqueIdProvider.mock';
8 8
 import { Commands } from './Commands';
9 9
 import { CommandsObserver } from '../events/CommandsObserver';
10 10
 import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
11
+import { OptionsProcessor } from './OptionsProcessor';
11 12
 
12 13
 describe('Commands', () => {
13 14
   let uut: Commands;
@@ -22,12 +23,16 @@ describe('Commands', () => {
22 23
     mockedNativeCommandsSender = mock(NativeCommandsSender);
23 24
     nativeCommandsSender = instance(mockedNativeCommandsSender);
24 25
 
26
+    const mockedOptionsProcessor = mock(OptionsProcessor);
27
+    const optionsProcessor = instance(mockedOptionsProcessor);
28
+
25 29
     uut = new Commands(
26 30
       nativeCommandsSender,
27 31
       new LayoutTreeParser(),
28
-      new LayoutTreeCrawler(new UniqueIdProvider(), store),
32
+      new LayoutTreeCrawler(new UniqueIdProvider(), store, optionsProcessor),
29 33
       commandsObserver,
30
-      new UniqueIdProvider()
34
+      new UniqueIdProvider(),
35
+      optionsProcessor
31 36
     );
32 37
   });
33 38
 
@@ -353,7 +358,16 @@ describe('Commands', () => {
353 358
       const mockParser = { parse: () => 'parsed' };
354 359
       const mockCrawler = { crawl: (x: any) => x, processOptions: (x: any) => x };
355 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 373
     function getAllMethodsOfUut() {

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

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

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

@@ -4,6 +4,8 @@ import { LayoutType } from './LayoutType';
4 4
 import { LayoutTreeCrawler, LayoutNode } from './LayoutTreeCrawler';
5 5
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider.mock';
6 6
 import { Store } from '../components/Store';
7
+import { mock, instance } from 'ts-mockito';
8
+import { OptionsProcessor } from './OptionsProcessor';
7 9
 
8 10
 describe('LayoutTreeCrawler', () => {
9 11
   let uut: LayoutTreeCrawler;
@@ -11,7 +13,9 @@ describe('LayoutTreeCrawler', () => {
11 13
 
12 14
   beforeEach(() => {
13 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 21
   it('crawls a layout tree and adds unique id to each node', () => {
@@ -202,88 +206,6 @@ describe('LayoutTreeCrawler', () => {
202 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 209
   describe('LayoutNode', () => {
288 210
     it('convertable from same data structure', () => {
289 211
       const x = {

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

@@ -1,6 +1,7 @@
1 1
 import * as _ from 'lodash';
2
-import { OptionsProcessor } from './OptionsProcessor';
3 2
 import { LayoutType } from './LayoutType';
3
+import { OptionsProcessor } from './OptionsProcessor';
4
+import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
4 5
 
5 6
 export interface Data {
6 7
   name?: string;
@@ -15,13 +16,12 @@ export interface LayoutNode {
15 16
 }
16 17
 
17 18
 export class LayoutTreeCrawler {
18
-  private optionsProcessor: OptionsProcessor;
19 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 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 27
   crawl(node: LayoutNode): void {
@@ -29,14 +29,10 @@ export class LayoutTreeCrawler {
29 29
     if (node.type === LayoutType.Component) {
30 30
       this._handleComponent(node);
31 31
     }
32
-    this.processOptions(node.data.options);
32
+    this.optionsProcessor.processOptions(node.data.options);
33 33
     _.forEach(node.children, this.crawl);
34 34
   }
35 35
 
36
-  processOptions(options) {
37
-    this.optionsProcessor.processOptions(options);
38
-  }
39
-
40 36
   _handleComponent(node) {
41 37
     this._assertComponentDataName(node);
42 38
     this._savePropsToStore(node);

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

@@ -2,209 +2,130 @@ import { OptionsProcessor } from './OptionsProcessor';
2 2
 import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
3 3
 import { Store } from '../components/Store';
4 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 10
 describe('navigation options', () => {
7 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 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 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 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 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 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 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 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 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 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 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 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 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,37 +1,54 @@
1 1
 import * as _ from 'lodash';
2
-import { processColor } from 'react-native';
3
-import * as resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
4 2
 
5 3
 import { Store } from '../components/Store';
6 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 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 30
       this.processButtonsPassProps(key, value);
19 31
 
20 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 38
   private processColor(key: string, value: any, options: Record<string, any>) {
27 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 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,7 +69,7 @@ export class OptionsProcessor {
52 69
       if (value.passProps) {
53 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
 }