Skip to content
Open
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
165 changes: 165 additions & 0 deletions src/ui-kit/form-controls/checkbox/checkbox.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { TestBed } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { FormsModule, FormControl } from "@angular/forms";
import { SamCheckboxComponent } from "./checkbox.component";
import { FieldsetWrapper } from "../../wrappers/fieldset-wrapper";
import { SamFormService } from "../../form-service";
import type { ComponentFixture } from "@angular/core/testing";

describe("The Sam Checkbox component", () => {
describe("rendered tests", () => {
let component: SamCheckboxComponent;
let fixture: ComponentFixture<SamCheckboxComponent>;

const options = [
{ value: "dc", label: "Washington DC", name: "dc" },
{ value: "ma", label: "Maryland", name: "ma" },
{ value: "va", label: "Virginia", name: "va" },
];

beforeEach(() => {
TestBed.configureTestingModule({
imports: [FormsModule],
declarations: [SamCheckboxComponent, FieldsetWrapper],
providers: [SamFormService],
});

fixture = TestBed.createComponent(SamCheckboxComponent);
component = fixture.componentInstance;
component.options = options;
component.label = "Pick a state";
component.name = "my-checkboxes";
});

it("should implement controlvalueaccessor", () => {
component.registerOnChange(() => undefined);
component.registerOnTouched(() => undefined);
component.setDisabledState(true);
expect(component.disabled).toBe(true);
component.writeValue(["dc"]);
expect(component.value).toEqual(["dc"]);
});

it("should display a checkbox for each option", () => {
fixture.detectChanges();
const checkboxes = fixture.debugElement.queryAll(By.css("input"));
expect(checkboxes.length).toBe(options.length);
});

it("should check the checkbox when the option value is in the model", () => {
component.writeValue(["ma"]);
fixture.detectChanges();
expect(component.isChecked("ma")).toBe(true);
expect(component.isChecked("dc")).toBe(false);
});

it("should insert a checked option into the model in options order", () => {
fixture.detectChanges();
component.onCheckChanged("va", true, "va");
component.onCheckChanged("dc", true, "dc");
// dc appears before va in the options list, so it should be
// inserted before va even though it was checked second
expect(component.model).toEqual(["dc", "va"]);
});

it("should remove an option from the model when unchecked", () => {
fixture.detectChanges();
component.writeValue(["dc", "ma"]);
component.onCheckChanged("dc", false, "dc");
expect(component.model).toEqual(["ma"]);
});

it("should emit modelChange and optionSelected when an option changes", () => {
fixture.detectChanges();
let emittedModel: unknown;
let emittedSelection: unknown;
component.modelChange.subscribe((val) => (emittedModel = val));
component.optionSelected.subscribe((val) => (emittedSelection = val));

component.onCheckChanged("dc", true, "dc");

expect(emittedModel).toEqual(["dc"]);
expect(emittedSelection).toEqual({
model: ["dc"],
selected: "dc",
id: "dc",
});
});

it("should select all options when the select-all checkbox is checked", () => {
component.hasSelectAll = true;
fixture.detectChanges();
component.onSelectAllChange(true);
expect(component.model).toEqual(["dc", "ma", "va"]);
});

it("should clear all options when the select-all checkbox is unchecked", () => {
component.hasSelectAll = true;
fixture.detectChanges();
component.writeValue(["dc", "ma", "va"]);
component.onSelectAllChange(false);
expect(component.model).toEqual([]);
});

it("should not count disabled options as active options", () => {
component.options = [
{ value: "dc", label: "Washington DC", name: "dc" },
{ value: "ma", label: "Maryland", name: "ma", disabled: true },
];
fixture.detectChanges();
expect(component.activeOptions).toBe(1);
});

it("should not allow a disabled option to remain selected via setModelValue", () => {
component.options = [
{ value: "dc", label: "Washington DC", name: "dc" },
{ value: "ma", label: "Maryland", name: "ma", disabled: true },
];
fixture.detectChanges();
component.setModelValue(["dc", "ma"]);
expect(component.model).toEqual(["dc"]);
});

it("should derive the select-all label from the id when set", () => {
component.id = "my-id";
fixture.detectChanges();
expect(component.checkAllLabelOrId()).toBe("all-my-id");
});

it("should derive the select-all label from the label when no id is set", () => {
component.id = undefined;
fixture.detectChanges();
expect(component.checkAllLabelOrId()).toBe("all-Pick a state");
});

it("should format errors from a form control on init and on value changes", () => {
const control = new FormControl([]);
component.control = control;
fixture.detectChanges();
component.ngOnInit();

expect(() => control.setValue(["dc"])).not.toThrow();
});

it("should show a hint message", () => {
const hint = "Life pro tip: eat vegetables";
component.hint = hint;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain(hint);
});

it("should show an error message", () => {
const errorMessage = "Uh-oh, something went wrong";
component.errorMessage = errorMessage;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain(errorMessage);
});

it("should show a label", () => {
const labelText = "Pick from the following options";
component.label = labelText;
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain(labelText);
});
});
});
129 changes: 126 additions & 3 deletions src/ui-kit/form-controls/date-time/date-time.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { TestBed } from "@angular/core/testing";
import { FormsModule } from "@angular/forms";
import { FormsModule, FormControl } from "@angular/forms";

