UNPKG

parse-mockdb

Version:
1,654 lines (1,484 loc) 55.7 kB
'use strict'; const assert = require('assert'); const ParseMockDB = require('../src/parse-mockdb'); const Parse = require('parse/node'); class Brand extends Parse.Object { constructor(attributes, options) { super('Brand', attributes, options); } } Parse.Object.registerSubclass('Brand', Brand); class Item extends Parse.Object { constructor(attributes, options) { super('Item', attributes, options); } } Parse.Object.registerSubclass('Item', Item); class Store extends Parse.Object { constructor(attributes, options) { super('Store', attributes, options); } } Parse.Object.registerSubclass('Store', Store); class CustomUserSubclass extends Parse.User { } function createBrandP(name) { const brand = new Brand(); brand.set('name', name); return brand.save(); } function createItemP(price, brand, extra) { const item = new Item(); item.set('price', price); if (brand) { item.set('brand', brand); } if (extra) { item.set(extra); } return item.save(); } function createStoreWithItemP(item) { const store = new Store(); store.set('item', item); return store.save(); } function createUserP(name) { const user = new CustomUserSubclass(); user.set('name', name); return user.save(); } function itemQueryP(price) { const query = new Parse.Query(Item); query.equalTo('price', price); return query.find(); } function behavesLikeParseObjectOnBeforeSave(typeName, ParseObjectOrUserSubclass) { context('when object has beforeSave hook registered', () => { function beforeSavePromise(request) { const { original, object } = request; if (object.get('error')) { return Promise.reject('whoah'); } if (original && object.get('value') <= original.get('value')) { return Promise.reject('The value can only go up, not down'); } object.set('cool', true); return Promise.resolve(object); } it('runs the hook before saving the model and persists the object', () => { ParseMockDB.registerHook(typeName, 'beforeSave', beforeSavePromise); const object = new ParseObjectOrUserSubclass(); assert(!object.has('cool')); return object.save().then(savedObject => { assert(savedObject.has('cool')); assert(savedObject.get('cool')); return new Parse.Query(ParseObjectOrUserSubclass).first().then(queriedObject => { assert(queriedObject.has('cool')); assert(queriedObject.get('cool')); }); }); }); it('rejects the save if there is a problem', () => { ParseMockDB.registerHook(typeName, 'beforeSave', beforeSavePromise); const object = new ParseObjectOrUserSubclass({ error: true }); return object.save().then(() => { assert.fail(null, null, 'should not have saved'); }, error => { assert.equal(error, 'whoah'); }); }); it('rejects the save if there is a problem based on the previous value', () => { ParseMockDB.registerHook(typeName, 'beforeSave', beforeSavePromise); const object = new ParseObjectOrUserSubclass({ value: 4 }); return object.save().then(() => { object.set('value', 3); return object.save(); }).then(() => { assert.fail(null, null, 'should not have saved'); }, error => { assert.equal(error, 'The value can only go up, not down'); }); }); }); } function behavesLikeParseObjectOnBeforeDelete(typeName, ParseObjectOrUserSubclass) { context('when object has beforeDelete hook registered', () => { let beforeDeleteWasRun; beforeEach(() => { beforeDeleteWasRun = false; }); function beforeDeletePromise(request) { const object = request.object; if (object.get('error')) { return Promise.reject('whoah'); } beforeDeleteWasRun = true; return Promise.resolve(); } it('runs the hook before deleting the object', () => { ParseMockDB.registerHook(typeName, 'beforeDelete', beforeDeletePromise); const promises = []; promises.push(new ParseObjectOrUserSubclass() .save() .then(savedParseObjectOrUserSubclass => Parse.Object.destroyAll([savedParseObjectOrUserSubclass])) .then(() => assert(beforeDeleteWasRun)) ); promises.push(new Parse.Query(ParseObjectOrUserSubclass) .find() .then(results => { assert.equal(results.length, 0); })); return Promise.all(promises); }); it('rejects the delete if there is a problem', () => { ParseMockDB.registerHook(typeName, 'beforeDelete', beforeDeletePromise); const object = new ParseObjectOrUserSubclass({ error: true }); return object.save().then(savedParseObjectOrUserSubclass => Parse.Object.destroyAll([savedParseObjectOrUserSubclass]) ).then(() => { assert.fail(null, null, 'should not have deleted'); }, (error) => { assert.equal(error, 'whoah'); return new Parse.Query(ParseObjectOrUserSubclass).find(); }).then((results) => { assert.equal(results.length, 1); }); }); }); } function behavesLikeParseObjectOnAfterSave(typeName, ParseObjectOrUserSubclass) { context('when object has afterSave hook registered', () => { let didAfterSave; let objectInAfterSave; function afterSavePromise(request) { didAfterSave = true; objectInAfterSave = request.object; return Promise.resolve(); } beforeEach(() => { didAfterSave = false; objectInAfterSave = {}; }); context('when saving a new object', () => { it('runs the hook after saving the model and persisting the object', () => { ParseMockDB.registerHook(typeName, 'afterSave', afterSavePromise); const object = new ParseObjectOrUserSubclass(); return object.save().then(() => assert(didAfterSave)); }); it("get all the object's attributes during the afterSave hook", () => { ParseMockDB.registerHook(typeName, 'afterSave', afterSavePromise); const object = new ParseObjectOrUserSubclass({ name: 'abc' }); return object.save().then((savedObject) => { assert(didAfterSave); assert.equal( objectInAfterSave.get('createdAt').getTime(), savedObject.get('createdAt').getTime() ); assert.equal( objectInAfterSave.get('updatedAt').getTime(), savedObject.get('updatedAt').getTime() ); assert.equal( objectInAfterSave.id, savedObject.id ); assert.equal( objectInAfterSave.get('name'), savedObject.get('name') ); }); }); context('when the afterSave hook hits an error', () => { beforeEach(() => { const badHook = () => Promise.reject(new Error('Something went wrong')); ParseMockDB.registerHook(typeName, 'afterSave', badHook); }); it('still saves the object', () => { const object = new ParseObjectOrUserSubclass(); return object.save().then((savedObject) => { assert(!!savedObject.id); }); }); }); }); context('when updating an existing object', () => { let object; beforeEach(() => { // Tricky: We're creating this object before registering the hook, // so it won't fire here. object = new ParseObjectOrUserSubclass({ name: 'original' }); return object.save(); }); it('runs the hook after saving the model and persisting the object', () => { ParseMockDB.registerHook(typeName, 'afterSave', afterSavePromise); object.set('name', 'updated'); return object.save().then(() => assert(didAfterSave)); }); it("get all the object's attributes during the afterSave hook", () => { ParseMockDB.registerHook(typeName, 'afterSave', afterSavePromise); object.set('name', 'updated'); return object.save().then((savedObject) => { assert(didAfterSave); assert.equal( objectInAfterSave.get('createdAt').getTime(), savedObject.get('createdAt').getTime() ); assert.equal( objectInAfterSave.get('updatedAt').getTime(), savedObject.get('updatedAt').getTime() ); assert.equal( objectInAfterSave.id, savedObject.id ); assert.equal( objectInAfterSave.get('name'), 'updated' ); }); }); context('when the afterSave hook hits an error', () => { beforeEach(() => { const badHook = () => Promise.reject(new Error('Something went wrong')); ParseMockDB.registerHook(typeName, 'afterSave', badHook); }); it('still saves the object', () => { object.set('name', 'updated'); return object.save().then((savedObject) => { assert(!!savedObject.id); }); }); }); }); }); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } describe('ParseMock', () => { beforeEach(() => { ParseMockDB.mockDB(Parse); }); afterEach(() => { ParseMockDB.cleanUp(); }); context('supports Parse.User subclasses', () => { it('should save user', () => createUserP('Tom').then((user) => { assert.equal(user.get('name'), 'Tom'); }) ); it('should save and find a user', () => createUserP('Tom').then(() => { const query = new Parse.Query(CustomUserSubclass); query.equalTo('name', 'Tom'); return query.first().then((user) => { assert.equal(user.get('name'), 'Tom'); }); }) ); behavesLikeParseObjectOnBeforeSave('_User', CustomUserSubclass); behavesLikeParseObjectOnBeforeDelete('_User', CustomUserSubclass); behavesLikeParseObjectOnAfterSave('_User', CustomUserSubclass); }); it('should save correctly', () => createItemP(30).then((item) => { assert.equal(item.get('price'), 30); }) ); it('should come back with createdAt', () => { let createdAt; return createItemP(30).then((item) => { assert(item.createdAt); createdAt = item.createdAt; return (new Parse.Query(Item)).first(); }).then((fetched) => { assert.equal(createdAt.getTime(), fetched.createdAt.getTime()); }); }); it('should get a specific ID correctly', () => createItemP(30).then(item => { const query = new Parse.Query(Item); return query.get(item.id).then(fetchedItem => { assert.equal(fetchedItem.id, item.id); }); }) ); it('should match a correct equalTo query on price', () => createItemP(30) .then((item) => itemQueryP(30) .then(results => { assert.equal(results[0].id, item.id); assert.equal(results[0].get('price'), item.get('price')); }) ) ); it('should match a query that uses equalTo as contains constraint', () => createItemP(30) .then((item) => new Parse.Object('Factory').save({ items: [item], }) .then(savedComp => new Parse.Query('Factory') .equalTo('items', item) .find() .then(results => { assert.equal(results[0].id, savedComp.id); }) ) ) ); it('should match a query that uses equalTo as contains constraint with 0 as parameter', () => new Parse.Object('Factory').save({ items: [0, 1], }).then(savedComp => new Parse.Query('Factory') .equalTo('items', 0) .find() .then(results => { assert.equal(results[0].id, savedComp.id); }) ) ); it('should not allow array values as equalTo parameter for array columns', () => new Parse.Object('Factory').save({ items: [0, 1], }).then(() => new Parse.Query('Factory') .equalTo('items', [0, 1]) .find() .then(() => Promise.reject( new Error('Promise should have failed')), () => Promise.resolve(true)) ) ); it('should not match objects with [] as field value and 0 as query parameter', () => new Parse.Object('Factory').save({ items: [], }).then(() => new Parse.Query('Factory') .equalTo('items', 0) .find() .then(results => { assert.equal(results.length, 0); }) ) ); it('should not match objects with null as field value and \'\' as query parameter', () => new Parse.Object('Factory').save({ name: null, }).then(() => new Parse.Query('Factory') .equalTo('items', '') .find() .then(results => { assert.equal(results.length, 0); }) ) ); it('should save and find an item', () => { const item = new Item(); item.set('price', 30); return item.save() .then(() => { const query = new Parse.Query(Item); query.equalTo('price', 30); return query.first().then(returnedItem => { assert.equal(returnedItem.get('price'), 30); }); }); }); it('should save and find an item via object comparison', () => { const startItem = new Item({ cool: { awesome: true } }); return startItem.save().then(() => { const query = new Parse.Query(Item); query.equalTo('cool', { awesome: true }); return query.first().then((item) => { assert(item.get('cool').awesome); }); }); }); it('should save a nested item and return it with the save', () => new Item().save({ price: 45, }).then((item0) => new Item().save({ price: 50, }).then((item2) => new Item({ price: 55, }).save().then((item3) => { const brand = new Brand(); const item1 = new Item(); item1.id = item0.id; // create pointer to item0 brand.set('items', [item1, item2, item3]); return brand.save(); }) ) ).then((brand) => { assert.equal(brand.get('items')[0].get('price'), undefined); assert.equal(brand.get('items')[1].get('price'), 50); assert.equal(brand.get('items')[2].get('price'), 55); brand.get('items')[2].set('price', 30); brand.set('name', 'foo'); return brand.save(); }) .then((sbrand) => { assert.equal(sbrand.get('items')[0].get('price'), undefined); assert.equal(sbrand.get('items')[1].get('price'), 50); assert.equal(sbrand.get('items')[2].get('price'), 30); }) ); it('should save a nested item and return it with the save even with a hook defined', () => { ParseMockDB.registerHook('Brand', 'beforeSave', request => { const object = request.object; object.set('name', 'bar'); return Promise.resolve(object); }); return new Item().save({ price: 45, }).then((item0) => new Item().save({ price: 50, }).then((item2) => new Item({ price: 55, }).save().then((item3) => { const brand = new Brand(); const item1 = new Item(); item1.id = item0.id; // create pointer to item0 brand.set('items', [item1, item2, item3]); return brand.save(); }) ) ).then((brand) => { assert.equal(brand.get('items')[0].get('price'), undefined); assert.equal(brand.get('items')[1].get('price'), 50); assert.equal(brand.get('items')[2].get('price'), 55); brand.get('items')[2].set('price', 30); brand.set('name', 'foo'); return brand.save(); }) .then((sbrand) => { assert.equal(sbrand.get('items')[0].get('price'), undefined); assert.equal(sbrand.get('items')[1].get('price'), 50); assert.equal(sbrand.get('items')[2].get('price'), 30); }); }); it('should support increment', () => createItemP(30).then((item) => { item.increment('price', 5); return item.save(); }).then((item) => { assert.equal(item.get('price'), 35); }) ); it('should support negative increment', () => createItemP(30).then((item) => { item.increment('price', -5); return item.save(); }).then((item) => { assert.equal(item.get('price'), 25); }) ); it('should increment a non-existent field', () => createItemP(30).then((item) => item .increment('foo') .save() ).then((item) => { assert.equal(item.get('foo'), 1); }) ); it('should match an item that is within a kilometer radius of a geo point', () => // the used two points are 133.4 km away according to http://www.movable-type.co.uk/scripts/latlong.html new Item().save({ location: new Parse.GeoPoint(49, 7), }).then(item => new Parse.Query(Item) .withinKilometers('location', new Parse.GeoPoint(48, 8), 134) .find() .then(results => { assert.equal(results[0].id, item.id); }) ) ); it('should not match an item that is not within a kilometer radius of a geo point', () => // the used two points are 133.4 km away according to http://www.movable-type.co.uk/scripts/latlong.html new Item().save({ location: new Parse.GeoPoint(49, 7), }).then(() => new Parse.Query(Item) .withinKilometers('location', new Parse.GeoPoint(48, 8), 133) .find() ).then(results => { assert.equal(results.length, 0); }) ); xit('should sort matches of a geo query from nearest to furthest', () => // the used two points are 133.4 km away according to http://www.movable-type.co.uk/scripts/latlong.html new Item().save({ location: new Parse.GeoPoint(49, 7), }).then(item1 => new Item().save({ location: new Parse.GeoPoint(49, 8), }).then(item2 => new Parse.Query(Item) .withinKilometers('location', new Parse.GeoPoint(48, 8), 134) .find() .then(results => { assert.equal(results[0].id, item2.id); assert.equal(results[1].id, item1.id); }) ) ) ); it('should use a custom order over ordering from nearest to furthest in a geo query', () => // the used two points are 133.4 km away according to http://www.movable-type.co.uk/scripts/latlong.html new Item().save({ price: 21, location: new Parse.GeoPoint(49, 7), }).then(item1 => new Item().save({ price: 20, location: new Parse.GeoPoint(49, 8), }).then(item2 => new Parse.Query(Item) .withinKilometers('location', new Parse.GeoPoint(48, 8), 134) .ascending('price') .find() .then(results => { assert.equal(results[0].id, item2.id); assert.equal(results[1].id, item1.id); }) ) ) ); it('should use a descending order for query', () => new Item().save({ price: 21, }).then(item1 => new Item().save({ price: 25, }).then(item2 => new Item().save({ price: 20, }).then(item3 => new Parse.Query(Item) .descending('price') .find() .then(results => { assert.equal(results[0].id, item2.id); assert.equal(results[1].id, item1.id); assert.equal(results[2].id, item3.id); }) ) ) ) ); it('should use a descending order for text queries', () => new Item().save({ moniker: 'Meerkat', }).then(item1 => new Item().save({ moniker: 'Aardvaark', }).then(item2 => new Item().save({ moniker: 'Zebra', }).then(item3 => new Parse.Query(Item) .descending('moniker') .find() .then(results => { assert.equal(results[0].id, item3.id); assert.equal(results[1].id, item1.id); assert.equal(results[2].id, item2.id); }) ) ) ) ); it('should use an ascending order for date query', () => new Item().save({ expires: new Date(2017, 0, 2), }).then(item1 => new Item().save({ expires: new Date(2017, 0, 1), }).then(item2 => new Parse.Query(Item) .ascending('expires') .find() .then(results => { assert.equal(results[0].id, item2.id); assert.equal(results[1].id, item1.id); }) ) ) ); it('should use an descending order a query on createdAt', () => new Item().save().then(item1 => // we need to make sure the created at dates are different! sleep(1).then(() => new Item().save() ).then(item2 => new Parse.Query(Item) .descending('createdAt') .find() .then(results => { assert.equal(results[0].id, item2.id); assert.equal(results[1].id, item1.id); }) ) ) ); it('should support multiple sorting parameters', () => new Item().save({ active: true, price: 20, }).then(item1 => new Item().save({ active: true, price: 21, }).then(item2 => new Item().save({ active: false, price: 20, }).then(item3 => new Parse.Query(Item) .descending('active') .addAscending('price') .find() .then(results => { assert.equal(results[0].id, item1.id); assert.equal(results[1].id, item2.id); assert.equal(results[2].id, item3.id); }) ) ) ) ); it('should support unset', () => createItemP(30).then((item) => { item.unset('price'); return item.save(); }).then((item) => { assert(!item.has('price')); }) ); it('should support add', () => createItemP(30).then((item) => { item.add('languages', 'JS'); return item.save(); }).then((item) => { assert.deepEqual(item.get('languages'), ['JS']); }) ); it('should support addUnique', () => createItemP(30).then((item) => { item.add('languages', 'JS'); item.add('languages', 'Ruby'); return item.save(); }).then((item) => { assert.deepEqual(item.get('languages'), ['JS', 'Ruby']); item.addUnique('languages', 'JS'); return item.save(); }).then((item) => { assert.deepEqual(item.get('languages'), ['JS', 'Ruby']); }) ); it('should support addUnique with parse objects', () => new Item().save().then(i => new Brand() .save() .then(b => { b.addUnique('items', i); return b.save(); }) .then(b => { b.addUnique('items', i); return b.save(); }) .then(b => b.fetch()) .then(b => { assert.equal(b.get('items').length, 1); assert.equal(b.get('items')[0].id, i.id); }) ) ); it('should support addUnique with dates', () => new Item() .save() .then(i => { i.addUnique('dates', new Date(5)); return i.save(); }) .then(i => { i.addUnique('dates', new Date(5)); return i.save(); }) .then(i => i.fetch()) .then(i => { assert.equal(i.get('dates').length, 1); assert.equal(i.get('dates')[0].getTime(), 5); }) ); it('should support remove', () => createItemP(30).then((item) => { item.add('languages', 'JS'); item.add('languages', 'JS'); item.add('languages', 'Ruby'); return item.save(); }).then((item) => { assert.deepEqual(item.get('languages'), ['JS', 'JS', 'Ruby']); item.remove('languages', 'JS'); return item.save(); }).then((item) => { assert.deepEqual(item.get('languages'), ['Ruby']); }) ); it('should saveAll and find 2 items', () => { const item = new Item(); item.set('price', 30); const item2 = new Item(); item2.set('price', 30); return Parse.Object.saveAll([item, item2]).then((items) => { assert.equal(items.length, 2); const query = new Parse.Query(Item); query.equalTo('price', 30); return query.find().then((finalItems) => { assert.equal(finalItems.length, 2); assert.equal(finalItems[0].get('price'), 30); assert.equal(finalItems[1].get('price'), 30); }); }); }); it('should find an item matching an or query', () => new Item() .set('price', 30) .save() .then(item => { const query = new Parse.Query(Item); query.equalTo('price', 30); const otherQuery = new Parse.Query(Item); otherQuery.equalTo('name', 'Chicken'); const orQuery = Parse.Query.or(query, otherQuery); return orQuery.find().then((items) => { assert.equal(items[0].id, item.id); }); }) ); it('should not find any items if they do not match an or query', () => new Item() .set('price', 30) .save() .then(() => { const query = new Parse.Query(Item); query.equalTo('price', 50); const otherQuery = new Parse.Query(Item); otherQuery.equalTo('name', 'Chicken'); const orQuery = Parse.Query.or(query, otherQuery); return orQuery.find().then((items) => { assert.equal(items.length, 0); }); }) ); it('should save 2 items and get one for a first() query', () => Promise.all([createItemP(30), createItemP(20)]).then(() => { const query = new Parse.Query(Item); return query.first().then((item) => { assert.equal(item.get('price'), 30); }); }) ); it('should handle nested includes', () => createBrandP('Acme') .then((newBrand) => createItemP(30, newBrand) .then((item) => { const brand = item.get('brand'); return createStoreWithItemP(item).then(() => { const query = new Parse.Query(Store); query.include('item'); query.include('item.brand'); return query.first().then((result) => { const resultItem = result.get('item'); const resultBrand = resultItem.get('brand'); assert.equal(resultItem.id, item.id); assert.equal(resultBrand.get('name'), 'Acme'); assert.equal(resultBrand.id, brand.id); }); }); }) ) ); it('should return invalid pointers if they are not included', () => { const item = new Item(); item.id = 'ZZZZZZZZ'; return createStoreWithItemP(item).then(() => { const query = new Parse.Query(Store); return query.first().then((result) => { assert.strictEqual(result.get('item').id, item.id); }); }); }); it('should leave includes of invalid pointers undefined', () => { const item = new Item(); item.id = 'ZZZZZZZZ'; return createStoreWithItemP(item).then(() => { const query = new Parse.Query(Store); query.include('item'); query.include('item.brand'); return query.first().then((result) => { assert.strictEqual(result.get('item'), undefined); }); }); }); it('should handle multiple nested includes', () => { let a1; let a2; let b; let c; return Promise.all([ new Parse.Object('a', { value: '1' }).save(), new Parse.Object('a', { value: '2' }).save(), ]) .then(([savedA1, savedA2]) => { a1 = savedA1; a2 = savedA2; return new Parse.Object('b', { a1, a2 }).save(); }) .then((savedB) => { b = savedB; return new Parse.Object('c', { b }).save(); }) .then((savedC) => { c = savedC; return new Parse.Query('c') .include('b') .include('b.a1') .include('b.a2') .first(); }) .then((loadedC) => { assert.equal(loadedC.id, c.id); assert.equal(loadedC.get('b').id, b.id); assert.equal(loadedC.get('b').get('a1').id, a1.id); assert.equal(loadedC.get('b').get('a2').id, a2.id); assert.equal(loadedC.get('b').get('a1').get('value'), a1.get('value')); assert.equal(loadedC.get('b').get('a2').get('value'), a2.get('value')); }); }); it('should handle includes over arrays of pointers', () => { const item1 = new Item({ cool: true }); const item2 = new Item({ cool: false }); const items = [item1, item2]; return Parse.Object.saveAll(items).then(() => { const brand = new Brand({ items, }); return brand.save(); }).then(() => { const q = new Parse.Query(Brand).include('items'); return q.first(); }).then((brand) => { assert(brand.get('items')[0].get('cool')); assert(!brand.get('items')[1].get('cool')); }); }); it('should handle nested includes over arrays of pointers', () => { const store = new Store({ location: 'SF' }); const item1 = new Item({ cool: true, store }); const item2 = new Item({ cool: false }); const items = [item1, item2]; return Parse.Object.saveAll(items.concat([store])).then(() => { const brand = new Brand({ items, }); return brand.save(); }).then(() => { const q = new Parse.Query(Brand).include('items,items.store'); return q.first(); }).then((brand) => { assert.equal(brand.get('items')[0].get('store').get('location'), 'SF'); assert(!brand.get('items')[1].get('cool')); }); }); it('should handle includes for array of string', () => { const item = new Item({ alternateNames: ['item1', 'originalItem'] }); return Parse.Object.saveAll([item]).then(() => { const brand = new Brand({ item, }); return brand.save(); }).then(() => { const q = new Parse.Query(Brand).include('item,item.alternateNames'); return q.first(); }).then((brand) => { assert.deepEqual(brand.get('item').get('alternateNames'), ['item1', 'originalItem']); }); }); it('should handle includes where item is missing', () => { const item = new Item({ cool: true }); const brand1 = new Brand({}); const brand2 = new Brand({ item }); return Parse.Object.saveAll([item, brand1, brand2]).then(() => { const q = new Parse.Query(Brand).include('item'); return q.find(); }).then((brands) => { assert(!brands[0].has('item')); assert(brands[1].has('item')); }); }); it('should handle includes where nested array item is missing', () => { const store = new Store({ location: 'SF' }); const item1 = new Item({ cool: true, store }); const item2 = new Item({ cool: false }); const items = [item1, item2]; return Parse.Object.saveAll(items.concat([store])).then(() => { const brand = new Brand({ items, }); return brand.save(); }).then(() => { const q = new Parse.Query(Brand).include('items,items.blah,wow'); return q.first(); }).then((brand) => { assert(brand.get('items')[0].get('cool')); assert(!brand.get('items')[1].get('cool')); }); }); it('should handle delete', () => { const item = new Item(); return item.save().then(() => new Parse.Query(Item).first() ).then((foundItem) => { assert(foundItem); return foundItem.destroy(); }).then(() => new Parse.Query(Item).first()) .then((foundItem) => { assert(!foundItem); }); }); it('should do a fetch query', () => { let savedItem; return new Item().save({ price: 30 }).then((item1) => { savedItem = item1; return Item.createWithoutData(item1.id).fetch(); }).then((fetched) => { assert.equal(fetched.id, savedItem.id); assert.equal(fetched.get('price'), 30); }); }); it('should find with objectId', () => { let savedItem; return new Item().save({ price: 30 }).then((item1) => { savedItem = item1; return new Parse.Query(Item).equalTo('objectId', item1.id).first(); }).then((fetched) => { assert.equal(fetched.id, savedItem.id); assert.equal(fetched.get('price'), 30); }); }); it('should get objectId', () => { let savedItem; return new Item().save({ price: 30 }).then((item1) => { savedItem = item1; return new Parse.Query(Item).get(item1.id); }).then((fetched) => { assert.equal(fetched.id, savedItem.id); assert.equal(fetched.get('price'), 30); }); }); it('should find with objectId and where', () => Promise.all([ new Item().save({ price: 30 }), new Item().save({ name: 'Device' }), ]).then(([item1]) => { const itemQuery = new Parse.Query(Item); itemQuery.exists('nonExistent'); itemQuery.equalTo('objectId', item1.id); return itemQuery.find().then((items) => { assert.equal(items.length, 0); }); }) ); it('should match a correct when exists query', () => Promise.all([ new Item().save({ price: 30 }), new Item().save({ name: 'Device' }), ]).then(([item1]) => { const itemQuery = new Parse.Query(Item); itemQuery.exists('price'); return itemQuery.find().then((items) => { assert.equal(items.length, 1); assert.equal(items[0].id, item1.id); }); }) ); it('should match a correct when doesNotExist query', () => Promise.all([ new Item().save({ price: 30 }), new Item().save({ name: 'Device' }), ]).then(([item1, item2]) => { // eslint-disable-line no-unused-vars const itemQuery = new Parse.Query(Item); itemQuery.doesNotExist('price'); return itemQuery.find().then((items) => { assert.equal(items.length, 1); assert.equal(items[0].id, item2.id); }); }) ); it('should match a correct equalTo query for an object', () => createItemP(30).then((item) => { const store = new Store(); store.set('item', item); return store.save().then((savedStore) => { const query = new Parse.Query(Store); query.equalTo('item', item); return query.find().then((results) => { assert.equal(results[0].id, savedStore.id); }); }); }) ); it('should handle an equalTo null query for an object without a null field', () => createItemP(30).then((item) => { const store = new Store(); store.set('item', item); return store.save().then(() => { const query = new Parse.Query(Store); query.equalTo('item', null); return query.find().then((results) => { assert.equal(results.length, 0); }); }); }) ); it('should handle an equalTo null query for an object with a null field', () => { const store = new Store(); return store.save().then((savedStore) => { const query = new Parse.Query(Store); query.equalTo('item', null); return query.find().then((results) => { assert.equal(results[0].id, savedStore.id); }); }); }); it('should handle a notEqualTo null query for an object without a null field', () => createItemP(30).then((item) => { const store = new Store(); store.set('item', item); return store.save().then((savedStore) => { const query = new Parse.Query(Store); query.notEqualTo('item', null); return query.find().then((results) => { assert.equal(results[0].id, savedStore.id); }); }); }) ); it('should handle a notEqualTo null query for an object with a null field', () => { const store = new Store(); return store.save().then(() => { const query = new Parse.Query(Store); query.notEqualTo('item', null); return query.find().then((results) => { assert.equal(results.length, 0); }); }); }); it('should not match an incorrect equalTo query on price', () => createItemP(30).then(() => itemQueryP(20).then((results) => { assert.equal(results.length, 0); }) ) ); it('should not match an incorrect equalTo query on price and name', () => createItemP(30).then(() => { const query = new Parse.Query(Item); query.equalTo('price', 30); query.equalTo('name', 'pants'); return query.find().then((results) => { assert.equal(results.length, 0); }); }) ); it('should match a containedIn query', () => createItemP(30).then(() => { const query = new Parse.Query(Item); query.containedIn('price', [40, 30, 90]); return query.find().then((results) => { assert.equal(results.length, 1); }); }) ); it('should not match an incorrect containedIn query', () => createItemP(30).then(() => { const query = new Parse.Query(Item); query.containedIn('price', [40, 90]); return query.find().then((results) => { assert.equal(results.length, 0); }); }) ); it('should match a containedIn query on array of items', () => createItemP(30, 'Cereal', { languages: ['ruby', 'js', 'python'] }).then(() => { const query = new Parse.Query(Item); query.containedIn('languages', ['ruby']); return query.find().then((results) => { assert.equal(results.length, 1); }); }) ); it('should find 2 objects when there are 2 matches', () => Promise.all([createItemP(20), createItemP(20)]).then(() => { const query = new Parse.Query(Item); query.equalTo('price', 20); return query.find().then((results) => { assert.equal(results.length, 2); }); }) ); it('should first() 1 object when there are 2 matches', () => Promise.all([createItemP(20), createItemP(20)]).then(([item1]) => { const query = new Parse.Query(Item); query.equalTo('price', 20); return query.first().then((result) => { assert.equal(result.id, item1.id); }); }) ); it('should match a query with 1 objects when 2 objects are present', () => Promise.all([createItemP(20), createItemP(30)]).then(() => { const query = new Parse.Query(Item); query.equalTo('price', 20); return query.find().then((results) => { assert.equal(results.length, 1); }); }) ); it('should match a date', () => { const bornOnDate = new Date(); const item = new Item({ bornOnDate }); return item.save().then(() => { const query = new Parse.Query(Item); query.equalTo('bornOnDate', bornOnDate); return query.first().then((result) => { assert(result.get('bornOnDate', bornOnDate)); }); }); }); it('should properly handle date in query operator', () => { const bornOnDate = new Date(); const middleDate = new Date(); const expireDate = new Date(); middleDate.setDate(bornOnDate.getDate() + 1); expireDate.setDate(bornOnDate.getDate() + 2); const item = new Item({ bornOnDate, expireDate, }); return item.save().then(() => { const query = new Parse.Query(Item); query.lessThan('bornOnDate', middleDate); query.greaterThan('expireDate', middleDate); return query.first().then((result) => { assert(result); }); }); }); it('should handle $nin', () => Promise.all([createItemP(20), createItemP(30)]).then(() => { const query = new Parse.Query(Item); query.notContainedIn('price', [30]); return query.find(); }).then((results) => { assert.equal(results.length, 1); assert.equal(results[0].get('price'), 20); }) ); it('should handle $nin on array field', () => { const item1 = createItemP(20, 'crap', { languages: ['ruby', 'js', 'python'] }); const item2 = createItemP(30, 'crap', { languages: ['ruby', 'js'] }); Promise.all([item1, item2]).then(() => { const query = new Parse.Query(Item); query.notContainedIn('languages', ['python']); return query.find(); }).then((results) => { assert.equal(results.length, 1); assert.equal(results[0].get('price'), 30); }); }); it('should handle $nin on objectId', () => createItemP(30).then((item) => { const query = new Parse.Query(Item); query.notContainedIn('objectId', [item.id]); return query.find(); }).then((results) => { assert.equal(results.length, 0); }) ); it('should handle $nin with an empty array', () => createItemP(30).then(() => { const query = new Parse.Query(Item); query.notContainedIn('objectId', []); return query.find(); }).then((results) => { assert.equal(results.length, 1); }) ); it('should handle startsWith queries', () => createBrandP('Acme').then(() => { const query = new Parse.Query(Brand); query.startsWith('name', 'Ac'); return query.find(); }).then((results) => { assert.equal(results.length, 1); }) ); it('should handle matches queries', () => createBrandP('Acme').then(() => { const query = new Parse.Query(Brand); query.matches('name', /ac/i); return query.find(); }).then((results) => { assert.equal(results.length, 1); }) ); it('should handle matches queries that dont match', () => createBrandP('Acme').then(() => { const query = new Parse.Query(Brand); query.matches('name', /ac/); return query.find(); }).then((results) => { assert.equal(results.length, 0); }) ); it('should not overwrite included objects after a save', () => createBrandP('Acme').then((brand) => createItemP(30, brand).then((item) => createStoreWithItemP(item).then(() => { const query = new Parse.Query(Store); query.include('item'); query.include('item.brand'); return query.first().then((str) => { str.set('lol', 'wut'); return str.save().then(() => { assert.equal(str.get('item').get('brand').get('name'), brand.get('name')); }); }); }) ) ) ); it('should update an existing object correctly', () => Promise.all([createItemP(30), createItemP(20)]).then(([item1, item2]) => createStoreWithItemP(item1).then((store) => { item2.set('price', 10); store.set('item', item2); return store.save().then((returnedStore) => { assert(returnedStore.has('item')); assert.equal(returnedStore.get('item').get('price'), 10); assert(returnedStore.get('updatedAt') instanceof Date); }); }) ) ); it('should support a nested query', () => { const brand0 = new Brand(); brand0.set('name', 'Acme'); brand0.set('country', 'US'); return brand0.save().then((brand) => { const item = new Item(); item.set('price', 30); item.set('country_code', 'US'); item.set('state', 'CA'); item.set('brand', brand); return item.save(); }).then(() => { const store = new Store(); store.set('state', 'CA'); return store.save(); }).then((store) => { const brandQuery = new Parse.Query(Brand); brandQuery.equalTo('name', 'Acme'); const itemQuery = new Parse.Query(Item); itemQuery.matchesKeyInQuery('country_code', 'country', brandQuery); const storeQuery = new Parse.Query(Store); storeQuery.matchesKeyInQuery('state', 'state', itemQuery); return Promise.all([storeQuery.find(), Promise.resolve(store)]); }) .then(([storeMatches, store]) => { assert.equal(storeMatches.length, 1); assert.equal(storeMatches[0].id, store.id); }); }); it('should find items not filtered by a notContainedIn', () => createItemP(30).then(() => { const query = new Parse.Query(Item); query.equalTo('price', 30); query.notContainedIn('objectId', [234]); return query.find().then((items) => { assert.equal(items.length, 1); }); }) ); it('should find not items filtered by a notContainedIn', () => createItemP(30).then((item) => { const query = new Parse.Query(Item); query.equalTo('price', 30); query.notContainedIn('objectId', [item.id]); return query.find().then((items) => { assert.equal(items.length, 0); }); }) ); it('should handle a lessThan query', () => createItemP(30).then(() => { const query = new Parse.Query(Item); query.lessThan('createdAt', new Date('2024-01-01T23:28:56.782Z')); return query.find().then((items) => { assert.equal(items.length, 1); const newQuery = new Parse.Query(Item); newQuery.greaterThan('createdAt', new Date()); return newQuery.find().then((moreItems) => { assert.equal(moreItems.length, 0); }); }); }) ); it('should handle a lessThanOrEqualTo query', () => createItemP(30).then(() => { const query = new Parse.Query(Item); query.lessThanOrEqualTo('price', 30); return query.find().then((items) => { assert.equal(items.length, 1); query.lessThanOrEqualTo('price', 20); return query.find().then((moreItems) => { assert.equal(moreItems.length, 0); }); }); }) ); it('should handle a greaterThan query', () => createItemP(30).then(() => { const query = new Parse.Query(Item); query.greaterThan('price', 20); return query.find().then((items) => { assert.equal(items.length, 1); query.greaterThan('price', 50); return query.find().then((moreItems) => { assert.equal(moreItems.length, 0); }); }); }) ); it('should handle a greaterThanOrEqualTo query', () => createItemP(30).then(() => { const query = new Parse.Query(Item); query.greaterThanOrEqualTo('price', 30); return query.find().then((items) => { assert.equal(items.length, 1); query.greaterThanOrEqualTo('price', 50); return query.find().then((moreItems) => { assert.equal(moreItems.length, 0); }); }); }) ); it('should handle multiple conditions for a single key', () => createItemP(30).then(() => { const query = new Parse.Query(Item); query.greaterThan('price', 20); query.lessThan('price', 40); return query.find().then((items) => { assert.equal(items.length, 1); query.greaterThan('price', 30); return query.find().then((moreItems) => { assert.equal(moreItems.length, 0); }); }); }) ); it('should correctly handle matchesQuery', () => createBrandP('Acme').then((brand) => createItemP(30, brand).then((item) => createStoreWithItemP(item).then(() => { const brandQuery = new Parse.Query(Brand); brandQuery.equalTo('name', 'Acme'); const itemQuery = new Parse.Query(Item); itemQuery.matchesQuery('brand', brandQuery); const storeQuery = new Parse.Query(Store); storeQuery.matchesQuery('item', itemQuery); return storeQuery.find().then((store) => { assert(store); }); }) ) ) ); it('should correctly count items in a matchesQuery', () => createBrandP('Acme').then((brand) => createItemP(30, brand).then((item) => createStoreWithItemP(item).then(() => { const itemQuery = new Parse.Query(Item); itemQuery.equalTo('price', 30); const storeQuery = new Parse.Query(Store); storeQuery.matchesQuery('item', itemQuery); return storeQuery.count().then((storeCount) => { assert.equal(storeCount, 1); }); }) ) ) ); it('should skip and limit items appropriately', () => createBrandP('Acme').then(() => createBrandP('Acme 2').then(() => { const brandQuery = new Parse.Query(Brand); brandQuery.limit(1); return brandQuery.find().then((brands) => { assert.equal(brands.length, 1); const brandQuery2 = new Parse.Query(Brand); brandQuery2.limit(1); brandQuery2.skip(1); return brandQuery2.find().then((moreBrands) => { assert.equal(moreBrands.length, 1); assert.notEqual(moreBrands[0].id, brands[0].id); }); }); }) ) ); it('should deep save and update nested objects', () => { const brand = new Brand(); brand.set('name', 'Acme'); brand.set('country', 'US'); const item = new Item(); item.set('price', 30); item.set('country_code', 'US'); brand.set('items', [item]); return brand.save().then((savedBrand) => { assert.equal(savedBrand.get('items')[0].get('price'), item.get('price')); const item2 = new Item(); item2.set('price', 20); brand.set('items', [item2]); return brand.save().then((updatedBrand) => { assert.equal(updatedBrand.get('items')[0].get('price'), 20); }); }); }); context('when object has beforeSave hook registered', () => { behavesLikeParseObjectOnBeforeSave('Brand', Brand); }); context('when object has beforeDelete hook registered', () => { behavesLikeParseObjectOnBeforeDelete('Brand', Brand); }); context('when object has afterSave hook registered', () => { behavesLikeParseObjectOnAfterSave('Brand', Brand); }); it('successfully uses containsAll query', () => Promise.all([createItemP(30), createItemP(20)]).then(([item1, item2]) => { const store = new Store({ items: [item1.toPointer(), item2.toPointer()], }); return store.save().then(() => { const query = new Parse.Query(Store); query.containsAll('items', [item1.toPointer(), item2.toPointer()]);