Skip to content

Commit

Permalink
refactor: Unit page - refactoring breadcrumbs, view live and preview …
Browse files Browse the repository at this point in the history
…links buttons (#827)
  • Loading branch information
ihor-romaniuk authored Feb 14, 2024
1 parent 60c1a03 commit 51c5f9c
Show file tree
Hide file tree
Showing 12 changed files with 53 additions and 133 deletions.
1 change: 0 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ EXAMS_BASE_URL=''
FAVICON_URL=''
LANGUAGE_PREFERENCE_COOKIE_NAME=''
LMS_BASE_URL=''
PREVIEW_BASE_URL=''
LEARNING_BASE_URL=''
LOGIN_URL=''
LOGO_TRADEMARK_URL=''
Expand Down
1 change: 0 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,3 @@ HOTJAR_VERSION=6
HOTJAR_DEBUG=true
INVITE_STUDENTS_EMAIL_TO="[email protected]"
AI_TRANSLATIONS_BASE_URL='http://localhost:18760'
PREVIEW_BASE_URL='http://preview.localhost:18000'
1 change: 0 additions & 1 deletion .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ EXAMS_BASE_URL=
FAVICON_URL='https://edx-cdn.org/v3/default/favicon.ico'
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
LMS_BASE_URL='http://localhost:18000'
PREVIEW_BASE_URL='http://preview.localhost:18000'
LEARNING_BASE_URL='http://localhost:2000'
LOGIN_URL='http://localhost:18000/login'
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
Expand Down
4 changes: 1 addition & 3 deletions src/course-unit/CourseUnit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ const CourseUnit = ({ courseId }) => {
/>
)}
breadcrumbs={(
<Breadcrumbs
courseId={courseId}
/>
<Breadcrumbs />
)}
headerActions={(
<HeaderNavigations
Expand Down
16 changes: 7 additions & 9 deletions src/course-unit/CourseUnit.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
import userEvent from '@testing-library/user-event';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react';
import { getConfig, initializeMockApp } from '@edx/frontend-platform';
import { initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';

import {
Expand All @@ -28,14 +28,11 @@ import { executeThunk } from '../utils';
import CourseUnit from './CourseUnit';
import headerNavigationsMessages from './header-navigations/messages';
import headerTitleMessages from './header-title/messages';
import { getUnitPreviewPath, getUnitViewLivePath } from './utils';
import messages from './add-component/messages';

let axiosMock;
let store;
const courseId = '123';
const sectionId = 'graded_interactions';
const subsectionId = '19a30717eff543078a5d94ae9d6c18a5';
const blockId = '567890';
const unitDisplayName = courseUnitIndexMock.metadata.display_name;
const mockedUsedNavigate = jest.fn();
Expand Down Expand Up @@ -97,20 +94,21 @@ describe('<CourseUnit />', () => {
const { open } = window;
window.open = jest.fn();
const { getByRole } = render(<RootWrapper />);
const {
draft_preview_link: draftPreviewLink,
published_preview_link: publishedPreviewLink,
} = courseSectionVerticalMock;

await waitFor(() => {
const viewLiveButton = getByRole('button', { name: headerNavigationsMessages.viewLiveButton.defaultMessage });
userEvent.click(viewLiveButton);
expect(window.open).toHaveBeenCalled();
const VIEW_LIVE_LINK = getConfig().LMS_BASE_URL + getUnitViewLivePath(courseId, blockId);
expect(window.open).toHaveBeenCalledWith(VIEW_LIVE_LINK, '_blank');
expect(window.open).toHaveBeenCalledWith(publishedPreviewLink, '_blank');

const previewButton = getByRole('button', { name: headerNavigationsMessages.previewButton.defaultMessage });
userEvent.click(previewButton);
expect(window.open).toHaveBeenCalled();
// eslint-disable-next-line max-len
const PREVIEW_LINK = getConfig().PREVIEW_BASE_URL + getUnitPreviewPath(courseId, sectionId, subsectionId, blockId);
expect(window.open).toHaveBeenCalledWith(PREVIEW_LINK, '_blank');
expect(window.open).toHaveBeenCalledWith(draftPreviewLink, '_blank');
});

window.open = open;
Expand Down
83 changes: 27 additions & 56 deletions src/course-unit/breadcrumbs/Breadcrumbs.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Dropdown, Icon } from '@edx/paragon';
Expand All @@ -7,60 +6,38 @@ import {
ChevronRight as ChevronRightIcon,
} from '@edx/paragon/icons';

import { useCourseOutline } from '../../course-outline/hooks';
import { getCourseUnitData } from '../data/selectors';
import { createCorrectInternalRoute } from '../../utils';
import { getCourseSectionVertical } from '../data/selectors';
import messages from './messages';

const Breadcrumbs = ({ courseId }) => {
const Breadcrumbs = () => {
const intl = useIntl();
const { ancestorInfo } = useSelector(getCourseUnitData);
const { sectionsList, isLoading: isLoadingCourseOutline } = useCourseOutline({ courseId });
const activeCourseSectionInfo = sectionsList.find((block) => block.id === ancestorInfo?.ancestors[1]?.id);

const breadcrumbs = {
section: {
id: ancestorInfo?.ancestors[1]?.id,
displayName: ancestorInfo?.ancestors[1]?.displayName,
dropdownItems: sectionsList,
},
subsection: {
id: ancestorInfo?.ancestors[0]?.id,
displayName: ancestorInfo?.ancestors[0]?.displayName,
dropdownItems: activeCourseSectionInfo?.childInfo.children || [],
},
};

const getLoadingPlaceholder = () => (
<div className="small px-3 py-2" role="status">
{intl.formatMessage(messages.loading)}
</div>
);
const { ancestorXblocks } = useSelector(getCourseSectionVertical);
const [section, subsection] = ancestorXblocks ?? [];

return (
<nav className="d-flex align-center mb-2.5">
<ol className="p-0 m-0 d-flex align-center">
<li className="d-flex">
<Dropdown>
<Dropdown.Toggle id="breadcrumbs-dropdown-section" variant="link" className="p-0 text-primary small">
<span className="small text-gray-700">{breadcrumbs.section.displayName}</span>
<span className="small text-gray-700">{section.title}</span>
<Icon
src={ArrowDropDownIcon}
className="text-primary ml-1"
/>
</Dropdown.Toggle>
<Dropdown.Menu>
{(isLoadingCourseOutline || !breadcrumbs.section.dropdownItems.length)
? getLoadingPlaceholder()
: breadcrumbs.section.dropdownItems.map(({ id, studioUrl, displayName }) => (
<Dropdown.Item
key={id}
href={studioUrl}
className="small"
data-testid="breadcrumbs-section-dropdown-item"
>
{displayName}
</Dropdown.Item>
))}
{section.children.map(({ url, displayName }) => (
<Dropdown.Item
key={url}
href={createCorrectInternalRoute(url)}
className="small"
data-testid="breadcrumbs-section-dropdown-item"
>
{displayName}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
<Icon
Expand All @@ -73,25 +50,23 @@ const Breadcrumbs = ({ courseId }) => {
<li className="d-flex">
<Dropdown>
<Dropdown.Toggle id="breadcrumbs-dropdown-subsection" variant="link" className="p-0 text-primary">
<span className="small text-gray-700">{breadcrumbs.subsection.displayName}</span>
<span className="small text-gray-700">{subsection.title}</span>
<Icon
src={ArrowDropDownIcon}
className="text-primary ml-1"
/>
</Dropdown.Toggle>
<Dropdown.Menu>
{(isLoadingCourseOutline || !breadcrumbs.subsection.dropdownItems.length)
? getLoadingPlaceholder()
: breadcrumbs.subsection.dropdownItems.map(({ id, studioUrl, displayName }) => (
<Dropdown.Item
key={id}
href={studioUrl}
className="small"
data-testid="breadcrumbs-subsection-dropdown-item"
>
{displayName}
</Dropdown.Item>
))}
{subsection.children.map(({ url, displayName }) => (
<Dropdown.Item
key={url}
href={createCorrectInternalRoute(url)}
className="small"
data-testid="breadcrumbs-subsection-dropdown-item"
>
{displayName}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
</li>
Expand All @@ -100,8 +75,4 @@ const Breadcrumbs = ({ courseId }) => {
);
};

Breadcrumbs.propTypes = {
courseId: PropTypes.string.isRequired,
};

export default Breadcrumbs;
4 changes: 4 additions & 0 deletions src/course-unit/breadcrumbs/Breadcrumbs.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
.course-unit {
.sub-header {
background: transparent;
}

.sub-header-title .sub-header-breadcrumbs {
.dropdown-toggle::after {
display: none;
Expand Down
38 changes: 9 additions & 29 deletions src/course-unit/breadcrumbs/Breadcrumbs.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,13 @@ import { initializeMockApp } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';

import { getCourseOutlineIndexApiUrl } from '../../course-outline/data/api';
import { fetchCourseOutlineIndexQuery } from '../../course-outline/data/thunk';
import { courseOutlineIndexMock } from '../../course-outline/__mocks__';
import initializeStore from '../../store';
import { executeThunk } from '../../utils';
import { getCourseUnitApiUrl } from '../data/api';
import { fetchCourseUnitQuery } from '../data/thunk';
import { courseUnitIndexMock } from '../__mocks__';
import { getCourseSectionVerticalApiUrl, getCourseUnitApiUrl } from '../data/api';
import { fetchCourseSectionVerticalData, fetchCourseUnitQuery } from '../data/thunk';
import { courseSectionVerticalMock, courseUnitIndexMock } from '../__mocks__';
import Breadcrumbs from './Breadcrumbs';

import messages from './messages';

let axiosMock;
let store;
const courseId = '123';
Expand Down Expand Up @@ -56,6 +51,10 @@ describe('<Breadcrumbs />', () => {
.onGet(getCourseUnitApiUrl(courseId))
.reply(200, courseUnitIndexMock);
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
axiosMock
.onGet(getCourseSectionVerticalApiUrl(courseId))
.reply(200, courseSectionVerticalMock);
await executeThunk(fetchCourseSectionVerticalData(courseId), store.dispatch);
});

it('render Breadcrumbs component correctly', async () => {
Expand All @@ -67,26 +66,7 @@ describe('<Breadcrumbs />', () => {
});
});

it('render dropdown loading placeholder on pending', async () => {
const { getByText, queryAllByTestId } = renderComponent();

await waitFor(() => {
expect(getByText(breadcrumbsExpected.section.displayName)).toBeInTheDocument();
expect(getByText(breadcrumbsExpected.subsection.displayName)).toBeInTheDocument();
});

const button = getByText(breadcrumbsExpected.section.displayName);
userEvent.click(button);

expect(queryAllByTestId('breadcrumbs-section-dropdown-item')).toHaveLength(0);
expect(getByText(messages.loading.defaultMessage)).toBeInTheDocument();
});

it('render Breadcrumbs\'s dropdown menus correctly', async () => {
axiosMock
.onGet(getCourseOutlineIndexApiUrl(courseId))
.reply(200, courseOutlineIndexMock);
await executeThunk(fetchCourseOutlineIndexQuery(courseId), store.dispatch);
const { getByText, queryAllByTestId } = renderComponent();

expect(getByText(breadcrumbsExpected.section.displayName)).toBeInTheDocument();
Expand All @@ -97,10 +77,10 @@ describe('<Breadcrumbs />', () => {
const button = getByText(breadcrumbsExpected.section.displayName);
userEvent.click(button);
await waitFor(() => {
expect(queryAllByTestId('breadcrumbs-section-dropdown-item')).toHaveLength(4);
expect(queryAllByTestId('breadcrumbs-section-dropdown-item')).toHaveLength(5);
});

userEvent.click(getByText(breadcrumbsExpected.subsection.displayName));
expect(queryAllByTestId('breadcrumbs-subsection-dropdown-item')).toHaveLength(3);
expect(queryAllByTestId('breadcrumbs-subsection-dropdown-item')).toHaveLength(2);
});
});
13 changes: 5 additions & 8 deletions src/course-unit/hooks.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { AppContext } from '@edx/frontend-platform/react';
import { useNavigate } from 'react-router-dom';

import { RequestStatus } from '../data/constants';
Expand All @@ -13,21 +12,21 @@ import {
fetchCourseSectionVerticalData,
} from './data/thunk';
import {
getCourseSectionVertical,
getCourseUnitData,
getLoadingStatus,
getSavingStatus,
} from './data/selectors';
import { updateSavingStatus } from './data/slice';
import { getUnitViewLivePath, getUnitPreviewPath } from './utils';

// eslint-disable-next-line import/prefer-default-export
export const useCourseUnit = ({ courseId, blockId }) => {
const dispatch = useDispatch();

const { config } = useContext(AppContext);
const courseUnit = useSelector(getCourseUnitData);
const savingStatus = useSelector(getSavingStatus);
const loadingStatus = useSelector(getLoadingStatus);
const { draftPreviewLink, publishedPreviewLink } = useSelector(getCourseSectionVertical);
const navigate = useNavigate();
const [isTitleEditFormOpen, toggleTitleEditForm] = useState(false);

Expand All @@ -36,12 +35,10 @@ export const useCourseUnit = ({ courseId, blockId }) => {

const headerNavigationsActions = {
handleViewLive: () => {
window.open(config.LMS_BASE_URL + getUnitViewLivePath(courseId, blockId), '_blank');
window.open(publishedPreviewLink, '_blank');
},
handlePreview: () => {
const subsectionId = courseUnit.ancestorInfo?.ancestors[0]?.id.split('@').pop();
const sectionId = courseUnit.ancestorInfo?.ancestors[1]?.id.split('@').pop();
window.open(config.PREVIEW_BASE_URL + getUnitPreviewPath(courseId, sectionId, subsectionId, blockId), '_blank');
window.open(draftPreviewLink, '_blank');
},
};

Expand Down
23 changes: 0 additions & 23 deletions src/course-unit/utils.jsx

This file was deleted.

1 change: 0 additions & 1 deletion src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ initialize({
SUPPORT_URL: process.env.SUPPORT_URL || null,
SUPPORT_EMAIL: process.env.SUPPORT_EMAIL || null,
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
PREVIEW_BASE_URL: process.env.PREVIEW_BASE_URL,
EXAMS_BASE_URL: process.env.EXAMS_BASE_URL || null,
CALCULATOR_HELP_URL: process.env.CALCULATOR_HELP_URL || null,
ENABLE_PROGRESS_GRAPH_SETTINGS: process.env.ENABLE_PROGRESS_GRAPH_SETTINGS || 'false',
Expand Down
1 change: 0 additions & 1 deletion src/setupTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ mergeConfig({
CALCULATOR_HELP_URL: process.env.CALCULATOR_HELP_URL || null,
ENABLE_PROGRESS_GRAPH_SETTINGS: process.env.ENABLE_PROGRESS_GRAPH_SETTINGS || 'false',
ENABLE_TEAM_TYPE_SETTING: process.env.ENABLE_TEAM_TYPE_SETTING === 'true',
PREVIEW_BASE_URL: process.env.PREVIEW_BASE_URL || '',
}, 'CourseAuthoringConfig');

// Mock the plugins repo so jest will stop complaining about ES6 syntax
Expand Down

0 comments on commit 51c5f9c

Please sign in to comment.