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
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
import { EvoDatepickerComponent, FlatpickrOptions } from './evo-datepicker.component';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Russian } from 'flatpickr/dist/l10n/ru';
import { EvoControlErrorComponent } from '../evo-control-error';
import { IMaskModule } from 'angular-imask';
import { EvoUiClassDirective } from '../../directives';
import {Component, ViewChild} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
import {createHostFactory} from '@ngneat/spectator';
import {IMaskModule} from 'angular-imask';
import {Russian} from 'flatpickr/dist/l10n/ru';
import {EvoUiClassDirective} from '../../directives';
import {EvoControlErrorComponent} from '../evo-control-error';
import {EvoDatepickerComponent, FlatpickrOptions} from './evo-datepicker.component';

@Component({
selector: 'evo-datepicker-wrapper',
template: '',
})
class EvoDatepickerWrapperComponent {
@ViewChild(EvoDatepickerComponent) evoDatepickerComponent: EvoDatepickerComponent;

control = new FormControl([new Date(2018, 3, 5)]);
config: FlatpickrOptions = {
locale: Russian,
dateFormat: 'd.m.Y',
time_24hr: true,
};
}

const createHost = createHostFactory({
component: EvoDatepickerComponent,
imports: [FormsModule, ReactiveFormsModule, IMaskModule],
declarations: [EvoDatepickerComponent, EvoControlErrorComponent, EvoUiClassDirective],
host: EvoDatepickerWrapperComponent,
});

describe('EvoDatepickerComponent', () => {
let component: EvoDatepickerComponent;
Expand All @@ -25,16 +49,8 @@ describe('EvoDatepickerComponent', () => {

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
IMaskModule,
],
declarations: [
EvoDatepickerComponent,
EvoControlErrorComponent,
EvoUiClassDirective,
],
imports: [FormsModule, ReactiveFormsModule, IMaskModule],
declarations: [EvoDatepickerComponent, EvoControlErrorComponent, EvoUiClassDirective],
}).compileComponents();
});

Expand All @@ -59,10 +75,12 @@ describe('EvoDatepickerComponent', () => {
it('should close datepicker on outside element click', () => {
openDatepicker();
expect(document.querySelector('.flatpickr-calendar').classList.contains('open')).toBeTruthy();
document.body.dispatchEvent(new MouseEvent('mousedown', {
button: 0,
bubbles: true,
}));
document.body.dispatchEvent(
new MouseEvent('mousedown', {
button: 0,
bubbles: true,
}),
);
expect(document.querySelector('.flatpickr-calendar').classList.contains('open')).toBeFalsy();
});

Expand Down Expand Up @@ -100,3 +118,26 @@ describe('EvoDatepickerComponent', () => {
expect(component.getDefaultFlatpickrOptions()['appendTo']).toBeDefined();
});
});

