1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
  2. Anuncie Aqui
    Anuncie aqui você Também: fdantas@4each.com.br

Test IntersectionObserver's callback's actual code in an Angular directive

Discussão em 'Angular' iniciado por Rawnak Yazdani, Junho 17, 2025.

  1. I am facing difficulties reaching the callback of the following IntersectionObserver's callback (which is inside a directive) in angular test code and thus are not being able to fulfill the test coverage.

    Code of the directive:

    @Directive({
    selector: '[observeElementVisibility]',
    standalone: true,
    })
    export class ElementVisibilityHandlerDirective implements OnDestroy {
    inputGroupEntity = input<GroupEntityType>();
    flagGroupEntityRecentlyAdded = input<boolean>(false);

    inputSingleEntity = input<SingleEntityType>();
    flagSingleEntityRecentlyAdded = input<boolean>(false);

    readonly onVisibleGroupEntity = output<GroupEntityType>();
    readonly onVisibleSingleEntity = output<SingleEntityType>();

    private observer!: IntersectionObserver;

    constructor(
    private el: ElementRef<Element>,
    private zone: NgZone
    ) {
    effect(() => {
    const inputGroupEntity = this.inputGroupEntity();
    if (inputGroupEntity && this.flagGroupEntityRecentlyAdded()) {
    this.placeObserver(true, inputGroupEntity);
    }
    });

    effect(() => {
    const inputSingleEntity = this.inputSingleEntity();
    if (inputSingleEntity && this.flagSingleEntityRecentlyAdded()) {
    this.placeObserver(false, undefined, inputSingleEntity);
    }
    });
    }

    placeObserver(
    isGroupEntity: boolean,
    inputGroupEntity: GroupEntityType | undefined = undefined,
    inputSingleEntity: SingleEntityType | undefined = undefined
    ): void {
    this.zone.runOutsideAngular(() => {
    this.observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
    if (entry.isIntersecting) {
    this.zone.run(() => {
    if (isGroupEntity && inputGroupEntity) {
    this.onVisibleGroupEntity.emit(inputGroupEntity);
    } else if (!isGroupEntity && inputSingleEntity) {
    this.onVisibleSingleEntity.emit(inputSingleEntity);
    }
    });
    this.observer.unobserve(this.el.nativeElement);
    }
    });
    });
    this.observer.observe(this.el.nativeElement);
    });
    }

    ngOnDestroy(): void {
    if (this.observer) {
    this.observer.disconnect();
    }
    }
    }


    What I tried in the test code taking idea from this page:

    // Mock test component to host the directive
    @Component({
    template: `
    <div
    observeElementVisibility
    [inputGroupEntity]="groupEntity"
    [flagGroupEntityRecentlyAdded]="flagGroupEntityRecentlyAdded"
    [inputSingleEntity]="singleEntity"
    [flagSingleEntityRecentlyAdded]="flagSingleEntityRecentlyAdded"
    (onVisibleGroupEntity)="onVisibleGroupEntity($event)"
    (onVisibleSingleEntity)="onVisibleSingleEntity($event)"
    ></div>
    `,
    standalone: true,
    imports: [ElementVisibilityHandlerDirective]
    })
    class TestHostComponent {
    groupEntity: GroupEntityType | undefined = undefined;
    flagGroupEntityRecentlyAdded = false;
    singleEntity: SingleEntityType | undefined = undefined;
    flagSingleEntityRecentlyAdded = false;

    onVisibleGroupEntity(task: GroupEntityType): void {
    }

    onVisibleSingleEntity(task: SingleEntityType): void {
    }
    }

    describe('ElementVisibilityHandlerDirective', () => {
    let component: TestHostComponent;
    let fixture: ComponentFixture<TestHostComponent>;
    let directive: ElementVisibilityHandlerDirective;

    // Mock data
    const mockGroupEntityType: GroupEntityType = groupedTasks()[0] as GroupEntityType;

    const mockSingleEntityType: SingleEntityType = taskCardMock();

    class MockIntersectionObserver {
    observe: () => void;
    unobserve: () => void;
    disconnect: () => void;

    constructor(
    public callback: (entries: Array<IntersectionObserverEntry>) => void
    ) {
    this.observe = jasmine.createSpy('observe');
    this.unobserve = jasmine.createSpy('unobserve');
    this.disconnect = jasmine.createSpy('disconnect');
    }
    }

    beforeEach(async () => {
    (window as any).IntersectionObserver = MockIntersectionObserver;

    await TestBed.configureTestingModule({
    imports: [TestHostComponent, ElementVisibilityHandlerDirective],
    }).compileComponents();

    fixture = TestBed.createComponent(TestHostComponent);
    component = fixture.componentInstance;

    const directiveElement = fixture.debugElement.query(
    (debugEl) => debugEl.injector.get(ElementVisibilityHandlerDirective, null) !== null
    );
    directive = directiveElement.injector.get(ElementVisibilityHandlerDirective);
    });

    describe('IntersectionObserver callback for Group Entities', () => {
    it('should emit visibleGroupedTasks when group entity intersects', async () => {
    component.groupEntity = mockGroupEntityType;
    component.flagGroupEntityRecentlyAdded = true;
    fixture.detectChanges();

    spyOn(component, 'onVisibleGroupEntity');
    const mockObserver = new (MockIntersectionObserver as any)(
    (entries: any) => {
    entries.forEach((entry: any) => {
    directive.onVisibleGroupEntity.emit(mockGroupEntityType);
    });
    }
    );
    const mockEntry: IntersectionObserverEntry = {
    isIntersecting: true,
    target: fixture.nativeElement.querySelector('div'),
    boundingClientRect: {} as DOMRectReadOnly,
    intersectionRatio: 1,
    intersectionRect: {} as DOMRectReadOnly,
    rootBounds: {} as DOMRectReadOnly,
    time: Date.now()
    };

    mockObserver.observe(fixture.nativeElement.querySelector('div'));
    mockObserver.callback([mockEntry], mockObserver);

    expect(component.onVisibleGroupEntity).toHaveBeenCalledWith(mockGroupEntityType);
    });
    });
    });


    The above test code works, but in test coverage report the directive's code remains untouched in the IntersectionObserver's callback area like this:

    [​IMG]

    How can I fulfill this test area?

    Continue reading...

Compartilhe esta Página