Skip to content

Commit

Permalink
feat(stark-ui): add stark form util
Browse files Browse the repository at this point in the history
  • Loading branch information
SuperITMan committed Dec 29, 2018
1 parent 0b4c0e0 commit 1f94d93
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/stark-ui/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./util/component";
export * from "./util/dom";
export * from "./util/form";
1 change: 1 addition & 0 deletions packages/stark-ui/src/util/form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./form/form.util";
209 changes: 209 additions & 0 deletions packages/stark-ui/src/util/form/form.util.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { StarkFormControlState, StarkFormUtil } from "./form.util";
import { FormControl, FormGroup /*, Validators*/ } from "@angular/forms";

// import Spy = jasmine.Spy;

const _startCase: Function = require("lodash/startCase");

describe("Util: FormUtil", () => {
const formItemStates: string[] = ["untouched", "touched", "pristine", "dirty"];

let mockFormGroup: FormGroup;
let mockFormControls: FormControl[];

function getMockFormControl(name: string): FormControl {
return jasmine.createSpyObj<FormControl>(name, [
"value",
"setValue",
"markAsUntouched",
"markAsTouched",
"markAsPristine",
"markAsDirty"
]);
}

function assertFormControl(formItem: FormControl, newState?: string): void {
if (newState) {
const newStateSetter: string = "markAs" + _startCase(newState);
expect(formItem[newStateSetter]).toHaveBeenCalledTimes(1);
}

// check that the setters for other states where not called
const nonUsedStates: string[] = formItemStates.filter((state: string) => state !== newState);
for (const nonUsedState of nonUsedStates) {
const nonUsedStateSetter: string = "markAs" + _startCase(nonUsedState);
expect(formItem[nonUsedStateSetter]).not.toHaveBeenCalled();
}
}

beforeEach(() => {
mockFormControls = [getMockFormControl("item1"), getMockFormControl("item2")];

mockFormGroup = <any>{
controls: {
formControl0: mockFormControls[0],
formControl1: mockFormControls[1]
}
};
});

describe("setFormChildControlsState", () => {
it("should call markAsTouched() on every form control if state to be set is 'touched'", () => {
const stateToBeSet: StarkFormControlState = "touched";

StarkFormUtil.setFormChildControlsState(mockFormGroup, [stateToBeSet]);

for (const formControl of mockFormControls) {
assertFormControl(formControl, stateToBeSet);
}
});

it("should call markAsUntouched() on every form control if state to be set is 'untouched'", () => {
const stateToBeSet: StarkFormControlState = "untouched";

StarkFormUtil.setFormChildControlsState(mockFormGroup, [stateToBeSet]);

for (const formControl of mockFormControls) {
assertFormControl(formControl, stateToBeSet);
}
});

it("should call markAsPristine() on every form control if state to be set is 'pristine'", () => {
const stateToBeSet: StarkFormControlState = "pristine";

StarkFormUtil.setFormChildControlsState(mockFormGroup, [stateToBeSet]);

for (const formControl of mockFormControls) {
assertFormControl(formControl, stateToBeSet);
}
});

it("should call markAsDirty() on every form control if state to be set is 'dirty'", () => {
const stateToBeSet: StarkFormControlState = "dirty";

StarkFormUtil.setFormChildControlsState(mockFormGroup, [stateToBeSet]);

for (const formControl of mockFormControls) {
assertFormControl(formControl, stateToBeSet);
}
});

it("should NOT call any method on any form control if state to be set is unknown", () => {
const stateToBeSet: any = "unknown state";

StarkFormUtil.setFormChildControlsState(mockFormGroup, [stateToBeSet]);

for (const formControl of mockFormControls) {
assertFormControl(formControl);
}
});
});

describe("setFormControlState", () => {
let formItem: FormControl;

beforeEach(() => {
formItem = getMockFormControl("dummy item");
});

it("should call markAsTouched() on the given form item if state to be set is 'touched'", () => {
const stateToBeSet: StarkFormControlState = "touched";

StarkFormUtil.setFormControlState(formItem, [stateToBeSet]);

assertFormControl(formItem, stateToBeSet);
});

it("should call markAsUntouched() on the given form item if state to be set is 'untouched'", () => {
const stateToBeSet: StarkFormControlState = "untouched";

StarkFormUtil.setFormControlState(formItem, [stateToBeSet]);

assertFormControl(formItem, stateToBeSet);
});

it("should call markAsPristine() on the given form item if state to be set is 'pristine'", () => {
const stateToBeSet: StarkFormControlState = "pristine";

StarkFormUtil.setFormControlState(formItem, [stateToBeSet]);

assertFormControl(formItem, stateToBeSet);
});

it("should call markAsDirty() on the given form item if state to be set is 'dirty'", () => {
const stateToBeSet: StarkFormControlState = "dirty";

StarkFormUtil.setFormControlState(formItem, [stateToBeSet]);

assertFormControl(formItem, stateToBeSet);
});

it("should NOT call any method on the given form item if state to be set is unknown", () => {
const stateToBeSet: any = "unknown state";

StarkFormUtil.setFormControlState(formItem, [stateToBeSet]);

assertFormControl(formItem);
});
});

describe("isFormGroupValid", () => {
it("should set form state to 'pristine' and return TRUE if the form is valid", () => {
spyOn(StarkFormUtil, "setFormChildControlsState");

const result: boolean = StarkFormUtil.isFormGroupValid(mockFormGroup);

expect(result).toBe(true);
expect(StarkFormUtil.setFormChildControlsState).toHaveBeenCalledTimes(1);
expect(StarkFormUtil.setFormChildControlsState).toHaveBeenCalledWith(mockFormGroup, ["pristine"]);
});

it("should set form state to 'touched' and return FALSE if the form is NOT valid", () => {
mockFormGroup = <any>{
controls: {
formControl0: mockFormControls[0],
formControl1: mockFormControls[1]
},
invalid: true
};

spyOn(StarkFormUtil, "setFormChildControlsState");

const result: boolean = StarkFormUtil.isFormGroupValid(mockFormGroup);

expect(result).toBe(false);
expect(StarkFormUtil.setFormChildControlsState).toHaveBeenCalledTimes(1);
expect(StarkFormUtil.setFormChildControlsState).toHaveBeenCalledWith(mockFormGroup, ["touched"]);
});
});

describe("isFormGroup", () => {
it("should return TRUE if the form is an instance of an IFormController", () => {
const result: boolean = StarkFormUtil.isFormGroup(mockFormGroup);

expect(result).toBe(true);
});

it("should return FALSE if the form is NOT an instance of an IFormController", () => {
const result: boolean = StarkFormUtil.isFormGroup("this is not a form");

expect(result).toBe(false);
});
});

describe("isFormControl", () => {
it("should return TRUE if the form item is an instance of an FormControl", () => {
const mockFormItem: FormControl = getMockFormControl("dummy item");

const result: boolean = StarkFormUtil.isFormControl(mockFormItem);

expect(result).toBe(true);
});

it("should return FALSE if the form item is NOT an instance of an FormControl", () => {
const result: boolean = StarkFormUtil.isFormControl("this is not a form item");

expect(result).toBe(false);
});
});
});
122 changes: 122 additions & 0 deletions packages/stark-ui/src/util/form/form.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { AbstractControl, FormControl, FormGroup } from "@angular/forms";

