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

✨(website) creates lives on the website #2134

Merged
merged 1 commit into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Versioning](https://semver.org/spec/v2.0.0.html).
- Live session model
- Livesession backend rewrite
- Add sentry
- Create a live from the website (#2134)
- Add a License Manager widget for LTI VOD view
- Add a title to the classroom file dropzone
- Add can_edit property on a serialized video
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ jest.mock('../Contents/Contents', () => ({
jest.mock('features/Contents', () => ({
ClassRoomRouter: () => <div>My ClassRoomRouter</div>,
VideoRouter: () => <div>My VideoRouter</div>,
LiveRouter: () => <div>My LiveRouter</div>,
}));

describe('<ContentsRouter/>', () => {
Expand Down Expand Up @@ -38,4 +39,11 @@ describe('<ContentsRouter/>', () => {
});
expect(screen.getByText('My VideoRouter')).toBeInTheDocument();
});

test('render route /my-contents/lives', () => {
render(<ContentsRouter />, {
routerOptions: { history: ['/my-contents/lives'] },
});
expect(screen.getByText('My LiveRouter')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from 'lib-tests';

import LiveCreate from './LiveCreate';

jest.mock('./LiveCreateForm', () => ({
__esModule: true,
default: () => <div>My WebinarCreate Form</div>,
}));

describe('<LiveCreate />', () => {
test('renders LiveCreate', () => {
render(<LiveCreate />);

const button = screen.getByRole('button', { name: /Create Webinar/i });
expect(button).toBeInTheDocument();
expect(
screen.queryByRole('heading', { name: /Create Webinar/i }),
).not.toBeInTheDocument();

userEvent.click(button);

expect(
screen.getByRole('heading', { name: /Create Webinar/i }),
).toBeInTheDocument();
expect(screen.getByText('My WebinarCreate Form')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Button, Heading, Text } from 'grommet';
import { useResponsive } from 'lib-components';
import { Fragment } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { Link, Route, Switch, useHistory } from 'react-router-dom';

import { WhiteCard } from 'components/Cards';
import { Modal } from 'components/Modal';
import { routes } from 'routes';

import LiveCreateForm from './LiveCreateForm';

const messages = defineMessages({
WebinarTitle: {
defaultMessage: 'Webinars',
description: 'Webinars title',
id: 'features.Contents.features.Webinar.Create.WebinarTitle',
},
CreateWebinarLabel: {
defaultMessage: 'Create Webinar',
description: 'Text heading create webinar.',
id: 'features.Contents.features.Webinar.Create.CreateWebinarLabel',
},
});

const LiveCreate = () => {
const intl = useIntl();
const { breakpoint } = useResponsive();
const history = useHistory();

const liveRoute = routes.CONTENTS.subRoutes.LIVE;
const livePath = liveRoute.path;
const liveCreatePath = liveRoute.subRoutes?.CREATE?.path || '';

return (
<Fragment>
<WhiteCard
flex="shrink"
direction={breakpoint === 'xxsmall' ? 'column' : 'row'}
gap={breakpoint === 'xxsmall' ? 'small' : 'none'}
justify="between"
align="center"
height={{ min: '5rem' }}
margin={{ bottom: 'medium' }}
>
<Text size="large" weight="bold">
{intl.formatMessage(messages.WebinarTitle)}
</Text>
<Link to={liveCreatePath}>
<Button
primary
label={intl.formatMessage(messages.CreateWebinarLabel)}
/>
</Link>
</WhiteCard>
<Switch>
<Route path={liveCreatePath} exact>
<Modal
isOpen
onClose={() => {
history.push(livePath);
}}
>
<Heading
level={2}
margin={{ top: 'xxsmall' }}
textAlign="center"
weight="bold"
>
{intl.formatMessage(messages.CreateWebinarLabel)}
</Heading>
<LiveCreateForm />
</Modal>
</Route>
</Switch>
</Fragment>
);
};

export default LiveCreate;
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import { screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock';
import { createMemoryHistory } from 'history';
import { render, Deferred } from 'lib-tests';
import { Router } from 'react-router-dom';

import LiveCreateForm from './LiveCreateForm';

const playlistsResponse = {
count: 1,
next: null,
previous: null,
results: [
{ id: 'some-playlist-id', title: 'some playlist title' },
{ id: 'an-other-playlist', title: 'an other title' },
],
};

const consoleError = jest
.spyOn(console, 'error')
.mockImplementation(() => jest.fn());

let deferredPlaylists: Deferred<unknown>;
describe('<LiveCreateForm />', () => {
beforeEach(() => {
deferredPlaylists = new Deferred();
fetchMock.get(
'/api/playlists/?limit=20&offset=0&ordering=-created_on&can_edit=true',
deferredPlaylists.promise,
);
});

afterEach(() => {
fetchMock.restore();
jest.resetAllMocks();
consoleError.mockClear();
});

test('renders LiveCreateForm', async () => {
render(<LiveCreateForm />);

deferredPlaylists.resolve(playlistsResponse);

expect(screen.getByRole('textbox', { name: /title/i })).toBeInTheDocument();
expect(
await screen.findByRole('button', {
name: 'Choose the playlist.; Selected: some-playlist-id',
}),
).toBeInTheDocument();
expect(
screen.getByRole('textbox', { name: /description/i }),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: /Add Webinar/i }),
).toBeInTheDocument();
});

test('fields mandatory', async () => {
render(<LiveCreateForm />);

deferredPlaylists.resolve(playlistsResponse);

fireEvent.change(screen.getByRole('textbox', { name: /title/i }), {
target: { value: 'my title' },
});

userEvent.click(
await screen.findByRole('button', {
name: 'Choose the playlist.; Selected: some-playlist-id',
}),
);

userEvent.click(
await screen.findByRole('option', { name: 'an other title' }),
);

expect(
await screen.findByRole('button', {
name: 'Choose the playlist.; Selected: an-other-playlist',
}),
).toBeInTheDocument();

await waitFor(() =>
expect(
screen.getByRole('button', { name: /Add Webinar/i }),
).not.toBeDisabled(),
);
});

test('fields are posted correctly', async () => {
const history = createMemoryHistory();
fetchMock.post('/api/videos/', {
ok: true,
id: '1234',
});

fetchMock.mock(
'/api/videos/1234/initiate-live/',
{
ok: true,
id: '1234',
is_live: true,
},
{
method: 'POST',
},
);

render(
<Router history={history}>
<LiveCreateForm />
</Router>,
);

deferredPlaylists.resolve(playlistsResponse);

fireEvent.change(screen.getByRole('textbox', { name: /title/i }), {
target: { value: 'my title' },
});

fireEvent.change(screen.getByRole('textbox', { name: /description/i }), {
target: { value: 'my description' },
});

userEvent.click(
await screen.findByRole('button', {
name: 'Choose the playlist.; Selected: some-playlist-id',
}),
);

userEvent.click(
await screen.findByRole('option', { name: 'an other title' }),
);

expect(
await screen.findByRole('button', {
name: 'Choose the playlist.; Selected: an-other-playlist',
}),
).toBeInTheDocument();

const submit = screen.getByRole('button', { name: /Add Webinar/i });

await waitFor(() => expect(submit).not.toBeDisabled());

userEvent.click(submit);

await waitFor(() => {
expect(
fetchMock.lastCall('/api/videos/', {
method: 'POST',
})?.[1],
).toEqual({
headers: { 'Content-Type': 'application/json' },
method: 'POST',
body: JSON.stringify({
playlist: 'an-other-playlist',
title: 'my title',
live_type: 'jitsi',
description: 'my description',
}),
});
});

await waitFor(() => {
expect(history.location.pathname).toBe('/my-contents/lives/1234');
});
});

test('post failed', async () => {
fetchMock.post('/api/videos/', 500);

render(<LiveCreateForm />);

deferredPlaylists.resolve(playlistsResponse);

fireEvent.change(screen.getByRole('textbox', { name: /title/i }), {
target: { value: 'my title' },
});

fireEvent.change(screen.getByRole('textbox', { name: /description/i }), {
target: { value: 'my description' },
});

userEvent.click(
await screen.findByRole('button', {
name: 'Choose the playlist.; Selected: some-playlist-id',
}),
);

userEvent.click(
await screen.findByRole('option', { name: 'an other title' }),
);

expect(
await screen.findByRole('button', {
name: 'Choose the playlist.; Selected: an-other-playlist',
}),
).toBeInTheDocument();

const submit = screen.getByRole('button', { name: /Add Webinar/i });

await waitFor(() => expect(submit).not.toBeDisabled());

userEvent.click(submit);

expect(
await screen.findByText(
/Sorry, an error has occurred. Please try again./i,
),
).toBeInTheDocument();

expect(consoleError).toHaveBeenCalled();
});

test('error playlist', async () => {
render(<LiveCreateForm />);

deferredPlaylists.resolve(500);

expect(
await screen.findByText(
/Sorry, an error has occurred. Please try again./i,
),
).toBeInTheDocument();

expect(consoleError).toHaveBeenCalled();
});
});
Loading