diff --git a/src/courseware/course/sequence/Unit/hooks/useIFrameBehavior.js b/src/courseware/course/sequence/Unit/hooks/useIFrameBehavior.js index 53c6bed5cc..a8d31ab030 100644 --- a/src/courseware/course/sequence/Unit/hooks/useIFrameBehavior.js +++ b/src/courseware/course/sequence/Unit/hooks/useIFrameBehavior.js @@ -86,6 +86,23 @@ const useIFrameBehavior = ({ useEventListener('message', receiveMessage); // Send visibility status to the iframe. It's used to mark XBlocks as viewed. + const updateIframeVisibility = () => { + const iframeElement = document.getElementById(elementId); + const rect = iframeElement.getBoundingClientRect(); + const visibleInfo = { + type: 'unit.visibilityStatus', + data: { + topPosition: rect.top, + viewportHeight: window.innerHeight, + }, + }; + iframeElement.contentWindow.postMessage( + visibleInfo, + `${getConfig().LMS_BASE_URL}`, + ); + }; + + // Set up visibility tracking event listeners. React.useEffect(() => { if (!hasLoaded) { return undefined; @@ -96,27 +113,9 @@ const useIFrameBehavior = ({ return undefined; } - const updateIframeVisibility = () => { - const rect = iframeElement.getBoundingClientRect(); - const visibleInfo = { - type: 'unit.visibilityStatus', - data: { - topPosition: rect.top, - viewportHeight: window.innerHeight, - }, - }; - iframeElement.contentWindow.postMessage( - visibleInfo, - `${getConfig().LMS_BASE_URL}`, - ); - }; - // Throttle the update function to prevent it from sending too many messages to the iframe. const throttledUpdateVisibility = throttle(updateIframeVisibility, 100); - // Update the visibility of the iframe in case the element is already visible. - updateIframeVisibility(); - // Add event listeners to update the visibility of the iframe when the window is scrolled or resized. window.addEventListener('scroll', throttledUpdateVisibility); window.addEventListener('resize', throttledUpdateVisibility); @@ -147,6 +146,9 @@ const useIFrameBehavior = ({ dispatch(processEvent(e.data, fetchCourse)); } }; + + // Update the visibility of the iframe in case the element is already visible. + updateIframeVisibility(); }; return { diff --git a/src/courseware/course/sequence/Unit/hooks/useIFrameBehavior.test.js b/src/courseware/course/sequence/Unit/hooks/useIFrameBehavior.test.js index 395d0c8b03..1b56f14b1b 100644 --- a/src/courseware/course/sequence/Unit/hooks/useIFrameBehavior.test.js +++ b/src/courseware/course/sequence/Unit/hooks/useIFrameBehavior.test.js @@ -275,7 +275,7 @@ describe('useIFrameBehavior hook', () => { }); }); describe('visibility tracking', () => { - it('sets up visibility tracking after iframe has loaded', () => { + it('sets up visibility tracking after iframe loads', () => { state.mockVals({ ...defaultStateVals, hasLoaded: true }); useIFrameBehavior(props); @@ -286,17 +286,8 @@ describe('useIFrameBehavior hook', () => { expect(global.window.addEventListener).toHaveBeenCalledTimes(2); expect(global.window.addEventListener).toHaveBeenCalledWith('scroll', expect.any(Function)); expect(global.window.addEventListener).toHaveBeenCalledWith('resize', expect.any(Function)); - // Initial visibility update. - expect(postMessage).toHaveBeenCalledWith( - { - type: 'unit.visibilityStatus', - data: { - topPosition: 100, - viewportHeight: 800, - }, - }, - config.LMS_BASE_URL, - ); + // Initial visibility update is handled by the `handleIFrameLoad` method. + expect(postMessage).not.toHaveBeenCalled(); }); it('does not set up visibility tracking before iframe has loaded', () => { state.mockVals({ ...defaultStateVals, hasLoaded: false }); @@ -345,6 +336,20 @@ describe('useIFrameBehavior hook', () => { window.onmessage(event); expect(dispatch).toHaveBeenCalledWith(processEvent(event.data, fetchCourse)); }); + it('updates initial iframe visibility on load', () => { + hook = useIFrameBehavior(props); + hook.handleIFrameLoad(); + expect(postMessage).toHaveBeenCalledWith( + { + type: 'unit.visibilityStatus', + data: { + topPosition: 100, + viewportHeight: 800, + }, + }, + config.LMS_BASE_URL, + ); + }); }); it('forwards handleIframeLoad, showError, and hasLoaded from state fields', () => { state.mockVals(stateVals);