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

feat/support use modal hooks #6739

Closed
1 change: 1 addition & 0 deletions src/components/modal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type { ModalShowProps, ModalShowHandler } from './show'
export type { ModalAlertProps } from './alert'
export type { ModalConfirmProps } from './confirm'

export { useModal } from './use-modal'
export default attachPropertiesToComponent(Modal, {
show,
alert,
Expand Down
306 changes: 305 additions & 1 deletion src/components/modal/tests/modal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
act,
waitFakeTimers,
} from 'testing'
import Modal, { ModalAlertProps } from '..'
import Modal, { ModalAlertProps, useModal } from '..'
import PortalProvider from '../../portal-provider'

const classPrefix = `adm-modal`

Expand Down Expand Up @@ -250,4 +251,307 @@ describe('Modal', () => {
await promise
})
})

const UseModalApp: React.FC<{ element: React.ReactNode }> = ({ element }) => {
return <PortalProvider>{element}</PortalProvider>
}

const UseModalAlert = (props: ModalAlertProps) => {
const { alert } = useModal()

return (
<button
onClick={() => {
alert({
content: 'content',
...props,
})
}}
>
btn
</button>
)
}

test('afterShow should be called', async () => {
const afterShow = jest.fn()
render(<UseModalApp element={<UseModalAlert afterShow={afterShow} />} />)
fireEvent.click(screen.getByRole('button', { name: 'btn' }))
await waitFor(() => expect(afterShow).toBeCalled())
})

test('onConfirm should be called', async () => {
const onConfirm = jest.fn()
render(<UseModalApp element={<UseModalAlert onConfirm={onConfirm} />} />)
fireEvent.click(screen.getByRole('button', { name: 'btn' }))
const modal = $$(`.${classPrefix}`)[0]
fireEvent.click(screen.getByRole('button', { name: '我知道了' }))
expect(onConfirm).toBeCalled()
await waitForElementToBeRemoved(modal)
})

test('close on mask click', async () => {
const onClose = jest.fn()
const afterClose = jest.fn()
render(
<UseModalApp
element={
<UseModalAlert
closeOnMaskClick
onClose={onClose}
afterClose={afterClose}
/>
}
/>
)

fireEvent.click(screen.getByRole('button', { name: 'btn' }))
await waitFakeTimers()

const mask = document.querySelector('.adm-mask-aria-button')!
fireEvent.click(mask)

await waitForElementToBeRemoved(mask)

expect(onClose).toBeCalled()
expect(afterClose).toBeCalled()
})

test('show close button', async () => {
render(<UseModalApp element={<UseModalAlert showCloseButton />} />)
fireEvent.click(screen.getByRole('button', { name: 'btn' }))
expect($$(`.adm-center-popup-close`)).toHaveLength(1)
})

test('custom content', async () => {
render(
<UseModalApp
element={
<UseModalAlert
header={<div>header</div>}
title='title'
content={<div>content</div>}
image='https://gw.alipayobjects.com/mdn/rms_efa86a/afts/img/A*SE7kRojatZ0AAAAAAAAAAAAAARQnAQ'
/>
}
/>
)

fireEvent.click(screen.getByRole('button', { name: 'btn' }))
expect($$(`.${classPrefix}-header`)).toHaveLength(1)
expect($$(`.${classPrefix}-title`)).toHaveLength(1)
expect($$(`.${classPrefix}-image-container`)).toHaveLength(1)
expect($$(`.${classPrefix}-content`)[0].firstChild).not.toHaveClass(
'adm-auto-center'
)
})

test('wait for alert to complete', async () => {
const fn = jest.fn()
const Demo = () => {
const { alert } = useModal()
return (
<button
onClick={async () => {
await alert({
content: 'content',
})
fn()
}}
>
btn
</button>
)
}
render(<UseModalApp element={<Demo />} />)

fireEvent.click(screen.getByRole('button', { name: 'btn' }))
const modal = $$(`.${classPrefix}`)[0]
fireEvent.click(screen.getByRole('button', { name: '我知道了' }))
await waitForElementToBeRemoved(modal)
expect(fn).toBeCalled()
})

