A2: implement delete button
closes ADMIN-2233 test plan: - click the delete button which will open a confirmation dialog that roughly matches the design - a11y for the dialog should make sense - the close button should dismiss the dialog with no effect - the cancel button should dismiss the dialog with no effect - the delete button should start a delete operation - when the delete operation completes, you should be redirected to the assignment index screen with a message indicating the assignment has been deleted - if your user does not have permission to delete the assignment, or some other error occurs, then the in-development error screen should be shown Change-Id: I0c3304360d3c389296bd0910d02d8215bdb0ac9e Reviewed-on: https://gerrit.instructure.com/177754 Reviewed-by: Carl Kibler <ckibler@instructure.com> Tested-by: Jenkins QA-Review: Carl Kibler <ckibler@instructure.com> Product-Review: Jon Willesen <jonw+gerrit@instructure.com>
This commit is contained in:
parent
57b7d0ca0e
commit
d82b5fafe6
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {bool, func, string} from 'prop-types'
|
||||
|
||||
import Button from '@instructure/ui-buttons/lib/components/Button'
|
||||
import CloseButton from '@instructure/ui-buttons/lib/components/CloseButton'
|
||||
import Heading from '@instructure/ui-elements/lib/components/Heading'
|
||||
|
||||
import Mask from '@instructure/ui-overlays/lib/components/Mask'
|
||||
import Modal, {
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter
|
||||
} from '@instructure/ui-overlays/lib/components/Modal'
|
||||
import Spinner from '@instructure/ui-elements/lib/components/Spinner'
|
||||
import Text from '@instructure/ui-elements/lib/components/Text'
|
||||
import View from '@instructure/ui-layout/lib/components/View'
|
||||
|
||||
export default class ConfirmDialog extends React.Component {
|
||||
static propTypes = {
|
||||
open: bool,
|
||||
working: bool,
|
||||
|
||||
modalLabel: string, // defaults to heading
|
||||
heading: string,
|
||||
message: string.isRequired,
|
||||
confirmLabel: string.isRequired,
|
||||
cancelLabel: string.isRequired,
|
||||
closeLabel: string.isRequired,
|
||||
spinnerLabel: string.isRequired,
|
||||
|
||||
onClose: func,
|
||||
onConfirm: func,
|
||||
onCancel: func
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
open: false,
|
||||
heading: '',
|
||||
onClose: () => {},
|
||||
onConfirm: () => {},
|
||||
onCancel: () => {}
|
||||
}
|
||||
|
||||
setTestIdCloseButton(buttonElt) {
|
||||
if (!buttonElt) return
|
||||
buttonElt.setAttribute('data-testid', 'confirm-dialog-close-button')
|
||||
}
|
||||
|
||||
setTestIdCancelButton(buttonElt) {
|
||||
if (!buttonElt) return
|
||||
buttonElt.setAttribute('data-testid', 'confirm-dialog-cancel-button')
|
||||
}
|
||||
|
||||
setTestIdConfirmButton(buttonElt) {
|
||||
if (!buttonElt) return
|
||||
buttonElt.setAttribute('data-testid', 'confirm-dialog-confirm-button')
|
||||
}
|
||||
|
||||
modalLabel() {
|
||||
return this.props.modalLabel ? this.props.modalLabel : this.props.heading
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal label={this.modalLabel()} open={this.props.open}>
|
||||
<ModalHeader>
|
||||
<Heading level="h2">{this.props.heading}</Heading>
|
||||
<CloseButton
|
||||
placement="end"
|
||||
onClick={this.props.onClose}
|
||||
buttonRef={this.setTestIdCloseButton}
|
||||
disabled={this.props.working}
|
||||
>
|
||||
{this.props.closeLabel}
|
||||
</CloseButton>
|
||||
</ModalHeader>
|
||||
<ModalBody padding="0">
|
||||
<View as="div" padding="medium" style={{position: 'relative'}}>
|
||||
<Text size="large">{this.props.message}</Text>
|
||||
{this.props.working ? (
|
||||
<Mask>
|
||||
<Spinner size="small" title={this.props.spinnerLabel} />
|
||||
</Mask>
|
||||
) : null}
|
||||
</View>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onClick={this.props.onCancel}
|
||||
margin="0 x-small 0 0"
|
||||
disabled={this.props.working}
|
||||
buttonRef={this.setTestIdCancelButton}
|
||||
>
|
||||
{this.props.cancelLabel}
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={this.props.onConfirm}
|
||||
margin="0 x-small 0 0"
|
||||
disabled={this.props.working}
|
||||
buttonRef={this.setTestIdConfirmButton}
|
||||
>
|
||||
{this.props.confirmLabel}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -38,12 +38,14 @@ export default class Header extends React.Component {
|
|||
static propTypes = {
|
||||
assignment: TeacherAssignmentShape.isRequired,
|
||||
onUnsubmittedClick: func,
|
||||
onPublishChange: func
|
||||
onPublishChange: func,
|
||||
onDelete: func
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
onUnsubmittedClick: () => {},
|
||||
onPublishChange: () => {}
|
||||
onPublishChange: () => {},
|
||||
onDelete: () => {}
|
||||
}
|
||||
|
||||
renderIcon() {
|
||||
|
|
|
@ -18,11 +18,15 @@
|
|||
|
||||
import React from 'react'
|
||||
import {string} from 'prop-types'
|
||||
import I18n from 'i18n!assignments_2'
|
||||
|
||||
import ScreenReaderContent from '@instructure/ui-a11y/lib/components/ScreenReaderContent'
|
||||
|
||||
import {queryAssignment, setWorkflow} from '../api'
|
||||
import Header from './Header'
|
||||
import ContentTabs from './ContentTabs'
|
||||
|
||||
import ConfirmDialog from './ConfirmDialog'
|
||||
import MessageStudentsWho from './MessageStudentsWho'
|
||||
import TeacherViewContext, {TeacherViewContextDefaults} from './TeacherViewContext'
|
||||
|
||||
|
@ -36,6 +40,8 @@ export default class TeacherView extends React.Component {
|
|||
this.state = {
|
||||
messageStudentsWhoOpen: false,
|
||||
assignment: {},
|
||||
confirmDelete: false,
|
||||
deletingNow: false,
|
||||
loading: true,
|
||||
errors: []
|
||||
}
|
||||
|
@ -67,10 +73,10 @@ export default class TeacherView extends React.Component {
|
|||
return {assignment: {...state.assignment, ...updates}}
|
||||
}
|
||||
|
||||
async setWorkflowApiCall(newAssignmentState) {
|
||||
async setWorkflowApiCall(assignment, newAssignmentState) {
|
||||
const errors = []
|
||||
try {
|
||||
const {graphqlErrors} = await setWorkflow(this.state.assignment, newAssignmentState)
|
||||
const {errors: graphqlErrors} = await setWorkflow(assignment, newAssignmentState)
|
||||
if (graphqlErrors) errors.push(...graphqlErrors)
|
||||
} catch (error) {
|
||||
errors.push(error)
|
||||
|
@ -82,8 +88,8 @@ export default class TeacherView extends React.Component {
|
|||
const oldAssignmentState = this.state.assignment.state
|
||||
|
||||
// be optimistic
|
||||
this.setState(state => this.assignmentStateUpdate(state, {state: newAssignmentState}))
|
||||
const errors = await this.setWorkflowApiCall(newAssignmentState)
|
||||
this.setState(state => this.assignmentStateUpdate(state, newAssignmentState))
|
||||
const errors = await this.setWorkflowApiCall(this.state.assignment, newAssignmentState)
|
||||
if (errors.length > 0) {
|
||||
this.setState(state => ({
|
||||
errors,
|
||||
|
@ -100,6 +106,35 @@ export default class TeacherView extends React.Component {
|
|||
this.setState({messageStudentsWhoOpen: false})
|
||||
}
|
||||
|
||||
handleDeleteButtonPressed = () => {
|
||||
this.setState({confirmDelete: true})
|
||||
}
|
||||
|
||||
handleCancelDelete = () => {
|
||||
this.setState({confirmDelete: false})
|
||||
}
|
||||
|
||||
handleReallyDelete = async () => {
|
||||
this.setState({deletingNow: true})
|
||||
const errors = await this.setWorkflowApiCall(this.state.assignment, 'deleted')
|
||||
if (errors.length === 0) {
|
||||
this.handleDeleteSuccess()
|
||||
} else {
|
||||
this.handleDeleteError(errors)
|
||||
}
|
||||
}
|
||||
|
||||
handleDeleteSuccess = () => {
|
||||
// reloading a deleted assignment has the effect of redirecting to the
|
||||
// assignments index page with a flash message indicating the assignment
|
||||
// has been deleted.
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
handleDeleteError = errors => {
|
||||
this.setState({errors, confirmDelete: false, deletingNow: false})
|
||||
}
|
||||
|
||||
renderErrors() {
|
||||
return <pre>Error: {JSON.stringify(this.state.errors, null, 2)}</pre>
|
||||
}
|
||||
|
@ -108,6 +143,25 @@ export default class TeacherView extends React.Component {
|
|||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
renderConfirmDialog() {
|
||||
return (
|
||||
<ConfirmDialog
|
||||
open={this.state.confirmDelete}
|
||||
working={this.state.deletingNow}
|
||||
modalLabel={I18n.t('confirm delete')}
|
||||
heading={I18n.t('Delete')}
|
||||
message={I18n.t('Are you sure you want to delete this assignment?')}
|
||||
confirmLabel={I18n.t('Delete')}
|
||||
cancelLabel={I18n.t('Cancel')}
|
||||
closeLabel={I18n.t('close')}
|
||||
spinnerLabel={I18n.t('deleting assignment')}
|
||||
onClose={this.handleCancelDelete}
|
||||
onCancel={this.handleCancelDelete}
|
||||
onConfirm={this.handleReallyDelete}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.loading) return this.renderLoading()
|
||||
if (this.state.errors.length > 0) return this.renderErrors()
|
||||
|
@ -115,6 +169,7 @@ export default class TeacherView extends React.Component {
|
|||
return (
|
||||
<TeacherViewContext.Provider value={this.contextValue}>
|
||||
<div>
|
||||
{this.renderConfirmDialog()}
|
||||
<ScreenReaderContent>
|
||||
<h1>{assignment.name}</h1>
|
||||
</ScreenReaderContent>
|
||||
|
@ -122,6 +177,7 @@ export default class TeacherView extends React.Component {
|
|||
assignment={assignment}
|
||||
onUnsubmittedClick={this.handleUnsubmittedClick}
|
||||
onPublishChange={this.handlePublishChange}
|
||||
onDelete={this.handleDeleteButtonPressed}
|
||||
/>
|
||||
<ContentTabs assignment={assignment} />
|
||||
<MessageStudentsWho
|
||||
|
|
|
@ -44,12 +44,14 @@ export default class Toolbox extends React.Component {
|
|||
static propTypes = {
|
||||
assignment: TeacherAssignmentShape.isRequired,
|
||||
onUnsubmittedClick: func,
|
||||
onPublishChange: func
|
||||
onPublishChange: func,
|
||||
onDelete: func
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
onUnsubmittedClick: () => {},
|
||||
onPublishChange: () => {}
|
||||
onPublishChange: () => {},
|
||||
onDelete: () => {}
|
||||
}
|
||||
|
||||
submissions() {
|
||||
|
@ -82,8 +84,8 @@ export default class Toolbox extends React.Component {
|
|||
|
||||
renderDelete() {
|
||||
return (
|
||||
<Button margin="0 0 0 x-small" icon={<IconTrash />}>
|
||||
<ScreenReaderContent>{I18n.t('Delete')}</ScreenReaderContent>
|
||||
<Button margin="0 0 0 x-small" icon={<IconTrash />} onClick={this.props.onDelete}>
|
||||
<ScreenReaderContent>{I18n.t('delete assignment')}</ScreenReaderContent>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {render, fireEvent} from 'react-testing-library'
|
||||
|
||||
import ConfirmDialog from '../ConfirmDialog'
|
||||
|
||||
function renderConfirmDialog(overrideProps = {}) {
|
||||
const props = {
|
||||
open: true,
|
||||
working: false,
|
||||
heading: 'the thing',
|
||||
message: 'do you want to do the thing?',
|
||||
confirmLabel: 'do the thing',
|
||||
cancelLabel: 'refrain from doing the thing',
|
||||
closeLabel: 'close the dialog',
|
||||
spinnerLabel: 'doing the thing',
|
||||
...overrideProps
|
||||
}
|
||||
return render(<ConfirmDialog {...props} />)
|
||||
}
|
||||
|
||||
it('triggers close', () => {
|
||||
const onClose = jest.fn()
|
||||
const {getByTestId} = renderConfirmDialog({onClose})
|
||||
fireEvent.click(getByTestId('confirm-dialog-close-button'))
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('triggers cancel', () => {
|
||||
const onCancel = jest.fn()
|
||||
const {getByTestId} = renderConfirmDialog({onCancel})
|
||||
fireEvent.click(getByTestId('confirm-dialog-cancel-button'))
|
||||
expect(onCancel).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('triggers confirm', () => {
|
||||
const onConfirm = jest.fn()
|
||||
const {getByTestId} = renderConfirmDialog({onConfirm})
|
||||
fireEvent.click(getByTestId('confirm-dialog-confirm-button'))
|
||||
expect(onConfirm).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows the spinner and disabled buttons when working', () => {
|
||||
const {getByText, getByTestId} = renderConfirmDialog({working: true})
|
||||
expect(getByText('doing the thing')).toBeInTheDocument()
|
||||
expect(getByTestId('confirm-dialog-close-button').getAttribute('disabled')).toBe('')
|
||||
expect(getByTestId('confirm-dialog-cancel-button').getAttribute('disabled')).toBe('')
|
||||
expect(getByTestId('confirm-dialog-confirm-button').getAttribute('disabled')).toBe('')
|
||||
})
|
|
@ -16,21 +16,13 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {render, fireEvent, wait, waitForElement} from 'react-testing-library'
|
||||
import {fireEvent, waitForElement} from 'react-testing-library'
|
||||
import {mockAssignment, findInputForLabel} from '../../test-utils'
|
||||
import TeacherView from '../TeacherView'
|
||||
import {queryAssignment, setWorkflow} from '../../api'
|
||||
import {setWorkflow} from '../../api'
|
||||
import {renderTeacherView} from './integration/integration-utils'
|
||||
|
||||
jest.mock('../../api')
|
||||
|
||||
async function renderTeacherView(assignment = mockAssignment()) {
|
||||
queryAssignment.mockReturnValueOnce({data: {assignment}})
|
||||
const result = render(<TeacherView assignmentLid={assignment.lid} />)
|
||||
await wait() // wait a tick for the api promise to resolve
|
||||
return result
|
||||
}
|
||||
|
||||
it('shows the message students who dialog when the unsubmitted button is clicked', async () => {
|
||||
const {getByText, getByTestId} = await renderTeacherView()
|
||||
fireEvent.click(getByText(/unsubmitted/i))
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {fireEvent, wait, waitForElement} from 'react-testing-library'
|
||||
import {mockAssignment, waitForNoElement} from '../../../test-utils'
|
||||
import {renderTeacherView} from './integration-utils'
|
||||
import {setWorkflow} from '../../../api'
|
||||
|
||||
jest.mock('../../../api')
|
||||
|
||||
async function openDeleteDialog(assignment = mockAssignment()) {
|
||||
const fns = await renderTeacherView(assignment)
|
||||
const openDeleteButton = await waitForElement(() => fns.getByText('delete assignment'))
|
||||
fireEvent.click(openDeleteButton)
|
||||
return fns
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('allows close', async () => {
|
||||
const {getByTestId} = await openDeleteDialog()
|
||||
const closeButton = await waitForElement(() => getByTestId('confirm-dialog-close-button'))
|
||||
fireEvent.click(closeButton)
|
||||
await waitForNoElement(() => getByTestId('confirm-dialog-close-button'))
|
||||
expect(setWorkflow).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('allows cancel', async () => {
|
||||
const {getByTestId} = await openDeleteDialog()
|
||||
const cancelButton = await waitForElement(() => getByTestId('confirm-dialog-cancel-button'))
|
||||
fireEvent.click(cancelButton)
|
||||
await waitForNoElement(() => getByTestId('confirm-dialog-cancel-button'))
|
||||
expect(setWorkflow).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('deletes the assignment and reloads', async () => {
|
||||
const reloadSpy = jest.spyOn(window.location, 'reload')
|
||||
setWorkflow.mockReturnValueOnce({data: {}})
|
||||
const assignment = mockAssignment()
|
||||
const {getByText, getByTestId} = await openDeleteDialog(assignment)
|
||||
const reallyDeleteButton = await waitForElement(() =>
|
||||
getByTestId('confirm-dialog-confirm-button')
|
||||
)
|
||||
fireEvent.click(reallyDeleteButton)
|
||||
await waitForElement(() => getByText('deleting assignment')) // the spinner
|
||||
await wait(() => expect(setWorkflow).toHaveBeenCalledWith(assignment, 'deleted'))
|
||||
expect(reloadSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
/* eslint-disable jest/no-disabled-tests */
|
||||
// errors aren't really implemented yet
|
||||
it.skip('reports errors', async () => {
|
||||
setWorkflow.mockReturnValueOnce({
|
||||
errors: [
|
||||
/* errors data structures go here */
|
||||
]
|
||||
})
|
||||
const {getByTestId} = await openDeleteDialog()
|
||||
const reallyDeleteButton = await waitForElement(() =>
|
||||
getByTestId('confirm-dialog-confirm-button')
|
||||
)
|
||||
fireEvent.click(reallyDeleteButton)
|
||||
// waitForElement(() => {getBySomething('some kind of error message alert')})
|
||||
})
|
||||
/* eslint-enable jest/no-disabled-tests */
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {render, waitForElement} from 'react-testing-library'
|
||||
import TeacherView from '../../TeacherView'
|
||||
|
||||
// api module should be mocked by the test file
|
||||
import {queryAssignment} from '../../../api'
|
||||
import {mockAssignment} from '../../../test-utils'
|
||||
|
||||
export async function renderTeacherView(assignment = mockAssignment()) {
|
||||
queryAssignment.mockReturnValueOnce({data: {assignment}})
|
||||
const result = render(<TeacherView assignmentLid={assignment.lid} />)
|
||||
// wait for the queryAssignment promise to resolve and the view to render in response
|
||||
await waitForElement(() => result.getByText(assignment.name))
|
||||
return result
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
import {TeacherViewContextDefaults} from './components/TeacherViewContext'
|
||||
import {wait} from 'react-testing-library'
|
||||
|
||||
// because our version of jsdom doesn't support elt.closest('a') yet. Should soon.
|
||||
export function closest(el, selector) {
|
||||
|
@ -32,6 +33,24 @@ export function findInputForLabel(labelChild, container) {
|
|||
return input
|
||||
}
|
||||
|
||||
export function waitForNoElement(queryFn) {
|
||||
// use wait instead of waitForElement because waitForElement doesn't seem to
|
||||
// trigger the callback when elements disappear
|
||||
return wait(() => {
|
||||
let elt = null
|
||||
try {
|
||||
elt = queryFn()
|
||||
} catch (e) {
|
||||
// if queryFn throws, assume element can't be found and succeed
|
||||
return
|
||||
}
|
||||
|
||||
// fail if the element was found
|
||||
if (elt !== null) throw new Error(`element is still present`)
|
||||
// otherwise success
|
||||
})
|
||||
}
|
||||
|
||||
export function mockCourse(overrides) {
|
||||
return {
|
||||
lid: 'course-lid',
|
||||
|
|
|
@ -40,5 +40,5 @@ if (process.env.DEPRECATION_SENTRY_DSN) {
|
|||
|
||||
// set up mocks for native APIs
|
||||
if (!('MutationObserver' in window)) {
|
||||
Object.defineProperty(window, 'MutationObserver', { value: require('mutation-observer') })
|
||||
Object.defineProperty(window, 'MutationObserver', { value: require('@sheerun/mutationobserver-shim') })
|
||||
}
|
||||
|
|
|
@ -112,6 +112,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@sentry/webpack-plugin": "^1.5.2",
|
||||
"@sheerun/mutationobserver-shim": "0.3.2",
|
||||
"@yarnpkg/lockfile": "^1.0.2",
|
||||
"axe-core": "~2.1.7",
|
||||
"babel-cli": "^6",
|
||||
|
@ -193,7 +194,6 @@
|
|||
"merge-stream": "^1",
|
||||
"mockdate": "^2.0.2",
|
||||
"moxios": "^0.4",
|
||||
"mutation-observer": "^1.0.3",
|
||||
"nyc": "^13",
|
||||
"prettier": "^1",
|
||||
"qunitjs": "^1.14.0",
|
||||
|
|
|
@ -38,5 +38,5 @@ document.documentElement.setAttribute('dir', 'ltr');
|
|||
|
||||
// set up mocks for native APIs
|
||||
if (!('MutationObserver' in window)) {
|
||||
Object.defineProperty(window, 'MutationObserver', { value: require('mutation-observer') });
|
||||
Object.defineProperty(window, 'MutationObserver', { value: require('@sheerun/mutationobserver-shim') });
|
||||
}
|
||||
|
|
|
@ -1001,7 +1001,7 @@
|
|||
dependencies:
|
||||
"@sentry/cli" "^1.35.5"
|
||||
|
||||
"@sheerun/mutationobserver-shim@^0.3.2":
|
||||
"@sheerun/mutationobserver-shim@0.3.2", "@sheerun/mutationobserver-shim@^0.3.2":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b"
|
||||
integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==
|
||||
|
@ -12483,11 +12483,6 @@ multipipe@^0.1.2:
|
|||
dependencies:
|
||||
duplexer2 "0.0.2"
|
||||
|
||||
mutation-observer@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/mutation-observer/-/mutation-observer-1.0.3.tgz#42e9222b101bca82e5ba9d5a7acf4a14c0f263d0"
|
||||
integrity sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA==
|
||||
|
||||
mute-stream@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
|
||||
|
|
Loading…
Reference in New Issue