Skip to content

Commit

Permalink
♻️(frontend) create commun widget retention date
Browse files Browse the repository at this point in the history
lib_video and lib_classroom have the same widget retention date,
so we create a common widget in lib_components, it helps to develop
and maintain the widget in one place.
  • Loading branch information
AntoLC committed Aug 29, 2023
1 parent 459e2e5 commit abde855
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 243 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,16 @@ describe('Classroom <RetentionDate />', () => {
it('renders the component and set a date with success', async () => {
const mockedClassroom = classroomMockFactory();

fetchMock.mock(`/api/classrooms/${mockedClassroom.id}/`, 200, {
method: 'PATCH',
});
fetchMock.mock(
`/api/classrooms/${mockedClassroom.id}/`,
{
status: 200,
body: mockedClassroom,
},
{
method: 'PATCH',
},
);

render(
wrapInClassroom(
Expand Down Expand Up @@ -76,16 +83,29 @@ describe('Classroom <RetentionDate />', () => {
`{"retention_date":"${retentionDate.toISODate()!}"}`,
);
expect(lastCall?.[1]?.method).toBe('PATCH');

await screen.findByText('Classroom updated.');
});

it('renders the component with a default date and deletes it', async () => {
const retentionDate = DateTime.utc()
.plus({ days: 1 })
.set({ second: 0, millisecond: 0 });

const mockedClassroom = classroomMockFactory({
retention_date: '2020-03-01',
retention_date: retentionDate.toISODate(),
});

fetchMock.mock(`/api/classrooms/${mockedClassroom.id}/`, 200, {
method: 'PATCH',
});
fetchMock.mock(
`/api/classrooms/${mockedClassroom.id}/`,
{
status: 200,
body: mockedClassroom,
},
{
method: 'PATCH',
},
);

render(
wrapInClassroom(
Expand All @@ -101,7 +121,9 @@ describe('Classroom <RetentionDate />', () => {
const inputRetentionDate = within(
screen.getByTestId('retention-date-picker'),
).getByRole('presentation');
expect(inputRetentionDate).toHaveTextContent('3/1/2020');
expect(inputRetentionDate).toHaveTextContent(
retentionDate.toLocaleString(),
);

const deleteButton = await screen.findByRole('button', {
name: 'Delete retention date',
Expand All @@ -116,6 +138,8 @@ describe('Classroom <RetentionDate />', () => {
expect(lastCall?.[0]).toBe(`/api/classrooms/${mockedClassroom.id}/`);
expect(lastCall?.[1]?.body).toEqual('{"retention_date":null}');
expect(lastCall?.[1]?.method).toBe('PATCH');

await screen.findByText('Classroom updated.');
});

it('fails to update the video and displays the right error message', async () => {
Expand All @@ -134,8 +158,6 @@ describe('Classroom <RetentionDate />', () => {
),
);

expect(screen.getAllByText('Retention date')).toBeTruthy();

const deleteButton = await screen.findByRole('button', {
name: 'Delete retention date',
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
import { CunninghamProvider, DatePicker } from '@openfun/cunningham-react';
import { Box, Button } from 'grommet';
import { Nullable } from 'lib-common';
import { Classroom, FoldableItem, debounce } from 'lib-components';
import { DateTime } from 'luxon';
import { useState } from 'react';
import {
Classroom,
RetentionDate as CommonRetentionDate,
debounce,
} from 'lib-components';
import toast from 'react-hot-toast';
import { defineMessages, useIntl } from 'react-intl';
import styled from 'styled-components';

import { useUpdateClassroom } from '@lib-classroom/data/queries';
import { useCurrentClassroom } from '@lib-classroom/hooks/useCurrentClassroom';

const messages = defineMessages({
info: {
defaultMessage:
'This widget allows you to change the retention date of the classroom. Once this date is reached, the classroom will be deleted.',
description: 'Info of the widget used to change classroom retention date.',
id: 'components.ClassroomRetentionDate.info',
},
title: {
defaultMessage: 'Retention date',
description: 'Title of the widget used to change classroom retention date.',
id: 'components.ClassroomRetentionDate.title',
ressource: {
defaultMessage: 'classroom',
description:
'Type of ressource displayed in the info retention date widget.',
id: 'component.ClassroomRetentionDate.ressource',
},
updateClassroomSuccess: {
defaultMessage: 'Classroom updated.',
Expand All @@ -33,28 +26,11 @@ const messages = defineMessages({
description: 'Message displayed when classroom update has failed.',
id: 'component.ClassroomRetentionDate.updateVideoFail',
},
deleteClassroomRetentionDateButton: {
defaultMessage: 'Delete retention date',
description: 'Button used to delete classroom retention date.',
id: 'component.ClassroomRetentionDate.deleteClassroomRetentionDateButton',
},
});

const StyledAnchorButton = styled(Button)`
height: 50px;
font-family: 'Roboto-Medium';
display: flex;
align-items: center;
justify-content: center;
`;

export const RetentionDate = () => {
const intl = useIntl();
const classroom = useCurrentClassroom();
const [selectedRetentionDate, setSelectedRetentionDate] = useState<
Nullable<string>
>(classroom.retention_date);

const updateClassroomMutation = useUpdateClassroom(classroom.id, {
onSuccess: () => {
toast.success(intl.formatMessage(messages.updateClassroomSuccess));
Expand All @@ -69,88 +45,15 @@ export const RetentionDate = () => {
},
);

/**
* @param new_retention_date in the format YYYY-MM-DD HH:MM:SS (ISO 8601) UTC
* @returns
*/
function onChange(new_retention_date: string | null) {
/**
* The date is in UTC format so not as the client has chosen.
* We need to convert it to local date, then reconvert to YYYY-MM-DD
* locale `en-CA` is used to convert the date in the format YYYY-MM-DD
*/
const local_new_retention_date = new_retention_date
? new Date(new_retention_date).toLocaleDateString('en-CA')
: null;

setSelectedRetentionDate(local_new_retention_date || null);

if (
new_retention_date &&
new Date(new_retention_date).getTime() <=
new Date(DateTime.local().toISODate() || '').getTime()
) {
return;
}

debouncedUpdatedClassroom({
retention_date: local_new_retention_date,
});
}

return (
<FoldableItem
infoText={intl.formatMessage(messages.info)}
initialOpenValue
title={intl.formatMessage(messages.title)}
>
<Box
direction="column"
gap="small"
style={{ marginTop: '0.75rem' }}
data-testid="retention-date-picker"
>
<CunninghamProvider>
<DatePicker
fullWidth
label={intl.formatMessage(messages.title)}
locale={intl.locale}
minValue={
DateTime.local()
.plus({ days: 1 })
.set({
hour: 0,
minute: 0,
second: 0,
})
.toISO() as string
}
onChange={onChange}
value={
selectedRetentionDate
? new Date(selectedRetentionDate).toISOString()
: null
}
/>
</CunninghamProvider>
<StyledAnchorButton
disabled={!selectedRetentionDate}
a11yTitle={intl.formatMessage(
messages.deleteClassroomRetentionDateButton,
)}
fill="horizontal"
label={intl.formatMessage(
messages.deleteClassroomRetentionDateButton,
)}
primary
title={intl.formatMessage(
messages.deleteClassroomRetentionDateButton,
)}
onClick={() => {
onChange(null);
}}
/>
</Box>
</FoldableItem>
<CommonRetentionDate
retentionDate={classroom.retention_date}
ressource={intl.formatMessage(messages.ressource)}
onChange={function (newRetentionDate: string | null): void {
debouncedUpdatedClassroom({
retention_date: newRetentionDate,
});
}}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock';
import { render, userTypeDatePicker } from 'lib-tests';
import { DateTime, Settings } from 'luxon';

import { InfoWidgetModalProvider } from '@lib-components/hooks/stores/useInfoWidgetModal';

import { RetentionDate } from '.';

Settings.defaultLocale = 'en';
Settings.defaultZone = 'Europe/Paris';

jest.mock('lib-components', () => ({
...jest.requireActual('lib-components'),
report: jest.fn(),
}));

const mockedOnChange = jest.fn();
const onChange = (new_retention_date: string | null) =>
mockedOnChange(new_retention_date);

describe('RetentionDate', () => {
afterEach(() => {
jest.resetAllMocks();
fetchMock.restore();
});

it('renders the component and set a date with success', async () => {
render(
<InfoWidgetModalProvider value={null}>
<RetentionDate
retentionDate={null}
ressource="test"
onChange={onChange}
/>
</InfoWidgetModalProvider>,
);

expect(screen.getAllByText('Retention date')).toBeTruthy();

const inputStartingAtDate = within(
screen.getByTestId('retention-date-picker'),
).getByRole('presentation');

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

const startingAt = DateTime.local()
.plus({ days: 1 })
.set({ second: 0, millisecond: 0 });

await userTypeDatePicker(
startingAt,
screen.getAllByText(/Retention date/i)[1],
);

expect(inputStartingAtDate).toHaveTextContent(startingAt.toLocaleString());

await waitFor(() => expect(mockedOnChange).toHaveBeenCalledTimes(1));
});

it("set a date previous at today and don't change", async () => {
render(
<InfoWidgetModalProvider value={null}>
<RetentionDate
retentionDate={null}
ressource="test"
onChange={onChange}
/>
</InfoWidgetModalProvider>,
);

expect(screen.getAllByText('Retention date')).toBeTruthy();

const inputStartingAtDate = within(
screen.getByTestId('retention-date-picker'),
).getByRole('presentation');

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

const startingAt = DateTime.local()
.minus({ days: 1 })
.set({ second: 0, millisecond: 0 });

await userTypeDatePicker(
startingAt,
screen.getAllByText(/Retention date/i)[1],
);

expect(inputStartingAtDate).toHaveTextContent(startingAt.toLocaleString());

await waitFor(() => expect(mockedOnChange).not.toHaveBeenCalled());
});

it('renders the component with a default date and deletes it', async () => {
render(
<InfoWidgetModalProvider value={null}>
<RetentionDate
retentionDate="2020-03-01"
ressource="test"
onChange={onChange}
/>
</InfoWidgetModalProvider>,
);

expect(screen.getAllByText('Retention date')).toBeTruthy();

const inputStartingAtDate = within(
screen.getByTestId('retention-date-picker'),
).getByRole('presentation');
expect(inputStartingAtDate).toHaveTextContent('3/1/2020');

const deleteButton = await screen.findByRole('button', {
name: 'Delete retention date',
});

await userEvent.click(deleteButton);

await waitFor(() => expect(mockedOnChange).toHaveBeenCalledTimes(1));
expect(inputStartingAtDate).toHaveTextContent('mm/dd/yyyy');
});
});
Loading

0 comments on commit abde855

Please sign in to comment.