feat: library units tab (#1754)
Implements the "Units" tab on the Library Authoring.
This commit is contained in:
@@ -215,6 +215,10 @@ describe('<LibraryAuthoringPage />', () => {
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'Collections' }));
|
||||
expect(await screen.findByText('No matching collections found in this library.')).toBeInTheDocument();
|
||||
|
||||
// Navigate to the units tab
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'Units' }));
|
||||
expect(await screen.findByText('No matching components found in this library.')).toBeInTheDocument();
|
||||
|
||||
// Go back to Home tab
|
||||
// This step is necessary to avoid the url change leak to other tests
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'All Content' }));
|
||||
@@ -898,7 +902,7 @@ describe('<LibraryAuthoringPage />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Disables Type filter on Collections tab', async () => {
|
||||
it('Disables Type filter on Collections and Units tab', async () => {
|
||||
await renderLibraryPage();
|
||||
|
||||
expect(await screen.findByText('Content library')).toBeInTheDocument();
|
||||
@@ -918,6 +922,12 @@ describe('<LibraryAuthoringPage />', () => {
|
||||
// No Types filter shown
|
||||
expect(screen.queryByRole('button', { name: /type/i })).not.toBeInTheDocument();
|
||||
|
||||
// Navigate to the units tab
|
||||
fireEvent.click(await screen.findByRole('tab', { name: 'Units' }));
|
||||
expect((await screen.findAllByText('Test Unit'))[0]).toBeInTheDocument();
|
||||
// No Types filter shown
|
||||
expect(screen.queryByRole('button', { name: /type/i })).not.toBeInTheDocument();
|
||||
|
||||
// Navigate to the components tab
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'Components' }));
|
||||
// Text components should be shown
|
||||
|
||||
@@ -146,7 +146,12 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
|
||||
} = useLibraryContext();
|
||||
const { openInfoSidebar, sidebarComponentInfo } = useSidebarContext();
|
||||
|
||||
const { insideCollections, insideComponents, navigateTo } = useLibraryRoutes();
|
||||
const {
|
||||
insideCollections,
|
||||
insideComponents,
|
||||
insideUnits,
|
||||
navigateTo,
|
||||
} = useLibraryRoutes();
|
||||
|
||||
// The activeKey determines the currently selected tab.
|
||||
const getActiveKey = () => {
|
||||
@@ -159,6 +164,9 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
|
||||
if (insideComponents) {
|
||||
return ContentType.components;
|
||||
}
|
||||
if (insideUnits) {
|
||||
return ContentType.units;
|
||||
}
|
||||
return ContentType.home;
|
||||
};
|
||||
const [activeKey, setActiveKey] = useState<ContentType>(getActiveKey);
|
||||
@@ -217,13 +225,14 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
|
||||
const activeTypeFilters = {
|
||||
components: 'type = "library_block"',
|
||||
collections: 'type = "collection"',
|
||||
units: 'block_type = "unit"',
|
||||
};
|
||||
if (activeKey !== ContentType.home) {
|
||||
extraFilter.push(activeTypeFilters[activeKey]);
|
||||
}
|
||||
|
||||
// Disable filtering by block/problem type when viewing the Collections tab.
|
||||
const overrideTypesFilter = insideCollections ? new TypesFilterData() : undefined;
|
||||
const overrideTypesFilter = (insideCollections || insideUnits) ? new TypesFilterData() : undefined;
|
||||
|
||||
return (
|
||||
<div className="d-flex">
|
||||
@@ -260,13 +269,14 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
|
||||
className="my-3"
|
||||
>
|
||||
<Tab eventKey={ContentType.home} title={intl.formatMessage(messages.homeTab)} />
|
||||
<Tab eventKey={ContentType.components} title={intl.formatMessage(messages.componentsTab)} />
|
||||
<Tab eventKey={ContentType.collections} title={intl.formatMessage(messages.collectionsTab)} />
|
||||
<Tab eventKey={ContentType.components} title={intl.formatMessage(messages.componentsTab)} />
|
||||
<Tab eventKey={ContentType.units} title={intl.formatMessage(messages.unitsTab)} />
|
||||
</Tabs>
|
||||
<ActionRow className="my-3">
|
||||
<SearchKeywordsField className="mr-3" />
|
||||
<FilterByTags />
|
||||
{!insideCollections && <FilterByBlockType />}
|
||||
{!(insideCollections || insideUnits) && <FilterByBlockType />}
|
||||
<FilterByPublished />
|
||||
<ClearFiltersButton />
|
||||
<ActionRow.Spacer />
|
||||
|
||||
@@ -58,6 +58,10 @@ const LibraryLayout = () => {
|
||||
path={ROUTES.COMPONENTS}
|
||||
element={context(<LibraryAuthoringPage />)}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.UNITS}
|
||||
element={context(<LibraryAuthoringPage />)}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.COLLECTIONS}
|
||||
element={context(<LibraryAuthoringPage />)}
|
||||
|
||||
@@ -481,6 +481,44 @@
|
||||
"display_name": "String Response Problem",
|
||||
"description": "Problem"
|
||||
}
|
||||
},
|
||||
{
|
||||
"display_name": "Test Unit",
|
||||
"block_id": "test-unit-9284e2",
|
||||
"id": "lctAximTESTunittest-unit-9284e2-a9a4386e",
|
||||
"type": "library_container",
|
||||
"breadcrumbs": [
|
||||
{
|
||||
"display_name": "Test Library"
|
||||
}
|
||||
],
|
||||
"created": 1742221203.895054,
|
||||
"modified": 1742221203.895054,
|
||||
"usage_key": "lct:Axim:TEST:unit:test-unit-9284e2",
|
||||
"block_type": "unit",
|
||||
"context_key": "lib:Axim:TEST",
|
||||
"org": "Axim",
|
||||
"access_id": 15,
|
||||
"num_children": 0,
|
||||
"_formatted": {
|
||||
"display_name": "Test Unit",
|
||||
"block_id": "test-unit-9284e2",
|
||||
"id": "lctAximTESTunittest-unit-9284e2-a9a4386e",
|
||||
"type": "library_container",
|
||||
"breadcrumbs": [
|
||||
{
|
||||
"display_name": "Test Library"
|
||||
}
|
||||
],
|
||||
"created": "1742221203.895054",
|
||||
"modified": "1742221203.895054",
|
||||
"usage_key": "lct:Axim:TEST:unit:test-unit-9284e2",
|
||||
"block_type": "unit",
|
||||
"context_key": "lib:Axim:TEST",
|
||||
"org": "Axim",
|
||||
"access_id": "15",
|
||||
"num_children": "0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"query": "",
|
||||
|
||||
@@ -46,6 +46,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Collections',
|
||||
description: 'Tab label for the collections tab',
|
||||
},
|
||||
unitsTab: {
|
||||
id: 'course-authoring.library-authoring.units-tab',
|
||||
defaultMessage: 'Units',
|
||||
description: 'Tab label for the units tab',
|
||||
},
|
||||
componentsTempPlaceholder: {
|
||||
id: 'course-authoring.library-authoring.components-temp-placeholder',
|
||||
defaultMessage: 'There are {componentCount} components in this library',
|
||||
|
||||
@@ -258,6 +258,33 @@ describe('Library Authoring routes', () => {
|
||||
path: '/collections/clctnId2',
|
||||
},
|
||||
},
|
||||
// "Units" tab
|
||||
{
|
||||
label: 'navigate from All Content tab to Units',
|
||||
origin: {
|
||||
path: '',
|
||||
params: {},
|
||||
},
|
||||
destination: {
|
||||
path: '/units',
|
||||
params: {
|
||||
contentType: ContentType.units,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'navigate from Units tab to All Content tab',
|
||||
origin: {
|
||||
path: '/units',
|
||||
params: {},
|
||||
},
|
||||
destination: {
|
||||
path: '',
|
||||
params: {
|
||||
contentType: ContentType.home,
|
||||
},
|
||||
},
|
||||
},
|
||||
])(
|
||||
'$label',
|
||||
async ({ origin, destination }) => {
|
||||
@@ -280,7 +307,7 @@ describe('Library Authoring routes', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockNavigate).toBeCalledWith({
|
||||
expect(mockNavigate).toHaveBeenCalledWith({
|
||||
pathname: `/library/${mockContentLibrary.libraryId}${destination.path}`,
|
||||
search: '',
|
||||
});
|
||||
|
||||
@@ -20,6 +20,8 @@ export const ROUTES = {
|
||||
COMPONENTS: '/components/:componentId?',
|
||||
// * Collections tab, with an optionally selected collectionId in the sidebar.
|
||||
COLLECTIONS: '/collections/:collectionId?',
|
||||
// * Units tab, with an optionally selected unitId in the sidebar.
|
||||
UNITS: '/units/:unitId?',
|
||||
// * All Content tab, with an optionally selected componentId in the sidebar.
|
||||
COMPONENT: '/component/:componentId',
|
||||
// * All Content tab, with an optionally selected collectionId in the sidebar.
|
||||
@@ -31,8 +33,9 @@ export const ROUTES = {
|
||||
|
||||
export enum ContentType {
|
||||
home = '',
|
||||
components = 'components',
|
||||
collections = 'collections',
|
||||
components = 'components',
|
||||
units = 'units',
|
||||
}
|
||||
|
||||
export type NavigateToData = {
|
||||
@@ -45,6 +48,7 @@ export type LibraryRoutesData = {
|
||||
insideCollection: PathMatch<string> | null;
|
||||
insideCollections: PathMatch<string> | null;
|
||||
insideComponents: PathMatch<string> | null;
|
||||
insideUnits: PathMatch<string> | null;
|
||||
|
||||
// Navigate using the best route from the current location for the given parameters.
|
||||
navigateTo: (dict?: NavigateToData) => void;
|
||||
@@ -59,6 +63,7 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
|
||||
const insideCollection = matchPath(BASE_ROUTE + ROUTES.COLLECTION, pathname);
|
||||
const insideCollections = matchPath(BASE_ROUTE + ROUTES.COLLECTIONS, pathname);
|
||||
const insideComponents = matchPath(BASE_ROUTE + ROUTES.COMPONENTS, pathname);
|
||||
const insideUnits = matchPath(BASE_ROUTE + ROUTES.UNITS, pathname);
|
||||
|
||||
const navigateTo = useCallback(({
|
||||
componentId,
|
||||
@@ -78,6 +83,8 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
|
||||
route = ROUTES.COMPONENTS;
|
||||
} else if (contentType === ContentType.collections) {
|
||||
route = ROUTES.COLLECTIONS;
|
||||
} else if (contentType === ContentType.units) {
|
||||
route = ROUTES.UNITS;
|
||||
} else if (contentType === ContentType.home) {
|
||||
route = ROUTES.HOME;
|
||||
} else if (insideCollections) {
|
||||
@@ -97,6 +104,11 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
|
||||
// We're inside the Components tab, so stay there,
|
||||
// optionally selecting a component.
|
||||
route = ROUTES.COMPONENTS;
|
||||
} else if (insideUnits) {
|
||||
// We're inside the Units tab, so stay there,
|
||||
// optionally selecting a unit.
|
||||
// istanbul ignore next: this will be covered when we add unit selection
|
||||
route = ROUTES.UNITS;
|
||||
} else if (componentId) {
|
||||
// We're inside the All Content tab, so stay there,
|
||||
// and select a component.
|
||||
@@ -124,5 +136,6 @@ export const useLibraryRoutes = (): LibraryRoutesData => {
|
||||
insideCollection,
|
||||
insideCollections,
|
||||
insideComponents,
|
||||
insideUnits,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user