Skip to content

Commit

Permalink
feat: [AXIMST-749] convert the feature to the plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
ihor-romaniuk committed Apr 8, 2024
1 parent 96c0e0f commit e0b25b1
Show file tree
Hide file tree
Showing 57 changed files with 438 additions and 457 deletions.
27 changes: 27 additions & 0 deletions example.env.config.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import UnitTranslationPlugin from '@plugins/UnitTranslationPlugin';
import { Trigger as CourseOutlineSidebarTrigger } from '@plugins/CourseOutlineSidebar';
import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework';

// Load environment variables from .env file
Expand All @@ -18,6 +19,32 @@ const config = {
},
],
},
sequence_container_plugin: {
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'COURSE_OUTLINE_SIDEBAR',
type: DIRECT_PLUGIN,
priority: 1,
RenderWidget: CourseOutlineSidebarTrigger,
},
},
],
},
course_content_triggers_plugin: {
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'COURSE_OUTLINE_SIDEBAR',
type: DIRECT_PLUGIN,
priority: 1,
RenderWidget: CourseOutlineSidebarTrigger,
},
},
],
},
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,55 @@ import {
ChevronLeft as ChevronLeftIcon,
} from '@openedx/paragon/icons';

import { useModel } from '../../../../../generic/model-store';
import { LOADING, LOADED } from '../../../../../course-home/data/slice';
import PageLoading from '../../../../../generic/PageLoading';
import {
getSequenceId,
getCourseOutline,
getCourseOutlineStatus,
getCoursewareOutlineSidebarSettings,
getCourseOutlineShouldUpdate,
} from '../../../../data/selectors';
import { getCourseOutlineStructure } from '../../../../data/thunks';
import SidebarContext from '../../SidebarContext';
import { ID } from './CourseOutlineTrigger';
import { useModel } from '@src/generic/model-store';
import PageLoading from '@src/generic/PageLoading';
import { usePluginsSelector } from '@src/generic/plugin-store';
import { LOADED } from '@src/course-home/data/slice';
import SidebarContext from '@src/courseware/course/sidebar/SidebarContext';
import { getSequenceId, getUnitsModel } from './data/selectors';
import SidebarSection from './SidebarSection';
import SidebarSequence from './SidebarSequence';
import messages from './messages';
import { ID, LAYOUT } from './constants';
import { useCourseOutlineSidebar } from './hooks';
import { getCourseOutlineStructure, updateCourseOutlineCompletion } from './data/thunks';

