import 'reflect-metadata'; import { requiredMocks, } from './../../../../test-mocks'; requiredMocks(jest); import { ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, ViewContainerRef, } from '@angular/core'; import * as mock from './../../../mocks/index'; const initMockDocument = mock.document(jest); const initViewContainerRef = mock.viewContainerRef(jest); import { TooltipComponent, } from './tooltip.component'; import { TooltipDirective, } from './tooltip.directive'; import { BrowserDetectorService, } from './../../../helpers/services/index'; const initTooltipDirective = ( viewContainerRef?: ViewContainerRef, componentFactoryResolver?: ComponentFactoryResolver, document = initMockDocument(), browserDetectorService?: BrowserDetectorService, ) => { return new TooltipDirective( viewContainerRef, componentFactoryResolver, document, browserDetectorService, ); }; const initChangeDetector = ( tooltipDirective: TooltipDirective, ) => { tooltipDirective.tooltipComponent .instance.changeDetectorRef = {} as ChangeDetectorRef; tooltipDirective.tooltipComponent.instance .changeDetectorRef.markForCheck = jest.fn(); }; const initTooltipComponent = ( tooltipDirective: TooltipDirective, ) => { const tooltipComponent = { instance: {}, location: { nativeElement: { style: {}, }, }, } as ComponentRef; tooltipDirective.tooltipComponent = tooltipComponent; }; describe('ngOnInit', () => { // tslint:disable-next-line test('It calls resolveComponentFactory on componentFactoryResolver with the TooltipComponent', () => { const viewContainerRef = { } as ViewContainerRef; viewContainerRef.createComponent = jest.fn().mockReturnValue({ location: {}, }); const componentFactoryResolver = {} as ComponentFactoryResolver; componentFactoryResolver.resolveComponentFactory = jest.fn(); const tooltipDirective = initTooltipDirective( viewContainerRef, componentFactoryResolver, ); tooltipDirective.initTooltipComponentVariables = jest.fn(); tooltipDirective.ngOnInit(); expect(componentFactoryResolver .resolveComponentFactory) .toHaveBeenCalledWith( TooltipComponent, ); }); // tslint:disable-next-line test('It calls createComponent on viewContainerRef with the result of componentFactoryResolver.resolveComponentFactory', () => { const viewContainerRef = { } as ViewContainerRef; const resolveComponentResult = {}; viewContainerRef.createComponent = jest.fn().mockReturnValue({ location: {}, }); const componentFactoryResolver = {} as ComponentFactoryResolver; componentFactoryResolver.resolveComponentFactory = jest.fn().mockReturnValue(resolveComponentResult); const tooltipDirective = initTooltipDirective( viewContainerRef, componentFactoryResolver, ); tooltipDirective.initTooltipComponentVariables = jest.fn(); tooltipDirective.ngOnInit(); expect(viewContainerRef.createComponent) .toHaveBeenCalledWith( resolveComponentResult, ); }); // tslint:disable-next-line test('It sets the tooltipComponent to the response of viewContainerRef.createComponent', () => { const viewContainerRef = { } as ViewContainerRef; const createComponentResult = { location: {}, }; viewContainerRef.createComponent = jest.fn().mockReturnValue( createComponentResult, ); const componentFactoryResolver = {} as ComponentFactoryResolver; componentFactoryResolver.resolveComponentFactory = jest.fn(); const tooltipDirective = initTooltipDirective( viewContainerRef, componentFactoryResolver, ); tooltipDirective.initTooltipComponentVariables = jest.fn(); tooltipDirective.ngOnInit(); expect(tooltipDirective.tooltipComponent) .toBe(createComponentResult); }); // tslint:disable-next-line test('It calls document.body.appendChild with tooltipComponent.location.nativeElement', () => { const viewContainerRef = { } as ViewContainerRef; const expectedTooltipComponentNativeElement = {}; const createComponentResult = { location: { nativeElement: expectedTooltipComponentNativeElement, }, }; viewContainerRef.createComponent = jest.fn().mockReturnValue( createComponentResult, ); const componentFactoryResolver = {} as ComponentFactoryResolver; componentFactoryResolver.resolveComponentFactory = jest.fn(); const document = initMockDocument(); const tooltipDirective = initTooltipDirective( viewContainerRef, componentFactoryResolver, document, ); tooltipDirective.initTooltipComponentVariables = jest.fn(); tooltipDirective.ngOnInit(); expect( document.body.appendChild, ).toHaveBeenCalledWith( expectedTooltipComponentNativeElement, ); }); // tslint:disable-next-line test('It calls initTooltipComponentVariables', () => { const viewContainerRef = { } as ViewContainerRef; const createComponentResult = { location: {}, }; viewContainerRef.createComponent = jest.fn().mockReturnValue( createComponentResult, ); const componentFactoryResolver = {} as ComponentFactoryResolver; componentFactoryResolver.resolveComponentFactory = jest.fn(); const tooltipDirective = initTooltipDirective( viewContainerRef, componentFactoryResolver, ); tooltipDirective.initTooltipComponentVariables = jest.fn(); tooltipDirective.ngOnInit(); expect( tooltipDirective.initTooltipComponentVariables, ).toHaveBeenCalled(); }); }); describe('ngOnChanges', () => { // tslint:disable-next-line test('It calls initTooltipComponentVariables', () => { const tooltipDirective = initTooltipDirective(); tooltipDirective.initTooltipComponentVariables = jest.fn(); tooltipDirective.ngOnChanges(); expect( tooltipDirective.initTooltipComponentVariables, ).toHaveBeenCalled(); }); }); describe('ngOnDestroy', () => { // tslint:disable-next-line test('It calls tooltipComponent.destroy', () => { const tooltipDirective = initTooltipDirective(); initTooltipComponent(tooltipDirective); tooltipDirective.tooltipComponent.destroy = jest.fn(); tooltipDirective.ngOnDestroy(); expect(tooltipDirective.tooltipComponent.destroy) .toHaveBeenCalled(); }); }); describe('updateTooltipLocation', () => { // tslint:disable-next-line test('It sets the tooltipComponent left to the difference between the containers bounding rectangle and the document body and the parentElementWidth', () => { const viewContainerRef = initViewContainerRef(); viewContainerRef.element.nativeElement .getBoundingClientRect = jest.fn() .mockReturnValue({ left: 10, width: 20, }); const document = initMockDocument(); document.body.getBoundingClientRect = jest.fn() .mockReturnValue({ left: 5, }); const tooltipDirective = initTooltipDirective( viewContainerRef, undefined, document, ); initTooltipComponent(tooltipDirective); tooltipDirective.shouldBeBelow = jest.fn() .mockReturnValue(true); tooltipDirective.shouldBeRightSide = jest.fn() .mockReturnValue(true); tooltipDirective.updateTooltipLocation(); expect( tooltipDirective.tooltipComponent .location.nativeElement.style.left, ).toBe('25px'); }); // tslint:disable-next-line test('It sets the tooltipComponent left to the difference between the containers bounding rectangle and the document body if shouldBeRightSide is false', () => { const viewContainerRef = initViewContainerRef(); viewContainerRef.element.nativeElement .getBoundingClientRect = jest.fn() .mockReturnValue({ left: 10, width: 20, }); const document = initMockDocument(); document.body.getBoundingClientRect = jest.fn() .mockReturnValue({ left: 5, }); const tooltipDirective = initTooltipDirective( viewContainerRef, undefined, document, ); initTooltipComponent(tooltipDirective); tooltipDirective.shouldBeBelow = jest.fn() .mockReturnValue(true); tooltipDirective.shouldBeRightSide = jest.fn() .mockReturnValue(false); tooltipDirective.updateTooltipLocation(); expect( tooltipDirective.tooltipComponent .location.nativeElement.style.left, ).toBe('5px'); }); // tslint:disable-next-line test('It sets the tooltipComponent.isToRight to the result of shouldBeRightSide', () => { const viewContainerRef = initViewContainerRef(); viewContainerRef.element.nativeElement .getBoundingClientRect = jest.fn() .mockReturnValue({}); const document = initMockDocument(); document.body.getBoundingClientRect = jest.fn() .mockReturnValue({}); const tooltipDirective = initTooltipDirective( viewContainerRef, undefined, document, ); initTooltipComponent(tooltipDirective); tooltipDirective.tooltipComponent .instance.isToRight = false; tooltipDirective.shouldBeBelow = jest.fn() .mockReturnValue(true); tooltipDirective.shouldBeRightSide = jest.fn() .mockReturnValue(true); tooltipDirective.updateTooltipLocation(); expect( tooltipDirective.tooltipComponent .instance.isToRight, ).toBe(true); }); // tslint:disable-next-line test('It sets the tooltipComponent.isBelow to the result of shouldBeBelow', () => { const viewContainerRef = initViewContainerRef(); viewContainerRef.element.nativeElement .getBoundingClientRect = jest.fn() .mockReturnValue({}); const document = initMockDocument(); document.body.getBoundingClientRect = jest.fn() .mockReturnValue({}); const tooltipDirective = initTooltipDirective( viewContainerRef, undefined, document, ); initTooltipComponent(tooltipDirective); tooltipDirective.tooltipComponent .instance.isBelow = false; tooltipDirective.shouldBeBelow = jest.fn() .mockReturnValue(true); tooltipDirective.shouldBeRightSide = jest.fn() .mockReturnValue(true); tooltipDirective.updateTooltipLocation(); expect( tooltipDirective.tooltipComponent .instance.isBelow, ).toBe(true); }); // tslint:disable-next-line test('It sets the tooltipComponent top to half way down the containing element taking into account the body scroll', () => { const viewContainerRef = initViewContainerRef(); viewContainerRef.element.nativeElement .getBoundingClientRect = jest.fn() .mockReturnValue({ height: 20, top: 15, }); const document = initMockDocument(); document.body.getBoundingClientRect = jest.fn() .mockReturnValue({ top: 10, }); const tooltipDirective = initTooltipDirective( viewContainerRef, undefined, document, ); initTooltipComponent(tooltipDirective); tooltipDirective.shouldBeBelow = jest.fn(); tooltipDirective.shouldBeRightSide = jest.fn(); tooltipDirective.updateTooltipLocation(); expect( tooltipDirective.tooltipComponent .location.nativeElement.style.top, ).toBe('15px'); }); // tslint:disable-next-line test('It sets the tooltipComponent to display block', () => { const viewContainerRef = initViewContainerRef(); viewContainerRef.element.nativeElement .getBoundingClientRect = jest.fn() .mockReturnValue({}); const document = initMockDocument(); document.body.getBoundingClientRect = jest.fn() .mockReturnValue({}); const tooltipDirective = initTooltipDirective( viewContainerRef, undefined, document, ); initTooltipComponent(tooltipDirective); tooltipDirective.shouldBeBelow = jest.fn(); tooltipDirective.shouldBeRightSide = jest.fn(); tooltipDirective.updateTooltipLocation(); expect( tooltipDirective.tooltipComponent .location.nativeElement.style.display, ).toBe('block'); }); // tslint:disable-next-line test('It sets the tooltipComponent to position absolute', () => { const viewContainerRef = initViewContainerRef(); viewContainerRef.element.nativeElement .getBoundingClientRect = jest.fn() .mockReturnValue({}); const document = initMockDocument(); document.body.getBoundingClientRect = jest.fn() .mockReturnValue({}); const tooltipDirective = initTooltipDirective( viewContainerRef, undefined, document, ); initTooltipComponent(tooltipDirective); tooltipDirective.shouldBeBelow = jest.fn(); tooltipDirective.shouldBeRightSide = jest.fn(); tooltipDirective.updateTooltipLocation(); expect( tooltipDirective.tooltipComponent .location.nativeElement.style.position, ).toBe('absolute'); }); }); describe('shouldBeRightSide', () => { const itShouldBeRightSideTest = ( bodyWidth: number, tooltipWidth: number, startingLeft: number, ) => { const document = initMockDocument(); document.body = { offsetWidth: bodyWidth, } as HTMLElement; const tooltipDirective = initTooltipDirective( undefined, undefined, document, ); tooltipDirective.tooltipWidth = tooltipWidth; return tooltipDirective.shouldBeRightSide( startingLeft, ); }; // tslint:disable-next-line test('It returns false if the tooltips right edge goes off the edge of the page', () => { expect( itShouldBeRightSideTest( 1, 2, 0, ), ).toBe(false); }); // tslint:disable-next-line test('It returns true if the tooltips right edge is within the edge of the page', () => { expect( itShouldBeRightSideTest( 2, 1, 0, ), ).toBe(true); }); // tslint:disable-next-line test('It returns false if the tooltips goes off the page because of its starting left', () => { expect( itShouldBeRightSideTest( 2, 1, 2, ), ).toBe(false); }); // tslint:disable-next-line test('It returns true if the tooltips right edge is equal the edge of the page', () => { expect( itShouldBeRightSideTest( 2, 2, 0, ), ).toBe(false); }); }); describe('shouldBeBelow', () => { const shouldBeBelowTest = ( parentTop: number, viewportHeight: number, ) => { const viewContainerRef = initViewContainerRef(); viewContainerRef.element.nativeElement .getBoundingClientRect = jest.fn() .mockReturnValue({ top: parentTop, }); const document = initMockDocument(); document.documentElement = { clientHeight: viewportHeight, } as HTMLElement; const tooltipDirective = initTooltipDirective( viewContainerRef, undefined, document, ); return tooltipDirective.shouldBeBelow(); }; // tslint:disable-next-line test('It returns true if the position of the parent is above the half way point of the viewport', () => { expect( shouldBeBelowTest( 1, 3, ), ).toBe(true); }); // tslint:disable-next-line test('It returns false if the position of the parent is below the half way point of the viewport', () => { expect( shouldBeBelowTest( 2, 3, ), ).toBe(false); }); // tslint:disable-next-line test('It returns false if the position of the parent is equal the half way point of the viewport', () => { expect( shouldBeBelowTest( 2, 4, ), ).toBe(false); }); }); describe('initTooltipComponentVariables', () => { // tslint:disable-next-line test('It sets the tooltipComponent.text to the value in tooltip', () => { const tooltipDirective = initTooltipDirective(); initTooltipComponent(tooltipDirective); initChangeDetector(tooltipDirective); tooltipDirective.tooltip = 'test'; tooltipDirective.initTooltipComponentVariables(); expect( tooltipDirective.tooltipComponent .instance.text, ).toBe(tooltipDirective.tooltip); }); // tslint:disable-next-line test('It sets the tooltipComponent.width to the value in tooltipWidth', () => { const tooltipDirective = initTooltipDirective(); initTooltipComponent(tooltipDirective); initChangeDetector(tooltipDirective); tooltipDirective.tooltipWidth = 3; tooltipDirective.initTooltipComponentVariables(); expect( tooltipDirective.tooltipComponent .instance.width, ).toBe(tooltipDirective.tooltipWidth); }); // tslint:disable-next-line test('It calls tooltipComponent.instance.changeDetectorRef.markForCheck', () => { const tooltipDirective = initTooltipDirective(); initTooltipComponent(tooltipDirective); initChangeDetector(tooltipDirective); tooltipDirective.initTooltipComponentVariables(); expect( tooltipDirective.tooltipComponent .instance.changeDetectorRef.markForCheck, ).toHaveBeenCalled(); }); }); describe('changeTooltipVisibility', () => { // tslint:disable-next-line test('It calls updateTooltipLocation if visible is true', () => { const tooltipDirective = initTooltipDirective(); initTooltipComponent(tooltipDirective); initChangeDetector(tooltipDirective); tooltipDirective.updateTooltipLocation = jest.fn(); tooltipDirective.changeTooltipVisibility(true); expect( tooltipDirective.updateTooltipLocation, ).toHaveBeenCalled(); }); // tslint:disable-next-line test('It does not call updateTooltipLocation if visible is false', () => { const tooltipDirective = initTooltipDirective(); initTooltipComponent(tooltipDirective); initChangeDetector(tooltipDirective); tooltipDirective.updateTooltipLocation = jest.fn(); tooltipDirective.changeTooltipVisibility(false); expect( tooltipDirective.updateTooltipLocation, ).not.toHaveBeenCalled(); }); // tslint:disable-next-line test('It sets tooltipComponent visible to the argument', () => { const tooltipDirective = initTooltipDirective(); initTooltipComponent(tooltipDirective); initChangeDetector(tooltipDirective); tooltipDirective.tooltipComponent.instance .visible = false; tooltipDirective.updateTooltipLocation = jest.fn(); tooltipDirective.changeTooltipVisibility(true); expect( tooltipDirective.tooltipComponent.instance .visible, ).toBe(true); }); // tslint:disable-next-line test('It calls markForCheck on the tooltipComponents changeDetectorRef', () => { const tooltipDirective = initTooltipDirective(); initTooltipComponent(tooltipDirective); initChangeDetector(tooltipDirective); tooltipDirective.updateTooltipLocation = jest.fn(); tooltipDirective.changeTooltipVisibility(true); expect( tooltipDirective.tooltipComponent.instance .changeDetectorRef.markForCheck, ).toHaveBeenCalled(); }); }); describe('onHover', () => { const initOnHoverData = () => { const browserDetectorService = {} as BrowserDetectorService; browserDetectorService.isIOS = jest.fn().mockReturnValueOnce(false); const tooltipDirective = initTooltipDirective( undefined, undefined, undefined, browserDetectorService, ); tooltipDirective.onClickTriggered = false; tooltipDirective.changeTooltipVisibility = jest.fn(); return { browserDetectorService, tooltipDirective, }; }; test('Calls browserDetectorService.isIOS', () => { const { browserDetectorService, tooltipDirective, } = initOnHoverData(); tooltipDirective.onHover(); expect(browserDetectorService.isIOS).toHaveBeenCalled(); }); // tslint:disable-next-line test('It calls changeTooltipVisibility with true if onClickTriggered is false and browserDetectorService.isIOS returns false', () => { const { tooltipDirective, } = initOnHoverData(); tooltipDirective.onHover(); expect( tooltipDirective.changeTooltipVisibility, ).toHaveBeenCalledWith(true); }); // tslint:disable-next-line test('It does not call changeTooltipVisibility if onClickTriggered is true and browserDetectorService.isIOS returns false', () => { const { tooltipDirective, } = initOnHoverData(); tooltipDirective.onClickTriggered = true; tooltipDirective.onHover(); expect(tooltipDirective.changeTooltipVisibility).not.toHaveBeenCalled(); }); // tslint:disable-next-line test('It does not call changeTooltipVisibility if onClickTriggered is false and browserDetectorService.isIOS returns true', () => { const { browserDetectorService, tooltipDirective, } = initOnHoverData(); browserDetectorService.isIOS = jest.fn().mockReturnValueOnce(true); tooltipDirective.onHover(); expect(tooltipDirective.changeTooltipVisibility).not.toHaveBeenCalled(); }); }); describe('onMouseOut', () => { // tslint:disable-next-line test('It calls changeTooltipVisibility with false if toClickTriggered is false', () => { const tooltipDirective = initTooltipDirective(); tooltipDirective.onClickTriggered = false; tooltipDirective.changeTooltipVisibility = jest.fn(); tooltipDirective.onMouseOut(); expect(tooltipDirective .changeTooltipVisibility, ).toHaveBeenCalledWith(false); }); // tslint:disable-next-line test('It does not call changeTooltipVisibility if toClickTriggered is true', () => { const tooltipDirective = initTooltipDirective(); tooltipDirective.onClickTriggered = true; tooltipDirective.changeTooltipVisibility = jest.fn(); tooltipDirective.onMouseOut(); expect(tooltipDirective .changeTooltipVisibility, ).not.toHaveBeenCalled(); }); }); describe('onClick', () => { // tslint:disable-next-line test('It calls changeTooltipVisibility with true', () => { const tooltipDirective = initTooltipDirective(); tooltipDirective.changeTooltipVisibility = jest.fn(); tooltipDirective.onClick(); expect( tooltipDirective.changeTooltipVisibility, ).toHaveBeenCalledWith(true); }); // tslint:disable-next-line test('It sets onClickTriggered to true', () => { const tooltipDirective = initTooltipDirective(); tooltipDirective.changeTooltipVisibility = jest.fn(); tooltipDirective.onClickTriggered = false; tooltipDirective.onClick(); expect( tooltipDirective.onClickTriggered, ).toBe(true); }); // tslint:disable-next-line test('It calls setTimeout with the onClickVisibleTime', () => { jest.useFakeTimers(); const tooltipDirective = initTooltipDirective(); tooltipDirective.changeTooltipVisibility = jest.fn(); tooltipDirective.onClickVisibleTime = 1; tooltipDirective.onClick(); expect(setTimeout.mock.calls[0][1]) .toBe(1); }); // tslint:disable-next-line test('It calls changeTooltipVisibility with false after the timer has run', () => { jest.useFakeTimers(); const tooltipDirective = initTooltipDirective(); const changeVisiblityMock = jest.fn(); tooltipDirective.changeTooltipVisibility = changeVisiblityMock; tooltipDirective.onClick(); changeVisiblityMock.mockReset(); jest.runAllTimers(); expect( changeVisiblityMock, ).toHaveBeenCalledWith(false); }); // tslint:disable-next-line test('It sets onClickTriggered to false after the timer has run', () => { jest.useFakeTimers(); const tooltipDirective = initTooltipDirective(); tooltipDirective.changeTooltipVisibility = jest.fn(); tooltipDirective.onClick(); tooltipDirective.onClickTriggered = true; jest.runAllTimers(); expect( tooltipDirective.onClickTriggered, ).toBe(false); }); });