From d9b530cf15c0b7e052b6698d7bc5c939d7fa464a Mon Sep 17 00:00:00 2001 From: KFrederiksen Date: Fri, 18 Jan 2019 12:44:13 -0600 Subject: [PATCH 1/3] fix(DatePickerInput): Handle Keyboard Input Events --- .../elements/date-picker/DatePickerInput.ts | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts b/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts index fde64582d..372fea6fa 100644 --- a/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts +++ b/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts @@ -37,7 +37,17 @@ const DATE_VALUE_ACCESSOR = { selector: 'novo-date-picker-input', providers: [DATE_VALUE_ACCESSOR], template: ` - + @@ -49,6 +59,8 @@ export class NovoDatePickerInputElement implements OnInit, ControlValueAccessor public value: any; public formattedValue: string = ''; private userDefinedFormat: boolean; + private currentValue: string = ''; + private valueChanged: boolean = false; /** View -> model callback called when value changes */ _onChange: (value: any) => void = () => {}; @@ -121,34 +133,35 @@ export class NovoDatePickerInputElement implements OnInit, ControlValueAccessor /** END: Convenient Panel Methods. */ _handleKeydown(event: KeyboardEvent): void { - if ((event.keyCode === ESCAPE || event.keyCode === ENTER || event.keyCode === TAB) && this.panelOpen) { - this._handleEvent(event, true); - this.closePanel(); - event.stopPropagation(); + if (!this.panelOpen) { + this.openPanel(); } - } - - _handleInput(event: KeyboardEvent): void { - if (document.activeElement === event.target) { - this._handleEvent(event, false); + if (event.keyCode === ENTER || event.keyCode === TAB) { + let value = (event.target as HTMLInputElement).value; + this.formatDate(value, true); + if (event.keyCode === TAB) { + this.closePanel(); + } + } else if (event.keyCode === ESCAPE) { + this.formattedValue = this.currentValue; + this.closePanel(); } + event.stopPropagation(); } _handleBlur(event: FocusEvent): void { + if (this.currentValue !== this.formattedValue) { + this.formattedValue = this.currentValue; + } this.blurEvent.emit(event); } _handleFocus(event: FocusEvent): void { this.openPanel(); + this.currentValue = this.formattedValue; this.focusEvent.emit(event); } - _handleEvent(event: Event, blur: boolean): void { - let value = (event.target as HTMLInputElement).value; - this.formatDate(value, blur); - this.openPanel(); - } - protected formatDate(value: string, blur: boolean) { try { let [dateTimeValue, formatted] = this.dateFormatService.parseString(value, false, 'date'); @@ -205,6 +218,7 @@ export class NovoDatePickerInputElement implements OnInit, ControlValueAccessor if (this.value) { let test = this.formatDateValue(this.value); this.formattedValue = test; + this.currentValue = this.formattedValue; } } From d65e64e0aa6760bd8b9efd5d165602def16a2ad7 Mon Sep 17 00:00:00 2001 From: KFrederiksen Date: Wed, 23 Jan 2019 15:15:00 -0600 Subject: [PATCH 2/3] Change event to Pick style and add unit testing --- .../date-picker/DatePickerInput.spec.ts | 149 +++++++++++++++++- .../elements/date-picker/DatePickerInput.ts | 18 ++- 2 files changed, 160 insertions(+), 7 deletions(-) diff --git a/projects/novo-elements/src/elements/date-picker/DatePickerInput.spec.ts b/projects/novo-elements/src/elements/date-picker/DatePickerInput.spec.ts index fbc72a12b..15ec4ab37 100644 --- a/projects/novo-elements/src/elements/date-picker/DatePickerInput.spec.ts +++ b/projects/novo-elements/src/elements/date-picker/DatePickerInput.spec.ts @@ -1,12 +1,14 @@ // NG2 +import { EventEmitter } from '@angular/core'; import { TestBed, async } from '@angular/core/testing'; +import { TAB, ENTER, ESCAPE } from '@angular/cdk/keycodes'; // App import { NovoDatePickerInputElement } from './DatePickerInput'; import { NovoLabelService } from '../../services/novo-label-service'; import { NovoDatePickerModule } from './DatePicker.module'; import { DateFormatService } from '../../services/date-format/DateFormat'; -xdescribe('Elements: NovoDatePickerInputElement', () => { +describe('Elements: NovoDatePickerInputElement', () => { let fixture; let component; @@ -22,6 +24,151 @@ xdescribe('Elements: NovoDatePickerInputElement', () => { component = fixture.debugElement.componentInstance; })); + describe('Method: openPanel()', () => { + it('should open the Calendar picker panel if the field has not been disabled', () => { + //Arrange + component.disabled = false; + spyOn(component.overlay, 'openPanel'); + //Act + component.openPanel(); + //Assert + expect(component.overlay.openPanel).toHaveBeenCalled(); + }); + + it('should not open the Calendar picker panel if the field has been disabled', () => { + //Arrange + component.disabled = true; + spyOn(component.overlay, 'openPanel'); + //Act + component.openPanel(); + //Assert + expect(component.overlay.openPanel).not.toHaveBeenCalled(); + }); + }); + + describe('Method: _handleKeydown(event)', () => { + let keyboardEvent: Pick; + beforeEach(async () => { + keyboardEvent = { keyCode: undefined, target: new EventTarget(), stopPropagation: () => {} }; + spyOn(component, 'openPanel').and.callFake(() => {}); + spyOn(component, 'closePanel').and.callFake(() => {}); + spyOn(component, 'formatDate'); + spyOn(keyboardEvent, 'stopPropagation'); + }); + + it('should call formatDate if the Enter key has been pressed', () => { + //Arrange + const enterEvent: Pick = { + ...keyboardEvent, + keyCode: ENTER, + }; + //Act + component._handleKeydown(enterEvent); + //Assert + expect(component.formatDate).toHaveBeenCalled(); + expect(enterEvent.stopPropagation).toHaveBeenCalled(); + }); + + it('should call formatDate if the Tab key has been pressed', () => { + //Arrange + const tabEvent: Pick = { + ...keyboardEvent, + keyCode: TAB, + }; + //Act + component._handleKeydown(tabEvent); + //Assert + expect(component.formatDate).toHaveBeenCalled(); + expect(tabEvent.stopPropagation).toHaveBeenCalled(); + }); + + it('should call closePanel if the Tab key has been pressed', () => { + //Arrange + const tabEvent: Pick = { + ...keyboardEvent, + keyCode: TAB, + }; + //Act + component._handleKeydown(tabEvent); + //Assert + expect(component.closePanel).toHaveBeenCalled(); + expect(tabEvent.stopPropagation).toHaveBeenCalled(); + }); + + it('should set value that appears in the field to the value it had before edits were made if the Escape key has been pressed', () => { + //Arrange + const escapeEvent: Pick = { + ...keyboardEvent, + keyCode: ESCAPE, + }; + component.formattedValue = 'Test1'; + component.currentValue = 'Test2'; + //Act + component._handleKeydown(escapeEvent); + //Assert + expect(component.formattedValue).toBe('Test2'); + expect(escapeEvent.stopPropagation).toHaveBeenCalled(); + }); + + it('should call closePanel if the Escape key has been pressed', () => { + //Arrange + const escapeEvent: Pick = { + ...keyboardEvent, + keyCode: ESCAPE, + }; + //Act + component._handleKeydown(escapeEvent); + //Assert + expect(component.closePanel).toHaveBeenCalled(); + expect(escapeEvent.stopPropagation).toHaveBeenCalled(); + }); + }); + + describe('Method: _handleBlur()', () => { + let blurEvent: Pick = { type: 'blur' }; + it('should reset the value in the field if it is different from the original', () => { + //Arrange + spyOn(component.blurEvent, 'emit'); + component.currentValue = 'Test1'; + component.formattedValue = 'Test2'; + //Act + component._handleBlur(blurEvent); + //Assert + expect(component.formattedValue).toBe('Test1'); + }); + }); + + describe('Method: _handleFocus()', () => { + let focusEvent: Pick = { type: 'focus' }; + + it('should set the current value to that which is currently in the field upon focusing', () => { + //Arrange + spyOn(component.focusEvent, 'emit'); + spyOn(component, 'openPanel').and.callFake(() => {}); + component.currentValue = 'Test1'; + component.formattedValue = 'Test2'; + //Act + component._handleFocus(focusEvent); + //Assert + expect(component.currentValue).toBe('Test2'); + }); + }); + + describe('Method: _setFormValue()', () => { + it('should set the currentValue and formattedValue to the same upon saving when this.value is defined', () => { + //Arrange + component.value = '06/07/2008'; + component.formattedValue = ''; + component.currentValue = ''; + spyOn(component, 'formatDateValue').and.returnValue(component.value); + //Act + component._setFormValue(component.value); + //Assert + expect(component.currentValue).toBe('06/07/2008'); + expect(component.formattedValue).toBe('06/07/2008'); + }); + }); + describe('Method: formatDate()', () => { it('should call parseString from the dateFormatService and then dispatchOnChange.', () => { spyOn(component.dateFormatService, 'parseString').and.callThrough(); diff --git a/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts b/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts index 372fea6fa..4b5c53ada 100644 --- a/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts +++ b/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts @@ -124,29 +124,35 @@ export class NovoDatePickerInputElement implements OnInit, ControlValueAccessor this.overlay.openPanel(); } } + closePanel(): void { this.overlay.closePanel(); } + get panelOpen(): boolean { return this.overlay && this.overlay.panelOpen; } /** END: Convenient Panel Methods. */ - _handleKeydown(event: KeyboardEvent): void { + _handleKeydown({ keyCode, target, stopPropagation }: Pick): void { if (!this.panelOpen) { this.openPanel(); } - if (event.keyCode === ENTER || event.keyCode === TAB) { - let value = (event.target as HTMLInputElement).value; + if (keyCode === ENTER || keyCode === TAB) { + let value = this._getHTMLInputElementValue(target); this.formatDate(value, true); - if (event.keyCode === TAB) { + if (keyCode === TAB) { this.closePanel(); } - } else if (event.keyCode === ESCAPE) { + } else if (keyCode === ESCAPE) { this.formattedValue = this.currentValue; this.closePanel(); } - event.stopPropagation(); + stopPropagation(); + } + + _getHTMLInputElementValue(target: EventTarget): string { + return (target as HTMLInputElement).value; } _handleBlur(event: FocusEvent): void { From 71c80561c572e4578a69339a49b4ab006db1e78e Mon Sep 17 00:00:00 2001 From: KFrederiksen Date: Wed, 23 Jan 2019 15:51:12 -0600 Subject: [PATCH 3/3] Add formatting for Travis CLI --- .../date-picker/DatePickerInput.spec.ts | 60 +++++++++---------- .../elements/date-picker/DatePickerInput.ts | 20 +++---- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/projects/novo-elements/src/elements/date-picker/DatePickerInput.spec.ts b/projects/novo-elements/src/elements/date-picker/DatePickerInput.spec.ts index 15ec4ab37..a0f5c5093 100644 --- a/projects/novo-elements/src/elements/date-picker/DatePickerInput.spec.ts +++ b/projects/novo-elements/src/elements/date-picker/DatePickerInput.spec.ts @@ -26,22 +26,22 @@ describe('Elements: NovoDatePickerInputElement', () => { describe('Method: openPanel()', () => { it('should open the Calendar picker panel if the field has not been disabled', () => { - //Arrange + // Arrange component.disabled = false; spyOn(component.overlay, 'openPanel'); - //Act + // Act component.openPanel(); - //Assert + // Assert expect(component.overlay.openPanel).toHaveBeenCalled(); }); it('should not open the Calendar picker panel if the field has been disabled', () => { - //Arrange + // Arrange component.disabled = true; spyOn(component.overlay, 'openPanel'); - //Act + // Act component.openPanel(); - //Assert + // Assert expect(component.overlay.openPanel).not.toHaveBeenCalled(); }); }); @@ -57,68 +57,68 @@ describe('Elements: NovoDatePickerInputElement', () => { }); it('should call formatDate if the Enter key has been pressed', () => { - //Arrange + // Arrange const enterEvent: Pick = { ...keyboardEvent, keyCode: ENTER, }; - //Act + // Act component._handleKeydown(enterEvent); - //Assert + // Assert expect(component.formatDate).toHaveBeenCalled(); expect(enterEvent.stopPropagation).toHaveBeenCalled(); }); it('should call formatDate if the Tab key has been pressed', () => { - //Arrange + // Arrange const tabEvent: Pick = { ...keyboardEvent, keyCode: TAB, }; - //Act + // Act component._handleKeydown(tabEvent); - //Assert + // Assert expect(component.formatDate).toHaveBeenCalled(); expect(tabEvent.stopPropagation).toHaveBeenCalled(); }); it('should call closePanel if the Tab key has been pressed', () => { - //Arrange + // Arrange const tabEvent: Pick = { ...keyboardEvent, keyCode: TAB, }; - //Act + // Act component._handleKeydown(tabEvent); - //Assert + // Assert expect(component.closePanel).toHaveBeenCalled(); expect(tabEvent.stopPropagation).toHaveBeenCalled(); }); it('should set value that appears in the field to the value it had before edits were made if the Escape key has been pressed', () => { - //Arrange + // Arrange const escapeEvent: Pick = { ...keyboardEvent, keyCode: ESCAPE, }; component.formattedValue = 'Test1'; component.currentValue = 'Test2'; - //Act + // Act component._handleKeydown(escapeEvent); - //Assert + // Assert expect(component.formattedValue).toBe('Test2'); expect(escapeEvent.stopPropagation).toHaveBeenCalled(); }); it('should call closePanel if the Escape key has been pressed', () => { - //Arrange + // Arrange const escapeEvent: Pick = { ...keyboardEvent, keyCode: ESCAPE, }; - //Act + // Act component._handleKeydown(escapeEvent); - //Assert + // Assert expect(component.closePanel).toHaveBeenCalled(); expect(escapeEvent.stopPropagation).toHaveBeenCalled(); }); @@ -127,13 +127,13 @@ describe('Elements: NovoDatePickerInputElement', () => { describe('Method: _handleBlur()', () => { let blurEvent: Pick = { type: 'blur' }; it('should reset the value in the field if it is different from the original', () => { - //Arrange + // Arrange spyOn(component.blurEvent, 'emit'); component.currentValue = 'Test1'; component.formattedValue = 'Test2'; - //Act + // Act component._handleBlur(blurEvent); - //Assert + // Assert expect(component.formattedValue).toBe('Test1'); }); }); @@ -142,28 +142,28 @@ describe('Elements: NovoDatePickerInputElement', () => { let focusEvent: Pick = { type: 'focus' }; it('should set the current value to that which is currently in the field upon focusing', () => { - //Arrange + // Arrange spyOn(component.focusEvent, 'emit'); spyOn(component, 'openPanel').and.callFake(() => {}); component.currentValue = 'Test1'; component.formattedValue = 'Test2'; - //Act + // Act component._handleFocus(focusEvent); - //Assert + // Assert expect(component.currentValue).toBe('Test2'); }); }); describe('Method: _setFormValue()', () => { it('should set the currentValue and formattedValue to the same upon saving when this.value is defined', () => { - //Arrange + // Arrange component.value = '06/07/2008'; component.formattedValue = ''; component.currentValue = ''; spyOn(component, 'formatDateValue').and.returnValue(component.value); - //Act + // Act component._setFormValue(component.value); - //Assert + // Assert expect(component.currentValue).toBe('06/07/2008'); expect(component.formattedValue).toBe('06/07/2008'); }); diff --git a/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts b/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts index 4b5c53ada..ce5897400 100644 --- a/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts +++ b/projects/novo-elements/src/elements/date-picker/DatePickerInput.ts @@ -37,16 +37,16 @@ const DATE_VALUE_ACCESSOR = { selector: 'novo-date-picker-input', providers: [DATE_VALUE_ACCESSOR], template: ` -