feat(core): view engine - add `WrappedValue` support (#14216)
Part of #14013
This commit is contained in:
parent
1bc5368ea0
commit
08ff67ea11
|
@ -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;
|
||||
|
|
|
@ -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<any, string>();
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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');
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue