1- import React from 'react'
2- import { render , screen , fireEvent , waitFor } from '@testing-library/react'
3- import { MemoryRouter } from 'react-router-dom'
1+ import { render , screen , fireEvent , act , waitFor } from '@testing-library/react'
2+ import { BrowserRouter } from 'react-router-dom'
43import { AppStateContext } from '../../state/AppProvider'
54import Draft from './Draft'
5+ import { Section } from '../../api/models'
66import { saveAs } from 'file-saver'
7+ import { defaultMockState } from '../../test/test.utils' ;
8+ import { MemoryRouter } from 'react-router-dom' ;
9+
10+ import { Document , Packer , Paragraph , TextRun } from 'docx'
711
8- // Mock the SectionCard component
9- jest . mock ( '../../components/DraftCards/SectionCard' , ( ) => ( ) => (
10- < div data-testid = "mock-section-card" > Mock Section Card</ div >
11- ) )
1212
13+ // Mocks for third-party components and modules
1314jest . mock ( 'file-saver' , ( ) => ( {
1415 saveAs : jest . fn ( ) ,
16+ } ) )
17+
18+ jest . mock ( '../../components/DraftCards/TitleCard' , ( ) => ( { children } : { children : React . ReactNode } ) => (
19+ < div data-testid = "title-card" >
20+ Title Card
21+ { children }
22+ </ div >
23+ ) )
24+
25+ jest . mock ( '../../components/DraftCards/SectionCard' , ( ) => ( {
26+ __esModule : true ,
27+ default : ( { sectionIdx } : { sectionIdx : number } ) => (
28+ < div data-testid = { `section-card-${ sectionIdx } ` } > Section { sectionIdx } </ div >
29+ ) ,
30+ } ) )
31+
32+ // Mock CommandBarButton from Fluent UI
33+ jest . mock ( '@fluentui/react' , ( ) => ( {
34+ ...jest . requireActual ( '@fluentui/react' ) ,
35+ CommandBarButton : ( { onClick, disabled, text, ariaLabel, iconProps } : any ) => (
36+ < button
37+ aria-label = { ariaLabel }
38+ onClick = { onClick }
39+ disabled = { disabled }
40+ data-testid = "command-bar-button" >
41+ { iconProps ?. iconName && < span > { iconProps . iconName } </ span > }
42+ { text }
43+ </ button >
44+ ) ,
1545} ) ) ;
1646
17- // jest.mock('../../components/DraftCards/TitleCard', () => () => <div data-testid="mock-title-card">Mock Title Card</div>)
47+ jest . mock ( 'react-router-dom' , ( ) => ( {
48+ ...jest . requireActual ( 'react-router-dom' ) ,
49+ useNavigate : jest . fn ( ) ,
50+ } ) )
51+
52+ jest . mock ( 'docx' , ( ) => ( {
53+ Document : jest . fn ( ) . mockImplementation ( ( options ) => {
54+ return {
55+ sections : options . sections || [ ] ,
56+ }
57+ } ) ,
58+ Packer : {
59+ toBlob : jest . fn ( ) . mockResolvedValue ( new Blob ( ) ) , // Mock the toBlob method
60+ } ,
61+ Paragraph : jest . fn ( ) ,
62+ TextRun : jest . fn ( ) ,
63+ } ) )
64+
1865const mockAppState = {
19- state : {
20- draftedDocument : {
21- sections : [
22- { title : 'Section 1' , content : 'Content of section 1.' } ,
23- { title : 'Section 2' , content : 'Content of section 2.' }
24- ]
25- } ,
26- draftedDocumentTitle : 'Sample Draft'
66+ draftedDocument : {
67+ sections : [
68+ { title : 'Section 1' , content : 'Content of section 1.' } ,
69+ { title : 'Section 2' , content : 'Content of section 2.' }
70+ ]
2771 } ,
28- dispatch : jest . fn ( )
72+ isLoadedSections : [ ] ,
73+ draftedDocumentTitle : 'Sample Draft'
2974}
3075
76+
3177const renderComponent = ( appState : any ) => {
3278 return render (
3379 < MemoryRouter >
34- < AppStateContext . Provider value = { appState } >
80+ < AppStateContext . Provider value = { { state : appState , dispatch : jest . fn ( ) } } >
3581 < Draft />
3682 </ AppStateContext . Provider >
3783 </ MemoryRouter >
@@ -41,139 +87,183 @@ const renderComponent = (appState: any) => {
4187describe ( 'Draft Component' , ( ) => {
4288 beforeEach ( ( ) => {
4389 jest . clearAllMocks ( )
90+
4491 } )
4592
46- // it('renders title card and section cards', () => {
47- // renderComponent(mockAppState)
4893
49- // expect(screen.getByTestId('mock-title-card')).toBeInTheDocument()
50- // expect(screen.getByText('Section 1')).toBeInTheDocument()
51- // expect(screen.getByText('Content of section 1.')).toBeInTheDocument()
52- // expect(screen.getByText('Section 2')).toBeInTheDocument()
53- // expect(screen.getByText('Content of section 2.')).toBeInTheDocument()
54- // })
94+ test ( 'renders TitleCard and SectionCards correctly' , async ( ) => {
95+ renderComponent ( mockAppState )
5596
56- it ( 'redirects to home page if draftedDocument is empty' , ( ) => {
57- const appStateWithEmptyDraft = {
58- ...mockAppState ,
59- state : {
60- ...mockAppState . state ,
61- draftedDocument : null
62- }
63- }
97+ expect ( screen . getByTestId ( 'title-card' ) ) . toBeInTheDocument ( )
6498
65- renderComponent ( appStateWithEmptyDraft )
66- expect ( window . location . pathname ) . toBe ( '/' )
67- } )
99+ await waitFor ( ( ) => {
100+ expect ( screen . getByTestId ( 'section-card-0' ) ) . toBeInTheDocument ( )
101+ expect ( screen . getByTestId ( 'section-card-1' ) ) . toBeInTheDocument ( )
102+ } )
68103
69- it ( 'sanitizes title correctly' , ( ) => {
70- const sanitizedTitle = mockAppState . state . draftedDocumentTitle . replace ( / [ ^ a - z A - Z 0 - 9 ] / g, '' )
71- expect ( sanitizedTitle ) . toBe ( 'SampleDraft' )
72- } )
73-
74- it ( 'exports document when export button is clicked' , async ( ) => {
75- const { saveAs } = require ( 'file-saver' ) ;
76104
77- renderComponent ( mockAppState )
105+ } )
78106
79- fireEvent . click ( screen . getByRole ( 'button' , { name : / E x p o r t D o c u m e n t / i } ) )
107+ test ( 'disables the export button when sections are not loaded' , async ( ) => {
108+ const mockStateWithIncompleteLoad = {
109+ ...mockAppState ,
110+ isLoadedSections : [ { title : 'Section 1' , content : 'Content of section 1' } ] , // One section not loaded
111+ }
112+ renderComponent ( mockStateWithIncompleteLoad )
80113
81114 await waitFor ( ( ) => {
82- saveAs ( new Blob ( [ 'test content' ] ) , 'DraftTemplate-SampleDraft.docx' ) ;
83- expect ( saveAs ) . toHaveBeenCalledWith ( expect . any ( Blob ) , 'DraftTemplate-SampleDraft.docx' )
115+ const exportButton = screen . getByTestId ( 'command-bar-button' )
116+ expect ( exportButton ) . toBeDisabled ( )
84117 } )
118+
85119 } )
86-
87120
88- test ( 'renders empty string when draftedDocumentTitle is an empty string' , ( ) => {
89- const appStateWithEmptyTitle = {
121+ test ( 'enabled the export button when sections are loaded' , async ( ) => {
122+ const mockStateWithIncompleteLoad = {
90123 ...mockAppState ,
91- state : {
92- ...mockAppState . state ,
93- draftedDocumentTitle : ''
94- }
124+ isLoadedSections : [ { title : 'Section 1' , content : 'Content of section 1' } ,
125+ { title : 'Section 2' , content : 'Content of section 2.' }
126+ ] ,
95127 }
128+ renderComponent ( mockStateWithIncompleteLoad )
129+ await waitFor ( ( ) => {
130+ const exportButton = screen . getByTestId ( 'command-bar-button' )
131+ expect ( exportButton ) . toBeEnabled ( )
132+ } )
96133
97- renderComponent ( appStateWithEmptyTitle )
98-
99- expect ( screen . getByDisplayValue ( '' ) ) . toBeInTheDocument ( )
100134 } )
101135
102- test ( 'renders empty string when draftedDocumentTitle is null' , ( ) => {
103- const appStateWithNullTitle = {
136+ test ( 'triggers exportToWord function when export button is clicked' , async ( ) => {
137+ const mockStateWithIncompleteLoad = {
104138 ...mockAppState ,
105- state : {
106- ...mockAppState . state ,
107- draftedDocumentTitle : null
108- }
139+ isLoadedSections : [ { title : 'Section 1' , content : 'Content of section 1' } ,
140+ { title : 'Section 2' , content : 'Content of section 2.' }
141+ ] ,
109142 }
143+ renderComponent ( mockStateWithIncompleteLoad )
110144
111- renderComponent ( appStateWithNullTitle )
145+ await waitFor ( async ( ) => {
112146
113- expect ( screen . getByDisplayValue ( '' ) ) . toBeInTheDocument ( )
114- } )
147+ const exportButton = screen . getByText ( / E x p o r t D o c u m e n t / i)
115148
116- test ( 'returns draftedDocumentTitle when it is a valid string' , ( ) => {
117- renderComponent ( mockAppState )
149+ await act ( async ( ) => {
150+ fireEvent . click ( exportButton )
151+ } )
118152
119- expect ( screen . getByDisplayValue ( 'Sample Draft' ) ) . toBeInTheDocument ( )
153+ expect ( saveAs ) . toHaveBeenCalled ( )
154+ } ) ;
120155 } )
121156
122- test ( 'does not crash when draftedDocument is null' , ( ) => {
123- const appStateWithNullDocument = {
157+ test ( 'does not render any SectionCard when sections array is empty' , async ( ) => {
158+ const mockStateWithIncompleteLoad = {
124159 ...mockAppState ,
125- state : {
126- ...mockAppState . state ,
127- draftedDocument : null
160+ draftedDocument : {
161+ sections : [ ]
128162 }
129163 }
164+ renderComponent ( mockStateWithIncompleteLoad )
165+
166+ await waitFor ( ( ) => {
167+ const sectionCards = screen . queryAllByTestId ( / ^ s e c t i o n - c a r d - / )
168+ expect ( sectionCards ) . toHaveLength ( 0 )
169+ } ) ;
170+ } )
130171
131- renderComponent ( appStateWithNullDocument )
172+ test ( 'does not render any SectionCard when sections array is null' , async ( ) => {
173+ const mockStateWithIncompleteLoad = {
174+ ...mockAppState ,
175+ draftedDocument : {
176+ sections : null
177+ }
178+ }
179+ renderComponent ( mockStateWithIncompleteLoad )
132180
133- expect ( screen . queryByText ( 'Section 1' ) ) . not . toBeInTheDocument ( )
134- expect ( screen . queryByText ( 'Section 2' ) ) . not . toBeInTheDocument ( )
181+ await waitFor ( ( ) => {
182+ const sectionCards = screen . queryAllByTestId ( / ^ s e c t i o n - c a r d - / )
183+ expect ( sectionCards ) . toHaveLength ( 0 )
184+ } ) ;
135185 } )
136186
137- test ( 'does not crash when appStateContext is undefined' , ( ) => {
138- const appStateWithUndefinedContext = {
139- state : { }
187+ test ( 'redirects to home page when draftedDocument is empty' , async ( ) => {
188+ const mockStateEmptyDoc = {
189+ ...mockAppState ,
190+ draftedDocument : null ,
191+ sections : [ ] ,
192+ isLoadedSections : [ ] ,
193+ draftedDocumentTitle : null ,
140194 }
141195
142- renderComponent ( appStateWithUndefinedContext )
196+ const mockNavigate = jest . fn ( )
197+ jest . spyOn ( require ( 'react-router-dom' ) , 'useNavigate' ) . mockReturnValue ( mockNavigate )
198+
199+ renderComponent ( mockStateEmptyDoc )
200+
201+ await waitFor ( ( ) => {
202+ expect ( mockNavigate ) . toHaveBeenCalledWith ( '/' )
203+ } )
143204
144- expect ( screen . getByDisplayValue ( '' ) ) . toBeInTheDocument ( )
145205 } )
146206
147- test ( 'does not render any SectionCard when sections array is empty' , ( ) => {
148- const appStateWithEmptySections = {
207+ test ( 'does not call saveAs if export button is disabled' , async ( ) => {
208+ const mockStateWithSectionsNotLoaded = {
149209 ...mockAppState ,
150- state : {
151- ...mockAppState . state ,
152- draftedDocument : {
153- sections : [ ]
154- }
155- }
210+ isLoadedSections : [ ] , // Sections are not loaded
156211 }
157212
158- renderComponent ( appStateWithEmptySections )
213+ renderComponent ( mockStateWithSectionsNotLoaded )
214+
215+ const exportButton = screen . getByText ( / E x p o r t D o c u m e n t / i)
159216
160- const sectionCards = screen . queryAllByTestId ( 'mock-section-card' )
161- expect ( sectionCards . length ) . toBe ( 0 )
217+ // Ensure the button is disabled and clicking it won't trigger export
218+ expect ( exportButton ) . toBeDisabled ( )
219+
220+ await act ( async ( ) => {
221+ fireEvent . click ( exportButton )
222+ } )
223+
224+ expect ( saveAs ) . not . toHaveBeenCalled ( )
162225 } )
163226
164- test ( 'renders SectionCard for each section in draftedDocument' , async ( ) => {
165- renderComponent ( mockAppState )
227+ test ( 'calls saveAs when exportToWord is triggered' , async ( ) => {
166228
167- await waitFor ( ( ) => {
168- const sectionCards = screen . getAllByTestId ( 'mock-section-card' ) ;
169- expect ( sectionCards . length ) . toBe ( mockAppState . state . draftedDocument . sections . length ) ;
170- } ) ;
229+ const mockStateWithIncompleteLoad = {
230+ ...mockAppState ,
231+ isLoadedSections : [ { title : 'Section 1' , content : 'Content of section 1' } ,
232+ { title : 'Section 2' , content : 'Content of section 2.' }
233+ ] , // One section not loaded
234+ }
235+ renderComponent ( mockStateWithIncompleteLoad )
236+
237+ await waitFor ( async ( ) => {
238+
239+ const exportButton = screen . getByText ( / E x p o r t D o c u m e n t / i)
240+
241+ fireEvent . click ( exportButton )
242+ //expect(Packer.toBlob).toHaveBeenCalledTimes(1)
243+ await waitFor ( ( ) => {
244+ expect ( saveAs ) . toHaveBeenCalled ( )
245+ } )
246+ } ) ;
171247 } )
172248
173- test ( 'getTitle function returns correct title when draftedDocumentTitle is valid' , ( ) => {
174- renderComponent ( mockAppState )
175- expect ( screen . getByDisplayValue ( 'Sample Draft' ) ) . toBeInTheDocument ( )
249+
250+ test ( 'generate document when draftedDocumentTitle is null' , async ( ) => {
251+ const mockStateWithIncompleteLoad = {
252+ ...mockAppState ,
253+ isLoadedSections : [ { title : 'Section 1' , content : 'Content of section 1' } ,
254+ { title : 'Section 2' , content : 'Content of section 2.' }
255+ ] ,
256+ draftedDocumentTitle : null
257+ }
258+ renderComponent ( mockStateWithIncompleteLoad )
259+ await waitFor ( async ( ) => {
260+ const exportButton = screen . getByText ( / E x p o r t D o c u m e n t / i)
261+ fireEvent . click ( exportButton )
262+
263+ await waitFor ( ( ) => {
264+ expect ( Document ) . toHaveBeenCalledTimes ( 1 )
265+ } )
266+ } ) ;
176267 } )
177268
178-
179269} )
0 commit comments