zotero/test/tests/retractionsTest.js
2023-09-03 18:51:50 -04:00

430 lines
12 KiB
JavaScript

describe("Retractions", function () {
var userLibraryID;
var win;
var zp;
var checkQueueItemsStub;
var retractedDOI = '10.1056/NEJMoa1200303'; // mixed case
before(async function () {
userLibraryID = Zotero.Libraries.userLibraryID;
win = await loadZoteroPane();
zp = win.ZoteroPane;
await Zotero.Retractions.updateFromServer();
// Remove debouncing on checkQueuedItems()
checkQueueItemsStub = sinon.stub(Zotero.Retractions, 'checkQueuedItems').callsFake(() => {
return Zotero.Retractions._checkQueuedItemsInternal();
});
});
beforeEach(async function () {
var ids = await Zotero.DB.columnQueryAsync("SELECT itemID FROM retractedItems");
if (ids.length) {
await Zotero.Items.erase(ids);
}
});
afterEach(async function () {
win.document.getElementById('retracted-items-close').click();
checkQueueItemsStub.resetHistory();
});
after(async function () {
win.close();
checkQueueItemsStub.restore();
var ids = await Zotero.DB.columnQueryAsync("SELECT itemID FROM retractedItems");
if (ids.length) {
await Zotero.Items.erase(ids);
}
});
async function createRetractedItem(options = {}) {
var o = {
itemType: 'journalArticle'
};
Object.assign(o, options);
var item = createUnsavedDataObject('item', o);
item.setField('DOI', retractedDOI);
if (Zotero.DB.inTransaction()) {
await item.save();
}
else {
await item.saveTx();
}
while (!checkQueueItemsStub.called) {
await Zotero.Promise.delay(50);
}
await checkQueueItemsStub.returnValues[0];
checkQueueItemsStub.resetHistory();
return item;
}
async function createRetractedItemWithExtraDOI(options = {}) {
var o = {
itemType: 'journalArticle'
};
Object.assign(o, options);
var item = createUnsavedDataObject('item', o);
item.setField('extra', 'DOI: ' + retractedDOI);
if (Zotero.DB.inTransaction()) {
await item.save();
}
else {
await item.saveTx();
}
while (!checkQueueItemsStub.called) {
await Zotero.Promise.delay(50);
}
await checkQueueItemsStub.returnValues[0];
checkQueueItemsStub.resetHistory();
return item;
}
function bannerShown() {
var container = win.document.getElementById('retracted-items-container');
if (container.getAttribute('collapsed') == 'true') {
return false;
}
if (!container.hasAttribute('collapsed')) {
return true;
}
throw new Error("'collapsed' attribute not found");
}
describe("#updateFromServer()", function () {
var server;
var baseURL;
before(function () {
Zotero.HTTP.mock = sinon.FakeXMLHttpRequest;
baseURL = ZOTERO_CONFIG.API_URL + 'retractions/';
});
beforeEach(function () {
server = sinon.fakeServer.create();
server.autoRespond = true;
});
after(async function () {
Zotero.HTTP.mock = null;
// Restore the real list from the server. We could just mock it as part of the suite.
await Zotero.Retractions.updateFromServer();
});
/*it("shouldn't show banner or virtual collection for already flagged items on list update", async function () {
await Zotero.Retractions.updateFromServer();
});*/
it("should remove retraction flag from items that no longer match prefix list", async function () {
var doi = '10.1234/abcde';
var hash = Zotero.Utilities.Internal.sha1(doi);
var prefix = hash.substr(0, 5);
var lines = [
Zotero.Retractions.TYPE_DOI + prefix + ' 12345\n',
Zotero.Retractions.TYPE_DOI + 'aaaaa 23456\n'
];
var listCount = 0;
var searchCount = 0;
server.respond(function (req) {
if (req.method == 'GET' && req.url == baseURL + 'list') {
listCount++;
if (listCount == 1) {
req.respond(
200,
{
'Content-Type': 'text/plain',
'ETag': 'abcdefg'
},
lines.join('')
);
}
else if (listCount == 2) {
req.respond(
200,
{
'Content-Type': 'text/plain',
'ETag': 'bcdefgh'
},
lines[1]
);
}
}
else if (req.method == 'POST' && req.url == baseURL + 'search') {
searchCount++;
if (searchCount == 1) {
req.respond(
200,
{
'Content-Type': 'application/json'
},
JSON.stringify([
{
doi: hash,
retractionDOI: '10.1234/bcdef',
date: '2019-01-02',
reasons: [
"Error in Data"
],
urls: []
}
])
);
}
}
});
await Zotero.Retractions.updateFromServer();
// Create item with DOI from list
var promise = waitForItemEvent('refresh');
var item = createUnsavedDataObject('item', { itemType: 'journalArticle' });
item.setField('DOI', doi);
await item.saveTx();
await promise;
assert.isTrue(Zotero.Retractions.isRetracted(item));
// Make a second request, with the entry removed
promise = waitForItemEvent('refresh');
await Zotero.Retractions.updateFromServer();
await promise;
assert.isFalse(Zotero.Retractions.isRetracted(item));
});
});
describe("#shouldShowCitationWarning()", function () {
it("should return false if citation warning is hidden", async function () {
var item = await createRetractedItem();
assert.isTrue(Zotero.Retractions.shouldShowCitationWarning(item));
await Zotero.Retractions.disableCitationWarningsForItem(item);
assert.isFalse(Zotero.Retractions.shouldShowCitationWarning(item));
});
it("should return false if retraction is hidden", async function () {
var item = await createRetractedItem();
assert.isTrue(Zotero.Retractions.shouldShowCitationWarning(item));
await Zotero.Retractions.hideRetraction(item);
assert.isFalse(Zotero.Retractions.shouldShowCitationWarning(item));
});
});
describe("#getRetractionsFromJSON()", function () {
it("should identify object with retracted DOI", async function () {
var spy = sinon.spy(Zotero.HTTP, 'request');
var json = [
{
},
{
DOI: retractedDOI
},
{
DOI: '10.1234/abcd'
}
];
var indexes = await Zotero.Retractions.getRetractionsFromJSON(json);
assert.sameMembers(indexes, [1]);
assert.equal(spy.callCount, 1);
indexes = await Zotero.Retractions.getRetractionsFromJSON(json);
assert.sameMembers(indexes, [1]);
// Result should've been cached, so we should have it without another API request
assert.equal(spy.callCount, 1);
spy.restore();
});
it("should identify object with retracted DOI in Extra", async function () {
var json = [
{
extra: `DOI: ${retractedDOI}`
}
];
var indexes = await Zotero.Retractions.getRetractionsFromJSON(json);
assert.sameMembers(indexes, [0]);
});
it("should identify object with retracted DOI on subsequent line in Extra", async function () {
var json = [
{
extra: `Foo: Bar\nDOI: ${retractedDOI}`
}
];
var indexes = await Zotero.Retractions.getRetractionsFromJSON(json);
assert.sameMembers(indexes, [0]);
});
});
describe("Notification Banner", function () {
it("should show banner when retracted item is added", async function () {
assert.isFalse(bannerShown());
await createRetractedItem();
do {
await delay(10);
}
while (!bannerShown());
});
it("should show banner when retracted item with DOI in Extra is added", async function () {
assert.isFalse(bannerShown());
await createRetractedItemWithExtraDOI();
do {
await delay(10);
}
while (!bannerShown());
});
it("shouldn't show banner when item in trash is added", async function () {
await createRetractedItem({ deleted: true });
await delay(50);
assert.isFalse(bannerShown());
});
});
describe("virtual collection", function () {
it("should show/hide Retracted Items collection when a retracted item is found/erased", async function () {
// Create item
var item = await createRetractedItem();
assert.ok(zp.collectionsView.getRowIndexByID("R" + userLibraryID));
// Erase item
var promise = waitForItemEvent('refresh');
await item.eraseTx();
await promise;
assert.isFalse(zp.collectionsView.getRowIndexByID("R" + userLibraryID));
});
it("should unhide Retracted Items collection when retracted item is found", async function () {
await createRetractedItem();
// Hide collection
await zp.setVirtual(userLibraryID, 'retracted', false);
// Add another retracted item, which should unhide it
await createRetractedItem();
assert.ok(zp.collectionsView.getRowIndexByID("R" + userLibraryID));
});
it("should hide Retracted Items collection when last retracted item is moved to trash", async function () {
var rowID = "R" + userLibraryID;
// Create item
var item = await createRetractedItem();
assert.ok(zp.collectionsView.getRowIndexByID(rowID));
// Select Retracted Items collection
await zp.collectionsView.selectByID(rowID);
await waitForItemsLoad(win);
// Erase item
item.deleted = true;
await item.saveTx();
await Zotero.Promise.delay(50);
// Retracted Items should be gone
assert.isFalse(zp.collectionsView.getRowIndexByID(rowID));
// And My Library should be selected
assert.equal(zp.collectionsView.selectedTreeRow.id, "L" + userLibraryID);
});
it("should hide Retracted Items collection when last retracted item is marked as hidden", async function () {
var rowID = "R" + userLibraryID;
// Create item
var item = await createRetractedItem();
assert.ok(zp.collectionsView.getRowIndexByID(rowID));
// Select Retracted Items collection
await zp.collectionsView.selectByID(rowID);
await waitForItemsLoad(win);
await Zotero.Retractions.hideRetraction(item);
await Zotero.Promise.delay(50);
// Retracted Items should be gone
assert.isFalse(zp.collectionsView.getRowIndexByID(rowID));
// And My Library should be selected
assert.equal(zp.collectionsView.selectedTreeRow.id, "L" + userLibraryID);
});
it("shouldn't hide Retracted Items collection when last retracted item is marked to not show a citation warning", async function () {
var rowID = "R" + userLibraryID;
// Create item
var item = await createRetractedItem();
assert.ok(zp.collectionsView.getRowIndexByID(rowID));
// Select Retracted Items collection
await zp.collectionsView.selectByID(rowID);
await waitForItemsLoad(win);
await Zotero.Retractions.disableCitationWarningsForItem(item);
await Zotero.Promise.delay(50);
// Should still be showing
assert.ok(zp.collectionsView.getRowIndexByID("R" + userLibraryID));
});
it("should show Retracted Items collection when retracted item is restored from trash", async function () {
// Create trashed item
var item = await createRetractedItem({ deleted: true });
await Zotero.Promise.delay(50);
assert.isFalse(zp.collectionsView.getRowIndexByID("R" + userLibraryID));
// Restore item
item.deleted = false;
await item.saveTx();
await Zotero.Promise.delay(50);
assert.ok(zp.collectionsView.getRowIndexByID("R" + userLibraryID));
});
});
describe("retractions.enabled", function () {
beforeEach(function () {
Zotero.Prefs.clear('retractions.enabled');
});
it("should hide virtual collection and banner when false", async function () {
var item = await createRetractedItem();
await Zotero.Promise.delay(50);
var itemRetractionBox = win.document.getElementById('retraction-box');
assert.isFalse(itemRetractionBox.hidden);
var spies = [
sinon.spy(Zotero.Retractions, '_removeAllEntries'),
sinon.spy(Zotero.Retractions, 'isRetracted')
];
Zotero.Prefs.set('retractions.enabled', false);
while (!spies[0].called || !spies[1].called) {
await Zotero.Promise.delay(50);
}
await spies[0].returnValues[0];
await spies[1].returnValues[0];
spies.forEach(spy => spy.restore());
assert.isFalse(Zotero.Retractions.isRetracted(item));
assert.isFalse(zp.collectionsView.getRowIndexByID("R" + userLibraryID));
assert.isFalse(bannerShown());
assert.isTrue(itemRetractionBox.hidden);
await item.eraseTx();
});
});
});