Skip to content

Commit

Permalink
💄(frontend) replace to cunningham Datepicker
Browse files Browse the repository at this point in the history
Replace the Grommet Datepicker with the Cunningham Datepicker.
  • Loading branch information
AntoLC committed Jul 18, 2023
1 parent 135ba71 commit 0025936
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 106 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { screen } from '@testing-library/react';
import { screen, within } from '@testing-library/react';
import faker from 'faker';
import fetchMock from 'fetch-mock';
import { useCurrentResourceContext, useJwt } from 'lib-components';
Expand Down Expand Up @@ -33,7 +33,7 @@ describe('<ClassroomWidgetProvider />', () => {

afterEach(() => fetchMock.restore());

it('renders widgets', () => {
it('renders widgets', async () => {
mockedUseCurrentResourceContext.mockReturnValue([
{
permissions: {
Expand All @@ -55,12 +55,16 @@ describe('<ClassroomWidgetProvider />', () => {

// Description
expect(
screen.getByRole('textbox', { name: 'Description' }),
await screen.findByRole('textbox', { name: 'Description' }),
).toBeInTheDocument();

// Scheduling
const inputStartingAtDate = screen.getByLabelText(/starting date/i);
expect(inputStartingAtDate).toHaveValue('2022/01/13');
const inputStartingAtDate = within(
screen.getByTestId('starting-at-date-picker'),
).getByRole('presentation');

expect(inputStartingAtDate).toHaveTextContent('1/13/2022');

const inputStartingAtTime = screen.getByLabelText(/starting time/i);
expect(inputStartingAtTime).toHaveValue('12:00');
const inputEstimatedDuration = screen.getByLabelText(/estimated duration/i);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { fireEvent, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import userEventInit from '@testing-library/user-event';
import fetchMock from 'fetch-mock';
import { InfoWidgetModalProvider } from 'lib-components';
import { Deferred, render } from 'lib-tests';
import { Deferred, render, userTypeDatePicker } from 'lib-tests';
import { DateTime, Duration, Settings } from 'luxon';

import { classroomMockFactory } from '@lib-classroom/utils/tests/factories';
Expand All @@ -23,6 +23,10 @@ jest.mock('lib-components', () => ({
Settings.defaultLocale = 'en';
Settings.defaultZone = 'Europe/Paris';

const userEvent = userEventInit.setup({
advanceTimers: jest.advanceTimersByTime,
});

describe('<Scheduling />', () => {
beforeEach(() => {
jest.useFakeTimers();
Expand Down Expand Up @@ -73,12 +77,8 @@ describe('<Scheduling />', () => {
),
);

// using userEvent.type with following input doesn't work
const inputStartingAtDate = screen.getByLabelText(/starting date/i);
fireEvent.change(inputStartingAtDate, {
target: { value: startingAt.toFormat('yyyy/MM/dd') },
});
fireEvent.blur(inputStartingAtDate);
await userTypeDatePicker(startingAt, userEvent);

deferredPatch.resolve({ message: 'Classroom scheduled.' });

// using userEvent.type with following input doesn't work
Expand Down
32 changes: 32 additions & 0 deletions src/frontend/packages/lib_common/src/cunningham-style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,35 @@
@import '@openfun/cunningham-react/icons';
@import '@openfun/cunningham-react/style';
@import './cunningham-tokens.css';

.c__field {
line-height: initial;
}
.c__button--tertiary {
color: var(--c--theme--colors--primary-400);
}
.labelled-box label {
color: var(--c--theme--colors--primary-500);
}
.labelled-box label.placeholder {
top: 50%;
transform: translateY(-50%);
}
/**
* Date picker
*/
.c__date-picker__wrapper {
transition: all var(--c--theme--transitions--duration)
var(--c--theme--transitions--ease-out);
}
.c__date-picker:not(.c__date-picker--disabled):hover .c__date-picker__wrapper {
box-shadow: var(--c--theme--colors--primary-500) 0px 0px 0px 2px;
}
.c__date-picker.c__date-picker--invalid:not(.c__date-picker--disabled):hover
.c__date-picker__wrapper {
box-shadow: var(--c--theme--colors--danger-300) 0px 0px 0px 2px;
}
.c__date-picker__wrapper button[aria-label='Clear date'],
.c__date-picker.c__date-picker--invalid .c__date-picker__wrapper * {
color: var(--c--theme--colors--danger-300);
}
16 changes: 16 additions & 0 deletions src/frontend/packages/lib_common/src/cunningham-tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,20 @@
--c--theme--transitions--ease-out: cubic-bezier(0.33, 1, 0.68, 1);
--c--theme--transitions--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
--c--theme--transitions--duration: 250ms;
--c--components--forms-datepicker--color: var(
--c--theme--colors--primary-400
);
--c--components--forms-datepicker--border-color: var(
--c--theme--colors--primary-500
);
--c--components--forms-datepicker--border-radius--hover: var(
--c--components--forms-datepicker--border-radius
);
--c--components--forms-datepicker--border-radius--focus: var(
--c--components--forms-datepicker--border-radius
);
--c--components--forms-datepicker--border-color--hover: var(
--c--components--forms-datepicker--border-color
);
--c--components--forms-field--color: var(--c--theme--colors--primary-500);
}
1 change: 1 addition & 0 deletions src/frontend/packages/lib_components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"prettier": "prettier --list-different '**/*.+(ts|tsx|json|js|jsx)' --ignore-path ../../../.prettierignore"
},
"peerDependencies": {
"@openfun/cunningham-react": "*",
"grommet": "*",
"grommet-icons": "*",
"luxon": "3.3.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { fireEvent, screen } from '@testing-library/react';
import { render } from 'lib-tests';
import { fireEvent, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render, userTypeDatePicker } from 'lib-tests';
import { DateTime, Duration, Settings } from 'luxon';
import React from 'react';

Expand All @@ -9,7 +10,7 @@ Settings.defaultLocale = 'en';
Settings.defaultZone = 'Europe/Paris';

describe('<SchedulingFields />', () => {
it('triggers callbacks when updating fields', () => {
it('triggers callbacks when updating fields', async () => {
const onStartingAtChange = jest.fn();
const onEstimatedDurationChange = jest.fn();

Expand All @@ -22,13 +23,16 @@ describe('<SchedulingFields />', () => {
/>,
);

const inputStartingAtDate = within(
screen.getByTestId('starting-at-date-picker'),
).getByRole('presentation');
expect(inputStartingAtDate).toHaveTextContent('mm/dd/yyyy');

const startingAt = DateTime.local()
.plus({ days: 1 })
.set({ second: 0, millisecond: 0 });
const inputStartingAtDate = screen.getByLabelText(/starting date/i);
fireEvent.change(inputStartingAtDate, {
target: { value: startingAt.toFormat('yyyy/MM/dd') },
});
await userTypeDatePicker(startingAt);
expect(inputStartingAtDate).toHaveTextContent(startingAt.toLocaleString());

const inputStartingAtTime = screen.getByLabelText(/starting time/i);
fireEvent.change(inputStartingAtTime, {
Expand Down Expand Up @@ -58,9 +62,10 @@ describe('<SchedulingFields />', () => {
/>,
);

expect(
screen.getByDisplayValue(startingAt.toFormat('yyyy/MM/dd')),
).toBeInTheDocument();
const inputStartingAtDate = within(
screen.getByTestId('starting-at-date-picker'),
).getByRole('presentation');
expect(inputStartingAtDate).toHaveTextContent('1/27/2022');
expect(
screen.getByDisplayValue(
startingAt.toLocaleString(DateTime.TIME_24_SIMPLE),
Expand All @@ -83,7 +88,7 @@ describe('<SchedulingFields />', () => {
).toBeInTheDocument();
});

it('clears inputs', () => {
it('clears inputs', async () => {
const startingAt = DateTime.local(2022, 1, 27, 14, 22);
const estimatedDuration = Duration.fromObject({ minutes: 30 });
const onStartingAtChange = jest.fn();
Expand All @@ -98,8 +103,19 @@ describe('<SchedulingFields />', () => {
/>,
);

const inputStartingAtDate = screen.getByLabelText(/starting date/i);
fireEvent.change(inputStartingAtDate, { target: { value: null } });
const inputStartingAtDate = within(
screen.getByTestId('starting-at-date-picker'),
).getByRole('presentation');
expect(inputStartingAtDate).toHaveTextContent('1/27/2022');

await userEvent.click(
screen.getByRole('button', {
name: /Clear date/i,
}),
);

expect(inputStartingAtDate).toHaveTextContent('mm/dd/yyyy');

expect(onStartingAtChange).toHaveBeenCalledWith(null);

const inputStartingAtTime = screen.getByLabelText(/starting time/i);
Expand All @@ -111,7 +127,7 @@ describe('<SchedulingFields />', () => {
expect(onEstimatedDurationChange).toHaveBeenCalledWith(null);
});

it('does not allow to set a date outside bounded range', () => {
it('does not allow to set a date outside bounded range', async () => {
const onStartingAtChange = jest.fn();
const onEstimatedDurationChange = jest.fn();

Expand All @@ -125,12 +141,15 @@ describe('<SchedulingFields />', () => {
);

const startingAtPast = DateTime.local().minus({ days: 1 });
const inputStartingAtDate = screen.getByLabelText(/starting date/i);
expect(inputStartingAtDate).toHaveValue('');
fireEvent.change(inputStartingAtDate, {
target: { value: startingAtPast.toFormat('yyyy/MM/dd') },
const inputStartingAtDate = within(
screen.getByTestId('starting-at-date-picker'),
).getByRole('presentation');
expect(inputStartingAtDate).toHaveTextContent('mm/dd/yyyy');
await userTypeDatePicker(startingAtPast);
const allSpin = await screen.findAllByRole('spinbutton');

allSpin.forEach((spin) => {
expect(spin).toHaveAttribute('aria-invalid', 'true');
});

expect(inputStartingAtDate).toHaveValue('');
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Box, DateInput, FormField, Text, TextInput } from 'grommet';
import { CunninghamProvider, DatePicker } from '@openfun/cunningham-react';
import { Box, FormField, Text, TextInput } from 'grommet';
import { MarginType } from 'grommet/utils';
import { Nullable } from 'lib-common';
import { DateTime, Duration, Settings } from 'luxon';
Expand Down Expand Up @@ -107,17 +108,8 @@ export const SchedulingFields = ({
);
const [startingAtError, setStartingAtError] = useState<Nullable<string>>();

const onStartingAtDateInputChange = (event: { value: string | string[] }) => {
let value: string;
if (Array.isArray(event.value)) {
value = '';
if (event.value.length > 0) {
value = event.value[0];
}
} else {
value = event.value;
}
onStartingAtChangeTrigger(value, currentStartingAtTime);
const onStartingAtDateInputChange = (value: string | null) => {
onStartingAtChangeTrigger(value || '', currentStartingAtTime);
};

const onStartingAtChangeTrigger = (
Expand Down Expand Up @@ -219,43 +211,20 @@ export const SchedulingFields = ({
margin={margin}
gap="small"
>
<Box>
<FormField
label={intl.formatMessage(messages.startingAtDateTextLabel)}
htmlFor="starting_at_date"
margin="none"
background={startingAtError ? 'status-error-off' : 'white'}
// Fix of the calendar icon still clickable when component disabled (see below)
style={{ pointerEvents: disabled ? 'none' : undefined }}
height="80%"
disabled={disabled}
>
<DateInput
dropProps={{
align: { top: 'bottom', left: 'left' },
style: {
borderRadius: '4px',
boxShadow: 'rgb(0 0 0 / 23%) 4px 5px 17px',
},
}}
id="starting_at_date"
format={intl.locale === 'fr' ? 'dd/mm/yyyy' : 'yyyy/mm/dd'}
value={currentStartingAtDate || undefined}
onChange={onStartingAtDateInputChange}
calendarProps={{
bounds: [
DateTime.local().toISO() as string,
DateTime.local().plus({ years: 1 }).toISO() as string,
],
}}
// TODO : calendar icon still clickable even when component is disabled
// need to open an issue on grommet's github
<Box data-testid="starting-at-date-picker">
<CunninghamProvider>
<DatePicker
disabled={disabled}
fullWidth
label={intl.formatMessage(messages.startingAtDateTextLabel)}
locale={intl.locale}
maxValue={DateTime.local().plus({ years: 1 }).toISO() as string}
minValue={DateTime.local().toISO() as string}
onChange={onStartingAtDateInputChange}
state={startingAtError ? 'error' : 'default'}
value={currentStartingAtDate || null}
/>
</FormField>
<FormHelpText>
{intl.locale === 'fr' ? 'dd/mm/yyyy' : 'yyyy/mm/dd'}
</FormHelpText>
</CunninghamProvider>
</Box>
<Box>
<FormField
Expand Down
1 change: 1 addition & 0 deletions src/frontend/packages/lib_tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"jest-matchmedia-mock": "1.1.0",
"jest-styled-components": "7.1.1",
"jsdom-screenshot": "4.0.0",
"luxon": "3.3.0",
"prettier": "*",
"react": "*",
"react-dom": "*",
Expand Down
30 changes: 30 additions & 0 deletions src/frontend/packages/lib_tests/src/cunningham.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { screen } from '@testing-library/react';
import userEventInit from '@testing-library/user-event';
import { DateTime } from 'luxon';

/**
* To simulate a user typing a date in the Cunningham date picker
* @param startingAt
* @param userEvent
*/
export const userTypeDatePicker = async (
startingAt: DateTime,
userEvent?: ReturnType<typeof userEventInit.setup>,
) => {
const localUserEvent = userEvent || userEventInit;
await localUserEvent.click(screen.getByText(/Starting date/i));

const [monthSegment, daySegment, yearSegment] = await screen.findAllByRole(
'spinbutton',
);

await localUserEvent.click(monthSegment);
expect(monthSegment).toHaveFocus();
await localUserEvent.keyboard(startingAt.toFormat('MM'));

expect(daySegment).toHaveFocus();
await localUserEvent.keyboard(startingAt.toFormat('dd'));

expect(yearSegment).toHaveFocus();
await localUserEvent.keyboard(startingAt.toFormat('yyyy'));
};
1 change: 1 addition & 0 deletions src/frontend/packages/lib_tests/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './Deferred';
export * from './cunningham';
export * from './imageSnapshot';
export * from './intl';
export * from './render';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,12 @@ describe('<VideoWidgetProvider />', () => {
render(wrapInVideo(<VideoWidgetProvider isLive isTeacher />, mockVideo));

await screen.findByText('Description');
const inputStartingAtDate = screen.getByLabelText(/starting date/i);
expect(inputStartingAtDate).toHaveValue('2022/01/13');

const inputStartingAtDate = within(
screen.getByTestId('starting-at-date-picker'),
).getByRole('presentation');
expect(inputStartingAtDate).toHaveTextContent('1/13/2022');

const inputStartingAtTime = screen.getByLabelText(/starting time/i);
expect(inputStartingAtTime).toHaveValue('12:00');
const inputEstimatedDuration =
Expand Down
Loading

0 comments on commit 0025936

Please sign in to comment.