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

LOOM-1668 backpack date picker fires change event on value change #3608

Merged
22 changes: 17 additions & 5 deletions packages/bpk-component-datepicker/src/BpkDatepicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/

import { createRef, Component } from 'react';
import type { ReactElement} from 'react';
import type { ReactElement, RefObject } from 'react';

import BpkBreakpoint, { BREAKPOINTS } from '../../bpk-component-breakpoint';
import {
Expand All @@ -33,13 +33,15 @@ import {
import BpkInput, { withOpenEvents } from '../../bpk-component-input';
import BpkModal from '../../bpk-component-modal';
import BpkPopover from '../../bpk-component-popover';
import { setNativeValue } from '../../bpk-react-utils';

import type {
DaysOfWeek,
ReactComponent,
SelectionConfiguration,
} from '../../bpk-component-calendar';


const Input = withOpenEvents(BpkInput);

const DefaultCalendar = withCalendarState(
Expand All @@ -62,7 +64,7 @@ type Props = {
id: string;
title: string;
/**
* Because this component uses a modal on mobile viewports, you need to let it know what
* Because this component uses a modal on mobile viewports, you need to let it know what
* the root element of your application is by returning its DOM node via this prop
* This is to "hide" your application from screen readers whilst the datepicker is open.
* The "pagewrap" element id is a convention we use internally at Skyscanner. In most cases it should "just work".
Expand Down Expand Up @@ -107,7 +109,11 @@ type State = {
};

class BpkDatepicker extends Component<Props, State> {
inputRef: React.RefObject<HTMLInputElement>;
inputRef: (ref:HTMLInputElement) => void;
amburrrshand marked this conversation as resolved.
Show resolved Hide resolved

elementRef?: HTMLInputElement;

focusRef?: RefObject<HTMLInputElement>;

static defaultProps = {
calendarComponent: DefaultCalendar,
Expand Down Expand Up @@ -140,7 +146,10 @@ class BpkDatepicker extends Component<Props, State> {
this.state = {
isOpen: props.isOpen!,
};
this.inputRef = createRef();
this.focusRef = createRef();
this.inputRef = (ref) => {
this.elementRef = ref
}
}

componentDidUpdate(prevProps: Props, prevState: State) {
Expand Down Expand Up @@ -269,7 +278,9 @@ class BpkDatepicker extends Component<Props, State> {
DateUtils.isSameDay(newEndDate, selectionConfiguration.startDate))
) {
onDateSelect(selectionConfiguration.startDate, newEndDate);
this.elementRef && setNativeValue(this.elementRef, this.props.formatDate(newEndDate));
} else {
this.elementRef && setNativeValue(this.elementRef, this.props.formatDate(newStartDate));
onDateSelect(newStartDate);
}
}
Expand Down Expand Up @@ -312,8 +323,9 @@ class BpkDatepicker extends Component<Props, State> {
delete rest.isOpen;

const input = inputComponent || (
<div ref={this.inputRef} >
<div ref={this.focusRef} >
<Input
inputRef={this.inputRef}
id={id}
name={`${id}_input`}
value={this.getValue(selectionConfiguration!, formatDate)}
Expand Down
183 changes: 183 additions & 0 deletions packages/bpk-component-datepicker/src/form-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* Backpack - Skyscanner's Design System
*
* Copyright 2016 Skyscanner Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


import { useEffect, useState } from 'react';

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { CALENDAR_SELECTION_TYPE } from '../../bpk-component-calendar';
import { format } from '../../bpk-component-calendar/src/date-utils';
import {
weekDays,
formatMonth,
formatDateFull,
} from '../../bpk-component-calendar/test-utils';

import BpkDatepicker from './BpkDatepicker';

const formatDate = (date: Date) => format(date, 'dd/MM/yyyy');

const inputProps = {
onChange: () => null,
placeholder: 'placeholder',
large: true,
};

describe('BpkDatepicker form test', () => {
it('should work as a form component in a form', async () => {
const Wrap = () => (
<form data-testid="form">
<BpkDatepicker
id="datepicker"
closeButtonText="Close"
daysOfWeek={weekDays}
changeMonthLabel="Change month"
previousMonthLabel="Go to previous month"
nextMonthLabel="Go to next month"
title="Departure date"
weekStartsOn={1}
getApplicationElement={() => document.createElement('div')}
formatDate={formatDate}
formatMonth={formatMonth}
formatDateFull={formatDateFull}
inputProps={inputProps}
selectionConfiguration={{
type: CALENDAR_SELECTION_TYPE.single,
date: new Date(2020, 2, 19),
}}
data-testid="myDatepicker"
/>
</form>
);
render(<Wrap />);

const inputField = screen.getByRole('textbox', {
name: /19th March 2020/i,
});
await userEvent.click(inputField);

const formData = new FormData(
screen.getByTestId('form') as HTMLFormElement,
);
expect(Object.fromEntries(formData.entries())).toEqual({ datepicker_input: '19/03/2020' });
});

it('should work as a form component in a form for two way trip', async () => {
const Wrap = () => (
<form data-testid="form">
<BpkDatepicker
id="datepicker"
closeButtonText="Close"
daysOfWeek={weekDays}
changeMonthLabel="Change month"
previousMonthLabel="Go to previous month"
nextMonthLabel="Go to next month"
title="Departure date"
weekStartsOn={1}
getApplicationElement={() => document.createElement('div')}
formatDate={formatDate}
formatMonth={formatMonth}
formatDateFull={formatDateFull}
inputProps={inputProps}
selectionConfiguration={{
type: CALENDAR_SELECTION_TYPE.range,
startDate: new Date(2020, 2, 19),
endDate: new Date(2020, 3, 19),
}}
data-testid="myDatepicker"
/>
</form>
);
render(<Wrap />);

const inputField = screen.getByRole('textbox', {
name: /19th March 2020/i,
});
await userEvent.click(inputField);

const formData = new FormData(
screen.getByTestId('form') as HTMLFormElement,
);
expect(Object.fromEntries(formData.entries())).toEqual({ datepicker_input: '19/03/2020 - 19/04/2020' });
});

it('should emit a change event when input is changed', async () => {
const formValidation = jest.fn();
const Wrap = () => {
const [calendarDate, setCalendarDate] = useState(new Date(2020, 2, 19));
useEffect(() => {
document.addEventListener('change', formValidation);
}, []);
return (
<form data-testid="form">
<BpkDatepicker
id="datepicker"
closeButtonText="Close"
daysOfWeek={weekDays}
changeMonthLabel="Change month"
previousMonthLabel="Go to previous month"
nextMonthLabel="Go to next month"
title="Departure date"
weekStartsOn={1}
getApplicationElement={() => document.createElement('div')}
formatDate={formatDate}
formatMonth={formatMonth}
formatDateFull={formatDateFull}
inputProps={inputProps}
minDate={new Date(2020, 2, 1)}
maxDate={new Date(2020, 2, 31)}
onDateSelect={(date1, date2) => {
setCalendarDate(date1);
}}
selectionConfiguration={{
type: CALENDAR_SELECTION_TYPE.single,
date: calendarDate,
}}
data-testid="myDatepicker"
/>
</form>
);
};

render(<Wrap />);

const inputField = screen.getByRole('textbox', {
name: /Thursday, 19th March 2020/i,
});

await userEvent.click(inputField);

const calendarDialog = screen.getByRole('dialog', {
name: 'Departure date',
});

expect(calendarDialog).toBeInTheDocument();

const dateButton = screen.getByRole('button', {
name: /30/i,
});

await userEvent.click(dateButton);

expect(inputField.getAttribute('value')).toEqual('30/03/2020');
expect(formValidation).toHaveBeenCalledTimes(1);
});
});

Loading