diff --git a/build/config.js b/build/config.js index d32d2eebc..710405a45 100644 --- a/build/config.js +++ b/build/config.js @@ -8,7 +8,7 @@ Object.keys(Components).forEach(function(key) { externals[`element-ui/packages/${key}/style.css`] = `element-ui/lib/${key}/style.css`; }); -Object.keys(dependencies).forEach(function (key) { +Object.keys(dependencies).forEach(function(key) { externals[key] = key; }); diff --git a/packages/popover/src/main.vue b/packages/popover/src/main.vue index 2159f3cab..4488febe0 100644 --- a/packages/popover/src/main.vue +++ b/packages/popover/src/main.vue @@ -49,7 +49,7 @@ export default { const popper = this.popper || this.$refs.popper; if (!reference && this.$slots.reference && this.$slots.reference[0]) { - reference = this.$slots.reference[0].elm; + reference = this.referenceElm = this.$slots.reference[0].elm; } if (this.trigger === 'click') { on(reference, 'click', () => { this.showPopper = !this.showPopper; }); diff --git a/src/utils/clickoutside.js b/src/utils/clickoutside.js index 63a45992d..2b3ef0744 100644 --- a/src/utils/clickoutside.js +++ b/src/utils/clickoutside.js @@ -1,8 +1,10 @@ import { on } from 'wind-dom/src/event'; const nodeList = []; +const ctx = '@@clickoutsideContext'; + on(document, 'click', e => { - nodeList.forEach(node => node[clickoutsideContext].documentHandler(e)); + nodeList.forEach(node => node[ctx].documentHandler(e)); }); /** * v-clickoutside @@ -12,23 +14,24 @@ on(document, 'click', e => { *
* ``` */ -const clickoutsideContext = '@@clickoutsideContext'; - export default { bind(el, binding, vnode) { const id = nodeList.push(el) - 1; const documentHandler = function(e) { if (!vnode.context || el.contains(e.target) || - !vnode.context.popperElm || - vnode.context.popperElm.contains(e.target)) return; + (vnode.context.popperElm && + vnode.context.popperElm.contains(e.target))) return; + if (binding.expression) { - vnode.context[el[clickoutsideContext].methodName](); + el[ctx].methodName && + vnode.context[el[ctx].methodName] && + vnode.context[el[ctx].methodName](); } else { - el[clickoutsideContext].bindingFn(); + el[ctx].bindingFn && el[ctx].bindingFn(); } }; - el[clickoutsideContext] = { + el[ctx] = { id, documentHandler, methodName: binding.expression, @@ -37,15 +40,17 @@ export default { }, update(el, binding) { - el[clickoutsideContext].methodName = binding.expression; - el[clickoutsideContext].bindingFn = binding.value; + el[ctx].methodName = binding.expression; + el[ctx].bindingFn = binding.value; }, unbind(el) { - nodeList.splice(el[clickoutsideContext].id, 1); + nodeList.splice(el[ctx].id, 1); + delete el[ctx]; }, install(Vue) { + /* istanbul ignore next */ Vue.directive('clickoutside', { bind: this.bind, unbind: this.unbind diff --git a/src/utils/resize-event.js b/src/utils/resize-event.js index a02a9ec10..a5aa48f2a 100644 --- a/src/utils/resize-event.js +++ b/src/utils/resize-event.js @@ -4,6 +4,7 @@ * version: 0.5.3 **/ +/* istanbul ignore next */ const requestFrame = (function() { const raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || function(fn) { @@ -14,6 +15,7 @@ const requestFrame = (function() { }; })(); +/* istanbul ignore next */ const cancelFrame = (function() { const cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.clearTimeout; return function(id) { @@ -21,6 +23,7 @@ const cancelFrame = (function() { }; })(); +/* istanbul ignore next */ const resetTrigger = function(element) { const trigger = element.__resizeTrigger__; const expand = trigger.firstElementChild; @@ -35,10 +38,12 @@ const resetTrigger = function(element) { expand.scrollTop = expand.scrollHeight; }; +/* istanbul ignore next */ const checkTriggers = function(element) { return element.offsetWidth !== element.__resizeLast__.width || element.offsetHeight !== element.__resizeLast__.height; }; +/* istanbul ignore next */ const scrollListener = function(event) { resetTrigger(this); if (this.__resizeRAF__) cancelFrame(this.__resizeRAF__); @@ -62,6 +67,7 @@ let animation = false; let keyFramePrefix = ''; let animationStartEvent = 'animationstart'; +/* istanbul ignore next */ if (!attachEvent) { const testElement = document.createElement('fakeelement'); if (testElement.style.animationName !== undefined) { @@ -83,6 +89,7 @@ if (!attachEvent) { } let stylesCreated = false; +/* istanbul ignore next */ const createStyles = function() { if (!stylesCreated) { const animationKeyframes = `@${keyFramePrefix}keyframes ${RESIZE_ANIMATION_NAME} { from { opacity: 0; } to { opacity: 0; } } `; @@ -110,6 +117,7 @@ const createStyles = function() { } }; +/* istanbul ignore next */ export const addResizeListener = function(element, fn) { if (attachEvent) { element.attachEvent('onresize', fn); @@ -143,6 +151,7 @@ export const addResizeListener = function(element, fn) { } }; +/* istanbul ignore next */ export const removeResizeListener = function(element, fn) { if (attachEvent) { element.detachEvent('onresize', fn); diff --git a/src/utils/vue-popper.js b/src/utils/vue-popper.js index 70a3cf92b..c0a4e6400 100644 --- a/src/utils/vue-popper.js +++ b/src/utils/vue-popper.js @@ -70,12 +70,17 @@ export default { const options = this.options; const popper = this.popperElm = this.popperElm || this.popper || this.$refs.popper; - const reference = this.referenceElm = this.referenceElm || this.reference || this.$refs.reference || this.$slots.reference[0].elm; + let reference = this.referenceElm = this.referenceElm || this.reference || this.$refs.reference; + if (!reference && + this.$slots.reference && + this.$slots.reference[0]) { + reference = this.referenceElm = this.$slots.reference[0].elm; + } if (!popper || !reference) return; if (this.visibleArrow) this.appendArrow(popper); if (this.appendToBody) document.body.appendChild(this.popperElm); - if (this.popperJS && this.popperJS.hasOwnProperty('destroy')) { + if (this.popperJS && this.popperJS.destroy) { this.popperJS.destroy(); } @@ -95,6 +100,7 @@ export default { }, doDestroy() { + /* istanbul ignore if */ if (this.showPopper || !this.popperJS) return; this.popperJS.destroy(); this.popperJS = null; @@ -110,7 +116,9 @@ export default { let placementMap = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' }; let placement = this.popperJS._popper.getAttribute('x-placement').split('-')[0]; let origin = placementMap[placement]; - this.popperJS._popper.style.transformOrigin = ['top', 'bottom'].indexOf(placement) > -1 ? `center ${ origin }` : `${ origin } center`; + this.popperJS._popper.style.transformOrigin = ['top', 'bottom'].indexOf(placement) > -1 + ? `center ${ origin }` + : `${ origin } center`; }, appendArrow(element) { diff --git a/test/unit/specs/util.clickoutside.spec.js b/test/unit/specs/util.clickoutside.spec.js new file mode 100644 index 000000000..2c0c2d514 --- /dev/null +++ b/test/unit/specs/util.clickoutside.spec.js @@ -0,0 +1,148 @@ +import Clickoutside from 'element-ui/src/utils/clickoutside'; +const ctx = '@@clickoutsideContext'; + +describe('Utils:Clickoutside', () => { + it('create', () => { + let count = 0; + const el = document.createElement('div'); + const vnode = { + context: { + handleClick: () => ++count + } + }; + const binding = { + expression: 'handleClick' + }; + + Clickoutside.bind(el, binding, vnode); + expect(el[ctx]).to.exist; + }); + + it('cotext not exist', () => { + const el = document.createElement('div'); + const vnode = {}; + const binding = { + expression: 'handleClick' + }; + + Clickoutside.bind(el, binding, vnode); + expect(el[ctx]).to.exist; + }); + + it('binding expression', () => { + const el = document.createElement('div'); + let count = 0; + const vnode = { + context: { + handleClick: () => ++count + } + }; + const binding = { + expression: 'handleClick' + }; + + Clickoutside.bind(el, binding, vnode); + document.body.click(); + expect(count).to.equal(1); + }); + + it('click inside', () => { + const el = document.createElement('div'); + const insideElm = document.createElement('div'); + let count = 0; + const vnode = { + context: { + handleClick: () => ++count + } + }; + const binding = { + expression: 'handleClick' + }; + + el.appendChild(insideElm); + Clickoutside.bind(el, binding, vnode); + insideElm.click(); + expect(count).to.equal(0); + document.body.click(); + expect(count).to.equal(1); + }); + + it('tigger event in popperElm', () => { + const el = document.createElement('div'); + const insideElm = document.createElement('div'); + let count = 0; + const vnode = { + context: { + handleClick: () => ++count, + popperElm: document.createElement('div') + } + }; + const binding = { + expression: 'handleClick' + }; + + vnode.context.popperElm.appendChild(insideElm); + Clickoutside.bind(el, binding, vnode); + insideElm.click(); + expect(count).to.equal(0); + document.body.click(); + expect(count).to.equal(1); + }); + + it('binding value', () => { + const el = document.createElement('div'); + let count = 0; + const vnode = { + context: {} + }; + const binding = { + value: () => ++count + }; + + Clickoutside.bind(el, binding, vnode); + expect(count).to.equal(0); + document.body.click(); + expect(count).to.equal(1); + }); + + it('update', () => { + let count = 0; + const el = document.createElement('div'); + const vnode = { + context: { + abc: () => ++count, + ddd: () => ++count + } + }; + const binding = { + expression: 'abc' + }; + + const newBinding = { + expression: 'ddd' + }; + + Clickoutside.bind(el, binding, vnode); + expect(el[ctx].methodName).to.equal('abc'); + Clickoutside.update(el, newBinding); + expect(el[ctx].methodName).to.equal('ddd'); + }); + + it('unbind', () => { + const el = document.createElement('div'); + let count = 0; + const vnode = { + context: {} + }; + const binding = { + value: () => ++count + }; + + Clickoutside.bind(el, binding, vnode); + document.body.click(); + Clickoutside.unbind(el); + document.body.click(); + expect(count).to.equal(1); + expect(el[ctx]).to.not.exist; + }); +}); diff --git a/test/unit/specs/util.vue-popper.spec.js b/test/unit/specs/util.vue-popper.spec.js index fedf7ad0c..a3275f2d4 100644 --- a/test/unit/specs/util.vue-popper.spec.js +++ b/test/unit/specs/util.vue-popper.spec.js @@ -12,13 +12,71 @@ const Popper = Object.assign({}, VuePopper, { } }); +const CleanPopper = Object.assign({}, VuePopper, { + render(h) { + return h('div'); + } +}); + describe('Utils:VuePopper', () => { + it('set popper not reference', () => { + const vm = createTest(CleanPopper, { + popper: document.createElement('div') + }); + vm.createPopper(); + expect(vm.popperElm).to.exist; + expect(vm.referenceElm).to.not.exist; + expect(vm.popperJS).to.not.exist; + }); + + it('set reference not popper', () => { + const vm = createTest(CleanPopper, { + reference: document.createElement('div') + }); + vm.createPopper(); + expect(vm.referenceElm).to.exist; + expect(vm.popperElm).to.not.exist; + expect(vm.popperJS).to.not.exist; + }); + + it('set reference by slot', () => { + const vm = createTest(CleanPopper); + vm.$slots['reference'] = [{ + elm: document.createElement('div') + }]; + vm.createPopper(); + expect(vm.referenceElm).to.exist; + expect(vm.popperElm).to.not.exist; + expect(vm.popperJS).to.not.exist; + }); + it('createPopper', () => { const vm = createTest(Popper, { placement: 'top' }); vm.createPopper(); expect(vm.popperJS._popper.getAttribute('x-placement')).to.equal('top'); }); + it('destroy popper when calling createPopper twice', () => { + const vm = createTest(Popper); + vm.createPopper(); + const popperJS = vm.popperJS; + + expect(vm.popperJS).to.exist; + expect(vm.popperJS).to.equal(popperJS); + vm.createPopper(); + expect(vm.popperJS).to.not.equal(popperJS); + }); + + it('updatePopper', () => { + const vm = createTest(Popper); + vm.updatePopper(); + const popperJS = vm.popperJS; + + expect(vm.popperJS).to.exist; + vm.updatePopper(); + expect(vm.popperJS).to.equal(popperJS); + }); + it('doDestroy', () => { const vm = createTest(Popper, { placement: 'top' }); vm.createPopper(); @@ -27,6 +85,15 @@ describe('Utils:VuePopper', () => { expect(vm.popperJS).to.not.exist; }); + it('destroyPopper', () => { + const vm = createTest(Popper); + const vm2 = createTest(Popper); + + vm.createPopper(); + expect(() => vm.destroyPopper()).to.not.throw(); + expect(() => vm2.destroyPopper()).to.not.throw(); + }); + it('placement', () => { const vm = createTest(Popper, { placement: 'bottom-start' }); const vm2 = createTest(Popper, { placement: 'bottom-abc' }); @@ -46,6 +113,61 @@ describe('Utils:VuePopper', () => { expect(vm.popperJS._popper.querySelector('div[x-arrow]')).to.exist; }); + it('update showPopper', done => { + const vm = createTest(Popper); + expect(vm.popperJS).to.not.exist; + vm.showPopper = true; + setTimeout(_ => { + expect(vm.popperJS).to.exist; + vm.showPopper = false; + setTimeout(_ => { + expect(vm.popperJS).to.exist; + }, 50); + done(); + }, 50); + }); + + it('resetTransformOrigin', () => { + const vm = createTest(Popper, { + placement: 'left' + }); + vm.createPopper(); + expect(vm.popperJS._popper.style.transformOrigin).to.include('right center'); + }); + + it('appendArrow', () => { + const vm = createTest(Popper, { + visibleArrow: true + }); + expect(vm.appended).to.empty; + vm.createPopper(); + expect(vm.appended).to.true; + vm.appendArrow(); + expect(vm.popperJS._popper.querySelector('[x-arrow]')).to.exist; + expect(vm.appended).to.true; + }); + + it('appendArrow: add scoped', () => { + const popper = document.createElement('div'); + popper.setAttribute('_v-110', true); + const vm = createTest(CleanPopper, { + reference: document.createElement('div'), + visibleArrow: true, + popper + }); + expect(vm.appended).to.empty; + vm.createPopper(); + expect(vm.popperJS._popper.querySelector('[x-arrow][_v-110]')).to.exist; + }); + + it('appendToBody set false', () => { + const vm = createTest(Popper, { + appendToBody: false + }); + vm.createPopper(); + expect(document.body.contains(vm.popperElm)).to.false; + }); + it('destroy', () => { const vm = createTest(Popper, true);