/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * These tests unit test the functionality of UrlbarController by stubbing out the * model and providing stubs to be called. */ "use strict"; // A fake ProvidersManager. let fPM; let sandbox; let generalListener; let controller; /** * Asserts that the query context has the expected values. * * @param {UrlbarQueryContext} context The query context. * @param {object} expectedValues The expected values for the UrlbarQueryContext. */ function assertContextMatches(context, expectedValues) { Assert.ok( context instanceof UrlbarQueryContext, "Should be a UrlbarQueryContext" ); for (let [key, value] of Object.entries(expectedValues)) { Assert.equal( context[key], value, `Should have the expected value for ${key} in the UrlbarQueryContext` ); } } add_task(function setup() { sandbox = sinon.createSandbox(); fPM = { startQuery: sandbox.stub(), cancelQuery: sandbox.stub(), }; generalListener = { onQueryStarted: sandbox.stub(), onQueryResults: sandbox.stub(), onQueryCancelled: sandbox.stub(), }; controller = UrlbarTestUtils.newMockController({ manager: fPM, }); controller.addQueryListener(generalListener); }); add_task(function test_constructor_throws() { Assert.throws( () => new UrlbarController(), /Missing options: input/, "Should throw if the input was not supplied" ); Assert.throws( () => new UrlbarController({ input: {} }), /input is missing 'window' property/, "Should throw if the input is not a UrlbarInput" ); Assert.throws( () => new UrlbarController({ input: { window: {} } }), /input.window should be an actual browser window/, "Should throw if the input.window is not a window" ); Assert.throws( () => new UrlbarController({ input: { window: { location: "about:fake", }, }, }), /input.window should be an actual browser window/, "Should throw if the input.window is not an object" ); Assert.throws( () => new UrlbarController({ input: { window: { location: { href: "about:fake", }, }, }, }), /input.window should be an actual browser window/, "Should throw if the input.window does not have the correct location" ); Assert.throws( () => new UrlbarController({ input: { window: { location: { href: AppConstants.BROWSER_CHROME_URL, }, }, }, }), /input.isPrivate must be set/, "Should throw if input.isPrivate is not set" ); new UrlbarController({ input: { isPrivate: false, window: { location: { href: AppConstants.BROWSER_CHROME_URL, }, }, }, }); Assert.ok(true, "Correct call should not throw"); }); add_task(function test_add_and_remove_listeners() { Assert.throws( () => controller.addQueryListener(null), /Expected listener to be an object/, "Should throw for a null listener" ); Assert.throws( () => controller.addQueryListener(123), /Expected listener to be an object/, "Should throw for a non-object listener" ); const listener = {}; controller.addQueryListener(listener); Assert.ok( controller._listeners.has(listener), "Should have added the listener to the list." ); // Adding a non-existent listener shouldn't throw. controller.removeQueryListener(123); controller.removeQueryListener(listener); Assert.ok( !controller._listeners.has(listener), "Should have removed the listener from the list" ); sandbox.resetHistory(); }); add_task(function test__notify() { const listener1 = { onFake: sandbox.stub().callsFake(() => { throw new Error("fake error"); }), }; const listener2 = { onFake: sandbox.stub(), }; controller.addQueryListener(listener1); controller.addQueryListener(listener2); const param = "1234"; controller.notify("onFake", param); Assert.equal( listener1.onFake.callCount, 1, "Should have called the first listener method." ); Assert.deepEqual( listener1.onFake.args[0], [param], "Should have called the first listener with the correct argument" ); Assert.equal( listener2.onFake.callCount, 1, "Should have called the second listener method." ); Assert.deepEqual( listener2.onFake.args[0], [param], "Should have called the first listener with the correct argument" ); controller.removeQueryListener(listener2); controller.removeQueryListener(listener1); // This should succeed without errors. controller.notify("onNewFake"); sandbox.resetHistory(); }); add_task(function test_handle_query_starts_search() { const context = createContext(); controller.startQuery(context); Assert.equal( fPM.startQuery.callCount, 1, "Should have called startQuery once" ); Assert.equal( fPM.startQuery.args[0].length, 2, "Should have called startQuery with two arguments" ); assertContextMatches(fPM.startQuery.args[0][0], {}); Assert.equal( fPM.startQuery.args[0][1], controller, "Should have passed the controller as the second argument" ); Assert.equal( generalListener.onQueryStarted.callCount, 1, "Should have called onQueryStarted for the listener" ); Assert.deepEqual( generalListener.onQueryStarted.args[0], [context], "Should have called onQueryStarted with the context" ); sandbox.resetHistory(); }); add_task(async function test_handle_query_starts_search_sets_allowAutofill() { let originalValue = Services.prefs.getBoolPref("browser.urlbar.autoFill"); Services.prefs.setBoolPref("browser.urlbar.autoFill", !originalValue); await controller.startQuery(createContext()); Assert.equal( fPM.startQuery.callCount, 1, "Should have called startQuery once" ); Assert.equal( fPM.startQuery.args[0].length, 2, "Should have called startQuery with two arguments" ); assertContextMatches(fPM.startQuery.args[0][0], { allowAutofill: !originalValue, }); Assert.equal( fPM.startQuery.args[0][1], controller, "Should have passed the controller as the second argument" ); sandbox.resetHistory(); Services.prefs.clearUserPref("browser.urlbar.autoFill"); }); add_task(function test_cancel_query() { const context = createContext(); controller.startQuery(context); controller.cancelQuery(); Assert.equal( fPM.cancelQuery.callCount, 1, "Should have called cancelQuery once" ); Assert.equal( fPM.cancelQuery.args[0].length, 1, "Should have called cancelQuery with one argument" ); Assert.equal( generalListener.onQueryCancelled.callCount, 1, "Should have called onQueryCancelled for the listener" ); Assert.deepEqual( generalListener.onQueryCancelled.args[0], [context], "Should have called onQueryCancelled with the context" ); sandbox.resetHistory(); }); add_task(function test_receiveResults() { const context = createContext(); context.results = []; controller.receiveResults(context); Assert.equal( generalListener.onQueryResults.callCount, 1, "Should have called onQueryResults for the listener" ); Assert.deepEqual( generalListener.onQueryResults.args[0], [context], "Should have called onQueryResults with the context" ); sandbox.resetHistory(); }); add_task(async function test_notifications_order() { // Clear any pending notifications. const context = createContext(); await controller.startQuery(context); // Check that when multiple queries are executed, the notifications arrive // in the proper order. let collectingListener = new Proxy( {}, { _notifications: [], get(target, name) { if (name == "notifications") { return this._notifications; } return () => { this._notifications.push(name); }; }, } ); controller.addQueryListener(collectingListener); controller.startQuery(context); Assert.deepEqual( ["onQueryStarted"], collectingListener.notifications, "Check onQueryStarted is fired synchronously" ); controller.startQuery(context); Assert.deepEqual( ["onQueryStarted", "onQueryCancelled", "onQueryFinished", "onQueryStarted"], collectingListener.notifications, "Check order of notifications" ); controller.cancelQuery(); Assert.deepEqual( [ "onQueryStarted", "onQueryCancelled", "onQueryFinished", "onQueryStarted", "onQueryCancelled", "onQueryFinished", ], collectingListener.notifications, "Check order of notifications" ); await controller.startQuery(context); controller.cancelQuery(); Assert.deepEqual( [ "onQueryStarted", "onQueryCancelled", "onQueryFinished", "onQueryStarted", "onQueryCancelled", "onQueryFinished", "onQueryStarted", "onQueryFinished", ], collectingListener.notifications, "Check order of notifications" ); });