diff --git a/integration/test/ParseACLTest.js b/integration/test/ParseACLTest.js index d1340cfe7..32f8e9442 100644 --- a/integration/test/ParseACLTest.js +++ b/integration/test/ParseACLTest.js @@ -10,7 +10,9 @@ describe('Parse.ACL', () => { it('acl must be valid', () => { const user = new Parse.User(); - assert.equal(user.setACL(`Ceci n'est pas un ACL.`), false); + expect(() => user.setACL(`Ceci n'est pas un ACL.`)).toThrow( + new Parse.Error(Parse.Error.OTHER_CAUSE, 'ACL must be a Parse ACL.') + ); }); it('can refresh object with acl', async () => { diff --git a/integration/test/ParseObjectTest.js b/integration/test/ParseObjectTest.js index e289923ad..05977687c 100644 --- a/integration/test/ParseObjectTest.js +++ b/integration/test/ParseObjectTest.js @@ -546,24 +546,20 @@ describe('Parse Object', () => { }); }); - it('cannot create invalid key names', done => { + it('cannot create invalid key names', async () => { + const error = new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: "foo^bar"`); const item = new Parse.Object('Item'); - assert(!item.set({ 'foo^bar': 'baz' })); - item.save({ 'foo^bar': 'baz' }).catch(e => { - assert.equal(e.code, Parse.Error.INVALID_KEY_NAME); - done(); - }); + expect(() => { + item.set({ 'foo^bar': 'baz' }); + }).toThrow(error); + await expectAsync(item.save({ 'foo^bar': 'baz' })).toBeRejectedWith(error); }); it('cannot use invalid key names in multiple sets', () => { const item = new Parse.Object('Item'); - assert( - !item.set({ - foobar: 'baz', - 'foo^bar': 'baz', - }) - ); - assert(!item.get('foobar')); + expect(() => { + item.set({ foobar: 'baz', 'foo^bar': 'baz' }); + }).toThrow(new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: "foo^bar"`)); }); it('can unset fields', done => { @@ -1135,12 +1131,43 @@ describe('Parse Object', () => { parent.set('children', [child1, child2]); parent.set('bastard', child3); - expect(parent.save).toThrow(); - let results = await new Parse.Query(Child).find(); - assert.equal(results.length, 0); - await parent.save(null, { cascadeSave: true }); - results = await new Parse.Query(Child).find(); + const results = await new Parse.Query(Child).find(); + assert.equal(results.length, 3); + + parent.set('dead', true); + child1.set('dead', true); + await parent.save(null); + const rob = await new Parse.Query(Child).equalTo('name', 'rob').first(); + expect(rob.get('dead')).toBe(true); + + parent.set('lastname', 'stark'); + child3.set('lastname', 'stark'); + await parent.save(null, { cascadeSave: false }); + const john = await new Parse.Query(Child).doesNotExist('lastname').first(); + expect(john.get('lastname')).toBeUndefined(); + }); + + it('can skip cascade (default true) saving as per request', async () => { + const Parent = Parse.Object.extend('Parent'); + const Child = Parse.Object.extend('Child'); + + const parent = new Parent(); + const child1 = new Child(); + const child2 = new Child(); + const child3 = new Child(); + + child1.set('name', 'rob'); + child2.set('name', 'sansa'); + child3.set('name', 'john'); + parent.set('children', [child1, child2]); + parent.set('bastard', child3); + + // cascadeSave option default true + await parent.save(null, { + /* cascadeSave: true */ + }); + const results = await new Parse.Query(Child).find(); assert.equal(results.length, 3); parent.set('dead', true); diff --git a/src/ParseInstallation.ts b/src/ParseInstallation.ts index fa69e2929..465d59175 100644 --- a/src/ParseInstallation.ts +++ b/src/ParseInstallation.ts @@ -40,7 +40,9 @@ class ParseInstallation extends ParseObject { constructor(attributes?: AttributeMap) { super('_Installation'); if (attributes && typeof attributes === 'object') { - if (!this.set(attributes)) { + try { + this.set(attributes || {}); + } catch (_) { throw new Error("Can't create an invalid Installation"); } } diff --git a/src/ParseObject.ts b/src/ParseObject.ts index bd3d6f81e..c53930757 100644 --- a/src/ParseObject.ts +++ b/src/ParseObject.ts @@ -136,8 +136,12 @@ class ParseObject { options = attributes as any; } } - if (toSet && !this.set(toSet, options)) { - throw new Error("Can't create an invalid Parse Object"); + if (toSet) { + try { + this.set(toSet, options); + } catch (_) { + throw new Error("Can't create an invalid Parse Object"); + } } } @@ -733,9 +737,9 @@ class ParseObject { * @param {(string|object)} value The value to give it. * @param {object} options A set of options for the set. * The only supported option is error. - * @returns {(ParseObject|boolean)} true if the set succeeded. + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - set(key: any, value?: any, options?: any): ParseObject | boolean { + set(key: any, value?: any, options?: any): this { let changes = {}; const newOps = {}; if (key && typeof key === 'object') { @@ -804,12 +808,9 @@ class ParseObject { // Validate changes if (!options.ignoreValidation) { - const validation = this.validate(newValues); - if (validation) { - if (typeof options.error === 'function') { - options.error(this, validation); - } - return false; + const validationError = this.validate(newValues); + if (validationError) { + throw validationError; } } @@ -831,9 +832,9 @@ class ParseObject { * * @param {string} attr The string name of an attribute. * @param options - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - unset(attr: string, options?: { [opt: string]: any }): ParseObject | boolean { + unset(attr: string, options?: { [opt: string]: any }): this { options = options || {}; options.unset = true; return this.set(attr, null, options); @@ -845,9 +846,9 @@ class ParseObject { * * @param attr {String} The key. * @param amount {Number} The amount to increment by (optional). - * @returns {(ParseObject|boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - increment(attr: string, amount?: number): ParseObject | boolean { + increment(attr: string, amount?: number): this { if (typeof amount === 'undefined') { amount = 1; } @@ -863,9 +864,9 @@ class ParseObject { * * @param attr {String} The key. * @param amount {Number} The amount to decrement by (optional). - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - decrement(attr: string, amount?: number): ParseObject | boolean { + decrement(attr: string, amount?: number): this { if (typeof amount === 'undefined') { amount = 1; } @@ -881,9 +882,9 @@ class ParseObject { * * @param attr {String} The key. * @param item {} The item to add. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - add(attr: string, item: any): ParseObject | boolean { + add(attr: string, item: any): this { return this.set(attr, new AddOp([item])); } @@ -893,9 +894,9 @@ class ParseObject { * * @param attr {String} The key. * @param items {Object[]} The items to add. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - addAll(attr: string, items: Array): ParseObject | boolean { + addAll(attr: string, items: Array): this { return this.set(attr, new AddOp(items)); } @@ -906,9 +907,9 @@ class ParseObject { * * @param attr {String} The key. * @param item {} The object to add. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - addUnique(attr: string, item: any): ParseObject | boolean { + addUnique(attr: string, item: any): this { return this.set(attr, new AddUniqueOp([item])); } @@ -919,9 +920,9 @@ class ParseObject { * * @param attr {String} The key. * @param items {Object[]} The objects to add. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - addAllUnique(attr: string, items: Array): ParseObject | boolean { + addAllUnique(attr: string, items: Array): this { return this.set(attr, new AddUniqueOp(items)); } @@ -931,9 +932,9 @@ class ParseObject { * * @param attr {String} The key. * @param item {} The object to remove. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - remove(attr: string, item: any): ParseObject | boolean { + remove(attr: string, item: any): this { return this.set(attr, new RemoveOp([item])); } @@ -943,9 +944,9 @@ class ParseObject { * * @param attr {String} The key. * @param items {Object[]} The object to remove. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - removeAll(attr: string, items: Array): ParseObject | boolean { + removeAll(attr: string, items: Array): this { return this.set(attr, new RemoveOp(items)); } @@ -1099,7 +1100,7 @@ class ParseObject { } for (const key in attrs) { if (!/^[A-Za-z][0-9A-Za-z_.]*$/.test(key)) { - return new ParseError(ParseError.INVALID_KEY_NAME); + return new ParseError(ParseError.INVALID_KEY_NAME, `Invalid key name: "${key}"`); } } return false; @@ -1124,10 +1125,10 @@ class ParseObject { * * @param {Parse.ACL} acl An instance of Parse.ACL. * @param {object} options - * @returns {(ParseObject | boolean)} Whether the set passed validation. + * @returns {Parse.Object} Returns the object, so you can chain this call. * @see Parse.Object#set */ - setACL(acl: ParseACL, options?: any): ParseObject | boolean { + setACL(acl: ParseACL, options?: any): this { return this.set('ACL', acl, options); } @@ -1154,9 +1155,9 @@ class ParseObject { /** * Clears all attributes on a model * - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - clear(): ParseObject | boolean { + clear(): this { const attributes = this.attributes; const erasable = {}; let readonly = ['createdAt', 'updatedAt']; @@ -1320,7 +1321,7 @@ class ParseObject { * @returns {Promise} A promise that is fulfilled when the save * completes. */ - save( + async save( arg1: undefined | string | { [attr: string]: any } | null, arg2: SaveOptions | any, arg3?: SaveOptions @@ -1337,17 +1338,9 @@ class ParseObject { attrs[arg1] = arg2; options = arg3; } - options = options || {}; if (attrs) { - let validationError; - options.error = (_, validation) => { - validationError = validation; - }; - const success = this.set(attrs, options); - if (!success) { - return Promise.reject(validationError); - } + this.set(attrs, options); } const saveOptions = ParseObject._getRequestOptions(options); const controller = CoreManager.getObjectController(); @@ -1985,7 +1978,9 @@ class ParseObject { } if (attributes && typeof attributes === 'object') { - if (!this.set(attributes || {}, options)) { + try { + this.set(attributes || {}, options); + } catch (_) { throw new Error("Can't create an invalid Parse Object"); } } diff --git a/src/ParseSession.ts b/src/ParseSession.ts index b20cb4f12..e783a305b 100644 --- a/src/ParseSession.ts +++ b/src/ParseSession.ts @@ -20,7 +20,9 @@ class ParseSession extends ParseObject { constructor(attributes?: any) { super('_Session'); if (attributes && typeof attributes === 'object') { - if (!this.set(attributes || {})) { + try { + this.set(attributes || {}); + } catch (_) { throw new Error("Can't create an invalid Session"); } } diff --git a/src/ParseUser.ts b/src/ParseUser.ts index 2cc4287a0..47be73de2 100644 --- a/src/ParseUser.ts +++ b/src/ParseUser.ts @@ -41,7 +41,9 @@ class ParseUser extends ParseObject { constructor(attributes?: AttributeMap) { super('_User'); if (attributes && typeof attributes === 'object') { - if (!this.set(attributes || {})) { + try { + this.set(attributes || {}); + } catch (_) { throw new Error("Can't create an invalid Parse User"); } } diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index 36453bb1e..565b57c96 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -965,7 +965,7 @@ describe('ParseObject', () => { o.validate({ 'invalid!key': 12, }) - ).toEqual(new ParseError(ParseError.INVALID_KEY_NAME)); + ).toEqual(new ParseError(ParseError.INVALID_KEY_NAME, `Invalid key name: "invalid!key"`)); expect( o.validate({ @@ -982,16 +982,13 @@ describe('ParseObject', () => { it('validates attributes on set()', () => { const o = new ParseObject('Listing'); - expect(o.set('ACL', 'not an acl')).toBe(false); + expect(() => { + o.set('ACL', 'not an acl'); + }).toThrow(new ParseError(ParseError.OTHER_CAUSE, 'ACL must be a Parse ACL.')); expect(o.set('ACL', { '*': { read: true, write: false } })).toBe(o); - expect(o.set('$$$', 'o_O')).toBe(false); - - o.set('$$$', 'o_O', { - error: function (obj, err) { - expect(obj).toBe(o); - expect(err.code).toBe(105); - }, - }); + expect(() => { + o.set('$$$', 'o_O'); + }).toThrow(new ParseError(ParseError.INVALID_KEY_NAME, `Invalid key name: "$$$"`)); }); it('ignores validation if ignoreValidation option is passed to set()', () => { @@ -1947,22 +1944,25 @@ describe('ParseObject', () => { await result; }); - it('will fail for a circular dependency of non-existing objects', () => { + it('will fail for a circular dependency of non-existing objects', async () => { const parent = new ParseObject('Item'); const child = new ParseObject('Item'); parent.set('child', child); child.set('parent', parent); - expect(parent.save.bind(parent)).toThrow('Cannot create a pointer to an unsaved Object.'); + await expect(parent.save()).rejects.toThrowError( + 'Cannot create a pointer to an unsaved Object.' + ); }); - it('will fail for deeper unsaved objects', () => { + it('will fail for deeper unsaved objects', async () => { const parent = new ParseObject('Item'); const child = new ParseObject('Item'); const grandchild = new ParseObject('Item'); parent.set('child', child); child.set('child', grandchild); - - expect(parent.save.bind(parent)).toThrow('Cannot create a pointer to an unsaved Object.'); + await expect(parent.save()).rejects.toThrowError( + 'Cannot create a pointer to an unsaved Object.' + ); }); it('does not mark shallow objects as dirty', () => { diff --git a/types/ParseObject.d.ts b/types/ParseObject.d.ts index e51b18858..7b49bfce2 100644 --- a/types/ParseObject.d.ts +++ b/types/ParseObject.d.ts @@ -241,59 +241,59 @@ declare class ParseObject { * @param {(string|object)} value The value to give it. * @param {object} options A set of options for the set. * The only supported option is error. - * @returns {(ParseObject|boolean)} true if the set succeeded. + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - set(key: any, value?: any, options?: any): ParseObject | boolean; + set(key: any, value?: any, options?: any): this; /** * Remove an attribute from the model. This is a noop if the attribute doesn't * exist. * * @param {string} attr The string name of an attribute. * @param options - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ unset( attr: string, options?: { [opt: string]: any; } - ): ParseObject | boolean; + ): this; /** * Atomically increments the value of the given attribute the next time the * object is saved. If no amount is specified, 1 is used by default. * * @param attr {String} The key. * @param amount {Number} The amount to increment by (optional). - * @returns {(ParseObject|boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - increment(attr: string, amount?: number): ParseObject | boolean; + increment(attr: string, amount?: number): this; /** * Atomically decrements the value of the given attribute the next time the * object is saved. If no amount is specified, 1 is used by default. * * @param attr {String} The key. * @param amount {Number} The amount to decrement by (optional). - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - decrement(attr: string, amount?: number): ParseObject | boolean; + decrement(attr: string, amount?: number): this; /** * Atomically add an object to the end of the array associated with a given * key. * * @param attr {String} The key. * @param item {} The item to add. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - add(attr: string, item: any): ParseObject | boolean; + add(attr: string, item: any): this; /** * Atomically add the objects to the end of the array associated with a given * key. * * @param attr {String} The key. * @param items {Object[]} The items to add. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - addAll(attr: string, items: Array): ParseObject | boolean; + addAll(attr: string, items: Array): this; /** * Atomically add an object to the array associated with a given key, only * if it is not already present in the array. The position of the insert is @@ -301,9 +301,9 @@ declare class ParseObject { * * @param attr {String} The key. * @param item {} The object to add. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - addUnique(attr: string, item: any): ParseObject | boolean; + addUnique(attr: string, item: any): this; /** * Atomically add the objects to the array associated with a given key, only * if it is not already present in the array. The position of the insert is @@ -311,27 +311,27 @@ declare class ParseObject { * * @param attr {String} The key. * @param items {Object[]} The objects to add. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - addAllUnique(attr: string, items: Array): ParseObject | boolean; + addAllUnique(attr: string, items: Array): this; /** * Atomically remove all instances of an object from the array associated * with a given key. * * @param attr {String} The key. * @param item {} The object to remove. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - remove(attr: string, item: any): ParseObject | boolean; + remove(attr: string, item: any): this; /** * Atomically remove all instances of the objects from the array associated * with a given key. * * @param attr {String} The key. * @param items {Object[]} The object to remove. - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - removeAll(attr: string, items: Array): ParseObject | boolean; + removeAll(attr: string, items: Array): this; /** * Returns an instance of a subclass of Parse.Op describing what kind of * modification has been performed on this field since the last time it was @@ -410,10 +410,10 @@ declare class ParseObject { * * @param {Parse.ACL} acl An instance of Parse.ACL. * @param {object} options - * @returns {(ParseObject | boolean)} Whether the set passed validation. + * @returns {Parse.Object} Returns the object, so you can chain this call. * @see Parse.Object#set */ - setACL(acl: ParseACL, options?: any): ParseObject | boolean; + setACL(acl: ParseACL, options?: any): this; /** * Clears any (or specific) changes to this object made since the last call to save() * @@ -423,9 +423,9 @@ declare class ParseObject { /** * Clears all attributes on a model * - * @returns {(ParseObject | boolean)} + * @returns {Parse.Object} Returns the object, so you can chain this call. */ - clear(): ParseObject | boolean; + clear(): this; /** * Fetch the model from the server. If the server's representation of the * model differs from its current attributes, they will be overriden. diff --git a/types/ParseUser.d.ts b/types/ParseUser.d.ts index 9a88c2131..98295d105 100644 --- a/types/ParseUser.d.ts +++ b/types/ParseUser.d.ts @@ -171,7 +171,7 @@ declare class ParseUser extends ParseObject { * @param {string} email * @returns {boolean} */ - setEmail(email: string): boolean | ParseObject; + setEmail(email: string): this; /** * Returns the session token for this user, if the user has been logged in, * or if it is the result of a query with the master key. Otherwise, returns