Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/course-unit/__mocks__/courseSectionVertical.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ module.exports = {
tab: 'common',
support_level: true,
},
{
display_name: 'In Video Quiz',
category: 'invideoquiz',
boilerplate_name: null,
hinted: false,
tab: 'common',
support_level: true,
},
],
display_name: 'Advanced',
support_legend: {
Expand Down
32 changes: 32 additions & 0 deletions src/course-unit/add-component/AddComponent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,38 @@ describe('<AddComponent />', () => {
});
});

it('calls handleCreateNewCourseXBlock with callback that opens editor when InVideoQuiz is selected from Advanced modal', async () => {
handleCreateNewCourseXBlockMock.mockImplementation((_params, callback) => {
if (callback) {
callback({ courseKey: 'course-v1:test', locator: 'block-v1:test+invideoquiz' });
}
});

const user = userEvent.setup();
const { getByRole } = renderComponent();
Comment on lines +394 to +401
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test overrides handleCreateNewCourseXBlockMock with mockImplementation and invokes the callback, but never restores the original behavior. Since the same mock is reused across tests (and the suite doesn’t clear mocks in beforeEach), this implementation can leak into later tests and trigger unexpected editor rendering/state updates. Use mockImplementationOnce (or reset/clear the mock in beforeEach/afterEach) and avoid calling the callback unless the test asserts the resulting UI/state.

Suggested change
handleCreateNewCourseXBlockMock.mockImplementation((_params, callback) => {
if (callback) {
callback({ courseKey: 'course-v1:test', locator: 'block-v1:test+invideoquiz' });
}
});
const user = userEvent.setup();
const { getByRole } = renderComponent();
handleCreateNewCourseXBlockMock.mockImplementationOnce((_params, _callback) => {
// Intentionally left blank: this test only asserts that a callback is passed,
// it does not depend on invoking the callback or its side effects.
});
const user = userEvent.setup();
const { getByRole } = renderComponent();
const { getByRole } = renderComponent();

Copilot uses AI. Check for mistakes.
const advancedButton = getByRole('button', {
name: new RegExp(`${messages.buttonText.defaultMessage} Advanced`, 'i'),
});

await user.click(advancedButton);
const modalContainer = getByRole('dialog');

const radioInput = within(modalContainer).getByRole('radio', { name: 'In Video Quiz' });
const sendBtn = within(modalContainer).getByRole('button', { name: messages.modalBtnText.defaultMessage });

expect(sendBtn).toBeDisabled();
await user.click(radioInput);
expect(sendBtn).not.toBeDisabled();

await user.click(sendBtn);

expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
parentLocator: '123',
type: COMPONENT_TYPES.invideoquiz,
category: COMPONENT_TYPES.invideoquiz,
}, expect.any(Function));
});

it('verifies "Text" component creation and submission in modal', async () => {
const user = userEvent.setup();
const { getByRole } = renderComponent();
Expand Down
14 changes: 13 additions & 1 deletion src/course-unit/add-component/AddComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,19 @@ const AddComponent = ({
showAddLibraryContentModal();
break;
case COMPONENT_TYPES.advanced:
handleCreateNewCourseXBlock({ type: moduleName, category: moduleName, parentLocator: blockId });
if (moduleName === COMPONENT_TYPES.invideoquiz) {
handleCreateNewCourseXBlock(
{ type: moduleName, category: moduleName, parentLocator: blockId },
({ courseKey, locator }) => {
setCourseId(courseKey);
setBlockType(moduleName);
setNewBlockId(locator);
showXBlockEditorModal();
},
);
} else {
handleCreateNewCourseXBlock({ type: moduleName, category: moduleName, parentLocator: blockId });
}
break;
case COMPONENT_TYPES.openassessment:
handleCreateNewCourseXBlock({ boilerplate: moduleName, category: type, parentLocator: blockId });
Expand Down
1 change: 1 addition & 0 deletions src/generic/block-type-utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const COMPONENT_TYPES = {
video: 'video',
dragAndDrop: 'drag-and-drop-v2',
games: 'games',
invideoquiz: 'invideoquiz',
lti: 'lti_consumer',
scorm: 'scorm',
h5p: 'h5pxblock',
Expand Down