scribe('markForCheck()', () => {
      @Component({
        selector: 'on-push-comp',
        template: '{{ value }}',
        changeDetection: ChangeDetectionStrategy.OnPush,
        standalone: false,
      })
      class OnPushComp implements DoCheck {
        value = 'one';

        doCheckCount = 0;

        constructor(public cdr: ChangeDetectorRef) {}

        ngDoCheck() {
          this.doCheckCount++;
        }
      }

      @Component({
        template: '{{ value }} - <on-push-comp></on-push-comp>',
        changeDetection: ChangeDetectionStrategy.OnPush,
        standalone: false,
      })
      class OnPushParent {
        @ViewChild(OnPushComp) comp!: OnPushComp;
        value = 'one';
      }

      it('should ensure OnPush components are checked', () => {
        TestBed.configureTestingModule({declarations: [OnPushParent, OnPushComp]});
        const fixture = TestBed.createComponent(OnPushParent);
        fixture.detectChanges();

        expect(fixture.nativeElement.textContent).toEqual('one - one');

        fixture.componentInstance.comp.value = 'two';
        fixture.detectChanges();
        expect(fixture.nativeElement.textContent).toEqual('one - one');

        fixture.componentInstance.comp.cdr.markForCheck();

        // Change detection should not have run yet, since markForCheck
        // does not itself schedule change detection.
        expect(fixture.nativeElement.textContent).toEqual('one - one');

        fixture.detectChanges();
        expect(fixture.nativeElement.textContent).toEqual('one - two');
      });

      it('should never schedule change detection on its own', () => {
        TestBed.configureTestingModule({declarations: [OnPushParent, OnPushComp]});
        const fixture = TestBed.createComponent(OnPushParent);
        fixture.detectChanges();
        const comp = fixture.componentInstance.comp;

        expect(comp.doCheckCount).toEqual(1);

        comp.cdr.markForCheck();
        comp.cdr.markForCheck();

        expect(comp.doCheckCount).toEqual(1);
      });

      it('should ensure ancestor OnPush components are checked', () => {
        TestBed.configureTestingModule({declarations: [OnPushParent, OnPushComp]});
        const fixture = TestBed.createComponent(OnPushParent);
        fixture.detectChanges();

        expect(fixture.nativeElement.textContent).toEqual('one - one');

        fixture.componentInstance.value = 'two';
        fixture.detectChanges();
        expect(fixture.nativeElement.textContent).toEqual('one - one');

        fixture.componentInstance.comp.cdr.markForCheck();
        fixture.detectChanges();
        expect(fixture.nativeElement.textContent).toEqual('two - one');
      });

      it('should ensure OnPush components in embedded views are checked', () => {
        @Component({
          template: '{{ value }} - <on-push-comp *ngIf="showing"></on-push-comp>',
          changeDetection: ChangeDetectionStrategy.OnPush,
          standalone: false,
        })
        class EmbeddedViewParent {
          @ViewChild(OnPushComp) comp!: OnPushComp;
          value = 'one';
          showing = true;
        }

        TestBed.configureTestingModule({
          declarations: [EmbeddedViewParent, OnPushComp],
          imports: [CommonModule],
        });
        const fixture = TestBed.createComponent(EmbeddedViewParent);
        fixture.detectChanges();

        expect(fixture.nativeElement.textContent).toEqual('one - one');

        fixture.componentInstance.comp.value = 'two';
        fixture.detectChanges();
        expect(fixture.nativeElement.textContent).toEqual('one - one');

        fixture.componentInstance.comp.cdr.markForCheck();
        // markForCheck should not trigger change detection on its own.
        expect(fixture.nativeElement.textContent).toEqual('one - one');

        fixture.detectChanges();
        expect(fixture.nativeElement.textContent).toEqual('one - two');

        fixture.componentInstance.value = 'two';
        fixture.detectChanges();
        expect(fixture.nativeElement.textContent).toEqual('one - two');

        fixture.componentInstance.comp.cdr.markForCheck();
        fixture.detectChanges();
        expect(fixture.nativeElement.textContent).toEqual('two - two');
      });

      it('async pipe should trigger CD for embedded views where the declaration and insertion views are different', () => {
        @Component({
          selector: 'insertion',
          changeDetection: ChangeDetectionStrategy.OnPush,
          template: ` <ng-container [ngTemplateOutlet]="template"> </ng-container> `,
          standalone: false,
        })
        class Insertion {
          @Input() template!: TemplateRef<{}>;
        }

        // This component uses async pipe (which calls markForCheck) in a view that has different
        // insertion and declaration views.
        @Component({
          changeDetection: ChangeDetectionStrategy.OnPush,
          template: `
          <insertion [template]="ref"></insertion>
          <ng-template #ref>
            {{value | async}}
          </ng-template>
          `,
          standalone: false,
        })
        class Declaration {
          value = new BehaviorSubject('initial value');
        }

        const fixture = TestBed.configureTestingModule({
          declarations: [Insertion, Declaration],
        }).createComponent(Declaration);
        fixture.detectChanges();
        expect(fixture.debugElement.nativeElement.textContent).toContain('initial value');
        fixture.componentInstance.value.next('new value');
        fixture.detectChanges();
        expect(fixture.debugElement.nativeElement.textContent).toContain('new value');
      });

      // TODO(kara): add test for dynamic views once bug fix is in
    });

    describe('checkNoChanges', () => {
      let comp: NoChangesComp;

      @Component({
        selector: 'no-changes-comp',
        template: '{{ value }}',
        standalone: false,
      })
      class NoChangesComp {
        value = 1;
        doCheckCount = 0;
        contentCheckCount = 0;
        viewCheckCount = 0;

        ngDoCheck() {
          this.doCheckCount++;
        }

        ngAfterContentChecked() {
          this.contentCheckCount++;
        }

        ngAfterViewChecked() {
          this.viewCheckCount++;
        }

        constructor(public cdr: ChangeDetectorRef) {
          comp = this;
        }
      }

      @Component({
        template: '{{ value }} - <no-changes-comp></no-changes-comp>',
        standalone: false,
      })
      class AppComp {
        value = 1;

        constructor(public cdr: ChangeDetectorRef) {}
      }

      // Custom error handler that just rethrows all the errors from the
      // view, rather than logging them out. Used to keep our logs clean.
      class RethrowErrorHandler extends ErrorHandler {
        override handleError(error: any) {
          throw error;
        }
      }

      it('should throw if bindings in current view have changed', () => {
        TestBed.configureTestingModule({
          declarations: [NoChangesComp],
          providers: [{provide: ErrorHandler, useClass: RethrowErrorHandler}],
        });
        const fixture = TestBed.createComponent(NoChangesComp);

        expect(() => {
          fixture.componentInstance.cdr.checkNoChanges();
        }).toThrowError(
          /ExpressionChangedAfterItHasBeenCheckedError: .+ Previous value: '.*undefined'. Current value: '.*1'/gi,
        );
      });

      it('should throw if interpolations in current view have changed', () => {
        TestBed.configureTestingModule({
          declarations: [AppComp, NoChangesComp],
          providers: [{provide: ErrorHandler, useClass: RethrowErrorHandler}],
        });
        const fixture = TestBed.createComponent(AppComp);

        expect(() => fixture.componentInstance.cdr.checkNoChanges()).toThrowError(
          /ExpressionChangedAfterItHasBeenCheckedError: .+ Previous value: '.*undefined'. Current value: '.*1'/gi,
        );
      });

      it('should throw if bindings in embedded view have changed', () => {
        @Component({
          template: '{{ showing }}',
          standalone: false,
        })
        class EmbeddedViewApp {
          showing = true;
          constructor(public cdr: ChangeDetectorRef) {}
        }

        TestBed.configureTestingModule({
          declarations: [EmbeddedViewApp],
          imports: [CommonModule],
          providers: [{provide: ErrorHandler, useClass: RethrowErrorHandler}],
        });
        const fixture = TestBed.createComponent(EmbeddedViewApp);

        expect(() => fixture.componentInstance.cdr.checkNoChanges()).toThrowError(
          /ExpressionChangedAfterItHasBeenCheckedError: .+ Previous value: '.*undefined'. Current value: '.*true'/gi,
        );
      });

      it('should NOT call lifecycle hooks', () => {
        TestBed.configureTestingModule({
          declarations: [AppComp, NoChangesComp],
          providers: [{provide: ErrorHandler, useClass: RethrowErrorHandler}],
        });

        const fixture = TestBed.createComponent(AppComp);
        fixture.detectChanges();

        expect(comp.doCheckCount).toEqual(1);
        expect(comp.contentCheckCount).toEqual(1);
        expect(comp.viewCheckCount).toEqual(1);

        comp.value = 2;
        expect(() => fixture.componentInstance.cdr.checkNoChanges()).toThrow();
        expect(comp.doCheckCount).toEqual(1);
        expect(comp.contentCheckCount).toEqual(1);
        expect(comp.viewCheckCount).toEqual(1);
      });


intersection observer once all deferred blocks have been loaded', fakeAsync(() => {
      @Component({
        standalone: true,
        template: `
            <button #triggerOne></button>
            @defer (on viewport(triggerOne)) {
              One
            }

            <button #triggerTwo></button>
            @defer (on viewport(triggerTwo)) {
              Two
            }
          `,
      })
      class MyCmp {}

      const fixture = TestBed.createComponent(MyCmp);
      fixture.detectChanges();
      expect(activeObservers.length).toBe(1);

      const buttons = Array.from<HTMLElement>(fixture.nativeElement.querySelectorAll('button'));
      const observer = activeObservers[0];
      const disconnectSpy = spyOn(observer, 'disconnect').and.callThrough();

      expect(Array.from(observer.observedElements)).toEqual(buttons);

      MockIntersectionObserver.invokeCallbacksForElement(buttons[0], true);
      fixture.detectChanges();

      expect(disconnectSpy).not.toHaveBeenCalled();
      expect(Array.from(observer.observedElements)).toEqual([buttons[1]]);

      MockIntersectionObserver.invokeCallbacksForElement(buttons[1], true);
      fixture.detectChanges();

      expect(disconnectSpy).toHaveBeenCalled();
      expect(observer.observedElements.size).toBe(0);
    }));

    it('should prefetch resources when the trigger comes into the viewport', fakeAsync(() => {
      @Component({
        standalone: true,
        selector: 'root-app',
        template: `
             @defer (when isLoaded; prefetch on viewport(trigger)) {
              Main content
             }
             <button #trigger></button>
           `,
      })
      class MyCmp {
        // We need a `when` trigger here so that `on idle` doesn't get added automatically.
        readonly isLoaded = false;
      }

      let loadingFnInvokedTimes = 0;

      TestBed.configureTestingModule({
        providers: [
          {
            provide: ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR,
            useValue: {
              intercept: () => () => {
                loadingFnInvokedTimes++;
                return [];
              },
            },
          },
        ],
      });

      clearDirectiveDefs(MyCmp);

      const fixture = TestBed.createComponent(MyCmp);
      fixture.detectChanges();

      expect(loadingFnInvokedTimes).toBe(0);

      const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
      MockIntersectionObserver.invokeCallbacksForElement(button, true);
      fixture.detectChanges();
      flush();

      expect(loadingFnInvokedTimes).toBe(1);
    }));

    it('should prefetch resources when an implicit trigger comes into the viewport', fakeAsync(() => {
      @Component({
        standalone: true,
        selector: 'root-app',
        template: `
             @defer (when isLoaded; prefetch on viewport) {
              Main content
             } @placeholder {
               <button></button>
             }
           `,
      })
      class MyCmp {
        // We need a `when` trigger here so that `on idle` doesn't get added automatically.
        readonly isLoaded = false;
      }

      let loadingFnInvokedTimes = 0;

      TestBed.configureTestingModule({
        providers: [
          {
            provide: ɵDEFER_BLOCK_DEPENDENCY_INTERCEPTOR,
            useValue: {
              intercept: () => () => {
                loadingFnInvokedTimes++;
                return [];
              },
            },
          },
        ],
      });

      clearDirectiveDefs(MyCmp);

      const fixture = TestBed.createComponent(MyCmp);
      fixture.detectChanges();

      expect(loadingFnInvokedTimes).toBe(0);

      const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
      MockIntersectionObserver.invokeCallbacksForElement(button, true);
      fixture.detectChanges();
      flush();

      expect(loadingFnInvokedTimes).toBe(1);
    }));

    it('should load deferred content in a loop', fakeAsync(() => {
      @Component({
        standalone: true,
        template: `
              @for (item of items; track item) {
                @defer (on viewport) {d{{item}} }
                @placeholder {<button>p{{item}} </button>}
              }
           `,
      })
      class MyCmp {
        items = [1, 2, 3, 4, 5, 6];
      }

      const fixture = TestBed.createComponent(MyCmp);
      fixture.detectChanges();
      const buttons = Array.from<Element>(fixture.nativeElement.querySelectorAll('button'));
      const items = fixture.componentInstance.items;

      // None of the blocks are loaded yet.
      expect(fixture.nativeElement.textContent.trim()).toBe('p1 p2 p3 p4 p5 p6');

      // First half of the blocks is loaded.
      for (let i = 0; i < items.length / 2; i++) {
        MockIntersectionObserver.invokeCallbacksForElement(buttons[i], true);
        fixture.detectChanges();
        flush();
      }
      expect(fixture.nativeElement.textContent.trim()).toBe('d1 d2 d3 p4 p5 p6');

      // Second half of the blocks is loaded.
      for (let i = items.length / 2; i < items.length; i++) {
        MockIntersectionObserver.invokeCallbacksForElement(buttons[i], true);
        fixture.detectChanges();
        flush();
      }
      expect(fixture.nativeElement.textContent.trim()).toBe('d1 d2 d3 d4 d5 d6');
    }));
  });

  describe('DOM-based events cleanup', () => {
    it('should unbind `interaction` trigger events when the deferred block is loaded', async () => {
      @Component({
        standalone: true,
        template: `
          @defer (
            when isVisible;
            on interaction(trigger);
            prefetch on interaction(prefetchTrigger)
          ) { Main content }
          <button #trigger></button>

        `,
      })
      class MyCmp {
        isVisible = false;
      }

      const fixture = TestBed.createComponent(MyCmp);
      fixture.detectChanges();

      const button = fixture.nativeElement.querySelector('button');
      const triggerSpy = spyOn(button, 'removeEventListener');
      const div = fixture.nativeElement.querySelector('div');
      const prefetchSpy = spyOn(div, 'removeEventListener');

      fixture.componentInstance.isVisible = true;
      fixture.detectChanges();

      await allPendingDynamicImports();
      fixture.detectChanges();

      // Verify that trigger element is cleaned up.
      expect(triggerSpy).toHaveBeenCalledTimes(2);
      expect(triggerSpy).toHaveBeenCalledWith('click', jasmine.any(Function), jasmine.any(Object));
      expect(triggerSpy).toHaveBeenCalledWith(
        'keydown',
        jasmine.any(Function),
        jasmine.any(Object),
      );

      // Verify that prefetch trigger element is cleaned up.
      expect(prefetchSpy).toHaveBeenCalledTimes(2);
      expect(prefetchSpy).toHaveBeenCalledWith('click', jasmine.any(Function), jasmine.any(Object));
      expect(prefetchSpy).toHaveBeenCalledWith(
        'keydown',
        jasmine.any(Function),
        jasmine.any(Object),
      );
    });

    it('should unbind `hover` trigger events when the deferred block is loaded', async () => {
      @Component({
        standalone: true,
        template: `
          @defer (
            when isVisible;
            on hover(trigger);
            prefetch on hover(prefetchTrigger)
          ) { Main content }
          <button #trigger></button>

        `,
      })
      class MyCmp {
        isVisible = false;
      }

      const fixture = TestBed.createComponent(MyCmp);
      fixture.detectChanges();

      const button = fixture.nativeElement.querySelector('button');
      const triggerSpy = spyOn(button, 'removeEventListener');
      const div = fixture.nativeElement.querySelector('div');
      const prefetchSpy = spyOn(div, 'removeEventListener');

      fixture.componentInstance.isVisible = true;
      fixture.detectChanges();

      await allPendingDynamicImports();
      fixture.detectChanges();

      // Verify that trigger element is cleaned up.
      expect(triggerSpy).toHaveBeenCalledTimes(3);
      expect(triggerSpy).toHaveBeenCalledWith(
        'mouseenter',
        jasmine.any(Function),
        jasmine.any(Object),
      );
      expect(triggerSpy).toHaveBeenCalledWith(
        'mouseover',
        jasmine.any(Function),
        jasmine.any(Object),
      );
      expect(triggerSpy).toHaveBeenCalledWith(
        'focusin',
        jasmine.any(Function),
        jasmine.any(Object),
      );

      // Verify that prefetch trigger element is cleaned up.
      expect(prefetchSpy).toHaveBeenCalledTimes(3);
      expect(prefetchSpy).toHaveBeenCalledWith(
        'mouseenter',
        jasmine.any(Function),
        jasmine.any(Object),
      );
      expect(prefetchSpy).toHaveBeenCalledWith(
        'mouseover',
        jasmine.any(Function),
        jasmine.any(Object),
      );
      expect(prefetchSpy).toHaveBeenCalledWith(
        'focusin',
        jasmine.any(Function),
        jasmine.any(Object),
      );
    });
  });

  describe('DI', () => {


('should create the root node in the correct namespace when previous node is SVG', () => {
      @Component({
        template: `

Some random content

          <svg></svg>
        `,
        standalone: false,
      })
      class TestComp {
        constructor(public viewContainerRef: ViewContainerRef) {}
      }

      @Component({
        selector: 'dynamic-comp',
        template: '',
        standalone: false,
      })
      class DynamicComponent {}

      TestBed.configureTestingModule({declarations: [DynamicComponent]});
      const fixture = TestBed.createComponent(TestComp);

      // Note: it's important that we **don't** call `fixture.detectChanges` between here and
      // the component being created, because running change detection will reset Ivy's
      // namespace state which will make the test pass.

      const componentRef =
        fixture.componentInstance.viewContainerRef.createComponent(DynamicComponent);
      const element = componentRef.location.nativeElement;
      expect((element.namespaceURI || '').toLowerCase()).not.toContain('svg');
      componentRef.destroy();
    });

    it('should be compatible with componentRef generated via TestBed.createComponent in component factory', () => {
      @Component({
        selector: 'child',
        template: `Child Component`,
        standalone: false,
      })
      class Child {}

      @Component({
        selector: 'comp',
        template: '<ng-template #ref></ng-template>',
        standalone: false,
      })
      class Comp {
        @ViewChild('ref', {read: ViewContainerRef, static: true})
        viewContainerRef!: ViewContainerRef;

        ngOnInit() {
          const makeComponentFactory = (componentType: any) => ({
            create: () => TestBed.createComponent(componentType).componentRef,
          });
          this.viewContainerRef.createComponent(makeComponentFactory(Child) as any);
        }
      }

      TestBed.configureTestingModule({declarations: [Comp, Child]});

      const fixture = TestBed.createComponent(Comp);
      fixture.detectChanges();

      expect(fixture.debugElement.nativeElement.innerHTML).toContain('Child Component');
    });

    it('should return ComponentRef with ChangeDetectorRef attached to root view', () => {
      @Component({
        selector: 'dynamic-cmp',
        template: ``,
        standalone: false,
      })
      class DynamicCmp {
        doCheckCount = 0;

        ngDoCheck() {
          this.doCheckCount++;
        }
      }

      @Component({
        template: ``,
        standalone: false,
      })
      class TestCmp {
        constructor(public viewContainerRef: ViewContainerRef) {}
      }

      const fixture = TestBed.createComponent(TestCmp);
      const testCmpInstance = fixture.componentInstance;
      const dynamicCmpRef = testCmpInstance.viewContainerRef.createComponent(DynamicCmp);

      // change detection didn't run at all
      expect(dynamicCmpRef.instance.doCheckCount).toBe(0);

      // running change detection on the dynamicCmpRef level
      dynamicCmpRef.changeDetectorRef.detectChanges();
      expect(dynamicCmpRef.instance.doCheckCount).toBe(1);

      // running change detection on the TestBed fixture level
      fixture.changeDetectorRef.detectChanges();
      expect(dynamicCmpRef.instance.doCheckCount).toBe(2);

      // The injector should retrieve the change detector ref for DynamicComp. As such,
      // the doCheck hook for DynamicComp should NOT run upon ref.detectChanges().
      const changeDetector = dynamicCmpRef.injector.get(ChangeDetectorRef);
      changeDetector.detectChanges();
      expect(dynamicCmpRef.instance.doCheckCount).toBe(2);
    });

    describe('createComponent using Type', () => {
      const TOKEN_A = new InjectionToken('A');
      const TOKEN_B = new InjectionToken('B');

      @Component({
        selector: 'child-a',
        template: `[Child Component A]`,
        standalone: false,
      })
      class ChildA {}

      @Component({
        selector: 'child-b',
        template: `
          [Child Component B]
          <ng-content></ng-content>
          {{ tokenA }}
          {{ tokenB }}
        `,
        standalone: false,
      })
      class ChildB {
        constructor(
          private injector: Injector,
          public renderer: Renderer2,
        ) {}
        get tokenA() {
          return this.injector.get(TOKEN_A);
        }
        get tokenB() {
          return this.injector.get(TOKEN_B);
        }
      }

      @Component({
        selector: 'app',
        template: '',
        providers: [{provide: TOKEN_B, useValue: '[TokenB - Value]'}],
        standalone: false,
      })
      class App {
        constructor(
          public viewContainerRef: ViewContainerRef,
          public ngModuleRef: NgModuleRef<unknown>,
          public injector: Injector,
        ) {}
      }

      @NgModule({
        declarations: [App, ChildA, ChildB],
        providers: [{provide: TOKEN_A, useValue: '[TokenA - Value]'}],
      })
      class AppModule {}

      let fixture!: ComponentFixture<App>;
      beforeEach(() => {
        TestBed.configureTestingModule({imports: [AppModule]});
        fixture = TestBed.createComponent(App);
        fixture.detectChanges();
      });

      it('should be able to create a component when Type is provided', () => {
        fixture.componentInstance.viewContainerRef.createComponent(ChildA);
        expect(fixture.nativeElement.parentNode.textContent).toContain('[Child Component A]');
      });

      it('should maintain connection with module injector when custom injector is provided', () => {
        const comp = fixture.componentInstance;
        const environmentInjector = createEnvironmentInjector(
          [{provide: TOKEN_B, useValue: '[TokenB - CustomValue]'}],
          TestBed.inject(EnvironmentInjector),
        );

        // Use factory-less way of creating a component.
        comp.viewContainerRef.createComponent(ChildB, {injector: environmentInjector});
        fixture.detectChanges();

        // Custom injector provides only `TOKEN_B`,
        // so `TOKEN_A` should be retrieved from the module injector.
        expect(getElementText(fixture.nativeElement.parentNode)).toContain(
          '[TokenA - Value] [TokenB - CustomValue]',
        );

        // Use factory-based API to compare the output with the factory-less one.
        const factoryBasedChildB = createComponent(ChildB, {environmentInjector});
        fixture.detectChanges();

        // Custom injector provides only `TOKEN_B`,
        // so `TOKEN_A` should be retrieved from the module injector
        expect(getElementText(fixture.nativeElement.parentNode)).toContain(
          '[TokenA - Value] [TokenB - CustomValue]',
        );
      });

      it('should throw if class without @Component decorator is used as Component type', () => {
        class MyClassWithoutComponentDecorator {}
        const createComponent = () => {
          fixture.componentInstance.viewContainerRef.createComponent(
            MyClassWithoutComponentDecorator,
          );
        };
        expect(createComponent).toThrowError(
          /Provided Component class doesn't contain Component definition./,
        );
      });

      describe('`options` argument handling', () => {
        it('should work correctly when an empty object is provided', () => {
          fixture.componentInstance.viewContainerRef.createComponent(ChildA, {});
          expect(fixture.nativeElement.parentNode.textContent).toContain('[Child Component A]');
        });

        it('should take provided `options` arguments into account', () => {
          const {viewContainerRef, ngModuleRef, injector} = fixture.componentInstance;
          viewContainerRef.createComponent(ChildA);

          const projectableNode = document.createElement('div');
          const textNode = document.createTextNode('[Projectable Node]');
          projectableNode.appendChild(textNode);
          const projectableNodes = [[projectableNode]];

          // Insert ChildB in front of ChildA (since index = 0)
          viewContainerRef.createComponent(ChildB, {
            index: 0,
            injector,
            ngModuleRef,
            projectableNodes,
          });

          fixture.detectChanges();

          expect(getElementText(fixture.nativeElement.parentNode)).toContain(
            '[Child Component B] ' +
              '[Projectable Node] ' +
              '[TokenA - Value] ' +
              '[TokenB - Value] ' +
              '[Child Component A]',
          );
        });
      });
    });
  });


