Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions e2e/SideMenu.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import TestIDs from '../playground/src/testIDs';

const { elementByLabel, elementById } = Utils;

describe.e2e('SideMenu', () => {
describe('SideMenu', () => {
beforeEach(async () => {
await device.launchApp({ newInstance: true });
await elementById(TestIDs.SIDE_MENU_BTN).tap();
Expand Down Expand Up @@ -38,13 +38,13 @@ describe.e2e('SideMenu', () => {
await expect(elementById(TestIDs.CLOSE_RIGHT_SIDE_MENU_BTN)).toBeNotVisible();
});

it('should rotate', async () => {
it.e2e('should rotate', async () => {
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
await device.setOrientation('landscape');
await expect(elementById(TestIDs.LEFT_SIDE_MENU_PUSH_BTN)).toBeVisible();
});

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

it('should set left drawer width', async () => {
it.e2e('should set left drawer width', async () => {
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
await expect(elementById(TestIDs.SIDE_MENU_LEFT_DRAWER_HEIGHT_TEXT)).toBeVisible();
await expect(elementByLabel('left drawer width: 250')).toBeVisible();
});

it('should change left drawer width', async () => {
it.e2e('should change left drawer width', async () => {
await elementById(TestIDs.CHANGE_LEFT_SIDE_MENU_WIDTH_BTN).tap();
await elementById(TestIDs.OPEN_LEFT_SIDE_MENU_BTN).tap();
await expect(elementByLabel('left drawer width: 50')).toBeVisible();
});

it('should set right drawer width', async () => {
it.e2e('should set right drawer width', async () => {
await elementById(TestIDs.OPEN_RIGHT_SIDE_MENU_BTN).tap();
await expect(elementByLabel('right drawer width: 250')).toBeVisible();
});

it('should change right drawer width', async () => {
it.e2e('should change right drawer width', async () => {
await elementById(TestIDs.CHANGE_RIGHT_SIDE_MENU_WIDTH_BTN).tap();
await elementById(TestIDs.OPEN_RIGHT_SIDE_MENU_BTN).tap();
await expect(elementByLabel('right drawer width: 50')).toBeVisible();
Expand Down
3 changes: 2 additions & 1 deletion lib/Mock/Components/ComponentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export const ComponentScreen = connect(
}

isVisible(): boolean {
return LayoutStore.isVisibleLayout(this.props.layoutNode);
const isVisible = LayoutStore.isVisibleLayout(this.props.layoutNode);
return isVisible;
}

renderTabBar() {
Expand Down
9 changes: 9 additions & 0 deletions lib/Mock/Components/LayoutComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BottomTabs } from './BottomTabs';
import { ComponentProps } from '../ComponentProps';
import { ComponentScreen } from './ComponentScreen';
import { Stack } from './Stack';
import { SideMenuRoot, SideMenuCenter, SideMenuLeft, SideMenuRight } from './SideMenu';

export const LayoutComponent = class extends Component<ComponentProps> {
render() {
Expand All @@ -14,6 +15,14 @@ export const LayoutComponent = class extends Component<ComponentProps> {
return <Stack layoutNode={this.props.layoutNode} />;
case 'Component':
return <ComponentScreen layoutNode={this.props.layoutNode} />;
case 'SideMenuRoot':
return <SideMenuRoot layoutNode={this.props.layoutNode} />;
case 'SideMenuLeft':
return <SideMenuLeft layoutNode={this.props.layoutNode} />;
case 'SideMenuCenter':
return <SideMenuCenter layoutNode={this.props.layoutNode} />;
case 'SideMenuRight':
return <SideMenuRight layoutNode={this.props.layoutNode} />;
}

return <View />;
Expand Down
27 changes: 27 additions & 0 deletions lib/Mock/Components/SideMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { Component } from 'react';
import { connect } from '../connect';
import { ComponentProps } from '../ComponentProps';
import { LayoutComponent } from './LayoutComponent';
import ParentNode from '../Layouts/ParentNode';

export const SideMenuRoot = connect(
class extends Component<ComponentProps> {
render() {
const children = this.props.layoutNode.children;
return children.map((child: ParentNode) => {
return <LayoutComponent key={child.nodeId} layoutNode={child} />;
});
}
}
);

class SideMenuComponent extends Component<ComponentProps> {
render() {
const children = this.props.layoutNode.children;
const component = children[0];
return <LayoutComponent key={component.nodeId} layoutNode={component} />;
}
}
export const SideMenuLeft = connect(SideMenuComponent);
export const SideMenuCenter = connect(SideMenuComponent);
export const SideMenuRight = connect(SideMenuComponent);
6 changes: 4 additions & 2 deletions lib/Mock/Layouts/BottomTabsNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ export default class BottomTabsNode extends ParentNode {
this.selectedIndex = layout.data?.options?.bottomTabs?.currentTabIndex || 0;
}

mergeOptions(options: Options) {
super.mergeOptions(options);
mergeOptions(_options: Options) {
super.mergeOptions(_options);

const { options } = this.data;
if (options.bottomTabs?.currentTabIndex) {
this.selectedIndex = options.bottomTabs?.currentTabIndex;
switchTabByIndex(this, this.selectedIndex);
Expand Down
15 changes: 14 additions & 1 deletion lib/Mock/Layouts/LayoutNodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import BottomTabs from './BottomTabsNode';
import ComponentNode from './ComponentNode';
import Stack from './StackNode';
import ParentNode from './ParentNode';
import SideMenuRootNode, {
SideMenuLeftNode,
SideMenuRightNode,
SideMenuCenterNode,
} from './SideMenu';

export default class LayoutNodeFactory {
static create(layout: any, parentNode?: ParentNode) {
Expand All @@ -10,7 +15,15 @@ export default class LayoutNodeFactory {
return new ComponentNode(layout, parentNode);
case 'Stack':
return new Stack(layout, parentNode);
default:
case 'SideMenuRoot':
return new SideMenuRootNode(layout, parentNode);
case 'SideMenuLeft':
return new SideMenuLeftNode(layout, parentNode);
case 'SideMenuCenter':
return new SideMenuCenterNode(layout, parentNode);
case 'SideMenuRight':
return new SideMenuRightNode(layout, parentNode);
default: // TODO Undo
case 'BottomTabs':
return new BottomTabs(layout, parentNode);
}
Expand Down
4 changes: 4 additions & 0 deletions lib/Mock/Layouts/ParentNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export default class ParentNode extends Node {
return this;
}

applyOptions(_options: Options) {
this.parentNode?.applyOptions(_options);
}

mergeOptions(options: Options) {
this.data.options = _.mergeWith(this.data.options, options, (objValue, srcValue, key) => {
if (_.isArray(objValue)) {
Expand Down
87 changes: 87 additions & 0 deletions lib/Mock/Layouts/SideMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import ParentNode from './ParentNode';
import ComponentNode from './ComponentNode';
import { Options } from '../../src/index';
import * as layoutActions from '../actions/layoutActions';
import { NodeType } from './Node';

const isCenterChild = (child: ParentNode) => child.type === 'SideMenuCenter';
const isLeftChild = (child: ParentNode) => child.type === 'SideMenuLeft';
const isRightChild = (child: ParentNode) => child.type === 'SideMenuRight';

export default class SideMenuRootNode extends ParentNode {
visibleChild: ParentNode;

constructor(layout: any, parentNode?: ParentNode) {
super(layout, 'SideMenuRoot', parentNode);

this.visibleChild = this._getCenterChild();
if (!this.visibleChild) {
throw new Error('SideMenuRootNode must have a SideMenuCenter child');
}
}

applyOptions(_options: Options) {
super.applyOptions(_options);

this._updateVisibility(_options);
}

mergeOptions(options: Options) {
super.mergeOptions(options);

this._updateVisibility(options);
}

/**
* @override
*/
getVisibleLayout(): ComponentNode {
return this.visibleChild.getVisibleLayout();
}

_updateVisibility(options: Options) {
if (options.sideMenu) {
if (options.sideMenu.left?.visible) {
this.visibleChild = this._getLeftChild();
layoutActions.openSideMenu(this.visibleChild);
} else if (options.sideMenu.right?.visible) {
this.visibleChild = this._getRightChild();
layoutActions.openSideMenu(this.visibleChild);
} else {
this.visibleChild = this._getCenterChild();
layoutActions.closeSideMenu(this.visibleChild);
}
}
}

_getCenterChild = () => this.children.find(isCenterChild) as ParentNode;
_getLeftChild = () => this.children.find(isLeftChild) as ParentNode;
_getRightChild = () => this.children.find(isRightChild) as ParentNode;
}

export class SideMenuNode extends ParentNode {
constructor(layout: any, type: NodeType, parentNode?: ParentNode) {
super(layout, type, parentNode);
}

getVisibleLayout() {
return this.children[0].getVisibleLayout();
}
}

export class SideMenuLeftNode extends SideMenuNode {
constructor(layout: any, parentNode?: ParentNode) {
super(layout, 'SideMenuLeft', parentNode);
}
}
export class SideMenuRightNode extends SideMenuNode {
constructor(layout: any, parentNode?: ParentNode) {
super(layout, 'SideMenuRight', parentNode);
}
}

export class SideMenuCenterNode extends SideMenuNode {
constructor(layout: any, parentNode?: ParentNode) {
super(layout, 'SideMenuCenter', parentNode);
}
}
52 changes: 42 additions & 10 deletions lib/Mock/Stores/LayoutStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import _ from 'lodash';
import BottomTabsNode from '../Layouts/BottomTabsNode';
import ParentNode from '../Layouts/ParentNode';
import LayoutNodeFactory from '../Layouts/LayoutNodeFactory';
import { Options } from '../../src/interfaces/Options';
import { SideMenuNode } from '../Layouts/SideMenu';
import StackNode from '../Layouts/StackNode';
import { Options } from '../../src/interfaces/Options';

const remx = require('remx');

const state = remx.state({
root: {},
modals: [],
overlays: [],
sideMenu: undefined,
});

const setters = remx.setters({
Expand Down Expand Up @@ -75,6 +77,23 @@ const setters = remx.setters({
selectTabIndex(layout: BottomTabsNode, index: number) {
getters.getLayoutById(layout.nodeId).selectedIndex = index;
},
openSideMenu(layout: SideMenuNode) {
if (state.sideMenu) {
throw new Error(
'A side-menu is already open; Mocked-testing of multiple side-menu scenarios is not supported yet.' +
' You can submit a request in https://github.com/wix/react-native-navigation/issues/new/choose.'
);
}
state.sideMenu = layout;
},
closeSideMenu(_layout: SideMenuNode) {
state.sideMenu = undefined;
},
applyOptions(componentId: string, options: Options) {
const layout = getters.getLayoutById(componentId);
if (layout) layout.applyOptions(options);
else console.warn(`[RNN error] Merge options failure: cannot find layout for: ${componentId}`);
},
mergeOptions(componentId: string, options: Options) {
const layout = getters.getLayoutById(componentId);
if (layout) layout.mergeOptions(options);
Expand All @@ -87,12 +106,26 @@ const getters = remx.getters({
return state.root;
},
getVisibleLayout() {
let layout: ParentNode | undefined;
if (state.modals.length > 0) {
return _.last<ParentNode>(state.modals)!.getVisibleLayout();
} else if (!_.isEqual(state.root, {})) return state.root.getVisibleLayout();
layout = _.last<ParentNode>(state.modals)!;
} else if (!_.isEqual(state.root, {})) {
layout = state.root;
}

// Note: While this logic should be fair for all use cases (i.e. even multiple side-menus across tabs),
// there is no current test case that justifies it. Nevertheless, it's required to pass the tests,
// because otherwise getVisibleLayout() would not be revisited whenever side-menus are opened/closed.
if (layout && state.sideMenu && findNode(state.sideMenu.nodeId, layout!)) {
layout = state.sideMenu.parentNode;
}

return layout?.getVisibleLayout();
},
isVisibleLayout(layout: ParentNode) {
return getters.getVisibleLayout() && getters.getVisibleLayout().nodeId === layout.nodeId;
const nodeId = layout.nodeId;
const visibleLayout = getters.getVisibleLayout();
return visibleLayout?.nodeId === nodeId;
},
getModals() {
return state.modals;
Expand All @@ -101,13 +134,12 @@ const getters = remx.getters({
return state.overlays;
},
getLayoutById(layoutId: string) {
if (getters.getModalById(layoutId))
return findParentNode(layoutId, getters.getModalById(layoutId));
if (getters.getModalById(layoutId)) return findNode(layoutId, getters.getModalById(layoutId));

return findParentNode(layoutId, state.root);
return findNode(layoutId, state.root);
},
getModalById(layoutId: string) {
return _.find(state.modals, (layout) => findParentNode(layoutId, layout));
return _.find(state.modals, (layout) => findNode(layoutId, layout));
},
getLayoutChildren(layoutId: string) {
return getters.getLayoutById(layoutId).children;
Expand All @@ -120,13 +152,13 @@ const getters = remx.getters({
},
});

function findParentNode(layoutId: string, layout: ParentNode): any | ParentNode {
function findNode(layoutId: string, layout: ParentNode): any | ParentNode {
if (layoutId === layout.nodeId) {
return layout;
} else if (layout.children) {
for (let i = 0; i < layout.children.length; i += 1) {
const child = layout.children[i];
const result = findParentNode(layoutId, child);
const result = findNode(layoutId, child);

if (result !== false) {
return result;
Expand Down
11 changes: 11 additions & 0 deletions lib/Mock/actions/layoutActions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ParentNode from '../Layouts/ParentNode';
import { SideMenuNode } from '../Layouts/SideMenu';
import { LayoutStore } from '../Stores/LayoutStore';

export const switchTabByIndex = (bottomTabs: ParentNode | undefined, index: number) => {
Expand All @@ -8,3 +9,13 @@ export const switchTabByIndex = (bottomTabs: ParentNode | undefined, index: numb
LayoutStore.getVisibleLayout().componentDidAppear();
}
};

export const openSideMenu = (sideMenu: SideMenuNode) => {
LayoutStore.openSideMenu(sideMenu);
LayoutStore.getVisibleLayout().componentDidAppear();
};

export const closeSideMenu = (layout: SideMenuNode) => {
LayoutStore.getVisibleLayout().componentDidDisappear();
LayoutStore.closeSideMenu(layout);
};
1 change: 1 addition & 0 deletions lib/Mock/mocks/NativeCommandsSender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class NativeCommandsSender {
const layoutNode = LayoutNodeFactory.create(layout, stack);
stack.getVisibleLayout().componentDidDisappear();
LayoutStore.push(layoutNode, stack);
LayoutStore.applyOptions(layoutNode.nodeId, layoutNode.data.options);
stack.getVisibleLayout().componentDidAppear();
resolve(stack.getVisibleLayout().nodeId);
this.reportCommandCompletion(CommandName.Push, commandId);
Expand Down