Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 221 additions & 2 deletions tedi/components/notifications/alert/alert.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ComponentFixture, TestBed, fakeAsync, tick } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { AlertComponent, AlertRole, AlertType } from "./alert.component";
import {
AlertComponent,
AlertRole,
AlertType,
AlertTitleType,
AlertVariant,
} from "./alert.component";
import { TEDI_TRANSLATION_DEFAULT_TOKEN } from "../../../tokens/translation.token";

describe("AlertComponent", () => {
Expand Down Expand Up @@ -109,4 +115,217 @@ describe("AlertComponent", () => {

expect((fixture.nativeElement as HTMLElement).style.display).toBe("none");
});

describe("title", () => {
it("should display title when provided", () => {
fixture.componentRef.setInput("title", "Test Alert Title");
fixture.detectChanges();

const titleElement = fixture.debugElement.query(
By.css(".tedi-alert__title")
);
expect(titleElement).toBeTruthy();
expect(titleElement.nativeElement.textContent).toContain(
"Test Alert Title"
);
});

it("should not display title element when title is not provided", () => {
fixture.componentRef.setInput("title", undefined);
fixture.detectChanges();

const titleElement = fixture.debugElement.query(
By.css(".tedi-alert__title")
);
expect(titleElement).toBeNull();
});
});

describe("titleElement", () => {
it("should use h2 as default title element", () => {
fixture.componentRef.setInput("title", "Test Title");
fixture.detectChanges();

const h2Element = fixture.debugElement.query(
By.css("h2.tedi-alert__title")
);
expect(h2Element).toBeTruthy();
});

it("should use specified title element tag", () => {
const titleElements: AlertTitleType[] = [
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"div",
];

for (const tag of titleElements) {
fixture.componentRef.setInput("title", "Test Title");
fixture.componentRef.setInput("titleElement", tag);
fixture.detectChanges();

const titleTag = fixture.debugElement.query(
By.css(`${tag}.tedi-alert__title`)
);
expect(titleTag).toBeTruthy();
}
});
});

describe("icon", () => {
it("should display icon in head when title and icon are provided", () => {
fixture.componentRef.setInput("title", "Test Title");
fixture.componentRef.setInput("icon", "info");
fixture.detectChanges();

const iconElement = fixture.debugElement.query(
By.css(".tedi-alert__head > tedi-icon")
);
expect(iconElement).toBeTruthy();
expect(iconElement.nativeElement.textContent).toBe("info");
});

it("should display icon in content when no title but icon is provided", () => {
fixture.componentRef.setInput("icon", "warning");
fixture.detectChanges();

const iconElement = fixture.debugElement.query(
By.css(".tedi-alert__content-icon")
);
expect(iconElement).toBeTruthy();
expect(iconElement.nativeElement.textContent).toBe("warning");
});

it("should not display icon when not provided", () => {
fixture.componentRef.setInput("icon", "");
fixture.detectChanges();

const iconElement = fixture.debugElement.query(By.css("tedi-icon"));
expect(iconElement).toBeNull();
});
});

describe("open", () => {
it("should be visible when open is true", () => {
fixture.componentRef.setInput("open", true);
fixture.detectChanges();

expect(element.style.display).toBe("flex");
});

it("should be hidden when open is false", () => {
fixture.componentRef.setInput("open", false);
fixture.detectChanges();

expect(element.style.display).toBe("none");
});
});

describe("closeDelay", () => {
it("should close immediately when closeDelay is 0", () => {
fixture.componentRef.setInput("showClose", true);
fixture.componentRef.setInput("closeDelay", 0);
fixture.detectChanges();

const closeButton = fixture.debugElement.query(
By.css(".tedi-alert__close")
).nativeElement as HTMLButtonElement;

closeButton.click();
fixture.detectChanges();

expect(element.style.display).toBe("none");
});

it("should delay close when closeDelay is set", fakeAsync(() => {
fixture.componentRef.setInput("showClose", true);
fixture.componentRef.setInput("closeDelay", 300);
fixture.detectChanges();

const closeButton = fixture.debugElement.query(
By.css(".tedi-alert__close")
).nativeElement as HTMLButtonElement;

closeButton.click();
fixture.detectChanges();

expect(element.style.display).toBe("flex");
tick(300);
fixture.detectChanges();
expect(element.style.display).toBe("none");
}));
});

describe("closeClick", () => {
it("should emit closeClick event when close button is clicked", () => {
fixture.componentRef.setInput("showClose", true);
fixture.detectChanges();

const closeClickSpy = jest.fn();
component.closeClick.subscribe(closeClickSpy);

const closeButton = fixture.debugElement.query(
By.css(".tedi-alert__close")
).nativeElement as HTMLButtonElement;

closeButton.click();
fixture.detectChanges();

expect(closeClickSpy).toHaveBeenCalled();
});
});

describe("aria-label", () => {
it("should set aria-label with type only when no title", () => {
fixture.componentRef.setInput("type", "warning");
fixture.componentRef.setInput("title", undefined);
fixture.detectChanges();

expect(element.getAttribute("aria-label")).toBe("warning alert");
});

it("should set aria-label with type and title when title is provided", () => {
fixture.componentRef.setInput("type", "danger");
fixture.componentRef.setInput("title", "Error occurred");
fixture.detectChanges();

expect(element.getAttribute("aria-label")).toBe(
"danger alert: Error occurred"
);
});
});

describe("variant", () => {
it("should apply default variant without extra classes", () => {
fixture.componentRef.setInput("variant", "default");
fixture.detectChanges();

expect(element.classList.contains("tedi-alert")).toBe(true);
expect(element.classList.contains("tedi-alert--global")).toBe(false);
expect(element.classList.contains("tedi-alert--no-side-borders")).toBe(
false
);
});

it("should apply all variant classes correctly", () => {
const variants: AlertVariant[] = ["default", "global", "noSideBorders"];

for (const variant of variants) {
fixture.componentRef.setInput("variant", variant);
fixture.detectChanges();

if (variant === "global") {
expect(element.classList.contains("tedi-alert--global")).toBe(true);
} else if (variant === "noSideBorders") {
expect(
element.classList.contains("tedi-alert--no-side-borders")
).toBe(true);
}
}
});
});
});
20 changes: 19 additions & 1 deletion tedi/components/notifications/alert/alert.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
computed,
input,
model,
output,
ChangeDetectionStrategy,
ViewEncapsulation,
} from "@angular/core";
Expand Down Expand Up @@ -78,6 +79,17 @@ export class AlertComponent {
*/
open = model(true);

/**
* Delay in milliseconds before setting "open" to false when close is triggered.
* @default 0
*/
closeDelay = input(0);

/**
* Close click output
*/
readonly closeClick = output<void>();

getAriaLive = computed(() => {
switch (this.role()) {
case "alert":
Expand Down Expand Up @@ -106,6 +118,12 @@ export class AlertComponent {
});

handleClose() {
this.open.set(false);
this.closeClick.emit();
const delay = this.closeDelay();
if (delay > 0) {
setTimeout(() => this.open.set(false), delay);
} else {
this.open.set(false);
}
}
}
1 change: 1 addition & 0 deletions tedi/components/notifications/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./alert/alert.component";
export * from "./toast/toast.component";
Loading