@@ -30,6 +30,11 @@ jest.mock('react-redux', () => ({
3030 useDispatch : jest . fn ( ) ,
3131} ) ) ;
3232
33+ jest . mock ( 'lodash' , ( ) => ( {
34+ ...jest . requireActual ( 'lodash' ) ,
35+ throttle : jest . fn ( ( fn ) => fn ) ,
36+ } ) ) ;
37+
3338jest . mock ( './useLoadBearingHook' , ( ) => jest . fn ( ) ) ;
3439
3540jest . mock ( '@edx/frontend-platform/logging' , ( ) => ( {
@@ -64,7 +69,10 @@ const dispatch = jest.fn();
6469useDispatch . mockReturnValue ( dispatch ) ;
6570
6671const postMessage = jest . fn ( ) ;
67- const frame = { contentWindow : { postMessage } } ;
72+ const frame = {
73+ contentWindow : { postMessage } ,
74+ getBoundingClientRect : jest . fn ( ( ) => ( { top : 100 } ) ) ,
75+ } ;
6876const mockGetElementById = jest . fn ( ( ) => frame ) ;
6977const testHash = '#test-hash' ;
7078
@@ -87,6 +95,10 @@ describe('useIFrameBehavior hook', () => {
8795 beforeEach ( ( ) => {
8896 jest . clearAllMocks ( ) ;
8997 state . mock ( ) ;
98+ global . document . getElementById = mockGetElementById ;
99+ global . window . addEventListener = jest . fn ( ) ;
100+ global . window . removeEventListener = jest . fn ( ) ;
101+ global . window . innerHeight = 800 ;
90102 } ) ;
91103 afterEach ( ( ) => {
92104 state . resetVals ( ) ;
@@ -265,6 +277,53 @@ describe('useIFrameBehavior hook', () => {
265277 } ) ;
266278 } ) ;
267279 } ) ;
280+ describe ( 'visibility tracking' , ( ) => {
281+ it ( 'sets up visibility tracking after iframe has loaded' , ( ) => {
282+ state . mockVals ( { ...defaultStateVals , hasLoaded : true } ) ;
283+ useIFrameBehavior ( props ) ;
284+
285+ const effects = getEffects ( [ true , props . elementId ] , React ) ;
286+ expect ( effects . length ) . toEqual ( 2 ) ;
287+ effects [ 0 ] ( ) ; // Execute the visibility tracking effect.
288+
289+ expect ( global . window . addEventListener ) . toHaveBeenCalledTimes ( 2 ) ;
290+ expect ( global . window . addEventListener ) . toHaveBeenCalledWith ( 'scroll' , expect . any ( Function ) ) ;
291+ expect ( global . window . addEventListener ) . toHaveBeenCalledWith ( 'resize' , expect . any ( Function ) ) ;
292+ // Initial visibility update.
293+ expect ( postMessage ) . toHaveBeenCalledWith (
294+ {
295+ type : 'unit.visibilityStatus' ,
296+ data : {
297+ topPosition : 100 ,
298+ viewportHeight : 800 ,
299+ } ,
300+ } ,
301+ config . LMS_BASE_URL ,
302+ ) ;
303+ } ) ;
304+ it ( 'does not set up visibility tracking before iframe has loaded' , ( ) => {
305+ state . mockVals ( { ...defaultStateVals , hasLoaded : false } ) ;
306+ useIFrameBehavior ( props ) ;
307+
308+ const effects = getEffects ( [ false , props . elementId ] , React ) ;
309+ expect ( effects ) . toBeNull ( ) ;
310+
311+ expect ( global . window . addEventListener ) . not . toHaveBeenCalled ( ) ;
312+ expect ( postMessage ) . not . toHaveBeenCalled ( ) ;
313+ } ) ;
314+ it ( 'cleans up event listeners on unmount' , ( ) => {
315+ state . mockVals ( { ...defaultStateVals , hasLoaded : true } ) ;
316+ useIFrameBehavior ( props ) ;
317+
318+ const effects = getEffects ( [ true , props . elementId ] , React ) ;
319+ const cleanup = effects [ 0 ] ( ) ; // Execute the effect and get the cleanup function.
320+ cleanup ( ) ; // Call the cleanup function.
321+
322+ expect ( global . window . removeEventListener ) . toHaveBeenCalledTimes ( 2 ) ;
323+ expect ( global . window . removeEventListener ) . toHaveBeenCalledWith ( 'scroll' , expect . any ( Function ) ) ;
324+ expect ( global . window . removeEventListener ) . toHaveBeenCalledWith ( 'resize' , expect . any ( Function ) ) ;
325+ } ) ;
326+ } ) ;
268327 } ) ;
269328 describe ( 'output' , ( ) => {
270329 describe ( 'handleIFrameLoad' , ( ) => {
0 commit comments