@@ -353,4 +353,158 @@ describe('SaleTunnel', () => {
353353
354354 screen . getByText ( 'Subscription confirmed!' ) ;
355355 } , 10000 ) ;
356+
357+ it ( 'should display the appropriate error message when there are not enough seats available' , async ( ) => {
358+ const course = PacedCourseFactory ( ) . one ( ) ;
359+ const product = ProductFactory ( ) . one ( ) ;
360+ const offering = OfferingFactory ( { course, product, is_withdrawable : false } ) . one ( ) ;
361+ const paymentPlan = PaymentPlanFactory ( ) . one ( ) ;
362+ const offeringOrganization = OfferingBatchOrderFactory ( {
363+ product : { id : product . id , title : product . title } ,
364+ } ) . one ( ) ;
365+
366+ fetchMock . get (
367+ `https://joanie.endpoint/api/v1.0/courses/${ course . code } /products/${ product . id } /` ,
368+ offering ,
369+ ) ;
370+ fetchMock . get (
371+ `https://joanie.endpoint/api/v1.0/courses/${ course . code } /products/${ product . id } /payment-plan/` ,
372+ paymentPlan ,
373+ ) ;
374+ fetchMock . get ( `https://joanie.endpoint/api/v1.0/enrollments/` , [ ] ) ;
375+ fetchMock . get (
376+ `https://joanie.endpoint/api/v1.0/orders/?${ queryString . stringify ( {
377+ product_id : product . id ,
378+ course_code : course . code ,
379+ state : NOT_CANCELED_ORDER_STATES ,
380+ } ) } `,
381+ [ ] ,
382+ ) ;
383+ fetchMock . get (
384+ `https://joanie.endpoint/api/v1.0/offerings/${ offering . id } /get-organizations/` ,
385+ offeringOrganization ,
386+ ) ;
387+
388+ render ( < CourseProductItem productId = { product . id } course = { course } /> , {
389+ queryOptions : { client : createTestQueryClient ( { user : richieUser } ) } ,
390+ } ) ;
391+
392+ // Verify product info
393+ await screen . findByRole ( 'heading' , { level : 3 , name : product . title } ) ;
394+ await screen . findByText ( formatPrice ( product . price_currency , product . price ) ) ;
395+ expect ( screen . queryByText ( 'Purchased' ) ) . not . toBeInTheDocument ( ) ;
396+
397+ const user = userEvent . setup ( ) ;
398+ const buyButton = screen . getByRole ( 'button' , { name : product . call_to_action } ) ;
399+
400+ expect ( screen . queryByTestId ( 'generic-sale-tunnel-payment-step' ) ) . not . toBeInTheDocument ( ) ;
401+ await user . click ( buyButton ) ;
402+ await screen . findByTestId ( 'generic-sale-tunnel-payment-step' ) ;
403+
404+ // Verify learning path
405+ await screen . findByText ( 'Your learning path' ) ;
406+ const targetCourses = await screen . findAllByTestId ( 'product-target-course' ) ;
407+ expect ( targetCourses ) . toHaveLength ( product . target_courses . length ) ;
408+ targetCourses . forEach ( ( targetCourse , index ) => {
409+ const courseItem = product . target_courses [ index ] ;
410+ const courseDetail = within ( targetCourse ) . getByTestId (
411+ `target-course-detail-${ courseItem . code } ` ,
412+ ) ;
413+ const summary = courseDetail . querySelector ( 'summary' ) ! ;
414+ expect ( summary ) . toHaveTextContent ( courseItem . title ) ;
415+
416+ const courseRuns = targetCourse . querySelectorAll (
417+ '.product-detail-row__course-run-dates__item' ,
418+ ) ;
419+ const openedCourseRuns = courseItem . course_runs . filter (
420+ ( cr : CourseRun ) => cr . state . priority <= Priority . FUTURE_NOT_YET_OPEN ,
421+ ) ;
422+ expect ( courseRuns ) . toHaveLength ( openedCourseRuns . length ) ;
423+ } ) ;
424+
425+ // Select group buy form
426+ await screen . findByText ( 'Purchase type' ) ;
427+ const formTypeSelect = screen . getByRole ( 'combobox' , { name : 'Purchase type' } ) ;
428+ const menu : HTMLDivElement = screen . getByRole ( 'listbox' , { name : 'Purchase type' } ) ;
429+ expectMenuToBeClosed ( menu ) ;
430+ await user . click ( formTypeSelect ) ;
431+ expectMenuToBeOpen ( menu ) ;
432+ await user . click ( screen . getByText ( 'Group purchase (B2B)' ) ) ;
433+
434+ // Company step
435+ const $companyName = await screen . findByRole ( 'textbox' , { name : 'Company name' } ) ;
436+ const $idNumber = screen . getByRole ( 'textbox' , { name : / I d e n t i f i c a t i o n n u m b e r / } ) ;
437+ const $address = screen . getByRole ( 'textbox' , { name : 'Address' } ) ;
438+ const $postCode = screen . getByRole ( 'textbox' , { name : 'Post code' } ) ;
439+ const $city = screen . getByRole ( 'textbox' , { name : 'City' } ) ;
440+ const $country = screen . getByRole ( 'combobox' , { name : 'Country' } ) ;
441+
442+ await user . type ( $companyName , 'GIP-FUN' ) ;
443+ await user . type ( $idNumber , '789 242 229 01694' ) ;
444+ await user . type ( $address , '61 Bis Rue de la Glaciere' ) ;
445+ await user . type ( $postCode , '75013' ) ;
446+ await user . type ( $city , 'Paris' ) ;
447+
448+ const countryMenu : HTMLDivElement = screen . getByRole ( 'listbox' , { name : 'Country' } ) ;
449+ await user . click ( $country ) ;
450+ expectMenuToBeOpen ( countryMenu ) ;
451+ await user . click ( screen . getByText ( 'France' ) ) ;
452+
453+ expect ( $companyName ) . toHaveValue ( 'GIP-FUN' ) ;
454+ const visibleValue = $country . querySelector ( '.c__select__inner__value span' ) ;
455+ expect ( visibleValue ! . textContent ) . toBe ( 'France' ) ;
456+
457+ // Follow-up step
458+ await user . click ( screen . getByRole ( 'button' , { name : 'Next' } ) ) ;
459+ const $lastName = await screen . findByRole ( 'textbox' , { name : 'Last name' } ) ;
460+ const $firstName = screen . getByRole ( 'textbox' , { name : 'First name' } ) ;
461+ const $role = screen . getByRole ( 'textbox' , { name : 'Role' } ) ;
462+ const $email = screen . getByRole ( 'textbox' , { name : 'Email' } ) ;
463+ const $phone = screen . getByRole ( 'textbox' , { name : 'Phone' } ) ;
464+
465+ await user . type ( $lastName , 'Doe' ) ;
466+ await user . type ( $firstName , 'John' ) ;
467+ await user . type ( $role , 'HR' ) ;
468+ await user . type ( $email , '[email protected] ' ) ; 469+ await user . type ( $phone , '+338203920103' ) ;
470+
471+ expect ( $lastName ) . toHaveValue ( 'Doe' ) ;
472+ expect ( $email ) . toHaveValue ( '[email protected] ' ) ; 473+
474+ // Signatory step
475+ await user . click ( screen . getByRole ( 'button' , { name : 'Next' } ) ) ;
476+ const $signatoryLastName = await screen . findByRole ( 'textbox' , { name : 'Last name' } ) ;
477+ const $signatoryFirstName = screen . getByRole ( 'textbox' , { name : 'First name' } ) ;
478+ const $signatoryRole = screen . getByRole ( 'textbox' , { name : 'Role' } ) ;
479+ const $signatoryEmail = screen . getByRole ( 'textbox' , { name : 'Email' } ) ;
480+ const $signatoryPhone = screen . getByRole ( 'textbox' , { name : 'Phone' } ) ;
481+
482+ await user . type ( $signatoryLastName , 'Doe' ) ;
483+ await user . type ( $signatoryFirstName , 'John' ) ;
484+ await user . type ( $signatoryRole , 'CEO' ) ;
485+ await user . type ( $signatoryEmail , '[email protected] ' ) ; 486+ await user . type ( $signatoryPhone , '+338203920103' ) ;
487+
488+ // Participants step
489+ await user . click ( screen . getByRole ( 'button' , { name : 'Next' } ) ) ;
490+ const $nbParticipants = await screen . findByLabelText ( 'How many participants ?' ) ;
491+ await user . type ( $nbParticipants , '13' ) ;
492+ expect ( $nbParticipants ) . toHaveValue ( 13 ) ;
493+
494+ fetchMock . post ( 'https://joanie.endpoint/api/v1.0/batch-orders/' , {
495+ status : 422 ,
496+ body : {
497+ __all__ : [ 'Maximum number of orders reached for product Credential Product' ] ,
498+ } ,
499+ } ) ;
500+
501+ const $subscribeButton = screen . getByRole ( 'button' , {
502+ name : `Subscribe` ,
503+ } ) as HTMLButtonElement ;
504+ await user . click ( $subscribeButton ) ;
505+
506+ await screen . findByText (
507+ 'Unable to create the order: the maximum number of available seats for this offering has been reached. Please contact support for more information.' ,
508+ ) ;
509+ } , 30000 ) ;
356510} ) ;
0 commit comments