Skip to content

Commit

Permalink
test: Add InputTime/utils tests and prepare for timeZoneOffset pr…
Browse files Browse the repository at this point in the history
…op (#255)

* test: add tests for utils, and prepare for timeZoneOffset props
* docs: Add story example for InputTime with custom formatTime function
* fix: Remove duplicate InputTime story
  • Loading branch information
pixelbandito authored Aug 17, 2020
1 parent fa0eb75 commit 6d82a4a
Show file tree
Hide file tree
Showing 5 changed files with 367 additions and 55 deletions.
2 changes: 1 addition & 1 deletion src/components/InputTime/AsString.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const AsString: React.FC<AsStringProps> = ({
}, [customStep]);

const dropdownStep: number | undefined = useMemo(
() => (step && step >= 60 * 10 ? step : undefined),
() => (step && step >= SECONDS_PER_MINUTE * 10 ? step : undefined),
[step]
);

Expand Down
7 changes: 6 additions & 1 deletion src/components/InputTime/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ const Dropdown: React.FC<DropdownProps> = ({
(stepFrom && getDateTimeFromShortTimeString(stepFrom)) ||
getStartOfDay(new Date());
const end = getEndOfDay(new Date());
const maxAttempts = (SECONDS_PER_HOUR + SECONDS_PER_DAY) * 10; // 10 minute incremenents, buffer of an hour for daylight savings, plus 1 for start/end point.

const maxAttempts =
// 1 day + 1 hour buffer for daylight savings, divided into 10-minute increments
// because that's the shortest step the dropdown supports
(SECONDS_PER_HOUR + SECONDS_PER_DAY) / (SECONDS_PER_MINUTE * 10);

const maxDate = max ? getDateTimeFromShortTimeString(max) : undefined;
const minDate = min ? getDateTimeFromShortTimeString(min) : undefined;
let attempts = 0;
Expand Down
45 changes: 2 additions & 43 deletions src/components/InputTime/InputTime.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useRef } from 'react';
import { render, fireEvent /*, waitFor*/ } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { guessTimeFromStringInputOutputMap } from './utils.test';

import InputTime from './InputTime';

Expand Down Expand Up @@ -364,49 +365,7 @@ describe('InputTime', () => {

describe('when passed ISO dates', () => {
it('Handles several time string patterns', () => {
const inputOutputMap = {
'1': {
hours: 1,
minutes: 0,
},
'1 p': {
hours: 13,
minutes: 0,
},
'3 15': {
hours: 3,
minutes: 15,
},
'3 15 pm': {
hours: 15,
minutes: 15,
},
'00:00': {
hours: 0,
minutes: 0,
},
'12:00': {
hours: 12,
minutes: 0,
},
'12 a': {
hours: 0,
minutes: 0,
},
'12 p': {
hours: 12,
minutes: 0,
},
'7h42': {
hours: 7,
minutes: 42,
},
'8,19p': {
hours: 20,
minutes: 19,
},
};

const inputOutputMap = guessTimeFromStringInputOutputMap;
const handleChange = jest.fn((e) => e.target.value);

const { getByLabelText } = render(
Expand Down
315 changes: 315 additions & 0 deletions src/components/InputTime/utils.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
import React from 'react';
import { render } from '@testing-library/react';

import {
handleDispatchNativeInputChange,
guessTimeFromString,
getStartOfDay,
getShortTimeString,
getLocaleTimeStringFromShortTimeString,
getEndOfDay,
getDateTimeFromShortTimeString,
} from './utils';

const getFormattedTimeWithTimeZone = ({
date,
timeZoneName,
}): {
date: Date;
timeZoneName: string;
} =>
date.toLocaleTimeString('en-US', {
timeZone: timeZoneName,
});

// eslint-disable-next-line import/prefer-default-export
export const guessTimeFromStringInputOutputMap = {
'1': {
hours: 1,
minutes: 0,
},
'1 p': {
hours: 13,
minutes: 0,
},
'3 15': {
hours: 3,
minutes: 15,
},
'3 15 pm': {
hours: 15,
minutes: 15,
},
'00:00': {
hours: 0,
minutes: 0,
},
'12:00': {
hours: 12,
minutes: 0,
},
'12 a': {
hours: 0,
minutes: 0,
},
'12 p': {
hours: 12,
minutes: 0,
},
'7h42': {
hours: 7,
minutes: 42,
},
'8,19p': {
hours: 20,
minutes: 19,
},
};

describe('handleDispatchNativeInputChange', () => {
it('dispatches an on change event', () => {
const onChange = jest.fn((event) => {
event.persist();
return event;
});

const { getByTestId } = render(
<input data-testid="foo" onChange={onChange} type="text" />
);

const input = getByTestId('foo');
handleDispatchNativeInputChange(input as HTMLInputElement, 'bar');
expect(onChange).toBeCalledTimes(1);

expect(onChange).toBeCalledWith(
expect.objectContaining({
target: expect.objectContaining({
value: 'bar',
}),
})
);
});
});

describe('getEndOfDay', () => {
it('works for local time', () => {
// Result is cast to UTC, using UTC input, local environment for EOD
const now = new Date();
const endOfDay = getEndOfDay(now);
const startOfNextDay = new Date(endOfDay);
startOfNextDay.setMilliseconds(startOfNextDay.getMilliseconds() + 1);
expect(endOfDay.getDate()).toEqual(now.getDate());
expect(endOfDay.getDate()).not.toEqual(startOfNextDay.getDate());
});

it('works for local time from a date constructed with a timezone', () => {
// Result is cast to UTC, using non-UTC input, local environment for EOD
const date = new Date('2020-07-20T12:30-02:00');

const expectedDateString = new Date(date);
expectedDateString.setDate(expectedDateString.getDate() + 1);
expectedDateString.setHours(0);
expectedDateString.setMinutes(0);
expectedDateString.setSeconds(0);
expectedDateString.setMilliseconds(-1);

const endOfDay = getEndOfDay(date);
expect(endOfDay.toISOString()).toEqual(expectedDateString.toISOString());
});

describe('with optional `timeZoneOffset`', () => {
it('works for different time zones', () => {
// Result is cast to UTC, using UTC input, timeZoneOffset for EOD
const date = new Date('2020-07-20T12:30');
const endOfDayTimeString = '11:59:59 PM';
let timeZoneOffset: number;

// Central Daylight Time
timeZoneOffset = -300; // -5:00
const endOfDayCDT = getEndOfDay(date, { timeZoneOffset });

const formattedTimeCDT = getFormattedTimeWithTimeZone({
date: endOfDayCDT,
timeZoneName: 'America/Chicago',
});

// India Standard Time
timeZoneOffset = 330; // +5:30
const endOfDayIST = getEndOfDay(date, { timeZoneOffset });

const formattedTimeIST = getFormattedTimeWithTimeZone({
date: endOfDayIST,
timeZoneName: 'Asia/Calcutta',
});

expect(formattedTimeCDT).toEqual(endOfDayTimeString);
expect(formattedTimeIST).toEqual(endOfDayTimeString);
expect(endOfDayCDT.toISOString()).not.toEqual(endOfDayIST.toISOString());
});
});
});

describe('getStartOfDay', () => {
it('works for local time', () => {
// Result is cast to UTC, using UTC input, local environment for EOD
const now = new Date();
const startOfDay = getStartOfDay(now);
const endOfPrevDay = new Date(startOfDay);
endOfPrevDay.setMilliseconds(endOfPrevDay.getMilliseconds() - 1);
expect(startOfDay.getDate()).toEqual(now.getDate());
expect(startOfDay.getDate()).not.toEqual(endOfPrevDay.getDate());
});

it('works for local time from a date constructed with a timezone', () => {
// Result is cast to UTC, using non-UTC input, local environment for EOD
const date = new Date('2020-07-20T12:30-02:00');

const expectedDateString = new Date(date);
expectedDateString.setHours(0);
expectedDateString.setMinutes(0);
expectedDateString.setSeconds(0);
expectedDateString.setMilliseconds(0);

const startOfDay = getStartOfDay(date);
expect(startOfDay.toISOString()).toEqual(expectedDateString.toISOString());
});

describe('with optional `timeZoneOffset`', () => {
it('works for different time zones', () => {
// Result is cast to UTC, using UTC input, timeZoneOffset for EOD
const date = new Date('2020-07-20T12:30');
const startOfDayTimeString = '12:00:00 AM';
let timeZoneOffset: number;

// Central Daylight Time
timeZoneOffset = -300; // -5:00
const startOfDayCDT = getStartOfDay(date, { timeZoneOffset });

const formattedTimeCDT = getFormattedTimeWithTimeZone({
date: startOfDayCDT,
timeZoneName: 'America/Chicago',
});

// India Standard Time
timeZoneOffset = 330; // +5:30
const startOfDayIST = getStartOfDay(date, { timeZoneOffset });

const formattedTimeIST = getFormattedTimeWithTimeZone({
date: startOfDayIST,
timeZoneName: 'Asia/Calcutta',
});

expect(formattedTimeCDT).toEqual(startOfDayTimeString);
expect(formattedTimeIST).toEqual(startOfDayTimeString);

expect(startOfDayCDT.toISOString()).not.toEqual(
startOfDayIST.toISOString()
);
});
});
});

describe('getShortTimeString', () => {
it('makes strings in the form "12:30"', () => {
expect(getShortTimeString(12, 30)).toEqual('12:30');
});

it('pads single digit numbers with "0"', () => {
expect(getShortTimeString(9, 1)).toEqual('09:01');
});

it('handles zeroes', () => {
expect(getShortTimeString(0, 0)).toEqual('00:00');
});
});

describe('getDateTimeFromShortTimeString', () => {
it('turns hour and minute into a local date', () => {
let date: Date;

date = getDateTimeFromShortTimeString('12:30');
expect(date.toLocaleTimeString('en-US')).toEqual('12:30:00 PM');

date = getDateTimeFromShortTimeString('00:30');
expect(date.toLocaleTimeString('en-US')).toEqual('12:30:00 AM');

date = getDateTimeFromShortTimeString('23:59');
expect(date.toLocaleTimeString('en-US')).toEqual('11:59:00 PM');
});

describe('with optional `timeZoneOffset`', () => {
it('works for different time zones', () => {
const dateCDT = getDateTimeFromShortTimeString('12:30', {
timeZoneOffset: -300,
});

const formattedTimeCDT = getFormattedTimeWithTimeZone({
date: dateCDT,
timeZoneName: 'America/Chicago',
});

const dateIST = getDateTimeFromShortTimeString('12:30', {
timeZoneOffset: +330,
});

const formattedTimeIST = getFormattedTimeWithTimeZone({
date: dateIST,
timeZoneName: 'Asia/Calcutta',
});

expect(formattedTimeCDT).toEqual('12:30:00 PM');
expect(formattedTimeIST).toEqual('12:30:00 PM');
expect(dateCDT.toISOString()).not.toEqual(dateIST.toISOString());
});
});
});

describe('getLocaleTimeStringFromShortTimeString', () => {
it('formats a time with the default locale', () => {
const expectedDate = new Date();
expectedDate.setHours(12);
expectedDate.setMinutes(30);
expectedDate.setSeconds(0);
expectedDate.setMilliseconds(0);

const expectedString = expectedDate.toLocaleTimeString(undefined, {
hour: 'numeric',
minute: '2-digit',
});

expect(getLocaleTimeStringFromShortTimeString('12:30')).toEqual(
expectedString
);
});

describe('with optional `formatTime`', () => {
it('uses a custom format', () => {
const formatTime = (date: Date) =>
`${date.getMinutes()}__${date.getHours()}`;

expect(
getLocaleTimeStringFromShortTimeString('12:30', {
formatTime,
})
).toEqual('30__12');
});
});
});

describe('guessTimeFromString', () => {
it('handles several time input patterns', () => {
Object.entries(guessTimeFromStringInputOutputMap).forEach(
([input, expected]) => {
/*
Don't use `userEvent.type()`` here because typing "3:15 pm" sets the
value multiple times, with 03:00, 03:10, 03:15, and 15:15 values
*/
const output = guessTimeFromString(input);
expect(output).toStrictEqual({
...expected,
time: getShortTimeString(expected.hours, expected.minutes),
});
}
);
});
});
Loading

0 comments on commit 6d82a4a

Please sign in to comment.