toolkit/components/places/tests/unit/test_async_transactions.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:32efebe2092d
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 const bmsvc = PlacesUtils.bookmarks;
8 const tagssvc = PlacesUtils.tagging;
9 const annosvc = PlacesUtils.annotations;
10 const PT = PlacesTransactions;
11
12 // Create and add bookmarks observer.
13 let observer = {
14 __proto__: NavBookmarkObserver.prototype,
15
16 tagRelatedGUIDs: new Set(),
17
18 reset: function () {
19 this.itemsAdded = new Map();
20 this.itemsRemoved = new Map();
21 this.itemsChanged = new Map();
22 this.itemsMoved = new Map();
23 this.beginUpdateBatch = false;
24 this.endUpdateBatch = false;
25 },
26
27 onBeginUpdateBatch: function () {
28 this.beginUpdateBatch = true;
29 },
30
31 onEndUpdateBatch: function () {
32 this.endUpdateBatch = true;
33 },
34
35 onItemAdded:
36 function (aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
37 aGUID, aParentGUID) {
38 // Ignore tag items.
39 if (aParentId == PlacesUtils.tagsFolderId ||
40 (aParentId != PlacesUtils.placesRootId &&
41 bmsvc.getFolderIdForItem(aParentId) == PlacesUtils.tagsFolderId)) {
42 this.tagRelatedGUIDs.add(aGUID);
43 return;
44 }
45
46 this.itemsAdded.set(aGUID, { itemId: aItemId
47 , parentGUID: aParentGUID
48 , index: aIndex
49 , itemType: aItemType
50 , title: aTitle
51 , uri: aURI });
52 },
53
54 onItemRemoved:
55 function (aItemId, aParentId, aIndex, aItemType, aURI, aGUID, aParentGUID) {
56 if (this.tagRelatedGUIDs.has(aGUID))
57 return;
58
59 this.itemsRemoved.set(aGUID, { parentGUID: aParentGUID
60 , index: aIndex
61 , itemType: aItemType });
62 },
63
64 onItemChanged:
65 function (aItemId, aProperty, aIsAnnoProperty, aNewValue, aLastModified,
66 aItemType, aParentId, aGUID, aParentGUID) {
67 if (this.tagRelatedGUIDs.has(aGUID))
68 return;
69
70 let changesForGUID = this.itemsChanged.get(aGUID);
71 if (changesForGUID === undefined) {
72 changesForGUID = new Map();
73 this.itemsChanged.set(aGUID, changesForGUID);
74 }
75
76 let newValue = aNewValue;
77 if (aIsAnnoProperty) {
78 if (annosvc.itemHasAnnotation(aItemId, aProperty))
79 newValue = annosvc.getItemAnnotation(aItemId, aProperty);
80 else
81 newValue = null;
82 }
83 let change = { isAnnoProperty: aIsAnnoProperty
84 , newValue: newValue
85 , lastModified: aLastModified
86 , itemType: aItemType };
87 changesForGUID.set(aProperty, change);
88 },
89
90 onItemVisited: () => {},
91
92 onItemMoved:
93 function (aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex, aItemType,
94 aGUID, aOldParentGUID, aNewParentGUID) {
95 this.itemsMoved.set(aGUID, { oldParentGUID: aOldParentGUID
96 , oldIndex: aOldIndex
97 , newParentGUID: aNewParentGUID
98 , newIndex: aNewIndex
99 , itemType: aItemType });
100 }
101 };
102 observer.reset();
103
104 // index at which items should begin
105 let bmStartIndex = 0;
106
107 // get bookmarks root id
108 let root = PlacesUtils.bookmarksMenuFolderId;
109
110 function run_test() {
111 bmsvc.addObserver(observer, false);
112 do_register_cleanup(function () {
113 bmsvc.removeObserver(observer);
114 });
115
116 run_next_test();
117 }
118
119 function sanityCheckTransactionHistory() {
120 do_check_true(PT.undoPosition <= PT.length);
121
122 let check_entry_throws = f => {
123 try {
124 f();
125 do_throw("PT.entry should throw for invalid input");
126 } catch(ex) {}
127 };
128 check_entry_throws( () => PT.entry(-1) );
129 check_entry_throws( () => PT.entry({}) );
130 check_entry_throws( () => PT.entry(PT.length) );
131
132 if (PT.undoPosition < PT.length)
133 do_check_eq(PT.topUndoEntry, PT.entry(PT.undoPosition));
134 else
135 do_check_null(PT.topUndoEntry);
136 if (PT.undoPosition > 0)
137 do_check_eq(PT.topRedoEntry, PT.entry(PT.undoPosition - 1));
138 else
139 do_check_null(PT.topRedoEntry);
140 }
141
142 function getTransactionsHistoryState() {
143 let history = [];
144 for (let i = 0; i < PT.length; i++) {
145 history.push(PT.entry(i));
146 }
147 return [history, PT.undoPosition];
148 }
149
150 function ensureUndoState(aExpectedEntries = [], aExpectedUndoPosition = 0) {
151 // ensureUndoState is called in various places during this test, so it's
152 // a good places to sanity-check the transaction-history APIs in all
153 // cases.
154 sanityCheckTransactionHistory();
155
156 let [actualEntries, actualUndoPosition] = getTransactionsHistoryState();
157 do_check_eq(actualEntries.length, aExpectedEntries.length);
158 do_check_eq(actualUndoPosition, aExpectedUndoPosition);
159
160 function checkEqualEntries(aExpectedEntry, aActualEntry) {
161 do_check_eq(aExpectedEntry.length, aActualEntry.length);
162 aExpectedEntry.forEach( (t, i) => do_check_eq(t, aActualEntry[i]) );
163 }
164 aExpectedEntries.forEach( (e, i) => checkEqualEntries(e, actualEntries[i]) );
165 }
166
167 function ensureItemsAdded(...items) {
168 do_check_eq(observer.itemsAdded.size, items.length);
169 for (let item of items) {
170 do_check_true(observer.itemsAdded.has(item.GUID));
171 let info = observer.itemsAdded.get(item.GUID);
172 do_check_eq(info.parentGUID, item.parentGUID);
173 if ("title" in item)
174 do_check_eq(info.title, item.title);
175 if ("index" in item)
176 do_check_eq(info.index, item.index);
177 if ("itemType" in item)
178 do_check_eq(info.itemType, item.itemType);
179 }
180 }
181
182 function ensureItemsRemoved(...items) {
183 do_check_eq(observer.itemsRemoved.size, items.length);
184 for (let item of items) {
185 do_check_true(observer.itemsRemoved.has(item.GUID));
186 let info = observer.itemsRemoved.get(item.GUID);
187 do_check_eq(info.parentGUID, item.parentGUID);
188 if ("index" in item)
189 do_check_eq(info.index, item.index);
190 }
191 }
192
193 function ensureItemsChanged(...items) {
194 for (let item of items) {
195 do_check_true(observer.itemsChanged.has(item.GUID));
196 let changes = observer.itemsChanged.get(item.GUID);
197 do_check_true(changes.has(item.property));
198 let info = changes.get(item.property);
199 do_check_eq(info.isAnnoProperty, Boolean(item.isAnnoProperty));
200 do_check_eq(info.newValue, item.newValue);
201 if ("uri" in item)
202 do_check_true(item.uri.equals(info.uri));
203 }
204 }
205
206 function ensureAnnotationsSet(aGUID, aAnnos) {
207 do_check_true(observer.itemsChanged.has(aGUID));
208 let changes = observer.itemsChanged.get(aGUID);
209 for (let anno of aAnnos) {
210 do_check_true(changes.has(anno.name));
211 let changeInfo = changes.get(anno.name);
212 do_check_true(changeInfo.isAnnoProperty);
213 do_check_eq(changeInfo.newValue, anno.value);
214 }
215 }
216
217 function ensureItemsMoved(...items) {
218 do_check_true(observer.itemsMoved.size, items.length);
219 for (let item of items) {
220 do_check_true(observer.itemsMoved.has(item.GUID));
221 let info = observer.itemsMoved.get(item.GUID);
222 do_check_eq(info.oldParentGUID, item.oldParentGUID);
223 do_check_eq(info.oldIndex, item.oldIndex);
224 do_check_eq(info.newParentGUID, item.newParentGUID);
225 do_check_eq(info.newIndex, item.newIndex);
226 }
227 }
228
229 function ensureTimestampsUpdated(aGUID, aCheckDateAdded = false) {
230 do_check_true(observer.itemsChanged.has(aGUID));
231 let changes = observer.itemsChanged.get(aGUID);
232 if (aCheckDateAdded)
233 do_check_true(changes.has("dateAdded"))
234 do_check_true(changes.has("lastModified"));
235 }
236
237 function ensureTagsForURI(aURI, aTags) {
238 let tagsSet = tagssvc.getTagsForURI(aURI);
239 do_check_eq(tagsSet.length, aTags.length);
240 do_check_true(aTags.every( t => tagsSet.indexOf(t) != -1 ));
241 }
242
243 function* createTestFolderInfo(aTitle = "Test Folder") {
244 return { parentGUID: yield PlacesUtils.promiseItemGUID(root)
245 , title: "Test Folder" };
246 }
247
248 add_task(function* test_invalid_transact_calls() {
249 try {
250 PT.transact({ execute: () => {}, undo: () => {}, redo: () => {}});
251 do_throw("transact shouldn't accept 'external' transactions");
252 PT.transact(null);
253 do_throw("transact should throw for invalid arguments");
254 }
255 catch(ex) { }
256 });
257
258 add_task(function* test_recycled_transactions() {
259 function ensureTransactThrowsFor(aTransaction) {
260 let [txns, undoPosition] = getTransactionsHistoryState();
261 try {
262 yield PT.transact(aTransaction);
263 do_throw("Shouldn't be able to use the same transaction twice");
264 }
265 catch(ex) { }
266 ensureUndoState(txns, undoPosition);
267 }
268
269 let txn_a = PT.NewFolder(yield createTestFolderInfo());
270 ensureTransactThrowsFor(txn_a);
271 yield PT.transact(txn_a);
272 ensureUndoState([[txn_a]], 0);
273
274 yield PT.undo();
275 ensureUndoState([[txn_a]], 1);
276 ensureTransactThrowsFor(txn_a);
277
278 yield PT.clearTransactionsHistory();
279 ensureUndoState();
280 ensureTransactThrowsFor(txn_a);
281
282 let txn_b = PT.NewFolder(yield createTestFolderInfo());
283 yield PT.transact(function* () {
284 try {
285 yield txn_a;
286 do_throw("Shouldn't be able to use the same transaction twice");
287 }
288 catch(ex) { }
289 ensureUndoState();
290 yield txn_b;
291 });
292 ensureUndoState([[txn_b]], 0);
293
294 yield PT.undo();
295 ensureUndoState([[txn_b]], 1);
296 ensureTransactThrowsFor(txn_a);
297 ensureTransactThrowsFor(txn_b);
298
299 yield PT.clearTransactionsHistory();
300 ensureUndoState();
301 observer.reset();
302 });
303
304 add_task(function* test_nested_batches() {
305 let txn_a = PT.NewFolder(yield createTestFolderInfo()),
306 txn_b = PT.NewFolder(yield createTestFolderInfo());
307 yield PT.transact(function* () {
308 yield txn_a;
309 yield (function*() {
310 yield txn_b;
311 }());
312 });
313 ensureUndoState([[txn_b, txn_a]], 0);
314
315 yield PT.undo();
316 ensureUndoState([[txn_b, txn_a]], 1);
317
318 yield PT.clearTransactionsHistory();
319 ensureUndoState();
320 observer.reset();
321 });
322
323 add_task(function* test_new_folder_with_annotation() {
324 const ANNO = { name: "TestAnno", value: "TestValue" };
325 let folder_info = yield createTestFolderInfo();
326 folder_info.index = bmStartIndex;
327 folder_info.annotations = [ANNO];
328 ensureUndoState();
329 let txn = PT.NewFolder(folder_info);
330 folder_info.GUID = yield PT.transact(txn);
331 let ensureDo = function* (aRedo = false) {
332 ensureUndoState([[txn]], 0);
333 yield ensureItemsAdded(folder_info);
334 ensureAnnotationsSet(folder_info.GUID, [ANNO]);
335 if (aRedo)
336 ensureTimestampsUpdated(folder_info.GUID, true);
337 observer.reset();
338 };
339
340 let ensureUndo = () => {
341 ensureUndoState([[txn]], 1);
342 ensureItemsRemoved({ GUID: folder_info.GUID
343 , parentGUID: folder_info.parentGUID
344 , index: bmStartIndex });
345 observer.reset();
346 };
347
348 yield ensureDo();
349 yield PT.undo();
350 yield ensureUndo();
351 yield PT.redo();
352 yield ensureDo(true);
353 yield PT.undo();
354 ensureUndo();
355 yield PT.clearTransactionsHistory();
356 ensureUndoState();
357 });
358
359 add_task(function* test_new_bookmark() {
360 let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root)
361 , uri: NetUtil.newURI("http://test_create_item.com")
362 , index: bmStartIndex
363 , title: "Test creating an item" };
364
365 ensureUndoState();
366 let txn = PT.NewBookmark(bm_info);
367 bm_info.GUID = yield PT.transact(txn);
368
369 let ensureDo = function* (aRedo = false) {
370 ensureUndoState([[txn]], 0);
371 yield ensureItemsAdded(bm_info);
372 if (aRedo)
373 ensureTimestampsUpdated(bm_info.GUID, true);
374 observer.reset();
375 };
376 let ensureUndo = () => {
377 ensureUndoState([[txn]], 1);
378 ensureItemsRemoved({ GUID: bm_info.GUID
379 , parentGUID: bm_info.parentGUID
380 , index: bmStartIndex });
381 observer.reset();
382 };
383
384 yield ensureDo();
385 yield PT.undo();
386 ensureUndo();
387 yield PT.redo(true);
388 yield ensureDo();
389 yield PT.undo();
390 ensureUndo();
391
392 yield PT.clearTransactionsHistory();
393 ensureUndoState();
394 });
395
396 add_task(function* test_merge_create_folder_and_item() {
397 let folder_info = yield createTestFolderInfo();
398 let bm_info = { uri: NetUtil.newURI("http://test_create_item_to_folder.com")
399 , title: "Test Bookmark"
400 , index: bmStartIndex };
401
402 let { folderTxn, bkmTxn } = yield PT.transact( function* () {
403 let folderTxn = PT.NewFolder(folder_info);
404 folder_info.GUID = bm_info.parentGUID = yield folderTxn;
405 let bkmTxn = PT.NewBookmark(bm_info);
406 bm_info.GUID = yield bkmTxn;;
407 return { folderTxn: folderTxn, bkmTxn: bkmTxn};
408 });
409
410 let ensureDo = function* () {
411 ensureUndoState([[bkmTxn, folderTxn]], 0);
412 yield ensureItemsAdded(folder_info, bm_info);
413 observer.reset();
414 };
415
416 let ensureUndo = () => {
417 ensureUndoState([[bkmTxn, folderTxn]], 1);
418 ensureItemsRemoved(folder_info, bm_info);
419 observer.reset();
420 };
421
422 yield ensureDo();
423 yield PT.undo();
424 ensureUndo();
425 yield PT.redo();
426 yield ensureDo();
427 yield PT.undo();
428 ensureUndo();
429
430 yield PT.clearTransactionsHistory();
431 ensureUndoState();
432 });
433
434 add_task(function* test_move_items_to_folder() {
435 let folder_a_info = yield createTestFolderInfo("Folder A");
436 let bkm_a_info = { uri: NetUtil.newURI("http://test_move_items.com")
437 , title: "Bookmark A" };
438 let bkm_b_info = { uri: NetUtil.newURI("http://test_move_items.com")
439 , title: "Bookmark B" };
440
441 // Test moving items within the same folder.
442 let [folder_a_txn, bkm_a_txn, bkm_b_txn] = yield PT.transact(function* () {
443 let folder_a_txn = PT.NewFolder(folder_a_info);
444
445 folder_a_info.GUID =
446 bkm_a_info.parentGUID = bkm_b_info.parentGUID = yield folder_a_txn;
447 let bkm_a_txn = PT.NewBookmark(bkm_a_info);
448 bkm_a_info.GUID = yield bkm_a_txn;
449 let bkm_b_txn = PT.NewBookmark(bkm_b_info);
450 bkm_b_info.GUID = yield bkm_b_txn;
451 return [folder_a_txn, bkm_a_txn, bkm_b_txn];
452 });
453
454 ensureUndoState([[bkm_b_txn, bkm_a_txn, folder_a_txn]], 0);
455
456 let moveTxn = PT.MoveItem({ GUID: bkm_a_info.GUID
457 , newParentGUID: folder_a_info.GUID });
458 yield PT.transact(moveTxn);
459
460 let ensureDo = () => {
461 ensureUndoState([[moveTxn], [bkm_b_txn, bkm_a_txn, folder_a_txn]], 0);
462 ensureItemsMoved({ GUID: bkm_a_info.GUID
463 , oldParentGUID: folder_a_info.GUID
464 , newParentGUID: folder_a_info.GUID
465 , oldIndex: 0
466 , newIndex: 1 });
467 observer.reset();
468 };
469 let ensureUndo = () => {
470 ensureUndoState([[moveTxn], [bkm_b_txn, bkm_a_txn, folder_a_txn]], 1);
471 ensureItemsMoved({ GUID: bkm_a_info.GUID
472 , oldParentGUID: folder_a_info.GUID
473 , newParentGUID: folder_a_info.GUID
474 , oldIndex: 1
475 , newIndex: 0 });
476 observer.reset();
477 };
478
479 ensureDo();
480 yield PT.undo();
481 ensureUndo();
482 yield PT.redo();
483 ensureDo();
484 yield PT.undo();
485 ensureUndo();
486
487 yield PT.clearTransactionsHistory(false, true);
488 ensureUndoState([[bkm_b_txn, bkm_a_txn, folder_a_txn]], 0);
489
490 // Test moving items between folders.
491 let folder_b_info = yield createTestFolderInfo("Folder B");
492 let folder_b_txn = PT.NewFolder(folder_b_info);
493 folder_b_info.GUID = yield PT.transact(folder_b_txn);
494 ensureUndoState([ [folder_b_txn]
495 , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 0);
496
497 moveTxn = PT.MoveItem({ GUID: bkm_a_info.GUID
498 , newParentGUID: folder_b_info.GUID
499 , newIndex: bmsvc.DEFAULT_INDEX });
500 yield PT.transact(moveTxn);
501
502 ensureDo = () => {
503 ensureUndoState([ [moveTxn]
504 , [folder_b_txn]
505 , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 0);
506 ensureItemsMoved({ GUID: bkm_a_info.GUID
507 , oldParentGUID: folder_a_info.GUID
508 , newParentGUID: folder_b_info.GUID
509 , oldIndex: 0
510 , newIndex: 0 });
511 observer.reset();
512 };
513 let ensureUndo = () => {
514 ensureUndoState([ [moveTxn]
515 , [folder_b_txn]
516 , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 1);
517 ensureItemsMoved({ GUID: bkm_a_info.GUID
518 , oldParentGUID: folder_b_info.GUID
519 , newParentGUID: folder_a_info.GUID
520 , oldIndex: 0
521 , newIndex: 0 });
522 observer.reset();
523 };
524
525 ensureDo();
526 yield PT.undo();
527 ensureUndo();
528 yield PT.redo();
529 ensureDo();
530 yield PT.undo();
531 ensureUndo();
532
533 // Clean up
534 yield PT.undo(); // folder_b_txn
535 yield PT.undo(); // folder_a_txn + the bookmarks;
536 do_check_eq(observer.itemsRemoved.size, 4);
537 ensureUndoState([ [moveTxn]
538 , [folder_b_txn]
539 , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 3);
540 yield PT.clearTransactionsHistory();
541 ensureUndoState();
542 });
543
544 add_task(function* test_remove_folder() {
545 let folder_level_1_info = yield createTestFolderInfo("Folder Level 1");
546 let folder_level_2_info = { title: "Folder Level 2" };
547 let [folder_level_1_txn,
548 folder_level_2_txn] = yield PT.transact(function* () {
549 let folder_level_1_txn = PT.NewFolder(folder_level_1_info);
550 folder_level_1_info.GUID = yield folder_level_1_txn;
551 folder_level_2_info.parentGUID = folder_level_1_info.GUID;
552 let folder_level_2_txn = PT.NewFolder(folder_level_2_info);
553 folder_level_2_info.GUID = yield folder_level_2_txn;
554 return [folder_level_1_txn, folder_level_2_txn];
555 });
556
557 ensureUndoState([[folder_level_2_txn, folder_level_1_txn]]);
558 yield ensureItemsAdded(folder_level_1_info, folder_level_2_info);
559 observer.reset();
560
561 let remove_folder_2_txn = PT.RemoveItem(folder_level_2_info);
562 yield PT.transact(remove_folder_2_txn);
563
564 ensureUndoState([ [remove_folder_2_txn]
565 , [folder_level_2_txn, folder_level_1_txn] ]);
566 yield ensureItemsRemoved(folder_level_2_info);
567
568 // Undo RemoveItem "Folder Level 2"
569 yield PT.undo();
570 ensureUndoState([ [remove_folder_2_txn]
571 , [folder_level_2_txn, folder_level_1_txn] ], 1);
572 yield ensureItemsAdded(folder_level_2_info);
573 ensureTimestampsUpdated(folder_level_2_info.GUID, true);
574 observer.reset();
575
576 // Redo RemoveItem "Folder Level 2"
577 yield PT.redo();
578 ensureUndoState([ [remove_folder_2_txn]
579 , [folder_level_2_txn, folder_level_1_txn] ]);
580 yield ensureItemsRemoved(folder_level_2_info);
581 observer.reset();
582
583 // Undo it again
584 yield PT.undo();
585 ensureUndoState([ [remove_folder_2_txn]
586 , [folder_level_2_txn, folder_level_1_txn] ], 1);
587 yield ensureItemsAdded(folder_level_2_info);
588 ensureTimestampsUpdated(folder_level_2_info.GUID, true);
589 observer.reset();
590
591 // Undo the creation of both folders
592 yield PT.undo();
593 ensureUndoState([ [remove_folder_2_txn]
594 , [folder_level_2_txn, folder_level_1_txn] ], 2);
595 yield ensureItemsRemoved(folder_level_2_info, folder_level_1_info);
596 observer.reset();
597
598 // Redo the creation of both folders
599 yield PT.redo();
600 ensureUndoState([ [remove_folder_2_txn]
601 , [folder_level_2_txn, folder_level_1_txn] ], 1);
602 yield ensureItemsAdded(folder_level_1_info, folder_level_2_info);
603 ensureTimestampsUpdated(folder_level_1_info.GUID, true);
604 ensureTimestampsUpdated(folder_level_2_info.GUID, true);
605 observer.reset();
606
607 // Redo RemoveItem "Folder Level 2"
608 yield PT.redo();
609 ensureUndoState([ [remove_folder_2_txn]
610 , [folder_level_2_txn, folder_level_1_txn] ]);
611 yield ensureItemsRemoved(folder_level_2_info);
612 observer.reset();
613
614 // Undo everything one last time
615 yield PT.undo();
616 ensureUndoState([ [remove_folder_2_txn]
617 , [folder_level_2_txn, folder_level_1_txn] ], 1);
618 yield ensureItemsAdded(folder_level_2_info);
619 observer.reset();
620
621 yield PT.undo();
622 ensureUndoState([ [remove_folder_2_txn]
623 , [folder_level_2_txn, folder_level_1_txn] ], 2);
624 yield ensureItemsRemoved(folder_level_2_info, folder_level_2_info);
625 observer.reset();
626
627 yield PT.clearTransactionsHistory();
628 ensureUndoState();
629 });
630
631 add_task(function* test_add_and_remove_bookmarks_with_additional_info() {
632 const testURI = NetUtil.newURI("http://add.remove.tag")
633 , TAG_1 = "TestTag1", TAG_2 = "TestTag2"
634 , KEYWORD = "test_keyword"
635 , POST_DATA = "post_data"
636 , ANNO = { name: "TestAnno", value: "TestAnnoValue" };
637
638 let folder_info = yield createTestFolderInfo();
639 folder_info.GUID = yield PT.transact(PT.NewFolder(folder_info));
640 let ensureTags = ensureTagsForURI.bind(null, testURI);
641
642 // Check that the NewBookmark transaction preserves tags.
643 observer.reset();
644 let b1_info = { parentGUID: folder_info.GUID, uri: testURI, tags: [TAG_1] };
645 b1_info.GUID = yield PT.transact(PT.NewBookmark(b1_info));
646 ensureTags([TAG_1]);
647 yield PT.undo();
648 ensureTags([]);
649
650 observer.reset();
651 yield PT.redo();
652 ensureTimestampsUpdated(b1_info.GUID, true);
653 ensureTags([TAG_1]);
654
655 // Check if the RemoveItem transaction removes and restores tags of children
656 // correctly.
657 yield PT.transact(PT.RemoveItem(folder_info.GUID));
658 ensureTags([]);
659
660 observer.reset();
661 yield PT.undo();
662 ensureTimestampsUpdated(b1_info.GUID, true);
663 ensureTags([TAG_1]);
664
665 yield PT.redo();
666 ensureTags([]);
667
668 observer.reset();
669 yield PT.undo();
670 ensureTimestampsUpdated(b1_info.GUID, true);
671 ensureTags([TAG_1]);
672
673 // * Check that no-op tagging (the uri is already tagged with TAG_1) is
674 // also a no-op on undo.
675 // * Test the "keyword" property of the NewBookmark transaction.
676 observer.reset();
677 let b2_info = { parentGUID: folder_info.GUID
678 , uri: testURI, tags: [TAG_1, TAG_2]
679 , keyword: KEYWORD
680 , postData: POST_DATA
681 , annotations: [ANNO] };
682 b2_info.GUID = yield PT.transact(PT.NewBookmark(b2_info));
683 let b2_post_creation_changes = [
684 { GUID: b2_info.GUID
685 , isAnnoProperty: true
686 , property: ANNO.name
687 , newValue: ANNO.value },
688 { GUID: b2_info.GUID
689 , property: "keyword"
690 , newValue: KEYWORD },
691 { GUID: b2_info.GUID
692 , isAnnoProperty: true
693 , property: PlacesUtils.POST_DATA_ANNO
694 , newValue: POST_DATA } ];
695 ensureItemsChanged(...b2_post_creation_changes);
696 ensureTags([TAG_1, TAG_2]);
697
698 observer.reset();
699 yield PT.undo();
700 yield ensureItemsRemoved(b2_info);
701 ensureTags([TAG_1]);
702
703 // Check if RemoveItem correctly restores keywords, tags and annotations.
704 observer.reset();
705 yield PT.redo();
706 ensureItemsChanged(...b2_post_creation_changes);
707 ensureTags([TAG_1, TAG_2]);
708
709 // Test RemoveItem for multiple items.
710 observer.reset();
711 yield PT.transact(PT.RemoveItem(b1_info.GUID));
712 yield PT.transact(PT.RemoveItem(b2_info.GUID));
713 yield PT.transact(PT.RemoveItem(folder_info.GUID));
714 yield ensureItemsRemoved(b1_info, b2_info, folder_info);
715 ensureTags([]);
716
717 observer.reset();
718 yield PT.undo();
719 yield ensureItemsAdded(folder_info);
720 ensureTags([]);
721
722 observer.reset();
723 yield PT.undo();
724 ensureItemsChanged(...b2_post_creation_changes);
725 ensureTags([TAG_1, TAG_2]);
726
727 observer.reset();
728 yield PT.undo();
729 yield ensureItemsAdded(b1_info);
730 ensureTags([TAG_1, TAG_2]);
731
732 // The redo calls below cleanup everything we did.
733 observer.reset();
734 yield PT.redo();
735 yield ensureItemsRemoved(b1_info);
736 ensureTags([TAG_1, TAG_2]);
737
738 observer.reset();
739 yield PT.redo();
740 yield ensureItemsRemoved(b2_info);
741 ensureTags([]);
742
743 observer.reset();
744 yield PT.redo();
745 yield ensureItemsRemoved(folder_info);
746 ensureTags([]);
747
748 yield PT.clearTransactionsHistory();
749 ensureUndoState();
750 });
751
752 add_task(function* test_creating_and_removing_a_separator() {
753 let folder_info = yield createTestFolderInfo();
754 let separator_info = {};
755 let undoEntries = [];
756
757 observer.reset();
758 let create_txns = yield PT.transact(function* () {
759 let folder_txn = PT.NewFolder(folder_info);
760 folder_info.GUID = separator_info.parentGUID = yield folder_txn;
761 let separator_txn = PT.NewSeparator(separator_info);
762 separator_info.GUID = yield separator_txn;
763 return [separator_txn, folder_txn];
764 });
765 undoEntries.unshift(create_txns);
766 ensureUndoState(undoEntries, 0);
767 ensureItemsAdded(folder_info, separator_info);
768
769 observer.reset();
770 yield PT.undo();
771 ensureUndoState(undoEntries, 1);
772 ensureItemsRemoved(folder_info, separator_info);
773
774 observer.reset();
775 yield PT.redo();
776 ensureUndoState(undoEntries, 0);
777 ensureItemsAdded(folder_info, separator_info);
778
779 observer.reset();
780 let remove_sep_txn = PT.RemoveItem(separator_info);
781 yield PT.transact(remove_sep_txn);
782 undoEntries.unshift([remove_sep_txn]);
783 ensureUndoState(undoEntries, 0);
784 ensureItemsRemoved(separator_info);
785
786 observer.reset();
787 yield PT.undo();
788 ensureUndoState(undoEntries, 1);
789 ensureItemsAdded(separator_info);
790
791 observer.reset();
792 yield PT.undo();
793 ensureUndoState(undoEntries, 2);
794 ensureItemsRemoved(folder_info, separator_info);
795
796 observer.reset();
797 yield PT.redo();
798 ensureUndoState(undoEntries, 1);
799 ensureItemsAdded(folder_info, separator_info);
800
801 // Clear redo entries and check that |redo| does nothing
802 observer.reset();
803 yield PT.clearTransactionsHistory(false, true);
804 undoEntries.shift();
805 ensureUndoState(undoEntries, 0);
806 yield PT.redo();
807 ensureItemsAdded();
808 ensureItemsRemoved();
809
810 // Cleanup
811 observer.reset();
812 yield PT.undo();
813 ensureUndoState(undoEntries, 1);
814 ensureItemsRemoved(folder_info, separator_info);
815 yield PT.clearTransactionsHistory();
816 ensureUndoState();
817 });
818
819 add_task(function* test_edit_title() {
820 let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root)
821 , uri: NetUtil.newURI("http://test_create_item.com")
822 , title: "Original Title" };
823
824 function ensureTitleChange(aCurrentTitle) {
825 ensureItemsChanged({ GUID: bm_info.GUID
826 , property: "title"
827 , newValue: aCurrentTitle});
828 }
829
830 bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
831
832 observer.reset();
833 yield PT.transact(PT.EditTitle({ GUID: bm_info.GUID, title: "New Title" }));
834 ensureTitleChange("New Title");
835
836 observer.reset();
837 yield PT.undo();
838 ensureTitleChange("Original Title");
839
840 observer.reset();
841 yield PT.redo();
842 ensureTitleChange("New Title");
843
844 // Cleanup
845 observer.reset();
846 yield PT.undo();
847 ensureTitleChange("Original Title");
848 yield PT.undo();
849 ensureItemsRemoved(bm_info);
850
851 yield PT.clearTransactionsHistory();
852 ensureUndoState();
853 });
854
855 add_task(function* test_edit_url() {
856 let oldURI = NetUtil.newURI("http://old.test_editing_item_uri.com/");
857 let newURI = NetUtil.newURI("http://new.test_editing_item_uri.com/");
858 let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root)
859 , uri: oldURI
860 , tags: ["TestTag"]};
861
862 function ensureURIAndTags(aPreChangeURI, aPostChangeURI, aOLdURITagsPreserved) {
863 ensureItemsChanged({ GUID: bm_info.GUID
864 , property: "uri"
865 , newValue: aPostChangeURI.spec });
866 ensureTagsForURI(aPostChangeURI, bm_info.tags);
867 ensureTagsForURI(aPreChangeURI, aOLdURITagsPreserved ? bm_info.tags : []);
868 }
869
870 bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
871 ensureTagsForURI(oldURI, bm_info.tags);
872
873 // When there's a single bookmark for the same url, tags should be moved.
874 observer.reset();
875 yield PT.transact(PT.EditURI({ GUID: bm_info.GUID, uri: newURI }));
876 ensureURIAndTags(oldURI, newURI, false);
877
878 observer.reset();
879 yield PT.undo();
880 ensureURIAndTags(newURI, oldURI, false);
881
882 observer.reset();
883 yield PT.redo();
884 ensureURIAndTags(oldURI, newURI, false);
885
886 observer.reset();
887 yield PT.undo();
888 ensureURIAndTags(newURI, oldURI, false);
889
890 // When there're multiple bookmarks for the same url, tags should be copied.
891 let bm2_info = Object.create(bm_info);
892 bm2_info.GUID = yield PT.transact(PT.NewBookmark(bm2_info));
893 let bm3_info = Object.create(bm_info);
894 bm3_info.uri = newURI;
895 bm3_info.GUID = yield PT.transact(PT.NewBookmark(bm3_info));
896
897 observer.reset();
898 yield PT.transact(PT.EditURI({ GUID: bm_info.GUID, uri: newURI }));
899 ensureURIAndTags(oldURI, newURI, true);
900
901 observer.reset();
902 yield PT.undo();
903 ensureURIAndTags(newURI, oldURI, true);
904
905 observer.reset();
906 yield PT.redo();
907 ensureURIAndTags(oldURI, newURI, true);
908
909 // Cleanup
910 observer.reset();
911 yield PT.undo();
912 ensureURIAndTags(newURI, oldURI, true);
913 yield PT.undo();
914 yield PT.undo();
915 yield PT.undo();
916 ensureItemsRemoved(bm3_info, bm2_info, bm_info);
917
918 yield PT.clearTransactionsHistory();
919 ensureUndoState();
920 });
921
922 add_task(function* test_edit_keyword() {
923 let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root)
924 , uri: NetUtil.newURI("http://test.edit.keyword") };
925 const KEYWORD = "test_keyword";
926 bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
927 function ensureKeywordChange(aCurrentKeyword = "") {
928 ensureItemsChanged({ GUID: bm_info.GUID
929 , property: "keyword"
930 , newValue: aCurrentKeyword });
931 }
932
933 bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
934
935 observer.reset();
936 yield PT.transact(PT.EditKeyword({ GUID: bm_info.GUID, keyword: KEYWORD }));
937 ensureKeywordChange(KEYWORD);
938
939 observer.reset();
940 yield PT.undo();
941 ensureKeywordChange();
942
943 observer.reset();
944 yield PT.redo();
945 ensureKeywordChange(KEYWORD);
946
947 // Cleanup
948 observer.reset();
949 yield PT.undo();
950 ensureKeywordChange();
951 yield PT.undo();
952 ensureItemsRemoved(bm_info);
953
954 yield PT.clearTransactionsHistory();
955 ensureUndoState();
956 });
957
958 add_task(function* test_tag_uri_unbookmarked_uri() {
959 let info = { uri: NetUtil.newURI("http://un.book.marked"), tags: ["MyTag"] };
960
961 function ensureDo() {
962 // A new bookmark should be created.
963 // (getMostRecentBookmarkForURI ignores tags)
964 do_check_neq(PlacesUtils.getMostRecentBookmarkForURI(info.uri), -1);
965 ensureTagsForURI(info.uri, info.tags);
966 }
967 function ensureUndo() {
968 do_check_eq(PlacesUtils.getMostRecentBookmarkForURI(info.uri), -1);
969 ensureTagsForURI(info.uri, []);
970 }
971
972 yield PT.transact(PT.TagURI(info));
973 ensureDo();
974 yield PT.undo();
975 ensureUndo();
976 yield PT.redo();
977 ensureDo();
978 yield PT.undo();
979 ensureUndo();
980 });
981
982 add_task(function* test_tag_uri_bookmarked_uri() {
983 let bm_info = { uri: NetUtil.newURI("http://bookmarked.uri")
984 , parentGUID: yield PlacesUtils.promiseItemGUID(root) };
985 bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
986
987 let tagging_info = { uri: bm_info.uri, tags: ["MyTag"] };
988 yield PT.transact(PT.TagURI(tagging_info));
989 ensureTagsForURI(tagging_info.uri, tagging_info.tags);
990
991 yield PT.undo();
992 ensureTagsForURI(tagging_info.uri, []);
993 yield PT.redo();
994 ensureTagsForURI(tagging_info.uri, tagging_info.tags);
995
996 // Cleanup
997 yield PT.undo();
998 ensureTagsForURI(tagging_info.uri, []);
999 observer.reset();
1000 yield PT.undo();
1001 ensureItemsRemoved(bm_info);
1002
1003 yield PT.clearTransactionsHistory();
1004 ensureUndoState();
1005 });
1006
1007 add_task(function* test_untag_uri() {
1008 let bm_info = { uri: NetUtil.newURI("http://test.untag.uri")
1009 , parentGUID: yield PlacesUtils.promiseItemGUID(root)
1010 , tags: ["T"]};
1011 bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
1012
1013 yield PT.transact(PT.UntagURI(bm_info));
1014 ensureTagsForURI(bm_info.uri, []);
1015 yield PT.undo();
1016 ensureTagsForURI(bm_info.uri, bm_info.tags);
1017 yield PT.redo();
1018 ensureTagsForURI(bm_info.uri, []);
1019 yield PT.undo();
1020 ensureTagsForURI(bm_info.uri, bm_info.tags);
1021
1022 // Also test just passing the uri (should remove all tags)
1023 yield PT.transact(PT.UntagURI(bm_info.uri));
1024 ensureTagsForURI(bm_info.uri, []);
1025 yield PT.undo();
1026 ensureTagsForURI(bm_info.uri, bm_info.tags);
1027 yield PT.redo();
1028 ensureTagsForURI(bm_info.uri, []);
1029 });
1030
1031 add_task(function* test_set_item_annotation() {
1032 let bm_info = { uri: NetUtil.newURI("http://test.item.annotation")
1033 , parentGUID: yield PlacesUtils.promiseItemGUID(root) };
1034 let anno_info = { name: "TestAnno", value: "TestValue" };
1035 function ensureAnnoState(aSet) {
1036 ensureAnnotationsSet(bm_info.GUID,
1037 [{ name: anno_info.name
1038 , value: aSet ? anno_info.value : null }]);
1039 }
1040
1041 bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
1042
1043 observer.reset();
1044 yield PT.transact(PT.SetItemAnnotation({ GUID: bm_info.GUID
1045 , annotationObject: anno_info }));
1046 ensureAnnoState(true);
1047
1048 observer.reset();
1049 yield PT.undo();
1050 ensureAnnoState(false);
1051
1052 observer.reset();
1053 yield PT.redo();
1054 ensureAnnoState(true);
1055
1056 // Test removing the annotation by not passing the |value| property.
1057 observer.reset();
1058 yield PT.transact(
1059 PT.SetItemAnnotation({ GUID: bm_info.GUID
1060 , annotationObject: { name: anno_info.name }}));
1061 ensureAnnoState(false);
1062
1063 observer.reset();
1064 yield PT.undo();
1065 ensureAnnoState(true);
1066
1067 observer.reset();
1068 yield PT.redo();
1069 ensureAnnoState(false);
1070 });
1071
1072 add_task(function* test_sort_folder_by_name() {
1073 let folder_info = yield createTestFolderInfo();
1074
1075 let uri = NetUtil.newURI("http://sort.by.name/");
1076 let preSep = [{ title: i, uri: uri } for (i of ["3","2","1"])];
1077 let sep = {};
1078 let postSep = [{ title: l, uri: uri } for (l of ["c","b","a"])];
1079 let originalOrder = [...preSep, sep, ...postSep];
1080 let sortedOrder = [...preSep.slice(0).reverse(),
1081 sep,
1082 ...postSep.slice(0).reverse()];
1083 yield PT.transact(function* () {
1084 folder_info.GUID = yield PT.NewFolder(folder_info);
1085 for (let info of originalOrder) {
1086 info.parentGUID = folder_info.GUID;
1087 info.GUID = yield info == sep ?
1088 PT.NewSeparator(info) : PT.NewBookmark(info);
1089 }
1090 });
1091
1092 let folderId = yield PlacesUtils.promiseItemId(folder_info.GUID);
1093 let folderContainer = PlacesUtils.getFolderContents(folderId).root;
1094 function ensureOrder(aOrder) {
1095 for (let i = 0; i < folderContainer.childCount; i++) {
1096 do_check_eq(folderContainer.getChild(i).bookmarkGuid, aOrder[i].GUID);
1097 }
1098 }
1099
1100 ensureOrder(originalOrder);
1101 yield PT.transact(PT.SortByName(folder_info.GUID));
1102 ensureOrder(sortedOrder);
1103 yield PT.undo();
1104 ensureOrder(originalOrder);
1105 yield PT.redo();
1106 ensureOrder(sortedOrder);
1107
1108 // Cleanup
1109 observer.reset();
1110 yield PT.undo();
1111 ensureOrder(originalOrder);
1112 yield PT.undo();
1113 ensureItemsRemoved(...originalOrder, folder_info);
1114 });
1115
1116 add_task(function* test_livemark_txns() {
1117 let livemark_info =
1118 { feedURI: NetUtil.newURI("http://test.feed.uri")
1119 , parentGUID: yield PlacesUtils.promiseItemGUID(root)
1120 , title: "Test Livemark" };
1121 function ensureLivemarkAdded() {
1122 ensureItemsAdded({ GUID: livemark_info.GUID
1123 , title: livemark_info.title
1124 , parentGUID: livemark_info.parentGUID
1125 , itemType: bmsvc.TYPE_FOLDER });
1126 let annos = [{ name: PlacesUtils.LMANNO_FEEDURI
1127 , value: livemark_info.feedURI.spec }];
1128 if ("siteURI" in livemark_info) {
1129 annos.push({ name: PlacesUtils.LMANNO_SITEURI
1130 , value: livemark_info.siteURI.spec });
1131 }
1132 ensureAnnotationsSet(livemark_info.GUID, annos);
1133 }
1134 function ensureLivemarkRemoved() {
1135 ensureItemsRemoved({ GUID: livemark_info.GUID
1136 , parentGUID: livemark_info.parentGUID });
1137 }
1138
1139 function* _testDoUndoRedoUndo() {
1140 observer.reset();
1141 livemark_info.GUID = yield PT.transact(PT.NewLivemark(livemark_info));
1142 ensureLivemarkAdded();
1143
1144 observer.reset();
1145 yield PT.undo();
1146 ensureLivemarkRemoved();
1147
1148 observer.reset();
1149 yield PT.redo();
1150 ensureLivemarkAdded();
1151
1152 yield PT.undo();
1153 ensureLivemarkRemoved();
1154 }
1155
1156 yield* _testDoUndoRedoUndo()
1157 livemark_info.siteURI = NetUtil.newURI("http://feed.site.uri");
1158 yield* _testDoUndoRedoUndo();
1159
1160 yield PT.clearTransactionsHistory();
1161 });

mercurial