diff --git a/src/library-authoring/components/ContainerCard.scss b/src/library-authoring/components/ContainerCard.scss
new file mode 100644
index 000000000..f5c6b6bb1
--- /dev/null
+++ b/src/library-authoring/components/ContainerCard.scss
@@ -0,0 +1,7 @@
+.container-card-preview-text {
+ display: -webkit-box; /* stylelint-disable-line value-no-vendor-prefix */
+ line-clamp: 3;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+};
diff --git a/src/library-authoring/components/ContainerCard.test.tsx b/src/library-authoring/components/ContainerCard.test.tsx
index cff6e6bbc..23ca834a8 100644
--- a/src/library-authoring/components/ContainerCard.test.tsx
+++ b/src/library-authoring/components/ContainerCard.test.tsx
@@ -68,17 +68,51 @@ describe('', () => {
({ axiosMock, mockShowToast } = initializeMocks());
});
- it('should render the card with title', () => {
- render();
+ test.each([
+ {
+ label: 'should render the unit card with title',
+ containerType: ContainerType.Unit,
+ displayName: 'unit Display Formated Name',
+ },
+ {
+ label: 'should render the subsection card with title',
+ containerType: ContainerType.Subsection,
+ displayName: 'subsection Display Formated Name',
+ },
+ {
+ label: 'should render the section card with title',
+ containerType: ContainerType.Section,
+ displayName: 'section Display Formated Name',
+ },
+ ])('$label', ({ containerType, displayName }) => {
+ const container = getContainerHitSample(containerType);
+ render();
- expect(screen.getByText('unit Display Formated Name')).toBeInTheDocument();
+ expect(screen.getByText(displayName)).toBeInTheDocument();
expect(screen.queryByText('2')).toBeInTheDocument(); // Component count
});
- it('should render published content', () => {
- render(, true);
+ test.each([
+ {
+ label: 'sould render published content of unit card',
+ containerType: ContainerType.Unit,
+ displayName: 'Published unit Display Name',
+ },
+ {
+ label: 'sould render published content of subsection card',
+ containerType: ContainerType.Subsection,
+ displayName: 'Published subsection Display Name',
+ },
+ {
+ label: 'sould render published content of section card',
+ containerType: ContainerType.Section,
+ displayName: 'Published section Display Name',
+ },
+ ])('$label', ({ containerType, displayName }) => {
+ const container = getContainerHitSample(containerType);
+ render(, true);
- expect(screen.getByText('Published unit Display Name')).toBeInTheDocument();
+ expect(screen.getByText(displayName)).toBeInTheDocument();
expect(screen.queryByText('1')).toBeInTheDocument(); // Published Component Count
});
@@ -153,7 +187,7 @@ describe('', () => {
fireEvent.click(deleteMenuItem);
// Confirm delete Modal is open
- expect(screen.getByText('Delete Unit'));
+ expect(await screen.findByText('Delete Unit')).toBeInTheDocument();
const deleteButton = screen.getByRole('button', { name: /delete/i });
fireEvent.click(deleteButton);
@@ -200,14 +234,14 @@ describe('', () => {
expect(mockShowToast).toHaveBeenCalledWith('Failed to delete unit');
});
- it('should render no child blocks in card preview', async () => {
+ it('should render no child blocks in unit card preview', async () => {
render();
expect(screen.queryByTitle('lb:org1:Demo_course:html:text-0')).not.toBeInTheDocument();
expect(screen.queryByText('+0')).not.toBeInTheDocument();
});
- it('should render <=5 child blocks in card preview', async () => {
+ it('should render <=5 child blocks in unit card preview', async () => {
const containerWith5Children = {
...getContainerHitSample(),
content: {
@@ -220,7 +254,7 @@ describe('', () => {
expect(screen.queryByText('+0')).not.toBeInTheDocument();
});
- it('should render >5 child blocks with +N in card preview', async () => {
+ it('should render >5 child blocks with +N in unit card preview', async () => {
const containerWith6Children = {
...getContainerHitSample(),
content: {
@@ -233,7 +267,7 @@ describe('', () => {
expect(screen.queryByText('+2')).toBeInTheDocument();
});
- it('should render published child blocks when rendering a published card preview', async () => {
+ it('should render published child blocks when rendering a published unit card preview', async () => {
const containerWithPublishedChildren = {
...getContainerHitSample(),
content: {
@@ -253,4 +287,104 @@ describe('', () => {
expect((await screen.findAllByTitle(/lb:org1:Demo_course:html:text-*/)).length).toBe(2);
expect(screen.queryByText('+2')).not.toBeInTheDocument();
});
+
+ test.each([
+ {
+ label: 'should render published child in subsection card preview',
+ containerType: ContainerType.Subsection,
+ childrenType: 'unit',
+ displayName: 'Published subsection Display Name',
+ expected: /contains unit 0, unit 1\./i,
+ },
+ {
+ label: 'should render published child in section card preview',
+ containerType: ContainerType.Section,
+ childrenType: 'subsection',
+ displayName: 'Published section Display Name',
+ expected: /contains subsection 0, subsection 1\./i,
+ },
+ ])('$label', ({
+ containerType,
+ childrenType,
+ displayName,
+ expected,
+ }) => {
+ const containerWithChildren = {
+ ...getContainerHitSample(containerType),
+ content: {
+ childUsageKeys: Array(6).fill('').map(
+ (_child, idx) => `lct:org1:Demo_Course:${childrenType}:${childrenType}-${idx}`,
+ ),
+ childDisplayNames: Array(6).fill('').map((_child, idx) => `${childrenType} ${idx}`),
+ },
+ published: {
+ content: {
+ childUsageKeys: Array(2).fill('').map(
+ (_child, idx) => `lct:org1:Demo_Course:${childrenType}:${childrenType}-${idx}`,
+ ),
+ childDisplayNames: Array(2).fill('').map((_child, idx) => `${childrenType} ${idx}`),
+ },
+ },
+ } satisfies ContainerHit;
+
+ render(, true);
+
+ expect(screen.getByText(displayName)).toBeInTheDocument();
+ expect(screen.getByText(expected)).toBeInTheDocument();
+ });
+
+ test.each([
+ {
+ label: 'should render subsection card preview with children',
+ containerType: ContainerType.Subsection,
+ childrenType: 'unit',
+ displayName: 'subsection Display Formated Name',
+ expected: /contains unit 0, unit 1\./i,
+ },
+ {
+ label: 'should render section card preview with children',
+ containerType: ContainerType.Section,
+ childrenType: 'subsection',
+ displayName: 'section Display Formated Name',
+ expected: /contains subsection 0, subsection 1\./i,
+ },
+ ])('$label', ({
+ containerType,
+ childrenType,
+ displayName,
+ expected,
+ }) => {
+ const containerWithChildren = {
+ ...getContainerHitSample(containerType),
+ content: {
+ childUsageKeys: Array(2).fill('').map(
+ (_child, idx) => `lct:org1:Demo_Course:${childrenType}:${childrenType}-${idx}`,
+ ),
+ childDisplayNames: Array(2).fill('').map((_child, idx) => `${childrenType} ${idx}`),
+ },
+ } satisfies ContainerHit;
+ render();
+
+ expect(screen.getByText(displayName)).toBeInTheDocument();
+ expect(screen.getByText(expected)).toBeInTheDocument();
+ });
+
+ test.each([
+ {
+ label: 'should render subsection card preview without children',
+ containerType: ContainerType.Subsection,
+ displayName: 'subsection Display Formated Name',
+ },
+ {
+ label: 'should render section card preview without children',
+ containerType: ContainerType.Section,
+ displayName: 'section Display Formated Name',
+ },
+ ])('$label', ({ containerType, displayName }) => {
+ const container = getContainerHitSample(containerType);
+ render();
+
+ expect(screen.getByText(displayName)).toBeInTheDocument();
+ expect(screen.queryByText(/contains/i)).not.toBeInTheDocument();
+ });
});
diff --git a/src/library-authoring/components/ContainerCard.tsx b/src/library-authoring/components/ContainerCard.tsx
index 1a4977d6a..ecad91940 100644
--- a/src/library-authoring/components/ContainerCard.tsx
+++ b/src/library-authoring/components/ContainerCard.tsx
@@ -13,7 +13,7 @@ import { MoreVert } from '@openedx/paragon/icons';
import { getItemIcon, getComponentStyleColor } from '../../generic/block-type-utils';
import { ContainerType, getBlockType } from '../../generic/key-utils';
import { ToastContext } from '../../generic/toast-context';
-import { type ContainerHit, PublishStatus } from '../../search-manager';
+import { type ContainerHit, Highlight, PublishStatus } from '../../search-manager';
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
import { useLibraryContext } from '../common/context/LibraryContext';
import { SidebarActions, useSidebarContext } from '../common/context/SidebarContext';
@@ -88,7 +88,7 @@ export const ContainerMenu = ({ containerKey, containerType, displayName } : Con
-
+
{insideCollection && (
@@ -96,7 +96,7 @@ export const ContainerMenu = ({ containerKey, containerType, displayName } : Con
)}
-
+
@@ -111,17 +111,17 @@ export const ContainerMenu = ({ containerKey, containerType, displayName } : Con
);
};
-type ContainerCardPreviewProps = {
- childUsageKeys: Array;
+type UnitCardPreviewProps = {
+ childKeys: Array;
showMaxChildren?: number;
};
-const ContainerCardPreview = ({ childUsageKeys, showMaxChildren = 5 }: ContainerCardPreviewProps) => {
- const hiddenChildren = childUsageKeys.length - showMaxChildren;
+const UnitcardPreview = ({ childKeys, showMaxChildren = 5 }: UnitCardPreviewProps) => {
+ const hiddenChildren = childKeys.length - showMaxChildren;
return (
{
- childUsageKeys.slice(0, showMaxChildren).map((usageKey, idx) => {
+ childKeys.slice(0, showMaxChildren).map((usageKey, idx) => {
const blockType = getBlockType(usageKey);
let blockPreview: ReactNode;
let classNames;
@@ -162,6 +162,51 @@ const ContainerCardPreview = ({ childUsageKeys, showMaxChildren = 5 }: Container
);
};
+type ContainerCardPreviewProps = {
+ hit: ContainerHit,
+};
+
+const ContainerCardPreview = ({ hit }: ContainerCardPreviewProps) => {
+ const intl = useIntl();
+ const { showOnlyPublished } = useLibraryContext();
+ const {
+ blockType: itemType,
+ published,
+ content,
+ } = hit;
+
+ if (itemType === 'unit') {
+ const childKeys: Array = (
+ showOnlyPublished ? published?.content?.childUsageKeys : content?.childUsageKeys
+ ) ?? [];
+
+ return ;
+ }
+ // TODO Section highlights
+
+ const childNames: Array = (
+ showOnlyPublished ? published?.content?.childDisplayNames : content?.childDisplayNames
+ ) ?? [];
+
+ if (childNames.length > 0) {
+ // Preview with a truncated text with all children display names
+ const childrenText = intl.formatMessage(
+ messages.containerPreviewText,
+ {
+ children: childNames.join(', '),
+ },
+ );
+
+ return (
+
+
+
+ );
+ }
+ // Empty preview
+ return null;
+};
+
type ContainerCardProps = {
hit: ContainerHit,
};
@@ -179,7 +224,6 @@ const ContainerCard = ({ hit } : ContainerCardProps) => {
published,
publishStatus,
usageKey: containerKey,
- content,
} = hit;
const numChildrenCount = showOnlyPublished ? (
@@ -190,10 +234,6 @@ const ContainerCard = ({ hit } : ContainerCardProps) => {
showOnlyPublished ? formatted.published?.displayName : formatted.displayName
) ?? '';
- const childUsageKeys: Array = (
- showOnlyPublished ? published?.content?.childUsageKeys : content?.childUsageKeys
- ) ?? [];
-
const selected = sidebarComponentInfo?.id === containerKey;
const { navigateTo } = useLibraryRoutes();
@@ -237,7 +277,7 @@ const ContainerCard = ({ hit } : ContainerCardProps) => {
}
+ preview={}
tags={tags}
numChildren={numChildrenCount}
actions={(
diff --git a/src/library-authoring/components/index.scss b/src/library-authoring/components/index.scss
new file mode 100644
index 000000000..fd6ce4244
--- /dev/null
+++ b/src/library-authoring/components/index.scss
@@ -0,0 +1,2 @@
+@import "./BaseCard.scss";
+@import "./ContainerCard.scss";
diff --git a/src/library-authoring/components/messages.ts b/src/library-authoring/components/messages.ts
index e107eedd2..e2d281034 100644
--- a/src/library-authoring/components/messages.ts
+++ b/src/library-authoring/components/messages.ts
@@ -276,5 +276,10 @@ const messages = defineMessages({
defaultMessage: 'Failed to undo remove component operation',
description: 'Message to display on failure to undo delete component',
},
+ containerPreviewText: {
+ id: 'course-authoring.library-authoring.container.preview.text',
+ defaultMessage: 'Contains {children}.',
+ description: 'Preview message for section/subsections with the names of children separated by commas',
+ },
});
export default messages;
diff --git a/src/library-authoring/index.scss b/src/library-authoring/index.scss
index 3c439e73b..1de953373 100644
--- a/src/library-authoring/index.scss
+++ b/src/library-authoring/index.scss
@@ -1,5 +1,5 @@
@import "./component-info/ComponentPreview";
-@import "./components/BaseCard";
+@import "./components";
@import "./generic";
@import "./LibraryAuthoringPage";
@import "./units";
diff --git a/src/search-manager/data/api.ts b/src/search-manager/data/api.ts
index 55b3e0bc8..d6cf9db23 100644
--- a/src/search-manager/data/api.ts
+++ b/src/search-manager/data/api.ts
@@ -54,6 +54,7 @@ export interface ContentDetails {
htmlContent?: string;
capaContent?: string;
childUsageKeys?: Array;
+ childDisplayNames?: Array;
[k: string]: any;
}
@@ -179,6 +180,7 @@ export interface CollectionHit extends BaseContentHit {
*/
interface ContainerHitContent {
childUsageKeys?: string[],
+ childDisplayNames?: string[],
}
export interface ContainerHit extends BaseContentHit {
type: 'library_container';