Skip to content

Commit

Permalink
Merge pull request #1134 from christophercr/feature/input-mask-direct…
Browse files Browse the repository at this point in the history
…ives

feat(stark-ui): implement directives for email, number and timestamp masks
  • Loading branch information
SuperITMan authored Feb 15, 2019
2 parents 58dec6c + 3452894 commit 6446e3c
Show file tree
Hide file tree
Showing 29 changed files with 2,475 additions and 53 deletions.
1 change: 1 addition & 0 deletions packages/rollup.config.common-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const globals = {
"prismjs/components/prism-css-extras.min.js": "Prism.languages.css.selector",
"prismjs/components/prism-scss.min.js": "Prism.languages.scss",
"text-mask-core": "textMaskCore",
"text-mask-addons": "textMaskAddons",
uuid: "uuid",

rxjs: "rxjs",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export * from "./directives/email-mask.directive";
export * from "./directives/number-mask-config.intf";
export * from "./directives/number-mask.directive";
export * from "./directives/text-mask.constants";
export * from "./directives/text-mask.directive";
export * from "./directives/text-mask-config.intf";
export * from "./directives/timestamp-mask-config.intf";
export * from "./directives/timestamp-mask.directive";
export * from "./directives/timestamp-pipe.fn";
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
/* tslint:disable:completed-docs no-duplicate-string no-identical-functions no-big-function */
import { Component, DebugElement } from "@angular/core";
import { FormControl, FormsModule, ReactiveFormsModule } from "@angular/forms";
import { By } from "@angular/platform-browser";
import { ComponentFixture, fakeAsync, TestBed } from "@angular/core/testing";
import { Observer } from "rxjs";
import { StarkEmailMaskDirective } from "./email-mask.directive";

describe("EmailMaskDirective", () => {
let fixture: ComponentFixture<TestComponent>;
let hostComponent: TestComponent;
let inputElement: DebugElement;

@Component({
selector: "test-component",
template: getTemplate("[starkEmailMask]='emailMaskConfig'")
})
class TestComponent {
public emailMaskConfig: boolean;
public ngModelValue: string = "";
public formControl: FormControl = new FormControl("");
}

function getTemplate(emailMaskDirective: string): string {
return "<input " + "type='text' " + emailMaskDirective + ">";
}

function initializeComponentFixture(): void {
fixture = TestBed.createComponent(TestComponent);
hostComponent = fixture.componentInstance;
inputElement = fixture.debugElement.query(By.css("input"));
// trigger initial data binding
fixture.detectChanges();
}

function changeInputValue(inputDebugElement: DebugElement, value: string, eventType: string = "input"): void {
(<HTMLInputElement>inputDebugElement.nativeElement).value = value;

// more verbose way to create and trigger an event (the only way it works in IE)
// https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
const ev: Event = document.createEvent("Event");
ev.initEvent(eventType, true, true);
(<HTMLInputElement>inputDebugElement.nativeElement).dispatchEvent(ev);
}

// Inject module dependencies
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [StarkEmailMaskDirective, TestComponent],
imports: [FormsModule, ReactiveFormsModule],
providers: []
});
});

describe("uncontrolled", () => {
beforeEach(fakeAsync(() => {
// compile template and css
return TestBed.compileComponents();
}));

beforeEach(() => {
initializeComponentFixture();
});

it("should render the appropriate content", () => {
expect(inputElement.attributes["ng-reflect-mask-config"]).toBeDefined(); // starkEmailMask directive
});

it("should update the input value and show the mask only when a valid event is triggered in the input field", () => {
// Angular2 text-mask directive handles only the "input" event
const validEvents: string[] = ["input"];

for (const eventType of validEvents) {
changeInputValue(inputElement, "");
fixture.detectChanges();
expect(inputElement.nativeElement.value).toBe("");

changeInputValue(inputElement, "my-email@", eventType);
fixture.detectChanges();

expect(inputElement.nativeElement.value).toBe("my-email@ .");
}

const invalidEvents: string[] = ["blur", "keyup", "change", "focus", "keydown", "keypress", "click"];

for (const eventType of invalidEvents) {
changeInputValue(inputElement, "");
fixture.detectChanges();
expect(inputElement.nativeElement.value).toBe("");

changeInputValue(inputElement, "my-email", eventType);
fixture.detectChanges();

expect(inputElement.nativeElement.value).toBe("my-email"); // no mask shown
}
});

it("should prevent invalid values to be entered in the input field when the value is changed manually", () => {
const invalidValues: string[] = ["@@", "@.a.", " @ .", "[email protected]@."];

for (const value of invalidValues) {
changeInputValue(inputElement, value);
fixture.detectChanges();

expect(inputElement.nativeElement.value).toBe("");
}
});

it("should remove the mask only when the config is set to false", () => {
changeInputValue(inputElement, "my-email@");
fixture.detectChanges();

expect(inputElement.nativeElement.value).toBe("my-email@ .");

hostComponent.emailMaskConfig = <any>undefined;
fixture.detectChanges();

changeInputValue(inputElement, "[email protected]@.");
fixture.detectChanges();

expect(inputElement.nativeElement.value).toBe("my-email@ ."); // the mask is enabled by default

hostComponent.emailMaskConfig = <any>""; // use case when the directive is used with no inputs: <input type='text' starkEmailMask>
fixture.detectChanges();

changeInputValue(inputElement, "[email protected]@.");
fixture.detectChanges();

expect(inputElement.nativeElement.value).toBe("my-email@ ."); // the mask is enabled by default

hostComponent.emailMaskConfig = false;
fixture.detectChanges();

changeInputValue(inputElement, "what@@.ever@.");
fixture.detectChanges();

expect(inputElement.nativeElement.value).toBe("what@@.ever@."); // no mask at all
});
});

