Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Formatting numbers and allowing key navigation on datepicker #2443

Merged
merged 2 commits into from
Dec 17, 2024
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
2 changes: 2 additions & 0 deletions packages/ffe-datepicker-react/src/datelogic/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export type CalendarButtonState = {
isSelected: boolean;
isEnabled: boolean;
};

export type Locale = 'nb' | 'nn' | 'en';
108 changes: 49 additions & 59 deletions packages/ffe-datepicker-react/src/datepicker/Datepicker.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import userEvent from '@testing-library/user-event';
const defaultProps = {
value: '',
onChange: () => {},
locale: 'nb' as const,
labelId: 'datepicker-label',
};

const renderDatePicker = (props?: Partial<DatepickerProps>) =>
Expand All @@ -23,14 +25,40 @@ describe('<Datepicker />', () => {

it('contains a single DateInput component', () => {
renderDatePicker();
expect(screen.getByRole('textbox')).toBeInTheDocument();
expect(screen.getByRole('group')).toBeInTheDocument();
});

it('does not contain a Calendar component', () => {
renderDatePicker();
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});

it('responds to arrow up and down', async () => {
renderDatePicker();
const [dayInput] = screen.getAllByRole('spinbutton');
await userEvent.type(dayInput, '{arrowup}');
expect(dayInput).toHaveValue(1);
await userEvent.type(dayInput, '{arrowdown}');
expect(dayInput).toHaveValue(31);
});

it('triggers onchange when arrows are used', async () => {
const onChange = jest.fn();
renderDatePicker({ onChange });
const [dayInput] = screen.getAllByRole('spinbutton');
await userEvent.type(dayInput, '{arrowup}');
expect(onChange).toHaveBeenCalledTimes(1);
});

it('reponds to arrow left and right', async () => {
renderDatePicker();
const [dayInput, monthInput] = screen.getAllByRole('spinbutton');
await userEvent.type(dayInput, '{arrowright}');
expect(monthInput).toHaveFocus();
await userEvent.type(monthInput, '{arrowleft}');
expect(dayInput).toHaveFocus();
});

describe('with click on button', () => {
const user = userEvent.setup();
it('contains a Calendar', async () => {
Expand All @@ -54,8 +82,8 @@ describe('<Datepicker />', () => {
it('calls onChange method', async () => {
const onChange = jest.fn();
renderDatePicker({ onChange });
const input = screen.getByRole('textbox');
await user.type(input, '1');
const [dayInput] = screen.getAllByRole('spinbutton');
await user.type(dayInput, '4');
expect(onChange).toHaveBeenCalledTimes(1);
});
});
Expand All @@ -81,33 +109,24 @@ describe('<Datepicker />', () => {
describe('ariaInvalid', () => {
it('has correct aria-invalid value if given prop', () => {
renderDatePicker({ ariaInvalid: true });
const input = screen.getByRole('textbox');
expect(input.getAttribute('aria-invalid')).toBe('true');
const [date, month, year] =
screen.getAllByRole('spinbutton');
expect(date.getAttribute('aria-invalid')).toBe('true');
expect(month.getAttribute('aria-invalid')).toBe('true');
expect(year.getAttribute('aria-invalid')).toBe('true');
});

it('has correct aria-describedby if aria-describedby given as input prop', () => {
const inputProps = {
'aria-describedby': 'test',
};
renderDatePicker({
ariaInvalid: true,
inputProps,
ariaDescribedby: 'test',
});
const input = screen.getByRole('textbox');
expect(input.getAttribute('aria-describedby')).toBe('test');
});
});

describe('inputProps', () => {
it('is passed on to input field', () => {
const inputProps = {
className: 'customClass',
id: 'custom-input-id',
};
renderDatePicker({ inputProps });
const input = screen.getByRole('textbox');
expect(input.classList.contains('customClass')).toBe(true);
expect(input.getAttribute('id')).toBe('custom-input-id');
const [date, month, year] =
screen.getAllByRole('spinbutton');
expect(date.getAttribute('aria-describedby')).toBe('test');
expect(month.getAttribute('aria-describedby')).toBe('test');
expect(year.getAttribute('aria-describedby')).toBe('test');
});
});

Expand All @@ -126,43 +145,14 @@ describe('<Datepicker />', () => {
});
});

describe('try to be smart in which century to place an input of two digit years', () => {
const user = userEvent.setup();
it('defaults to the 20th century', async () => {
const onChange = jest.fn();
renderDatePicker({ onChange, value: '101099' });

const input = screen.getByRole('textbox');
await user.type(input, '{Tab}');
expect(onChange).toHaveBeenCalledWith('10.10.2099');
});

it('assumes last century if maxDate is today-ish', async () => {
const onChange = jest.fn();
renderDatePicker({
maxDate: '02.02.2022',
onChange,
value: '111199',
});

const input = screen.getByRole('textbox');
await user.type(input, '{Tab}');

expect(onChange).toHaveBeenCalledWith('11.11.1999');
});

it('assumes this century if maxDate is today-ish but input is rather close to it', async () => {
const onChange = jest.fn();
renderDatePicker({
maxDate: '02.02.2022',
onChange,
value: '121220',
});

const input = screen.getByRole('textbox');
await user.type(input, '{Tab}');
describe('with value', () => {
it('has correct value in input field', () => {
renderDatePicker({ value: '01.01.2021' });

expect(onChange).toHaveBeenCalledWith('12.12.2020');
const [date, month, year] = screen.getAllByRole('spinbutton');
expect(date).toHaveValue(1);
expect(month).toHaveValue(1);
expect(year).toHaveValue(2021);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Datepicker } from './Datepicker';
import { Datepicker, DatepickerProps } from './Datepicker';
import type { StoryObj, Meta } from '@storybook/react';
import { InputGroup } from '@sb1/ffe-form-react';

Expand All @@ -14,17 +14,89 @@ type Story = StoryObj<typeof Datepicker>;
export const Standard: Story = {
args: {
locale: 'nb',
maxDate: '31.12.2016',
minDate: '01.01.2016',
maxDate: '31.12.2025',
minDate: '01.01.2024',
labelId: 'datepicker-label',
},
render: function Render({ value, onChange, ...args }) {
const [date, setDate] = useState('01.01.2016');
render: function Render({ value, onChange, ...args }: DatepickerProps) {
const [date, setDate] = useState('01.12.2024');

return (
<InputGroup label="Dato" labelId={Standard?.args?.labelId}>
{inputProps => (
<Datepicker
value={value ?? date}
onChange={setDate}
{...inputProps}
{...args}
/>
)}
</InputGroup>
);
},
};

export const FieldMessageString: Story = {
args: {
...Standard.args,
},
render: function Render({ value, onChange, ...args }: DatepickerProps) {
const [date, setDate] = useState('01.12.2024');

return (
<InputGroup
label="Dato"
aria-invalid={true}
fieldMessage={'Ugyldig dato'}
labelId={Standard?.args?.labelId}
>
{inputProps => (
<Datepicker
value={value ?? date}
onChange={setDate}
{...inputProps}
{...args}
/>
)}
</InputGroup>
);
},
};

export const FullWidth: Story = {
args: {
...Standard.args,
fullWidth: true,
},
render: function Render({ value, onChange, ...args }: DatepickerProps) {
const [date, setDate] = useState('01.12.2024');

return (
<InputGroup label="Dato">
<InputGroup label="Dato" labelId={Standard?.args?.labelId}>
<Datepicker
value={value ?? date}
onChange={setDate}
{...args}
/>
</InputGroup>
);
},
};

export const CalendarAbove: Story = {
args: {
...Standard.args,
calendarAbove: true,
},
render: function Render({ value, onChange, ...args }: DatepickerProps) {
const [date, setDate] = useState('01.12.2024');

return (
<InputGroup label="Dato" labelId={Standard?.args?.labelId}>
<Datepicker
value={value ?? date}
onChange={onChange ?? setDate}
onChange={setDate}
{...args}
/>
</InputGroup>
);
Expand Down
Loading
Loading