export type StarkFormControlState = "untouched" | "touched" | "pristine" | "dirty";
export type StarkFormState = "untouched" | "pristine" | "submitted" | "dirty";

export class StarkFormUtil {
/**
* Set all the fields of the form group to the given state(s)
* @param formGroup - Angular form group object
* @param statesToBeSet - States to be set to the different controls in the form
*/
public static setFormChildControlsState(formGroup: FormGroup, statesToBeSet: StarkFormControlState[]): void {
// Verifying it is indeed an Angular FormController
if (StarkFormUtil.isFormGroup(formGroup)) {
for (const key of Object.keys(formGroup.controls)) {
// filtering just the ngModel child objects of the form
const formControl: AbstractControl = formGroup.controls[key];
if (StarkFormUtil.isFormControl(formControl)) {
this.setFormControlState(formControl, statesToBeSet);
}
}
}
}

/**
* Set the specified form control to the given state(s)
* @param formControl - Angular form control object contained in an Angular form group
* @param statesToBeSet - States to be set to the form control
*/
public static setFormControlState(formControl: FormControl, statesToBeSet: StarkFormControlState[]): void {
for (const formControlState of statesToBeSet) {
switch (formControlState) {
case "untouched":
// control has not lost focus yet
formControl.markAsUntouched();
break;
case "touched":
// control has lost focus.
formControl.markAsTouched();
break;
case "pristine":
// user has not interacted with the form yet.
formControl.markAsPristine();
break;
case "dirty":
// user has already interacted with the form
formControl.markAsDirty();
break;
default:
break;
}
}
}

/**
* Set the form to the given state(s). Possible states: "untouched", "pristine", "dirty", "submitted"
* @param formGroup - Angular form group object
* @param statesToBeSet - States to be set to the form
*/
public static setFormGroupState(formGroup: FormGroup, statesToBeSet: StarkFormState[]): void {
// Verifying it is indeed an Angular FormController
if (StarkFormUtil.isFormGroup(formGroup)) {
for (const formControlState of statesToBeSet) {
switch (formControlState) {
case "untouched":
// control has not lost focus yet
formGroup.markAsUntouched();
break;
case "pristine":
// user has not interacted with the form yet.
formGroup.markAsPristine();
break;
case "dirty":
// user has already interacted with the form
formGroup.markAsDirty();
break;
case "submitted":
// user has already interacted with the form
// FIXME Find alternative to $setSubmitted with ReactiveForm if needed
// formGroup.$setSubmitted();
break;
default:
break;
}
}
}
}

/**
* Check whether the form group is valid (all fields are valid).
* If valid, all the form fields will be set to their "pristine" state, otherwise to their "touched" state
* (calling setFormControlState function).
* @param formGroup - Angular form group object
*/
public static isFormGroupValid(formGroup: FormGroup): boolean {
if (formGroup.invalid) {
StarkFormUtil.setFormChildControlsState(formGroup, ["touched"]);
return false;
} else {
StarkFormUtil.setFormChildControlsState(formGroup, ["pristine"]);
return true;
}
}

// type guard
/**
* Check that the given form is an actual instance of an Angular form.
* @param formGroup - Angular form object
*/
public static isFormGroup(formGroup: any): formGroup is FormGroup {
return typeof formGroup !== "undefined" && formGroup.hasOwnProperty("controls");
}

// type guard
/**
* Check that the given form field object is an actual instance of an Angular form field.
* @param formControl - Angular form field object
*/
public static isFormControl(formControl: any): formControl is FormControl {
return typeof formControl === "object" && typeof formControl["setValue"] !== "undefined";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<stark-dropdown
dropdownId="disabledDropdown"
[options]="[1, 2, 3, 4, 5, 6, 7]"
placeholder="Sorry, it's disabled"
[isDisabled]="true"
multiSelect
>
</stark-dropdown>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Component } from "@angular/core";

@Component({
selector: "demo-dropdown",
templateUrl: "./demo-dropdown.component.html"
})
export class DemoDropdownComponent {}

0 comments on commit 1f94d93

Please sign in to comment.