1- import { async , ComponentFixture , TestBed } from '@angular/core/testing' ;
1+ import { ComponentFixture , TestBed , waitForAsync } from '@angular/core/testing' ;
2+ import * as JSGantt from 'jsgantt-improved' ;
23
34import { GanttEditorComponent } from './gantt.component' ;
45
5- describe ( 'GantteditorComponent ' , ( ) => {
6+ describe ( 'GanttEditorComponent ' , ( ) => {
67 let component : GanttEditorComponent ;
78 let fixture : ComponentFixture < GanttEditorComponent > ;
9+ let mockEditorInstance : any ;
810
9- beforeEach ( async ( ( ) => {
11+ function buildMockEditor ( overrides : any = { } ) : any {
12+ return {
13+ getDivId : jasmine . createSpy ( 'getDivId' ) . and . returnValue ( 'mock-div-id' ) ,
14+ setOptions : jasmine . createSpy ( 'setOptions' ) ,
15+ AddTaskItemObject : jasmine . createSpy ( 'AddTaskItemObject' ) ,
16+ Draw : jasmine . createSpy ( 'Draw' ) ,
17+ options : { } ,
18+ ...overrides
19+ } ;
20+ }
21+
22+ beforeEach ( waitForAsync ( ( ) => {
1023 TestBed . configureTestingModule ( {
11- declarations : [ GanttEditorComponent ]
12- } )
13- . compileComponents ( ) ;
24+ declarations : [ GanttEditorComponent ]
25+ } ) . compileComponents ( ) ;
1426 } ) ) ;
1527
1628 beforeEach ( ( ) => {
29+ mockEditorInstance = buildMockEditor ( ) ;
30+ spyOn ( JSGantt as any , 'GanttChart' ) . and . returnValue ( mockEditorInstance ) ;
31+
1732 fixture = TestBed . createComponent ( GanttEditorComponent ) ;
1833 component = fixture . componentInstance ;
1934 fixture . detectChanges ( ) ;
@@ -22,4 +37,142 @@ describe('GantteditorComponent', () => {
2237 it ( 'should be created' , ( ) => {
2338 expect ( component ) . toBeTruthy ( ) ;
2439 } ) ;
40+
41+ // ---------------------------------------------------------------------------
42+ // destroy()
43+ // ---------------------------------------------------------------------------
44+
45+ describe ( 'destroy()' , ( ) => {
46+ it ( 'should clear the container innerHTML when an editor exists' , ( ) => {
47+ const container = component . ganttEditorContainer . nativeElement ;
48+ container . innerHTML = '<div>stale chart content</div>' ;
49+
50+ component . destroy ( ) ;
51+
52+ expect ( container . innerHTML ) . toBe ( '' ) ;
53+ } ) ;
54+
55+ it ( 'should null out the editor reference when an editor exists' , ( ) => {
56+ expect ( component . getEditor ( ) ) . not . toBeNull ( ) ;
57+
58+ component . destroy ( ) ;
59+
60+ expect ( component . getEditor ( ) ) . toBeNull ( ) ;
61+ } ) ;
62+
63+ it ( 'should be a no-op (not throw) when editor is already null' , ( ) => {
64+ component . destroy ( ) ; // first call nulls the editor
65+ expect ( ( ) => component . destroy ( ) ) . not . toThrow ( ) ;
66+ } ) ;
67+
68+ it ( 'should not clear the container when editor is null' , ( ) => {
69+ const container = component . ganttEditorContainer . nativeElement ;
70+ container . innerHTML = '<div>existing content</div>' ;
71+ component . destroy ( ) ; // null the editor
72+
73+ component . destroy ( ) ; // second call — should leave the DOM untouched
74+ expect ( container . innerHTML ) . toBe ( '<div>existing content</div>' ) ;
75+ } ) ;
76+ } ) ;
77+
78+ // ---------------------------------------------------------------------------
79+ // data setter
80+ // ---------------------------------------------------------------------------
81+
82+ describe ( 'data setter' , ( ) => {
83+ it ( 'should store the supplied value' , ( ) => {
84+ const rows = [ { pID : 1 , pName : 'Task A' } ] ;
85+ component . data = rows ;
86+ // Access via getEditor flow: the data should have been passed to AddTaskItemObject
87+ expect ( mockEditorInstance . AddTaskItemObject ) . toHaveBeenCalled ( ) ;
88+ } ) ;
89+
90+ it ( 'should call destroy() then re-initialise when an editor already exists' , ( ) => {
91+ const destroySpy = spyOn ( component , 'destroy' ) . and . callThrough ( ) ;
92+ const initSpy = spyOn ( component , 'ngOnInit' ) . and . callThrough ( ) ;
93+
94+ component . data = [ { pID : 2 , pName : 'Task B' } ] ;
95+
96+ expect ( destroySpy ) . toHaveBeenCalledTimes ( 1 ) ;
97+ expect ( initSpy ) . toHaveBeenCalledTimes ( 1 ) ;
98+ } ) ;
99+
100+ it ( 'should still call ngOnInit when no prior editor exists but container is available' , ( ) => {
101+ // Reset so there is no editor
102+ component . destroy ( ) ;
103+ ( JSGantt as any ) . GanttChart . calls . reset ( ) ;
104+
105+ const initSpy = spyOn ( component , 'ngOnInit' ) . and . callThrough ( ) ;
106+ component . data = [ { pID : 3 , pName : 'Task C' } ] ;
107+
108+ expect ( initSpy ) . toHaveBeenCalledTimes ( 1 ) ;
109+ } ) ;
110+
111+ it ( 'should not call ngOnInit when ganttEditorContainer is not yet available' , ( ) => {
112+ // Simulate the case where the setter fires before the view is initialised
113+ ( component as any ) . ganttEditorContainer = undefined ;
114+ component . destroy ( ) ; // clear editor so we test the no-editor branch
115+
116+ const initSpy = spyOn ( component , 'ngOnInit' ) . and . callThrough ( ) ;
117+ component . data = [ { pID : 4 , pName : 'Task D' } ] ;
118+
119+ expect ( initSpy ) . not . toHaveBeenCalled ( ) ;
120+ } ) ;
121+
122+ it ( 'should create a fresh GanttChart instance on each data update' , ( ) => {
123+ const callsBefore = ( JSGantt as any ) . GanttChart . calls . count ( ) ;
124+ component . data = [ { pID : 5 , pName : 'Task E' } ] ;
125+ const callsAfter = ( JSGantt as any ) . GanttChart . calls . count ( ) ;
126+
127+ expect ( callsAfter ) . toBe ( callsBefore + 1 ) ;
128+ } ) ;
129+
130+ it ( 'should register each data row with AddTaskItemObject' , ( ) => {
131+ const rows = [
132+ { pID : 10 , pName : 'Task 1' } ,
133+ { pID : 11 , pName : 'Task 2' } ,
134+ { pID : 12 , pName : 'Task 3' }
135+ ] ;
136+
137+ component . data = rows ;
138+
139+ expect ( mockEditorInstance . AddTaskItemObject ) . toHaveBeenCalledTimes ( rows . length ) ;
140+ } ) ;
141+
142+ it ( 'should set pGantt on each row to the new editor instance' , ( ) => {
143+ const rows = [ { pID : 20 , pName : 'Task X' } ] ;
144+ component . data = rows ;
145+
146+ expect ( rows [ 0 ] . pGantt ) . toBe ( component . getEditor ( ) ) ;
147+ } ) ;
148+ } ) ;
149+
150+ // ---------------------------------------------------------------------------
151+ // destroy() + data setter interaction
152+ // ---------------------------------------------------------------------------
153+
154+ describe ( 'destroy() then data setter interaction' , ( ) => {
155+ it ( 'should leave the container clean before re-rendering' , ( ) => {
156+ const container = component . ganttEditorContainer . nativeElement ;
157+ // Simulate stale content from a previous render
158+ container . innerHTML = '<div id="stale">old chart</div>' ;
159+
160+ component . data = [ { pID : 30 , pName : 'Fresh Task' } ] ;
161+
162+ // After destroy clears innerHTML and GanttChart re-renders, the stale
163+ // content should be gone (GanttChart writes its own output)
164+ expect ( container . querySelector ( '#stale' ) ) . toBeNull ( ) ;
165+ } ) ;
166+
167+ it ( 'should expose the new editor via getEditor() after re-render' , ( ) => {
168+ const firstEditor = component . getEditor ( ) ;
169+ const newMock = buildMockEditor ( ) ;
170+ ( JSGantt as any ) . GanttChart . and . returnValue ( newMock ) ;
171+
172+ component . data = [ { pID : 40 , pName : 'New Task' } ] ;
173+
174+ expect ( component . getEditor ( ) ) . toBe ( newMock ) ;
175+ expect ( component . getEditor ( ) ) . not . toBe ( firstEditor ) ;
176+ } ) ;
177+ } ) ;
25178} ) ;
0 commit comments