scribe('root view container ref', () => {
    let containerEl: HTMLElement | null = null;

    beforeEach(() => (containerEl = null));

    /**
     * Creates a new test component renderer instance that wraps the root element
     * in another element. This allows us to test if elements have been inserted into
     * the parent element of the root component.
     */
    function createTestComponentRenderer(document: any): TestComponentRenderer {
      return {
        insertRootElement(rootElementId: string) {
          const rootEl = document.createElement('div');
          rootEl.id = rootElementId;

          containerEl = document.createElement('div');
          document.body.appendChild(containerEl);
          containerEl!.appendChild(rootEl);
        },
        removeAllRootElements() {
          containerEl?.remove();
        },
      };
    }

    const TEST_COMPONENT_RENDERER = {
      provide: TestComponentRenderer,
      useFactory: createTestComponentRenderer,
      deps: [DOCUMENT],
    };

    it('should check bindings for components dynamically created by root component', () => {
      @Component({
        selector: 'dynamic-cmpt-with-bindings',
        template: `check count: {{checkCount}}`,
        standalone: false,
      })
      class DynamicCompWithBindings implements DoCheck {
        checkCount = 0;

        ngDoCheck() {
          this.checkCount++;
        }
      }

      @Component({
        template: ``,
        standalone: false,
      })
      class TestComp {
        constructor(public vcRef: ViewContainerRef) {}
      }

      TestBed.configureTestingModule({
        declarations: [TestComp, DynamicCompWithBindings],
        providers: [TEST_COMPONENT_RENDERER],
      });
      const fixture = TestBed.createComponent(TestComp);
      const {vcRef} = fixture.componentInstance;
      fixture.detectChanges();

      expect(containerEl!.childNodes.length).toBe(2);
      expect(containerEl!.childNodes[1].nodeType).toBe(Node.COMMENT_NODE);

      expect((containerEl!.childNodes[0] as Element).tagName).toBe('DIV');

      vcRef.createComponent(DynamicCompWithBindings);
      fixture.detectChanges();

      expect(containerEl!.childNodes.length).toBe(3);
      expect(containerEl!.childNodes[1].textContent).toBe('check count: 1');

      fixture.detectChanges();

      expect(containerEl!.childNodes.length).toBe(3);
      expect(containerEl!.childNodes[1].textContent).toBe('check count: 2');
    });

    it('should create deep DOM tree immediately for dynamically created components', () => {
      @Component({
        template: ``,
        standalone: false,
      })
      class TestComp {
        constructor(public vcRef: ViewContainerRef) {}
      }

      @Component({
        selector: 'child',
        template: `<div>{{name}}</div>`,
        standalone: false,
      })
      class Child {
        name = 'text';
      }

      @Component({
        selector: 'dynamic-cmpt-with-children',
        template: `<child></child>`,
        standalone: false,
      })
      class DynamicCompWithChildren {}

      TestBed.configureTestingModule({
        declarations: [TestComp, DynamicCompWithChildren, Child],
        providers: [TEST_COMPONENT_RENDERER],
      });

      const fixture = TestBed.createComponent(TestComp);
      const {vcRef} = fixture.componentInstance;
      fixture.detectChanges();

      expect(containerEl!.childNodes.length).toBe(2);
      expect(containerEl!.childNodes[1].nodeType).toBe(Node.COMMENT_NODE);

      expect((containerEl!.childNodes[0] as Element).tagName).toBe('DIV');

      vcRef.createComponent(DynamicCompWithChildren);

      expect(containerEl!.childNodes.length).toBe(3);
      expect(getElementHtml(containerEl!.childNodes[1] as Element)).toBe(
        '<child>

</child>',
      );

      fixture.detectChanges();

      expect(containerEl!.childNodes.length).toBe(3);
      expect(getElementHtml(containerEl!.childNodes[1] as Element)).toBe(
        `<child><div>text</div></child>`,
      );
    });
  });
});

