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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/places/tests/queries/test_async.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,366 @@
     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 +let tests = [
    1.11 +  {
    1.12 +    desc: "nsNavHistoryFolderResultNode: Basic test, asynchronously open and " +
    1.13 +          "close container with a single child",
    1.14 +
    1.15 +    loading: function (node, newState, oldState) {
    1.16 +      this.checkStateChanged("loading", 1);
    1.17 +      this.checkArgs("loading", node, oldState, node.STATE_CLOSED);
    1.18 +    },
    1.19 +
    1.20 +    opened: function (node, newState, oldState) {
    1.21 +      this.checkStateChanged("opened", 1);
    1.22 +      this.checkState("loading", 1);
    1.23 +      this.checkArgs("opened", node, oldState, node.STATE_LOADING);
    1.24 +
    1.25 +      print("Checking node children");
    1.26 +      compareArrayToResult(this.data, node);
    1.27 +
    1.28 +      print("Closing container");
    1.29 +      node.containerOpen = false;
    1.30 +    },
    1.31 +
    1.32 +    closed: function (node, newState, oldState) {
    1.33 +      this.checkStateChanged("closed", 1);
    1.34 +      this.checkState("opened", 1);
    1.35 +      this.checkArgs("closed", node, oldState, node.STATE_OPENED);
    1.36 +      this.success();
    1.37 +    }
    1.38 +  },
    1.39 +
    1.40 +  {
    1.41 +    desc: "nsNavHistoryFolderResultNode: After async open and no changes, " +
    1.42 +          "second open should be synchronous",
    1.43 +
    1.44 +    loading: function (node, newState, oldState) {
    1.45 +      this.checkStateChanged("loading", 1);
    1.46 +      this.checkState("closed", 0);
    1.47 +      this.checkArgs("loading", node, oldState, node.STATE_CLOSED);
    1.48 +    },
    1.49 +
    1.50 +    opened: function (node, newState, oldState) {
    1.51 +      let cnt = this.checkStateChanged("opened", 1, 2);
    1.52 +      let expectOldState = cnt === 1 ? node.STATE_LOADING : node.STATE_CLOSED;
    1.53 +      this.checkArgs("opened", node, oldState, expectOldState);
    1.54 +
    1.55 +      print("Checking node children");
    1.56 +      compareArrayToResult(this.data, node);
    1.57 +
    1.58 +      print("Closing container");
    1.59 +      node.containerOpen = false;
    1.60 +    },
    1.61 +
    1.62 +    closed: function (node, newState, oldState) {
    1.63 +      let cnt = this.checkStateChanged("closed", 1, 2);
    1.64 +      this.checkArgs("closed", node, oldState, node.STATE_OPENED);
    1.65 +
    1.66 +      switch (cnt) {
    1.67 +      case 1:
    1.68 +        node.containerOpen = true;
    1.69 +        break;
    1.70 +      case 2:
    1.71 +        this.success();
    1.72 +        break;
    1.73 +      }
    1.74 +    }
    1.75 +  },
    1.76 +
    1.77 +  {
    1.78 +    desc: "nsNavHistoryFolderResultNode: After closing container in " +
    1.79 +          "loading(), opened() should not be called",
    1.80 +
    1.81 +    loading: function (node, newState, oldState) {
    1.82 +      this.checkStateChanged("loading", 1);
    1.83 +      this.checkArgs("loading", node, oldState, node.STATE_CLOSED);
    1.84 +      print("Closing container");
    1.85 +      node.containerOpen = false;
    1.86 +    },
    1.87 +
    1.88 +    opened: function (node, newState, oldState) {
    1.89 +      do_throw("opened should not be called");
    1.90 +    },
    1.91 +
    1.92 +    closed: function (node, newState, oldState) {
    1.93 +      this.checkStateChanged("closed", 1);
    1.94 +      this.checkState("loading", 1);
    1.95 +      this.checkArgs("closed", node, oldState, node.STATE_LOADING);
    1.96 +      this.success();
    1.97 +    }
    1.98 +  }
    1.99 +];
   1.100 +
   1.101 +
   1.102 +/**
   1.103 + * Instances of this class become the prototypes of the test objects above.
   1.104 + * Each test can therefore use the methods of this class, or they can override
   1.105 + * them if they want.  To run a test, call setup() and then run().
   1.106 + */
   1.107 +function Test() {
   1.108 +  // This maps a state name to the number of times it's been observed.
   1.109 +  this.stateCounts = {};
   1.110 +  // Promise object resolved when the next test can be run.
   1.111 +  this.deferNextTest = Promise.defer();
   1.112 +}
   1.113 +
   1.114 +Test.prototype = {
   1.115 +  /**
   1.116 +   * Call this when an observer observes a container state change to sanity
   1.117 +   * check the arguments.
   1.118 +   *
   1.119 +   * @param aNewState
   1.120 +   *        The name of the new state.  Used only for printing out helpful info.
   1.121 +   * @param aNode
   1.122 +   *        The node argument passed to containerStateChanged.
   1.123 +   * @param aOldState
   1.124 +   *        The old state argument passed to containerStateChanged.
   1.125 +   * @param aExpectOldState
   1.126 +   *        The expected old state.
   1.127 +   */
   1.128 +  checkArgs: function (aNewState, aNode, aOldState, aExpectOldState) {
   1.129 +    print("Node passed on " + aNewState + " should be result.root");
   1.130 +    do_check_eq(this.result.root, aNode);
   1.131 +    print("Old state passed on " + aNewState + " should be " + aExpectOldState);
   1.132 +
   1.133 +    // aOldState comes from xpconnect and will therefore be defined.  It may be
   1.134 +    // zero, though, so use strict equality just to make sure aExpectOldState is
   1.135 +    // also defined.
   1.136 +    do_check_true(aOldState === aExpectOldState);
   1.137 +  },
   1.138 +
   1.139 +  /**
   1.140 +   * Call this when an observer observes a container state change.  It registers
   1.141 +   * the state change and ensures that it has been observed the given number
   1.142 +   * of times.  See checkState for parameter explanations.
   1.143 +   *
   1.144 +   * @return The number of times aState has been observed, including the new
   1.145 +   *         observation.
   1.146 +   */
   1.147 +  checkStateChanged: function (aState, aExpectedMin, aExpectedMax) {
   1.148 +    print(aState + " state change observed");
   1.149 +    if (!this.stateCounts.hasOwnProperty(aState))
   1.150 +      this.stateCounts[aState] = 0;
   1.151 +    this.stateCounts[aState]++;
   1.152 +    return this.checkState(aState, aExpectedMin, aExpectedMax);
   1.153 +  },
   1.154 +
   1.155 +  /**
   1.156 +   * Ensures that the state has been observed the given number of times.
   1.157 +   *
   1.158 +   * @param  aState
   1.159 +   *         The name of the state.
   1.160 +   * @param  aExpectedMin
   1.161 +   *         The state must have been observed at least this number of times.
   1.162 +   * @param  aExpectedMax
   1.163 +   *         The state must have been observed at most this number of times.
   1.164 +   *         This parameter is optional.  If undefined, it's set to
   1.165 +   *         aExpectedMin.
   1.166 +   * @return The number of times aState has been observed, including the new
   1.167 +   *         observation.
   1.168 +   */
   1.169 +  checkState: function (aState, aExpectedMin, aExpectedMax) {
   1.170 +    let cnt = this.stateCounts[aState] || 0;
   1.171 +    if (aExpectedMax === undefined)
   1.172 +      aExpectedMax = aExpectedMin;
   1.173 +    if (aExpectedMin === aExpectedMax) {
   1.174 +      print(aState + " should be observed only " + aExpectedMin +
   1.175 +            " times (actual = " + cnt + ")");
   1.176 +    }
   1.177 +    else {
   1.178 +      print(aState + " should be observed at least " + aExpectedMin +
   1.179 +            " times and at most " + aExpectedMax + " times (actual = " +
   1.180 +            cnt + ")");
   1.181 +    }
   1.182 +    do_check_true(cnt >= aExpectedMin && cnt <= aExpectedMax);
   1.183 +    return cnt;
   1.184 +  },
   1.185 +
   1.186 +  /**
   1.187 +   * Asynchronously opens the root of the test's result.
   1.188 +   */
   1.189 +  openContainer: function () {
   1.190 +    // Set up the result observer.  It delegates to this object's callbacks and
   1.191 +    // wraps them in a try-catch so that errors don't get eaten.
   1.192 +    this.observer = let (self = this) {
   1.193 +      containerStateChanged: function (container, oldState, newState) {
   1.194 +        print("New state passed to containerStateChanged() should equal the " +
   1.195 +              "container's current state");
   1.196 +        do_check_eq(newState, container.state);
   1.197 +
   1.198 +        try {
   1.199 +          switch (newState) {
   1.200 +          case Ci.nsINavHistoryContainerResultNode.STATE_LOADING:
   1.201 +            self.loading(container, newState, oldState);
   1.202 +            break;
   1.203 +          case Ci.nsINavHistoryContainerResultNode.STATE_OPENED:
   1.204 +            self.opened(container, newState, oldState);
   1.205 +            break;
   1.206 +          case Ci.nsINavHistoryContainerResultNode.STATE_CLOSED:
   1.207 +            self.closed(container, newState, oldState);
   1.208 +            break;
   1.209 +          default:
   1.210 +            do_throw("Unexpected new state! " + newState);
   1.211 +          }
   1.212 +        }
   1.213 +        catch (err) {
   1.214 +          do_throw(err);
   1.215 +        }
   1.216 +      },
   1.217 +    };
   1.218 +    this.result.addObserver(this.observer, false);
   1.219 +
   1.220 +    print("Opening container");
   1.221 +    this.result.root.containerOpen = true;
   1.222 +  },
   1.223 +
   1.224 +  /**
   1.225 +   * Starts the test and returns a promise resolved when the test completes.
   1.226 +   */
   1.227 +  run: function () {
   1.228 +    this.openContainer();
   1.229 +    return this.deferNextTest.promise;
   1.230 +  },
   1.231 +
   1.232 +  /**
   1.233 +   * This must be called before run().  It adds a bookmark and sets up the
   1.234 +   * test's result.  Override if need be.
   1.235 +   */
   1.236 +  setup: function () {
   1.237 +    // Populate the database with different types of bookmark items.
   1.238 +    this.data = DataHelper.makeDataArray([
   1.239 +      { type: "bookmark" },
   1.240 +      { type: "separator" },
   1.241 +      { type: "folder" },
   1.242 +      { type: "bookmark", uri: "place:terms=foo" }
   1.243 +    ]);
   1.244 +    yield task_populateDB(this.data);
   1.245 +
   1.246 +    // Make a query.
   1.247 +    this.query = PlacesUtils.history.getNewQuery();
   1.248 +    this.query.setFolders([DataHelper.defaults.bookmark.parent], 1);
   1.249 +    this.opts = PlacesUtils.history.getNewQueryOptions();
   1.250 +    this.opts.asyncEnabled = true;
   1.251 +    this.result = PlacesUtils.history.executeQuery(this.query, this.opts);
   1.252 +  },
   1.253 +
   1.254 +  /**
   1.255 +   * Call this when the test has succeeded.  It cleans up resources and starts
   1.256 +   * the next test.
   1.257 +   */
   1.258 +  success: function () {
   1.259 +    this.result.removeObserver(this.observer);
   1.260 +
   1.261 +    // Resolve the promise object that indicates that the next test can be run.
   1.262 +    this.deferNextTest.resolve();
   1.263 +  }
   1.264 +};
   1.265 +
   1.266 +/**
   1.267 + * This makes it a little bit easier to use the functions of head_queries.js.
   1.268 + */
   1.269 +let DataHelper = {
   1.270 +  defaults: {
   1.271 +    bookmark: {
   1.272 +      parent: PlacesUtils.bookmarks.unfiledBookmarksFolder,
   1.273 +      uri: "http://example.com/",
   1.274 +      title: "test bookmark"
   1.275 +    },
   1.276 +
   1.277 +    folder: {
   1.278 +      parent: PlacesUtils.bookmarks.unfiledBookmarksFolder,
   1.279 +      title: "test folder"
   1.280 +    },
   1.281 +
   1.282 +    separator: {
   1.283 +      parent: PlacesUtils.bookmarks.unfiledBookmarksFolder
   1.284 +    }
   1.285 +  },
   1.286 +
   1.287 +  /**
   1.288 +   * Converts an array of simple bookmark item descriptions to the more verbose
   1.289 +   * format required by task_populateDB() in head_queries.js.
   1.290 +   *
   1.291 +   * @param  aData
   1.292 +   *         An array of objects, each of which describes a bookmark item.
   1.293 +   * @return An array of objects suitable for passing to populateDB().
   1.294 +   */
   1.295 +  makeDataArray: function DH_makeDataArray(aData) {
   1.296 +    return let (self = this) aData.map(function (dat) {
   1.297 +      let type = dat.type;
   1.298 +      dat = self._makeDataWithDefaults(dat, self.defaults[type]);
   1.299 +      switch (type) {
   1.300 +      case "bookmark":
   1.301 +        return {
   1.302 +          isBookmark: true,
   1.303 +          uri: dat.uri,
   1.304 +          parentFolder: dat.parent,
   1.305 +          index: PlacesUtils.bookmarks.DEFAULT_INDEX,
   1.306 +          title: dat.title,
   1.307 +          isInQuery: true
   1.308 +        };
   1.309 +      case "separator":
   1.310 +        return {
   1.311 +          isSeparator: true,
   1.312 +          parentFolder: dat.parent,
   1.313 +          index: PlacesUtils.bookmarks.DEFAULT_INDEX,
   1.314 +          isInQuery: true
   1.315 +        };
   1.316 +      case "folder":
   1.317 +        return {
   1.318 +          isFolder: true,
   1.319 +          readOnly: false,
   1.320 +          parentFolder: dat.parent,
   1.321 +          index: PlacesUtils.bookmarks.DEFAULT_INDEX,
   1.322 +          title: dat.title,
   1.323 +          isInQuery: true
   1.324 +        };
   1.325 +      default:
   1.326 +        do_throw("Unknown data type when populating DB: " + type);
   1.327 +      }
   1.328 +    });
   1.329 +  },
   1.330 +
   1.331 +  /**
   1.332 +   * Returns a copy of aData, except that any properties that are undefined but
   1.333 +   * defined in aDefaults are set to the corresponding values in aDefaults.
   1.334 +   *
   1.335 +   * @param  aData
   1.336 +   *         An object describing a bookmark item.
   1.337 +   * @param  aDefaults
   1.338 +   *         An object describing the default bookmark item.
   1.339 +   * @return A copy of aData with defaults values set.
   1.340 +   */
   1.341 +  _makeDataWithDefaults: function DH__makeDataWithDefaults(aData, aDefaults) {
   1.342 +    let dat = {};
   1.343 +    for (let [prop, val] in Iterator(aDefaults)) {
   1.344 +      dat[prop] = aData.hasOwnProperty(prop) ? aData[prop] : val;
   1.345 +    }
   1.346 +    return dat;
   1.347 +  }
   1.348 +};
   1.349 +
   1.350 +function run_test()
   1.351 +{
   1.352 +  run_next_test();
   1.353 +}
   1.354 +
   1.355 +add_task(function test_async()
   1.356 +{
   1.357 +  for (let [, test] in Iterator(tests)) {
   1.358 +    remove_all_bookmarks();
   1.359 +
   1.360 +    test.__proto__ = new Test();
   1.361 +    yield test.setup();
   1.362 +
   1.363 +    print("------ Running test: " + test.desc);
   1.364 +    yield test.run();
   1.365 +  }
   1.366 +
   1.367 +  remove_all_bookmarks();
   1.368 +  print("All tests done, exiting");
   1.369 +});

mercurial