1818 */
1919
2020import { Location } from "@angular/common" ;
21- import { NO_ERRORS_SCHEMA } from "@angular/core" ;
21+ import { CUSTOM_ELEMENTS_SCHEMA , NO_ERRORS_SCHEMA } from "@angular/core" ;
2222import { ComponentFixture , TestBed } from "@angular/core/testing" ;
2323import { HttpClientTestingModule } from "@angular/common/http/testing" ;
2424import { ActivatedRoute , Router } from "@angular/router" ;
@@ -130,12 +130,12 @@ describe("WorkspaceComponent", () => {
130130 routerMock = { navigate : vi . fn ( ) } ;
131131 locationMock = { go : vi . fn ( ) } ;
132132
133- // TODO(#5015): drop this template override once CodeEditorComponent's
134- // own spec is fixed. Real child rendering would let us assert
135- // editor-lifecycle wiring; today we stub the host element so the
136- // heavyweight children don't compile in the test build.
133+ // Drop the standalone component's child imports and allow unknown elements via
134+ // CUSTOM_ELEMENTS_SCHEMA. The template still renders, so `<ng-template #codeEditor>`
135+ // is wired up and the @ViewChild query resolves to a real ViewContainerRef, while
136+ // the children's transitive dependencies stay out of the test build.
137137 TestBed . overrideComponent ( WorkspaceComponent , {
138- set : { template : '<div #codeEditor class="stub-host"></div>' , imports : [ ] , providers : [ ] } ,
138+ set : { imports : [ ] , providers : [ ] , schemas : [ CUSTOM_ELEMENTS_SCHEMA ] } ,
139139 } ) ;
140140
141141 await TestBed . configureTestingModule ( {
@@ -168,6 +168,8 @@ describe("WorkspaceComponent", () => {
168168 // ngOnDestroy clears the ViewContainerRef bound to `#codeEditor`. Tests that
169169 // exercise individual methods skip change detection, so the @ViewChild query
170170 // is never resolved; assign a stub to keep TestBed teardown from throwing.
171+ // Tests that exercise `fixture.detectChanges()` will overwrite this with
172+ // the live ViewContainerRef during ngAfterViewInit.
171173 component . codeEditorViewRef = { clear : vi . fn ( ) } as any ;
172174 }
173175
@@ -205,7 +207,12 @@ describe("WorkspaceComponent", () => {
205207 // retrieveWorkflow is consumed inside loadWorkflowWithId — keep it pending so
206208 // we can observe the pre-completion loading state.
207209 workflowPersistService . retrieveWorkflow . mockReturnValue ( new Subject ( ) ) ;
208- fixture . detectChanges ( ) ;
210+ // Drive the lifecycle hooks directly. Going through fixture.detectChanges()
211+ // would re-render `[nzSpinning]="isLoading"` mid-cycle (isLoading flips from
212+ // false to true inside ngAfterViewInit) and Angular's dev-mode stability
213+ // check would throw NG0100.
214+ component . ngOnInit ( ) ;
215+ component . ngAfterViewInit ( ) ;
209216 expect ( component . isLoading ) . toBe ( true ) ;
210217 expect ( workflowActionService . disableWorkflowModification ) . toHaveBeenCalled ( ) ;
211218 } ) ;
@@ -337,13 +344,15 @@ describe("WorkspaceComponent", () => {
337344 describe ( "ngOnDestroy" , ( ) => {
338345 it ( "persists the workflow on destroy when the user is signed in and persist is enabled" , async ( ) => {
339346 await createFixture ( ) ;
347+ fixture . detectChanges ( ) ;
340348 component . ngOnDestroy ( ) ;
341349 expect ( workflowPersistService . persistWorkflow ) . toHaveBeenCalledWith ( stubWorkflow ) ;
342350 expect ( workflowActionService . clearWorkflow ) . toHaveBeenCalled ( ) ;
343351 } ) ;
344352
345353 it ( "skips the persist call when the user is not signed in" , async ( ) => {
346354 await createFixture ( ) ;
355+ fixture . detectChanges ( ) ;
347356 userService . isLogin . mockReturnValue ( false ) ;
348357 component . ngOnDestroy ( ) ;
349358 expect ( workflowPersistService . persistWorkflow ) . not . toHaveBeenCalled ( ) ;
@@ -359,4 +368,20 @@ describe("WorkspaceComponent", () => {
359368 expect ( component . copilotEnabled ) . toBe ( false ) ;
360369 } ) ;
361370 } ) ;
371+
372+ // Exercises the rendered template: the `<ng-template #codeEditor>` outlet is
373+ // present, so the @ViewChild query resolves to a live ViewContainerRef and
374+ // ngAfterViewInit can publish it to CodeEditorService.
375+ describe ( "child rendering side effects" , ( ) => {
376+ it ( "publishes the resolved ViewContainerRef to CodeEditorService.vc on view init" , async ( ) => {
377+ codeEditorService . vc = undefined ;
378+ await createFixture ( ) ;
379+ fixture . detectChanges ( ) ;
380+ // createEmbeddedView is present on a real ViewContainerRef but not on the
381+ // pre-fixture stub, so checking it distinguishes the resolved query from
382+ // the placeholder.
383+ expect ( codeEditorService . vc ) . toBe ( component . codeEditorViewRef ) ;
384+ expect ( typeof codeEditorService . vc . createEmbeddedView ) . toBe ( "function" ) ;
385+ } ) ;
386+ } ) ;
362387} ) ;
0 commit comments