@@ -8,6 +8,7 @@ import { Factory } from 'rosie';
88
99import { camelCaseObject , initializeMockApp , snakeCaseObject } from '@edx/frontend-platform' ;
1010import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth' ;
11+ import { logError } from '@edx/frontend-platform/logging' ;
1112import { AppProvider } from '@edx/frontend-platform/react' ;
1213
1314import { ContentActions } from '../../data/constants' ;
@@ -27,6 +28,11 @@ import ActionsDropdown from './ActionsDropdown';
2728import '../post-comments/data/__factories__' ;
2829import '../posts/data/__factories__' ;
2930
31+ jest . mock ( '@edx/frontend-platform/logging' , ( ) => ( {
32+ ...jest . requireActual ( '@edx/frontend-platform/logging' ) ,
33+ logError : jest . fn ( ) ,
34+ } ) ) ;
35+
3036let store ;
3137let axiosMock ;
3238const commentsApiUrl = getCommentsApiUrl ( ) ;
@@ -303,4 +309,148 @@ describe('ActionsDropdown', () => {
303309 } ) ;
304310 } ) ;
305311 } ) ;
312+
313+ it ( 'applies in-context-sidebar class when inContextSidebar is in URL' , async ( ) => {
314+ const originalLocation = window . location ;
315+ delete window . location ;
316+ window . location = { ...originalLocation , search : '?inContextSidebar=true' } ;
317+
318+ const discussionObject = buildTestContent ( ) . discussion ;
319+ await mockThreadAndComment ( discussionObject ) ;
320+
321+ renderComponent ( { ...camelCaseObject ( discussionObject ) } ) ;
322+
323+ const openButton = await findOpenActionsDropdownButton ( ) ;
324+ await act ( async ( ) => {
325+ fireEvent . click ( openButton ) ;
326+ } ) ;
327+
328+ const dropdown = screen . getByTestId ( 'actions-dropdown-modal-popup' ) . closest ( '.actions-dropdown' ) ;
329+ expect ( dropdown ) . toHaveClass ( 'in-context-sidebar' ) ;
330+
331+ window . location = originalLocation ;
332+ } ) ;
333+
334+ it ( 'does not apply in-context-sidebar class when inContextSidebar is not in URL' , async ( ) => {
335+ const originalLocation = window . location ;
336+ delete window . location ;
337+ window . location = { ...originalLocation , search : '' } ;
338+
339+ const discussionObject = buildTestContent ( ) . discussion ;
340+ await mockThreadAndComment ( discussionObject ) ;
341+
342+ renderComponent ( { ...camelCaseObject ( discussionObject ) } ) ;
343+
344+ const openButton = await findOpenActionsDropdownButton ( ) ;
345+ await act ( async ( ) => {
346+ fireEvent . click ( openButton ) ;
347+ } ) ;
348+
349+ const dropdown = screen . getByTestId ( 'actions-dropdown-modal-popup' ) . closest ( '.actions-dropdown' ) ;
350+ expect ( dropdown ) . not . toHaveClass ( 'in-context-sidebar' ) ;
351+
352+ window . location = originalLocation ;
353+ } ) ;
354+
355+ it ( 'handles SSR environment when window is undefined' , ( ) => {
356+ const testSSRLogic = ( ) => {
357+ if ( typeof window !== 'undefined' ) {
358+ return window . location . search . includes ( 'inContextSidebar' ) ;
359+ }
360+ return false ;
361+ } ;
362+
363+ const originalWindow = global . window ;
364+ const originalProcess = global . process ;
365+
366+ try {
367+ delete global . window ;
368+
369+ const result = testSSRLogic ( ) ;
370+ expect ( result ) . toBe ( false ) ;
371+
372+ global . window = originalWindow ;
373+ const resultWithWindow = testSSRLogic ( ) ;
374+ expect ( resultWithWindow ) . toBe ( false ) ;
375+ } finally {
376+ global . window = originalWindow ;
377+ global . process = originalProcess ;
378+ }
379+ } ) ;
380+
381+ it ( 'calls logError for unknown action' , async ( ) => {
382+ const discussionObject = buildTestContent ( ) . discussion ;
383+ await mockThreadAndComment ( discussionObject ) ;
384+
385+ logError . mockClear ( ) ;
386+
387+ renderComponent ( {
388+ ...discussionObject ,
389+ actionHandlers : {
390+ [ ContentActions . EDIT_CONTENT ] : jest . fn ( ) ,
391+ } ,
392+ } ) ;
393+
394+ const openButton = await findOpenActionsDropdownButton ( ) ;
395+ await act ( async ( ) => {
396+ fireEvent . click ( openButton ) ;
397+ } ) ;
398+
399+ const copyLinkButton = await screen . findByText ( 'Copy link' ) ;
400+ await act ( async ( ) => {
401+ fireEvent . click ( copyLinkButton ) ;
402+ } ) ;
403+
404+ expect ( logError ) . toHaveBeenCalledWith ( 'Unknown or unimplemented action copy_link' ) ;
405+ } ) ;
406+
407+ describe ( 'posting restrictions' , ( ) => {
408+ it ( 'removes edit action when posting is disabled' , async ( ) => {
409+ const discussionObject = buildTestContent ( {
410+ editable_fields : [ 'raw_body' ] ,
411+ } ) . discussion ;
412+
413+ await mockThreadAndComment ( discussionObject ) ;
414+
415+ axiosMock . onGet ( `${ getCourseConfigApiUrl ( ) } ${ courseId } /` )
416+ . reply ( 200 , { isPostingEnabled : false } ) ;
417+
418+ await executeThunk ( fetchCourseConfig ( courseId ) , store . dispatch , store . getState ) ;
419+
420+ renderComponent ( { ...discussionObject } ) ;
421+
422+ const openButton = await findOpenActionsDropdownButton ( ) ;
423+ await act ( async ( ) => {
424+ fireEvent . click ( openButton ) ;
425+ } ) ;
426+
427+ await waitFor ( ( ) => {
428+ expect ( screen . queryByText ( 'Edit' ) ) . not . toBeInTheDocument ( ) ;
429+ } ) ;
430+ } ) ;
431+
432+ it ( 'keeps edit action when posting is enabled' , async ( ) => {
433+ const discussionObject = buildTestContent ( {
434+ editable_fields : [ 'raw_body' ] ,
435+ } ) . discussion ;
436+
437+ await mockThreadAndComment ( discussionObject ) ;
438+
439+ axiosMock . onGet ( `${ getCourseConfigApiUrl ( ) } ${ courseId } /` )
440+ . reply ( 200 , { isPostingEnabled : true } ) ;
441+
442+ await executeThunk ( fetchCourseConfig ( courseId ) , store . dispatch , store . getState ) ;
443+
444+ renderComponent ( { ...discussionObject } ) ;
445+
446+ const openButton = await findOpenActionsDropdownButton ( ) ;
447+ await act ( async ( ) => {
448+ fireEvent . click ( openButton ) ;
449+ } ) ;
450+
451+ await waitFor ( ( ) => {
452+ expect ( screen . queryByText ( 'Edit' ) ) . toBeInTheDocument ( ) ;
453+ } ) ;
454+ } ) ;
455+ } ) ;
306456} ) ;
0 commit comments