From 61906d3517428b6d52d3284b8d26d1a46e01dad7 Mon Sep 17 00:00:00 2001 From: James Davies Date: Mon, 3 Jun 2013 13:04:12 +1000 Subject: [PATCH] fix($parse): unwrap promise when setting a field This fixes an inconsistency where you can't call the setter function when the expression resolves to a top level field name on a promise. Setting a field on an unresolved promise will throw an exception. (This shouldn't really happen in your template/js code and points to a programming error.) Closes #1827 --- src/ng/parse.js | 11 ++++++ test/ng/parseSpec.js | 80 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/src/ng/parse.js b/src/ng/parse.js index 9660c76f710b..7c244bd86571 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -766,6 +766,17 @@ function setter(obj, path, setValue, fullExp) { obj[key] = propertyObj; } obj = propertyObj; + if (obj.then) { + if (!("$$v" in obj)) { + (function(promise) { + promise.then(function(val) { promise.$$v = val; }); } + )(obj); + } + if (obj.$$v === undefined) { + obj.$$v = {}; + } + obj = obj.$$v; + } } key = ensureSafeMemberName(element.shift(), fullExp); obj[key] = setValue; diff --git a/test/ng/parseSpec.js b/test/ng/parseSpec.js index 62425f125e08..568a3b15bad0 100644 --- a/test/ng/parseSpec.js +++ b/test/ng/parseSpec.js @@ -845,6 +845,86 @@ describe('parser', function() { scope.$digest(); expect(scope.$eval('greeting')).toBe(undefined); }); + + + describe('assignment into promises', function() { + // This behavior is analogous to assignments to non-promise values + // that are lazily set on the scope. + it('should evaluate a resolved object promise and set its value', inject(function($parse) { + scope.person = promise; + deferred.resolve({'name': 'Bill Gates'}); + + var getter = $parse('person.name'); + expect(getter(scope)).toBe(undefined); + + scope.$digest(); + expect(getter(scope)).toBe('Bill Gates'); + getter.assign(scope, 'Warren Buffet'); + expect(getter(scope)).toBe('Warren Buffet'); + })); + + + it('should evaluate a resolved primitive type promise and set its value', inject(function($parse) { + scope.greeting = promise; + deferred.resolve('Salut!'); + + var getter = $parse('greeting'); + expect(getter(scope)).toBe(undefined); + + scope.$digest(); + expect(getter(scope)).toBe('Salut!'); + + getter.assign(scope, 'Bonjour'); + expect(getter(scope)).toBe('Bonjour'); + })); + + + it('should evaluate an unresolved promise and set and remember its value', inject(function($parse) { + scope.person = promise; + + var getter = $parse('person.name'); + expect(getter(scope)).toBe(undefined); + + scope.$digest(); + expect(getter(scope)).toBe(undefined); + + getter.assign(scope, 'Bonjour'); + scope.$digest(); + + expect(getter(scope)).toBe('Bonjour'); + + var c1Getter = $parse('person.A.B.C1'); + scope.$digest(); + expect(c1Getter(scope)).toBe(undefined); + c1Getter.assign(scope, 'c1_value'); + scope.$digest(); + expect(c1Getter(scope)).toBe('c1_value'); + + // Set another property on the person.A.B + var c2Getter = $parse('person.A.B.C2'); + scope.$digest(); + expect(c2Getter(scope)).toBe(undefined); + c2Getter.assign(scope, 'c2_value'); + scope.$digest(); + expect(c2Getter(scope)).toBe('c2_value'); + + // c1 should be unchanged. + expect($parse('person.A')(scope)).toEqual( + {B: {C1: 'c1_value', C2: 'c2_value'}}); + })); + + + it('should evaluate a resolved promise and overwrite the previous set value in the absense of the getter', + inject(function($parse) { + scope.person = promise; + var c1Getter = $parse('person.A.B.C1'); + c1Getter.assign(scope, 'c1_value'); + // resolving the promise should update the tree. + deferred.resolve({A: {B: {C1: 'resolved_c1'}}}); + scope.$digest(); + expect(c1Getter(scope)).toEqual('resolved_c1'); + })); + }); }); describe('dereferencing', function() {