describe('EvoDatepickerComponent: under test host', () => {
let wrapperComponent: EvoDatepickerWrapperComponent;
let fixture: ComponentFixture<EvoDatepickerWrapperComponent>;
let component: EvoDatepickerComponent;

const createTestHost = function (template?: string) {
const host = createHost(
template || `<evo-datepicker [config]="config" [formControl]="control"></evo-datepicker>`,
);
wrapperComponent = host.hostComponent;
fixture = host.hostFixture;
component = wrapperComponent.evoDatepickerComponent;
};

it('should display initial value from external formControl on initialization', () => {
createTestHost(`<evo-datepicker [config]="config" [formControl]="control"></evo-datepicker>`);

expect(component).toBeTruthy();
const input = fixture.nativeElement.querySelector('.evo-datepicker__input') as HTMLInputElement;
expect(input.value).toBe('05.04.2018');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import {cssClasses, renderRangeTime} from './templates';
import {EvoBaseControl} from '../../common/evo-base-control';
import {EvoControlStates} from '../../common/evo-control-state-manager/evo-control-states.enum';
import flatpickr from 'flatpickr';
import {EvoControlErrorComponent} from '../evo-control-error/evo-control-error.component';
import {IMaskDirective} from 'angular-imask';
import {EvoUiClassDirective} from '../../directives/evo-ui-class.directive';

export * from './flatpickr-options.interface';

Expand Down Expand Up @@ -49,8 +52,9 @@ type SelectedDates = string[] | Date[];
},
],
})
export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewInit, ControlValueAccessor, OnChanges, OnInit, OnDestroy {

export class EvoDatepickerComponent
extends EvoBaseControl
implements AfterViewInit, ControlValueAccessor, OnChanges, OnInit, OnDestroy {
@ViewChild('flatpickr', {static: true})
flatpickrElement: ElementRef;

Expand Down Expand Up @@ -91,24 +95,21 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI

elements: any = {};

maskConfig: { mask: any, pattern?: string, max?: Date };
maskConfig: {mask: any; pattern?: string; max?: Date};

private flatpickr: any;
private pendingValue: SelectedDates | null = null;

constructor(
private zone: NgZone,
private elementRef: ElementRef,
protected injector: Injector,
) {
constructor(private zone: NgZone, private elementRef: ElementRef, protected injector: Injector) {
super(injector);
}

get inputClass(): { [cssClass: string]: boolean } {
get inputClass(): {[cssClass: string]: boolean} {
return {
'disabled': this.disabled,
'hidden': !this.isValueExist(),
'valid': this.currentState[EvoControlStates.valid],
'invalid': this.currentState[EvoControlStates.invalid],
disabled: this.disabled,
hidden: !this.isValueExist(),
valid: this.currentState[EvoControlStates.valid],
invalid: this.currentState[EvoControlStates.invalid],
};
}

Expand All @@ -130,57 +131,53 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
return classes;
}

onChange = (value) => {
};

onTouched = () => {
};
onChange = (value) => {};
onTouched = () => {};

writeValue(value: SelectedDates) {
this.updatePickerIfNeed(value);
this.propagateChange(value);
this.onChange(value);
}

registerOnChange(fn: any) {
this.onChange = fn;
this.propagateChange = fn;
}

registerOnTouched(fn: any) {
this.onTouched = fn;
}

propagateChange = (_: any) => {
}

handleMaskComplete(value) {
if (this.maskedInput) {
const date = this.flatpickrElement.nativeElement._flatpickr.parseDate(value, this.config.dateFormat);
this.setDateFromInput(date);
}
}

setDateFromInput(date: SelectedDates) {
this.flatpickrElement.nativeElement._flatpickr.setDate(date, true);
setDateFromInput(date: SelectedDates, triggerChange = true) {
this.flatpickrElement.nativeElement._flatpickr.setDate(date, triggerChange);
}

ngAfterViewInit() {
const config = this.getConfig();

if (this.pendingValue) {
config.defaultDate = this.pendingValue as Date[];
this.pendingValue = null;
}

this.zone.runOutsideAngular(() => {
this.flatpickr = flatpickr(this.flatpickrElement.nativeElement, config);
});

if (this.setDate) {
this.setDateFromInput(this.setDate);
this.setDateFromInput(this.setDate, false);
}
this.customizePicker();
}

ngOnChanges(changes: SimpleChanges) {
if (changes.hasOwnProperty('setDate') && changes['setDate'].currentValue) {
this.setDateFromInput(changes['setDate'].currentValue);
this.setDateFromInput(changes['setDate'].currentValue, false);
}
}

Expand All @@ -204,8 +201,8 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
}

onDatepickerClick(event: MouseEvent) {
if (this.config.allowInput &&
(event.target as HTMLElement).classList.contains(cssClasses.INPUT) ||
if (
(this.config.allowInput && (event.target as HTMLElement).classList.contains(cssClasses.INPUT)) ||
this.disabled
) {
return;
Expand All @@ -224,10 +221,12 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI

isValueExist(): boolean {
if (!this.flatpickr) {
if (this.pendingValue?.length) {
return true;
}
const defaultDate = this.config.defaultDate;

return Array.isArray(defaultDate) ?
(this.config.defaultDate as Date[]).length > 0 : !!defaultDate;
return Array.isArray(defaultDate) ? (this.config.defaultDate as Date[]).length > 0 : !!defaultDate;
} else {
return this.flatpickr.selectedDates.length > 0;
}
Expand All @@ -251,7 +250,7 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
this.setTimeConstraints(selectedDates);
this.updateLabelValues(selectedDates);

this.zone.run(() => this.writeValue(selectedDates));
this.zone.run(() => this.onChange(selectedDates));
},
onClose: (selectedDates: [Date, Date]) => {
this.handleSingleSelectedValueInRange(selectedDates);
Expand Down Expand Up @@ -324,7 +323,7 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
hour: timeWrapper.getElementsByClassName(cssClasses.SELECTOR_HOUR)[1],
minute: timeWrapper.getElementsByClassName(cssClasses.SELECTOR_MINUTE)[1],
label: timeWrapper.getElementsByClassName(cssClasses.TIME_LABEL_UNTIL)[0],
}
},
};

this.elements.from.hourField = this.elements.from.hour.previousElementSibling;
Expand Down Expand Up @@ -417,7 +416,7 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
untilDate.setHours(23, 59, 0, 0);

const updatedDates = [selectedDates[0], untilDate];
this.writeValue(updatedDates);
this.setDateFromInput(updatedDates);
}
}

Expand All @@ -426,7 +425,6 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
this.disableTimeFromSelectors();
}
this.updateTimeFieldsContent();

}

