Weather.test.jsx (6983B)
1 import React from "react"; 2 import { mount } from "enzyme"; 3 import { Provider } from "react-redux"; 4 import { INITIAL_STATE, reducers } from "common/Reducers.sys.mjs"; 5 import { combineReducers, createStore } from "redux"; 6 import { Weather } from "content-src/components/Weather/Weather"; 7 import { actionTypes as at } from "common/Actions.mjs"; 8 import { LinkMenu } from "content-src/components/LinkMenu/LinkMenu"; 9 10 const PREF_SYS_SHOW_WEATHER = "system.showWeather"; 11 const PREF_SYS_SHOW_WEATHER_OPT_IN = "system.showWeatherOptIn"; 12 const PREF_OPT_IN_DISPLAYED = "weather.optInDisplayed"; 13 const PREF_OPT_IN_ACCEPTED = "weather.optInAccepted"; 14 const PREF_STATIC_WEATHER_DATA = "weather.staticData.enabled"; 15 16 // keeps initialize = true and provides fake suggestion + location data 17 // so the component skips <WeatherPlaceholder>. 18 const weatherInit = { 19 initialized: true, 20 suggestions: [ 21 { 22 forecast: { url: "https://example.com" }, 23 current_conditions: { 24 temperature: { c: 22, f: 72 }, 25 icon_id: 3, 26 summary: "Sunny", 27 }, 28 }, 29 ], 30 locationData: { city: "Testville" }, 31 }; 32 33 // base mockState for general Weather-rendering tests. 34 // Opt-in is disabled here since it's only shown in specific locations 35 const mockState = { 36 ...INITIAL_STATE, 37 Prefs: { 38 ...INITIAL_STATE.Prefs, 39 values: { 40 ...INITIAL_STATE.Prefs.values, 41 [PREF_SYS_SHOW_WEATHER]: true, 42 [PREF_SYS_SHOW_WEATHER_OPT_IN]: false, 43 "feeds.weatherfeed": true, 44 }, 45 }, 46 Weather: { ...weatherInit }, 47 }; 48 49 // mock state for opt-in prompt tests. 50 // Ensures the opt-in dialog appears by default. 51 const optInMockState = { 52 ...mockState, 53 Prefs: { 54 ...mockState.Prefs, 55 values: { 56 ...mockState.Prefs.values, 57 showWeather: true, 58 [PREF_SYS_SHOW_WEATHER_OPT_IN]: true, 59 [PREF_OPT_IN_DISPLAYED]: true, 60 [PREF_OPT_IN_ACCEPTED]: false, 61 [PREF_STATIC_WEATHER_DATA]: true, 62 "weather.locationSearchEnabled": true, 63 "weather.display": "simple", 64 "weather.temperatureUnits": "c", 65 }, 66 }, 67 }; 68 69 function WrapWithProvider({ children, state = INITIAL_STATE }) { 70 const store = createStore(combineReducers(reducers), state); 71 return <Provider store={store}>{children}</Provider>; 72 } 73 74 describe("<Weather>", () => { 75 let wrapper; 76 let sandbox; 77 let dispatch; 78 79 beforeEach(() => { 80 sandbox = sinon.createSandbox(); 81 dispatch = sandbox.stub(); 82 }); 83 84 afterEach(() => { 85 sandbox.restore(); 86 wrapper?.unmount(); 87 }); 88 89 it("should render and show <Weather> if the `system.showWeather` pref is enabled", () => { 90 wrapper = mount( 91 <WrapWithProvider state={mockState}> 92 <Weather dispatch={dispatch} /> 93 </WrapWithProvider> 94 ); 95 assert.ok(wrapper.exists()); 96 assert.ok(wrapper.find(".weather").exists()); 97 }); 98 99 describe("Opt-in prompt actions", () => { 100 it("should dispatch correct actions when user accepts weather opt-in", () => { 101 const store = createStore(combineReducers(reducers), optInMockState); 102 sinon.spy(store, "dispatch"); 103 104 wrapper = mount( 105 <Provider store={store}> 106 <Weather /> 107 </Provider> 108 ); 109 110 const acceptBtn = wrapper.find("#accept-opt-in"); 111 acceptBtn.simulate("click", { preventDefault() {} }); 112 113 const dispatchedActions = store.dispatch 114 .getCalls() 115 .map(call => call.args[0]); 116 117 assert.ok( 118 dispatchedActions.some( 119 action => action.type === at.WEATHER_USER_OPT_IN_LOCATION 120 ), 121 "Expected WEATHER_USER_OPT_IN_LOCATION to be dispatched" 122 ); 123 124 assert.ok( 125 dispatchedActions.some( 126 action => 127 action.type === at.WEATHER_OPT_IN_PROMPT_SELECTION && 128 action.data === "accepted opt-in" 129 ), 130 "Expected WEATHER_OPT_IN_PROMPT_SELECTION with accepted opt-in" 131 ); 132 }); 133 134 it("should dispatch correct actions when user rejects weather opt-in", () => { 135 const store = createStore(combineReducers(reducers), optInMockState); 136 sinon.spy(store, "dispatch"); 137 138 wrapper = mount( 139 <Provider store={store}> 140 <Weather /> 141 </Provider> 142 ); 143 144 const acceptBtn = wrapper.find("#reject-opt-in"); 145 acceptBtn.simulate("click", { preventDefault() {} }); 146 147 const dispatchedActions = store.dispatch 148 .getCalls() 149 .map(call => call.args[0]); 150 151 assert.ok( 152 dispatchedActions.some( 153 action => 154 action.type === at.WEATHER_OPT_IN_PROMPT_SELECTION && 155 action.data === "rejected opt-in" 156 ), 157 "Expected WEATHER_OPT_IN_PROMPT_SELECTION with rejected opt-in" 158 ); 159 }); 160 161 it("should render a shorter context menu when system.showWeatherOptIn is enabled", () => { 162 wrapper = mount( 163 <WrapWithProvider state={optInMockState}> 164 <Weather dispatch={dispatch} /> 165 </WrapWithProvider> 166 ); 167 168 // find the inner _Weather component (the real class) 169 const inner = wrapper.find("_Weather"); 170 assert.ok(inner.exists(), "Inner _Weather component should exist"); 171 172 // toggle context menu state on the real instance 173 inner.instance().setState({ showContextMenu: true }); 174 wrapper.update(); 175 176 const menu = wrapper.find(LinkMenu); 177 assert.ok( 178 menu.exists(), 179 "Expected LinkMenu to render when context menu opened" 180 ); 181 182 const contextMenuOptions = menu.prop("options"); 183 assert.deepEqual(contextMenuOptions, [ 184 "ChangeWeatherLocation", 185 "DetectLocation", 186 "HideWeather", 187 "OpenLearnMoreURL", 188 ]); 189 }); 190 191 it("should dispatch correct actions when 'Detect my location' option in context menu is clicked", () => { 192 const store = createStore(combineReducers(reducers), optInMockState); 193 sinon.spy(store, "dispatch"); 194 195 wrapper = mount( 196 <Provider store={store}> 197 <Weather /> 198 </Provider> 199 ); 200 201 // find the inner _Weather component 202 const inner = wrapper.find("_Weather"); 203 assert.ok(inner.exists(), "Inner _Weather component should exist"); 204 205 // toggle context menu state on the real instance 206 inner.instance().setState({ showContextMenu: true }); 207 wrapper.update(); 208 209 const menu = wrapper.find(LinkMenu); 210 assert.ok( 211 menu.exists(), 212 "Expected LinkMenu to render when context menu opened" 213 ); 214 215 const detectLocationBtn = wrapper.find( 216 '[data-l10n-id="newtab-weather-menu-detect-my-location"]' 217 ); 218 219 assert.ok(detectLocationBtn.exists()); 220 221 detectLocationBtn.simulate("click", { preventDefault() {} }); 222 223 const dispatchedActions = store.dispatch 224 .getCalls() 225 .map(call => call.args[0]); 226 227 assert.ok( 228 dispatchedActions.some( 229 action => action.type === at.WEATHER_USER_OPT_IN_LOCATION 230 ), 231 "Expected WEATHER_USER_OPT_IN_LOCATION to be dispatched" 232 ); 233 }); 234 }); 235 });