test_UrlbarController_unit.js (9253B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 /** 5 * These tests unit test the functionality of UrlbarController by stubbing out the 6 * model and providing stubs to be called. 7 */ 8 9 "use strict"; 10 11 // A fake ProvidersManager. 12 let fPM; 13 let sandbox; 14 let generalListener; 15 let controller; 16 17 /** 18 * Asserts that the query context has the expected values. 19 * 20 * @param {UrlbarQueryContext} context The query context. 21 * @param {object} expectedValues The expected values for the UrlbarQueryContext. 22 */ 23 function assertContextMatches(context, expectedValues) { 24 Assert.ok( 25 context instanceof UrlbarQueryContext, 26 "Should be a UrlbarQueryContext" 27 ); 28 29 for (let [key, value] of Object.entries(expectedValues)) { 30 Assert.equal( 31 context[key], 32 value, 33 `Should have the expected value for ${key} in the UrlbarQueryContext` 34 ); 35 } 36 } 37 38 add_setup(function () { 39 sandbox = sinon.createSandbox(); 40 41 fPM = { 42 startQuery: sandbox.stub(), 43 cancelQuery: sandbox.stub(), 44 }; 45 46 generalListener = { 47 onQueryStarted: sandbox.stub(), 48 onQueryResults: sandbox.stub(), 49 onQueryCancelled: sandbox.stub(), 50 }; 51 52 controller = UrlbarTestUtils.newMockController({ 53 manager: fPM, 54 }); 55 controller.addListener(generalListener); 56 }); 57 58 add_task(function test_constructor_throws() { 59 Assert.throws( 60 () => new UrlbarController(), 61 /options is undefined/, 62 "Should throw if the input was not supplied" 63 ); 64 Assert.throws( 65 () => new UrlbarController({ input: {} }), 66 /input is missing 'window' property/, 67 "Should throw if the input is not a UrlbarInput" 68 ); 69 Assert.throws( 70 () => new UrlbarController({ input: { window: {} } }), 71 /input.window should be an actual browser window/, 72 "Should throw if the input.window is not a window" 73 ); 74 Assert.throws( 75 () => 76 new UrlbarController({ 77 input: { 78 window: { 79 location: "about:fake", 80 }, 81 }, 82 }), 83 /input.window should be an actual browser window/, 84 "Should throw if the input.window is not an object" 85 ); 86 Assert.throws( 87 () => 88 new UrlbarController({ 89 input: { 90 window: { 91 location: { 92 href: "about:fake", 93 }, 94 }, 95 }, 96 }), 97 /input.window should be an actual browser window/, 98 "Should throw if the input.window does not have the correct location" 99 ); 100 Assert.throws( 101 () => 102 new UrlbarController({ 103 input: { 104 window: { 105 location: { 106 href: AppConstants.BROWSER_CHROME_URL, 107 }, 108 }, 109 }, 110 }), 111 /input.isPrivate must be set/, 112 "Should throw if input.isPrivate is not set" 113 ); 114 115 new UrlbarController({ 116 input: { 117 isPrivate: false, 118 window: { 119 location: { 120 href: AppConstants.BROWSER_CHROME_URL, 121 }, 122 }, 123 }, 124 }); 125 Assert.ok(true, "Correct call should not throw"); 126 }); 127 128 add_task(function test_add_and_remove_listeners() { 129 Assert.throws( 130 () => controller.addListener(null), 131 /Expected listener to be an object/, 132 "Should throw for a null listener" 133 ); 134 Assert.throws( 135 () => controller.addListener(123), 136 /Expected listener to be an object/, 137 "Should throw for a non-object listener" 138 ); 139 140 const listener = {}; 141 142 controller.addListener(listener); 143 144 Assert.ok( 145 controller._listeners.has(listener), 146 "Should have added the listener to the list." 147 ); 148 149 // Adding a non-existent listener shouldn't throw. 150 controller.removeListener(123); 151 152 controller.removeListener(listener); 153 154 Assert.ok( 155 !controller._listeners.has(listener), 156 "Should have removed the listener from the list" 157 ); 158 159 sandbox.resetHistory(); 160 }); 161 162 add_task(function test__notify() { 163 const listener1 = { 164 onFake: sandbox.stub().callsFake(() => { 165 throw new Error("fake error"); 166 }), 167 }; 168 const listener2 = { 169 onFake: sandbox.stub(), 170 }; 171 172 controller.addListener(listener1); 173 controller.addListener(listener2); 174 175 const param = "1234"; 176 177 controller.notify("onFake", param); 178 179 Assert.equal( 180 listener1.onFake.callCount, 181 1, 182 "Should have called the first listener method." 183 ); 184 Assert.deepEqual( 185 listener1.onFake.args[0], 186 [param], 187 "Should have called the first listener with the correct argument" 188 ); 189 Assert.equal( 190 listener2.onFake.callCount, 191 1, 192 "Should have called the second listener method." 193 ); 194 Assert.deepEqual( 195 listener2.onFake.args[0], 196 [param], 197 "Should have called the first listener with the correct argument" 198 ); 199 200 controller.removeListener(listener2); 201 controller.removeListener(listener1); 202 203 // This should succeed without errors. 204 controller.notify("onNewFake"); 205 206 sandbox.resetHistory(); 207 }); 208 209 add_task(function test_handle_query_starts_search() { 210 const context = createContext(); 211 controller.startQuery(context); 212 213 Assert.equal( 214 fPM.startQuery.callCount, 215 1, 216 "Should have called startQuery once" 217 ); 218 Assert.equal( 219 fPM.startQuery.args[0].length, 220 2, 221 "Should have called startQuery with two arguments" 222 ); 223 224 assertContextMatches(fPM.startQuery.args[0][0], {}); 225 Assert.equal( 226 fPM.startQuery.args[0][1], 227 controller, 228 "Should have passed the controller as the second argument" 229 ); 230 231 Assert.equal( 232 generalListener.onQueryStarted.callCount, 233 1, 234 "Should have called onQueryStarted for the listener" 235 ); 236 Assert.deepEqual( 237 generalListener.onQueryStarted.args[0], 238 [context], 239 "Should have called onQueryStarted with the context" 240 ); 241 242 sandbox.resetHistory(); 243 }); 244 245 add_task(async function test_handle_query_starts_search_sets_allowAutofill() { 246 let originalValue = Services.prefs.getBoolPref("browser.urlbar.autoFill"); 247 Services.prefs.setBoolPref("browser.urlbar.autoFill", !originalValue); 248 249 await controller.startQuery(createContext()); 250 251 Assert.equal( 252 fPM.startQuery.callCount, 253 1, 254 "Should have called startQuery once" 255 ); 256 Assert.equal( 257 fPM.startQuery.args[0].length, 258 2, 259 "Should have called startQuery with two arguments" 260 ); 261 262 assertContextMatches(fPM.startQuery.args[0][0], { 263 allowAutofill: !originalValue, 264 }); 265 Assert.equal( 266 fPM.startQuery.args[0][1], 267 controller, 268 "Should have passed the controller as the second argument" 269 ); 270 271 sandbox.resetHistory(); 272 273 Services.prefs.clearUserPref("browser.urlbar.autoFill"); 274 }); 275 276 add_task(function test_cancel_query() { 277 const context = createContext(); 278 controller.startQuery(context); 279 280 controller.cancelQuery(); 281 282 Assert.equal( 283 fPM.cancelQuery.callCount, 284 1, 285 "Should have called cancelQuery once" 286 ); 287 Assert.equal( 288 fPM.cancelQuery.args[0].length, 289 1, 290 "Should have called cancelQuery with one argument" 291 ); 292 293 Assert.equal( 294 generalListener.onQueryCancelled.callCount, 295 1, 296 "Should have called onQueryCancelled for the listener" 297 ); 298 Assert.deepEqual( 299 generalListener.onQueryCancelled.args[0], 300 [context], 301 "Should have called onQueryCancelled with the context" 302 ); 303 304 sandbox.resetHistory(); 305 }); 306 307 add_task(function test_receiveResults() { 308 const context = createContext(); 309 context.results = []; 310 controller.receiveResults(context); 311 312 Assert.equal( 313 generalListener.onQueryResults.callCount, 314 1, 315 "Should have called onQueryResults for the listener" 316 ); 317 Assert.deepEqual( 318 generalListener.onQueryResults.args[0], 319 [context], 320 "Should have called onQueryResults with the context" 321 ); 322 323 sandbox.resetHistory(); 324 }); 325 326 add_task(async function test_notifications_order() { 327 // Clear any pending notifications. 328 const context = createContext(); 329 await controller.startQuery(context); 330 331 // Check that when multiple queries are executed, the notifications arrive 332 // in the proper order. 333 let collectingListener = new Proxy( 334 {}, 335 { 336 _notifications: [], 337 get(target, name) { 338 if (name == "notifications") { 339 return this._notifications; 340 } 341 return () => { 342 this._notifications.push(name); 343 }; 344 }, 345 } 346 ); 347 controller.addListener(collectingListener); 348 controller.startQuery(context); 349 Assert.deepEqual( 350 ["onQueryStarted"], 351 collectingListener.notifications, 352 "Check onQueryStarted is fired synchronously" 353 ); 354 controller.startQuery(context); 355 Assert.deepEqual( 356 ["onQueryStarted", "onQueryCancelled", "onQueryFinished", "onQueryStarted"], 357 collectingListener.notifications, 358 "Check order of notifications" 359 ); 360 controller.cancelQuery(); 361 Assert.deepEqual( 362 [ 363 "onQueryStarted", 364 "onQueryCancelled", 365 "onQueryFinished", 366 "onQueryStarted", 367 "onQueryCancelled", 368 "onQueryFinished", 369 ], 370 collectingListener.notifications, 371 "Check order of notifications" 372 ); 373 await controller.startQuery(context); 374 controller.cancelQuery(); 375 Assert.deepEqual( 376 [ 377 "onQueryStarted", 378 "onQueryCancelled", 379 "onQueryFinished", 380 "onQueryStarted", 381 "onQueryCancelled", 382 "onQueryFinished", 383 "onQueryStarted", 384 "onQueryFinished", 385 ], 386 collectingListener.notifications, 387 "Check order of notifications" 388 ); 389 });