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

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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/. */
     7 const bmsvc   = PlacesUtils.bookmarks;
     8 const tagssvc = PlacesUtils.tagging;
     9 const annosvc = PlacesUtils.annotations;
    10 const PT      = PlacesTransactions;
    12 // Create and add bookmarks observer.
    13 let observer = {
    14   __proto__: NavBookmarkObserver.prototype,
    16   tagRelatedGUIDs: new Set(),
    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   },
    27   onBeginUpdateBatch: function () {
    28     this.beginUpdateBatch = true;
    29   },
    31   onEndUpdateBatch: function () {
    32     this.endUpdateBatch = true;
    33   },
    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     }
    46     this.itemsAdded.set(aGUID, { itemId:         aItemId
    47                                , parentGUID:     aParentGUID
    48                                , index:          aIndex
    49                                , itemType:       aItemType
    50                                , title:          aTitle
    51                                , uri:            aURI });
    52   },
    54   onItemRemoved:
    55   function (aItemId, aParentId, aIndex, aItemType, aURI, aGUID, aParentGUID) {
    56     if (this.tagRelatedGUIDs.has(aGUID))
    57       return;
    59     this.itemsRemoved.set(aGUID, { parentGUID: aParentGUID
    60                                  , index:      aIndex
    61                                  , itemType:   aItemType });
    62   },
    64   onItemChanged:
    65   function (aItemId, aProperty, aIsAnnoProperty, aNewValue, aLastModified,
    66             aItemType, aParentId, aGUID, aParentGUID) {
    67     if (this.tagRelatedGUIDs.has(aGUID))
    68       return;
    70     let changesForGUID = this.itemsChanged.get(aGUID);
    71     if (changesForGUID === undefined) {
    72       changesForGUID = new Map();
    73       this.itemsChanged.set(aGUID, changesForGUID);
    74     }
    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   },
    90   onItemVisited: () => {},
    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();
   104 // index at which items should begin
   105 let bmStartIndex = 0;
   107 // get bookmarks root id
   108 let root = PlacesUtils.bookmarksMenuFolderId;
   110 function run_test() {
   111   bmsvc.addObserver(observer, false);
   112   do_register_cleanup(function () {
   113     bmsvc.removeObserver(observer);
   114   });
   116   run_next_test();
   117 }
   119 function sanityCheckTransactionHistory() {
   120   do_check_true(PT.undoPosition <= PT.length);
   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) );
   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 }
   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 }
   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();
   156   let [actualEntries, actualUndoPosition] = getTransactionsHistoryState();
   157   do_check_eq(actualEntries.length, aExpectedEntries.length);
   158   do_check_eq(actualUndoPosition, aExpectedUndoPosition);
   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 }
   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 }
   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 }
   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 }
   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 }
   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 }
   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 }
   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 }
   243 function* createTestFolderInfo(aTitle = "Test Folder") {
   244   return { parentGUID: yield PlacesUtils.promiseItemGUID(root)
   245          , title: "Test Folder" };
   246 }
   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 });
   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   }
   269   let txn_a = PT.NewFolder(yield createTestFolderInfo());
   270   ensureTransactThrowsFor(txn_a);
   271   yield PT.transact(txn_a);
   272   ensureUndoState([[txn_a]], 0);
   274   yield PT.undo();
   275   ensureUndoState([[txn_a]], 1);
   276   ensureTransactThrowsFor(txn_a);
   278   yield PT.clearTransactionsHistory();
   279   ensureUndoState();
   280   ensureTransactThrowsFor(txn_a);
   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);
   294   yield PT.undo();
   295   ensureUndoState([[txn_b]], 1);
   296   ensureTransactThrowsFor(txn_a);
   297   ensureTransactThrowsFor(txn_b);
   299   yield PT.clearTransactionsHistory();
   300   ensureUndoState();
   301   observer.reset();
   302 });
   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);
   315   yield PT.undo();
   316   ensureUndoState([[txn_b, txn_a]], 1);
   318   yield PT.clearTransactionsHistory();
   319   ensureUndoState();
   320   observer.reset();
   321 });
   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   };
   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   };
   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 });
   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" };
   365   ensureUndoState();
   366   let txn = PT.NewBookmark(bm_info);
   367   bm_info.GUID = yield PT.transact(txn);
   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   };
   384   yield ensureDo();
   385   yield PT.undo();
   386   ensureUndo();
   387   yield PT.redo(true);
   388   yield ensureDo();
   389   yield PT.undo();
   390   ensureUndo();
   392   yield PT.clearTransactionsHistory();
   393   ensureUndoState();
   394 });
   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 };
   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   });
   410   let ensureDo = function* () {
   411     ensureUndoState([[bkmTxn, folderTxn]], 0);
   412     yield ensureItemsAdded(folder_info, bm_info);
   413     observer.reset();
   414   };
   416   let ensureUndo = () => {
   417     ensureUndoState([[bkmTxn, folderTxn]], 1);
   418     ensureItemsRemoved(folder_info, bm_info);
   419     observer.reset();
   420   };
   422   yield ensureDo();
   423   yield PT.undo();
   424   ensureUndo();
   425   yield PT.redo();
   426   yield ensureDo();
   427   yield PT.undo();
   428   ensureUndo();
   430   yield PT.clearTransactionsHistory();
   431   ensureUndoState();
   432 });
   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" };
   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);
   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   });
   454   ensureUndoState([[bkm_b_txn, bkm_a_txn, folder_a_txn]], 0);
   456   let moveTxn = PT.MoveItem({ GUID:          bkm_a_info.GUID
   457                             , newParentGUID: folder_a_info.GUID });
   458   yield PT.transact(moveTxn);
   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   };
   479   ensureDo();
   480   yield PT.undo();
   481   ensureUndo();
   482   yield PT.redo();
   483   ensureDo();
   484   yield PT.undo();
   485   ensureUndo();
   487   yield PT.clearTransactionsHistory(false, true);
   488   ensureUndoState([[bkm_b_txn, bkm_a_txn, folder_a_txn]], 0);
   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);
   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);
   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   };
   525   ensureDo();
   526   yield PT.undo();
   527   ensureUndo();
   528   yield PT.redo();
   529   ensureDo();
   530   yield PT.undo();
   531   ensureUndo();
   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 });
   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   });
   557   ensureUndoState([[folder_level_2_txn, folder_level_1_txn]]);
   558   yield ensureItemsAdded(folder_level_1_info, folder_level_2_info);
   559   observer.reset();
   561   let remove_folder_2_txn = PT.RemoveItem(folder_level_2_info);
   562   yield PT.transact(remove_folder_2_txn);
   564   ensureUndoState([ [remove_folder_2_txn]
   565                   , [folder_level_2_txn, folder_level_1_txn] ]);
   566   yield ensureItemsRemoved(folder_level_2_info);
   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();
   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();
   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();
   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();
   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();
   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();
   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();
   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();
   627   yield PT.clearTransactionsHistory();
   628   ensureUndoState();
   629 });
   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" };
   638   let folder_info = yield createTestFolderInfo();
   639   folder_info.GUID = yield PT.transact(PT.NewFolder(folder_info));
   640   let ensureTags = ensureTagsForURI.bind(null, testURI);
   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([]);
   650   observer.reset();
   651   yield PT.redo();
   652   ensureTimestampsUpdated(b1_info.GUID, true);
   653   ensureTags([TAG_1]);
   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([]);
   660   observer.reset();
   661   yield PT.undo();
   662   ensureTimestampsUpdated(b1_info.GUID, true);
   663   ensureTags([TAG_1]);
   665   yield PT.redo();
   666   ensureTags([]);
   668   observer.reset();
   669   yield PT.undo();
   670   ensureTimestampsUpdated(b1_info.GUID, true);
   671   ensureTags([TAG_1]);
   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]);
   698   observer.reset();
   699   yield PT.undo();
   700   yield ensureItemsRemoved(b2_info);
   701   ensureTags([TAG_1]);
   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]);
   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([]);
   717   observer.reset();
   718   yield PT.undo();
   719   yield ensureItemsAdded(folder_info);
   720   ensureTags([]);
   722   observer.reset();
   723   yield PT.undo();
   724   ensureItemsChanged(...b2_post_creation_changes);
   725   ensureTags([TAG_1, TAG_2]);
   727   observer.reset();
   728   yield PT.undo();
   729   yield ensureItemsAdded(b1_info);
   730   ensureTags([TAG_1, TAG_2]);
   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]);
   738   observer.reset();
   739   yield PT.redo();
   740   yield ensureItemsRemoved(b2_info);
   741   ensureTags([]);
   743   observer.reset();
   744   yield PT.redo();
   745   yield ensureItemsRemoved(folder_info);
   746   ensureTags([]);
   748   yield PT.clearTransactionsHistory();
   749   ensureUndoState();
   750 });
   752 add_task(function* test_creating_and_removing_a_separator() {
   753   let folder_info = yield createTestFolderInfo();
   754   let separator_info = {};
   755   let undoEntries = [];
   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);
   769   observer.reset();
   770   yield PT.undo();
   771   ensureUndoState(undoEntries, 1);
   772   ensureItemsRemoved(folder_info, separator_info);
   774   observer.reset();
   775   yield PT.redo();
   776   ensureUndoState(undoEntries, 0);
   777   ensureItemsAdded(folder_info, separator_info);
   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);
   786   observer.reset();
   787   yield PT.undo();
   788   ensureUndoState(undoEntries, 1);
   789   ensureItemsAdded(separator_info);
   791   observer.reset();
   792   yield PT.undo();
   793   ensureUndoState(undoEntries, 2);
   794   ensureItemsRemoved(folder_info, separator_info);
   796   observer.reset();
   797   yield PT.redo();
   798   ensureUndoState(undoEntries, 1);
   799   ensureItemsAdded(folder_info, separator_info);
   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();
   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 });
   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" };
   824   function ensureTitleChange(aCurrentTitle) {
   825     ensureItemsChanged({ GUID: bm_info.GUID
   826                        , property: "title"
   827                        , newValue: aCurrentTitle});
   828   }
   830   bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
   832   observer.reset();
   833   yield PT.transact(PT.EditTitle({ GUID: bm_info.GUID, title: "New Title" }));
   834   ensureTitleChange("New Title");
   836   observer.reset();
   837   yield PT.undo();
   838   ensureTitleChange("Original Title");
   840   observer.reset();
   841   yield PT.redo();
   842   ensureTitleChange("New Title");
   844   // Cleanup
   845   observer.reset();
   846   yield PT.undo();
   847   ensureTitleChange("Original Title");
   848   yield PT.undo();
   849   ensureItemsRemoved(bm_info);
   851   yield PT.clearTransactionsHistory();
   852   ensureUndoState();
   853 });
   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"]};
   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   }
   870   bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
   871   ensureTagsForURI(oldURI, bm_info.tags);
   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);
   878   observer.reset();
   879   yield PT.undo();
   880   ensureURIAndTags(newURI, oldURI, false);
   882   observer.reset();
   883   yield PT.redo();
   884   ensureURIAndTags(oldURI, newURI, false);
   886   observer.reset();
   887   yield PT.undo();
   888   ensureURIAndTags(newURI, oldURI, false);
   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));
   897   observer.reset();
   898   yield PT.transact(PT.EditURI({ GUID: bm_info.GUID, uri: newURI }));
   899   ensureURIAndTags(oldURI, newURI, true);
   901   observer.reset();
   902   yield PT.undo();
   903   ensureURIAndTags(newURI, oldURI, true);
   905   observer.reset();
   906   yield PT.redo();
   907   ensureURIAndTags(oldURI, newURI, true);
   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);
   918   yield PT.clearTransactionsHistory();
   919   ensureUndoState();
   920 });
   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   }
   933   bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
   935   observer.reset();
   936   yield PT.transact(PT.EditKeyword({ GUID: bm_info.GUID, keyword: KEYWORD }));
   937   ensureKeywordChange(KEYWORD);
   939   observer.reset();
   940   yield PT.undo();
   941   ensureKeywordChange();
   943   observer.reset();
   944   yield PT.redo();
   945   ensureKeywordChange(KEYWORD);
   947   // Cleanup
   948   observer.reset();
   949   yield PT.undo();
   950   ensureKeywordChange();
   951   yield PT.undo();
   952   ensureItemsRemoved(bm_info);
   954   yield PT.clearTransactionsHistory();
   955   ensureUndoState();
   956 });
   958 add_task(function* test_tag_uri_unbookmarked_uri() {
   959   let info = { uri: NetUtil.newURI("http://un.book.marked"), tags: ["MyTag"] };
   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   }
   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 });
   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));
   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);
   991   yield PT.undo();
   992   ensureTagsForURI(tagging_info.uri, []);
   993   yield PT.redo();
   994   ensureTagsForURI(tagging_info.uri, tagging_info.tags);
   996   // Cleanup
   997   yield PT.undo();
   998   ensureTagsForURI(tagging_info.uri, []);
   999   observer.reset();
  1000   yield PT.undo();
  1001   ensureItemsRemoved(bm_info);
  1003   yield PT.clearTransactionsHistory();
  1004   ensureUndoState();
  1005 });
  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));
  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);
  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 });
  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 }]);
  1041   bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
  1043   observer.reset();
  1044   yield PT.transact(PT.SetItemAnnotation({ GUID: bm_info.GUID
  1045                                          , annotationObject: anno_info }));
  1046   ensureAnnoState(true);
  1048   observer.reset();
  1049   yield PT.undo();
  1050   ensureAnnoState(false);
  1052   observer.reset();
  1053   yield PT.redo();
  1054   ensureAnnoState(true);
  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);
  1063   observer.reset();
  1064   yield PT.undo();
  1065   ensureAnnoState(true);
  1067   observer.reset();
  1068   yield PT.redo();
  1069   ensureAnnoState(false);
  1070 });
  1072 add_task(function* test_sort_folder_by_name() {
  1073   let folder_info = yield createTestFolderInfo();
  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);
  1090   });
  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);
  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);
  1108   // Cleanup
  1109   observer.reset();
  1110   yield PT.undo();
  1111   ensureOrder(originalOrder);
  1112   yield PT.undo();
  1113   ensureItemsRemoved(...originalOrder, folder_info);
  1114 });
  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 });
  1132     ensureAnnotationsSet(livemark_info.GUID, annos);
  1134   function ensureLivemarkRemoved() {
  1135     ensureItemsRemoved({ GUID:       livemark_info.GUID
  1136                        , parentGUID: livemark_info.parentGUID });
  1139   function* _testDoUndoRedoUndo() {
  1140     observer.reset();
  1141     livemark_info.GUID = yield PT.transact(PT.NewLivemark(livemark_info));
  1142     ensureLivemarkAdded();
  1144     observer.reset();
  1145     yield PT.undo();
  1146     ensureLivemarkRemoved();
  1148     observer.reset();
  1149     yield PT.redo();
  1150     ensureLivemarkAdded();
  1152     yield PT.undo();
  1153     ensureLivemarkRemoved();
  1156   yield* _testDoUndoRedoUndo()
  1157   livemark_info.siteURI = NetUtil.newURI("http://feed.site.uri");
  1158   yield* _testDoUndoRedoUndo();
  1160   yield PT.clearTransactionsHistory();
  1161 });

mercurial