浏览代码

Auto generate docs (#2315)

* Auto generate docs

Docs can be generatred by running yarn run gen-doc
Add jsdoc and jsdoc2md dev dependencies

* Add tests to NavigationOption params

* fix tests

* Fix e2e and implement missing classes after rebase

* f
Guy Carmeli 7 年前
父节点
当前提交
dcf68af948

+ 53
- 0
docs/docs/Navigation.md 查看文件

@@ -0,0 +1,53 @@
1
+<a name="Navigation"></a>
2
+
3
+## Navigation
4
+
5
+* [Navigation](#Navigation)
6
+    * [.registerContainer(containerName, getContainerFunc)](#Navigation+registerContainer)
7
+    * [.setRoot(root)](#Navigation+setRoot)
8
+    * [.setOptions(containerId, options)](#Navigation+setOptions)
9
+
10
+
11
+* * *
12
+
13
+<a name="Navigation+registerContainer"></a>
14
+
15
+### navigation.registerContainer(containerName, getContainerFunc)
16
+Every screen component in your app must be registered with a unique name. The component itself is a traditional React component extending React.Component.
17
+
18
+
19
+| Param | Type | Description |
20
+| --- | --- | --- |
21
+| containerName | <code>string</code> | Unique container name |
22
+| getContainerFunc | <code>function</code> | generator function, typically `() => require('./myContainer')` |
23
+
24
+
25
+* * *
26
+
27
+<a name="Navigation+setRoot"></a>
28
+
29
+### navigation.setRoot(root)
30
+Reset the navigation stack to a new screen (the stack root is changed).
31
+
32
+
33
+| Param | Type |
34
+| --- | --- |
35
+| root | <code>Root</code> | 
36
+
37
+
38
+* * *
39
+
40
+<a name="Navigation+setOptions"></a>
41
+
42
+### navigation.setOptions(containerId, options)
43
+Change a containers navigation options
44
+
45
+
46
+| Param | Type | Description |
47
+| --- | --- | --- |
48
+| containerId | <code>string</code> | Unique container name |
49
+| options | <code>NavigationOptions</code> |  |
50
+
51
+
52
+* * *
53
+

+ 5
- 0
docs/templates/docs.hbs 查看文件

@@ -0,0 +1,5 @@
1
+{{>header~}}
2
+{{>body}}
3
+{{>member-index~}}
4
+{{>separator~}}
5
+{{>members~}}

+ 0
- 0
docs/templates/scope.hbs 查看文件


+ 20
- 2
lib/src/Navigation.js 查看文件

@@ -9,7 +9,10 @@ const LayoutTreeCrawler = require('./commands/LayoutTreeCrawler');
9 9
 const PrivateEventsListener = require('./events/PrivateEventsListener');
10 10
 const PublicEventsRegistry = require('./events/PublicEventsRegistry');
11 11
 const Element = require('./adapters/Element');
12
+const Root = require('./params/Root');
13
+const NavigationOptions = require('./params/NavigationOptions');
12 14
 
15
+/** @constructor */
13 16
 class Navigation {
14 17
   constructor() {
15 18
     this.store = new Store();
@@ -25,20 +28,35 @@ class Navigation {
25 28
     this.privateEventsListener.listenAndHandlePrivateEvents();
26 29
     this.Element = Element;
27 30
   }
31
+
32
+  /**
33
+   * Every screen component in your app must be registered with a unique name. The component itself is a traditional React component extending React.Component.
34
+   * @param {string} containerName Unique container name
35
+   * @param {function} getContainerFunc generator function, typically `() => require('./myContainer')`
36
+   */
28 37
   registerContainer(containerName, getContainerFunc) {
29 38
     this.containerRegistry.registerContainer(containerName, getContainerFunc);
30 39
   }
31 40
 
41
+  /**
42
+   * Reset the navigation stack to a new screen (the stack root is changed).
43
+   * @param {Root} root
44
+   */
32 45
   setRoot(params) {
33
-    return this.commands.setRoot(params);
46
+    return this.commands.setRoot(new Root(params));
34 47
   }
35 48
 
36 49
   setDefaultOptions(options) {
37 50
     this.commands.setDefaultOptions(options);
38 51
   }
39 52
 
53
+  /**
54
+   * Change a containers navigation options
55
+   * @param {string} containerId Unique container name
56
+   * @param {NavigationOptions} options
57
+   */
40 58
   setOptions(containerId, options) {
41
-    this.commands.setOptions(containerId, options);
59
+    this.commands.setOptions(containerId, new NavigationOptions(options));
42 60
   }
43 61
 
44 62
   showModal(params) {

+ 19
- 0
lib/src/params/BottomTabs.js 查看文件

@@ -0,0 +1,19 @@
1
+const { forEach } = require('lodash');
2
+const Container = require('./Container');
3
+
4
+class BottomTabs {
5
+  /**
6
+   * @constructor
7
+   * @typedef {Object} BottomTabs
8
+   * @property {Container[]} tabs
9
+   */
10
+  constructor(tabs) {
11
+    if (!tabs || tabs.length === 0) {
12
+      throw new Error('BottomTabs undefined');
13
+    }
14
+    this.tabs = [];
15
+    forEach(tabs, (tab) => this.tabs.push({ container: new Container(tab.container) }));
16
+  }
17
+}
18
+
19
+module.exports = BottomTabs;

+ 34
- 0
lib/src/params/BottomTabs.test.js 查看文件

@@ -0,0 +1,34 @@
1
+const BottomTabs = require('./BottomTabs');
2
+
3
+const TAB1 = {
4
+  container: {
5
+    name: 'navigation.playground.TextScreen',
6
+    passProps: {
7
+      text: 'This is a side menu center screen tab 2'
8
+    }
9
+  }
10
+};
11
+const TAB2 = {
12
+  container: {
13
+    name: 'navigation.playground.TextScreen',
14
+    passProps: {
15
+      text: 'This is a side menu center screen tab 1'
16
+    }
17
+  }
18
+};
19
+const TABS = [TAB1, TAB2];
20
+
21
+describe('ContainerRegistry', () => {
22
+  it('parses tabs correctly', () => {
23
+    const uut = new BottomTabs(TABS);
24
+    expect(uut.tabs.length).toBe(2);
25
+  });
26
+
27
+  it('throws if tabs are undefined', () => {
28
+    expect(() => new BottomTabs()).toThrowError('BottomTabs undefined');
29
+  });
30
+
31
+  it('throws if zero tabs', () => {
32
+    expect(() => new BottomTabs([])).toThrowError('BottomTabs undefined');
33
+  });
34
+});

+ 16
- 0
lib/src/params/Button.js 查看文件

@@ -0,0 +1,16 @@
1
+class Button {
2
+  /**
3
+   * @property {String} id
4
+   * @property {string} [testID]
5
+   * @property {string} [title]
6
+   * @property {string} [color]
7
+   */
8
+  constructor(params) {
9
+    this.id = params.id;
10
+    this.testID = params.testID;
11
+    this.title = params.title;
12
+    this.color = params.color;
13
+  }
14
+}
15
+
16
+module.exports = Button;

+ 18
- 0
lib/src/params/Button.test.js 查看文件

@@ -0,0 +1,18 @@
1
+const Button = require('./Button');
2
+
3
+const BUTTON = {
4
+  id: 'myBtn',
5
+  testID: 'BTN',
6
+  title: 'My Button',
7
+  color: 'red'
8
+};
9
+
10
+describe('Button', () => {
11
+  it('parses button', () => {
12
+    const uut = new Button(BUTTON);
13
+    expect(uut.id).toEqual('myBtn');
14
+    expect(uut.testID).toEqual('BTN');
15
+    expect(uut.title).toEqual('My Button');
16
+    expect(uut.color).toEqual('red');
17
+  });
18
+});

+ 16
- 0
lib/src/params/Container.js 查看文件

@@ -0,0 +1,16 @@
1
+class Container {
2
+  /**
3
+   * @typedef {Object} Container
4
+   * @property {string} name The container's registered name
5
+   * @property {Object} [passProps] props
6
+   */
7
+  constructor(params) {
8
+    if (!params || !params.name) {
9
+      throw new Error('Container name is undefined');
10
+    }
11
+    this.name = params.name;
12
+    this.passProps = params.passProps;
13
+  }
14
+}
15
+
16
+module.exports = Container;

+ 21
- 0
lib/src/params/Container.test.js 查看文件

@@ -0,0 +1,21 @@
1
+const Container = require('./Container');
2
+
3
+const PASS_PROPS = {
4
+  number: 9
5
+};
6
+const CONTAINER = {
7
+  name: 'myScreen',
8
+  passProps: PASS_PROPS
9
+};
10
+
11
+describe('ContainerRegistry', () => {
12
+  it('parses container correctly', () => {
13
+    const uut = new Container(CONTAINER);
14
+    expect(uut.name).toBe('myScreen');
15
+    expect(uut.passProps).toEqual(PASS_PROPS);
16
+  });
17
+
18
+  it('throws if container name is missing', () => {
19
+    expect(() => new Container()).toThrowError('Container name is undefined');
20
+  });
21
+});

+ 32
- 0
lib/src/params/NavigationOptions.js 查看文件

@@ -0,0 +1,32 @@
1
+const TopBar = require('./TopBar');
2
+const TabBar = require('./TabBar');
3
+const Button = require('./Button');
4
+
5
+ /**
6
+ * A module for adding two values.
7
+ * @module NavigationOptions
8
+ */
9
+
10
+ /**
11
+  * NavigationOptions are used by containers to customize their behavior and style.
12
+  * @alias module:NavigationOptions
13
+  */
14
+class NavigationOptions {
15
+  /**
16
+   * @typedef {Object} NavigationOptions
17
+   * @property {TopBar} topBar
18
+   * @property {TabBar} bottomTabs
19
+   * @property {String} [orientation]
20
+   * @property {Button} [rightButtons]
21
+   * @property {Button} [leftButtons]
22
+   */
23
+  constructor(options) {
24
+    this.topBar = options.topBar && new TopBar(options.topBar);
25
+    this.bottomTabs = options.bottomTabs && new TabBar(options.bottomTabs);
26
+    this.orientation = options.orientation;
27
+    this.rightButtons = options.rightButtons && options.rightButtons.map((button) => new Button(button));
28
+    this.leftButtons = options.leftButtons && options.leftButtons.map((button) => new Button(button));
29
+  }
30
+}
31
+
32
+module.exports = NavigationOptions;

+ 40
- 0
lib/src/params/NavigationOptions.test.js 查看文件

@@ -0,0 +1,40 @@
1
+const NavigationOptions = require('./NavigationOptions');
2
+const TabBar = require('./TabBar');
3
+const TopBar = require('./TopBar');
4
+
5
+const TAB_BAR = {};
6
+const TOP_BAR = {};
7
+const RIGHT_BUTTONS = [
8
+  {
9
+    id: 'myBtn',
10
+    testID: 'BTN',
11
+    title: 'My Button',
12
+    color: 'red'
13
+  }
14
+];
15
+const LEFT_BUTTONS = [
16
+  {
17
+    id: 'myBtn',
18
+    testID: 'BTN',
19
+    title: 'My Button',
20
+    color: 'red'
21
+  }
22
+];
23
+const NAVIGATION_OPTIONS = {
24
+  topBar: TOP_BAR,
25
+  bottomTabs: TAB_BAR,
26
+  orientation: 'portrait',
27
+  rightButtons: RIGHT_BUTTONS,
28
+  leftButtons: LEFT_BUTTONS
29
+};
30
+
31
+describe('NavigationOptions', () => {
32
+  it('parses navigationOptions correctly', () => {
33
+    const uut = new NavigationOptions(NAVIGATION_OPTIONS);
34
+    expect(uut.bottomTabs).toBeInstanceOf(TabBar);
35
+    expect(uut.topBar).toBeInstanceOf(TopBar);
36
+    expect(uut.orientation).toEqual('portrait');
37
+    expect(uut.rightButtons).toEqual(RIGHT_BUTTONS);
38
+    expect(uut.leftButtons).toEqual(LEFT_BUTTONS);
39
+  });
40
+});

+ 19
- 0
lib/src/params/Root.js 查看文件

@@ -0,0 +1,19 @@
1
+const Container = require('./Container');
2
+const SideMenu = require('./SideMenu');
3
+const BottomTabs = require('./BottomTabs');
4
+
5
+class Root {
6
+  /**
7
+   * @typedef {Object} Root
8
+   * @property {Container} container
9
+   * @property {SideMenu} [sideMenu]
10
+   * @property {BottomTabs} [bottomTabs]
11
+   */
12
+  constructor(root) {
13
+    this.container = root.container && new Container(root.container);
14
+    this.sideMenu = root.sideMenu && new SideMenu(root.sideMenu);
15
+    this.bottomTabs = root.bottomTabs && new BottomTabs(root.bottomTabs).tabs;
16
+  }
17
+}
18
+
19
+module.exports = Root;

+ 83
- 0
lib/src/params/Root.test.js 查看文件

@@ -0,0 +1,83 @@
1
+const Root = require('./Root');
2
+
3
+const CONTAINER = { name: 'myScreen' };
4
+const SIDE_MENU = {
5
+  left: {
6
+    container: {
7
+      name: 'navigation.playground.TextScreen',
8
+      passProps: {
9
+        text: 'This is a left side menu screen'
10
+      }
11
+    }
12
+  },
13
+  right: {
14
+    container: {
15
+      name: 'navigation.playground.TextScreen',
16
+      passProps: {
17
+        text: 'This is a right side menu screen'
18
+      }
19
+    }
20
+  }
21
+};
22
+const BOTTOM_TABS = [
23
+  {
24
+    container: {
25
+      name: 'navigation.playground.TextScreen',
26
+      passProps: {
27
+        text: 'This is a side menu center screen tab 1'
28
+      }
29
+    }
30
+  },
31
+  {
32
+    container: {
33
+      name: 'navigation.playground.TextScreen',
34
+      passProps: {
35
+        text: 'This is a side menu center screen tab 2'
36
+      }
37
+    }
38
+  },
39
+  {
40
+    container: {
41
+      name: 'navigation.playground.TextScreen',
42
+      passProps: {
43
+        text: 'This is a side menu center screen tab 3'
44
+      }
45
+    }
46
+  }
47
+];
48
+
49
+describe('Root', () => {
50
+  it('Parses Root', () => {
51
+    const uut = new Root(simpleRoot());
52
+    expect(uut.container.name).toEqual(CONTAINER.name);
53
+  });
54
+
55
+  it('parses root with sideMenu', () => {
56
+    const uut = new Root(rootWithSideMenu());
57
+    expect(uut.container.name).toEqual(CONTAINER.name);
58
+    expect(uut.sideMenu).toEqual(SIDE_MENU);
59
+  });
60
+
61
+  it('parses root with BottomTabs', () => {
62
+    const uut = new Root(rootWithBottomTabs());
63
+    expect(uut.bottomTabs).toEqual(BOTTOM_TABS);
64
+  });
65
+});
66
+
67
+function rootWithBottomTabs() {
68
+  return {
69
+    container: CONTAINER,
70
+    bottomTabs: BOTTOM_TABS
71
+  };
72
+}
73
+
74
+function simpleRoot() {
75
+  return { container: CONTAINER };
76
+}
77
+
78
+function rootWithSideMenu() {
79
+  return {
80
+    container: CONTAINER,
81
+    sideMenu: SIDE_MENU
82
+  };
83
+}

+ 15
- 0
lib/src/params/SideMenu.js 查看文件

@@ -0,0 +1,15 @@
1
+const Container = require('./Container');
2
+
3
+class SideMenu {
4
+  /**
5
+  * @typedef {Object} SideMenu
6
+  * @property {Container} [left]
7
+  * @property {Container} [right]
8
+  */
9
+  constructor(params) {
10
+    this.left = params.left && { container: new Container(params.left.container) };
11
+    this.right = params.right && { container: new Container(params.right.container) };
12
+  }
13
+}
14
+
15
+module.exports = SideMenu;

+ 13
- 0
lib/src/params/SideMenu.test.js 查看文件

@@ -0,0 +1,13 @@
1
+const SideMenu = require('./SideMenu');
2
+
3
+const LEFT = { container: { name: 'myLeftScreen' } };
4
+const RIGHT = { container: { name: 'myRightScreen' } };
5
+const SIDE_MENU = { left: LEFT, right: RIGHT };
6
+
7
+describe('SideMenu', () => {
8
+  it('parses SideMenu', () => {
9
+    const uut = new SideMenu(SIDE_MENU);
10
+    expect(uut.left).toEqual(LEFT);
11
+    expect(uut.right).toEqual(RIGHT);
12
+  });
13
+});

+ 24
- 0
lib/src/params/TabBar.js 查看文件

@@ -0,0 +1,24 @@
1
+const { isEmpty } = require('lodash');
2
+
3
+class TabBar {
4
+  /**
5
+   * @typedef {Object} TabBar
6
+   * @property {string} [currentTabId]
7
+   * @property {number} [currentTabIndex]
8
+   * @property {number} [tabBadge]
9
+   * @property {boolean} [hidden]
10
+   * @property {boolean} [animateHide]
11
+   */
12
+  constructor(params) {
13
+    if (isEmpty(params)) {
14
+      return;
15
+    }
16
+    this.currentTabId = params.currentTabId;
17
+    this.currentTabIndex = params.currentTabIndex;
18
+    this.tabBadge = params.tabBadge;
19
+    this.hidden = params.hidden;
20
+    this.animateHide = params.animateHide;
21
+  }
22
+}
23
+
24
+module.exports = TabBar;

+ 20
- 0
lib/src/params/TabBar.test.js 查看文件

@@ -0,0 +1,20 @@
1
+const TabBar = require('./TabBar');
2
+
3
+const TAB_BAR = {
4
+  currentTabId: 1,
5
+  currentTabIndex: 2,
6
+  tabBadge: 3,
7
+  hidden: true,
8
+  animateHide: true
9
+};
10
+
11
+describe('TabBar', () => {
12
+  it('parses TabBar options', () => {
13
+    const uut = new TabBar(TAB_BAR);
14
+    expect(uut.currentTabId).toEqual(1);
15
+    expect(uut.currentTabIndex).toEqual(2);
16
+    expect(uut.tabBadge).toEqual(3);
17
+    expect(uut.hidden).toEqual(true);
18
+    expect(uut.animateHide).toEqual(true);
19
+  });
20
+});

+ 27
- 0
lib/src/params/TopBar.js 查看文件

@@ -0,0 +1,27 @@
1
+class TopBar {
2
+  /**
3
+   * @typedef {Object} TopBar
4
+   * @property {string} [title]
5
+   * @property {color} [backgroundColor]
6
+   * @property {color} [textColor]
7
+   * @property {number} [textFontSize]
8
+   * @property {string} [textFontFamily]
9
+   * @property {boolean} [hidden]
10
+   * @property {boolean} [animateHide]
11
+   * @property {boolean} [hideOnScroll]
12
+   * @property {boolean} [transparent]
13
+   */
14
+  constructor(options) {
15
+    this.title = options.title;
16
+    this.backgroundColor = options.backgroundColor;
17
+    this.textColor = options.textColor;
18
+    this.textFontSize = options.textFontSize;
19
+    this.textFontFamily = options.textFontFamily;
20
+    this.hidden = options.hidden;
21
+    this.animateHide = options.animateHide;
22
+    this.hideOnScroll = options.hideOnScroll;
23
+    this.transparent = options.transparent;
24
+  }
25
+}
26
+
27
+module.exports = TopBar;

+ 28
- 0
lib/src/params/TopBar.test.js 查看文件

@@ -0,0 +1,28 @@
1
+const TopBar = require('./TopBar');
2
+
3
+const TOP_BAR = {
4
+  title: 'something',
5
+  backgroundColor: 'red',
6
+  textColor: 'green',
7
+  textFontSize: 13,
8
+  textFontFamily: 'guttmanYadBrush',
9
+  hidden: true,
10
+  animateHide: true,
11
+  hideOnScroll: true,
12
+  transparent: true
13
+};
14
+
15
+describe('TopBar', () => {
16
+  it('Parses TopBar', () => {
17
+    const uut = new TopBar(TOP_BAR);
18
+    expect(uut.title).toEqual('something');
19
+    expect(uut.backgroundColor).toEqual('red');
20
+    expect(uut.textColor).toEqual('green');
21
+    expect(uut.textFontSize).toEqual(13);
22
+    expect(uut.textFontFamily).toEqual('guttmanYadBrush');
23
+    expect(uut.hidden).toEqual(true);
24
+    expect(uut.animateHide).toEqual(true);
25
+    expect(uut.hideOnScroll).toEqual(true);
26
+    expect(uut.transparent).toEqual(true);
27
+  });
28
+});

+ 10
- 7
package.json 查看文件

@@ -38,7 +38,8 @@
38 38
     "test-e2e-ios": "node ./scripts/test.e2e.ios.js",
39 39
     "test-all": "node ./scripts/test.all.js",
40 40
     "test-watch": "jest --coverage --watch",
41
-    "release": "node ./scripts/release.js"
41
+    "release": "node ./scripts/release.js",
42
+    "gen-doc": "node ./scripts/generate-js-doc.js"
42 43
   },
43 44
   "peerDependencies": {
44 45
     "react": "*",
@@ -49,21 +50,23 @@
49 50
     "prop-types": "15.x.x"
50 51
   },
51 52
   "devDependencies": {
52
-    "xo": "0.18.x",
53
+    "detox": "6.x.x",
53 54
     "eslint-config-xo": "0.18.x",
54 55
     "eslint-config-xo-react": "0.13.x",
55 56
     "eslint-plugin-react": "7.x.x",
56
-    "detox": "6.x.x",
57 57
     "jest": "21.x.x",
58
-    "mocha": "3.x.x",
58
+    "jsdoc": "3.x.x",
59
+    "jsdoc-to-markdown": "3.x.x",
60
+    "mocha": "4.x.x",
59 61
     "react": "16.0.0-alpha.6",
60 62
     "react-native": "0.44.2",
63
+    "react-redux": "5.x.x",
61 64
     "react-test-renderer": "16.0.0-alpha.6",
62
-    "remx": "2.x.x",
63 65
     "redux": "3.x.x",
64
-    "react-redux": "5.x.x",
66
+    "remx": "2.x.x",
65 67
     "semver": "5.x.x",
66
-    "shell-utils": "1.x.x"
68
+    "shell-utils": "1.x.x",
69
+    "xo": "0.18.x"
67 70
   },
68 71
   "babel": {
69 72
     "env": {

+ 38
- 0
scripts/generate-js-doc.js 查看文件

@@ -0,0 +1,38 @@
1
+const jsdoc2md = require('jsdoc-to-markdown');
2
+const fs = require('fs');
3
+const path = require('path');
4
+
5
+const inputFiles = ['./lib/src/params/NavigationOptions.js', './lib/src/Navigation.js'];
6
+const outputDir = './docs/docs/';
7
+const partial = ['./docs/templates/scope.hbs', './docs/templates/docs.hbs'];
8
+
9
+const generateMarkdownForFile = (file) => {
10
+  const templateData = jsdoc2md.getTemplateDataSync({ files: file });
11
+  const classNames = getClassesInFile(templateData);
12
+  classNames.forEach((className) => createDocFileForClass(className, templateData));
13
+};
14
+
15
+function getClassesInFile(templateData) {
16
+  const classNames = templateData.reduce((classNames, identifier) => {
17
+    if (identifier.kind === 'class') {
18
+      classNames.push(identifier.name);
19
+    }
20
+    return classNames;
21
+  }, []);
22
+  return classNames;
23
+}
24
+
25
+function createDocFileForClass(className, templateData) {
26
+  const template = `{{#class name="${className}"}}{{>docs}}{{/class}}`;
27
+  const options = {
28
+    data: templateData,
29
+    template,
30
+    separators: true,
31
+    partial
32
+  };
33
+  console.log(`rendering ${className}, template: ${template}`);
34
+  const output = jsdoc2md.renderSync(options);
35
+  fs.writeFileSync(path.resolve(outputDir, `${className}.md`), output);
36
+}
37
+
38
+inputFiles.forEach((inputFile) => generateMarkdownForFile(inputFile));