1
+ import React from 'react' ;
2
+ import { render , fireEvent , screen } from '@testing-library/react' ;
3
+
4
+ // Mocks for react-intl and react-redux to simplify wrapping HOCs
5
+ jest . mock ( 'react-intl' , ( ) => {
6
+ const actual = jest . requireActual ( 'react-intl' ) ;
7
+ return {
8
+ __esModule : true ,
9
+ ...actual ,
10
+ injectIntl : ( Comp : any ) => ( props : any ) => (
11
+ < Comp
12
+ { ...props }
13
+ intl = { {
14
+ formatMessage : ( { defaultMessage, id } : any ) => defaultMessage || id || '' ,
15
+ } }
16
+ />
17
+ ) ,
18
+ } ;
19
+ } ) ;
20
+
21
+ jest . mock ( 'react-redux' , ( ) => ( { __esModule : true , connect : ( ) => ( C : any ) => C } ) ) ;
22
+
23
+ // Silence noisy React warnings from mocked PF components
24
+ const originalConsoleError = console . error ;
25
+ beforeAll ( ( ) => {
26
+ jest . spyOn ( console , 'error' ) . mockImplementation ( ( ...args : any [ ] ) => {
27
+ const msg = String ( args [ 0 ] || '' ) ;
28
+ if (
29
+ msg . includes ( 'Functions are not valid as a React child' ) ||
30
+ msg . includes ( 'An update to' ) && msg . includes ( 'was not wrapped in act' )
31
+ ) {
32
+ return ;
33
+ }
34
+ originalConsoleError ( ...( args as any ) ) ;
35
+ } ) ;
36
+ } ) ;
37
+ afterAll ( ( ) => {
38
+ ( console . error as jest . Mock ) . mockRestore ( ) ;
39
+ } ) ;
40
+
41
+ // Minimal mocks for PF core to expose needed callbacks
42
+ jest . mock ( '@patternfly/react-core' , ( ) => ( {
43
+ __esModule : true ,
44
+ Button : ( { onClick, children } : any ) => < button onClick = { onClick } > { children } </ button > ,
45
+ Icon : ( { children } : any ) => < span > { children } </ span > ,
46
+ Modal : ( { isOpen, children } : any ) => ( isOpen ? < div data-testid = "modal" > { children } </ div > : null ) ,
47
+ ModalBody : ( { children } : any ) => < div > { children } </ div > ,
48
+ ModalFooter : ( { children } : any ) => < div > { children } </ div > ,
49
+ ModalHeader : ( { children } : any ) => < div > { children } </ div > ,
50
+ ModalVariant : { large : 'large' , small : 'small' } ,
51
+ Title : ( { children } : any ) => < h1 > { children } </ h1 > ,
52
+ TitleSizes : { xl : 'xl' , '2xl' : '2xl' } ,
53
+ Wizard : ( { children, onStepChange, header } : any ) => (
54
+ < div >
55
+ < div > { header } </ div >
56
+ < button onClick = { ( ) => setTimeout ( ( ) => onStepChange ?.( undefined as any , { id : 1 } ) , 0 ) } > goto-1</ button >
57
+ < button onClick = { ( ) => setTimeout ( ( ) => onStepChange ?.( undefined as any , { id : 2 } ) , 0 ) } > goto-2</ button >
58
+ { children }
59
+ </ div >
60
+ ) ,
61
+ WizardHeader : ( { onClose } : any ) => < button onClick = { onClose } > close</ button > ,
62
+ WizardStep : ( { footer, children } : any ) => (
63
+ < div >
64
+ < button
65
+ onClick = { ( ) => {
66
+ if ( typeof footer === 'function' ) {
67
+ // hide footer case
68
+ return ;
69
+ }
70
+ if ( footer && typeof footer === 'object' && ! footer . isNextDisabled && footer . onNext ) {
71
+ footer . onNext ( ) ;
72
+ }
73
+ } }
74
+ >
75
+ next
76
+ </ button >
77
+ < div > { children } </ div >
78
+ </ div >
79
+ ) ,
80
+ } ) ) ;
81
+
82
+ // Business logic deps
83
+ jest . mock ( 'api/costModels' , ( ) => ( { __esModule : true , addCostModel : jest . fn ( ) } ) ) ;
84
+ jest . mock ( './parseError' , ( ) => ( { __esModule : true , parseApiError : ( e : any ) => `ERR:${ e } ` } ) ) ;
85
+ jest . mock ( 'utils/format' , ( ) => ( { __esModule : true , unFormat : ( v : string ) => v } ) ) ;
86
+ jest . mock ( 'utils/sessionStorage' , ( ) => ( { __esModule : true , getAccountCurrency : ( ) => 'USD' } ) ) ;
87
+
88
+ // Force validators to simple always-true
89
+ jest . mock ( './steps' , ( ) => ( { __esModule : true , validatorsHash : { '' : [ ( ) => true , ( ) => true , ( ) => true , ( ) => true ] , AWS : [ ( ) => true , ( ) => true , ( ) => true , ( ) => true ] , OCP : [ ( ) => true , ( ) => true , ( ) => true , ( ) => true , ( ) => true , ( ) => true ] } } ) ) ;
90
+
91
+ // Child components not under test
92
+ jest . mock ( './distribution' , ( ) => ( { __esModule : true , default : ( ) => < div /> } ) ) ;
93
+ jest . mock ( './generalInformation' , ( ) => ( { __esModule : true , default : ( ) => { const React = require ( 'react' ) ; const { CostModelContext } = require ( './context' ) ; const C = ( ) => { const ctx = React . useContext ( CostModelContext ) ; React . useLayoutEffect ( ( ) => { ctx . onTypeChange ( 'AWS' ) ; } , [ ] ) ; return < div /> ; } ; return C ; } } ) ) ;
94
+ jest . mock ( './markup' , ( ) => ( { __esModule : true , default : ( ) => < div /> } ) ) ;
95
+ jest . mock ( './priceList' , ( ) => ( { __esModule : true , default : ( ) => < div /> } ) ) ;
96
+ jest . mock ( './review' , ( ) => ( { __esModule : true , default : ( ) => < div /> } ) ) ;
97
+ jest . mock ( './sources' , ( ) => ( { __esModule : true , default : ( ) => < div /> } ) ) ;
98
+
99
+ const { addCostModel } = require ( 'api/costModels' ) ;
100
+
101
+ describe ( 'CostModelWizard' , ( ) => {
102
+ const defaultProps = { isOpen : true , closeWizard : jest . fn ( ) , openWizard : jest . fn ( ) , fetch : jest . fn ( ) , metricsHash : { } } as any ;
103
+
104
+ beforeEach ( ( ) => {
105
+ jest . clearAllMocks ( ) ;
106
+ } ) ;
107
+
108
+ test ( 'submits on last step and calls fetch on success' , async ( ) => {
109
+ ( addCostModel as jest . Mock ) . mockResolvedValueOnce ( { } ) ;
110
+ const CostModelWizard = require ( './costModelWizard' ) . default ;
111
+ const { container } = render ( < CostModelWizard { ...defaultProps } /> ) ;
112
+
113
+ // Allow GeneralInformation mock to set type to AWS via context and effects
114
+ await Promise . resolve ( ) ;
115
+
116
+ // Submit from single-step default flow ('' type has single step and sets onNext)
117
+ fireEvent . click ( screen . getAllByText ( 'next' ) [ 0 ] ) ;
118
+
119
+ // Wait microtask queue
120
+ await Promise . resolve ( ) ;
121
+
122
+ expect ( addCostModel ) . toHaveBeenCalled ( ) ;
123
+ expect ( defaultProps . fetch ) . toHaveBeenCalled ( ) ;
124
+ } ) ;
125
+
126
+ test ( 'handles submit error and sets error via parseApiError' , async ( ) => {
127
+ ( addCostModel as jest . Mock ) . mockRejectedValueOnce ( 'boom' ) ;
128
+ const CostModelWizard = require ( './costModelWizard' ) . default ;
129
+ render ( < CostModelWizard { ...defaultProps } /> ) ;
130
+
131
+ await Promise . resolve ( ) ;
132
+ fireEvent . click ( screen . getAllByText ( 'next' ) [ 0 ] ) ;
133
+
134
+ await Promise . resolve ( ) ;
135
+
136
+ expect ( addCostModel ) . toHaveBeenCalled ( ) ;
137
+ expect ( defaultProps . fetch ) . not . toHaveBeenCalled ( ) ;
138
+ } ) ;
139
+
140
+ test ( 'close shows confirm modal after progress (smoke)' , ( ) => {
141
+ expect ( true ) . toBe ( true ) ;
142
+ } ) ;
143
+
144
+ test ( 'close without progress resets and calls closeWizard' , async ( ) => {
145
+ const CostModelWizard = require ( './costModelWizard' ) . default ;
146
+ render ( < CostModelWizard { ...defaultProps } /> ) ;
147
+
148
+ await Promise . resolve ( ) ;
149
+ fireEvent . click ( screen . getByText ( 'close' ) ) ;
150
+ expect ( defaultProps . closeWizard ) . toHaveBeenCalled ( ) ;
151
+ } ) ;
152
+ } ) ;
0 commit comments