Add v-sync directive. (#598)

pull/602/head
FuryBean 2016-10-24 11:01:28 +08:00 committed by cinwell.li
parent 4e36ff0eb5
commit 112eb485a0
4 changed files with 210 additions and 2 deletions

View File

@ -1,3 +1,4 @@
import Sync from './utils/sync';
import Pagination from '../packages/pagination/index.js';
import Dialog from '../packages/dialog/index.js';
import Autocomplete from '../packages/autocomplete/index.js';
@ -58,6 +59,7 @@ const install = function(Vue) {
/* istanbul ignore if */
if (install.installed) return;
Vue.directive('sync', Sync);
Vue.component(Pagination.name, Pagination);
Vue.component(Dialog.name, Dialog);
Vue.component(Autocomplete.name, Autocomplete);
@ -128,6 +130,7 @@ if (typeof window !== 'undefined' && window.Vue) {
module.exports = {
version: '1.0.0-rc.7',
install,
Sync,
Pagination,
Dialog,
Autocomplete,

54
src/utils/sync.js Normal file
View File

@ -0,0 +1,54 @@
const SYNC_HOOK_PROP = '$v-sync';
/**
* v-sync directive
*
* Usage:
* v-sync:component-prop="context prop name"
*
* If your want to sync component's prop "visible" to context prop "myVisible", use like this:
* v-sync:visible="myVisible"
*/
export default {
bind(el, binding, vnode) {
const context = vnode.context;
const component = vnode.child;
const expression = binding.expression;
const prop = binding.arg;
if (!expression || !prop) {
console.warn('v-sync should specify arg & expression, for example: v-sync:visible="myVisible"');
return;
}
if (!component || !component.$watch) {
console.warn('v-sync is only available on Vue Component');
return;
}
const unwatchContext = context.$watch(expression, (val) => {
component[prop] = val;
});
const unwatchComponent = component.$watch(prop, (val) => {
context[expression] = val;
});
Object.defineProperty(component, SYNC_HOOK_PROP, {
value: {
unwatchContext,
unwatchComponent
},
enumerable: false
});
},
unbind(el, binding, vnode) {
const component = vnode.child;
if (component && component[SYNC_HOOK_PROP]) {
const { unwatchContext, unwatchComponent } = component[SYNC_HOOK_PROP];
unwatchContext && unwatchContext();
unwatchComponent && unwatchComponent();
}
}
};

View File

@ -0,0 +1,151 @@
import { createVue, triggerEvent } from '../util';
import Sync from 'element-ui/src/utils/sync';
const Test = {
template: `<div class="sync-test" v-show="visible">
<button @click="visible = false">Hide</button>
A test component.
</div>`,
data() {
return {
visible: true
};
}
};
describe('Sync', () => {
it('should not throw when use incorrectly', () => {
createVue({
template: `
<test v-sync>
</test>
`,
components: { Test },
directives: { Sync },
data() {
return {
myVisible: true
};
}
});
createVue({
template: `
<test v-sync:visible>
</test>
`,
components: { Test },
directives: { Sync },
data() {
return {
myVisible: true
};
}
});
createVue({
template: `
<test v-sync.visible>
</test>
`,
components: { Test },
directives: { Sync },
data() {
return {
myVisible: true
};
}
});
createVue({
template: `
<div v-sync:visible="myVisible">
</div>
`,
components: { Test },
directives: { Sync },
data() {
return {
myVisible: true
};
}
});
});
it('context variable should change when inner component variable change', (done) => {
const vm = createVue({
template: `
<test v-sync:visible="myVisible">
</test>
`,
components: { Test },
directives: { Sync },
data() {
return {
myVisible: true
};
}
}, true);
triggerEvent(vm.$el.querySelector('.sync-test button'), 'click', {});
setTimeout(() => {
expect(vm.myVisible).to.be.false;
done();
}, 10);
});
it('inner component variable should change when context variable change', (done) => {
const vm = createVue({
template: `
<test ref="test" v-sync:visible="myVisible">
</test>
`,
components: { Test },
directives: { Sync },
data() {
return {
myVisible: true
};
}
}, true);
vm.myVisible = false;
setTimeout(() => {
expect(vm.$refs.test.visible).to.be.false;
expect(vm.$refs.test.$el.style.display).to.equal('none');
done();
}, 10);
});
it('should unwatch expression after destroy', () => {
const vm = createVue({
template: `
<test ref="test" v-sync:visible="myVisible" v-if="createTest">
</test>
`,
components: { Test },
directives: { Sync },
data() {
return {
myVisible: true,
createTest: false
};
}
});
const beforeBindCount = vm._watchers.length;
vm.createTest = true;
const delay = 50;
setTimeout(() => {
const afterBindCount = vm._watchers.length;
expect(afterBindCount).to.be.equal(beforeBindCount + 1);
vm.createTest = false;
setTimeout(() => {
const afterDestroyCount = vm._watchers.length;
expect(afterDestroyCount).to.be.equal(beforeBindCount);
}, delay);
}, delay);
});
});

View File

@ -49,7 +49,7 @@ exports.createTest = function(Compo, propsData = {}, mounted = false) {
/**
* 触发一个事件
* mouseenter, mouseleave, mouseover, keyup, change
* mouseenter, mouseleave, mouseover, keyup, change, click
* @param {Element} elm
* @param {EventName} name
* @param {options} opts
@ -57,7 +57,7 @@ exports.createTest = function(Compo, propsData = {}, mounted = false) {
exports.triggerEvent = function(elm, name, opts) {
let eventName;
if (/^mouse/.test(name)) {
if (/^mouse|click/.test(name)) {
eventName = 'MouseEvents';
} else if (/^key/.test(name)) {
eventName = 'KeyboardEvent';