Skip to content

Commit 1e258b8

Browse files
committed
Implement basic functionality
1 parent 9b84952 commit 1e258b8

8 files changed

Lines changed: 182 additions & 19 deletions

File tree

e2e/SideMenu.test.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import TestIDs from '../playground/src/testIDs';
33

44
const { elementByLabel, elementById } = Utils;
55

6-
describe.e2e('SideMenu', () => {
6+
describe('SideMenu', () => {
77
beforeEach(async () => {
88
await device.launchApp({ newInstance: true });
99
await elementById(TestIDs.SIDE_MENU_BTN).tap();
@@ -38,13 +38,13 @@ describe.e2e('SideMenu', () => {
3838
await expect(elementById(TestIDs.CLOSE_RIGHT_SIDE_MENU_BTN)).toBeNotVisible();
3939
});
4040

41-
it('should rotate', async () => {
41+
it.e2e('should rotate', async () => {
4242
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
4343
await device.setOrientation('landscape');
4444
await expect(elementById(TestIDs.LEFT_SIDE_MENU_PUSH_BTN)).toBeVisible();
4545
});
4646

47-
it(':ios: rotation should update drawer height', async () => {
47+
it.e2e(':ios: rotation should update drawer height', async () => {
4848
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
4949
await expect(elementByLabel('left drawer height: 869')).toBeVisible();
5050
await device.setOrientation('landscape');
@@ -53,23 +53,24 @@ describe.e2e('SideMenu', () => {
5353
await expect(elementByLabel('left drawer height: 869')).toBeVisible();
5454
});
5555

56-
it('should set left drawer width', async () => {
56+
it.e2e('should set left drawer width', async () => {
5757
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
58+
await expect(elementById(TestIDs.SIDE_MENU_LEFT_DRAWER_HEIGHT_TEXT)).toBeVisible();
5859
await expect(elementByLabel('left drawer width: 250')).toBeVisible();
5960
});
6061

61-
it('should change left drawer width', async () => {
62+
it.e2e('should change left drawer width', async () => {
6263
await elementById(TestIDs.CHANGE_LEFT_SIDE_MENU_WIDTH_BTN).tap();
6364
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
6465
await expect(elementByLabel('left drawer width: 50')).toBeVisible();
6566
});
6667

67-
it('should set right drawer width', async () => {
68+
it.e2e('should set right drawer width', async () => {
6869
await elementById(TestIDs.OPEN_RIGHT_SIDE_MENU_BTN).tap();
6970
await expect(elementByLabel('right drawer width: 250')).toBeVisible();
7071
});
7172

72-
it('should change right drawer width', async () => {
73+
it.e2e('should change right drawer width', async () => {
7374
await elementById(TestIDs.CHANGE_RIGHT_SIDE_MENU_WIDTH_BTN).tap();
7475
await elementById(TestIDs.OPEN_RIGHT_SIDE_MENU_BTN).tap();
7576
await expect(elementByLabel('right drawer width: 50')).toBeVisible();

lib/Mock/Components/ComponentScreen.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export const ComponentScreen = connect(
2525
}
2626

2727
isVisible(): boolean {
28-
return LayoutStore.isVisibleLayout(this.props.layoutNode);
28+
const isVisible = LayoutStore.isVisibleLayout(this.props.layoutNode);
29+
return isVisible;
2930
}
3031

3132
renderTabBar() {

lib/Mock/Components/LayoutComponent.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { BottomTabs } from './BottomTabs';
44
import { ComponentProps } from '../ComponentProps';
55
import { ComponentScreen } from './ComponentScreen';
66
import { Stack } from './Stack';
7+
import { SideMenuRoot, SideMenuCenter, SideMenuLeft, SideMenuRight } from './SideMenu';
78

89
export const LayoutComponent = class extends Component<ComponentProps> {
910
render() {
@@ -14,6 +15,14 @@ export const LayoutComponent = class extends Component<ComponentProps> {
1415
return <Stack layoutNode={this.props.layoutNode} />;
1516
case 'Component':
1617
return <ComponentScreen layoutNode={this.props.layoutNode} />;
18+
case 'SideMenuRoot':
19+
return <SideMenuRoot layoutNode={this.props.layoutNode} />;
20+
case 'SideMenuLeft':
21+
return <SideMenuLeft layoutNode={this.props.layoutNode} />;
22+
case 'SideMenuCenter':
23+
return <SideMenuCenter layoutNode={this.props.layoutNode} />;
24+
case 'SideMenuRight':
25+
return <SideMenuRight layoutNode={this.props.layoutNode} />;
1726
}
1827

1928
return <View />;

lib/Mock/Components/SideMenu.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React, { Component } from 'react';
2+
import { connect } from '../connect';
3+
import { ComponentProps } from '../ComponentProps';
4+
import { LayoutComponent } from './LayoutComponent';
5+
import ParentNode from '../Layouts/ParentNode';
6+
7+
export const SideMenuRoot = connect(
8+
class extends Component<ComponentProps> {
9+
render() {
10+
const children = this.props.layoutNode.children;
11+
return children.map((child: ParentNode) => {
12+
return <LayoutComponent key={child.nodeId} layoutNode={child} />;
13+
});
14+
}
15+
}
16+
);
17+
18+
class SideMenuComponent extends Component<ComponentProps> {
19+
render() {
20+
const children = this.props.layoutNode.children;
21+
const component = children[0];
22+
return <LayoutComponent key={component.nodeId} layoutNode={component} />;
23+
}
24+
}
25+
export const SideMenuLeft = connect(SideMenuComponent);
26+
export const SideMenuCenter = connect(SideMenuComponent);
27+
export const SideMenuRight = connect(SideMenuComponent);

lib/Mock/Layouts/LayoutNodeFactory.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import BottomTabs from './BottomTabsNode';
22
import ComponentNode from './ComponentNode';
33
import Stack from './StackNode';
44
import ParentNode from './ParentNode';
5+
import SideMenuRootNode, {
6+
SideMenuLeftNode,
7+
SideMenuRightNode,
8+
SideMenuCenterNode,
9+
} from './SideMenu';
510

611
export default class LayoutNodeFactory {
712
static create(layout: any, parentNode?: ParentNode) {
@@ -10,7 +15,15 @@ export default class LayoutNodeFactory {
1015
return new ComponentNode(layout, parentNode);
1116
case 'Stack':
1217
return new Stack(layout, parentNode);
13-
default:
18+
case 'SideMenuRoot':
19+
return new SideMenuRootNode(layout, parentNode);
20+
case 'SideMenuLeft':
21+
return new SideMenuLeftNode(layout, parentNode);
22+
case 'SideMenuCenter':
23+
return new SideMenuCenterNode(layout, parentNode);
24+
case 'SideMenuRight':
25+
return new SideMenuRightNode(layout, parentNode);
26+
default: // TODO Undo
1427
case 'BottomTabs':
1528
return new BottomTabs(layout, parentNode);
1629
}

lib/Mock/Layouts/SideMenu.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import ParentNode from './ParentNode';
2+
import ComponentNode from './ComponentNode';
3+
import { Options } from '../../src/index';
4+
import * as layoutActions from '../actions/layoutActions';
5+
import { NodeType } from './Node';
6+
7+
const isCenterChild = (child: ParentNode) => child.type === 'SideMenuCenter';
8+
const isLeftChild = (child: ParentNode) => child.type === 'SideMenuLeft';
9+
const isRightChild = (child: ParentNode) => child.type === 'SideMenuRight';
10+
11+
export default class SideMenuRootNode extends ParentNode {
12+
visibleChild: ParentNode;
13+
14+
constructor(layout: any, parentNode?: ParentNode) {
15+
super(layout, 'SideMenuRoot', parentNode);
16+
17+
this.visibleChild = this._getCenterChild();
18+
if (!this.visibleChild) {
19+
throw new Error('SideMenuRootNode must have a SideMenuCenter child');
20+
}
21+
}
22+
23+
mergeOptions(options: Options) {
24+
super.mergeOptions(options);
25+
26+
this._updateVisibility(options);
27+
}
28+
29+
/**
30+
* @override
31+
*/
32+
getVisibleLayout(): ComponentNode {
33+
return this.visibleChild.getVisibleLayout();
34+
}
35+
36+
_updateVisibility(options: Options) {
37+
if (options.sideMenu?.left?.visible) {
38+
this.visibleChild = this._getLeftChild();
39+
layoutActions.openSideMenu(this.visibleChild);
40+
} else if (options.sideMenu?.right?.visible) {
41+
this.visibleChild = this._getRightChild();
42+
layoutActions.openSideMenu(this.visibleChild);
43+
} else {
44+
this.visibleChild = this._getCenterChild();
45+
layoutActions.closeSideMenu(this.visibleChild);
46+
}
47+
}
48+
49+
_getCenterChild = () => this.children.find(isCenterChild) as ParentNode;
50+
_getLeftChild = () => this.children.find(isLeftChild) as ParentNode;
51+
_getRightChild = () => this.children.find(isRightChild) as ParentNode;
52+
}
53+
54+
export class SideMenuNode extends ParentNode {
55+
constructor(layout: any, type: NodeType, parentNode?: ParentNode) {
56+
super(layout, type, parentNode);
57+
}
58+
59+
getVisibleLayout() {
60+
return this.children[0].getVisibleLayout();
61+
}
62+
}
63+
64+
export class SideMenuLeftNode extends SideMenuNode {
65+
constructor(layout: any, parentNode?: ParentNode) {
66+
super(layout, 'SideMenuLeft', parentNode);
67+
}
68+
}
69+
export class SideMenuRightNode extends SideMenuNode {
70+
constructor(layout: any, parentNode?: ParentNode) {
71+
super(layout, 'SideMenuRight', parentNode);
72+
}
73+
}
74+
75+
export class SideMenuCenterNode extends SideMenuNode {
76+
constructor(layout: any, parentNode?: ParentNode) {
77+
super(layout, 'SideMenuCenter', parentNode);
78+
}
79+
}

lib/Mock/Stores/LayoutStore.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ import _ from 'lodash';
22
import BottomTabsNode from '../Layouts/BottomTabsNode';
33
import ParentNode from '../Layouts/ParentNode';
44
import LayoutNodeFactory from '../Layouts/LayoutNodeFactory';
5-
import { Options } from '../../src/interfaces/Options';
5+
import { SideMenuNode } from '../Layouts/SideMenu';
66
import StackNode from '../Layouts/StackNode';
7+
import { Options } from '../../src/interfaces/Options';
78

89
const remx = require('remx');
910

1011
const state = remx.state({
1112
root: {},
1213
modals: [],
1314
overlays: [],
15+
sideMenu: undefined,
1416
});
1517

1618
const setters = remx.setters({
@@ -75,6 +77,12 @@ const setters = remx.setters({
7577
selectTabIndex(layout: BottomTabsNode, index: number) {
7678
getters.getLayoutById(layout.nodeId).selectedIndex = index;
7779
},
80+
openSideMenu(layout: SideMenuNode) {
81+
state.sideMenu = layout;
82+
},
83+
closeSideMenu(_layout: SideMenuNode) {
84+
state.sideMenu = undefined;
85+
},
7886
mergeOptions(componentId: string, options: Options) {
7987
const layout = getters.getLayoutById(componentId);
8088
if (layout) layout.mergeOptions(options);
@@ -87,12 +95,27 @@ const getters = remx.getters({
8795
return state.root;
8896
},
8997
getVisibleLayout() {
98+
let layout: ParentNode | undefined;
9099
if (state.modals.length > 0) {
91-
return _.last<ParentNode>(state.modals)!.getVisibleLayout();
92-
} else if (!_.isEqual(state.root, {})) return state.root.getVisibleLayout();
100+
layout = _.last<ParentNode>(state.modals)!;
101+
} else if (!_.isEqual(state.root, {})) {
102+
layout = state.root;
103+
}
104+
105+
// TODO revisit this logic; working or not - state.sideMenu has
106+
// to be touched here in order to force reevaluation of the visible layout
107+
// while side menu open/close handling (i.e. to move away-from / back-to the
108+
// side-menu center child).
109+
if (layout && state.sideMenu && findNode(state.sideMenu.nodeId, layout!)) {
110+
layout = state.sideMenu.parentNode;
111+
}
112+
113+
return layout?.getVisibleLayout();
93114
},
94115
isVisibleLayout(layout: ParentNode) {
95-
return getters.getVisibleLayout() && getters.getVisibleLayout().nodeId === layout.nodeId;
116+
const nodeId = layout.nodeId;
117+
const visibleLayout = getters.getVisibleLayout();
118+
return visibleLayout?.nodeId === nodeId;
96119
},
97120
getModals() {
98121
return state.modals;
@@ -101,13 +124,12 @@ const getters = remx.getters({
101124
return state.overlays;
102125
},
103126
getLayoutById(layoutId: string) {
104-
if (getters.getModalById(layoutId))
105-
return findParentNode(layoutId, getters.getModalById(layoutId));
127+
if (getters.getModalById(layoutId)) return findNode(layoutId, getters.getModalById(layoutId));
106128

107-
return findParentNode(layoutId, state.root);
129+
return findNode(layoutId, state.root);
108130
},
109131
getModalById(layoutId: string) {
110-
return _.find(state.modals, (layout) => findParentNode(layoutId, layout));
132+
return _.find(state.modals, (layout) => findNode(layoutId, layout));
111133
},
112134
getLayoutChildren(layoutId: string) {
113135
return getters.getLayoutById(layoutId).children;
@@ -120,13 +142,13 @@ const getters = remx.getters({
120142
},
121143
});
122144

123-
function findParentNode(layoutId: string, layout: ParentNode): any | ParentNode {
145+
function findNode(layoutId: string, layout: ParentNode): any | ParentNode {
124146
if (layoutId === layout.nodeId) {
125147
return layout;
126148
} else if (layout.children) {
127149
for (let i = 0; i < layout.children.length; i += 1) {
128150
const child = layout.children[i];
129-
const result = findParentNode(layoutId, child);
151+
const result = findNode(layoutId, child);
130152

131153
if (result !== false) {
132154
return result;

lib/Mock/actions/layoutActions.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ParentNode from '../Layouts/ParentNode';
2+
import { SideMenuNode } from '../Layouts/SideMenu';
23
import { LayoutStore } from '../Stores/LayoutStore';
34

45
export const switchTabByIndex = (bottomTabs: ParentNode | undefined, index: number) => {
@@ -8,3 +9,13 @@ export const switchTabByIndex = (bottomTabs: ParentNode | undefined, index: numb
89
LayoutStore.getVisibleLayout().componentDidAppear();
910
}
1011
};
12+
13+
export const openSideMenu = (sideMenu: SideMenuNode) => {
14+
LayoutStore.openSideMenu(sideMenu);
15+
LayoutStore.getVisibleLayout().componentDidAppear();
16+
};
17+
18+
export const closeSideMenu = (layout: SideMenuNode) => {
19+
LayoutStore.getVisibleLayout().componentDidDisappear();
20+
LayoutStore.closeSideMenu(layout);
21+
};

0 commit comments

Comments
 (0)