@Component({
  template: `
    <ng-template #tplRef let-name>{{name}}</ng-template>

  `,
  standalone: false,
})
class EmbeddedViewInsertionComp {}

@Directive({
  selector: '[vcref]',
  standalone: false,
})
class VCRefDirective {
  @Input() tplRef: TemplateRef<any> | undefined;
  @Input() name: string = '';

  // Injecting the ViewContainerRef to create a dynamic container in which
  // embedded views will be created
  constructor(
    public vcref: ViewContainerRef,
    public elementRef: ElementRef,
  ) {}

  createView(s: string, index?: number): EmbeddedViewRef<any> {
    if (!this.tplRef) {
      throw new Error('No template reference passed to directive.');
    }

    return this.vcref.createEmbeddedView(this.tplRef, {$implicit: s}, index);
  }
}

@Component({
  selector: `embedded-cmp-with-ngcontent`,
  template: `<ng-content></ng-content><hr><ng-content></ng-content>`,
  standalone: false,
})
class EmbeddedComponentWithNgContent {}

@Component({
  selector: 'view-container-ref-comp',
  template: `
    <ng-template #ref0>0</ng-template>
    <ng-template #ref1>1</ng-template>
    <ng-template #ref2>2</ng-template>
  `,
  standalone: false,
})
class ViewContainerRefComp {
  @ViewChildren(TemplateRef) templates!: QueryList<TemplateRef<any>>;

  constructor(public vcr: ViewContainerRef) {}
}

@Component({
  selector: 'view-container-ref-app',
  template: `
    <view-container-ref-comp></view-container-ref-comp>
  `,
  standalone: false,
})
class ViewContainerRefApp {
  @ViewChild(ViewContainerRefComp) vcrComp!: ViewContainerRefComp;
}

@Directive({
  selector: '[structDir]',
  standalone: false,
})
export class StructDir {
  constructor(
    private vcref: ViewContainerRef,
    private tplRef: TemplateRef<any>,
  ) {}

  create() {
    this.vcref.createEmbeddedView(this.tplRef);
  }

  destroy() {
    this.vcref.clear();
  }
}

@Component({
  selector: 'destroy-cases',
  template: `  `,
  standalone: false,
})
class DestroyCasesComp {
  @ViewChildren(StructDir) structDirs!: QueryList<StructDir>;
}

@Directive({
  selector: '[constructorDir]',
  standalone: false,
})
class ConstructorDir {
  constructor(vcref: ViewContainerRef, tplRef: TemplateRef<any>) {
    vcref.createEmbeddedView(tplRef);
  }
}

@Component({
  selector: 'constructor-app',
  template: `

  `,
  standalone: false,
})
class ConstructorApp {
  @ViewChild('foo', {static: true}) foo!: ElementRef;
}

@Component({
  selector: 'constructor-app-with-queries',
  template: `
    <ng-template constructorDir #foo>

    </ng-template>
  `,
  standalone: false,
})
class ConstructorAppWithQueries {
  @ViewChild('foo', {static: true}) foo!: TemplateRef<any>;
}


