Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
8 changes: 4 additions & 4 deletions coverage-floor.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"statements": 88.73,
"branches": 78.65,
"functions": 85.9,
"lines": 88.72
"statements": 95.6,
"branches": 90.09,
"functions": 92.78,
"lines": 95.67
}
16 changes: 16 additions & 0 deletions src/ui-kit/components/banner/banner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,20 @@ describe("The Sam Banner component", () => {
component.toggleDetails();
expect(component.showDetail).toBe(true);
});

it("should close the detail when it is open", () => {
component.toggleDetails();
expect(component.showDetail).toBe(true);

component.closeDetail();

expect(component.showDetail).toBe(false);
});

it("should do nothing when the detail is already closed", () => {
expect(component.showDetail).toBe(false);

expect(() => component.closeDetail()).not.toThrow();
expect(component.showDetail).toBe(false);
});
});
148 changes: 148 additions & 0 deletions src/ui-kit/components/data-table/sort-header.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { ChangeDetectorRef } from "@angular/core";
import { Subject } from "rxjs";
import {
SamSortHeaderComponent,
SamSortHeaderIntl,
} from "./sort-header.component";
import { SamSortDirective } from "./sort.directive";

describe("SamSortHeaderIntl", () => {
it("returns the id as the button label", () => {
const intl = new SamSortHeaderIntl();
expect(intl.sortButtonLabel("name")).toBe("name");
});

it("describes ascending sort", () => {
const intl = new SamSortHeaderIntl();
expect(intl.sortDescriptionLabel("name", "asc")).toBe(
"Sorted by name ascending"
);
});

it("describes descending sort", () => {
const intl = new SamSortHeaderIntl();
expect(intl.sortDescriptionLabel("name", "desc")).toBe(
"Sorted by name descending"
);
});
});

describe("SamSortHeaderComponent", () => {
function createSort(overrides: Partial<SamSortDirective> = {}) {
return {
active: "",
direction: "",
samSortChange: new Subject<void>(),
register: vi.fn(),
deregister: vi.fn(),
sort: vi.fn(),
...overrides,
} as unknown as SamSortDirective;
}

function createComponent(sort: SamSortDirective, cdkColumnDef: any = null) {
const cdr = { markForCheck: vi.fn() } as unknown as ChangeDetectorRef;
return new SamSortHeaderComponent(
new SamSortHeaderIntl(),
cdr,
sort,
cdkColumnDef
);
}

it("subscribes to the sort directive's samSortChange and marks for check", () => {
const sort = createSort();
const header = createComponent(sort);
const cdrSpy = vi.spyOn(header["_changeDetectorRef"], "markForCheck");
(sort.samSortChange as Subject<void>).next();
expect(cdrSpy).toHaveBeenCalled();
});

it("defaults the id from the containing CdkColumnDef when none is provided", () => {
const sort = createSort();
const header = createComponent(sort, { name: "columnName" });
header.ngOnInit();
expect(header.id).toBe("columnName");
expect(sort.register).toHaveBeenCalledWith(header);
});

it("keeps an explicitly-set id rather than defaulting from the column def", () => {
const sort = createSort();
const header = createComponent(sort, { name: "columnName" });
header.id = "explicitId";
header.ngOnInit();
expect(header.id).toBe("explicitId");
});

it("registers with the sort directive on init", () => {
const sort = createSort();
const header = createComponent(sort);
header.id = "name";
header.ngOnInit();
expect(sort.register).toHaveBeenCalledWith(header);
});

it("deregisters and unsubscribes on destroy", () => {
const sort = createSort();
const header = createComponent(sort);
header.id = "name";
header.ngOnInit();
const unsubscribeSpy = vi.spyOn(header.sortSubscription, "unsubscribe");
header.ngOnDestroy();
expect(sort.deregister).toHaveBeenCalledWith(header);
expect(unsubscribeSpy).toHaveBeenCalled();
});

it("reports sorted when this header's id matches the active sort and a direction is set", () => {
const sort = createSort({ active: "name", direction: "asc" });
const header = createComponent(sort);
header.id = "name";
expect(header._isSorted()).toBeTruthy();
});

it("reports not sorted when a different header is active", () => {
const sort = createSort({ active: "other", direction: "asc" });
const header = createComponent(sort);
header.id = "name";
expect(header._isSorted()).toBeFalsy();
});

it("reports not sorted when the direction is empty even if this header is active", () => {
const sort = createSort({ active: "name", direction: "" });
const header = createComponent(sort);
header.id = "name";
expect(header._isSorted()).toBeFalsy();
});

it("samSortHeaderSorted() reflects _isSorted()", () => {
const sort = createSort({ active: "name", direction: "asc" });
const header = createComponent(sort);
header.id = "name";
expect(header.samSortHeaderSorted()).toBeTruthy();
});

it("delegates hostClick to the sort directive when not disabled", () => {
const sort = createSort();
const header = createComponent(sort);
header.disabled = false;
header.hostClick();
expect(sort.sort).toHaveBeenCalledWith(header);
});

it("does nothing on hostClick when disabled", () => {
const sort = createSort();
const header = createComponent(sort);
header.disabled = true;
header.hostClick();
expect(sort.sort).not.toHaveBeenCalled();
});

it("coerces disableClear through coerceBooleanProperty", () => {
const sort = createSort();
const header = createComponent(sort);
header.disableClear = "" as never;
expect(header.disableClear).toBe(true);
header.disableClear = false;
expect(header.disableClear).toBe(false);
});
});
72 changes: 72 additions & 0 deletions src/ui-kit/components/header-next/header.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,76 @@ describe("SamHeaderNextComponent", () => {
"notification icon exists"
);
});

