Daniel Zlotin 7 лет назад
Родитель
Сommit
59d8c960b3
3 измененных файлов: 153 добавлений и 372 удалений
  1. 9
    9
      lib/src/commands/Examples.js
  2. 54
    60
      lib/src/commands/LayoutTreeParser.js
  3. 90
    303
      lib/src/commands/LayoutTreeParser.test.js

+ 9
- 9
lib/src/commands/Examples.js Просмотреть файл

@@ -13,7 +13,7 @@ const topBarOptions = {
13 13
 
14 14
 const singleComponent = {
15 15
   component: {
16
-    name: 'myReactComponent'
16
+    name: 'MyReactComponent'
17 17
   }
18 18
 };
19 19
 
@@ -21,13 +21,13 @@ const stackOfComponentsWithTopBar = {
21 21
   stack: [
22 22
     {
23 23
       component: {
24
-        name: 'myReactComponent1'
24
+        name: 'MyReactComponent1'
25 25
       }
26 26
     },
27 27
 
28 28
     {
29 29
       component: {
30
-        name: 'myReactComponent2',
30
+        name: 'MyReactComponent2',
31 31
         options: topBarOptions // optional
32 32
       }
33 33
     }
@@ -40,7 +40,7 @@ const bottomTabs = {
40 40
     stackOfComponentsWithTopBar,
41 41
     {
42 42
       component: {
43
-        name: 'myReactComponent1'
43
+        name: 'MyReactComponent1'
44 44
       }
45 45
     }
46 46
   ]
@@ -61,8 +61,7 @@ const topTabs = {
61 61
     singleComponent,
62 62
     singleComponent,
63 63
     stackOfComponentsWithTopBar
64
-  ],
65
-  options: topBarOptions
64
+  ]
66 65
 };
67 66
 
68 67
 const complexLayout = {
@@ -76,6 +75,8 @@ const complexLayout = {
76 75
           stack: [
77 76
             {
78 77
               topTabs: [
78
+                stackOfComponentsWithTopBar,
79
+                stackOfComponentsWithTopBar,
79 80
                 {
80 81
                   topTabs: [
81 82
                     singleComponent,
@@ -85,9 +86,7 @@ const complexLayout = {
85 86
                     stackOfComponentsWithTopBar
86 87
                   ],
87 88
                   options: topBarOptions
88
-                },
89
-                stackOfComponentsWithTopBar,
90
-                stackOfComponentsWithTopBar
89
+                }
91 90
               ]
92 91
             }
93 92
           ]
@@ -101,6 +100,7 @@ module.exports = {
101 100
   passProps,
102 101
   topBarOptions,
103 102
   singleComponent,
103
+  stackOfComponentsWithTopBar,
104 104
   bottomTabs,
105 105
   sideMenu,
106 106
   topTabs,

+ 54
- 60
lib/src/commands/LayoutTreeParser.js Просмотреть файл

@@ -2,97 +2,91 @@ const _ = require('lodash');
2 2
 const LayoutTypes = require('./LayoutTypes');
3 3
 
4 4
 class LayoutTreeParser {
5
+  constructor() {
6
+    this.parse = this.parse.bind(this);
7
+  }
8
+
5 9
   /**
6
-   * returns correct layout tree of {type, children, data?}
10
+   * returns correct layout tree of nodes which are { type, data, children }
7 11
    */
8
-  parseFromSimpleJSON(simpleJsonApi) {
9
-    if (simpleJsonApi.sideMenu) {
10
-      return this._createSideMenu(simpleJsonApi);
11
-    }
12
-    if (simpleJsonApi.bottomTabs) {
13
-      return this._createTabs(simpleJsonApi.bottomTabs);
14
-    }
15
-    if (simpleJsonApi.topTabs) {
16
-      return this._createTopTabs(simpleJsonApi);
17
-    }
18
-    if (simpleJsonApi.name) {
19
-      return this._createContainer(simpleJsonApi);
12
+  parse(api) {
13
+    if (api.topTabs) {
14
+      return this._topTabs(api.topTabs);
15
+    } else if (api.sideMenu) {
16
+      return this._sideMenu(api.sideMenu);
17
+    } else if (api.bottomTabs) {
18
+      return this._bottomTabs(api.bottomTabs);
19
+    } else if (api.stack) {
20
+      return this._stack(api.stack);
21
+    } else if (api.component) {
22
+      return this._component(api.component);
20 23
     }
21
-    return this._createContainerStackWithContainerData(simpleJsonApi.container);
24
+    throw new Error(`unknown LayoutType "${_.keys(api)}"`);
22 25
   }
23 26
 
24
-  createDialogContainer(data) {
25
-    return {
26
-      type: LayoutTypes.CustomDialog,
27
-      data,
28
-      children: []
29
-    };
30
-  }
31
-
32
-  _createTabs(tabs) {
33
-    return {
34
-      type: LayoutTypes.BottomTabs,
35
-      children: _.map(tabs, (t) => this._createContainerStackWithContainerData(t.container))
36
-    };
37
-  }
38
-
39
-  _createTopTabs(node) {
27
+  _topTabs(api) {
40 28
     return {
41 29
       type: LayoutTypes.TopTabs,
42
-      data: _.pick(node, 'navigationOptions'),
43
-      children: _.map(node.topTabs, (t) => this._createTopTab(t))
30
+      data: {},
31
+      children: _.map(api, this.parse)
44 32
     };
45 33
   }
46 34
 
47
-  _createTopTab(data) {
48
-    return {
49
-      type: LayoutTypes.TopTab,
50
-      data,
51
-      children: []
52
-    };
53
-  }
54
-
55
-  _createContainerStackWithContainerData(containerData) {
56
-    return {
57
-      type: LayoutTypes.ContainerStack,
58
-      children: [this._createContainer(containerData)]
59
-    };
60
-  }
61
-
62
-  _createSideMenu(layout) {
35
+  _sideMenu(api) {
63 36
     return {
64 37
       type: LayoutTypes.SideMenuRoot,
65
-      children: this._createSideMenuChildren(layout)
38
+      data: {},
39
+      children: this._sideMenuChildren(api)
66 40
     };
67 41
   }
68 42
 
69
-  _createSideMenuChildren(layout) {
43
+  _sideMenuChildren(api) {
44
+    if (!api.center) {
45
+      throw new Error(`sideMenu.center is required`);
46
+    }
70 47
     const children = [];
71
-    if (layout.sideMenu.left) {
48
+    if (api.left) {
72 49
       children.push({
73 50
         type: LayoutTypes.SideMenuLeft,
74
-        children: [this._createContainer(layout.sideMenu.left.container)]
51
+        data: {},
52
+        children: [this.parse(api.left)]
75 53
       });
76 54
     }
77 55
     children.push({
78 56
       type: LayoutTypes.SideMenuCenter,
79
-      children: [
80
-        layout.bottomTabs ? this._createTabs(layout.bottomTabs) : this._createContainerStackWithContainerData(layout.container)
81
-      ]
57
+      data: {},
58
+      children: [this.parse(api.center)]
82 59
     });
83
-    if (layout.sideMenu.right) {
60
+    if (api.right) {
84 61
       children.push({
85 62
         type: LayoutTypes.SideMenuRight,
86
-        children: [this._createContainer(layout.sideMenu.right.container)]
63
+        data: {},
64
+        children: [this.parse(api.right)]
87 65
       });
88 66
     }
89 67
     return children;
90 68
   }
91 69
 
92
-  _createContainer(data) {
70
+  _bottomTabs(api) {
93 71
     return {
94
-      type: LayoutTypes.Container,
95
-      data,
72
+      type: LayoutTypes.BottomTabs,
73
+      data: {},
74
+      children: _.map(api, this.parse)
75
+    };
76
+  }
77
+
78
+  _stack(api) {
79
+    return {
80
+      type: LayoutTypes.ComponentStack,
81
+      data: {},
82
+      children: _.map(api, this.parse)
83
+    };
84
+  }
85
+
86
+  _component(api) {
87
+    return { 
88
+      type: LayoutTypes.Component,
89
+      data: api,
96 90
       children: []
97 91
     };
98 92
   }

+ 90
- 303
lib/src/commands/LayoutTreeParser.test.js Просмотреть файл

@@ -1,4 +1,7 @@
1 1
 const LayoutTreeParser = require('./LayoutTreeParser');
2
+const Examples = require('./Examples');
3
+const LayoutTypes = require('./LayoutTypes');
4
+const _ = require('lodash');
2 5
 
3 6
 describe('LayoutTreeParser', () => {
4 7
   let uut;
@@ -7,325 +10,109 @@ describe('LayoutTreeParser', () => {
7 10
     uut = new LayoutTreeParser();
8 11
   });
9 12
 
10
-  describe('parseFromSimpleJSON', () => {
11
-    it('parses single screen', () => {
12
-      expect(uut.parseFromSimpleJSON(SimpleLayouts.singleScreenApp))
13
-        .toEqual({
14
-          type: 'ContainerStack',
15
-          children: [
16
-            {
17
-              type: 'Container',
18
-              data: {
19
-                name: 'com.example.MyScreen'
20
-              },
21
-              children: []
22
-            }
23
-          ]
24
-        });
13
+  describe('parses into { type, data, children }', () => {
14
+    it('unknown type', () => {
15
+      expect(() => uut.parse({ wut: {} })).toThrow(new Error('unknown LayoutType "wut"'));
25 16
     });
26 17
 
27
-    it('parses single screen with props', () => {
28
-      expect(uut.parseFromSimpleJSON(SimpleLayouts.singleScreenWithAditionalParams))
29
-        .toEqual({
30
-          type: 'ContainerStack',
31
-          children: [
32
-            {
33
-              type: 'Container',
34
-              children: [],
35
-              data: {
36
-                name: 'com.example.MyScreen',
37
-                passProps: SimpleLayouts.passProps,
38
-                style: {},
39
-                buttons: {}
40
-              }
41
-            }
42
-          ]
43
-        });
44
-      const parsedPropsFn = uut.parseFromSimpleJSON(SimpleLayouts.singleScreenWithAditionalParams)
45
-        .children[0].data.passProps.fnProp;
46
-      expect(parsedPropsFn).toBe(SimpleLayouts.passProps.fnProp);
47
-      expect(parsedPropsFn()).toEqual('Hello from a function');
18
+    it('single component', () => {
19
+      expect(uut.parse(Examples.singleComponent)).toEqual({
20
+        type: LayoutTypes.Component,
21
+        data: { name: 'MyReactComponent' },
22
+        children: []
23
+      });
48 24
     });
49 25
 
50
-    it('parses tab based', () => {
51
-      expect(uut.parseFromSimpleJSON(SimpleLayouts.tabBasedApp))
52
-        .toEqual({
53
-          type: 'BottomTabs',
54
-          children: [
55
-            {
56
-              type: 'ContainerStack',
57
-              children: [
58
-                {
59
-                  type: 'Container',
60
-                  children: [],
61
-                  data: {
62
-                    name: 'com.example.ATab'
63
-                  }
64
-                }
65
-              ]
66
-            },
67
-            {
68
-              type: 'ContainerStack',
69
-              children: [
70
-                {
71
-                  type: 'Container',
72
-                  children: [],
73
-                  data: {
74
-                    name: 'com.example.SecondTab'
75
-                  }
76
-                }
77
-              ]
78
-            },
79
-            {
80
-              type: 'ContainerStack',
81
-              children: [
82
-                {
83
-                  type: 'Container',
84
-                  children: [],
85
-                  data: {
86
-                    name: 'com.example.ATab'
87
-                  }
88
-                }
89
-              ]
90
-            }
91
-          ]
92
-        });
26
+    it('pass props', () => {
27
+      const result = uut.parse({
28
+        component: {
29
+          name: 'MyScreen',
30
+          passProps: Examples.passProps
31
+        }
32
+      });
33
+      expect(result).toEqual({
34
+        type: LayoutTypes.Component,
35
+        data: { name: 'MyScreen', passProps: Examples.passProps },
36
+        children: []
37
+      });
38
+      expect(result.data.passProps).toBe(Examples.passProps);
39
+      expect(result.data.passProps.fnProp()).toEqual('Hello from a function');
93 40
     });
94 41
 
95
-    it('parses side menus', () => {
96
-      expect(uut.parseFromSimpleJSON(SimpleLayouts.singleWithSideMenu))
97
-        .toEqual({
98
-          type: 'SideMenuRoot',
99
-          children: [
100
-            {
101
-              type: 'SideMenuLeft',
102
-              children: [
103
-                {
104
-                  type: 'Container',
105
-                  data: {
106
-                    name: 'com.example.SideMenu'
107
-                  },
108
-                  children: []
109
-                }
110
-              ]
111
-            },
112
-            {
113
-              type: 'SideMenuCenter',
114
-              children: [
115
-                {
116
-                  type: 'ContainerStack',
117
-                  children: [
118
-                    {
119
-                      type: 'Container',
120
-                      data: {
121
-                        name: 'com.example.MyScreen'
122
-                      },
123
-                      children: []
124
-                    }
125
-                  ]
126
-                }
127
-              ]
128
-            }
129
-          ]
130
-        });
42
+    it('stack of components with top bar', () => {
43
+      expect(uut.parse(Examples.stackOfComponentsWithTopBar)).toEqual({
44
+        type: LayoutTypes.ComponentStack,
45
+        data: {},
46
+        children: [
47
+          {
48
+            type: LayoutTypes.Component,
49
+            data: { name: 'MyReactComponent1' },
50
+            children: []
51
+          },
52
+          {
53
+            type: LayoutTypes.Component,
54
+            data: { name: 'MyReactComponent2', options: Examples.topBarOptions },
55
+            children: []
56
+          }
57
+        ]
58
+      });
131 59
     });
132 60
 
133
-    it('parses side menu right', () => {
134
-      expect(uut.parseFromSimpleJSON(SimpleLayouts.singleWithRightSideMenu))
135
-        .toEqual({
136
-          type: 'SideMenuRoot',
137
-          children: [
138
-            {
139
-              type: 'SideMenuCenter',
140
-              children: [
141
-                {
142
-                  type: 'ContainerStack',
143
-                  children: [
144
-                    {
145
-                      type: 'Container',
146
-                      data: {
147
-                        name: 'com.example.MyScreen'
148
-                      },
149
-                      children: []
150
-                    }
151
-                  ]
152
-                }
153
-              ]
154
-            },
155
-            {
156
-              type: 'SideMenuRight',
157
-              children: [
158
-                {
159
-                  type: 'Container',
160
-                  data: {
161
-                    name: 'com.example.SideMenu'
162
-                  },
163
-                  children: []
164
-                }
165
-              ]
166
-            }
167
-          ]
168
-        });
61
+    it('bottom tabs', () => {
62
+      const result = uut.parse(Examples.bottomTabs);
63
+      expect(_.keys(result)).toEqual(['type', 'data', 'children']);
64
+      expect(result.type).toEqual(LayoutTypes.BottomTabs);
65
+      expect(result.data).toEqual({});
66
+      expect(result.children.length).toEqual(3);
67
+      expect(result.children[0].type).toEqual(LayoutTypes.ComponentStack);
68
+      expect(result.children[1].type).toEqual(LayoutTypes.ComponentStack);
69
+      expect(result.children[2].type).toEqual(LayoutTypes.Component);
169 70
     });
170 71
 
171
-    it('parses both side menus', () => {
172
-      expect(uut.parseFromSimpleJSON(SimpleLayouts.singleWithBothMenus))
173
-        .toEqual({
174
-          type: 'SideMenuRoot',
175
-          children: [
176
-            {
177
-              type: 'SideMenuLeft',
178
-              children: [
179
-                {
180
-                  type: 'Container',
181
-                  data: {
182
-                    name: 'com.example.Menu1'
183
-                  },
184
-                  children: []
185
-                }
186
-              ]
187
-            },
188
-            {
189
-              type: 'SideMenuCenter',
190
-              children: [
191
-                {
192
-                  type: 'ContainerStack',
193
-                  children: [
194
-                    {
195
-                      type: 'Container',
196
-                      data: {
197
-                        name: 'com.example.MyScreen'
198
-                      },
199
-                      children: []
200
-                    }
201
-                  ]
202
-                }
203
-              ]
204
-            },
205
-            {
206
-              type: 'SideMenuRight',
207
-              children: [
208
-                {
209
-                  type: 'Container',
210
-                  data: {
211
-                    name: 'com.example.Menu2'
212
-                  },
213
-                  children: []
214
-                }
215
-              ]
216
-            }
217
-          ]
218
-        });
72
+    it('side menus', () => {
73
+      const result = uut.parse(Examples.sideMenu);
74
+      expect(_.keys(result)).toEqual(['type', 'data', 'children']);
75
+      expect(result.type).toEqual(LayoutTypes.SideMenuRoot);
76
+      expect(result.data).toEqual({});
77
+      expect(result.children.length).toEqual(3);
78
+      expect(result.children[0].type).toEqual(LayoutTypes.SideMenuLeft);
79
+      expect(result.children[1].type).toEqual(LayoutTypes.SideMenuCenter);
80
+      expect(result.children[2].type).toEqual(LayoutTypes.SideMenuRight);
81
+      expect(result.children[0].children.length).toEqual(1);
82
+      expect(result.children[0].children[0].type).toEqual(LayoutTypes.Component);
83
+      expect(result.children[1].children.length).toEqual(1);
84
+      expect(result.children[1].children[0].type).toEqual(LayoutTypes.ComponentStack);
85
+      expect(result.children[2].children.length).toEqual(1);
86
+      expect(result.children[2].children[0].type).toEqual(LayoutTypes.Component);
219 87
     });
220 88
 
221
-    it('parses bottomTabs with side menus', () => {
222
-      expect(uut.parseFromSimpleJSON(SimpleLayouts.tabBasedWithBothSideMenus))
223
-        .toEqual({
224
-          type: 'SideMenuRoot',
225
-          children: [
226
-            {
227
-              type: 'SideMenuLeft',
228
-              children: [
229
-                {
230
-                  type: 'Container',
231
-                  data: {
232
-                    name: 'com.example.Menu1'
233
-                  },
234
-                  children: []
235
-                }
236
-              ]
237
-            },
238
-            {
239
-              type: 'SideMenuCenter',
240
-              children: [
241
-                {
242
-                  type: 'BottomTabs',
243
-                  children: [
244
-                    {
245
-                      type: 'ContainerStack',
246
-                      children: [
247
-                        {
248
-                          type: 'Container',
249
-                          data: {
250
-                            name: 'com.example.FirstTab'
251
-                          },
252
-                          children: []
253
-                        }
254
-                      ]
255
-                    },
256
-                    {
257
-                      type: 'ContainerStack',
258
-                      children: [
259
-                        {
260
-                          type: 'Container',
261
-                          data: {
262
-                            name: 'com.example.SecondTab'
263
-                          },
264
-                          children: []
265
-                        }
266
-                      ]
267
-                    }
268
-                  ]
269
-                }
270
-              ]
271
-            },
272
-            {
273
-              type: 'SideMenuRight',
274
-              children: [
275
-                {
276
-                  type: 'Container',
277
-                  data: {
278
-                    name: 'com.example.Menu2'
279
-                  },
280
-                  children: []
281
-                }
282
-              ]
283
-            }
284
-          ]
285
-        });
89
+    it('side menu center is require', () => {
90
+      expect(() => uut.parse({ sideMenu: {} })).toThrow(new Error('sideMenu.center is required'));
286 91
     });
287 92
 
288
-    it('parses bottomTabs with side menus', () => {
289
-      expect(uut.parseFromSimpleJSON(SimpleLayouts.singleScreenWithTopTabs))
290
-        .toEqual({
291
-          type: 'TopTabs',
292
-          children: [
293
-            {
294
-              type: 'TopTab',
295
-              data: {
296
-                name: 'navigation.playground.TextScreen'
297
-              },
298
-              children: []
299
-            },
300
-            {
301
-              type: 'TopTab',
302
-              data: {
303
-                name: 'navigation.playground.TextScreen'
304
-              },
305
-              children: []
306
-            },
307
-            {
308
-              type: 'TopTab',
309
-              data: {
310
-                name: 'navigation.playground.TextScreen'
311
-              },
312
-              children: []
313
-            }
314
-          ],
315
-          data: {}
316
-        });
93
+    it('top tabs', () => {
94
+      const result = uut.parse(Examples.topTabs);
95
+      expect(_.keys(result)).toEqual(['type', 'data', 'children']);
96
+      expect(result.type).toEqual(LayoutTypes.TopTabs);
97
+      expect(result.data).toEqual({});
98
+      expect(result.children.length).toEqual(5);
99
+      expect(result.children[0].type).toEqual(LayoutTypes.Component);
100
+      expect(result.children[1].type).toEqual(LayoutTypes.Component);
101
+      expect(result.children[2].type).toEqual(LayoutTypes.Component);
102
+      expect(result.children[3].type).toEqual(LayoutTypes.Component);
103
+      expect(result.children[4].type).toEqual(LayoutTypes.ComponentStack);
317 104
     });
318
-  });
319
-
320
-  describe('createContainer', () => {
321
-    it('creates container object with passed data', () => {
322
-      expect(uut.parseFromSimpleJSON({ name: 'theContainer', foo: 'bar' })).toEqual({ type: 'Container', data: { name: 'theContainer', foo: 'bar' }, children: [] });
323
-    });
324
-  });
325 105
 
326
-  describe('createDialogContainer', () => {
327
-    it('creates dialog container object with passed data', () => {
328
-      expect(uut.createDialogContainer({ foo: 'bar' })).toEqual({ type: 'CustomDialog', data: { foo: 'bar' }, children: [] });
106
+    it('complex layout example', () => {
107
+      const result = uut.parse(Examples.complexLayout);
108
+      expect(result.type).toEqual('SideMenuRoot');
109
+      expect(result.children[1].type).toEqual('SideMenuCenter');
110
+      expect(result.children[1].children[0].type).toEqual('BottomTabs');
111
+      expect(result.children[1].children[0].children[2].type).toEqual('ComponentStack');
112
+      expect(result.children[1].children[0].children[2].children[0].type).toEqual('TopTabs');
113
+      expect(result.children[1].children[0].children[2].children[0].children[2].type).toEqual('TopTabs');
114
+      expect(result.children[1].children[0].children[2].children[0].children[2].children[4].type).toEqual('ComponentStack');
115
+      // expect(result.children[1].children[0].children[2].children[0].children[2].data).toEqual({ options: {} });
329 116
     });
330 117
   });
331 118
 });