diff --git a/modules/@angular/core/src/view/element.ts b/modules/@angular/core/src/view/element.ts index de1da340f6..b9b20812d1 100644 --- a/modules/@angular/core/src/view/element.ts +++ b/modules/@angular/core/src/view/element.ts @@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref'; import {SecurityContext} from '../security'; import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, ViewData, ViewDefinition, ViewFlags, asElementData} from './types'; -import {checkAndUpdateBinding, entryAction, setBindingDebugInfo, setCurrentNode, sliceErrorStack} from './util'; +import {checkAndUpdateBinding, entryAction, setBindingDebugInfo, setCurrentNode, sliceErrorStack, unwrapValue} from './util'; export function anchorDef( flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, @@ -248,6 +248,7 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu if (!checkAndUpdateBinding(view, def, bindingIdx, value)) { return; } + value = unwrapValue(value); const binding = def.bindings[bindingIdx]; const name = binding.name; diff --git a/modules/@angular/core/src/view/provider.ts b/modules/@angular/core/src/view/provider.ts index 0fd1adfe66..bb03884f96 100644 --- a/modules/@angular/core/src/view/provider.ts +++ b/modules/@angular/core/src/view/provider.ts @@ -17,7 +17,7 @@ import {Renderer} from '../render/api'; import {queryDef} from './query'; import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderOutputDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, ViewDefinition, ViewFlags, asElementData, asProviderData} from './types'; -import {checkAndUpdateBinding, checkAndUpdateBindingWithChange, entryAction, setBindingDebugInfo, setCurrentNode} from './util'; +import {checkAndUpdateBinding, checkAndUpdateBindingWithChange, entryAction, setBindingDebugInfo, setCurrentNode, unwrapValue} from './util'; const _tokenKeyCache = new Map(); @@ -278,6 +278,7 @@ function checkAndUpdateProp( changed = checkAndUpdateBinding(view, def, bindingIdx, value); } if (changed) { + value = unwrapValue(value); const binding = def.bindings[bindingIdx]; const propName = binding.name; // Note: This is still safe with Closure Compiler as diff --git a/modules/@angular/core/src/view/pure_expression.ts b/modules/@angular/core/src/view/pure_expression.ts index bca2986675..8d41caee5d 100644 --- a/modules/@angular/core/src/view/pure_expression.ts +++ b/modules/@angular/core/src/view/pure_expression.ts @@ -8,7 +8,7 @@ import {resolveDep, tokenKey} from './provider'; import {BindingDef, BindingType, DepDef, DepFlags, NodeData, NodeDef, NodeType, ProviderData, PureExpressionData, PureExpressionType, ViewData, asPureExpressionData} from './types'; -import {checkAndUpdateBinding} from './util'; +import {checkAndUpdateBinding, unwrapValue} from './util'; export function purePipeDef(pipeToken: any, argCount: number): NodeDef { return _pureExpressionDef( @@ -99,6 +99,17 @@ export function checkAndUpdatePureExpressionInline( } if (changed) { + v0 = unwrapValue(v0); + v1 = unwrapValue(v1); + v2 = unwrapValue(v2); + v3 = unwrapValue(v3); + v4 = unwrapValue(v4); + v5 = unwrapValue(v5); + v6 = unwrapValue(v6); + v7 = unwrapValue(v7); + v8 = unwrapValue(v8); + v9 = unwrapValue(v9); + const data = asPureExpressionData(view, def.index); let value: any; switch (def.pureExpression.type) { @@ -121,7 +132,7 @@ export function checkAndUpdatePureExpressionInline( case 4: value[3] = v3; case 3: - value[2] = v2; + value[3] = v2; case 2: value[1] = v1; case 1: @@ -208,16 +219,23 @@ export function checkAndUpdatePureExpressionDynamic(view: ViewData, def: NodeDef let value: any; switch (def.pureExpression.type) { case PureExpressionType.Array: - value = values; + value = new Array(values.length); + for (let i = 0; i < values.length; i++) { + value[i] = unwrapValue(values[i]); + } break; case PureExpressionType.Object: value = {}; for (let i = 0; i < values.length; i++) { - value[bindings[i].name] = values[i]; + value[bindings[i].name] = unwrapValue(values[i]); } break; case PureExpressionType.Pipe: - value = data.pipe.transform(values[0], ...values.slice(1)); + const params = new Array(values.length); + for (let i = 0; i < values.length; i++) { + params[i] = unwrapValue(values[i]); + } + value = data.pipe.transform(params[0], ...params.slice(1)); break; } data.value = value; diff --git a/modules/@angular/core/src/view/text.ts b/modules/@angular/core/src/view/text.ts index f0823a0813..df273216e4 100644 --- a/modules/@angular/core/src/view/text.ts +++ b/modules/@angular/core/src/view/text.ts @@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref'; import {looseIdentical} from '../facade/lang'; import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types'; -import {checkAndUpdateBinding, sliceErrorStack} from './util'; +import {checkAndUpdateBinding, sliceErrorStack, unwrapValue} from './util'; export function textDef(ngContentIndex: number, constants: string[]): NodeDef { // skip the call to sliceErrorStack itself + the call to this function. @@ -156,6 +156,7 @@ export function checkAndUpdateTextDynamic(view: ViewData, def: NodeDef, values: } function _addInterpolationPart(value: any, binding: BindingDef): string { + value = unwrapValue(value); const valueStr = value != null ? value.toString() : ''; return valueStr + binding.suffix; } diff --git a/modules/@angular/core/src/view/util.ts b/modules/@angular/core/src/view/util.ts index fca492320b..2032eaa043 100644 --- a/modules/@angular/core/src/view/util.ts +++ b/modules/@angular/core/src/view/util.ts @@ -7,7 +7,7 @@ */ import {isDevMode} from '../application_ref'; -import {devModeEqual} from '../change_detection/change_detection'; +import {WrappedValue, devModeEqual} from '../change_detection/change_detection'; import {SimpleChange} from '../change_detection/change_detection_util'; import {looseIdentical} from '../facade/lang'; import {Renderer} from '../render/api'; @@ -63,6 +63,13 @@ export function checkAndUpdateBindingWithChange( return null; } +export function unwrapValue(value: any): any { + if (value instanceof WrappedValue) { + value = value.wrapped; + } + return value; +} + export function declaredViewContainer(view: ViewData): ElementData { if (view.parent) { const parentView = view.parent; diff --git a/modules/@angular/core/test/view/element_spec.ts b/modules/@angular/core/test/view/element_spec.ts index 9bedcb6b85..1bdf58090a 100644 --- a/modules/@angular/core/test/view/element_spec.ts +++ b/modules/@angular/core/test/view/element_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, getDebugNode} from '@angular/core'; +import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; import {BindingType, DebugContext, DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, destroyView, elementDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -197,6 +197,43 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { }); }); + describe('general binding behavior', () => { + INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { + it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + let bindingValue: any; + + const {view, rootNodes} = createAndGetRootNodes(compViewDef( + [ + elementDef( + NodeFlags.None, null, null, 0, 'input', null, + [ + [BindingType.ElementProperty, 'title', SecurityContext.NONE], + ]), + ], + (view) => { + setCurrentNode(view, 0); + checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + })); + + const setterSpy = jasmine.createSpy('set'); + Object.defineProperty(rootNodes[0], 'title', {set: setterSpy}); + + bindingValue = 'v1'; + checkAndUpdateView(view); + expect(setterSpy).toHaveBeenCalledWith('v1'); + + setterSpy.calls.reset(); + checkAndUpdateView(view); + expect(setterSpy).not.toHaveBeenCalled(); + + setterSpy.calls.reset(); + bindingValue = WrappedValue.wrap('v1'); + checkAndUpdateView(view); + expect(setterSpy).toHaveBeenCalledWith('v1'); + }); + }); + }); + if (getDOM().supportsDOMEvents()) { describe('listen to DOM events', () => { let removeNodes: Node[]; diff --git a/modules/@angular/core/test/view/provider_spec.ts b/modules/@angular/core/test/view/provider_spec.ts index 7b8bf8fbfd..a6ba5f0eb1 100644 --- a/modules/@angular/core/test/view/provider_spec.ts +++ b/modules/@angular/core/test/view/provider_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, ElementRef, EventEmitter, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation, getDebugNode} from '@angular/core'; +import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, ElementRef, EventEmitter, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; import {BindingType, DebugContext, DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, destroyView, elementDef, providerDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -234,6 +234,39 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { expect(getDOM().getAttribute(el, 'ng-reflect-a')).toBe('v1'); } }); + + it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + let bindingValue: any; + let setterSpy = jasmine.createSpy('set'); + + class SomeService { + set a(value: any) { setterSpy(value); } + } + + const {view, rootNodes} = createAndGetRootNodes(compViewDef( + [ + elementDef(NodeFlags.None, null, null, 1, 'span'), + providerDef(NodeFlags.None, null, 0, SomeService, [], {a: [0, 'a']}) + ], + (view) => { + setCurrentNode(view, 1); + checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + })); + + bindingValue = 'v1'; + checkAndUpdateView(view); + expect(setterSpy).toHaveBeenCalledWith('v1'); + + setterSpy.calls.reset(); + checkAndUpdateView(view); + expect(setterSpy).not.toHaveBeenCalled(); + + setterSpy.calls.reset(); + bindingValue = WrappedValue.wrap('v1'); + checkAndUpdateView(view); + expect(setterSpy).toHaveBeenCalledWith('v1'); + + }); }); }); diff --git a/modules/@angular/core/test/view/pure_expression_spec.ts b/modules/@angular/core/test/view/pure_expression_spec.ts index 8f962f0a28..ebf7b01344 100644 --- a/modules/@angular/core/test/view/pure_expression_spec.ts +++ b/modules/@angular/core/test/view/pure_expression_spec.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core'; -import {DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, elementDef, providerDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; +import {PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue} from '@angular/core'; +import {DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, asPureExpressionData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, elementDef, providerDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {INLINE_DYNAMIC_VALUES, InlineDynamic, checkNodeInlineOrDynamic} from './helper'; @@ -39,113 +39,209 @@ export function main() { data: any; } - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should support pure arrays in ${InlineDynamic[inlineDynamic]} bindings`, () => { - let values: any[]; + describe('pure arrays', () => { - const {view, rootNodes} = createAndGetRootNodes(compViewDef( - [ - elementDef(NodeFlags.None, null, null, 2, 'span'), pureArrayDef(2), - providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']}) - ], - (view) => { - setCurrentNode(view, 1); - const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values); - setCurrentNode(view, 2); - checkNodeInlineOrDynamic(inlineDynamic, [pureValue]); - })); - const service = asProviderData(view, 2).instance; + INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { + it(`should support ${InlineDynamic[inlineDynamic]} bindings`, () => { + let values: any[]; - values = [1, 2]; - checkAndUpdateView(view); - const arr0 = service.data; - expect(arr0).toEqual([1, 2]); + const {view, rootNodes} = createAndGetRootNodes(compViewDef( + [ + elementDef(NodeFlags.None, null, null, 2, 'span'), pureArrayDef(2), + providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']}) + ], + (view) => { + setCurrentNode(view, 1); + const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values); + setCurrentNode(view, 2); + checkNodeInlineOrDynamic(inlineDynamic, [pureValue]); + })); + const service = asProviderData(view, 2).instance; - // instance should not change - // if the values don't change - checkAndUpdateView(view); - expect(service.data).toBe(arr0); + values = [1, 2]; + checkAndUpdateView(view); + const arr0 = service.data; + expect(arr0).toEqual([1, 2]); - values = [3, 2]; - checkAndUpdateView(view); - const arr1 = service.data; - expect(arr1).not.toBe(arr0); - expect(arr1).toEqual([3, 2]); + // instance should not change + // if the values don't change + checkAndUpdateView(view); + expect(service.data).toBe(arr0); + + values = [3, 2]; + checkAndUpdateView(view); + const arr1 = service.data; + expect(arr1).not.toBe(arr0); + expect(arr1).toEqual([3, 2]); + }); + + it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + let bindingValue: any; + const {view, rootNodes} = createAndGetRootNodes(compViewDef( + [ + elementDef(NodeFlags.None, null, null, 1, 'span'), + pureArrayDef(1), + ], + (view) => { + setCurrentNode(view, 1); + checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + })); + + const exprData = asPureExpressionData(view, 1); + + bindingValue = 'v1'; + checkAndUpdateView(view); + const v1Arr = exprData.value; + expect(v1Arr).toEqual(['v1']); + + checkAndUpdateView(view); + expect(exprData.value).toBe(v1Arr); + + bindingValue = WrappedValue.wrap('v1'); + checkAndUpdateView(view); + expect(exprData.value).not.toBe(v1Arr); + expect(exprData.value).toEqual(['v1']); + }); + }); + + }); + + describe('pure objects', () => { + INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { + it(`should support ${InlineDynamic[inlineDynamic]} bindings`, () => { + let values: any[]; + + const {view, rootNodes} = createAndGetRootNodes(compViewDef( + [ + elementDef(NodeFlags.None, null, null, 2, 'span'), pureObjectDef(['a', 'b']), + providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']}) + ], + (view) => { + setCurrentNode(view, 1); + const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values); + setCurrentNode(view, 2); + checkNodeInlineOrDynamic(inlineDynamic, [pureValue]); + })); + const service = asProviderData(view, 2).instance; + + values = [1, 2]; + checkAndUpdateView(view); + const obj0 = service.data; + expect(obj0).toEqual({a: 1, b: 2}); + + // instance should not change + // if the values don't change + checkAndUpdateView(view); + expect(service.data).toBe(obj0); + + values = [3, 2]; + checkAndUpdateView(view); + const obj1 = service.data; + expect(obj1).not.toBe(obj0); + expect(obj1).toEqual({a: 3, b: 2}); + }); + + it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + let bindingValue: any; + const {view, rootNodes} = createAndGetRootNodes(compViewDef( + [ + elementDef(NodeFlags.None, null, null, 1, 'span'), + pureObjectDef(['a']), + ], + (view) => { + setCurrentNode(view, 1); + checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + })); + + const exprData = asPureExpressionData(view, 1); + + bindingValue = 'v1'; + checkAndUpdateView(view); + const v1Obj = exprData.value; + expect(v1Obj).toEqual({'a': 'v1'}); + + checkAndUpdateView(view); + expect(exprData.value).toBe(v1Obj); + + bindingValue = WrappedValue.wrap('v1'); + checkAndUpdateView(view); + expect(exprData.value).not.toBe(v1Obj); + expect(exprData.value).toEqual({'a': 'v1'}); + }); }); }); - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should support pure objects in ${InlineDynamic[inlineDynamic]} bindings`, () => { - let values: any[]; + describe('pure pipes', () => { + INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { + it(`should support ${InlineDynamic[inlineDynamic]} bindings`, () => { + class SomePipe implements PipeTransform { + transform(v1: any, v2: any) { return [v1 + 10, v2 + 20]; } + } - const {view, rootNodes} = createAndGetRootNodes(compViewDef( - [ - elementDef(NodeFlags.None, null, null, 2, 'span'), pureObjectDef(['a', 'b']), - providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']}) - ], - (view) => { - setCurrentNode(view, 1); - const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values); - setCurrentNode(view, 2); - checkNodeInlineOrDynamic(inlineDynamic, [pureValue]); - })); - const service = asProviderData(view, 2).instance; + let values: any[]; - values = [1, 2]; - checkAndUpdateView(view); - const obj0 = service.data; - expect(obj0).toEqual({a: 1, b: 2}); + const {view, rootNodes} = createAndGetRootNodes(compViewDef( + [ + elementDef(NodeFlags.None, null, null, 3, 'span'), + providerDef(NodeFlags.None, null, 0, SomePipe, []), purePipeDef(SomePipe, 2), + providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']}) + ], + (view) => { + setCurrentNode(view, 2); + const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values); + setCurrentNode(view, 3); + checkNodeInlineOrDynamic(inlineDynamic, [pureValue]); + })); + const service = asProviderData(view, 3).instance; - // instance should not change - // if the values don't change - checkAndUpdateView(view); - expect(service.data).toBe(obj0); + values = [1, 2]; + checkAndUpdateView(view); + const obj0 = service.data; + expect(obj0).toEqual([11, 22]); - values = [3, 2]; - checkAndUpdateView(view); - const obj1 = service.data; - expect(obj1).not.toBe(obj0); - expect(obj1).toEqual({a: 3, b: 2}); - }); - }); + // instance should not change + // if the values don't change + checkAndUpdateView(view); + expect(service.data).toBe(obj0); - INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => { - it(`should support pure pipes in ${InlineDynamic[inlineDynamic]} bindings`, () => { - class SomePipe implements PipeTransform { - transform(v1: any, v2: any) { return [v1 + 10, v2 + 20]; } - } + values = [3, 2]; + checkAndUpdateView(view); + const obj1 = service.data; + expect(obj1).not.toBe(obj0); + expect(obj1).toEqual([13, 22]); + }); - let values: any[]; + it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + let bindingValue: any; + let transformSpy = jasmine.createSpy('transform'); - const {view, rootNodes} = createAndGetRootNodes(compViewDef( - [ - elementDef(NodeFlags.None, null, null, 3, 'span'), - providerDef(NodeFlags.None, null, 0, SomePipe, []), purePipeDef(SomePipe, 2), - providerDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']}) - ], - (view) => { - setCurrentNode(view, 2); - const pureValue = checkNodeInlineOrDynamic(inlineDynamic, values); - setCurrentNode(view, 3); - checkNodeInlineOrDynamic(inlineDynamic, [pureValue]); - })); - const service = asProviderData(view, 3).instance; + class SomePipe implements PipeTransform { + transform = transformSpy; + } - values = [1, 2]; - checkAndUpdateView(view); - const obj0 = service.data; - expect(obj0).toEqual([11, 22]); + const {view, rootNodes} = createAndGetRootNodes(compViewDef( + [ + elementDef(NodeFlags.None, null, null, 2, 'span'), + providerDef(NodeFlags.None, null, 0, SomePipe, []), + purePipeDef(SomePipe, 1), + ], + (view) => { + setCurrentNode(view, 2); + checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + })); - // instance should not change - // if the values don't change - checkAndUpdateView(view); - expect(service.data).toBe(obj0); + bindingValue = 'v1'; + checkAndUpdateView(view); + expect(transformSpy).toHaveBeenCalledWith('v1'); - values = [3, 2]; - checkAndUpdateView(view); - const obj1 = service.data; - expect(obj1).not.toBe(obj0); - expect(obj1).toEqual([13, 22]); + transformSpy.calls.reset(); + checkAndUpdateView(view); + expect(transformSpy).not.toHaveBeenCalled(); + + bindingValue = WrappedValue.wrap('v1'); + checkAndUpdateView(view); + expect(transformSpy).toHaveBeenCalledWith('v1'); + }); }); }); }); diff --git a/modules/@angular/core/test/view/text_spec.ts b/modules/@angular/core/test/view/text_spec.ts index bc346462a5..5d22d50c19 100644 --- a/modules/@angular/core/test/view/text_spec.ts +++ b/modules/@angular/core/test/view/text_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, getDebugNode} from '@angular/core'; +import {RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core'; import {DebugContext, DefaultServices, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asTextData, checkAndUpdateView, checkNoChangesView, checkNodeDynamic, checkNodeInline, createRootView, elementDef, rootRenderNodes, setCurrentNode, textDef, viewDef} from '@angular/core/src/view/index'; import {inject} from '@angular/core/testing'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -97,6 +97,35 @@ function defineTests(config: {directDom: boolean, viewFlags: number}) { const node = rootNodes[0]; expect(getDOM().getText(rootNodes[0])).toBe('0a1b2'); }); + + it(`should unwrap values with ${InlineDynamic[inlineDynamic]}`, () => { + let bindingValue: any; + + const {view, rootNodes} = createAndGetRootNodes(compViewDef( + [ + textDef(null, ['', '']), + ], + (view: ViewData) => { + setCurrentNode(view, 0); + checkNodeInlineOrDynamic(inlineDynamic, [bindingValue]); + })); + + const setterSpy = jasmine.createSpy('set'); + Object.defineProperty(rootNodes[0], 'nodeValue', {set: setterSpy}); + + bindingValue = 'v1'; + checkAndUpdateView(view); + expect(setterSpy).toHaveBeenCalledWith('v1'); + + setterSpy.calls.reset(); + checkAndUpdateView(view); + expect(setterSpy).not.toHaveBeenCalled(); + + setterSpy.calls.reset(); + bindingValue = WrappedValue.wrap('v1'); + checkAndUpdateView(view); + expect(setterSpy).toHaveBeenCalledWith('v1'); + }); }); });