Relations support for Z.DataObjectUtilities.diff() and applyChanges()

This commit is contained in:
Dan Stillman 2015-05-20 03:41:22 -04:00
parent c9aed60acd
commit e8a04dffd0
2 changed files with 608 additions and 260 deletions

View File

@ -259,7 +259,9 @@ Zotero.DataObjectUtilities = {
// All remaining fields don't exist in data1 // All remaining fields don't exist in data1
if (data2[field] === false || data2[field] === "") { let val = data2[field];
if (val === false || val === ""
|| (typeof val == 'object' && Object.keys(val).length == 0)) {
continue; continue;
} }
@ -351,10 +353,38 @@ Zotero.DataObjectUtilities = {
}, },
_relationsDiff: function (data1, data2) { _relationsDiff: function (data1, data2) {
if (!data1.length && !data2.length) { data2 = data2 || {};
return []; var changeset = [];
for (let pred in data1) {
let vals1 = typeof data1[pred] == 'string' ? [data1[pred]] : data1[pred];
let vals2 = (!data2[pred] || data2[pred] === '')
? []
: typeof data2[pred] == 'string' ? [data2[pred]] : data2[pred];
var removed = Zotero.Utilities.arrayDiff(vals1, vals2);
for (let i = 0; i < removed.length; i++) {
changeset.push({
field: "relations",
op: "property-member-remove",
value: {
key: pred,
value: removed[i]
}
});
}
let added = Zotero.Utilities.arrayDiff(vals2, vals1);
for (let i = 0; i < added.length; i++) {
changeset.push({
field: "relations",
op: "property-member-add",
value: {
key: pred,
value: added[i]
}
});
}
} }
throw new Error("Unimplemented"); return changeset;
}, },
@ -385,10 +415,6 @@ Zotero.DataObjectUtilities = {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
break; break;
case 'relations':
throw new Error("Unimplemented");
break;
case 'tags': case 'tags':
let found = false; let found = false;
for (let i = 0; i < json[c.field].length; i++) { for (let i = 0; i < json[c.field].length; i++) {
@ -403,7 +429,7 @@ Zotero.DataObjectUtilities = {
break; break;
default: default:
throw new Error("Unexpected field"); throw new Error("Unexpected field '" + c.field + "'");
} }
} }
else if (c.op == 'member-remove') { else if (c.op == 'member-remove') {
@ -430,12 +456,67 @@ Zotero.DataObjectUtilities = {
break; break;
default: default:
throw new Error("Unexpected field"); throw new Error("Unexpected field '" + c.field + "'");
}
}
else if (c.op == 'property-member-add') {
switch (c.field) {
case 'relations':
let obj = json[c.field];
let prop = c.value.key;
let val = c.value.value;
if (!obj) {
obj = json[c.field] = {};
}
if (!obj[prop]) {
obj[prop] = [];
}
// Convert string to array
if (typeof obj[prop] == 'string') {
obj[prop] = [obj[prop]];
}
if (obj[prop].indexOf(val) == -1) {
obj[prop].push(val);
}
break;
default:
throw new Error("Unexpected field '" + c.field + "'");
}
}
else if (c.op == 'property-member-remove') {
switch (c.field) {
case 'relations':
let obj = json[c.field];
let prop = c.value.key;
let val = c.value.value;
if (!obj || !obj[prop]) {
continue;
}
if (typeof obj[prop] == 'string') {
// If propetty was the specified string, remove property
if (obj[prop] === val) {
delete obj[prop];
}
continue;
}
let pos = obj[prop].indexOf(val);
if (pos == -1) {
continue;
}
obj[prop].splice(pos, 1);
// If no more members in property array, remove property
if (obj[prop].length == 0) {
delete obj[prop];
}
break;
default:
throw new Error("Unexpected field '" + c.field + "'");
} }
} }
// TODO: properties
else { else {
throw new Error("Unimplemented"); throw new Error("Unexpected change operation '" + c.op + "'");
} }
} }
} }

View File

