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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/places/tests/unit/test_async_transactions.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1161 @@
     1.4 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim:set ts=2 sw=2 sts=2 et: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +const bmsvc   = PlacesUtils.bookmarks;
    1.11 +const tagssvc = PlacesUtils.tagging;
    1.12 +const annosvc = PlacesUtils.annotations;
    1.13 +const PT      = PlacesTransactions;
    1.14 +
    1.15 +// Create and add bookmarks observer.
    1.16 +let observer = {
    1.17 +  __proto__: NavBookmarkObserver.prototype,
    1.18 +
    1.19 +  tagRelatedGUIDs: new Set(),
    1.20 +
    1.21 +  reset: function () {
    1.22 +    this.itemsAdded = new Map();
    1.23 +    this.itemsRemoved = new Map();
    1.24 +    this.itemsChanged = new Map();
    1.25 +    this.itemsMoved = new Map();
    1.26 +    this.beginUpdateBatch = false;
    1.27 +    this.endUpdateBatch = false;
    1.28 +  },
    1.29 +
    1.30 +  onBeginUpdateBatch: function () {
    1.31 +    this.beginUpdateBatch = true;
    1.32 +  },
    1.33 +
    1.34 +  onEndUpdateBatch: function () {
    1.35 +    this.endUpdateBatch = true;
    1.36 +  },
    1.37 +
    1.38 +  onItemAdded:
    1.39 +  function (aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
    1.40 +            aGUID, aParentGUID) {
    1.41 +    // Ignore tag items.
    1.42 +    if (aParentId == PlacesUtils.tagsFolderId ||
    1.43 +        (aParentId != PlacesUtils.placesRootId &&
    1.44 +         bmsvc.getFolderIdForItem(aParentId) == PlacesUtils.tagsFolderId)) {
    1.45 +      this.tagRelatedGUIDs.add(aGUID);
    1.46 +      return;
    1.47 +    }
    1.48 +
    1.49 +    this.itemsAdded.set(aGUID, { itemId:         aItemId
    1.50 +                               , parentGUID:     aParentGUID
    1.51 +                               , index:          aIndex
    1.52 +                               , itemType:       aItemType
    1.53 +                               , title:          aTitle
    1.54 +                               , uri:            aURI });
    1.55 +  },
    1.56 +
    1.57 +  onItemRemoved:
    1.58 +  function (aItemId, aParentId, aIndex, aItemType, aURI, aGUID, aParentGUID) {
    1.59 +    if (this.tagRelatedGUIDs.has(aGUID))
    1.60 +      return;
    1.61 +
    1.62 +    this.itemsRemoved.set(aGUID, { parentGUID: aParentGUID
    1.63 +                                 , index:      aIndex
    1.64 +                                 , itemType:   aItemType });
    1.65 +  },
    1.66 +
    1.67 +  onItemChanged:
    1.68 +  function (aItemId, aProperty, aIsAnnoProperty, aNewValue, aLastModified,
    1.69 +            aItemType, aParentId, aGUID, aParentGUID) {
    1.70 +    if (this.tagRelatedGUIDs.has(aGUID))
    1.71 +      return;
    1.72 +
    1.73 +    let changesForGUID = this.itemsChanged.get(aGUID);
    1.74 +    if (changesForGUID === undefined) {
    1.75 +      changesForGUID = new Map();
    1.76 +      this.itemsChanged.set(aGUID, changesForGUID);
    1.77 +    }
    1.78 +
    1.79 +    let newValue = aNewValue;
    1.80 +    if (aIsAnnoProperty) {
    1.81 +      if (annosvc.itemHasAnnotation(aItemId, aProperty))
    1.82 +        newValue = annosvc.getItemAnnotation(aItemId, aProperty);
    1.83 +      else
    1.84 +        newValue = null;
    1.85 +    }
    1.86 +    let change = { isAnnoProperty: aIsAnnoProperty
    1.87 +                 , newValue: newValue
    1.88 +                 , lastModified: aLastModified
    1.89 +                 , itemType: aItemType };
    1.90 +    changesForGUID.set(aProperty, change);
    1.91 +  },
    1.92 +
    1.93 +  onItemVisited: () => {},
    1.94 +
    1.95 +  onItemMoved:
    1.96 +  function (aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex, aItemType,
    1.97 +            aGUID, aOldParentGUID, aNewParentGUID) {
    1.98 +    this.itemsMoved.set(aGUID, { oldParentGUID: aOldParentGUID
    1.99 +                               , oldIndex:      aOldIndex
   1.100 +                               , newParentGUID: aNewParentGUID
   1.101 +                               , newIndex:      aNewIndex
   1.102 +                               , itemType:      aItemType });
   1.103 +  }
   1.104 +};
   1.105 +observer.reset();
   1.106 +
   1.107 +// index at which items should begin
   1.108 +let bmStartIndex = 0;
   1.109 +
   1.110 +// get bookmarks root id
   1.111 +let root = PlacesUtils.bookmarksMenuFolderId;
   1.112 +
   1.113 +function run_test() {
   1.114 +  bmsvc.addObserver(observer, false);
   1.115 +  do_register_cleanup(function () {
   1.116 +    bmsvc.removeObserver(observer);
   1.117 +  });
   1.118 +
   1.119 +  run_next_test();
   1.120 +}
   1.121 +
   1.122 +function sanityCheckTransactionHistory() {
   1.123 +  do_check_true(PT.undoPosition <= PT.length);
   1.124 +
   1.125 +  let check_entry_throws = f => {
   1.126 +    try {
   1.127 +      f();
   1.128 +      do_throw("PT.entry should throw for invalid input");
   1.129 +    } catch(ex) {}
   1.130 +  };
   1.131 +  check_entry_throws( () => PT.entry(-1) );
   1.132 +  check_entry_throws( () => PT.entry({}) );
   1.133 +  check_entry_throws( () => PT.entry(PT.length) );
   1.134 +
   1.135 +  if (PT.undoPosition < PT.length)
   1.136 +    do_check_eq(PT.topUndoEntry, PT.entry(PT.undoPosition));
   1.137 +  else
   1.138 +    do_check_null(PT.topUndoEntry);
   1.139 +  if (PT.undoPosition > 0)
   1.140 +    do_check_eq(PT.topRedoEntry, PT.entry(PT.undoPosition - 1));
   1.141 +  else
   1.142 +    do_check_null(PT.topRedoEntry);
   1.143 +}
   1.144 +
   1.145 +function getTransactionsHistoryState() {
   1.146 +  let history = [];
   1.147 +  for (let i = 0; i < PT.length; i++) {
   1.148 +    history.push(PT.entry(i));
   1.149 +  }
   1.150 +  return [history, PT.undoPosition];
   1.151 +}
   1.152 +
   1.153 +function ensureUndoState(aExpectedEntries = [], aExpectedUndoPosition = 0) {
   1.154 +  // ensureUndoState is called in various places during this test, so it's
   1.155 +  // a good places to sanity-check the transaction-history APIs in all
   1.156 +  // cases.
   1.157 +  sanityCheckTransactionHistory();
   1.158 +
   1.159 +  let [actualEntries, actualUndoPosition] = getTransactionsHistoryState();
   1.160 +  do_check_eq(actualEntries.length, aExpectedEntries.length);
   1.161 +  do_check_eq(actualUndoPosition, aExpectedUndoPosition);
   1.162 +
   1.163 +  function checkEqualEntries(aExpectedEntry, aActualEntry) {
   1.164 +    do_check_eq(aExpectedEntry.length, aActualEntry.length);
   1.165 +    aExpectedEntry.forEach( (t, i) => do_check_eq(t, aActualEntry[i]) );
   1.166 +  }
   1.167 +  aExpectedEntries.forEach( (e, i) => checkEqualEntries(e, actualEntries[i]) );
   1.168 +}
   1.169 +
   1.170 +function ensureItemsAdded(...items) {
   1.171 +  do_check_eq(observer.itemsAdded.size, items.length);
   1.172 +  for (let item of items) {
   1.173 +    do_check_true(observer.itemsAdded.has(item.GUID));
   1.174 +    let info = observer.itemsAdded.get(item.GUID);
   1.175 +    do_check_eq(info.parentGUID, item.parentGUID);
   1.176 +    if ("title" in item)
   1.177 +      do_check_eq(info.title, item.title);
   1.178 +    if ("index" in item)
   1.179 +      do_check_eq(info.index, item.index);
   1.180 +    if ("itemType" in item)
   1.181 +      do_check_eq(info.itemType, item.itemType);
   1.182 +  }
   1.183 +}
   1.184 +
   1.185 +function ensureItemsRemoved(...items) {
   1.186 +  do_check_eq(observer.itemsRemoved.size, items.length);
   1.187 +  for (let item of items) {
   1.188 +    do_check_true(observer.itemsRemoved.has(item.GUID));
   1.189 +    let info = observer.itemsRemoved.get(item.GUID);
   1.190 +    do_check_eq(info.parentGUID, item.parentGUID);
   1.191 +    if ("index" in item)
   1.192 +      do_check_eq(info.index, item.index);
   1.193 +  }
   1.194 +}
   1.195 +
   1.196 +function ensureItemsChanged(...items) {
   1.197 +  for (let item of items) {
   1.198 +    do_check_true(observer.itemsChanged.has(item.GUID));
   1.199 +    let changes = observer.itemsChanged.get(item.GUID);
   1.200 +    do_check_true(changes.has(item.property));
   1.201 +    let info = changes.get(item.property);
   1.202 +    do_check_eq(info.isAnnoProperty, Boolean(item.isAnnoProperty));
   1.203 +    do_check_eq(info.newValue, item.newValue);
   1.204 +    if ("uri" in item)
   1.205 +      do_check_true(item.uri.equals(info.uri));
   1.206 +  }
   1.207 +}
   1.208 +
   1.209 +function ensureAnnotationsSet(aGUID, aAnnos) {
   1.210 +  do_check_true(observer.itemsChanged.has(aGUID));
   1.211 +  let changes = observer.itemsChanged.get(aGUID);
   1.212 +  for (let anno of aAnnos) {
   1.213 +    do_check_true(changes.has(anno.name));
   1.214 +    let changeInfo = changes.get(anno.name);
   1.215 +    do_check_true(changeInfo.isAnnoProperty);
   1.216 +    do_check_eq(changeInfo.newValue, anno.value);
   1.217 +  }
   1.218 +}
   1.219 +
   1.220 +function ensureItemsMoved(...items) {
   1.221 +  do_check_true(observer.itemsMoved.size, items.length);
   1.222 +  for (let item of items) {
   1.223 +    do_check_true(observer.itemsMoved.has(item.GUID));
   1.224 +    let info = observer.itemsMoved.get(item.GUID);
   1.225 +    do_check_eq(info.oldParentGUID, item.oldParentGUID);
   1.226 +    do_check_eq(info.oldIndex, item.oldIndex);
   1.227 +    do_check_eq(info.newParentGUID, item.newParentGUID);
   1.228 +    do_check_eq(info.newIndex, item.newIndex);
   1.229 +  }
   1.230 +}
   1.231 +
   1.232 +function ensureTimestampsUpdated(aGUID, aCheckDateAdded = false) {
   1.233 +  do_check_true(observer.itemsChanged.has(aGUID));
   1.234 +  let changes = observer.itemsChanged.get(aGUID);
   1.235 +  if (aCheckDateAdded)
   1.236 +    do_check_true(changes.has("dateAdded"))
   1.237 +  do_check_true(changes.has("lastModified"));
   1.238 +}
   1.239 +
   1.240 +function ensureTagsForURI(aURI, aTags) {
   1.241 +  let tagsSet = tagssvc.getTagsForURI(aURI);
   1.242 +  do_check_eq(tagsSet.length, aTags.length);
   1.243 +  do_check_true(aTags.every( t => tagsSet.indexOf(t) != -1 ));
   1.244 +}
   1.245 +
   1.246 +function* createTestFolderInfo(aTitle = "Test Folder") {
   1.247 +  return { parentGUID: yield PlacesUtils.promiseItemGUID(root)
   1.248 +         , title: "Test Folder" };
   1.249 +}
   1.250 +
   1.251 +add_task(function* test_invalid_transact_calls() {
   1.252 +  try {
   1.253 +    PT.transact({ execute: () => {}, undo: () => {}, redo: () => {}});
   1.254 +    do_throw("transact shouldn't accept 'external' transactions");
   1.255 +    PT.transact(null);
   1.256 +    do_throw("transact should throw for invalid arguments");
   1.257 +  }
   1.258 +  catch(ex) { }
   1.259 +});
   1.260 +
   1.261 +add_task(function* test_recycled_transactions() {
   1.262 +  function ensureTransactThrowsFor(aTransaction) {
   1.263 +    let [txns, undoPosition] = getTransactionsHistoryState();
   1.264 +    try {
   1.265 +      yield PT.transact(aTransaction);
   1.266 +      do_throw("Shouldn't be able to use the same transaction twice");
   1.267 +    }
   1.268 +    catch(ex) { }
   1.269 +    ensureUndoState(txns, undoPosition);
   1.270 +  }
   1.271 +
   1.272 +  let txn_a = PT.NewFolder(yield createTestFolderInfo());
   1.273 +  ensureTransactThrowsFor(txn_a);
   1.274 +  yield PT.transact(txn_a);
   1.275 +  ensureUndoState([[txn_a]], 0);
   1.276 +
   1.277 +  yield PT.undo();
   1.278 +  ensureUndoState([[txn_a]], 1);
   1.279 +  ensureTransactThrowsFor(txn_a);
   1.280 +
   1.281 +  yield PT.clearTransactionsHistory();
   1.282 +  ensureUndoState();
   1.283 +  ensureTransactThrowsFor(txn_a);
   1.284 +
   1.285 +  let txn_b = PT.NewFolder(yield createTestFolderInfo());
   1.286 +  yield PT.transact(function* () {
   1.287 +    try {
   1.288 +      yield txn_a;
   1.289 +      do_throw("Shouldn't be able to use the same transaction twice");
   1.290 +    }
   1.291 +    catch(ex) { }
   1.292 +    ensureUndoState();
   1.293 +    yield txn_b;
   1.294 +  });
   1.295 +  ensureUndoState([[txn_b]], 0);
   1.296 +
   1.297 +  yield PT.undo();
   1.298 +  ensureUndoState([[txn_b]], 1);
   1.299 +  ensureTransactThrowsFor(txn_a);
   1.300 +  ensureTransactThrowsFor(txn_b);
   1.301 +
   1.302 +  yield PT.clearTransactionsHistory();
   1.303 +  ensureUndoState();
   1.304 +  observer.reset();
   1.305 +});
   1.306 +
   1.307 +add_task(function* test_nested_batches() {
   1.308 +  let txn_a = PT.NewFolder(yield createTestFolderInfo()),
   1.309 +      txn_b = PT.NewFolder(yield createTestFolderInfo());
   1.310 +  yield PT.transact(function* () {
   1.311 +    yield txn_a;
   1.312 +    yield (function*() {
   1.313 +      yield txn_b;
   1.314 +    }());
   1.315 +  });
   1.316 +  ensureUndoState([[txn_b, txn_a]], 0);
   1.317 +
   1.318 +  yield PT.undo();
   1.319 +  ensureUndoState([[txn_b, txn_a]], 1);
   1.320 +
   1.321 +  yield PT.clearTransactionsHistory();
   1.322 +  ensureUndoState();
   1.323 +  observer.reset();
   1.324 +});
   1.325 +
   1.326 +add_task(function* test_new_folder_with_annotation() {
   1.327 +  const ANNO = { name: "TestAnno", value: "TestValue" };
   1.328 +  let folder_info = yield createTestFolderInfo();
   1.329 +  folder_info.index = bmStartIndex;
   1.330 +  folder_info.annotations = [ANNO];
   1.331 +  ensureUndoState();
   1.332 +  let txn = PT.NewFolder(folder_info);
   1.333 +  folder_info.GUID = yield PT.transact(txn);
   1.334 +  let ensureDo = function* (aRedo = false) {
   1.335 +    ensureUndoState([[txn]], 0);
   1.336 +    yield ensureItemsAdded(folder_info);
   1.337 +    ensureAnnotationsSet(folder_info.GUID, [ANNO]);
   1.338 +    if (aRedo)
   1.339 +      ensureTimestampsUpdated(folder_info.GUID, true);
   1.340 +    observer.reset();
   1.341 +  };
   1.342 +
   1.343 +  let ensureUndo = () => {
   1.344 +    ensureUndoState([[txn]], 1);
   1.345 +    ensureItemsRemoved({ GUID:       folder_info.GUID
   1.346 +                       , parentGUID: folder_info.parentGUID
   1.347 +                       , index:      bmStartIndex });
   1.348 +    observer.reset();
   1.349 +  };
   1.350 +
   1.351 +  yield ensureDo();
   1.352 +  yield PT.undo();
   1.353 +  yield ensureUndo();
   1.354 +  yield PT.redo();
   1.355 +  yield ensureDo(true);
   1.356 +  yield PT.undo();
   1.357 +  ensureUndo();
   1.358 +  yield PT.clearTransactionsHistory();
   1.359 +  ensureUndoState();
   1.360 +});
   1.361 +
   1.362 +add_task(function* test_new_bookmark() {
   1.363 +  let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root)
   1.364 +                , uri:        NetUtil.newURI("http://test_create_item.com")
   1.365 +                , index:      bmStartIndex
   1.366 +                , title:      "Test creating an item" };
   1.367 +
   1.368 +  ensureUndoState();
   1.369 +  let txn = PT.NewBookmark(bm_info);
   1.370 +  bm_info.GUID = yield PT.transact(txn);
   1.371 +
   1.372 +  let ensureDo = function* (aRedo = false) {
   1.373 +    ensureUndoState([[txn]], 0);
   1.374 +    yield ensureItemsAdded(bm_info);
   1.375 +    if (aRedo)
   1.376 +      ensureTimestampsUpdated(bm_info.GUID, true);
   1.377 +    observer.reset();
   1.378 +  };
   1.379 +  let ensureUndo = () => {
   1.380 +    ensureUndoState([[txn]], 1);
   1.381 +    ensureItemsRemoved({ GUID:       bm_info.GUID
   1.382 +                       , parentGUID: bm_info.parentGUID
   1.383 +                       , index:      bmStartIndex });
   1.384 +    observer.reset();
   1.385 +  };
   1.386 +
   1.387 +  yield ensureDo();
   1.388 +  yield PT.undo();
   1.389 +  ensureUndo();
   1.390 +  yield PT.redo(true);
   1.391 +  yield ensureDo();
   1.392 +  yield PT.undo();
   1.393 +  ensureUndo();
   1.394 +
   1.395 +  yield PT.clearTransactionsHistory();
   1.396 +  ensureUndoState();
   1.397 +});
   1.398 +
   1.399 +add_task(function* test_merge_create_folder_and_item() {
   1.400 +  let folder_info = yield createTestFolderInfo();
   1.401 +  let bm_info = { uri: NetUtil.newURI("http://test_create_item_to_folder.com")
   1.402 +                , title: "Test Bookmark"
   1.403 +                , index: bmStartIndex };
   1.404 +
   1.405 +  let { folderTxn, bkmTxn } = yield PT.transact( function* () {
   1.406 +    let folderTxn = PT.NewFolder(folder_info);
   1.407 +    folder_info.GUID = bm_info.parentGUID = yield folderTxn;
   1.408 +    let bkmTxn = PT.NewBookmark(bm_info);
   1.409 +    bm_info.GUID = yield bkmTxn;;
   1.410 +    return { folderTxn: folderTxn, bkmTxn: bkmTxn};
   1.411 +  });
   1.412 +
   1.413 +  let ensureDo = function* () {
   1.414 +    ensureUndoState([[bkmTxn, folderTxn]], 0);
   1.415 +    yield ensureItemsAdded(folder_info, bm_info);
   1.416 +    observer.reset();
   1.417 +  };
   1.418 +
   1.419 +  let ensureUndo = () => {
   1.420 +    ensureUndoState([[bkmTxn, folderTxn]], 1);
   1.421 +    ensureItemsRemoved(folder_info, bm_info);
   1.422 +    observer.reset();
   1.423 +  };
   1.424 +
   1.425 +  yield ensureDo();
   1.426 +  yield PT.undo();
   1.427 +  ensureUndo();
   1.428 +  yield PT.redo();
   1.429 +  yield ensureDo();
   1.430 +  yield PT.undo();
   1.431 +  ensureUndo();
   1.432 +
   1.433 +  yield PT.clearTransactionsHistory();
   1.434 +  ensureUndoState();
   1.435 +});
   1.436 +
   1.437 +add_task(function* test_move_items_to_folder() {
   1.438 +  let folder_a_info = yield createTestFolderInfo("Folder A");
   1.439 +  let bkm_a_info = { uri: NetUtil.newURI("http://test_move_items.com")
   1.440 +                   , title: "Bookmark A" };
   1.441 +  let bkm_b_info = { uri: NetUtil.newURI("http://test_move_items.com")
   1.442 +                   , title: "Bookmark B" };
   1.443 +
   1.444 +  // Test moving items within the same folder.
   1.445 +  let [folder_a_txn, bkm_a_txn, bkm_b_txn] = yield PT.transact(function* () {
   1.446 +    let folder_a_txn = PT.NewFolder(folder_a_info);
   1.447 +
   1.448 +    folder_a_info.GUID =
   1.449 +      bkm_a_info.parentGUID = bkm_b_info.parentGUID = yield folder_a_txn;
   1.450 +    let bkm_a_txn = PT.NewBookmark(bkm_a_info);
   1.451 +    bkm_a_info.GUID = yield bkm_a_txn;
   1.452 +    let bkm_b_txn = PT.NewBookmark(bkm_b_info);
   1.453 +    bkm_b_info.GUID = yield bkm_b_txn;
   1.454 +    return [folder_a_txn, bkm_a_txn, bkm_b_txn];
   1.455 +  });
   1.456 +
   1.457 +  ensureUndoState([[bkm_b_txn, bkm_a_txn, folder_a_txn]], 0);
   1.458 +
   1.459 +  let moveTxn = PT.MoveItem({ GUID:          bkm_a_info.GUID
   1.460 +                            , newParentGUID: folder_a_info.GUID });
   1.461 +  yield PT.transact(moveTxn);
   1.462 +
   1.463 +  let ensureDo = () => {
   1.464 +    ensureUndoState([[moveTxn], [bkm_b_txn, bkm_a_txn, folder_a_txn]], 0);
   1.465 +    ensureItemsMoved({ GUID:          bkm_a_info.GUID
   1.466 +                     , oldParentGUID: folder_a_info.GUID
   1.467 +                     , newParentGUID: folder_a_info.GUID
   1.468 +                     , oldIndex:      0
   1.469 +                     , newIndex:      1 });
   1.470 +    observer.reset();
   1.471 +  };
   1.472 +  let ensureUndo = () => {
   1.473 +    ensureUndoState([[moveTxn], [bkm_b_txn, bkm_a_txn, folder_a_txn]], 1);
   1.474 +    ensureItemsMoved({ GUID:          bkm_a_info.GUID
   1.475 +                     , oldParentGUID: folder_a_info.GUID
   1.476 +                     , newParentGUID: folder_a_info.GUID
   1.477 +                     , oldIndex:      1
   1.478 +                     , newIndex:      0 });
   1.479 +    observer.reset();
   1.480 +  };
   1.481 +
   1.482 +  ensureDo();
   1.483 +  yield PT.undo();
   1.484 +  ensureUndo();
   1.485 +  yield PT.redo();
   1.486 +  ensureDo();
   1.487 +  yield PT.undo();
   1.488 +  ensureUndo();
   1.489 +
   1.490 +  yield PT.clearTransactionsHistory(false, true);
   1.491 +  ensureUndoState([[bkm_b_txn, bkm_a_txn, folder_a_txn]], 0);
   1.492 +
   1.493 +  // Test moving items between folders.
   1.494 +  let folder_b_info = yield createTestFolderInfo("Folder B");
   1.495 +  let folder_b_txn = PT.NewFolder(folder_b_info);
   1.496 +  folder_b_info.GUID = yield PT.transact(folder_b_txn);
   1.497 +  ensureUndoState([ [folder_b_txn]
   1.498 +                  , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 0);
   1.499 +
   1.500 +  moveTxn = PT.MoveItem({ GUID:          bkm_a_info.GUID
   1.501 +                        , newParentGUID: folder_b_info.GUID
   1.502 +                        , newIndex:      bmsvc.DEFAULT_INDEX });
   1.503 +  yield PT.transact(moveTxn);
   1.504 +
   1.505 +  ensureDo = () => {
   1.506 +    ensureUndoState([ [moveTxn]
   1.507 +                    , [folder_b_txn]
   1.508 +                    , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 0);
   1.509 +    ensureItemsMoved({ GUID:          bkm_a_info.GUID
   1.510 +                     , oldParentGUID: folder_a_info.GUID
   1.511 +                     , newParentGUID: folder_b_info.GUID
   1.512 +                     , oldIndex:      0
   1.513 +                     , newIndex:      0 });
   1.514 +    observer.reset();
   1.515 +  };
   1.516 +  let ensureUndo = () => {
   1.517 +    ensureUndoState([ [moveTxn]
   1.518 +                    , [folder_b_txn]
   1.519 +                    , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 1);
   1.520 +    ensureItemsMoved({ GUID:          bkm_a_info.GUID
   1.521 +                     , oldParentGUID: folder_b_info.GUID
   1.522 +                     , newParentGUID: folder_a_info.GUID
   1.523 +                     , oldIndex:      0
   1.524 +                     , newIndex:      0 });
   1.525 +    observer.reset();
   1.526 +  };
   1.527 +
   1.528 +  ensureDo();
   1.529 +  yield PT.undo();
   1.530 +  ensureUndo();
   1.531 +  yield PT.redo();
   1.532 +  ensureDo();
   1.533 +  yield PT.undo();
   1.534 +  ensureUndo();
   1.535 +
   1.536 +  // Clean up
   1.537 +  yield PT.undo();  // folder_b_txn
   1.538 +  yield PT.undo();  // folder_a_txn + the bookmarks;
   1.539 +  do_check_eq(observer.itemsRemoved.size, 4);
   1.540 +  ensureUndoState([ [moveTxn]
   1.541 +                  , [folder_b_txn]
   1.542 +                  , [bkm_b_txn, bkm_a_txn, folder_a_txn] ], 3);
   1.543 +  yield PT.clearTransactionsHistory();
   1.544 +  ensureUndoState();
   1.545 +});
   1.546 +
   1.547 +add_task(function* test_remove_folder() {
   1.548 +  let folder_level_1_info = yield createTestFolderInfo("Folder Level 1");
   1.549 +  let folder_level_2_info = { title: "Folder Level 2" };
   1.550 +  let [folder_level_1_txn,
   1.551 +       folder_level_2_txn] = yield PT.transact(function* () {
   1.552 +    let folder_level_1_txn  = PT.NewFolder(folder_level_1_info);
   1.553 +    folder_level_1_info.GUID = yield folder_level_1_txn;
   1.554 +    folder_level_2_info.parentGUID = folder_level_1_info.GUID;
   1.555 +    let folder_level_2_txn = PT.NewFolder(folder_level_2_info);
   1.556 +    folder_level_2_info.GUID = yield folder_level_2_txn;
   1.557 +    return [folder_level_1_txn, folder_level_2_txn];
   1.558 +  });
   1.559 +
   1.560 +  ensureUndoState([[folder_level_2_txn, folder_level_1_txn]]);
   1.561 +  yield ensureItemsAdded(folder_level_1_info, folder_level_2_info);
   1.562 +  observer.reset();
   1.563 +
   1.564 +  let remove_folder_2_txn = PT.RemoveItem(folder_level_2_info);
   1.565 +  yield PT.transact(remove_folder_2_txn);
   1.566 +
   1.567 +  ensureUndoState([ [remove_folder_2_txn]
   1.568 +                  , [folder_level_2_txn, folder_level_1_txn] ]);
   1.569 +  yield ensureItemsRemoved(folder_level_2_info);
   1.570 +
   1.571 +  // Undo RemoveItem "Folder Level 2"
   1.572 +  yield PT.undo();
   1.573 +  ensureUndoState([ [remove_folder_2_txn]
   1.574 +                  , [folder_level_2_txn, folder_level_1_txn] ], 1);
   1.575 +  yield ensureItemsAdded(folder_level_2_info);
   1.576 +  ensureTimestampsUpdated(folder_level_2_info.GUID, true);
   1.577 +  observer.reset();
   1.578 +
   1.579 +  // Redo RemoveItem "Folder Level 2"
   1.580 +  yield PT.redo();
   1.581 +  ensureUndoState([ [remove_folder_2_txn]
   1.582 +                  , [folder_level_2_txn, folder_level_1_txn] ]);
   1.583 +  yield ensureItemsRemoved(folder_level_2_info);
   1.584 +  observer.reset();
   1.585 +
   1.586 +  // Undo it again
   1.587 +  yield PT.undo();
   1.588 +  ensureUndoState([ [remove_folder_2_txn]
   1.589 +                  , [folder_level_2_txn, folder_level_1_txn] ], 1);
   1.590 +  yield ensureItemsAdded(folder_level_2_info);
   1.591 +  ensureTimestampsUpdated(folder_level_2_info.GUID, true);
   1.592 +  observer.reset();
   1.593 +
   1.594 +  // Undo the creation of both folders
   1.595 +  yield PT.undo();
   1.596 +  ensureUndoState([ [remove_folder_2_txn]
   1.597 +                  , [folder_level_2_txn, folder_level_1_txn] ], 2);
   1.598 +  yield ensureItemsRemoved(folder_level_2_info, folder_level_1_info);
   1.599 +  observer.reset();
   1.600 +
   1.601 +  // Redo the creation of both folders
   1.602 +  yield PT.redo();
   1.603 +  ensureUndoState([ [remove_folder_2_txn]
   1.604 +                  , [folder_level_2_txn, folder_level_1_txn] ], 1);
   1.605 +  yield ensureItemsAdded(folder_level_1_info, folder_level_2_info);
   1.606 +  ensureTimestampsUpdated(folder_level_1_info.GUID, true);
   1.607 +  ensureTimestampsUpdated(folder_level_2_info.GUID, true);
   1.608 +  observer.reset();
   1.609 +
   1.610 +  // Redo RemoveItem "Folder Level 2"
   1.611 +  yield PT.redo();
   1.612 +  ensureUndoState([ [remove_folder_2_txn]
   1.613 +                  , [folder_level_2_txn, folder_level_1_txn] ]);
   1.614 +  yield ensureItemsRemoved(folder_level_2_info);
   1.615 +  observer.reset();
   1.616 +
   1.617 +  // Undo everything one last time
   1.618 +  yield PT.undo();
   1.619 +  ensureUndoState([ [remove_folder_2_txn]
   1.620 +                  , [folder_level_2_txn, folder_level_1_txn] ], 1);
   1.621 +  yield ensureItemsAdded(folder_level_2_info);
   1.622 +  observer.reset();
   1.623 +
   1.624 +  yield PT.undo();
   1.625 +  ensureUndoState([ [remove_folder_2_txn]
   1.626 +                  , [folder_level_2_txn, folder_level_1_txn] ], 2);
   1.627 +  yield ensureItemsRemoved(folder_level_2_info, folder_level_2_info);
   1.628 +  observer.reset();
   1.629 +
   1.630 +  yield PT.clearTransactionsHistory();
   1.631 +  ensureUndoState();
   1.632 +});
   1.633 +
   1.634 +add_task(function* test_add_and_remove_bookmarks_with_additional_info() {
   1.635 +  const testURI = NetUtil.newURI("http://add.remove.tag")
   1.636 +      , TAG_1 = "TestTag1", TAG_2 = "TestTag2"
   1.637 +      , KEYWORD = "test_keyword"
   1.638 +      , POST_DATA = "post_data"
   1.639 +      , ANNO = { name: "TestAnno", value: "TestAnnoValue" };
   1.640 +
   1.641 +  let folder_info = yield createTestFolderInfo();
   1.642 +  folder_info.GUID = yield PT.transact(PT.NewFolder(folder_info));
   1.643 +  let ensureTags = ensureTagsForURI.bind(null, testURI);
   1.644 +
   1.645 +  // Check that the NewBookmark transaction preserves tags.
   1.646 +  observer.reset();
   1.647 +  let b1_info = { parentGUID: folder_info.GUID, uri: testURI, tags: [TAG_1] };
   1.648 +  b1_info.GUID = yield PT.transact(PT.NewBookmark(b1_info));
   1.649 +  ensureTags([TAG_1]);
   1.650 +  yield PT.undo();
   1.651 +  ensureTags([]);
   1.652 +
   1.653 +  observer.reset();
   1.654 +  yield PT.redo();
   1.655 +  ensureTimestampsUpdated(b1_info.GUID, true);
   1.656 +  ensureTags([TAG_1]);
   1.657 +
   1.658 +  // Check if the RemoveItem transaction removes and restores tags of children
   1.659 +  // correctly.
   1.660 +  yield PT.transact(PT.RemoveItem(folder_info.GUID));
   1.661 +  ensureTags([]);
   1.662 +
   1.663 +  observer.reset();
   1.664 +  yield PT.undo();
   1.665 +  ensureTimestampsUpdated(b1_info.GUID, true);
   1.666 +  ensureTags([TAG_1]);
   1.667 +
   1.668 +  yield PT.redo();
   1.669 +  ensureTags([]);
   1.670 +
   1.671 +  observer.reset();
   1.672 +  yield PT.undo();
   1.673 +  ensureTimestampsUpdated(b1_info.GUID, true);
   1.674 +  ensureTags([TAG_1]);
   1.675 +
   1.676 +  // * Check that no-op tagging (the uri is already tagged with TAG_1) is
   1.677 +  //   also a no-op on undo.
   1.678 +  // * Test the "keyword" property of the NewBookmark transaction.
   1.679 +  observer.reset();
   1.680 +  let b2_info = { parentGUID:  folder_info.GUID
   1.681 +                , uri:         testURI, tags: [TAG_1, TAG_2]
   1.682 +                , keyword:     KEYWORD
   1.683 +                , postData:    POST_DATA
   1.684 +                , annotations: [ANNO] };
   1.685 +  b2_info.GUID = yield PT.transact(PT.NewBookmark(b2_info));
   1.686 +  let b2_post_creation_changes = [
   1.687 +   { GUID: b2_info.GUID
   1.688 +   , isAnnoProperty: true
   1.689 +   , property: ANNO.name
   1.690 +   , newValue: ANNO.value },
   1.691 +   { GUID: b2_info.GUID
   1.692 +   , property: "keyword"
   1.693 +   , newValue: KEYWORD },
   1.694 +   { GUID: b2_info.GUID
   1.695 +   , isAnnoProperty: true
   1.696 +   , property: PlacesUtils.POST_DATA_ANNO
   1.697 +   , newValue: POST_DATA } ];
   1.698 +  ensureItemsChanged(...b2_post_creation_changes);
   1.699 +  ensureTags([TAG_1, TAG_2]);
   1.700 +
   1.701 +  observer.reset();
   1.702 +  yield PT.undo();
   1.703 +  yield ensureItemsRemoved(b2_info);
   1.704 +  ensureTags([TAG_1]);
   1.705 +
   1.706 +  // Check if RemoveItem correctly restores keywords, tags and annotations.
   1.707 +  observer.reset();
   1.708 +  yield PT.redo();
   1.709 +  ensureItemsChanged(...b2_post_creation_changes);
   1.710 +  ensureTags([TAG_1, TAG_2]);
   1.711 +
   1.712 +  // Test RemoveItem for multiple items.
   1.713 +  observer.reset();
   1.714 +  yield PT.transact(PT.RemoveItem(b1_info.GUID));
   1.715 +  yield PT.transact(PT.RemoveItem(b2_info.GUID));
   1.716 +  yield PT.transact(PT.RemoveItem(folder_info.GUID));
   1.717 +  yield ensureItemsRemoved(b1_info, b2_info, folder_info);
   1.718 +  ensureTags([]);
   1.719 +
   1.720 +  observer.reset();
   1.721 +  yield PT.undo();
   1.722 +  yield ensureItemsAdded(folder_info);
   1.723 +  ensureTags([]);
   1.724 +
   1.725 +  observer.reset();
   1.726 +  yield PT.undo();
   1.727 +  ensureItemsChanged(...b2_post_creation_changes);
   1.728 +  ensureTags([TAG_1, TAG_2]);
   1.729 +
   1.730 +  observer.reset();
   1.731 +  yield PT.undo();
   1.732 +  yield ensureItemsAdded(b1_info);
   1.733 +  ensureTags([TAG_1, TAG_2]);
   1.734 +
   1.735 +  // The redo calls below cleanup everything we did.
   1.736 +  observer.reset();
   1.737 +  yield PT.redo();
   1.738 +  yield ensureItemsRemoved(b1_info);
   1.739 +  ensureTags([TAG_1, TAG_2]);
   1.740 +
   1.741 +  observer.reset();
   1.742 +  yield PT.redo();
   1.743 +  yield ensureItemsRemoved(b2_info);
   1.744 +  ensureTags([]);
   1.745 +
   1.746 +  observer.reset();
   1.747 +  yield PT.redo();
   1.748 +  yield ensureItemsRemoved(folder_info);
   1.749 +  ensureTags([]);
   1.750 +
   1.751 +  yield PT.clearTransactionsHistory();
   1.752 +  ensureUndoState();
   1.753 +});
   1.754 +
   1.755 +add_task(function* test_creating_and_removing_a_separator() {
   1.756 +  let folder_info = yield createTestFolderInfo();
   1.757 +  let separator_info = {};
   1.758 +  let undoEntries = [];
   1.759 +
   1.760 +  observer.reset();
   1.761 +  let create_txns = yield PT.transact(function* () {
   1.762 +    let folder_txn = PT.NewFolder(folder_info);
   1.763 +    folder_info.GUID = separator_info.parentGUID = yield folder_txn;
   1.764 +    let separator_txn = PT.NewSeparator(separator_info);
   1.765 +    separator_info.GUID = yield separator_txn;
   1.766 +    return [separator_txn, folder_txn];
   1.767 +  });
   1.768 +  undoEntries.unshift(create_txns);
   1.769 +  ensureUndoState(undoEntries, 0);
   1.770 +  ensureItemsAdded(folder_info, separator_info);
   1.771 +
   1.772 +  observer.reset();
   1.773 +  yield PT.undo();
   1.774 +  ensureUndoState(undoEntries, 1);
   1.775 +  ensureItemsRemoved(folder_info, separator_info);
   1.776 +
   1.777 +  observer.reset();
   1.778 +  yield PT.redo();
   1.779 +  ensureUndoState(undoEntries, 0);
   1.780 +  ensureItemsAdded(folder_info, separator_info);
   1.781 +
   1.782 +  observer.reset();
   1.783 +  let remove_sep_txn = PT.RemoveItem(separator_info);
   1.784 +  yield PT.transact(remove_sep_txn);
   1.785 +  undoEntries.unshift([remove_sep_txn]);
   1.786 +  ensureUndoState(undoEntries, 0);
   1.787 +  ensureItemsRemoved(separator_info);
   1.788 +
   1.789 +  observer.reset();
   1.790 +  yield PT.undo();
   1.791 +  ensureUndoState(undoEntries, 1);
   1.792 +  ensureItemsAdded(separator_info);
   1.793 +
   1.794 +  observer.reset();
   1.795 +  yield PT.undo();
   1.796 +  ensureUndoState(undoEntries, 2);
   1.797 +  ensureItemsRemoved(folder_info, separator_info);
   1.798 +
   1.799 +  observer.reset();
   1.800 +  yield PT.redo();
   1.801 +  ensureUndoState(undoEntries, 1);
   1.802 +  ensureItemsAdded(folder_info, separator_info);
   1.803 +
   1.804 +  // Clear redo entries and check that |redo| does nothing
   1.805 +  observer.reset();
   1.806 +  yield PT.clearTransactionsHistory(false, true);
   1.807 +  undoEntries.shift();
   1.808 +  ensureUndoState(undoEntries, 0);
   1.809 +  yield PT.redo();
   1.810 +  ensureItemsAdded();
   1.811 +  ensureItemsRemoved();
   1.812 +
   1.813 +  // Cleanup
   1.814 +  observer.reset();
   1.815 +  yield PT.undo();
   1.816 +  ensureUndoState(undoEntries, 1);
   1.817 +  ensureItemsRemoved(folder_info, separator_info);
   1.818 +  yield PT.clearTransactionsHistory();
   1.819 +  ensureUndoState();
   1.820 +});
   1.821 +
   1.822 +add_task(function* test_edit_title() {
   1.823 +  let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root)
   1.824 +                , uri:        NetUtil.newURI("http://test_create_item.com")
   1.825 +                , title:      "Original Title" };
   1.826 +
   1.827 +  function ensureTitleChange(aCurrentTitle) {
   1.828 +    ensureItemsChanged({ GUID: bm_info.GUID
   1.829 +                       , property: "title"
   1.830 +                       , newValue: aCurrentTitle});
   1.831 +  }
   1.832 +
   1.833 +  bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
   1.834 +
   1.835 +  observer.reset();
   1.836 +  yield PT.transact(PT.EditTitle({ GUID: bm_info.GUID, title: "New Title" }));
   1.837 +  ensureTitleChange("New Title");
   1.838 +
   1.839 +  observer.reset();
   1.840 +  yield PT.undo();
   1.841 +  ensureTitleChange("Original Title");
   1.842 +
   1.843 +  observer.reset();
   1.844 +  yield PT.redo();
   1.845 +  ensureTitleChange("New Title");
   1.846 +
   1.847 +  // Cleanup
   1.848 +  observer.reset();
   1.849 +  yield PT.undo();
   1.850 +  ensureTitleChange("Original Title");
   1.851 +  yield PT.undo();
   1.852 +  ensureItemsRemoved(bm_info);
   1.853 +
   1.854 +  yield PT.clearTransactionsHistory();
   1.855 +  ensureUndoState();
   1.856 +});
   1.857 +
   1.858 +add_task(function* test_edit_url() {
   1.859 +  let oldURI = NetUtil.newURI("http://old.test_editing_item_uri.com/");
   1.860 +  let newURI = NetUtil.newURI("http://new.test_editing_item_uri.com/");
   1.861 +  let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root)
   1.862 +                , uri:        oldURI
   1.863 +                , tags:       ["TestTag"]};
   1.864 +
   1.865 +  function ensureURIAndTags(aPreChangeURI, aPostChangeURI, aOLdURITagsPreserved) {
   1.866 +    ensureItemsChanged({ GUID: bm_info.GUID
   1.867 +                       , property: "uri"
   1.868 +                       , newValue: aPostChangeURI.spec });
   1.869 +    ensureTagsForURI(aPostChangeURI, bm_info.tags);
   1.870 +    ensureTagsForURI(aPreChangeURI, aOLdURITagsPreserved ? bm_info.tags : []);
   1.871 +  }
   1.872 +
   1.873 +  bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
   1.874 +  ensureTagsForURI(oldURI, bm_info.tags);
   1.875 +
   1.876 +  // When there's a single bookmark for the same url, tags should be moved.
   1.877 +  observer.reset();
   1.878 +  yield PT.transact(PT.EditURI({ GUID: bm_info.GUID, uri: newURI }));
   1.879 +  ensureURIAndTags(oldURI, newURI, false);
   1.880 +
   1.881 +  observer.reset();
   1.882 +  yield PT.undo();
   1.883 +  ensureURIAndTags(newURI, oldURI, false);
   1.884 +
   1.885 +  observer.reset();
   1.886 +  yield PT.redo();
   1.887 +  ensureURIAndTags(oldURI, newURI, false);
   1.888 +
   1.889 +  observer.reset();
   1.890 +  yield PT.undo();
   1.891 +  ensureURIAndTags(newURI, oldURI, false);
   1.892 +
   1.893 +  // When there're multiple bookmarks for the same url, tags should be copied.
   1.894 +  let bm2_info = Object.create(bm_info);
   1.895 +  bm2_info.GUID = yield PT.transact(PT.NewBookmark(bm2_info));
   1.896 +  let bm3_info = Object.create(bm_info);
   1.897 +  bm3_info.uri = newURI;
   1.898 +  bm3_info.GUID = yield PT.transact(PT.NewBookmark(bm3_info));
   1.899 +
   1.900 +  observer.reset();
   1.901 +  yield PT.transact(PT.EditURI({ GUID: bm_info.GUID, uri: newURI }));
   1.902 +  ensureURIAndTags(oldURI, newURI, true);
   1.903 +
   1.904 +  observer.reset();
   1.905 +  yield PT.undo();
   1.906 +  ensureURIAndTags(newURI, oldURI, true);
   1.907 +
   1.908 +  observer.reset();
   1.909 +  yield PT.redo();
   1.910 +  ensureURIAndTags(oldURI, newURI, true);
   1.911 +
   1.912 +  // Cleanup
   1.913 +  observer.reset();
   1.914 +  yield PT.undo();
   1.915 +  ensureURIAndTags(newURI, oldURI, true);
   1.916 +  yield PT.undo();
   1.917 +  yield PT.undo();
   1.918 +  yield PT.undo();
   1.919 +  ensureItemsRemoved(bm3_info, bm2_info, bm_info);
   1.920 +
   1.921 +  yield PT.clearTransactionsHistory();
   1.922 +  ensureUndoState();
   1.923 +});
   1.924 +
   1.925 +add_task(function* test_edit_keyword() {
   1.926 +  let bm_info = { parentGUID: yield PlacesUtils.promiseItemGUID(root)
   1.927 +                , uri:        NetUtil.newURI("http://test.edit.keyword") };
   1.928 +  const KEYWORD = "test_keyword";
   1.929 +  bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
   1.930 +  function ensureKeywordChange(aCurrentKeyword = "") {
   1.931 +    ensureItemsChanged({ GUID: bm_info.GUID
   1.932 +                       , property: "keyword"
   1.933 +                       , newValue: aCurrentKeyword });
   1.934 +  }
   1.935 +
   1.936 +  bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
   1.937 +
   1.938 +  observer.reset();
   1.939 +  yield PT.transact(PT.EditKeyword({ GUID: bm_info.GUID, keyword: KEYWORD }));
   1.940 +  ensureKeywordChange(KEYWORD);
   1.941 +
   1.942 +  observer.reset();
   1.943 +  yield PT.undo();
   1.944 +  ensureKeywordChange();
   1.945 +
   1.946 +  observer.reset();
   1.947 +  yield PT.redo();
   1.948 +  ensureKeywordChange(KEYWORD);
   1.949 +
   1.950 +  // Cleanup
   1.951 +  observer.reset();
   1.952 +  yield PT.undo();
   1.953 +  ensureKeywordChange();
   1.954 +  yield PT.undo();
   1.955 +  ensureItemsRemoved(bm_info);
   1.956 +
   1.957 +  yield PT.clearTransactionsHistory();
   1.958 +  ensureUndoState();
   1.959 +});
   1.960 +
   1.961 +add_task(function* test_tag_uri_unbookmarked_uri() {
   1.962 +  let info = { uri: NetUtil.newURI("http://un.book.marked"), tags: ["MyTag"] };
   1.963 +
   1.964 +  function ensureDo() {
   1.965 +    // A new bookmark should be created.
   1.966 +    // (getMostRecentBookmarkForURI ignores tags)
   1.967 +    do_check_neq(PlacesUtils.getMostRecentBookmarkForURI(info.uri), -1);
   1.968 +    ensureTagsForURI(info.uri, info.tags);
   1.969 +  }
   1.970 +  function ensureUndo() {
   1.971 +    do_check_eq(PlacesUtils.getMostRecentBookmarkForURI(info.uri), -1);
   1.972 +    ensureTagsForURI(info.uri, []);
   1.973 +  }
   1.974 +
   1.975 +  yield PT.transact(PT.TagURI(info));
   1.976 +  ensureDo();
   1.977 +  yield PT.undo();
   1.978 +  ensureUndo();
   1.979 +  yield PT.redo();
   1.980 +  ensureDo();
   1.981 +  yield PT.undo();
   1.982 +  ensureUndo();
   1.983 +});
   1.984 +
   1.985 +add_task(function* test_tag_uri_bookmarked_uri() {
   1.986 +  let bm_info = { uri: NetUtil.newURI("http://bookmarked.uri")
   1.987 +                , parentGUID: yield PlacesUtils.promiseItemGUID(root) };
   1.988 +  bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
   1.989 +
   1.990 +  let tagging_info = { uri: bm_info.uri, tags: ["MyTag"] };
   1.991 +  yield PT.transact(PT.TagURI(tagging_info));
   1.992 +  ensureTagsForURI(tagging_info.uri, tagging_info.tags);
   1.993 +
   1.994 +  yield PT.undo();
   1.995 +  ensureTagsForURI(tagging_info.uri, []);
   1.996 +  yield PT.redo();
   1.997 +  ensureTagsForURI(tagging_info.uri, tagging_info.tags);
   1.998 +
   1.999 +  // Cleanup
  1.1000 +  yield PT.undo();
  1.1001 +  ensureTagsForURI(tagging_info.uri, []);
  1.1002 +  observer.reset();
  1.1003 +  yield PT.undo();
  1.1004 +  ensureItemsRemoved(bm_info);
  1.1005 +
  1.1006 +  yield PT.clearTransactionsHistory();
  1.1007 +  ensureUndoState();
  1.1008 +});
  1.1009 +
  1.1010 +add_task(function* test_untag_uri() {
  1.1011 +  let bm_info = { uri: NetUtil.newURI("http://test.untag.uri")
  1.1012 +                , parentGUID: yield PlacesUtils.promiseItemGUID(root)
  1.1013 +                , tags: ["T"]};
  1.1014 +  bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
  1.1015 +
  1.1016 +  yield PT.transact(PT.UntagURI(bm_info));
  1.1017 +  ensureTagsForURI(bm_info.uri, []);
  1.1018 +  yield PT.undo();
  1.1019 +  ensureTagsForURI(bm_info.uri, bm_info.tags);
  1.1020 +  yield PT.redo();
  1.1021 +  ensureTagsForURI(bm_info.uri, []);
  1.1022 +  yield PT.undo();
  1.1023 +  ensureTagsForURI(bm_info.uri, bm_info.tags);
  1.1024 +
  1.1025 +  // Also test just passing the uri (should remove all tags)
  1.1026 +  yield PT.transact(PT.UntagURI(bm_info.uri));
  1.1027 +  ensureTagsForURI(bm_info.uri, []);
  1.1028 +  yield PT.undo();
  1.1029 +  ensureTagsForURI(bm_info.uri, bm_info.tags);
  1.1030 +  yield PT.redo();
  1.1031 +  ensureTagsForURI(bm_info.uri, []);
  1.1032 +});
  1.1033 +
  1.1034 +add_task(function* test_set_item_annotation() {
  1.1035 +  let bm_info = { uri: NetUtil.newURI("http://test.item.annotation")
  1.1036 +                , parentGUID: yield PlacesUtils.promiseItemGUID(root) };
  1.1037 +  let anno_info = { name: "TestAnno", value: "TestValue" };
  1.1038 +  function ensureAnnoState(aSet) {
  1.1039 +    ensureAnnotationsSet(bm_info.GUID,
  1.1040 +                         [{ name: anno_info.name
  1.1041 +                          , value: aSet ? anno_info.value : null }]);
  1.1042 +  }
  1.1043 +
  1.1044 +  bm_info.GUID = yield PT.transact(PT.NewBookmark(bm_info));
  1.1045 +
  1.1046 +  observer.reset();
  1.1047 +  yield PT.transact(PT.SetItemAnnotation({ GUID: bm_info.GUID
  1.1048 +                                         , annotationObject: anno_info }));
  1.1049 +  ensureAnnoState(true);
  1.1050 +
  1.1051 +  observer.reset();
  1.1052 +  yield PT.undo();
  1.1053 +  ensureAnnoState(false);
  1.1054 +
  1.1055 +  observer.reset();
  1.1056 +  yield PT.redo();
  1.1057 +  ensureAnnoState(true);
  1.1058 +
  1.1059 +  // Test removing the annotation by not passing the |value| property.
  1.1060 +  observer.reset();
  1.1061 +  yield PT.transact(
  1.1062 +    PT.SetItemAnnotation({ GUID: bm_info.GUID
  1.1063 +                         , annotationObject: { name: anno_info.name }}));
  1.1064 +  ensureAnnoState(false);
  1.1065 +
  1.1066 +  observer.reset();
  1.1067 +  yield PT.undo();
  1.1068 +  ensureAnnoState(true);
  1.1069 +
  1.1070 +  observer.reset();
  1.1071 +  yield PT.redo();
  1.1072 +  ensureAnnoState(false);
  1.1073 +});
  1.1074 +
  1.1075 +add_task(function* test_sort_folder_by_name() {
  1.1076 +  let folder_info = yield createTestFolderInfo();
  1.1077 +
  1.1078 +  let uri = NetUtil.newURI("http://sort.by.name/");
  1.1079 +  let preSep =  [{ title: i, uri: uri } for (i of ["3","2","1"])];
  1.1080 +  let sep = {};
  1.1081 +  let postSep = [{ title: l, uri: uri } for (l of ["c","b","a"])];
  1.1082 +  let originalOrder = [...preSep, sep, ...postSep];
  1.1083 +  let sortedOrder = [...preSep.slice(0).reverse(),
  1.1084 +                     sep,
  1.1085 +                     ...postSep.slice(0).reverse()];
  1.1086 +  yield PT.transact(function* () {
  1.1087 +    folder_info.GUID = yield PT.NewFolder(folder_info);
  1.1088 +    for (let info of originalOrder) {
  1.1089 +      info.parentGUID = folder_info.GUID;
  1.1090 +      info.GUID = yield info == sep ?
  1.1091 +                  PT.NewSeparator(info) : PT.NewBookmark(info);
  1.1092 +    }
  1.1093 +  });
  1.1094 +
  1.1095 +  let folderId = yield PlacesUtils.promiseItemId(folder_info.GUID);
  1.1096 +  let folderContainer = PlacesUtils.getFolderContents(folderId).root;
  1.1097 +  function ensureOrder(aOrder) {
  1.1098 +    for (let i = 0; i < folderContainer.childCount; i++) {
  1.1099 +      do_check_eq(folderContainer.getChild(i).bookmarkGuid, aOrder[i].GUID);
  1.1100 +    }
  1.1101 +  }
  1.1102 +
  1.1103 +  ensureOrder(originalOrder);
  1.1104 +  yield PT.transact(PT.SortByName(folder_info.GUID));
  1.1105 +  ensureOrder(sortedOrder);
  1.1106 +  yield PT.undo();
  1.1107 +  ensureOrder(originalOrder);
  1.1108 +  yield PT.redo();
  1.1109 +  ensureOrder(sortedOrder);
  1.1110 +
  1.1111 +  // Cleanup
  1.1112 +  observer.reset();
  1.1113 +  yield PT.undo();
  1.1114 +  ensureOrder(originalOrder);
  1.1115 +  yield PT.undo();
  1.1116 +  ensureItemsRemoved(...originalOrder, folder_info);
  1.1117 +});
  1.1118 +
  1.1119 +add_task(function* test_livemark_txns() {
  1.1120 +  let livemark_info =
  1.1121 +    { feedURI: NetUtil.newURI("http://test.feed.uri")
  1.1122 +    , parentGUID: yield PlacesUtils.promiseItemGUID(root)
  1.1123 +    , title: "Test Livemark" };
  1.1124 +  function ensureLivemarkAdded() {
  1.1125 +    ensureItemsAdded({ GUID:       livemark_info.GUID
  1.1126 +                     , title:      livemark_info.title
  1.1127 +                     , parentGUID: livemark_info.parentGUID
  1.1128 +                     , itemType:   bmsvc.TYPE_FOLDER });
  1.1129 +    let annos = [{ name:  PlacesUtils.LMANNO_FEEDURI
  1.1130 +                 , value: livemark_info.feedURI.spec }];
  1.1131 +    if ("siteURI" in livemark_info) {
  1.1132 +      annos.push({ name: PlacesUtils.LMANNO_SITEURI
  1.1133 +                 , value: livemark_info.siteURI.spec });
  1.1134 +    }
  1.1135 +    ensureAnnotationsSet(livemark_info.GUID, annos);
  1.1136 +  }
  1.1137 +  function ensureLivemarkRemoved() {
  1.1138 +    ensureItemsRemoved({ GUID:       livemark_info.GUID
  1.1139 +                       , parentGUID: livemark_info.parentGUID });
  1.1140 +  }
  1.1141 +
  1.1142 +  function* _testDoUndoRedoUndo() {
  1.1143 +    observer.reset();
  1.1144 +    livemark_info.GUID = yield PT.transact(PT.NewLivemark(livemark_info));
  1.1145 +    ensureLivemarkAdded();
  1.1146 +
  1.1147 +    observer.reset();
  1.1148 +    yield PT.undo();
  1.1149 +    ensureLivemarkRemoved();
  1.1150 +
  1.1151 +    observer.reset();
  1.1152 +    yield PT.redo();
  1.1153 +    ensureLivemarkAdded();
  1.1154 +
  1.1155 +    yield PT.undo();
  1.1156 +    ensureLivemarkRemoved();
  1.1157 +  }
  1.1158 +
  1.1159 +  yield* _testDoUndoRedoUndo()
  1.1160 +  livemark_info.siteURI = NetUtil.newURI("http://feed.site.uri");
  1.1161 +  yield* _testDoUndoRedoUndo();
  1.1162 +
  1.1163 +  yield PT.clearTransactionsHistory();
  1.1164 +});

mercurial