From bef80fcdeac68d4d510987a03b37811667919b7b Mon Sep 17 00:00:00 2001 From: bigopon Date: Mon, 25 Sep 2017 05:01:02 +1000 Subject: [PATCH 1/4] feat(Let): support let element --- src/binding-language.js | 66 +++++++++- src/let-expression.js | 114 +++++++++++++++++ src/let-interpolation-binding-expression.js | 103 ++++++++++++++++ test/binding-language.spec.js | 39 ++++++ test/let-expression.spec.js | 130 ++++++++++++++++++++ 5 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 src/let-expression.js create mode 100644 src/let-interpolation-binding-expression.js create mode 100644 test/let-expression.spec.js diff --git a/src/binding-language.js b/src/binding-language.js index 1d4e4c0..4aeabfa 100644 --- a/src/binding-language.js +++ b/src/binding-language.js @@ -1,9 +1,12 @@ /*eslint indent:0*/ import {BindingLanguage, BehaviorInstruction} from 'aurelia-templating'; -import {Parser, ObserverLocator, NameExpression, bindingMode} from 'aurelia-binding'; +import {Parser, ObserverLocator, NameExpression, bindingMode, camelCase, LiteralString} from 'aurelia-binding'; import {InterpolationBindingExpression} from './interpolation-binding-expression'; import {SyntaxInterpreter} from './syntax-interpreter'; import {AttributeMap} from './attribute-map'; +import {LetExpression} from './let-expression'; +import {LetInterpolationBindingExpression} from './let-interpolation-binding-expression'; +import * as LogManager from 'aurelia-logging'; let info = {}; @@ -86,6 +89,67 @@ export class TemplatingBindingLanguage extends BindingLanguage { return instruction; } + /** + * @param {ViewResources} resources + * @param {Element} letElement + * @param {(LetExpression | LetInterpolationBindingExpression)[]} existingLetExpressions + */ + createLetExpressions(resources, letElement, existingLetExpressions) { + existingLetExpressions = existingLetExpressions || []; + let attributes = letElement.attributes; + /**@type {Attr} */ + let attr; + /**@type {string[]} */ + let parts; + let attrName; + let attrValue; + let command; + for (let i = 0, ii = attributes.length; ii > i; ++i) { + attr = attributes[i]; + attrName = attr.name; + attrValue = attr.nodeValue; + parts = attrName.split('.'); + + if (parts.length === 2) { + command = parts[1]; + if (command !== 'bind') { + LogManager.getLogger('templating-binding-language') + .warn(`Detected invalid let command. Expected "${part[0]}.bind", given "${attrName}"`); + continue; + } + existingLetExpressions.push(new LetExpression( + this.observerLocator, + camelCase(parts[0]), + this.parser.parse(attrValue), + resources.lookupFunctions + )); + } else { + attrName = camelCase(attrName); + parts = this.parseInterpolation(resources, attrValue); + if (parts === null) { + LogManager.getLogger('templating-binding-language') + .warn(`Detected string literal in let bindings. Did you mean "${ attrName }.bind=${ attrValue }" or "${ attrName }=\${${ attrValue }}" ?`); + } + if (parts) { + existingLetExpressions.push(new LetInterpolationBindingExpression( + this.observerLocator, + attrName, + parts, + resources.lookupFunctions + )); + } else { + existingLetExpressions.push(new LetExpression( + this.observerLocator, + attrName, + new LiteralString(attrValue), + resources.lookupFunctions + )); + } + } + } + return existingLetExpressions; + } + inspectTextContent(resources, value) { const parts = this.parseInterpolation(resources, value); if (parts === null) { diff --git a/src/let-expression.js b/src/let-expression.js new file mode 100644 index 0000000..ea6deff --- /dev/null +++ b/src/let-expression.js @@ -0,0 +1,114 @@ +import { + connectable, + enqueueBindingConnect, + createOverrideContext, + sourceContext +} from 'aurelia-binding'; + +export class LetExpression { + /** + * @param {ObserverLocator} observerLocator + * @param {string} targetProperty + * @param {Expression} sourceExpression + * @param {{}} lookupFunctions + */ + constructor(observerLocator, targetProperty, sourceExpression, lookupFunctions) { + this.observerLocator = observerLocator; + this.sourceExpression = sourceExpression; + this.targetProperty = targetProperty; + this.lookupFunctions = lookupFunctions; + this.discrete = false; + } + + createBinding() { + return new Let( + this.observerLocator, + this.sourceExpression, + this.targetProperty, + this.lookupFunctions + ); + } +} + +@connectable() +export class Let { + /** + * + * @param {ObserverLocator} observerLocator + * @param {Expression} sourceExpression + * @param {Function | Element} target + * @param {string} targetProperty + * @param {*} lookupFunctions + */ + constructor(observerLocator, sourceExpression, targetProperty, lookupFunctions) { + this.observerLocator = observerLocator; + this.sourceExpression = sourceExpression; + this.targetProperty = targetProperty; + this.lookupFunctions = lookupFunctions; + this.source = null; + this.target = null; + } + + updateSource() { + const value = this.sourceExpression.evaluate(this.source, this.lookupFunctions); + this.target[this.targetProperty] = value; + } + + call(context, newValue, oldValue) { + if (!this.isBound) { + return; + } + if (context === sourceContext) { + this.updateSource(); + return; + } + throw new Error(`Unexpected call context ${context}`); + } + + /** + * @param {Scope} source Binding context + */ + bind(source) { + if (this.isBound) { + if (this.originalSource === source) { + return; + } + this.unbind(); + } + + let { bindingContext, parentOverrideContext } = source.overrideContext; + + this.isBound = true; + this.originalSource = source; + this.source = createOverrideContext(bindingContext, parentOverrideContext); + this.target = bindingContext; + + if (this.sourceExpression.bind) { + this.sourceExpression.bind(this, this.source, this.lookupFunctions); + } + + enqueueBindingConnect(this); + } + + unbind() { + if (!this.isBound) { + return; + } + this.isBound = false; + if (this.sourceExpression.unbind) { + this.sourceExpression.unbind(this, this.source); + } + this.source = null; + this.originalSource = null; + this.target = null; + this.unobserve(true); + } + + connect() { + if (!this.isBound) { + return; + } + this.updateSource(); + this.sourceExpression.connect(this, this.source); + } +} diff --git a/src/let-interpolation-binding-expression.js b/src/let-interpolation-binding-expression.js new file mode 100644 index 0000000..af6bde0 --- /dev/null +++ b/src/let-interpolation-binding-expression.js @@ -0,0 +1,103 @@ +import {bindingMode, createOverrideContext} from 'aurelia-binding'; + +import { + InterpolationBinding, + ChildInterpolationBinding +} from './interpolation-binding-expression'; + +export class LetInterpolationBindingExpression { + /** + * @param {ObserverLocator} observerLocator + * @param {string} targetProperty + * @param {string[]} parts + * @param {Lookups} lookupFunctions + */ + constructor(observerLocator, targetProperty, parts, lookupFunctions) { + this.observerLocator = observerLocator; + this.targetProperty = targetProperty; + this.parts = parts; + this.lookupFunctions = lookupFunctions; + } + + createBinding() { + return new LetInterpolationBinding( + this.observerLocator, + this.parts, + this.targetProperty, + this.lookupFunctions + ); + } +} + +export class LetInterpolationBinding { + /** + * + * @param {ObserverLocator} observerLocator + * @param {strign} targetProperty + * @param {string[]} parts + * @param {Lookups} lookupFunctions + */ + constructor(observerLocator, targetProperty, parts, lookupFunctions) { + this.observerLocator = observerLocator; + this.parts = parts; + this.targetProperty = targetProperty; + this.lookupFunctions = lookupFunctions; + this.target = null; + } + + /** + * @param {Scope} source + */ + bind(source) { + if (this.isBound) { + if (this.originalSource === source) { + return; + } + this.unbind(); + } + + let { bindingContext, parentOverrideContext } = source.overrideContext; + + this.isBound = true; + this.originalSource = source; + this.source = createOverrideContext(bindingContext, parentOverrideContext); + this.target = bindingContext; + + this.interpolationBinding = this.createInterpolationBinding(); + this.interpolationBinding.bind(this.source); + } + + unbind() { + if (!this.isBound) { + return; + } + this.isBound = false; + this.source = null; + this.originalSource = null; + this.target = null; + this.interpolationBinding.unbind(); + this.interpolationBinding = null; + } + + createInterpolationBinding() { + if (this.parts.length === 3) { + return new ChildInterpolationBinding( + this.target, + this.observerLocator, + this.parts[1], + bindingMode.oneWay, + this.lookupFunctions, + this.targetProperty, + this.parts[0], + this.parts[2] + ); + } + return new InterpolationBinding(this.observerLocator, + this.parts, + this.target, + this.targetProperty, + bindingMode.oneWay, + this.lookupFunctions + ); + } +} diff --git a/test/binding-language.spec.js b/test/binding-language.spec.js index 219ad0a..ece133d 100644 --- a/test/binding-language.spec.js +++ b/test/binding-language.spec.js @@ -3,7 +3,18 @@ import {TemplatingBindingLanguage} from '../src/binding-language'; import {Container} from 'aurelia-dependency-injection'; import {NameExpression} from 'aurelia-binding'; +import { + LetExpression, + Let +} from '../src/let-expression'; + +import { + LetInterpolationBindingExpression, + LetInterpolationBinding +} from '../src/let-interpolation-binding-expression'; + describe('BindingLanguage', () => { + /**@type {TemplatingBindingLanguage} */ let language; beforeAll(() => { @@ -28,4 +39,32 @@ describe('BindingLanguage', () => { expect(expression instanceof NameExpression).toBe(true); expect(expression.lookupFunctions).toBe(resources.lookupFunctions); }); + + describe('createLetExpressions', () => { + let resources; + + beforeEach(() => { + resources = { lookupFunctions: {} }; + }); + + it('creates correct let expressions', () => { + + let el1 = div(); + el1.setAttribute('foo.bind', 'bar'); + expect(language.createLetExpressions(resources, el1)[0] instanceof LetExpression).toBe(true); + + let el2 = div(); + el2.setAttribute('foo', '${bar}'); + expect(language.createLetExpressions(resources, el2)[0] instanceof LetInterpolationBindingExpression).toBe(true); + + let el3 = div(); + el3.setAttribute('foo', 'bar'); + expect(language.createLetExpressions(resources, el3)[0] instanceof LetExpression).toBe(true); + }); + + function div() { + return document.createElement('div'); + } + }); + }); diff --git a/test/let-expression.spec.js b/test/let-expression.spec.js new file mode 100644 index 0000000..5b069f0 --- /dev/null +++ b/test/let-expression.spec.js @@ -0,0 +1,130 @@ +import { + ObserverLocator, + Parser, + createOverrideContext, + createScopeForTest +} from 'aurelia-binding'; + +import {Container} from 'aurelia-dependency-injection'; + +import {TemplatingBindingLanguage} from '../src/binding-language'; +import { + Let, + LetExpression +} from '../src/let-expression'; +import { + LetInterpolationBindingExpression, + LetInterpolationBinding +} from '../src/let-interpolation-binding-expression'; +import { + InterpolationBinding, + ChildInterpolationBinding +} from '../src/interpolation-binding-expression'; + +describe('Let', () => { + /**@type {ObserverLocator} */ + let observerLocator; + /**@type {Parser} */ + let parser; + /**@type {TemplatingBindingLanguage} */ + let language; + let LookupFunctions = {}; + let checkDelay = 40; + + beforeEach(() => { + let ct = new Container(); + language = ct.get(TemplatingBindingLanguage); + observerLocator = ct.get(ObserverLocator); + parser = ct.get(Parser); + }); + + describe('LetExpression', () => { + it('creates binding', () => { + let letExpression = new LetExpression(observerLocator, 'foo', parser.parse('bar'), LookupFunctions); + let binding = letExpression.createBinding(); + expect(binding instanceof Let).toBe(true); + }); + }); + + describe('Let binding', () => { + it('binds correctly', done => { + let vm = { foo: 'bar', baz: { foo: 'baz' } }; + let scope = createScopeForTest(vm); + let binding = new Let(observerLocator, parser.parse('baz.foo'), 'bar', LookupFunctions); + + binding.bind(scope); + + expect(binding.target).toBe(vm); + expect(binding.source.bindingContext).toBe(vm); + + expect(vm.bar).toBe('baz'); + + vm.baz.foo = 'bar'; + expect(vm.bar).toBe('baz'); + + setTimeout(() => { + expect(vm.bar).toBe('bar'); + + binding.unbind(); + expect(binding.source).toBe(null); + + done(); + }, checkDelay * 2); + }); + }); + + describe('LetInterpolationBinding', () => { + it('gets created correctly', () => { + let letInterExpression = new LetInterpolationBindingExpression(observerLocator, 'foo', ['', 'bar', ''], LookupFunctions); + let letInterBinding = letInterExpression.createBinding(); + + expect(letInterBinding instanceof LetInterpolationBinding).toBe(true); + }); + + it('binds ChildInterpolationBinding correctly', done => { + let vm = { foo: 'bar', baz: { foo: 'baz' } }; + let scope = createScopeForTest(vm); + + let binding = new LetInterpolationBinding(observerLocator, 'bar', ['', parser.parse('baz.foo'), ''], LookupFunctions); + binding.bind(scope); + + expect(binding.target).toBe(vm); + expect(binding.interpolationBinding instanceof ChildInterpolationBinding).toBe(true); + expect(vm.bar).toBe('baz'); + vm.baz.foo = 'bar'; + expect(vm.bar).toBe('baz'); + + setTimeout(() => { + expect(vm.bar).toBe('bar'); + + binding.unbind(); + expect(binding.interpolationBinding).toBe(null); + expect(binding.target).toBe(null); + + done(); + }, checkDelay * 2); + }); + + it('binds InterpolationBinding correctly', done => { + let vm = { foo: 'bar', baz: { foo: 'baz' } }; + let scope = createScopeForTest(vm); + let binding = new LetInterpolationBinding(observerLocator, 'bar', ['foo is: ', parser.parse('foo'), '. And baz.foo is ', parser.parse('baz.foo'), ''], LookupFunctions); + binding.bind(scope); + + expect(binding.target).toBe(vm); + expect(binding.interpolationBinding instanceof InterpolationBinding).toBe(true); + expect(vm.bar).toBe('foo is: bar. And baz.foo is baz'); + vm.foo = 'foo'; + expect(vm.bar).toBe('foo is: bar. And baz.foo is baz'); + + setTimeout(() => { + expect(vm.bar).toBe('foo is: foo. And baz.foo is baz'); + + binding.unbind(); + expect(binding.interpolationBinding).toBe(null); + + done(); + }, checkDelay * 2); + }); + }); +}); From 4a9f74e20de06797bdcb478cff0815ebb3ca5ffd Mon Sep 17 00:00:00 2001 From: bigopon Date: Mon, 11 Dec 2017 21:54:23 +1100 Subject: [PATCH 2/4] simplify scope, adjust typings --- dist/aurelia-templating-binding.d.ts | 43 +++++++++++++++++++-- src/let-expression.js | 14 +++---- src/let-interpolation-binding-expression.js | 16 +++----- 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/dist/aurelia-templating-binding.d.ts b/dist/aurelia-templating-binding.d.ts index 3b145e6..07a86b8 100644 --- a/dist/aurelia-templating-binding.d.ts +++ b/dist/aurelia-templating-binding.d.ts @@ -16,24 +16,25 @@ import { } from 'aurelia-binding'; import { BehaviorInstruction, - BindingLanguage + BindingLanguage, + ViewResources } from 'aurelia-templating'; export declare class AttributeMap { static inject: any; elements: any; allElements: any; constructor(svg?: any); - + /** * Maps a specific HTML element attribute to a javascript property. */ register(elementName?: any, attributeName?: any, propertyName?: any): any; - + /** * Maps an HTML attribute to a javascript property. */ registerUniversal(attributeName?: any, propertyName?: any): any; - + /** * Returns the javascript property name for a particlar HTML attribute. */ @@ -74,13 +75,47 @@ export declare class SyntaxInterpreter { options(resources?: any, element?: any, info?: any, existingInstruction?: any, context?: any): any; 'for'(resources?: any, element?: any, info?: any, existingInstruction?: any): any; 'two-way'(resources?: any, element?: any, info?: any, existingInstruction?: any): any; + 'to-view'(resources?: any, element?: any, info?: any, existingInstruction?: any): any; + 'from-view'(resources?: any, element?: any, info?: any, existingInstruction?: any): any; 'one-way'(resources?: any, element?: any, info?: any, existingInstruction?: any): any; 'one-time'(resources?: any, element?: any, info?: any, existingInstruction?: any): any; } + +export declare class LetExpression { + createBinding(): LetBinding +} + +export declare class LetBinding { + constructor(); + updateSource(): any; + call(context): any; + bind(source?: any): any; + unbind(): any; + connect(): any; +} + +export declare class LetInterpolationBindingExpression { + createBinding(): LetInterpolationBinding +} + +export declare class LetInterpolationBinding { + constructor(); + updateSource(): any; + call(context): any; + bind(source?: any): any; + unbind(): any; + connect(): any; +} + export declare class TemplatingBindingLanguage extends BindingLanguage { static inject: any; constructor(parser?: any, observerLocator?: any, syntaxInterpreter?: any, attributeMap?: any); inspectAttribute(resources?: any, elementName?: any, attrName?: any, attrValue?: any): any; + createLetExpressions( + resources: ViewResources, + letElement: HTMLElement, + existingLetExpressions: (LetExpression | LetInterpolationBindingExpression)[] + ): (LetExpression | LetInterpolationBindingExpression)[] createAttributeInstruction(resources?: any, element?: any, theInfo?: any, existingInstruction?: any, context?: any): any; inspectTextContent(resources?: any, value?: any): any; parseInterpolation(resources?: any, value?: any): any; diff --git a/src/let-expression.js b/src/let-expression.js index ea6deff..79e832c 100644 --- a/src/let-expression.js +++ b/src/let-expression.js @@ -54,7 +54,7 @@ export class Let { this.target[this.targetProperty] = value; } - call(context, newValue, oldValue) { + call(context) { if (!this.isBound) { return; } @@ -70,21 +70,18 @@ export class Let { */ bind(source) { if (this.isBound) { - if (this.originalSource === source) { + if (this.source === source) { return; } this.unbind(); } - let { bindingContext, parentOverrideContext } = source.overrideContext; - this.isBound = true; - this.originalSource = source; - this.source = createOverrideContext(bindingContext, parentOverrideContext); - this.target = bindingContext; + this.source = source; + this.target = source.bindingContext; if (this.sourceExpression.bind) { - this.sourceExpression.bind(this, this.source, this.lookupFunctions); + this.sourceExpression.bind(this, source, this.lookupFunctions); } enqueueBindingConnect(this); @@ -99,7 +96,6 @@ export class Let { this.sourceExpression.unbind(this, this.source); } this.source = null; - this.originalSource = null; this.target = null; this.unobserve(true); } diff --git a/src/let-interpolation-binding-expression.js b/src/let-interpolation-binding-expression.js index af6bde0..505d9f9 100644 --- a/src/let-interpolation-binding-expression.js +++ b/src/let-interpolation-binding-expression.js @@ -31,7 +31,6 @@ export class LetInterpolationBindingExpression { export class LetInterpolationBinding { /** - * * @param {ObserverLocator} observerLocator * @param {strign} targetProperty * @param {string[]} parts @@ -50,21 +49,18 @@ export class LetInterpolationBinding { */ bind(source) { if (this.isBound) { - if (this.originalSource === source) { + if (this.source === source) { return; } this.unbind(); } - let { bindingContext, parentOverrideContext } = source.overrideContext; - this.isBound = true; - this.originalSource = source; - this.source = createOverrideContext(bindingContext, parentOverrideContext); - this.target = bindingContext; + this.source = source + this.target = source.bindingContext; this.interpolationBinding = this.createInterpolationBinding(); - this.interpolationBinding.bind(this.source); + this.interpolationBinding.bind(source); } unbind() { @@ -73,7 +69,6 @@ export class LetInterpolationBinding { } this.isBound = false; this.source = null; - this.originalSource = null; this.target = null; this.interpolationBinding.unbind(); this.interpolationBinding = null; @@ -92,7 +87,8 @@ export class LetInterpolationBinding { this.parts[2] ); } - return new InterpolationBinding(this.observerLocator, + return new InterpolationBinding( + this.observerLocator, this.parts, this.target, this.targetProperty, From 5ab75bcf5e62467929c3b58666cbba8ad42fe9d5 Mon Sep 17 00:00:00 2001 From: "bigopon.777@gmail.com" Date: Thu, 28 Jun 2018 13:41:39 +1000 Subject: [PATCH 3/4] to view by default, accepts options to assign to view model --- src/binding-language.js | 2 +- src/let-expression.js | 224 ++++++------ ...ion.js => let-interpolation-expression.js} | 203 +++++----- test/binding-language.spec.js | 2 +- test/let-expression.spec.js | 346 +++++++++++------- 5 files changed, 436 insertions(+), 341 deletions(-) rename src/{let-interpolation-binding-expression.js => let-interpolation-expression.js} (81%) diff --git a/src/binding-language.js b/src/binding-language.js index 4aeabfa..4eb0952 100644 --- a/src/binding-language.js +++ b/src/binding-language.js @@ -5,7 +5,7 @@ import {InterpolationBindingExpression} from './interpolation-binding-expression import {SyntaxInterpreter} from './syntax-interpreter'; import {AttributeMap} from './attribute-map'; import {LetExpression} from './let-expression'; -import {LetInterpolationBindingExpression} from './let-interpolation-binding-expression'; +import {LetInterpolationBindingExpression} from './let-interpolation-expression'; import * as LogManager from 'aurelia-logging'; let info = {}; diff --git a/src/let-expression.js b/src/let-expression.js index 79e832c..1d18761 100644 --- a/src/let-expression.js +++ b/src/let-expression.js @@ -1,110 +1,114 @@ -import { - connectable, - enqueueBindingConnect, - createOverrideContext, - sourceContext -} from 'aurelia-binding'; - -export class LetExpression { - /** - * @param {ObserverLocator} observerLocator - * @param {string} targetProperty - * @param {Expression} sourceExpression - * @param {{}} lookupFunctions - */ - constructor(observerLocator, targetProperty, sourceExpression, lookupFunctions) { - this.observerLocator = observerLocator; - this.sourceExpression = sourceExpression; - this.targetProperty = targetProperty; - this.lookupFunctions = lookupFunctions; - this.discrete = false; - } - - createBinding() { - return new Let( - this.observerLocator, - this.sourceExpression, - this.targetProperty, - this.lookupFunctions - ); - } -} - -@connectable() -export class Let { - /** - * - * @param {ObserverLocator} observerLocator - * @param {Expression} sourceExpression - * @param {Function | Element} target - * @param {string} targetProperty - * @param {*} lookupFunctions - */ - constructor(observerLocator, sourceExpression, targetProperty, lookupFunctions) { - this.observerLocator = observerLocator; - this.sourceExpression = sourceExpression; - this.targetProperty = targetProperty; - this.lookupFunctions = lookupFunctions; - this.source = null; - this.target = null; - } - - updateSource() { - const value = this.sourceExpression.evaluate(this.source, this.lookupFunctions); - this.target[this.targetProperty] = value; - } - - call(context) { - if (!this.isBound) { - return; - } - if (context === sourceContext) { - this.updateSource(); - return; - } - throw new Error(`Unexpected call context ${context}`); - } - - /** - * @param {Scope} source Binding context - */ - bind(source) { - if (this.isBound) { - if (this.source === source) { - return; - } - this.unbind(); - } - - this.isBound = true; - this.source = source; - this.target = source.bindingContext; - - if (this.sourceExpression.bind) { - this.sourceExpression.bind(this, source, this.lookupFunctions); - } - - enqueueBindingConnect(this); - } - - unbind() { - if (!this.isBound) { - return; - } - this.isBound = false; - if (this.sourceExpression.unbind) { - this.sourceExpression.unbind(this, this.source); - } - this.source = null; - this.target = null; - this.unobserve(true); - } - - connect() { - if (!this.isBound) { - return; - } - this.updateSource(); - this.sourceExpression.connect(this, this.source); - } -} +import { + connectable, + enqueueBindingConnect, + createOverrideContext, + sourceContext +} from 'aurelia-binding'; + +export class LetExpression { + /** + * @param {ObserverLocator} observerLocator + * @param {string} targetProperty + * @param {Expression} sourceExpression + * @param {any} lookupFunctions + * @param {boolean} toBindingContext indicates let binding result should be assigned to binding context + */ + constructor(observerLocator, targetProperty, sourceExpression, lookupFunctions, toBindingContext) { + this.observerLocator = observerLocator; + this.sourceExpression = sourceExpression; + this.targetProperty = targetProperty; + this.lookupFunctions = lookupFunctions; + this.toBindingContext = toBindingContext; + } + + createBinding() { + return new Let( + this.observerLocator, + this.sourceExpression, + this.targetProperty, + this.lookupFunctions, + this.toBindingContext + ); + } +} + +@connectable() +export class Let { + /** + * + * @param {ObserverLocator} observerLocator + * @param {Expression} sourceExpression + * @param {Function | Element} target + * @param {string} targetProperty + * @param {*} lookupFunctions + * @param {boolean} toBindingContext indicates let binding result should be assigned to binding context + */ + constructor(observerLocator, sourceExpression, targetProperty, lookupFunctions, toBindingContext) { + this.observerLocator = observerLocator; + this.sourceExpression = sourceExpression; + this.targetProperty = targetProperty; + this.lookupFunctions = lookupFunctions; + this.source = null; + this.target = null; + this.toBindingContext = toBindingContext; + } + + updateSource() { + const value = this.sourceExpression.evaluate(this.source, this.lookupFunctions); + this.target[this.targetProperty] = value; + } + + call(context) { + if (!this.isBound) { + return; + } + if (context === sourceContext) { + this.updateSource(); + return; + } + throw new Error(`Unexpected call context ${context}`); + } + + /** + * @param {Scope} source Binding context + */ + bind(source) { + if (this.isBound) { + if (this.source === source) { + return; + } + this.unbind(); + } + + this.isBound = true; + this.source = source; + this.target = this.toBindingContext ? source.bindingContext : source.overrideContext; + + if (this.sourceExpression.bind) { + this.sourceExpression.bind(this, source, this.lookupFunctions); + } + + enqueueBindingConnect(this); + } + + unbind() { + if (!this.isBound) { + return; + } + this.isBound = false; + if (this.sourceExpression.unbind) { + this.sourceExpression.unbind(this, this.source); + } + this.source = null; + this.target = null; + this.unobserve(true); + } + + connect() { + if (!this.isBound) { + return; + } + this.updateSource(); + this.sourceExpression.connect(this, this.source); + } +} diff --git a/src/let-interpolation-binding-expression.js b/src/let-interpolation-expression.js similarity index 81% rename from src/let-interpolation-binding-expression.js rename to src/let-interpolation-expression.js index 505d9f9..6806ee9 100644 --- a/src/let-interpolation-binding-expression.js +++ b/src/let-interpolation-expression.js @@ -1,99 +1,104 @@ -import {bindingMode, createOverrideContext} from 'aurelia-binding'; - -import { - InterpolationBinding, - ChildInterpolationBinding -} from './interpolation-binding-expression'; - -export class LetInterpolationBindingExpression { - /** - * @param {ObserverLocator} observerLocator - * @param {string} targetProperty - * @param {string[]} parts - * @param {Lookups} lookupFunctions - */ - constructor(observerLocator, targetProperty, parts, lookupFunctions) { - this.observerLocator = observerLocator; - this.targetProperty = targetProperty; - this.parts = parts; - this.lookupFunctions = lookupFunctions; - } - - createBinding() { - return new LetInterpolationBinding( - this.observerLocator, - this.parts, - this.targetProperty, - this.lookupFunctions - ); - } -} - -export class LetInterpolationBinding { - /** - * @param {ObserverLocator} observerLocator - * @param {strign} targetProperty - * @param {string[]} parts - * @param {Lookups} lookupFunctions - */ - constructor(observerLocator, targetProperty, parts, lookupFunctions) { - this.observerLocator = observerLocator; - this.parts = parts; - this.targetProperty = targetProperty; - this.lookupFunctions = lookupFunctions; - this.target = null; - } - - /** - * @param {Scope} source - */ - bind(source) { - if (this.isBound) { - if (this.source === source) { - return; - } - this.unbind(); - } - - this.isBound = true; - this.source = source - this.target = source.bindingContext; - - this.interpolationBinding = this.createInterpolationBinding(); - this.interpolationBinding.bind(source); - } - - unbind() { - if (!this.isBound) { - return; - } - this.isBound = false; - this.source = null; - this.target = null; - this.interpolationBinding.unbind(); - this.interpolationBinding = null; - } - - createInterpolationBinding() { - if (this.parts.length === 3) { - return new ChildInterpolationBinding( - this.target, - this.observerLocator, - this.parts[1], - bindingMode.oneWay, - this.lookupFunctions, - this.targetProperty, - this.parts[0], - this.parts[2] - ); - } - return new InterpolationBinding( - this.observerLocator, - this.parts, - this.target, - this.targetProperty, - bindingMode.oneWay, - this.lookupFunctions - ); - } -} +import {bindingMode, createOverrideContext} from 'aurelia-binding'; + +import { + InterpolationBinding, + ChildInterpolationBinding +} from './interpolation-binding-expression'; + +export class LetInterpolationBindingExpression { + /** + * @param {ObserverLocator} observerLocator + * @param {string} targetProperty + * @param {string[]} parts + * @param {Lookups} lookupFunctions + * @param {boolean} toBindingContext indicates let binding result should be assigned to binding context + */ + constructor(observerLocator, targetProperty, parts, lookupFunctions, toBindingContext) { + this.observerLocator = observerLocator; + this.targetProperty = targetProperty; + this.parts = parts; + this.lookupFunctions = lookupFunctions; + this.toBindingContext = toBindingContext; + } + + createBinding() { + return new LetInterpolationBinding( + this.observerLocator, + this.parts, + this.targetProperty, + this.lookupFunctions, + this.toBindingContext + ); + } +} + +export class LetInterpolationBinding { + /** + * @param {ObserverLocator} observerLocator + * @param {strign} targetProperty + * @param {string[]} parts + * @param {Lookups} lookupFunctions + * @param {boolean} toBindingContext indicates let binding result should be assigned to binding context + */ + constructor(observerLocator, targetProperty, parts, lookupFunctions, toBindingContext) { + this.observerLocator = observerLocator; + this.parts = parts; + this.targetProperty = targetProperty; + this.lookupFunctions = lookupFunctions; + this.toBindingContext = toBindingContext; + this.target = null; + } + + /** + * @param {Scope} source + */ + bind(source) { + if (this.isBound) { + if (this.source === source) { + return; + } + this.unbind(); + } + + this.isBound = true; + this.source = source + this.target = this.toBindingContext ? source.bindingContext : source.overrideContext; + + this.interpolationBinding = this.createInterpolationBinding(); + this.interpolationBinding.bind(source); + } + + unbind() { + if (!this.isBound) { + return; + } + this.isBound = false; + this.source = null; + this.target = null; + this.interpolationBinding.unbind(); + this.interpolationBinding = null; + } + + createInterpolationBinding() { + if (this.parts.length === 3) { + return new ChildInterpolationBinding( + this.target, + this.observerLocator, + this.parts[1], + bindingMode.oneWay, + this.lookupFunctions, + this.targetProperty, + this.parts[0], + this.parts[2] + ); + } + return new InterpolationBinding( + this.observerLocator, + this.parts, + this.target, + this.targetProperty, + bindingMode.oneWay, + this.lookupFunctions + ); + } +} diff --git a/test/binding-language.spec.js b/test/binding-language.spec.js index ece133d..ef4d552 100644 --- a/test/binding-language.spec.js +++ b/test/binding-language.spec.js @@ -11,7 +11,7 @@ import { import { LetInterpolationBindingExpression, LetInterpolationBinding -} from '../src/let-interpolation-binding-expression'; +} from '../src/let-interpolation-expression'; describe('BindingLanguage', () => { /**@type {TemplatingBindingLanguage} */ diff --git a/test/let-expression.spec.js b/test/let-expression.spec.js index 5b069f0..bc0d007 100644 --- a/test/let-expression.spec.js +++ b/test/let-expression.spec.js @@ -1,130 +1,216 @@ -import { - ObserverLocator, - Parser, - createOverrideContext, - createScopeForTest -} from 'aurelia-binding'; - -import {Container} from 'aurelia-dependency-injection'; - -import {TemplatingBindingLanguage} from '../src/binding-language'; -import { - Let, - LetExpression -} from '../src/let-expression'; -import { - LetInterpolationBindingExpression, - LetInterpolationBinding -} from '../src/let-interpolation-binding-expression'; -import { - InterpolationBinding, - ChildInterpolationBinding -} from '../src/interpolation-binding-expression'; - -describe('Let', () => { - /**@type {ObserverLocator} */ - let observerLocator; - /**@type {Parser} */ - let parser; - /**@type {TemplatingBindingLanguage} */ - let language; - let LookupFunctions = {}; - let checkDelay = 40; - - beforeEach(() => { - let ct = new Container(); - language = ct.get(TemplatingBindingLanguage); - observerLocator = ct.get(ObserverLocator); - parser = ct.get(Parser); - }); - - describe('LetExpression', () => { - it('creates binding', () => { - let letExpression = new LetExpression(observerLocator, 'foo', parser.parse('bar'), LookupFunctions); - let binding = letExpression.createBinding(); - expect(binding instanceof Let).toBe(true); - }); - }); - - describe('Let binding', () => { - it('binds correctly', done => { - let vm = { foo: 'bar', baz: { foo: 'baz' } }; - let scope = createScopeForTest(vm); - let binding = new Let(observerLocator, parser.parse('baz.foo'), 'bar', LookupFunctions); - - binding.bind(scope); - - expect(binding.target).toBe(vm); - expect(binding.source.bindingContext).toBe(vm); - - expect(vm.bar).toBe('baz'); - - vm.baz.foo = 'bar'; - expect(vm.bar).toBe('baz'); - - setTimeout(() => { - expect(vm.bar).toBe('bar'); - - binding.unbind(); - expect(binding.source).toBe(null); - - done(); - }, checkDelay * 2); - }); - }); - - describe('LetInterpolationBinding', () => { - it('gets created correctly', () => { - let letInterExpression = new LetInterpolationBindingExpression(observerLocator, 'foo', ['', 'bar', ''], LookupFunctions); - let letInterBinding = letInterExpression.createBinding(); - - expect(letInterBinding instanceof LetInterpolationBinding).toBe(true); - }); - - it('binds ChildInterpolationBinding correctly', done => { - let vm = { foo: 'bar', baz: { foo: 'baz' } }; - let scope = createScopeForTest(vm); - - let binding = new LetInterpolationBinding(observerLocator, 'bar', ['', parser.parse('baz.foo'), ''], LookupFunctions); - binding.bind(scope); - - expect(binding.target).toBe(vm); - expect(binding.interpolationBinding instanceof ChildInterpolationBinding).toBe(true); - expect(vm.bar).toBe('baz'); - vm.baz.foo = 'bar'; - expect(vm.bar).toBe('baz'); - - setTimeout(() => { - expect(vm.bar).toBe('bar'); - - binding.unbind(); - expect(binding.interpolationBinding).toBe(null); - expect(binding.target).toBe(null); - - done(); - }, checkDelay * 2); - }); - - it('binds InterpolationBinding correctly', done => { - let vm = { foo: 'bar', baz: { foo: 'baz' } }; - let scope = createScopeForTest(vm); - let binding = new LetInterpolationBinding(observerLocator, 'bar', ['foo is: ', parser.parse('foo'), '. And baz.foo is ', parser.parse('baz.foo'), ''], LookupFunctions); - binding.bind(scope); - - expect(binding.target).toBe(vm); - expect(binding.interpolationBinding instanceof InterpolationBinding).toBe(true); - expect(vm.bar).toBe('foo is: bar. And baz.foo is baz'); - vm.foo = 'foo'; - expect(vm.bar).toBe('foo is: bar. And baz.foo is baz'); - - setTimeout(() => { - expect(vm.bar).toBe('foo is: foo. And baz.foo is baz'); - - binding.unbind(); - expect(binding.interpolationBinding).toBe(null); - - done(); - }, checkDelay * 2); - }); - }); -}); +import { + ObserverLocator, + Parser, + createOverrideContext, + createScopeForTest +} from 'aurelia-binding'; + +import {Container} from 'aurelia-dependency-injection'; + +import {TemplatingBindingLanguage} from '../src/binding-language'; +import { + Let, + LetExpression +} from '../src/let-expression'; +import { + LetInterpolationBindingExpression, + LetInterpolationBinding +} from '../src/let-interpolation-expression'; +import { + InterpolationBinding, + ChildInterpolationBinding +} from '../src/interpolation-binding-expression'; + +describe('Let', () => { + /**@type {ObserverLocator} */ + let observerLocator; + /**@type {Parser} */ + let parser; + /**@type {TemplatingBindingLanguage} */ + let language; + let LookupFunctions = {}; + let checkDelay = 40; + + beforeEach(() => { + let ct = new Container(); + language = ct.get(TemplatingBindingLanguage); + observerLocator = ct.get(ObserverLocator); + parser = ct.get(Parser); + }); + + describe('LetExpression', () => { + it('creates binding', () => { + let letExpression = new LetExpression(observerLocator, 'foo', parser.parse('bar'), LookupFunctions); + let binding = letExpression.createBinding(); + expect(binding instanceof Let).toBe(true); + }); + }); + + describe('Let binding', () => { + it('binds to overrideContext', done => { + let vm = { foo: 'bar', baz: { foo: 'baz' } }; + let scope = createScopeForTest(vm); + let binding = new Let(observerLocator, parser.parse('baz.foo'), 'bar', LookupFunctions); + + binding.bind(scope); + + expect(binding.target).toBe(scope.overrideContext); + + expect(scope.overrideContext.bar).toBe('baz'); + + vm.baz.foo = 'bar'; + expect(scope.overrideContext.bar).toBe('baz'); + + setTimeout(() => { + expect(scope.overrideContext.bar).toBe('bar'); + + binding.unbind(); + expect(binding.source).toBe(null); + + done(); + }, checkDelay * 2); + }); + + it('binds to bindingContext', done => { + let vm = { foo: 'bar', baz: { foo: 'baz' } }; + let scope = createScopeForTest(vm); + let binding = new Let(observerLocator, parser.parse('baz.foo'), 'bar', LookupFunctions, true); + + binding.bind(scope); + + expect(binding.target).toBe(scope.bindingContext); + expect(binding.target).toBe(vm); + + expect(scope.bindingContext.bar).toBe('baz'); + + vm.baz.foo = 'bar'; + expect(scope.bindingContext.bar).toBe('baz'); + + setTimeout(() => { + expect(scope.bindingContext.bar).toBe('bar'); + + binding.unbind(); + expect(binding.source).toBe(null); + + done(); + }, checkDelay * 2); + }); + }); + + describe('[interpolation]', () => { + it('gets created correctly', () => { + let letInterExpression = new LetInterpolationBindingExpression(observerLocator, 'foo', ['', 'bar', ''], LookupFunctions); + let letInterBinding = letInterExpression.createBinding(); + + expect(letInterBinding instanceof LetInterpolationBinding).toBe(true); + }); + + describe('ChildInterpolationBinding', () => { + + it('binds to overrideContext', done => { + let vm = { foo: 'bar', baz: { foo: 'baz' } }; + let scope = createScopeForTest(vm); + + let binding = new LetInterpolationBinding(observerLocator, 'bar', ['', parser.parse('baz.foo'), ''], LookupFunctions); + binding.bind(scope); + + expect(binding.target).toBe(scope.overrideContext); + expect(binding.interpolationBinding instanceof ChildInterpolationBinding).toBe(true); + + expect(scope.overrideContext.bar).toBe('baz'); + vm.baz.foo = 'bar'; + expect(scope.overrideContext.bar).toBe('baz'); + + setTimeout(() => { + expect(scope.overrideContext.bar).toBe('bar'); + + binding.unbind(); + expect(binding.interpolationBinding).toBe(null); + expect(binding.target).toBe(null); + + done(); + }, checkDelay * 2); + }); + + it('binds to bindingContext', done => { + let vm = { foo: 'bar', baz: { foo: 'baz' } }; + let scope = createScopeForTest(vm); + + let binding = new LetInterpolationBinding(observerLocator, 'bar', ['', parser.parse('baz.foo'), ''], LookupFunctions, true); + binding.bind(scope); + + expect(binding.target).toBe(scope.bindingContext); + expect(binding.target).toBe(vm); + + expect(binding.interpolationBinding instanceof ChildInterpolationBinding).toBe(true); + expect(scope.bindingContext.bar).toBe('baz'); + vm.baz.foo = 'bar'; + expect(scope.bindingContext.bar).toBe('baz'); + + setTimeout(() => { + expect(scope.bindingContext.bar).toBe('bar'); + + binding.unbind(); + expect(binding.interpolationBinding).toBe(null); + expect(binding.target).toBe(null); + + done(); + }, checkDelay * 2); + }); + }); + + describe('InterpolationBinding', () => { + it('binds to overrideContext', done => { + let vm = { foo: 'bar', baz: { foo: 'baz' } }; + let scope = createScopeForTest(vm); + let binding = new LetInterpolationBinding(observerLocator, 'bar', ['foo is: ', parser.parse('foo'), '. And baz.foo is ', parser.parse('baz.foo'), ''], LookupFunctions); + binding.bind(scope); + + expect(binding.target).toBe(scope.overrideContext); + expect(binding.interpolationBinding instanceof InterpolationBinding).toBe(true); + + expect(scope.overrideContext.bar).toBe('foo is: bar. And baz.foo is baz'); + vm.foo = 'foo'; + expect(scope.overrideContext.bar).toBe('foo is: bar. And baz.foo is baz'); + + setTimeout(() => { + expect(scope.overrideContext.bar).toBe('foo is: foo. And baz.foo is baz'); + + binding.unbind(); + expect(binding.interpolationBinding).toBe(null); + + done(); + }, checkDelay * 2); + }); + + it('binds to bindingContext', done => { + let vm = { foo: 'bar', baz: { foo: 'baz' } }; + let scope = createScopeForTest(vm); + let binding = new LetInterpolationBinding( + observerLocator, + 'bar', + ['foo is: ', parser.parse('foo'), '. And baz.foo is ', parser.parse('baz.foo'), ''], + LookupFunctions, + true + ); + binding.bind(scope); + + expect(binding.target).toBe(vm); + expect(binding.interpolationBinding instanceof InterpolationBinding).toBe(true); + expect(vm.bar).toBe('foo is: bar. And baz.foo is baz'); + vm.foo = 'foo'; + expect(vm.bar).toBe('foo is: bar. And baz.foo is baz'); + + setTimeout(() => { + expect(vm.bar).toBe('foo is: foo. And baz.foo is baz'); + + binding.unbind(); + expect(binding.interpolationBinding).toBe(null); + + done(); + }, checkDelay * 2); + }); + + }); + }); +}); From 863b6efc28adc5affc8542b580193fb21b9bc88a Mon Sep 17 00:00:00 2001 From: "bigopon.777@gmail.com" Date: Fri, 28 Sep 2018 18:27:04 +1000 Subject: [PATCH 4/4] fix(BindingLanguage+Let): enhance implementation --- package.json | 3 ++ src/binding-language.js | 31 +++++++++++------- src/let-expression.js | 16 ++++----- src/let-interpolation-expression.js | 5 ++- test/binding-language.spec.js | 50 ++++++++++++++++++++++------- 5 files changed, 71 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index a800d43..71bc2fc 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,9 @@ "type": "git", "url": "http://github.com/aurelia/templating-binding" }, + "scripts": { + "test": "karma start" + }, "jspm": { "registry": "npm", "jspmPackage": true, diff --git a/src/binding-language.js b/src/binding-language.js index 4eb0952..72abdc1 100644 --- a/src/binding-language.js +++ b/src/binding-language.js @@ -21,6 +21,7 @@ export class TemplatingBindingLanguage extends BindingLanguage { this.emptyStringExpression = this.parser.parse('\'\''); syntaxInterpreter.language = this; this.attributeMap = attributeMap; + this.toBindingContextAttr = 'to-binding-context'; } inspectAttribute(resources, elementName, attrName, attrValue) { @@ -92,10 +93,9 @@ export class TemplatingBindingLanguage extends BindingLanguage { /** * @param {ViewResources} resources * @param {Element} letElement - * @param {(LetExpression | LetInterpolationBindingExpression)[]} existingLetExpressions */ - createLetExpressions(resources, letElement, existingLetExpressions) { - existingLetExpressions = existingLetExpressions || []; + createLetExpressions(resources, letElement) { + let expressions = []; let attributes = letElement.attributes; /**@type {Attr} */ let attr; @@ -104,24 +104,31 @@ export class TemplatingBindingLanguage extends BindingLanguage { let attrName; let attrValue; let command; + let toBindingContextAttr = this.toBindingContextAttr; + let toBindingContext = letElement.hasAttribute(toBindingContextAttr); for (let i = 0, ii = attributes.length; ii > i; ++i) { attr = attributes[i]; attrName = attr.name; attrValue = attr.nodeValue; parts = attrName.split('.'); + if (attrName === toBindingContextAttr) { + continue; + } + if (parts.length === 2) { command = parts[1]; if (command !== 'bind') { LogManager.getLogger('templating-binding-language') - .warn(`Detected invalid let command. Expected "${part[0]}.bind", given "${attrName}"`); + .warn(`Detected invalid let command. Expected "${parts[0]}.bind", given "${attrName}"`); continue; } - existingLetExpressions.push(new LetExpression( + expressions.push(new LetExpression( this.observerLocator, camelCase(parts[0]), this.parser.parse(attrValue), - resources.lookupFunctions + resources.lookupFunctions, + toBindingContext )); } else { attrName = camelCase(attrName); @@ -131,23 +138,25 @@ export class TemplatingBindingLanguage extends BindingLanguage { .warn(`Detected string literal in let bindings. Did you mean "${ attrName }.bind=${ attrValue }" or "${ attrName }=\${${ attrValue }}" ?`); } if (parts) { - existingLetExpressions.push(new LetInterpolationBindingExpression( + expressions.push(new LetInterpolationBindingExpression( this.observerLocator, attrName, parts, - resources.lookupFunctions + resources.lookupFunctions, + toBindingContext )); } else { - existingLetExpressions.push(new LetExpression( + expressions.push(new LetExpression( this.observerLocator, attrName, new LiteralString(attrValue), - resources.lookupFunctions + resources.lookupFunctions, + toBindingContext )); } } } - return existingLetExpressions; + return expressions; } inspectTextContent(resources, value) { diff --git a/src/let-expression.js b/src/let-expression.js index 1d18761..0c64834 100644 --- a/src/let-expression.js +++ b/src/let-expression.js @@ -1,7 +1,6 @@ import { connectable, enqueueBindingConnect, - createOverrideContext, sourceContext } from 'aurelia-binding'; @@ -35,12 +34,11 @@ export class LetExpression { @connectable() export class Let { /** - * - * @param {ObserverLocator} observerLocator - * @param {Expression} sourceExpression - * @param {Function | Element} target + * @param {ObserverLocator} observerLocator + * @param {Expression} sourceExpression + * @param {Function | Element} target * @param {string} targetProperty - * @param {*} lookupFunctions + * @param {*} lookupFunctions * @param {boolean} toBindingContext indicates let binding result should be assigned to binding context */ constructor(observerLocator, sourceExpression, targetProperty, lookupFunctions, toBindingContext) { @@ -53,7 +51,7 @@ export class Let { this.toBindingContext = toBindingContext; } - updateSource() { + updateTarget() { const value = this.sourceExpression.evaluate(this.source, this.lookupFunctions); this.target[this.targetProperty] = value; } @@ -63,7 +61,7 @@ export class Let { return; } if (context === sourceContext) { - this.updateSource(); + this.updateTarget(); return; } throw new Error(`Unexpected call context ${context}`); @@ -108,7 +106,7 @@ export class Let { if (!this.isBound) { return; } - this.updateSource(); + this.updateTarget(); this.sourceExpression.connect(this, this.source); } } diff --git a/src/let-interpolation-expression.js b/src/let-interpolation-expression.js index 6806ee9..0a211c5 100644 --- a/src/let-interpolation-expression.js +++ b/src/let-interpolation-expression.js @@ -1,5 +1,4 @@ -import {bindingMode, createOverrideContext} from 'aurelia-binding'; - +import {bindingMode} from 'aurelia-binding'; import { InterpolationBinding, ChildInterpolationBinding @@ -61,7 +60,7 @@ export class LetInterpolationBinding { } this.isBound = true; - this.source = source + this.source = source; this.target = this.toBindingContext ? source.bindingContext : source.overrideContext; this.interpolationBinding = this.createInterpolationBinding(); diff --git a/test/binding-language.spec.js b/test/binding-language.spec.js index ef4d552..96d173b 100644 --- a/test/binding-language.spec.js +++ b/test/binding-language.spec.js @@ -2,6 +2,7 @@ import './setup'; import {TemplatingBindingLanguage} from '../src/binding-language'; import {Container} from 'aurelia-dependency-injection'; import {NameExpression} from 'aurelia-binding'; +import {Logger} from 'aurelia-logging'; import { LetExpression, @@ -17,7 +18,7 @@ describe('BindingLanguage', () => { /**@type {TemplatingBindingLanguage} */ let language; - beforeAll(() => { + beforeEach(() => { language = new Container().get(TemplatingBindingLanguage); }); @@ -47,19 +48,46 @@ describe('BindingLanguage', () => { resources = { lookupFunctions: {} }; }); - it('creates correct let expressions', () => { + it('works with .bind command', () => { + let el = div(); + el.setAttribute('foo.bind', 'bar'); + const expressions = language.createLetExpressions(resources, el); + expect(expressions[0] instanceof LetExpression).toBe(true); + }); + + it('warns when binding command is not bind', () => { + const loggerSpy = spyOn(Logger.prototype, 'warn').and.callThrough(); + let callCount = 0; + ['one-way', 'two-way', 'one-time', 'from-view'].forEach(cmd => { + let el = div(); + el.setAttribute(`foo.${cmd}`, 'bar'); + language.createLetExpressions(resources, el); + expect(loggerSpy.calls.count()).toBe(++callCount, `It should have had been called ${callCount} times`); + }); + }); - let el1 = div(); - el1.setAttribute('foo.bind', 'bar'); - expect(language.createLetExpressions(resources, el1)[0] instanceof LetExpression).toBe(true); + it('works with interpolation', () => { + let el = div(); + el.setAttribute('foo', '${bar}'); + const expressions = language.createLetExpressions(resources, el); + expect(expressions.length).toBe(1, 'It should have had 1 instruction'); + expect(expressions[0] instanceof LetInterpolationBindingExpression).toBe(true); + }); + + it('creates correct let expressions', () => { + let el = div(); + el.setAttribute('foo', 'bar'); + expect(language.createLetExpressions(resources, el)[0] instanceof LetExpression).toBe(true); + }); - let el2 = div(); - el2.setAttribute('foo', '${bar}'); - expect(language.createLetExpressions(resources, el2)[0] instanceof LetInterpolationBindingExpression).toBe(true); + it('understands to-binding-context', () => { + let el = div(); + el.setAttribute('foo.bind', 'bar'); + el.setAttribute(language.toBindingContextAttr, ''); - let el3 = div(); - el3.setAttribute('foo', 'bar'); - expect(language.createLetExpressions(resources, el3)[0] instanceof LetExpression).toBe(true); + const expressions = language.createLetExpressions(resources, el); + expect(expressions.length).toBe(1, 'It should have not created expression from to-binding-context'); + expect(expressions[0].toBindingContext).toBe(true); }); function div() {