import {
ConnectedOverlayPositionChange,
ConnectionPositionPair,
OverlayConfig,
Overlay,
} from "@angular/cdk/overlay";
import { ComponentPortal } from "@angular/cdk/portal";
import { Component } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { Observable, Subject } from "rxjs";
import { TooltipDirective } from "./tooltip.directive";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
standalone: true,
imports: [TooltipDirective],
template: ` `,
})
class TooltipHostComponent {
tooltipText = "Hello Tooltip";
}
/** Minimal strategy shape the directive expects */
interface StrategyLike {
withFlexibleDimensions: (flex: boolean) => StrategyLike;
withPush: (push: boolean) => StrategyLike;
withPositions: (positions: ReadonlyArray) => StrategyLike;
readonly positionChanges: Observable;
}
/** Minimal Overlay service shape */
interface OverlayLike {
position: () => { flexibleConnectedTo: (_: unknown) => StrategyLike };
create: (_: OverlayConfig) => OverlayRefStub;
scrollStrategies: { reposition: () => unknown };
}
interface OverlayRefStub {
attach: (portal: ComponentPortal) => unknown;
updatePosition: () => void;
}
describe("TooltipDirective (visibility only)", () => {
let fixture: ComponentFixture;
beforeEach(() => {
const positionChanges$ = new Subject();
const strategy: StrategyLike = {
withFlexibleDimensions: jest.fn(() => strategy),
withPush: jest.fn(() => strategy),
withPositions: jest.fn(() => strategy),
get positionChanges() {
return positionChanges$.asObservable();
},
};
const overlayRefStub: OverlayRefStub = {
attach: jest.fn(() => ({
changeDetectorRef: { detectChanges: jest.fn() },
location: {
nativeElement: {
querySelector: jest.fn().mockReturnValue({ id: "tip-123" }),
},
},
})),
updatePosition: jest.fn(),
};
const overlayMock: OverlayLike = {
position: () => ({ flexibleConnectedTo: () => strategy }),
create: (_: OverlayConfig) => overlayRefStub,
scrollStrategies: { reposition: () => ({}) },
};
TestBed.configureTestingModule({
imports: [TooltipHostComponent],
providers: [{ provide: Overlay, useValue: overlayMock as unknown as Overlay }],
});
fixture = TestBed.createComponent(TooltipHostComponent);
fixture.detectChanges();
});
function getDirective(): TooltipDirective {
const hostDE = fixture.debugElement.query(By.directive(TooltipDirective));
return hostDE.injector.get(TooltipDirective);
}
it("sets isVisible to true on mouseenter", () => {
const button: HTMLButtonElement = fixture.debugElement.query(By.css("button")).nativeElement;
const directive = getDirective();
const isVisible = (directive as unknown as { isVisible: () => boolean }).isVisible;
button.dispatchEvent(new Event("mouseenter"));
expect(isVisible()).toBe(true);
});
it("sets isVisible to true on focus", () => {
const button: HTMLButtonElement = fixture.debugElement.query(By.css("button")).nativeElement;
const directive = getDirective();
const isVisible = (directive as unknown as { isVisible: () => boolean }).isVisible;
button.dispatchEvent(new Event("focus"));
expect(isVisible()).toBe(true);
});
});