Permalink
Browse files

feat(ValueConverter): enable signal

fixes #353
  • Loading branch information...
bigopon authored and jdanyow committed Oct 8, 2017
1 parent b6565eb commit f6ad52a035e3efef4df5643c6f5544d8e82d10be
Showing with 140 additions and 61 deletions.
  1. +13 −0 src/ast.js
  2. +14 −0 src/signals.js
  3. +113 −61 test/binding-expression.spec.js
View
@@ -1,5 +1,6 @@
import {Unparser} from './unparser';
import {getContextFor} from './scope';
import {connectBindingToSignal} from './signals';
export class Expression {
constructor() {
@@ -148,6 +149,18 @@ export class ValueConverter extends Expression {
while (i--) {
expressions[i].connect(binding, scope);
}
let converter = binding.lookupFunctions.valueConverters(this.name);
if (!converter) {
throw new Error(`No ValueConverter named "${this.name}" was found!`);
}
let signals = converter.signals;
if (signals === undefined) {
return;
}
i = signals.length;
while (i--) {
connectBindingToSignal(binding, signals[i]);
}
}
}
View
@@ -0,0 +1,14 @@
const signals = {};
export function connectBindingToSignal(binding, name) {
if (!signals.hasOwnProperty(name)) {
signals[name] = 0;
}
binding.observeProperty(signals, name);
}
export function signalBindings(name) {
if (signals.hasOwnProperty(name)) {
signals[name]++;
}
}
@@ -5,6 +5,7 @@ import {BindingEngine} from '../src/binding-engine';
import {checkDelay} from './shared';
import {createScopeForTest} from '../src/scope';
import {sourceContext} from '../src/connectable-binding';
import {signalBindings} from '../src/signals';
describe('BindingExpression', () => {
let bindingEngine;
@@ -50,101 +51,152 @@ describe('BindingExpression', () => {
expect(binding.targetObserver.hasSubscribers()).toBe(true);
expect(binding.targetObserver.hasSubscriber(sourceContext, sourceObserver)).toBe(false);
source.foo.bar = 'xup';
setTimeout(() => {
expect(target.value).toBe('');
target.value = 'xup';
target.dispatchEvent(new CustomEvent('input'));
setTimeout(() => {
expect(source.foo.bar).toBe('xup');
binding.unbind();
expect(binding.targetObserver.hasSubscribers()).toBe(false);
done();
}, checkDelay * 2);
}, checkDelay * 2);
});
it('handles ValueConverter', done => {
let valueConverters = {
numberToString: { toView: value => value.toString(), fromView: value => parseInt(value) },
multiply: { toView: (value, arg) => value * arg, fromView: (value, arg) => value / arg }
};
spyOn(valueConverters.numberToString, 'toView').and.callThrough();
spyOn(valueConverters.numberToString, 'fromView').and.callThrough();
spyOn(valueConverters.multiply, 'toView').and.callThrough();
spyOn(valueConverters.multiply, 'fromView').and.callThrough();
let lookupFunctions = { valueConverters: name => valueConverters[name] };
let source = { foo: { bar: 1 }, arg: 2 };
let target = document.createElement('input');
let bindingExpression = bindingEngine.createBindingExpression('value', 'foo.bar | multiply:arg | numberToString', bindingMode.twoWay, lookupFunctions);
let binding = bindingExpression.createBinding(target);
binding.bind(createScopeForTest(source));
expect(target.value).toBe('2');
expect(valueConverters.numberToString.toView).toHaveBeenCalledWith(2);
expect(valueConverters.multiply.toView).toHaveBeenCalledWith(1, 2);
let sourceObserver = bindingEngine.observerLocator.getObserver(source.foo, 'bar');
expect(sourceObserver.hasSubscribers()).toBe(true);
let argObserver = bindingEngine.observerLocator.getObserver(source, 'arg');
expect(argObserver.hasSubscribers()).toBe(true);
expect(binding.targetObserver.hasSubscribers()).toBe(true);
source.foo.bar = 2;
setTimeout(() => {
expect(target.value).toBe('4');
expect(valueConverters.numberToString.toView).toHaveBeenCalledWith(4);
expect(valueConverters.multiply.toView).toHaveBeenCalledWith(2, 2);
valueConverters.numberToString.toView.calls.reset();
valueConverters.numberToString.fromView.calls.reset();
valueConverters.multiply.toView.calls.reset();
valueConverters.multiply.fromView.calls.reset();
source.arg = 4;
describe('ValueConverter', () => {
it('handles ValueConverter without signals', done => {
let valueConverters = {
numberToString: { toView: value => value.toString(), fromView: value => parseInt(value, 10) },
multiply: { toView: (value, arg) => value * arg, fromView: (value, arg) => value / arg }
};
spyOn(valueConverters.numberToString, 'toView').and.callThrough();
spyOn(valueConverters.numberToString, 'fromView').and.callThrough();
spyOn(valueConverters.multiply, 'toView').and.callThrough();
spyOn(valueConverters.multiply, 'fromView').and.callThrough();
let lookupFunctions = { valueConverters: name => valueConverters[name] };
let source = { foo: { bar: 1 }, arg: 2 };
let target = document.createElement('input');
let bindingExpression = bindingEngine.createBindingExpression('value', 'foo.bar | multiply:arg | numberToString', bindingMode.twoWay, lookupFunctions);
let binding = bindingExpression.createBinding(target);
binding.bind(createScopeForTest(source));
expect(target.value).toBe('2');
expect(valueConverters.numberToString.toView).toHaveBeenCalledWith(2);
expect(valueConverters.multiply.toView).toHaveBeenCalledWith(1, 2);
let sourceObserver = bindingEngine.observerLocator.getObserver(source.foo, 'bar');
expect(sourceObserver.hasSubscribers()).toBe(true);
let argObserver = bindingEngine.observerLocator.getObserver(source, 'arg');
expect(argObserver.hasSubscribers()).toBe(true);
expect(binding.targetObserver.hasSubscribers()).toBe(true);
source.foo.bar = 2;
setTimeout(() => {
expect(target.value).toBe('8');
expect(valueConverters.numberToString.toView).toHaveBeenCalledWith(8);
expect(valueConverters.numberToString.fromView).not.toHaveBeenCalled();
expect(valueConverters.multiply.toView).toHaveBeenCalledWith(2, 4);
expect(valueConverters.multiply.fromView).not.toHaveBeenCalled();
expect(target.value).toBe('4');
expect(valueConverters.numberToString.toView).toHaveBeenCalledWith(4);
expect(valueConverters.multiply.toView).toHaveBeenCalledWith(2, 2);
valueConverters.numberToString.toView.calls.reset();
valueConverters.numberToString.fromView.calls.reset();
valueConverters.multiply.toView.calls.reset();
valueConverters.multiply.fromView.calls.reset();
target.value = '24';
target.dispatchEvent(DOM.createCustomEvent('change'));
source.arg = 4;
setTimeout(() => {
expect(valueConverters.numberToString.toView).toHaveBeenCalledWith(24);
expect(valueConverters.numberToString.fromView).toHaveBeenCalledWith('24');
expect(valueConverters.multiply.toView).toHaveBeenCalledWith(6, 4);
expect(valueConverters.multiply.fromView).toHaveBeenCalledWith(24, 4);
expect(target.value).toBe('8');
expect(valueConverters.numberToString.toView).toHaveBeenCalledWith(8);
expect(valueConverters.numberToString.fromView).not.toHaveBeenCalled();
expect(valueConverters.multiply.toView).toHaveBeenCalledWith(2, 4);
expect(valueConverters.multiply.fromView).not.toHaveBeenCalled();
valueConverters.numberToString.toView.calls.reset();
valueConverters.numberToString.fromView.calls.reset();
valueConverters.multiply.toView.calls.reset();
valueConverters.multiply.fromView.calls.reset();
expect(source.foo.bar).toBe(6);
binding.unbind();
expect(sourceObserver.hasSubscribers()).toBe(false);
expect(argObserver.hasSubscribers()).toBe(false);
expect(binding.targetObserver.hasSubscribers()).toBe(false);
source.foo.bar = 4;
target.value = '24';
target.dispatchEvent(DOM.createCustomEvent('change'));
setTimeout(() => {
expect(valueConverters.numberToString.toView).not.toHaveBeenCalled();
expect(valueConverters.numberToString.fromView).not.toHaveBeenCalled();
expect(valueConverters.multiply.toView).not.toHaveBeenCalled();
expect(valueConverters.multiply.fromView).not.toHaveBeenCalled();
expect(target.value).toBe('24');
done();
expect(valueConverters.numberToString.toView).toHaveBeenCalledWith(24);
expect(valueConverters.numberToString.fromView).toHaveBeenCalledWith('24');
expect(valueConverters.multiply.toView).toHaveBeenCalledWith(6, 4);
expect(valueConverters.multiply.fromView).toHaveBeenCalledWith(24, 4);
valueConverters.numberToString.toView.calls.reset();
valueConverters.numberToString.fromView.calls.reset();
valueConverters.multiply.toView.calls.reset();
valueConverters.multiply.fromView.calls.reset();
expect(source.foo.bar).toBe(6);
binding.unbind();
expect(sourceObserver.hasSubscribers()).toBe(false);
expect(argObserver.hasSubscribers()).toBe(false);
expect(binding.targetObserver.hasSubscribers()).toBe(false);
source.foo.bar = 4;
setTimeout(() => {
expect(valueConverters.numberToString.toView).not.toHaveBeenCalled();
expect(valueConverters.numberToString.fromView).not.toHaveBeenCalled();
expect(valueConverters.multiply.toView).not.toHaveBeenCalled();
expect(valueConverters.multiply.fromView).not.toHaveBeenCalled();
expect(target.value).toBe('24');
done();
}, checkDelay * 2);
}, checkDelay * 2);
}, checkDelay * 2);
}, checkDelay * 2);
}, checkDelay * 2);
});
it('handles ValueConverter with signals', done => {
let prefix = '_';
let valueConverters = {
withSingleSignals: {
signals: ['hello'],
toView: val => prefix + val
},
withMultipleSignals: {
signals: ['hello', 'world'],
toView: val => prefix + val
}
};
let lookupFunctions = { valueConverters: name => valueConverters[name] };
let source = { foo: { bar: 1 }, arg: 2 };
let target1 = document.createElement('input');
let bindingExpression1 = bindingEngine.createBindingExpression(
'value',
'foo.bar | withSingleSignals',
bindingMode.oneWay,
lookupFunctions
);
let binding1 = bindingExpression1.createBinding(target1);
let target2 = document.createElement('input');
let bindingExpression2 = bindingEngine.createBindingExpression(
'value',
'foo.bar | withMultipleSignals',
bindingMode.oneWay,
lookupFunctions
);
let binding2 = bindingExpression2.createBinding(target2);
let scope = createScopeForTest(source);
binding1.bind(scope);
binding2.bind(scope);
expect(target1.value).toBe('_1');
expect(target2.value).toBe('_1');
prefix = '';
signalBindings('hello');
setTimeout(() => {
expect(target1.value).toBe('1');
expect(target2.value).toBe('1');
prefix = '_';
signalBindings('world');
setTimeout(() => {
expect(target1.value).toBe('1');
expect(target2.value).toBe('_1');
done();
}, checkDelay * 2);
}, checkDelay * 2);
});
});
it('handles BindingBehavior', done => {
let bindingBehaviors = {
numberToString: { bind: (binding, source) => {}, unbind: (binding, source) => {} },
multiply: { bind: (binding, source) => {}, unbind: (binding, source) => {} }
}
};
spyOn(bindingBehaviors.numberToString, 'bind').and.callThrough();
spyOn(bindingBehaviors.numberToString, 'unbind').and.callThrough();
spyOn(bindingBehaviors.multiply, 'bind').and.callThrough();

0 comments on commit f6ad52a

Please sign in to comment.