feat: Unit card previews [FC-0083] (#1774)

Adds block tiles to the Unit card to indicate type and quantity of children in the container.
This commit is contained in:
Jillian
2025-04-09 08:17:04 +09:30
committed by GitHub
parent 451b821c3b
commit 7ceeb32820
9 changed files with 266 additions and 7 deletions

View File

@@ -498,6 +498,56 @@ mockGetContainerMetadata.applyMock = () => {
jest.spyOn(api, 'getContainerMetadata').mockImplementation(mockGetContainerMetadata);
};
/**
* Mock for `getContainerChildren()`
*
* This mock returns a fixed response for the given container ID.
*/
export async function mockGetContainerChildren(containerId: string): Promise<api.LibraryBlockMetadata[]> {
let numChildren: number;
switch (containerId) {
case mockGetContainerChildren.fiveChildren:
numChildren = 5;
break;
case mockGetContainerChildren.sixChildren:
numChildren = 6;
break;
default:
numChildren = 0;
break;
}
return Promise.resolve(
Array(numChildren).fill(mockGetContainerChildren.childTemplate).map((child, idx) => (
{
...child,
// Generate a unique ID for each child block to avoid "duplicate key" errors in tests
id: `lb:org1:Demo_course:html:text-${idx}`,
}
)),
);
}
mockGetContainerChildren.fiveChildren = 'lct:org1:Demo_Course:unit:unit-5';
mockGetContainerChildren.sixChildren = 'lct:org1:Demo_Course:unit:unit-6';
mockGetContainerChildren.childTemplate = {
id: 'lb:org1:Demo_course:html:text',
blockType: 'html',
defKey: 'def_key',
displayName: 'text block',
lastPublished: null,
publishedBy: null,
lastDraftCreated: null,
lastDraftCreatedBy: null,
hasUnpublishedChanges: false,
created: null,
modified: null,
tagsCount: 0,
collections: [] as api.CollectionMetadata[],
} satisfies api.LibraryBlockMetadata;
/** Apply this mock. Returns a spy object that can tell you if it's been called. */
mockGetContainerChildren.applyMock = () => {
jest.spyOn(api, 'getContainerChildren').mockImplementation(mockGetContainerChildren);
};
/**
* Mock for `getXBlockOLX()`
*

View File

@@ -111,6 +111,10 @@ export const getLibraryContainersApiUrl = (libraryId: string) => `${getApiBaseUr
* Get the URL for the container detail api.
*/
export const getLibraryContainerApiUrl = (containerId: string) => `${getApiBaseUrl()}/api/libraries/v2/containers/${containerId}/`;
/**
* Get the URL for a single container children api.
*/
export const getLibraryContainerChildrenApiUrl = (containerId: string) => `${getLibraryContainerApiUrl(containerId)}children/`;
export interface ContentLibrary {
id: string;
@@ -616,3 +620,12 @@ export async function updateContainerMetadata(
const client = getAuthenticatedHttpClient();
await client.patch(getLibraryContainerApiUrl(containerId), snakeCaseObject(containerData));
}
/**
* Fetch a library container's children's metadata.
*/
export async function getContainerChildren(containerId: string): Promise<LibraryBlockMetadata[]> {
const client = getAuthenticatedHttpClient();
const { data } = await client.get(getLibraryContainerChildrenApiUrl(containerId));
return camelCaseObject(data);
}

View File

@@ -13,6 +13,7 @@ import {
getLibraryCollectionApiUrl,
getBlockTypesMetaDataUrl,
getLibraryContainerApiUrl,
getLibraryContainerChildrenApiUrl,
} from './api';
import {
useCommitLibraryChanges,
@@ -23,6 +24,7 @@ import {
useCollection,
useBlockTypesMetadata,
useContainer,
useContainerChildren,
} from './apiHooks';
let axiosMock;
@@ -152,4 +154,79 @@ describe('library api hooks', () => {
expect(result.current.data).toEqual({ testData: 'test-value' });
expect(axiosMock.history.get[0].url).toEqual(url);
});
it('should get container children', async () => {
const containerId = 'lct:lib:org:unit:unit1';
const url = getLibraryContainerChildrenApiUrl(containerId);
axiosMock.onGet(url).reply(200, [
{
id: 'lb:org1:Demo_course:html:text',
block_type: 'html',
def_key: 'def_key',
display_name: 'text block',
last_published: null,
published_by: null,
last_draft_created: null,
last_draft_created_by: null,
has_unpublished_changes: false,
created: null,
modified: null,
tags_count: 0,
collections: ['col1', 'col2'],
},
{
id: 'lb:org1:Demo_course:video:video1',
block_type: 'video',
def_key: 'def_key',
display_name: 'video block',
last_published: null,
published_by: null,
last_draft_created: null,
last_draft_created_by: null,
has_unpublished_changes: false,
created: null,
modified: null,
tags_count: 0,
collections: ['col2'],
},
]);
const { result } = renderHook(() => useContainerChildren(containerId), { wrapper });
await waitFor(() => {
expect(result.current.isLoading).toBeFalsy();
});
expect(result.current.data).toEqual([
{
id: 'lb:org1:Demo_course:html:text',
blockType: 'html',
defKey: 'def_key',
displayName: 'text block',
lastPublished: null,
publishedBy: null,
lastDraftCreated: null,
lastDraftCreatedBy: null,
hasUnpublishedChanges: false,
created: null,
modified: null,
tagsCount: 0,
collections: ['col1', 'col2'],
},
{
id: 'lb:org1:Demo_course:video:video1',
blockType: 'video',
defKey: 'def_key',
displayName: 'video block',
lastPublished: null,
publishedBy: null,
lastDraftCreated: null,
lastDraftCreatedBy: null,
hasUnpublishedChanges: false,
created: null,
modified: null,
tagsCount: 0,
collections: ['col2'],
},
]);
expect(axiosMock.history.get[0].url).toEqual(url);
});
});

View File

@@ -51,6 +51,7 @@ import {
getContainerMetadata,
updateContainerMetadata,
type UpdateContainerDataRequest,
getContainerChildren,
} from './api';
import { VersionSpec } from '../LibraryBlock';
@@ -95,6 +96,11 @@ export const libraryAuthoringQueryKeys = {
'blockTypes',
libraryId,
],
container: (libraryId?: string, containerId?: string) => [
...libraryAuthoringQueryKeys.all,
libraryId,
containerId,
],
};
export const xblockQueryKeys = {
@@ -114,11 +120,12 @@ export const xblockQueryKeys = {
};
export const containerQueryKeys = {
all: ['container'],
all: ['container', 'children'],
/**
* Base key for data specific to a container
*/
container: (usageKey?: string) => [...containerQueryKeys.all, usageKey],
children: (usageKey?: string) => [...containerQueryKeys.all, usageKey, 'children'],
};
/**
@@ -613,3 +620,14 @@ export const useUpdateContainer = (containerId: string) => {
},
});
};
/**
* Get the metadata and children for a container in a library
*/
export const useContainerChildren = (containerId: string) => (
useQuery({
enabled: !!containerId,
queryKey: containerQueryKeys.children(containerId),
queryFn: () => getContainerChildren(containerId!),
})
);