UNPKG

materialuiupgraded

Version:

Material-UI's workspace package

851 lines (739 loc) 26.8 kB
import React from 'react'; import { assert } from 'chai'; import { spy, stub, useFakeTimers } from 'sinon'; import css from 'dom-helpers/style'; import { createShallow, createMount, getClasses } from '../test-utils'; import Grow from '../Grow'; import Paper from '../Paper'; import Popover from './Popover'; describe('<Popover />', () => { let shallow; let mount; let classes; const defaultProps = { open: false, }; before(() => { shallow = createShallow({ dive: true }); mount = createMount(); classes = getClasses( <Popover {...defaultProps}> <div /> </Popover>, ); }); after(() => { mount.cleanUp(); }); describe('root node', () => { it('should render a Modal with an invisible backdrop as the root node', () => { const wrapper = shallow( <Popover {...defaultProps}> <div /> </Popover>, ); assert.strictEqual(wrapper.props().BackdropProps.invisible, true); }); it('should pass onClose prop to Modal', () => { const fn = () => {}; const wrapper = shallow( <Popover {...defaultProps} onClose={fn}> <div /> </Popover>, ); assert.strictEqual(wrapper.props().onClose, fn, 'should be the onClose function'); }); it('should pass open prop to Modal as `open`', () => { const wrapper = shallow( <Popover {...defaultProps}> <div /> </Popover>, ); assert.strictEqual(wrapper.props().open, false); wrapper.setProps({ open: true }); assert.strictEqual(wrapper.props().open, true); wrapper.setProps({ open: false }); assert.strictEqual(wrapper.props().open, false); }); describe('getOffsetTop', () => { let instance; let rect; before(() => { instance = shallow( <Popover {...defaultProps}> <div /> </Popover>, ).instance(); rect = { height: 1 }; }); it('should return vertical when vertical is a number', () => { const vertical = 1; const offsetTop = instance.handleGetOffsetTop('', vertical); assert.strictEqual(offsetTop, vertical); }); it("should return half of rect.height if vertical is 'center'", () => { const vertical = 'center'; const offsetTop = instance.handleGetOffsetTop(rect, vertical); assert.strictEqual(offsetTop, rect.height / 2); }); it("should return rect.height if vertical is 'bottom'", () => { const vertical = 'bottom'; const offsetTop = instance.handleGetOffsetTop(rect, vertical); assert.strictEqual(offsetTop, rect.height); }); it('should return zero if vertical is something else', () => { const vertical = undefined; const offsetTop = instance.handleGetOffsetTop(rect, vertical); assert.strictEqual(offsetTop, 0); }); }); describe('getOffsetLeft', () => { let instance; let rect; before(() => { instance = shallow( <Popover {...defaultProps}> <div /> </Popover>, ).instance(); rect = { width: 1 }; }); it('should return horizontal when horizontal is a number', () => { const horizontal = 1; const offsetLeft = instance.handleGetOffsetLeft('', horizontal); assert.strictEqual(offsetLeft, horizontal); }); it("should return half of rect.width if horizontal is 'center'", () => { const horizontal = 'center'; const offsetLeft = instance.handleGetOffsetLeft(rect, horizontal); assert.strictEqual(offsetLeft, rect.width / 2); }); it("should return rect.width if horizontal is 'right'", () => { const horizontal = 'right'; const offsetLeft = instance.handleGetOffsetLeft(rect, horizontal); assert.strictEqual(offsetLeft, rect.width); }); it('should return zero if horizontal is something else', () => { const horizontal = undefined; const offsetLeft = instance.handleGetOffsetLeft(rect, horizontal); assert.strictEqual(offsetLeft, 0); }); }); }); describe('transition', () => { it('should have Transition as the only child of Modal', () => { const wrapper = shallow( <Popover {...defaultProps}> <div /> </Popover>, ); assert.strictEqual(wrapper.children().length, 1, 'should have one child'); assert.strictEqual(wrapper.childAt(0).type(), Grow); assert.strictEqual( wrapper.childAt(0).props().appear, true, 'should transition on first appearance', ); }); it('should set the transition in/out based on the open prop', () => { const wrapper = shallow( <Popover {...defaultProps}> <div /> </Popover>, ); assert.strictEqual(wrapper.childAt(0).props().in, false); wrapper.setProps({ open: true }); assert.strictEqual(wrapper.childAt(0).props().in, true); wrapper.setProps({ open: false }); assert.strictEqual(wrapper.childAt(0).props().in, false); }); it('should fire Popover transition event callbacks', () => { const events = ['onEntering', 'onEnter', 'onEntered', 'onExit', 'onExiting', 'onExited']; const handlers = events.reduce((result, eventHook) => { result[eventHook] = spy(); return result; }, {}); const wrapper = shallow( <Popover {...defaultProps} {...handlers}> <div /> </Popover>, ); events.forEach(eventHook => { const event = eventHook.charAt(2).toLowerCase() + eventHook.slice(3); wrapper.find(Grow).simulate(event, { style: {} }); assert.strictEqual( handlers[eventHook].callCount, 1, `should have called the ${eventHook} handler`, ); }); }); }); describe('paper', () => { it('should have Paper as the only child of Transition', () => { const wrapper = shallow( <Popover {...defaultProps}> <div /> </Popover>, ); assert.strictEqual(wrapper.childAt(0).children().length, 1, 'should have one child'); assert.strictEqual( wrapper .childAt(0) .childAt(0) .type(), Paper, ); }); it('should have the paper class and user classes', () => { const wrapper = shallow( <Popover {...defaultProps} className="test-class"> <div /> </Popover>, ); assert.strictEqual(wrapper.hasClass('test-class'), true); const paper = wrapper.childAt(0).childAt(0); assert.strictEqual(paper.hasClass(classes.paper), true); }); it('should have a elevation prop passed down', () => { const wrapper = shallow( <Popover {...defaultProps}> <div /> </Popover>, ); assert.strictEqual( wrapper .childAt(0) .childAt(0) .prop('elevation'), 8, 'should be 8 elevation by default', ); wrapper.setProps({ elevation: 16 }); assert.strictEqual( wrapper .childAt(0) .childAt(0) .prop('elevation'), 16, 'should be 16 elevation', ); }); }); describe('transition lifecycle', () => { const element = { style: { offsetTop: 'auto', left: 'auto', opacity: 1, transform: undefined, transformOrigin: undefined, transition: undefined, }, }; describe('handleEnter(element)', () => { let wrapper; let handleEnter; before(() => { handleEnter = spy(); wrapper = shallow( <Popover {...defaultProps} onEnter={handleEnter}> <div /> </Popover>, ); wrapper.instance().handleEnter(element); }); it('should set the inline styles for the enter phase', () => { assert.strictEqual( element.style.top === '16px' && element.style.left === '16px', true, 'should offset the element from the top left of the screen by 16px', ); assert.strictEqual( element.style.transformOrigin, wrapper.instance().getPositioningStyle(element).transformOrigin, 'should have a transformOrigin', ); }); }); }); describe('prop: anchorEl', () => { it('should accept a function', () => { const anchorElSpy = spy(); shallow( <Popover {...defaultProps} anchorEl={anchorElSpy}> <div /> </Popover>, ); assert.strictEqual(anchorElSpy.callCount, 1); }); }); describe('positioning on an anchor', () => { let anchorEl; let wrapper; let popoverEl; let openPopover; let expectPopover; before(() => { openPopover = (anchorOrigin, renderShallow) => { if (!anchorEl) { anchorEl = window.document.createElement('div'); } css(anchorEl, { width: '50px', height: '50px', position: 'absolute', top: '100px', left: '100px', }); window.document.body.appendChild(anchorEl); return new Promise(resolve => { const component = ( <Popover {...defaultProps} anchorEl={anchorEl} anchorOrigin={anchorOrigin} transitionDuration={0} onEntered={() => { popoverEl = window.document.querySelector('[data-mui-test="Popover"]'); resolve(); }} > <div /> </Popover> ); wrapper = renderShallow ? shallow(component) : mount(component); wrapper.setProps({ open: true }); if (renderShallow) { resolve(); } }); }; expectPopover = (top, left) => { assert.strictEqual( popoverEl.style.top, `${top}px`, 'should position at the correct top offset', ); assert.strictEqual( popoverEl.style.left, `${left}px`, 'should position at the correct left offset', ); wrapper.unmount(); }; }); after(() => { window.document.body.removeChild(anchorEl); }); it('should be positioned over the top left of the anchor', () => { return openPopover({ vertical: 'top', horizontal: 'left' }).then(() => { const anchorRect = anchorEl.getBoundingClientRect(); const expectedTop = anchorRect.top <= 16 ? 16 : anchorRect.top; const expectedLeft = anchorRect.left <= 16 ? 16 : anchorRect.left; expectPopover(expectedTop, expectedLeft); }); }); it('should be positioned over the center left of the anchor', () => { return openPopover({ vertical: 'center', horizontal: 'left' }).then(() => { const anchorRect = anchorEl.getBoundingClientRect(); const anchorTop = anchorRect.top + anchorRect.height / 2; const expectedTop = anchorTop <= 16 ? 16 : anchorTop; const expectedLeft = anchorRect.left <= 16 ? 16 : anchorRect.left; expectPopover(expectedTop, expectedLeft); }); }); it('should be positioned over the bottom left of the anchor', () => { return openPopover({ vertical: 'bottom', horizontal: 'left' }).then(() => { const anchorRect = anchorEl.getBoundingClientRect(); const expectedTop = anchorRect.bottom <= 16 ? 16 : anchorRect.bottom; const expectedLeft = anchorRect.left <= 16 ? 16 : anchorRect.left; expectPopover(expectedTop, expectedLeft); }); }); it('should be positioned over the center center of the anchor', () => { return openPopover({ vertical: 'center', horizontal: 'center' }).then(() => { const anchorRect = anchorEl.getBoundingClientRect(); const anchorTop = anchorRect.top + anchorRect.height / 2; const anchorLeft = anchorRect.left + anchorRect.height / 2; const expectedTop = anchorTop <= 16 ? 16 : anchorTop; const expectedLeft = anchorLeft <= 16 ? 16 : anchorLeft; expectPopover(expectedTop, expectedLeft); }); }); it('should be positioned over the top right of the anchor', () => { return openPopover({ vertical: 'top', horizontal: 'right' }).then(() => { const anchorRect = anchorEl.getBoundingClientRect(); const expectedTop = anchorRect.top <= 16 ? 16 : anchorRect.top; const expectedLeft = anchorRect.right <= 16 ? 16 : anchorRect.right; expectPopover(expectedTop, expectedLeft); }); }); it('should be positioned over the bottom right of the anchor', () => { return openPopover({ vertical: 'bottom', horizontal: 'right' }).then(() => { const anchorRect = anchorEl.getBoundingClientRect(); const expectedTop = anchorRect.bottom <= 16 ? 16 : anchorRect.bottom; const expectedLeft = anchorRect.right <= 16 ? 16 : anchorRect.right; expectPopover(expectedTop, expectedLeft); }); }); it('should pass through container prop if container and anchorEl props are provided', () => { const container = {}; const shallowWrapper = shallow(<Popover container={container} open />); assert.strictEqual( shallowWrapper .dive() .find('Modal') .props().container, container, 'should pass through container prop if both container and anchorEl props are provided', ); }); it("should use anchorEl's parent body as container if container prop not provided", () => { return openPopover(undefined, true).then(() => { assert.strictEqual( wrapper .dive() .find('Modal') .props().container, window.document.body, "should use anchorEl's parent body as Modal container", ); }); }); it('should not pass container to Modal if container or anchorEl props are notprovided', () => { const shallowWrapper = shallow(<Popover open />); assert.strictEqual( shallowWrapper .dive() .find('Modal') .props().container, undefined, 'should not pass a container prop if neither container or anchorEl are provided', ); }); }); describe('prop anchorReference="anchorPosition"', () => { const anchorPosition = { top: 300, left: 500 }; let wrapper; let popoverEl; let openPopover; let expectPopover; before(() => { openPopover = anchorOrigin => { return new Promise(resolve => { wrapper = mount( <Popover {...defaultProps} anchorReference="anchorPosition" anchorPosition={anchorPosition} anchorOrigin={anchorOrigin} transitionDuration={0} onEntered={() => { popoverEl = window.document.querySelector('[data-mui-test="Popover"]'); resolve(); }} > <div /> </Popover>, ); wrapper.setProps({ open: true }); }); }; expectPopover = (top, left) => { assert.strictEqual( popoverEl.style.top, `${top}px`, 'should position at the correct top offset', ); assert.strictEqual( popoverEl.style.left, `${left}px`, 'should position at the correct left offset', ); wrapper.unmount(); }; }); it('should be positioned according to the passed coordinates', () => { return openPopover().then(() => { expectPopover(anchorPosition.top, anchorPosition.left); }); }); it('should ignore the anchorOrigin prop when being positioned', () => { return openPopover({ vertical: 'top', horizontal: 'right' }).then(() => { expectPopover(anchorPosition.top, anchorPosition.left); }); }); }); describe('prop anchorReference="none"', () => { let wrapper; let popoverEl; let openPopover; let expectPopover; before(() => { openPopover = () => { return new Promise(resolve => { wrapper = mount( <Popover {...defaultProps} anchorReference="none" transitionDuration={0} onEntered={() => { popoverEl = window.document.querySelector('[data-mui-test="Popover"]'); resolve(); }} PaperProps={{ style: { top: 11, left: 12, }, }} > <div /> </Popover>, ); wrapper.setProps({ open: true }); }); }; expectPopover = (top, left) => { assert.strictEqual( popoverEl.style.top, `${top}px`, 'should position at the correct top offset', ); assert.strictEqual( popoverEl.style.left, `${left}px`, 'should position at the correct left offset', ); wrapper.unmount(); }; }); it('should not try to change the position', () => { return openPopover().then(() => { expectPopover(11, 12); }); }); }); describe('on window resize', () => { let clock; before(() => { clock = useFakeTimers(); }); after(() => { clock.restore(); }); it('should recalculate position if the popover is open', () => { const wrapper = shallow( <Popover {...defaultProps} open transitionDuration={0}> <div /> </Popover>, ); const instance = wrapper.instance(); stub(instance, 'setPositioningStyles'); wrapper .find('EventListener') .at(0) .simulate('resize'); clock.tick(166); assert.strictEqual( instance.setPositioningStyles.called, true, 'position styles recalculated', ); }); it('should not recalculate position if the popover is closed', () => { const wrapper = mount( <Popover {...defaultProps} transitionDuration={0}> <div /> </Popover>, ); assert.strictEqual( wrapper.contains('EventListener'), false, 'no component listening on resize', ); }); }); [0, 8, 16].forEach(marginThreshold => { describe('getPositioningStyle(element)', () => { let instance; let element; let anchorOffset; let tempAnchorOffset; let transformOrigin; let positioningStyle; let innerHeightContainer; let innerWidthContainer; before(() => { instance = shallow( <Popover {...defaultProps} marginThreshold={marginThreshold}> <div /> </Popover>, ).instance(); instance.getContentAnchorOffset = spy(); innerHeightContainer = global.window.innerHeight; innerWidthContainer = global.window.innerWidth; global.window.innerHeight = marginThreshold * 2; global.window.innerWidth = marginThreshold * 2; anchorOffset = { top: marginThreshold, left: marginThreshold }; instance.getAnchorOffset = stub().returns(anchorOffset); transformOrigin = { vertical: 0, horizontal: 0 }; instance.getTransformOrigin = stub().returns(transformOrigin); instance.getTransformOriginValue = stub().returns(true); element = { offsetHeight: 0, offsetWidth: 0 }; }); after(() => { global.window.innerHeight = innerHeightContainer; global.window.innerWidth = innerWidthContainer; }); describe('no offsets', () => { before(() => { positioningStyle = instance.getPositioningStyle(element); }); after(() => { instance.getAnchorOffset = stub().returns(anchorOffset); }); it('should set top to marginThreshold', () => { assert.strictEqual(positioningStyle.top, `${marginThreshold}px`); }); it('should set left to marginThreshold', () => { assert.strictEqual(positioningStyle.left, `${marginThreshold}px`); }); it('should transformOrigin according to marginThreshold', () => { assert.strictEqual(positioningStyle.transformOrigin, '0px 0px'); }); }); describe('top < marginThreshold', () => { before(() => { tempAnchorOffset = { top: marginThreshold - 1, left: marginThreshold }; instance.getAnchorOffset = stub().returns(tempAnchorOffset); positioningStyle = instance.getPositioningStyle(element); }); after(() => { instance.getAnchorOffset = stub().returns(anchorOffset); }); it('should set top to marginThreshold', () => { assert.strictEqual(positioningStyle.top, `${marginThreshold}px`); }); it('should set left to marginThreshold', () => { assert.strictEqual(positioningStyle.left, `${marginThreshold}px`); }); it('should transformOrigin according to marginThreshold', () => { assert.strictEqual(positioningStyle.transformOrigin, '0px -1px'); }); }); describe('bottom > heightThreshold', () => { before(() => { tempAnchorOffset = { top: marginThreshold + 1, left: marginThreshold }; instance.getAnchorOffset = stub().returns(tempAnchorOffset); positioningStyle = instance.getPositioningStyle(element); }); after(() => { instance.getAnchorOffset = stub().returns(anchorOffset); }); it('should set top to marginThreshold', () => { assert.strictEqual(positioningStyle.top, `${marginThreshold}px`); }); it('should set left to marginThreshold', () => { assert.strictEqual(positioningStyle.left, `${marginThreshold}px`); }); it('should transformOrigin according to marginThreshold', () => { assert.strictEqual(positioningStyle.transformOrigin, '0px 1px'); }); }); describe('left < marginThreshold', () => { before(() => { tempAnchorOffset = { top: marginThreshold, left: marginThreshold - 1 }; instance.getAnchorOffset = stub().returns(tempAnchorOffset); positioningStyle = instance.getPositioningStyle(element); }); after(() => { instance.getAnchorOffset = stub().returns(anchorOffset); }); it('should set top to marginThreshold', () => { assert.strictEqual(positioningStyle.top, `${marginThreshold}px`); }); it('should set left to marginThreshold', () => { assert.strictEqual(positioningStyle.left, `${marginThreshold}px`); }); it('should transformOrigin according to marginThreshold', () => { assert.strictEqual(positioningStyle.transformOrigin, '-1px 0px'); }); }); describe('right > widthThreshold', () => { before(() => { tempAnchorOffset = { top: marginThreshold, left: marginThreshold + 1 }; instance.getAnchorOffset = stub().returns(tempAnchorOffset); positioningStyle = instance.getPositioningStyle(element); }); after(() => { instance.getAnchorOffset = stub().returns(anchorOffset); }); it('should set top to marginThreshold', () => { assert.strictEqual(positioningStyle.top, `${marginThreshold}px`); }); it('should set left to marginThreshold', () => { assert.strictEqual(positioningStyle.left, `${marginThreshold}px`); }); it('should transformOrigin according to marginThreshold', () => { assert.strictEqual(positioningStyle.transformOrigin, '1px 0px'); }); }); }); }); describe('prop: getContentAnchorEl', () => { it('should position accordingly', () => { const element = { scrollTop: 5, contains: () => true }; const child = { offsetTop: 40, clientHeight: 20, parentNode: element }; const wrapper = shallow( <Popover {...defaultProps} getContentAnchorEl={() => child}> <div /> </Popover>, ); assert.strictEqual(wrapper.instance().getContentAnchorOffset(element), 45); }); }); describe('prop: action', () => { it('should be able to access updatePosition function', () => { let popoverActions = {}; mount( <Popover {...defaultProps} action={actions => { popoverActions = actions; }} > <div>content #1</div> <div>content #2</div> <div>content #3</div> </Popover>, ); assert.strictEqual( typeof popoverActions.updatePosition === 'function', true, 'Should be a function.', ); popoverActions.updatePosition(); }); }); describe('prop: transitionDuration', () => { it('should apply the auto property if supported', () => { const wrapper = shallow( <Popover {...defaultProps}> <div /> </Popover>, ); assert.strictEqual(wrapper.find(Grow).props().timeout, 'auto'); }); it('should not apply the auto property if not supported', () => { const TransitionComponent = props => <div {...props} />; const wrapper = shallow( <Popover {...defaultProps} TransitionComponent={TransitionComponent}> <div /> </Popover>, ); assert.strictEqual(wrapper.find(TransitionComponent).props().timeout, undefined); }); }); });