2016-01-08 08:31:43 +00:00
|
|
|
describe("Zotero.Schema", function() {
|
2016-02-29 09:23:00 +00:00
|
|
|
describe("#initializeSchema()", function () {
|
|
|
|
it("should set last client version", function* () {
|
|
|
|
yield resetDB({
|
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true
|
|
|
|
});
|
|
|
|
|
|
|
|
var sql = "SELECT value FROM settings WHERE setting='client' AND key='lastVersion'";
|
|
|
|
var lastVersion = yield Zotero.DB.valueQueryAsync(sql);
|
|
|
|
yield assert.eventually.equal(Zotero.DB.valueQueryAsync(sql), Zotero.version);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("#updateSchema()", function () {
|
|
|
|
it("should set last client version", function* () {
|
|
|
|
var sql = "REPLACE INTO settings (setting, key, value) VALUES ('client', 'lastVersion', ?)";
|
2018-09-26 06:50:05 +00:00
|
|
|
yield Zotero.DB.queryAsync(sql, "5.0old");
|
2016-02-29 09:23:00 +00:00
|
|
|
|
|
|
|
yield Zotero.Schema.updateSchema();
|
|
|
|
|
|
|
|
var sql = "SELECT value FROM settings WHERE setting='client' AND key='lastVersion'";
|
|
|
|
var lastVersion = yield Zotero.DB.valueQueryAsync(sql);
|
|
|
|
yield assert.eventually.equal(Zotero.DB.valueQueryAsync(sql), Zotero.version);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
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("Global Schema", function () {
|
|
|
|
var schemaJSON, schema;
|
|
|
|
|
|
|
|
before(async function () {
|
|
|
|
schemaJSON = await Zotero.File.getResourceAsync('resource://zotero/schema/global/schema.json');
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(async function () {
|
|
|
|
await resetDB({
|
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true
|
|
|
|
});
|
|
|
|
schema = JSON.parse(schemaJSON);
|
|
|
|
});
|
|
|
|
|
|
|
|
after(async function() {
|
|
|
|
await resetDB({
|
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("#migrateExtraFields()", function () {
|
2020-03-19 19:59:50 +00:00
|
|
|
async function migrate() {
|
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
|
|
|
schema.version++;
|
|
|
|
schema.itemTypes.find(x => x.itemType == 'book').fields.splice(0, 1, { field: 'fooBar' })
|
|
|
|
var newLocales = {};
|
|
|
|
Object.keys(schema.locales).forEach((locale) => {
|
|
|
|
var o = schema.locales[locale];
|
|
|
|
o.fields.fooBar = 'Foo Bar';
|
|
|
|
newLocales[locale] = o;
|
|
|
|
});
|
|
|
|
await Zotero.Schema._updateGlobalSchemaForTest(schema);
|
|
|
|
await Zotero.Schema.migrateExtraFields();
|
2020-03-19 19:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
it("should add a new field and migrate values from Extra", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'book' });
|
|
|
|
item.setField('numPages', "10");
|
|
|
|
item.setField('extra', 'Foo Bar: This is a value.\nnumber-of-pages: 11\nThis is another line.');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
await migrate();
|
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
|
|
|
|
|
|
|
assert.isNumber(Zotero.ItemFields.getID('fooBar'));
|
|
|
|
assert.equal(Zotero.ItemFields.getLocalizedString('fooBar'), 'Foo Bar');
|
|
|
|
assert.equal(item.getField('fooBar'), 'This is a value.');
|
|
|
|
// Existing fields shouldn't be overwritten and should be left in Extra
|
|
|
|
assert.equal(item.getField('numPages'), '10');
|
|
|
|
assert.equal(item.getField('extra'), 'number-of-pages: 11\nThis is another line.');
|
2019-09-28 11:39:52 +00:00
|
|
|
assert.isFalse(item.synced);
|
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-03-15 17:51:04 +00:00
|
|
|
|
2020-03-17 14:37:16 +00:00
|
|
|
it("should migrate valid creator", async function () {
|
2020-03-15 17:51:04 +00:00
|
|
|
var item = await createDataObject('item', { itemType: 'book' });
|
|
|
|
item.setCreators([
|
|
|
|
{
|
|
|
|
firstName: 'Abc',
|
|
|
|
lastName: 'Def',
|
|
|
|
creatorType: 'author',
|
|
|
|
fieldMode: 0
|
|
|
|
}
|
|
|
|
]);
|
|
|
|
item.setField('extra', 'editor: Last || First\nFoo: Bar');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
2020-03-19 19:59:50 +00:00
|
|
|
await migrate();
|
2020-03-15 17:51:04 +00:00
|
|
|
|
|
|
|
var creators = item.getCreators();
|
|
|
|
assert.lengthOf(creators, 2);
|
|
|
|
assert.propertyVal(creators[0], 'firstName', 'Abc');
|
|
|
|
assert.propertyVal(creators[0], 'lastName', 'Def');
|
|
|
|
assert.propertyVal(creators[0], 'creatorTypeID', Zotero.CreatorTypes.getID('author'));
|
|
|
|
assert.propertyVal(creators[1], 'firstName', 'First');
|
|
|
|
assert.propertyVal(creators[1], 'lastName', 'Last');
|
|
|
|
assert.propertyVal(creators[1], 'creatorTypeID', Zotero.CreatorTypes.getID('editor'));
|
|
|
|
assert.equal(item.getField('extra'), 'Foo: Bar');
|
|
|
|
assert.isFalse(item.synced);
|
|
|
|
});
|
2020-03-15 23:52:44 +00:00
|
|
|
|
2020-03-17 14:37:16 +00:00
|
|
|
it("shouldn't migrate creator not valid for item type", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'book' });
|
|
|
|
item.setCreators([
|
|
|
|
{
|
|
|
|
firstName: 'Abc',
|
|
|
|
lastName: 'Def',
|
|
|
|
creatorType: 'author',
|
|
|
|
fieldMode: 0
|
|
|
|
}
|
|
|
|
]);
|
|
|
|
item.setField('extra', 'container-author: Last || First\nFoo: Bar');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
2020-03-19 19:59:50 +00:00
|
|
|
await migrate();
|
2020-03-17 14:37:16 +00:00
|
|
|
|
|
|
|
var creators = item.getCreators();
|
|
|
|
assert.lengthOf(creators, 1);
|
|
|
|
assert.propertyVal(creators[0], 'firstName', 'Abc');
|
|
|
|
assert.propertyVal(creators[0], 'lastName', 'Def');
|
|
|
|
assert.propertyVal(creators[0], 'creatorTypeID', Zotero.CreatorTypes.getID('author'));
|
|
|
|
assert.equal(item.getField('extra'), 'container-author: Last || First\nFoo: Bar');
|
|
|
|
assert.isTrue(item.synced);
|
|
|
|
});
|
|
|
|
|
2020-03-15 23:52:44 +00:00
|
|
|
it("shouldn't migrate fields in read-only library", async function () {
|
|
|
|
var library = await createGroup({ editable: false, filesEditable: false });
|
|
|
|
var item = createUnsavedDataObject('item', { libraryID: library.libraryID, itemType: 'book' });
|
|
|
|
item.setField('extra', 'Foo Bar: This is a value.');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx({
|
|
|
|
skipEditCheck: true
|
|
|
|
});
|
|
|
|
|
2020-03-19 19:59:50 +00:00
|
|
|
await migrate();
|
2020-03-15 23:52:44 +00:00
|
|
|
|
|
|
|
assert.isNumber(Zotero.ItemFields.getID('fooBar'));
|
|
|
|
assert.equal(item.getField('fooBar'), '');
|
|
|
|
assert.equal(item.getField('extra'), 'Foo Bar: This is a value.');
|
|
|
|
assert.isTrue(item.synced);
|
|
|
|
});
|
2020-03-17 17:51:19 +00:00
|
|
|
|
2020-03-22 19:19:24 +00:00
|
|
|
it("should change item type if 'type:' is defined", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'document' });
|
|
|
|
item.setField('extra', 'type: personal_communication');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
await migrate();
|
|
|
|
|
|
|
|
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('letter'));
|
|
|
|
assert.equal(item.getField('extra'), '');
|
|
|
|
assert.isFalse(item.synced);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should remove 'type:' line for CSL type if item is the first mapped Zotero type", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'letter' });
|
|
|
|
item.setField('extra', 'type: personal_communication');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
await migrate();
|
|
|
|
|
|
|
|
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('letter'));
|
|
|
|
assert.equal(item.getField('extra'), '');
|
|
|
|
assert.isFalse(item.synced);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should remove 'type:' line for CSL type if item is a non-primary mapped Zotero type", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'instantMessage' });
|
|
|
|
item.setField('extra', 'type: personal_communication');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
await migrate();
|
|
|
|
|
|
|
|
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('instantMessage'));
|
|
|
|
assert.equal(item.getField('extra'), '');
|
|
|
|
assert.isFalse(item.synced);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should move existing fields that would be invalid in the new 'type:' type to Extra", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'book' });
|
|
|
|
item.setField('numPages', '123');
|
|
|
|
item.setField('extra', 'type: article-journal\nJournal Abbreviation: abc.\nnumPages: 234');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
await migrate();
|
|
|
|
|
|
|
|
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('journalArticle'));
|
|
|
|
assert.equal(item.getField('journalAbbreviation'), 'abc.');
|
|
|
|
// Migrated real field should be placed at beginning, followed by unused line from Extra
|
|
|
|
assert.equal(item.getField('extra'), 'Num Pages: 123\nnumPages: 234');
|
|
|
|
assert.isFalse(item.synced);
|
|
|
|
});
|
|
|
|
|
2020-03-17 17:51:19 +00:00
|
|
|
it("shouldn't migrate invalid item type", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'book' });
|
|
|
|
item.setField('numPages', 30);
|
|
|
|
item.setCreators(
|
|
|
|
[
|
|
|
|
{
|
|
|
|
firstName: 'Abc',
|
|
|
|
lastName: 'Def',
|
|
|
|
creatorType: 'author',
|
|
|
|
fieldMode: 0
|
|
|
|
},
|
|
|
|
{
|
|
|
|
firstName: 'Ghi',
|
|
|
|
lastName: 'Jkl',
|
|
|
|
creatorType: 'author',
|
|
|
|
fieldMode: 0
|
|
|
|
}
|
|
|
|
]
|
|
|
|
);
|
|
|
|
item.setField('extra', 'type: invalid');
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
2020-03-19 19:59:50 +00:00
|
|
|
await migrate();
|
2020-03-17 17:51:19 +00:00
|
|
|
|
|
|
|
assert.equal(item.getField('numPages'), 30);
|
|
|
|
var creators = item.getCreators();
|
|
|
|
assert.lengthOf(creators, 2);
|
|
|
|
assert.equal(item.itemTypeID, Zotero.ItemTypes.getID('book'));
|
2020-03-17 18:17:02 +00:00
|
|
|
assert.equal(item.getField('extra'), 'type: invalid');
|
2020-03-17 17:51:19 +00:00
|
|
|
assert.isTrue(item.synced);
|
|
|
|
});
|
2023-03-21 10:23:16 +00:00
|
|
|
|
|
|
|
it("shouldn't migrate certain fields temporarily", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'book' });
|
|
|
|
var extra = 'event-place: Event Place\npublisher-place: Publisher Place\nIssued: 2020/2023';
|
|
|
|
item.setField('extra', extra);
|
|
|
|
item.synced = true;
|
|
|
|
await item.saveTx();
|
|
|
|
|
|
|
|
await migrate();
|
|
|
|
|
|
|
|
assert.equal(item.getField('place'), '');
|
|
|
|
assert.equal(item.getField('date'), '');
|
|
|
|
assert.equal(item.getField('extra'), extra);
|
|
|
|
});
|
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
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2021-08-29 07:31:41 +00:00
|
|
|
describe("Repository Check", function () {
|
|
|
|
describe("Notices", function () {
|
|
|
|
var win;
|
|
|
|
var server;
|
|
|
|
|
|
|
|
before(async function () {
|
2021-08-29 08:41:52 +00:00
|
|
|
// We need bundled files
|
|
|
|
await resetDB({
|
|
|
|
thisArg: this
|
|
|
|
});
|
|
|
|
|
2021-08-29 07:31:41 +00:00
|
|
|
win = await loadZoteroPane();
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
Zotero.HTTP.mock = sinon.FakeXMLHttpRequest;
|
|
|
|
server = sinon.fakeServer.create();
|
|
|
|
server.autoRespond = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(function () {
|
|
|
|
Zotero.Prefs.clear('hiddenNotices');
|
|
|
|
});
|
|
|
|
|
|
|
|
after(function () {
|
|
|
|
win.close();
|
|
|
|
Zotero.HTTP.mock = null;
|
|
|
|
});
|
|
|
|
|
|
|
|
function createResponseWithMessage(message) {
|
|
|
|
server.respond(function (req) {
|
|
|
|
if (req.method != "POST" || !req.url.includes('/repo/updated')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
req.respond(
|
|
|
|
200,
|
|
|
|
{
|
|
|
|
"Content-Type": "application/xml"
|
|
|
|
},
|
|
|
|
'<xml>'
|
|
|
|
+ '<currentTime>1630219842</currentTime>'
|
|
|
|
+ message
|
|
|
|
+ '</xml>'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
it("should show dialog if repo returns a message", async function () {
|
|
|
|
createResponseWithMessage(
|
|
|
|
`<message infoURL="https://example.com">This is a warning</message>`
|
|
|
|
);
|
|
|
|
|
|
|
|
var promise = waitForDialog(function (dialog) {
|
|
|
|
var html = dialog.document.documentElement.outerHTML;
|
|
|
|
assert.include(html, "This is a warning");
|
|
|
|
});
|
|
|
|
await Zotero.Schema.updateFromRepository(3);
|
|
|
|
await promise;
|
|
|
|
|
|
|
|
// Don't show id-less message again for a day
|
|
|
|
var spy = sinon.spy(Zotero, 'debug');
|
|
|
|
await Zotero.Schema.updateFromRepository(3);
|
|
|
|
assert.notEqual(spy.args.findIndex(x => {
|
|
|
|
return typeof x[0] == 'string' && x[0].startsWith("Not showing hidden");
|
|
|
|
}), -1);
|
|
|
|
spy.restore();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("shouldn't show message with id again for 1 day even if not hidden", async function () {
|
|
|
|
var id = Zotero.Utilities.randomString();
|
|
|
|
createResponseWithMessage(
|
|
|
|
`<message id="${id}" infoURL="https://example.com">This is a warning</message>`
|
|
|
|
);
|
|
|
|
|
|
|
|
var promise = waitForDialog();
|
|
|
|
await Zotero.Schema.updateFromRepository(3);
|
|
|
|
await promise;
|
|
|
|
|
|
|
|
// Make sure notice is hidden for 1 day
|
|
|
|
var hiddenNotices;
|
|
|
|
var tries = 0;
|
|
|
|
var ttl = 86400;
|
|
|
|
while (tries < 100) {
|
|
|
|
tries++;
|
|
|
|
hiddenNotices = Zotero.Prefs.get('hiddenNotices');
|
|
|
|
if (!hiddenNotices) {
|
|
|
|
await Zotero.Promise.delay(10);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
hiddenNotices = JSON.parse(hiddenNotices);
|
|
|
|
assert.property(hiddenNotices, id);
|
|
|
|
assert.approximately(hiddenNotices[id], Math.round(Date.now() / 1000) + ttl, 10);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
it("shouldn't show message with id again for 30 days", async function () {
|
|
|
|
var id = Zotero.Utilities.randomString();
|
|
|
|
createResponseWithMessage(
|
|
|
|
`<message id="${id}" infoURL="https://example.com">This is a warning</message>`
|
|
|
|
);
|
|
|
|
|
|
|
|
var promise = waitForDialog(function (dialog) {
|
|
|
|
var doc = dialog.document;
|
|
|
|
var innerHTML = doc.documentElement.innerHTML;
|
|
|
|
assert.include(innerHTML, "This is a warning");
|
|
|
|
assert.include(innerHTML, Zotero.getString('general.dontShowAgainFor', 30, 30));
|
|
|
|
// Check "Don't show again"
|
|
|
|
doc.getElementById('checkbox').click();
|
|
|
|
});
|
|
|
|
await Zotero.Schema.updateFromRepository(3);
|
|
|
|
await promise;
|
|
|
|
|
|
|
|
// Make sure notice is hidden for 30 days
|
|
|
|
var hiddenNotices;
|
|
|
|
var tries = 0;
|
|
|
|
var ttl = 30 * 86400;
|
|
|
|
while (tries < 100) {
|
|
|
|
tries++;
|
|
|
|
hiddenNotices = Zotero.Prefs.get('hiddenNotices');
|
|
|
|
if (!hiddenNotices) {
|
|
|
|
await Zotero.Promise.delay(10);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
hiddenNotices = JSON.parse(hiddenNotices);
|
|
|
|
assert.property(hiddenNotices, id);
|
|
|
|
assert.approximately(hiddenNotices[id], Math.round(Date.now() / 1000) + ttl, 10);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
it("shouldn't show message with id if before expiration", async function () {
|
|
|
|
var id = Zotero.Utilities.randomString();
|
|
|
|
createResponseWithMessage(
|
|
|
|
`<message id="${id}" infoURL="https://example.com">This is a warning</message>`
|
|
|
|
);
|
|
|
|
|
|
|
|
// Set expiration for 30 days from now
|
|
|
|
var ttl = 30 * 86400;
|
|
|
|
Zotero.Prefs.set(
|
|
|
|
'hiddenNotices',
|
|
|
|
JSON.stringify({
|
|
|
|
[id]: Math.round(Date.now() / 1000) + ttl
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
// Message should be hidden
|
|
|
|
var spy = sinon.spy(Zotero, 'debug');
|
|
|
|
await Zotero.Schema.updateFromRepository(3);
|
|
|
|
assert.notEqual(spy.args.findIndex(x => {
|
|
|
|
return typeof x[0] == 'string' && x[0].startsWith("Not showing hidden");
|
|
|
|
}), -1);
|
|
|
|
spy.restore();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2016-01-08 08:31:43 +00:00
|
|
|
describe("#integrityCheck()", function () {
|
|
|
|
before(function* () {
|
|
|
|
yield resetDB({
|
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true
|
|
|
|
});
|
|
|
|
})
|
|
|
|
|
2021-01-17 08:30:00 +00:00
|
|
|
it("should create missing tables unless 'skipReconcile' is true", async function () {
|
2020-11-16 23:01:16 +00:00
|
|
|
await Zotero.DB.queryAsync("DROP TABLE retractedItems");
|
|
|
|
assert.isFalse(await Zotero.DB.tableExists('retractedItems'));
|
2021-01-17 08:30:00 +00:00
|
|
|
assert.isTrue(await Zotero.Schema.integrityCheck(false, { skipReconcile: true }));
|
|
|
|
|
2020-11-16 23:01:16 +00:00
|
|
|
assert.isFalse(await Zotero.Schema.integrityCheck());
|
|
|
|
assert.isTrue(await Zotero.Schema.integrityCheck(true));
|
|
|
|
assert.isTrue(await Zotero.DB.tableExists('retractedItems'));
|
|
|
|
});
|
|
|
|
|
2016-01-08 08:31:43 +00:00
|
|
|
it("should repair a foreign key violation", function* () {
|
|
|
|
yield assert.eventually.isTrue(Zotero.Schema.integrityCheck());
|
|
|
|
|
|
|
|
yield Zotero.DB.queryAsync("PRAGMA foreign_keys = OFF");
|
|
|
|
yield Zotero.DB.queryAsync("INSERT INTO itemTags VALUES (1234,1234,0)");
|
|
|
|
yield Zotero.DB.queryAsync("PRAGMA foreign_keys = ON");
|
|
|
|
|
|
|
|
yield assert.eventually.isFalse(Zotero.Schema.integrityCheck());
|
|
|
|
yield assert.eventually.isTrue(Zotero.Schema.integrityCheck(true));
|
|
|
|
yield assert.eventually.isTrue(Zotero.Schema.integrityCheck());
|
|
|
|
})
|
2020-03-13 22:04:04 +00:00
|
|
|
|
|
|
|
it("should repair invalid nesting between two collections", async function () {
|
|
|
|
var c1 = await createDataObject('collection');
|
|
|
|
var c2 = await createDataObject('collection', { parentID: c1.id });
|
|
|
|
await Zotero.DB.queryAsync(
|
|
|
|
"UPDATE collections SET parentCollectionID=? WHERE collectionID=?",
|
|
|
|
[c2.id, c1.id]
|
|
|
|
);
|
|
|
|
|
|
|
|
await assert.isFalse(await Zotero.Schema.integrityCheck());
|
|
|
|
await assert.isTrue(await Zotero.Schema.integrityCheck(true));
|
|
|
|
await assert.isTrue(await Zotero.Schema.integrityCheck());
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should repair invalid nesting between three collections", async function () {
|
|
|
|
var c1 = await createDataObject('collection');
|
|
|
|
var c2 = await createDataObject('collection', { parentID: c1.id });
|
|
|
|
var c3 = await createDataObject('collection', { parentID: c2.id });
|
|
|
|
await Zotero.DB.queryAsync(
|
|
|
|
"UPDATE collections SET parentCollectionID=? WHERE collectionID=?",
|
|
|
|
[c3.id, c2.id]
|
|
|
|
);
|
|
|
|
|
|
|
|
await assert.isFalse(await Zotero.Schema.integrityCheck());
|
|
|
|
await assert.isTrue(await Zotero.Schema.integrityCheck(true));
|
|
|
|
await assert.isTrue(await Zotero.Schema.integrityCheck());
|
|
|
|
});
|
2021-02-19 10:19:51 +00:00
|
|
|
|
|
|
|
it("should allow embedded-image attachments under notes", async function () {
|
|
|
|
var item = await createDataObject('item', { itemType: 'note' });
|
|
|
|
await createEmbeddedImage(item);
|
|
|
|
await assert.isTrue(await Zotero.Schema.integrityCheck());
|
|
|
|
});
|
2016-01-08 08:31:43 +00:00
|
|
|
})
|
2021-08-16 23:50:44 +00:00
|
|
|
|
|
|
|
describe("Database Upgrades", function () {
|
|
|
|
after(async function () {
|
|
|
|
await resetDB({
|
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should upgrade 4.0 database", async function () {
|
|
|
|
await resetDB({
|
|
|
|
thisArg: this,
|
|
|
|
skipBundledFiles: true,
|
|
|
|
dbFile: OS.Path.join(getTestDataDirectory().path, 'zotero-4.0.sqlite.zip')
|
|
|
|
});
|
|
|
|
// Make sure we can open the Zotero pane without errors
|
|
|
|
win = await loadZoteroPane();
|
|
|
|
win.close();
|
|
|
|
});
|
|
|
|
});
|
2016-01-08 08:31:43 +00:00
|
|
|
})
|