tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit e39b68fb2b2453ed0d962a7bffc8b273359c3618
parent 4407671260579ab82834a81c18fe72d8f59c77f5
Author: Reem H <42309026+reemhamz@users.noreply.github.com>
Date:   Wed,  8 Oct 2025 05:57:51 +0000

Bug 1992297 - Add unit tests for weather opt-in feature. r=home-newtab-reviewers,npypchenko

Differential Revision: https://phabricator.services.mozilla.com/D267552

Diffstat:
Mbrowser/extensions/newtab/content-src/components/Weather/Weather.jsx | 2++
Mbrowser/extensions/newtab/karma.mc.config.js | 11+++++++----
Abrowser/extensions/newtab/test/unit/content-src/components/Weather.test.jsx | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 174 insertions(+), 4 deletions(-)

diff --git a/browser/extensions/newtab/content-src/components/Weather/Weather.jsx b/browser/extensions/newtab/content-src/components/Weather/Weather.jsx @@ -471,12 +471,14 @@ export class _Weather extends React.PureComponent { type="default" data-l10n-id="newtab-weather-opt-in-not-now" onClick={this.handleRejectOptIn} + id="reject-opt-in" /> <moz-button size="small" type="default" data-l10n-id="newtab-weather-opt-in-yes" onClick={this.handleAcceptOptIn} + id="accept-opt-in" /> </moz-button-group> </div> diff --git a/browser/extensions/newtab/karma.mc.config.js b/browser/extensions/newtab/karma.mc.config.js @@ -304,10 +304,13 @@ module.exports = function (config) { functions: 0, branches: 0, }, - /** - * Weather.jsx is tested via an xpcshell test - */ - "content-src/components/Weather/*.jsx": { + "content-src/components/Weather/Weather.jsx": { + statements: 51.1, + lines: 52.38, + functions: 31.2, + branches: 31.2, + }, + "content-src/components/Weather/LocationSearch.jsx": { statements: 0, lines: 0, functions: 0, diff --git a/browser/extensions/newtab/test/unit/content-src/components/Weather.test.jsx b/browser/extensions/newtab/test/unit/content-src/components/Weather.test.jsx @@ -0,0 +1,165 @@ +import React from "react"; +import { mount } from "enzyme"; +import { Provider } from "react-redux"; +import { INITIAL_STATE, reducers } from "common/Reducers.sys.mjs"; +import { combineReducers, createStore } from "redux"; +import { Weather } from "content-src/components/Weather/Weather"; +import { actionTypes as at } from "common/Actions.mjs"; +import { LinkMenu } from "content-src/components/LinkMenu/LinkMenu"; + +const PREF_SYS_SHOW_WEATHER = "system.showWeather"; +const PREF_SYS_SHOW_WEATHER_OPT_IN = "system.showWeatherOptIn"; +const PREF_OPT_IN_DISPLAYED = "weather.optInDisplayed"; +const PREF_OPT_IN_ACCEPTED = "weather.optInAccepted"; +const PREF_STATIC_WEATHER_DATA = "weather.staticData.enabled"; + +// keeps initialize = true and provides fake suggestion + location data +// so the component skips <WeatherPlaceholder>. +const weatherInit = { + initialized: true, + suggestions: [ + { + forecast: { url: "https://example.com" }, + current_conditions: { + temperature: { c: 22, f: 72 }, + icon_id: 3, + summary: "Sunny", + }, + }, + ], + locationData: { city: "Testville" }, +}; + +// base mockState for general Weather-rendering tests. +// Opt-in is disabled here since it's only shown in specific locations +const mockState = { + ...INITIAL_STATE, + Prefs: { + ...INITIAL_STATE.Prefs, + values: { + ...INITIAL_STATE.Prefs.values, + [PREF_SYS_SHOW_WEATHER]: true, + [PREF_SYS_SHOW_WEATHER_OPT_IN]: false, + }, + }, + Weather: { ...weatherInit }, +}; + +// mock state for opt-in prompt tests. +// Ensures the opt-in dialog appears by default. +const optInMockState = { + ...mockState, + Prefs: { + ...mockState.Prefs, + values: { + ...mockState.Prefs.values, + showWeather: true, + [PREF_SYS_SHOW_WEATHER_OPT_IN]: true, + [PREF_OPT_IN_DISPLAYED]: true, + [PREF_OPT_IN_ACCEPTED]: false, + [PREF_STATIC_WEATHER_DATA]: true, + "weather.locationSearchEnabled": true, + "weather.display": "simple", + "weather.temperatureUnits": "c", + }, + }, +}; + +function WrapWithProvider({ children, state = INITIAL_STATE }) { + const store = createStore(combineReducers(reducers), state); + return <Provider store={store}>{children}</Provider>; +} + +describe("<Weather>", () => { + let wrapper; + let sandbox; + let dispatch; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + dispatch = sandbox.stub(); + }); + + afterEach(() => { + sandbox.restore(); + wrapper?.unmount(); + }); + + it("should render and show <Weather> if the `system.showWeather` pref is enabled", () => { + wrapper = mount( + <WrapWithProvider state={mockState}> + <Weather dispatch={dispatch} /> + </WrapWithProvider> + ); + assert.ok(wrapper.exists()); + assert.ok(wrapper.find(".weather").exists()); + }); + + describe("Opt-in prompt actions", () => { + it("should dispatch correct actions when user accepts weather opt-in", () => { + const store = createStore(combineReducers(reducers), optInMockState); + sinon.spy(store, "dispatch"); + + wrapper = mount( + <Provider store={store}> + <Weather /> + </Provider> + ); + + const acceptBtn = wrapper.find("#accept-opt-in"); + acceptBtn.simulate("click", { preventDefault() {} }); + + const dispatchedActions = store.dispatch + .getCalls() + .map(call => call.args[0]); + + assert.ok( + dispatchedActions.some( + action => action.type === at.WEATHER_USER_OPT_IN_LOCATION + ), + "Expected WEATHER_USER_OPT_IN_LOCATION to be dispatched" + ); + + assert.ok( + dispatchedActions.some( + action => + action.type === at.WEATHER_OPT_IN_PROMPT_SELECTION && + action.data === "accepted opt-in" + ), + "Expected WEATHER_OPT_IN_PROMPT_SELECTION with accepted opt-in" + ); + }); + + it("should render a shorter context menu when system.showWeatherOptIn is enabled", () => { + wrapper = mount( + <WrapWithProvider state={optInMockState}> + <Weather dispatch={dispatch} /> + </WrapWithProvider> + ); + + // find the inner _Weather component (the real class) + const inner = wrapper.find("_Weather"); + assert.ok(inner.exists(), "Inner _Weather component should exist"); + + // toggle context menu state on the real instance + inner.instance().setState({ showContextMenu: true }); + wrapper.update(); + + const menu = wrapper.find(LinkMenu); + assert.ok( + menu.exists(), + "Expected LinkMenu to render when context menu opened" + ); + + const contextMenuOptions = menu.prop("options"); + assert.deepEqual(contextMenuOptions, [ + "ChangeWeatherLocation", + "DetectLocation", + "HideWeather", + "OpenLearnMoreURL", + ]); + }); + + // TODO: Add test for "detect my location" telemetry once telemetry for that action is set up in another patch + }); +});