-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Course outline Top level page (#36)
* feat: [2u-259] add components * feat: [2u-259] fix sidebar * feat: [2u-259] add tests, fix links * feat: [2u-259] fix messages * feat: [2u-159] fix reducer and sidebar * feat: [2u-259] fix reducer * feat: [2u-259] remove warning from selectors * feat: [2u-259] remove indents --------- Co-authored-by: Vladislav Keblysh <[email protected]> feat: Course outline Status Bar (#50) * feat: [2u-259] add components * feat: [2u-259] fix sidebar * feat: [2u-259] add tests, fix links * feat: [2u-259] fix messages * feat: [2u-159] fix reducer and sidebar * feat: add checklist * feat: [2u-259] fix reducer * feat: [2u-259] remove warning from selectors * feat: [2u-259] remove indents * feat: [2u-259] add api, enable modal * feat: [2u-259] add tests * feat: [2u-259] add translates * feat: [2u-271] fix transalates * feat: [2u-281] fix isQuery pending, utils, hooks * feat: [2u-281] fix useScrollToHashElement * feat: [2u-271] fix imports --------- Co-authored-by: Vladislav Keblysh <[email protected]> feat: Course Outline Reindex (#55) * feat: [2u-277] add alerts * feat: [2u-277] add translates * feat: [2u-277] fix tests * fix: [2u-277] fix slice and hook --------- Co-authored-by: Vladislav Keblysh <[email protected]> fix: Course outline tests (#56) * fix: fixed course outline status bar tests * fix: fixed course outline status bar tests * fix: fixed course outline enable highlights modal tests * fix: enable modal tests fix: increase code coverage on the page
- Loading branch information
1 parent
bebbc15
commit 2d8e1d6
Showing
59 changed files
with
5,467 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { useIntl } from '@edx/frontend-platform/i18n'; | ||
import { | ||
Container, | ||
Layout, | ||
TransitionReplace, | ||
} from '@edx/paragon'; | ||
import { | ||
CheckCircle as CheckCircleIcon, | ||
Warning as WarningIcon, | ||
} from '@edx/paragon/icons'; | ||
|
||
import SubHeader from '../generic/sub-header/SubHeader'; | ||
import { RequestStatus } from '../data/constants'; | ||
import InternetConnectionAlert from '../generic/internet-connection-alert'; | ||
import AlertMessage from '../generic/alert-message'; | ||
import HeaderNavigations from './header-navigations/HeaderNavigations'; | ||
import OutlineSideBar from './outline-sidebar/OutlineSidebar'; | ||
import messages from './messages'; | ||
import { useCourseOutline } from './hooks'; | ||
import StatusBar from './status-bar/StatusBar'; | ||
import EnableHighlightsModal from './enable-highlights-modal/EnableHighlightsModal'; | ||
|
||
const CourseOutline = ({ courseId }) => { | ||
const intl = useIntl(); | ||
|
||
const { | ||
savingStatus, | ||
statusBarData, | ||
isLoading, | ||
isReIndexShow, | ||
showErrorAlert, | ||
showSuccessAlert, | ||
isSectionsExpanded, | ||
isEnableHighlightsModalOpen, | ||
isInternetConnectionAlertFailed, | ||
isDisabledReindexButton, | ||
headerNavigationsActions, | ||
openEnableHighlightsModal, | ||
closeEnableHighlightsModal, | ||
handleEnableHighlightsSubmit, | ||
handleInternetConnectionFailed, | ||
} = useCourseOutline({ courseId }); | ||
|
||
if (isLoading) { | ||
// eslint-disable-next-line react/jsx-no-useless-fragment | ||
return <></>; | ||
} | ||
|
||
return ( | ||
<> | ||
<Container size="xl" className="m-4"> | ||
<section className="course-outline-container mb-4 mt-5"> | ||
<TransitionReplace> | ||
{showSuccessAlert ? ( | ||
<AlertMessage | ||
key={intl.formatMessage(messages.alertSuccessAriaLabelledby)} | ||
show={showSuccessAlert} | ||
variant="success" | ||
icon={CheckCircleIcon} | ||
title={intl.formatMessage(messages.alertSuccessTitle)} | ||
description={intl.formatMessage(messages.alertSuccessDescription)} | ||
aria-hidden="true" | ||
aria-labelledby={intl.formatMessage(messages.alertSuccessAriaLabelledby)} | ||
aria-describedby={intl.formatMessage(messages.alertSuccessAriaDescribedby)} | ||
/> | ||
) : null} | ||
</TransitionReplace> | ||
<SubHeader | ||
className="mt-5" | ||
title={intl.formatMessage(messages.headingTitle)} | ||
subtitle={intl.formatMessage(messages.headingSubtitle)} | ||
withSubHeaderContent={false} | ||
headerActions={( | ||
<HeaderNavigations | ||
isReIndexShow={isReIndexShow} | ||
isSectionsExpanded={isSectionsExpanded} | ||
headerNavigationsActions={headerNavigationsActions} | ||
isDisabledReindexButton={isDisabledReindexButton} | ||
/> | ||
)} | ||
/> | ||
<Layout | ||
lg={[{ span: 9 }, { span: 3 }]} | ||
md={[{ span: 9 }, { span: 3 }]} | ||
sm={[{ span: 9 }, { span: 3 }]} | ||
xs={[{ span: 9 }, { span: 3 }]} | ||
xl={[{ span: 9 }, { span: 3 }]} | ||
> | ||
<Layout.Element> | ||
<article> | ||
<div> | ||
<section className="course-outline-section"> | ||
<StatusBar | ||
courseId={courseId} | ||
isLaoding={isLoading} | ||
statusBarData={statusBarData} | ||
openEnableHighlightsModal={openEnableHighlightsModal} | ||
/> | ||
</section> | ||
</div> | ||
</article> | ||
</Layout.Element> | ||
<Layout.Element> | ||
<OutlineSideBar courseId={courseId} /> | ||
</Layout.Element> | ||
</Layout> | ||
<EnableHighlightsModal | ||
isOpen={isEnableHighlightsModalOpen} | ||
close={closeEnableHighlightsModal} | ||
onEnableHighlightsSubmit={handleEnableHighlightsSubmit} | ||
highlightsDocUrl={statusBarData.highlightsDocUrl} | ||
/> | ||
</section> | ||
</Container> | ||
<div className="alert-toast"> | ||
<InternetConnectionAlert | ||
isFailed={isInternetConnectionAlertFailed} | ||
isQueryPending={savingStatus === RequestStatus.PENDING} | ||
onInternetConnectionFailed={handleInternetConnectionFailed} | ||
/> | ||
{showErrorAlert && ( | ||
<AlertMessage | ||
key={intl.formatMessage(messages.alertErrorTitle)} | ||
show={showErrorAlert} | ||
variant="danger" | ||
icon={WarningIcon} | ||
title={intl.formatMessage(messages.alertErrorTitle)} | ||
aria-hidden="true" | ||
/> | ||
)} | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
CourseOutline.propTypes = { | ||
courseId: PropTypes.string.isRequired, | ||
}; | ||
|
||
export default CourseOutline; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
@import "./header-navigations/HeaderNavigations"; | ||
@import "./outline-sidebar/OulineSidebar"; | ||
@import "./status-bar/StatusBar"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import React from 'react'; | ||
import { | ||
render, waitFor, cleanup, | ||
} from '@testing-library/react'; | ||
import { IntlProvider } from '@edx/frontend-platform/i18n'; | ||
import { AppProvider } from '@edx/frontend-platform/react'; | ||
import { initializeMockApp } from '@edx/frontend-platform'; | ||
import MockAdapter from 'axios-mock-adapter'; | ||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; | ||
|
||
import { | ||
getCourseBestPracticesApiUrl, | ||
getCourseLaunchApiUrl, | ||
getCourseOutlineIndexApiUrl, | ||
getCourseReindexApiUrl, | ||
getEnableHighlightsEmailsApiUrl, | ||
} from './data/api'; | ||
import { | ||
enableCourseHighlightsEmailsQuery, | ||
fetchCourseBestPracticesQuery, | ||
fetchCourseLaunchQuery, | ||
fetchCourseOutlineIndexQuery, | ||
fetchCourseReindexQuery, | ||
} from './data/thunk'; | ||
import initializeStore from '../store'; | ||
import { | ||
courseOutlineIndexMock, | ||
courseBestPracticesMock, | ||
courseLaunchMock, | ||
} from './__mocks__'; | ||
import { executeThunk } from '../utils'; | ||
import CourseOutline from './CourseOutline'; | ||
import messages from './messages'; | ||
|
||
let axiosMock; | ||
let store; | ||
const mockPathname = '/foo-bar'; | ||
const courseId = '123'; | ||
|
||
jest.mock('react-router-dom', () => ({ | ||
...jest.requireActual('react-router-dom'), | ||
useLocation: () => ({ | ||
pathname: mockPathname, | ||
}), | ||
})); | ||
|
||
const RootWrapper = () => ( | ||
<AppProvider store={store}> | ||
<IntlProvider locale="en"> | ||
<CourseOutline courseId={courseId} /> | ||
</IntlProvider> | ||
</AppProvider> | ||
); | ||
|
||
describe('<CourseOutline />', () => { | ||
beforeEach(async () => { | ||
initializeMockApp({ | ||
authenticatedUser: { | ||
userId: 3, | ||
username: 'abc123', | ||
administrator: true, | ||
roles: [], | ||
}, | ||
}); | ||
|
||
store = initializeStore(); | ||
axiosMock = new MockAdapter(getAuthenticatedHttpClient()); | ||
axiosMock | ||
.onGet(getCourseOutlineIndexApiUrl(courseId)) | ||
.reply(200, courseOutlineIndexMock); | ||
await executeThunk(fetchCourseOutlineIndexQuery(courseId), store.dispatch); | ||
}); | ||
|
||
it('render CourseOutline component correctly', async () => { | ||
const { getByText } = render(<RootWrapper />); | ||
|
||
await waitFor(() => { | ||
expect(getByText(messages.headingTitle.defaultMessage)).toBeInTheDocument(); | ||
expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('check reindex and render success alert is correctly', async () => { | ||
const { getByText } = render(<RootWrapper />); | ||
|
||
axiosMock | ||
.onGet(getCourseReindexApiUrl(courseOutlineIndexMock.reindexLink)) | ||
.reply(200); | ||
await executeThunk(fetchCourseReindexQuery(courseId, courseOutlineIndexMock.reindexLink), store.dispatch); | ||
|
||
expect(getByText(messages.alertSuccessDescription.defaultMessage)).toBeInTheDocument(); | ||
}); | ||
|
||
it('render error alert after failed reindex correctly', async () => { | ||
const { getByText } = render(<RootWrapper />); | ||
|
||
axiosMock | ||
.onGet(getCourseReindexApiUrl('some link')) | ||
.reply(500); | ||
await executeThunk(fetchCourseReindexQuery(courseId, 'some link'), store.dispatch); | ||
|
||
expect(getByText(messages.alertErrorTitle.defaultMessage)).toBeInTheDocument(); | ||
}); | ||
|
||
it('render checklist value correctly', async () => { | ||
const { getByText } = render(<RootWrapper />); | ||
|
||
axiosMock | ||
.onGet(getCourseBestPracticesApiUrl({ | ||
courseId, excludeGraded: true, all: true, | ||
})) | ||
.reply(200, courseBestPracticesMock); | ||
|
||
axiosMock | ||
.onGet(getCourseLaunchApiUrl({ | ||
courseId, gradedOnly: true, validateOras: true, all: true, | ||
})) | ||
.reply(200, courseLaunchMock); | ||
|
||
await executeThunk(fetchCourseLaunchQuery({ | ||
courseId, gradedOnly: true, validateOras: true, all: true, | ||
}), store.dispatch); | ||
await executeThunk(fetchCourseBestPracticesQuery({ | ||
courseId, excludeGraded: true, all: true, | ||
}), store.dispatch); | ||
|
||
expect(getByText('4/9 completed')).toBeInTheDocument(); | ||
}); | ||
|
||
it('check enable highlights when enable highlights query is successfully', async () => { | ||
cleanup(); | ||
const { getByText } = render(<RootWrapper />); | ||
|
||
axiosMock | ||
.onGet(getCourseOutlineIndexApiUrl(courseId)) | ||
.reply(200, { | ||
...courseOutlineIndexMock, | ||
highlightsEnabledForMessaging: false, | ||
}); | ||
|
||
axiosMock | ||
.onPost(getEnableHighlightsEmailsApiUrl(courseId), { | ||
publish: 'republish', | ||
metadata: { | ||
highlights_enabled_for_messaging: true, | ||
}, | ||
}) | ||
.reply(200); | ||
|
||
await executeThunk(enableCourseHighlightsEmailsQuery(courseId), store.dispatch); | ||
expect(getByText('Enabled')).toBeInTheDocument(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
module.exports = { | ||
isSelfPaced: false, | ||
sections: { | ||
totalNumber: 6, | ||
totalVisible: 4, | ||
numberWithHighlights: 2, | ||
highlightsActiveForCourse: true, | ||
highlightsEnabled: true, | ||
}, | ||
subsections: { | ||
totalVisible: 5, | ||
numWithOneBlockType: 2, | ||
numBlockTypes: { | ||
min: 0, | ||
max: 3, | ||
mean: 1, | ||
median: 1, | ||
mode: 1, | ||
}, | ||
}, | ||
units: { | ||
totalVisible: 9, | ||
numBlocks: { | ||
min: 1, | ||
max: 2, | ||
mean: 2, | ||
median: 2, | ||
mode: 2, | ||
}, | ||
}, | ||
videos: { | ||
totalNumber: 7, | ||
numMobileEncoded: 0, | ||
numWithValId: 3, | ||
durations: { | ||
min: null, | ||
max: null, | ||
mean: null, | ||
median: null, | ||
mode: null, | ||
}, | ||
}, | ||
}; |
Oops, something went wrong.