-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨(front) add retention date widgets to classrooms
Add the possibility to add / edit the retention date on classrooms through a widget
- Loading branch information
Showing
5 changed files
with
252 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
...lib_classroom/src/components/ClassroomWidgetProvider/widgets/RetentionDate/index.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { fireEvent, screen, waitFor } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import fetchMock from 'fetch-mock'; | ||
import { InfoWidgetModalProvider, useJwt } from 'lib-components'; | ||
import { render } from 'lib-tests'; | ||
|
||
import { classroomMockFactory } from '@lib-classroom/utils/tests/factories'; | ||
import { wrapInClassroom } from '@lib-classroom/utils/wrapInClassroom'; | ||
|
||
import { RetentionDate } from '.'; | ||
|
||
jest.mock('lib-components', () => ({ | ||
...jest.requireActual('lib-components'), | ||
report: jest.fn(), | ||
})); | ||
|
||
describe('Classroom <RetentionDate />', () => { | ||
beforeEach(() => { | ||
useJwt.setState({ | ||
jwt: 'json web token', | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
fetchMock.restore(); | ||
}); | ||
|
||
it('renders the component and set a date with success', async () => { | ||
const mockedClassroom = classroomMockFactory(); | ||
|
||
fetchMock.mock(`/api/classrooms/${mockedClassroom.id}/`, 200, { | ||
method: 'PATCH', | ||
}); | ||
|
||
render( | ||
wrapInClassroom( | ||
<InfoWidgetModalProvider value={null}> | ||
<RetentionDate /> | ||
</InfoWidgetModalProvider>, | ||
mockedClassroom, | ||
), | ||
); | ||
|
||
expect(screen.getByText('Retention date')).toBeInTheDocument(); | ||
const datePickerInput = screen.getByRole('textbox'); | ||
|
||
fireEvent.change(datePickerInput, { | ||
target: { value: '2020/01/01' }, | ||
}); | ||
|
||
expect((datePickerInput as HTMLInputElement).value).toBe('2020/01/01'); | ||
|
||
await waitFor(() => expect(fetchMock.calls()).toHaveLength(1)); | ||
|
||
const lastCall = fetchMock.lastCall(); | ||
expect(lastCall).not.toBe(undefined); | ||
expect(lastCall?.[0]).toBe(`/api/classrooms/${mockedClassroom.id}/`); | ||
expect(lastCall?.[1]?.body).toEqual('{"retention_date":"2020-01-01"}'); | ||
expect(lastCall?.[1]?.method).toBe('PATCH'); | ||
}); | ||
|
||
it('renders the component with a default date and deletes it', async () => { | ||
const mockedClassroom = classroomMockFactory({ | ||
retention_date: '2020-01-01', | ||
}); | ||
|
||
fetchMock.mock(`/api/classrooms/${mockedClassroom.id}/`, 200, { | ||
method: 'PATCH', | ||
}); | ||
|
||
render( | ||
wrapInClassroom( | ||
<InfoWidgetModalProvider value={null}> | ||
<RetentionDate /> | ||
</InfoWidgetModalProvider>, | ||
mockedClassroom, | ||
), | ||
); | ||
|
||
expect(screen.getByText('Retention date')).toBeInTheDocument(); | ||
const datePickerInput = await screen.findByRole('textbox'); | ||
|
||
expect((datePickerInput as HTMLInputElement).value).toBe('2020/01/01'); | ||
|
||
const deleteButton = await screen.findByRole('button', { | ||
name: 'Delete retention date', | ||
}); | ||
|
||
await userEvent.click(deleteButton); | ||
|
||
await waitFor(() => expect(fetchMock.calls()).toHaveLength(1)); | ||
|
||
const lastCall = fetchMock.lastCall(); | ||
expect(lastCall).not.toBe(undefined); | ||
expect(lastCall?.[0]).toBe(`/api/classrooms/${mockedClassroom.id}/`); | ||
expect(lastCall?.[1]?.body).toEqual('{"retention_date":null}'); | ||
expect(lastCall?.[1]?.method).toBe('PATCH'); | ||
}); | ||
|
||
it('fails to update the video and displays the right error message', async () => { | ||
// Set by default with an All rights reserved license | ||
const mockedClassroom = classroomMockFactory({ | ||
retention_date: '2020-01-01', | ||
}); | ||
fetchMock.patch(`/api/classrooms/${mockedClassroom.id}/`, 401); | ||
|
||
render( | ||
wrapInClassroom( | ||
<InfoWidgetModalProvider value={null}> | ||
<RetentionDate /> | ||
</InfoWidgetModalProvider>, | ||
mockedClassroom, | ||
), | ||
); | ||
|
||
expect(screen.getByText('Retention date')).toBeInTheDocument(); | ||
|
||
const deleteButton = await screen.findByRole('button', { | ||
name: 'Delete retention date', | ||
}); | ||
|
||
await userEvent.click(deleteButton); | ||
|
||
await waitFor(() => expect(fetchMock.calls()).toHaveLength(1)); | ||
|
||
await screen.findByText('Classroom update has failed!'); | ||
}); | ||
}); |
114 changes: 114 additions & 0 deletions
114
...ages/lib_classroom/src/components/ClassroomWidgetProvider/widgets/RetentionDate/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { Box, Button, DateInput } from 'grommet'; | ||
import { Nullable } from 'lib-common'; | ||
import { Classroom, FoldableItem, debounce } from 'lib-components'; | ||
import { DateTime } from 'luxon'; | ||
import { useState } from 'react'; | ||
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', | ||
}, | ||
updateClassroomSuccess: { | ||
defaultMessage: 'Classroom updated.', | ||
description: 'Message displayed when classroom is successfully updated.', | ||
id: 'component.ClassroomRetentionDate.updateVideoSuccess', | ||
}, | ||
updateClassroomFail: { | ||
defaultMessage: 'Classroom update has failed!', | ||
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)); | ||
}, | ||
onError: () => { | ||
toast.error(intl.formatMessage(messages.updateClassroomFail)); | ||
}, | ||
}); | ||
const debouncedUpdatedClassroom = debounce<Classroom>( | ||
(updatedClassroomProperty: Partial<Classroom>) => { | ||
updateClassroomMutation.mutate(updatedClassroomProperty); | ||
}, | ||
); | ||
|
||
function onChange(new_retention_date: string | string[]) { | ||
let new_retention_date_formatted = null; | ||
if (new_retention_date && typeof new_retention_date === 'string') { | ||
const utcDateTime = DateTime.fromISO(new_retention_date, { zone: 'utc' }); | ||
const localDateTime = utcDateTime.toLocal(); | ||
new_retention_date_formatted = localDateTime.toFormat('yyyy-MM-dd'); | ||
} | ||
debouncedUpdatedClassroom({ retention_date: new_retention_date_formatted }); | ||
setSelectedRetentionDate(new_retention_date_formatted); | ||
} | ||
|
||
return ( | ||
<FoldableItem | ||
infoText={intl.formatMessage(messages.info)} | ||
initialOpenValue | ||
title={intl.formatMessage(messages.title)} | ||
> | ||
<Box direction="column" gap="small" style={{ marginTop: '0.75rem' }}> | ||
<DateInput | ||
format={intl.locale === 'fr' ? 'dd/mm/yyyy' : 'yyyy/mm/dd'} | ||
value={selectedRetentionDate || ''} | ||
onChange={({ value }) => onChange(value)} | ||
/> | ||
<StyledAnchorButton | ||
disabled={!selectedRetentionDate} | ||
a11yTitle={intl.formatMessage( | ||
messages.deleteClassroomRetentionDateButton, | ||
)} | ||
fill="horizontal" | ||
label={intl.formatMessage( | ||
messages.deleteClassroomRetentionDateButton, | ||
)} | ||
primary | ||
title={intl.formatMessage( | ||
messages.deleteClassroomRetentionDateButton, | ||
)} | ||
onClick={() => { | ||
onChange(''); | ||
}} | ||
/> | ||
</Box> | ||
</FoldableItem> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters