diff --git a/src/courseware/course/sequence/Unit/index.jsx b/src/courseware/course/sequence/Unit/index.jsx index d6c6dc6f1..a3081c7e8 100644 --- a/src/courseware/course/sequence/Unit/index.jsx +++ b/src/courseware/course/sequence/Unit/index.jsx @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React from 'react'; +import { useSearchParams } from 'react-router-dom'; import { AppContext } from '@edx/frontend-platform/react'; import { useIntl } from '@edx/frontend-platform/i18n'; @@ -23,6 +24,7 @@ const Unit = ({ id, }) => { const { formatMessage } = useIntl(); + const [searchParams] = useSearchParams(); const { authenticatedUser } = React.useContext(AppContext); const examAccess = useExamAccess({ id }); const shouldDisplayHonorCode = useShouldDisplayHonorCode({ courseId, id }); @@ -35,6 +37,7 @@ const Unit = ({ view, format, examAccess, + jumpToId: searchParams.get('jumpToId'), })); const iframeUrl = getUrl(); diff --git a/src/courseware/course/sequence/Unit/index.test.jsx b/src/courseware/course/sequence/Unit/index.test.jsx index 7ae0735ba..bdaf2743d 100644 --- a/src/courseware/course/sequence/Unit/index.test.jsx +++ b/src/courseware/course/sequence/Unit/index.test.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { when } from 'jest-when'; import { formatMessage, shallow } from '@edx/react-unit-test-utils/dist'; +import { useSearchParams } from 'react-router-dom'; import { useModel } from '@src/generic/model-store'; @@ -14,6 +15,7 @@ import { modelKeys, views } from './constants'; import * as hooks from './hooks'; jest.mock('./hooks', () => ({ useUnitData: jest.fn() })); +jest.mock('react-router-dom'); jest.mock('@edx/frontend-platform/i18n', () => { const utils = jest.requireActual('@edx/react-unit-test-utils/dist'); @@ -82,7 +84,11 @@ when(useModel) let el; describe('Unit component', () => { + const searchParams = { get: (prop) => prop }; + const setSearchParams = jest.fn(); + beforeEach(() => { + useSearchParams.mockImplementation(() => [searchParams, setSearchParams]); jest.clearAllMocks(); el = shallow(); }); diff --git a/src/courseware/course/sequence/Unit/urls.js b/src/courseware/course/sequence/Unit/urls.js index e227b4fb7..7e9fa3c94 100644 --- a/src/courseware/course/sequence/Unit/urls.js +++ b/src/courseware/course/sequence/Unit/urls.js @@ -1,5 +1,5 @@ import { getConfig } from '@edx/frontend-platform'; -import { stringify } from 'query-string'; +import { stringifyUrl } from 'query-string'; export const iframeParams = { show_title: 0, @@ -12,15 +12,20 @@ export const getIFrameUrl = ({ view, format, examAccess, + jumpToId, }) => { const xblockUrl = `${getConfig().LMS_BASE_URL}/xblock/${id}`; - const params = stringify({ - ...iframeParams, - view, - ...(format && { format }), - ...(!examAccess.blockAccess && { exam_access: examAccess.accessToken }), + return stringifyUrl({ + url: xblockUrl, + query: { + ...iframeParams, + view, + ...(format && { format }), + ...(!examAccess.blockAccess && { exam_access: examAccess.accessToken }), + jumpToId, // Pass jumpToId as query param as fragmentIdentifier is not passed to server. + }, + fragmentIdentifier: jumpToId, // this is used by browser to scroll to correct block. }); - return `${xblockUrl}?${params}`; }; export default { diff --git a/src/courseware/course/sequence/Unit/urls.test.js b/src/courseware/course/sequence/Unit/urls.test.js index 2b0e46d8f..9de154e2d 100644 --- a/src/courseware/course/sequence/Unit/urls.test.js +++ b/src/courseware/course/sequence/Unit/urls.test.js @@ -1,12 +1,12 @@ import { getConfig } from '@edx/frontend-platform'; -import { stringify } from 'query-string'; +import { stringifyUrl } from 'query-string'; import { getIFrameUrl, iframeParams } from './urls'; jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn(), })); jest.mock('query-string', () => ({ - stringify: jest.fn((...args) => ({ stringify: args })), + stringifyUrl: jest.fn((arg) => ({ stringifyUrl: arg })), })); const config = { LMS_BASE_URL: 'test-lms-url' }; @@ -21,41 +21,43 @@ const props = { describe('urls module getIFrameUrl', () => { test('format provided, exam access and token available', () => { - const params = stringify({ - ...iframeParams, - view: props.view, - format: props.format, - exam_access: props.examAccess.accessToken, + const url = stringifyUrl({ + url: `${config.LMS_BASE_URL}/xblock/${props.id}`, + query: { + ...iframeParams, + view: props.view, + format: props.format, + exam_access: props.examAccess.accessToken, + }, }); - expect(getIFrameUrl(props)).toEqual(`${config.LMS_BASE_URL}/xblock/${props.id}?${params}`); + expect(getIFrameUrl(props)).toEqual(url); }); test('no format provided, exam access blocked', () => { - const params = stringify({ ...iframeParams, view: props.view }); + const url = stringifyUrl({ + url: `${config.LMS_BASE_URL}/xblock/${props.id}`, + query: { ...iframeParams, view: props.view }, + }); expect(getIFrameUrl({ id: props.id, view: props.view, examAccess: { blockAccess: true }, - })).toEqual(`${config.LMS_BASE_URL}/xblock/${props.id}?${params}`); + })).toEqual(url); }); - test('src and dest languages provided', () => { - const params = stringify({ - ...iframeParams, - view: props.view, - src_lang: 'test-src-lang', - dest_lang: 'test-dest-lang', + test('jumpToId and fragmentIdentifier is added to url', () => { + const url = stringifyUrl({ + url: `${config.LMS_BASE_URL}/xblock/${props.id}`, + query: { + ...iframeParams, + view: props.view, + format: props.format, + exam_access: props.examAccess.accessToken, + jumpToId: 'some-xblock-id', + }, + fragmentIdentifier: 'some-xblock-id', }); expect(getIFrameUrl({ ...props, - srcLanguage: 'test-src-lang', - destLanguage: 'test-dest-lang', - })).toEqual(`${config.LMS_BASE_URL}/xblock/${props.id}?${params}`); - }); - test('src and dest languages provided are the same', () => { - const params = stringify({ ...iframeParams, view: props.view }); - expect(getIFrameUrl({ - ...props, - srcLanguage: 'test-lang', - destLanguage: 'test-lang', - })).toEqual(`${config.LMS_BASE_URL}/xblock/${props.id}?${params}`); + jumpToId: 'some-xblock-id', + })).toEqual(url); }); });