295 lines
10 KiB
JavaScript
295 lines
10 KiB
JavaScript
import React from 'react';
|
|
import { View, Text } from 'react-native';
|
|
import { render, waitFor, mockReactInstance, nthChild } from './helpers';
|
|
import { MenuOptions, MenuTrigger } from '../src/index';
|
|
import MenuOutside from '../src/renderers/MenuOutside';
|
|
import Backdrop from '../src/Backdrop';
|
|
import MenuPlaceholder from '../src/MenuPlaceholder';
|
|
import ContextMenu from '../src/renderers/ContextMenu';
|
|
const { objectContaining, createSpy } = jasmine;
|
|
|
|
jest.dontMock('../src/MenuProvider');
|
|
jest.dontMock('../src/menuRegistry');
|
|
|
|
jest.mock('../src/helpers', () => ({
|
|
deprecatedComponent: jest.fn(() => jest.fn()),
|
|
measure: () => ({
|
|
then: cb => cb({
|
|
x: 0,
|
|
y: 0,
|
|
width: 100,
|
|
height: 50,
|
|
}),
|
|
}),
|
|
lo: x => x,
|
|
iterator2array: it => [...it],
|
|
}));
|
|
|
|
const {default: MenuProvider, PopupMenuContext} = require('../src/MenuProvider');
|
|
|
|
describe('MenuProvider', () => {
|
|
|
|
/* eslint-disable react/display-name */
|
|
function makeMenuStub(name) {
|
|
let opened = false;
|
|
return {
|
|
getName: ()=>name,
|
|
_getOpened: ()=>opened,
|
|
_setOpened: (value)=>opened=value,
|
|
_isOpen: ()=>opened,
|
|
_getTrigger: ()=>(<MenuTrigger/>),
|
|
_getOptions: ()=>(<MenuOptions/>),
|
|
props : {
|
|
onOpen: createSpy(),
|
|
onClose: createSpy(),
|
|
onBackdropPress: createSpy(),
|
|
type: 'context',
|
|
renderer: ContextMenu,
|
|
},
|
|
}
|
|
}
|
|
|
|
const defaultLayout = {
|
|
nativeEvent: {
|
|
layout: {
|
|
width: 400,
|
|
height: 600,
|
|
},
|
|
},
|
|
};
|
|
|
|
let menu1;
|
|
|
|
beforeEach(() => {
|
|
menu1 = makeMenuStub('menu1');
|
|
});
|
|
|
|
// render menu provider in default configuration and call "standard" lifecycle methods
|
|
function renderProvider(props) {
|
|
const rendered = render(
|
|
<MenuProvider {...props}/>
|
|
);
|
|
const { instance, output } = rendered;
|
|
rendered.placeholder = mockReactInstance();
|
|
instance._onPlaceholderRef(rendered.placeholder);
|
|
// for tests mimic old ctx api
|
|
const ctx = output.props.value
|
|
instance.getChildContext = () => ctx
|
|
// and "strip" context provider
|
|
rendered.output = nthChild(output, 1)
|
|
return rendered;
|
|
}
|
|
|
|
// renders placeholder and returns array of rendered backdrop and options
|
|
function renderPlaceholderChildren(ctx) {
|
|
const { output } = render(<MenuPlaceholder ctx={ctx} />);
|
|
if (output === null) {
|
|
return [];
|
|
}
|
|
return output.props.children;
|
|
}
|
|
|
|
it('should expose api', () => {
|
|
const { instance } = render(
|
|
<MenuProvider />
|
|
);
|
|
expect(typeof instance.openMenu).toEqual('function');
|
|
expect(typeof instance.closeMenu).toEqual('function');
|
|
expect(typeof instance.toggleMenu).toEqual('function');
|
|
expect(typeof instance.isMenuOpen).toEqual('function');
|
|
// context is now "renderer" -> see 'should render child components'
|
|
});
|
|
|
|
it('should render child components', () => {
|
|
let { output } = render(
|
|
<MenuProvider>
|
|
<View />
|
|
<Text>Some text</Text>
|
|
</MenuProvider>
|
|
);
|
|
// check context
|
|
expect(output.type).toEqual(PopupMenuContext.Provider);
|
|
const { menuRegistry, menuActions }=output.props.value;
|
|
expect(typeof menuRegistry).toEqual('object');
|
|
expect(typeof menuActions).toEqual('object');
|
|
expect(typeof menuActions.openMenu).toEqual('function');
|
|
expect(typeof menuActions.closeMenu).toEqual('function');
|
|
expect(typeof menuActions.toggleMenu).toEqual('function');
|
|
expect(typeof menuActions.isMenuOpen).toEqual('function');
|
|
// plus internal methods
|
|
expect(typeof menuActions._notify).toEqual('function');
|
|
// check the rest
|
|
output = nthChild(output, 1)
|
|
expect(output.type).toEqual(View);
|
|
expect(typeof output.props.onLayout).toEqual('function');
|
|
expect(output.props.children.length).toEqual(2);
|
|
const [ components, placeholder ] = output.props.children;
|
|
expect(components.type).toEqual(View);
|
|
expect(placeholder.type).toEqual(MenuPlaceholder);
|
|
expect(components.props.children).toEqual([
|
|
<View />,
|
|
<Text>Some text</Text>,
|
|
]);
|
|
});
|
|
|
|
it('should not render backdrop / options initially', () => {
|
|
const { instance } = renderProvider();
|
|
const [ backdrop, options ] = renderPlaceholderChildren(instance);
|
|
expect(backdrop).toBeFalsy();
|
|
expect(options).toBeFalsy();
|
|
});
|
|
|
|
it('should open menu', () => {
|
|
const { output: initOutput, instance } = renderProvider();
|
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
|
initOutput.props.onLayout(defaultLayout);
|
|
menuRegistry.subscribe(menu1);
|
|
return menuActions.openMenu('menu1').then(() => {
|
|
expect(menuActions.isMenuOpen()).toEqual(true);
|
|
expect(menu1._getOpened()).toEqual(true);
|
|
initOutput.props.onLayout(defaultLayout);
|
|
// next render will start rendering open menu
|
|
const [ backdrop, options ] = renderPlaceholderChildren(instance);
|
|
expect(backdrop.type).toEqual(Backdrop);
|
|
expect(options.type).toEqual(MenuOutside);
|
|
// on open was called only once
|
|
expect(menu1.props.onOpen.calls.count()).toEqual(1);
|
|
});
|
|
});
|
|
|
|
it('should close menu', () => {
|
|
const { output: initOutput, instance } = renderProvider();
|
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
|
initOutput.props.onLayout(defaultLayout);
|
|
menuRegistry.subscribe(menu1);
|
|
return menuActions.openMenu('menu1').then(() =>
|
|
menuActions.closeMenu().then(() => {
|
|
expect(menuActions.isMenuOpen()).toEqual(false);
|
|
expect(menu1.props.onClose).toHaveBeenCalled();
|
|
const [ backdrop, options ] = renderPlaceholderChildren(instance);
|
|
expect(backdrop).toBeFalsy();
|
|
expect(options).toBeFalsy();
|
|
}));
|
|
});
|
|
|
|
it('should toggle menu', () => {
|
|
const { instance } = renderProvider();
|
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
|
menuRegistry.subscribe(menu1);
|
|
return menuActions.toggleMenu('menu1').then(() => {
|
|
expect(menuActions.isMenuOpen()).toEqual(true);
|
|
expect(menu1._isOpen()).toEqual(true);
|
|
return menuActions.toggleMenu('menu1').then(() => {
|
|
expect(menuActions.isMenuOpen()).toEqual(false);
|
|
expect(menu1._isOpen()).toEqual(false);
|
|
return menuActions.toggleMenu('menu1').then(() => {
|
|
expect(menuActions.isMenuOpen()).toEqual(true);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should not open non existing menu', () => {
|
|
const { output: initOutput, instance } = renderProvider();
|
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
|
initOutput.props.onLayout(defaultLayout);
|
|
menuRegistry.subscribe(menu1);
|
|
return menuActions.openMenu('menu_not_existing').then(() => {
|
|
expect(menuActions.isMenuOpen()).toEqual(false);
|
|
const [ backdrop, options ] = renderPlaceholderChildren(instance);
|
|
expect(backdrop).toBeFalsy();
|
|
expect(options).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
it('should not open menu if not initialized', () => {
|
|
const { instance } = renderProvider();
|
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
|
menuRegistry.subscribe(menu1);
|
|
return menuActions.openMenu('menu1').then(() => {
|
|
expect(menuActions.isMenuOpen()).toEqual(true);
|
|
const [ backdrop, options ] = renderPlaceholderChildren(instance);
|
|
// on layout has not been not called
|
|
expect(backdrop).toBeFalsy();
|
|
expect(options).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
it('should update options layout', () => {
|
|
const { output: initOutput, instance } = renderProvider();
|
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
|
initOutput.props.onLayout(defaultLayout);
|
|
menuRegistry.subscribe(menu1);
|
|
return menuActions.openMenu('menu1').then(() => {
|
|
const [ , options ] = renderPlaceholderChildren(instance);
|
|
expect(typeof options.props.onLayout).toEqual('function');
|
|
options.props.onLayout({
|
|
nativeEvent: {
|
|
layout: {
|
|
width: 22,
|
|
height: 33,
|
|
},
|
|
},
|
|
});
|
|
expect(menuRegistry.getMenu('menu1')).toEqual(objectContaining({
|
|
optionsLayout: {
|
|
width: 22,
|
|
isOutside: true,
|
|
height: 33,
|
|
},
|
|
}));
|
|
});
|
|
});
|
|
|
|
it('should render backdrop that will trigger onBackdropPress', () => {
|
|
const { output: initOutput, instance } = renderProvider();
|
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
|
initOutput.props.onLayout(defaultLayout);
|
|
menuRegistry.subscribe(menu1);
|
|
return menuActions.openMenu('menu1').then(() => {
|
|
const [ backdrop ] = renderPlaceholderChildren(instance);
|
|
expect(backdrop.type).toEqual(Backdrop);
|
|
backdrop.props.onPress();
|
|
expect(menu1.props.onBackdropPress).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('should close the menu if backHandler prop is true and back button is pressed', () => {
|
|
const { output: initOutput, instance } = renderProvider({backHandler: true});
|
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
|
initOutput.props.onLayout(defaultLayout);
|
|
menuRegistry.subscribe(menu1);
|
|
return menuActions.openMenu('menu1').then(() => {
|
|
instance._handleBackButton();
|
|
return waitFor(() => !instance.isMenuOpen())
|
|
.then(() => false)
|
|
.catch(() => true)
|
|
.then((isOpen) => expect(isOpen).toEqual(false), 1000);
|
|
})
|
|
});
|
|
|
|
it('should not close the menu if backHandler prop is false and back button is pressed', () => {
|
|
const { output: initOutput, instance } = renderProvider({backHandler: false});
|
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
|
initOutput.props.onLayout(defaultLayout);
|
|
menuRegistry.subscribe(menu1);
|
|
return menuActions.openMenu('menu1').then(() => {
|
|
instance._handleBackButton();
|
|
expect(instance.isMenuOpen()).toEqual(true);
|
|
})
|
|
});
|
|
|
|
it('should invoke custom handler if backHandler prop is a function and back button is pressed', () => {
|
|
const handler = jest.fn().mockReturnValue(true);
|
|
const { output: initOutput, instance } = renderProvider({backHandler: handler});
|
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
|
initOutput.props.onLayout(defaultLayout);
|
|
menuRegistry.subscribe(menu1);
|
|
return menuActions.openMenu('menu1').then(() => {
|
|
instance._handleBackButton();
|
|
expect(handler.mock.calls).toHaveLength(1);
|
|
})
|
|
});
|
|
|
|
});
|