// Load the implementations that should be tested
import { SamDateTimeComponent } from "./date-time.component";
import { SamDateComponent } from "../date/date.component";
import { SamTimeComponent } from "../time/time.component";
import { SamUIKitModule } from "../../index";
import { SamFormService } from "../../form-service";
import { SamWrapperModule } from "../../wrappers";

describe("The Sam Date Time component", () => {
let component: SamDateTimeComponent;
let fixture: any;

// provide our implementations or mocks to the dependency injector
beforeEach(() => {
TestBed.configureTestingModule({
imports: [SamWrapperModule, FormsModule],
Expand All @@ -25,9 +23,134 @@ describe("The Sam Date Time component", () => {
component = fixture.componentInstance;
component.value = "2016-12-31T12:01";
component.name = "test";
fixture.detectChanges();
});

it("Should compile", function () {
expect(true).toBe(true);
});

it("should throw a 508-compliance error when no name is provided", () => {
component.name = undefined;
expect(() => component.ngOnInit()).toThrowError(/508 compliance/);
});

it("should parse an initial value into date and time parts", () => {
component.writeValue("2016-12-31T12:01");
expect(component.date).toBe("2016-12-31");
expect(component.time).toBe("12:01");
});

it("should reset date and time parts when written a falsy value", () => {
component.writeValue("2016-12-31T12:01");
component.writeValue(undefined);
expect(component.value).toBe("");
expect(component.date).toBe("");
expect(component.time).toBe("");
});

it("should log an error and leave date/time unset for an unparsable value", () => {
const errorSpy = vi
.spyOn(console, "error")
.mockImplementation(() => undefined);
component.date = undefined;
component.time = undefined;
component.value = "not a real date";
component.parseValueString();
expect(errorSpy).toHaveBeenCalledWith(
"[value] for sam-date-time is invalid"
);
errorSpy.mockRestore();
});

it("should emit the combined value through registered onChange", () => {
let emitted: string;
component.registerOnChange((val) => (emitted = val));
component.emitChanges("2020-01-01T10:00");
expect(component.value).toBe("2020-01-01T10:00");
expect(emitted).toBe("2020-01-01T10:00");
});

it("should not throw when emitting changes without a registered onChange", () => {
component.onChange = undefined;
expect(() => component.emitChanges("2020-01-01T10:00")).not.toThrow();
});

it("should emit undefined when both date and time inputs are empty", () => {
let emitted: string | undefined = "not-called";
component.registerOnChange((val) => (emitted = val));
vi.spyOn(component.dateComponent, "isEmptyField").mockReturnValue(true);
vi.spyOn(component.timeComponent, "isEmptyField").mockReturnValue(true);

component.onInputChange();

expect(emitted).toBeUndefined();
});

it("should emit the combined date and time when both inputs are valid", () => {
let emitted: string | undefined;
component.registerOnChange((val) => (emitted = val));
vi.spyOn(component.dateComponent, "isEmptyField").mockReturnValue(false);
vi.spyOn(component.timeComponent, "isEmptyField").mockReturnValue(false);
vi.spyOn(component.dateComponent, "isValid").mockReturnValue(true);
vi.spyOn(component.timeComponent, "isValid").mockReturnValue(true);
component.date = "2020-01-01";
component.time = "10:00";

component.onInputChange();

expect(emitted).toBe("2020-01-01T10:00");
});

it("should emit 'Invalid Date Time' when the inputs are non-empty but invalid", () => {
let emitted: string | undefined;
component.registerOnChange((val) => (emitted = val));
vi.spyOn(component.dateComponent, "isEmptyField").mockReturnValue(false);
vi.spyOn(component.timeComponent, "isEmptyField").mockReturnValue(false);
vi.spyOn(component.dateComponent, "isValid").mockReturnValue(false);
vi.spyOn(component.timeComponent, "isValid").mockReturnValue(true);

component.onInputChange();

expect(emitted).toBe("Invalid Date Time");
});

it("should move focus to the time input's hour field on date blur", () => {
const focusSpy = vi.fn();
component.timeComponent.hourV = { nativeElement: { focus: focusSpy } };

component.dateBlur();

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

it("should clear the date and time inputs on resetInput", () => {
component.date = "2020-01-01";
component.time = "10:00";
component.resetInput();
expect(component.date).toBe("");
expect(component.time).toBe("");
});

it("should wire up a form control and format errors on status change without the form service", () => {
const control = new FormControl("");
component.control = control;
component.useFormService = false;

expect(() => {
component.ngOnInit();
control.setValue("changed");
}).not.toThrow();
});

it("should format errors through the SamFormService when useFormService is set", () => {
const formService = TestBed.inject(SamFormService);
const control = new FormControl("");
component.control = control;
component.useFormService = true;
component.ngOnInit();

expect(() => formService.fireSubmit(control.root)).not.toThrow();
expect(() => formService.fireReset(control.root)).not.toThrow();
});
});
35 changes: 35 additions & 0 deletions src/ui-kit/form-controls/number/number.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,40 @@ describe("The Sam Number component", () => {
fixture.detectChanges();
expect(component.value).toBe(11);
});

it("should format errors through the SamFormService when useFormService is set", () => {
const formService = TestBed.inject(SamFormService);
const c = new FormControl("");
component.name = "test-name";
component.control = c;
component.useFormService = true;
component.ngOnInit();
component.ngAfterViewInit();

expect(() => formService.fireSubmit(c.root)).not.toThrow();
expect(() => formService.fireReset(c.root)).not.toThrow();
});

it("should prevent invalid keys like 'e', '-', and '+'", () => {
fixture.detectChanges();
const preventDefault = vi.fn();
component.keyDownHandler({ key: "e", preventDefault });
expect(preventDefault).toHaveBeenCalled();

preventDefault.mockClear();
component.keyDownHandler({ key: "-", preventDefault });
expect(preventDefault).toHaveBeenCalled();

preventDefault.mockClear();
component.keyDownHandler({ key: "+", preventDefault });
expect(preventDefault).toHaveBeenCalled();
});
Comment thread
Copilot marked this conversation as resolved.

it("should allow valid numeric keys", () => {
fixture.detectChanges();
const preventDefault = vi.fn();
component.keyDownHandler({ key: "5", preventDefault });
expect(preventDefault).not.toHaveBeenCalled();
});
});
});
12 changes: 12 additions & 0 deletions src/ui-kit/form-controls/radiobutton/radiobutton.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,17 @@ describe("The Sam Radio Buttons component", () => {
component.name = "test-name";
component.ngOnInit();
});

it("should format errors when the control's value changes", () => {
const c = new FormControl("", () => {
return undefined;
});
component.control = c;
component.name = "test-name";
component.ngOnInit();
component.ngAfterViewInit();

expect(() => c.setValue("va")).not.toThrow();
});
});
});
Loading
Loading