describe('afterNextRender', () => {
      it('should run with the correct timing', () => {
        @Component({
          selector: 'dynamic-comp',
          standalone: false,
        })
        class DynamicComp {
          afterRenderCount = 0;

          constructor() {
            afterNextRender(() => {
              this.afterRenderCount++;
            });
          }
        }

        @Component({
          selector: 'comp',
          standalone: false,
        })
        class Comp {
          afterRenderCount = 0;
          changeDetectorRef = inject(ChangeDetectorRef);
          viewContainerRef = inject(ViewContainerRef);

          constructor() {
            afterNextRender(() => {
              this.afterRenderCount++;
            });
          }
        }

        TestBed.configureTestingModule({
          declarations: [Comp],
          ...COMMON_CONFIGURATION,
        });
        const component = createAndAttachComponent(Comp);
        const compInstance = component.instance;
        const viewContainerRef = compInstance.viewContainerRef;
        const dynamicCompRef = viewContainerRef.createComponent(DynamicComp);

        // It hasn't run at all
        expect(dynamicCompRef.instance.afterRenderCount).toBe(0);
        expect(compInstance.afterRenderCount).toBe(0);

        // Running change detection at the dynamicCompRef level
        dynamicCompRef.changeDetectorRef.detectChanges();
        expect(dynamicCompRef.instance.afterRenderCount).toBe(0);
        expect(compInstance.afterRenderCount).toBe(0);

        // Running change detection at the compInstance level
        compInstance.changeDetectorRef.detectChanges();
        expect(dynamicCompRef.instance.afterRenderCount).toBe(0);
        expect(compInstance.afterRenderCount).toBe(0);

        // Running change detection at the Application level
        TestBed.inject(ApplicationRef).tick();
        expect(dynamicCompRef.instance.afterRenderCount).toBe(1);
        expect(compInstance.afterRenderCount).toBe(1);

        // Running change detection after removing view.
        viewContainerRef.remove();
        TestBed.inject(ApplicationRef).tick();
        expect(dynamicCompRef.instance.afterRenderCount).toBe(1);
        expect(compInstance.afterRenderCount).toBe(1);
      });

      it('should not run until views have stabilized', async () => {
        // This test uses two components, a Reader and Writer, and arranges CD so that Reader
        // is checked, and then Writer makes Reader dirty again. An `afterNextRender` should not run
        // until Reader has been fully refreshed.

        TestBed.configureTestingModule(COMMON_CONFIGURATION);
        const appRef = TestBed.inject(ApplicationRef);

        const counter = signal(0);
        @Component({standalone: true, template: '{{counter()}}'})
        class Reader {
          counter = counter;
        }

        @Component({standalone: true, template: ''})
        class Writer {
          ngAfterViewInit(): void {
            counter.set(1);
          }
        }

        const ref = createAndAttachComponent(Reader);
        createAndAttachComponent(Writer);

        let textAtAfterRender: string = '';
        afterNextRender(
          () => {
            // Reader should've been fully refreshed, so capture its template state at this moment.
            textAtAfterRender = ref.location.nativeElement.innerHTML;
          },
          {injector: appRef.injector},
        );

        await appRef.whenStable();
        expect(textAtAfterRender).toBe('1');
      });

      it('should run all hooks after outer change detection', () => {
        let log: string[] = [];

        @Component({
          selector: 'child-comp',
          standalone: false,
        })
        class ChildComp {
          constructor() {
            afterNextRender(() => {
              log.push('child-comp');
            });
          }
        }

        @Component({
          selector: 'parent',
          template: `<child-comp></child-comp>`,
          standalone: false,
        })
        class ParentComp {
          changeDetectorRef = inject(ChangeDetectorRef);

          constructor() {
            afterNextRender(() => {
              log.push('parent-comp');
            });
          }

          ngOnInit() {
            log.push('pre-cd');
            this.changeDetectorRef.detectChanges();
            log.push('post-cd');
          }
        }

        TestBed.configureTestingModule({
          declarations: [ChildComp, ParentComp],
          ...COMMON_CONFIGURATION,
        });
        createAndAttachComponent(ParentComp);
        expect(log).toEqual([]);

        TestBed.inject(ApplicationRef).tick();
        expect(log).toEqual(['pre-cd', 'post-cd', 'parent-comp', 'child-comp']);
      });

      it('should unsubscribe when calling destroy', () => {
        let hookRef: AfterRenderRef | null = null;
        let afterRenderCount = 0;

        @Component({
          selector: 'comp',
          standalone: false,
        })
        class Comp {
          constructor() {
            hookRef = afterNextRender(() => {
              afterRenderCount++;
            });
          }
        }

        TestBed.configureTestingModule({
          declarations: [Comp],
          ...COMMON_CONFIGURATION,
        });
        createAndAttachComponent(Comp);
        expect(afterRenderCount).toBe(0);

        hookRef!.destroy();
        TestBed.inject(ApplicationRef).tick();
        expect(afterRenderCount).toBe(0);
      });

      it('should throw if called recursively', () => {
        class RethrowErrorHandler extends ErrorHandler {
          override handleError(error: any): void {
            throw error;
          }
        }

        @Component({
          selector: 'comp',
          standalone: false,
        })
        class Comp {
          appRef = inject(ApplicationRef);
          injector = inject(EnvironmentInjector);

          ngOnInit() {
            afterNextRender(
              () => {
                this.appRef.tick();
              },
              {injector: this.injector},
            );
          }
        }

        TestBed.configureTestingModule({
          declarations: [Comp],
          ...COMMON_CONFIGURATION,
          providers: [
            {provide: ErrorHandler, useClass: RethrowErrorHandler},
            ...COMMON_CONFIGURATION.providers,
          ],
        });
        createAndAttachComponent(Comp);
        expect(() => TestBed.inject(ApplicationRef).tick()).toThrowError(
          /ApplicationRef.tick is called recursively/,
        );
      });

      it('should defer nested hooks to the next cycle', () => {
        let outerHookCount = 0;
        let innerHookCount = 0;

        @Component({
          selector: 'comp',
          standalone: false,
        })
        class Comp {
          injector = inject(Injector);

          constructor() {
            afterNextRender(() => {
              outerHookCount++;

              afterNextRender(
                () => {
                  innerHookCount++;
                },
                {injector: this.injector},
              );
            });
          }
        }

        TestBed.configureTestingModule({
          declarations: [Comp],
          ...COMMON_CONFIGURATION,
        });
        createAndAttachComponent(Comp);

        // It hasn't run at all
        expect(outerHookCount).toBe(0);
        expect(innerHookCount).toBe(0);

        // Running change detection (first time)
        TestBed.inject(ApplicationRef).tick();
        expect(outerHookCount).toBe(1);
        expect(innerHookCount).toBe(0);

        // Running change detection (second time)
        TestBed.inject(ApplicationRef).tick();
        expect(outerHookCount).toBe(1);
        expect(innerHookCount).toBe(1);

        // Running change detection (third time)
        TestBed.inject(ApplicationRef).tick();
        expect(outerHookCount).toBe(1);
        expect(innerHookCount).toBe(1);
      });

      it('should run outside of the Angular zone', () => {
        const zoneLog: boolean[] = [];

        @Component({
          selector: 'comp',
          standalone: false,
        })
        class Comp {
          constructor() {
            afterNextRender(() => {
              zoneLog.push(NgZone.isInAngularZone());
            });
          }
        }

        TestBed.configureTestingModule({
          declarations: [Comp],
          ...COMMON_CONFIGURATION,
        });
        createAndAttachComponent(Comp);

        expect(zoneLog).toEqual([]);
        TestBed.inject(NgZone).run(() => {
          TestBed.inject(ApplicationRef).tick();
          expect(zoneLog).toEqual([false]);
        });
      });

      it('should propagate errors to the ErrorHandler', () => {
        const log: string[] = [];

        class FakeErrorHandler extends ErrorHandler {
          override handleError(error: any): void {
            log.push((error as Error).message);
          }
        }

        @Component({
          template: '',
          standalone: false,
        })
        class Comp {
          constructor() {
            afterNextRender(() => {
              log.push('pass 1');
            });

            afterNextRender(() => {
              throw new Error('fail 1');
            });

            afterNextRender(() => {
              log.push('pass 2');
            });

            afterNextRender(() => {
              throw new Error('fail 2');
            });
          }
        }

        TestBed.configureTestingModule({
          declarations: [Comp],
          providers: [COMMON_PROVIDERS, {provide: ErrorHandler, useClass: FakeErrorHandler}],
        });
        createAndAttachComponent(Comp);

        expect(log).toEqual([]);
        TestBed.inject(ApplicationRef).tick();
        expect(log).toEqual(['pass 1', 'fail 1', 'pass 2', 'fail 2']);
      });


it('should destroy after the hook has run', () => {
      let hookRef: AfterRenderRef | null = null;
      let afterRenderCount = 0;

      @Component({selector: 'comp', standalone: false})
      class Comp {
        constructor() {
          hookRef = afterNextRender(() => {
            afterRenderCount++;
          });
        }
      }

      TestBed.configureTestingModule({
        declarations: [Comp],
        ...COMMON_CONFIGURATION,
      });
      createAndAttachComponent(Comp);
      const appRef = TestBed.inject(ApplicationRef);
      const destroySpy = spyOn(hookRef!, 'destroy').and.callThrough();
      expect(afterRenderCount).toBe(0);
      expect(destroySpy).not.toHaveBeenCalled();

      // Run once and ensure that it was called and then cleaned up.
      appRef.tick();
      expect(afterRenderCount).toBe(1);
      expect(destroySpy).toHaveBeenCalledTimes(1);

      // Make sure we're not retaining it.
      appRef.tick();
      expect(afterRenderCount).toBe(1);
      expect(destroySpy).toHaveBeenCalledTimes(1);
    });
  });

  describe('server', () => {
    const COMMON_CONFIGURATION = {
      providers: [{provide: PLATFORM_ID, useValue: PLATFORM_SERVER_ID}],
    };

    describe('afterRender', () => {
      it('should not run', () => {
        let afterRenderCount = 0;

        @Component({
          selector: 'comp',
          standalone: false,
        })
        class Comp {
          constructor() {
            afterRender(() => {
              afterRenderCount++;
            });
          }
        }

        TestBed.configureTestingModule({
          declarations: [Comp],
          ...COMMON_CONFIGURATION,
        });
        createAndAttachComponent(Comp);
        TestBed.inject(ApplicationRef).tick();
        expect(afterRenderCount).toBe(0);
      });
    });

    describe('afterNextRender', () => {
      it('should not run', () => {
        let afterRenderCount = 0;

        @Component({
          selector: 'comp',
          standalone: false,
        })
        class Comp {
          constructor() {
            afterNextRender(() => {
              afterRenderCount++;
            });
          }
        }

        TestBed.configureTestingModule({
          declarations: [Comp],
          ...COMMON_CONFIGURATION,
        });
        createAndAttachComponent(Comp);
        TestBed.inject(ApplicationRef).tick();
        expect(afterRenderCount).toBe(0);
      });
    });
  });
});


