michael@0: /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const bmsvc = PlacesUtils.bookmarks; michael@0: const tagssvc = PlacesUtils.tagging; michael@0: const annosvc = PlacesUtils.annotations; michael@0: const PT = PlacesTransactions; michael@0: michael@0: // Create and add bookmarks observer. michael@0: let observer = { michael@0: __proto__: NavBookmarkObserver.prototype, michael@0: michael@0: tagRelatedGUIDs: new Set(), michael@0: michael@0: reset: function () { michael@0: this.itemsAdded = new Map(); michael@0: this.itemsRemoved = new Map(); michael@0: this.itemsChanged = new Map(); michael@0: this.itemsMoved = new Map(); michael@0: this.beginUpdateBatch = false; michael@0: this.endUpdateBatch = false; michael@0: }, michael@0: michael@0: onBeginUpdateBatch: function () { michael@0: this.beginUpdateBatch = true; michael@0: }, michael@0: michael@0: onEndUpdateBatch: function () { michael@0: this.endUpdateBatch = true; michael@0: }, michael@0: michael@0: onItemAdded: michael@0: function (aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded, michael@0: aGUID, aParentGUID) { michael@0: // Ignore tag items. michael@0: if (aParentId == PlacesUtils.tagsFolderId || michael@0: (aParentId != PlacesUtils.placesRootId && michael@0: bmsvc.getFolderIdForItem(aParentId) == PlacesUtils.tagsFolderId)) { michael@0: this.tagRelatedGUIDs.add(aGUID); michael@0: return; michael@0: } michael@0: michael@0: this.itemsAdded.set(aGUID, { itemId: aItemId michael@0: , parentGUID: aParentGUID michael@0: , index: aIndex michael@0: , itemType: aItemType michael@0: , title: aTitle michael@0: , uri: aURI }); michael@0: }, michael@0: michael@0: onItemRemoved: michael@0: function (aItemId, aParentId, aIndex, aItemType, aURI, aGUID, aParentGUID) { michael@0: if (this.tagRelatedGUIDs.has(aGUID)) michael@0: return; michael@0: michael@0: this.itemsRemoved.set(aGUID, { parentGUID: aParentGUID michael@0: , index: aIndex michael@0: , itemType: aItemType }); michael@0: }, michael@0: michael@0: onItemChanged: michael@0: function (aItemId, aProperty, aIsAnnoProperty, aNewValue, aLastModified, michael@0: aItemType, aParentId, aGUID, aParentGUID) { michael@0: if (this.tagRelatedGUIDs.has(aGUID)) michael@0: return; michael@0: michael@0: let changesForGUID = this.itemsChanged.get(aGUID); michael@0: if (changesForGUID === undefined) { michael@0: changesForGUID = new Map(); michael@0: this.itemsChanged.set(aGUID, changesForGUID); michael@0: } michael@0: michael@0: let newValue = aNewValue; michael@0: if (aIsAnnoProperty) { michael@0: if (annosvc.itemHasAnnotation(aItemId, aProperty)) michael@0: newValue = annosvc.getItemAnnotation(aItemId, aProperty); michael@0: else michael@0: newValue = null; michael@0: } michael@0: let change = { isAnnoProperty: aIsAnnoProperty michael@0: , newValue: newValue michael@0: , lastModified: aLastModified michael@0: , itemType: aItemType }; michael@0: changesForGUID.set(aProperty, change); michael@0: }, michael@0: michael@0: onItemVisited: () => {}, michael@0: michael@0: onItemMoved: michael@0: function (aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex, aItemType, michael@0: aGUID, aOldParentGUID, aNewParentGUID) { michael@0: this.itemsMoved.set(aGUID, { oldParentGUID: aOldParentGUID michael@0: , oldIndex: aOldIndex michael@0: , newParentGUID: aNewParentGUID michael@0: , newIndex: aNewIndex michael@0: , itemType: aItemType }); michael@0: } michael@0: }; michael@0: observer.reset(); michael@0: michael@0: // index at which items should begin michael@0: let bmStartIndex = 0; michael@0: michael@0: // get bookmarks root id michael@0: let root = PlacesUtils.bookmarksMenuFolderId; michael@0: michael@0: function run_test() { michael@0: bmsvc.addObserver(observer, false); michael@0: do_register_cleanup(function () { michael@0: bmsvc.removeObserver(observer); michael@0: }); michael@0: michael@0: run_next_test(); michael@0: } michael@0: michael@0: function sanityCheckTransactionHistory() { michael@0: do_check_true(PT.undoPosition <= PT.length); michael@0: michael@0: let check_entry_throws = f => { michael@0: try { michael@0: f(); michael@0: do_throw("PT.entry should throw for invalid input"); michael@0: } catch(ex) {} michael@0: }; michael@0: check_entry_throws( () => PT.entry(-1) ); michael@0: check_entry_throws( () => PT.entry({}) ); michael@0: check_entry_throws( () => PT.entry(PT.length) ); michael@0: michael@0: if (PT.undoPosition < PT.length) michael@0: do_check_eq(PT.topUndoEntry, PT.entry(PT.undoPosition)); michael@0: else michael@0: do_check_null(PT.topUndoEntry); michael@0: if (PT.undoPosition > 0) michael@0: do_check_eq(PT.topRedoEntry, PT.entry(PT.undoPosition - 1)); michael@0: else michael@0: do_check_null(PT.topRedoEntry); michael@0: } michael@0: michael@0: function getTransactionsHistoryState() { michael@0: let history = []; michael@0: for (let i = 0; i < PT.length; i++) { michael@0: history.push(PT.entry(i)); michael@0: } michael@0: return [history, PT.undoPosition]; michael@0: } michael@0: michael@0: function ensureUndoState(aExpectedEntries = [], aExpectedUndoPosition = 0) { michael@0: // ensureUndoState is called in various places during this test, so it's michael@0: // a good places to sanity-check the transaction-history APIs in all michael@0: // cases. michael@0: sanityCheckTransactionHistory(); michael@0: michael@0: let [actualEntries, actualUndoPosition] = getTransactionsHistoryState(); michael@0: do_check_eq(actualEntries.length, aExpectedEntries.length); michael@0: do_check_eq(actualUndoPosition, aExpectedUndoPosition); michael@0: michael@0: function checkEqualEntries(aExpectedEntry, aActualEntry) { michael@0: do_check_eq(aExpectedEntry.length, aActualEntry.length); michael@0: aExpectedEntry.forEach( (t, i) => do_check_eq(t, aActualEntry[i]) ); michael@0: } michael@0: aExpectedEntries.forEach( (e, i) => checkEqualEntries(e, actualEntries[i]) ); michael@0: } michael@0: michael@0: function ensureItemsAdded(...items) { michael@0: do_check_eq(observer.itemsAdded.size, items.length); michael@0: for (let item of items) { michael@0: do_check_true(observer.itemsAdded.has(item.GUID)); michael@0: let info = observer.itemsAdded.get(item.GUID); michael@0: do_check_eq(info.parentGUID, item.parentGUID); michael@0: if ("title" in item) michael@0: do_check_eq(info.title, item.title); michael@0: if ("index" in item) michael@0: do_check_eq(info.index, item.index); michael@0: if ("itemType" in item) michael@0: do_check_eq(info.itemType, item.itemType); michael@0: } michael@0: } michael@0: michael@0: function ensureItemsRemoved(...items) { michael@0: do_check_eq(observer.itemsRemoved.size, items.length); michael@0: for (let item of items) { michael@0: do_check_true(observer.itemsRemoved.has(item.GUID)); michael@0: let info = observer.itemsRemoved.get(item.GUID); michael@0: do_check_eq(info.parentGUID, item.parentGUID); michael@0: if ("index" in item) michael@0: do_check_eq(info.index, item.index); michael@0: } michael@0: } michael@0: michael@0: function ensureItemsChanged(...items) { michael@0: for (let item of items) { michael@0: do_check_true(observer.itemsChanged.has(item.GUID)); michael@0: let changes = observer.itemsChanged.get(item.GUID); michael@0: do_check_true(changes.has(item.property)); michael@0: let info = changes.get(item.property); michael@0: do_check_eq(info.isAnnoProperty, Boolean(item.isAnnoProperty)); michael@0: do_check_eq(info.newValue, item.newValue); michael@0: if ("uri" in item) michael@0: do_check_true(item.uri.equals(info.uri)); michael@0: } michael@0: } michael@0: michael@0: function ensureAnnotationsSet(aGUID, aAnnos) { michael@0: do_check_true(observer.itemsChanged.has(aGUID)); michael@0: let changes = observer.itemsChanged.get(aGUID); michael@0: for (let anno of aAnnos) { michael@0: do_check_true(changes.has(anno.name)); michael@0: let changeInfo = changes.get(anno.name); michael@0: do_check_true(changeInfo.isAnnoProperty); michael@0: do_check_eq(changeInfo.newValue, anno.value); michael@0: } michael@0: } michael@0: michael@0: function ensureItemsMoved(...items) { michael@0: do_check_true(observer.itemsMoved.size, items.length); michael@0: for (let item of items) { michael@0: do_check_true(observer.itemsMoved.has(item.GUID)); michael@0: let info = observer.itemsMoved.get(item.GUID); michael@0: do_check_eq(info.oldParentGUID, item.oldParentGUID); michael@0: do_check_eq(info.oldIndex, item.oldIndex); michael@0: do_check_eq(info.newParentGUID, item.newParentGUID); michael@0: do_check_eq(info.newIndex, item.newIndex); michael@0: } michael@0: } michael@0: michael@0: function ensureTimestampsUpdated(aGUID, aCheckDateAdded = false) { michael@0: do_check_true(observer.itemsChanged.has(aGUID)); michael@0: let changes = observer.itemsChanged.get(aGUID); michael@0: if (aCheckDateAdded) michael@0: do_check_true(changes.has("dateAdded")) michael@0: do_check_true(changes.has("lastModified")); michael@0: } michael@0: michael@0: function ensureTagsForURI(aURI, aTags) { michael@0: let tagsSet = tagssvc.getTagsForURI(aURI); michael@0: do_check_eq(tagsSet.length, aTags.length); michael@0: do_check_true(aTags.every( t => tagsSet.indexOf(t) != -1 )); michael@0: } michael@0: michael@0: function* createTestFolderInfo(aTitle = "Test Folder") { michael@0: return { parentGUID: yield PlacesUtils.promiseItemGUID(root) michael@0: , title: "Test Folder" }; michael@0: } michael@0: michael@0: add_task(function* test_invalid_transact_calls() { michael@0: try { michael@0: PT.transact({ execute: () => {}, undo: () => {}, redo: () => {}}); michael@0: do_throw("transact shouldn't accept 'external' transactions"); michael@0: PT.transact(null); michael@0: do_throw("transact should throw for invalid arguments"); michael@0: } michael@0: catch(ex) { } michael@0: }); michael@0: michael@0: add_task(function* test_recycled_transactions() { michael@0: function ensureTransactThrowsFor(aTransaction) { michael@0: let [txns, undoPosition] = getTransactionsHistoryState(); michael@0: try { michael@0: yield PT.transact(aTransaction); michael@0: do_throw("Shouldn't be able to use the same transaction twice"); michael@0: } michael@0: catch(ex) { } michael@0: ensureUndoState(txns, undoPosition); michael@0: } michael@0: michael@0: let txn_a = PT.NewFolder(yield createTestFolderInfo()); michael@0: ensureTransactThrowsFor(txn_a); michael@0: yield PT.transact(txn_a); michael@0: ensureUndoState([[txn_a]], 0); michael@0: michael@0: yield PT.undo(); michael@0: ensureUndoState([[txn_a]], 1); michael@0: ensureTransactThrowsFor(txn_a); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: ensureTransactThrowsFor(txn_a); michael@0: michael@0: let txn_b = PT.NewFolder(yield createTestFolderInfo()); michael@0: yield PT.transact(function* () { michael@0: try { michael@0: yield txn_a; michael@0: do_throw("Shouldn't be able to use the same transaction twice"); michael@0: } michael@0: catch(ex) { } michael@0: ensureUndoState(); michael@0: yield txn_b; michael@0: }); michael@0: ensureUndoState([[txn_b]], 0); michael@0: michael@0: yield PT.undo(); michael@0: ensureUndoState([[txn_b]], 1); michael@0: ensureTransactThrowsFor(txn_a); michael@0: ensureTransactThrowsFor(txn_b); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: observer.reset(); michael@0: }); michael@0: michael@0: add_task(function* test_nested_batches() { michael@0: let txn_a = PT.NewFolder(yield createTestFolderInfo()), michael@0: txn_b = PT.NewFolder(yield createTestFolderInfo()); michael@0: yield PT.transact(function* () { michael@0: yield txn_a; michael@0: yield (function*() { michael@0: yield txn_b; michael@0: }()); michael@0: }); michael@0: ensureUndoState([[txn_b, txn_a]], 0); michael@0: michael@0: yield PT.undo(); michael@0: ensureUndoState([[txn_b, txn_a]], 1); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: observer.reset(); michael@0: }); michael@0: michael@0: add_task(function* test_new_folder_with_annotation() { michael@0: const ANNO = { name: "TestAnno", value: "TestValue" }; michael@0: let folder_info = yield createTestFolderInfo(); michael@0: folder_info.index = bmStartIndex; michael@0: folder_info.annotations = [ANNO]; michael@0: ensureUndoState(); michael@0: let txn = PT.NewFolder(folder_info); michael@0: folder_info.GUID = yield PT.transact(txn); michael@0: let ensureDo = function* (aRedo = false) { michael@0: ensureUndoState([[txn]], 0); michael@0: yield ensureItemsAdded(folder_info); michael@0: ensureAnnotationsSet(folder_info.GUID, [ANNO]); michael@0: if (aRedo) michael@0: ensureTimestampsUpdated(folder_info.GUID, true); michael@0: observer.reset(); michael@0: }; michael@0: michael@0: let ensureUndo = () => { michael@0: ensureUndoState([[txn]], 1); michael@0: ensureItemsRemoved({ GUID: folder_info.GUID michael@0: , parentGUID: folder_info.parentGUID michael@0: , index: bmStartIndex }); michael@0: observer.reset(); michael@0: }; michael@0: michael@0: yield ensureDo(); michael@0: yield PT.undo(); michael@0: yield ensureUndo(); michael@0: yield PT.redo(); michael@0: yield ensureDo(true); michael@0: yield PT.undo(); michael@0: ensureUndo(); michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: }); michael@0: michael@0: add_task(function* test_new_bookmark() { michael@0: let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root) michael@0: , uri: NetUtil.newURI("http://test_create_item.com") michael@0: , index: bmStartIndex michael@0: , title: "Test creating an item" }; michael@0: michael@0: ensureUndoState(); michael@0: let txn = PT.NewBookmark(bm_info); michael@0: bm_info.GUID = yield PT.transact(txn); michael@0: michael@0: let ensureDo = function* (aRedo = false) { michael@0: ensureUndoState([[txn]], 0); michael@0: yield ensureItemsAdded(bm_info); michael@0: if (aRedo) michael@0: ensureTimestampsUpdated(bm_info.GUID, true); michael@0: observer.reset(); michael@0: }; michael@0: let ensureUndo = () => { michael@0: ensureUndoState([[txn]], 1); michael@0: ensureItemsRemoved({ GUID: bm_info.GUID michael@0: , parentGUID: bm_info.parentGUID michael@0: , index: bmStartIndex }); michael@0: observer.reset(); michael@0: }; michael@0: michael@0: yield ensureDo(); michael@0: yield PT.undo(); michael@0: ensureUndo(); michael@0: yield PT.redo(true); michael@0: yield ensureDo(); michael@0: yield PT.undo(); michael@0: ensureUndo(); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: }); michael@0: michael@0: add_task(function* test_merge_create_folder_and_item() { michael@0: let folder_info = yield createTestFolderInfo(); michael@0: let bm_info = { uri: NetUtil.newURI("http://test_create_item_to_folder.com") michael@0: , title: "Test Bookmark" michael@0: , index: bmStartIndex }; michael@0: michael@0: let { folderTxn, bkmTxn } = yield PT.transact( function* () { michael@0: let folderTxn = PT.NewFolder(folder_info); michael@0: folder_info.GUID = bm_info.parentGUID = yield folderTxn; michael@0: let bkmTxn = PT.NewBookmark(bm_info); michael@0: bm_info.GUID = yield bkmTxn;; michael@0: return { folderTxn: folderTxn, bkmTxn: bkmTxn}; michael@0: }); michael@0: michael@0: let ensureDo = function* () { michael@0: ensureUndoState([[bkmTxn, folderTxn]], 0); michael@0: yield ensureItemsAdded(folder_info, bm_info); michael@0: observer.reset(); michael@0: }; michael@0: michael@0: let ensureUndo = () => { michael@0: ensureUndoState([[bkmTxn, folderTxn]], 1); michael@0: ensureItemsRemoved(folder_info, bm_info); michael@0: observer.reset(); michael@0: }; michael@0: michael@0: yield ensureDo(); michael@0: yield PT.undo(); michael@0: ensureUndo(); michael@0: yield PT.redo(); michael@0: yield ensureDo(); michael@0: yield PT.undo(); michael@0: ensureUndo(); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: }); michael@0: michael@0: add_task(function* test_move_items_to_folder() { michael@0: let folder_a_info = yield createTestFolderInfo("Folder A"); michael@0: let bkm_a_info = { uri: NetUtil.newURI("http://test_move_items.com") michael@0: , title: "Bookmark A" }; michael@0: let bkm_b_info = { uri: NetUtil.newURI("http://test_move_items.com") michael@0: , title: "Bookmark B" }; michael@0: michael@0: // Test moving items within the same folder. michael@0: let [folder_a_txn, bkm_a_txn, bkm_b_txn] = yield PT.transact(function* () { michael@0: let folder_a_txn = PT.NewFolder(folder_a_info); michael@0: michael@0: folder_a_info.GUID = michael@0: bkm_a_info.parentGUID = bkm_b_info.parentGUID = yield folder_a_txn; michael@0: let bkm_a_txn = PT.NewBookmark(bkm_a_info); michael@0: bkm_a_info.GUID = yield bkm_a_txn; michael@0: let bkm_b_txn = PT.NewBookmark(bkm_b_info); michael@0: bkm_b_info.GUID = yield bkm_b_txn; michael@0: return [folder_a_txn, bkm_a_txn, bkm_b_txn]; michael@0: }); michael@0: michael@0: ensureUndoState([[bkm_b_txn, bkm_a_txn, folder_a_txn]], 0); michael@0: michael@0: let moveTxn = PT.MoveItem({ GUID: bkm_a_info.GUID michael@0: , newParentGUID: folder_a_info.GUID }); michael@0: yield PT.transact(moveTxn); michael@0: michael@0: let ensureDo = () => { michael@0: ensureUndoState([[moveTxn], [bkm_b_txn, bkm_a_txn, folder_a_txn]], 0); michael@0: ensureItemsMoved({ GUID: bkm_a_info.GUID michael@0: , oldParentGUID: folder_a_info.GUID michael@0: , newParentGUID: folder_a_info.GUID michael@0: , oldIndex: 0 michael@0: , newIndex: 1 }); michael@0: observer.reset(); michael@0: }; michael@0: let ensureUndo = () => { michael@0: ensureUndoState([[moveTxn], [bkm_b_txn, bkm_a_txn, folder_a_txn]], 1); michael@0: ensureItemsMoved({ GUID: bkm_a_info.GUID michael@0: , oldParentGUID: folder_a_info.GUID michael@0: , newParentGUID: folder_a_info.GUID michael@0: , oldIndex: 1 michael@0: , newIndex: 0 }); michael@0: observer.reset(); michael@0: }; michael@0: michael@0: ensureDo(); michael@0: yield PT.undo(); michael@0: ensureUndo(); michael@0: yield PT.redo(); michael@0: ensureDo(); michael@0: yield PT.undo(); michael@0: ensureUndo(); michael@0: michael@0: yield PT.clearTransactionsHistory(false, true); michael@0: ensureUndoState([[bkm_b_txn, bkm_a_txn, folder_a_txn]], 0); michael@0: michael@0: // Test moving items between folders. michael@0: let folder_b_info = yield createTestFolderInfo("Folder B"); michael@0: let folder_b_txn = PT.NewFolder(folder_b_info); michael@0: folder_b_info.GUID = yield PT.transact(folder_b_txn); michael@0: ensureUndoState([ [folder_b_txn] michael@0: , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 0); michael@0: michael@0: moveTxn = PT.MoveItem({ GUID: bkm_a_info.GUID michael@0: , newParentGUID: folder_b_info.GUID michael@0: , newIndex: bmsvc.DEFAULT_INDEX }); michael@0: yield PT.transact(moveTxn); michael@0: michael@0: ensureDo = () => { michael@0: ensureUndoState([ [moveTxn] michael@0: , [folder_b_txn] michael@0: , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 0); michael@0: ensureItemsMoved({ GUID: bkm_a_info.GUID michael@0: , oldParentGUID: folder_a_info.GUID michael@0: , newParentGUID: folder_b_info.GUID michael@0: , oldIndex: 0 michael@0: , newIndex: 0 }); michael@0: observer.reset(); michael@0: }; michael@0: let ensureUndo = () => { michael@0: ensureUndoState([ [moveTxn] michael@0: , [folder_b_txn] michael@0: , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 1); michael@0: ensureItemsMoved({ GUID: bkm_a_info.GUID michael@0: , oldParentGUID: folder_b_info.GUID michael@0: , newParentGUID: folder_a_info.GUID michael@0: , oldIndex: 0 michael@0: , newIndex: 0 }); michael@0: observer.reset(); michael@0: }; michael@0: michael@0: ensureDo(); michael@0: yield PT.undo(); michael@0: ensureUndo(); michael@0: yield PT.redo(); michael@0: ensureDo(); michael@0: yield PT.undo(); michael@0: ensureUndo(); michael@0: michael@0: // Clean up michael@0: yield PT.undo(); // folder_b_txn michael@0: yield PT.undo(); // folder_a_txn + the bookmarks; michael@0: do_check_eq(observer.itemsRemoved.size, 4); michael@0: ensureUndoState([ [moveTxn] michael@0: , [folder_b_txn] michael@0: , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 3); michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: }); michael@0: michael@0: add_task(function* test_remove_folder() { michael@0: let folder_level_1_info = yield createTestFolderInfo("Folder Level 1"); michael@0: let folder_level_2_info = { title: "Folder Level 2" }; michael@0: let [folder_level_1_txn, michael@0: folder_level_2_txn] = yield PT.transact(function* () { michael@0: let folder_level_1_txn = PT.NewFolder(folder_level_1_info); michael@0: folder_level_1_info.GUID = yield folder_level_1_txn; michael@0: folder_level_2_info.parentGUID = folder_level_1_info.GUID; michael@0: let folder_level_2_txn = PT.NewFolder(folder_level_2_info); michael@0: folder_level_2_info.GUID = yield folder_level_2_txn; michael@0: return [folder_level_1_txn, folder_level_2_txn]; michael@0: }); michael@0: michael@0: ensureUndoState([[folder_level_2_txn, folder_level_1_txn]]); michael@0: yield ensureItemsAdded(folder_level_1_info, folder_level_2_info); michael@0: observer.reset(); michael@0: michael@0: let remove_folder_2_txn = PT.RemoveItem(folder_level_2_info); michael@0: yield PT.transact(remove_folder_2_txn); michael@0: michael@0: ensureUndoState([ [remove_folder_2_txn] michael@0: , [folder_level_2_txn, folder_level_1_txn] ]); michael@0: yield ensureItemsRemoved(folder_level_2_info); michael@0: michael@0: // Undo RemoveItem "Folder Level 2" michael@0: yield PT.undo(); michael@0: ensureUndoState([ [remove_folder_2_txn] michael@0: , [folder_level_2_txn, folder_level_1_txn] ], 1); michael@0: yield ensureItemsAdded(folder_level_2_info); michael@0: ensureTimestampsUpdated(folder_level_2_info.GUID, true); michael@0: observer.reset(); michael@0: michael@0: // Redo RemoveItem "Folder Level 2" michael@0: yield PT.redo(); michael@0: ensureUndoState([ [remove_folder_2_txn] michael@0: , [folder_level_2_txn, folder_level_1_txn] ]); michael@0: yield ensureItemsRemoved(folder_level_2_info); michael@0: observer.reset(); michael@0: michael@0: // Undo it again michael@0: yield PT.undo(); michael@0: ensureUndoState([ [remove_folder_2_txn] michael@0: , [folder_level_2_txn, folder_level_1_txn] ], 1); michael@0: yield ensureItemsAdded(folder_level_2_info); michael@0: ensureTimestampsUpdated(folder_level_2_info.GUID, true); michael@0: observer.reset(); michael@0: michael@0: // Undo the creation of both folders michael@0: yield PT.undo(); michael@0: ensureUndoState([ [remove_folder_2_txn] michael@0: , [folder_level_2_txn, folder_level_1_txn] ], 2); michael@0: yield ensureItemsRemoved(folder_level_2_info, folder_level_1_info); michael@0: observer.reset(); michael@0: michael@0: // Redo the creation of both folders michael@0: yield PT.redo(); michael@0: ensureUndoState([ [remove_folder_2_txn] michael@0: , [folder_level_2_txn, folder_level_1_txn] ], 1); michael@0: yield ensureItemsAdded(folder_level_1_info, folder_level_2_info); michael@0: ensureTimestampsUpdated(folder_level_1_info.GUID, true); michael@0: ensureTimestampsUpdated(folder_level_2_info.GUID, true); michael@0: observer.reset(); michael@0: michael@0: // Redo RemoveItem "Folder Level 2" michael@0: yield PT.redo(); michael@0: ensureUndoState([ [remove_folder_2_txn] michael@0: , [folder_level_2_txn, folder_level_1_txn] ]); michael@0: yield ensureItemsRemoved(folder_level_2_info); michael@0: observer.reset(); michael@0: michael@0: // Undo everything one last time michael@0: yield PT.undo(); michael@0: ensureUndoState([ [remove_folder_2_txn] michael@0: , [folder_level_2_txn, folder_level_1_txn] ], 1); michael@0: yield ensureItemsAdded(folder_level_2_info); michael@0: observer.reset(); michael@0: michael@0: yield PT.undo(); michael@0: ensureUndoState([ [remove_folder_2_txn] michael@0: , [folder_level_2_txn, folder_level_1_txn] ], 2); michael@0: yield ensureItemsRemoved(folder_level_2_info, folder_level_2_info); michael@0: observer.reset(); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: }); michael@0: michael@0: add_task(function* test_add_and_remove_bookmarks_with_additional_info() { michael@0: const testURI = NetUtil.newURI("http://add.remove.tag") michael@0: , TAG_1 = "TestTag1", TAG_2 = "TestTag2" michael@0: , KEYWORD = "test_keyword" michael@0: , POST_DATA = "post_data" michael@0: , ANNO = { name: "TestAnno", value: "TestAnnoValue" }; michael@0: michael@0: let folder_info = yield createTestFolderInfo(); michael@0: folder_info.GUID = yield PT.transact(PT.NewFolder(folder_info)); michael@0: let ensureTags = ensureTagsForURI.bind(null, testURI); michael@0: michael@0: // Check that the NewBookmark transaction preserves tags. michael@0: observer.reset(); michael@0: let b1_info = { parentGUID: folder_info.GUID, uri: testURI, tags: [TAG_1] }; michael@0: b1_info.GUID = yield PT.transact(PT.NewBookmark(b1_info)); michael@0: ensureTags([TAG_1]); michael@0: yield PT.undo(); michael@0: ensureTags([]); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: ensureTimestampsUpdated(b1_info.GUID, true); michael@0: ensureTags([TAG_1]); michael@0: michael@0: // Check if the RemoveItem transaction removes and restores tags of children michael@0: // correctly. michael@0: yield PT.transact(PT.RemoveItem(folder_info.GUID)); michael@0: ensureTags([]); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureTimestampsUpdated(b1_info.GUID, true); michael@0: ensureTags([TAG_1]); michael@0: michael@0: yield PT.redo(); michael@0: ensureTags([]); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureTimestampsUpdated(b1_info.GUID, true); michael@0: ensureTags([TAG_1]); michael@0: michael@0: // * Check that no-op tagging (the uri is already tagged with TAG_1) is michael@0: // also a no-op on undo. michael@0: // * Test the "keyword" property of the NewBookmark transaction. michael@0: observer.reset(); michael@0: let b2_info = { parentGUID: folder_info.GUID michael@0: , uri: testURI, tags: [TAG_1, TAG_2] michael@0: , keyword: KEYWORD michael@0: , postData: POST_DATA michael@0: , annotations: [ANNO] }; michael@0: b2_info.GUID = yield PT.transact(PT.NewBookmark(b2_info)); michael@0: let b2_post_creation_changes = [ michael@0: { GUID: b2_info.GUID michael@0: , isAnnoProperty: true michael@0: , property: ANNO.name michael@0: , newValue: ANNO.value }, michael@0: { GUID: b2_info.GUID michael@0: , property: "keyword" michael@0: , newValue: KEYWORD }, michael@0: { GUID: b2_info.GUID michael@0: , isAnnoProperty: true michael@0: , property: PlacesUtils.POST_DATA_ANNO michael@0: , newValue: POST_DATA } ]; michael@0: ensureItemsChanged(...b2_post_creation_changes); michael@0: ensureTags([TAG_1, TAG_2]); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: yield ensureItemsRemoved(b2_info); michael@0: ensureTags([TAG_1]); michael@0: michael@0: // Check if RemoveItem correctly restores keywords, tags and annotations. michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: ensureItemsChanged(...b2_post_creation_changes); michael@0: ensureTags([TAG_1, TAG_2]); michael@0: michael@0: // Test RemoveItem for multiple items. michael@0: observer.reset(); michael@0: yield PT.transact(PT.RemoveItem(b1_info.GUID)); michael@0: yield PT.transact(PT.RemoveItem(b2_info.GUID)); michael@0: yield PT.transact(PT.RemoveItem(folder_info.GUID)); michael@0: yield ensureItemsRemoved(b1_info, b2_info, folder_info); michael@0: ensureTags([]); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: yield ensureItemsAdded(folder_info); michael@0: ensureTags([]); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureItemsChanged(...b2_post_creation_changes); michael@0: ensureTags([TAG_1, TAG_2]); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: yield ensureItemsAdded(b1_info); michael@0: ensureTags([TAG_1, TAG_2]); michael@0: michael@0: // The redo calls below cleanup everything we did. michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: yield ensureItemsRemoved(b1_info); michael@0: ensureTags([TAG_1, TAG_2]); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: yield ensureItemsRemoved(b2_info); michael@0: ensureTags([]); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: yield ensureItemsRemoved(folder_info); michael@0: ensureTags([]); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: }); michael@0: michael@0: add_task(function* test_creating_and_removing_a_separator() { michael@0: let folder_info = yield createTestFolderInfo(); michael@0: let separator_info = {}; michael@0: let undoEntries = []; michael@0: michael@0: observer.reset(); michael@0: let create_txns = yield PT.transact(function* () { michael@0: let folder_txn = PT.NewFolder(folder_info); michael@0: folder_info.GUID = separator_info.parentGUID = yield folder_txn; michael@0: let separator_txn = PT.NewSeparator(separator_info); michael@0: separator_info.GUID = yield separator_txn; michael@0: return [separator_txn, folder_txn]; michael@0: }); michael@0: undoEntries.unshift(create_txns); michael@0: ensureUndoState(undoEntries, 0); michael@0: ensureItemsAdded(folder_info, separator_info); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureUndoState(undoEntries, 1); michael@0: ensureItemsRemoved(folder_info, separator_info); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: ensureUndoState(undoEntries, 0); michael@0: ensureItemsAdded(folder_info, separator_info); michael@0: michael@0: observer.reset(); michael@0: let remove_sep_txn = PT.RemoveItem(separator_info); michael@0: yield PT.transact(remove_sep_txn); michael@0: undoEntries.unshift([remove_sep_txn]); michael@0: ensureUndoState(undoEntries, 0); michael@0: ensureItemsRemoved(separator_info); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureUndoState(undoEntries, 1); michael@0: ensureItemsAdded(separator_info); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureUndoState(undoEntries, 2); michael@0: ensureItemsRemoved(folder_info, separator_info); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: ensureUndoState(undoEntries, 1); michael@0: ensureItemsAdded(folder_info, separator_info); michael@0: michael@0: // Clear redo entries and check that |redo| does nothing michael@0: observer.reset(); michael@0: yield PT.clearTransactionsHistory(false, true); michael@0: undoEntries.shift(); michael@0: ensureUndoState(undoEntries, 0); michael@0: yield PT.redo(); michael@0: ensureItemsAdded(); michael@0: ensureItemsRemoved(); michael@0: michael@0: // Cleanup michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureUndoState(undoEntries, 1); michael@0: ensureItemsRemoved(folder_info, separator_info); michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: }); michael@0: michael@0: add_task(function* test_edit_title() { michael@0: let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root) michael@0: , uri: NetUtil.newURI("http://test_create_item.com") michael@0: , title: "Original Title" }; michael@0: michael@0: function ensureTitleChange(aCurrentTitle) { michael@0: ensureItemsChanged({ GUID: bm_info.GUID michael@0: , property: "title" michael@0: , newValue: aCurrentTitle}); michael@0: } michael@0: michael@0: bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info)); michael@0: michael@0: observer.reset(); michael@0: yield PT.transact(PT.EditTitle({ GUID: bm_info.GUID, title: "New Title" })); michael@0: ensureTitleChange("New Title"); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureTitleChange("Original Title"); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: ensureTitleChange("New Title"); michael@0: michael@0: // Cleanup michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureTitleChange("Original Title"); michael@0: yield PT.undo(); michael@0: ensureItemsRemoved(bm_info); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: }); michael@0: michael@0: add_task(function* test_edit_url() { michael@0: let oldURI = NetUtil.newURI("http://old.test_editing_item_uri.com/"); michael@0: let newURI = NetUtil.newURI("http://new.test_editing_item_uri.com/"); michael@0: let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root) michael@0: , uri: oldURI michael@0: , tags: ["TestTag"]}; michael@0: michael@0: function ensureURIAndTags(aPreChangeURI, aPostChangeURI, aOLdURITagsPreserved) { michael@0: ensureItemsChanged({ GUID: bm_info.GUID michael@0: , property: "uri" michael@0: , newValue: aPostChangeURI.spec }); michael@0: ensureTagsForURI(aPostChangeURI, bm_info.tags); michael@0: ensureTagsForURI(aPreChangeURI, aOLdURITagsPreserved ? bm_info.tags : []); michael@0: } michael@0: michael@0: bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info)); michael@0: ensureTagsForURI(oldURI, bm_info.tags); michael@0: michael@0: // When there's a single bookmark for the same url, tags should be moved. michael@0: observer.reset(); michael@0: yield PT.transact(PT.EditURI({ GUID: bm_info.GUID, uri: newURI })); michael@0: ensureURIAndTags(oldURI, newURI, false); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureURIAndTags(newURI, oldURI, false); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: ensureURIAndTags(oldURI, newURI, false); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureURIAndTags(newURI, oldURI, false); michael@0: michael@0: // When there're multiple bookmarks for the same url, tags should be copied. michael@0: let bm2_info = Object.create(bm_info); michael@0: bm2_info.GUID = yield PT.transact(PT.NewBookmark(bm2_info)); michael@0: let bm3_info = Object.create(bm_info); michael@0: bm3_info.uri = newURI; michael@0: bm3_info.GUID = yield PT.transact(PT.NewBookmark(bm3_info)); michael@0: michael@0: observer.reset(); michael@0: yield PT.transact(PT.EditURI({ GUID: bm_info.GUID, uri: newURI })); michael@0: ensureURIAndTags(oldURI, newURI, true); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureURIAndTags(newURI, oldURI, true); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: ensureURIAndTags(oldURI, newURI, true); michael@0: michael@0: // Cleanup michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureURIAndTags(newURI, oldURI, true); michael@0: yield PT.undo(); michael@0: yield PT.undo(); michael@0: yield PT.undo(); michael@0: ensureItemsRemoved(bm3_info, bm2_info, bm_info); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: }); michael@0: michael@0: add_task(function* test_edit_keyword() { michael@0: let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root) michael@0: , uri: NetUtil.newURI("http://test.edit.keyword") }; michael@0: const KEYWORD = "test_keyword"; michael@0: bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info)); michael@0: function ensureKeywordChange(aCurrentKeyword = "") { michael@0: ensureItemsChanged({ GUID: bm_info.GUID michael@0: , property: "keyword" michael@0: , newValue: aCurrentKeyword }); michael@0: } michael@0: michael@0: bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info)); michael@0: michael@0: observer.reset(); michael@0: yield PT.transact(PT.EditKeyword({ GUID: bm_info.GUID, keyword: KEYWORD })); michael@0: ensureKeywordChange(KEYWORD); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureKeywordChange(); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: ensureKeywordChange(KEYWORD); michael@0: michael@0: // Cleanup michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureKeywordChange(); michael@0: yield PT.undo(); michael@0: ensureItemsRemoved(bm_info); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: }); michael@0: michael@0: add_task(function* test_tag_uri_unbookmarked_uri() { michael@0: let info = { uri: NetUtil.newURI("http://un.book.marked"), tags: ["MyTag"] }; michael@0: michael@0: function ensureDo() { michael@0: // A new bookmark should be created. michael@0: // (getMostRecentBookmarkForURI ignores tags) michael@0: do_check_neq(PlacesUtils.getMostRecentBookmarkForURI(info.uri), -1); michael@0: ensureTagsForURI(info.uri, info.tags); michael@0: } michael@0: function ensureUndo() { michael@0: do_check_eq(PlacesUtils.getMostRecentBookmarkForURI(info.uri), -1); michael@0: ensureTagsForURI(info.uri, []); michael@0: } michael@0: michael@0: yield PT.transact(PT.TagURI(info)); michael@0: ensureDo(); michael@0: yield PT.undo(); michael@0: ensureUndo(); michael@0: yield PT.redo(); michael@0: ensureDo(); michael@0: yield PT.undo(); michael@0: ensureUndo(); michael@0: }); michael@0: michael@0: add_task(function* test_tag_uri_bookmarked_uri() { michael@0: let bm_info = { uri: NetUtil.newURI("http://bookmarked.uri") michael@0: , parentGUID: yield PlacesUtils.promiseItemGUID(root) }; michael@0: bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info)); michael@0: michael@0: let tagging_info = { uri: bm_info.uri, tags: ["MyTag"] }; michael@0: yield PT.transact(PT.TagURI(tagging_info)); michael@0: ensureTagsForURI(tagging_info.uri, tagging_info.tags); michael@0: michael@0: yield PT.undo(); michael@0: ensureTagsForURI(tagging_info.uri, []); michael@0: yield PT.redo(); michael@0: ensureTagsForURI(tagging_info.uri, tagging_info.tags); michael@0: michael@0: // Cleanup michael@0: yield PT.undo(); michael@0: ensureTagsForURI(tagging_info.uri, []); michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureItemsRemoved(bm_info); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: ensureUndoState(); michael@0: }); michael@0: michael@0: add_task(function* test_untag_uri() { michael@0: let bm_info = { uri: NetUtil.newURI("http://test.untag.uri") michael@0: , parentGUID: yield PlacesUtils.promiseItemGUID(root) michael@0: , tags: ["T"]}; michael@0: bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info)); michael@0: michael@0: yield PT.transact(PT.UntagURI(bm_info)); michael@0: ensureTagsForURI(bm_info.uri, []); michael@0: yield PT.undo(); michael@0: ensureTagsForURI(bm_info.uri, bm_info.tags); michael@0: yield PT.redo(); michael@0: ensureTagsForURI(bm_info.uri, []); michael@0: yield PT.undo(); michael@0: ensureTagsForURI(bm_info.uri, bm_info.tags); michael@0: michael@0: // Also test just passing the uri (should remove all tags) michael@0: yield PT.transact(PT.UntagURI(bm_info.uri)); michael@0: ensureTagsForURI(bm_info.uri, []); michael@0: yield PT.undo(); michael@0: ensureTagsForURI(bm_info.uri, bm_info.tags); michael@0: yield PT.redo(); michael@0: ensureTagsForURI(bm_info.uri, []); michael@0: }); michael@0: michael@0: add_task(function* test_set_item_annotation() { michael@0: let bm_info = { uri: NetUtil.newURI("http://test.item.annotation") michael@0: , parentGUID: yield PlacesUtils.promiseItemGUID(root) }; michael@0: let anno_info = { name: "TestAnno", value: "TestValue" }; michael@0: function ensureAnnoState(aSet) { michael@0: ensureAnnotationsSet(bm_info.GUID, michael@0: [{ name: anno_info.name michael@0: , value: aSet ? anno_info.value : null }]); michael@0: } michael@0: michael@0: bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info)); michael@0: michael@0: observer.reset(); michael@0: yield PT.transact(PT.SetItemAnnotation({ GUID: bm_info.GUID michael@0: , annotationObject: anno_info })); michael@0: ensureAnnoState(true); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureAnnoState(false); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: ensureAnnoState(true); michael@0: michael@0: // Test removing the annotation by not passing the |value| property. michael@0: observer.reset(); michael@0: yield PT.transact( michael@0: PT.SetItemAnnotation({ GUID: bm_info.GUID michael@0: , annotationObject: { name: anno_info.name }})); michael@0: ensureAnnoState(false); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureAnnoState(true); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: ensureAnnoState(false); michael@0: }); michael@0: michael@0: add_task(function* test_sort_folder_by_name() { michael@0: let folder_info = yield createTestFolderInfo(); michael@0: michael@0: let uri = NetUtil.newURI("http://sort.by.name/"); michael@0: let preSep = [{ title: i, uri: uri } for (i of ["3","2","1"])]; michael@0: let sep = {}; michael@0: let postSep = [{ title: l, uri: uri } for (l of ["c","b","a"])]; michael@0: let originalOrder = [...preSep, sep, ...postSep]; michael@0: let sortedOrder = [...preSep.slice(0).reverse(), michael@0: sep, michael@0: ...postSep.slice(0).reverse()]; michael@0: yield PT.transact(function* () { michael@0: folder_info.GUID = yield PT.NewFolder(folder_info); michael@0: for (let info of originalOrder) { michael@0: info.parentGUID = folder_info.GUID; michael@0: info.GUID = yield info == sep ? michael@0: PT.NewSeparator(info) : PT.NewBookmark(info); michael@0: } michael@0: }); michael@0: michael@0: let folderId = yield PlacesUtils.promiseItemId(folder_info.GUID); michael@0: let folderContainer = PlacesUtils.getFolderContents(folderId).root; michael@0: function ensureOrder(aOrder) { michael@0: for (let i = 0; i < folderContainer.childCount; i++) { michael@0: do_check_eq(folderContainer.getChild(i).bookmarkGuid, aOrder[i].GUID); michael@0: } michael@0: } michael@0: michael@0: ensureOrder(originalOrder); michael@0: yield PT.transact(PT.SortByName(folder_info.GUID)); michael@0: ensureOrder(sortedOrder); michael@0: yield PT.undo(); michael@0: ensureOrder(originalOrder); michael@0: yield PT.redo(); michael@0: ensureOrder(sortedOrder); michael@0: michael@0: // Cleanup michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureOrder(originalOrder); michael@0: yield PT.undo(); michael@0: ensureItemsRemoved(...originalOrder, folder_info); michael@0: }); michael@0: michael@0: add_task(function* test_livemark_txns() { michael@0: let livemark_info = michael@0: { feedURI: NetUtil.newURI("http://test.feed.uri") michael@0: , parentGUID: yield PlacesUtils.promiseItemGUID(root) michael@0: , title: "Test Livemark" }; michael@0: function ensureLivemarkAdded() { michael@0: ensureItemsAdded({ GUID: livemark_info.GUID michael@0: , title: livemark_info.title michael@0: , parentGUID: livemark_info.parentGUID michael@0: , itemType: bmsvc.TYPE_FOLDER }); michael@0: let annos = [{ name: PlacesUtils.LMANNO_FEEDURI michael@0: , value: livemark_info.feedURI.spec }]; michael@0: if ("siteURI" in livemark_info) { michael@0: annos.push({ name: PlacesUtils.LMANNO_SITEURI michael@0: , value: livemark_info.siteURI.spec }); michael@0: } michael@0: ensureAnnotationsSet(livemark_info.GUID, annos); michael@0: } michael@0: function ensureLivemarkRemoved() { michael@0: ensureItemsRemoved({ GUID: livemark_info.GUID michael@0: , parentGUID: livemark_info.parentGUID }); michael@0: } michael@0: michael@0: function* _testDoUndoRedoUndo() { michael@0: observer.reset(); michael@0: livemark_info.GUID = yield PT.transact(PT.NewLivemark(livemark_info)); michael@0: ensureLivemarkAdded(); michael@0: michael@0: observer.reset(); michael@0: yield PT.undo(); michael@0: ensureLivemarkRemoved(); michael@0: michael@0: observer.reset(); michael@0: yield PT.redo(); michael@0: ensureLivemarkAdded(); michael@0: michael@0: yield PT.undo(); michael@0: ensureLivemarkRemoved(); michael@0: } michael@0: michael@0: yield* _testDoUndoRedoUndo() michael@0: livemark_info.siteURI = NetUtil.newURI("http://feed.site.uri"); michael@0: yield* _testDoUndoRedoUndo(); michael@0: michael@0: yield PT.clearTransactionsHistory(); michael@0: });