Skip to content

Commit

Permalink
feat(datepicker): step 3, excluding dates (#219)
Browse files Browse the repository at this point in the history
* feat(datepicker): step 3, excluding dates

* feat(datepicker): step 3, excluding dates
  • Loading branch information
Leotheluck authored and dpellier committed Oct 4, 2023
1 parent cd851a1 commit ed6cdb7
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
enum ODS_DATEPICKER_DAY {
monday = 1,
tuesday = 2,
wednesday = 3,
thursday = 4,
friday = 5,
saturday = 6,
sunday = 0,
}

const ODS_DATEPICKER_DAYS = Object.freeze(Object.values(ODS_DATEPICKER_DAY));

export {
ODS_DATEPICKER_DAY,
ODS_DATEPICKER_DAYS,
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import { OdsDatepickerAttribute } from '../interfaces/attributes';
const DEFAULT_ATTRIBUTE: OdsDatepickerAttribute = Object.freeze({
clearable: false,
color: ODS_THEME_COLOR_INTENT.primary,
datesDisabled: [],
daysOfWeekDisabled: [],
disabled: false,
error: false,
format: 'dd/mm/yyyy',
inline: false,
maxDate: null,
minDate: null,
placeholder: '',
value: null,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ class OdsDatepickerController {
}
}

onChange(newValue: Date | undefined | null, oldValue?: Date | null) {
onChange(newValue: Date | undefined | null, oldValue?: Date | null): void {
if(!this.component.disabled) {
if (newValue === undefined || newValue === null || isNaN(newValue.getTime())) {
if (!this.validateValue(newValue)) {
this.component.value = null;
this.component.datepickerInstance?.setDate({ clear: true });
this.component.emitDatepickerValueChange(null, oldValue ? oldValue : null);
} else {
this.component.value = newValue;
this.component.datepickerInstance?.setDate(newValue);
this.component.emitValueChange(newValue, oldValue);
this.component.emitDatepickerValueChange(newValue, oldValue ? oldValue : null);
}
}
}
Expand All @@ -35,6 +36,36 @@ class OdsDatepickerController {
this.component.hasFocus = false;
this.component.emitBlur();
}

private getMidnightDate(date: Date): Date {
return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
}

private validateValue(value?: Date | null | undefined): boolean {
if (!value || value === null || isNaN(value.getTime())) {
return false;
}

const midnightValue = this.getMidnightDate(value).getTime();

if (this.component.datesDisabled && this.component.datesDisabled.some(d => this.getMidnightDate(d).getTime() === midnightValue)) {
return false;
}

if (this.component.daysOfWeekDisabled && this.component.daysOfWeekDisabled.includes(value.getDay())) {
return false;
}

if (this.component.maxDate && midnightValue > this.getMidnightDate(this.component.maxDate).getTime()) {
return false;
}

if (this.component.minDate && midnightValue < this.getMidnightDate(this.component.minDate).getTime()) {
return false;
}

return true;
}
}

export {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
import type { ODS_DATEPICKER_DAY } from '../constants/datepicker-day';

interface OdsDatepickerAttribute {
/**
Expand All @@ -9,6 +10,14 @@ interface OdsDatepickerAttribute {
* Defines the Datepicker's color (see component principles)
*/
color?: ODS_THEME_COLOR_INTENT;
/**
* Defines the Datepicker's disabled dates
*/
datesDisabled?: Date[];
/**
* Defines the Datepicker's disabled days of the week (monday, tuesday...)
*/
daysOfWeekDisabled?: ODS_DATEPICKER_DAY[];
/**
* Defines if the Datepicker should be disabled or not (lower opacity and not interactable)
*/
Expand All @@ -25,6 +34,14 @@ interface OdsDatepickerAttribute {
* Defines if the Datepicker should be displayed inline or not
*/
inline?: boolean;
/**
* Defines the Datepicker's maximum selectable date
*/
maxDate?: Date | undefined | null;
/**
* Defines the Datepicker's minimum selectable date
*/
minDate?: Date | undefined | null;
/**
* Defines if the Datepicker should display a placeholder message
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { OsdsDatepicker } from './osds-datepicker';
import { newE2EPage } from '@stencil/core/testing';
import { odsComponentAttributes2StringAttributes, odsStringAttributes2Str } from '@ovhcloud/ods-common-testing';
import { DEFAULT_ATTRIBUTE } from './constants/default-attributes';
import { ODS_DATEPICKER_DAY } from './constants/datepicker-day';

describe('e2e:osds-datepicker', () => {
let page: E2EPage;
Expand All @@ -22,23 +23,34 @@ describe('e2e:osds-datepicker', () => {
pickerElement.style.setProperty('animation', 'none');
});

if (attributes.value) {
const dateStr = attributes.value.toISOString();
await page.evaluate((dateStr) => {
const datepicker = document.querySelector('osds-datepicker') as unknown as OsdsDatepicker;
datepicker.value = new Date(dateStr);
}, dateStr);
}
await page.emulateTimezone('Europe/London');

await page.evaluate(
(datesDisabledJSON, daysOfWeekDisabledJSON, maxDateJSON, minDateJSON, valueJSON) => {
const datepicker = document.querySelector('osds-datepicker') as unknown as OsdsDatepicker;
datesDisabledJSON && (datepicker.datesDisabled = JSON.parse(datesDisabledJSON).map((str: string) => new Date(str)));
daysOfWeekDisabledJSON && (datepicker.daysOfWeekDisabled = JSON.parse(daysOfWeekDisabledJSON));
maxDateJSON && (datepicker.maxDate = new Date(JSON.parse(maxDateJSON)));
minDateJSON && (datepicker.minDate = new Date(JSON.parse(minDateJSON)));
valueJSON && (datepicker.value = new Date(JSON.parse(valueJSON)));
},
JSON.stringify(attributes.datesDisabled?.map(date => date.toISOString())),
JSON.stringify(attributes.daysOfWeekDisabled),
JSON.stringify(attributes.maxDate?.toISOString()),
JSON.stringify(attributes.minDate?.toISOString()),
JSON.stringify(attributes.value?.toISOString()),
);
}

const attributeConfigurations = [
{ name: 'clearable', options: [true, false] },
{ name: 'daysOfWeekDisabled', options: [[], [ODS_DATEPICKER_DAY.saturday, ODS_DATEPICKER_DAY.sunday]] },
{ name: 'disabled', options: [true, false] },
{ name: 'error', options: [false, true] },
{ name: 'format', options: ['dd/mm/yyyy', 'mm/dd/yyyy'] },
{ name: 'inline', options: [true, false] },
{ name: 'placeholder', options: ['', 'placeholder message'] },
{ name: 'value', options: ['', new Date('1999-11-02')] },
{ name: 'value', options: [null, new Date('1999-11-02')] },
];

function generateCombinations(attributes: typeof attributeConfigurations): Array<Partial<OdsDatepickerAttribute>> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { E2EElement, E2EPage } from '@stencil/core/testing';
import type { OdsDatepickerAttribute } from './interfaces/attributes';
import type { OsdsDatepicker } from './osds-datepicker';
import { newE2EPage } from '@stencil/core/testing';
import { odsComponentAttributes2StringAttributes, odsStringAttributes2Str } from '@ovhcloud/ods-common-testing';
import { DEFAULT_ATTRIBUTE } from './constants/default-attributes';
import { ODS_DATEPICKER_DAY } from './constants/datepicker-day';

describe('e2e:osds-datepicker', () => {
let page: E2EPage;
Expand All @@ -15,47 +17,97 @@ describe('e2e:osds-datepicker', () => {
await page.setContent(`<osds-datepicker ${odsStringAttributes2Str(stringAttributes)}></osds-datepicker>`);
await page.evaluate(() => document.body.style.setProperty('margin', '0px'));

await page.evaluate(() => {
const pickerElement = document.querySelector('osds-datepicker')?.shadowRoot?.querySelector('.datepicker') as HTMLElement;
pickerElement.style.setProperty('animation', 'none');
});

el = await page.find('osds-datepicker');
await page.emulateTimezone('Europe/London');

await page.evaluate(
(datesDisabledJSON, daysOfWeekDisabledJSON, maxDateJSON, minDateJSON, valueJSON) => {
const datepicker = document.querySelector('osds-datepicker') as unknown as OsdsDatepicker;
datesDisabledJSON && (datepicker.datesDisabled = JSON.parse(datesDisabledJSON).map((str: string) => new Date(str)));
daysOfWeekDisabledJSON && (datepicker.daysOfWeekDisabled = JSON.parse(daysOfWeekDisabledJSON));
maxDateJSON && (datepicker.maxDate = new Date(JSON.parse(maxDateJSON)));
minDateJSON && (datepicker.minDate = new Date(JSON.parse(minDateJSON)));
valueJSON && (datepicker.value = new Date(JSON.parse(valueJSON)));
},
JSON.stringify(attributes.datesDisabled?.map(date => date.toISOString())),
JSON.stringify(attributes.daysOfWeekDisabled),
JSON.stringify(attributes.maxDate?.toISOString()),
JSON.stringify(attributes.minDate?.toISOString()),
JSON.stringify(attributes.value?.toISOString()),
);
}

async function getDatepickerValue(): Promise<Date | null> {
const value = await page.evaluate(() => {
const datepicker = document.querySelector('osds-datepicker') as unknown as OsdsDatepicker;
return datepicker.value?.toISOString();
});
return value ? new Date(value) : null;
}

it('should render', async () => {
await setup({ attributes: {} });
await page.waitForChanges();
expect(el).not.toBeNull();
expect(el).toHaveClass('hydrated');
});

it('should render with a value', async () => {
const date = new Date('1999-11-02');
await setup({ attributes: { value: date } });
await page.waitForChanges();
expect(el).not.toBeNull();
expect(el).toHaveClass('hydrated');
const value = await getDatepickerValue();
expect(value).toEqual(date);
});

it('should display a datepicker when focused', async () => {
await setup({ attributes: {} });
await el.click();
await page.waitForChanges();
const datepicker = await page.find('osds-datepicker >>> .datepicker');
expect(datepicker).not.toBeNull();
});

it('should allow datepicker to be cleared when clearable is true', async () => {
await setup({ attributes: { clearable: true, value: new Date('2023-09-11') } });
const date = new Date('1999-11-02');
await setup({ attributes: { clearable: true, value: date } });
await el.click();
await page.waitForChanges();
let value = await getDatepickerValue();
expect(value).toEqual(date);
const clearButton = await page.find('osds-datepicker >>> osds-input >>> osds-icon[name="close"]');
await clearButton.click();
expect(await el.getProperty('value')).toBe(null);
await page.waitForChanges();
value = await getDatepickerValue();
expect(value).toEqual(date);
});

it('should be disabled when disabled attribute is true', async () => {
await setup({ attributes: { disabled: true } });
const datepicker = await page.find('osds-datepicker');
await page.waitForChanges();
expect(datepicker).toHaveAttribute('disabled');
});

it('should update the value when date is selected from the datepicker', async () => {
await setup({ attributes: {} });
let value = await el.getProperty('value');
await page.waitForChanges();
let value = await getDatepickerValue();
expect(value).toBe(null);
await el.click();
await page.waitForChanges();
const dateButton = await page.find('osds-datepicker >>> .datepicker .datepicker-grid .datepicker-cell:first-child');
await dateButton.click();
await page.waitForChanges();
value = await el.getProperty('value');
expect(value).not.toBe(null);
value = await getDatepickerValue();
expect(value).toBeInstanceOf(Date);
});

it('should format date according to format attribute', async () => {
Expand Down Expand Up @@ -119,4 +171,74 @@ describe('e2e:osds-datepicker', () => {
const decadeGrid = await page.find('osds-datepicker >>> .datepicker .decades');
expect(decadeGrid).toBeNull();
});

it('should disable the desired days of the week', async () => {
await setup({ attributes: { daysOfWeekDisabled: [ODS_DATEPICKER_DAY.saturday, ODS_DATEPICKER_DAY.sunday] } });
await el.click();
await page.waitForChanges();
let dateButton = await page.find('osds-datepicker >>> .datepicker .datepicker-grid .datepicker-cell:nth-child(3)');
expect(dateButton).not.toHaveClass('disabled');
dateButton = await page.find('osds-datepicker >>> .datepicker .datepicker-grid .datepicker-cell:first-child');
expect(dateButton).toHaveClass('disabled');
});

it('should *not* allow to select out of scope date', async () => {
const date = new Date('2024-01-15');
await setup({ attributes: {
datesDisabled: [new Date('2024-01-11'), new Date('2024-01-17')],
daysOfWeekDisabled: [ODS_DATEPICKER_DAY.saturday, ODS_DATEPICKER_DAY.sunday],
maxDate: new Date('2024-01-25'),
minDate: new Date('2024-01-10'),
value: date,
} });
await page.waitForChanges();
await el.click();
await page.waitForChanges();
let value = await getDatepickerValue();
expect(value).toEqual(date);
const dateButton = await page.find('osds-datepicker >>> .datepicker .datepicker-grid .datepicker-cell:nth-child(7)');
expect(dateButton).toHaveClass('disabled');
await dateButton.click();
await page.waitForChanges();
value = await getDatepickerValue();
expect(value).toEqual(date);
});

it('should allow to select inside of scope date', async () => {
const date = new Date('2024-01-15');
await setup({ attributes: {
datesDisabled: [new Date('2024-01-11'), new Date('2024-01-17')],
daysOfWeekDisabled: [ODS_DATEPICKER_DAY.saturday, ODS_DATEPICKER_DAY.sunday],
maxDate: new Date('2024-01-25'),
minDate: new Date('2024-01-10'),
value: date,
} });
await page.waitForChanges();
await el.click();
await page.waitForChanges();
let value = await getDatepickerValue();
expect(value).toEqual(date);
const dateButton = await page.find('osds-datepicker >>> .datepicker .datepicker-grid .datepicker-cell:nth-child(17)');
expect(dateButton).not.toHaveClass('disabled');
await dateButton.click();
await page.waitForChanges();
value = await getDatepickerValue();
expect(value).toEqual(new Date('2024-01-16'));
});

it('should disable dates before the minDate', async () => {
await setup({ attributes: { minDate: new Date('2023-05-10'), value: new Date('2023-05-15') } });
await el.click();
await page.waitForChanges();
const beforeMinDateButton = await page.find(`osds-datepicker >>> .datepicker .datepicker-grid .datepicker-cell:first-child`);
expect(beforeMinDateButton).toHaveClass('disabled');
});

it('should disable dates after the maxDate', async () => {
await setup({ attributes: { maxDate: new Date('2023-05-20'), value: new Date('2023-05-15') } });
await el.click();
await page.waitForChanges();
const afterMaxDateButton = await page.find(`osds-datepicker >>> .datepicker .datepicker-grid .datepicker-cell:last-child`);
expect(afterMaxDateButton).toHaveClass('disabled');
});
});
Loading

0 comments on commit ed6cdb7

Please sign in to comment.