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"; 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 . 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, "feeds.weatherfeed": true, }, }, 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 {children}; } describe("", () => { let wrapper; let sandbox; let dispatch; beforeEach(() => { sandbox = sinon.createSandbox(); dispatch = sandbox.stub(); }); afterEach(() => { sandbox.restore(); wrapper?.unmount(); }); it("should render and show if the `system.showWeather` pref is enabled", () => { wrapper = mount( ); 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( ); const acceptBtn = wrapper.find("#accept-opt-in"); acceptBtn.simulate("click", { preventDefault() {} }); const dispatchedActions = store.dispatch .getCalls() .map(call => call.args[0]); // Old events (backward compatibility) 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" ); // New unified event const unifiedEvent = dispatchedActions.find( action => action.type === at.WIDGETS_USER_EVENT ); assert.ok(unifiedEvent, "Expected WIDGETS_USER_EVENT to be dispatched"); assert.equal(unifiedEvent.data.widget_name, "weather"); assert.equal(unifiedEvent.data.widget_source, "widget"); assert.equal(unifiedEvent.data.user_action, "opt_in_accepted"); assert.equal(unifiedEvent.data.action_value, true); assert.equal(unifiedEvent.data.widget_size, "mini"); }); it("should dispatch correct actions when user rejects weather opt-in", () => { const store = createStore(combineReducers(reducers), optInMockState); sinon.spy(store, "dispatch"); wrapper = mount( ); const acceptBtn = wrapper.find("#reject-opt-in"); acceptBtn.simulate("click", { preventDefault() {} }); const dispatchedActions = store.dispatch .getCalls() .map(call => call.args[0]); // Old event (backward compatibility) assert.ok( dispatchedActions.some( action => action.type === at.WEATHER_OPT_IN_PROMPT_SELECTION && action.data === "rejected opt-in" ), "Expected WEATHER_OPT_IN_PROMPT_SELECTION with rejected opt-in" ); // New unified event const unifiedEvent = dispatchedActions.find( action => action.type === at.WIDGETS_USER_EVENT ); assert.ok(unifiedEvent, "Expected WIDGETS_USER_EVENT to be dispatched"); assert.equal(unifiedEvent.data.widget_name, "weather"); assert.equal(unifiedEvent.data.widget_source, "widget"); assert.equal(unifiedEvent.data.user_action, "opt_in_accepted"); assert.equal(unifiedEvent.data.action_value, false); assert.equal(unifiedEvent.data.widget_size, "mini"); }); it("should render a shorter context menu when system.showWeatherOptIn is enabled", () => { wrapper = mount( ); // panel-list should render with only the shortened menu items const panelList = wrapper.find("panel-list"); assert.ok(panelList.exists(), "Expected panel-list to render"); // Check that the correct menu items are present assert.ok( wrapper.find("#weather-menu-change-location").exists(), "ChangeWeatherLocation item should be present" ); assert.ok( wrapper.find("#weather-menu-detect-location").exists(), "DetectLocation item should be present" ); assert.ok( wrapper.find("#weather-menu-hide").exists(), "HideWeather item should be present" ); assert.ok( wrapper.find("#weather-menu-learn-more").exists(), "OpenLearnMoreURL item should be present" ); // Check that temperature/display options are NOT present (shortened menu) assert.ok( !wrapper.find("#weather-menu-temp-celsius").exists(), "Temperature unit option should not be present in shortened menu" ); }); it("should dispatch correct actions when 'Detect my location' option in context menu is clicked", () => { const store = createStore(combineReducers(reducers), optInMockState); sinon.spy(store, "dispatch"); wrapper = mount( ); // Mock the panel element's hide method const weatherInstance = wrapper.find("_Weather").instance(); weatherInstance.panelElement = { hide: sinon.spy() }; // Find the detect location panel-item const detectLocationBtn = wrapper.find("#weather-menu-detect-location"); assert.ok( detectLocationBtn.exists(), "Detect location button should exist" ); detectLocationBtn.simulate("click", { preventDefault() {} }); const dispatchedActions = store.dispatch .getCalls() .map(call => call.args[0]); // Old event (backward compatibility) assert.ok( dispatchedActions.some( action => action.type === at.WEATHER_USER_OPT_IN_LOCATION ), "Expected WEATHER_USER_OPT_IN_LOCATION to be dispatched" ); // New unified event const unifiedEvent = dispatchedActions.find( action => action.type === at.WIDGETS_USER_EVENT ); assert.ok(unifiedEvent, "Expected WIDGETS_USER_EVENT to be dispatched"); assert.equal(unifiedEvent.data.widget_name, "weather"); assert.equal(unifiedEvent.data.widget_source, "context_menu"); assert.equal(unifiedEvent.data.user_action, "detect_location"); assert.equal(unifiedEvent.data.widget_size, "mini"); }); it("should dispatch correct actions when weather display mode is changed", () => { const fullMenuState = { ...optInMockState, Prefs: { ...optInMockState.Prefs, values: { ...optInMockState.Prefs.values, [PREF_STATIC_WEATHER_DATA]: false, }, }, }; const store = createStore(combineReducers(reducers), fullMenuState); sinon.spy(store, "dispatch"); wrapper = mount( ); const weatherInstance = wrapper.find("_Weather").instance(); weatherInstance.panelElement = { hide: sinon.spy() }; const displayMenuItem = wrapper.find("#weather-menu-display-detailed"); assert.ok(displayMenuItem.exists(), "Display menu item should exist"); displayMenuItem.simulate("click", { preventDefault() {} }); const dispatchedActions = store.dispatch .getCalls() .map(call => call.args[0]); const unifiedEvent = dispatchedActions.find( action => action.type === at.WIDGETS_USER_EVENT ); assert.ok(unifiedEvent, "Expected WIDGETS_USER_EVENT to be dispatched"); assert.equal(unifiedEvent.data.widget_name, "weather"); assert.equal(unifiedEvent.data.widget_source, "context_menu"); assert.equal(unifiedEvent.data.user_action, "change_weather_display"); assert.equal(unifiedEvent.data.action_value, "detailed"); assert.equal(unifiedEvent.data.widget_size, "mini"); }); it("should dispatch correct actions when temperature unit is changed", () => { const fullMenuState = { ...optInMockState, Prefs: { ...optInMockState.Prefs, values: { ...optInMockState.Prefs.values, [PREF_STATIC_WEATHER_DATA]: false, }, }, }; const store = createStore(combineReducers(reducers), fullMenuState); sinon.spy(store, "dispatch"); wrapper = mount( ); const weatherInstance = wrapper.find("_Weather").instance(); weatherInstance.panelElement = { hide: sinon.spy() }; const tempMenuItem = wrapper.find("#weather-menu-temp-fahrenheit"); assert.ok(tempMenuItem.exists(), "Temperature menu item should exist"); tempMenuItem.simulate("click", { preventDefault() {} }); const dispatchedActions = store.dispatch .getCalls() .map(call => call.args[0]); const unifiedEvent = dispatchedActions.find( action => action.type === at.WIDGETS_USER_EVENT ); assert.ok(unifiedEvent, "Expected WIDGETS_USER_EVENT to be dispatched"); assert.equal(unifiedEvent.data.widget_name, "weather"); assert.equal(unifiedEvent.data.widget_source, "context_menu"); assert.equal(unifiedEvent.data.user_action, "change_temperature_units"); assert.equal(unifiedEvent.data.action_value, "f"); assert.equal(unifiedEvent.data.widget_size, "mini"); }); }); });