www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

dataObjectTest.js (17141B)


      1 "use strict";
      2 
      3 describe("Zotero.DataObject", function() {
      4 	var types = ['collection', 'item', 'search'];
      5 	
      6 	describe("#library", function () {
      7 		it("should return a Zotero.Library", function* () {
      8 			var item = yield createDataObject('item');
      9 			assert.equal(item.library, Zotero.Libraries.userLibrary);
     10 		});
     11 	});
     12 	
     13 	describe("#libraryID", function () {
     14 		it("should return a libraryID", function* () {
     15 			var item = yield createDataObject('item');
     16 			assert.isNumber(item.libraryID);
     17 			assert.equal(item.libraryID, Zotero.Libraries.userLibraryID);
     18 		});
     19 	});
     20 	
     21 	describe("#key", function () {
     22 		it("shouldn't update .loaded on get if unset", function* () {
     23 			for (let type of types) {
     24 				let param;
     25 				if (type == 'item') {
     26 					param = 'book';
     27 				}
     28 				let obj = new Zotero[Zotero.Utilities.capitalize(type)](param);
     29 				obj.libraryID = Zotero.Libraries.userLibraryID;
     30 				assert.isNull(obj.key, 'key is null for ' + type);
     31 				assert.isFalse(obj._loaded.primaryData, 'primary data not loaded for ' + type);
     32 				obj.key = Zotero.DataObjectUtilities.generateKey();
     33 			}
     34 		})
     35 	})
     36 	
     37 	describe("#version", function () {
     38 		it("should be set to 0 after creating object", function* () {
     39 			for (let type of types) {
     40 				let obj = yield createDataObject(type);
     41 				assert.equal(obj.version, 0);
     42 				yield obj.eraseTx();
     43 			}
     44 		})
     45 		
     46 		it("should be set after creating object", function* () {
     47 			for (let type of types) {
     48 				let obj = yield createDataObject(type, { version: 1234 });
     49 				assert.equal(obj.version, 1234, type + " version mismatch");
     50 				yield obj.eraseTx();
     51 			}
     52 		})
     53 	})
     54 	
     55 	describe("#synced", function () {
     56 		it("should be set to false after creating object", function* () {
     57 			for (let type of types) {
     58 				var obj = createUnsavedDataObject(type);
     59 				var id = yield obj.saveTx();
     60 				assert.isFalse(obj.synced);
     61 				yield obj.eraseTx();
     62 			}
     63 		});
     64 		
     65 		it("should be set to false after modifying object", function* () {
     66 			for (let type of types) {
     67 				var obj = createUnsavedDataObject(type);
     68 				var id = yield obj.saveTx();
     69 				
     70 				obj.synced = true;
     71 				yield obj.saveTx();
     72 				
     73 				if (type == 'item') {
     74 					obj.setField('title', Zotero.Utilities.randomString());
     75 				}
     76 				else {
     77 					obj.name = Zotero.Utilities.randomString();
     78 				}
     79 				yield obj.saveTx();
     80 				assert.isFalse(obj.synced);
     81 				
     82 				yield obj.eraseTx();
     83 			}
     84 		});
     85 		
     86 		it("should be changed to true explicitly with no other changes", function* () {
     87 			for (let type of types) {
     88 				var obj = createUnsavedDataObject(type);
     89 				var id = yield obj.saveTx();
     90 				
     91 				obj.synced = true;
     92 				yield obj.saveTx();
     93 				assert.isTrue(obj.synced);
     94 				
     95 				yield obj.eraseTx();
     96 			}
     97 		});
     98 		
     99 		it("should be changed to true explicitly with other field changes", function* () {
    100 			for (let type of types) {
    101 				var obj = createUnsavedDataObject(type);
    102 				var id = yield obj.saveTx();
    103 				
    104 				if (type == 'item') {
    105 					obj.setField('title', Zotero.Utilities.randomString());
    106 				}
    107 				else {
    108 					obj.name = Zotero.Utilities.randomString();
    109 				}
    110 				obj.synced = true;
    111 				yield obj.saveTx();
    112 				assert.isTrue(obj.synced);
    113 				
    114 				yield obj.eraseTx();
    115 			}
    116 		});
    117 		
    118 		it("should remain at true if set explicitly", function* () {
    119 			for (let type of types) {
    120 				var obj = createUnsavedDataObject(type);
    121 				obj.synced = true;
    122 				var id = yield obj.saveTx();
    123 				assert.isTrue(obj.synced);
    124 				
    125 				if (type == 'item') {
    126 					obj.setField('title', 'test');
    127 				}
    128 				else {
    129 					obj.name = Zotero.Utilities.randomString();
    130 				}
    131 				obj.synced = true;
    132 				yield obj.saveTx();
    133 				assert.isTrue(obj.synced);
    134 				
    135 				yield obj.eraseTx();
    136 			}
    137 		});
    138 		
    139 		it("should be unchanged if skipSyncedUpdate passed", function* () {
    140 			for (let type of types) {
    141 				var obj = createUnsavedDataObject(type);
    142 				var id = yield obj.saveTx();
    143 				
    144 				obj.synced = 1;
    145 				yield obj.saveTx();
    146 				
    147 				if (type == 'item') {
    148 					obj.setField('title', Zotero.Utilities.randomString());
    149 				}
    150 				else {
    151 					obj.name = Zotero.Utilities.randomString();
    152 				}
    153 				yield obj.saveTx({
    154 					skipSyncedUpdate: true
    155 				});
    156 				assert.ok(obj.synced);
    157 				
    158 				yield obj.eraseTx();
    159 			}
    160 		});
    161 	})
    162 	
    163 	describe("#loadPrimaryData()", function () {
    164 		it("should load unloaded primary data if partially set", function* () {
    165 			var objs = {};
    166 			for (let type of types) {
    167 				let obj = createUnsavedDataObject(type);
    168 				yield obj.save({
    169 					skipCache: true
    170 				});
    171 				objs[type] = {
    172 					key: obj.key,
    173 					version: obj.version
    174 				};
    175 			}
    176 			
    177 			for (let type of types) {
    178 				let obj = new Zotero[Zotero.Utilities.capitalize(type)];
    179 				obj.libraryID = Zotero.Libraries.userLibraryID;
    180 				obj.key = objs[type].key;
    181 				yield obj.loadPrimaryData();
    182 				assert.equal(obj.version, objs[type].version);
    183 			}
    184 		})
    185 	})
    186 	
    187 	describe("#loadAllData()", function () {
    188 		it("should load data on a regular item", function* () {
    189 			var item = new Zotero.Item('book');
    190 			var id = yield item.saveTx();
    191 			yield item.loadAllData();
    192 			assert.throws(item.getNote.bind(item), 'getNote() can only be called on notes and attachments');
    193 		})
    194 		
    195 		it("should load data on an attachment item", function* () {
    196 			var item = new Zotero.Item('attachment');
    197 			var id = yield item.saveTx();
    198 			yield item.loadAllData();
    199 			assert.equal(item.getNote(), '');
    200 		})
    201 		
    202 		it("should load data on a note item", function* () {
    203 			var item = new Zotero.Item('note');
    204 			var id = yield item.saveTx();
    205 			yield item.loadAllData();
    206 			assert.equal(item.getNote(), '');
    207 		})
    208 	})
    209 	
    210 	
    211 	describe("#hasChanged()", function () {
    212 		it("should return false if 'synced' was set but unchanged and nothing else changed", function* () {
    213 			for (let type of types) {
    214 				// True
    215 				var obj = createUnsavedDataObject(type);
    216 				obj.synced = true;
    217 				var id = yield obj.saveTx();
    218 				assert.isTrue(obj.synced);
    219 				
    220 				obj.synced = true;
    221 				assert.isFalse(obj.hasChanged(), type + " shouldn't be changed");
    222 				
    223 				// False
    224 				var obj = createUnsavedDataObject(type);
    225 				obj.synced = false;
    226 				var id = yield obj.saveTx();
    227 				assert.isFalse(obj.synced);
    228 				obj.synced = false;
    229 				assert.isFalse(obj.hasChanged(), type + " shouldn't be changed");
    230 			}
    231 		})
    232 		
    233 		it("should return true if 'synced' was set but unchanged and another primary field changed", function* () {
    234 			for (let type of types) {
    235 				let obj = createUnsavedDataObject(type);
    236 				obj.synced = true;
    237 				yield obj.saveTx();
    238 				
    239 				obj.synced = true;
    240 				obj.version = 1234;
    241 				assert.isTrue(obj.hasChanged());
    242 			}
    243 		})
    244 	});
    245 	
    246 	
    247 	describe("#save()", function () {
    248 		it("should add new identifiers to cache", function* () {
    249 			for (let type of types) {
    250 				let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
    251 				let obj = createUnsavedDataObject(type);
    252 				let id = yield obj.saveTx();
    253 				let { libraryID, key } = objectsClass.getLibraryAndKeyFromID(id);
    254 				assert.typeOf(key, 'string');
    255 				assert.equal(objectsClass.getIDFromLibraryAndKey(libraryID, key), id);
    256 			}
    257 		})
    258 		
    259 		it("should reset changed state on objects", function* () {
    260 			for (let type of types) {
    261 				let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
    262 				let obj = createUnsavedDataObject(type);
    263 				yield obj.saveTx();
    264 				assert.isFalse(obj.hasChanged());
    265 			}
    266 		})
    267 		
    268 		it("should handle additional tag change in the middle of a save", function* () {
    269 			var item = yield createDataObject('item');
    270 			item.setTags(['a']);
    271 			
    272 			var deferred = new Zotero.Promise.defer();
    273 			var origFunc = Zotero.Notifier.queue.bind(Zotero.Notifier);
    274 			sinon.stub(Zotero.Notifier, "queue").callsFake(function (event, type, ids, extraData) {
    275 				// Add a new tag after the first one has been added to the DB and before the save is
    276 				// finished. The changed state should've cleared before saving to the DB the first
    277 				// time, so the second setTags() should mark the item as changed and allow the new tag
    278 				// to be saved in the second saveTx().
    279 				if (event == 'add' && type == 'item-tag') {
    280 					item.setTags(['a', 'b']);
    281 					Zotero.Notifier.queue.restore();
    282 					deferred.resolve(item.saveTx());
    283 				}
    284 				origFunc(...arguments);
    285 			});
    286 			
    287 			yield Zotero.Promise.all([item.saveTx(), deferred.promise]);
    288 			assert.sameMembers(item.getTags().map(o => o.tag), ['a', 'b']);
    289 			var tags = yield Zotero.DB.columnQueryAsync(
    290 				"SELECT name FROM tags JOIN itemTags USING (tagID) WHERE itemID=?", item.id
    291 			);
    292 			assert.sameMembers(tags, ['a', 'b']);
    293 		});
    294 		
    295 		describe("Edit Check", function () {
    296 			var group;
    297 			
    298 			before(function* () {
    299 				group = yield createGroup({
    300 					editable: false
    301 				});
    302 			});
    303 			
    304 			it("should disallow saving to read-only libraries", function* () {
    305 				let item = createUnsavedDataObject('item', { libraryID: group.libraryID });
    306 				var e = yield getPromiseError(item.saveTx());
    307 				assert.ok(e);
    308 				assert.include(e.message, "read-only");
    309 			});
    310 			
    311 			it("should allow saving if skipEditCheck is passed", function* () {
    312 				let item = createUnsavedDataObject('item', { libraryID: group.libraryID });
    313 				var e = yield getPromiseError(item.saveTx({
    314 					skipEditCheck: true
    315 				}));
    316 				assert.isFalse(e);
    317 			});
    318 			
    319 			it("should allow saving if skipAll is passed", function* () {
    320 				let item = createUnsavedDataObject('item', { libraryID: group.libraryID });
    321 				var e = yield getPromiseError(item.saveTx({
    322 					skipAll: true
    323 				}));
    324 				assert.isFalse(e);
    325 			});
    326 		});
    327 		
    328 		describe("Options", function () {
    329 			describe("#skipAll", function () {
    330 				it("should include edit check", function* () {
    331 					
    332 				});
    333 			});
    334 		});
    335 	})
    336 	
    337 	describe("#erase()", function () {
    338 		it("shouldn't trigger notifier if skipNotifier is passed", function* () {
    339 			let observerIDs = [];
    340 			let promises = [];
    341 			for (let type of types) {
    342 				let obj = yield createDataObject(type);
    343 				// For items, test automatic child item deletion
    344 				if (type == 'item') {
    345 					yield createDataObject(type, { itemType: 'note', parentID: obj.id });
    346 				}
    347 				
    348 				let deferred = Zotero.Promise.defer();
    349 				promises.push(deferred.promise);
    350 				observerIDs.push(Zotero.Notifier.registerObserver(
    351 					{
    352 						notify: function (event) {
    353 							if (event == 'delete') {
    354 								deferred.reject("Notifier called for erase on " + type);
    355 							}
    356 						}
    357 					},
    358 					type,
    359 					'test'
    360 				));
    361 				yield obj.eraseTx({
    362 					skipNotifier: true
    363 				});
    364 			}
    365 			yield Zotero.Promise.all(promises)
    366 				// Give notifier time to trigger
    367 				.timeout(100).catch(Zotero.Promise.TimeoutError, (e) => {})
    368 			
    369 			for (let id of observerIDs) {
    370 				Zotero.Notifier.unregisterObserver(id);
    371 			}
    372 		})
    373 		
    374 		it("should delete object versions from sync cache", function* () {
    375 			for (let type of types) {
    376 				let obj = yield createDataObject(type);
    377 				let libraryID = obj.libraryID;
    378 				let key = obj.key;
    379 				let json = obj.toJSON();
    380 				yield Zotero.Sync.Data.Local.saveCacheObjects(type, libraryID, [json]);
    381 				yield obj.eraseTx();
    382 				let versions = yield Zotero.Sync.Data.Local.getCacheObjectVersions(
    383 					type, libraryID, key
    384 				);
    385 				assert.lengthOf(versions, 0);
    386 			}
    387 		})
    388 	})
    389 	
    390 	describe("#updateVersion()", function() {
    391 		it("should update the object version", function* () {
    392 			for (let type of types) {
    393 				let obj = yield createDataObject(type);
    394 				assert.equal(obj.version, 0);
    395 				
    396 				yield obj.updateVersion(1234);
    397 				assert.equal(obj.version, 1234);
    398 				assert.isFalse(obj.hasChanged());
    399 				
    400 				obj.synced = true;
    401 				assert.ok(obj.hasChanged());
    402 				yield obj.updateVersion(1235);
    403 				assert.equal(obj.version, 1235);
    404 				assert.ok(obj.hasChanged());
    405 				
    406 				yield obj.eraseTx();
    407 			}
    408 		})
    409 	})
    410 	
    411 	describe("#updateSynced()", function() {
    412 		it("should update the object sync status", function* () {
    413 			for (let type of types) {
    414 				let obj = yield createDataObject(type);
    415 				assert.isFalse(obj.synced);
    416 				
    417 				yield obj.updateSynced(false);
    418 				assert.isFalse(obj.synced);
    419 				assert.isFalse(obj.hasChanged());
    420 				
    421 				yield obj.updateSynced(true);
    422 				assert.ok(obj.synced);
    423 				assert.isFalse(obj.hasChanged());
    424 				
    425 				obj.version = 1234;
    426 				assert.ok(obj.hasChanged());
    427 				yield obj.updateSynced(false);
    428 				assert.isFalse(obj.synced);
    429 				assert.ok(obj.hasChanged());
    430 				
    431 				yield obj.eraseTx();
    432 			}
    433 		})
    434 		
    435 		it("should clear changed status", function* () {
    436 			var item = createUnsavedDataObject('item');
    437 			item.synced = true;
    438 			yield item.saveTx();
    439 			
    440 			// Only synced changed
    441 			item.synced = false;
    442 			assert.isTrue(item.hasChanged());
    443 			assert.isTrue(item._changed.primaryData.synced);
    444 			yield item.updateSynced(true);
    445 			assert.isFalse(item.hasChanged());
    446 			// Should clear primary data change object
    447 			assert.isUndefined(item._changed.primaryData);
    448 			
    449 			// Another primary field also changed
    450 			item.setField('dateModified', '2017-02-27 12:34:56');
    451 			item.synced = false;
    452 			assert.isTrue(item.hasChanged());
    453 			assert.isTrue(item._changed.primaryData.synced);
    454 			yield item.updateSynced(true);
    455 			assert.isTrue(item.hasChanged());
    456 			// Should clear only 'synced' change status
    457 			assert.isUndefined(item._changed.primaryData.synced);
    458 		});
    459 	})
    460 	
    461 	describe("Relations", function () {
    462 		var types = ['collection', 'item'];
    463 		
    464 		function makeObjectURI(objectType) {
    465 			var objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType);
    466 			return 'http://zotero.org/groups/1/' + objectTypePlural + '/'
    467 				+ Zotero.Utilities.generateObjectKey();
    468 		}
    469 		
    470 		describe("#addRelation()", function () {
    471 			it("should add a relation to an object", function* () {
    472 				for (let type of types) {
    473 					let predicate = 'owl:sameAs';
    474 					let object = makeObjectURI(type);
    475 					let obj = createUnsavedDataObject(type);
    476 					obj.addRelation(predicate, object);
    477 					yield obj.saveTx();
    478 					var relations = obj.getRelations();
    479 					assert.property(relations, predicate);
    480 					assert.include(relations[predicate], object);
    481 				}
    482 			})
    483 		})
    484 		
    485 		describe("#removeRelation()", function () {
    486 			it("should remove a relation from an object", function* () {
    487 				for (let type of types) {
    488 					let predicate = 'owl:sameAs';
    489 					let object = makeObjectURI(type);
    490 					let obj = createUnsavedDataObject(type);
    491 					obj.addRelation(predicate, object);
    492 					yield obj.saveTx();
    493 					
    494 					obj.removeRelation(predicate, object);
    495 					yield obj.saveTx();
    496 					
    497 					assert.lengthOf(Object.keys(obj.getRelations()), 0);
    498 				}
    499 			})
    500 		})
    501 		
    502 		describe("#hasRelation()", function () {
    503 			it("should return true if an object has a given relation", function* () {
    504 				for (let type of types) {
    505 					let predicate = 'owl:sameAs';
    506 					let object = makeObjectURI(type);
    507 					let obj = createUnsavedDataObject(type);
    508 					obj.addRelation(predicate, object);
    509 					yield obj.saveTx();
    510 					assert.ok(obj.hasRelation(predicate, object));
    511 				}
    512 			})
    513 		})
    514 		
    515 		describe("#setRelations()", function () {
    516 			it("shouldn't allow invalid 'relations' predicates", function* () {
    517 				var item = new Zotero.Item("book");
    518 				assert.throws(() => {
    519 					item.setRelations({
    520 						"0": ["http://example.com/foo"]
    521 					});
    522 				});
    523 			});
    524 		});
    525 		
    526 		describe("#_getLinkedObject()", function () {
    527 			it("should return a linked object in another library", function* () {
    528 				var group = yield getGroup();
    529 				var item1 = yield createDataObject('item');
    530 				var item2 = yield createDataObject('item', { libraryID: group.libraryID });
    531 				var item2URI = Zotero.URI.getItemURI(item2);
    532 				
    533 				yield item2.addLinkedItem(item1);
    534 				var linkedItem = yield item1.getLinkedItem(item2.libraryID);
    535 				assert.equal(linkedItem.id, item2.id);
    536 			})
    537 			
    538 			it("shouldn't return reverse linked objects by default", function* () {
    539 				var group = yield getGroup();
    540 				var item1 = yield createDataObject('item');
    541 				var item1URI = Zotero.URI.getItemURI(item1);
    542 				var item2 = yield createDataObject('item', { libraryID: group.libraryID });
    543 				
    544 				yield item2.addLinkedItem(item1);
    545 				var linkedItem = yield item2.getLinkedItem(item1.libraryID);
    546 				assert.isFalse(linkedItem);
    547 			})
    548 			
    549 			it("should return reverse linked objects with bidirectional flag", function* () {
    550 				var group = yield getGroup();
    551 				var item1 = yield createDataObject('item');
    552 				var item1URI = Zotero.URI.getItemURI(item1);
    553 				var item2 = yield createDataObject('item', { libraryID: group.libraryID });
    554 				
    555 				yield item2.addLinkedItem(item1);
    556 				var linkedItem = yield item2.getLinkedItem(item1.libraryID, true);
    557 				assert.equal(linkedItem.id, item1.id);
    558 			})
    559 		})
    560 		
    561 		describe("#_addLinkedObject()", function () {
    562 			it("should add an owl:sameAs relation", function* () {
    563 				var group = yield getGroup();
    564 				var item1 = yield createDataObject('item');
    565 				var dateModified = item1.getField('dateModified');
    566 				var item2 = yield createDataObject('item', { libraryID: group.libraryID });
    567 				var item2URI = Zotero.URI.getItemURI(item2);
    568 				
    569 				yield item2.addLinkedItem(item1);
    570 				var preds = item1.getRelationsByPredicate(Zotero.Relations.linkedObjectPredicate);
    571 				assert.include(preds, item2URI);
    572 				
    573 				// Make sure Date Modified hasn't changed
    574 				assert.equal(item1.getField('dateModified'), dateModified);
    575 			})
    576 		})
    577 	})
    578 })