Skip to content

Commit 4d8978f

Browse files
committed
test: library unit page
1 parent 73a1c26 commit 4d8978f

4 files changed

Lines changed: 130 additions & 1 deletion

File tree

src/library-authoring/data/api.mocks.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ export async function mockXBlockFields(usageKey: string): Promise<api.XBlockFiel
271271
case thisMock.usageKeyNewProblem: return thisMock.dataNewProblem;
272272
case thisMock.usageKeyNewVideo: return thisMock.dataNewVideo;
273273
case thisMock.usageKeyThirdParty: return thisMock.dataThirdParty;
274+
case thisMock.usageKey0: return thisMock.dataHtml0;
274275
default: throw new Error(`No mock has been set up for usageKey "${usageKey}"`);
275276
}
276277
}
@@ -281,6 +282,13 @@ mockXBlockFields.dataHtml = {
281282
data: '<p>This is a text component which uses <strong>HTML</strong>.</p>',
282283
metadata: { displayName: 'Introduction to Testing' },
283284
} satisfies api.XBlockFields;
285+
// Mock of another "regular" HTML (Text) block:
286+
mockXBlockFields.usageKey0 = 'lb:org1:Demo_course:html:text-0';
287+
mockXBlockFields.dataHtml0 = {
288+
displayName: 'text block 0',
289+
data: '<p>This is a text component which uses <strong>HTML</strong>.</p>',
290+
metadata: { displayName: 'text block 0' },
291+
} satisfies api.XBlockFields;
284292
// Mock of a blank/new HTML (Text) block:
285293
mockXBlockFields.usageKeyNewHtml = 'lb:Axim:TEST:html:123';
286294
mockXBlockFields.dataNewHtml = {
@@ -464,7 +472,7 @@ mockGetCollectionMetadata.applyMock = () => {
464472
*/
465473
export async function mockGetContainerMetadata(containerId: string): Promise<api.Container> {
466474
switch (containerId) {
467-
case mockGetCollectionMetadata.collectionIdError:
475+
case mockGetContainerMetadata.containerIdError:
468476
throw createAxiosError({
469477
code: 404,
470478
message: 'Not found.',
@@ -506,6 +514,9 @@ mockGetContainerMetadata.applyMock = () => {
506514
export async function mockGetContainerChildren(containerId: string): Promise<api.LibraryBlockMetadata[]> {
507515
let numChildren: number;
508516
switch (containerId) {
517+
case mockGetContainerMetadata.containerId:
518+
numChildren = 3;
519+
break;
509520
case mockGetContainerChildren.fiveChildren:
510521
numChildren = 5;
511522
break;
@@ -522,6 +533,7 @@ export async function mockGetContainerChildren(containerId: string): Promise<api
522533
...child,
523534
// Generate a unique ID for each child block to avoid "duplicate key" errors in tests
524535
id: `lb:org1:Demo_course:html:text-${idx}`,
536+
displayName: `text block ${idx}`,
525537
}
526538
)),
527539
);

src/library-authoring/units/LibraryUnitBlocks.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export const LibraryUnitBlocks = () => {
5151
}
5252

5353
if (isError) {
54+
// istanbul ignore next
5455
return <ErrorAlert error={error} />;
5556
}
5657

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import userEvent from '@testing-library/user-event';
2+
import {
3+
initializeMocks,
4+
render,
5+
screen,
6+
waitFor,
7+
within,
8+
} from '../../testUtils';
9+
import {
10+
mockContentLibrary,
11+
mockXBlockFields,
12+
mockGetContainerMetadata,
13+
mockGetContainerChildren,
14+
mockLibraryBlockMetadata,
15+
} from '../data/api.mocks';
16+
import { mockContentSearchConfig, mockGetBlockTypes } from '../../search-manager/data/api.mock';
17+
import { mockBroadcastChannel, mockClipboardEmpty } from '../../generic/data/api.mock';
18+
import LibraryLayout from '../LibraryLayout';
19+
20+
const path = '/library/:libraryId/*';
21+
const libraryTitle = mockContentLibrary.libraryData.title;
22+
23+
mockClipboardEmpty.applyMock();
24+
mockGetContainerMetadata.applyMock();
25+
mockGetContainerChildren.applyMock();
26+
mockContentSearchConfig.applyMock();
27+
mockGetBlockTypes.applyMock();
28+
mockContentLibrary.applyMock();
29+
mockXBlockFields.applyMock();
30+
mockLibraryBlockMetadata.applyMock();
31+
mockBroadcastChannel();
32+
33+
describe('<LibraryUnitPage />', () => {
34+
beforeEach(() => {
35+
initializeMocks();
36+
});
37+
38+
const renderLibraryUnitPage = (unitId?: string, libraryId?: string) => {
39+
const libId = libraryId || mockContentLibrary.libraryId;
40+
const uId = unitId || mockGetContainerMetadata.containerId;
41+
render(<LibraryLayout />, {
42+
path,
43+
routerProps: {
44+
initialEntries: [`/library/${libId}/unit/${uId}`],
45+
},
46+
});
47+
};
48+
49+
it('shows the spinner before the query is complete', async () => {
50+
// This mock will never return data about the collection (it loads forever):
51+
renderLibraryUnitPage(mockGetContainerMetadata.containerIdLoading);
52+
const spinner = screen.getByRole('status');
53+
expect(spinner.textContent).toEqual('Loading...');
54+
});
55+
56+
it('shows an error component if no unit returned', async () => {
57+
// This mock will simulate incorrect unit id
58+
renderLibraryUnitPage(mockGetContainerMetadata.containerIdError);
59+
const errorMessage = 'Not found';
60+
expect(await screen.findByRole('alert')).toHaveTextContent(errorMessage);
61+
});
62+
63+
it('shows unit data', async () => {
64+
renderLibraryUnitPage();
65+
expect((await screen.findAllByText(libraryTitle))[0]).toBeInTheDocument();
66+
// Unit title
67+
expect((await screen.findAllByText(mockGetContainerMetadata.containerData.displayName))[0]).toBeInTheDocument();
68+
// unit info button
69+
expect(await screen.findByRole('button', { name: 'Unit Info' })).toBeInTheDocument();
70+
expect((await screen.findAllByRole('button', { name: 'Drag to reorder' })).length).toEqual(3);
71+
// check all children components are rendered.
72+
expect(await screen.findByText('text block 0')).toBeInTheDocument();
73+
expect(await screen.findByText('text block 1')).toBeInTheDocument();
74+
expect(await screen.findByText('text block 2')).toBeInTheDocument();
75+
// 3 preview iframes
76+
expect((await screen.findAllByTestId('block-preview')).length).toEqual(3);
77+
});
78+
79+
it('should open and close the unit sidebar', async () => {
80+
renderLibraryUnitPage();
81+
82+
// sidebar should be visible by default
83+
const sidebar = await screen.findByTestId('library-sidebar');
84+
85+
const { findByText } = within(sidebar);
86+
87+
// The mock data for the sidebar has a title of "Test Unit"
88+
expect(await findByText('Test Unit')).toBeInTheDocument();
89+
90+
// should close if open
91+
userEvent.click(await screen.findByText('Unit Info'));
92+
await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument());
93+
94+
// Open again
95+
userEvent.click(await screen.findByText('Unit Info'));
96+
expect(await screen.findByTestId('library-sidebar')).toBeInTheDocument();
97+
});
98+
99+
it('should open and component sidebar on component selection', async () => {
100+
renderLibraryUnitPage();
101+
102+
const component = await screen.findByText('text block 0');
103+
userEvent.click(component);
104+
const sidebar = await screen.findByTestId('library-sidebar');
105+
106+
const { findByRole, findByText } = within(sidebar);
107+
108+
// The mock data for the sidebar has a title of "text block 0"
109+
expect(await findByText('text block 0')).toBeInTheDocument();
110+
111+
const closeButton = await findByRole('button', { name: /close/i });
112+
userEvent.click(closeButton);
113+
await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument());
114+
});
115+
});

src/library-authoring/units/LibraryUnitPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export const LibraryUnitPage = () => {
131131
}
132132

133133
if (isError) {
134+
// istanbul ignore next
134135
return <ErrorAlert error={error} />;
135136
}
136137

0 commit comments

Comments
 (0)