test(ivy): add canonical spec for object literals (#22045)

PR Close #22045
This commit is contained in:
Kara Erickson 2018-02-06 11:13:28 -08:00 committed by Miško Hevery
parent efc67ee5ef
commit b58c3527e9
3 changed files with 279 additions and 66 deletions

View File

@ -11,7 +11,7 @@ import {NO_CHANGE, bind, getTView} from './instructions';
/**
* Updates an expression in an object literal if the expression has changed.
* Updates an expression in an object or array literal if the expression has changed.
* Used in objectLiteral instructions.
*
* @param obj Object to update
@ -27,16 +27,17 @@ function updateBinding(obj: any, key: string | number, exp: any): boolean {
return false;
}
/** Updates two expressions in an object literal if they have changed. */
function updateBinding2(obj: any, key1: number, exp1: any, key2: number, exp2: any): boolean {
/** Updates two expressions in an object or array literal if they have changed. */
function updateBinding2(
obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any): boolean {
let different = updateBinding(obj, key1, exp1);
return updateBinding(obj, key2, exp2) || different;
}
/** Updates four expressions in an object literal if they have changed. */
/** Updates four expressions in an object or array literal if they have changed. */
function updateBinding4(
obj: any, key1: number, exp1: any, key2: number, exp2: any, key3: number, exp3: any,
key4: number, exp4: any): boolean {
obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any,
key3: string | number, exp3: any, key4: string | number, exp4: any): boolean {
let different = updateBinding2(obj, key1, exp1, key2, exp2);
return updateBinding2(obj, key3, exp3, key4, exp4) || different;
}
@ -62,7 +63,7 @@ function copyObject(obj: any): any {
}
/**
* Updates the expression in the given object if it has changed and returns a copy of the object.
* Updates the expression in the given object or array if it has changed and returns a copy.
* Or if the expression hasn't changed, returns NO_CHANGE.
*
* @param objIndex Index of object blueprint in objectLiterals
@ -83,8 +84,7 @@ export function objectLiteral1(objIndex: number, obj: any, key: string | number,
}
/**
* Updates the expressions in the given object if they have changed and returns a copy of the
* object.
* Updates the expressions in the given object or array if they have changed and returns a copy.
* Or if no expressions have changed, returns NO_CHANGE.
*
* @param objIndex
@ -96,14 +96,14 @@ export function objectLiteral1(objIndex: number, obj: any, key: string | number,
* @returns A copy of the array or NO_CHANGE
*/
export function objectLiteral2(
objIndex: number, obj: any, key1: number, exp1: any, key2: number, exp2: any): any {
objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number,
exp2: any): any {
obj = getMutableBlueprint(objIndex, obj);
return updateBinding2(obj, key1, exp1, key2, exp2) ? copyObject(obj) : NO_CHANGE;
}
/**
* Updates the expressions in the given object if they have changed and returns a copy of the
* object.
* Updates the expressions in the given object or array if they have changed and returns a copy.
* Or if no expressions have changed, returns NO_CHANGE.
*
* @param objIndex
@ -117,16 +117,15 @@ export function objectLiteral2(
* @returns A copy of the object or NO_CHANGE
*/
export function objectLiteral3(
objIndex: number, obj: any, key1: number, exp1: any, key2: number, exp2: any, key3: number,
exp3: any): any {
objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any,
key3: string | number, exp3: any): any {
obj = getMutableBlueprint(objIndex, obj);
let different = updateBinding2(obj, key1, exp1, key2, exp2);
return updateBinding(obj, key3, exp3) || different ? copyObject(obj) : NO_CHANGE;
}
/**
* Updates the expressions in the given object if they have changed and returns a copy of the
* object.
* Updates the expressions in the given object or array if they have changed and returns a copy.
* Or if no expressions have changed, returns NO_CHANGE.
*
* @param objIndex
@ -142,16 +141,15 @@ export function objectLiteral3(
* @returns A copy of the object or NO_CHANGE
*/
export function objectLiteral4(
objIndex: number, obj: any, key1: number, exp1: any, key2: number, exp2: any, key3: number,
exp3: any, key4: number, exp4: any): any {
objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any,
key3: string | number, exp3: any, key4: string | number, exp4: any): any {
obj = getMutableBlueprint(objIndex, obj);
return updateBinding4(obj, key1, exp1, key2, exp2, key3, exp3, key4, exp4) ? copyObject(obj) :
NO_CHANGE;
}
/**
* Updates the expressions in the given object if they have changed and returns a copy of the
* object.
* Updates the expressions in the given object or array if they have changed and returns a copy.
* Or if no expressions have changed, returns NO_CHANGE.
*
* @param objIndex
@ -169,16 +167,16 @@ export function objectLiteral4(
* @returns A copy of the object or NO_CHANGE
*/
export function objectLiteral5(
objIndex: number, obj: any, key1: number, exp1: any, key2: number, exp2: any, key3: number,
exp3: any, key4: number, exp4: any, key5: number, exp5: any): any {
objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any,
key3: string | number, exp3: any, key4: string | number, exp4: any, key5: string | number,
exp5: any): any {
obj = getMutableBlueprint(objIndex, obj);
let different = updateBinding4(obj, key1, exp1, key2, exp2, key3, exp3, key4, exp4);
return updateBinding(obj, key5, exp5) || different ? copyObject(obj) : NO_CHANGE;
}
/**
* Updates the expressions in the given object if they have changed and returns a copy of the
* object.
* Updates the expressions in the given object or array if they have changed and returns a copy.
* Or if no expressions have changed, returns NO_CHANGE.
*
* @param objIndex
@ -198,16 +196,16 @@ export function objectLiteral5(
* @returns A copy of the object or NO_CHANGE
*/
export function objectLiteral6(
objIndex: number, obj: any, key1: number, exp1: any, key2: number, exp2: any, key3: number,
exp3: any, key4: number, exp4: any, key5: number, exp5: any, key6: number, exp6: any): any {
objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any,
key3: string | number, exp3: any, key4: string | number, exp4: any, key5: string | number,
exp5: any, key6: string | number, exp6: any): any {
obj = getMutableBlueprint(objIndex, obj);
let different = updateBinding4(obj, key1, exp1, key2, exp2, key3, exp3, key4, exp4);
return updateBinding2(obj, key5, exp5, key6, exp6) || different ? copyObject(obj) : NO_CHANGE;
}
/**
* Updates the expressions in the given object if they have changed and returns a copy of the
* object.
* Updates the expressions in the given object or array if they have changed and returns a copy.
* Or if no expressions have changed, returns NO_CHANGE.
*
* @param objIndex
@ -229,9 +227,9 @@ export function objectLiteral6(
* @returns A copy of the object or NO_CHANGE
*/
export function objectLiteral7(
objIndex: number, obj: any, key1: number, exp1: any, key2: number, exp2: any, key3: number,
exp3: any, key4: number, exp4: any, key5: number, exp5: any, key6: number, exp6: any,
key7: number, exp7: any): any {
objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any,
key3: string | number, exp3: any, key4: string | number, exp4: any, key5: string | number,
exp5: any, key6: string | number, exp6: any, key7: string | number, exp7: any): any {
obj = getMutableBlueprint(objIndex, obj);
let different = updateBinding4(obj, key1, exp1, key2, exp2, key3, exp3, key4, exp4);
different = updateBinding2(obj, key5, exp5, key6, exp6) || different;
@ -239,8 +237,7 @@ export function objectLiteral7(
}
/**
* Updates the expressions in the given object if they have changed and returns a copy of the
* object.
* Updates the expressions in the given object or array if they have changed and returns a copy.
* Or if no expressions have changed, returns NO_CHANGE.
*
* @param objIndex
@ -264,9 +261,10 @@ export function objectLiteral7(
* @returns A copy of the object or NO_CHANGE
*/
export function objectLiteral8(
objIndex: number, obj: any, key1: number, exp1: any, key2: number, exp2: any, key3: number,
exp3: any, key4: number, exp4: any, key5: number, exp5: any, key6: number, exp6: any,
key7: number, exp7: any, key8: number, exp8: any): any {
objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any,
key3: string | number, exp3: any, key4: string | number, exp4: any, key5: string | number,
exp5: any, key6: string | number, exp6: any, key7: string | number, exp7: any,
key8: string | number, exp8: any): any {
obj = getMutableBlueprint(objIndex, obj);
let different = updateBinding4(obj, key1, exp1, key2, exp2, key3, exp3, key4, exp4);
return updateBinding4(obj, key5, exp5, key6, exp6, key7, exp7, key8, exp8) || different ?

View File

@ -173,7 +173,10 @@ describe('compiler specification', () => {
expect(log).toEqual(['ChildComponent', 'SomeDirective']);
});
describe('memoization', () => {
describe('value composition', () => {
it('should support array literals', () => {
@Component({
selector: 'my-comp',
template: `
@ -204,8 +207,6 @@ describe('compiler specification', () => {
});
}
it('should memoize array literals', () => {
@Component({
selector: 'my-app',
template: `
@ -241,6 +242,152 @@ describe('compiler specification', () => {
expect(e0_literal).toEqual(['Nancy', null]);
});
it('should support object literals', () => {
@Component({
selector: 'object-comp',
template: `
<p> {{ config.duration }} </p>
<p> {{ config.animation }} </p>
`
})
class ObjectComp {
config: {[key: string]: any};
static ngComponentDef = r3.defineComponent({
type: ObjectComp,
tag: 'object-comp',
factory: function ObjectComp_Factory() { return new ObjectComp(); },
template: function ObjectComp_Template(ctx: ObjectComp, cm: boolean) {
if (cm) {
r3.E(0, 'p');
r3.T(1);
r3.e();
r3.E(2, 'p');
r3.T(3);
r3.e();
}
r3.t(1, r3.b(ctx.config.duration));
r3.t(3, r3.b(ctx.config.animation));
},
inputs: {config: 'config'}
});
}
@Component({
selector: 'my-app',
template: `
<object-comp [config]="{duration: 500, animation: name}"></object-comp>
`
})
class MyApp {
name = 'slide';
// NORMATIVE
static ngComponentDef = r3.defineComponent({
type: MyApp,
tag: 'my-app',
factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(ctx: MyApp, cm: boolean) {
if (cm) {
r3.E(0, ObjectComp);
r3.e();
}
r3.p(0, 'config', r3.o1(0, e0_literal, 'animation', ctx.name));
ObjectComp.ngComponentDef.h(1, 0);
r3.r(1, 0);
}
});
// /NORMATIVE
}
// NORMATIVE
const e0_literal = {duration: 500, animation: null};
// /NORMATIVE
expect(renderComp(MyApp)).toEqual(`<object-comp><p>500</p><p>slide</p></object-comp>`);
expect(e0_literal).toEqual({duration: 500, animation: null});
});
it('should support expressions nested deeply in object/array literals', () => {
@Component({
selector: 'nested-comp',
template: `
<p> {{ config.animation }} </p>
<p> {{config.actions[0].opacity }} </p>
<p> {{config.actions[1].duration }} </p>
`
})
class NestedComp {
config: {[key: string]: any};
static ngComponentDef = r3.defineComponent({
type: NestedComp,
tag: 'nested-comp',
factory: function NestedComp_Factory() { return new NestedComp(); },
template: function NestedComp_Template(ctx: NestedComp, cm: boolean) {
if (cm) {
r3.E(0, 'p');
r3.T(1);
r3.e();
r3.E(2, 'p');
r3.T(3);
r3.e();
r3.E(4, 'p');
r3.T(5);
r3.e();
}
r3.t(1, r3.b(ctx.config.animation));
r3.t(3, r3.b(ctx.config.actions[0].opacity));
r3.t(5, r3.b(ctx.config.actions[1].duration));
},
inputs: {config: 'config'}
});
}
@Component({
selector: 'my-app',
template: `
<nested-comp [config]="{animation: name, actions: [{ opacity: 0, duration: 0}, {opacity: 1, duration: duration }]}">
</nested-comp>
`
})
class MyApp {
name = 'slide';
duration = 100;
// NORMATIVE
static ngComponentDef = r3.defineComponent({
type: MyApp,
tag: 'my-app',
factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(ctx: MyApp, cm: boolean) {
if (cm) {
r3.E(0, NestedComp);
r3.e();
}
r3.p(
0, 'config',
r3.o2(
2, e0_literal_2, 'animation', ctx.name, 'actions',
r3.o1(1, e0_literal_1, 1, r3.o1(0, e0_literal, 'duration', ctx.duration))));
NestedComp.ngComponentDef.h(1, 0);
r3.r(1, 0);
}
});
// /NORMATIVE
}
// NORMATIVE
const e0_literal = {opacity: 1, duration: null};
const c0 = {opacity: 0, duration: 0};
const e0_literal_1 = [c0, null];
const e0_literal_2 = {animation: null, actions: null};
// /NORMATIVE
expect(renderComp(MyApp))
.toEqual(`<nested-comp><p>slide</p><p>0</p><p>100</p></nested-comp>`);
});
});
it('should support content projection', () => {

View File

@ -250,4 +250,72 @@ describe('array literals', () => {
expect(e0_literal).toEqual({duration: 500, animation: null});
});
it('should support expressions nested deeply in object/array literals', () => {
let nestedComp: NestedComp;
class NestedComp {
config: {[key: string]: any};
static ngComponentDef = defineComponent({
type: NestedComp,
tag: 'nested-comp',
factory: function NestedComp_Factory() { return nestedComp = new NestedComp(); },
template: function NestedComp_Template(ctx: NestedComp, cm: boolean) {},
inputs: {config: 'config'}
});
}
/**
* <nested-comp [config]="{animation: name, actions: [{ opacity: 0, duration: 0}, {opacity: 1,
* duration: duration }]}">
* </nested-comp>
*/
function Template(ctx: any, cm: boolean) {
if (cm) {
E(0, NestedComp);
e();
}
p(0, 'config', o2(2, e0_literal_2, 'animation', ctx.name, 'actions',
o1(1, e0_literal_1, 1, o1(0, e0_literal, 'duration', ctx.duration))));
NestedComp.ngComponentDef.h(1, 0);
r(1, 0);
}
const e0_literal = {opacity: 1, duration: null};
const e0_literal_1 = [{opacity: 0, duration: 0}, null];
const e0_literal_2 = {animation: null, actions: null};
renderToHtml(Template, {name: 'slide', duration: 100});
expect(nestedComp !.config).toEqual({
animation: 'slide',
actions: [{opacity: 0, duration: 0}, {opacity: 1, duration: 100}]
});
const firstConfig = nestedComp !.config;
renderToHtml(Template, {name: 'slide', duration: 100});
expect(nestedComp !.config).toEqual({
animation: 'slide',
actions: [{opacity: 0, duration: 0}, {opacity: 1, duration: 100}]
});
expect(nestedComp !.config).toBe(firstConfig);
renderToHtml(Template, {name: 'slide', duration: 50});
expect(nestedComp !.config).toEqual({
animation: 'slide',
actions: [{opacity: 0, duration: 0}, {opacity: 1, duration: 50}]
});
expect(nestedComp !.config).not.toBe(firstConfig);
renderToHtml(Template, {name: 'tap', duration: 50});
expect(nestedComp !.config).toEqual({
animation: 'tap',
actions: [{opacity: 0, duration: 0}, {opacity: 1, duration: 50}]
});
expect(e0_literal).toEqual({opacity: 1, duration: null});
expect(e0_literal_1).toEqual([{opacity: 0, duration: 0}, null]);
expect(e0_literal_2).toEqual({animation: null, actions: null});
});
});