Allow closing upload modal in B&I

refs MAT-168
flag=rce_buttons_and_icons

Test Plan
- Open a buttons and icons tray
- Navigate to the image section
- Click "Upload Image"
> Verify an image upload modal appears
> Verify pressing "escape" key closes
  the modal
> Verify pressing the "x" in the top
  right corner closes the modal
> Verify pressing the "Close" button in
  the modal footer closes the modal
> Verify the modal can be re-opened after
  being closed

Change-Id: I139885b1d693a910b22a4a2e64a9cf5170031596
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/283227
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Joe Hernandez <joe.hernandez@instructure.com>
QA-Review: Joe Hernandez <joe.hernandez@instructure.com>
Product-Review: Weston Dransfield <wdransfield@instructure.com>
This commit is contained in:
Weston Dransfield 2022-01-20 12:27:36 -07:00
parent ba9bfc7b42
commit 162b421ea6
6 changed files with 59 additions and 25 deletions

View File

@ -19,17 +19,20 @@
import React from 'react'
import formatMessage from '../../../../../../format-message'
import {actions} from '../../../reducers/imageSection'
import {UploadFile} from '../../../../shared/Upload/UploadFile'
const Upload = ({editor}) => {
const Upload = ({editor, dispatch}) => {
return (
<UploadFile
accept={undefined}
editor={editor}
label={formatMessage('Upload Image')}
panels={['COMPUTER']}
onDismiss={() => {}}
onDismiss={() => {
dispatch(actions.CLEAR_MODE)
}}
/>
)
}

View File

@ -131,17 +131,15 @@ describe('ImageSection', () => {
})
describe('when the "upload image" mode is selected', () => {
let getByText
let rendered
beforeEach(() => {
fetchMock.mock('/api/session', '{}')
const rendered = subject({editor: new FakeEditor()})
rendered = subject({editor: new FakeEditor()})
getByText = rendered.getByText
fireEvent.click(getByText('Add Image'))
fireEvent.click(getByText('Upload Image'))
fireEvent.click(rendered.getByText('Add Image'))
fireEvent.click(rendered.getByText('Upload Image'))
})
afterEach(() => {
@ -149,7 +147,18 @@ describe('ImageSection', () => {
})
it('renders the image upload modal', async () => {
await waitFor(() => expect(getByText('Upload Image')).toBeInTheDocument())
await waitFor(() => expect(rendered.getByText('Upload Image')).toBeInTheDocument())
})
describe('and the the "close" button is clicked', () => {
beforeEach(async () => {
const button = await rendered.findAllByText(/Close/i)
fireEvent.click(button[0])
})
it('closes the modal', () => {
expect(rendered.queryByText('Upload Image')).toBe(null)
})
})
})

View File

@ -17,7 +17,7 @@
*/
import React from 'react'
import {render, waitFor} from '@testing-library/react'
import {render, waitFor, fireEvent} from '@testing-library/react'
import Upload from '../Upload'
import FakeEditor from '../../../../../shared/__tests__/FakeEditor'
import fetchMock from 'fetch-mock'
@ -31,20 +31,35 @@ jest.mock('../../../../../../../bridge', () => {
})
let props
const subject = overrides => render(<Upload {...props} />)
const subject = () => render(<Upload {...props} />)
describe('Upload()', () => {
beforeEach(() => {
props = {editor: new FakeEditor()}
props = {editor: new FakeEditor(), dispatch: jest.fn()}
fetchMock.mock('/api/session', '{}')
})
afterEach(() => {
fetchMock.restore()
jest.clearAllMocks()
})
it('renders an upload modal', async () => {
const {getAllByText} = subject(props)
await waitFor(() => expect(getAllByText('Upload Image').length).toBe(2))
})
describe('when the "Close" button is pressed', () => {
let rendered
beforeEach(async () => {
rendered = subject()
const button = await rendered.findAllByText(/Close/i)
fireEvent.click(button[0])
})
it('closes the modal', async () => {
expect(props.dispatch).toHaveBeenCalled()
})
})
})

View File

@ -75,7 +75,7 @@ describe('<CreateButtonForm />', () => {
xmlns="http://www.w3.org/2000/svg"
>
<metadata>
{"name":"","alt":"","shape":"square","size":"small","color":null,"outlineColor":null,"outlineSize":"none","text":"","textSize":"small","textColor":null,"textBackgroundColor":null,"textPosition":"middle","encodedImage":"","encodedImageType":"course","encodedImageName":""}
{"name":"","alt":"","shape":"square","size":"small","color":null,"outlineColor":null,"outlineSize":"none","text":"","textSize":"small","textColor":null,"textBackgroundColor":null,"textPosition":"middle","encodedImage":"","encodedImageType":"","encodedImageName":""}
</metadata>
<svg
fill="none"

View File

@ -16,64 +16,68 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import imageSection, {defaultState} from '../imageSection'
import imageSection, {initialState} from '../imageSection'
describe('imageSection()', () => {
const subject = (action, stateOverrides) =>
imageSection({...defaultState, ...stateOverrides}, action)
imageSection({...initialState, ...stateOverrides}, action)
it('handles "ClearMode" actions', () => {
expect(subject({type: 'ClearMode'})).toMatchObject(initialState)
})
it('handles "Course" actions', () => {
expect(subject({type: 'Course'})).toMatchObject({
...defaultState,
...initialState,
mode: 'Course'
})
})
it('handles "Upload" actions', () => {
expect(subject({type: 'Upload'})).toMatchObject({
...defaultState,
...initialState,
mode: 'Upload'
})
})
it('handles "SingleColor" actions', () => {
expect(subject({type: 'SingleColor'})).toMatchObject({
...defaultState,
...initialState,
mode: 'SingleColor'
})
})
it('handles "MultiColor" actions', () => {
expect(subject({type: 'MultiColor'})).toMatchObject({
...defaultState,
...initialState,
mode: 'MultiColor'
})
})
it('handles "StartLoading" actions', () => {
expect(subject({type: 'StartLoading'})).toMatchObject({
...defaultState,
...initialState,
loading: true
})
})
it('handles "StopLoading" actions', () => {
expect(subject({type: 'StopLoading'})).toMatchObject({
...defaultState,
...initialState,
loading: false
})
})
it('handles "SetImage" actions', () => {
expect(subject({type: 'SetImage', payload: 'img'})).toMatchObject({
...defaultState,
...initialState,
image: 'img'
})
})
it('handles "SetImageName" actions', () => {
expect(subject({type: 'SetImageName', payload: 'name'})).toMatchObject({
...defaultState,
...initialState,
imageName: 'name'
})
})

View File

@ -19,7 +19,7 @@
import formatMessage from '../../../../format-message'
export const initialState = {
mode: 'course', // TODO: Update to 'upload' once we support it
mode: '',
image: '',
imageName: '',
loading: false,
@ -30,7 +30,8 @@ export const actions = {
SET_IMAGE: {type: 'SetImage'},
SET_IMAGE_NAME: {type: 'SetImageName'},
START_LOADING: {type: 'StartLoading'},
STOP_LOADING: {type: 'StopLoading'}
STOP_LOADING: {type: 'StopLoading'},
CLEAR_MODE: {type: 'ClearMode'}
}
export const modes = {
@ -50,6 +51,8 @@ const imageSection = (state, action) => {
return {...state, image: action.payload}
case actions.SET_IMAGE_NAME.type:
return {...state, imageName: action.payload}
case actions.CLEAR_MODE.type:
return {...state, mode: ''}
case modes.uploadImages.type:
return {...state, mode: modes.uploadImages.type}
case modes.singleColorImages.type: