diff --git a/.eslintrc.json b/.eslintrc.json index 5ee807d06..b45eb6759 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -23,6 +23,7 @@ "prefer-const": "error", "space-infix-ops": "error", "no-useless-escape": "off", - "no-var": "error" + "no-var": "error", + "no-console": 0 } } diff --git a/integration/test/ParseLocalDatastoreTest.js b/integration/test/ParseLocalDatastoreTest.js index 9b01139b5..cbf9c2428 100644 --- a/integration/test/ParseLocalDatastoreTest.js +++ b/integration/test/ParseLocalDatastoreTest.js @@ -8,18 +8,64 @@ const Item = Parse.Object.extend('Item'); global.localStorage = require('./mockLocalStorage'); const mockRNStorage = require('./mockRNStorage'); +const LocalDatastoreUtils = require('../../lib/node/LocalDatastoreUtils'); -const DEFAULT_PIN = Parse.LocalDatastore.DEFAULT_PIN; -const PIN_PREFIX = Parse.LocalDatastore.PIN_PREFIX; +const DEFAULT_PIN = LocalDatastoreUtils.DEFAULT_PIN; +const PIN_PREFIX = LocalDatastoreUtils.PIN_PREFIX; + +function LDS_KEY(object) { + return Parse.LocalDatastore.getKeyForObject(object); +} function runTest(controller) { describe(`Parse Object Pinning (${controller.name})`, () => { - beforeEach(() => { + beforeEach(async () => { const StorageController = require(controller.file); Parse.CoreManager.setAsyncStorage(mockRNStorage); Parse.CoreManager.setLocalDatastoreController(StorageController); Parse.enableLocalDatastore(); - Parse.LocalDatastore._clear(); + await Parse.LocalDatastore._clear(); + }); + + it(`${controller.name} can clear localDatastore`, async () => { + const obj1 = new TestObject(); + const obj2 = new TestObject(); + const obj3 = new TestObject(); + const objects = [obj1, obj2, obj3]; + await Parse.Object.pinAll(objects); + await Parse.Object.pinAllWithName('test_pin', objects); + await Parse.Object.saveAll(objects); + + await Parse.LocalDatastore.pinWithName('DO_NOT_CLEAR', {}); + + let storage = await Parse.LocalDatastore._getRawStorage(); + assert.equal(Object.keys(storage).length, 6); + + await Parse.LocalDatastore._clear(); + + storage = await Parse.LocalDatastore._getRawStorage(); + assert.equal(Object.keys(storage).length, 1); + assert.equal(storage['DO_NOT_CLEAR'], '{}'); + await Parse.LocalDatastore.unPinWithName('DO_NOT_CLEAR'); + }); + + it(`${controller.name} can getAllContents localDatastore`, async () => { + const obj1 = new TestObject(); + const obj2 = new TestObject(); + const obj3 = new TestObject(); + const objects = [obj1, obj2, obj3]; + await Parse.Object.pinAll(objects); + await Parse.Object.pinAllWithName('test_pin', objects); + await Parse.Object.saveAll(objects); + + await Parse.LocalDatastore.pinWithName('DO_NOT_FETCH', {}); + + const storage = await Parse.LocalDatastore._getRawStorage(); + assert.equal(Object.keys(storage).length, 6); + + const LDS = await Parse.LocalDatastore._getAllContents(); + assert.equal(Object.keys(LDS).length, 5); + assert.equal(LDS['DO_NOT_FETCH'], null); }); it(`${controller.name} can pin (unsaved)`, async () => { @@ -28,14 +74,14 @@ function runTest(controller) { // Since object not saved check localId let localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 2); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${object.className}_${object._localId}`]); - assert.deepEqual(localDatastore[`${object.className}_${object._localId}`], object._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(object)]); + assert.deepEqual(localDatastore[LDS_KEY(object)], [object._toFullJSON()]); await object.save(); // Check if localDatastore updated localId to objectId localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 2); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${object.className}_${object.id}`]); - assert.deepEqual(localDatastore[`${object.className}_${object.id}`], object._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(object)]); + assert.deepEqual(localDatastore[LDS_KEY(object)], [object._toFullJSON()]); }); it(`${controller.name} cannot pin unsaved pointer`, async () => { @@ -55,10 +101,10 @@ function runTest(controller) { await object.save(); await object.pin(); const localDatastore = await Parse.LocalDatastore._getAllContents(); - const cachedObject = localDatastore[`${object.className}_${object.id}`]; + const cachedObject = localDatastore[LDS_KEY(object)][0]; assert.equal(Object.keys(localDatastore).length, 2); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${object.className}_${object.id}`]); - assert.deepEqual(localDatastore[`${object.className}_${object.id}`], object._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(object)]); + assert.deepEqual(localDatastore[LDS_KEY(object)], [object._toFullJSON()]); assert.equal(cachedObject.objectId, object.id); assert.equal(cachedObject.field, 'test'); }); @@ -73,10 +119,10 @@ function runTest(controller) { await object.save(); const localDatastore = await Parse.LocalDatastore._getAllContents(); - const cachedObject = localDatastore[`${object.className}_${object.id}`]; + const cachedObject = localDatastore[LDS_KEY(object)][0]; assert.equal(Object.keys(localDatastore).length, 2); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${object.className}_${object.id}`]); - assert.deepEqual(localDatastore[`${object.className}_${object.id}`], object._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(object)]); + assert.deepEqual(localDatastore[LDS_KEY(object)], [object._toFullJSON()]); assert.equal(cachedObject.objectId, object.id); assert.equal(cachedObject.field, 'new info'); }); @@ -109,12 +155,12 @@ function runTest(controller) { await parent.pin(); const localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${parent.className}_${parent.id}`), true); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${child.className}_${child.id}`), true); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${grandchild.className}_${grandchild.id}`), true); - assert.deepEqual(localDatastore[`${parent.className}_${parent.id}`], parent._toFullJSON()); - assert.deepEqual(localDatastore[`${child.className}_${child.id}`], child._toFullJSON()); - assert.deepEqual(localDatastore[`${grandchild.className}_${grandchild.id}`], grandchild._toFullJSON()); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(parent)), true); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(child)), true); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(grandchild)), true); + assert.deepEqual(localDatastore[LDS_KEY(parent)], [parent._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(child)], [child._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(grandchild)], [grandchild._toFullJSON()]); }); it(`${controller.name} can pinAll (unsaved)`, async () => { @@ -127,19 +173,19 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1.id}`, `${obj2.className}_${obj2.id}`, `${obj3.className}_${obj3.id}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1.id}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2.id}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can pinAll (saved)`, async () => { @@ -153,10 +199,10 @@ function runTest(controller) { await Parse.Object.pinAll(objects); const localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1.id}`, `${obj2.className}_${obj2.id}`, `${obj3.className}_${obj3.id}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1.id}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2.id}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can pinAllWithName (unsaved)`, async () => { @@ -169,19 +215,19 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_pin'], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_pin'], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_pin'], [`${obj1.className}_${obj1.id}`, `${obj2.className}_${obj2.id}`, `${obj3.className}_${obj3.id}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1.id}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2.id}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_pin'], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can pinAllWithName (saved)`, async () => { @@ -194,10 +240,10 @@ function runTest(controller) { await Parse.Object.pinAllWithName('test_pin', objects); const localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_pin'], [`${obj1.className}_${obj1.id}`, `${obj2.className}_${obj2.id}`, `${obj3.className}_${obj3.id}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1.id}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2.id}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_pin'], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can unPin on destroy`, async () => { @@ -212,20 +258,20 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert(Object.keys(localDatastore).length === 4); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${obj1.className}_${obj1.id}`), true); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${obj2.className}_${obj2.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${obj1.className}_${obj1.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${obj2.className}_${obj2.id}`), true); - assert(localDatastore[`${obj1.className}_${obj1.id}`]); - assert(localDatastore[`${obj2.className}_${obj2.id}`]); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(obj1)), true); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(obj2)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(obj1)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(obj2)), true); + assert(localDatastore[LDS_KEY(obj1)]); + assert(localDatastore[LDS_KEY(obj2)]); await obj1.destroy(); localDatastore = await Parse.LocalDatastore._getAllContents(); assert(Object.keys(localDatastore).length === 3); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj2.className}_${obj2.id}`]); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_pin'], [`${obj2.className}_${obj2.id}`]); - assert(localDatastore[`${obj2.className}_${obj2.id}`]); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj2)]); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_pin'], [LDS_KEY(obj2)]); + assert(localDatastore[LDS_KEY(obj2)]); }); it(`${controller.name} can unPin on destroyAll`, async () => { @@ -239,23 +285,23 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert(Object.keys(localDatastore).length === 5); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${obj1.className}_${obj1.id}`), true); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${obj2.className}_${obj2.id}`), true); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${obj3.className}_${obj3.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${obj1.className}_${obj1.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${obj2.className}_${obj2.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${obj3.className}_${obj3.id}`), true); - assert(localDatastore[`${obj1.className}_${obj1.id}`]); - assert(localDatastore[`${obj2.className}_${obj2.id}`]); - assert(localDatastore[`${obj3.className}_${obj3.id}`]); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(obj1)), true); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(obj2)), true); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(obj3)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(obj1)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(obj2)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(obj3)), true); + assert(localDatastore[LDS_KEY(obj1)]); + assert(localDatastore[LDS_KEY(obj2)]); + assert(localDatastore[LDS_KEY(obj3)]); await Parse.Object.destroyAll([obj1, obj3]); localDatastore = await Parse.LocalDatastore._getAllContents(); assert(Object.keys(localDatastore).length === 3); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj2.className}_${obj2.id}`]); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_pin'], [`${obj2.className}_${obj2.id}`]); - assert(localDatastore[`${obj2.className}_${obj2.id}`]); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj2)]); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_pin'], [LDS_KEY(obj2)]); + assert(localDatastore[LDS_KEY(obj2)]); }); it(`${controller.name} can unPin with pinAll (unsaved)`, async () => { @@ -268,26 +314,26 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await obj2.unPin(); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 3); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 3); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1.id}`, `${obj3.className}_${obj3.id}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1.id}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can unPin with pinAll (saved)`, async () => { @@ -300,10 +346,10 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); @@ -311,9 +357,9 @@ function runTest(controller) { localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 3); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1.id}`, `${obj3.className}_${obj3.id}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1.id}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can unPin / unPinAll without pin (unsaved)`, async () => { @@ -360,24 +406,24 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert(Object.keys(localDatastore).length === 4); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.unPinAll([obj1, obj2]); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 2); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 2); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj3.className}_${obj3.id}`]); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can unPinAll (saved)`, async () => { @@ -390,18 +436,18 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); await Parse.Object.unPinAll([obj1, obj2]); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 2); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj3.className}_${obj3.id}`]); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can unPinAllObjects (unsaved)`, async () => { @@ -414,26 +460,26 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert(Object.keys(localDatastore).length === 4); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.unPinAllObjects(); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 3); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 3); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1.id}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2.id}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can unPinAllObjects (saved)`, async () => { @@ -446,19 +492,19 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[DEFAULT_PIN], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[DEFAULT_PIN], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); Parse.Object.unPinAllObjects(); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 3); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1.id}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2.id}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can unPinAllWithName (unsaved)`, async () => { @@ -471,24 +517,24 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert(Object.keys(localDatastore).length === 4); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.unPinAllWithName('test_unpin', [obj1, obj2]); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 2); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [`${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 2); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [`${obj3.className}_${obj3.id}`]); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can unPinAllWithName (saved)`, async () => { @@ -501,18 +547,18 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); await Parse.Object.unPinAllWithName('test_unpin', [obj1, obj2]); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 2); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [`${obj3.className}_${obj3.id}`]); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can unPinAllObjectsWithName (unsaved)`, async () => { @@ -525,26 +571,26 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert(Object.keys(localDatastore).length === 4); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.unPinAllObjectsWithName('test_unpin'); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 3); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 3); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1.id}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2.id}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can unPinAllObjectsWithName (saved)`, async () => { @@ -557,19 +603,19 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 4); - assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [`${obj1.className}_${obj1._localId}`, `${obj2.className}_${obj2._localId}`, `${obj3.className}_${obj3._localId}`]); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1._localId}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2._localId}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3._localId}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[PIN_PREFIX + 'test_unpin'], [LDS_KEY(obj1), LDS_KEY(obj2), LDS_KEY(obj3)]); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); await Parse.Object.saveAll(objects); await Parse.Object.unPinAllObjectsWithName('test_unpin'); localDatastore = await Parse.LocalDatastore._getAllContents(); assert.equal(Object.keys(localDatastore).length, 3); - assert.deepEqual(localDatastore[`${obj1.className}_${obj1.id}`], obj1._toFullJSON()); - assert.deepEqual(localDatastore[`${obj2.className}_${obj2.id}`], obj2._toFullJSON()); - assert.deepEqual(localDatastore[`${obj3.className}_${obj3.id}`], obj3._toFullJSON()); + assert.deepEqual(localDatastore[LDS_KEY(obj1)], [obj1._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj2)], [obj2._toFullJSON()]); + assert.deepEqual(localDatastore[LDS_KEY(obj3)], [obj3._toFullJSON()]); }); it(`${controller.name} can unPin and save reference`, async () => { @@ -583,20 +629,20 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert(Object.keys(localDatastore).length === 5); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${obj1.className}_${obj1.id}`), true); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${obj2.className}_${obj2.id}`), true); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${obj3.className}_${obj3.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${obj1.className}_${obj1.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${obj2.className}_${obj2.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${obj3.className}_${obj3.id}`), true); - assert(localDatastore[`${obj1.className}_${obj1.id}`]); - assert(localDatastore[`${obj2.className}_${obj2.id}`]); - assert(localDatastore[`${obj3.className}_${obj3.id}`]); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(obj1)), true); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(obj2)), true); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(obj3)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(obj1)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(obj2)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(obj3)), true); + assert(localDatastore[LDS_KEY(obj1)]); + assert(localDatastore[LDS_KEY(obj2)]); + assert(localDatastore[LDS_KEY(obj3)]); await obj1.unPin(); localDatastore = await Parse.LocalDatastore._getAllContents(); - assert(localDatastore[`${obj1.className}_${obj1.id}`]); + assert(localDatastore[LDS_KEY(obj1)]); }); it(`${controller.name} can unPin and save reference with children`, async () => { @@ -612,24 +658,24 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); assert(Object.keys(localDatastore).length === 6); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${obj1.className}_${obj1.id}`), true); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${obj2.className}_${obj2.id}`), true); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${obj3.className}_${obj3.id}`), true); - assert.equal(localDatastore[DEFAULT_PIN].includes(`${child.className}_${child.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${obj1.className}_${obj1.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${obj2.className}_${obj2.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${obj3.className}_${obj3.id}`), true); - assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(`${child.className}_${child.id}`), true); - assert(localDatastore[`${obj1.className}_${obj1.id}`]); - assert(localDatastore[`${obj2.className}_${obj2.id}`]); - assert(localDatastore[`${obj3.className}_${obj3.id}`]); - assert(localDatastore[`${child.className}_${child.id}`]); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(obj1)), true); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(obj2)), true); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(obj3)), true); + assert.equal(localDatastore[DEFAULT_PIN].includes(LDS_KEY(child)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(obj1)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(obj2)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(obj3)), true); + assert.equal(localDatastore[PIN_PREFIX + 'test_pin'].includes(LDS_KEY(child)), true); + assert(localDatastore[LDS_KEY(obj1)]); + assert(localDatastore[LDS_KEY(obj2)]); + assert(localDatastore[LDS_KEY(obj3)]); + assert(localDatastore[LDS_KEY(child)]); await obj1.unPin(); localDatastore = await Parse.LocalDatastore._getAllContents(); - assert(localDatastore[`${obj1.className}_${obj1.id}`]); - assert(localDatastore[`${child.className}_${child.id}`]); + assert(localDatastore[LDS_KEY(obj1)]); + assert(localDatastore[LDS_KEY(child)]); }); it(`${controller.name} cannot fetchFromLocalDatastore (unsaved)`, async () => { @@ -756,7 +802,7 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); - assert.equal(localDatastore[`Item_${item.id}`].foo, 'bar'); + assert.equal(localDatastore[LDS_KEY(item)][0].foo, 'bar'); const itemAgain = new Item(); itemAgain.id = item.id; @@ -766,7 +812,7 @@ function runTest(controller) { assert.equal(itemAgain.get('foo'), 'changed'); assert.equal(fetchedItem.get('foo'), 'changed'); - assert.equal(localDatastore[`Item_${item.id}`].foo, 'changed'); + assert.equal(localDatastore[LDS_KEY(item)][0].foo, 'changed'); }); it('fetchAll updates LocalDatastore', async () => { @@ -783,8 +829,8 @@ function runTest(controller) { let localDatastore = await Parse.LocalDatastore._getAllContents(); - assert.equal(localDatastore[`Item_${item1.id}`].foo, 'bar'); - assert.equal(localDatastore[`Item_${item2.id}`].foo, 'baz'); + assert.equal(localDatastore[LDS_KEY(item1)][0].foo, 'bar'); + assert.equal(localDatastore[LDS_KEY(item2)][0].foo, 'baz'); const item1Again = new Item(); item1Again.id = item1.id; @@ -799,8 +845,8 @@ function runTest(controller) { assert.equal(item2Again.get('foo'), 'changed'); assert.equal(fetchedItems[0].get('foo'), 'changed'); assert.equal(fetchedItems[1].get('foo'), 'changed'); - assert.equal(localDatastore[`Item_${fetchedItems[0].id}`].foo, 'changed'); - assert.equal(localDatastore[`Item_${fetchedItems[1].id}`].foo, 'changed'); + assert.equal(localDatastore[LDS_KEY(fetchedItems[0])][0].foo, 'changed'); + assert.equal(localDatastore[LDS_KEY(fetchedItems[1])][0].foo, 'changed'); }); it(`${controller.name} can update Local Datastore from network`, async () => { @@ -822,7 +868,7 @@ function runTest(controller) { await Parse.LocalDatastore.updateFromServer(); const updatedLDS = await Parse.LocalDatastore._getAllContents(); - const childJSON = updatedLDS[`${child.className}_${child.id}`]; + const childJSON = updatedLDS[LDS_KEY(child)]; assert.equal(childJSON.foo, 'changed'); }); }); @@ -881,10 +927,14 @@ function runTest(controller) { const obj1 = new TestObject({ field: 1 }); const obj2 = new TestObject({ field: 2 }); const obj3 = new TestObject({ field: 3 }); + const obj4 = new TestObject({ field: 4 }); const objects = [obj1, obj2, obj3]; - await Parse.Object.saveAll(objects); + await Parse.Object.saveAll(objects); await Parse.Object.pinAll(objects); + await obj4.save(); + await obj4.pinWithName('DO_NOT_QUERY'); + const query = new Parse.Query(TestObject); query.fromPin(); const results = await query.find(); diff --git a/integration/test/mockRNStorage.js b/integration/test/mockRNStorage.js index 4e09cfac8..9318d2451 100644 --- a/integration/test/mockRNStorage.js +++ b/integration/test/mockRNStorage.js @@ -27,6 +27,16 @@ const mockRNStorage = { cb(undefined, Object.keys(mockStorage)); }, + multiGet(keys, cb) { + const objects = keys.map((key) => [key, mockStorage[key]]); + cb(undefined, objects); + }, + + multiRemove(keys, cb) { + keys.map((key) => delete mockStorage[key]); + cb(undefined); + }, + clear() { mockStorage = {}; }, diff --git a/src/.flowconfig b/src/.flowconfig index 3c28636fc..e36f40442 100644 --- a/src/.flowconfig +++ b/src/.flowconfig @@ -1,11 +1,10 @@ [ignore] -.*/node_modules/.* +.*/node_modules/ +.*/lib/ [include] -../package.json [libs] -interfaces/ [options] -unsafe.enable_getters_and_setters=true +suppress_comment= \\(.\\|\n\\)*\\@flow-disable-next diff --git a/src/LocalDatastore.js b/src/LocalDatastore.js index 3c6e3a153..0afe3c6d3 100644 --- a/src/LocalDatastore.js +++ b/src/LocalDatastore.js @@ -13,10 +13,28 @@ import CoreManager from './CoreManager'; import type ParseObject from './ParseObject'; import ParseQuery from './ParseQuery'; +import { DEFAULT_PIN, PIN_PREFIX, OBJECT_PREFIX } from './LocalDatastoreUtils'; -const DEFAULT_PIN = '_default'; -const PIN_PREFIX = 'parsePin_'; - +/** + * Provides a local datastore which can be used to store and retrieve Parse.Object.
+ * To enable this functionality, call Parse.enableLocalDatastore(). + * + * Pin object to add to local datastore + * + *
await object.pin();
+ *
await object.pinWithName('pinName');
+ * + * Query pinned objects + * + *
query.fromLocalDatastore();
+ *
query.fromPin();
+ *
query.fromPinWithName();
+ * + *
const localObjects = await query.find();
+ * + * @class Parse.LocalDatastore + * @static + */ const LocalDatastore = { fromPinWithName(name: string): Promise { const controller = CoreManager.getLocalDatastoreController(); @@ -38,6 +56,12 @@ const LocalDatastore = { return controller.getAllContents(); }, + // Use for testing + _getRawStorage(): Promise { + const controller = CoreManager.getLocalDatastoreController(); + return controller.getRawStorage(); + }, + _clear(): Promise { const controller = CoreManager.getLocalDatastoreController(); return controller.clear(); @@ -45,37 +69,49 @@ const LocalDatastore = { // Pin the object and children recursively // Saves the object and children key to Pin Name - async _handlePinWithName(name: string, object: ParseObject): Promise { + async _handlePinAllWithName(name: string, objects: Array): Promise { const pinName = this.getPinName(name); - const objects = this._getChildren(object); - objects[this.getKeyForObject(object)] = object._toFullJSON(); - for (const objectKey in objects) { - await this.pinWithName(objectKey, objects[objectKey]); + const toPinPromises = []; + const objectKeys = []; + for (const parent of objects) { + const children = this._getChildren(parent); + const parentKey = this.getKeyForObject(parent); + children[parentKey] = parent._toFullJSON(); + for (const objectKey in children) { + objectKeys.push(objectKey); + toPinPromises.push(this.pinWithName(objectKey, [children[objectKey]])); + } } - const pinned = await this.fromPinWithName(pinName) || []; - const objectIds = Object.keys(objects); - const toPin = [...new Set([...pinned, ...objectIds])]; - await this.pinWithName(pinName, toPin); + const fromPinPromise = this.fromPinWithName(pinName); + const [pinned] = await Promise.all([fromPinPromise, toPinPromises]); + const toPin = [...new Set([...(pinned || []), ...objectKeys])]; + return this.pinWithName(pinName, toPin); }, // Removes object and children keys from pin name // Keeps the object and children pinned - async _handleUnPinWithName(name: string, object: ParseObject) { + async _handleUnPinAllWithName(name: string, objects: Array) { const localDatastore = await this._getAllContents(); const pinName = this.getPinName(name); - const objects = this._getChildren(object); - const objectIds = Object.keys(objects); - objectIds.push(this.getKeyForObject(object)); + const promises = []; + let objectKeys = []; + for (const parent of objects) { + const children = this._getChildren(parent); + const parentKey = this.getKeyForObject(parent); + objectKeys.push(parentKey, ...Object.keys(children)); + } + objectKeys = [...new Set(objectKeys)]; + let pinned = localDatastore[pinName] || []; - pinned = pinned.filter(item => !objectIds.includes(item)); + pinned = pinned.filter(item => !objectKeys.includes(item)); if (pinned.length == 0) { - await this.unPinWithName(pinName); + promises.push(this.unPinWithName(pinName)); delete localDatastore[pinName]; } else { - await this.pinWithName(pinName, pinned); + promises.push(this.pinWithName(pinName, pinned)); localDatastore[pinName] = pinned; } - for (const objectKey of objectIds) { + for (const objectKey of objectKeys) { let hasReference = false; for (const key in localDatastore) { if (key === DEFAULT_PIN || key.startsWith(PIN_PREFIX)) { @@ -87,9 +123,10 @@ const LocalDatastore = { } } if (!hasReference) { - await this.unPinWithName(objectKey); + promises.push(this.unPinWithName(objectKey)); } } + return Promise.all(promises); }, // Retrieve all pointer fields from object recursively @@ -130,20 +167,22 @@ const LocalDatastore = { const localDatastore = await this._getAllContents(); const allObjects = []; for (const key in localDatastore) { - if (key !== DEFAULT_PIN && !key.startsWith(PIN_PREFIX)) { - allObjects.push(localDatastore[key]); + if (key.startsWith(OBJECT_PREFIX)) { + allObjects.push(localDatastore[key][0]); } } if (!name) { - return Promise.resolve(allObjects); + return allObjects; } - const pinName = await this.getPinName(name); - const pinned = await this.fromPinWithName(pinName); + const pinName = this.getPinName(name); + const pinned = localDatastore[pinName]; if (!Array.isArray(pinned)) { - return Promise.resolve([]); + return []; } - const objects = pinned.map(async (objectKey) => await this.fromPinWithName(objectKey)); - return Promise.all(objects); + const promises = pinned.map((objectKey) => this.fromPinWithName(objectKey)); + let objects = await Promise.all(promises); + objects = [].concat(...objects); + return objects.filter(object => object != null); }, // Replaces object pointers with pinned pointers @@ -154,10 +193,10 @@ const LocalDatastore = { if (!LDS) { LDS = await this._getAllContents(); } - const root = LDS[objectKey]; - if (!root) { + if (!LDS[objectKey] || LDS[objectKey].length === 0) { return null; } + const root = LDS[objectKey][0]; const queue = []; const meta = {}; @@ -172,8 +211,8 @@ const LocalDatastore = { const value = subTreeRoot[field]; if (value.__type && value.__type === 'Object') { const key = this.getKeyForObject(value); - const pointer = LDS[key]; - if (pointer) { + if (LDS[key] && LDS[key].length > 0) { + const pointer = LDS[key][0]; uniqueId++; meta[uniqueId] = pointer; subTreeRoot[field] = pointer; @@ -187,15 +226,16 @@ const LocalDatastore = { // Called when an object is save / fetched // Update object pin value - async _updateObjectIfPinned(object: ParseObject) { + async _updateObjectIfPinned(object: ParseObject): Promise { if (!this.isEnabled) { return; } const objectKey = this.getKeyForObject(object); const pinned = await this.fromPinWithName(objectKey); - if (pinned) { - await this.pinWithName(objectKey, object._toFullJSON()); + if (!pinned || pinned.length === 0) { + return; } + return this.pinWithName(objectKey, [object._toFullJSON()]); }, // Called when object is destroyed @@ -211,7 +251,9 @@ const LocalDatastore = { if (!pin) { return; } - await this.unPinWithName(objectKey); + const promises = [ + this.unPinWithName(objectKey) + ]; delete localDatastore[objectKey]; for (const key in localDatastore) { @@ -220,15 +262,16 @@ const LocalDatastore = { if (pinned.includes(objectKey)) { pinned = pinned.filter(item => item !== objectKey); if (pinned.length == 0) { - await this.unPinWithName(key); + promises.push(this.unPinWithName(key)); delete localDatastore[key]; } else { - await this.pinWithName(key, pinned); + promises.push(this.pinWithName(key, pinned)); localDatastore[key] = pinned; } } } } + return Promise.all(promises); }, // Update pin and references of the unsaved object @@ -236,15 +279,17 @@ const LocalDatastore = { if (!this.isEnabled) { return; } - const localKey = `${object.className}_${localId}`; + const localKey = `${OBJECT_PREFIX}${object.className}_${localId}`; const objectKey = this.getKeyForObject(object); const unsaved = await this.fromPinWithName(localKey); - if (!unsaved) { + if (!unsaved || unsaved.length === 0) { return; } - await this.unPinWithName(localKey); - await this.pinWithName(objectKey, unsaved); + const promises = [ + this.unPinWithName(localKey), + this.pinWithName(objectKey, unsaved), + ]; const localDatastore = await this._getAllContents(); for (const key in localDatastore) { @@ -253,11 +298,12 @@ const LocalDatastore = { if (pinned.includes(localKey)) { pinned = pinned.filter(item => item !== localKey); pinned.push(objectKey); - await this.pinWithName(key, pinned); + promises.push(this.pinWithName(key, pinned)); localDatastore[key] = pinned; } } } + return Promise.all(promises); }, /** @@ -266,7 +312,8 @@ const LocalDatastore = { *
    * await Parse.LocalDatastore.updateFromServer();
    * 
- * + * @method updateFromServer + * @name Parse.LocalDatastore.updateFromServer * @static */ async updateFromServer() { @@ -276,7 +323,7 @@ const LocalDatastore = { const localDatastore = await this._getAllContents(); const keys = []; for (const key in localDatastore) { - if (key !== DEFAULT_PIN && !key.startsWith(PIN_PREFIX)) { + if (key.startsWith(OBJECT_PREFIX)) { keys.push(key); } } @@ -286,7 +333,8 @@ const LocalDatastore = { this.isSyncing = true; const pointersHash = {}; for (const key of keys) { - const [className, objectId] = key.split('_'); + // Ignore the OBJECT_PREFIX + const [ , , className, objectId] = key.split('_'); if (!(className in pointersHash)) { pointersHash[className] = new Set(); } @@ -313,15 +361,14 @@ const LocalDatastore = { await Promise.all(pinPromises); this.isSyncing = false; } catch(error) { - console.log('Error syncing LocalDatastore'); // eslint-disable-line - console.log(error); // eslint-disable-line + console.error('Error syncing LocalDatastore: ', error); this.isSyncing = false; } }, getKeyForObject(object: any) { const objectId = object.objectId || object._getId(); - return `${object.className}_${objectId}`; + return `${OBJECT_PREFIX}${object.className}_${objectId}`; }, getPinName(pinName: ?string) { @@ -333,14 +380,12 @@ const LocalDatastore = { checkIfEnabled() { if (!this.isEnabled) { - console.log('Parse.enableLocalDatastore() must be called first'); // eslint-disable-line no-console + console.error('Parse.enableLocalDatastore() must be called first'); } return this.isEnabled; } }; -LocalDatastore.DEFAULT_PIN = DEFAULT_PIN; -LocalDatastore.PIN_PREFIX = PIN_PREFIX; LocalDatastore.isEnabled = false; LocalDatastore.isSyncing = false; diff --git a/src/LocalDatastoreController.browser.js b/src/LocalDatastoreController.browser.js index 3212fdceb..5b7333386 100644 --- a/src/LocalDatastoreController.browser.js +++ b/src/LocalDatastoreController.browser.js @@ -10,44 +10,64 @@ */ /* global localStorage */ +import { isLocalDatastoreKey } from './LocalDatastoreUtils'; const LocalDatastoreController = { - fromPinWithName(name: string): Promise { + fromPinWithName(name: string): Array { const values = localStorage.getItem(name); if (!values) { - return Promise.resolve(null); + return []; } const objects = JSON.parse(values); - return Promise.resolve(objects); + return objects; }, - pinWithName(name: string, value: any): Promise { + pinWithName(name: string, value: any) { try { const values = JSON.stringify(value); localStorage.setItem(name, values); } catch (e) { // Quota exceeded, possibly due to Safari Private Browsing mode - console.log(e.message); // eslint-disable-line no-console + console.log(e.message); } - return Promise.resolve(); }, - unPinWithName(name: string): Promise { - return Promise.resolve(localStorage.removeItem(name)); + unPinWithName(name: string) { + localStorage.removeItem(name); }, - getAllContents(): Promise { + getAllContents(): Object { const LDS = {}; + for (let i = 0; i < localStorage.length; i += 1) { + const key = localStorage.key(i); + if (isLocalDatastoreKey(key)) { + const value = localStorage.getItem(key); + LDS[key] = JSON.parse(value); + } + } + return LDS; + }, + + getRawStorage(): Object { + const storage = {}; for (let i = 0; i < localStorage.length; i += 1) { const key = localStorage.key(i); const value = localStorage.getItem(key); - LDS[key] = JSON.parse(value); + storage[key] = value; } - return Promise.resolve(LDS); + return storage; }, clear(): Promise { - return Promise.resolve(localStorage.clear()); + const toRemove = []; + for (let i = 0; i < localStorage.length; i += 1) { + const key = localStorage.key(i); + if (isLocalDatastoreKey(key)) { + toRemove.push(key); + } + } + const promises = toRemove.map(localStorage.removeItem); + return Promise.all(promises); } }; diff --git a/src/LocalDatastoreController.default.js b/src/LocalDatastoreController.default.js index c788247e4..524472169 100644 --- a/src/LocalDatastoreController.default.js +++ b/src/LocalDatastoreController.default.js @@ -8,18 +8,21 @@ * * @flow */ +import { isLocalDatastoreKey } from './LocalDatastoreUtils'; const memMap = {}; const LocalDatastoreController = { - fromPinWithName(name: string): ?any { - if (memMap.hasOwnProperty(name)) { - return memMap[name]; + fromPinWithName(name: string): Array { + if (!memMap.hasOwnProperty(name)) { + return []; } - return null; + const objects = JSON.parse(memMap[name]); + return objects; }, pinWithName(name: string, value: any) { - memMap[name] = value; + const values = JSON.stringify(value); + memMap[name] = values; }, unPinWithName(name: string) { @@ -27,12 +30,22 @@ const LocalDatastoreController = { }, getAllContents() { + const LDS = {}; + for (const key in memMap) { + if (memMap.hasOwnProperty(key) && isLocalDatastoreKey(key)) { + LDS[key] = JSON.parse(memMap[key]); + } + } + return LDS; + }, + + getRawStorage() { return memMap; }, clear() { for (const key in memMap) { - if (memMap.hasOwnProperty(key)) { + if (memMap.hasOwnProperty(key) && isLocalDatastoreKey(key)) { delete memMap[key]; } } diff --git a/src/LocalDatastoreController.react-native.js b/src/LocalDatastoreController.react-native.js index 62c6f1dfc..00ef16ff9 100644 --- a/src/LocalDatastoreController.react-native.js +++ b/src/LocalDatastoreController.react-native.js @@ -10,15 +10,16 @@ */ const RNStorage = require('./StorageController.react-native'); +import { isLocalDatastoreKey } from './LocalDatastoreUtils'; const LocalDatastoreController = { - async fromPinWithName(name: string): Promise { + async fromPinWithName(name: string): Promise> { const values = await RNStorage.getItemAsync(name); if (!values) { - return Promise.resolve(null); + return []; } const objects = JSON.parse(values); - return Promise.resolve(objects); + return objects; }, async pinWithName(name: string, value: any): Promise { @@ -27,29 +28,63 @@ const LocalDatastoreController = { await RNStorage.setItemAsync(name, values); } catch (e) { // Quota exceeded, possibly due to Safari Private Browsing mode - console.log(e.message); // eslint-disable-line no-console + console.error(e.message); } - return Promise.resolve(); }, - async unPinWithName(name: string): Promise { - await RNStorage.removeItemAsync(name); - return Promise.resolve(); + unPinWithName(name: string): Promise { + return RNStorage.removeItemAsync(name); }, async getAllContents(): Promise { - const LDS = {}; const keys = await RNStorage.getAllKeys(); + const batch = []; for (let i = 0; i < keys.length; i += 1) { const key = keys[i]; - const value = await RNStorage.getItemAsync(key); - LDS[key] = JSON.parse(value); + if (isLocalDatastoreKey(key)) { + batch.push(key); + } + } + const LDS = {}; + let results = []; + try { + results = await RNStorage.multiGet(batch); + } catch (error) { + console.error('Error getAllContents: ', error); + return {}; } - return Promise.resolve(LDS); + results.forEach((pair) => { + const [key, value] = pair; + try { + LDS[key] = JSON.parse(value); + } catch (error) { + LDS[key] = null; + } + }); + return LDS; }, - clear(): Promise { - return RNStorage.clear(); + async getRawStorage(): Promise { + const keys = await RNStorage.getAllKeys(); + const storage = {}; + const results = await RNStorage.multiGet(keys); + results.map((pair) => { + const [key, value] = pair; + storage[key] = value; + }); + return storage; + }, + + async clear(): Promise { + const keys = await RNStorage.getAllKeys(); + const batch = []; + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; + if (isLocalDatastoreKey(key)) { + batch.push(key); + } + } + return RNStorage.multiRemove(batch).catch(error => console.error('Error clearing local datastore: ', error)); } }; diff --git a/src/LocalDatastoreUtils.js b/src/LocalDatastoreUtils.js new file mode 100644 index 000000000..e35055d82 --- /dev/null +++ b/src/LocalDatastoreUtils.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ +const DEFAULT_PIN = '_default'; +const PIN_PREFIX = 'parsePin_'; +const OBJECT_PREFIX = 'Parse_LDS_'; + +function isLocalDatastoreKey(key: string): boolean { + return !!(key && (key === DEFAULT_PIN || key.startsWith(PIN_PREFIX) || key.startsWith(OBJECT_PREFIX))); +} + +export { + DEFAULT_PIN, + PIN_PREFIX, + OBJECT_PREFIX, + isLocalDatastoreKey, +}; diff --git a/src/ParseObject.js b/src/ParseObject.js index 678f6457c..90c7fb780 100644 --- a/src/ParseObject.js +++ b/src/ParseObject.js @@ -19,6 +19,7 @@ import parseDate from './parseDate'; import ParseError from './ParseError'; import ParseFile from './ParseFile'; import { when, continueWhile } from './promiseUtils'; +import { DEFAULT_PIN, PIN_PREFIX } from './LocalDatastoreUtils'; import { opFromJSON, @@ -1212,10 +1213,11 @@ class ParseObject { * * To retrieve object: * query.fromLocalDatastore() or query.fromPin() + * + * @return {Promise} A promise that is fulfilled when the pin completes. */ pin(): Promise { - const localDatastore = CoreManager.getLocalDatastore(); - return ParseObject.pinAllWithName(localDatastore.DEFAULT_PIN, [this]); + return ParseObject.pinAllWithName(DEFAULT_PIN, [this]); } /** @@ -1225,10 +1227,11 @@ class ParseObject { *
    * await object.unPin();
    * 
+ * + * @return {Promise} A promise that is fulfilled when the unPin completes. */ unPin(): Promise { - const localDatastore = CoreManager.getLocalDatastore(); - return ParseObject.unPinAllWithName(localDatastore.DEFAULT_PIN, [this]); + return ParseObject.unPinAllWithName(DEFAULT_PIN, [this]); } /** @@ -1237,17 +1240,17 @@ class ParseObject { *
    * const isPinned = await object.isPinned();
    * 
+ * + * @return {Promise} A boolean promise that is fulfilled if object is pinned. */ - async isPinned(): Promise { + async isPinned(): Promise { const localDatastore = CoreManager.getLocalDatastore(); - if (localDatastore.checkIfEnabled()) { - const objectKey = localDatastore.getKeyForObject(this); - const pin = await localDatastore.fromPinWithName(objectKey); - if (pin) { - return Promise.resolve(true); - } + if (!localDatastore.isEnabled) { + return Promise.reject('Parse.enableLocalDatastore() must be called first'); } - return Promise.resolve(false); + const objectKey = localDatastore.getKeyForObject(this); + const pin = await localDatastore.fromPinWithName(objectKey); + return pin.length > 0; } /** @@ -1264,6 +1267,7 @@ class ParseObject { * query.fromLocalDatastore() or query.fromPinWithName(name) * * @param {String} name Name of Pin. + * @return {Promise} A promise that is fulfilled when the pin completes. */ pinWithName(name: string): Promise { return ParseObject.pinAllWithName(name, [this]); @@ -1277,6 +1281,7 @@ class ParseObject { * * * @param {String} name Name of Pin. + * @return {Promise} A promise that is fulfilled when the unPin completes. */ unPinWithName(name: string): Promise { return ParseObject.unPinAllWithName(name, [this]); @@ -1291,20 +1296,23 @@ class ParseObject { * * You can create an unfetched pointer with Parse.Object.createWithoutData() * and then call fetchFromLocalDatastore() on it. + * + * @return {Promise} A promise that is fulfilled when the fetch completes. */ async fetchFromLocalDatastore(): Promise { const localDatastore = CoreManager.getLocalDatastore(); - if (localDatastore.checkIfEnabled()) { - const objectKey = localDatastore.getKeyForObject(this); - const pinned = await localDatastore._serializeObject(objectKey); - if (!pinned) { - throw new Error('Cannot fetch an unsaved ParseObject'); - } - const result = ParseObject.fromJSON(pinned); - this._finishFetch(result.toJSON()); - - return Promise.resolve(this); + if (!localDatastore.isEnabled) { + throw new Error('Parse.enableLocalDatastore() must be called first'); } + const objectKey = localDatastore.getKeyForObject(this); + const pinned = await localDatastore._serializeObject(objectKey); + if (!pinned) { + throw new Error('Cannot fetch an unsaved ParseObject'); + } + const result = ParseObject.fromJSON(pinned); + this._finishFetch(result.toJSON()); + + return this; } /** Static methods **/ @@ -1797,13 +1805,15 @@ class ParseObject { * query.fromLocalDatastore() or query.fromPin() * * @param {Array} objects A list of Parse.Object. + * @return {Promise} A promise that is fulfilled when the pin completes. * @static */ static pinAll(objects: Array): Promise { const localDatastore = CoreManager.getLocalDatastore(); - if (localDatastore.checkIfEnabled()) { - return ParseObject.pinAllWithName(localDatastore.DEFAULT_PIN, objects); + if (!localDatastore.isEnabled) { + return Promise.reject('Parse.enableLocalDatastore() must be called first'); } + return ParseObject.pinAllWithName(DEFAULT_PIN, objects); } /** @@ -1821,15 +1831,15 @@ class ParseObject { * * @param {String} name Name of Pin. * @param {Array} objects A list of Parse.Object. + * @return {Promise} A promise that is fulfilled when the pin completes. * @static */ - static async pinAllWithName(name: string, objects: Array): Promise { + static pinAllWithName(name: string, objects: Array): Promise { const localDatastore = CoreManager.getLocalDatastore(); - if (localDatastore.checkIfEnabled()) { - for (const object of objects) { - await localDatastore._handlePinWithName(name, object); - } + if (!localDatastore.isEnabled) { + return Promise.reject('Parse.enableLocalDatastore() must be called first'); } + return localDatastore._handlePinAllWithName(name, objects); } /** @@ -1841,13 +1851,15 @@ class ParseObject { * * * @param {Array} objects A list of Parse.Object. + * @return {Promise} A promise that is fulfilled when the unPin completes. * @static */ static unPinAll(objects: Array): Promise { const localDatastore = CoreManager.getLocalDatastore(); - if (localDatastore.checkIfEnabled()) { - return ParseObject.unPinAllWithName(localDatastore.DEFAULT_PIN, objects); + if (!localDatastore.isEnabled) { + return Promise.reject('Parse.enableLocalDatastore() must be called first'); } + return ParseObject.unPinAllWithName(DEFAULT_PIN, objects); } /** @@ -1859,15 +1871,15 @@ class ParseObject { * * @param {String} name Name of Pin. * @param {Array} objects A list of Parse.Object. + * @return {Promise} A promise that is fulfilled when the unPin completes. * @static */ - static async unPinAllWithName(name: string, objects: Array): Promise { + static unPinAllWithName(name: string, objects: Array): Promise { const localDatastore = CoreManager.getLocalDatastore(); - if (localDatastore.checkIfEnabled()) { - for (const object of objects) { - await localDatastore._handleUnPinWithName(name, object); - } + if (!localDatastore.isEnabled) { + return Promise.reject('Parse.enableLocalDatastore() must be called first'); } + return localDatastore._handleUnPinAllWithName(name, objects); } /** @@ -1877,13 +1889,15 @@ class ParseObject { * await Parse.Object.unPinAllObjects(); * * + * @return {Promise} A promise that is fulfilled when the unPin completes. * @static */ static unPinAllObjects(): Promise { const localDatastore = CoreManager.getLocalDatastore(); - if (localDatastore.checkIfEnabled()) { - return localDatastore.unPinWithName(localDatastore.DEFAULT_PIN); + if (!localDatastore.isEnabled) { + return Promise.reject('Parse.enableLocalDatastore() must be called first'); } + return localDatastore.unPinWithName(DEFAULT_PIN); } /** @@ -1895,13 +1909,15 @@ class ParseObject { * * * @param {String} name Name of Pin. + * @return {Promise} A promise that is fulfilled when the unPin completes. * @static */ static unPinAllObjectsWithName(name: string): Promise { const localDatastore = CoreManager.getLocalDatastore(); - if (localDatastore.checkIfEnabled()) { - return localDatastore.unPinWithName(localDatastore.PIN_PREFIX + name); + if (!localDatastore.isEnabled) { + return Promise.reject('Parse.enableLocalDatastore() must be called first'); } + return localDatastore.unPinWithName(PIN_PREFIX + name); } } diff --git a/src/ParseQuery.js b/src/ParseQuery.js index 2a3d4003d..47439c511 100644 --- a/src/ParseQuery.js +++ b/src/ParseQuery.js @@ -16,6 +16,7 @@ import ParseError from './ParseError'; import ParseGeoPoint from './ParseGeoPoint'; import ParseObject from './ParseObject'; import OfflineQuery from './OfflineQuery'; +import { DEFAULT_PIN } from './LocalDatastoreUtils'; import type LiveQuerySubscription from './LiveQuerySubscription'; import type { RequestOptions, FullOptions } from './RESTController'; @@ -1551,28 +1552,35 @@ class ParseQuery { /** * Changes the source of this query to all pinned objects. + * + * @return {Parse.Query} Returns the query, so you can chain this call. */ - fromLocalDatastore() { - this.fromPinWithName(null); + fromLocalDatastore(): ParseQuery { + return this.fromPinWithName(null); } /** * Changes the source of this query to the default group of pinned objects. + * + * @return {Parse.Query} Returns the query, so you can chain this call. */ - fromPin() { - const localDatastore = CoreManager.getLocalDatastore(); - this.fromPinWithName(localDatastore.DEFAULT_PIN); + fromPin(): ParseQuery { + return this.fromPinWithName(DEFAULT_PIN); } /** * Changes the source of this query to a specific group of pinned objects. + * + * @param {String} name The name of query source. + * @return {Parse.Query} Returns the query, so you can chain this call. */ - fromPinWithName(name: string) { + fromPinWithName(name: string): ParseQuery { const localDatastore = CoreManager.getLocalDatastore(); if (localDatastore.checkIfEnabled()) { this._queriesLocalDatastore = true; this._localDatastorePinName = name; } + return this; } } diff --git a/src/StorageController.react-native.js b/src/StorageController.react-native.js index ad51fc773..dbb3f1b4e 100644 --- a/src/StorageController.react-native.js +++ b/src/StorageController.react-native.js @@ -66,6 +66,30 @@ const StorageController = { }); }, + multiGet(keys: Array): Promise>> { + return new Promise((resolve, reject) => { + this.getAsyncStorage().multiGet(keys, function(err, result) { + if (err) { + reject(err); + } else { + resolve(result); + } + }); + }); + }, + + multiRemove(keys: Array): Promise { + return new Promise((resolve, reject) => { + this.getAsyncStorage().multiRemove(keys, function(err) { + if (err) { + reject(err); + } else { + resolve(keys); + } + }); + }); + }, + clear() { return this.getAsyncStorage().clear(); } diff --git a/src/__tests__/LocalDatastore-test.js b/src/__tests__/LocalDatastore-test.js index debdbf912..6c326ef4c 100644 --- a/src/__tests__/LocalDatastore-test.js +++ b/src/__tests__/LocalDatastore-test.js @@ -8,6 +8,7 @@ */ jest.autoMockOff(); +jest.unmock('../LocalDatastoreUtils'); const encode = require('../encode').default; @@ -105,6 +106,20 @@ const mockLocalStorage = require('./test_helpers/mockLocalStorage'); global.localStorage = mockLocalStorage; +const item1 = new ParseObject('Item'); +const item2 = new ParseObject('Item'); +const item3 = new ParseObject('Item'); + +item1.id = '1'; +item2.id = '2'; +item3.id = '3'; + +const KEY1 = LocalDatastore.getKeyForObject(item1); +const KEY2 = LocalDatastore.getKeyForObject(item2); +const KEY3 = LocalDatastore.getKeyForObject(item2); + +import { DEFAULT_PIN, PIN_PREFIX, OBJECT_PREFIX, isLocalDatastoreKey } from '../LocalDatastoreUtils'; + describe('LocalDatastore', () => { beforeEach(() => { CoreManager.setLocalDatastoreController(mockLocalStorageController); @@ -119,7 +134,7 @@ describe('LocalDatastore', () => { }); it('isDisabled', () => { - const spy = jest.spyOn(console, 'log'); + const spy = jest.spyOn(console, 'error'); LocalDatastore.isEnabled = false; const isEnabled = LocalDatastore.checkIfEnabled(); expect(isEnabled).toBe(false); @@ -140,107 +155,102 @@ describe('LocalDatastore', () => { expect(mockLocalStorageController.getAllContents).toHaveBeenCalledTimes(1); }); - it('_handlePinWithName no children', async () => { + it('_handlePinAllWithName no children', async () => { const object = new ParseObject('Item'); - await LocalDatastore._handlePinWithName('test_pin', object); + await LocalDatastore._handlePinAllWithName('test_pin', [object]); expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(2); }); - it('_handlePinWithName default pin', async () => { + it('_handlePinAllWithName default pin', async () => { const object = new ParseObject('Item'); - await LocalDatastore._handlePinWithName(LocalDatastore.DEFAULT_PIN, object); + await LocalDatastore._handlePinAllWithName(DEFAULT_PIN, [object]); expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(2); }); - it('_handlePinWithName unsaved children', async () => { + it('_handlePinAllWithName unsaved children', async () => { const parent = new ParseObject('Item'); const unsaved = { className: 'Item', __type: 'Object' }; parent.set('child', unsaved); - await LocalDatastore._handlePinWithName('test_pin', parent); + await LocalDatastore._handlePinAllWithName('test_pin', [parent]); expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(2); }); - it('_handlePinWithName with children', async () => { + it('_handlePinAllWithName with children', async () => { const parent = new ParseObject('Item'); const child = new ParseObject('Item'); const grandchild = new ParseObject('Item'); child.set('grandchild', grandchild); parent.set('child', child); - await LocalDatastore._handlePinWithName('test_pin', parent); + await LocalDatastore._handlePinAllWithName('test_pin', [parent]); expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(4); }); - it('_handleUnPinWithName default pin', async () => { - const object = new ParseObject('Item'); + it('_handleUnPinAllWithName default pin', async () => { const LDS = { - [LocalDatastore.DEFAULT_PIN]: [`Item_${object._getId()}`, '1234'], + [DEFAULT_PIN]: [KEY1, KEY2], }; mockLocalStorageController .getAllContents .mockImplementationOnce(() => LDS); - await LocalDatastore._handleUnPinWithName(LocalDatastore.DEFAULT_PIN, object); + await LocalDatastore._handleUnPinAllWithName(DEFAULT_PIN, [item1]); expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(LocalDatastore.DEFAULT_PIN, ['1234']); + expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(DEFAULT_PIN, [KEY2]); }); - it('_handleUnPinWithName specific pin', async () => { - const object = new ParseObject('Item'); + it('_handleUnPinAllWithName specific pin', async () => { const LDS = { - parsePin_test_pin: [`Item_${object._getId()}`, '1234'], + parsePin_test_pin: [KEY1, KEY2], }; mockLocalStorageController .getAllContents .mockImplementationOnce(() => LDS); - await LocalDatastore._handleUnPinWithName('test_pin', object); + await LocalDatastore._handleUnPinAllWithName('test_pin', [item1]); expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(LocalDatastore.PIN_PREFIX + 'test_pin', ['1234']); + expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(PIN_PREFIX + 'test_pin', [KEY2]); }); - it('_handleUnPinWithName default pin remove pinName', async () => { + it('_handleUnPinAllWithName default pin remove pinName', async () => { const object = new ParseObject('Item'); const LDS = { - [LocalDatastore.DEFAULT_PIN]: [], + [DEFAULT_PIN]: [], }; mockLocalStorageController .getAllContents .mockImplementationOnce(() => LDS); - await LocalDatastore._handleUnPinWithName(LocalDatastore.DEFAULT_PIN, object); + await LocalDatastore._handleUnPinAllWithName(DEFAULT_PIN, [object]); expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledTimes(2); - expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(LocalDatastore.DEFAULT_PIN); + expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(DEFAULT_PIN); }); - it('_handleUnPinWithName specific pin remove pinName', async () => { + it('_handleUnPinAllWithName specific pin remove pinName', async () => { const object = new ParseObject('Item'); const LDS = { - [LocalDatastore.PIN_PREFIX + 'test_pin']: [], + [PIN_PREFIX + 'test_pin']: [], }; mockLocalStorageController .getAllContents .mockImplementationOnce(() => LDS); - await LocalDatastore._handleUnPinWithName('test_pin', object); + await LocalDatastore._handleUnPinAllWithName('test_pin', [object]); expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledTimes(2); - expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(LocalDatastore.PIN_PREFIX + 'test_pin'); + expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(PIN_PREFIX + 'test_pin'); }); - it('_handleUnPinWithName remove if exist', async () => { - const obj1 = new ParseObject('Item'); - const obj2 = new ParseObject('Item'); - const obj3 = new ParseObject('Item'); - const objects = [`Item_${obj1.id}`, `Item_${obj2.id}`, `Item_${obj3.id}`]; + it('_handleUnPinAllWithName remove if exist', async () => { + const objects = [KEY1, KEY2, KEY3]; const LDS = { - [LocalDatastore.PIN_PREFIX + 'test_pin']: objects, - [LocalDatastore.PIN_PREFIX + 'test_pin_2']: objects, - [`Item_${obj1.id}`]: obj1._toFullJSON(), - [`Item_${obj2.id}`]: obj2._toFullJSON(), + [PIN_PREFIX + 'test_pin']: objects, + [PIN_PREFIX + 'test_pin_2']: objects, + [KEY1]: [item1._toFullJSON()], + [KEY2]: [item2._toFullJSON()], } mockLocalStorageController .getAllContents .mockImplementationOnce(() => LDS); - await LocalDatastore._handleUnPinWithName('test_pin', obj1); + await LocalDatastore._handleUnPinAllWithName('test_pin', [item1]); expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(LocalDatastore.PIN_PREFIX + 'test_pin', [`Item_${obj2.id}`, `Item_${obj3.id}`]); + expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(PIN_PREFIX + 'test_pin', [KEY2, KEY3]); }); it('_updateObjectIfPinned not pinned', async () => { @@ -251,25 +261,22 @@ describe('LocalDatastore', () => { }); it('_updateObjectIfPinned if pinned', async () => { - const object = new ParseObject('Item'); mockLocalStorageController .fromPinWithName - .mockImplementationOnce(() => [object]); + .mockImplementationOnce(() => [item1]); LocalDatastore.isEnabled = true; - await LocalDatastore._updateObjectIfPinned(object); + await LocalDatastore._updateObjectIfPinned(item1); expect(mockLocalStorageController.fromPinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.fromPinWithName.mock.results[0].value).toEqual([object]); + expect(mockLocalStorageController.fromPinWithName.mock.results[0].value).toEqual([item1]); expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(`Item_${object.id}`, object._toFullJSON()); + expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(KEY1, [item1._toFullJSON()]); }); it('_updateLocalIdForObject not pinned', async () => { - const object = new ParseObject('Item'); - object.id = '1234' - await LocalDatastore._updateLocalIdForObject('local0', object); + await LocalDatastore._updateLocalIdForObject('local0', item1); expect(mockLocalStorageController.fromPinWithName.mock.results[0].value).toEqual(undefined); }); @@ -277,10 +284,10 @@ describe('LocalDatastore', () => { const object = new ParseObject('Item'); const json = object._toFullJSON(); const localId = 'local' + object.id; - const localKey = `Item_${localId}`; + const localKey = `${OBJECT_PREFIX}Item_${localId}`; const LDS = { - [LocalDatastore.DEFAULT_PIN]: [localKey], - [localKey]: json, + [DEFAULT_PIN]: [localKey], + [localKey]: [json], }; mockLocalStorageController .fromPinWithName @@ -298,10 +305,10 @@ describe('LocalDatastore', () => { const object = new ParseObject('Item'); const json = object._toFullJSON(); const localId = 'local' + object.id; - const localKey = `Item_${localId}`; + const localKey = `${OBJECT_PREFIX}Item_${localId}`; const LDS = { - [LocalDatastore.PIN_PREFIX + 'test_pin']: [localKey], - [localKey]: json, + [PIN_PREFIX + 'test_pin']: [localKey], + [localKey]: [json], }; mockLocalStorageController .fromPinWithName @@ -320,8 +327,8 @@ describe('LocalDatastore', () => { const json = object._toFullJSON(); const localId = 'local' + object.id; const LDS = { - [LocalDatastore.DEFAULT_PIN]: [object.id], - [object.id]: json, + [DEFAULT_PIN]: [object.id], + [object.id]: [json], }; mockLocalStorageController .fromPinWithName @@ -337,20 +344,19 @@ describe('LocalDatastore', () => { expect(mockLocalStorageController.fromPinWithName.mock.results[0].value).toEqual(json); expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(`Item_${localId}`); + expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(`${OBJECT_PREFIX}Item_${localId}`); expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(`Item_${object.id}`, json); + expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(`${OBJECT_PREFIX}Item_${object.id}`, json); expect(mockLocalStorageController.getAllContents).toHaveBeenCalledTimes(1); }); it('_serializeObjectsFromPinName no name returns all objects', async () => { - const object = new ParseObject('Item'); - const json = object._toFullJSON(); + const json = item1._toFullJSON(); const LDS = { - [LocalDatastore.DEFAULT_PIN]: [object.id], - [object.id]: json, + [DEFAULT_PIN]: [KEY1], + [KEY1]: [json], }; mockLocalStorageController @@ -367,8 +373,8 @@ describe('LocalDatastore', () => { const object = new ParseObject('Item'); const json = object._toFullJSON(); const LDS = { - [LocalDatastore.DEFAULT_PIN]: [object.id, 'local10', 'local11'], - [object.id]: json, + [DEFAULT_PIN]: [object.id, 'local10', 'local11'], + [object.id]: [json], randomName: [object.id], }; @@ -376,7 +382,7 @@ describe('LocalDatastore', () => { .getAllContents .mockImplementationOnce(() => LDS); - const results = await LocalDatastore._serializeObjectsFromPinName(LocalDatastore.DEFAULT_PIN); + const results = await LocalDatastore._serializeObjectsFromPinName(DEFAULT_PIN); expect(results).toEqual([]); expect(mockLocalStorageController.getAllContents).toHaveBeenCalledTimes(1); @@ -388,15 +394,14 @@ describe('LocalDatastore', () => { const obj3 = new ParseObject('Item'); const LDS = { - [obj1.id]: obj1._toFullJSON(), - [obj2.id]: obj2._toFullJSON(), - [obj3.id]: obj3._toFullJSON(), - testPin: [obj1.id, obj2.id, obj3.id], + [obj1.id]: [obj1._toFullJSON()], + [obj2.id]: [obj2._toFullJSON()], + [obj3.id]: [obj3._toFullJSON()], + [PIN_PREFIX + 'testPin']: [obj1.id, obj2.id, obj3.id], }; mockLocalStorageController .fromPinWithName - .mockImplementationOnce(() => LDS.testPin) .mockImplementationOnce(() => LDS[obj1.id]) .mockImplementationOnce(() => LDS[obj2.id]) .mockImplementationOnce(() => LDS[obj3.id]); @@ -409,8 +414,7 @@ describe('LocalDatastore', () => { expect(results).toEqual([obj1._toFullJSON(), obj2._toFullJSON(), obj3._toFullJSON()]); expect(mockLocalStorageController.getAllContents).toHaveBeenCalledTimes(1); - - expect(mockLocalStorageController.fromPinWithName).toHaveBeenCalledTimes(4); + expect(mockLocalStorageController.fromPinWithName).toHaveBeenCalledTimes(3); }); it('_serializeObject no children', async () => { @@ -419,8 +423,8 @@ describe('LocalDatastore', () => { const json = object._toFullJSON(); const objectKey = `Item_1234`; const LDS = { - [LocalDatastore.DEFAULT_PIN]: [objectKey], - [objectKey]: json, + [DEFAULT_PIN]: [objectKey], + [objectKey]: [json], }; mockLocalStorageController @@ -446,50 +450,49 @@ describe('LocalDatastore', () => { }); it('_serializeObject with children', async () => { - const object = new ParseObject('Item'); - object.id = 1234; + const parent = new ParseObject('Item'); + parent.id = 1234; const child = new ParseObject('Item'); child.id = 5678; - object.set('child', child); - const newData = child._toFullJSON(); - newData.field = 'Serialize Me'; + parent.set('child', child); + const childJSON = child._toFullJSON(); + childJSON.field = 'Serialize Me'; + + const parentKey = LocalDatastore.getKeyForObject(parent); + const childKey = LocalDatastore.getKeyForObject(child); const LDS = { - [LocalDatastore.DEFAULT_PIN]: [`Item_${object.id}`, `Item_${child.id}`], - [`Item_${object.id}`]: object._toFullJSON(), - [`Item_${child.id}`]: newData, + [DEFAULT_PIN]: [parentKey, childKey], + [parentKey]: [parent._toFullJSON()], + [childKey]: [childJSON], }; mockLocalStorageController .getAllContents .mockImplementationOnce(() => LDS); - const expectedResults = object._toFullJSON(); - expectedResults.child = newData; - const result = await LocalDatastore._serializeObject(`Item_${object.id}`); + const expectedResults = parent._toFullJSON(); + expectedResults.child = childJSON; + const result = await LocalDatastore._serializeObject(parentKey); expect(result).toEqual(expectedResults); }); it('_destroyObjectIfPinned no objects found in pinName', async () => { - const object = new ParseObject('Item'); let LDS = {}; LocalDatastore.isEnabled = true; mockLocalStorageController .getAllContents .mockImplementationOnce(() => LDS); - await LocalDatastore._destroyObjectIfPinned(object); + await LocalDatastore._destroyObjectIfPinned(item1); expect(mockLocalStorageController.getAllContents).toHaveBeenCalledTimes(1); expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledTimes(0); jest.clearAllMocks(); - const obj1 = new ParseObject('Item'); - const obj2 = new ParseObject('Item'); - LDS = { - [`Item_${obj1.id}`]: obj1._toFullJSON(), - [`Item_${obj2.id}`]: obj2._toFullJSON(), - [LocalDatastore.DEFAULT_PIN]: [], + [KEY1]: [item1._toFullJSON()], + [KEY2]: [item2._toFullJSON()], + [DEFAULT_PIN]: [], }; mockLocalStorageController @@ -497,25 +500,20 @@ describe('LocalDatastore', () => { .mockImplementationOnce(() => LDS); LocalDatastore.isEnabled = true; - await LocalDatastore._destroyObjectIfPinned(obj1); + await LocalDatastore._destroyObjectIfPinned(item1); expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(`Item_${obj1.id}`); - + expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(KEY1); expect(mockLocalStorageController.getAllContents).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(0); }); it('_destroyObjectIfPinned no objects found in pinName remove pinName', async () => { - const obj1 = new ParseObject('Item'); - const obj2 = new ParseObject('Item'); - const LDS = { - [`Item_${obj1.id}`]: obj1._toFullJSON(), - [`Item_${obj2.id}`]: obj2._toFullJSON(), - [LocalDatastore.PIN_PREFIX + 'Custom_Pin']: [`Item_${obj2.id}`], - [LocalDatastore.DEFAULT_PIN]: [`Item_${obj2.id}`], + [KEY1]: [item1._toFullJSON()], + [KEY2]: [item2._toFullJSON()], + [PIN_PREFIX + 'Custom_Pin']: [KEY2], + [DEFAULT_PIN]: [KEY2], }; mockLocalStorageController @@ -523,24 +521,19 @@ describe('LocalDatastore', () => { .mockImplementationOnce(() => LDS); LocalDatastore.isEnabled = true; - await LocalDatastore._destroyObjectIfPinned(obj2); + await LocalDatastore._destroyObjectIfPinned(item2); expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledTimes(3); - expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(`Item_${obj2.id}`); - + expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(KEY2); expect(mockLocalStorageController.getAllContents).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(0); }); it('_destroyObjectIfPinned', async () => { - const obj1 = new ParseObject('Item'); - const obj2 = new ParseObject('Item'); - const LDS = { - [`Item_${obj1.id}`]: obj1._toFullJSON(), - [`Item_${obj2.id}`]: obj2._toFullJSON(), - [LocalDatastore.DEFAULT_PIN]: [`Item_${obj1.id}`, `Item_${obj2.id}`], + [KEY1]: [item1._toFullJSON()], + [KEY2]: [item2._toFullJSON()], + [DEFAULT_PIN]: [KEY1, KEY2], }; mockLocalStorageController @@ -548,41 +541,37 @@ describe('LocalDatastore', () => { .mockImplementationOnce(() => LDS); LocalDatastore.isEnabled = true; - await LocalDatastore._destroyObjectIfPinned(obj1); + await LocalDatastore._destroyObjectIfPinned(item1); expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(`Item_${obj1.id}`); - + expect(mockLocalStorageController.unPinWithName).toHaveBeenCalledWith(KEY1); expect(mockLocalStorageController.getAllContents).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(LocalDatastore.DEFAULT_PIN, [`Item_${obj2.id}`]); + expect(mockLocalStorageController.pinWithName).toHaveBeenCalledWith(DEFAULT_PIN, [KEY2]); }); it('_traverse', () => { // Skip if no objectId - let object = {} + const json = item1._toFullJSON(); let encountered = {}; - LocalDatastore._traverse(object, encountered); + LocalDatastore._traverse({}, encountered); expect(encountered).toEqual({}); // Set Encountered - object = { objectId: 1234, className: 'Item' }; encountered = {}; - LocalDatastore._traverse(object, encountered); - expect(encountered).toEqual({ 'Item_1234': object }); + LocalDatastore._traverse(json, encountered); + expect(encountered).toEqual({ [KEY1]: item1._toFullJSON() }); // Skip if already encountered - object = { objectId: 1234, className: 'Item' }; - encountered = { 'Item_1234': object }; - LocalDatastore._traverse(object, encountered); - expect(encountered).toEqual({ 'Item_1234': object }); + encountered = { [KEY1]: item1._toFullJSON() }; + LocalDatastore._traverse(json, encountered); + expect(encountered).toEqual({ [KEY1]: json }); // Test if null field exist still encounter - object = { objectId: 1234, className: 'Item', field: null }; + const object = { objectId: 1234, className: 'Item', field: null }; encountered = {}; LocalDatastore._traverse(object, encountered); - expect(encountered).toEqual({ 'Item_1234': object }); + expect(encountered).toEqual({ [`${OBJECT_PREFIX}Item_1234`]: object }); }); it('do not sync if disabled', async () => { @@ -623,19 +612,18 @@ describe('LocalDatastore', () => { it('updateFromServer on one object', async () => { LocalDatastore.isEnabled = true; LocalDatastore.isSyncing = false; - const object = new ParseObject('Item'); const LDS = { - [`Item_${object.id}`]: object._toFullJSON(), - [`${LocalDatastore.PIN_PREFIX}_testPinName`]: [`Item_${object.id}`], - [LocalDatastore.DEFAULT_PIN]: [`Item_${object.id}`], + [KEY1]: [item1._toFullJSON()], + [`${PIN_PREFIX}_testPinName`]: [KEY1], + [DEFAULT_PIN]: [KEY1], }; mockLocalStorageController .getAllContents .mockImplementationOnce(() => LDS); - object.set('updatedField', 'foo'); - mockQueryFind.mockImplementationOnce(() => Promise.resolve([object])); + item1.set('updatedField', 'foo'); + mockQueryFind.mockImplementationOnce(() => Promise.resolve([item1])); await LocalDatastore.updateFromServer(); @@ -651,24 +639,22 @@ describe('LocalDatastore', () => { it('updateFromServer handle error', async () => { LocalDatastore.isEnabled = true; LocalDatastore.isSyncing = false; - const object = new ParseObject('Item'); const LDS = { - [`Item_${object.id}`]: object._toFullJSON(), - [`${LocalDatastore.PIN_PREFIX}_testPinName`]: [`Item_${object.id}`], - [LocalDatastore.DEFAULT_PIN]: [`Item_${object.id}`], + [KEY1]: [item1._toFullJSON()], + [`${PIN_PREFIX}_testPinName`]: [KEY1], + [DEFAULT_PIN]: [KEY1], }; mockLocalStorageController .getAllContents .mockImplementationOnce(() => LDS); - object.set('updatedField', 'foo'); mockQueryFind.mockImplementationOnce(() => { expect(LocalDatastore.isSyncing).toBe(true); - return Promise.reject('Unable to connect to the Parse API') + return Promise.reject('Unable to connect to the Parse API'); }); - jest.spyOn(console, 'log'); + jest.spyOn(console, 'error'); await LocalDatastore.updateFromServer(); expect(mockLocalStorageController.getAllContents).toHaveBeenCalledTimes(1); @@ -678,22 +664,20 @@ describe('LocalDatastore', () => { expect(mockQueryInstance.equalTo.mock.calls.length).toBe(1); expect(mockQueryFind).toHaveBeenCalledTimes(1); expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(0); - expect(console.log).toHaveBeenCalledTimes(2); + expect(console.error).toHaveBeenCalledTimes(1); expect(LocalDatastore.isSyncing).toBe(false); }); it('updateFromServer on mixed object', async () => { LocalDatastore.isEnabled = true; LocalDatastore.isSyncing = false; - const obj1 = new ParseObject('Item'); - const obj2 = new ParseObject('Item'); - const obj3 = new ParseObject('TestObject'); + const testObject = new ParseObject('TestObject'); const LDS = { - [`Item_${obj1.id}`]: obj1._toFullJSON(), - [`Item_${obj2.id}`]: obj2._toFullJSON(), - [`TestObject_${obj3.id}`]: obj3._toFullJSON(), - [`${LocalDatastore.PIN_PREFIX}_testPinName`]: [`Item_${obj1.id}`], - [LocalDatastore.DEFAULT_PIN]: [`Item_${obj1.id}`], + [KEY1]: [item1._toFullJSON()], + [KEY2]: [item2._toFullJSON()], + [LocalDatastore.getKeyForObject(testObject)]: [testObject._toFullJSON()], + [`${PIN_PREFIX}_testPinName`]: [KEY1], + [DEFAULT_PIN]: [KEY1], }; mockLocalStorageController @@ -701,8 +685,8 @@ describe('LocalDatastore', () => { .mockImplementationOnce(() => LDS); mockQueryFind - .mockImplementationOnce(() => Promise.resolve([obj1, obj2])) - .mockImplementationOnce(() => Promise.resolve([obj3])); + .mockImplementationOnce(() => Promise.resolve([item1, item2])) + .mockImplementationOnce(() => Promise.resolve([testObject])); await LocalDatastore.updateFromServer(); @@ -717,9 +701,17 @@ describe('LocalDatastore', () => { expect(mockQueryFind).toHaveBeenCalledTimes(2); expect(mockLocalStorageController.pinWithName).toHaveBeenCalledTimes(3); }); + + it('isLocalDatastoreKey', () => { + expect(isLocalDatastoreKey(null)).toBe(false); + expect(isLocalDatastoreKey('')).toBe(false); + expect(isLocalDatastoreKey(DEFAULT_PIN)).toBe(true); + expect(isLocalDatastoreKey(PIN_PREFIX)).toBe(true); + expect(isLocalDatastoreKey(OBJECT_PREFIX)).toBe(true); + }); }); -describe('BrowserDatastoreController', async () => { +describe('BrowserDatastoreController', () => { beforeEach(async () => { await BrowserDatastoreController.clear(); }); @@ -733,16 +725,18 @@ describe('BrowserDatastoreController', async () => { }); it('can store and retrieve values', async () => { - expect(await BrowserDatastoreController.fromPinWithName('myKey')).toEqual(null); - await BrowserDatastoreController.pinWithName('myKey', [{ name: 'test' }]); - expect(await BrowserDatastoreController.fromPinWithName('myKey')).toEqual([{ name: 'test' }]); + expect(await BrowserDatastoreController.fromPinWithName(KEY1)).toEqual([]); + await BrowserDatastoreController.pinWithName(KEY1, [item1._toFullJSON()]); + expect(await BrowserDatastoreController.fromPinWithName(KEY1)).toEqual([item1._toFullJSON()]); + expect(await BrowserDatastoreController.getAllContents()).toEqual({ [KEY1]: [item1._toFullJSON()] }); }); it('can remove values', async () => { - await BrowserDatastoreController.pinWithName('myKey', [{ name: 'test' }]); - expect(await BrowserDatastoreController.fromPinWithName('myKey')).toEqual([{ name: 'test' }]); - await BrowserDatastoreController.unPinWithName('myKey'); - expect(await BrowserDatastoreController.fromPinWithName('myKey')).toEqual(null); + await BrowserDatastoreController.pinWithName(KEY1, [item1._toFullJSON()]); + expect(await BrowserDatastoreController.fromPinWithName(KEY1)).toEqual([item1._toFullJSON()]); + await BrowserDatastoreController.unPinWithName(KEY1); + expect(await BrowserDatastoreController.fromPinWithName(KEY1)).toEqual([]); + expect(await BrowserDatastoreController.getAllContents()).toEqual({}); }); }); @@ -760,17 +754,17 @@ describe('DefaultDatastoreController', () => { }); it('can store and retrieve values', async () => { - expect(await DefaultDatastoreController.fromPinWithName('myKey')).toEqual(null); - await DefaultDatastoreController.pinWithName('myKey', [{ name: 'test' }]); - expect(await DefaultDatastoreController.fromPinWithName('myKey')).toEqual([{ name: 'test' }]); - expect(await DefaultDatastoreController.getAllContents()).toEqual({ myKey: [ { name: 'test' } ] }); + expect(await DefaultDatastoreController.fromPinWithName(KEY1)).toEqual([]); + await DefaultDatastoreController.pinWithName(KEY1, [item1._toFullJSON()]); + expect(await DefaultDatastoreController.fromPinWithName(KEY1)).toEqual([item1._toFullJSON()]); + expect(await DefaultDatastoreController.getAllContents()).toEqual({ [KEY1]: [item1._toFullJSON()] }); }); it('can remove values', async () => { - await DefaultDatastoreController.pinWithName('myKey', [{ name: 'test' }]); - expect(await DefaultDatastoreController.fromPinWithName('myKey')).toEqual([{ name: 'test' }]); - await DefaultDatastoreController.unPinWithName('myKey'); - expect(await DefaultDatastoreController.fromPinWithName('myKey')).toEqual(null); + await DefaultDatastoreController.pinWithName(KEY1, [item1._toFullJSON()]); + expect(await DefaultDatastoreController.fromPinWithName(KEY1)).toEqual([item1._toFullJSON()]); + await DefaultDatastoreController.unPinWithName(KEY1); + expect(await DefaultDatastoreController.fromPinWithName(KEY1)).toEqual([]); expect(await DefaultDatastoreController.getAllContents()).toEqual({}); }); }); @@ -782,18 +776,20 @@ describe('LocalDatastore (BrowserDatastoreController)', () => { }); it('can store and retrieve values', async () => { - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual(null); - await LocalDatastore.pinWithName('myKey', [{ name: 'test' }]); - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual([{ name: 'test' }]); - expect(await LocalDatastore._getAllContents()).toEqual({ myKey: [ { name: 'test' } ] }); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([]); + await LocalDatastore.pinWithName(KEY1, [item1._toFullJSON()]); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([item1._toFullJSON()]); + expect(await LocalDatastore._getAllContents()).toEqual({ [KEY1]: [item1._toFullJSON()] }); + expect(await LocalDatastore._getRawStorage()).toEqual({ [KEY1]: JSON.stringify([item1._toFullJSON()]) }); }); it('can remove values', async () => { - await LocalDatastore.pinWithName('myKey', [{ name: 'test' }]); - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual([{ name: 'test' }]); - await LocalDatastore.unPinWithName('myKey'); - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual(null); + await LocalDatastore.pinWithName(KEY1, [item1._toFullJSON()]); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([item1._toFullJSON()]); + await LocalDatastore.unPinWithName(KEY1); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([]); expect(await LocalDatastore._getAllContents()).toEqual({}); + expect(await LocalDatastore._getRawStorage()).toEqual({}); }); it('can handle store error', async () => { @@ -820,18 +816,20 @@ describe('LocalDatastore (DefaultDatastoreController)', () => { }); it('can store and retrieve values', async () => { - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual(null); - await LocalDatastore.pinWithName('myKey', [{ name: 'test' }]); - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual([{ name: 'test' }]); - expect(await LocalDatastore._getAllContents()).toEqual({ myKey: [ { name: 'test' } ] }); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([]); + await LocalDatastore.pinWithName(KEY1, [item1._toFullJSON()]); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([item1._toFullJSON()]); + expect(await LocalDatastore._getAllContents()).toEqual({ [KEY1]: [item1._toFullJSON()] }); + expect(await LocalDatastore._getRawStorage()).toEqual({ [KEY1]: JSON.stringify([item1._toFullJSON()]) }); }); it('can remove values', async () => { - await LocalDatastore.pinWithName('myKey', [{ name: 'test' }]); - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual([{ name: 'test' }]); - await LocalDatastore.unPinWithName('myKey'); - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual(null); + await LocalDatastore.pinWithName(KEY1, [item1._toFullJSON()]); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([item1._toFullJSON()]); + await LocalDatastore.unPinWithName(KEY1); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([]); expect(await LocalDatastore._getAllContents()).toEqual({}); + expect(await LocalDatastore._getRawStorage()).toEqual({}); }); }); @@ -843,18 +841,20 @@ describe('LocalDatastore (RNDatastoreController)', () => { }); it('can store and retrieve values', async () => { - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual(null); - await LocalDatastore.pinWithName('myKey', [{ name: 'test' }]); - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual([{ name: 'test' }]); - expect(await LocalDatastore._getAllContents()).toEqual({ myKey: [{ name: 'test' }] }); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([]); + await LocalDatastore.pinWithName(KEY1, [item1._toFullJSON()]); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([item1._toFullJSON()]); + expect(await LocalDatastore._getAllContents()).toEqual({ [KEY1]: [item1._toFullJSON()] }); + expect(await LocalDatastore._getRawStorage()).toEqual({ [KEY1]: JSON.stringify([item1._toFullJSON()]) }); }); it('can remove values', async () => { - await LocalDatastore.pinWithName('myKey', [{ name: 'test' }]); - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual([{ name: 'test' }]); - await LocalDatastore.unPinWithName('myKey'); - expect(await LocalDatastore.fromPinWithName('myKey')).toEqual(null); + await LocalDatastore.pinWithName(KEY1, [item1._toFullJSON()]); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([item1._toFullJSON()]); + await LocalDatastore.unPinWithName(KEY1); + expect(await LocalDatastore.fromPinWithName(KEY1)).toEqual([]); expect(await LocalDatastore._getAllContents()).toEqual({}); + expect(await LocalDatastore._getRawStorage()).toEqual({}); }); it('can handle store error', async () => { @@ -870,4 +870,45 @@ describe('LocalDatastore (RNDatastoreController)', () => { expect(e.message).toBe('error thrown'); } }); + + it('can handle getAllContents undefined', async () => { + await RNDatastoreController.pinWithName(KEY1, undefined); + const contents = await RNDatastoreController.getAllContents(); + expect(contents[KEY1]).toEqual(null); + }); + + it('can handle getAllContents non-LDS object', async () => { + await RNDatastoreController.pinWithName(KEY1, item1._toFullJSON()); + await RNDatastoreController.pinWithName('DO_NOT_FETCH', undefined); + const contents = await RNDatastoreController.getAllContents(); + expect(contents[KEY1]).toEqual(item1._toFullJSON()); + expect(contents['DO_NOT_FETCH']).toBeUndefined(); + }); + + it('can handle clear error', async () => { + const mockStorageError = { + multiRemove(keys, cb) { + cb('error thrown'); + }, + getAllKeys(cb) { + cb(undefined, [KEY1, 'DO_NOT_CLEAR']); + } + }; + CoreManager.setAsyncStorage(mockStorageError); + await LocalDatastore._clear(); + }); + + it('can handle multiget error', async () => { + const mockStorageError = { + multiGet(keys, cb) { + cb('error thrown'); + }, + getAllKeys(cb) { + cb(undefined, [KEY1, 'DO_NOT_CLEAR']); + } + }; + CoreManager.setAsyncStorage(mockStorageError); + const LDS = await LocalDatastore._getAllContents(); + expect(LDS).toEqual({}); + }); }); diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js index 891d988fd..e27a14fdd 100644 --- a/src/__tests__/ParseObject-test.js +++ b/src/__tests__/ParseObject-test.js @@ -103,15 +103,15 @@ mockQuery.prototype.find = function() { }; jest.setMock('../ParseQuery', mockQuery); +import { DEFAULT_PIN, PIN_PREFIX } from '../LocalDatastoreUtils'; + const mockLocalDatastore = { - DEFAULT_PIN: '_default', - PIN_PREFIX: 'parsePin_', isEnabled: false, fromPinWithName: jest.fn(), pinWithName: jest.fn(), unPinWithName: jest.fn(), - _handlePinWithName: jest.fn(), - _handleUnPinWithName: jest.fn(), + _handlePinAllWithName: jest.fn(), + _handleUnPinAllWithName: jest.fn(), _getAllContent: jest.fn(), _serializeObjectsFromPinName: jest.fn(), _serializeObject: jest.fn(), @@ -124,7 +124,7 @@ const mockLocalDatastore = { getKeyForObject: jest.fn(), checkIfEnabled: jest.fn(() => { if (!mockLocalDatastore.isEnabled) { - console.log('Parse.enableLocalDatastore() must be called first'); // eslint-disable-line no-console + console.error('Parse.enableLocalDatastore() must be called first'); } return mockLocalDatastore.isEnabled; }), @@ -141,7 +141,6 @@ const ParseOp = require('../ParseOp'); const RESTController = require('../RESTController'); const SingleInstanceStateController = require('../SingleInstanceStateController'); const unsavedChildren = require('../unsavedChildren').default; -const LocalDatastore = require('../LocalDatastore'); const mockXHR = require('./test_helpers/mockXHR'); @@ -2615,29 +2614,29 @@ describe('ParseObject pin', () => { it('can pin to default', async () => { const object = new ParseObject('Item'); await object.pin(); - expect(mockLocalDatastore._handlePinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalDatastore._handlePinWithName).toHaveBeenCalledWith(LocalDatastore.DEFAULT_PIN, object); + expect(mockLocalDatastore._handlePinAllWithName).toHaveBeenCalledTimes(1); + expect(mockLocalDatastore._handlePinAllWithName).toHaveBeenCalledWith(DEFAULT_PIN, [object]); }); it('can unPin to default', async () => { const object = new ParseObject('Item'); await object.unPin(); - expect(mockLocalDatastore._handleUnPinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalDatastore._handleUnPinWithName).toHaveBeenCalledWith(LocalDatastore.DEFAULT_PIN, object); + expect(mockLocalDatastore._handleUnPinAllWithName).toHaveBeenCalledTimes(1); + expect(mockLocalDatastore._handleUnPinAllWithName).toHaveBeenCalledWith(DEFAULT_PIN, [object]); }); it('can pin to specific pin', async () => { const object = new ParseObject('Item'); await object.pinWithName('test_pin'); - expect(mockLocalDatastore._handlePinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalDatastore._handlePinWithName).toHaveBeenCalledWith('test_pin', object); + expect(mockLocalDatastore._handlePinAllWithName).toHaveBeenCalledTimes(1); + expect(mockLocalDatastore._handlePinAllWithName).toHaveBeenCalledWith('test_pin', [object]); }); it('can unPin to specific', async () => { const object = new ParseObject('Item'); await object.unPinWithName('test_pin'); - expect(mockLocalDatastore._handleUnPinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalDatastore._handleUnPinWithName).toHaveBeenCalledWith('test_pin', object); + expect(mockLocalDatastore._handleUnPinAllWithName).toHaveBeenCalledTimes(1); + expect(mockLocalDatastore._handleUnPinAllWithName).toHaveBeenCalledWith('test_pin', [object]); }); it('can check if pinned', async () => { @@ -2646,9 +2645,9 @@ describe('ParseObject pin', () => { mockLocalDatastore .fromPinWithName .mockImplementationOnce(() => { - return { 'Item_1234': object._toFullJSON() } + return [object._toFullJSON()]; }) - .mockImplementationOnce(() => null); + .mockImplementationOnce(() => []); let isPinned = await object.isPinned(); expect(isPinned).toEqual(true); @@ -2685,56 +2684,93 @@ describe('ParseObject pin', () => { const obj1 = new ParseObject('Item'); const obj2 = new ParseObject('Item'); await ParseObject.pinAll([obj1, obj2]); - expect(mockLocalDatastore._handlePinWithName).toHaveBeenCalledTimes(2); - expect(mockLocalDatastore._handlePinWithName.mock.calls[0]).toEqual([LocalDatastore.DEFAULT_PIN, obj1]); - expect(mockLocalDatastore._handlePinWithName.mock.calls[1]).toEqual([LocalDatastore.DEFAULT_PIN, obj2]); + expect(mockLocalDatastore._handlePinAllWithName).toHaveBeenCalledTimes(1); + expect(mockLocalDatastore._handlePinAllWithName.mock.calls[0]).toEqual([DEFAULT_PIN, [obj1, obj2]]); }); it('can unPinAll', async () => { const obj1 = new ParseObject('Item'); const obj2 = new ParseObject('Item'); await ParseObject.unPinAll([obj1, obj2]); - expect(mockLocalDatastore._handleUnPinWithName).toHaveBeenCalledTimes(2); - expect(mockLocalDatastore._handleUnPinWithName.mock.calls[0]).toEqual([LocalDatastore.DEFAULT_PIN, obj1]); - expect(mockLocalDatastore._handleUnPinWithName.mock.calls[1]).toEqual([LocalDatastore.DEFAULT_PIN, obj2]); + expect(mockLocalDatastore._handleUnPinAllWithName).toHaveBeenCalledTimes(1); + expect(mockLocalDatastore._handleUnPinAllWithName.mock.calls[0]).toEqual([DEFAULT_PIN, [obj1, obj2]]); }); it('can unPinAllObjects', async () => { await ParseObject.unPinAllObjects(); expect(mockLocalDatastore.unPinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalDatastore.unPinWithName.mock.calls[0]).toEqual([LocalDatastore.DEFAULT_PIN]); + expect(mockLocalDatastore.unPinWithName.mock.calls[0]).toEqual([DEFAULT_PIN]); }); it('can unPinAllObjectsWithName', async () => { await ParseObject.unPinAllObjectsWithName('123'); expect(mockLocalDatastore.unPinWithName).toHaveBeenCalledTimes(1); - expect(mockLocalDatastore.unPinWithName.mock.calls[0]).toEqual([LocalDatastore.PIN_PREFIX + '123']); + expect(mockLocalDatastore.unPinWithName.mock.calls[0]).toEqual([PIN_PREFIX + '123']); }); it('cannot pin when localDatastore disabled', async () => { mockLocalDatastore.isEnabled = false; - const spy = jest.spyOn( - console, - 'log' - ); const name = 'test_pin'; const obj = new ParseObject('Item'); - await obj.pin(); - await obj.unPin(); - await obj.isPinned(); - await obj.pinWithName(name); - await obj.unPinWithName(name); - await obj.fetchFromLocalDatastore(); - - await ParseObject.pinAll([obj]); - await ParseObject.unPinAll([obj]); - await ParseObject.pinAllWithName(name, [obj]); - await ParseObject.unPinAllWithName(name, [obj]); - await ParseObject.unPinAllObjects(); - await ParseObject.unPinAllObjectsWithName(name); - - expect(spy).toHaveBeenCalledTimes(12); - expect(spy).toHaveBeenCalledWith('Parse.enableLocalDatastore() must be called first'); - spy.mockRestore(); + try { + await obj.pin(); + } catch (error) { + expect(error).toBe('Parse.enableLocalDatastore() must be called first'); + } + try { + await obj.unPin(); + } catch (error) { + expect(error).toBe('Parse.enableLocalDatastore() must be called first'); + } + try { + await obj.isPinned(); + } catch (error) { + expect(error).toBe('Parse.enableLocalDatastore() must be called first'); + } + try { + await obj.pinWithName(); + } catch (error) { + expect(error).toBe('Parse.enableLocalDatastore() must be called first'); + } + try { + await obj.unPinWithName(); + } catch (error) { + expect(error).toBe('Parse.enableLocalDatastore() must be called first'); + } + try { + await obj.fetchFromLocalDatastore(); + } catch (error) { + expect(error.message).toBe('Parse.enableLocalDatastore() must be called first'); + } + try { + await ParseObject.pinAll([obj]); + } catch (error) { + expect(error).toBe('Parse.enableLocalDatastore() must be called first'); + } + try { + await ParseObject.unPinAll([obj]); + } catch (error) { + expect(error).toBe('Parse.enableLocalDatastore() must be called first'); + } + try { + await ParseObject.pinAllWithName(name, [obj]); + } catch (error) { + expect(error).toBe('Parse.enableLocalDatastore() must be called first'); + } + try { + await ParseObject.unPinAllWithName(name, [obj]); + } catch (error) { + expect(error).toBe('Parse.enableLocalDatastore() must be called first'); + } + try { + await ParseObject.unPinAllObjects(); + } catch (error) { + expect(error).toBe('Parse.enableLocalDatastore() must be called first'); + } + try { + await ParseObject.unPinAllObjectsWithName(name); + } catch (error) { + expect(error).toBe('Parse.enableLocalDatastore() must be called first'); + } }); }); diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js index 194a38402..7b8213653 100644 --- a/src/__tests__/ParseQuery-test.js +++ b/src/__tests__/ParseQuery-test.js @@ -45,13 +45,14 @@ const mockLocalDatastore = { jest.setMock('../LocalDatastore', mockLocalDatastore); let CoreManager = require('../CoreManager'); -const LocalDatastore = require('../LocalDatastore'); const ParseError = require('../ParseError').default; const ParseGeoPoint = require('../ParseGeoPoint').default; let ParseObject = require('../ParseObject'); let ParseQuery = require('../ParseQuery').default; const LiveQuerySubscription = require('../LiveQuerySubscription').default; +import { DEFAULT_PIN } from '../LocalDatastoreUtils'; + describe('ParseQuery', () => { it('can be constructed from a class name', () => { const q = new ParseQuery('Item'); @@ -2192,7 +2193,7 @@ describe('ParseQuery LocalDatastore', () => { expect(q._localDatastorePinName).toBe(null); q.fromPin(); expect(q._queriesLocalDatastore).toBe(true); - expect(q._localDatastorePinName).toBe(LocalDatastore.DEFAULT_PIN); + expect(q._localDatastorePinName).toBe(DEFAULT_PIN); }); it('can query from pin with name', () => { diff --git a/src/__tests__/test_helpers/mockRNStorage.js b/src/__tests__/test_helpers/mockRNStorage.js index 4e09cfac8..9318d2451 100644 --- a/src/__tests__/test_helpers/mockRNStorage.js +++ b/src/__tests__/test_helpers/mockRNStorage.js @@ -27,6 +27,16 @@ const mockRNStorage = { cb(undefined, Object.keys(mockStorage)); }, + multiGet(keys, cb) { + const objects = keys.map((key) => [key, mockStorage[key]]); + cb(undefined, objects); + }, + + multiRemove(keys, cb) { + keys.map((key) => delete mockStorage[key]); + cb(undefined); + }, + clear() { mockStorage = {}; },