describe("with ngModel", () => {
beforeEach(fakeAsync(() => {
const newTemplate: string = getTemplate("[(ngModel)]='ngModelValue' [starkEmailMask]='emailMaskConfig'");

TestBed.overrideTemplate(TestComponent, newTemplate);

// compile template and css
return TestBed.compileComponents();
}));

beforeEach(() => {
initializeComponentFixture();
});

it("should render the appropriate content", () => {
expect(inputElement.attributes["ng-reflect-mask-config"]).toBeDefined(); // starkEmailMask directive
});

it("should update the input value and show the mask only when a valid event is triggered in the input field", () => {
// Angular2 text-mask directive handles only the "input" event
const validEvents: string[] = ["input"];

for (const eventType of validEvents) {
changeInputValue(inputElement, "");
fixture.detectChanges();
expect(hostComponent.ngModelValue).toBe("");

changeInputValue(inputElement, "my-email@", eventType);
fixture.detectChanges();

expect(hostComponent.ngModelValue).toBe("my-email@ .");
}

const invalidEvents: string[] = ["blur", "keyup", "change", "focus", "keydown", "keypress", "click"];

for (const eventType of invalidEvents) {
changeInputValue(inputElement, "");
fixture.detectChanges();
expect(hostComponent.ngModelValue).toBe("");

changeInputValue(inputElement, "my-email@", eventType);
fixture.detectChanges();

// IMPORTANT: the ngModel is not changed with invalid events, just with "input" events
expect(hostComponent.ngModelValue).toBe(""); // no mask shown
}
});

it("should prevent invalid values to be entered in the input field when the value is changed manually", () => {
const invalidValues: string[] = ["@@", "@.a.", " @ .", "[email protected]@."];

for (const value of invalidValues) {
changeInputValue(inputElement, value);
fixture.detectChanges();

expect(hostComponent.ngModelValue).toBe("");
}
});

it("should remove the mask only when the config is set to false", () => {
changeInputValue(inputElement, "my-email@");
fixture.detectChanges();

expect(hostComponent.ngModelValue).toBe("my-email@ .");

hostComponent.emailMaskConfig = <any>undefined;
fixture.detectChanges();

changeInputValue(inputElement, "[email protected]@.");
fixture.detectChanges();

expect(hostComponent.ngModelValue).toBe("my-email@ ."); // the mask is enabled by default

hostComponent.emailMaskConfig = <any>""; // use case when the directive is used with no inputs: <input type='text' [(ngModel)]='ngModelValue' starkEmailMask>
fixture.detectChanges();

changeInputValue(inputElement, "[email protected]@.");
fixture.detectChanges();

expect(hostComponent.ngModelValue).toBe("my-email@ ."); // the mask is enabled by default

hostComponent.emailMaskConfig = false;
fixture.detectChanges();

changeInputValue(inputElement, "what@@.ever@.");
fixture.detectChanges();

expect(hostComponent.ngModelValue).toBe("what@@.ever@."); // no mask at all
});
});

