rewrite "Modal" component in React
This commit is contained in:
parent
11e927cc61
commit
7c144b3e14
|
|
@ -194,9 +194,9 @@
|
|||
},
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"vue",
|
||||
"ts",
|
||||
"tsx",
|
||||
"vue",
|
||||
"json",
|
||||
"node"
|
||||
],
|
||||
|
|
|
|||
164
resources/assets/src/components/Modal.tsx
Normal file
164
resources/assets/src/components/Modal.tsx
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import $ from 'jquery'
|
||||
import 'bootstrap'
|
||||
import { trans } from '../scripts/i18n'
|
||||
import ModalHeader from './ModalHeader'
|
||||
import ModalBody from './ModalBody'
|
||||
import ModalFooter from './ModalFooter'
|
||||
|
||||
export type ModalOptions = {
|
||||
mode?: 'alert' | 'confirm' | 'prompt'
|
||||
show?: boolean
|
||||
title?: string
|
||||
text?: string
|
||||
dangerousHTML?: string
|
||||
input?: string
|
||||
placeholder?: string
|
||||
inputType?: string
|
||||
validator?(value: any): string | boolean | undefined
|
||||
type?: string
|
||||
showHeader?: boolean
|
||||
center?: boolean
|
||||
okButtonText?: string
|
||||
okButtonType?: string
|
||||
cancelButtonText?: string
|
||||
cancelButtonType?: string
|
||||
flexFooter?: boolean
|
||||
}
|
||||
|
||||
type Props = {
|
||||
id?: string
|
||||
children?: React.ReactNode
|
||||
footer?: React.ReactNode
|
||||
onConfirm?(payload: { value: string }): void
|
||||
onDismiss?(): void
|
||||
}
|
||||
|
||||
export type ModalResult = {
|
||||
value: string
|
||||
}
|
||||
|
||||
const Modal = React.forwardRef<HTMLDivElement, ModalOptions & Props>(
|
||||
(props, forwardedRef) => {
|
||||
const [hidden, setHidden] = useState(false)
|
||||
const [value, setValue] = useState(props.input!)
|
||||
const [valid, setValid] = useState(true)
|
||||
const [validatorMessage, setValidatorMessage] = useState('')
|
||||
const ref = (forwardedRef ??
|
||||
useRef<HTMLDivElement>(null)) as React.RefObject<HTMLDivElement>
|
||||
|
||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(event.target.value)
|
||||
}
|
||||
|
||||
const confirm = () => {
|
||||
const { validator } = props
|
||||
if (typeof validator === 'function') {
|
||||
const result = validator(value)
|
||||
if (typeof result === 'string') {
|
||||
setValidatorMessage(result)
|
||||
setValid(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
setHidden(true)
|
||||
props.onConfirm?.({ value })
|
||||
$(ref.current!).modal('hide')
|
||||
}
|
||||
|
||||
const dismiss = () => {
|
||||
setHidden(true)
|
||||
props.onDismiss?.()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const onHide = () => {
|
||||
/* istanbul ignore else */
|
||||
if (!hidden) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
const onHidden = () => setHidden(false)
|
||||
|
||||
const el = $(ref.current!)
|
||||
el.on('hide.bs.modal', onHide).on('hidden.bs.modal', onHidden)
|
||||
|
||||
return () => {
|
||||
el.off('hide.bs.modal', onHide).off('hidden.bs.modal', onHidden)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
id={props.id}
|
||||
className={`modal fade ${props.show ? 'show' : ''}`}
|
||||
tabIndex={-1}
|
||||
role="dialog"
|
||||
aria-hidden={!props.show}
|
||||
ref={ref}
|
||||
>
|
||||
<div
|
||||
className={`modal-dialog ${
|
||||
props.center ? 'modal-dialog-centered' : ''
|
||||
}`}
|
||||
role="document"
|
||||
>
|
||||
<div className={`modal-content bg-${props.type}`}>
|
||||
<ModalHeader
|
||||
show={props.showHeader}
|
||||
title={props.title}
|
||||
onDismiss={dismiss}
|
||||
/>
|
||||
<ModalBody
|
||||
text={props.text}
|
||||
dangerousHTML={props.dangerousHTML}
|
||||
showInput={props.mode === 'prompt'}
|
||||
value={value}
|
||||
onChange={handleInputChange}
|
||||
inputType={props.inputType}
|
||||
placeholder={props.placeholder}
|
||||
invalid={!valid}
|
||||
validatorMessage={validatorMessage}
|
||||
>
|
||||
{props.children}
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
showCancelButton={props.mode !== 'alert'}
|
||||
flexFooter={props.flexFooter}
|
||||
okButtonType={props.okButtonType}
|
||||
okButtonText={props.okButtonText}
|
||||
cancelButtonType={props.cancelButtonType}
|
||||
cancelButtonText={props.cancelButtonText}
|
||||
onConfirm={confirm}
|
||||
onDismiss={dismiss}
|
||||
>
|
||||
{props.footer}
|
||||
</ModalFooter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Modal.displayName = 'Modal'
|
||||
|
||||
Modal.defaultProps = {
|
||||
mode: 'confirm',
|
||||
title: trans('general.tip'),
|
||||
text: '',
|
||||
input: '',
|
||||
placeholder: '',
|
||||
inputType: 'text',
|
||||
type: 'default',
|
||||
showHeader: true,
|
||||
center: false,
|
||||
okButtonText: trans('general.confirm'),
|
||||
okButtonType: 'primary',
|
||||
cancelButtonText: trans('general.cancel'),
|
||||
cancelButtonType: 'secondary',
|
||||
flexFooter: false,
|
||||
}
|
||||
|
||||
export default Modal
|
||||
52
resources/assets/src/components/ModalBody.tsx
Normal file
52
resources/assets/src/components/ModalBody.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import React from 'react'
|
||||
|
||||
interface Props {
|
||||
text?: string
|
||||
dangerousHTML?: string
|
||||
showInput: boolean
|
||||
inputType?: string
|
||||
value?: string
|
||||
onChange?: React.ChangeEventHandler<HTMLInputElement>
|
||||
placeholder?: string
|
||||
invalid?: boolean
|
||||
validatorMessage?: string
|
||||
}
|
||||
|
||||
const ModalBody: React.FC<Props> = props => {
|
||||
const main = (() => {
|
||||
if (props.children) {
|
||||
return props.children
|
||||
} else if (props.text) {
|
||||
return props.text.split(/\r?\n/).map((line, i) => <p key={i}>{line}</p>)
|
||||
} else if (props.dangerousHTML) {
|
||||
return <div dangerouslySetInnerHTML={{ __html: props.dangerousHTML }} />
|
||||
}
|
||||
})()
|
||||
|
||||
return (
|
||||
<div className="modal-body">
|
||||
{main}
|
||||
{props.showInput && (
|
||||
<>
|
||||
<div className="form-group">
|
||||
<input
|
||||
value={props.value}
|
||||
onChange={props.onChange}
|
||||
type={props.inputType}
|
||||
className="form-control"
|
||||
placeholder={props.placeholder}
|
||||
></input>
|
||||
</div>
|
||||
{props.invalid && (
|
||||
<div className="alert alert-danger">
|
||||
<i className="icon far fa-times-circle"></i>
|
||||
<span className="ml-1">{props.validatorMessage}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalBody
|
||||
46
resources/assets/src/components/ModalFooter.tsx
Normal file
46
resources/assets/src/components/ModalFooter.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import React from 'react'
|
||||
|
||||
interface Props {
|
||||
showCancelButton: boolean
|
||||
flexFooter?: boolean
|
||||
okButtonText?: string
|
||||
okButtonType?: string
|
||||
cancelButtonText?: string
|
||||
cancelButtonType?: string
|
||||
onConfirm?(): void
|
||||
onDismiss?(): void
|
||||
}
|
||||
|
||||
const ModalFooter: React.FC<Props> = props => {
|
||||
const classes = ['modal-footer']
|
||||
if (props.flexFooter) {
|
||||
classes.push('d-flex', 'justify-content-between')
|
||||
}
|
||||
const footerClass = classes.join(' ')
|
||||
|
||||
return props.children ? (
|
||||
<div className={footerClass}>{props.children}</div>
|
||||
) : (
|
||||
<div className={footerClass}>
|
||||
{props.showCancelButton && (
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-${props.cancelButtonType}`}
|
||||
data-dismiss="modal"
|
||||
onClick={props.onDismiss}
|
||||
>
|
||||
{props.cancelButtonText}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-${props.okButtonType}`}
|
||||
onClick={props.onConfirm}
|
||||
>
|
||||
{props.okButtonText}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModalFooter
|
||||
25
resources/assets/src/components/ModalHeader.tsx
Normal file
25
resources/assets/src/components/ModalHeader.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react'
|
||||
|
||||
interface Props {
|
||||
show?: boolean
|
||||
title?: string
|
||||
onDismiss?(): void
|
||||
}
|
||||
|
||||
const ModalHeader: React.FC<Props> = props =>
|
||||
props.show ? (
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">{props.title}</h5>
|
||||
<button
|
||||
type="button"
|
||||
className="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
onClick={props.onDismiss}
|
||||
>
|
||||
<span aria-hidden>×</span>
|
||||
</button>
|
||||
</div>
|
||||
) : null
|
||||
|
||||
export default ModalHeader
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import $ from 'jquery'
|
||||
import 'bootstrap'
|
||||
import Vue from 'vue'
|
||||
import Modal from '../components/Modal.vue'
|
||||
|
||||
export interface ModalOptions {
|
||||
mode?: 'alert' | 'confirm' | 'prompt'
|
||||
title?: string
|
||||
text?: string
|
||||
dangerousHTML?: string
|
||||
input?: string
|
||||
placeholder?: string
|
||||
inputType?: string
|
||||
validator?(value: any): string | boolean | void
|
||||
type?: string
|
||||
showHeader?: boolean
|
||||
center?: boolean
|
||||
okButtonText?: string
|
||||
okButtonType?: string
|
||||
cancelButtonText?: string
|
||||
cancelButtonType?: string
|
||||
flexFooter?: boolean
|
||||
}
|
||||
|
||||
export interface ModalResult {
|
||||
value: string
|
||||
}
|
||||
|
||||
export function showModal(options: ModalOptions = {}): Promise<ModalResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
|
||||
const instance = new Vue({
|
||||
render: h => h(Modal, {
|
||||
props: Object.assign({ center: true }, options),
|
||||
on: {
|
||||
confirm: resolve,
|
||||
dismiss: reject,
|
||||
},
|
||||
}),
|
||||
}).$mount(container)
|
||||
|
||||
$(instance.$el)
|
||||
.modal('show')
|
||||
.on('hidden.bs.modal', () => {
|
||||
instance.$el.remove()
|
||||
instance.$destroy()
|
||||
})
|
||||
})
|
||||
}
|
||||
32
resources/assets/src/scripts/modal.tsx
Normal file
32
resources/assets/src/scripts/modal.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import $ from 'jquery'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import Modal, { ModalOptions, ModalResult } from '../components/Modal'
|
||||
|
||||
export function showModal(options: ModalOptions = {}): Promise<ModalResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
|
||||
const ref = React.createRef<HTMLDivElement>()
|
||||
ReactDOM.render(
|
||||
<Modal
|
||||
{...options}
|
||||
ref={ref}
|
||||
center
|
||||
onConfirm={resolve}
|
||||
onDismiss={reject}
|
||||
/>,
|
||||
container,
|
||||
)
|
||||
|
||||
$(ref.current!)
|
||||
.modal('show')
|
||||
.on('hidden.bs.modal', () => {
|
||||
setTimeout(() => {
|
||||
ReactDOM.unmountComponentAtNode(container)
|
||||
container.remove()
|
||||
}, 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
export function truthy(message: string) {
|
||||
return (value?: unknown): string | void => {
|
||||
return (value?: unknown): string | undefined => {
|
||||
if (!value) {
|
||||
return message
|
||||
}
|
||||
|
|
|
|||
2
resources/assets/src/shims.d.ts
vendored
2
resources/assets/src/shims.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue'
|
||||
import JQuery from 'jquery'
|
||||
import { ModalOptions, ModalResult } from './scripts/modal'
|
||||
import { ModalOptions, ModalResult } from './components/Modal'
|
||||
import { Toast } from './scripts/toast'
|
||||
|
||||
declare global {
|
||||
|
|
|
|||
255
resources/assets/tests/components/Modal.test.tsx
Normal file
255
resources/assets/tests/components/Modal.test.tsx
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
import React from 'react'
|
||||
import { render, fireEvent, act } from '@testing-library/react'
|
||||
import { trans } from '@/scripts/i18n'
|
||||
import $ from 'jquery'
|
||||
import Modal from '@/components/Modal'
|
||||
|
||||
test('hidden by default', () => {
|
||||
const { queryByRole } = render(<Modal />)
|
||||
expect(queryByRole('dialog')).toBeNull()
|
||||
})
|
||||
|
||||
test('receive id', () => {
|
||||
const { getByRole } = render(<Modal id="kumiko" show />)
|
||||
expect(getByRole('dialog')).toHaveAttribute('id', 'kumiko')
|
||||
})
|
||||
|
||||
test('centered dialog', () => {
|
||||
const { getByRole } = render(<Modal center show />)
|
||||
expect(getByRole('document')).toHaveClass('modal-dialog-centered')
|
||||
})
|
||||
|
||||
test('background color', () => {
|
||||
const { container } = render(<Modal type="primary" show />)
|
||||
expect(container.querySelector('.modal-content')).toHaveClass('bg-primary')
|
||||
})
|
||||
|
||||
test('forward ref', () => {
|
||||
const ref = React.createRef<HTMLDivElement>()
|
||||
render(<Modal ref={ref} show />)
|
||||
expect(ref.current).not.toBeNull()
|
||||
})
|
||||
|
||||
test('jQuery events', () => {
|
||||
const ref = React.createRef<HTMLDivElement>()
|
||||
render(<Modal ref={ref} show />)
|
||||
act(() => {
|
||||
$(ref.current!)
|
||||
.trigger('hide.bs.modal')
|
||||
.trigger('hidden.bs.modal')
|
||||
})
|
||||
})
|
||||
|
||||
describe('modal header', () => {
|
||||
it('modal title', () => {
|
||||
const { queryByText } = render(<Modal title="Tips" show />)
|
||||
expect(queryByText('Tips')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('hide modal header', () => {
|
||||
const { queryByText } = render(
|
||||
<Modal title="Tips" showHeader={false} show />,
|
||||
)
|
||||
expect(queryByText('Tips')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('modal body', () => {
|
||||
it('custom children', () => {
|
||||
const { queryByText } = render(<Modal show>body</Modal>)
|
||||
expect(queryByText('body')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('text with linebreaks', () => {
|
||||
const { queryByText } = render(<Modal text={'L1\nL2'} show />)
|
||||
expect(queryByText('L1')).toBeInTheDocument()
|
||||
expect(queryByText('L2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('dangerous HTML', () => {
|
||||
const { getByText } = render(
|
||||
<Modal dangerousHTML="<h1 class='h1'>ab</h1>" show />,
|
||||
)
|
||||
expect(getByText('ab')).toHaveClass('h1')
|
||||
})
|
||||
|
||||
describe('input control', () => {
|
||||
it('set default value', () => {
|
||||
const { queryByDisplayValue } = render(
|
||||
<Modal mode="prompt" input="val" show />,
|
||||
)
|
||||
expect(queryByDisplayValue('val')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('placeholder', () => {
|
||||
const { queryByPlaceholderText } = render(
|
||||
<Modal mode="prompt" placeholder="hint" show />,
|
||||
)
|
||||
expect(queryByPlaceholderText('hint')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('input control type', () => {
|
||||
const { getByPlaceholderText } = render(
|
||||
<Modal
|
||||
mode="prompt"
|
||||
placeholder="password"
|
||||
inputType="password"
|
||||
show
|
||||
/>,
|
||||
)
|
||||
expect(getByPlaceholderText('password')).toHaveAttribute(
|
||||
'type',
|
||||
'password',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('modal footer', () => {
|
||||
it('custom footer content', () => {
|
||||
const { queryByText } = render(<Modal footer={<div>footer</div>} show />)
|
||||
expect(queryByText('footer')).toBeInTheDocument()
|
||||
expect(queryByText(trans('general.confirm'))).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('flex footer', () => {
|
||||
const { getByText } = render(<Modal footer="footer" flexFooter show />)
|
||||
expect(getByText('footer')).toHaveClass('d-flex', 'justify-content-between')
|
||||
})
|
||||
|
||||
it('custom ok button', () => {
|
||||
const { getByText } = render(
|
||||
<Modal okButtonType="primary" okButtonText="kumiko" show />,
|
||||
)
|
||||
expect(getByText('kumiko')).toHaveClass('btn-primary')
|
||||
})
|
||||
|
||||
it('custom cancel button', () => {
|
||||
const { getByText } = render(
|
||||
<Modal cancelButtonType="success" cancelButtonText="reina" show />,
|
||||
)
|
||||
expect(getByText('reina')).toHaveClass('btn-success')
|
||||
})
|
||||
})
|
||||
|
||||
describe('"alert" mode', () => {
|
||||
it('buttons', () => {
|
||||
const resolve = jest.fn()
|
||||
const { getByText, queryByText } = render(
|
||||
<Modal mode="alert" onConfirm={resolve} show />,
|
||||
)
|
||||
fireEvent.click(getByText(trans('general.confirm')))
|
||||
expect(resolve).toBeCalledWith({ value: '' })
|
||||
expect(queryByText(trans('general.cancel'))).toBeNull()
|
||||
})
|
||||
|
||||
it('confirm callback is optional', () => {
|
||||
const { getByText } = render(<Modal mode="alert" show />)
|
||||
fireEvent.click(getByText(trans('general.confirm')))
|
||||
})
|
||||
})
|
||||
|
||||
describe('"confirm" mode', () => {
|
||||
it('default mode is "confirm"', () => {
|
||||
const { queryByText } = render(<Modal show />)
|
||||
expect(queryByText(trans('general.confirm'))).toBeInTheDocument()
|
||||
expect(queryByText(trans('general.cancel'))).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('"confirm" button', () => {
|
||||
const resolve = jest.fn()
|
||||
const reject = jest.fn()
|
||||
const { getByText } = render(
|
||||
<Modal mode="prompt" onConfirm={resolve} onDismiss={reject} show />,
|
||||
)
|
||||
fireEvent.click(getByText(trans('general.confirm')))
|
||||
expect(resolve).toBeCalledWith({ value: '' })
|
||||
expect(reject).not.toBeCalled()
|
||||
})
|
||||
|
||||
it('"cancel" button', () => {
|
||||
const resolve = jest.fn()
|
||||
const reject = jest.fn()
|
||||
const { getByText } = render(
|
||||
<Modal mode="prompt" onConfirm={resolve} onDismiss={reject} show />,
|
||||
)
|
||||
fireEvent.click(getByText(trans('general.cancel')))
|
||||
expect(resolve).not.toBeCalled()
|
||||
expect(reject).toBeCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('"prompt" mode', () => {
|
||||
it('retrieve input value', () => {
|
||||
const resolve = jest.fn()
|
||||
const reject = jest.fn()
|
||||
const { getByPlaceholderText, getByText } = render(
|
||||
<Modal
|
||||
mode="prompt"
|
||||
placeholder="hint"
|
||||
onConfirm={resolve}
|
||||
onDismiss={reject}
|
||||
show
|
||||
/>,
|
||||
)
|
||||
fireEvent.change(getByPlaceholderText('hint'), {
|
||||
target: { value: 'my' },
|
||||
})
|
||||
fireEvent.click(getByText(trans('general.confirm')))
|
||||
expect(resolve).toBeCalledWith({ value: 'my' })
|
||||
expect(reject).not.toBeCalled()
|
||||
})
|
||||
|
||||
it('cancel dialog', () => {
|
||||
const resolve = jest.fn()
|
||||
const reject = jest.fn()
|
||||
const { getByText } = render(
|
||||
<Modal mode="prompt" onConfirm={resolve} onDismiss={reject} show />,
|
||||
)
|
||||
fireEvent.click(getByText(trans('general.cancel')))
|
||||
expect(resolve).not.toBeCalled()
|
||||
expect(reject).toBeCalled()
|
||||
})
|
||||
|
||||
it('validate input', () => {
|
||||
const resolve = jest.fn()
|
||||
const reject = jest.fn()
|
||||
const validator = jest.fn().mockReturnValue(true)
|
||||
const { getByText } = render(
|
||||
<Modal
|
||||
mode="prompt"
|
||||
input="val"
|
||||
validator={validator}
|
||||
onConfirm={resolve}
|
||||
onDismiss={reject}
|
||||
show
|
||||
/>,
|
||||
)
|
||||
fireEvent.click(getByText(trans('general.confirm')))
|
||||
expect(resolve).toBeCalledWith({ value: 'val' })
|
||||
expect(reject).not.toBeCalled()
|
||||
})
|
||||
|
||||
it('report validator message', () => {
|
||||
const message = 'Invalid input.'
|
||||
const resolve = jest.fn()
|
||||
const reject = jest.fn()
|
||||
const validator = jest.fn().mockReturnValue(message)
|
||||
const { getByText, queryByText } = render(
|
||||
<Modal
|
||||
mode="prompt"
|
||||
input="val"
|
||||
validator={validator}
|
||||
onConfirm={resolve}
|
||||
onDismiss={reject}
|
||||
show
|
||||
/>,
|
||||
)
|
||||
expect(queryByText(message)).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(getByText(trans('general.confirm')))
|
||||
expect(queryByText(message)).toBeInTheDocument()
|
||||
expect(resolve).not.toBeCalled()
|
||||
expect(reject).not.toBeCalled()
|
||||
})
|
||||
})
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import $ from 'jquery'
|
||||
import { act } from 'react-dom/test-utils'
|
||||
import { showModal } from '@/scripts/modal'
|
||||
|
||||
test('show modal', async () => {
|
||||
|
|
@ -11,6 +12,9 @@ test('show modal', async () => {
|
|||
const { value } = await showModal()
|
||||
expect(value).toBe('')
|
||||
|
||||
$('.modal').trigger('hidden.bs.modal')
|
||||
act(() => {
|
||||
$('.modal').trigger('hidden.bs.modal')
|
||||
jest.runAllTimers()
|
||||
})
|
||||
expect(document.querySelector('.modal')).toBeNull()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ModalOptions, ModalResult } from '../../src/scripts/modal'
|
||||
import { ModalOptions, ModalResult } from '../../src/components/Modal'
|
||||
import { Toast } from '../../src/scripts/toast'
|
||||
|
||||
export const showModal = {} as jest.Mock<Promise<ModalResult>, [ModalOptions | void]>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user