describe("openMobileNav() / closeMobileNav() / navAnimationEnd()", () => {
beforeEach(() => {
fixture.detectChanges();
});

it("activates the mobile nav", () => {
component.openMobileNav();
expect(component.mobileNavActive).toBe(true);
});

it("deactivates the mobile nav and refocuses the open-nav button", () => {
component.mobileNavActive = true;
const focusSpy = vi.spyOn(component.openNavBtn.nativeElement, "focus");

component.closeMobileNav();

expect(component.mobileNavActive).toBe(false);
expect(focusSpy).toHaveBeenCalled();
});

it("focuses the close-nav button once the open animation ends", () => {
const focusSpy = vi.spyOn(component.closeNavBtn.nativeElement, "focus");

component.navAnimationEnd();

expect(focusSpy).toHaveBeenCalled();
});
});

describe("onBrowserResize()", () => {
beforeEach(() => {
fixture.detectChanges();
});

it("deactivates the mobile nav when it's active and the close button is no longer visible", () => {
component.mobileNavActive = true;
vi.spyOn(
component.closeNavBtn.nativeElement,
"getBoundingClientRect"
).mockReturnValue({ width: 0 } as DOMRect);

component.onBrowserResize({} as Event);

expect(component.mobileNavActive).toBe(false);
});

it("leaves the mobile nav active when the close button is still visible", () => {
component.mobileNavActive = true;
vi.spyOn(
component.closeNavBtn.nativeElement,
"getBoundingClientRect"
).mockReturnValue({ width: 40 } as DOMRect);

component.onBrowserResize({} as Event);

expect(component.mobileNavActive).toBe(true);
});

it("does nothing when the mobile nav is not active", () => {
component.mobileNavActive = false;
const getRectSpy = vi.spyOn(
component.closeNavBtn.nativeElement,
"getBoundingClientRect"
);

component.onBrowserResize({} as Event);

expect(getRectSpy).not.toHaveBeenCalled();
expect(component.mobileNavActive).toBe(false);
});
});
});
78 changes: 78 additions & 0 deletions src/ui-kit/components/image/image.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,82 @@ describe("The Sam Image Component", () => {
expect(overEvent.stopPropagation).toHaveBeenCalled();
expect(overEvent.preventDefault).toHaveBeenCalled();
});

it("does not toggle edit mode when the component is not editable", () => {
component.editable = false;
fixture.detectChanges();
const before = component.editMode;

de.query(By.css("button.edit-button")).nativeElement.dispatchEvent(
new Event("click")
);

expect(component.editMode).toBe(before);
});

it("does not emit fileChange on save when no temporary image is staged", () => {
const emitted = vi.fn();
component.fileChange.subscribe(emitted);

de.query(By.css("button.save-button")).nativeElement.dispatchEvent(
new Event("click")
);

expect(emitted).not.toHaveBeenCalled();
});

it("truncates a long file name in the file picker label", () => {
const file = new File(["hello"], "a-very-long-file-name.png", {
type: "image/png",
});
const fileInputEl: HTMLInputElement = de.query(
By.css("input#file")
).nativeElement;
Object.defineProperty(fileInputEl, "files", { value: [file] });
fileInputEl.dispatchEvent(new Event("change"));

expect(component.generateFilePickerLabelText()).toBe("a-very-l...");
});

it("falls back to placeholder label text when no file is staged", () => {
expect(component.generateFilePickerLabelText()).toBe("Select a file");
});

it("labels the done button 'Save' only while an image is staged", () => {
expect(component.generateDoneText()).toBe("Done");

const file = new File(["hello"], "x.png", { type: "image/png" });
const fileInputEl: HTMLInputElement = de.query(
By.css("input#file")
).nativeElement;
Object.defineProperty(fileInputEl, "files", { value: [file] });
fileInputEl.dispatchEvent(new Event("change"));

expect(component.generateDoneText()).toBe("Save");
});

it("prefers the staged image over the committed src", () => {
expect(component.generateSrc()).toBe(washingtonImg);

const file = new File(["hello"], "x.png", { type: "image/png" });
const fileInputEl: HTMLInputElement = de.query(
By.css("input#file")
).nativeElement;
Object.defineProperty(fileInputEl, "files", { value: [file] });
fileInputEl.dispatchEvent(new Event("change"));

expect(component.generateSrc()).toContain("data:fake");
});

it("hides the edit button when not editable or already editing", () => {
component.editable = false;
expect(component.hideEditButton()).toBe(true);

component.editable = true;
component.editMode = true;
expect(component.hideEditButton()).toBe(true);

component.editMode = false;
expect(component.hideEditButton()).toBe(false);
});
});
Loading
Loading