Skip to content

Commit

Permalink
feat: [FC-0070] rendering library content in unit page
Browse files Browse the repository at this point in the history
  • Loading branch information
ihor-romaniuk committed Nov 25, 2024
1 parent 55fe87a commit 09c33b0
Show file tree
Hide file tree
Showing 32 changed files with 426 additions and 140 deletions.
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const COURSE_BLOCK_NAMES = ({
chapter: { id: 'chapter', name: 'Section' },
sequential: { id: 'sequential', name: 'Subsection' },
vertical: { id: 'vertical', name: 'Unit' },
libraryContent: { id: 'library_content', name: 'Library content' },
component: { id: 'component', name: 'Component' },
});

Expand Down
82 changes: 51 additions & 31 deletions src/course-unit/CourseUnit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import ProcessingNotification from '../generic/processing-notification';
import { SavingErrorAlert } from '../generic/saving-error-alert';
import ConnectionErrorAlert from '../generic/ConnectionErrorAlert';
import Loading from '../generic/Loading';
import { COURSE_BLOCK_NAMES } from '../constants';
import AddComponent from './add-component/AddComponent';
import HeaderTitle from './header-title/HeaderTitle';
import Breadcrumbs from './breadcrumbs/Breadcrumbs';
Expand All @@ -45,6 +46,7 @@ const CourseUnit = ({ courseId }) => {
isLoading,
sequenceId,
unitTitle,
unitCategory,
errorMessage,
sequenceStatus,
savingStatus,
Expand All @@ -71,6 +73,19 @@ const CourseUnit = ({ courseId }) => {
handleNavigateToTargetUnit,
} = useCourseUnit({ courseId, blockId });

const isUnitVerticalType = unitCategory === COURSE_BLOCK_NAMES.vertical.id;
const isUnitLibraryType = unitCategory === COURSE_BLOCK_NAMES.libraryContent.id;

const unitLayout = [{ span: 12 }, { span: 0 }];
const defaultLayout = {
lg: [{ span: 8 }, { span: 4 }],
md: [{ span: 8 }, { span: 4 }],
sm: [{ span: 8 }, { span: 3 }],
xs: [{ span: 9 }, { span: 3 }],
xl: [{ span: 9 }, { span: 3 }],
};
const layoutGrid = isUnitLibraryType ? { lg: unitLayout } : defaultLayout;

useEffect(() => {
document.title = getPageHeadTitle('', unitTitle);
}, [unitTitle]);
Expand Down Expand Up @@ -142,28 +157,28 @@ const CourseUnit = ({ courseId }) => {
/>
)}
breadcrumbs={(
<Breadcrumbs />
<Breadcrumbs
courseId={courseId}
sequenceId={sequenceId}
/>
)}
headerActions={(
<HeaderNavigations
unitCategory={unitCategory}
headerNavigationsActions={headerNavigationsActions}
/>
)}
/>
<Sequence
courseId={courseId}
sequenceId={sequenceId}
unitId={blockId}
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
showPasteUnit={showPasteUnit}
/>
<Layout
lg={[{ span: 8 }, { span: 4 }]}
md={[{ span: 8 }, { span: 4 }]}
sm={[{ span: 8 }, { span: 3 }]}
xs={[{ span: 9 }, { span: 3 }]}
xl={[{ span: 9 }, { span: 3 }]}
>
{isUnitVerticalType && (
<Sequence
courseId={courseId}
sequenceId={sequenceId}
unitId={blockId}
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
showPasteUnit={showPasteUnit}
/>
)}
<Layout {...layoutGrid}>
<Layout.Element>
{currentlyVisibleToStudents && (
<AlertMessage
Expand All @@ -184,11 +199,13 @@ const CourseUnit = ({ courseId }) => {
unitXBlockActions={unitXBlockActions}
courseVerticalChildren={courseVerticalChildren.children}
/>
<AddComponent
blockId={blockId}
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
/>
{showPasteXBlock && canPasteComponent && (
{isUnitVerticalType && (
<AddComponent
blockId={blockId}
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
/>
)}
{showPasteXBlock && canPasteComponent && isUnitVerticalType && (
<PasteComponent
clipboardData={sharedClipboardData}
onClick={handleCreateNewCourseXBlock}
Expand All @@ -205,18 +222,21 @@ const CourseUnit = ({ courseId }) => {
</Layout.Element>
<Layout.Element>
<Stack gap={3}>
<Sidebar data-testid="course-unit-sidebar">
<PublishControls blockId={blockId} />
</Sidebar>
{getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true'
&& (
<Sidebar className="tags-sidebar">
<TagsSidebarControls />
</Sidebar>
{isUnitVerticalType && (
<>
<Sidebar data-testid="course-unit-sidebar">
<PublishControls blockId={blockId} />
</Sidebar>
{getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && (
<Sidebar className="tags-sidebar">
<TagsSidebarControls />
</Sidebar>
)}
<Sidebar data-testid="course-unit-location-sidebar">
<LocationInfo />
</Sidebar>
</>
)}
<Sidebar data-testid="course-unit-location-sidebar">
<LocationInfo />
</Sidebar>
</Stack>
</Layout.Element>
</Layout>
Expand Down
4 changes: 4 additions & 0 deletions src/course-unit/CourseUnit.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
@import "./move-modal";
@import "./preview-changes";

.course-unit {
min-width: 900px;
}

.course-unit__alert {
margin-bottom: 1.75rem;
}
89 changes: 85 additions & 4 deletions src/course-unit/CourseUnit.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import sidebarMessages from './sidebar/messages';
import { extractCourseUnitId } from './sidebar/utils';
import CourseUnit from './CourseUnit';

import { getClipboardUrl } from '../generic/data/api';
import configureModalMessages from '../generic/configure-modal/messages';
import { getContentTaxonomyTagsApiUrl, getContentTaxonomyTagsCountApiUrl } from '../content-tags-drawer/data/api';
import addComponentMessages from './add-component/messages';
Expand Down Expand Up @@ -138,6 +139,9 @@ describe('<CourseUnit />', () => {
global.localStorage.clear();
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock
.onGet(getClipboardUrl())
.reply(200, clipboardUnit);
axiosMock
.onGet(getCourseUnitApiUrl(courseId))
.reply(200, courseUnitIndexMock);
Expand Down Expand Up @@ -226,6 +230,19 @@ describe('<CourseUnit />', () => {
display_name: newDisplayName,
},
});
axiosMock
.onGet(getCourseSectionVerticalApiUrl(blockId))
.reply(200, {
...courseSectionVerticalMock,
xblock_info: {
...courseSectionVerticalMock.xblock_info,
display_name: newDisplayName,
},
xblock: {
...courseSectionVerticalMock.xblock,
display_name: newDisplayName,
},
});

await waitFor(() => {
const unitHeaderTitle = getByTestId('unit-header-title');
Expand Down Expand Up @@ -914,9 +931,7 @@ describe('<CourseUnit />', () => {
.reply(200, clipboardMockResponse);
axiosMock
.onGet(getCourseSectionVerticalApiUrl(blockId))
.reply(200, {
...updatedCourseSectionVerticalData,
});
.reply(200, updatedCourseSectionVerticalData);

global.localStorage.setItem('staticFileNotices', JSON.stringify(clipboardMockResponse.staticFileNotices));
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
Expand Down Expand Up @@ -1190,7 +1205,7 @@ describe('<CourseUnit />', () => {

axiosMock
.onGet(getCourseUnitApiUrl(blockId))
.reply(200, {});
.reply(200, courseUnitIndexMock);

await act(async () => {
await waitFor(() => {
Expand Down Expand Up @@ -1324,4 +1339,70 @@ describe('<CourseUnit />', () => {
);
});
});

describe('Library Content page', () => {
const newUnitId = '12345';
const sequinceId = 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5';
const messageEvent = new MessageEvent('message', {
data: {
type: messageTypes.handleViewXBlockContent,
payload: {
destination: `http://localhost:18001/container/${newUnitId}`,
},
},
origin: '*',
});

beforeEach(async () => {
axiosMock
.onGet(getCourseSectionVerticalApiUrl(blockId))
.reply(200, {
...courseSectionVerticalMock,
xblock: {
...courseSectionVerticalMock.xblock,
category: 'library_content',
},
xblock_info: {
...courseSectionVerticalMock.xblock_info,
category: 'library_content',
},
});
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
});

it('navigates to library content page on receive window event', () => {
render(<RootWrapper />);

window.dispatchEvent(messageEvent);
expect(mockedUsedNavigate).toHaveBeenCalledWith(`/course/${courseId}/container/${newUnitId}/${sequinceId}`);
});

it('should render library content page correctly', async () => {
const {
getByText,
getByRole,
queryByRole,
getByTestId,
} = render(<RootWrapper />);

const currentSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name;
const currentSubSectionName = courseUnitIndexMock.ancestor_info.ancestors[1].display_name;

await waitFor(() => {
const unitHeaderTitle = getByTestId('unit-header-title');
expect(getByText(unitDisplayName)).toBeInTheDocument();
expect(within(unitHeaderTitle).getByRole('button', { name: headerTitleMessages.altButtonEdit.defaultMessage })).toBeInTheDocument();
expect(within(unitHeaderTitle).getByRole('button', { name: headerTitleMessages.altButtonSettings.defaultMessage })).toBeInTheDocument();
expect(getByRole('button', { name: currentSectionName })).toBeInTheDocument();
expect(getByRole('button', { name: currentSubSectionName })).toBeInTheDocument();

expect(queryByRole('heading', { name: addComponentMessages.title.defaultMessage })).not.toBeInTheDocument();
expect(queryByRole('button', { name: headerNavigationsMessages.viewLiveButton.defaultMessage })).not.toBeInTheDocument();
expect(queryByRole('button', { name: headerNavigationsMessages.previewButton.defaultMessage })).not.toBeInTheDocument();

expect(queryByRole('heading', { name: /unit tags/i })).not.toBeInTheDocument();
expect(queryByRole('heading', { name: /unit location/i })).not.toBeInTheDocument();
});
});
});
});
2 changes: 1 addition & 1 deletion src/course-unit/add-component/AddComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
const [isOpenAdvanced, openAdvanced, closeAdvanced] = useToggle(false);
const [isOpenHtml, openHtml, closeHtml] = useToggle(false);
const [isOpenOpenAssessment, openOpenAssessment, closeOpenAssessment] = useToggle(false);
const { componentTemplates } = useSelector(getCourseSectionVertical);
const { componentTemplates = {} } = useSelector(getCourseSectionVertical);
const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle();
const [isSelectLibraryContentModalOpen, showSelectLibraryContentModal, closeSelectLibraryContentModal] = useToggle();
const [selectedComponents, setSelectedComponents] = useState([]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import { Icon } from '@openedx/paragon';
import { EditNote as EditNoteIcon } from '@openedx/paragon/icons';

import { COMPONENT_TYPES, COMPONENT_TYPE_ICON_MAP } from '../../../generic/block-type-utils/constants';
import { COMPONENT_TYPE_ICON_MAP } from '../../../generic/block-type-utils/constants';

const AddComponentIcon = ({ type }) => {
const icon = COMPONENT_TYPE_ICON_MAP[type] || EditNoteIcon;
Expand All @@ -11,7 +11,7 @@ const AddComponentIcon = ({ type }) => {
};

AddComponentIcon.propTypes = {
type: PropTypes.oneOf(Object.values(COMPONENT_TYPES)).isRequired,
type: PropTypes.string.isRequired,
};

export default AddComponentIcon;
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const ComponentModalView = ({
<OverlayTrigger
placement="right"
overlay={(
<Tooltip>
<Tooltip id={`${componentTemplate.displayName}-support-tooltip`}>
{supportLabels[componentTemplate.supportLevel].tooltip}
</Tooltip>
)}
Expand Down
Loading

0 comments on commit 09c33b0

Please sign in to comment.