Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 | }); |