const CourseOutlineTray = ({ intl }) => {
const [completedIds, setCompletedIds] = useState([]);
const [selectedSection, setSelectedSection] = useState(null);
const [isDisplaySequenceLevel, setDisplaySequenceLevel, setDisplaySectionLevel] = useToggle(true);

const dispatch = useDispatch();

const {
handleToggleCollapse, isActiveEntranceExam,
} = useCourseOutlineSidebar();

const activeSequenceId = useSelector(getSequenceId);
const { sections = {}, sequences = {} } = useSelector(getCourseOutline);
const courseOutlineStatus = useSelector(getCourseOutlineStatus);
const courseOutlineShouldUpdate = useSelector(getCourseOutlineShouldUpdate);
const { enabled: isEnabled } = useSelector(getCoursewareOutlineSidebarSettings);
const sequenceNavUnits = useSelector(getUnitsModel) || {};
const {
loadingStatus, courseOutlineShouldUpdate, structure: courseOutlineStructure,
} = usePluginsSelector(ID);
const { sections = {}, sequences = {} } = courseOutlineStructure ?? {};

const {
courseId,
unitId,
currentSidebar,
toggleSidebar,
shouldDisplayFullScreen,
} = useContext(SidebarContext);

const course = useModel('coursewareMeta', courseId);
const {
sectionId: activeSectionId,
} = useModel('sequences', activeSequenceId);
const {
entranceExamEnabled,
entranceExamPassed,
} = course.entranceExamData || {};

const sectionsIds = Object.keys(sections);
const sequenceIds = sections[selectedSection || activeSectionId]?.sequenceIds || [];
const backButtonTitle = sections[selectedSection || activeSectionId]?.title;
const isActiveEntranceExam = entranceExamEnabled && !entranceExamPassed;

const handleToggleCollapse = () => {
if (currentSidebar === ID) {
toggleSidebar(null);
window.sessionStorage.setItem('hideCourseOutlineSidebar', 'true');
} else {
toggleSidebar(ID);
window.sessionStorage.removeItem('hideCourseOutlineSidebar');
}
};
const newCompletedUnitIds = Object.keys(sequenceNavUnits).filter((id) => sequenceNavUnits[id].complete);

if (JSON.stringify(completedIds.sort()) !== JSON.stringify(newCompletedUnitIds.sort())) {
setCompletedIds(newCompletedUnitIds);
}

const handleBackToSectionLevel = () => {
setDisplaySectionLevel();
Expand Down Expand Up @@ -104,16 +94,22 @@ const CourseOutlineTray = ({ intl }) => {
);

useEffect(() => {
if ((isEnabled && courseOutlineStatus !== LOADED) || courseOutlineShouldUpdate) {
if (loadingStatus !== LOADED || courseOutlineShouldUpdate) {
dispatch(getCourseOutlineStructure(courseId));
}
}, [courseId, isEnabled, courseOutlineShouldUpdate]);
}, [courseId, courseOutlineShouldUpdate]);

if (!isEnabled || isActiveEntranceExam) {
useEffect(() => {
if (courseId && completedIds.length) {
dispatch(updateCourseOutlineCompletion(completedIds));
}
}, [courseId, completedIds]);

if (isActiveEntranceExam) {
return null;
}

if (courseOutlineStatus === LOADING) {
if (!loadingStatus) {
return (
<div className={classNames('outline-sidebar-wrapper', {
'flex-shrink-0 mr-4 h-auto': !shouldDisplayFullScreen,
Expand Down Expand Up @@ -167,6 +163,12 @@ CourseOutlineTray.propTypes = {
intl: intlShape.isRequired,
};

CourseOutlineTray.ID = ID;
// CourseOutlineTray.ID = ID;

export default injectIntl(CourseOutlineTray);

export const OUTLINE_SIDEBAR = {
ID,
LAYOUT,
Sidebar: injectIntl(CourseOutlineTray),
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,40 @@ import userEvent from '@testing-library/user-event';
import { AppProvider } from '@edx/frontend-platform/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';

import { initializeTestStore } from '../../../../../setupTest';
import courseOutlineMessages from '../../../../../course-home/outline-tab/messages';
import SidebarContext from '../../SidebarContext';
import courseOutlineMessages from '@src/course-home/outline-tab/messages';
import SidebarContext from '@src/courseware/course/sidebar/SidebarContext';
import CourseOutlineTray from './CourseOutlineTray';
import { ID as outlineSidebarId } from './CourseOutlineTrigger';
import { ID } from './constants';
import messages from './messages';
import { initOutlineSidebarTestStore } from './utils';

describe('<CourseOutlineTray />', () => {
let store;
let section = {};
let sequence = {};
let unit;
let unitId;
let courseId;
let mockData;

const initTestStore = async (options) => {
store = await initializeTestStore(options);
const initTestStore = async (options = {}) => {
store = await initOutlineSidebarTestStore(options);

const state = store.getState();
courseId = state.courseware.courseId;
[unitId] = Object.keys(state.models.units);

if (Object.keys(state.courseware.courseOutline).length) {
const [activeSequenceId] = Object.keys(state.courseware.courseOutline.sequences);
sequence = state.courseware.courseOutline.sequences[activeSequenceId];
const activeSectionId = Object.keys(state.courseware.courseOutline.sections)[0];
section = state.courseware.courseOutline.sections[activeSectionId];
if (state.plugins[ID]?.structure && Object.keys(state.plugins[ID]?.structure).length) {
const [activeSequenceId] = Object.keys(state.plugins[ID].structure.sequences);
sequence = state.plugins[ID].structure.sequences[activeSequenceId];
const activeSectionId = Object.keys(state.plugins[ID].structure.sections)[0];
section = state.plugins[ID].structure.sections[activeSectionId];
[unitId] = sequence.unitIds;
unit = state.courseware.courseOutline.units[unitId];
}

mockData = {
courseId,
unitId,
currentSidebar: outlineSidebarId,
currentSidebar: ID,
toggleSidebar: jest.fn(),
};
};
Expand Down Expand Up @@ -67,26 +66,6 @@ describe('<CourseOutlineTray />', () => {
expect(screen.queryByRole('button', { name: 'Course Outline' })).not.toBeInTheDocument();
});

it('doesn\'t render when outline sidebar is disabled', async () => {
await initTestStore({ outlineSidebarSettings: { enabled: false } });
renderWithProvider();

await expect(screen.queryByText(messages.loading.defaultMessage)).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: section.title })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: messages.toggleCourseOutlineTrigger.defaultMessage })).not.toBeInTheDocument();
});

it('renders correctly when course outline is loaded', async () => {
await initTestStore();
renderWithProvider();

await expect(screen.queryByText(messages.loading.defaultMessage)).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: section.title })).toBeInTheDocument();
expect(screen.getByRole('button', { name: messages.toggleCourseOutlineTrigger.defaultMessage })).toBeInTheDocument();
expect(screen.getByRole('button', { name: `${sequence.title} , ${courseOutlineMessages.incompleteAssignment.defaultMessage}` })).toBeInTheDocument();
expect(screen.getByRole('link', { name: unit.title })).toBeInTheDocument();
});

it('collapses sidebar correctly when toggle button is clicked', async () => {
const mockToggleSidebar = jest.fn();
await initTestStore();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,40 @@
import { useContext } from 'react';
import { useSelector } from 'react-redux';
import { useContext, useEffect } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { IconButton } from '@openedx/paragon';
import { MenuOpen as MenuOpenIcon } from '@openedx/paragon/icons';

import { useModel } from '../../../../../generic/model-store';
import { getCoursewareOutlineSidebarSettings } from '../../../../data/selectors';
import { LAYOUT_LEFT } from '../../common/constants';
import SidebarContext from '../../SidebarContext';
import SidebarContext from '@src/courseware/course/sidebar/SidebarContext';
import { extendSidebars } from '@src/courseware/course/sidebar/sidebars';
import { OUTLINE_SIDEBAR } from './CourseOutlineTray';
import { ID } from './constants';
import messages from './messages';

export const ID = 'COURSE_OUTLINE';
export const LAYOUT = LAYOUT_LEFT;
import { useCourseOutlineSidebar } from './hooks';

const CourseOutlineTrigger = ({ intl, isMobileView }) => {
const {
courseId,
currentSidebar,
toggleSidebar,
shouldDisplayFullScreen,
} = useContext(SidebarContext);

const course = useModel('coursewareMeta', courseId);
const {
entranceExamEnabled,
entranceExamPassed,
} = course.entranceExamData || {};
const { enabled: isEnabled } = useSelector(getCoursewareOutlineSidebarSettings);
handleToggleCollapse, isActiveEntranceExam,
} = useCourseOutlineSidebar();

const isDisplayForDesktopView = !isMobileView && !shouldDisplayFullScreen && currentSidebar !== ID;
const isDisplayForMobileView = isMobileView && shouldDisplayFullScreen;
const isActiveEntranceExam = entranceExamEnabled && !entranceExamPassed;

if ((!isDisplayForDesktopView && !isDisplayForMobileView) || !isEnabled || isActiveEntranceExam) {
// Adding CourseOutlineSidebar to the list of all sidebars on the unit page
// only when connecting CourseOutlineSidebar via PluginSlot.
useEffect(() => {
extendSidebars(ID, OUTLINE_SIDEBAR);
}, []);

if ((!isDisplayForDesktopView && !isDisplayForMobileView) || isActiveEntranceExam) {
return null;
}

const handleToggleCollapse = () => {
if (currentSidebar === ID) {
toggleSidebar(null);
window.sessionStorage.setItem('hideCourseOutlineSidebar', 'true');
} else {
toggleSidebar(ID);
window.sessionStorage.removeItem('hideCourseOutlineSidebar');
}
};

return (
<div className={classNames('outline-sidebar-heading-wrapper bg-light-200 collapsed', {
'flex-shrink-0 mr-4 p-2.5': isDisplayForDesktopView,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import userEvent from '@testing-library/user-event';
import { AppProvider } from '@edx/frontend-platform/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';

import { initializeTestStore } from '../../../../../setupTest';
import SidebarContext from '../../SidebarContext';
import { ID as discussionSidebarId } from '../discussions/DiscussionsTrigger';
import CourseOutlineTrigger, { ID as outlineSidebarId } from './CourseOutlineTrigger';
import SidebarContext from '@src/courseware/course/sidebar/SidebarContext';
import { ID as discussionSidebarId } from '@src/courseware/course/sidebar/sidebars/discussions/DiscussionsTrigger';
import CourseOutlineTrigger from './CourseOutlineTrigger';
import { ID as outlineSidebarId } from './constants';
import { initOutlineSidebarTestStore } from './utils';
import messages from './messages';

describe('<CourseOutlineTrigger />', () => {
Expand All @@ -16,7 +17,7 @@ describe('<CourseOutlineTrigger />', () => {
let store;

const initTestStore = async (options) => {
store = await initializeTestStore(options);
store = await initOutlineSidebarTestStore(options);
const state = store.getState();
courseId = state.courseware.courseId;
[unitId] = Object.keys(state.models.units);
Expand Down Expand Up @@ -95,14 +96,4 @@ describe('<CourseOutlineTrigger />', () => {
expect(mockToggleSidebar).toHaveBeenCalledTimes(1);
expect(mockToggleSidebar).toHaveBeenCalledWith(null);
});

it('does not render when isEnabled is false', async () => {
await initTestStore({ outlineSidebarSettings: { enabled: false } });
renderWithProvider({}, { isMobileView: false });

const toggleButton = await screen.queryByRole('button', {
name: messages.toggleCourseOutlineTrigger.defaultMessage,
});
expect(toggleButton).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ChevronRight as ChevronRightIcon,
} from '@openedx/paragon/icons';

import courseOutlineMessages from '../../../../../course-home/outline-tab/messages';
import courseOutlineMessages from '@src/course-home/outline-tab/messages';
import { CompletionSolidIcon } from './icons';

const SidebarSection = ({ intl, section, handleSelectSection }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { IntlProvider } from '@edx/frontend-platform/i18n';

import courseOutlineMessages from '../../../../../course-home/outline-tab/messages';
import courseOutlineMessages from '@src/course-home/outline-tab/messages';
import SidebarSection from './SidebarSection';

describe('<SidebarSection />', () => {
Expand Down
Loading

0 comments on commit e0b25b1

Please sign in to comment.