Test item data loading and the field lookup cache

The _loadItemData() tests pass on the code before this change too: they pin
the normalization it must keep (newlines, ISBN hyphenation, base-mapped fields,
absent fields) and that setField() is still called for every value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
TheNEwmanator15 2026-09-26 01:19:45 +01:00
parent 912801a349
commit 2b928b8a01
2 changed files with 109 additions and 0 deletions

View file

@ -64,4 +64,42 @@ describe("Zotero.ItemFields", function () {
assert.equal(Zotero.ItemFields.getDirection('book', 'creator-0-lastName', 'ar'), 'rtl');
});
});
describe("#_getLoadInfo()", function () {
it("should resolve a base field to the item type's field", function () {
var info = Zotero.ItemFields._getLoadInfo(
Zotero.ItemTypes.getID('thesis'), Zotero.ItemFields.getID('publisher')
);
assert.equal(info.fieldID, Zotero.ItemFields.getID('university'));
assert.isTrue(info.valid);
});
it("should mark a field that isn't valid for the item type", function () {
var info = Zotero.ItemFields._getLoadInfo(
Zotero.ItemTypes.getID('book'), Zotero.ItemFields.getID('websiteTitle')
);
assert.isFalse(info.valid);
});
it("should return false for an unknown field", function () {
assert.isFalse(Zotero.ItemFields._getLoadInfo(Zotero.ItemTypes.getID('book'), 999999));
});
// A plugin that replaces a lookup method must still be consulted
it("should use a replaced lookup method", function () {
var bookID = Zotero.ItemTypes.getID('book');
var titleID = Zotero.ItemFields.getID('title');
assert.isTrue(Zotero.ItemFields._getLoadInfo(bookID, titleID).valid);
var original = Zotero.ItemFields.isValidForType;
Zotero.ItemFields.isValidForType = () => false;
try {
assert.isFalse(Zotero.ItemFields._getLoadInfo(bookID, titleID).valid);
}
finally {
Zotero.ItemFields.isValidForType = original;
}
assert.isTrue(Zotero.ItemFields._getLoadInfo(bookID, titleID).valid);
});
});
})

View file

@ -799,4 +799,75 @@ describe("Zotero.Items", function () {
assert.include(ids, att.id);
});
});
describe("#_loadItemData()", function () {
// Point a stored field at a raw value, bypassing setField(), then reload from the DB
async function loadRaw(item, field, raw) {
var fieldID = Zotero.ItemFields.getID(field);
var valueID = await Zotero.DB.valueQueryAsync("SELECT valueID FROM itemDataValues WHERE value=?", raw);
if (!valueID) {
valueID = Zotero.ID.get('itemDataValues');
await Zotero.DB.queryAsync("INSERT INTO itemDataValues (valueID, value) VALUES (?, ?)", [valueID, raw]);
}
await Zotero.DB.queryAsync(
"REPLACE INTO itemData (itemID, fieldID, valueID) VALUES (?, ?, ?)", [item.id, fieldID, valueID]
);
await Zotero.Items._loadDataTypeInLibrary('itemData', item.libraryID, [item.id]);
}
it("should strip newlines from single-line fields when loading", async function () {
var item = await createDataObject('item', { title: "Title" });
await loadRaw(item, 'title', "Line one\nLine two");
assert.equal(item.getField('title'), "Line one Line two");
});
it("should keep newlines in multiline fields when loading", async function () {
var item = await createDataObject('item');
await loadRaw(item, 'abstractNote', "Line one\nLine two");
assert.equal(item.getField('abstractNote'), "Line one\nLine two");
});
it("should hyphenate ISBNs when loading", async function () {
var item = await createDataObject('item', { itemType: 'book' });
await loadRaw(item, 'ISBN', "9780306406157");
assert.equal(item.getField('ISBN'), "978-0-306-40615-7");
});
it("should load a base-mapped field into the item type's field", async function () {
var item = await createDataObject('item', { itemType: 'thesis' });
await loadRaw(item, 'university', "University of Somewhere");
assert.equal(item.getField('university'), "University of Somewhere");
assert.equal(item.getField('publisher', false, true), "University of Somewhere");
});
it("should set absent fields to empty", async function () {
var item = await createDataObject('item', { itemType: 'book' });
await Zotero.Items._loadDataTypeInLibrary('itemData', item.libraryID, [item.id]);
assert.strictEqual(item.getField('volume'), "");
});
// Plugins wrap Zotero.Item.prototype.setField (e.g., zotero-plugin-toolkit's
// FieldHook), so loading must keep calling it for every value
it("should call setField for every loaded value", async function () {
var item = await createDataObject('item', { itemType: 'book', title: "Called" });
var original = Zotero.Item.prototype.setField;
var calls = [];
Zotero.Item.prototype.setField = function (field, value, loadIn) {
if (this.id == item.id) {
calls.push([field, value, loadIn]);
}
return original.apply(this, arguments);
};
try {
await Zotero.Items._loadDataTypeInLibrary('itemData', item.libraryID, [item.id]);
}
finally {
Zotero.Item.prototype.setField = original;
}
var titleID = Zotero.ItemFields.getID('title');
assert.deepInclude(calls, [titleID, "Called", true]);
// One call per field of the item type: stored values plus absent ones set empty
assert.lengthOf(calls, Zotero.ItemFields.getItemTypeFields(item.itemTypeID).length);
});
});
});