Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"dev": "PUBLIC_PATH=/learning/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
"test": "fedx-scripts jest --coverage --passWithNoTests",
"test": "NODE_ENV=test fedx-scripts jest --coverage --passWithNoTests",
"test:watch": "fedx-scripts jest --watch --passWithNoTests",
"types": "tsc --noEmit"
},
Expand Down Expand Up @@ -97,4 +97,4 @@
],
"normalizeFilenames": "^.+?(\\..+?)\\.\\w+$"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {

import { DashedCircleIcon } from '../icons';

const CompletionIcon = ({ completionStat: { completed = 0, total = 0 } }) => {
const CompletionIcon = ({ completionStat: { completed = 0, total = 0 }, enabled }) => {
const percentage = total !== 0 ? Math.min((completed / total) * 100, 100) : 0;
const remainder = 100 - percentage;

switch (true) {
case !completed:
case !completed || !enabled:
return <LmsCompletionSolidIcon className="text-gray-300" data-testid="completion-solid-icon" />;
case completed === total:
return <CheckCircleIcon className="text-success" data-testid="check-circle-icon" />;
Expand All @@ -25,6 +25,7 @@ CompletionIcon.propTypes = {
completed: PropTypes.number,
total: PropTypes.number,
}).isRequired,
enabled: PropTypes.bool.isRequired,
};

export default CompletionIcon;
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,33 @@ import { render, screen } from '@testing-library/react';
import CompletionIcon from './CompletionIcon';

describe('CompletionIcon', () => {
it('renders check circle icon when completion is equal to total', () => {
it('renders check circle icon when completion is equal to total and completion tracking is enabled', () => {
const completionStat = { completed: 5, total: 5 };
render(<CompletionIcon completionStat={completionStat} />);
render(<CompletionIcon completionStat={completionStat} enabled />);
expect(screen.getByTestId('check-circle-icon')).toBeInTheDocument();
});

it('renders dashed circle icon when completion is between 0 and total', () => {
it('renders dashed circle icon when completion is between 0 and total and completion tracking is enabled', () => {
const completionStat = { completed: 2, total: 5 };
render(<CompletionIcon completionStat={completionStat} />);
render(<CompletionIcon completionStat={completionStat} enabled />);
expect(screen.getByTestId('dashed-circle-icon')).toBeInTheDocument();
});

it('renders completion solid icon when completion is 0', () => {
it('renders completion solid icon when completion is between 0 and total and completion tracking is not enabled', () => {
const completionStat = { completed: 2, total: 5 };
render(<CompletionIcon completionStat={completionStat} enabled={false} />);
expect(screen.getByTestId('completion-solid-icon')).toBeInTheDocument();
});

it('renders completion solid icon when completion is 0 and enabled', () => {
const completionStat = { completed: 0, total: 5 };
render(<CompletionIcon completionStat={completionStat} enabled />);
expect(screen.getByTestId('completion-solid-icon')).toBeInTheDocument();
});

it('renders completion solid icon when completion is at any value and not enabled', () => {
const completionStat = { completed: 0, total: 5 };
render(<CompletionIcon completionStat={completionStat} />);
render(<CompletionIcon completionStat={completionStat} enabled={false} />);
expect(screen.getByTestId('completion-solid-icon')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,24 @@ const SidebarSection = ({ section, handleSelectSection }) => {
completionStat,
} = section;

const { activeSequenceId } = useCourseOutlineSidebar();
const { activeSequenceId, isEnabledCompletionTracking } = useCourseOutlineSidebar();
const isActiveSection = sequenceIds.includes(activeSequenceId);

const sectionTitle = (
<>
<div className="col-auto p-0">
<CompletionIcon completionStat={completionStat} />
<CompletionIcon completionStat={completionStat} enabled={isEnabledCompletionTracking} />
</div>
<div className="col-10 ml-3 p-0 flex-grow-1 text-dark-500 text-left text-break">
{title}
<span className="sr-only">
, {intl.formatMessage(complete
? courseOutlineMessages.completedSection
: courseOutlineMessages.incompleteSection)}
</span>
{isEnabledCompletionTracking && (
<span className="sr-only">
, {intl.formatMessage(complete
? courseOutlineMessages.completedSection
: courseOutlineMessages.incompleteSection)}
</span>
)}

</div>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,24 @@ const SidebarSequence = ({
} = sequence;

const [open, setOpen] = useState(defaultOpen);
const { activeSequenceId, units } = useCourseOutlineSidebar();
const { activeSequenceId, units, isEnabledCompletionTracking } = useCourseOutlineSidebar();
const isActiveSequence = id === activeSequenceId;

const sectionTitle = (
<>
<div className="col-auto p-0" style={{ fontSize: '1.1rem' }}>
<CompletionIcon completionStat={completionStat} />
<CompletionIcon completionStat={completionStat} enabled={isEnabledCompletionTracking} />
</div>
<div className="col-9 d-flex flex-column flex-grow-1 ml-3 mr-auto p-0 text-left">
<span className="align-middle text-dark-500">{title}</span>
{specialExamInfo && <span className="align-middle small text-muted">{specialExamInfo}</span>}
<span className="sr-only">
, {intl.formatMessage(complete
? courseOutlineMessages.completedAssignment
: courseOutlineMessages.incompleteAssignment)}
</span>
{isEnabledCompletionTracking && (
<span className="sr-only">
, {intl.formatMessage(complete
? courseOutlineMessages.completedAssignment
: courseOutlineMessages.incompleteAssignment)}
</span>
)}
</div>
</>
);
Expand All @@ -69,6 +71,7 @@ const SidebarSequence = ({
activeUnitId={activeUnitId}
isFirst={index === 0}
isLocked={type === UNIT_ICON_TYPES.lock}
isCompletionTrackingEnabled={isEnabledCompletionTracking}
/>
))}
</ol>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('<SidebarSequence />', () => {
expect(screen.queryByText(unit.title)).not.toBeInTheDocument();
});

it('renders correctly when sequence is not collapsed and complete', async () => {
it('renders correctly when sequence is not collapsed and complete and completion tracking enabled', async () => {
const user = userEvent.setup();
await initTestStore();
renderWithProvider({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const SidebarUnit = ({
isActive,
isLocked,
activeUnitId,
isCompletionTrackingEnabled,
}) => {
const intl = useIntl();
const {
Expand All @@ -24,6 +25,7 @@ const SidebarUnit = ({
} = unit;

const iconType = isLocked ? UNIT_ICON_TYPES.lock : icon;
const completeAndEnabled = complete && isCompletionTrackingEnabled;

return (
<li className={classNames({ 'bg-info-100': isActive, 'border-top border-light': !isFirst })}>
Expand All @@ -36,15 +38,17 @@ const SidebarUnit = ({
}}
>
<div className="col-auto p-0">
<UnitIcon type={iconType} isCompleted={complete} />
<UnitIcon type={iconType} isCompleted={completeAndEnabled} />
</div>
<div className="col-10 p-0 ml-3 text-break">
<span className="align-middle">
{title}
</span>
<span className="sr-only">
, {intl.formatMessage(complete ? messages.completedUnit : messages.incompleteUnit)}
</span>
{isCompletionTrackingEnabled && (
<span className="sr-only">
, {intl.formatMessage(complete ? messages.completedUnit : messages.incompleteUnit)}
</span>
)}
</div>
</UnitLinkWrapper>
</li>
Expand All @@ -66,6 +70,7 @@ SidebarUnit.propTypes = {
courseId: PropTypes.string.isRequired,
sequenceId: PropTypes.string.isRequired,
activeUnitId: PropTypes.string.isRequired,
isCompletionTrackingEnabled: PropTypes.bool.isRequired,
};

export default SidebarUnit;
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('<SidebarUnit />', () => {
unit={{ ...unit, icon: 'video', isLocked: false }}
isActive={false}
activeUnitId={unit.id}
isCompletionTrackingEnabled
{...props}
/>
</MemoryRouter>
Expand All @@ -68,7 +69,7 @@ describe('<SidebarUnit />', () => {
expect(container.querySelector('.text-success')).not.toBeInTheDocument();
});

it('renders correctly when unit is complete', async () => {
it('renders correctly when unit is complete and tracking enabled', async () => {
await initTestStore();
const container = renderWithProvider({ unit: { ...unit, complete: true } });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import { ID } from './constants';
export const useCourseOutlineSidebar = () => {
const dispatch = useDispatch();
const isCollapsedOutlineSidebar = window.sessionStorage.getItem('hideCourseOutlineSidebar');
const { enableNavigationSidebar: isEnabledSidebar } = useSelector(getCoursewareOutlineSidebarSettings);
const {
enableNavigationSidebar: isEnabledSidebar,
enableCompletionTracking: isEnabledCompletionTracking,
} = useSelector(getCoursewareOutlineSidebarSettings);
const courseOutlineShouldUpdate = useSelector(getCourseOutlineShouldUpdate);
const courseOutlineStatus = useSelector(getCourseOutlineStatus);
const sequenceStatus = useSelector(getSequenceStatus);
Expand Down Expand Up @@ -110,6 +113,7 @@ export const useCourseOutlineSidebar = () => {
currentSidebar,
shouldDisplayFullScreen,
isEnabledSidebar,
isEnabledCompletionTracking,
isOpen,
setIsOpen,
handleToggleCollapse,
Expand Down
1 change: 1 addition & 0 deletions src/courseware/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,6 @@ export async function getCoursewareOutlineSidebarToggles(courseId) {
return {
enable_navigation_sidebar: data.enable_navigation_sidebar || false,
always_open_auxiliary_sidebar: data.always_open_auxiliary_sidebar || false,
enable_completion_tracking: data.enable_completion_tracking || false,
};
}
3 changes: 3 additions & 0 deletions src/courseware/data/redux.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ describe('Data layer integration tests', () => {
axiosMock.onGet(coursewareSidebarSettingsUrl).reply(200, {
enable_navigation_sidebar: true,
always_open_auxiliary_sidebar: true,
enable_completion_tracking: true,
});

await executeThunk(thunks.fetchCourse(courseId), store.dispatch);
Expand All @@ -126,6 +127,7 @@ describe('Data layer integration tests', () => {
expect(state.courseware.coursewareOutlineSidebarSettings).toEqual({
enableNavigationSidebar: true,
alwaysOpenAuxiliarySidebar: true,
enableCompletionTracking: true,
});

// check that at least one key camel cased, thus course data normalized
Expand Down Expand Up @@ -154,6 +156,7 @@ describe('Data layer integration tests', () => {
expect(state.courseware.coursewareOutlineSidebarSettings).toEqual({
enableNavigationSidebar: false,
alwaysOpenAuxiliarySidebar: false,
enableCompletionTracking: false,
});

// check that at least one key camel cased, thus course data normalized
Expand Down
5 changes: 4 additions & 1 deletion src/courseware/data/thunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,11 @@ export function fetchCourse(courseId) {
const {
enable_navigation_sidebar: enableNavigationSidebar,
always_open_auxiliary_sidebar: alwaysOpenAuxiliarySidebar,
enable_completion_tracking: enableCompletionTracking,
} = coursewareOutlineSidebarTogglesResult.value;
dispatch(setCoursewareOutlineSidebarToggles({ enableNavigationSidebar, alwaysOpenAuxiliarySidebar }));
dispatch(setCoursewareOutlineSidebarToggles(
{ enableNavigationSidebar, alwaysOpenAuxiliarySidebar, enableCompletionTracking },
));
}

// Log errors for each request if needed. Outline failures may occur
Expand Down
2 changes: 2 additions & 0 deletions src/setupTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export async function initializeTestStore(options = {}, overrideStore = true) {
const provider = options?.provider || 'legacy';
const enableNavigationSidebar = options.enableNavigationSidebar || { enable_navigation_sidebar: true };
const alwaysOpenAuxiliarySidebar = options.alwaysOpenAuxiliarySidebar || { always_open_auxiliary_sidebar: true };
const enableCompletionTracking = options.enableCompletionTracking || { enable_completion_tracking: true };

axiosMock.onGet(courseMetadataUrl).reply(200, courseMetadata);
axiosMock.onGet(courseHomeMetadataUrl).reply(200, courseHomeMetadata);
Expand All @@ -187,6 +188,7 @@ export async function initializeTestStore(options = {}, overrideStore = true) {
axiosMock.onGet(coursewareSidebarSettingsUrl).reply(200, {
...enableNavigationSidebar,
...alwaysOpenAuxiliarySidebar,
...enableCompletionTracking,
});

axiosMock.onGet(outlineSidebarUrl).reply(200, {
Expand Down