private updateTimeFieldsContent() {
Expand Down Expand Up @@ -509,7 +507,7 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
const {fromHour, fromMinute} = this.getSelectedFrom();
const {untilHour, untilMinute} = this.getSelectedUntil();

if ((fromHour > untilHour) || (fromHour === untilHour && fromMinute > untilMinute)) {
if (fromHour > untilHour || (fromHour === untilHour && fromMinute > untilMinute)) {
this.resetTime();
}
}
Expand All @@ -525,10 +523,10 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
}

private getSelectorVaulesAsString(): {
fromHour: string,
fromMinute: string,
untilHour: string,
untilMinute: string
fromHour: string;
fromMinute: string;
untilHour: string;
untilMinute: string;
} {
return {
fromHour: this.elements.from.hour.options[this.elements.from.hour.selectedIndex].value,
Expand All @@ -538,14 +536,14 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
};
}

private getSelectedFrom(): { fromHour: number, fromMinute: number } {
private getSelectedFrom(): {fromHour: number; fromMinute: number} {
return {
fromHour: Number(this.elements.from.hour.options[this.elements.from.hour.selectedIndex].value),
fromMinute: Number(this.elements.from.minute.options[this.elements.from.minute.selectedIndex].value),
};
}

private getSelectedUntil(): { untilHour: number, untilMinute: number } {
private getSelectedUntil(): {untilHour: number; untilMinute: number} {
return {
untilHour: Number(this.elements.until.hour.options[this.elements.until.hour.selectedIndex].value),
untilMinute: Number(this.elements.until.minute.options[this.elements.until.minute.selectedIndex].value),
Expand Down Expand Up @@ -585,18 +583,21 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
}

private updatePickerIfNeed(value: SelectedDates): void {
if (this.flatpickr) {
const selectedDates = this.getSelectedDatesWithDatePickerFormat(this.flatpickr.selectedDates);
const values = this.getSelectedDatesWithDatePickerFormat(value);
if (!this.flatpickr) {
this.pendingValue = value;
return;
}

if (!isEqual(values, selectedDates)) {
this.setDateFromInput(value);
}
const selectedDates = this.getSelectedDatesWithDatePickerFormat(this.flatpickr.selectedDates);
const values = this.getSelectedDatesWithDatePickerFormat(value);

if (!isEqual(values, selectedDates)) {
this.setDateFromInput(value, false);
}
}

private getSelectedDatesWithDatePickerFormat(dateRange: SelectedDates): string[] {
if (dateRange && dateRange.length && typeof (dateRange[0]) !== 'string') {
if (dateRange && dateRange.length && typeof dateRange[0] !== 'string') {
return (dateRange as Date[]).map((date) => this.toDatePickerFormat(date));
}

Expand Down Expand Up @@ -641,13 +642,17 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
}

private isSameDate(firstDate: Date, secondDate: Date): boolean {
return firstDate && secondDate && firstDate.getDate() === secondDate.getDate() &&
return (
firstDate &&
secondDate &&
firstDate.getDate() === secondDate.getDate() &&
firstDate.getMonth() === secondDate.getMonth() &&
firstDate.getFullYear() === secondDate.getFullYear();
firstDate.getFullYear() === secondDate.getFullYear()
);
}

private getSelectedIndexByMinutes(minutes: number): number {
return Math.round(minutes / 5) * 5 / 15;
return (Math.round(minutes / 5) * 5) / 15;
}

private resetTimeAfterOpen() {
Expand All @@ -658,7 +663,9 @@ export class EvoDatepickerComponent extends EvoBaseControl implements AfterViewI
this.elements.from.minute.selectedIndex = this.getSelectedIndexByMinutes(selectedDates[0].getMinutes());

this.elements.until.hour.selectedIndex = selectedDates[1].getHours();
this.elements.until.minute.selectedIndex = this.getSelectedIndexByMinutes(selectedDates[1].getMinutes());
this.elements.until.minute.selectedIndex = this.getSelectedIndexByMinutes(
selectedDates[1].getMinutes(),
);
this.addConstraintsAfterOpen(selectedDates);
this.updateTimeFieldsContent();
}
Expand Down
Loading
Loading