diff --git a/client/recordUtil.js b/client/recordUtil.js index 27160f5..f4fa2a8 100644 --- a/client/recordUtil.js +++ b/client/recordUtil.js @@ -250,25 +250,49 @@ const mergeRecord = (record1, record2) => { /** * Within an array of [record, object], merge items whose records have the - * same objectId. + * same objectId and same actions. + * Requirements: + * 1) should not combine CREATE when object is not null: + * [[CREATE, object], [UPDATE, object]] => [[CREATE, object], [UPDATE, object]] + * [[CREATE, null], [UPDATE, null]] => [[CREATE(2), null]] + * [[CREATE, null], [UPDATE, object]] => ??? seems impossible => [[CREATE(2), object]] + * 2) should not combine any DELETE + * 3) all of UPDATE should be combined to single update * @param {Array} recordsAndObjects Same format as input to resolveRecords(). * @returns {Array} */ const mergeRecords = (recordsAndObjects) => { - const objectIdMap = {} // map of objectId to [record, object] + var mergedResult = [] recordsAndObjects.forEach((recordAndObject) => { const record = recordAndObject[0] - const object = recordAndObject[1] + const existingObject = recordAndObject[1] const id = JSON.stringify(record.objectId) - if (objectIdMap[id]) { - const mergedRecord = mergeRecord(objectIdMap[id][0], record) - objectIdMap[id] = [mergedRecord, object] - } else { - objectIdMap[id] = recordAndObject + + if (!mergedResult.length || + (record.action === proto.actions.CREATE && existingObject) || + record.action === proto.actions.DELETE) { + mergedResult.push(recordAndObject) + return } - }) - // webkit does not support Object.values - return Object.keys(objectIdMap).map((key) => objectIdMap[key]) + + // We reach this point in 2 cases + // 1. record.action is UPDATE => should merge with UPDATE or [CREATE, null] + // 2. record.action is CREATE and existingObject is null => should merge with UPDATE or [CREATE, null] + + // Go through mergedResult from last to first + for (var j = mergedResult.length - 1; j >= 0; --j) { + if (JSON.stringify(mergedResult[j][0].objectId) === id && + // Should not merge with CREATE or DELETE, only UPDATE + (mergedResult[j][0].action === proto.actions.UPDATE || + (mergedResult[j][0].action === proto.actions.CREATE && !mergedResult[j][1]))) { + mergedResult[j][0] = mergeRecord(mergedResult[j][0], record) + return + } + } + mergedResult.push(recordAndObject) + }) // recordsAndObjects.forEach + + return mergedResult } /** diff --git a/lib/api.proto b/lib/api.proto index 1c3c08b..8e1d6a7 100644 --- a/lib/api.proto +++ b/lib/api.proto @@ -26,6 +26,11 @@ message SecretboxRecord { bytes nonceRandom = 3; } +message MetaInfo { + optional string key = 1; + optional string value = 2; +} + message SyncRecord { enum Action { CREATE = 0; @@ -49,6 +54,7 @@ message SyncRecord { repeated string fields = 6; bool hideInToolbar = 7; string order = 8; + repeated MetaInfo metaInfo= 9; } message SiteSetting { string hostPattern = 1; diff --git a/lib/api.proto.js b/lib/api.proto.js index 8f9e821..13c7836 100644 --- a/lib/api.proto.js +++ b/lib/api.proto.js @@ -1047,6 +1047,216 @@ return SecretboxRecord; })(); + api.MetaInfo = (function() { + + /** + * Properties of a MetaInfo. + * @memberof api + * @interface IMetaInfo + * @property {string|null} [key] MetaInfo key + * @property {string|null} [value] MetaInfo value + */ + + /** + * Constructs a new MetaInfo. + * @memberof api + * @classdesc Represents a MetaInfo. + * @implements IMetaInfo + * @constructor + * @param {api.IMetaInfo=} [properties] Properties to set + */ + function MetaInfo(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * MetaInfo key. + * @member {string} key + * @memberof api.MetaInfo + * @instance + */ + MetaInfo.prototype.key = ""; + + /** + * MetaInfo value. + * @member {string} value + * @memberof api.MetaInfo + * @instance + */ + MetaInfo.prototype.value = ""; + + /** + * Creates a new MetaInfo instance using the specified properties. + * @function create + * @memberof api.MetaInfo + * @static + * @param {api.IMetaInfo=} [properties] Properties to set + * @returns {api.MetaInfo} MetaInfo instance + */ + MetaInfo.create = function create(properties) { + return new MetaInfo(properties); + }; + + /** + * Encodes the specified MetaInfo message. Does not implicitly {@link api.MetaInfo.verify|verify} messages. + * @function encode + * @memberof api.MetaInfo + * @static + * @param {api.IMetaInfo} message MetaInfo message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MetaInfo.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.key != null && message.hasOwnProperty("key")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.key); + if (message.value != null && message.hasOwnProperty("value")) + writer.uint32(/* id 2, wireType 2 =*/18).string(message.value); + return writer; + }; + + /** + * Encodes the specified MetaInfo message, length delimited. Does not implicitly {@link api.MetaInfo.verify|verify} messages. + * @function encodeDelimited + * @memberof api.MetaInfo + * @static + * @param {api.IMetaInfo} message MetaInfo message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MetaInfo.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a MetaInfo message from the specified reader or buffer. + * @function decode + * @memberof api.MetaInfo + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {api.MetaInfo} MetaInfo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MetaInfo.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.api.MetaInfo(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a MetaInfo message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof api.MetaInfo + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {api.MetaInfo} MetaInfo + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MetaInfo.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a MetaInfo message. + * @function verify + * @memberof api.MetaInfo + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + MetaInfo.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.key != null && message.hasOwnProperty("key")) + if (!$util.isString(message.key)) + return "key: string expected"; + if (message.value != null && message.hasOwnProperty("value")) + if (!$util.isString(message.value)) + return "value: string expected"; + return null; + }; + + /** + * Creates a MetaInfo message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof api.MetaInfo + * @static + * @param {Object.} object Plain object + * @returns {api.MetaInfo} MetaInfo + */ + MetaInfo.fromObject = function fromObject(object) { + if (object instanceof $root.api.MetaInfo) + return object; + var message = new $root.api.MetaInfo(); + if (object.key != null) + message.key = String(object.key); + if (object.value != null) + message.value = String(object.value); + return message; + }; + + /** + * Creates a plain object from a MetaInfo message. Also converts values to other types if specified. + * @function toObject + * @memberof api.MetaInfo + * @static + * @param {api.MetaInfo} message MetaInfo + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + MetaInfo.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.key = ""; + object.value = ""; + } + if (message.key != null && message.hasOwnProperty("key")) + object.key = message.key; + if (message.value != null && message.hasOwnProperty("value")) + object.value = message.value; + return object; + }; + + /** + * Converts this MetaInfo to JSON. + * @function toJSON + * @memberof api.MetaInfo + * @instance + * @returns {Object.} JSON object + */ + MetaInfo.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + return MetaInfo; + })(); + api.SyncRecord = (function() { /** @@ -1817,6 +2027,7 @@ * @property {Array.|null} [fields] Bookmark fields * @property {boolean|null} [hideInToolbar] Bookmark hideInToolbar * @property {string|null} [order] Bookmark order + * @property {Array.|null} [metaInfo] Bookmark metaInfo */ /** @@ -1829,6 +2040,7 @@ */ function Bookmark(properties) { this.fields = []; + this.metaInfo = []; if (properties) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) if (properties[keys[i]] != null) @@ -1899,6 +2111,14 @@ */ Bookmark.prototype.order = ""; + /** + * Bookmark metaInfo. + * @member {Array.} metaInfo + * @memberof api.SyncRecord.Bookmark + * @instance + */ + Bookmark.prototype.metaInfo = $util.emptyArray; + /** * Creates a new Bookmark instance using the specified properties. * @function create @@ -1940,6 +2160,9 @@ writer.uint32(/* id 7, wireType 0 =*/56).bool(message.hideInToolbar); if (message.order != null && message.hasOwnProperty("order")) writer.uint32(/* id 8, wireType 2 =*/66).string(message.order); + if (message.metaInfo != null && message.metaInfo.length) + for (var i = 0; i < message.metaInfo.length; ++i) + $root.api.MetaInfo.encode(message.metaInfo[i], writer.uint32(/* id 9, wireType 2 =*/74).fork()).ldelim(); return writer; }; @@ -2000,6 +2223,11 @@ case 8: message.order = reader.string(); break; + case 9: + if (!(message.metaInfo && message.metaInfo.length)) + message.metaInfo = []; + message.metaInfo.push($root.api.MetaInfo.decode(reader, reader.uint32())); + break; default: reader.skipType(tag & 7); break; @@ -2065,6 +2293,15 @@ if (message.order != null && message.hasOwnProperty("order")) if (!$util.isString(message.order)) return "order: string expected"; + if (message.metaInfo != null && message.hasOwnProperty("metaInfo")) { + if (!Array.isArray(message.metaInfo)) + return "metaInfo: array expected"; + for (var i = 0; i < message.metaInfo.length; ++i) { + var error = $root.api.MetaInfo.verify(message.metaInfo[i]); + if (error) + return "metaInfo." + error; + } + } return null; }; @@ -2113,6 +2350,16 @@ message.hideInToolbar = Boolean(object.hideInToolbar); if (object.order != null) message.order = String(object.order); + if (object.metaInfo) { + if (!Array.isArray(object.metaInfo)) + throw TypeError(".api.SyncRecord.Bookmark.metaInfo: array expected"); + message.metaInfo = []; + for (var i = 0; i < object.metaInfo.length; ++i) { + if (typeof object.metaInfo[i] !== "object") + throw TypeError(".api.SyncRecord.Bookmark.metaInfo: object expected"); + message.metaInfo[i] = $root.api.MetaInfo.fromObject(object.metaInfo[i]); + } + } return message; }; @@ -2129,8 +2376,10 @@ if (!options) options = {}; var object = {}; - if (options.arrays || options.defaults) + if (options.arrays || options.defaults) { object.fields = []; + object.metaInfo = []; + } if (options.defaults) { object.site = null; object.isFolder = false; @@ -2177,6 +2426,11 @@ object.hideInToolbar = message.hideInToolbar; if (message.order != null && message.hasOwnProperty("order")) object.order = message.order; + if (message.metaInfo && message.metaInfo.length) { + object.metaInfo = []; + for (var j = 0; j < message.metaInfo.length; ++j) + object.metaInfo[j] = $root.api.MetaInfo.toObject(message.metaInfo[j], options); + } return object; }; diff --git a/test/client/fixtures/resolveLaptop.js b/test/client/fixtures/resolveLaptop.js index bea0fae..bbd3c5a 100644 --- a/test/client/fixtures/resolveLaptop.js +++ b/test/client/fixtures/resolveLaptop.js @@ -1 +1 @@ -module.exports.data = [[{"action":0,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.facebook.com/","title":"Facebook – log in or sign up"}},"deviceId":[1],"objectData":"bookmark","objectId":[180,199,181,215,249,207,92,243,63,69,164,188,146,53,214,145],"syncTimestamp":1499182364028},null],[{"action":1,"bookmark":{"isFolder":false,"site":{"creationTime":0,"customTitle":"","favicon":"https://abs.twimg.com/favicons/favicon.ico","lastAccessedTime":1499182519524,"location":"https://twitter.com/brave","title":"Brave Software (@brave) | Twitter"}},"deviceId":[0],"objectData":"bookmark","objectId":[102,169,71,255,160,7,199,37,174,1,89,148,37,235,137,188],"syncTimestamp":1499182523711},null],[{"action":0,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://en.m.wikipedia.org/wiki/Main_Page","title":"Wikipedia, the free encyclopedia"}},"deviceId":[1],"objectData":"bookmark","objectId":[50,155,253,23,226,251,247,250,190,65,73,221,118,109,70,61],"syncTimestamp":1499182560767},null],[{"action":0,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://www.yahoo.com/","title":"Yahoo"}},"deviceId":[1],"objectData":"bookmark","objectId":[100,238,226,152,162,241,78,165,80,209,69,56,43,239,246,120],"syncTimestamp":1499183235567},null],[{"action":1,"bookmark":{"isFolder":false,"site":{"creationTime":0,"customTitle":"","favicon":"chrome-extension://mnojpmjdmbbfmejpflffifhffcmidifd/img/newtab/defaultTopSitesIcon/brave.ico","lastAccessedTime":1,"location":"https://brave.com/","title":"Brave Software | Building a Better Web"}},"deviceId":[0],"objectData":"bookmark","objectId":[57,70,144,212,237,131,12,182,117,184,46,131,46,82,113,92],"syncTimestamp":1499183408150},null],[{"action":0,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://www.theguardian.com/international","title":"News, sport and opinion from the Guardian's global edition | The Guardian"}},"deviceId":[2],"objectData":"bookmark","objectId":[50,18,21,48,179,186,120,100,165,225,251,230,29,214,223,181],"syncTimestamp":1499197431917},null],[{"action":0,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder1"}},"deviceId":[2],"objectData":"bookmark","objectId":[8,146,83,105,4,60,9,110,111,10,161,34,175,6,41,174],"syncTimestamp":1499197515513},null],[{"action":1,"bookmark":{"isFolder":false,"parentFolderObjectId":[8,146,83,105,4,60,9,110,111,10,161,34,175,6,41,174],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://www.theguardian.com/international","title":"News, sport and opinion from the Guardian's global edition | The Guardian"}},"deviceId":[2],"objectData":"bookmark","objectId":[50,18,21,48,179,186,120,100,165,225,251,230,29,214,223,181],"syncTimestamp":1499197515547},null],[{"action":0,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder0"}},"deviceId":[2],"objectData":"bookmark","objectId":[185,137,250,90,121,233,6,219,254,150,22,101,172,45,193,26],"syncTimestamp":1499197572153},null],[{"action":1,"bookmark":{"isFolder":true,"parentFolderObjectId":[185,137,250,90,121,233,6,219,254,150,22,101,172,45,193,26],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder1"}},"deviceId":[2],"objectData":"bookmark","objectId":[8,146,83,105,4,60,9,110,111,10,161,34,175,6,41,174],"syncTimestamp":1499197572168},null],[{"action":1,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder1"}},"deviceId":[2],"objectData":"bookmark","objectId":[8,146,83,105,4,60,9,110,111,10,161,34,175,6,41,174],"syncTimestamp":1499197616732},null],[{"action":0,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[2],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499197733375},null],[{"action":0,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder2"}},"deviceId":[4],"objectData":"bookmark","objectId":[12,150,93,122,6,116,243,239,237,197,68,195,0,188,183,150],"syncTimestamp":1499276712538},null],[{"action":1,"bookmark":{"isFolder":false,"parentFolderObjectId":[12,150,93,122,6,116,243,239,237,197,68,195,0,188,183,150],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://www.yahoo.com/","title":"Yahoo"}},"deviceId":[4],"objectData":"bookmark","objectId":[100,238,226,152,162,241,78,165,80,209,69,56,43,239,246,120],"syncTimestamp":1499276712550},null],[{"action":0,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder3"}},"deviceId":[4],"objectData":"bookmark","objectId":[186,183,34,202,153,217,44,45,153,87,34,117,39,115,246,80],"syncTimestamp":1499276733365},null],[{"action":1,"bookmark":{"isFolder":true,"parentFolderObjectId":[186,183,34,202,153,217,44,45,153,87,34,117,39,115,246,80],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder2"}},"deviceId":[4],"objectData":"bookmark","objectId":[12,150,93,122,6,116,243,239,237,197,68,195,0,188,183,150],"syncTimestamp":1499276733388},null],[{"action":1,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder2"}},"deviceId":[4],"objectData":"bookmark","objectId":[12,150,93,122,6,116,243,239,237,197,68,195,0,188,183,150],"syncTimestamp":1499276801913},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[4],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499277601715},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[4],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499277601736},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[4],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499277601751},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[4],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499277601768},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[4],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499277601785},null],[{"action":2,"bookmark":{"isFolder":false,"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.facebook.com/","title":"Facebook – log in or sign up"}},"deviceId":[5],"objectData":"bookmark","objectId":[180,199,181,215,249,207,92,243,63,69,164,188,146,53,214,145],"syncTimestamp":1499277751881},null],[{"action":2,"bookmark":{"isFolder":false,"site":{"creationTime":0,"customTitle":"","favicon":"https://abs.twimg.com/favicons/favicon.ico","lastAccessedTime":1499182519524,"location":"https://twitter.com/brave","title":"Brave Software (@brave) | Twitter"}},"deviceId":[5],"objectData":"bookmark","objectId":[102,169,71,255,160,7,199,37,174,1,89,148,37,235,137,188],"syncTimestamp":1499277754662},null],[{"action":2,"bookmark":{"isFolder":false,"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://en.m.wikipedia.org/wiki/Main_Page","title":"Wikipedia, the free encyclopedia"}},"deviceId":[5],"objectData":"bookmark","objectId":[50,155,253,23,226,251,247,250,190,65,73,221,118,109,70,61],"syncTimestamp":1499277757092},null],[{"action":2,"bookmark":{"isFolder":false,"site":{"creationTime":0,"customTitle":"","favicon":"chrome-extension://mnojpmjdmbbfmejpflffifhffcmidifd/img/newtab/defaultTopSitesIcon/brave.ico","lastAccessedTime":1,"location":"https://brave.com/","title":"Brave Software | Building a Better Web"}},"deviceId":[5],"objectData":"bookmark","objectId":[57,70,144,212,237,131,12,182,117,184,46,131,46,82,113,92],"syncTimestamp":1499277759402},null]] +module.exports.data = [[{"action":0,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.facebook.com/","title":"Facebook – log in or sign up"}},"deviceId":[1],"objectData":"bookmark","objectId":[180,199,181,215,249,207,92,243,63,69,164,188,146,53,214,145],"syncTimestamp":1499182364028},null],[{"action":1,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"https://abs.twimg.com/favicons/favicon.ico","lastAccessedTime":1499182519524,"location":"https://twitter.com/brave","title":"Brave Software (@brave) | Twitter"}},"deviceId":[0],"objectData":"bookmark","objectId":[102,169,71,255,160,7,199,37,174,1,89,148,37,235,137,188],"syncTimestamp":1499182523711},null],[{"action":0,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://en.m.wikipedia.org/wiki/Main_Page","title":"Wikipedia, the free encyclopedia"}},"deviceId":[1],"objectData":"bookmark","objectId":[50,155,253,23,226,251,247,250,190,65,73,221,118,109,70,61],"syncTimestamp":1499182560767},null],[{"action":0,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://www.yahoo.com/","title":"Yahoo"}},"deviceId":[1],"objectData":"bookmark","objectId":[100,238,226,152,162,241,78,165,80,209,69,56,43,239,246,120],"syncTimestamp":1499183235567},null],[{"action":1,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"chrome-extension://mnojpmjdmbbfmejpflffifhffcmidifd/img/newtab/defaultTopSitesIcon/brave.ico","lastAccessedTime":1,"location":"https://brave.com/","title":"Brave Software | Building a Better Web"}},"deviceId":[0],"objectData":"bookmark","objectId":[57,70,144,212,237,131,12,182,117,184,46,131,46,82,113,92],"syncTimestamp":1499183408150},null],[{"action":0,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://www.theguardian.com/international","title":"News, sport and opinion from the Guardian's global edition | The Guardian"}},"deviceId":[2],"objectData":"bookmark","objectId":[50,18,21,48,179,186,120,100,165,225,251,230,29,214,223,181],"syncTimestamp":1499197431917},null],[{"action":0,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder1"}},"deviceId":[2],"objectData":"bookmark","objectId":[8,146,83,105,4,60,9,110,111,10,161,34,175,6,41,174],"syncTimestamp":1499197515513},null],[{"action":1,"bookmark":{"isFolder":false,"parentFolderObjectId":[8,146,83,105,4,60,9,110,111,10,161,34,175,6,41,174],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://www.theguardian.com/international","title":"News, sport and opinion from the Guardian's global edition | The Guardian"}},"deviceId":[2],"objectData":"bookmark","objectId":[50,18,21,48,179,186,120,100,165,225,251,230,29,214,223,181],"syncTimestamp":1499197515547},null],[{"action":0,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder0"}},"deviceId":[2],"objectData":"bookmark","objectId":[185,137,250,90,121,233,6,219,254,150,22,101,172,45,193,26],"syncTimestamp":1499197572153},null],[{"action":1,"bookmark":{"isFolder":true,"parentFolderObjectId":[185,137,250,90,121,233,6,219,254,150,22,101,172,45,193,26],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder1"}},"deviceId":[2],"objectData":"bookmark","objectId":[8,146,83,105,4,60,9,110,111,10,161,34,175,6,41,174],"syncTimestamp":1499197572168},null],[{"action":1,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder1"}},"deviceId":[2],"objectData":"bookmark","objectId":[8,146,83,105,4,60,9,110,111,10,161,34,175,6,41,174],"syncTimestamp":1499197616732},null],[{"action":0,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[2],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499197733375},null],[{"action":0,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder2"}},"deviceId":[4],"objectData":"bookmark","objectId":[12,150,93,122,6,116,243,239,237,197,68,195,0,188,183,150],"syncTimestamp":1499276712538},null],[{"action":1,"bookmark":{"isFolder":false,"parentFolderObjectId":[12,150,93,122,6,116,243,239,237,197,68,195,0,188,183,150],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://www.yahoo.com/","title":"Yahoo"}},"deviceId":[4],"objectData":"bookmark","objectId":[100,238,226,152,162,241,78,165,80,209,69,56,43,239,246,120],"syncTimestamp":1499276712550},null],[{"action":0,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder3"}},"deviceId":[4],"objectData":"bookmark","objectId":[186,183,34,202,153,217,44,45,153,87,34,117,39,115,246,80],"syncTimestamp":1499276733365},null],[{"action":1,"bookmark":{"isFolder":true,"parentFolderObjectId":[186,183,34,202,153,217,44,45,153,87,34,117,39,115,246,80],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder2"}},"deviceId":[4],"objectData":"bookmark","objectId":[12,150,93,122,6,116,243,239,237,197,68,195,0,188,183,150],"syncTimestamp":1499276733388},null],[{"action":1,"bookmark":{"isFolder":true,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"","title":"Folder2"}},"deviceId":[4],"objectData":"bookmark","objectId":[12,150,93,122,6,116,243,239,237,197,68,195,0,188,183,150],"syncTimestamp":1499276801913},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[4],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499277601715},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[4],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499277601736},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[4],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499277601751},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[4],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499277601768},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.slashdot.org/","title":"Slashdot"}},"deviceId":[4],"objectData":"bookmark","objectId":[28,88,77,105,60,148,56,216,234,167,75,220,142,91,173,37],"syncTimestamp":1499277601785},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://m.facebook.com/","title":"Facebook – log in or sign up"}},"deviceId":[5],"objectData":"bookmark","objectId":[180,199,181,215,249,207,92,243,63,69,164,188,146,53,214,145],"syncTimestamp":1499277751881},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"https://abs.twimg.com/favicons/favicon.ico","lastAccessedTime":1499182519524,"location":"https://twitter.com/brave","title":"Brave Software (@brave) | Twitter"}},"deviceId":[5],"objectData":"bookmark","objectId":[102,169,71,255,160,7,199,37,174,1,89,148,37,235,137,188],"syncTimestamp":1499277754662},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"","lastAccessedTime":0,"location":"https://en.m.wikipedia.org/wiki/Main_Page","title":"Wikipedia, the free encyclopedia"}},"deviceId":[5],"objectData":"bookmark","objectId":[50,155,253,23,226,251,247,250,190,65,73,221,118,109,70,61],"syncTimestamp":1499277757092},null],[{"action":2,"bookmark":{"isFolder":false,"parentFolderObjectId":[],"site":{"creationTime":0,"customTitle":"","favicon":"chrome-extension://mnojpmjdmbbfmejpflffifhffcmidifd/img/newtab/defaultTopSitesIcon/brave.ico","lastAccessedTime":1,"location":"https://brave.com/","title":"Brave Software | Building a Better Web"}},"deviceId":[5],"objectData":"bookmark","objectId":[57,70,144,212,237,131,12,182,117,184,46,131,46,82,113,92],"syncTimestamp":1499277759402},null]] diff --git a/test/client/recordUtil.js b/test/client/recordUtil.js index fa3dc3b..0a87639 100644 --- a/test/client/recordUtil.js +++ b/test/client/recordUtil.js @@ -377,7 +377,7 @@ test('recordUtil.resolve', (t) => { }) test('recordUtil.resolveRecords()', (t) => { - t.plan(5) + t.plan(7) t.test(`${t.name} resolves same data cross-platform on laptop and android`, (t) => { t.plan(1) @@ -405,7 +405,7 @@ test('recordUtil.resolveRecords()', (t) => { t.deepEquals(resolved, expected, t.name) }) - t.test(`${t.name} sequential Updates should become no op`, (t) => { + t.test(`${t.name} sequential Updates should become no op if the merged result equals to existingObject`, (t) => { t.plan(1) const update1 = UpdateRecord({ objectId: recordBookmark.objectId, @@ -437,11 +437,47 @@ test('recordUtil.resolveRecords()', (t) => { { site: Object.assign({}, siteProps, updateSiteProps) } ) }) - const input = [[recordBookmark, null], [updateBookmark, null]] + var recordBookmarkCopy = recordBookmark + recordBookmarkCopy.action = proto.actions.CREATE + const input = [[recordBookmarkCopy, null], [updateBookmark, null]] const resolved = recordUtil.resolveRecords(input) t.deepEquals(resolved, [expectedRecord], t.name) }) + t.test(`${t.name} Create + Update + Update of an existing object should resolve to a separate Update`, (t) => { + t.plan(1) + + var createBookmark = recordBookmark + createBookmark.action = proto.actions.CREATE + const existingObject = recordBookmark + + var updateBookmark1 = updateBookmark + updateBookmark1.bookmark.site.title = 'Title1' + var updateBookmark2 = updateBookmark + updateBookmark2.bookmark.site.title = 'Title2' + + const input = [[createBookmark, existingObject], [updateBookmark1, existingObject], [updateBookmark2, existingObject]] + const resolved = recordUtil.resolveRecords(input) + const expected = [updateBookmark2] + t.deepEquals(resolved, expected, t.name) + }) + + t.test(`${t.name} Update + Update of an existing object should resolve a single Update`, (t) => { + t.plan(1) + + const existingObject = recordBookmark + + var updateBookmark1 = updateBookmark + updateBookmark1.bookmark.site.title = 'Title1' + var updateBookmark2 = updateBookmark + updateBookmark2.bookmark.site.title = 'Title2' + + const input = [[updateBookmark1, existingObject], [updateBookmark2, existingObject]] + const resolved = recordUtil.resolveRecords(input) + const expected = [updateBookmark2] + t.deepEquals(resolved, expected, t.name) + }) + t.test(`${t.name} resolves bookmark records with same parent folder`, (t) => { t.plan(1) const record = {