@ -1,277 +1,544 @@
"use strict"; "use strict";
describe("Zotero.DataObjectUtilities", function() { describe("Zotero.DataObjectUtilities", function() {
// This is mostly covered by syncLocal::_reconcileChanges() tests, but we test some
// additional things here
describe("#diff()", function () { describe("#diff()", function () {
it("should not show empty items as different", function* () { //
var id1, id2, json1, json2; // Fields
yield Zotero.DB.executeTransaction(function* () { //
var item = new Zotero.Item('book'); describe("fields", function () {
id1 = yield item.save(); it("should not show empty items as different", function* () {
item = yield Zotero.Items.getAsync(id1); var id1, id2, json1, json2;
json1 = yield item.toJSON(); yield Zotero.DB.executeTransaction(function* () {
var item = new Zotero.Item('book');
id1 = yield item.save();
item = yield Zotero.Items.getAsync(id1);
json1 = yield item.toJSON();
var item = new Zotero.Item('book');
id2 = yield item.save();
item = yield Zotero.Items.getAsync(id2);
json2 = yield item.toJSON();
});
var item = new Zotero.Item('book'); var changes = Zotero.DataObjectUtilities.diff(json1, json2);
id2 = yield item.save(); assert.lengthOf(changes, 0);
item = yield Zotero.Items.getAsync(id2);
json2 = yield item.toJSON(); yield Zotero.Items.erase(id1, id2);
}); })
var changes = Zotero.DataObjectUtilities.diff(json1, json2); it("should not show empty strings as different", function () {
assert.lengthOf(changes, 0); var json1 = {
title: ""
};
var json2 = {
title: ""
};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
assert.lengthOf(changes, 0);
})
yield Zotero.Items.erase(id1, id2); it("should not show empty string and undefined as different", function () {
var json1 = {
title: ""
};
var json2 = {
place: ""
};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
assert.lengthOf(changes, 0);
})
}) })
it("should not show empty strings as different", function* () { //
var json1 = { // Creators
title: "" //
}; describe("creators", function () {
var json2 = { it("should not show identical creators as different", function () {
title: "" var json1 = {
}; creators: [
var changes = Zotero.DataObjectUtilities.diff(json1, json2); {
assert.lengthOf(changes, 0); name: "Center for History and New Media",
creatorType: "author"
}
]
};
var json2 = {
creators: [
{
creatorType: "author",
name: "Center for History and New Media"
}
]
};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
assert.lengthOf(changes, 0);
})
it("should not show an empty creators array and a missing one as different", function () {
var json1 = {
creators: []
};
var json2 = {};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
assert.lengthOf(changes, 0);
var json1 = {};
var json2 = {
creators: []
};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
assert.lengthOf(changes, 0);
})
}) })
it("should not show empty string and undefined as different", function* () { //
var json1 = { // Relations
title: "" //
}; describe("relations", function () {
var json2 = { it("should not show an empty relations object and a missing one as different", function () {
place: "" var json1 = {
}; relations: {}
var changes = Zotero.DataObjectUtilities.diff(json1, json2); };
assert.lengthOf(changes, 0); var json2 = {
};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
Zotero.debug(changes);
assert.lengthOf(changes, 0);
var json1 = {};
var json2 = {
relations: {}
};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
Zotero.debug(changes);
assert.lengthOf(changes, 0);
})
}) })
it("should not show identical creators as different", function* () { //
var json1 = { // Tags
creators: [ //
{ describe("tags", function () {
name: "Center for History and New Media", it("should not show manual tags with or without 'type' property as different", function () {
creatorType: "author" var json1 = {
} tags: [
] {
};
var json2 = {
creators: [
{
creatorType: "author",
name: "Center for History and New Media"
}
]
};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
assert.lengthOf(changes, 0);
})
it("should not show manual tags with or without 'type' property as different", function* () {
var json1 = {
tags: [
{
tag: "Foo"
}
]
};
var json2 = {
tags: [
{
tag: "Foo",
type: 0
}
]
};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
assert.lengthOf(changes, 0);
})
it("should show tags of different types as different", function* () {
var json1 = {
tags: [
{
tag: "Foo"
}
]
};
var json2 = {
tags: [
{
tag: "Foo",
type: 1
}
]
};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
assert.sameDeepMembers(
changes,
[
{
field: "tags",
op: "member-remove",
value: {
tag: "Foo" tag: "Foo"
} }
]
};
var json2 = {
tags: [
{
tag: "Foo",
type: 0
}
]
};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
assert.lengthOf(changes, 0);
})
it("should show tags of different types as different", function () {
var json1 = {
tags: [
{
tag: "Foo"
}
]
};
var json2 = {
tags: [
{
tag: "Foo",
type: 1
}
]
};
var changes = Zotero.DataObjectUtilities.diff(json1, json2);
assert.sameDeepMembers(
changes,
[
{
field: "tags",
op: "member-remove",
value: {
tag: "Foo"
}
},
{
field: "tags",
op: "member-add",
value: {
tag: "Foo",
type: 1
}
}
]
);
})
})
})
describe("#applyChanges()", function () {
//
// Fields
//
describe("fields", function () {
it("should set added/modified field values", function () {
var json = {
title: "A"
};
var changes = [
{
field: "title",
op: "add",
value: "B"
}, },
{
field: "date",
op: "modify",
value: "2015-05-19"
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.equal(json.title, "B");
assert.equal(json.date, "2015-05-19");
})
})
//
// Collections
//
describe("collections", function () {
it("should add a collection", function () {
var json = {
collections: ["AAAAAAAA"]
};
var changes = [
{
field: "collections",
op: "member-add",
value: "BBBBBBBB"
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.sameMembers(json.collections, ["AAAAAAAA", "BBBBBBBB"]);
})
it("should not duplicate an existing collection", function () {
var json = {
collections: ["AAAAAAAA"]
};
var changes = [
{
field: "collections",
op: "member-add",
value: "AAAAAAAA"
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.sameMembers(json.collections, ["AAAAAAAA"]);
assert.lengthOf(json.collections, 1);
})
it("should remove a collection", function () {
var json = {
collections: ["AAAAAAAA"]
};
var changes = [
{
field: "collections",
op: "member-remove",
value: "AAAAAAAA"
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.lengthOf(json.collections, 0);
})
})
//
// Relations
//
describe("relations", function () {
it("should add a predicate and object to an empty relations object", function () {
var json = {
relations: {}
};
var changes = [
{
field: "relations",
op: "property-member-add",
value: {
key: "a",
value: "A"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.sameDeepMembers([json.relations], [{ a: ["A"] }]);
})
it("should add a predicate and object to a missing relations object", function () {
var json = {};
var changes = [
{
field: "relations",
op: "property-member-add",
value: {
key: "a",
value: "A"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.sameDeepMembers([json.relations], [{ a: ["A"] }]);
})
it("should add an object to an existing predicate string", function () {
var json = {
relations: {
a: 'A1'
}
};
var changes = [
{
field: "relations",
op: "property-member-add",
value: {
key: "a",
value: "A2"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.sameDeepMembers([json.relations], [{ a: ["A1", "A2"] }]);
})
it("should add an object to an existing predicate array", function () {
var json = {
relations: {
a: ['A1']
}
};
var changes = [
{
field: "relations",
op: "property-member-add",
value: {
key: "a",
value: "A2"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.sameDeepMembers([json.relations], [{ a: ['A1', 'A2'] }]);
})
it("should ignore a removal for an missing relations object", function () {
var json = {};
var changes = [
{
field: "relations",
op: "property-member-remove",
value: {
key: "a",
value: "A"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.notProperty(json, 'relations');
})
it("should ignore a removal for a missing relations predicate", function () {
var json = {
relations: {}
};
var changes = [
{
field: "relations",
op: "property-member-remove",
value: {
key: "a",
value: "A"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.lengthOf(Object.keys(json.relations), 0);
})
it("should ignore a removal for a missing object", function () {
var json = {
relations: {
a: ['A1']
}
};
var changes = [
{
field: "relations",
op: "property-member-remove",
value: {
key: "a",
value: "A2"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.sameDeepMembers([json.relations], [{ a: ['A1'] }]);
})
it("should remove a predicate and object string from a relations object", function () {
var json = {
relations: {
a: "A"
}
};
var changes = [
{
field: "relations",
op: "property-member-remove",
value: {
key: "a",
value: "A"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.lengthOf(Object.keys(json.relations), 0);
})
it("should remove a predicate and object array from a relations object", function () {
var json = {
relations: {
a: ["A"]
}
};
var changes = [
{
field: "relations",
op: "property-member-remove",
value: {
key: "a",
value: "A"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.lengthOf(Object.keys(json.relations), 0);
})
it("should remove an object from an existing predicate array", function () {
var json = {
relations: {
a: ['A1', 'A2']
}
};
var changes = [
{
field: "relations",
op: "property-member-remove",
value: {
key: "a",
value: "A2"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.sameDeepMembers([json.relations], [{ a: ["A1"] }]);
})
})
//
// Tags
//
describe("tags", function () {
it("should add a tag", function () {
var json = {
tags: [
{
tag: "A"
}
]
};
var changes = [
{ {
field: "tags", field: "tags",
op: "member-add", op: "member-add",
value: { value: {
tag: "Foo", tag: "B"
type: 1
} }
} }
] ];
); Zotero.DataObjectUtilities.applyChanges(json, changes);
}) assert.sameDeepMembers(
}) json.tags,
[
describe("#applyChanges()", function () { {
it("should set added/modified field values", function* () { tag: "A"
var json = { },
title: "A" {
}; tag: "B"
var changes = [ }
{ ]
field: "title", );
op: "add", })
value: "B"
}, it("should not duplicate an existing tag", function () {
{ var json = {
field: "date", tags: [
op: "modify", {
value: "2015-05-19" tag: "A"
} }
]; ]
Zotero.DataObjectUtilities.applyChanges(json, changes); };
assert.equal(json.title, "B"); var changes = [
assert.equal(json.date, "2015-05-19");
})
it("should add a collection", function* () {
var json = {
collections: ["AAAAAAAA"]
};
var changes = [
{
field: "collections",
op: "member-add",
value: "BBBBBBBB"
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.sameMembers(json.collections, ["AAAAAAAA", "BBBBBBBB"]);
})
it("should not duplicate an existing collection", function* () {
var json = {
collections: ["AAAAAAAA"]
};
var changes = [
{
field: "collections",
op: "member-add",
value: "AAAAAAAA"
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.sameMembers(json.collections, ["AAAAAAAA"]);
assert.lengthOf(json.collections, 1);
})
it("should remove a collection", function* () {
var json = {
collections: ["AAAAAAAA"]
};
var changes = [
{
field: "collections",
op: "member-remove",
value: "AAAAAAAA"
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.lengthOf(json.collections, 0);
})
it("should add a tag", function* () {
var json = {
tags: [
{ {
tag: "A" field: "tags",
op: "member-add",
value: {
tag: "A"
}
} }
] ];
}; Zotero.DataObjectUtilities.applyChanges(json, changes);
var changes = [ assert.sameDeepMembers(
{ json.tags,
field: "tags", [
op: "member-add", {
value: { tag: "A"
tag: "B" }
} ]
} );
]; assert.lengthOf(json.tags, 1);
Zotero.DataObjectUtilities.applyChanges(json, changes); })
assert.sameDeepMembers(
json.tags, it("should remove a tag", function () {
[ var json = {
tags: [
{
tag: "A"
}
]
};
var changes = [
{ {
tag: "A" field: "tags",
}, op: "member-remove",
{ value: {
tag: "B" tag: "A"
}
} }
] ];
); Zotero.DataObjectUtilities.applyChanges(json, changes);
}) assert.lengthOf(json.tags, 0);
})
it("should not duplicate an existing tag", function* () {
var json = {
tags: [
{
tag: "A"
}
]
};
var changes = [
{
field: "tags",
op: "member-add",
value: {
tag: "A"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.sameDeepMembers(
json.tags,
[
{
tag: "A"
}
]
);
assert.lengthOf(json.tags, 1);
})
it("should remove a tag", function* () {
var json = {
tags: [
{
tag: "A"
}
]
};
var changes = [
{
field: "tags",
op: "member-remove",
value: {
tag: "A"
}
}
];
Zotero.DataObjectUtilities.applyChanges(json, changes);
assert.lengthOf(json.tags, 0);
}) })
}) })
}) })