describe("with FormControl", () => {
let mockValueChangeObserver: jasmine.SpyObj<Observer<any>>;

beforeEach(fakeAsync(() => {
const newTemplate: string = getTemplate("[formControl]='formControl' [starkEmailMask]='emailMaskConfig'");

TestBed.overrideTemplate(TestComponent, newTemplate);

// compile template and css
return TestBed.compileComponents();
}));

beforeEach(() => {
initializeComponentFixture();

mockValueChangeObserver = jasmine.createSpyObj<Observer<any>>("observerSpy", ["next", "error", "complete"]);
hostComponent.formControl.valueChanges.subscribe(mockValueChangeObserver);
});

it("should render the appropriate content", () => {
expect(inputElement.attributes["ng-reflect-mask-config"]).toBeDefined(); // starkEmailMask directive
});

it("should update the input value and show the mask only when a valid event is triggered in the input field", () => {
// Angular2 text-mask directive handles only the "input" event
const validEvents: string[] = ["input"];

for (const eventType of validEvents) {
changeInputValue(inputElement, "");
fixture.detectChanges();
expect(hostComponent.formControl.value).toBe("");
expect(mockValueChangeObserver.next).toHaveBeenCalledTimes(1);

mockValueChangeObserver.next.calls.reset();
changeInputValue(inputElement, "my-email@", eventType);
fixture.detectChanges();

expect(hostComponent.formControl.value).toBe("my-email@ .");
expect(mockValueChangeObserver.next).toHaveBeenCalledTimes(1);
expect(mockValueChangeObserver.error).not.toHaveBeenCalled();
expect(mockValueChangeObserver.complete).not.toHaveBeenCalled();
}

mockValueChangeObserver.next.calls.reset();
const invalidEvents: string[] = ["blur", "keyup", "change", "focus", "keydown", "keypress", "click"];

for (const eventType of invalidEvents) {
changeInputValue(inputElement, "");
fixture.detectChanges();
expect(hostComponent.formControl.value).toBe("");
expect(mockValueChangeObserver.next).toHaveBeenCalledTimes(1);

mockValueChangeObserver.next.calls.reset();
changeInputValue(inputElement, "my-email@", eventType);
fixture.detectChanges();

// IMPORTANT: the formControl is not changed with invalid events, just with "input" events
expect(hostComponent.formControl.value).toBe(""); // no mask shown
expect(mockValueChangeObserver.next).not.toHaveBeenCalled();
expect(mockValueChangeObserver.error).not.toHaveBeenCalled();
expect(mockValueChangeObserver.complete).not.toHaveBeenCalled();
}
});

it("should prevent invalid values to be entered in the input field when the value is changed manually", () => {
const invalidValues: string[] = ["@@", "@.a.", " @ .", "[email protected]@."];

for (const value of invalidValues) {
mockValueChangeObserver.next.calls.reset();
changeInputValue(inputElement, value);
fixture.detectChanges();

expect(hostComponent.formControl.value).toBe("");
expect(mockValueChangeObserver.next).toHaveBeenCalledTimes(1);
expect(mockValueChangeObserver.error).not.toHaveBeenCalled();
expect(mockValueChangeObserver.complete).not.toHaveBeenCalled();
}
});

it("should remove the mask when the config is undefined", () => {
changeInputValue(inputElement, "my-email@");
fixture.detectChanges();

expect(hostComponent.formControl.value).toBe("my-email@ .");
expect(mockValueChangeObserver.next).toHaveBeenCalledTimes(1);

mockValueChangeObserver.next.calls.reset();
hostComponent.emailMaskConfig = <any>undefined;
fixture.detectChanges();
expect(mockValueChangeObserver.next).not.toHaveBeenCalled(); // no value change, the mask is enabled by default

mockValueChangeObserver.next.calls.reset();
changeInputValue(inputElement, "[email protected]@.");
fixture.detectChanges();

expect(hostComponent.formControl.value).toBe("my-email@ ."); // the mask is enabled by default
expect(mockValueChangeObserver.next).toHaveBeenCalledTimes(1);

mockValueChangeObserver.next.calls.reset();
hostComponent.emailMaskConfig = <any>""; // use case when the directive is used with no inputs: <input type='text' [formControl]='formControl' starkEmailMask>
fixture.detectChanges();
expect(mockValueChangeObserver.next).not.toHaveBeenCalled(); // no value change, the mask is enabled by default

mockValueChangeObserver.next.calls.reset();
changeInputValue(inputElement, "[email protected]@.");
fixture.detectChanges();

expect(hostComponent.formControl.value).toBe("my-email@ ."); // the mask is enabled by default
expect(mockValueChangeObserver.next).toHaveBeenCalledTimes(1);

mockValueChangeObserver.next.calls.reset();
hostComponent.emailMaskConfig = false;
fixture.detectChanges();
expect(mockValueChangeObserver.next).not.toHaveBeenCalled(); // no value change, the mask was just disabled

mockValueChangeObserver.next.calls.reset();
changeInputValue(inputElement, "what@@.ever@.");
fixture.detectChanges();

expect(hostComponent.formControl.value).toBe("what@@.ever@."); // no mask at all
expect(mockValueChangeObserver.next).toHaveBeenCalledTimes(1);
expect(mockValueChangeObserver.error).not.toHaveBeenCalled();
expect(mockValueChangeObserver.complete).not.toHaveBeenCalled();
});
});
});
Loading

0 comments on commit 6446e3c

Please sign in to comment.