feat: whole course translations (#1330)
* feat: add language selection chore: update tests so we have less error message test: update test * test: update tests * chore: remove duplicate translation * chore: lint for console * chore: remove comments * chore: make sure the affect url frame refresh after the language selection change * chore: add whole_course_translation and language to courseware meta (#1305) * feat: Add feedback widget UI mock Add unit tests Fix snapshot Clean Sequence component logEvent calls Clean unit test Put feedback widget behind whole course translation flag Fix useFeedbackWidget test * chore: add src and dest translation * feat: first iteration of plugin translation chore: update plugin instruction * feat: Connect FeedbackWidget with backend services (#1325) Connect FeedbackWidget with backend services Move feedback widget to unit translation plugin * feat: Add authentication to WCT feedback endpoints (#1329) * chore: add fetch config and move feedback widget for the plugin chore: rewrite and test the api request chore: rebase chore: update translation feedback chore: test chore: add more tests * chore: rebase * chore: update requested change * chore: update package * chore: upgrade frontend-lib-special-exams and frontend-lib-learning-assistant * chore: update tests * chore: remove unneeded package * chore: update example config * chore: add source-map-loader * fix: feedback widget render error after submit feedback (#1335) * fix: feedback widget render error after submit feedback * fix: widget logic --------- Co-authored-by: Rodrigo Martin <rodrigom_94@hotmail.com>
This commit is contained in:
@@ -88,6 +88,7 @@ describe('CoursewareContainer', () => {
|
||||
<Routes>
|
||||
{DECODE_ROUTES.COURSEWARE.map((route) => (
|
||||
<Route
|
||||
key={route}
|
||||
path={route}
|
||||
element={<CoursewareContainer />}
|
||||
/>
|
||||
|
||||
@@ -62,6 +62,7 @@ const CourseBreadcrumb = ({
|
||||
<Menu>
|
||||
{content.map((item) => (
|
||||
<JumpNavMenuItem
|
||||
key={item.label}
|
||||
isDefault={item.default}
|
||||
sequences={item.sequences}
|
||||
courseId={courseId}
|
||||
@@ -169,8 +170,10 @@ const CourseBreadcrumbs = ({
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
{links.map((content) => (
|
||||
{links.map((content, i) => (
|
||||
<CourseBreadcrumb
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={i}
|
||||
courseId={courseId}
|
||||
sequenceId={sequenceId}
|
||||
content={content}
|
||||
|
||||
@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
import { breakpoints, useWindowSize } from '@edx/paragon';
|
||||
import { breakpoints, useWindowSize } from '@openedx/paragon';
|
||||
|
||||
import { getLocalStorage, setLocalStorage } from '../../../data/localStorage';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
|
||||
@@ -4,8 +4,8 @@ import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Icon, IconButton } from '@edx/paragon';
|
||||
import { ArrowBackIos, Close } from '@edx/paragon/icons';
|
||||
import { Icon, IconButton } from '@openedx/paragon';
|
||||
import { ArrowBackIos, Close } from '@openedx/paragon/icons';
|
||||
|
||||
import { useEventListener } from '../../../../generic/hooks';
|
||||
import WIDGETS from '../constants';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Icon, IconButton } from '@edx/paragon';
|
||||
import { Icon, IconButton } from '@openedx/paragon';
|
||||
|
||||
import { getLocalStorage, setLocalStorage } from '../../../../../data/localStorage';
|
||||
import { getSessionStorage, setSessionStorage } from '../../../../../data/sessionStorage';
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Factory } from 'rosie';
|
||||
|
||||
import { mergeConfig, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { breakpoints } from '@edx/paragon';
|
||||
import { breakpoints } from '@openedx/paragon';
|
||||
|
||||
import {
|
||||
initializeMockApp, render, screen, within, act, fireEvent, waitFor,
|
||||
|
||||
@@ -5,6 +5,7 @@ import React, {
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {
|
||||
sendTrackEvent,
|
||||
sendTrackingLogEvent,
|
||||
@@ -37,7 +38,10 @@ const Sequence = ({
|
||||
previousSequenceHandler,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const course = useModel('coursewareMeta', courseId);
|
||||
const {
|
||||
canAccessProctoredExams,
|
||||
license,
|
||||
} = useModel('coursewareMeta', courseId);
|
||||
const {
|
||||
isStaff,
|
||||
originalUserIsStaff,
|
||||
@@ -144,57 +148,60 @@ const Sequence = ({
|
||||
const gated = sequence && sequence.gatedContent !== undefined && sequence.gatedContent.gated;
|
||||
|
||||
const defaultContent = (
|
||||
<div className="sequence-container d-inline-flex flex-row w-100">
|
||||
<div className={classNames('sequence w-100', { 'position-relative': shouldDisplayNotificationTriggerInSequence })}>
|
||||
<div className="sequence-navigation-container">
|
||||
<SequenceNavigation
|
||||
sequenceId={sequenceId}
|
||||
unitId={unitId}
|
||||
className="mb-4"
|
||||
nextHandler={() => {
|
||||
logEvent('edx.ui.lms.sequence.next_selected', 'top');
|
||||
handleNext();
|
||||
}}
|
||||
onNavigate={(destinationUnitId) => {
|
||||
logEvent('edx.ui.lms.sequence.tab_selected', 'top', destinationUnitId);
|
||||
handleNavigate(destinationUnitId);
|
||||
}}
|
||||
previousHandler={() => {
|
||||
logEvent('edx.ui.lms.sequence.previous_selected', 'top');
|
||||
handlePrevious();
|
||||
}}
|
||||
/>
|
||||
{shouldDisplayNotificationTriggerInSequence && (
|
||||
enableNewSidebar === 'true' ? <NewSidebarTriggers /> : <SidebarTriggers />
|
||||
)}
|
||||
</div>
|
||||
<>
|
||||
<div className="sequence-container d-inline-flex flex-row w-100">
|
||||
<div className={classNames('sequence w-100', { 'position-relative': shouldDisplayNotificationTriggerInSequence })}>
|
||||
<div className="sequence-navigation-container">
|
||||
<SequenceNavigation
|
||||
sequenceId={sequenceId}
|
||||
unitId={unitId}
|
||||
className="mb-4"
|
||||
nextHandler={() => {
|
||||
logEvent('edx.ui.lms.sequence.next_selected', 'top');
|
||||
handleNext();
|
||||
}}
|
||||
onNavigate={(destinationUnitId) => {
|
||||
logEvent('edx.ui.lms.sequence.tab_selected', 'top', destinationUnitId);
|
||||
handleNavigate(destinationUnitId);
|
||||
}}
|
||||
previousHandler={() => {
|
||||
logEvent('edx.ui.lms.sequence.previous_selected', 'top');
|
||||
handlePrevious();
|
||||
}}
|
||||
/>
|
||||
{shouldDisplayNotificationTriggerInSequence && (
|
||||
enableNewSidebar === 'true' ? <NewSidebarTriggers /> : <SidebarTriggers />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="unit-container flex-grow-1">
|
||||
<SequenceContent
|
||||
courseId={courseId}
|
||||
gated={gated}
|
||||
sequenceId={sequenceId}
|
||||
unitId={unitId}
|
||||
unitLoadedHandler={handleUnitLoaded}
|
||||
/>
|
||||
{unitHasLoaded && (
|
||||
<UnitNavigation
|
||||
sequenceId={sequenceId}
|
||||
unitId={unitId}
|
||||
onClickPrevious={() => {
|
||||
logEvent('edx.ui.lms.sequence.previous_selected', 'bottom');
|
||||
handlePrevious();
|
||||
}}
|
||||
onClickNext={() => {
|
||||
logEvent('edx.ui.lms.sequence.next_selected', 'bottom');
|
||||
handleNext();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="unit-container flex-grow-1">
|
||||
<SequenceContent
|
||||
courseId={courseId}
|
||||
gated={gated}
|
||||
sequenceId={sequenceId}
|
||||
unitId={unitId}
|
||||
unitLoadedHandler={handleUnitLoaded}
|
||||
/>
|
||||
{unitHasLoaded && (
|
||||
<UnitNavigation
|
||||
sequenceId={sequenceId}
|
||||
unitId={unitId}
|
||||
onClickPrevious={() => {
|
||||
logEvent('edx.ui.lms.sequence.previous_selected', 'bottom');
|
||||
handlePrevious();
|
||||
}}
|
||||
onClickNext={() => {
|
||||
logEvent('edx.ui.lms.sequence.next_selected', 'bottom');
|
||||
handleNext();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{enableNewSidebar === 'true' ? <NewSidebar /> : <Sidebar />}
|
||||
</div>
|
||||
{enableNewSidebar === 'true' ? <NewSidebar /> : <Sidebar />}
|
||||
</div>
|
||||
<div id="whole-course-translation-feedback-widget" />
|
||||
</>
|
||||
);
|
||||
|
||||
if (sequenceStatus === 'loaded') {
|
||||
@@ -205,11 +212,11 @@ const Sequence = ({
|
||||
courseId={courseId}
|
||||
isStaff={isStaff}
|
||||
originalUserIsStaff={originalUserIsStaff}
|
||||
canAccessProctoredExams={course.canAccessProctoredExams}
|
||||
canAccessProctoredExams={canAccessProctoredExams}
|
||||
>
|
||||
{defaultContent}
|
||||
</SequenceExamWrapper>
|
||||
<CourseLicense license={course.license || undefined} />
|
||||
<CourseLicense license={license || undefined} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ErrorPage } from '@edx/frontend-platform/react';
|
||||
import { StrictDict } from '@edx/react-unit-test-utils';
|
||||
import { ModalDialog, Modal } from '@openedx/paragon';
|
||||
|
||||
import PageLoading from '../../../../generic/PageLoading';
|
||||
import PageLoading from '@src/generic/PageLoading';
|
||||
import * as hooks from './hooks';
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ErrorPage } from '@edx/frontend-platform/react';
|
||||
import { ModalDialog, Modal } from '@openedx/paragon';
|
||||
import { shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import PageLoading from '../../../../generic/PageLoading';
|
||||
import PageLoading from '@src/generic/PageLoading';
|
||||
|
||||
import * as hooks from './hooks';
|
||||
import ContentIFrame, { IFRAME_FEATURE_POLICY, testIDs } from './ContentIFrame';
|
||||
@@ -19,7 +19,7 @@ jest.mock('@openedx/paragon', () => jest.requireActual('@edx/react-unit-test-uti
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../../../generic/PageLoading', () => 'PageLoading');
|
||||
jest.mock('@src/generic/PageLoading', () => 'PageLoading');
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
useIFrameBehavior: jest.fn(),
|
||||
|
||||
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import PageLoading from '../../../../generic/PageLoading';
|
||||
import { useModel } from '@src/generic/model-store';
|
||||
import PageLoading from '@src/generic/PageLoading';
|
||||
|
||||
import messages from '../messages';
|
||||
import HonorCode from '../honor-code';
|
||||
|
||||
@@ -2,8 +2,8 @@ import React from 'react';
|
||||
|
||||
import { formatMessage, shallow } from '@edx/react-unit-test-utils';
|
||||
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import PageLoading from '../../../../generic/PageLoading';
|
||||
import { useModel } from '@src/generic/model-store';
|
||||
import PageLoading from '@src/generic/PageLoading';
|
||||
|
||||
import messages from '../messages';
|
||||
import HonorCode from '../honor-code';
|
||||
@@ -25,8 +25,8 @@ jest.mock('react', () => ({
|
||||
|
||||
jest.mock('../honor-code', () => 'HonorCode');
|
||||
jest.mock('../lock-paywall', () => 'LockPaywall');
|
||||
jest.mock('../../../../generic/model-store', () => ({ useModel: jest.fn() }));
|
||||
jest.mock('../../../../generic/PageLoading', () => 'PageLoading');
|
||||
jest.mock('@src/generic/model-store', () => ({ useModel: jest.fn() }));
|
||||
jest.mock('@src/generic/PageLoading', () => 'PageLoading');
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
useShouldDisplayHonorCode: jest.fn(() => false),
|
||||
|
||||
@@ -20,11 +20,24 @@ exports[`Unit component output snapshot: not bookmarked, do not show content 1`]
|
||||
<div
|
||||
className="unit"
|
||||
>
|
||||
<h1
|
||||
className="mb-0 h3"
|
||||
<div
|
||||
className="mb-0"
|
||||
>
|
||||
unit-title
|
||||
</h1>
|
||||
<h3
|
||||
className="h3"
|
||||
>
|
||||
unit-title
|
||||
</h3>
|
||||
<PluginSlot
|
||||
id="unit_title_plugin"
|
||||
pluginProps={
|
||||
Object {
|
||||
"courseId": "test-course-id",
|
||||
"unitId": "test-props-id",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<h2
|
||||
className="sr-only"
|
||||
>
|
||||
|
||||
@@ -5,9 +5,9 @@ import { useDispatch } from 'react-redux';
|
||||
import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils';
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
|
||||
import { fetchCourse } from '../../../../data';
|
||||
import { processEvent } from '../../../../../course-home/data/thunks';
|
||||
import { useEventListener } from '../../../../../generic/hooks';
|
||||
import { fetchCourse } from '@src/courseware/data';
|
||||
import { processEvent } from '@src/course-home/data/thunks';
|
||||
import { useEventListener } from '@src/generic/hooks';
|
||||
import { messageTypes } from '../constants';
|
||||
|
||||
import useLoadBearingHook from './useLoadBearingHook';
|
||||
|
||||
@@ -5,9 +5,9 @@ import { getEffects, mockUseKeyedState } from '@edx/react-unit-test-utils';
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { fetchCourse } from '../../../../data';
|
||||
import { processEvent } from '../../../../../course-home/data/thunks';
|
||||
import { useEventListener } from '../../../../../generic/hooks';
|
||||
import { fetchCourse } from '@src/courseware/data';
|
||||
import { processEvent } from '@src/course-home/data/thunks';
|
||||
import { useEventListener } from '@src/generic/hooks';
|
||||
|
||||
import { messageTypes } from '../constants';
|
||||
|
||||
@@ -33,13 +33,13 @@ jest.mock('@edx/frontend-platform/logging', () => ({
|
||||
logError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../data', () => ({
|
||||
jest.mock('@src/courseware/data', () => ({
|
||||
fetchCourse: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../../../../course-home/data/thunks', () => ({
|
||||
jest.mock('@src/course-home/data/thunks', () => ({
|
||||
processEvent: jest.fn((...args) => ({ processEvent: args })),
|
||||
}));
|
||||
jest.mock('../../../../../generic/hooks', () => ({
|
||||
jest.mock('@src/generic/hooks', () => ({
|
||||
useEventListener: jest.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils/dist';
|
||||
|
||||
import { useEventListener } from '../../../../../generic/hooks';
|
||||
import { useEventListener } from '@src/generic/hooks';
|
||||
|
||||
export const stateKeys = StrictDict({
|
||||
isOpen: 'isOpen',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { mockUseKeyedState } from '@edx/react-unit-test-utils';
|
||||
import { useEventListener } from '../../../../../generic/hooks';
|
||||
import { useEventListener } from '@src/generic/hooks';
|
||||
import { messageTypes } from '../constants';
|
||||
|
||||
import useModalIFrameData, { stateKeys, DEFAULT_HEIGHT } from './useModalIFrameData';
|
||||
@@ -8,7 +8,7 @@ jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
useCallback: jest.fn((cb, prereqs) => ({ cb, prereqs })),
|
||||
}));
|
||||
jest.mock('../../../../../generic/hooks', () => ({
|
||||
jest.mock('@src/generic/hooks', () => ({
|
||||
useEventListener: jest.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils/dist';
|
||||
import { useModel } from '../../../../../generic/model-store';
|
||||
import { useModel } from '@src/generic/model-store';
|
||||
|
||||
import { modelKeys } from '../constants';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getEffects, mockUseKeyedState } from '@edx/react-unit-test-utils';
|
||||
import { useModel } from '../../../../../generic/model-store';
|
||||
import { useModel } from '@src/generic/model-store';
|
||||
|
||||
import { modelKeys } from '../constants';
|
||||
|
||||
@@ -11,7 +11,7 @@ jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
useEffect: jest.fn(),
|
||||
}));
|
||||
jest.mock('../../../../../generic/model-store', () => ({
|
||||
jest.mock('@src/generic/model-store', () => ({
|
||||
useModel: jest.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@ import React from 'react';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import { useModel } from '@src/generic/model-store';
|
||||
import { usePluginsCallback } from '@src/generic/plugin-store';
|
||||
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||
import BookmarkButton from '../../bookmark/BookmarkButton';
|
||||
import messages from '../messages';
|
||||
import ContentIFrame from './ContentIFrame';
|
||||
@@ -28,16 +30,27 @@ const Unit = ({
|
||||
const isProcessing = unit.bookmarkedUpdateState === 'loading';
|
||||
const view = authenticatedUser ? views.student : views.public;
|
||||
|
||||
const iframeUrl = getIFrameUrl({
|
||||
const getUrl = usePluginsCallback('getIFrameUrl', () => getIFrameUrl({
|
||||
id,
|
||||
view,
|
||||
format,
|
||||
examAccess,
|
||||
});
|
||||
}));
|
||||
|
||||
const iframeUrl = getUrl();
|
||||
|
||||
return (
|
||||
<div className="unit">
|
||||
<h1 className="mb-0 h3">{unit.title}</h1>
|
||||
<div className="mb-0">
|
||||
<h3 className="h3">{unit.title}</h3>
|
||||
<PluginSlot
|
||||
id="unit_title_plugin"
|
||||
pluginProps={{
|
||||
courseId,
|
||||
unitId: id,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<h2 className="sr-only">{formatMessage(messages.headerPlaceholder)}</h2>
|
||||
<BookmarkButton
|
||||
unitId={unit.id}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { when } from 'jest-when';
|
||||
import { formatMessage, shallow } from '@edx/react-unit-test-utils/dist';
|
||||
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import { useModel } from '@src/generic/model-store';
|
||||
|
||||
import BookmarkButton from '../../bookmark/BookmarkButton';
|
||||
import UnitSuspense from './UnitSuspense';
|
||||
@@ -9,7 +10,7 @@ import ContentIFrame from './ContentIFrame';
|
||||
import Unit from '.';
|
||||
import messages from '../messages';
|
||||
import { getIFrameUrl } from './urls';
|
||||
import { views } from './constants';
|
||||
import { modelKeys, views } from './constants';
|
||||
import * as hooks from './hooks';
|
||||
|
||||
jest.mock('./hooks', () => ({ useUnitData: jest.fn() }));
|
||||
@@ -22,14 +23,14 @@ jest.mock('@edx/frontend-platform/i18n', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../../generic/PageLoading', () => 'PageLoading');
|
||||
jest.mock('@src/generic/PageLoading', () => 'PageLoading');
|
||||
jest.mock('../../bookmark/BookmarkButton', () => 'BookmarkButton');
|
||||
jest.mock('./ContentIFrame', () => 'ContentIFrame');
|
||||
jest.mock('./UnitSuspense', () => 'UnitSuspense');
|
||||
jest.mock('../honor-code', () => 'HonorCode');
|
||||
jest.mock('../lock-paywall', () => 'LockPaywall');
|
||||
|
||||
jest.mock('../../../../generic/model-store', () => ({
|
||||
jest.mock('@src/generic/model-store', () => ({
|
||||
useModel: jest.fn(),
|
||||
}));
|
||||
|
||||
@@ -70,7 +71,14 @@ const unit = {
|
||||
bookmarked: false,
|
||||
bookmarkedUpdateState: 'pending',
|
||||
};
|
||||
useModel.mockReturnValue(unit);
|
||||
const mockCoursewareMetaFn = jest.fn(() => ({ wholeCourseTranslationEnabled: false }));
|
||||
const mockUnitsFn = jest.fn(() => unit);
|
||||
|
||||
when(useModel)
|
||||
.calledWith('courseHomeMeta', props.courseId)
|
||||
.mockImplementation(mockCoursewareMetaFn)
|
||||
.calledWith(modelKeys.units, props.id)
|
||||
.mockImplementation(mockUnitsFn);
|
||||
|
||||
let el;
|
||||
describe('Unit component', () => {
|
||||
@@ -113,7 +121,7 @@ describe('Unit component', () => {
|
||||
});
|
||||
describe('bookmarked, bookmark update pending', () => {
|
||||
beforeEach(() => {
|
||||
useModel.mockReturnValueOnce({ ...unit, bookmarked: true });
|
||||
mockUnitsFn.mockReturnValueOnce({ ...unit, bookmarked: true });
|
||||
renderComponent();
|
||||
});
|
||||
test('snapshot', () => {
|
||||
|
||||
@@ -19,24 +19,43 @@ const props = {
|
||||
examAccess: { blockAccess: false, accessToken: 'test-access-token' },
|
||||
};
|
||||
|
||||
describe('urls module', () => {
|
||||
describe('getIFrameUrl', () => {
|
||||
test('format provided, exam access and token available', () => {
|
||||
const params = stringify({
|
||||
...iframeParams,
|
||||
view: props.view,
|
||||
format: props.format,
|
||||
exam_access: props.examAccess.accessToken,
|
||||
});
|
||||
expect(getIFrameUrl(props)).toEqual(`${config.LMS_BASE_URL}/xblock/${props.id}?${params}`);
|
||||
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,
|
||||
});
|
||||
test('no format provided, exam access blocked', () => {
|
||||
const params = stringify({ ...iframeParams, view: props.view });
|
||||
expect(getIFrameUrl({
|
||||
id: props.id,
|
||||
view: props.view,
|
||||
examAccess: { blockAccess: true },
|
||||
})).toEqual(`${config.LMS_BASE_URL}/xblock/${props.id}?${params}`);
|
||||
expect(getIFrameUrl(props)).toEqual(`${config.LMS_BASE_URL}/xblock/${props.id}?${params}`);
|
||||
});
|
||||
test('no format provided, exam access blocked', () => {
|
||||
const params = stringify({ ...iframeParams, view: props.view });
|
||||
expect(getIFrameUrl({
|
||||
id: props.id,
|
||||
view: props.view,
|
||||
examAccess: { blockAccess: true },
|
||||
})).toEqual(`${config.LMS_BASE_URL}/xblock/${props.id}?${params}`);
|
||||
});
|
||||
test('src and dest languages provided', () => {
|
||||
const params = stringify({
|
||||
...iframeParams,
|
||||
view: props.view,
|
||||
src_lang: 'test-src-lang',
|
||||
dest_lang: 'test-dest-lang',
|
||||
});
|
||||
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}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,8 +31,7 @@ const HiddenAfterDue = ({ courseId, intl }) => {
|
||||
<>
|
||||
<br />
|
||||
<FormattedMessage
|
||||
id="learn.hiddenAfterDue.gradeAvailable"
|
||||
defaultMessage="If you have completed this assignment, your grade is available on the {progressPage}."
|
||||
{...messages.gradeAvailable}
|
||||
values={{
|
||||
progressPage: progressLink,
|
||||
}}
|
||||
|
||||
@@ -79,7 +79,7 @@ const LockPaywall = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert variant="light" aria-live="off" icon={Locked} className="lock-paywall-container">
|
||||
<Alert variant="light" aria-live="off" icon={Locked} className="lock-paywall-container" data-testId="lock-paywall-test-id">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<h4 aria-level="3">
|
||||
|
||||
@@ -76,9 +76,9 @@ describe('Lock Paywall', () => {
|
||||
it('does not display anything if course does not have verified mode', async () => {
|
||||
const courseHomeMetadata = Factory.build('courseHomeMetadata', { verified_mode: null });
|
||||
const testStore = await initializeTestStore({ courseHomeMetadata, excludeFetchSequence: true }, false);
|
||||
const { container } = render(<LockPaywall {...mockData} courseId={courseHomeMetadata.id} />, { store: testStore });
|
||||
render(<LockPaywall {...mockData} courseId={courseHomeMetadata.id} />, { store: testStore });
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
expect(screen.queryByTestId('lock-paywall-test-id')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays past expiration message if expiration date has expired', async () => {
|
||||
|
||||
@@ -110,7 +110,7 @@ const SequenceNavigation = ({
|
||||
};
|
||||
|
||||
return sequenceStatus === LOADED && (
|
||||
<nav id="courseware-sequenceNavigation" className={classNames('sequence-navigation', className, { 'mr-2': shouldDisplayNotificationTriggerInSequence })}>
|
||||
<nav id="courseware-sequence-navigation" data-testid="courseware-sequence-navigation" className={classNames('sequence-navigation', className, { 'mr-2': shouldDisplayNotificationTriggerInSequence })}>
|
||||
{renderPreviousButton()}
|
||||
{renderUnitButtons()}
|
||||
{renderNextButton()}
|
||||
|
||||
@@ -33,9 +33,9 @@ describe('Sequence Navigation', () => {
|
||||
|
||||
it('is empty while loading', async () => {
|
||||
const testStore = await initializeTestStore({ excludeFetchSequence: true }, false);
|
||||
const { container } = render(<SequenceNavigation {...mockData} />, { store: testStore, wrapWithRouter: true });
|
||||
render(<SequenceNavigation {...mockData} />, { store: testStore, wrapWithRouter: true });
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
expect(screen.queryByTestId('courseware-sequence-navigation')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders empty div without unitId', () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ const SequenceNavigationDropdown = ({
|
||||
unitIds,
|
||||
}) => (
|
||||
<Dropdown className="sequence-navigation-dropdown">
|
||||
<Dropdown.Toggle variant="link" className="font-weight-normal w-100 border-right-0">
|
||||
<Dropdown.Toggle id="navigation-toggle" variant="link" className="font-weight-normal w-100 border-right-0">
|
||||
<FormattedMessage
|
||||
defaultMessage="{current} of {total}"
|
||||
description="The title of the mobile menu for sequence navigation of units"
|
||||
|
||||
@@ -6,7 +6,7 @@ const Sidebar = () => (
|
||||
{
|
||||
SIDEBAR_ORDER.map((sideBarId) => {
|
||||
const SidebarToRender = SIDEBARS[sideBarId].Sidebar;
|
||||
return <SidebarToRender />;
|
||||
return <SidebarToRender key={sideBarId} />;
|
||||
})
|
||||
}
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { useContext } from 'react';
|
||||
import { breakpoints, useWindowSize } from '@edx/paragon';
|
||||
import { breakpoints, useWindowSize } from '@openedx/paragon';
|
||||
import SidebarContext from './SidebarContext';
|
||||
import { SIDEBAR_ORDER, SIDEBARS } from './sidebars';
|
||||
|
||||
|
||||
@@ -29,9 +29,13 @@ const NotificationIcon = ({
|
||||
</>
|
||||
);
|
||||
|
||||
NotificationIcon.defaultProps = {
|
||||
status: null,
|
||||
};
|
||||
|
||||
NotificationIcon.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
status: PropTypes.string,
|
||||
notificationColor: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Factory } from 'rosie';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { breakpoints } from '@edx/paragon';
|
||||
import { breakpoints } from '@openedx/paragon';
|
||||
import { initializeTestStore, render } from '../../setupTest';
|
||||
import { buildTopicsFromUnits } from '../data/__factories__/discussionTopics.factory';
|
||||
import { executeThunk } from '../../utils';
|
||||
|
||||
@@ -39,6 +39,8 @@ Factory.define('courseMetadata')
|
||||
enabled: false,
|
||||
},
|
||||
marketing_url: null,
|
||||
language: 'en',
|
||||
whole_course_translation_enabled: false,
|
||||
celebrations: null,
|
||||
enroll_alert: null,
|
||||
course_exit_page_is_active: true,
|
||||
|
||||
@@ -115,6 +115,7 @@ function normalizeMetadata(metadata) {
|
||||
courseExitPageIsActive: data.course_exit_page_is_active,
|
||||
certificateData: camelCaseObject(data.certificate_data),
|
||||
entranceExamData: camelCaseObject(data.entrance_exam_data),
|
||||
language: data.language,
|
||||
timeOffsetMillis: getTimeOffsetMillis(headers && headers.date, requestTime, responseTime),
|
||||
verifyIdentityUrl: data.verify_identity_url,
|
||||
verificationStatus: data.verification_status,
|
||||
|
||||
@@ -335,6 +335,8 @@ describe('Courseware Service', () => {
|
||||
linkedin_add_to_profile_url: null,
|
||||
user_needs_integrity_signature: boolean(false),
|
||||
learning_assistant_enabled: boolean(false),
|
||||
language: string('en'),
|
||||
whole_course_translation_enabled: boolean(false),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user