() => {
  function getElementHtml(element: HTMLElement) {
    return element.innerHTML
      .replace(//g, '')
      .replace(/\sng-reflect-\S*="[^"]*"/g, '');
  }

  it('should project content', () => {
    @Component({
      selector: 'child',
      template: `<div><ng-content></ng-content></div>`,
      standalone: false,
    })
    class Child {}

    @Component({
      selector: 'parent',
      template: '<child>content</child>',
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child]});
    const fixture = TestBed.createComponent(Parent);
    fixture.detectChanges();

    expect(fixture.nativeElement.innerHTML).toBe(`<child><div>content</div></child>`);
  });

  it('should project content when <ng-content> is at a template root', () => {
    @Component({
      selector: 'child',
      template: '<ng-content></ng-content>',
      standalone: false,
    })
    class Child {}

    @Component({
      selector: 'parent',
      template: '<child>content</child>',
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child]});
    const fixture = TestBed.createComponent(Parent);
    fixture.detectChanges();

    expect(fixture.nativeElement.innerHTML).toBe(`<child>content</child>`);
  });

  it('should project content with siblings', () => {
    @Component({
      selector: 'child',
      template: '<ng-content></ng-content>',
      standalone: false,
    })
    class Child {}

    @Component({
      selector: 'parent',
      template: `<child>before<div>content</div>after</child>`,
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child]});
    const fixture = TestBed.createComponent(Parent);
    fixture.detectChanges();

    expect(fixture.nativeElement.innerHTML).toBe(`<child>before<div>content</div>after</child>`);
  });

  it('should be able to re-project content', () => {
    @Component({
      selector: 'grand-child',
      template: `<div><ng-content></ng-content></div>`,
      standalone: false,
    })
    class GrandChild {}

    @Component({
      selector: 'child',
      template: `<grand-child><ng-content></ng-content></grand-child>`,
      standalone: false,
    })
    class Child {}

    @Component({
      selector: 'parent',
      template: `<child><b>Hello</b>World!</child>`,
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child, GrandChild]});
    const fixture = TestBed.createComponent(Parent);
    fixture.detectChanges();

    expect(fixture.nativeElement.innerHTML).toBe(
      '<child><grand-child>
HelloWorld!
</grand-child></child>',
    );
  });

  it('should project components', () => {
    @Component({
      selector: 'child',
      template: `<div><ng-content></ng-content></div>`,
      standalone: false,
    })
    class Child {}

    @Component({
      selector: 'projected-comp',
      template: 'content',
      standalone: false,
    })
    class ProjectedComp {}

    @Component({
      selector: 'parent',
      template: `<child><projected-comp></projected-comp></child>`,
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child, ProjectedComp]});
    const fixture = TestBed.createComponent(Parent);
    fixture.detectChanges();

    expect(fixture.nativeElement.innerHTML).toBe(
      '<child>
<projected-comp>content</projected-comp>
</child>',
    );
  });

  it('should project components that have their own projection', () => {
    @Component({
      selector: 'child',
      template: `<div><ng-content></ng-content></div>`,
      standalone: false,
    })
    class Child {}

    @Component({
      selector: 'projected-comp',
      template: `<p><ng-content></ng-content></p>`,
      standalone: false,
    })
    class ProjectedComp {}

    @Component({
      selector: 'parent',
      template: `
        <child>
          <projected-comp>
Some content
Other content</projected-comp>
        </child>`,
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child, ProjectedComp]});
    const fixture = TestBed.createComponent(Parent);
    fixture.detectChanges();

    expect(fixture.nativeElement.innerHTML).toBe(
      `<child><div><projected-comp><p><div>Some content</div>Other content</p></projected-comp></div></child>`,
    );
  });

  it('should project with multiple instances of a component with projection', () => {
    @Component({
      selector: 'child',
      template: `<div><ng-content></ng-content></div>`,
      standalone: false,
    })
    class Child {}

    @Component({
      selector: 'projected-comp',
      template: `Before<ng-content></ng-content>After`,
      standalone: false,
    })
    class ProjectedComp {}

    @Component({
      selector: 'parent',
      template: `
        <child>
          <projected-comp>
A

123
</projected-comp>
          <projected-comp>
B

456
</projected-comp>
        </child>`,
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child, ProjectedComp]});
    const fixture = TestBed.createComponent(Parent);
    fixture.detectChanges();

    expect(fixture.nativeElement.innerHTML).toBe(
      '<child>
' +
        '<projected-comp>Before
A

123
After</projected-comp>' +
        '<projected-comp>Before
B

456
After</projected-comp>' +
        '
</child>',
    );
  });

  it('should re-project with multiple instances of a component with projection', () => {
    @Component({
      selector: 'child',
      template: `<div><ng-content></ng-content></div>`,
      standalone: false,
    })
    class Child {}

    @Component({
      selector: 'projected-comp',
      template: `Before<ng-content></ng-content>After`,
      standalone: false,
    })
    class ProjectedComp {}

    @Component({
      selector: 'parent',
      template: `
        <child>
          <projected-comp>
A
<ng-content></ng-content>
123
</projected-comp>
          <projected-comp>
B

456
</projected-comp>
        </child>`,
      standalone: false,
    })
    class Parent {}

    @Component({
      selector: 'app',
      template: `
        <parent>**ABC**</parent>
        <parent>**DEF**</parent>
     `,
      standalone: false,
    })
    class App {}

    TestBed.configureTestingModule({declarations: [App, Parent, Child, ProjectedComp]});
    const fixture = TestBed.createComponent(App);
    fixture.detectChanges();

    expect(fixture.nativeElement.innerHTML).toBe(
      '<parent><child>
' +
        '<projected-comp>Before
A
**ABC**
123
After</projected-comp>' +
        '<projected-comp>Before
B

456
After</projected-comp>' +
        '
</child></parent>' +
        '<parent><child>
' +
        '<projected-comp>Before
A
**DEF**
123
After</projected-comp>' +
        '<projected-comp>Before
B

456
After</projected-comp>' +
        '
</child></parent>',
    );
  });

  it('should project into dynamic views (with createEmbeddedView)', () => {
    @Component({
      selector: 'child',
      template: `Before-<ng-template [ngIf]="showing"><ng-content></ng-content></ng-template>-After`,
      standalone: false,
    })
    class Child {
      showing = false;
    }

    @Component({
      selector: 'parent',
      template: `<child><div>A</div>Some text</child>`,
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child], imports: [CommonModule]});

    const fixture = TestBed.createComponent(Parent);
    const childDebugEl = fixture.debugElement.query(By.directive(Child));
    const childInstance = childDebugEl.injector.get(Child);
    const childElement = childDebugEl.nativeElement as HTMLElement;

    childInstance.showing = true;
    fixture.detectChanges();

    expect(getElementHtml(childElement)).toBe(`Before-<div>A</div>Some text-After`);

    childInstance.showing = false;
    fixture.detectChanges();

    expect(getElementHtml(childElement)).toBe(`Before--After`);

    childInstance.showing = true;
    fixture.detectChanges();
    expect(getElementHtml(childElement)).toBe(`Before-<div>A</div>Some text-After`);
  });


it('should project into dynamic views with specific selectors', () => {
    @Component({
      selector: 'child',
      template: `
        <ng-content></ng-content>
        Before-
        <ng-template [ngIf]="showing">
          <ng-content select="div"></ng-content>
        </ng-template>
        -After`,
      standalone: false,
    })
    class Child {
      showing = false;
    }

    @Component({
      selector: 'parent',
      template: `
        <child>

A

          B
        </child>
      `,
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child], imports: [CommonModule]});

    const fixture = TestBed.createComponent(Parent);
    const childDebugEl = fixture.debugElement.query(By.directive(Child));
    const childInstance = childDebugEl.injector.get(Child);

    childInstance.showing = true;
    fixture.detectChanges();

    expect(getElementHtml(fixture.nativeElement)).toBe(
      '<child>B Before-
A
 -After</child>',
    );

    childInstance.showing = false;
    fixture.detectChanges();
    expect(getElementHtml(fixture.nativeElement)).toBe(
      '<child>B Before-  -After</child>',
    );

    childInstance.showing = true;
    fixture.detectChanges();
    expect(getElementHtml(fixture.nativeElement)).toBe(
      '<child>B Before-
A
 -After</child>',
    );
  });

  it('should project if <ng-content> is in a template that has different declaration/insertion points', () => {
    @Component({
      selector: 'comp',
      template: `<ng-template><ng-content></ng-content></ng-template>`,
      standalone: false,
    })
    class Comp {
      @ViewChild(TemplateRef, {static: true}) template!: TemplateRef<any>;
    }

    @Directive({
      selector: '[trigger]',
      standalone: false,
    })
    class Trigger {
      @Input() trigger!: Comp;

      constructor(public vcr: ViewContainerRef) {}

      open() {
        this.vcr.createEmbeddedView(this.trigger.template);
      }
    }

    @Component({
      selector: 'parent',
      template: `
        <button [trigger]="comp"></button>
        <comp #comp>Some content</comp>
      `,
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Trigger, Comp]});

    const fixture = TestBed.createComponent(Parent);
    const trigger = fixture.debugElement.query(By.directive(Trigger)).injector.get(Trigger);
    fixture.detectChanges();

    expect(getElementHtml(fixture.nativeElement)).toBe(`<button></button><comp></comp>`);

    trigger.open();
    expect(getElementHtml(fixture.nativeElement)).toBe(
      `<button></button>Some content<comp></comp>`,
    );
  });

  it('should project nodes into the last ng-content', () => {
    @Component({
      selector: 'child',
      template: `
<ng-content></ng-content>

          <ng-content></ng-content>`,
      standalone: false,
    })
    class Child {}

    @Component({
      selector: 'parent',
      template: `<child>content</child>`,
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child], imports: [CommonModule]});
    const fixture = TestBed.createComponent(Parent);
    fixture.detectChanges();

    expect(getElementHtml(fixture.nativeElement)).toBe(
      '<child>

content</child>',
    );
  });

  // https://stackblitz.com/edit/angular-ceqmnw?file=src%2Fapp%2Fapp.component.ts
  it('should project nodes into the last ng-content unrolled by ngFor', () => {
    @Component({
      selector: 'child',
      template:
        '
({{i}}):<ng-content></ng-content>
',
      standalone: false,
    })
    class Child {}

    @Component({
      selector: 'parent',
      template: '<child>content</child>',
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child], imports: [CommonModule]});
    const fixture = TestBed.createComponent(Parent);
    fixture.detectChanges();

    expect(getElementHtml(fixture.nativeElement)).toBe(
      '<child>
(0):

(1):content
</child>',
    );
  });

  it('should handle projected containers inside other containers', () => {
    @Component({
      selector: 'nested-comp',
      template: `<div>Child content</div>`,
      standalone: false,
    })
    class NestedComp {}

    @Component({
      selector: 'root-comp',
      template: `<ng-content></ng-content>`,
      standalone: false,
    })
    class RootComp {}

    @Component({
      selector: 'my-app',
      template: `
        <root-comp>
          <ng-container *ngFor="let item of items; last as last">
            <nested-comp *ngIf="!last"></nested-comp>
          </ng-container>
        </root-comp>
      `,
      standalone: false,
    })
    class MyApp {
      items = [1, 2];
    }

    TestBed.configureTestingModule({
      declarations: [MyApp, RootComp, NestedComp],
      imports: [CommonModule],
    });
    const fixture = TestBed.createComponent(MyApp);
    fixture.detectChanges();

    // expecting # of divs to be (items.length - 1), since last element is filtered out by *ngIf,
    // this applies to all other assertions below
    expect(fixture.nativeElement.querySelectorAll('div').length).toBe(1);

    fixture.componentInstance.items = [3, 4, 5];
    fixture.detectChanges();
    expect(fixture.nativeElement.querySelectorAll('div').length).toBe(2);

    fixture.componentInstance.items = [6, 7, 8, 9];
    fixture.detectChanges();
    expect(fixture.nativeElement.querySelectorAll('div').length).toBe(3);
  });

  it('should handle projection into element containers at the view root', () => {
    @Component({
      selector: 'root-comp',
      template: `
        <ng-template [ngIf]="show">
          <ng-container>
            <ng-content></ng-content>
          </ng-container>
        </ng-template>`,
      standalone: false,
    })
    class RootComp {
      @Input() show: boolean = true;
    }

    @Component({
      selector: 'my-app',
      template: `<root-comp [show]="show">

</root-comp>
      `,
      standalone: false,
    })
    class MyApp {
      show = true;
    }

    TestBed.configureTestingModule({declarations: [MyApp, RootComp]});
    const fixture = TestBed.createComponent(MyApp);

    fixture.detectChanges();
    expect(fixture.nativeElement.querySelectorAll('div').length).toBe(1);

    fixture.componentInstance.show = false;
    fixture.detectChanges();
    expect(fixture.nativeElement.querySelectorAll('div').length).toBe(0);
  });

  it('should handle projection of views with element containers at the root', () => {
    @Component({
      selector: 'root-comp',
      template: `<ng-template [ngIf]="show"><ng-content></ng-content></ng-template>`,
      standalone: false,
    })
    class RootComp {
      @Input() show: boolean = true;
    }

    @Component({
      selector: 'my-app',
      template: `<root-comp [show]="show"><ng-container><div></div></ng-container></root-comp>`,
      standalone: false,
    })
    class MyApp {
      show = true;
    }

    TestBed.configureTestingModule({declarations: [MyApp, RootComp]});
    const fixture = TestBed.createComponent(MyApp);

    fixture.detectChanges();
    expect(fixture.nativeElement.querySelectorAll('div').length).toBe(1);

    fixture.componentInstance.show = false;
    fixture.detectChanges();
    expect(fixture.nativeElement.querySelectorAll('div').length).toBe(0);
  });

  it('should project ng-container at the content root', () => {
    @Component({
      selector: 'child',
      template: `<ng-content></ng-content>`,
      standalone: false,
    })
    class Child {}

    @Component({
      selector: 'parent',
      template: `<child>
      <ng-container>
        <ng-container>content</ng-container>
      </ng-container>
    </child>
      `,
      standalone: false,
    })
    class Parent {}

    TestBed.configureTestingModule({declarations: [Parent, Child]});
    const fixture = TestBed.createComponent(Parent);

    fixture.detectChanges();
    expect(getElementHtml(fixture.nativeElement)).toBe('<child>content</child>');
  });


