-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #366 from open-craft/farhaan/fix-drag-drop-component
Re-write the dropzone component and fix styling issues
- Loading branch information
Showing
14 changed files
with
1,036 additions
and
496 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
78 changes: 78 additions & 0 deletions
78
src/editors/containers/VideoUploadEditor/VideoUploader.jsx
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,78 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { useIntl } from '@edx/frontend-platform/i18n'; | ||
import { | ||
Icon, IconButton, Dropzone, InputGroup, FormControl, | ||
} from '@edx/paragon'; | ||
import { ArrowForward, FileUpload } from '@edx/paragon/icons'; | ||
import { useDispatch } from 'react-redux'; | ||
import { thunkActions } from '../../data/redux'; | ||
import * as hooks from './hooks'; | ||
import messages from './messages'; | ||
|
||
const URLUploader = () => { | ||
const intl = useIntl(); | ||
return ( | ||
<div className="d-flex flex-column flex-wrap"> | ||
<div className="justify-content-center align-self-center bg-light rounded-circle p-4"> | ||
<Icon src={FileUpload} className="text-muted" /> | ||
</div> | ||
<div className="d-flex align-self-center justify-content-center flex-wrap flex-column pt-5"> | ||
<span style={{ fontSize: '1.35rem' }}>{intl.formatMessage(messages.dropVideoFileHere)}</span> | ||
<span className="align-self-center" style={{ fontSize: '0.8rem' }}>{intl.formatMessage(messages.info)}</span> | ||
</div> | ||
<div className="align-self-center justify-content-center mx-2 text-dark">OR</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export const VideoUploader = ({ setLoading }) => { | ||
const [textInputValue, setTextInputValue] = React.useState(''); | ||
const onURLUpload = hooks.onVideoUpload(); | ||
const intl = useIntl(); | ||
const dispatch = useDispatch(); | ||
|
||
const handleProcessUpload = ({ fileData }) => { | ||
dispatch(thunkActions.video.uploadVideo({ | ||
supportedFiles: [fileData], | ||
setLoadSpinner: setLoading, | ||
postUploadRedirect: hooks.onVideoUpload(), | ||
})); | ||
}; | ||
|
||
return ( | ||
<div> | ||
<Dropzone | ||
accept={{ 'video/*': ['.mp4', '.mov'] }} | ||
onProcessUpload={handleProcessUpload} | ||
inputComponent={<URLUploader />} | ||
/> | ||
<div className="d-flex video-id-prompt"> | ||
<InputGroup> | ||
<FormControl | ||
placeholder={intl.formatMessage(messages.pasteURL)} | ||
aria-label={intl.formatMessage(messages.pasteURL)} | ||
aria-describedby="basic-addon2" | ||
borderless | ||
onChange={(event) => { setTextInputValue(event.target.value); }} | ||
/> | ||
<div className="justify-content-center align-self-center bg-light rounded-circle p-0 x-small url-submit-button"> | ||
<IconButton | ||
alt={intl.formatMessage(messages.submitButtonAltText)} | ||
src={ArrowForward} | ||
iconAs={Icon} | ||
size="inline" | ||
onClick={() => { onURLUpload(textInputValue); }} | ||
/> | ||
</div> | ||
</InputGroup> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
VideoUploader.propTypes = { | ||
setLoading: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default VideoUploader; |
94 changes: 94 additions & 0 deletions
94
src/editors/containers/VideoUploadEditor/VideoUploader.test.jsx
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,94 @@ | ||
import React from 'react'; | ||
import { render, fireEvent, act } from '@testing-library/react'; | ||
import { IntlProvider } from '@edx/frontend-platform/i18n'; | ||
import { initializeMockApp } from '@edx/frontend-platform'; | ||
import { configureStore } from '@reduxjs/toolkit'; | ||
import { AppProvider } from '@edx/frontend-platform/react'; | ||
import '@testing-library/jest-dom'; | ||
import * as redux from 'react-redux'; | ||
import * as hooks from './hooks'; | ||
import { VideoUploader } from './VideoUploader'; | ||
|
||
jest.unmock('react-redux'); | ||
jest.unmock('@edx/frontend-platform/i18n'); | ||
jest.unmock('@edx/paragon'); | ||
jest.unmock('@edx/paragon/icons'); | ||
|
||
describe('VideoUploader', () => { | ||
const setLoadingMock = jest.fn(); | ||
const onURLUploadMock = jest.fn(); | ||
let store; | ||
|
||
beforeEach(async () => { | ||
store = configureStore({ | ||
reducer: (state, action) => ((action && action.newState) ? action.newState : state), | ||
preloadedState: { | ||
app: { | ||
learningContextId: 'course-v1:test+test+test', | ||
blockId: 'some-block-id', | ||
}, | ||
}, | ||
}); | ||
|
||
initializeMockApp({ | ||
authenticatedUser: { | ||
userId: 3, | ||
username: 'test-user', | ||
administrator: true, | ||
roles: [], | ||
}, | ||
}); | ||
}); | ||
|
||
const renderComponent = async (storeParam, setLoadingMockParam) => render( | ||
<AppProvider store={storeParam}> | ||
<IntlProvider locale="en"> | ||
<VideoUploader setLoading={setLoadingMockParam} /> | ||
</IntlProvider>, | ||
</AppProvider>, | ||
); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('renders as expected with default behavior', async () => { | ||
expect(await renderComponent(store, setLoadingMock)).toMatchSnapshot(); | ||
}); | ||
|
||
it('calls onURLUpload when URL submit button is clicked', async () => { | ||
const onVideoUploadSpy = jest.spyOn(hooks, 'onVideoUpload').mockImplementation(() => onURLUploadMock); | ||
|
||
const { getByPlaceholderText, getAllByRole } = await renderComponent(store, setLoadingMock); | ||
|
||
const urlInput = getByPlaceholderText('Paste your video ID or URL'); | ||
const urlSubmitButton = getAllByRole('button', { name: /submit/i }); | ||
expect(urlSubmitButton).toHaveLength(1); | ||
|
||
fireEvent.change(urlInput, { target: { value: 'https://example.com/video.mp4' } }); | ||
urlSubmitButton.forEach((button) => fireEvent.click(button)); | ||
expect(onURLUploadMock).toHaveBeenCalledWith('https://example.com/video.mp4'); | ||
|
||
onVideoUploadSpy.mockRestore(); | ||
}); | ||
|
||
it('calls handleProcessUpload when file is selected', async () => { | ||
const useDispatchSpy = jest.spyOn(redux, 'useDispatch'); | ||
const mockDispatchFn = jest.fn(); | ||
useDispatchSpy.mockReturnValue(mockDispatchFn); | ||
|
||
const { getByTestId } = await renderComponent(store, setLoadingMock); | ||
|
||
const fileInput = getByTestId('dropzone-container'); | ||
const file = new File(['file'], 'video.mp4', { | ||
type: 'video/mp4', | ||
}); | ||
Object.defineProperty(fileInput, 'files', { | ||
value: [file], | ||
}); | ||
await act(async () => fireEvent.drop(fileInput)); | ||
// Test dispacting thunkAction | ||
expect(mockDispatchFn).toHaveBeenCalledWith(expect.any(Function)); | ||
useDispatchSpy.mockRestore(); | ||
}); | ||
}); |
Oops, something went wrong.