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
146 changes: 146 additions & 0 deletions src/ui-kit/components/data-table/sort.directive.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { SamSortDirective, SamSortable } from "./sort.directive";

function makeSortable(overrides: Partial<SamSortable> = {}): SamSortable {
return {
id: "col-a",
start: undefined as unknown as SamSortable["start"],
disableClear: undefined as unknown as SamSortable["disableClear"],
...overrides,
};
}

describe("The Sam Sort directive", () => {
let directive: SamSortDirective;

beforeEach(() => {
directive = new SamSortDirective();
});

it("registers a sortable by id", () => {
const sortable = makeSortable({ id: "col-a" });
directive.register(sortable);

expect(directive.sortables.get("col-a")).toBe(sortable);
});

it("throws when registering a sortable with no id", () => {
const sortable = makeSortable({ id: "" });

expect(() => directive.register(sortable)).toThrow(
"Missing Sort Header ID error"
);
});

it("throws when registering a duplicate id", () => {
directive.register(makeSortable({ id: "col-a" }));

expect(() => directive.register(makeSortable({ id: "col-a" }))).toThrow(
"Duplicate Sort Header ID error: col-a"
);
});

it("deregisters a sortable by id", () => {
const sortable = makeSortable({ id: "col-a" });
directive.register(sortable);
directive.deregister(sortable);

expect(directive.sortables.has("col-a")).toBe(false);
});

it("activates a new sortable using its own start direction", () => {
const sortable = makeSortable({ id: "col-a", start: "desc" });

directive.sort(sortable);

expect(directive.active).toBe("col-a");
expect(directive.direction).toBe("desc");
});

it("falls back to the directive's start direction when the sortable has none", () => {
directive.start = "desc";
const sortable = makeSortable({ id: "col-a", start: undefined });

directive.sort(sortable);

expect(directive.direction).toBe("desc");
});

it("emits samSortChange with the active id and direction", () => {
const sortable = makeSortable({ id: "col-a", start: "asc" });
let emitted: any;
directive.samSortChange.subscribe((val) => {
emitted = val;
});

directive.sort(sortable);

expect(emitted).toEqual({ active: "col-a", direction: "asc" });
});

it("cycles asc -> desc -> clear on repeated sorts of the same sortable", () => {
const sortable = makeSortable({ id: "col-a", start: "asc" });

directive.sort(sortable);
expect(directive.direction).toBe("asc");

directive.sort(sortable);
expect(directive.direction).toBe("desc");

directive.sort(sortable);
expect(directive.direction).toBe("");

directive.sort(sortable);
expect(directive.direction).toBe("asc");
});

it("cycles desc -> asc -> clear when start is desc", () => {
const sortable = makeSortable({ id: "col-a", start: "desc" });

directive.sort(sortable);
expect(directive.direction).toBe("desc");

directive.sort(sortable);
expect(directive.direction).toBe("asc");

directive.sort(sortable);
expect(directive.direction).toBe("");
});

it("skips the clear step when disableClear is set on the directive", () => {
directive.disableClear = true;
const sortable = makeSortable({ id: "col-a", start: "asc" });

directive.sort(sortable);
expect(directive.direction).toBe("asc");

directive.sort(sortable);
expect(directive.direction).toBe("desc");

directive.sort(sortable);
expect(directive.direction).toBe("asc");
});

it("lets a sortable's own disableClear override the directive's setting", () => {
directive.disableClear = false;
const sortable = makeSortable({
id: "col-a",
start: "asc",
disableClear: true,
});

directive.sort(sortable);
expect(directive.direction).toBe("asc");

directive.sort(sortable);
expect(directive.direction).toBe("desc");

directive.sort(sortable);
expect(directive.direction).toBe("asc");
});

it("returns an empty direction from getNextSortDirection when no sortable is given", () => {
expect(
directive.getNextSortDirection(undefined as unknown as SamSortable)
).toBe("");
});
});
112 changes: 108 additions & 4 deletions src/ui-kit/components/image/image.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,34 @@ describe("The Sam Image Component", () => {
let fixture: ComponentFixture<SamImageComponent>;
let component: SamImageComponent;
let de: DebugElement;
let readAsDataURLSpy: ReturnType<typeof vi.spyOn>;

beforeEach(() => {
readAsDataURLSpy = vi
.spyOn(FileReader.prototype, "readAsDataURL")
.mockImplementation(function (this: FileReader, file: Blob) {
this.onload?.({
target: { result: `data:fake;name=${(file as File).name}` },
} as unknown as ProgressEvent<FileReader>);
});

TestBed.configureTestingModule({
declarations: [SamImageComponent],
}).compileComponents();
let emittedFile: File;

fixture = TestBed.createComponent(SamImageComponent);
component = fixture.componentInstance;
de = fixture.debugElement;

component.editable = true;
component.fileChange.subscribe((file: File) => {
emittedFile = file;
});
component.src = washingtonImg;
fixture.detectChanges();
});

afterEach(() => {
readAsDataURLSpy.mockRestore();
});

it("has an edit button that opens file upload", () => {
const buttonEl: DebugElement = de.query(By.css("button.edit-button"));

Expand All @@ -49,4 +58,99 @@ describe("The Sam Image Component", () => {
).nativeElement;
expect(buttonEl.disabled).toBe(true);
});

it("uploads a file, sets tmp state, and emits fileChange on save", () => {
const file = new File(["hello"], "washington.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.getFileName()).toBe("washington.png");

let emittedFile: File | undefined;
component.fileChange.subscribe((f) => {
emittedFile = f;
});

const saveButtonEl: HTMLButtonElement = de.query(
By.css("button.save-button")
).nativeElement;
saveButtonEl.dispatchEvent(new Event("click"));

expect(emittedFile).toBe(file);
expect(component.src).toBeDefined();
});

it("clears tmp file state when cancel is clicked", () => {
const file = new File(["hello"], "washington.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.getFileName()).toBe("washington.png");

const cancelButtonEl: HTMLButtonElement = de.query(
By.css("button.cancel-button")
).nativeElement;
cancelButtonEl.dispatchEvent(new Event("click"));

expect(component.getFileName()).toBe("");
expect(component.isImageTemporary()).toBe(false);
});

it("drops a file onto the container when in edit mode", () => {
component.editMode = true;
const file = new File(["hello"], "dropped.png", { type: "image/png" });

const containerEl: HTMLElement = de.query(
By.css("div.sam-image")
).nativeElement;
const dropEvent = new Event("drop", {
bubbles: true,
cancelable: true,
}) as Event & { dataTransfer: { files: File[] } };
dropEvent.dataTransfer = { files: [file] };
containerEl.dispatchEvent(dropEvent);

expect(component.getFileName()).toBe("dropped.png");
});

it("ignores a drop when not in edit mode", () => {
component.editMode = false;
const file = new File(["hello"], "dropped.png", { type: "image/png" });

const containerEl: HTMLElement = de.query(
By.css("div.sam-image")
).nativeElement;
const dropEvent = new Event("drop", {
bubbles: true,
cancelable: true,
}) as Event & { dataTransfer: { files: File[] } };
dropEvent.dataTransfer = { files: [file] };
containerEl.dispatchEvent(dropEvent);

expect(component.getFileName()).toBe("");
});

it("stops propagation and prevents default on drag enter/over", () => {
const enterEvent = {
stopPropagation: vi.fn(),
preventDefault: vi.fn(),
};
component.onDragEnter(enterEvent as unknown as DragEvent);
expect(enterEvent.stopPropagation).toHaveBeenCalled();
expect(enterEvent.preventDefault).toHaveBeenCalled();

const overEvent = {
stopPropagation: vi.fn(),
preventDefault: vi.fn(),
};
component.onDragOver(overEvent as unknown as DragEvent);
expect(overEvent.stopPropagation).toHaveBeenCalled();
expect(overEvent.preventDefault).toHaveBeenCalled();
});
});
Loading
Loading