test('wait for confirm to complete', async () => {
const fn = jest.fn()
const Confirm = () => {
const { confirm } = useModal()
return (
<button
onClick={async () => {
const res = await confirm({
content: 'content',
})
fn(res)
}}
>
btn
</button>
)
}

render(<UseModalApp element={<Confirm />} />)
const btn = screen.getByRole('button', { name: 'btn' })
fireEvent.click(btn)
fireEvent.click(screen.getByRole('button', { name: '确定' }))
await waitForElementToBeRemoved($$(`.${classPrefix}`)[0])

fireEvent.click(btn)
fireEvent.click(screen.getByRole('button', { name: '取消' }))
await waitForElementToBeRemoved($$(`.${classPrefix}`)[0])

expect(fn.mock.calls[0][0]).toBe(true)
expect(fn.mock.calls[1][0]).toBe(false)
})

test('custom actions', async () => {
const actions = [
{
key: 'read',
text: 'read',
primary: true,
},
{
key: 'download',
text: 'download',
danger: true,
},
{
key: 'share',
text: 'share',
disabled: true,
},
]

const Demo = () => {
const { show } = useModal()

return (
<button
onClick={() => {
show({
content: 'content',
closeOnAction: true,
actions,
})
}}
>
btn
</button>
)
}

render(<UseModalApp element={<Demo />} />)

fireEvent.click(screen.getByRole('button', { name: 'btn' }))
const download = screen.getByRole('button', { name: 'download' })
const share = screen.getByRole('button', { name: 'share' })
const modal = $$(`.${classPrefix}`)[0]
expect($$('.adm-button')).toHaveLength(actions.length)
expect(download).toHaveClass('adm-button-danger')
expect(share).toHaveClass('adm-button-disabled')
expect(share).toBeDisabled()
fireEvent.click(download)
await waitForElementToBeRemoved(modal)
})

test('without actions', async () => {
const Demo = () => {
const { show } = useModal()
return (
<button
onClick={() => {
show({
content: 'content',
closeOnAction: true,
})
}}
>
btn
</button>
)
}
render(<UseModalApp element={<Demo />} />)

fireEvent.click(screen.getByRole('button', { name: 'btn' }))
expect($$(`.${classPrefix}-footer-empty`)).toHaveLength(1)
})

test('action onClick', async () => {
const promise = Promise.resolve()
const onClick = jest.fn(() => promise)
const actions = [
{
key: 'ok',
text: 'ok',
onClick,
},
]

const Demo = () => {
const { show } = useModal()

return (
<button
onClick={() => {
show({
content: 'content',
actions,
})
}}
>
btn
</button>
)
}

render(<UseModalApp element={<Demo />} />)

fireEvent.click(screen.getByRole('button', { name: 'btn' }))
fireEvent.click(screen.getByRole('button', { name: 'ok' }))
expect(onClick).toBeCalled()
// await the promise instead of returning directly, because act expects a "void" result
await act(async () => {
await promise
})
})

test('close all modals when `clear` is called', async () => {
const onClose = jest.fn()

const Demo = () => {
const { show, clear } = useModal()

const actions = [
{
key: 'clear',
text: 'clear',
onClick: clear,
},
]

return (
<button
onClick={() => {
show({
content: 'content',
onClose,
actions,
})
}}
>
btn
</button>
)
}

render(<UseModalApp element={<Demo />} />)

fireEvent.click(screen.getByRole('button', { name: 'btn' }))
const modal = $$(`.${classPrefix}`)[0]

fireEvent.click(screen.getByRole('button', { name: 'clear' }))
expect(onClose).toBeCalled()
await waitForElementToBeRemoved(modal)
})
})
Loading
Loading