import React from 'react'; import { mount } from 'enzyme'; import { PopperContent } from '../'; import TooltipPopoverWrapper from '../TooltipPopoverWrapper'; describe('Tooltip', () => { let isOpen; let toggle; let element; let container; let target; let innerTarget; let synthEvent; beforeEach(() => { isOpen = false; toggle = () => { isOpen = !isOpen; }; element = document.createElement('div'); container = document.createElement('div'); element.innerHTML = '

This is the Tooltip target.

'; element.setAttribute('id', 'testContainer'); container.setAttribute('id', 'container'); element.appendChild(container); document.body.appendChild(element); target = document.getElementById('target'); innerTarget = document.getElementById('innerTarget'); synthEvent = { persist: () => {} }; jest.useFakeTimers(); }); afterEach(() => { jest.clearAllTimers(); document.body.removeChild(element); document.body.innerHTML = ''; element = null; container = null; target = null; innerTarget = null; }); it('should render with "hideArrow" false by default', () => { const wrapper = mount( Tooltip Content ); expect(wrapper.prop('hideArrow')).toBe(false); }); it('should render with "hideArrow" true when "hideArrow" prop is truthy', () => { const wrapper = mount( Tooltip Content ); expect(wrapper.prop('hideArrow')).toBe(true); }); it('should not render children if isOpen is false', () => { const wrapper = mount( Tooltip Content , { attachTo: container } ); const Tooltips = document.getElementsByClassName('tooltip'); expect(wrapper.find('.tooltip.show').hostNodes().length).toBe(0); expect(target.className).toBe(''); expect(Tooltips.length).toBe(0); wrapper.detach(); }); it('should render if isOpen is true', () => { isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const Tooltips = document.getElementsByClassName('tooltip'); expect(wrapper.find('.tooltip.show').hostNodes().length).toBe(1); expect(Tooltips.length).toBe(1); expect(Tooltips[0].textContent).toBe('Tooltip Content'); expect(wrapper.find('.tooltip.show').hostNodes().length).toBe(1); expect(Tooltips.length).toBe(1); expect(Tooltips[0].textContent).toBe('Tooltip Content'); wrapper.detach(); }); it('should render with target object', () => { isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const Tooltips = document.getElementsByClassName('tooltip'); expect(wrapper.find('.tooltip.show').hostNodes().length).toBe(1); expect(Tooltips.length).toBe(1); expect(Tooltips[0].textContent).toBe('Tooltip Content'); const tooltips = document.getElementsByClassName('tooltip'); expect(wrapper.find('.tooltip.show').hostNodes().length).toBe(1); expect(tooltips.length).toBe(1); expect(tooltips[0].textContent).toBe('Tooltip Content'); wrapper.detach(); }); it('should toggle isOpen', () => { const wrapper = mount( Tooltip Content , { attachTo: container } ); jest.runTimersToTime(150); expect(document.getElementsByClassName('tooltip').length).toBe(0); wrapper.setProps({ isOpen: true }); jest.runTimersToTime(150); expect(document.getElementsByClassName('tooltip').length).toBe(1); wrapper.setProps({ isOpen: false }); jest.runTimersToTime(150); expect(document.getElementsByClassName('tooltip').length).toBe(0); wrapper.detach(); }); it('should toggle isOpen', () => { const wrapper = mount( Tooltip Content , { attachTo: container } ); expect(document.getElementsByClassName('tooltip').length).toBe(0); wrapper.setProps({ isOpen: true }); jest.runTimersToTime(0); // slight async delay for getDerivedStateFromProps to update isOpen expect(document.getElementsByClassName('tooltip').length).toBe(1); wrapper.setProps({ isOpen: false }); jest.runTimersToTime(0); expect(document.getElementsByClassName('tooltip').length).toBe(0); wrapper.detach(); }); it('should handle target clicks', () => { const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); expect(isOpen).toBe(false); instance.handleDocumentClick({ target: target }); jest.runTimersToTime(200); expect(isOpen).toBe(true); instance.handleDocumentClick({ target: target }); jest.runTimersToTime(200); expect(isOpen).toBe(false); wrapper.detach(); }); it('should handle inner target clicks', () => { const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); expect(isOpen).toBe(false); instance.handleDocumentClick({ target: innerTarget }); jest.runTimersToTime(200); expect(isOpen).toBe(true); wrapper.detach(); }); it('should handle inner target click and correct placement', () => { const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); expect(isOpen).toBe(false); instance.handleDocumentClick({ target: innerTarget }); jest.runTimersToTime(200); expect(isOpen).toBe(true); wrapper.setProps({ isOpen: true }); expect(wrapper.find(PopperContent).props().target.id).toBe('target'); wrapper.detach(); }); it('should not do anything when document click outside of target', () => { const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); expect(isOpen).toBe(false); instance.handleDocumentClick({ target: container }); expect(isOpen).toBe(false); wrapper.detach(); }); it('should clear hide timeout if it exists on target click', () => { const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); instance.hideWithDelay(); expect(isOpen).toBe(false); instance.handleDocumentClick({ target: target }); jest.runTimersToTime(200); expect(isOpen).toBe(true); wrapper.setProps({ isOpen: isOpen }); instance.handleDocumentClick({ target: target }); expect(isOpen).toBe(true); wrapper.detach(); }); it('should open after receiving single touchstart and single click', () => { const wrapper = mount( Tooltip Content , { attachTo: container } ); expect(isOpen).toBe(false); target.dispatchEvent(new Event('touchstart')); jest.runTimersToTime(20); target.dispatchEvent(new Event('click')); jest.runTimersToTime(200); expect(isOpen).toBe(true); wrapper.detach(); }); it('should close after receiving single touchstart and single click', () => { isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); expect(isOpen).toBe(true); target.dispatchEvent(new Event('touchstart')); jest.runTimersToTime(20); target.dispatchEvent(new Event('click')); jest.runTimersToTime(200); expect(isOpen).toBe(false); wrapper.detach(); }); it('should pass down custom modifiers', () => { const wrapper = mount( Tooltip Content ); expect(wrapper.find(PopperContent).props().modifiers).toEqual({ preventOverflow: { boundariesElement: 'viewport' }, offset: { offset: 2 }, }); wrapper.unmount(); }); it('should pass down cssModule', () => { const cssModule = {}; const wrapper = mount( Tooltip Content ); expect(wrapper.find(PopperContent).props().cssModule).toBe(cssModule); wrapper.unmount(); }); it('should pass down offset', () => { const wrapper = mount( Tooltip content ); expect(wrapper.find(PopperContent).props().offset).toEqual('100'); wrapper.unmount(); }); it('should pass down flip', () => { const wrapper = mount( Tooltip Content ); expect(wrapper.find(PopperContent).props().flip).toBe(false); wrapper.unmount(); }); it('should not call props.toggle when disabled ', () => { const props = createSpyObj('props', ['toggle']); const event = createSpyObj('event', ['preventDefault']); const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); instance.toggle(event); expect(event.preventDefault).toHaveBeenCalled(); expect(props.toggle).not.toHaveBeenCalled(); wrapper.detach(); }); it('should not throw when props.toggle is not provided ', () => { const event = createSpyObj('event', ['preventDefault']); const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); instance.toggle(event); wrapper.detach(); }); it('should not throw when passed a ref object as the target', () => { const targetObj = React.createRef(); targetObj.current = createSpyObj('div', ['addEventListener', 'removeEventListener']); const event = createSpyObj('event', ['preventDefault']); const wrapper = mount( Yo!, { attachTo: container }); const instance = wrapper.instance(); instance.toggle(event); wrapper.detach(); expect(targetObj.current.addEventListener).toHaveBeenCalled(); expect(targetObj.current.removeEventListener).toHaveBeenCalled(); }); describe('multi target', () => { let targets, innerTarget, targetContainer; beforeEach(() => { targetContainer = document.createElement('div'); targetContainer.innerHTML = `Target 1Target 2Inner target` element.appendChild(targetContainer); targets = targetContainer.querySelectorAll('.example'); innerTarget = targetContainer.querySelector('.inner_example'); }); afterEach(() => { element.removeChild(targetContainer); targets = null; innerTarget = null; }); it("should attach tooltip on multiple target when a target selector matches multiple elements", () => { const wrapper = mount( Yo!, { attachTo: container }); targets[0].dispatchEvent(new Event('click')); jest.runTimersToTime(0) expect(isOpen).toBe(true); targets[0].dispatchEvent(new Event('click')); jest.runTimersToTime(0) expect(isOpen).toBe(false); targets[1].dispatchEvent(new Event('click')); jest.runTimersToTime(0) expect(isOpen).toBe(true); targets[1].dispatchEvent(new Event('click')); jest.runTimersToTime(0) expect(isOpen).toBe(false); wrapper.detach(); }); it("should attach tooltip on second target with correct placement, when inner element is clicked", () => { const wrapper = mount( Yo!, { attachTo: container }); innerTarget.dispatchEvent(new Event('click')); jest.runTimersToTime(0) expect(isOpen).toBe(true); wrapper.setProps({ isOpen: true }); expect(wrapper.find(PopperContent).props().target.className).toBe('example second'); wrapper.detach(); }); }); describe('delay', () => { it('should accept a number', () => { isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); instance.hideWithDelay(); expect(isOpen).toBe(true); jest.runTimersToTime(200); expect(isOpen).toBe(false); }); it('should accept an object', () => { isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); instance.hideWithDelay(); expect(isOpen).toBe(true); jest.runTimersToTime(200); expect(isOpen).toBe(false); }); it('should use default value if value is missing from object', () => { isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); instance.hideWithDelay(); expect(isOpen).toBe(true); jest.runTimersToTime(250); // Default hide value: 250 expect(isOpen).toBe(false); }); }); describe('hide', () => { it('should call toggle when isOpen', () => { const spy = jest.fn(toggle); isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); expect(spy).not.toHaveBeenCalled(); instance.hide(); expect(spy).toHaveBeenCalled(); wrapper.detach(); }); it('should not call toggle when isOpen is false', () => { const spy = jest.fn(toggle); const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); expect(spy).not.toHaveBeenCalled(); instance.hide(); expect(spy).not.toHaveBeenCalled(); wrapper.detach(); }); }); describe('show', () => { it('should call toggle when isOpen is false', () => { const spy = jest.fn(toggle); const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); expect(spy).not.toHaveBeenCalled(); instance.show(); expect(spy).toHaveBeenCalled(); wrapper.detach(); }); it('should not call toggle when isOpen', () => { const spy = jest.fn(toggle); isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); expect(spy).not.toHaveBeenCalled(); instance.show(); expect(spy).not.toHaveBeenCalled(); wrapper.detach(); }); }); describe('onMouseOverTooltip', () => { it('should clear timeout if it exists on target click', () => { const spy = jest.fn(toggle); const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); instance.hideWithDelay(); expect(isOpen).toBe(false); expect(spy).not.toHaveBeenCalled(); instance.showWithDelay(); jest.runTimersToTime(200); expect(spy).toHaveBeenCalled(); wrapper.detach(); }); it('should not call .toggle if isOpen', () => { const spy = jest.fn(toggle); isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); instance.showWithDelay(); jest.runTimersToTime(0); // delay: 0 toggle is still async expect(isOpen).toBe(true); expect(spy).not.toHaveBeenCalled(); wrapper.detach(); }); }); describe('onMouseLeaveTooltip', () => { it('should clear timeout if it exists on target click', () => { const spy = jest.fn(toggle); isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); instance.showWithDelay(); expect(isOpen).toBe(true); expect(spy).not.toHaveBeenCalled(); instance.hideWithDelay(); jest.runTimersToTime(200); expect(spy).toHaveBeenCalled(); wrapper.detach(); }); it('should not call .toggle if isOpen is false', () => { const spy = jest.fn(toggle); isOpen = false; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); instance.hideWithDelay(); jest.runTimersToTime(0); // delay: 0 toggle is still async expect(isOpen).toBe(false); expect(spy).not.toHaveBeenCalled(); wrapper.detach(); }); }); describe('autohide', () => { it('should keep Tooltip around when false and onmouseleave from Tooltip content', () => { const spy = jest.fn(toggle); isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); expect(isOpen).toBe(true); expect(spy).not.toHaveBeenCalled(); instance.onMouseLeaveTooltipContent(synthEvent); jest.runTimersToTime(100); expect(spy).not.toHaveBeenCalled(); jest.runTimersToTime(200); expect(spy).toHaveBeenCalled(); wrapper.detach(); }); it('clears showTimeout in onMouseLeaveTooltipContent', () => { const spy = jest.fn(toggle); isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); instance.showWithDelay(); expect(instance._showTimeout).toBeTruthy(); instance.onMouseLeaveTooltipContent(synthEvent); jest.runTimersToTime(300); expect(instance._showTimeout).toBeFalsy(); wrapper.detach(); }); it('clears hide timeout in onMouseOverTooltipContent', () => { const spy = jest.fn(toggle); isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); expect(isOpen).toBe(true); expect(spy).not.toHaveBeenCalled(); instance.onMouseLeaveTooltipContent(synthEvent); jest.runTimersToTime(100); expect(instance._hideTimeout).toBeTruthy(); instance.onMouseOverTooltipContent(); expect(instance._hideTimeout).toBeFalsy(); instance.onMouseOverTooltipContent(); wrapper.detach(); }); it('should not keep Tooltip around when autohide is true and Tooltip content is hovered over', () => { const spy = jest.fn(toggle); isOpen = true; const wrapper = mount( Tooltip Content , { attachTo: container } ); const instance = wrapper.instance(); expect(isOpen).toBe(true); expect(spy).not.toHaveBeenCalled(); instance.hideWithDelay(); jest.runTimersToTime(100); instance.onMouseOverTooltipContent(); jest.runTimersToTime(200); expect(spy).toHaveBeenCalled(); instance.onMouseLeaveTooltipContent(synthEvent); expect(instance._hideTimeout).toBeFalsy(); wrapper.detach(); }); it('should allow a function to be used as children', () => { const renderChildren = jest.fn(); mount( {renderChildren} ); expect(renderChildren).toHaveBeenCalled(); }); it('should render children properly when children is a function', () => { isOpen = true; const wrapper = mount( {() => 'Tooltip Content'} , { attachTo: container } ); const Tooltips = document.getElementsByClassName('tooltip'); expect(wrapper.find('.tooltip.show').hostNodes().length).toBe(1); expect(Tooltips.length).toBe(1); expect(Tooltips[0].textContent).toBe('Tooltip Content'); expect(wrapper.find('.tooltip.show').hostNodes().length).toBe(1); expect(Tooltips.length).toBe(1); expect(Tooltips[0].textContent).toBe('Tooltip Content'); wrapper.detach(); }); }); });