toolkit/components/places/tests/queries/test_async.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim:set ts=2 sw=2 sts=2 et: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 let tests = [
michael@0 8 {
michael@0 9 desc: "nsNavHistoryFolderResultNode: Basic test, asynchronously open and " +
michael@0 10 "close container with a single child",
michael@0 11
michael@0 12 loading: function (node, newState, oldState) {
michael@0 13 this.checkStateChanged("loading", 1);
michael@0 14 this.checkArgs("loading", node, oldState, node.STATE_CLOSED);
michael@0 15 },
michael@0 16
michael@0 17 opened: function (node, newState, oldState) {
michael@0 18 this.checkStateChanged("opened", 1);
michael@0 19 this.checkState("loading", 1);
michael@0 20 this.checkArgs("opened", node, oldState, node.STATE_LOADING);
michael@0 21
michael@0 22 print("Checking node children");
michael@0 23 compareArrayToResult(this.data, node);
michael@0 24
michael@0 25 print("Closing container");
michael@0 26 node.containerOpen = false;
michael@0 27 },
michael@0 28
michael@0 29 closed: function (node, newState, oldState) {
michael@0 30 this.checkStateChanged("closed", 1);
michael@0 31 this.checkState("opened", 1);
michael@0 32 this.checkArgs("closed", node, oldState, node.STATE_OPENED);
michael@0 33 this.success();
michael@0 34 }
michael@0 35 },
michael@0 36
michael@0 37 {
michael@0 38 desc: "nsNavHistoryFolderResultNode: After async open and no changes, " +
michael@0 39 "second open should be synchronous",
michael@0 40
michael@0 41 loading: function (node, newState, oldState) {
michael@0 42 this.checkStateChanged("loading", 1);
michael@0 43 this.checkState("closed", 0);
michael@0 44 this.checkArgs("loading", node, oldState, node.STATE_CLOSED);
michael@0 45 },
michael@0 46
michael@0 47 opened: function (node, newState, oldState) {
michael@0 48 let cnt = this.checkStateChanged("opened", 1, 2);
michael@0 49 let expectOldState = cnt === 1 ? node.STATE_LOADING : node.STATE_CLOSED;
michael@0 50 this.checkArgs("opened", node, oldState, expectOldState);
michael@0 51
michael@0 52 print("Checking node children");
michael@0 53 compareArrayToResult(this.data, node);
michael@0 54
michael@0 55 print("Closing container");
michael@0 56 node.containerOpen = false;
michael@0 57 },
michael@0 58
michael@0 59 closed: function (node, newState, oldState) {
michael@0 60 let cnt = this.checkStateChanged("closed", 1, 2);
michael@0 61 this.checkArgs("closed", node, oldState, node.STATE_OPENED);
michael@0 62
michael@0 63 switch (cnt) {
michael@0 64 case 1:
michael@0 65 node.containerOpen = true;
michael@0 66 break;
michael@0 67 case 2:
michael@0 68 this.success();
michael@0 69 break;
michael@0 70 }
michael@0 71 }
michael@0 72 },
michael@0 73
michael@0 74 {
michael@0 75 desc: "nsNavHistoryFolderResultNode: After closing container in " +
michael@0 76 "loading(), opened() should not be called",
michael@0 77
michael@0 78 loading: function (node, newState, oldState) {
michael@0 79 this.checkStateChanged("loading", 1);
michael@0 80 this.checkArgs("loading", node, oldState, node.STATE_CLOSED);
michael@0 81 print("Closing container");
michael@0 82 node.containerOpen = false;
michael@0 83 },
michael@0 84
michael@0 85 opened: function (node, newState, oldState) {
michael@0 86 do_throw("opened should not be called");
michael@0 87 },
michael@0 88
michael@0 89 closed: function (node, newState, oldState) {
michael@0 90 this.checkStateChanged("closed", 1);
michael@0 91 this.checkState("loading", 1);
michael@0 92 this.checkArgs("closed", node, oldState, node.STATE_LOADING);
michael@0 93 this.success();
michael@0 94 }
michael@0 95 }
michael@0 96 ];
michael@0 97
michael@0 98
michael@0 99 /**
michael@0 100 * Instances of this class become the prototypes of the test objects above.
michael@0 101 * Each test can therefore use the methods of this class, or they can override
michael@0 102 * them if they want. To run a test, call setup() and then run().
michael@0 103 */
michael@0 104 function Test() {
michael@0 105 // This maps a state name to the number of times it's been observed.
michael@0 106 this.stateCounts = {};
michael@0 107 // Promise object resolved when the next test can be run.
michael@0 108 this.deferNextTest = Promise.defer();
michael@0 109 }
michael@0 110
michael@0 111 Test.prototype = {
michael@0 112 /**
michael@0 113 * Call this when an observer observes a container state change to sanity
michael@0 114 * check the arguments.
michael@0 115 *
michael@0 116 * @param aNewState
michael@0 117 * The name of the new state. Used only for printing out helpful info.
michael@0 118 * @param aNode
michael@0 119 * The node argument passed to containerStateChanged.
michael@0 120 * @param aOldState
michael@0 121 * The old state argument passed to containerStateChanged.
michael@0 122 * @param aExpectOldState
michael@0 123 * The expected old state.
michael@0 124 */
michael@0 125 checkArgs: function (aNewState, aNode, aOldState, aExpectOldState) {
michael@0 126 print("Node passed on " + aNewState + " should be result.root");
michael@0 127 do_check_eq(this.result.root, aNode);
michael@0 128 print("Old state passed on " + aNewState + " should be " + aExpectOldState);
michael@0 129
michael@0 130 // aOldState comes from xpconnect and will therefore be defined. It may be
michael@0 131 // zero, though, so use strict equality just to make sure aExpectOldState is
michael@0 132 // also defined.
michael@0 133 do_check_true(aOldState === aExpectOldState);
michael@0 134 },
michael@0 135
michael@0 136 /**
michael@0 137 * Call this when an observer observes a container state change. It registers
michael@0 138 * the state change and ensures that it has been observed the given number
michael@0 139 * of times. See checkState for parameter explanations.
michael@0 140 *
michael@0 141 * @return The number of times aState has been observed, including the new
michael@0 142 * observation.
michael@0 143 */
michael@0 144 checkStateChanged: function (aState, aExpectedMin, aExpectedMax) {
michael@0 145 print(aState + " state change observed");
michael@0 146 if (!this.stateCounts.hasOwnProperty(aState))
michael@0 147 this.stateCounts[aState] = 0;
michael@0 148 this.stateCounts[aState]++;
michael@0 149 return this.checkState(aState, aExpectedMin, aExpectedMax);
michael@0 150 },
michael@0 151
michael@0 152 /**
michael@0 153 * Ensures that the state has been observed the given number of times.
michael@0 154 *
michael@0 155 * @param aState
michael@0 156 * The name of the state.
michael@0 157 * @param aExpectedMin
michael@0 158 * The state must have been observed at least this number of times.
michael@0 159 * @param aExpectedMax
michael@0 160 * The state must have been observed at most this number of times.
michael@0 161 * This parameter is optional. If undefined, it's set to
michael@0 162 * aExpectedMin.
michael@0 163 * @return The number of times aState has been observed, including the new
michael@0 164 * observation.
michael@0 165 */
michael@0 166 checkState: function (aState, aExpectedMin, aExpectedMax) {
michael@0 167 let cnt = this.stateCounts[aState] || 0;
michael@0 168 if (aExpectedMax === undefined)
michael@0 169 aExpectedMax = aExpectedMin;
michael@0 170 if (aExpectedMin === aExpectedMax) {
michael@0 171 print(aState + " should be observed only " + aExpectedMin +
michael@0 172 " times (actual = " + cnt + ")");
michael@0 173 }
michael@0 174 else {
michael@0 175 print(aState + " should be observed at least " + aExpectedMin +
michael@0 176 " times and at most " + aExpectedMax + " times (actual = " +
michael@0 177 cnt + ")");
michael@0 178 }
michael@0 179 do_check_true(cnt >= aExpectedMin && cnt <= aExpectedMax);
michael@0 180 return cnt;
michael@0 181 },
michael@0 182
michael@0 183 /**
michael@0 184 * Asynchronously opens the root of the test's result.
michael@0 185 */
michael@0 186 openContainer: function () {
michael@0 187 // Set up the result observer. It delegates to this object's callbacks and
michael@0 188 // wraps them in a try-catch so that errors don't get eaten.
michael@0 189 this.observer = let (self = this) {
michael@0 190 containerStateChanged: function (container, oldState, newState) {
michael@0 191 print("New state passed to containerStateChanged() should equal the " +
michael@0 192 "container's current state");
michael@0 193 do_check_eq(newState, container.state);
michael@0 194
michael@0 195 try {
michael@0 196 switch (newState) {
michael@0 197 case Ci.nsINavHistoryContainerResultNode.STATE_LOADING:
michael@0 198 self.loading(container, newState, oldState);
michael@0 199 break;
michael@0 200 case Ci.nsINavHistoryContainerResultNode.STATE_OPENED:
michael@0 201 self.opened(container, newState, oldState);
michael@0 202 break;
michael@0 203 case Ci.nsINavHistoryContainerResultNode.STATE_CLOSED:
michael@0 204 self.closed(container, newState, oldState);
michael@0 205 break;
michael@0 206 default:
michael@0 207 do_throw("Unexpected new state! " + newState);
michael@0 208 }
michael@0 209 }
michael@0 210 catch (err) {
michael@0 211 do_throw(err);
michael@0 212 }
michael@0 213 },
michael@0 214 };
michael@0 215 this.result.addObserver(this.observer, false);
michael@0 216
michael@0 217 print("Opening container");
michael@0 218 this.result.root.containerOpen = true;
michael@0 219 },
michael@0 220
michael@0 221 /**
michael@0 222 * Starts the test and returns a promise resolved when the test completes.
michael@0 223 */
michael@0 224 run: function () {
michael@0 225 this.openContainer();
michael@0 226 return this.deferNextTest.promise;
michael@0 227 },
michael@0 228
michael@0 229 /**
michael@0 230 * This must be called before run(). It adds a bookmark and sets up the
michael@0 231 * test's result. Override if need be.
michael@0 232 */
michael@0 233 setup: function () {
michael@0 234 // Populate the database with different types of bookmark items.
michael@0 235 this.data = DataHelper.makeDataArray([
michael@0 236 { type: "bookmark" },
michael@0 237 { type: "separator" },
michael@0 238 { type: "folder" },
michael@0 239 { type: "bookmark", uri: "place:terms=foo" }
michael@0 240 ]);
michael@0 241 yield task_populateDB(this.data);
michael@0 242
michael@0 243 // Make a query.
michael@0 244 this.query = PlacesUtils.history.getNewQuery();
michael@0 245 this.query.setFolders([DataHelper.defaults.bookmark.parent], 1);
michael@0 246 this.opts = PlacesUtils.history.getNewQueryOptions();
michael@0 247 this.opts.asyncEnabled = true;
michael@0 248 this.result = PlacesUtils.history.executeQuery(this.query, this.opts);
michael@0 249 },
michael@0 250
michael@0 251 /**
michael@0 252 * Call this when the test has succeeded. It cleans up resources and starts
michael@0 253 * the next test.
michael@0 254 */
michael@0 255 success: function () {
michael@0 256 this.result.removeObserver(this.observer);
michael@0 257
michael@0 258 // Resolve the promise object that indicates that the next test can be run.
michael@0 259 this.deferNextTest.resolve();
michael@0 260 }
michael@0 261 };
michael@0 262
michael@0 263 /**
michael@0 264 * This makes it a little bit easier to use the functions of head_queries.js.
michael@0 265 */
michael@0 266 let DataHelper = {
michael@0 267 defaults: {
michael@0 268 bookmark: {
michael@0 269 parent: PlacesUtils.bookmarks.unfiledBookmarksFolder,
michael@0 270 uri: "http://example.com/",
michael@0 271 title: "test bookmark"
michael@0 272 },
michael@0 273
michael@0 274 folder: {
michael@0 275 parent: PlacesUtils.bookmarks.unfiledBookmarksFolder,
michael@0 276 title: "test folder"
michael@0 277 },
michael@0 278
michael@0 279 separator: {
michael@0 280 parent: PlacesUtils.bookmarks.unfiledBookmarksFolder
michael@0 281 }
michael@0 282 },
michael@0 283
michael@0 284 /**
michael@0 285 * Converts an array of simple bookmark item descriptions to the more verbose
michael@0 286 * format required by task_populateDB() in head_queries.js.
michael@0 287 *
michael@0 288 * @param aData
michael@0 289 * An array of objects, each of which describes a bookmark item.
michael@0 290 * @return An array of objects suitable for passing to populateDB().
michael@0 291 */
michael@0 292 makeDataArray: function DH_makeDataArray(aData) {
michael@0 293 return let (self = this) aData.map(function (dat) {
michael@0 294 let type = dat.type;
michael@0 295 dat = self._makeDataWithDefaults(dat, self.defaults[type]);
michael@0 296 switch (type) {
michael@0 297 case "bookmark":
michael@0 298 return {
michael@0 299 isBookmark: true,
michael@0 300 uri: dat.uri,
michael@0 301 parentFolder: dat.parent,
michael@0 302 index: PlacesUtils.bookmarks.DEFAULT_INDEX,
michael@0 303 title: dat.title,
michael@0 304 isInQuery: true
michael@0 305 };
michael@0 306 case "separator":
michael@0 307 return {
michael@0 308 isSeparator: true,
michael@0 309 parentFolder: dat.parent,
michael@0 310 index: PlacesUtils.bookmarks.DEFAULT_INDEX,
michael@0 311 isInQuery: true
michael@0 312 };
michael@0 313 case "folder":
michael@0 314 return {
michael@0 315 isFolder: true,
michael@0 316 readOnly: false,
michael@0 317 parentFolder: dat.parent,
michael@0 318 index: PlacesUtils.bookmarks.DEFAULT_INDEX,
michael@0 319 title: dat.title,
michael@0 320 isInQuery: true
michael@0 321 };
michael@0 322 default:
michael@0 323 do_throw("Unknown data type when populating DB: " + type);
michael@0 324 }
michael@0 325 });
michael@0 326 },
michael@0 327
michael@0 328 /**
michael@0 329 * Returns a copy of aData, except that any properties that are undefined but
michael@0 330 * defined in aDefaults are set to the corresponding values in aDefaults.
michael@0 331 *
michael@0 332 * @param aData
michael@0 333 * An object describing a bookmark item.
michael@0 334 * @param aDefaults
michael@0 335 * An object describing the default bookmark item.
michael@0 336 * @return A copy of aData with defaults values set.
michael@0 337 */
michael@0 338 _makeDataWithDefaults: function DH__makeDataWithDefaults(aData, aDefaults) {
michael@0 339 let dat = {};
michael@0 340 for (let [prop, val] in Iterator(aDefaults)) {
michael@0 341 dat[prop] = aData.hasOwnProperty(prop) ? aData[prop] : val;
michael@0 342 }
michael@0 343 return dat;
michael@0 344 }
michael@0 345 };
michael@0 346
michael@0 347 function run_test()
michael@0 348 {
michael@0 349 run_next_test();
michael@0 350 }
michael@0 351
michael@0 352 add_task(function test_async()
michael@0 353 {
michael@0 354 for (let [, test] in Iterator(tests)) {
michael@0 355 remove_all_bookmarks();
michael@0 356
michael@0 357 test.__proto__ = new Test();
michael@0 358 yield test.setup();
michael@0 359
michael@0 360 print("------ Running test: " + test.desc);
michael@0 361 yield test.run();
michael@0 362 }
michael@0 363
michael@0 364 remove_all_bookmarks();
michael@0 365 print("All tests done, exiting");
michael@0 366 });

mercurial