@@ -39,6 +39,17 @@ const problemTemplate: ComponentTemplate = {
3939 supportLegend : { } ,
4040} ;
4141
42+ const openAssessmentTemplate : ComponentTemplate = {
43+ type : 'openassessment' ,
44+ displayName : 'Open Response' ,
45+ templates : [
46+ { displayName : 'Peer Assessment Only' , category : 'openassessment' , boilerplateName : 'peer-assessment' } ,
47+ { displayName : 'Self Assessment Only' , category : 'openassessment' , boilerplateName : 'self-assessment' } ,
48+ { displayName : 'Staff Assessment Only' , category : 'openassessment' , boilerplateName : 'staff-assessment' } ,
49+ ] ,
50+ supportLegend : { } ,
51+ } ;
52+
4253const advancedTemplate : ComponentTemplate = {
4354 type : 'advanced' ,
4455 displayName : 'Advanced' ,
@@ -55,7 +66,7 @@ const unitId = 'block-v1:edX+Demo+2025+type@vertical+block@unit1';
5566const renderWidget = ( props ?: Partial < React . ComponentProps < typeof AddComponentWidget > > ) => render (
5667 < AddComponentWidget
5768 unitId = { unitId }
58- componentTemplates = { [ htmlTemplate , problemTemplate , advancedTemplate ] }
69+ componentTemplates = { [ htmlTemplate , problemTemplate , openAssessmentTemplate , advancedTemplate ] }
5970 { ...props }
6071 /> ,
6172) ;
@@ -147,62 +158,87 @@ describe('<AddComponentWidget />', () => {
147158 } ) ;
148159 } ) ;
149160
150- it ( 'opens template selection modal for multi-template types' , async ( ) => {
151- renderWidget ( ) ;
152- // Open dropdown and click Problem (multiple templates)
161+ it ( 'creates problem component directly without modal (skips template selection)' , async ( ) => {
162+ const onCreated = jest . fn ( ) ;
163+ renderWidget ( { onComponentCreated : onCreated } ) ;
164+
165+ // Open dropdown and click Problem – should create directly, no modal
153166 const toggle = screen . getByText ( messages . addComponentButton . defaultMessage ) ;
154167 await act ( async ( ) => fireEvent . click ( toggle ) ) ;
155168 await act ( async ( ) => fireEvent . click ( screen . getByTestId ( 'add-component-item-problem' ) ) ) ;
156169
157- // Modal should appear with radio options
158- expect ( screen . getByText ( 'Add problem component' ) ) . toBeInTheDocument ( ) ;
159- expect ( screen . getByText ( 'Blank Problem' ) ) . toBeInTheDocument ( ) ;
160- expect ( screen . getByText ( 'Multiple Choice' ) ) . toBeInTheDocument ( ) ;
161- expect ( screen . getByText ( 'Checkboxes' ) ) . toBeInTheDocument ( ) ;
170+ // Should call createXBlock with the first/default template
171+ await waitFor ( ( ) => {
172+ expect ( mockCreateXBlock ) . toHaveBeenCalledWith ( {
173+ parentLocator : unitId ,
174+ type : 'problem' ,
175+ category : 'problem' ,
176+ boilerplate : undefined ,
177+ } ) ;
178+ } ) ;
179+
180+ // Modal should NOT appear
181+ expect ( screen . queryByText ( 'Add problem component' ) ) . not . toBeInTheDocument ( ) ;
162182 } ) ;
163183
164- it ( 'creates component from modal after selecting a template ' , async ( ) => {
184+ it ( 'creates open response component directly with Peer Assessment Only default (skips modal) ' , async ( ) => {
165185 const onCreated = jest . fn ( ) ;
166186 renderWidget ( { onComponentCreated : onCreated } ) ;
167187
168- // Open dropdown → click Problem → modal opens
188+ // Open dropdown and click Open Response – should create directly, no modal
169189 const toggle = screen . getByText ( messages . addComponentButton . defaultMessage ) ;
170190 await act ( async ( ) => fireEvent . click ( toggle ) ) ;
171- await act ( async ( ) => fireEvent . click ( screen . getByTestId ( 'add-component-item-problem' ) ) ) ;
172-
173- // Select "Multiple Choice" radio button
174- const multipleChoiceRadio = screen . getByLabelText ( 'Multiple Choice' ) ;
175- await act ( async ( ) => fireEvent . click ( multipleChoiceRadio ) ) ;
191+ await act ( async ( ) => fireEvent . click ( screen . getByTestId ( 'add-component-item-openassessment' ) ) ) ;
176192
177- // Click "Select" button to submit
178- const selectButton = screen . getByText ( messages . templateModalSelect . defaultMessage ) ;
179- await act ( async ( ) => fireEvent . click ( selectButton ) ) ;
180-
181- // Should create with the selected boilerplate
193+ // Should call createXBlock with 'peer-assessment' boilerplate
182194 await waitFor ( ( ) => {
183195 expect ( mockCreateXBlock ) . toHaveBeenCalledWith ( {
184196 parentLocator : unitId ,
185- type : 'problem ' ,
186- category : 'problem ' ,
187- boilerplate : 'multiple_choice ' ,
197+ type : 'openassessment ' ,
198+ category : 'openassessment ' ,
199+ boilerplate : 'peer-assessment ' ,
188200 } ) ;
189201 } ) ;
202+
203+ // Modal should NOT appear
204+ expect ( screen . queryByText ( 'Add open response component' ) ) . not . toBeInTheDocument ( ) ;
190205 } ) ;
191206
192- it ( 'closes modal without creating when cancel is clicked' , async ( ) => {
193- renderWidget ( ) ;
194- // Open dropdown → click Problem → modal opens
207+ it ( 'creates html/text component directly with default Text template (skips modal)' , async ( ) => {
208+ const onCreated = jest . fn ( ) ;
209+ // Use multi-template html to prove modal is skipped even with multiple templates
210+ const htmlMultiTemplate : ComponentTemplate = {
211+ type : 'html' ,
212+ displayName : 'Text' ,
213+ templates : [
214+ { displayName : 'Text' , category : 'html' , boilerplateName : undefined } ,
215+ { displayName : 'Raw HTML' , category : 'html' , boilerplateName : 'raw.yaml' } ,
216+ { displayName : 'Zooming Image Tool' , category : 'html' , boilerplateName : 'zooming_image.yaml' } ,
217+ ] ,
218+ supportLegend : { } ,
219+ } ;
220+ renderWidget ( {
221+ componentTemplates : [ htmlMultiTemplate , problemTemplate , openAssessmentTemplate , advancedTemplate ] ,
222+ onComponentCreated : onCreated ,
223+ } ) ;
224+
225+ // Open dropdown and click Text – should create directly, no modal
195226 const toggle = screen . getByText ( messages . addComponentButton . defaultMessage ) ;
196227 await act ( async ( ) => fireEvent . click ( toggle ) ) ;
197- await act ( async ( ) => fireEvent . click ( screen . getByTestId ( 'add-component-item-problem ' ) ) ) ;
228+ await act ( async ( ) => fireEvent . click ( screen . getByTestId ( 'add-component-item-html ' ) ) ) ;
198229
199- // Click "Cancel" button
200- const cancelButton = screen . getByText ( messages . templateModalCancel . defaultMessage ) ;
201- await act ( async ( ) => fireEvent . click ( cancelButton ) ) ;
230+ // Should call createXBlock with default Text (no boilerplate)
231+ await waitFor ( ( ) => {
232+ expect ( mockCreateXBlock ) . toHaveBeenCalledWith ( {
233+ parentLocator : unitId ,
234+ type : 'html' ,
235+ category : 'html' ,
236+ boilerplate : undefined ,
237+ } ) ;
238+ } ) ;
202239
203- // Modal should close, createXBlock should not be called
204- expect ( screen . queryByText ( 'Add problem component' ) ) . not . toBeInTheDocument ( ) ;
205- expect ( mockCreateXBlock ) . not . toHaveBeenCalled ( ) ;
240+ // Modal should NOT appear
241+ expect ( screen . queryByText ( 'Add text component' ) ) . not . toBeInTheDocument ( ) ;
206242 } ) ;
207243
208244 it ( 'creates advanced component directly on click' , async ( ) => {
@@ -269,15 +305,28 @@ describe('<AddComponentWidget />', () => {
269305 expect ( screen . queryByTestId ( 'add-component-item-paste' ) ) . not . toBeInTheDocument ( ) ;
270306 } ) ;
271307
272- it ( 'disables the Select button in modal when no template is selected' , async ( ) => {
273- renderWidget ( ) ;
274- // Open dropdown → click Problem → modal opens
308+ it ( 'still opens modal for types not in directCreateDefaults (e.g. custom multi-template)' , async ( ) => {
309+ // Create a custom type with multiple templates that is NOT in directCreateDefaults
310+ const customTemplate : ComponentTemplate = {
311+ type : 'custom_type' ,
312+ displayName : 'Custom' ,
313+ templates : [
314+ { displayName : 'Option A' , category : 'custom_type' , boilerplateName : 'option_a' } ,
315+ { displayName : 'Option B' , category : 'custom_type' , boilerplateName : 'option_b' } ,
316+ ] ,
317+ supportLegend : { } ,
318+ } ;
319+ renderWidget ( {
320+ componentTemplates : [ customTemplate ] ,
321+ } ) ;
322+
275323 const toggle = screen . getByText ( messages . addComponentButton . defaultMessage ) ;
276324 await act ( async ( ) => fireEvent . click ( toggle ) ) ;
277- await act ( async ( ) => fireEvent . click ( screen . getByTestId ( 'add-component-item-problem ' ) ) ) ;
325+ await act ( async ( ) => fireEvent . click ( screen . getByTestId ( 'add-component-item-custom_type ' ) ) ) ;
278326
279- // Select button should be disabled initially (no radio selected)
280- const selectButton = screen . getByText ( messages . templateModalSelect . defaultMessage ) ;
281- expect ( selectButton ) . toBeDisabled ( ) ;
327+ // Modal should appear
328+ expect ( screen . getByText ( 'Add custom component' ) ) . toBeInTheDocument ( ) ;
329+ expect ( screen . getByText ( 'Option A' ) ) . toBeInTheDocument ( ) ;
330+ expect ( screen . getByText ( 'Option B' ) ) . toBeInTheDocument ( ) ;
282331 } ) ;
283332} ) ;
0 commit comments