Adding constants and renaming src/course to src/active-course
src/course was confusing. And we had no constants for the various ‘loading’ strings all over the place.
This commit is contained in:
@@ -1,33 +1,33 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export const LOADING = 'loading';
|
||||
export const LOADED = 'loaded';
|
||||
export const FAILED = 'failed';
|
||||
export const DENIED = 'denied';
|
||||
export const COURSE_LOADING = 'loading';
|
||||
export const COURSE_LOADED = 'loaded';
|
||||
export const COURSE_FAILED = 'failed';
|
||||
export const COURSE_DENIED = 'denied';
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'activeCourse',
|
||||
initialState: {
|
||||
courseStatus: LOADING,
|
||||
courseStatus: COURSE_LOADING,
|
||||
courseId: null,
|
||||
},
|
||||
reducers: {
|
||||
fetchCourseRequest: (state, { payload }) => {
|
||||
state.courseId = payload.courseId;
|
||||
state.courseStatus = LOADING;
|
||||
state.courseStatus = COURSE_LOADING;
|
||||
},
|
||||
fetchCourseSuccess: (state, { payload }) => {
|
||||
state.courseId = payload.courseId;
|
||||
state.courseStatus = LOADED;
|
||||
state.courseStatus = COURSE_LOADED;
|
||||
},
|
||||
fetchCourseFailure: (state, { payload }) => {
|
||||
state.courseId = payload.courseId;
|
||||
state.courseStatus = FAILED;
|
||||
state.courseStatus = COURSE_FAILED;
|
||||
},
|
||||
fetchCourseDenied: (state, { payload }) => {
|
||||
state.courseId = payload.courseId;
|
||||
state.courseStatus = DENIED;
|
||||
state.courseStatus = COURSE_DENIED;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
export { activeCourseSelector } from './data/selectors';
|
||||
export {
|
||||
reducer,
|
||||
LOADING,
|
||||
LOADED,
|
||||
FAILED,
|
||||
DENIED,
|
||||
COURSE_LOADING,
|
||||
COURSE_LOADED,
|
||||
COURSE_FAILED,
|
||||
COURSE_DENIED,
|
||||
fetchCourseRequest,
|
||||
fetchCourseSuccess,
|
||||
fetchCourseFailure,
|
||||
@@ -1,16 +1,14 @@
|
||||
import { Factory } from 'rosie';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import * as thunks from './thunks';
|
||||
|
||||
import executeThunk from '../../utils';
|
||||
|
||||
import { COURSE_LOADED, COURSE_LOADING, COURSE_FAILED } from '../../active-course';
|
||||
import initializeMockApp from '../../setupTest';
|
||||
import initializeStore from '../../store';
|
||||
import { LOADING, FAILED } from '../../course';
|
||||
import executeThunk from '../../utils';
|
||||
|
||||
import * as thunks from './thunks';
|
||||
|
||||
const { loggingService } = initializeMockApp();
|
||||
|
||||
@@ -43,7 +41,7 @@ describe('Data layer integration tests', () => {
|
||||
|
||||
it('Should initialize store', () => {
|
||||
expect(store.getState().activeCourse.courseId).toBeNull();
|
||||
expect(store.getState().activeCourse.courseStatus).toEqual(LOADING);
|
||||
expect(store.getState().activeCourse.courseStatus).toEqual(COURSE_LOADING);
|
||||
});
|
||||
|
||||
describe('Test fetchDatesTab', () => {
|
||||
@@ -57,7 +55,7 @@ describe('Data layer integration tests', () => {
|
||||
await executeThunk(thunks.fetchDatesTab(courseId), store.dispatch);
|
||||
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(store.getState().activeCourse.courseStatus).toEqual(FAILED);
|
||||
expect(store.getState().activeCourse.courseStatus).toEqual(COURSE_FAILED);
|
||||
});
|
||||
|
||||
it('Should fetch, normalize, and save metadata', async () => {
|
||||
@@ -72,7 +70,7 @@ describe('Data layer integration tests', () => {
|
||||
await executeThunk(thunks.fetchDatesTab(courseId), store.dispatch);
|
||||
|
||||
const state = store.getState();
|
||||
expect(state.activeCourse.courseStatus).toEqual('loaded');
|
||||
expect(state.activeCourse.courseStatus).toEqual(COURSE_LOADED);
|
||||
expect(state.activeCourse.courseId).toEqual(courseId);
|
||||
expect(state.courseHome.displayResetDatesToast).toBe(false);
|
||||
|
||||
@@ -111,7 +109,7 @@ describe('Data layer integration tests', () => {
|
||||
await executeThunk(thunks.fetchOutlineTab(courseId), store.dispatch);
|
||||
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(store.getState().activeCourse.courseStatus).toEqual('failed');
|
||||
expect(store.getState().activeCourse.courseStatus).toEqual(COURSE_FAILED);
|
||||
});
|
||||
|
||||
it('Should fetch, normalize, and save metadata', async () => {
|
||||
@@ -126,7 +124,7 @@ describe('Data layer integration tests', () => {
|
||||
await executeThunk(thunks.fetchOutlineTab(courseId), store.dispatch);
|
||||
|
||||
const state = store.getState();
|
||||
expect(state.activeCourse.courseStatus).toEqual('loaded');
|
||||
expect(state.activeCourse.courseStatus).toEqual(COURSE_LOADED);
|
||||
expect(state.courseHome.displayResetDatesToast).toBe(false);
|
||||
|
||||
// Validate course
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export const LOADING = 'loading';
|
||||
export const LOADED = 'loaded';
|
||||
export const FAILED = 'failed';
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'course-home',
|
||||
initialState: {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
fetchCourseRequest,
|
||||
fetchCourseSuccess,
|
||||
fetchCourseFailure,
|
||||
} from '../../course';
|
||||
} from '../../active-course';
|
||||
|
||||
import {
|
||||
toggleResetDatesToast,
|
||||
|
||||
@@ -13,15 +13,20 @@ import {
|
||||
fetchSequence,
|
||||
getResumeBlock,
|
||||
saveSequencePosition,
|
||||
SEQUENCE_LOADED,
|
||||
SEQUENCE_LOADING,
|
||||
SEQUENCE_FAILED,
|
||||
} from './data';
|
||||
import { TabPage } from '../tab-page';
|
||||
|
||||
import {
|
||||
activeCourseSelector, COURSE_LOADED, COURSE_LOADING, COURSE_FAILED, COURSE_DENIED,
|
||||
} from '../active-course';
|
||||
import Course from './course';
|
||||
import { handleNextSectionCelebration } from './course/celebration';
|
||||
import { activeCourseSelector } from '../course';
|
||||
|
||||
const checkExamRedirect = memoize((sequenceStatus, sequence) => {
|
||||
if (sequenceStatus === 'loaded') {
|
||||
if (sequenceStatus === SEQUENCE_LOADED) {
|
||||
if (sequence.isTimeLimited && sequence.lmsWebUrl !== undefined) {
|
||||
global.location.assign(sequence.lmsWebUrl);
|
||||
}
|
||||
@@ -29,7 +34,7 @@ const checkExamRedirect = memoize((sequenceStatus, sequence) => {
|
||||
});
|
||||
|
||||
const checkResumeRedirect = memoize((courseStatus, courseId, sequenceId, firstSequenceId) => {
|
||||
if (courseStatus === 'loaded' && !sequenceId) {
|
||||
if (courseStatus === COURSE_LOADED && !sequenceId) {
|
||||
// Note that getResumeBlock is just an API call, not a redux thunk.
|
||||
getResumeBlock(courseId).then((data) => {
|
||||
// This is a replace because we don't want this change saved in the browser's history.
|
||||
@@ -43,7 +48,7 @@ const checkResumeRedirect = memoize((courseStatus, courseId, sequenceId, firstSe
|
||||
});
|
||||
|
||||
const checkContentRedirect = memoize((courseId, sequenceStatus, sequenceId, sequence, unitId) => {
|
||||
if (sequenceStatus === 'loaded' && sequenceId && !unitId) {
|
||||
if (sequenceStatus === SEQUENCE_LOADED && sequenceId && !unitId) {
|
||||
if (sequence.unitIds !== undefined && sequence.unitIds.length > 0) {
|
||||
const nextUnitId = sequence.unitIds[sequence.activeUnitIndex];
|
||||
// This is a replace because we don't want this change saved in the browser's history.
|
||||
@@ -60,7 +65,7 @@ class CoursewareContainer extends Component {
|
||||
sequenceStatus,
|
||||
sequence,
|
||||
} = this.props;
|
||||
if (sequenceStatus === 'loaded' && sequence.saveUnitPosition && unitId) {
|
||||
if (sequenceStatus === SEQUENCE_LOADED && sequence.saveUnitPosition && unitId) {
|
||||
const activeUnitIndex = sequence.unitIds.indexOf(unitId);
|
||||
this.props.saveSequencePosition(courseId, sequenceId, activeUnitIndex);
|
||||
}
|
||||
@@ -208,7 +213,7 @@ class CoursewareContainer extends Component {
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (courseStatus === 'denied') {
|
||||
if (courseStatus === COURSE_DENIED) {
|
||||
return this.renderDenied();
|
||||
}
|
||||
|
||||
@@ -258,8 +263,8 @@ CoursewareContainer.propTypes = {
|
||||
sequenceId: PropTypes.string,
|
||||
firstSequenceId: PropTypes.string,
|
||||
unitId: PropTypes.string,
|
||||
courseStatus: PropTypes.oneOf(['loaded', 'loading', 'failed', 'denied']).isRequired,
|
||||
sequenceStatus: PropTypes.oneOf(['loaded', 'loading', 'failed']).isRequired,
|
||||
courseStatus: PropTypes.oneOf([COURSE_LOADED, COURSE_LOADING, COURSE_FAILED, COURSE_DENIED]).isRequired,
|
||||
sequenceStatus: PropTypes.oneOf([SEQUENCE_LOADED, SEQUENCE_LOADING, SEQUENCE_FAILED]).isRequired,
|
||||
nextSequence: sequenceShape,
|
||||
previousSequence: sequenceShape,
|
||||
course: courseShape,
|
||||
@@ -292,7 +297,7 @@ const sequenceIdsSelector = createSelector(
|
||||
activeCourseSelector,
|
||||
(state) => state.models.sections,
|
||||
(courseStatus, course, sectionsById) => {
|
||||
if (courseStatus !== 'loaded') {
|
||||
if (courseStatus !== COURSE_LOADED) {
|
||||
return [];
|
||||
}
|
||||
const { sectionIds = [] } = course;
|
||||
@@ -333,7 +338,7 @@ const firstSequenceIdSelector = createSelector(
|
||||
activeCourseSelector,
|
||||
(state) => state.models.sections || {},
|
||||
(courseStatus, course, sectionsById) => {
|
||||
if (courseStatus !== 'loaded') {
|
||||
if (courseStatus !== COURSE_LOADED) {
|
||||
return null;
|
||||
}
|
||||
const { sectionIds = [] } = course;
|
||||
|
||||
@@ -6,6 +6,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faHome } from '@fortawesome/free-solid-svg-icons';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useModel } from '../../generic/model-store';
|
||||
import { COURSE_LOADED } from '../../active-course';
|
||||
import { SEQUENCE_LOADED } from '../data';
|
||||
|
||||
function CourseBreadcrumb({
|
||||
url, children, withSeparator, ...attrs
|
||||
@@ -44,7 +46,7 @@ export default function CourseBreadcrumbs({
|
||||
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
|
||||
|
||||
const links = useMemo(() => {
|
||||
if (courseStatus === 'loaded' && sequenceStatus === 'loaded') {
|
||||
if (courseStatus === COURSE_LOADED && sequenceStatus === SEQUENCE_LOADED) {
|
||||
return [section, sequence].filter(node => !!node).map((node) => ({
|
||||
id: node.id,
|
||||
label: node.title,
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
|
||||
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import * as thunks from './thunks';
|
||||
|
||||
import executeThunk from '../../../../utils';
|
||||
|
||||
import initializeMockApp from '../../../../setupTest';
|
||||
import initializeStore from '../../../../store';
|
||||
|
||||
import {
|
||||
addBookmark,
|
||||
removeBookmark,
|
||||
BOOKMARK_FAILED,
|
||||
BOOKMARK_LOADED,
|
||||
} from './thunks';
|
||||
|
||||
const { loggingService } = initializeMockApp();
|
||||
|
||||
const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
|
||||
@@ -32,24 +35,24 @@ describe('Data layer integration tests', () => {
|
||||
it('Should fail to create bookmark in case of error', async () => {
|
||||
axiosMock.onPost(createBookmarkURL).networkError();
|
||||
|
||||
await executeThunk(thunks.addBookmark(unitId), store.dispatch);
|
||||
await executeThunk(addBookmark(unitId), store.dispatch);
|
||||
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(axiosMock.history.post[0].url).toEqual(createBookmarkURL);
|
||||
expect(store.getState().models.units[unitId]).toEqual(expect.objectContaining({
|
||||
bookmarked: false,
|
||||
bookmarkedUpdateState: 'failed',
|
||||
bookmarkedUpdateState: BOOKMARK_FAILED,
|
||||
}));
|
||||
});
|
||||
|
||||
it('Should create bookmark and update model state', async () => {
|
||||
axiosMock.onPost(createBookmarkURL).reply(201);
|
||||
|
||||
await executeThunk(thunks.addBookmark(unitId), store.dispatch);
|
||||
await executeThunk(addBookmark(unitId), store.dispatch);
|
||||
|
||||
expect(store.getState().models.units[unitId]).toEqual(expect.objectContaining({
|
||||
bookmarked: true,
|
||||
bookmarkedUpdateState: 'loaded',
|
||||
bookmarkedUpdateState: BOOKMARK_LOADED,
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -60,24 +63,24 @@ describe('Data layer integration tests', () => {
|
||||
it('Should fail to remove bookmark in case of error', async () => {
|
||||
axiosMock.onDelete(deleteBookmarkURL).networkError();
|
||||
|
||||
await executeThunk(thunks.removeBookmark(unitId), store.dispatch);
|
||||
await executeThunk(removeBookmark(unitId), store.dispatch);
|
||||
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(axiosMock.history.delete[0].url).toEqual(deleteBookmarkURL);
|
||||
expect(store.getState().models.units[unitId]).toEqual(expect.objectContaining({
|
||||
bookmarked: true,
|
||||
bookmarkedUpdateState: 'failed',
|
||||
bookmarkedUpdateState: BOOKMARK_FAILED,
|
||||
}));
|
||||
});
|
||||
|
||||
it('Should delete bookmark and update model state', async () => {
|
||||
axiosMock.onDelete(deleteBookmarkURL).reply(201);
|
||||
|
||||
await executeThunk(thunks.removeBookmark(unitId), store.dispatch);
|
||||
await executeThunk(removeBookmark(unitId), store.dispatch);
|
||||
|
||||
expect(store.getState().models.units[unitId]).toEqual(expect.objectContaining({
|
||||
bookmarked: false,
|
||||
bookmarkedUpdateState: 'loaded',
|
||||
bookmarkedUpdateState: BOOKMARK_LOADED,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,10 @@ import {
|
||||
} from './api';
|
||||
import { updateModel } from '../../../../generic/model-store';
|
||||
|
||||
export const BOOKMARK_LOADING = 'loading';
|
||||
export const BOOKMARK_LOADED = 'loaded';
|
||||
export const BOOKMARK_FAILED = 'failed';
|
||||
|
||||
export function addBookmark(unitId) {
|
||||
return async (dispatch) => {
|
||||
// Optimistically update the bookmarked flag.
|
||||
@@ -13,7 +17,7 @@ export function addBookmark(unitId) {
|
||||
model: {
|
||||
id: unitId,
|
||||
bookmarked: true,
|
||||
bookmarkedUpdateState: 'loading',
|
||||
bookmarkedUpdateState: BOOKMARK_LOADING,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -24,7 +28,7 @@ export function addBookmark(unitId) {
|
||||
model: {
|
||||
id: unitId,
|
||||
bookmarked: true,
|
||||
bookmarkedUpdateState: 'loaded',
|
||||
bookmarkedUpdateState: BOOKMARK_LOADED,
|
||||
},
|
||||
}));
|
||||
} catch (error) {
|
||||
@@ -34,7 +38,7 @@ export function addBookmark(unitId) {
|
||||
model: {
|
||||
id: unitId,
|
||||
bookmarked: false,
|
||||
bookmarkedUpdateState: 'failed',
|
||||
bookmarkedUpdateState: BOOKMARK_FAILED,
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -49,7 +53,7 @@ export function removeBookmark(unitId) {
|
||||
model: {
|
||||
id: unitId,
|
||||
bookmarked: false,
|
||||
bookmarkedUpdateState: 'loading',
|
||||
bookmarkedUpdateState: BOOKMARK_LOADING,
|
||||
},
|
||||
}));
|
||||
try {
|
||||
@@ -59,7 +63,7 @@ export function removeBookmark(unitId) {
|
||||
model: {
|
||||
id: unitId,
|
||||
bookmarked: false,
|
||||
bookmarkedUpdateState: 'loaded',
|
||||
bookmarkedUpdateState: BOOKMARK_LOADED,
|
||||
},
|
||||
}));
|
||||
} catch (error) {
|
||||
@@ -69,7 +73,7 @@ export function removeBookmark(unitId) {
|
||||
model: {
|
||||
id: unitId,
|
||||
bookmarked: true,
|
||||
bookmarkedUpdateState: 'failed',
|
||||
bookmarkedUpdateState: BOOKMARK_FAILED,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
export { default as BookmarkButton } from './BookmarkButton';
|
||||
export { default as BookmarkFilledIcon } from './BookmarkFilledIcon';
|
||||
export { default as BookmarkOutlineIcon } from './BookmarkFilledIcon';
|
||||
|
||||
export {
|
||||
BOOKMARK_LOADING,
|
||||
BOOKMARK_LOADED,
|
||||
BOOKMARK_FAILED,
|
||||
} from './data/thunks';
|
||||
|
||||
@@ -15,6 +15,7 @@ import CourseLicense from '../course-license';
|
||||
import messages from './messages';
|
||||
import { SequenceNavigation, UnitNavigation } from './sequence-navigation';
|
||||
import SequenceContent from './SequenceContent';
|
||||
import { SEQUENCE_LOADED, SEQUENCE_LOADING } from '../../data';
|
||||
|
||||
function Sequence({
|
||||
unitId,
|
||||
@@ -73,7 +74,7 @@ function Sequence({
|
||||
const { add, remove } = useContext(UserMessagesContext);
|
||||
useEffect(() => {
|
||||
let id = null;
|
||||
if (sequenceStatus === 'loaded') {
|
||||
if (sequenceStatus === SEQUENCE_LOADED) {
|
||||
if (sequence.bannerText) {
|
||||
id = add({
|
||||
code: null,
|
||||
@@ -101,7 +102,7 @@ function Sequence({
|
||||
}
|
||||
}, [unit]);
|
||||
|
||||
if (sequenceStatus === 'loading') {
|
||||
if (sequenceStatus === SEQUENCE_LOADING) {
|
||||
if (!sequenceId) {
|
||||
return (<div> {intl.formatMessage(messages['learn.sequence.no.content'])} </div>);
|
||||
}
|
||||
@@ -114,7 +115,7 @@ function Sequence({
|
||||
|
||||
const gated = sequence && sequence.gatedContent !== undefined && sequence.gatedContent.gated;
|
||||
|
||||
if (sequenceStatus === 'loaded') {
|
||||
if (sequenceStatus === SEQUENCE_LOADED) {
|
||||
return (
|
||||
<div className="sequence-container">
|
||||
<div className="sequence">
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useModel } from '../../../generic/model-store';
|
||||
import PageLoading from '../../../generic/PageLoading';
|
||||
import { resetDeadlines } from '../../../course-home/data/thunks';
|
||||
import { fetchCourse } from '../../data/thunks';
|
||||
import { BOOKMARK_LOADING } from '../bookmark';
|
||||
|
||||
const LockPaywall = React.lazy(() => import('./lock-paywall'));
|
||||
|
||||
@@ -106,7 +107,7 @@ function Unit({
|
||||
<BookmarkButton
|
||||
unitId={unit.id}
|
||||
isBookmarked={unit.bookmarked}
|
||||
isProcessing={unit.bookmarkedUpdateState === 'loading'}
|
||||
isProcessing={unit.bookmarkedUpdateState === BOOKMARK_LOADING}
|
||||
/>
|
||||
{ contentTypeGatingEnabled && unit.graded && (
|
||||
<Suspense
|
||||
|
||||
@@ -5,13 +5,14 @@ import classNames from 'classnames';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
|
||||
import UnitButton from './UnitButton';
|
||||
import SequenceNavigationTabs from './SequenceNavigationTabs';
|
||||
import { useSequenceNavigationMetadata } from './hooks';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import { LOADED } from '../../../data/slice';
|
||||
import { SEQUENCE_LOADED } from '../../../data';
|
||||
|
||||
export default function SequenceNavigation({
|
||||
unitId,
|
||||
@@ -24,7 +25,7 @@ export default function SequenceNavigation({
|
||||
const sequence = useModel('sequences', sequenceId);
|
||||
const { isFirstUnit, isLastUnit } = useSequenceNavigationMetadata(sequenceId, unitId);
|
||||
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
|
||||
const isLocked = sequenceStatus === LOADED ? (
|
||||
const isLocked = sequenceStatus === SEQUENCE_LOADED ? (
|
||||
sequence.gatedContent !== undefined && sequence.gatedContent.gated
|
||||
) : undefined;
|
||||
|
||||
@@ -49,7 +50,7 @@ export default function SequenceNavigation({
|
||||
);
|
||||
};
|
||||
|
||||
return sequenceStatus === LOADED && (
|
||||
return sequenceStatus === SEQUENCE_LOADED && (
|
||||
<nav className={classNames('sequence-navigation', className)}>
|
||||
<Button className="previous-btn" onClick={previousSequenceHandler} disabled={isFirstUnit}>
|
||||
<FontAwesomeIcon icon={faChevronLeft} className="mr-2" size="sm" />
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
import { COURSE_LOADED } from '../../../../active-course';
|
||||
|
||||
import { sequenceIdsSelector } from '../../../data/selectors';
|
||||
|
||||
export function useSequenceNavigationMetadata(currentSequenceId, currentUnitId) {
|
||||
@@ -10,7 +12,7 @@ export function useSequenceNavigationMetadata(currentSequenceId, currentUnitId)
|
||||
const courseStatus = useSelector(state => state.activeCourse.courseStatus);
|
||||
|
||||
// If we don't know the sequence and unit yet, then assume no.
|
||||
if (courseStatus !== 'loaded' || !currentSequenceId || !currentUnitId) {
|
||||
if (courseStatus !== COURSE_LOADED || !currentSequenceId || !currentUnitId) {
|
||||
return { isFirstUnit: false, isLastUnit: false };
|
||||
}
|
||||
const isFirstSequence = sequenceIds.indexOf(currentSequenceId) === 0;
|
||||
|
||||
@@ -10,4 +10,9 @@ export {
|
||||
export {
|
||||
sequenceIdsSelector,
|
||||
} from './selectors';
|
||||
export { reducer } from './slice';
|
||||
export {
|
||||
reducer,
|
||||
SEQUENCE_LOADING,
|
||||
SEQUENCE_LOADED,
|
||||
SEQUENCE_FAILED,
|
||||
} from './slice';
|
||||
|
||||
@@ -11,6 +11,8 @@ import executeThunk from '../../utils';
|
||||
import buildSimpleCourseBlocks from './__factories__/courseBlocks.factory';
|
||||
import initializeMockApp from '../../setupTest';
|
||||
import initializeStore from '../../store';
|
||||
import { SEQUENCE_LOADING, SEQUENCE_LOADED, SEQUENCE_FAILED } from './slice';
|
||||
import { COURSE_LOADED, COURSE_FAILED, COURSE_DENIED } from '../../active-course';
|
||||
|
||||
const { loggingService } = initializeMockApp();
|
||||
|
||||
@@ -55,7 +57,7 @@ describe('Data layer integration tests', () => {
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(store.getState().activeCourse).toEqual(expect.objectContaining({
|
||||
courseId,
|
||||
courseStatus: 'failed',
|
||||
courseStatus: COURSE_FAILED,
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -78,7 +80,7 @@ describe('Data layer integration tests', () => {
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
expect(state.activeCourse.courseStatus).toEqual('denied');
|
||||
expect(state.activeCourse.courseStatus).toEqual(COURSE_DENIED);
|
||||
|
||||
// check that at least one key camel cased, thus course data normalized
|
||||
expect(state.models.courses[forbiddenCourseMetadata.id].canLoadCourseware).not.toBeUndefined();
|
||||
@@ -92,9 +94,9 @@ describe('Data layer integration tests', () => {
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
expect(state.activeCourse.courseStatus).toEqual('loaded');
|
||||
expect(state.activeCourse.courseStatus).toEqual(COURSE_LOADED);
|
||||
expect(state.activeCourse.courseId).toEqual(courseId);
|
||||
expect(state.courseware.sequenceStatus).toEqual('loading');
|
||||
expect(state.courseware.sequenceStatus).toEqual(SEQUENCE_LOADING);
|
||||
expect(state.courseware.sequenceId).toEqual(null);
|
||||
|
||||
// check that at least one key camel cased, thus course data normalized
|
||||
@@ -109,7 +111,7 @@ describe('Data layer integration tests', () => {
|
||||
await executeThunk(thunks.fetchSequence(sequenceId), store.dispatch);
|
||||
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(store.getState().courseware.sequenceStatus).toEqual('failed');
|
||||
expect(store.getState().courseware.sequenceStatus).toEqual(SEQUENCE_FAILED);
|
||||
});
|
||||
|
||||
it('Should fetch and normalize metadata, and then update existing models with sequence metadata', async () => {
|
||||
@@ -139,9 +141,9 @@ describe('Data layer integration tests', () => {
|
||||
// Update our state variable again.
|
||||
state = store.getState();
|
||||
|
||||
expect(state.activeCourse.courseStatus).toEqual('loaded');
|
||||
expect(state.activeCourse.courseStatus).toEqual(COURSE_LOADED);
|
||||
expect(state.activeCourse.courseId).toEqual(courseId);
|
||||
expect(state.courseware.sequenceStatus).toEqual('loading');
|
||||
expect(state.courseware.sequenceStatus).toEqual(SEQUENCE_LOADING);
|
||||
expect(state.courseware.sequenceId).toEqual(null);
|
||||
|
||||
await executeThunk(thunks.fetchSequence(sequenceId), store.dispatch);
|
||||
@@ -163,9 +165,9 @@ describe('Data layer integration tests', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
expect(state.activeCourse.courseStatus).toEqual('loaded');
|
||||
expect(state.activeCourse.courseStatus).toEqual(COURSE_LOADED);
|
||||
expect(state.activeCourse.courseId).toEqual(courseId);
|
||||
expect(state.courseware.sequenceStatus).toEqual('loaded');
|
||||
expect(state.courseware.sequenceStatus).toEqual(SEQUENCE_LOADED);
|
||||
expect(state.courseware.sequenceId).toEqual(sequenceId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
import { COURSE_LOADED } from '../../active-course';
|
||||
|
||||
export function sequenceIdsSelector(state) {
|
||||
if (state.activeCourse.courseStatus !== 'loaded') {
|
||||
if (state.activeCourse.courseStatus !== COURSE_LOADED) {
|
||||
return [];
|
||||
}
|
||||
const { sectionIds = [] } = state.models.courses[state.activeCourse.courseId];
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export const LOADING = 'loading';
|
||||
export const LOADED = 'loaded';
|
||||
export const FAILED = 'failed';
|
||||
export const DENIED = 'denied';
|
||||
export const SEQUENCE_LOADING = 'loading';
|
||||
export const SEQUENCE_LOADED = 'loaded';
|
||||
export const SEQUENCE_FAILED = 'failed';
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'courseware',
|
||||
initialState: {
|
||||
sequenceStatus: 'loading',
|
||||
sequenceStatus: SEQUENCE_LOADING,
|
||||
sequenceId: null,
|
||||
},
|
||||
reducers: {
|
||||
fetchSequenceRequest: (state, { payload }) => {
|
||||
state.sequenceId = payload.sequenceId;
|
||||
state.sequenceStatus = LOADING;
|
||||
state.sequenceStatus = SEQUENCE_LOADING;
|
||||
},
|
||||
fetchSequenceSuccess: (state, { payload }) => {
|
||||
state.sequenceId = payload.sequenceId;
|
||||
state.sequenceStatus = LOADED;
|
||||
state.sequenceStatus = SEQUENCE_LOADED;
|
||||
},
|
||||
fetchSequenceFailure: (state, { payload }) => {
|
||||
state.sequenceId = payload.sequenceId;
|
||||
state.sequenceStatus = FAILED;
|
||||
state.sequenceStatus = SEQUENCE_FAILED;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
fetchCourseSuccess,
|
||||
fetchCourseFailure,
|
||||
fetchCourseDenied,
|
||||
} from '../../course';
|
||||
} from '../../active-course';
|
||||
import {
|
||||
fetchSequenceRequest,
|
||||
fetchSequenceSuccess,
|
||||
|
||||
@@ -14,7 +14,7 @@ import { configureStore } from '@reduxjs/toolkit';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import AppProvider from '@edx/frontend-platform/react/AppProvider';
|
||||
import { reducer as activeCourseReducer } from './course';
|
||||
import { reducer as activeCourseReducer } from './active-course';
|
||||
import { reducer as coursewareReducer } from './courseware/data/slice';
|
||||
import { reducer as modelsReducer } from './generic/model-store';
|
||||
import { UserMessagesProvider } from './generic/user-messages';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
|
||||
import { reducer as activeCourseReducer } from './course';
|
||||
import { reducer as activeCourseReducer } from './active-course';
|
||||
import { reducer as courseHomeReducer } from './course-home';
|
||||
import { reducer as coursewareReducer } from './courseware';
|
||||
import { reducer as modelsReducer } from './generic/model-store';
|
||||
|
||||
@@ -10,6 +10,7 @@ import messages from './messages';
|
||||
import LoadedTabPage from './LoadedTabPage';
|
||||
import LearningToast from '../toast/LearningToast';
|
||||
import { toggleResetDatesToast } from '../course-home/data/slice';
|
||||
import { COURSE_LOADED, COURSE_LOADING } from '../active-course';
|
||||
|
||||
function TabPage({
|
||||
intl,
|
||||
@@ -22,7 +23,7 @@ function TabPage({
|
||||
} = useSelector(state => state.courseHome);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
if (courseStatus === 'loading') {
|
||||
if (courseStatus === COURSE_LOADING) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
@@ -33,7 +34,7 @@ function TabPage({
|
||||
);
|
||||
}
|
||||
|
||||
if (courseStatus === 'loaded') {
|
||||
if (courseStatus === COURSE_LOADED) {
|
||||
return (
|
||||
<>
|
||||
<LearningToast
|
||||
|
||||
Reference in New Issue
Block a user