2015-06-04 23:01:18 +00:00
|
|
|
|
"use strict";
|
|
|
|
|
|
2015-05-01 16:41:41 +00:00
|
|
|
|
describe("Zotero.Item", function () {
|
2015-04-25 07:14:53 +00:00
|
|
|
|
describe("#getField()", function () {
|
2015-06-02 23:08:52 +00:00
|
|
|
|
it("should return an empty string for valid unset fields on unsaved items", function () {
|
2015-04-25 07:14:53 +00:00
|
|
|
|
var item = new Zotero.Item('book');
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.strictEqual(item.getField('rights'), "");
|
2015-04-25 07:14:53 +00:00
|
|
|
|
});
|
|
|
|
|
|
2015-06-02 23:08:52 +00:00
|
|
|
|
it("should return an empty string for valid unset fields on unsaved items after setting on another field", function () {
|
2015-04-25 07:14:53 +00:00
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.setField('title', 'foo');
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.strictEqual(item.getField('rights'), "");
|
2015-04-25 07:14:53 +00:00
|
|
|
|
});
|
|
|
|
|
|
2015-06-02 23:08:52 +00:00
|
|
|
|
it("should return an empty string for invalid unset fields on unsaved items after setting on another field", function () {
|
2015-04-25 07:14:53 +00:00
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.setField('title', 'foo');
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.strictEqual(item.getField('invalid'), "");
|
2015-04-25 07:14:53 +00:00
|
|
|
|
});
|
2017-02-16 04:13:35 +00:00
|
|
|
|
|
|
|
|
|
it("should return a firstCreator for an unsaved item", function* () {
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
item.setCreators([
|
|
|
|
|
{
|
|
|
|
|
firstName: "A",
|
|
|
|
|
lastName: "B",
|
|
|
|
|
creatorType: "author"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
firstName: "C",
|
|
|
|
|
lastName: "D",
|
|
|
|
|
creatorType: "editor"
|
|
|
|
|
}
|
|
|
|
|
]);
|
|
|
|
|
assert.equal(item.getField('firstCreator'), "B");
|
|
|
|
|
});
|
2015-04-25 07:14:53 +00:00
|
|
|
|
});
|
|
|
|
|
|
2015-05-01 16:41:41 +00:00
|
|
|
|
describe("#setField", function () {
|
2015-06-02 18:34:51 +00:00
|
|
|
|
it("should throw an error if item type isn't set", function () {
|
2015-05-06 08:16:54 +00:00
|
|
|
|
var item = new Zotero.Item;
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.throws(() => item.setField('title', 'test'), "Item type must be set before setting field data");
|
2015-05-06 08:16:54 +00:00
|
|
|
|
})
|
|
|
|
|
|
2015-05-05 08:45:48 +00:00
|
|
|
|
it("should mark a field as changed", function () {
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.setField('title', 'Foo');
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.isTrue(item._changed.itemData[Zotero.ItemFields.getID('title')]);
|
|
|
|
|
assert.isTrue(item.hasChanged());
|
2015-05-05 08:45:48 +00:00
|
|
|
|
})
|
|
|
|
|
|
2016-05-18 20:07:00 +00:00
|
|
|
|
it("should save an integer as a string", function* () {
|
|
|
|
|
var val = 1234;
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.setField('numPages', val);
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
assert.strictEqual(item.getField('numPages'), "" + val);
|
|
|
|
|
// Setting again as string shouldn't register a change
|
|
|
|
|
assert.isFalse(item.setField('numPages', "" + val));
|
|
|
|
|
|
|
|
|
|
// Value should be TEXT in the DB
|
|
|
|
|
var sql = "SELECT TYPEOF(value) FROM itemData JOIN itemDataValues USING (valueID) "
|
|
|
|
|
+ "WHERE itemID=? AND fieldID=?";
|
|
|
|
|
var type = yield Zotero.DB.valueQueryAsync(sql, [item.id, Zotero.ItemFields.getID('numPages')]);
|
|
|
|
|
assert.equal(type, 'text');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should save integer 0 as a string", function* () {
|
|
|
|
|
var val = 0;
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.setField('numPages', val);
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
assert.strictEqual(item.getField('numPages'), "" + val);
|
|
|
|
|
// Setting again as string shouldn't register a change
|
|
|
|
|
assert.isFalse(item.setField('numPages', "" + val));
|
|
|
|
|
});
|
|
|
|
|
|
2015-06-02 23:08:52 +00:00
|
|
|
|
it('should clear an existing field when ""/null/false is passed', function* () {
|
2015-05-05 08:45:48 +00:00
|
|
|
|
var field = 'title';
|
2015-06-02 23:08:52 +00:00
|
|
|
|
var val = 'foo';
|
2015-05-05 08:45:48 +00:00
|
|
|
|
var fieldID = Zotero.ItemFields.getID(field);
|
|
|
|
|
var item = new Zotero.Item('book');
|
2015-06-02 23:08:52 +00:00
|
|
|
|
item.setField(field, val);
|
|
|
|
|
yield item.saveTx();
|
2015-05-05 08:45:48 +00:00
|
|
|
|
|
|
|
|
|
item.setField(field, "");
|
|
|
|
|
assert.ok(item._changed.itemData[fieldID]);
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.isTrue(item.hasChanged());
|
2015-05-05 08:45:48 +00:00
|
|
|
|
|
2015-06-02 23:08:52 +00:00
|
|
|
|
// Reset to original value
|
|
|
|
|
yield item.reload();
|
2015-05-05 08:45:48 +00:00
|
|
|
|
assert.isFalse(item.hasChanged());
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.equal(item.getField(field), val);
|
2015-05-05 08:45:48 +00:00
|
|
|
|
|
2015-06-02 23:08:52 +00:00
|
|
|
|
// false
|
2015-05-05 08:45:48 +00:00
|
|
|
|
item.setField(field, false);
|
|
|
|
|
assert.ok(item._changed.itemData[fieldID]);
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.isTrue(item.hasChanged());
|
|
|
|
|
|
|
|
|
|
// Reset to original value
|
2015-05-05 08:45:48 +00:00
|
|
|
|
yield item.reload();
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.isFalse(item.hasChanged());
|
|
|
|
|
assert.equal(item.getField(field), val);
|
|
|
|
|
|
|
|
|
|
// null
|
|
|
|
|
item.setField(field, null);
|
|
|
|
|
assert.ok(item._changed.itemData[fieldID]);
|
|
|
|
|
assert.isTrue(item.hasChanged());
|
2015-05-05 08:45:48 +00:00
|
|
|
|
|
2015-06-02 23:08:52 +00:00
|
|
|
|
yield item.saveTx();
|
|
|
|
|
assert.equal(item.getField(field), "");
|
|
|
|
|
})
|
|
|
|
|
|
2016-05-21 03:30:21 +00:00
|
|
|
|
it('should clear a field set to "0" when a ""/null/false is passed', function* () {
|
2015-06-02 23:08:52 +00:00
|
|
|
|
var field = 'title';
|
2016-05-18 20:07:00 +00:00
|
|
|
|
var val = "0";
|
2015-06-02 23:08:52 +00:00
|
|
|
|
var fieldID = Zotero.ItemFields.getID(field);
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.setField(field, val);
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.strictEqual(item.getField(field), val);
|
|
|
|
|
|
|
|
|
|
// ""
|
|
|
|
|
item.setField(field, "");
|
|
|
|
|
assert.ok(item._changed.itemData[fieldID]);
|
|
|
|
|
assert.isTrue(item.hasChanged());
|
|
|
|
|
|
|
|
|
|
// Reset to original value
|
|
|
|
|
yield item.reload();
|
2015-05-05 08:45:48 +00:00
|
|
|
|
assert.isFalse(item.hasChanged());
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.strictEqual(item.getField(field), val);
|
2015-05-05 08:45:48 +00:00
|
|
|
|
|
2015-06-02 23:08:52 +00:00
|
|
|
|
// False
|
|
|
|
|
item.setField(field, false);
|
|
|
|
|
assert.ok(item._changed.itemData[fieldID]);
|
|
|
|
|
assert.isTrue(item.hasChanged());
|
|
|
|
|
|
|
|
|
|
// Reset to original value
|
|
|
|
|
yield item.reload();
|
|
|
|
|
assert.isFalse(item.hasChanged());
|
|
|
|
|
assert.strictEqual(item.getField(field), val);
|
|
|
|
|
|
|
|
|
|
// null
|
2015-05-05 08:45:48 +00:00
|
|
|
|
item.setField(field, null);
|
|
|
|
|
assert.ok(item._changed.itemData[fieldID]);
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.isTrue(item.hasChanged());
|
2015-05-05 08:45:48 +00:00
|
|
|
|
|
2015-05-10 08:20:47 +00:00
|
|
|
|
yield item.saveTx();
|
2015-06-02 23:08:52 +00:00
|
|
|
|
assert.strictEqual(item.getField(field), "");
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should throw if value is undefined", function () {
|
|
|
|
|
var item = new Zotero.Item('book');
|
2015-08-06 08:04:37 +00:00
|
|
|
|
assert.throws(() => item.setField('title'), "'title' value cannot be undefined");
|
2015-05-05 08:45:48 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should not mark an empty field set to an empty string as changed", function () {
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.setField('url', '');
|
|
|
|
|
assert.isUndefined(item._changed.itemData);
|
|
|
|
|
})
|
|
|
|
|
|
2015-05-01 16:41:41 +00:00
|
|
|
|
it("should save version as object version", function* () {
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.setField("version", 1);
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
2015-05-01 16:41:41 +00:00
|
|
|
|
item = yield Zotero.Items.getAsync(id);
|
|
|
|
|
assert.equal(item.getField("version"), 1);
|
2015-05-21 06:47:01 +00:00
|
|
|
|
assert.equal(item.version, 1);
|
2015-05-01 16:41:41 +00:00
|
|
|
|
});
|
|
|
|
|
|
2015-06-02 18:34:51 +00:00
|
|
|
|
it("should save versionNumber for computerProgram", function* () {
|
2015-05-01 16:41:41 +00:00
|
|
|
|
var item = new Zotero.Item('computerProgram');
|
|
|
|
|
item.setField("versionNumber", "1.0");
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
2015-05-01 16:41:41 +00:00
|
|
|
|
item = yield Zotero.Items.getAsync(id);
|
|
|
|
|
assert.equal(item.getField("versionNumber"), "1.0");
|
|
|
|
|
});
|
2016-03-11 08:28:38 +00:00
|
|
|
|
|
|
|
|
|
it("should accept ISO 8601 dates", function* () {
|
|
|
|
|
var fields = {
|
|
|
|
|
accessDate: "2015-06-07T20:56:00Z",
|
|
|
|
|
dateAdded: "2015-06-07T20:57:00Z",
|
|
|
|
|
dateModified: "2015-06-07T20:58:00Z",
|
|
|
|
|
};
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
for (let i in fields) {
|
|
|
|
|
item.setField(i, fields[i]);
|
|
|
|
|
}
|
|
|
|
|
assert.equal(item.getField('accessDate'), '2015-06-07 20:56:00');
|
|
|
|
|
assert.equal(item.dateAdded, '2015-06-07 20:57:00');
|
|
|
|
|
assert.equal(item.dateModified, '2015-06-07 20:58:00');
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should accept SQL dates", function* () {
|
|
|
|
|
var fields = {
|
|
|
|
|
accessDate: "2015-06-07 20:56:00",
|
|
|
|
|
dateAdded: "2015-06-07 20:57:00",
|
|
|
|
|
dateModified: "2015-06-07 20:58:00",
|
|
|
|
|
};
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
for (let i in fields) {
|
|
|
|
|
item.setField(i, fields[i]);
|
|
|
|
|
item.getField(i, fields[i]);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2017-05-05 21:57:24 +00:00
|
|
|
|
it("should accept SQL accessDate without time", function* () {
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
var date = "2017-04-05";
|
|
|
|
|
item.setField("accessDate", date);
|
|
|
|
|
assert.strictEqual(item.getField('accessDate'), date);
|
|
|
|
|
});
|
|
|
|
|
|
2016-03-11 08:28:38 +00:00
|
|
|
|
it("should ignore unknown accessDate values", function* () {
|
|
|
|
|
var fields = {
|
|
|
|
|
accessDate: "foo"
|
|
|
|
|
};
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
for (let i in fields) {
|
|
|
|
|
item.setField(i, fields[i]);
|
|
|
|
|
}
|
|
|
|
|
assert.strictEqual(item.getField('accessDate'), '');
|
|
|
|
|
})
|
2015-05-01 16:41:41 +00:00
|
|
|
|
})
|
|
|
|
|
|
2015-05-12 23:55:14 +00:00
|
|
|
|
describe("#dateAdded", function () {
|
|
|
|
|
it("should use current time if value was not given for a new item", function* () {
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
item = Zotero.Items.get(id);
|
2015-05-12 23:55:14 +00:00
|
|
|
|
|
2015-05-13 00:09:10 +00:00
|
|
|
|
assert.closeTo(Zotero.Date.sqlToDate(item.dateAdded, true).getTime(), Date.now(), 2000);
|
2015-05-12 23:55:14 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should use given value for a new item", function* () {
|
|
|
|
|
var dateAdded = "2015-05-05 17:18:12";
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.dateAdded = dateAdded;
|
|
|
|
|
var id = yield item.saveTx();
|
|
|
|
|
item = yield Zotero.Items.getAsync(id);
|
|
|
|
|
assert.equal(item.dateAdded, dateAdded);
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2015-05-06 05:07:40 +00:00
|
|
|
|
describe("#dateModified", function () {
|
2015-05-12 23:55:14 +00:00
|
|
|
|
it("should use given value for a new item", function* () {
|
2015-05-06 05:07:40 +00:00
|
|
|
|
var dateModified = "2015-05-05 17:18:12";
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.dateModified = dateModified;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
2015-05-21 07:19:28 +00:00
|
|
|
|
assert.equal(item.dateModified, dateModified);
|
2015-05-06 05:07:40 +00:00
|
|
|
|
item = yield Zotero.Items.getAsync(id);
|
|
|
|
|
assert.equal(item.dateModified, dateModified);
|
|
|
|
|
})
|
|
|
|
|
|
2015-05-12 23:55:14 +00:00
|
|
|
|
it("should use given value when skipDateModifiedUpdate is set for a new item", function* () {
|
2015-05-06 05:07:40 +00:00
|
|
|
|
var dateModified = "2015-05-05 17:18:12";
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.dateModified = dateModified;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx({
|
2015-05-06 05:07:40 +00:00
|
|
|
|
skipDateModifiedUpdate: true
|
|
|
|
|
});
|
2015-05-21 07:19:28 +00:00
|
|
|
|
assert.equal(item.dateModified, dateModified);
|
2015-05-06 05:07:40 +00:00
|
|
|
|
item = yield Zotero.Items.getAsync(id);
|
|
|
|
|
assert.equal(item.dateModified, dateModified);
|
|
|
|
|
})
|
|
|
|
|
|
2015-05-12 23:55:14 +00:00
|
|
|
|
it("should use current time if value was not given for an existing item", function* () {
|
2015-05-06 05:07:40 +00:00
|
|
|
|
var dateModified = "2015-05-05 17:18:12";
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.dateModified = dateModified;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
item = Zotero.Items.get(id);
|
2015-05-06 05:07:40 +00:00
|
|
|
|
|
|
|
|
|
// Save again without changing Date Modified
|
|
|
|
|
item.setField('title', 'Test');
|
2015-05-10 08:20:47 +00:00
|
|
|
|
yield item.saveTx()
|
2015-05-06 05:07:40 +00:00
|
|
|
|
|
2015-05-13 00:09:10 +00:00
|
|
|
|
assert.closeTo(Zotero.Date.sqlToDate(item.dateModified, true).getTime(), Date.now(), 2000);
|
2015-05-06 05:07:40 +00:00
|
|
|
|
})
|
|
|
|
|
|
2015-05-12 23:55:14 +00:00
|
|
|
|
it("should use current time if the existing value was given for an existing item", function* () {
|
2015-05-06 05:07:40 +00:00
|
|
|
|
var dateModified = "2015-05-05 17:18:12";
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.dateModified = dateModified;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
item = Zotero.Items.get(id);
|
2015-05-06 05:07:40 +00:00
|
|
|
|
|
|
|
|
|
// Set Date Modified to existing value
|
|
|
|
|
item.setField('title', 'Test');
|
|
|
|
|
item.dateModified = dateModified;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
yield item.saveTx()
|
2015-05-13 00:09:10 +00:00
|
|
|
|
assert.closeTo(Zotero.Date.sqlToDate(item.dateModified, true).getTime(), Date.now(), 2000);
|
2015-05-06 05:07:40 +00:00
|
|
|
|
})
|
|
|
|
|
|
2015-05-12 23:55:14 +00:00
|
|
|
|
it("should use current time if value is not given when skipDateModifiedUpdate is set for a new item", function* () {
|
2015-05-06 05:07:40 +00:00
|
|
|
|
var item = new Zotero.Item('book');
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx({
|
2015-05-06 05:07:40 +00:00
|
|
|
|
skipDateModifiedUpdate: true
|
|
|
|
|
});
|
|
|
|
|
item = yield Zotero.Items.getAsync(id);
|
2015-05-13 00:09:10 +00:00
|
|
|
|
assert.closeTo(Zotero.Date.sqlToDate(item.dateModified, true).getTime(), Date.now(), 2000);
|
2015-05-06 05:07:40 +00:00
|
|
|
|
})
|
|
|
|
|
|
2015-05-12 23:55:14 +00:00
|
|
|
|
it("should keep original value when skipDateModifiedUpdate is set for an existing item", function* () {
|
2015-05-06 05:07:40 +00:00
|
|
|
|
var dateModified = "2015-05-05 17:18:12";
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.dateModified = dateModified;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
item = Zotero.Items.get(id);
|
2015-05-06 05:07:40 +00:00
|
|
|
|
|
|
|
|
|
// Resave with skipDateModifiedUpdate
|
|
|
|
|
item.setField('title', 'Test');
|
2015-05-10 08:20:47 +00:00
|
|
|
|
yield item.saveTx({
|
2015-05-06 05:07:40 +00:00
|
|
|
|
skipDateModifiedUpdate: true
|
|
|
|
|
})
|
|
|
|
|
assert.equal(item.dateModified, dateModified);
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2015-05-30 23:07:12 +00:00
|
|
|
|
describe("#deleted", function () {
|
|
|
|
|
it("should be set to true after save", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
item.deleted = true;
|
2017-02-15 18:15:30 +00:00
|
|
|
|
// Sanity check for itemsTest#trash()
|
|
|
|
|
assert.isTrue(item._changed.deleted);
|
2015-05-30 23:07:12 +00:00
|
|
|
|
yield item.saveTx();
|
|
|
|
|
assert.ok(item.deleted);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should be set to false after save", function* () {
|
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
item.deleted = true;
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
|
|
|
|
|
item.deleted = false;
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
assert.isFalse(item.deleted);
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2017-04-12 04:56:37 +00:00
|
|
|
|
describe("#inPublications", function () {
|
|
|
|
|
it("should add item to publications table", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
item.inPublications = true;
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
assert.ok(item.inPublications);
|
|
|
|
|
assert.equal(
|
|
|
|
|
(yield Zotero.DB.valueQueryAsync(
|
|
|
|
|
"SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)),
|
|
|
|
|
1
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should be set to false after save", function* () {
|
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
item.inPublications = false;
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
|
|
|
|
|
item.inPublications = false;
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
assert.isFalse(item.inPublications);
|
|
|
|
|
assert.equal(
|
|
|
|
|
(yield Zotero.DB.valueQueryAsync(
|
|
|
|
|
"SELECT COUNT(*) FROM publicationsItems WHERE itemID=?", item.id)),
|
|
|
|
|
0
|
|
|
|
|
);
|
2017-04-27 19:33:02 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should be invalid for linked-file attachments", function* () {
|
|
|
|
|
var item = yield createDataObject('item', { inPublications: true });
|
|
|
|
|
var attachment = yield Zotero.Attachments.linkFromFile({
|
|
|
|
|
file: OS.Path.join(getTestDataDirectory().path, 'test.png'),
|
|
|
|
|
parentItemID: item.id
|
|
|
|
|
});
|
|
|
|
|
attachment.inPublications = true;
|
|
|
|
|
var e = yield getPromiseError(attachment.saveTx());
|
|
|
|
|
assert.ok(e);
|
|
|
|
|
assert.include(e.message, "Linked-file attachments cannot be added to My Publications");
|
|
|
|
|
});
|
2017-05-05 01:18:44 +00:00
|
|
|
|
|
|
|
|
|
it("should be invalid for group library items", function* () {
|
|
|
|
|
var group = yield getGroup();
|
|
|
|
|
var item = yield createDataObject('item', { libraryID: group.libraryID });
|
|
|
|
|
item.inPublications = true;
|
|
|
|
|
var e = yield getPromiseError(item.saveTx());
|
|
|
|
|
assert.ok(e);
|
|
|
|
|
assert.equal(e.message, "Only items in user libraries can be added to My Publications");
|
|
|
|
|
});
|
2017-04-12 04:56:37 +00:00
|
|
|
|
});
|
|
|
|
|
|
2015-04-25 07:14:53 +00:00
|
|
|
|
describe("#parentID", function () {
|
2015-05-05 18:08:28 +00:00
|
|
|
|
it("should create a child note", function* () {
|
|
|
|
|
var item = new Zotero.Item('book');
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var parentItemID = yield item.saveTx();
|
2015-05-05 18:08:28 +00:00
|
|
|
|
|
|
|
|
|
item = new Zotero.Item('note');
|
|
|
|
|
item.parentID = parentItemID;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var childItemID = yield item.saveTx();
|
2015-05-05 18:08:28 +00:00
|
|
|
|
|
|
|
|
|
item = yield Zotero.Items.getAsync(childItemID);
|
|
|
|
|
assert.ok(item.parentID);
|
|
|
|
|
assert.equal(item.parentID, parentItemID);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("#parentKey", function () {
|
|
|
|
|
it("should be false for an unsaved attachment", function () {
|
|
|
|
|
var item = new Zotero.Item('attachment');
|
|
|
|
|
assert.isFalse(item.parentKey);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should be false on an unsaved non-attachment item", function () {
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
assert.isFalse(item.parentKey);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should not be marked as changed setting to false on an unsaved item", function () {
|
|
|
|
|
var item = new Zotero.Item('attachment');
|
|
|
|
|
item.attachmentLinkMode = 'linked_url';
|
|
|
|
|
item.parentKey = false;
|
|
|
|
|
assert.isUndefined(item._changed.parentKey);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should not mark item as changed if false and no existing parent", function* () {
|
|
|
|
|
var item = new Zotero.Item('attachment');
|
|
|
|
|
item.attachmentLinkMode = 'linked_url';
|
|
|
|
|
item.url = "https://www.zotero.org/";
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
2015-05-05 18:08:28 +00:00
|
|
|
|
item = yield Zotero.Items.getAsync(id);
|
|
|
|
|
|
|
|
|
|
item.parentKey = false;
|
|
|
|
|
assert.isFalse(item.hasChanged());
|
2015-04-25 07:14:53 +00:00
|
|
|
|
});
|
2015-05-07 05:00:45 +00:00
|
|
|
|
|
2018-01-05 00:10:41 +00:00
|
|
|
|
it("should not be marked as changed after a save", async function () {
|
|
|
|
|
var item = await createDataObject('item');
|
|
|
|
|
var attachment = new Zotero.Item('attachment');
|
|
|
|
|
attachment.attachmentLinkMode = 'linked_url';
|
|
|
|
|
await attachment.saveTx();
|
|
|
|
|
|
|
|
|
|
attachment.parentKey = item.key;
|
|
|
|
|
assert.isTrue(attachment._changed.parentKey);
|
|
|
|
|
await attachment.saveTx();
|
|
|
|
|
assert.isUndefined(attachment._changed.parentKey);
|
|
|
|
|
});
|
|
|
|
|
|
2015-05-07 05:00:45 +00:00
|
|
|
|
it("should move a top-level note under another item", function* () {
|
|
|
|
|
var noteItem = new Zotero.Item('note');
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield noteItem.saveTx()
|
2015-05-07 05:00:45 +00:00
|
|
|
|
noteItem = yield Zotero.Items.getAsync(id);
|
|
|
|
|
|
|
|
|
|
var item = new Zotero.Item('book');
|
2015-05-10 08:20:47 +00:00
|
|
|
|
id = yield item.saveTx();
|
2015-05-07 05:00:45 +00:00
|
|
|
|
var { libraryID, key } = Zotero.Items.getLibraryAndKeyFromID(id);
|
|
|
|
|
|
|
|
|
|
noteItem.parentKey = key;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
yield noteItem.saveTx();
|
2015-05-07 05:00:45 +00:00
|
|
|
|
|
|
|
|
|
assert.isFalse(noteItem.isTopLevelItem());
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should remove top-level item from collections when moving it under another item", function* () {
|
|
|
|
|
// Create a collection
|
|
|
|
|
var collection = new Zotero.Collection;
|
|
|
|
|
collection.name = "Test";
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var collectionID = yield collection.saveTx();
|
2015-05-07 05:00:45 +00:00
|
|
|
|
|
|
|
|
|
// Create a top-level note and add it to a collection
|
|
|
|
|
var noteItem = new Zotero.Item('note');
|
|
|
|
|
noteItem.addToCollection(collectionID);
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield noteItem.saveTx()
|
2015-05-07 05:00:45 +00:00
|
|
|
|
noteItem = yield Zotero.Items.getAsync(id);
|
|
|
|
|
|
|
|
|
|
var item = new Zotero.Item('book');
|
2015-05-10 08:20:47 +00:00
|
|
|
|
id = yield item.saveTx();
|
2015-05-07 05:00:45 +00:00
|
|
|
|
var { libraryID, key } = Zotero.Items.getLibraryAndKeyFromID(id);
|
|
|
|
|
noteItem.parentKey = key;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
yield noteItem.saveTx();
|
2015-05-07 05:00:45 +00:00
|
|
|
|
|
|
|
|
|
assert.isFalse(noteItem.isTopLevelItem());
|
|
|
|
|
})
|
2015-04-25 07:14:53 +00:00
|
|
|
|
});
|
2015-04-29 21:22:31 +00:00
|
|
|
|
|
2016-04-08 01:06:49 +00:00
|
|
|
|
describe("#getCreators()", function () {
|
|
|
|
|
it("should update after creators are removed", function* () {
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
item.setCreators([
|
|
|
|
|
{
|
|
|
|
|
creatorType: "author",
|
|
|
|
|
name: "A"
|
|
|
|
|
}
|
|
|
|
|
]);
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.lengthOf(item.getCreators(), 1);
|
|
|
|
|
|
|
|
|
|
item.setCreators([]);
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.lengthOf(item.getCreators(), 0);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
|
describe("#setCreators()", function () {
|
2015-05-08 17:26:11 +00:00
|
|
|
|
it("should accept an array of creators in API JSON format", function* () {
|
|
|
|
|
var creators = [
|
|
|
|
|
{
|
|
|
|
|
firstName: "First",
|
|
|
|
|
lastName: "Last",
|
|
|
|
|
creatorType: "author"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Test Name",
|
|
|
|
|
creatorType: "editor"
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
var item = new Zotero.Item("journalArticle");
|
|
|
|
|
item.setCreators(creators);
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
item = Zotero.Items.get(id);
|
2015-05-08 17:26:11 +00:00
|
|
|
|
assert.sameDeepMembers(item.getCreatorsJSON(), creators);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should accept an array of creators in internal format", function* () {
|
|
|
|
|
var creators = [
|
|
|
|
|
{
|
|
|
|
|
firstName: "First",
|
|
|
|
|
lastName: "Last",
|
|
|
|
|
fieldMode: 0,
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
|
creatorTypeID: Zotero.CreatorTypes.getID('author')
|
2015-05-08 17:26:11 +00:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
firstName: "",
|
|
|
|
|
lastName: "Test Name",
|
|
|
|
|
fieldMode: 1,
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
|
creatorTypeID: Zotero.CreatorTypes.getID('editor')
|
2015-05-08 17:26:11 +00:00
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
var item = new Zotero.Item("journalArticle");
|
|
|
|
|
item.setCreators(creators);
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
item = Zotero.Items.get(id);
|
2015-05-08 17:26:11 +00:00
|
|
|
|
assert.sameDeepMembers(item.getCreators(), creators);
|
|
|
|
|
})
|
2016-04-07 23:32:34 +00:00
|
|
|
|
|
|
|
|
|
it("should clear creators if empty array passed", function () {
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
item.setCreators([
|
|
|
|
|
{
|
|
|
|
|
firstName: "First",
|
|
|
|
|
lastName: "Last",
|
|
|
|
|
fieldMode: 0,
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
|
creatorTypeID: Zotero.CreatorTypes.getID('author')
|
2016-04-07 23:32:34 +00:00
|
|
|
|
}
|
|
|
|
|
]);
|
|
|
|
|
assert.lengthOf(item.getCreators(), 1);
|
|
|
|
|
item.setCreators([]);
|
|
|
|
|
assert.lengthOf(item.getCreators(), 0);
|
|
|
|
|
});
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
|
|
|
|
|
|
it("should switch to primary creator type if unknown type given", function () {
|
|
|
|
|
var item = createUnsavedDataObject('item', { itemType: 'book' });
|
|
|
|
|
item.setCreators([
|
|
|
|
|
{
|
|
|
|
|
firstName: "First",
|
|
|
|
|
lastName: "Last",
|
|
|
|
|
creatorType: "unknown"
|
|
|
|
|
}
|
|
|
|
|
]);
|
|
|
|
|
assert.equal(item.getCreators()[0].creatorTypeID, Zotero.CreatorTypes.getID('author'));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should switch to primary creator type on invalid creator type for a given item type", function () {
|
|
|
|
|
var item = createUnsavedDataObject('item', { itemType: 'book' });
|
|
|
|
|
item.setCreators([
|
|
|
|
|
{
|
|
|
|
|
firstName: "First",
|
|
|
|
|
lastName: "Last",
|
|
|
|
|
creatorType: "interviewee"
|
|
|
|
|
}
|
|
|
|
|
]);
|
|
|
|
|
assert.equal(item.getCreators()[0].creatorTypeID, Zotero.CreatorTypes.getID('author'));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should throw on unknown creator type in strict mode", function () {
|
|
|
|
|
var item = createUnsavedDataObject('item', { itemType: 'book' });
|
|
|
|
|
var f = () => {
|
|
|
|
|
item.setCreators(
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
firstName: "First",
|
|
|
|
|
lastName: "Last",
|
|
|
|
|
creatorType: "unknown"
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
{
|
|
|
|
|
strict: true
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
assert.throws(f, /^Unknown creator type/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should throw on invalid creator type for a given item type in strict mode", function () {
|
|
|
|
|
var item = createUnsavedDataObject('item', { itemType: 'book' });
|
|
|
|
|
var f = () => {
|
|
|
|
|
item.setCreators(
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
firstName: "First",
|
|
|
|
|
lastName: "Last",
|
|
|
|
|
creatorType: "interviewee"
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
{
|
|
|
|
|
strict: true
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
assert.throws(f, /^Invalid creator type/);
|
|
|
|
|
});
|
2015-05-08 17:26:11 +00:00
|
|
|
|
})
|
|
|
|
|
|
2017-08-30 22:06:25 +00:00
|
|
|
|
|
2019-01-29 09:57:40 +00:00
|
|
|
|
describe("#setCollections()", function () {
|
|
|
|
|
it("should add a collection with an all-numeric key", async function () {
|
|
|
|
|
var col = new Zotero.Collection();
|
|
|
|
|
col.libraryID = Zotero.Libraries.userLibraryID;
|
|
|
|
|
col.key = '23456789';
|
|
|
|
|
await col.loadPrimaryData();
|
|
|
|
|
col.name = 'Test';
|
|
|
|
|
var id = await col.saveTx();
|
|
|
|
|
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
item.setCollections([col.key]);
|
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.isTrue(col.hasItem(item));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2017-08-30 22:06:25 +00:00
|
|
|
|
describe("#numAttachments()", function () {
|
|
|
|
|
it("should include child attachments", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
var attachment = yield importFileAttachment('test.png', { parentID: item.id });
|
|
|
|
|
assert.equal(item.numAttachments(), 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("shouldn't include trashed child attachments by default", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
yield importFileAttachment('test.png', { parentID: item.id });
|
|
|
|
|
var attachment = yield importFileAttachment('test.png', { parentID: item.id });
|
|
|
|
|
attachment.deleted = true;
|
|
|
|
|
yield attachment.saveTx();
|
|
|
|
|
assert.equal(item.numAttachments(), 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should include trashed child attachments if includeTrashed=true", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
yield importFileAttachment('test.png', { parentID: item.id });
|
|
|
|
|
var attachment = yield importFileAttachment('test.png', { parentID: item.id });
|
|
|
|
|
attachment.deleted = true;
|
|
|
|
|
yield attachment.saveTx();
|
|
|
|
|
assert.equal(item.numAttachments(true), 2);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2015-05-28 02:18:40 +00:00
|
|
|
|
describe("#getAttachments()", function () {
|
|
|
|
|
it("#should return child attachments", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
var attachment = new Zotero.Item("attachment");
|
|
|
|
|
attachment.parentID = item.id;
|
|
|
|
|
attachment.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
|
|
|
|
|
yield attachment.saveTx();
|
|
|
|
|
|
|
|
|
|
var attachments = item.getAttachments();
|
|
|
|
|
assert.lengthOf(attachments, 1);
|
|
|
|
|
assert.equal(attachments[0], attachment.id);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("#should ignore trashed child attachments by default", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
var attachment = new Zotero.Item("attachment");
|
|
|
|
|
attachment.parentID = item.id;
|
|
|
|
|
attachment.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
|
|
|
|
|
attachment.deleted = true;
|
|
|
|
|
yield attachment.saveTx();
|
|
|
|
|
|
|
|
|
|
var attachments = item.getAttachments();
|
|
|
|
|
assert.lengthOf(attachments, 0);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("#should include trashed child attachments if includeTrashed=true", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
var attachment = new Zotero.Item("attachment");
|
|
|
|
|
attachment.parentID = item.id;
|
|
|
|
|
attachment.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
|
|
|
|
|
attachment.deleted = true;
|
|
|
|
|
yield attachment.saveTx();
|
|
|
|
|
|
|
|
|
|
var attachments = item.getAttachments(true);
|
|
|
|
|
assert.lengthOf(attachments, 1);
|
|
|
|
|
assert.equal(attachments[0], attachment.id);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("#should return an empty array for an item with no attachments", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
assert.lengthOf(item.getAttachments(), 0);
|
|
|
|
|
})
|
2016-04-08 01:06:49 +00:00
|
|
|
|
|
|
|
|
|
it("should update after an attachment is moved to another item", function* () {
|
|
|
|
|
var item1 = yield createDataObject('item');
|
|
|
|
|
var item2 = yield createDataObject('item');
|
|
|
|
|
var item3 = new Zotero.Item('attachment');
|
|
|
|
|
item3.parentID = item1.id;
|
|
|
|
|
item3.attachmentLinkMode = 'linked_url';
|
|
|
|
|
item3.setField('url', 'http://example.com');
|
|
|
|
|
yield item3.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.lengthOf(item1.getAttachments(), 1);
|
|
|
|
|
assert.lengthOf(item2.getAttachments(), 0);
|
|
|
|
|
|
|
|
|
|
item3.parentID = item2.id;
|
|
|
|
|
yield item3.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.lengthOf(item1.getAttachments(), 0);
|
|
|
|
|
assert.lengthOf(item2.getAttachments(), 1);
|
|
|
|
|
});
|
2015-05-28 02:18:40 +00:00
|
|
|
|
})
|
|
|
|
|
|
2017-08-30 22:06:25 +00:00
|
|
|
|
describe("#numNotes()", function () {
|
|
|
|
|
it("should include child notes", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
yield createDataObject('item', { itemType: 'note', parentID: item.id });
|
|
|
|
|
yield createDataObject('item', { itemType: 'note', parentID: item.id });
|
|
|
|
|
assert.equal(item.numNotes(), 2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("shouldn't include trashed child notes by default", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
yield createDataObject('item', { itemType: 'note', parentID: item.id });
|
|
|
|
|
yield createDataObject('item', { itemType: 'note', parentID: item.id, deleted: true });
|
|
|
|
|
assert.equal(item.numNotes(), 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should include trashed child notes with includeTrashed", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
yield createDataObject('item', { itemType: 'note', parentID: item.id });
|
|
|
|
|
yield createDataObject('item', { itemType: 'note', parentID: item.id, deleted: true });
|
|
|
|
|
assert.equal(item.numNotes(true), 2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should include child attachment notes with includeEmbedded", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
yield createDataObject('item', { itemType: 'note', parentID: item.id });
|
|
|
|
|
var attachment = yield importFileAttachment('test.png', { parentID: item.id });
|
|
|
|
|
attachment.setNote('test');
|
|
|
|
|
yield attachment.saveTx();
|
|
|
|
|
yield item.loadDataType('childItems');
|
|
|
|
|
assert.equal(item.numNotes(false, true), 2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("shouldn't include empty child attachment notes with includeEmbedded", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
yield createDataObject('item', { itemType: 'note', parentID: item.id });
|
|
|
|
|
var attachment = yield importFileAttachment('test.png', { parentID: item.id });
|
|
|
|
|
assert.equal(item.numNotes(false, true), 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// TODO: Fix numNotes(false, true) updating after child attachment note is added or removed
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2015-05-28 02:18:40 +00:00
|
|
|
|
describe("#getNotes()", function () {
|
|
|
|
|
it("#should return child notes", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
var note = new Zotero.Item("note");
|
|
|
|
|
note.parentID = item.id;
|
|
|
|
|
yield note.saveTx();
|
|
|
|
|
|
|
|
|
|
var notes = item.getNotes();
|
|
|
|
|
assert.lengthOf(notes, 1);
|
|
|
|
|
assert.equal(notes[0], note.id);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("#should ignore trashed child notes by default", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
var note = new Zotero.Item("note");
|
|
|
|
|
note.parentID = item.id;
|
|
|
|
|
note.deleted = true;
|
|
|
|
|
yield note.saveTx();
|
|
|
|
|
|
|
|
|
|
var notes = item.getNotes();
|
|
|
|
|
assert.lengthOf(notes, 0);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("#should include trashed child notes if includeTrashed=true", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
var note = new Zotero.Item("note");
|
|
|
|
|
note.parentID = item.id;
|
|
|
|
|
note.deleted = true;
|
|
|
|
|
yield note.saveTx();
|
|
|
|
|
|
|
|
|
|
var notes = item.getNotes(true);
|
|
|
|
|
assert.lengthOf(notes, 1);
|
|
|
|
|
assert.equal(notes[0], note.id);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("#should return an empty array for an item with no notes", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
assert.lengthOf(item.getNotes(), 0);
|
2016-04-08 01:06:49 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should update after a note is moved to another item", function* () {
|
|
|
|
|
var item1 = yield createDataObject('item');
|
|
|
|
|
var item2 = yield createDataObject('item');
|
|
|
|
|
var item3 = yield createDataObject('item', { itemType: 'note', parentID: item1.id });
|
|
|
|
|
|
|
|
|
|
assert.lengthOf(item1.getNotes(), 1);
|
|
|
|
|
assert.lengthOf(item2.getNotes(), 0);
|
|
|
|
|
|
|
|
|
|
item3.parentID = item2.id;
|
|
|
|
|
yield item3.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.lengthOf(item1.getNotes(), 0);
|
|
|
|
|
assert.lengthOf(item2.getNotes(), 1);
|
|
|
|
|
});
|
2015-05-28 02:18:40 +00:00
|
|
|
|
})
|
|
|
|
|
|
2015-05-06 06:49:16 +00:00
|
|
|
|
describe("#attachmentCharset", function () {
|
|
|
|
|
it("should get and set a value", function* () {
|
|
|
|
|
var charset = 'utf-8';
|
|
|
|
|
var item = new Zotero.Item("attachment");
|
|
|
|
|
item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
|
|
|
|
|
item.attachmentCharset = charset;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var itemID = yield item.saveTx();
|
2015-06-01 23:52:17 +00:00
|
|
|
|
assert.equal(item.attachmentCharset, charset);
|
2015-05-06 06:49:16 +00:00
|
|
|
|
item = yield Zotero.Items.getAsync(itemID);
|
|
|
|
|
assert.equal(item.attachmentCharset, charset);
|
|
|
|
|
})
|
|
|
|
|
|
2015-06-01 23:52:17 +00:00
|
|
|
|
it("should not allow a numerical value", function* () {
|
|
|
|
|
var charset = 1;
|
|
|
|
|
var item = new Zotero.Item("attachment");
|
|
|
|
|
try {
|
|
|
|
|
item.attachmentCharset = charset;
|
|
|
|
|
}
|
|
|
|
|
catch (e) {
|
|
|
|
|
assert.equal(e.message, "Character set must be a string")
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
assert.fail("Numerical charset was allowed");
|
|
|
|
|
})
|
|
|
|
|
|
2015-05-06 06:49:16 +00:00
|
|
|
|
it("should not be marked as changed if not changed", function* () {
|
|
|
|
|
var charset = 'utf-8';
|
|
|
|
|
var item = new Zotero.Item("attachment");
|
|
|
|
|
item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
|
|
|
|
|
item.attachmentCharset = charset;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var itemID = yield item.saveTx();
|
2015-05-06 06:49:16 +00:00
|
|
|
|
item = yield Zotero.Items.getAsync(itemID);
|
|
|
|
|
|
|
|
|
|
// Set charset to same value
|
|
|
|
|
item.attachmentCharset = charset
|
|
|
|
|
assert.isFalse(item.hasChanged());
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2015-04-29 21:22:31 +00:00
|
|
|
|
describe("#attachmentFilename", function () {
|
|
|
|
|
it("should get and set a filename for a stored file", function* () {
|
|
|
|
|
var filename = "test.txt";
|
|
|
|
|
|
|
|
|
|
// Create parent item
|
|
|
|
|
var item = new Zotero.Item("book");
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var parentItemID = yield item.saveTx();
|
2015-04-29 21:22:31 +00:00
|
|
|
|
|
|
|
|
|
// Create attachment item
|
|
|
|
|
var item = new Zotero.Item("attachment");
|
|
|
|
|
item.attachmentLinkMode = Zotero.Attachments.LINK_MODE_IMPORTED_FILE;
|
|
|
|
|
item.parentID = parentItemID;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var itemID = yield item.saveTx();
|
2015-04-29 21:22:31 +00:00
|
|
|
|
|
|
|
|
|
// Should be empty when unset
|
|
|
|
|
assert.equal(item.attachmentFilename, '');
|
|
|
|
|
|
|
|
|
|
// Set filename
|
|
|
|
|
item.attachmentFilename = filename;
|
2015-05-10 08:20:47 +00:00
|
|
|
|
yield item.saveTx();
|
2015-04-29 21:22:31 +00:00
|
|
|
|
item = yield Zotero.Items.getAsync(itemID);
|
|
|
|
|
|
|
|
|
|
// Check filename
|
|
|
|
|
assert.equal(item.attachmentFilename, filename);
|
|
|
|
|
|
|
|
|
|
// Check full path
|
|
|
|
|
var file = Zotero.Attachments.getStorageDirectory(item);
|
|
|
|
|
file.append(filename);
|
2015-09-29 07:55:10 +00:00
|
|
|
|
assert.equal(item.getFilePath(), file.path);
|
2015-04-29 21:22:31 +00:00
|
|
|
|
});
|
2015-10-29 07:41:54 +00:00
|
|
|
|
|
|
|
|
|
it.skip("should get and set a filename for a base-dir-relative file", function* () {
|
|
|
|
|
|
|
|
|
|
})
|
2015-05-06 08:18:24 +00:00
|
|
|
|
})
|
|
|
|
|
|
2015-09-29 07:55:10 +00:00
|
|
|
|
describe("#attachmentPath", function () {
|
|
|
|
|
it("should return an absolute path for a linked attachment", function* () {
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append('test.png');
|
|
|
|
|
var item = yield Zotero.Attachments.linkFromFile({ file });
|
|
|
|
|
assert.equal(item.attachmentPath, file.path);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should return a prefixed path for an imported file", function* () {
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append('test.png');
|
|
|
|
|
var item = yield Zotero.Attachments.importFromFile({ file });
|
|
|
|
|
|
|
|
|
|
assert.equal(item.attachmentPath, "storage:test.png");
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should set a prefixed relative path for a path within the defined base directory", function* () {
|
|
|
|
|
var dir = getTestDataDirectory().path;
|
|
|
|
|
var dirname = OS.Path.basename(dir);
|
|
|
|
|
var baseDir = OS.Path.dirname(dir);
|
|
|
|
|
Zotero.Prefs.set('saveRelativeAttachmentPath', true)
|
|
|
|
|
Zotero.Prefs.set('baseAttachmentPath', baseDir)
|
|
|
|
|
|
|
|
|
|
var file = OS.Path.join(dir, 'test.png');
|
|
|
|
|
|
|
|
|
|
var item = new Zotero.Item('attachment');
|
|
|
|
|
item.attachmentLinkMode = 'linked_file';
|
|
|
|
|
item.attachmentPath = file;
|
|
|
|
|
|
|
|
|
|
assert.equal(item.attachmentPath, "attachments:data/test.png");
|
|
|
|
|
|
|
|
|
|
Zotero.Prefs.set('saveRelativeAttachmentPath', false)
|
|
|
|
|
Zotero.Prefs.clear('baseAttachmentPath')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should return a prefixed path for a linked attachment within the defined base directory", function* () {
|
|
|
|
|
var dir = getTestDataDirectory().path;
|
|
|
|
|
var dirname = OS.Path.basename(dir);
|
|
|
|
|
var baseDir = OS.Path.dirname(dir);
|
|
|
|
|
Zotero.Prefs.set('saveRelativeAttachmentPath', true)
|
|
|
|
|
Zotero.Prefs.set('baseAttachmentPath', baseDir)
|
|
|
|
|
|
|
|
|
|
var file = OS.Path.join(dir, 'test.png');
|
|
|
|
|
|
|
|
|
|
var item = yield Zotero.Attachments.linkFromFile({
|
|
|
|
|
file: Zotero.File.pathToFile(file)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assert.equal(item.attachmentPath, "attachments:data/test.png");
|
|
|
|
|
|
|
|
|
|
Zotero.Prefs.set('saveRelativeAttachmentPath', false)
|
|
|
|
|
Zotero.Prefs.clear('baseAttachmentPath')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2015-08-07 19:36:46 +00:00
|
|
|
|
describe("#renameAttachmentFile()", function () {
|
|
|
|
|
it("should rename an attached file", function* () {
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append('test.png');
|
|
|
|
|
var item = yield Zotero.Attachments.importFromFile({
|
|
|
|
|
file: file
|
|
|
|
|
});
|
|
|
|
|
var newName = 'test2.png';
|
|
|
|
|
yield item.renameAttachmentFile(newName);
|
|
|
|
|
assert.equal(item.attachmentFilename, newName);
|
|
|
|
|
var path = yield item.getFilePathAsync();
|
|
|
|
|
assert.equal(OS.Path.basename(path), newName)
|
|
|
|
|
yield OS.File.exists(path);
|
|
|
|
|
|
2015-10-29 07:41:54 +00:00
|
|
|
|
// File should be flagged for upload
|
|
|
|
|
// DEBUG: Is this necessary?
|
2016-03-07 22:13:30 +00:00
|
|
|
|
assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_UPLOAD);
|
|
|
|
|
assert.isNull(item.attachmentSyncedHash);
|
2015-08-07 19:36:46 +00:00
|
|
|
|
})
|
2016-12-08 08:57:49 +00:00
|
|
|
|
|
|
|
|
|
it("should rename a linked file", function* () {
|
|
|
|
|
var filename = 'test.png';
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append(filename);
|
|
|
|
|
var tmpDir = yield getTempDirectory();
|
|
|
|
|
var tmpFile = OS.Path.join(tmpDir, filename);
|
|
|
|
|
yield OS.File.copy(file.path, tmpFile);
|
|
|
|
|
|
|
|
|
|
var item = yield Zotero.Attachments.linkFromFile({
|
|
|
|
|
file: tmpFile
|
|
|
|
|
});
|
|
|
|
|
var newName = 'test2.png';
|
|
|
|
|
yield assert.eventually.isTrue(item.renameAttachmentFile(newName));
|
|
|
|
|
assert.equal(item.attachmentFilename, newName);
|
|
|
|
|
var path = yield item.getFilePathAsync();
|
|
|
|
|
assert.equal(OS.Path.basename(path), newName)
|
|
|
|
|
yield OS.File.exists(path);
|
|
|
|
|
})
|
2015-08-07 19:36:46 +00:00
|
|
|
|
})
|
|
|
|
|
|
2015-10-12 23:00:05 +00:00
|
|
|
|
|
|
|
|
|
describe("#getBestAttachmentState()", function () {
|
|
|
|
|
it("should cache state for an existing file", function* () {
|
|
|
|
|
var parentItem = yield createDataObject('item');
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append('test.png');
|
|
|
|
|
var childItem = yield Zotero.Attachments.importFromFile({
|
|
|
|
|
file,
|
|
|
|
|
parentItemID: parentItem.id
|
|
|
|
|
});
|
|
|
|
|
yield parentItem.getBestAttachmentState();
|
|
|
|
|
assert.equal(parentItem.getBestAttachmentStateCached(), 1);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should cache state for a missing file", function* () {
|
|
|
|
|
var parentItem = yield createDataObject('item');
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append('test.png');
|
|
|
|
|
var childItem = yield Zotero.Attachments.importFromFile({
|
|
|
|
|
file,
|
|
|
|
|
parentItemID: parentItem.id
|
|
|
|
|
});
|
|
|
|
|
let path = yield childItem.getFilePathAsync();
|
|
|
|
|
yield OS.File.remove(path);
|
|
|
|
|
yield parentItem.getBestAttachmentState();
|
|
|
|
|
assert.equal(parentItem.getBestAttachmentStateCached(), -1);
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
describe("#fileExists()", function () {
|
|
|
|
|
it("should cache state for an existing file", function* () {
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append('test.png');
|
|
|
|
|
var item = yield Zotero.Attachments.importFromFile({ file });
|
|
|
|
|
yield item.fileExists();
|
|
|
|
|
assert.equal(item.fileExistsCached(), true);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should cache state for a missing file", function* () {
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append('test.png');
|
|
|
|
|
var item = yield Zotero.Attachments.importFromFile({ file });
|
|
|
|
|
let path = yield item.getFilePathAsync();
|
|
|
|
|
yield OS.File.remove(path);
|
|
|
|
|
yield item.fileExists();
|
|
|
|
|
assert.equal(item.fileExistsCached(), false);
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
2016-10-22 19:02:15 +00:00
|
|
|
|
describe("#relinkAttachmentFile", function () {
|
|
|
|
|
it("should copy a file elsewhere into the storage directory", function* () {
|
|
|
|
|
var filename = 'test.png';
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append(filename);
|
|
|
|
|
var tmpDir = yield getTempDirectory();
|
|
|
|
|
var tmpFile = OS.Path.join(tmpDir, filename);
|
|
|
|
|
yield OS.File.copy(file.path, tmpFile);
|
|
|
|
|
file = OS.Path.join(tmpDir, filename);
|
|
|
|
|
|
|
|
|
|
var item = yield Zotero.Attachments.importFromFile({ file });
|
|
|
|
|
let path = yield item.getFilePathAsync();
|
|
|
|
|
yield OS.File.remove(path);
|
|
|
|
|
yield OS.File.removeEmptyDir(OS.Path.dirname(path));
|
|
|
|
|
|
|
|
|
|
assert.isFalse(yield item.fileExists());
|
|
|
|
|
yield item.relinkAttachmentFile(file);
|
|
|
|
|
assert.isTrue(yield item.fileExists());
|
|
|
|
|
|
|
|
|
|
assert.isTrue(yield OS.File.exists(tmpFile));
|
|
|
|
|
});
|
2017-08-23 09:00:46 +00:00
|
|
|
|
|
|
|
|
|
it("should handle normalized filenames", function* () {
|
|
|
|
|
var item = yield importFileAttachment('test.png');
|
|
|
|
|
var path = yield item.getFilePathAsync();
|
|
|
|
|
var dir = OS.Path.dirname(path);
|
|
|
|
|
var filename = 'tést.pdf'.normalize('NFKD');
|
|
|
|
|
|
|
|
|
|
// Make sure we're actually testing something -- the test string should be differently
|
|
|
|
|
// normalized from what's done in getValidFileName
|
|
|
|
|
assert.notEqual(filename, Zotero.File.getValidFileName(filename));
|
|
|
|
|
|
|
|
|
|
var newPath = OS.Path.join(dir, filename);
|
|
|
|
|
yield OS.File.move(path, newPath);
|
|
|
|
|
|
|
|
|
|
assert.isFalse(yield item.fileExists());
|
|
|
|
|
yield item.relinkAttachmentFile(newPath);
|
|
|
|
|
assert.isTrue(yield item.fileExists());
|
|
|
|
|
});
|
2016-10-22 19:02:15 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2015-05-06 08:18:24 +00:00
|
|
|
|
describe("#setTags", function () {
|
|
|
|
|
it("should save an array of tags in API JSON format", function* () {
|
|
|
|
|
var tags = [
|
|
|
|
|
{
|
|
|
|
|
tag: "A"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
tag: "B"
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
var item = new Zotero.Item('journalArticle');
|
|
|
|
|
item.setTags(tags);
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
item = Zotero.Items.get(id);
|
2015-05-06 08:18:24 +00:00
|
|
|
|
assert.sameDeepMembers(item.getTags(tags), tags);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("shouldn't mark item as changed if tags haven't changed", function* () {
|
|
|
|
|
var tags = [
|
|
|
|
|
{
|
|
|
|
|
tag: "A"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
tag: "B"
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
var item = new Zotero.Item('journalArticle');
|
|
|
|
|
item.setTags(tags);
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
item = Zotero.Items.get(id);
|
2015-05-06 08:18:24 +00:00
|
|
|
|
item.setTags(tags);
|
|
|
|
|
assert.isFalse(item.hasChanged());
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should remove an existing tag", function* () {
|
|
|
|
|
var tags = [
|
|
|
|
|
{
|
|
|
|
|
tag: "A"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
tag: "B"
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
var item = new Zotero.Item('journalArticle');
|
|
|
|
|
item.setTags(tags);
|
2015-05-10 08:20:47 +00:00
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
item = Zotero.Items.get(id);
|
2015-05-06 08:18:24 +00:00
|
|
|
|
item.setTags(tags.slice(0));
|
2015-05-10 08:20:47 +00:00
|
|
|
|
yield item.saveTx();
|
2015-05-06 08:18:24 +00:00
|
|
|
|
assert.sameDeepMembers(item.getTags(tags), tags.slice(0));
|
|
|
|
|
})
|
|
|
|
|
})
|
2015-05-13 00:02:45 +00:00
|
|
|
|
|
2015-06-01 23:58:54 +00:00
|
|
|
|
describe("#addTag", function () {
|
|
|
|
|
it("should add a tag", function* () {
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
item.addTag('a');
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
var tags = item.getTags();
|
|
|
|
|
assert.deepEqual(tags, [{ tag: 'a' }]);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should add two tags", function* () {
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
item.addTag('a');
|
|
|
|
|
item.addTag('b');
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
var tags = item.getTags();
|
|
|
|
|
assert.sameDeepMembers(tags, [{ tag: 'a' }, { tag: 'b' }]);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should add two tags of different types", function* () {
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
item.addTag('a');
|
|
|
|
|
item.addTag('b', 1);
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
var tags = item.getTags();
|
|
|
|
|
assert.sameDeepMembers(tags, [{ tag: 'a' }, { tag: 'b', type: 1 }]);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should add a tag to an existing item", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
item.addTag('a');
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
var tags = item.getTags();
|
|
|
|
|
assert.deepEqual(tags, [{ tag: 'a' }]);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should add two tags to an existing item", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
item.addTag('a');
|
|
|
|
|
item.addTag('b');
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
var tags = item.getTags();
|
|
|
|
|
assert.sameDeepMembers(tags, [{ tag: 'a' }, { tag: 'b' }]);
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
Relations overhaul (requires new DB upgrade from 4.0)
Relations are now properties of collections and items rather than
first-class objects, stored in separate collectionRelations and
itemRelations tables with ids for subjects, with foreign keys to the
associated data objects.
Related items now use dc:relation relations rather than a separate table
(among other reasons, because API syncing won't necessarily sync both
items at the same time, so they can't be stored by id).
The UI assigns related-item relations bidirectionally, and checks for
related-item and linked-object relations are done unidirectionally by
default.
dc:isReplacedBy is now dc:replaces, so that the subject is an existing
object, and the predicate is now named
Zotero.Attachments.replacedItemPredicate.
Some additional work is still needed, notably around following
replaced-item relations, and migration needs to be tested more fully,
but this seems to mostly work.
2015-06-02 00:09:39 +00:00
|
|
|
|
//
|
|
|
|
|
// Relations and related items
|
|
|
|
|
//
|
|
|
|
|
describe("#addRelatedItem", function () {
|
2015-06-04 23:01:18 +00:00
|
|
|
|
it("should add a dc:relation relation to an item", function* () {
|
Relations overhaul (requires new DB upgrade from 4.0)
Relations are now properties of collections and items rather than
first-class objects, stored in separate collectionRelations and
itemRelations tables with ids for subjects, with foreign keys to the
associated data objects.
Related items now use dc:relation relations rather than a separate table
(among other reasons, because API syncing won't necessarily sync both
items at the same time, so they can't be stored by id).
The UI assigns related-item relations bidirectionally, and checks for
related-item and linked-object relations are done unidirectionally by
default.
dc:isReplacedBy is now dc:replaces, so that the subject is an existing
object, and the predicate is now named
Zotero.Attachments.replacedItemPredicate.
Some additional work is still needed, notably around following
replaced-item relations, and migration needs to be tested more fully,
but this seems to mostly work.
2015-06-02 00:09:39 +00:00
|
|
|
|
var item1 = yield createDataObject('item');
|
|
|
|
|
var item2 = yield createDataObject('item');
|
|
|
|
|
item1.addRelatedItem(item2);
|
2015-06-04 03:45:12 +00:00
|
|
|
|
yield item1.saveTx();
|
Relations overhaul (requires new DB upgrade from 4.0)
Relations are now properties of collections and items rather than
first-class objects, stored in separate collectionRelations and
itemRelations tables with ids for subjects, with foreign keys to the
associated data objects.
Related items now use dc:relation relations rather than a separate table
(among other reasons, because API syncing won't necessarily sync both
items at the same time, so they can't be stored by id).
The UI assigns related-item relations bidirectionally, and checks for
related-item and linked-object relations are done unidirectionally by
default.
dc:isReplacedBy is now dc:replaces, so that the subject is an existing
object, and the predicate is now named
Zotero.Attachments.replacedItemPredicate.
Some additional work is still needed, notably around following
replaced-item relations, and migration needs to be tested more fully,
but this seems to mostly work.
2015-06-02 00:09:39 +00:00
|
|
|
|
|
|
|
|
|
var rels = item1.getRelationsByPredicate(Zotero.Relations.relatedItemPredicate);
|
|
|
|
|
assert.lengthOf(rels, 1);
|
|
|
|
|
assert.equal(rels[0], Zotero.URI.getItemURI(item2));
|
|
|
|
|
})
|
|
|
|
|
|
2015-06-04 23:01:18 +00:00
|
|
|
|
it("should allow an unsaved item to be related to an item in the user library", function* () {
|
|
|
|
|
var item1 = yield createDataObject('item');
|
|
|
|
|
var item2 = createUnsavedDataObject('item');
|
|
|
|
|
item2.addRelatedItem(item1);
|
|
|
|
|
yield item2.saveTx();
|
|
|
|
|
|
|
|
|
|
var rels = item2.getRelationsByPredicate(Zotero.Relations.relatedItemPredicate);
|
|
|
|
|
assert.lengthOf(rels, 1);
|
|
|
|
|
assert.equal(rels[0], Zotero.URI.getItemURI(item1));
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should throw an error for a relation in a different library", function* () {
|
Relations overhaul (requires new DB upgrade from 4.0)
Relations are now properties of collections and items rather than
first-class objects, stored in separate collectionRelations and
itemRelations tables with ids for subjects, with foreign keys to the
associated data objects.
Related items now use dc:relation relations rather than a separate table
(among other reasons, because API syncing won't necessarily sync both
items at the same time, so they can't be stored by id).
The UI assigns related-item relations bidirectionally, and checks for
related-item and linked-object relations are done unidirectionally by
default.
dc:isReplacedBy is now dc:replaces, so that the subject is an existing
object, and the predicate is now named
Zotero.Attachments.replacedItemPredicate.
Some additional work is still needed, notably around following
replaced-item relations, and migration needs to be tested more fully,
but this seems to mostly work.
2015-06-02 00:09:39 +00:00
|
|
|
|
var group = yield getGroup();
|
|
|
|
|
var item1 = yield createDataObject('item');
|
|
|
|
|
var item2 = yield createDataObject('item', { libraryID: group.libraryID });
|
|
|
|
|
try {
|
|
|
|
|
item1.addRelatedItem(item2)
|
|
|
|
|
}
|
|
|
|
|
catch (e) {
|
|
|
|
|
assert.ok(e);
|
|
|
|
|
assert.equal(e.message, "Cannot relate item to an item in a different library");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
assert.fail("addRelatedItem() allowed for an item in a different library");
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2015-07-20 08:14:51 +00:00
|
|
|
|
describe("#save()", function () {
|
|
|
|
|
it("should throw an error for an empty item without an item type", function* () {
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
var e = yield getPromiseError(item.saveTx());
|
|
|
|
|
assert.ok(e);
|
|
|
|
|
assert.equal(e.message, "Item type must be set before saving");
|
|
|
|
|
})
|
2017-05-23 06:10:26 +00:00
|
|
|
|
|
|
|
|
|
it("should reload child items for parent items", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
var attachment = yield importFileAttachment('test.png', { parentItemID: item.id });
|
|
|
|
|
var note1 = new Zotero.Item('note');
|
|
|
|
|
note1.parentItemID = item.id;
|
|
|
|
|
yield note1.saveTx();
|
|
|
|
|
var note2 = new Zotero.Item('note');
|
|
|
|
|
note2.parentItemID = item.id;
|
|
|
|
|
yield note2.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.lengthOf(item.getAttachments(), 1);
|
|
|
|
|
assert.lengthOf(item.getNotes(), 2);
|
|
|
|
|
|
|
|
|
|
note2.parentItemID = null;
|
|
|
|
|
yield note2.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.lengthOf(item.getAttachments(), 1);
|
|
|
|
|
assert.lengthOf(item.getNotes(), 1);
|
|
|
|
|
});
|
2015-07-20 08:14:51 +00:00
|
|
|
|
})
|
|
|
|
|
|
2016-03-11 08:30:28 +00:00
|
|
|
|
|
2017-05-19 12:30:00 +00:00
|
|
|
|
describe("#_eraseData()", function () {
|
|
|
|
|
it("should remove relations pointing to this item", function* () {
|
|
|
|
|
var item1 = yield createDataObject('item');
|
|
|
|
|
var item2 = yield createDataObject('item');
|
|
|
|
|
item1.addRelatedItem(item2);
|
|
|
|
|
yield item1.saveTx();
|
|
|
|
|
item2.addRelatedItem(item1);
|
|
|
|
|
yield item2.saveTx();
|
|
|
|
|
|
|
|
|
|
yield item1.eraseTx();
|
|
|
|
|
|
|
|
|
|
assert.lengthOf(item2.relatedItems, 0);
|
|
|
|
|
yield assert.eventually.equal(
|
|
|
|
|
Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM itemRelations WHERE itemID=?", item2.id),
|
|
|
|
|
0
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2016-03-11 08:30:28 +00:00
|
|
|
|
describe("#multiDiff", function () {
|
|
|
|
|
it("should return set of alternatives for differing fields in other items", function* () {
|
|
|
|
|
var type = 'item';
|
|
|
|
|
|
|
|
|
|
var dates = ['2016-03-08 17:44:45'];
|
|
|
|
|
var accessDates = ['2016-03-08T18:44:45Z'];
|
|
|
|
|
var urls = ['http://www.example.com', 'http://example.net'];
|
|
|
|
|
|
|
|
|
|
var obj1 = createUnsavedDataObject(type);
|
|
|
|
|
obj1.setField('date', '2016-03-07 12:34:56'); // different in 1 and 3, not in 2
|
|
|
|
|
obj1.setField('url', 'http://example.com'); // different in all three
|
|
|
|
|
obj1.setField('title', 'Test'); // only in 1
|
|
|
|
|
|
|
|
|
|
var obj2 = createUnsavedDataObject(type);
|
|
|
|
|
obj2.setField('url', urls[0]);
|
|
|
|
|
obj2.setField('accessDate', accessDates[0]); // only in 2
|
|
|
|
|
|
|
|
|
|
var obj3 = createUnsavedDataObject(type);
|
|
|
|
|
obj3.setField('date', dates[0]);
|
|
|
|
|
obj3.setField('url', urls[1]);
|
|
|
|
|
|
|
|
|
|
var alternatives = obj1.multiDiff([obj2, obj3]);
|
|
|
|
|
|
|
|
|
|
assert.sameMembers(Object.keys(alternatives), ['url', 'date', 'accessDate']);
|
|
|
|
|
assert.sameMembers(alternatives.url, urls);
|
|
|
|
|
assert.sameMembers(alternatives.date, dates);
|
|
|
|
|
assert.sameMembers(alternatives.accessDate, accessDates);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2015-05-24 08:55:54 +00:00
|
|
|
|
describe("#clone()", function () {
|
|
|
|
|
// TODO: Expand to other data
|
|
|
|
|
it("should copy creators", function* () {
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.setCreators([
|
|
|
|
|
{
|
|
|
|
|
firstName: "A",
|
|
|
|
|
lastName: "Test",
|
|
|
|
|
creatorType: 'author'
|
|
|
|
|
}
|
|
|
|
|
]);
|
2015-06-04 03:45:12 +00:00
|
|
|
|
yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
var newItem = item.clone();
|
2015-05-24 08:55:54 +00:00
|
|
|
|
assert.sameDeepMembers(item.getCreators(), newItem.getCreators());
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2018-03-31 12:03:28 +00:00
|
|
|
|
describe("#moveToLibrary()", function () {
|
|
|
|
|
it("should move items from My Library to a filesEditable group", async function () {
|
|
|
|
|
var group = await createGroup();
|
|
|
|
|
|
|
|
|
|
var item = await createDataObject('item');
|
|
|
|
|
var attachment1 = await importFileAttachment('test.png', { parentID: item.id });
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append('test.png');
|
|
|
|
|
var attachment2 = await Zotero.Attachments.linkFromFile({
|
|
|
|
|
file,
|
|
|
|
|
parentItemID: item.id
|
|
|
|
|
});
|
|
|
|
|
var note = await createDataObject('item', { itemType: 'note', parentID: item.id });
|
|
|
|
|
|
|
|
|
|
var originalIDs = [item.id, attachment1.id, attachment2.id, note.id];
|
|
|
|
|
var originalAttachmentFile = attachment1.getFilePath();
|
|
|
|
|
var originalAttachmentHash = await attachment1.attachmentHash
|
|
|
|
|
|
|
|
|
|
assert.isTrue(await OS.File.exists(originalAttachmentFile));
|
|
|
|
|
|
|
|
|
|
var newItem = await item.moveToLibrary(group.libraryID);
|
|
|
|
|
|
|
|
|
|
// Old items and file should be gone
|
|
|
|
|
assert.isTrue(originalIDs.every(id => !Zotero.Items.get(id)));
|
|
|
|
|
assert.isFalse(await OS.File.exists(originalAttachmentFile));
|
|
|
|
|
|
|
|
|
|
// New items and stored file should exist; linked file should be gone
|
|
|
|
|
assert.equal(newItem.libraryID, group.libraryID);
|
|
|
|
|
assert.lengthOf(newItem.getAttachments(), 1);
|
|
|
|
|
var newAttachment = Zotero.Items.get(newItem.getAttachments()[0]);
|
|
|
|
|
assert.equal(await newAttachment.attachmentHash, originalAttachmentHash);
|
|
|
|
|
assert.lengthOf(newItem.getNotes(), 1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should move items from My Library to a non-filesEditable group", async function () {
|
|
|
|
|
var group = await createGroup({
|
|
|
|
|
filesEditable: false
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var item = await createDataObject('item');
|
|
|
|
|
var attachment = await importFileAttachment('test.png', { parentID: item.id });
|
|
|
|
|
|
|
|
|
|
var originalIDs = [item.id, attachment.id];
|
|
|
|
|
var originalAttachmentFile = attachment.getFilePath();
|
|
|
|
|
var originalAttachmentHash = await attachment.attachmentHash
|
|
|
|
|
|
|
|
|
|
assert.isTrue(await OS.File.exists(originalAttachmentFile));
|
|
|
|
|
|
|
|
|
|
var newItem = await item.moveToLibrary(group.libraryID);
|
|
|
|
|
|
|
|
|
|
// Old items and file should be gone
|
|
|
|
|
assert.isTrue(originalIDs.every(id => !Zotero.Items.get(id)));
|
|
|
|
|
assert.isFalse(await OS.File.exists(originalAttachmentFile));
|
|
|
|
|
|
|
|
|
|
// Parent should exist, but attachment should not
|
|
|
|
|
assert.equal(newItem.libraryID, group.libraryID);
|
|
|
|
|
assert.lengthOf(newItem.getAttachments(), 0);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2015-05-13 00:02:45 +00:00
|
|
|
|
describe("#toJSON()", function () {
|
2015-07-22 09:21:32 +00:00
|
|
|
|
describe("default mode", function () {
|
|
|
|
|
it("should output only fields with values", function* () {
|
|
|
|
|
var itemType = "book";
|
|
|
|
|
var title = "Test";
|
|
|
|
|
|
|
|
|
|
var item = new Zotero.Item(itemType);
|
|
|
|
|
item.setField("title", title);
|
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
item = Zotero.Items.get(id);
|
|
|
|
|
var json = item.toJSON();
|
2015-07-22 09:21:32 +00:00
|
|
|
|
|
|
|
|
|
assert.equal(json.itemType, itemType);
|
|
|
|
|
assert.equal(json.title, title);
|
|
|
|
|
assert.isUndefined(json.date);
|
|
|
|
|
assert.isUndefined(json.numPages);
|
|
|
|
|
})
|
2015-10-30 23:06:29 +00:00
|
|
|
|
|
|
|
|
|
it("should output 'deleted' as 1", function* () {
|
|
|
|
|
var itemType = "book";
|
|
|
|
|
var title = "Test";
|
|
|
|
|
|
|
|
|
|
var item = new Zotero.Item(itemType);
|
|
|
|
|
item.setField("title", title);
|
|
|
|
|
item.deleted = true;
|
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
item = Zotero.Items.get(id);
|
|
|
|
|
var json = item.toJSON();
|
2015-10-30 23:06:29 +00:00
|
|
|
|
|
|
|
|
|
assert.strictEqual(json.deleted, 1);
|
|
|
|
|
})
|
2015-12-22 06:49:45 +00:00
|
|
|
|
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
it.skip("should output attachment fields from file", function* () {
|
2015-12-23 09:52:09 +00:00
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append('test.png');
|
|
|
|
|
var item = yield Zotero.Attachments.importFromFile({ file });
|
|
|
|
|
|
|
|
|
|
yield Zotero.DB.executeTransaction(function* () {
|
|
|
|
|
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(
|
|
|
|
|
item.id, new Date().getTime()
|
|
|
|
|
);
|
|
|
|
|
yield Zotero.Sync.Storage.Local.setSyncedHash(
|
|
|
|
|
item.id, 'b32e33f529942d73bea4ed112310f804'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
var json = item.toJSON();
|
2015-12-23 09:52:09 +00:00
|
|
|
|
assert.equal(json.linkMode, 'imported_file');
|
|
|
|
|
assert.equal(json.filename, 'test.png');
|
|
|
|
|
assert.isUndefined(json.path);
|
|
|
|
|
assert.equal(json.mtime, (yield item.attachmentModificationTime));
|
|
|
|
|
assert.equal(json.md5, (yield item.attachmentHash));
|
|
|
|
|
})
|
|
|
|
|
|
2016-05-21 03:32:45 +00:00
|
|
|
|
it("should omit storage values with .skipStorageProperties", function* () {
|
|
|
|
|
var file = getTestDataDirectory();
|
|
|
|
|
file.append('test.png');
|
|
|
|
|
var item = yield Zotero.Attachments.importFromFile({ file });
|
|
|
|
|
|
|
|
|
|
item.attachmentSyncedModificationTime = new Date().getTime();
|
|
|
|
|
item.attachmentSyncedHash = 'b32e33f529942d73bea4ed112310f804';
|
|
|
|
|
yield item.saveTx({ skipAll: true });
|
|
|
|
|
|
|
|
|
|
var json = item.toJSON({
|
|
|
|
|
skipStorageProperties: true
|
|
|
|
|
});
|
|
|
|
|
assert.isUndefined(json.mtime);
|
|
|
|
|
assert.isUndefined(json.md5);
|
|
|
|
|
});
|
|
|
|
|
|
2015-12-23 09:52:09 +00:00
|
|
|
|
it("should output synced storage values with .syncedStorageProperties", function* () {
|
|
|
|
|
var item = new Zotero.Item('attachment');
|
|
|
|
|
item.attachmentLinkMode = 'imported_file';
|
|
|
|
|
item.fileName = 'test.txt';
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
|
|
|
|
|
var mtime = new Date().getTime();
|
|
|
|
|
var md5 = 'b32e33f529942d73bea4ed112310f804';
|
|
|
|
|
|
2016-03-07 22:13:30 +00:00
|
|
|
|
item.attachmentSyncedModificationTime = mtime;
|
|
|
|
|
item.attachmentSyncedHash = md5;
|
|
|
|
|
yield item.saveTx({ skipAll: true });
|
2015-12-23 09:52:09 +00:00
|
|
|
|
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
var json = item.toJSON({
|
2015-12-23 09:52:09 +00:00
|
|
|
|
syncedStorageProperties: true
|
|
|
|
|
});
|
|
|
|
|
assert.equal(json.mtime, mtime);
|
|
|
|
|
assert.equal(json.md5, md5);
|
|
|
|
|
})
|
|
|
|
|
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
it.skip("should output unset storage properties as null", function* () {
|
2015-12-22 06:49:45 +00:00
|
|
|
|
var item = new Zotero.Item('attachment');
|
|
|
|
|
item.attachmentLinkMode = 'imported_file';
|
|
|
|
|
item.fileName = 'test.txt';
|
|
|
|
|
var id = yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
var json = item.toJSON();
|
2015-12-22 06:49:45 +00:00
|
|
|
|
|
|
|
|
|
assert.isNull(json.mtime);
|
|
|
|
|
assert.isNull(json.md5);
|
|
|
|
|
})
|
2016-03-25 02:26:50 +00:00
|
|
|
|
|
|
|
|
|
it("shouldn't include filename or path for linked_url attachments", function* () {
|
|
|
|
|
var item = new Zotero.Item('attachment');
|
|
|
|
|
item.attachmentLinkMode = 'linked_url';
|
|
|
|
|
item.url = "https://www.zotero.org/";
|
|
|
|
|
var json = item.toJSON();
|
|
|
|
|
assert.notProperty(json, "filename");
|
|
|
|
|
assert.notProperty(json, "path");
|
|
|
|
|
});
|
2017-04-12 04:56:37 +00:00
|
|
|
|
|
|
|
|
|
it("should include inPublications=true for items in My Publications", function* () {
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
item.inPublications = true;
|
|
|
|
|
var json = item.toJSON();
|
|
|
|
|
assert.propertyVal(json, "inPublications", true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("shouldn't include inPublications for items not in My Publications in patch mode", function* () {
|
|
|
|
|
var item = createUnsavedDataObject('item');
|
|
|
|
|
var json = item.toJSON();
|
|
|
|
|
assert.notProperty(json, "inPublications");
|
|
|
|
|
});
|
|
|
|
|
|
2017-11-20 21:27:45 +00:00
|
|
|
|
it("should include inPublications=false for personal-library items not in My Publications in full mode", async function () {
|
|
|
|
|
var item = createUnsavedDataObject('item', { libraryID: Zotero.Libraries.userLibraryID });
|
2017-04-12 04:56:37 +00:00
|
|
|
|
var json = item.toJSON({ mode: 'full' });
|
|
|
|
|
assert.property(json, "inPublications", false);
|
|
|
|
|
});
|
2017-11-20 21:27:45 +00:00
|
|
|
|
|
|
|
|
|
it("shouldn't include inPublications=false for group items not in My Publications in full mode", function* () {
|
|
|
|
|
var group = yield getGroup();
|
|
|
|
|
var item = createUnsavedDataObject('item', { libraryID: group.libraryID });
|
|
|
|
|
var json = item.toJSON({ mode: 'full' });
|
|
|
|
|
assert.notProperty(json, "inPublications");
|
|
|
|
|
});
|
2015-05-13 00:02:45 +00:00
|
|
|
|
})
|
|
|
|
|
|
2015-07-22 09:21:32 +00:00
|
|
|
|
describe("'full' mode", function () {
|
|
|
|
|
it("should output all fields", function* () {
|
|
|
|
|
var itemType = "book";
|
|
|
|
|
var title = "Test";
|
|
|
|
|
|
|
|
|
|
var item = new Zotero.Item(itemType);
|
|
|
|
|
item.setField("title", title);
|
|
|
|
|
var id = yield item.saveTx();
|
|
|
|
|
item = yield Zotero.Items.getAsync(id);
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
var json = item.toJSON({ mode: 'full' });
|
2015-07-22 09:21:32 +00:00
|
|
|
|
assert.equal(json.title, title);
|
|
|
|
|
assert.equal(json.date, "");
|
|
|
|
|
assert.equal(json.numPages, "");
|
|
|
|
|
})
|
2015-05-13 00:02:45 +00:00
|
|
|
|
})
|
|
|
|
|
|
2015-07-22 09:21:32 +00:00
|
|
|
|
describe("'patch' mode", function () {
|
|
|
|
|
it("should output only fields that differ", function* () {
|
|
|
|
|
var itemType = "book";
|
|
|
|
|
var title = "Test";
|
|
|
|
|
var date = "2015-05-12";
|
|
|
|
|
|
|
|
|
|
var item = new Zotero.Item(itemType);
|
|
|
|
|
item.setField("title", title);
|
|
|
|
|
var id = yield item.saveTx();
|
|
|
|
|
item = yield Zotero.Items.getAsync(id);
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
var patchBase = item.toJSON();
|
2015-07-22 09:21:32 +00:00
|
|
|
|
|
|
|
|
|
item.setField("date", date);
|
|
|
|
|
yield item.saveTx();
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
var json = item.toJSON({
|
2015-07-22 09:21:32 +00:00
|
|
|
|
patchBase: patchBase
|
|
|
|
|
})
|
|
|
|
|
assert.isUndefined(json.itemType);
|
|
|
|
|
assert.isUndefined(json.title);
|
|
|
|
|
assert.equal(json.date, date);
|
|
|
|
|
assert.isUndefined(json.numPages);
|
|
|
|
|
assert.isUndefined(json.deleted);
|
|
|
|
|
assert.isUndefined(json.creators);
|
|
|
|
|
assert.isUndefined(json.relations);
|
|
|
|
|
assert.isUndefined(json.tags);
|
|
|
|
|
})
|
2015-05-13 00:02:45 +00:00
|
|
|
|
|
2015-07-22 09:21:32 +00:00
|
|
|
|
it("should include changed 'deleted' field", function* () {
|
|
|
|
|
// True to false
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.deleted = true;
|
|
|
|
|
var id = yield item.saveTx();
|
|
|
|
|
item = yield Zotero.Items.getAsync(id);
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
var patchBase = item.toJSON();
|
2015-07-22 09:21:32 +00:00
|
|
|
|
|
|
|
|
|
item.deleted = false;
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
var json = item.toJSON({
|
2015-07-22 09:21:32 +00:00
|
|
|
|
patchBase: patchBase
|
|
|
|
|
})
|
|
|
|
|
assert.isUndefined(json.title);
|
|
|
|
|
assert.isFalse(json.deleted);
|
|
|
|
|
|
|
|
|
|
// False to true
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.deleted = false;
|
|
|
|
|
var id = yield item.saveTx();
|
|
|
|
|
item = yield Zotero.Items.getAsync(id);
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
var patchBase = item.toJSON();
|
2015-07-22 09:21:32 +00:00
|
|
|
|
|
|
|
|
|
item.deleted = true;
|
Deasyncification :back: :cry:
While trying to get translation and citing working with asynchronously
generated data, we realized that drag-and-drop support was going to
be...problematic. Firefox only supports synchronous methods for
providing drag data (unlike, it seems, the DataTransferItem interface
supported by Chrome), which means that we'd need to preload all relevant
data on item selection (bounded by export.quickCopy.dragLimit) and keep
the translate/cite methods synchronous (or maintain two separate
versions).
What we're trying instead is doing what I said in #518 we weren't going
to do: loading most object data on startup and leaving many more
functions synchronous. Essentially, this takes the various load*()
methods described in #518, moves them to startup, and makes them operate
on entire libraries rather than individual objects.
The obvious downside here (other than undoing much of the work of the
last many months) is that it increases startup time, potentially quite a
lot for larger libraries. On my laptop, with a 3,000-item library, this
adds about 3 seconds to startup time. I haven't yet tested with larger
libraries. But I'm hoping that we can optimize this further to reduce
that delay. Among other things, this is loading data for all libraries,
when it should be able to load data only for the library being viewed.
But this is also fundamentally just doing some SELECT queries and
storing the results, so it really shouldn't need to be that slow (though
performance may be bounded a bit here by XPCOM overhead).
If we can make this fast enough, it means that third-party plugins
should be able to remain much closer to their current designs. (Some
things, including saving, will still need to be made asynchronous.)
2016-03-07 21:05:51 +00:00
|
|
|
|
var json = item.toJSON({
|
2015-07-22 09:21:32 +00:00
|
|
|
|
patchBase: patchBase
|
|
|
|
|
})
|
|
|
|
|
assert.isUndefined(json.title);
|
2015-10-30 23:06:29 +00:00
|
|
|
|
assert.strictEqual(json.deleted, 1);
|
2015-05-13 00:02:45 +00:00
|
|
|
|
})
|
2016-05-15 07:34:06 +00:00
|
|
|
|
|
|
|
|
|
it("should set 'parentItem' to false when cleared", function* () {
|
|
|
|
|
var item = yield createDataObject('item');
|
|
|
|
|
var note = new Zotero.Item('note');
|
|
|
|
|
note.parentID = item.id;
|
|
|
|
|
// Create initial JSON with parentItem
|
|
|
|
|
var patchBase = note.toJSON();
|
|
|
|
|
// Clear parent item and regenerate JSON
|
|
|
|
|
note.parentID = false;
|
|
|
|
|
var json = note.toJSON({ patchBase });
|
|
|
|
|
assert.isFalse(json.parentItem);
|
|
|
|
|
});
|
2016-08-15 08:38:04 +00:00
|
|
|
|
|
2017-05-03 00:15:54 +00:00
|
|
|
|
it("should include relations if related item was removed", function* () {
|
|
|
|
|
var item1 = yield createDataObject('item');
|
|
|
|
|
var item2 = yield createDataObject('item');
|
|
|
|
|
var item3 = yield createDataObject('item');
|
|
|
|
|
var item4 = yield createDataObject('item');
|
|
|
|
|
|
|
|
|
|
var relateItems = Zotero.Promise.coroutine(function* (i1, i2) {
|
|
|
|
|
yield Zotero.DB.executeTransaction(function* () {
|
|
|
|
|
i1.addRelatedItem(i2);
|
|
|
|
|
yield i1.save({
|
|
|
|
|
skipDateModifiedUpdate: true
|
|
|
|
|
});
|
|
|
|
|
i2.addRelatedItem(i1);
|
|
|
|
|
yield i2.save({
|
|
|
|
|
skipDateModifiedUpdate: true
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
yield relateItems(item1, item2);
|
|
|
|
|
yield relateItems(item1, item3);
|
|
|
|
|
yield relateItems(item1, item4);
|
|
|
|
|
|
|
|
|
|
var patchBase = item1.toJSON();
|
|
|
|
|
|
|
|
|
|
item1.removeRelatedItem(item2);
|
|
|
|
|
yield item1.saveTx();
|
|
|
|
|
item2.removeRelatedItem(item1);
|
|
|
|
|
yield item2.saveTx();
|
|
|
|
|
|
|
|
|
|
var json = item1.toJSON({ patchBase });
|
|
|
|
|
assert.sameMembers(json.relations['dc:relation'], item1.getRelations()['dc:relation']);
|
|
|
|
|
});
|
|
|
|
|
|
2016-08-15 08:38:04 +00:00
|
|
|
|
it("shouldn't clear storage properties from original in .skipStorageProperties mode", function* () {
|
|
|
|
|
var item = new Zotero.Item('attachment');
|
|
|
|
|
item.attachmentLinkMode = 'imported_file';
|
|
|
|
|
item.attachmentFilename = 'test.txt';
|
|
|
|
|
item.attachmentContentType = 'text/plain';
|
|
|
|
|
item.attachmentCharset = 'utf-8';
|
|
|
|
|
item.attachmentSyncedModificationTime = 1234567890000;
|
|
|
|
|
item.attachmentSyncedHash = '18d21750c8abd5e3afa8ea89e3dfa570';
|
|
|
|
|
var patchBase = item.toJSON({
|
|
|
|
|
syncedStorageProperties: true
|
|
|
|
|
});
|
|
|
|
|
item.setNote("Test");
|
|
|
|
|
var json = item.toJSON({
|
|
|
|
|
patchBase,
|
|
|
|
|
skipStorageProperties: true
|
|
|
|
|
});
|
|
|
|
|
Zotero.debug(json);
|
|
|
|
|
assert.equal(json.note, "Test");
|
|
|
|
|
assert.notProperty(json, "md5");
|
|
|
|
|
assert.notProperty(json, "mtime");
|
|
|
|
|
});
|
2015-05-13 00:02:45 +00:00
|
|
|
|
})
|
|
|
|
|
})
|
2016-07-18 23:56:50 +00:00
|
|
|
|
|
2015-06-04 03:42:08 +00:00
|
|
|
|
describe("#fromJSON()", function () {
|
2016-07-18 23:56:50 +00:00
|
|
|
|
it("should clear missing fields", function* () {
|
|
|
|
|
var item = new Zotero.Item('book');
|
|
|
|
|
item.setField('title', 'Test');
|
|
|
|
|
item.setField('date', '2016');
|
|
|
|
|
item.setField('accessDate', '2015-06-07T20:56:00Z');
|
|
|
|
|
yield item.saveTx();
|
|
|
|
|
var json = item.toJSON();
|
|
|
|
|
// Remove fields, which should cause them to be cleared in fromJSON()
|
|
|
|
|
delete json.date;
|
|
|
|
|
delete json.accessDate;
|
|
|
|
|
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.strictEqual(item.getField('title'), 'Test');
|
|
|
|
|
assert.strictEqual(item.getField('date'), '');
|
|
|
|
|
assert.strictEqual(item.getField('accessDate'), '');
|
|
|
|
|
});
|
|
|
|
|
|
2018-04-14 03:36:09 +00:00
|
|
|
|
it("should remove item from collection if 'collections' property not provided", function* () {
|
2017-07-11 05:22:07 +00:00
|
|
|
|
var collection = yield createDataObject('collection');
|
|
|
|
|
// Create standalone attachment in collection
|
|
|
|
|
var attachment = yield importFileAttachment('test.png', { collections: [collection.id] });
|
|
|
|
|
var item = yield createDataObject('item', { collections: [collection.id] });
|
|
|
|
|
|
2018-04-14 03:36:09 +00:00
|
|
|
|
assert.isTrue(collection.hasItem(attachment.id));
|
2017-07-11 05:22:07 +00:00
|
|
|
|
var json = attachment.toJSON();
|
|
|
|
|
json.path = 'storage:test2.png';
|
|
|
|
|
// Add to parent, which implicitly removes from collection
|
|
|
|
|
json.parentItem = item.key;
|
|
|
|
|
delete json.collections;
|
|
|
|
|
attachment.fromJSON(json);
|
2018-04-14 03:36:09 +00:00
|
|
|
|
yield attachment.saveTx();
|
|
|
|
|
assert.isFalse(collection.hasItem(attachment.id));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should remove child item from parent if 'parentKey' property not provided", async function () {
|
|
|
|
|
var item = await createDataObject('item');
|
|
|
|
|
var note = await createDataObject('item', { itemType: 'note', parentKey: [item.key] });
|
|
|
|
|
|
|
|
|
|
var json = note.toJSON();
|
|
|
|
|
delete json.parentItem;
|
|
|
|
|
|
|
|
|
|
note.fromJSON(json);
|
|
|
|
|
await note.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.lengthOf(item.getNotes(), 0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should remove item from trash if 'deleted' property not provided", async function () {
|
|
|
|
|
var item = await createDataObject('item', { deleted: true });
|
|
|
|
|
|
|
|
|
|
assert.isTrue(item.deleted);
|
|
|
|
|
|
|
|
|
|
var json = item.toJSON();
|
|
|
|
|
delete json.deleted;
|
|
|
|
|
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item.deleted);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should remove item from My Publications if 'inPublications' property not provided", async function () {
|
|
|
|
|
var item = await createDataObject('item', { inPublications: true });
|
|
|
|
|
|
|
|
|
|
assert.isTrue(item.inPublications);
|
|
|
|
|
|
|
|
|
|
var json = item.toJSON();
|
|
|
|
|
delete json.inPublications;
|
|
|
|
|
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
|
|
assert.isFalse(item.inPublications);
|
2017-07-11 05:22:07 +00:00
|
|
|
|
});
|
|
|
|
|
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
|
// Not currently following this behavior
|
|
|
|
|
/*it("should move valid field in Extra to field if not set", function () {
|
|
|
|
|
var doi = '10.1234/abcd';
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
title: "Test",
|
|
|
|
|
extra: `DOI: ${doi}`
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.equal(item.getField('DOI'), doi);
|
|
|
|
|
assert.equal(item.getField('extra'), '');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("shouldn't move valid field in Extra to field if also present in JSON", function () {
|
|
|
|
|
var doi1 = '10.1234/abcd';
|
|
|
|
|
var doi2 = '10.2345/bcde';
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
title: "Test",
|
|
|
|
|
DOI: doi1,
|
|
|
|
|
extra: `doi: ${doi2}`
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.equal(item.getField('DOI'), doi1);
|
|
|
|
|
assert.equal(item.getField('extra'), `doi: ${doi2}`);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("shouldn't move valid field in Extra to field if already set", function () {
|
|
|
|
|
var doi1 = '10.1234/abcd';
|
|
|
|
|
var doi2 = '10.2345/bcde';
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
title: "Test",
|
|
|
|
|
DOI: doi1,
|
|
|
|
|
extra: `doi: ${doi2}`
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item('journalArticle');
|
|
|
|
|
item.setField('DOI', doi1);
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.equal(item.getField('DOI'), doi1);
|
|
|
|
|
assert.equal(item.getField('extra'), `doi: ${doi2}`);
|
|
|
|
|
});*/
|
|
|
|
|
|
2020-01-28 03:00:04 +00:00
|
|
|
|
it("should ignore creator field in Extra", async function () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
extra: "Author: Name"
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item();
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.lengthOf(item.getCreatorsJSON(), 0);
|
|
|
|
|
assert.equal(item.getField('extra'), json.extra);
|
|
|
|
|
});
|
|
|
|
|
|
2020-02-09 17:44:40 +00:00
|
|
|
|
describe("not-strict mode", function () {
|
|
|
|
|
it("should handle Extra in non-strict mode", function () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
title: "Test",
|
|
|
|
|
extra: "Here's some extra text"
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item();
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.equal(item.getField('extra'), json.extra);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should store unknown fields in Extra", function () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
title: "Test",
|
|
|
|
|
fooBar: "123",
|
|
|
|
|
testField: "test value"
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.equal(item.getField('title'), 'Test');
|
|
|
|
|
assert.equal(item.getField('extra'), 'Foo Bar: 123\nTest Field: test value');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should replace unknown field in Extra", function () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
title: "Test",
|
|
|
|
|
foo: "BBB",
|
|
|
|
|
extra: "Foo: AAA\nBar: CCC"
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.equal(item.getField('title'), 'Test');
|
|
|
|
|
assert.equal(item.getField('extra'), 'Foo: BBB\nBar: CCC');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should store invalid-for-type field in Extra", function () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
title: "Test",
|
|
|
|
|
medium: "123"
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.equal(item.getField('title'), 'Test');
|
|
|
|
|
assert.equal(item.getField('extra'), 'Medium: 123');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should ignore invalid-for-type base-mapped field if valid-for-type base field is set in Extra", function () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "document",
|
|
|
|
|
publisher: "Foo", // Valid for 'document'
|
|
|
|
|
company: "Bar" // Not valid for 'document', but mapped to base field 'publisher'
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.equal(item.getField('publisher'), 'Foo');
|
|
|
|
|
assert.equal(item.getField('extra'), '');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("shouldn't include base field or invalid base-mapped field in Extra if valid base-mapped field is set", function () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "audioRecording",
|
|
|
|
|
publisher: "A", // Base field, which will be overwritten by the valid base-mapped field
|
|
|
|
|
label: "B", // Valid base-mapped field, which should be stored
|
|
|
|
|
company: "C", // Invalid base-mapped field, which should be ignored
|
|
|
|
|
foo: "D" // Invalid other field, which should be added to Extra
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.equal(item.getField('label'), 'B');
|
|
|
|
|
assert.equal(item.getField('extra'), 'Foo: D');
|
|
|
|
|
});
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
|
});
|
|
|
|
|
|
2020-02-09 17:44:40 +00:00
|
|
|
|
describe("strict mode", function () {
|
|
|
|
|
it("should throw on unknown field", function () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
title: "Test",
|
|
|
|
|
foo: "Bar"
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
var f = () => {
|
|
|
|
|
item.fromJSON(json, { strict: true });
|
|
|
|
|
};
|
|
|
|
|
assert.throws(f, /^Unknown field/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should throw on invalid field for a given item type", function () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
title: "Test",
|
|
|
|
|
numPages: "123"
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
var f = () => {
|
|
|
|
|
item.fromJSON(json, { strict: true });
|
|
|
|
|
};
|
|
|
|
|
assert.throws(f, /^Invalid field/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should throw on unknown creator type", function () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
title: "Test",
|
|
|
|
|
creators: [
|
|
|
|
|
{
|
|
|
|
|
firstName: "First",
|
|
|
|
|
lastName: "Last",
|
|
|
|
|
creatorType: "unknown"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
var f = () => {
|
|
|
|
|
item.fromJSON(json, { strict: true });
|
|
|
|
|
};
|
|
|
|
|
assert.throws(f, /^Unknown creator type/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should throw on invalid creator type for a given item type", function () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
title: "Test",
|
|
|
|
|
creators: [
|
|
|
|
|
{
|
|
|
|
|
firstName: "First",
|
|
|
|
|
lastName: "Last",
|
|
|
|
|
creatorType: "interviewee"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
var f = () => {
|
|
|
|
|
item.fromJSON(json, { strict: true });
|
|
|
|
|
};
|
|
|
|
|
assert.throws(f, /^Invalid creator type/);
|
|
|
|
|
});
|
Type/field handling overhaul
This changes the way item types, item fields, creator types, and CSL
mappings are defined and handled, in preparation for updated types and
fields.
Instead of being predefined in SQL files or code, type/field info is
read from a bundled JSON file shared with other parts of the Zotero
ecosystem [1], referred to as the "global schema". Updates to the
bundled schema file are automatically applied to the database at first
run, allowing changes to be made consistently across apps.
When syncing, invalid JSON properties are now rejected instead of being
ignored and processed later, which will allow for schema changes to be
made without causing problems in existing clients. We considered many
alternative approaches, but this approach is by far the simplest,
safest, and most transparent to the user.
For now, there are no actual changes to types and fields, since we'll
first need to do a sync cut-off for earlier versions that don't reject
invalid properties.
For third-party code, the main change is that type and field IDs should
no longer be hard-coded, since they may not be consistent in new
installs. For example, code should use `Zotero.ItemTypes.getID('note')`
instead of hard-coding `1`.
[1] https://github.com/zotero/zotero-schema
2019-05-16 08:56:46 +00:00
|
|
|
|
});
|
2015-11-12 22:24:03 +00:00
|
|
|
|
|
2015-06-07 21:39:40 +00:00
|
|
|
|
it("should accept ISO 8601 dates", function* () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
accessDate: "2015-06-07T20:56:00Z",
|
|
|
|
|
dateAdded: "2015-06-07T20:57:00Z",
|
|
|
|
|
dateModified: "2015-06-07T20:58:00Z",
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
2015-10-19 19:37:13 +00:00
|
|
|
|
item.fromJSON(json);
|
2015-06-07 21:39:40 +00:00
|
|
|
|
assert.equal(item.getField('accessDate'), '2015-06-07 20:56:00');
|
|
|
|
|
assert.equal(item.dateAdded, '2015-06-07 20:57:00');
|
|
|
|
|
assert.equal(item.dateModified, '2015-06-07 20:58:00');
|
|
|
|
|
})
|
|
|
|
|
|
2017-05-11 04:44:37 +00:00
|
|
|
|
it("should accept ISO 8601 access date without time", function* () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
accessDate: "2015-06-07",
|
|
|
|
|
dateAdded: "2015-06-07T20:57:00Z",
|
|
|
|
|
dateModified: "2015-06-07T20:58:00Z",
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
|
|
|
|
item.fromJSON(json);
|
|
|
|
|
assert.equal(item.getField('accessDate'), '2015-06-07');
|
|
|
|
|
assert.equal(item.dateAdded, '2015-06-07 20:57:00');
|
|
|
|
|
assert.equal(item.dateModified, '2015-06-07 20:58:00');
|
|
|
|
|
})
|
|
|
|
|
|
2015-06-07 21:39:40 +00:00
|
|
|
|
it("should ignore non–ISO 8601 dates", function* () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
accessDate: "2015-06-07 20:56:00",
|
|
|
|
|
dateAdded: "2015-06-07 20:57:00",
|
|
|
|
|
dateModified: "2015-06-07 20:58:00",
|
|
|
|
|
};
|
|
|
|
|
var item = new Zotero.Item;
|
2015-10-19 19:37:13 +00:00
|
|
|
|
item.fromJSON(json);
|
2015-06-07 21:39:40 +00:00
|
|
|
|
assert.strictEqual(item.getField('accessDate'), '');
|
|
|
|
|
// DEBUG: Should these be null, or empty string like other fields from getField()?
|
|
|
|
|
assert.isNull(item.dateAdded);
|
|
|
|
|
assert.isNull(item.dateModified);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it("should set creators", function* () {
|
|
|
|
|
var json = {
|
|
|
|
|
itemType: "journalArticle",
|
|
|
|
|
creators: [
|
|
|
|
|
{
|
|
|
|
|
firstName: "First",
|
|
|
|
|
lastName: "Last",
|
|
|
|
|
creatorType: "author"
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Test Name",
|
|
|
|
|
creatorType: "editor"
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var item = new Zotero.Item;
|
2015-10-19 19:37:13 +00:00
|
|
|
|
item.fromJSON(json);
|
2015-06-07 21:39:40 +00:00
|
|
|
|
var id = yield item.saveTx();
|
|
|
|
|
assert.sameDeepMembers(item.getCreatorsJSON(), json.creators);
|
|
|
|
|
})
|
|
|
|
|
|
2015-06-04 03:42:08 +00:00
|
|
|
|
it("should map a base field to an item-specific field", function* () {
|
|
|
|
|
var item = new Zotero.Item("bookSection");
|
2015-10-19 19:37:13 +00:00
|
|
|
|
item.fromJSON({
|
2015-06-04 03:42:08 +00:00
|
|
|
|
"itemType":"bookSection",
|
|
|
|
|
"publicationTitle":"Publication Title"
|
|
|
|
|
});
|
|
|
|
|
assert.equal(item.getField("bookTitle"), "Publication Title");
|
|
|
|
|
});
|
|
|
|
|
});
|
2015-04-25 07:14:53 +00:00
|
|
|
|
});
|