Skip to content

Commit

Permalink
feat(ffe-datepicker-react): nytt format på datepicker
Browse files Browse the repository at this point in the history
 !!Breaking change: fjernet inputProps.
  • Loading branch information
tuva-odegard committed Dec 17, 2024
1 parent 111330d commit 200d745
Show file tree
Hide file tree
Showing 12 changed files with 761 additions and 393 deletions.
8 changes: 8 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,11 @@ export type CalendarButtonState = {
isSelected: boolean;
isEnabled: boolean;
};

export type Locale = 'nb' | 'nn' | 'en';

type Month = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;

export const isMonth = (thing: any): thing is Month => {
return typeof thing === 'number' && thing >= 1 && thing <= 12;
};
112 changes: 51 additions & 61 deletions packages/ffe-datepicker-react/src/datepicker/Datepicker.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React from 'react';
import { Datepicker, DatepickerProps } from './Datepicker';
import { Datepicker, DatepickerProviderProps } from './Datepicker';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

const defaultProps = {
value: '',
onChange: () => {},
locale: 'nb' as const,
labelId: 'datepicker-label',
};

const renderDatePicker = (props?: Partial<DatepickerProps>) =>
const renderDatePicker = (props?: Partial<DatepickerProviderProps>) =>
render(<Datepicker {...defaultProps} {...props} />);

describe('<Datepicker />', () => {
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);
});
});
});
102 changes: 95 additions & 7 deletions packages/ffe-datepicker-react/src/datepicker/Datepicker.stories.tsx
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, DatepickerProviderProps } from './Datepicker';
import type { StoryObj, Meta } from '@storybook/react';
import { InputGroup } from '@sb1/ffe-form-react';

Expand All @@ -14,17 +14,105 @@ 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
}: DatepickerProviderProps) {
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
}: DatepickerProviderProps) {
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
}: DatepickerProviderProps) {
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
}: DatepickerProviderProps) {
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

0 comments on commit 200d745

Please sign in to comment.