Browse Source

Clickoutside: add test & improve vue-popper test (#577)

cinwell.li 8 năm trước cách đây
mục cha
commit
706d47b710

+ 1 - 1
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;
 });
 

+ 1 - 1
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; });

+ 16 - 11
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 => {
  * <div v-element-clickoutside="handleClose">
  * ```
  */
-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

+ 9 - 0
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);

+ 11 - 3
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) {

+ 148 - 0
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;
+  });
+});

+ 122 - 0
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);