import PACKAGE_JSON from 'RootDir/package'; import * as ESM from 'src/index'; import * as WEB from 'dist/v-click-outside-x'; import * as MIN from 'dist/v-click-outside-x.min'; const ESMREQ = require('src/index'); const WEBREQ = require('dist/v-click-outside-x'); const MINREQ = require('dist/v-click-outside-x.min'); const namePrefix = 'vClickOutside'; const methods = [ {method: ESM, name: `${namePrefix} ESM`}, {method: WEB, name: `${namePrefix} WEB`}, {method: MIN, name: `${namePrefix} MIN`}, {method: ESMREQ, name: `${namePrefix} ESMREQ`}, {method: WEBREQ, name: `${namePrefix} WEBREQ`}, {method: MINREQ, name: `${namePrefix} MINREQ`}, {method: ESM, name: `${namePrefix} ESM no document`, noDoc: true}, ]; const doc = window.document; methods.forEach(({method, name, noDoc}) => { const {directive, install} = method; let calls = 1; describe(`${name}`, () => { beforeAll(() => { if (noDoc) { calls = 0; delete window.document; } }); beforeEach(() => { doc.addEventListener = jest.fn(); doc.removeEventListener = jest.fn(); }); afterEach(() => { doc.addEventListener = undefined; doc.removeEventListener = undefined; }); describe('plugin', () => { it('the directive is an object', () => { expect.assertions(1); expect(directive).toBeInstanceOf(Object); }); it('it has all hook functions available', () => { expect.assertions(2); ['bind', 'unbind'].forEach(functionName => { expect(directive[functionName]).toBeInstanceOf(Function); }); }); it('$_captureInstances is an empty Map', () => { expect.assertions(1); expect(Object.keys(directive.$_captureInstances)).toHaveLength(0); }); it('$_nonCaptureInstances is an empty Map', () => { expect.assertions(1); expect(Object.keys(directive.$_nonCaptureInstances)).toHaveLength(0); }); it('$_onCaptureEvent to be a function', () => { expect.assertions(1); expect(directive.$_onCaptureEvent).toBeInstanceOf(Function); }); it('$_onNonCaptureEvent to be a function', () => { expect.assertions(1); expect(directive.$_onNonCaptureEvent).toBeInstanceOf(Function); }); it('version to be a string', () => { expect.assertions(1); expect(typeof directive.version).toStrictEqual('string'); }); it('version to be as per package.json', () => { expect.assertions(1); expect(directive.version).toStrictEqual(PACKAGE_JSON.version); }); it('version to be enumerable', () => { expect.assertions(1); expect( Object.prototype.propertyIsEnumerable.call(directive, 'version'), ).toBe(true); }); it('install the directive into the vue instance', () => { expect.assertions(2); const vue = { directive: jest.fn(), }; install(vue); expect(vue.directive).toHaveBeenCalledWith('click-outside', directive); expect(vue.directive).toHaveBeenCalledTimes(1); }); }); describe('directive', () => { describe('bind/unbind', () => { describe('bind exceptions', () => { it('throws an error if value is not a function', () => { expect.assertions(1); const div1 = doc.createElement('div'); const bindWithNoFunction = () => directive.bind(div1, {}); expect(bindWithNoFunction).toThrowErrorMatchingSnapshot(); }); }); describe('single', () => { const div1 = doc.createElement('div'); it('adds to the list and event listener', () => { expect.assertions(6); const eventHandler = jest.fn(); directive.bind(div1, {value: eventHandler}); expect(Object.keys(directive.$_nonCaptureInstances)).toHaveLength( 1, ); expect(directive.$_nonCaptureInstances).toHaveProperty('click'); const clickInstances = directive.$_nonCaptureInstances.click; expect(clickInstances).toBeInstanceOf(Array); expect(clickInstances).toHaveLength(1); expect(clickInstances.find(item => item.el === div1)).toBeDefined(); expect(doc.addEventListener.mock.calls).toHaveLength(calls); }); it('removes from the list and event listener', () => { expect.assertions(2); directive.unbind(div1); expect(Object.keys(directive.$_nonCaptureInstances)).toHaveLength( 0, ); expect(doc.removeEventListener.mock.calls).toHaveLength(calls); }); }); describe('multiple', () => { const div1 = doc.createElement('div'); const div2 = doc.createElement('div'); it('adds to the list and event listener', () => { expect.assertions(7); const eventHandler1 = jest.fn(); const eventHandler2 = jest.fn(); directive.bind(div1, {value: eventHandler1}); directive.bind(div2, {arg: 'click', value: eventHandler2}); expect(Object.keys(directive.$_nonCaptureInstances)).toHaveLength( 1, ); expect(directive.$_nonCaptureInstances).toHaveProperty('click'); const clickInstances = directive.$_nonCaptureInstances.click; expect(clickInstances).toBeInstanceOf(Array); expect(clickInstances).toHaveLength(2); expect(clickInstances.find(item => item.el === div1)).toBeDefined(); expect(clickInstances.find(item => item.el === div2)).toBeDefined(); expect(doc.addEventListener.mock.calls).toHaveLength(calls); }); it('removes from the list and the event listener', () => { expect.assertions(7); directive.unbind(div1); expect(Object.keys(directive.$_nonCaptureInstances)).toHaveLength( 1, ); expect(directive.$_nonCaptureInstances).toHaveProperty('click'); const clickInstances = directive.$_nonCaptureInstances.click; expect(clickInstances).toBeInstanceOf(Array); expect(clickInstances).toHaveLength(1); expect(clickInstances.find(item => item.el === div2)).toBeDefined(); directive.unbind(div2); expect(Object.keys(directive.$_nonCaptureInstances)).toHaveLength( 0, ); expect(doc.removeEventListener.mock.calls).toHaveLength(calls); }); }); describe('bind', () => { it('saves the instance binding and element', () => { expect.assertions(11); const div1 = doc.createElement('div'); const div2 = doc.createElement('div'); const div3 = doc.createElement('div'); const eventHandler1 = jest.fn(); const eventHandler2 = jest.fn(); directive.bind(div1, { arg: 'pointerdown', modifiers: {capture: true}, value: eventHandler1, }); directive.bind(div2, { arg: 'pointerdown', modifiers: {stop: true}, value: eventHandler2, }); directive.bind(div3, { arg: 'pointerdown', modifiers: {prevent: true}, value: eventHandler2, }); expect(Object.keys(directive.$_captureInstances)).toHaveLength(1); expect(directive.$_captureInstances).toHaveProperty('pointerdown'); const clickCaptureInstances = directive.$_captureInstances.pointerdown; expect(clickCaptureInstances).toBeInstanceOf(Array); expect(clickCaptureInstances).toHaveLength(1); expect( clickCaptureInstances.find(item => item.el === div1), ).toStrictEqual({ binding: { arg: 'pointerdown', modifiers: { capture: true, prevent: false, stop: false, }, value: eventHandler1, }, el: div1, }); expect(Object.keys(directive.$_nonCaptureInstances)).toHaveLength( 1, ); expect(directive.$_nonCaptureInstances).toHaveProperty( 'pointerdown', ); const clickNonCaptureInstances = directive.$_nonCaptureInstances.pointerdown; expect(clickNonCaptureInstances).toBeInstanceOf(Array); expect(clickNonCaptureInstances).toHaveLength(2); expect( clickNonCaptureInstances.find(item => item.el === div2), ).toStrictEqual({ binding: { arg: 'pointerdown', modifiers: { capture: false, prevent: false, stop: true, }, value: eventHandler2, }, el: div1, }); expect( clickNonCaptureInstances.find(item => item.el === div3), ).toStrictEqual({ binding: { arg: 'pointerdown', modifiers: { capture: false, prevent: true, stop: false, }, value: eventHandler2, }, el: div1, }); directive.unbind(div1); directive.unbind(div2); directive.unbind(div3); }); }); }); describe('$_onCaptureEvent', () => { const div1 = doc.createElement('div'); const span = doc.createElement('span'); div1.appendChild(span); it('calls the callback if the element is not the same and does not contain the event target', () => { expect.assertions(10); const a = doc.createElement('a'); const event = { preventDefault: jest.fn(), stopPropagation: jest.fn(), target: a, }; const eventHandler1 = jest.fn(); directive.bind(div1, {value: eventHandler1}); directive.$_onNonCaptureEvent(event); expect(eventHandler1).toHaveBeenCalledWith(event); expect(eventHandler1.mock.instances).toHaveLength(1); expect(eventHandler1.mock.instances[0]).toBe(directive); expect(event.preventDefault).not.toHaveBeenCalled(); expect(event.stopPropagation).not.toHaveBeenCalled(); directive.unbind(div1); const eventHandler2 = jest.fn(); directive.bind(div1, { arg: 'touchdown', modifiers: {capture: true, prevent: true, stop: true}, value: eventHandler2, }); directive.$_onCaptureEvent(event); expect(eventHandler2).toHaveBeenCalledWith(event); expect(eventHandler2.mock.instances).toHaveLength(1); expect(eventHandler2.mock.instances[0]).toBe(directive); expect(event.preventDefault).toHaveBeenCalled(); expect(event.stopPropagation).toHaveBeenCalled(); directive.unbind(div1); }); it('does not execute the callback if the event target its the element from the instance', () => { expect.assertions(2); const event = { target: div1, }; const eventHandler = jest.fn(); directive.bind(div1, {value: eventHandler}); directive.$_onNonCaptureEvent(event); expect(eventHandler).not.toHaveBeenCalled(); expect(eventHandler.mock.instances).toHaveLength(0); directive.unbind(div1); }); it('does not execute the callback if the event target is contained in the element from the instance', () => { expect.assertions(2); const event = { target: span, }; const eventHandler = jest.fn(); directive.bind(div1, {value: eventHandler}); directive.$_onNonCaptureEvent(event); expect(eventHandler).not.toHaveBeenCalled(); expect(eventHandler.mock.instances).toHaveLength(0); directive.unbind(div1); }); }); }); }); });