describe('with selectors', () => {
    it('should project nodes using attribute selectors', () => {
      @Component({
        selector: 'child',
        template: `
<ng-content select="span[title=toFirst]"></ng-content>

<ng-content select="span[title=toSecond]"></ng-content>
`,
        standalone: false,
      })
      class Child {}

      @Component({
        selector: 'parent',
        template: `<child><span title="toFirst">1</span><span title="toSecond">2</span></child>`,
        standalone: false,
      })
      class Parent {}

      TestBed.configureTestingModule({declarations: [Child, Parent]});
      const fixture = TestBed.createComponent(Parent);
      fixture.detectChanges();

      expect(getElementHtml(fixture.nativeElement)).toEqual(
        '<child>
1

2
</child>',
      );
    });

    it('should project nodes using class selectors', () => {
      @Component({
        selector: 'child',
        template: `
<ng-content select="span.toFirst"></ng-content>

<ng-content select="span.toSecond"></ng-content>
`,
        standalone: false,
      })
      class Child {}

      @Component({
        selector: 'parent',
        template: `<child><span class="toFirst">1</span><span class="toSecond">2</span></child>`,
        standalone: false,
      })
      class Parent {}

      TestBed.configureTestingModule({declarations: [Child, Parent]});
      const fixture = TestBed.createComponent(Parent);
      fixture.detectChanges();

      expect(getElementHtml(fixture.nativeElement)).toEqual(
        '<child>
1

2
</child>',
      );
    });

    it('should project nodes using class selectors when element has multiple classes', () => {
      @Component({
        selector: 'child',
        template: `
<ng-content select="span.toFirst"></ng-content>

<ng-content select="span.toSecond"></ng-content>
`,
        standalone: false,
      })
      class Child {}

      @Component({
        selector: 'parent',
        template: `<child><span class="other toFirst">1</span><span class="noise toSecond">2</span></child>`,
        standalone: false,
      })
      class Parent {}

      TestBed.configureTestingModule({declarations: [Child, Parent]});
      const fixture = TestBed.createComponent(Parent);
      fixture.detectChanges();

      expect(getElementHtml(fixture.nativeElement)).toEqual(
        '<child>
1

2
</child>',
      );
    });

    it('should project nodes into the first matching selector', () => {
      @Component({
        selector: 'child',
        template: `
<ng-content select="span"></ng-content>

<ng-content select="span.toSecond"></ng-content>
`,
        standalone: false,
      })
      class Child {}

      @Component({
        selector: 'parent',
        template: `<child><span class="toFirst">1</span><span class="toSecond">2</span></child>`,
        standalone: false,
      })
      class Parent {}

      TestBed.configureTestingModule({declarations: [Child, Parent]});
      const fixture = TestBed.createComponent(Parent);
      fixture.detectChanges();

      expect(getElementHtml(fixture.nativeElement)).toEqual(
        '<child>
12

</child>',
      );
    });

    it('should allow mixing ng-content with and without selectors', () => {
      @Component({
        selector: 'child',
        template: `
<ng-content select="span.toFirst"></ng-content>

<ng-content></ng-content>
`,
        standalone: false,
      })
      class Child {}

      @Component({
        selector: 'parent',
        template: `<child><span class="toFirst">1</span><span>remaining</span>more remaining</child>`,
        standalone: false,
      })
      class Parent {}

      TestBed.configureTestingModule({declarations: [Child, Parent]});
      const fixture = TestBed.createComponent(Parent);
      fixture.detectChanges();

      expect(getElementHtml(fixture.nativeElement)).toEqual(
        '<child>
1

remainingmore remaining
</child>',
      );
    });

    it('should allow mixing ng-content with and without selectors - ng-content first', () => {
      @Component({
        selector: 'child',
        template: `
<ng-content></ng-content>

<ng-content select="span.toSecond"></ng-content>
`,
        standalone: false,
      })
      class Child {}

      @Component({
        selector: 'parent',
        template: `<child><span>1</span><span class="toSecond">2</span>remaining</child>`,
        standalone: false,
      })
      class Parent {}

      TestBed.configureTestingModule({declarations: [Child, Parent]});
      const fixture = TestBed.createComponent(Parent);
      fixture.detectChanges();

      expect(getElementHtml(fixture.nativeElement)).toEqual(
        '<child>
1remaining

2
</child>',
      );
    });

    /**
     * Descending into projected content for selector-matching purposes is not supported
     * today: https://plnkr.co/edit/MYQcNfHSTKp9KvbzJWVQ?p=preview
     */
    it('should not descend into re-projected content', () => {
      @Component({
        selector: 'grand-child',
        template: `<ng-content select="span"></ng-content><hr><ng-content></ng-content>`,
        standalone: false,
      })
      class GrandChild {}

      @Component({
        selector: 'child',
        template: `<grand-child>
            <ng-content></ng-content>
            in child template
          </grand-child>`,
        standalone: false,
      })
      class Child {}

      @Component({
        selector: 'parent',
        template: `<child><span>parent content</span></child>`,
        standalone: false,
      })
      class Parent {}

      TestBed.configureTestingModule({declarations: [GrandChild, Child, Parent]});
      const fixture = TestBed.createComponent(Parent);
      fixture.detectChanges();

      expect(getElementHtml(fixture.nativeElement)).toEqual(
        '<child><grand-child>in child template
parent content</grand-child></child>',
      );
    });

    it('should not descend into re-projected content', () => {
      @Component({
        selector: 'card',
        template: `<ng-content select="[card-title]"></ng-content><hr><ng-content select="[card-content]"></ng-content>`,
        standalone: false,
      })
      class Card {}

      @Component({
        selector: 'card-with-title',
        template: `<card>

Title

         <ng-content card-content></ng-content>
       </card>`,
        standalone: false,
      })
      class CardWithTitle {}

      @Component({
        selector: 'parent',
        template: `<card-with-title>content</card-with-title>`,
        standalone: false,
      })
      class Parent {}

      TestBed.configureTestingModule({declarations: [Card, CardWithTitle, Parent]});
      const fixture = TestBed.createComponent(Parent);
      fixture.detectChanges();

      expect(getElementHtml(fixture.nativeElement)).toEqual(
        '<card-with-title><card>
Title

content</card></card-with-title>',
      );
    });

    it('should not match selectors against node having ngProjectAs attribute', () => {
      @Component({
        selector: 'child',
        template: `<ng-content select="div"></ng-content>`,
        standalone: false,
      })
      class Child {}

      @Component({
        selector: 'parent',
        template: `<child><div ngProjectAs="span">should not project</div><div>should project</div></child>`,
        standalone: false,
      })
      class Parent {}

      TestBed.configureTestingModule({declarations: [Child, Parent]});
      const fixture = TestBed.createComponent(Parent);
      fixture.detectChanges();

      expect(getElementHtml(fixture.nativeElement)).toEqual(
        '<child>
should project
</child>',
      );
    });

    // https://stackblitz.com/edit/angular-psokum?file=src%2Fapp%2Fapp.module.ts


it('should project nodes where attribute selector matches a binding', () => {
      @Component({
        selector: 'child',
        template: `<ng-content select="[title]"></ng-content>`,
        standalone: false,
      })
      class Child {}

      @Component({
        selector: 'parent',
        template: `<child><span [title]="'Some title'">Has title</span></child>`,
        standalone: false,
      })
      class Parent {}

      TestBed.configureTestingModule({declarations: [Child, Parent]});
      const fixture = TestBed.createComponent(Parent);
      fixture.detectChanges();

      expect(getElementHtml(fixture.nativeElement)).toEqual(
        '<child>Has title</child>',
      );
    });

    it('should match selectors against projected containers', () => {
      @Component({
        selector: 'child',
        template: `<span><ng-content select="div"></ng-content></span>`,
        standalone: false,
      })
      class Child {}

      @Component({
        template: `<child><div *ngIf="value">content</div></child>`,
        standalone: false,
      })
      class Parent {
        value = false;
      }
      TestBed.configureTestingModule({declarations: [Child, Parent]});
      const fixture = TestBed.createComponent(Parent);

      fixture.componentInstance.value = true;
      fixture.detectChanges();

      expect(getElementHtml(fixture.nativeElement)).toEqual(
        '<child>
content
</child>',
      );
    });
  });

  it('should handle projected containers inside other containers', () => {
    @Component({
      selector: 'child-comp', //
      template: '<ng-content></ng-content>',
      standalone: false,
    })
    class ChildComp {}

    @Component({
      selector: 'root-comp', //
      template: '<ng-content></ng-content>',
      standalone: false,
    })
    class RootComp {}

    @Component({
      selector: 'my-app',
      template: `
        <root-comp>
          <ng-container *ngFor="let item of items; last as last">
            <child-comp *ngIf="!last">{{ item }}|</child-comp>
          </ng-container>
        </root-comp>
      `,
      standalone: false,
    })
    class MyApp {
      items: number[] = [1, 2, 3];
    }

    TestBed.configureTestingModule({declarations: [ChildComp, RootComp, MyApp]});
    const fixture = TestBed.createComponent(MyApp);
    fixture.detectChanges();

    // expecting # of elements to be (items.length - 1), since last element is filtered out by
    // *ngIf, this applies to all other assertions below
    expect(fixture.nativeElement).toHaveText('1|2|');

    fixture.componentInstance.items = [4, 5];
    fixture.detectChanges();
    expect(fixture.nativeElement).toHaveText('4|');

    fixture.componentInstance.items = [6, 7, 8, 9];
    fixture.detectChanges();
    expect(fixture.nativeElement).toHaveText('6|7|8|');
  });

  it('should project content if the change detector has been detached', () => {
    @Component({
      selector: 'my-comp',
      template: '<ng-content></ng-content>',
      standalone: false,
    })
    class MyComp {
      constructor(changeDetectorRef: ChangeDetectorRef) {
        changeDetectorRef.detach();
      }
    }

    @Component({
      selector: 'my-app',
      template: `
        <my-comp>

hello

        </my-comp>
      `,
      standalone: false,
    })
    class MyApp {}

    TestBed.configureTestingModule({declarations: [MyComp, MyApp]});
    const fixture = TestBed.createComponent(MyApp);
    fixture.detectChanges();

    expect(fixture.nativeElement).toHaveText('hello');
  });

  it('should support ngProjectAs with a various number of other bindings and attributes', () => {
    @Directive({
      selector: '[color],[margin]',
      standalone: false,
    })
    class ElDecorator {
      @Input() color?: string;
      @Input() margin?: number;
    }
    @Component({
      selector: 'card',
      template: `
        <ng-content select="[card-title]"></ng-content>
        ---
        <ng-content select="[card-subtitle]"></ng-content>
        ---
        <ng-content select="[card-content]"></ng-content>
        ---
        <ng-content select="[card-footer]"></ng-content>
      `,
      standalone: false,
    })
    class Card {}

    @Component({
      selector: 'card-with-title',
      template: `
        <card>

Title

Subtitle

content

footer

        </card>
      `,
      standalone: false,
    })
    class CardWithTitle {}

    TestBed.configureTestingModule({declarations: [Card, CardWithTitle, ElDecorator]});
    const fixture = TestBed.createComponent(CardWithTitle);
    fixture.detectChanges();

    expect(fixture.nativeElement.textContent).toEqual('Title --- Subtitle --- content --- footer');
  });

  it('should support ngProjectAs on elements (including <ng-content>)', () => {
    @Component({
      selector: 'card',
      template: `
        <ng-content select="[card-title]"></ng-content>
        ---
        <ng-content select="[card-content]"></ng-content>
      `,
      standalone: false,
    })
    class Card {}

    @Component({
      selector: 'card-with-title',
      template: `
        <card>

Title

         <ng-content ngProjectAs="[card-content]"></ng-content>
        </card>
      `,
      standalone: false,
    })
    class CardWithTitle {}

    @Component({
      selector: 'app',
      template: `
        <card-with-title>content</card-with-title>
      `,
      standalone: false,
    })
    class App {}

    TestBed.configureTestingModule({declarations: [Card, CardWithTitle, App]});
    const fixture = TestBed.createComponent(App);
    fixture.detectChanges();

    expect(fixture.nativeElement.textContent).toEqual('Title --- content');
  });

  it('should not match multiple selectors in ngProjectAs', () => {
    @Component({
      selector: 'card',
      template: `
        <ng-content select="[card-title]"></ng-content>
        content
      `,
      standalone: false,
    })
    class Card {}

    @Component({
      template: `
        <card>

Title

        </card>
      `,
      standalone: false,
    })
    class App {}

    TestBed.configureTestingModule({declarations: [Card, App]});
    const fixture = TestBed.createComponent(App);
    fixture.detectChanges();

    expect(fixture.nativeElement.textContent).not.toEqual('Title content');
  });

  it('should preserve ngProjectAs and other attributes on projected element', () => {
    @Component({
      selector: 'projector',
      template: `<ng-content select="projectMe"></ng-content>`,
      standalone: false,
    })
    class Projector {}

    @Component({
      template: `
        <projector>

        </projector>
      `,
      standalone: false,
    })
    class Root {}

    TestBed.configureTestingModule({
      declarations: [Root, Projector],
    });
    const fixture = TestBed.createComponent(Root);
    fixture.detectChanges();

    const projectedElement = fixture.debugElement.query(By.css('div'));
    const {ngProjectAs, title} = projectedElement.attributes;
    expect(ngProjectAs).toBe('projectMe');
    expect(title).toBe('some title');
  });


