ReportContent.test.jsx (7369B)
1 import { mount } from "enzyme"; 2 import { INITIAL_STATE, reducers } from "common/Reducers.sys.mjs"; 3 import { ReportContent } from "content-src/components/DiscoveryStreamComponents/ReportContent/ReportContent"; 4 import { combineReducers, createStore } from "redux"; 5 import { Provider } from "react-redux"; 6 import { actionCreators as ac } from "common/Actions.mjs"; 7 import React from "react"; 8 9 const DEFAULT_PROPS = { 10 dispatch() {}, 11 prefs: { 12 ...INITIAL_STATE.Prefs, 13 values: { 14 ...INITIAL_STATE.Prefs.values, 15 "discoverystream.sections.enabled": true, 16 "unifiedAds.spocs.enabled": true, 17 }, 18 }, 19 }; 20 21 const BASE_REPORT = { 22 visible: true, 23 url: "https://example.com", 24 position: 1, 25 reporting_url: "https://example.com/report", 26 }; 27 28 function testState({ card_type, visible }) { 29 return { 30 Prefs: DEFAULT_PROPS.prefs, 31 DiscoveryStream: { 32 ...INITIAL_STATE.DiscoveryStream, 33 report: { 34 ...BASE_REPORT, 35 card_type, 36 visible, 37 }, 38 }, 39 }; 40 } 41 42 // Wrap this around any component that uses useSelector, 43 // or any mount that uses a child that uses redux. 44 function WrapWithProvider({ children, state, dispatch }) { 45 let store = createStore(combineReducers(reducers), state); 46 if (dispatch) { 47 store.dispatch = dispatch; 48 } 49 return <Provider store={store}>{children}</Provider>; 50 } 51 52 // patch dialog element's .showModal()/close() functions to prevent errors in tests 53 before(() => { 54 if (typeof HTMLDialogElement !== "undefined") { 55 HTMLDialogElement.prototype.showModal = function () { 56 this.open = true; 57 }; 58 HTMLDialogElement.prototype.close = function () { 59 this.open = false; 60 }; 61 } 62 }); 63 64 describe("Discovery Stream <ReportContent>", () => { 65 let wrapper; 66 let sandbox; 67 let dispatch; 68 69 beforeEach(() => { 70 sandbox = sinon.createSandbox(); 71 dispatch = sandbox.stub(); 72 73 wrapper = mount( 74 <WrapWithProvider> 75 <ReportContent 76 dispatch={dispatch} 77 {...DEFAULT_PROPS} 78 spocs={{ spocs: { data: {} } }} 79 /> 80 </WrapWithProvider> 81 ); 82 }); 83 84 afterEach(() => { 85 sandbox.restore(); 86 }); 87 88 it("should render", () => { 89 assert.ok(wrapper.exists()); 90 assert.ok(wrapper.find(".report-content-form").exists()); 91 }); 92 93 it("should open modal if report.visible is true", () => { 94 const state = testState({ visible: true }); 95 96 wrapper = mount( 97 <WrapWithProvider state={state}> 98 <ReportContent spocs={{ spocs: { data: {} } }} /> 99 </WrapWithProvider> 100 ); 101 assert.ok(wrapper.find("dialog").getDOMNode().open); 102 }); 103 104 it("should close modal if report.visible is false", () => { 105 const state = testState({ visible: false }); 106 107 wrapper = mount( 108 <WrapWithProvider state={state}> 109 <ReportContent spocs={{ spocs: { data: {} } }} /> 110 </WrapWithProvider> 111 ); 112 113 assert.equal(wrapper.find("dialog").getDOMNode().open, false); 114 }); 115 116 it("should render ad reporting options if card_type is spoc", () => { 117 const state = testState({ card_type: "spoc" }); 118 119 // in the ReportContent.jsx file, spocs.spocs.data is used to grab spoc data 120 wrapper = mount( 121 <WrapWithProvider state={state}> 122 <ReportContent spocs={{ spocs: { data: {} } }} /> 123 </WrapWithProvider> 124 ); 125 126 assert.ok(wrapper.find(".report-ads-options").exists()); 127 128 // test to make sure content options aren't displayed when report ads is open 129 assert.equal(wrapper.find(".report-content-options").length, 0); 130 }); 131 132 it("should render content reporting options if card_type is organic", () => { 133 const state = testState({ card_type: "organic" }); 134 135 // in the ReportContent.jsx file, spocs.spocs.data is used to grab spoc data 136 wrapper = mount( 137 <WrapWithProvider state={state}> 138 <ReportContent spocs={{}} /> 139 </WrapWithProvider> 140 ); 141 142 assert.ok(wrapper.find(".report-content-options").exists()); 143 144 // test to make sure ad options aren't displayed when report content is open 145 assert.equal(wrapper.find(".report-ads-options").length, 0); 146 }); 147 148 it("should dispatch REPORT_CLOSE when cancel button is clicked", () => { 149 const state = testState({ visible: true }); 150 151 wrapper = mount( 152 <WrapWithProvider state={state} dispatch={dispatch}> 153 <ReportContent spocs={{}} /> 154 </WrapWithProvider> 155 ); 156 157 // Cancel button implementation 158 const cancelButton = wrapper.find("moz-button.cancel-report-btn"); 159 assert.ok(cancelButton.exists()); 160 cancelButton.simulate("click"); 161 162 assert.calledOnce(dispatch); 163 164 const call = dispatch.getCall(0); 165 assert.deepEqual( 166 call.args[0], 167 ac.AlsoToMain({ 168 type: "REPORT_CLOSE", 169 }) 170 ); 171 }); 172 173 it("should dispatch REPORT_CONTENT_SUBMIT, BLOCK_URL, and SHOW_TOAST_MESSAGE when submit button is clicked", () => { 174 const state = testState({ visible: true, card_type: "organic" }); 175 176 wrapper = mount( 177 <WrapWithProvider state={state} dispatch={dispatch}> 178 <ReportContent spocs={{}} /> 179 </WrapWithProvider> 180 ); 181 182 // Submit button implementation 183 const submitButton = wrapper.find("moz-button.submit-report-btn"); 184 assert.ok(submitButton.exists()); 185 submitButton.simulate("click"); 186 187 // Assert both action types were dispatched 188 assert.calledThrice(dispatch); 189 190 const firstCall = dispatch.getCall(0); 191 const secondCall = dispatch.getCall(1); 192 const thirdCall = dispatch.getCall(2); 193 194 // Using .match instead of .deepEqual because submitting a report passes a lot of data during dispatch. And using .match makes it so we don't have to write out all the data 195 assert.match( 196 firstCall.args[0], 197 ac.AlsoToMain({ 198 type: "REPORT_CONTENT_SUBMIT", 199 }) 200 ); 201 202 assert.match( 203 secondCall.args[0], 204 ac.AlsoToMain({ 205 type: "BLOCK_URL", 206 }) 207 ); 208 209 assert.match( 210 thirdCall.args[0], 211 ac.OnlyToOneContent( 212 { 213 type: "SHOW_TOAST_MESSAGE", 214 }, 215 "ActivityStream:Content" 216 ) 217 ); 218 }); 219 220 it("should clear selected radio button when modal closes", () => { 221 // Initial render with modal visible 222 const openState = testState({ visible: true }); 223 224 wrapper = mount( 225 <WrapWithProvider state={openState} dispatch={dispatch}> 226 <ReportContent spocs={{ spocs: {} }} /> 227 </WrapWithProvider> 228 ); 229 230 // Select the first radio button 231 const radioGroup = wrapper.find("moz-radio-group").getDOMNode(); 232 const firstRadio = radioGroup.querySelector("moz-radio"); 233 234 firstRadio.setAttribute("checked", "true"); 235 236 // Assert it's checked before modal closes 237 assert.equal(firstRadio.hasAttribute("checked"), true); 238 239 // Simulate closing the modal to clear the radio button 240 const closedState = testState({ visible: false }); 241 242 wrapper = mount( 243 <WrapWithProvider state={closedState} dispatch={dispatch}> 244 <ReportContent spocs={{ spocs: {} }} /> 245 </WrapWithProvider> 246 ); 247 248 const updatedRadioGroup = wrapper.find("moz-radio-group").getDOMNode(); 249 const updatedFirstRadio = updatedRadioGroup.querySelector("moz-radio"); 250 251 // The previously checked radio should now be unchecked 252 assert.equal( 253 updatedFirstRadio.hasAttribute("checked"), 254 false, 255 "Radio button should be unchecked after modal closes" 256 ); 257 }); 258 });