it('should render the fallback content if content is not provided through projectableNodes', () => {
      @Component({
        standalone: true,
        template:
          `<ng-content>One fallback</ng-content>|` +
          `<ng-content>Two fallback</ng-content>|<ng-content>Three fallback</ng-content>`,
      })
      class Projection {}

      const hostElement = document.createElement('div');
      const environmentInjector = TestBed.inject(EnvironmentInjector);
      const paragraph = document.createElement('p');
      paragraph.textContent = 'override';
      const projectableNodes = [[paragraph]];
      const componentRef = createComponent(Projection, {
        hostElement,
        environmentInjector,
        projectableNodes,
      });
      componentRef.changeDetectorRef.detectChanges();

      expect(getElementHtml(hostElement)).toContain('
override
|Two fallback|Three fallback');
      componentRef.destroy();
    });

    it('should render the content through projectableNodes along with fallback', () => {
      @Component({
        standalone: true,
        template:
          `<ng-content>One fallback</ng-content>|` +
          `<ng-content>Two fallback</ng-content>|<ng-content>Three fallback</ng-content>`,
      })
      class Projection {}

      const hostElement = document.createElement('div');
      const environmentInjector = TestBed.inject(EnvironmentInjector);
      const paragraph = document.createElement('p');
      paragraph.textContent = 'override';
      const secondParagraph = document.createElement('p');
      secondParagraph.textContent = 'override';
      const projectableNodes = [[paragraph], [], [secondParagraph]];
      const componentRef = createComponent(Projection, {
        hostElement,
        environmentInjector,
        projectableNodes,
      });
      componentRef.changeDetectorRef.detectChanges();

      expect(getElementHtml(hostElement)).toContain('
override
|Two fallback|
override
');
    });

    it('should render fallback content when ng-content is inside an ng-template', () => {
      @Component({
        selector: 'projection',
        template: `<ng-container #ref/><ng-template #template><ng-content>Fallback</ng-content></ng-template>`,
        standalone: true,
      })
      class Projection {
        @ViewChild('template') template!: TemplateRef<unknown>;
        @ViewChild('ref', {read: ViewContainerRef}) viewContainerRef!: ViewContainerRef;

        createContent() {
          this.viewContainerRef.createEmbeddedView(this.template);
        }
      }

      @Component({
        standalone: true,
        imports: [Projection],
        template: `<projection/>`,
      })
      class App {
        @ViewChild(Projection) projection!: Projection;
      }

      const fixture = TestBed.createComponent(App);
      fixture.detectChanges();
      expect(getElementHtml(fixture.nativeElement)).toContain(`<projection></projection>`);

      fixture.componentInstance.projection.createContent();
      fixture.detectChanges();
      expect(getElementHtml(fixture.nativeElement)).toContain(`<projection>Fallback</projection>`);
    });

    it('should render the fallback content when ng-content is re-projected', () => {
      @Component({
        selector: 'inner-projection',
        template: `
          <ng-content select="[inner-header]">Inner header fallback</ng-content>
          <ng-content select="[inner-footer]">Inner footer fallback</ng-content>
        `,
        standalone: true,
      })
      class InnerProjection {}

      @Component({
        selector: 'projection',
        template: `
          <inner-projection>
            <ng-content select="[outer-header]" inner-header>Outer header fallback</ng-content>
            <ng-content select="[outer-footer]" inner-footer>Outer footer fallback</ng-content>
          </inner-projection>
        `,
        standalone: true,
        imports: [InnerProjection],
      })
      class Projection {}

      @Component({
        standalone: true,
        imports: [Projection],
        template: `
          <projection>
            Outer header override
          </projection>
        `,
      })
      class App {}

      const fixture = TestBed.createComponent(App);
      const content = getElementHtml(fixture.nativeElement);

      expect(content).toContain('Outer header override');
      expect(content).toContain('Outer footer fallback');
    });

    it('should not instantiate directives inside the fallback content', () => {
      let creationCount = 0;

      @Component({
        selector: 'fallback',
        standalone: true,
        template: 'Fallback',
      })
      class Fallback {
        constructor() {
          creationCount++;
        }
      }

      @Component({
        selector: 'projection',
        template: `<ng-content><fallback/></ng-content>`,
        standalone: true,
        imports: [Fallback],
      })
      class Projection {}

      @Component({
        standalone: true,
        imports: [Projection],
        template: `<projection>Hello</projection>`,
      })
      class App {}

      const fixture = TestBed.createComponent(App);
      expect(creationCount).toBe(0);
      expect(getElementHtml(fixture.nativeElement)).toContain(`<projection>Hello</projection>`);
    });

    it(
      'should render the fallback content when an instance of a component that uses ' +
        'fallback content is declared after one that does not',
      () => {
        @Component({
          selector: 'projection',
          template: `<ng-content>Fallback</ng-content>`,
          standalone: true,
        })
        class Projection {}

        @Component({
          standalone: true,
          imports: [Projection],
          template: `
            <projection>Content</projection>
            <projection/>
          `,
        })
        class App {}

        const fixture = TestBed.createComponent(App);
        expect(getElementHtml(fixture.nativeElement)).toContain(
          '<projection>Content</projection><projection>Fallback</projection>',
        );
      },
    );

    it(
      'should render the fallback content when an instance of a component that uses ' +
        'fallback content is declared before one that does not',
      () => {
        @Component({
          selector: 'projection',
          template: `<ng-content>Fallback</ng-content>`,
          standalone: true,
        })
        class Projection {}

        @Component({
          standalone: true,
          imports: [Projection],
          template: `
            <projection/>
            <projection>Content</projection>
          `,
        })
        class App {}

        const fixture = TestBed.createComponent(App);
        expect(getElementHtml(fixture.nativeElement)).toContain(
          '<projection>Fallback</projection><projection>Content</projection>',
        );
      },
    );
  });
});


describe('OnPush components with signals', () => {
  it('marks view dirty', () => {
    @Component({
      template: `{{value()}}{{incrementTemplateExecutions()}}`,
      changeDetection: ChangeDetectionStrategy.OnPush,
      standalone: true,
    })
    class OnPushCmp {
      numTemplateExecutions = 0;
      value = signal('initial');
      incrementTemplateExecutions() {
        this.numTemplateExecutions++;
        return '';
      }
    }
    const fixture = TestBed.createComponent(OnPushCmp);
    const instance = fixture.componentInstance;

    fixture.detectChanges();
    expect(instance.numTemplateExecutions).toBe(1);
    expect(fixture.nativeElement.textContent.trim()).toEqual('initial');

    fixture.detectChanges();
    // Should not be dirty, should not execute template
    expect(instance.numTemplateExecutions).toBe(1);

    instance.value.set('new');
    fixture.detectChanges();
    expect(instance.numTemplateExecutions).toBe(2);
    expect(instance.value()).toBe('new');
  });

  it("does not refresh a component when a signal notifies but isn't actually updated", () => {
    @Component({
      template: `{{memo()}}{{incrementTemplateExecutions()}}`,
      changeDetection: ChangeDetectionStrategy.OnPush,
      standalone: true,
    })
    class OnPushCmp {
      numTemplateExecutions = 0;
      value = signal({value: 'initial'});
      memo = computed(() => this.value().value, {equal: Object.is});
      incrementTemplateExecutions() {
        this.numTemplateExecutions++;
        return '';
      }
    }
    const fixture = TestBed.createComponent(OnPushCmp);
    const instance = fixture.componentInstance;

    fixture.detectChanges();
    expect(instance.numTemplateExecutions).toBe(1);
    expect(fixture.nativeElement.textContent.trim()).toEqual('initial');

    instance.value.update((v) => ({...v}));
    fixture.detectChanges();
    expect(instance.numTemplateExecutions).toBe(1);

    instance.value.update((v) => ({value: 'new'}));
    fixture.detectChanges();
    expect(instance.numTemplateExecutions).toBe(2);
    expect(fixture.nativeElement.textContent.trim()).toEqual('new');
  });

  it('should not mark components as dirty when signal is read in a constructor of a child component', () => {
    const state = signal('initial');

    @Component({
      selector: 'child',
      template: `child`,
      changeDetection: ChangeDetectionStrategy.OnPush,
      standalone: true,
    })
    class ChildReadingSignalCmp {
      constructor() {
        state();
      }
    }

    @Component({
      template: `
            {{incrementTemplateExecutions()}}
            <!-- Template constructed to execute child component constructor in the update pass of a host component -->
            <ng-template [ngIf]="true"><child></child></ng-template>
          `,
      changeDetection: ChangeDetectionStrategy.OnPush,
      standalone: true,
      imports: [NgIf, ChildReadingSignalCmp],
    })
    class OnPushCmp {
      numTemplateExecutions = 0;
      incrementTemplateExecutions() {
        this.numTemplateExecutions++;
        return '';
      }
    }

    const fixture = TestBed.createComponent(OnPushCmp);
    const instance = fixture.componentInstance;

    fixture.detectChanges();
    expect(instance.numTemplateExecutions).toBe(1);
    expect(fixture.nativeElement.textContent.trim()).toEqual('child');

    // The "state" signal is not accesses in the template's update function anywhere so it
    // shouldn't mark components as dirty / impact change detection.
    state.set('new');
    fixture.detectChanges();
    expect(instance.numTemplateExecutions).toBe(1);
  });

  it('should not mark components as dirty when signal is read in an input of a child component', () => {
    const state = signal('initial');

    @Component({
      selector: 'with-input-setter',
      standalone: true,
      template: '{{test}}',
    })
    class WithInputSetter {
      test = '';

      @Input()
      set testInput(newValue: string) {
        this.test = state() + ':' + newValue;
      }
    }

    @Component({
      template: `
            {{incrementTemplateExecutions()}}
            <!-- Template constructed to execute child component constructor in the update pass of a host component -->
            <ng-template [ngIf]="true"><with-input-setter [testInput]="'input'" /></ng-template>
          `,
      changeDetection: ChangeDetectionStrategy.OnPush,
      standalone: true,
      imports: [NgIf, WithInputSetter],
    })
    class OnPushCmp {
      numTemplateExecutions = 0;
      incrementTemplateExecutions() {
        this.numTemplateExecutions++;
        return '';
      }
    }

    const fixture = TestBed.createComponent(OnPushCmp);
    const instance = fixture.componentInstance;

    fixture.detectChanges();
    expect(instance.numTemplateExecutions).toBe(1);
    expect(fixture.nativeElement.textContent.trim()).toEqual('initial:input');

    // The "state" signal is not accesses in the template's update function anywhere so it
    // shouldn't mark components as dirty / impact change detection.
    state.set('new');
    fixture.detectChanges();
    expect(instance.numTemplateExecutions).toBe(1);
    expect(fixture.nativeElement.textContent.trim()).toEqual('initial:input');
  });

  it('should not mark components as dirty when signal is read in a query result setter', () => {
    const state = signal('initial');

    @Component({
      selector: 'with-query-setter',
      standalone: true,
      template: '<div #el>child</div>',
    })
    class WithQuerySetter {
      el: unknown;
      @ViewChild('el', {static: true})
      set elQuery(result: unknown) {
        // read a signal in a setter
        state();
        this.el = result;
      }
    }

    @Component({
      template: `
         {{incrementTemplateExecutions()}}
         <!-- Template constructed to execute child component constructor in the update pass of a host component -->
         <ng-template [ngIf]="true"><with-query-setter /></ng-template>
       `,
      changeDetection: ChangeDetectionStrategy.OnPush,
      standalone: true,
      imports: [NgIf, WithQuerySetter],
    })
    class OnPushCmp {
      numTemplateExecutions = 0;
      incrementTemplateExecutions() {
        this.numTemplateExecutions++;
        return '';
      }
    }

    const fixture = TestBed.createComponent(OnPushCmp);
    const instance = fixture.componentInstance;

    fixture.detectChanges();
    expect(instance.numTemplateExecutions).toBe(1);
    expect(fixture.nativeElement.textContent.trim()).toEqual('child');

    // The "state" signal is not accesses in the template's update function anywhere so it
    // shouldn't mark components as dirty / impact change detection.
    state.set('new');
    fixture.detectChanges();
    expect(instance.numTemplateExecutions).toBe(1);
  });

  it('can read a signal in a host binding in root view', () => {
    const useBlue = signal(false);
    @Component({
      template: `{{incrementTemplateExecutions()}}`,
      selector: 'child',
      host: {'[class.blue]': 'useBlue()'},
      changeDetection: ChangeDetectionStrategy.OnPush,
      standalone: true,
    })
    class MyCmp {
      useBlue = useBlue;

      numTemplateExecutions = 0;
      incrementTemplateExecutions() {
        this.numTemplateExecutions++;
        return '';
      }
    }

    const fixture = TestBed.createComponent(MyCmp);

    fixture.detectChanges();
    expect(fixture.nativeElement.outerHTML).not.toContain('blue');
    expect(fixture.componentInstance.numTemplateExecutions).toBe(1);

    useBlue.set(true);
    fixture.detectChanges();
    expect(fixture.nativeElement.outerHTML).toContain('blue');
    expect(fixture.componentInstance.numTemplateExecutions).toBe(1);
  });

  it('can read a signal in a host binding', () => {
    @Component({
      template: `{{incrementTemplateExecutions()}}`,
      selector: 'child',
      host: {'[class.blue]': 'useBlue()'},
      changeDetection: ChangeDetectionStrategy.OnPush,
      standalone: true,
    })
    class ChildCmp {
      useBlue = signal(false);

      numTemplateExecutions = 0;
      incrementTemplateExecutions() {
        this.numTemplateExecutions++;
        return '';
      }
    }

    @Component({
      template: `<child />`,
      changeDetection: ChangeDetectionStrategy.OnPush,
      imports: [ChildCmp],
      standalone: true,
    })
    class ParentCmp {}
    const fixture = TestBed.createComponent(ParentCmp);
    const child = fixture.debugElement.query((p) => p.componentInstance instanceof ChildCmp);
    const childInstance = child.componentInstance as ChildCmp;

    fixture.detectChanges();
    expect(childInstance.numTemplateExecutions).toBe(1);
    expect(child.nativeElement.outerHTML).not.toContain('blue');

    childInstance.useBlue.set(true);
    fixture.detectChanges();
    // We should not re-execute the child template. It didn't change, the host bindings did.
    expect(childInstance.numTemplateExecutions).toBe(1);
    expect(child.nativeElement.outerHTML).toContain('blue');
  });


it('can have signals in both template and host bindings', () => {
    @Component({
      template: ``,
      selector: 'child',
      host: {'[class.blue]': 'useBlue()'},
      changeDetection: ChangeDetectionStrategy.OnPush,
      standalone: true,
    })
    class ChildCmp {
      useBlue = signal(false);
    }

    @Component({
      template: `<child /> {{parentSignalValue()}}`,
      changeDetection: ChangeDetectionStrategy.OnPush,
      imports: [ChildCmp],
      standalone: true,
      selector: 'parent',
    })
    class ParentCmp {
      parentSignalValue = signal('initial');
    }

    // Wrapper component so we can effectively test ParentCmp being marked dirty
    @Component({
      template: `<parent />`,
      changeDetection: ChangeDetectionStrategy.OnPush,
      imports: [ParentCmp],
      standalone: true,
    })
    class TestWrapper {}

    const fixture = TestBed.createComponent(TestWrapper);
    const parent = fixture.debugElement.query((p) => p.componentInstance instanceof ParentCmp)
      .componentInstance as ParentCmp;
    const child = fixture.debugElement.query((p) => p.componentInstance instanceof ChildCmp)
      .componentInstance as ChildCmp;

    fixture.detectChanges();
    expect(fixture.nativeElement.outerHTML).toContain('initial');
    expect(fixture.nativeElement.outerHTML).not.toContain('blue');

    child.useBlue.set(true);
    fixture.detectChanges();
    expect(fixture.nativeElement.outerHTML).toContain('blue');

    // Set the signal in the parent again and ensure it gets updated
    parent.parentSignalValue.set('new');
    fixture.detectChanges();
    expect(fixture.nativeElement.outerHTML).toContain('new');

    // Set the signal in the child host binding again and ensure it is still updated
    child.useBlue.set(false);
    fixture.detectChanges();
    expect(fixture.nativeElement.outerHTML).not.toContain('blue');
  });

  it('should be able to write to signals during change-detecting a given template, in advance()', () => {
    const counter = signal(0);

    @Directive({
      standalone: true,
      selector: '[misunderstood]',
    })
    class MisunderstoodDir {
      ngOnInit(): void {
        counter.update((c) => c + 1);
      }
    }

    @Component({
      selector: 'test-component',
      standalone: true,
      imports: [MisunderstoodDir],
      template: `
          {{counter()}}

{{ 'force advance()' }}
        `,
    })
    class TestCmp {
      counter = counter;
    }

    const fixture = TestBed.createComponent(TestCmp);
    // CheckNoChanges should not throw ExpressionChanged error
    // and signal value is updated to latest value with 1 `detectChanges`
    fixture.detectChanges();
    expect(fixture.nativeElement.innerText).toContain('1');
    expect(fixture.nativeElement.innerText).toContain('force advance()');
  });

  it('should allow writing to signals during change-detecting a given template, at the end', () => {
    const counter = signal(0);

    @Directive({
      standalone: true,
      selector: '[misunderstood]',
    })
    class MisunderstoodDir {
      ngOnInit(): void {
        counter.update((c) => c + 1);
      }
    }

    @Component({
      selector: 'test-component',
      standalone: true,
      imports: [MisunderstoodDir],
      template: `
          {{counter()}}

        `,
    })
    class TestCmp {
      counter = counter;
    }

    const fixture = TestBed.createComponent(TestCmp);
    // CheckNoChanges should not throw ExpressionChanged error
    // and signal value is updated to latest value with 1 `detectChanges`
    fixture.detectChanges();
    expect(fixture.nativeElement.innerText).toBe('1');
  });

  it('should allow writing to signals in afterViewInit', () => {
    @Component({
      template: '{{loading()}}',
      standalone: true,
    })
    class MyComp {
      loading = signal(true);
      // Classic example of what would have caused ExpressionChanged...Error
      ngAfterViewInit() {
        this.loading.set(false);
      }
    }

    const fixture = TestBed.createComponent(MyComp);
    fixture.detectChanges();
    expect(fixture.nativeElement.innerText).toBe('false');
  });

  it('does not refresh view if signal marked dirty but did not change', () => {
    const val = signal('initial', {equal: () => true});

    @Component({
      template: '{{val()}}{{incrementChecks()}}',
      standalone: true,
      changeDetection: ChangeDetectionStrategy.OnPush,
    })
    class App {
      val = val;
      templateExecutions = 0;
      incrementChecks() {
        this.templateExecutions++;
      }
    }

    const fixture = TestBed.createComponent(App);
    fixture.detectChanges();
    expect(fixture.componentInstance.templateExecutions).toBe(1);
    expect(fixture.nativeElement.innerText).toContain('initial');

    val.set('new');
    fixture.detectChanges();
    expect(fixture.componentInstance.templateExecutions).toBe(1);
    expect(fixture.nativeElement.innerText).toContain('initial');
  });