dialog.test.tsx 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import { markRaw, nextTick } from 'vue'
  2. import { mount } from '@vue/test-utils'
  3. import { describe, expect, test, vi } from 'vitest'
  4. import { rAF } from '@element-plus/test-utils/tick'
  5. import triggerCompositeClick from '@element-plus/test-utils/composite-click'
  6. import { Delete } from '@element-plus/icons-vue'
  7. import Dialog from '../src/dialog.vue'
  8. const AXIOM = 'Rem is the best girl'
  9. describe('Dialog.vue', () => {
  10. test('render test', async () => {
  11. const wrapper = mount(<Dialog modelValue={true}>{AXIOM}</Dialog>)
  12. await nextTick()
  13. await rAF()
  14. await nextTick()
  15. expect(wrapper.find('.el-dialog__body').text()).toEqual(AXIOM)
  16. })
  17. test('dialog should have a title and header when it has been given', async () => {
  18. const HEADER = 'I am header'
  19. const wrapper = mount(
  20. <Dialog
  21. modelValue={true}
  22. v-slots={{
  23. header: () => HEADER,
  24. }}
  25. >
  26. {AXIOM}
  27. </Dialog>
  28. )
  29. await nextTick()
  30. expect(wrapper.find('.el-dialog__header').text()).toBe(HEADER)
  31. mount(
  32. <Dialog modelValue={true} title={HEADER}>
  33. {AXIOM}
  34. </Dialog>
  35. )
  36. await nextTick()
  37. expect(wrapper.find('.el-dialog__header').text()).toBe(HEADER)
  38. })
  39. test('dialog header should have slot props', async () => {
  40. const wrapper = mount(
  41. <Dialog
  42. modelValue={true}
  43. v-slots={{
  44. header: ({
  45. titleId,
  46. titleClass,
  47. close,
  48. }: {
  49. titleId: string
  50. titleClass: string
  51. close: () => void
  52. }) => (
  53. <button
  54. data-title-id={titleId}
  55. data-title-class={titleClass}
  56. onClick={close}
  57. />
  58. ),
  59. }}
  60. >
  61. {AXIOM}
  62. </Dialog>
  63. )
  64. await nextTick()
  65. const headerButton = wrapper.find('button')
  66. expect(headerButton.attributes()['data-title-id']).toBeTruthy()
  67. expect(headerButton.attributes()['data-title-class']).toBe(
  68. 'el-dialog__title'
  69. )
  70. expect(wrapper.emitted().close).toBeFalsy()
  71. headerButton.trigger('click')
  72. await nextTick()
  73. expect(wrapper.emitted()).toHaveProperty('close')
  74. })
  75. test('dialog should have a footer when footer has been given', async () => {
  76. const wrapper = mount(
  77. <Dialog modelValue={true} v-slots={{ footer: () => AXIOM }}>
  78. {AXIOM}
  79. </Dialog>
  80. )
  81. await nextTick()
  82. expect(wrapper.find('.el-dialog__footer').exists()).toBe(true)
  83. expect(wrapper.find('.el-dialog__footer').text()).toBe(AXIOM)
  84. })
  85. test('should append dialog to body when appendToBody is true', async () => {
  86. const wrapper = mount(
  87. <Dialog modelValue={true} appendToBody={true}>
  88. {AXIOM}
  89. </Dialog>
  90. )
  91. await nextTick()
  92. expect(
  93. document.body.firstElementChild!.classList.contains('el-overlay')
  94. ).toBe(true)
  95. wrapper.unmount()
  96. })
  97. test('should center dialog', async () => {
  98. const wrapper = mount(
  99. <Dialog modelValue={true} center={true}>
  100. {AXIOM}
  101. </Dialog>
  102. )
  103. await nextTick()
  104. expect(wrapper.find('.el-dialog--center').exists()).toBe(true)
  105. })
  106. test('should show close button', async () => {
  107. const wrapper = mount(<Dialog modelValue={true}>{AXIOM}</Dialog>)
  108. await nextTick()
  109. expect(wrapper.find('.el-dialog__close').exists()).toBe(true)
  110. })
  111. test('should hide close button when showClose = false', async () => {
  112. const wrapper = mount(
  113. <Dialog modelValue={true} showClose={false}>
  114. {AXIOM}
  115. </Dialog>
  116. )
  117. await nextTick()
  118. expect(wrapper.find('.el-dialog__headerbtn').exists()).toBe(false)
  119. })
  120. test('should close dialog when click on close button', async () => {
  121. const wrapper = mount(<Dialog modelValue={true}>{AXIOM}</Dialog>)
  122. await nextTick()
  123. await wrapper.find('.el-dialog__headerbtn').trigger('click')
  124. expect(wrapper.vm.visible).toBe(false)
  125. })
  126. describe('mask related', () => {
  127. test('should not have overlay mask when mask is false', async () => {
  128. const wrapper = mount(
  129. <Dialog modal={false} modelValue={true}>
  130. {AXIOM}
  131. </Dialog>
  132. )
  133. await nextTick()
  134. expect(wrapper.find('.el-overlay').exists()).toBe(false)
  135. })
  136. test('should close the modal when clicking on mask when `closeOnClickModal` is true', async () => {
  137. const wrapper = mount(<Dialog modelValue={true}>{AXIOM}</Dialog>)
  138. await nextTick()
  139. expect(wrapper.find('.el-overlay').exists()).toBe(true)
  140. expect(wrapper.find('.el-overlay-dialog').exists()).toBe(true)
  141. await triggerCompositeClick(wrapper.find('.el-overlay-dialog'))
  142. expect(wrapper.vm.visible).toBe(false)
  143. })
  144. })
  145. describe('life cycles', () => {
  146. test('should call before close', async () => {
  147. const beforeClose = vi.fn()
  148. const wrapper = mount(
  149. <Dialog modelValue={true} beforeClose={beforeClose}>
  150. {AXIOM}
  151. </Dialog>
  152. )
  153. await nextTick()
  154. await wrapper.find('.el-dialog__headerbtn').trigger('click')
  155. expect(beforeClose).toHaveBeenCalled()
  156. })
  157. test('should not close dialog when user cancelled', async () => {
  158. const beforeClose = vi
  159. .fn()
  160. .mockImplementation((hide: (cancel: boolean) => void) => hide(true))
  161. const wrapper = mount(
  162. <Dialog modelValue={true} beforeClose={beforeClose}>
  163. {AXIOM}
  164. </Dialog>
  165. )
  166. await nextTick()
  167. await wrapper.find('.el-dialog__headerbtn').trigger('click')
  168. expect(beforeClose).toHaveBeenCalled()
  169. expect(wrapper.vm.visible).toBe(true)
  170. })
  171. test('should open and close with delay', async () => {
  172. const wrapper = mount(
  173. <Dialog openDelay={200} closeDelay={200} modelValue={false}>
  174. {AXIOM}
  175. </Dialog>
  176. )
  177. expect(wrapper.vm.visible).toBe(false)
  178. await wrapper.setProps({
  179. modelValue: true,
  180. })
  181. })
  182. test('should destroy on close', async () => {
  183. const wrapper = mount(
  184. <Dialog modelValue={true} destroyOnClose={true}>
  185. {AXIOM}
  186. </Dialog>
  187. )
  188. expect(wrapper.vm.visible).toBe(true)
  189. await nextTick()
  190. await rAF()
  191. await nextTick()
  192. await wrapper.find('.el-dialog__headerbtn').trigger('click')
  193. await wrapper.setProps({
  194. // manually setting this prop because that Transition is not available in testing,
  195. // updating model value event was emitted via transition hooks.
  196. modelValue: false,
  197. })
  198. await nextTick()
  199. await rAF()
  200. await nextTick()
  201. expect(wrapper.find('.el-dialog__body').exists()).toBe(false)
  202. })
  203. test('should emit close event', async () => {
  204. let visible = true
  205. const onClose = vi.fn()
  206. const onClosed = vi.fn()
  207. const wrapper = mount(
  208. <Dialog
  209. modelValue={true}
  210. onUpdate:modelValue={(val: boolean) => (visible = val)}
  211. onClose={onClose}
  212. onClosed={onClosed}
  213. >
  214. {AXIOM}
  215. </Dialog>
  216. )
  217. expect(wrapper.vm.visible).toBe(true)
  218. await nextTick()
  219. await rAF()
  220. await nextTick()
  221. await triggerCompositeClick(wrapper.find('.el-overlay-dialog'))
  222. await nextTick()
  223. await rAF()
  224. await nextTick()
  225. expect(onClose).toHaveBeenCalled()
  226. expect(onClosed).toHaveBeenCalled()
  227. expect(visible).toBe(false)
  228. })
  229. test('closeIcon', async () => {
  230. const wrapper = mount(
  231. <Dialog modelValue={true} closeIcon={markRaw(Delete)}>
  232. {AXIOM}
  233. </Dialog>
  234. )
  235. await nextTick()
  236. await rAF()
  237. const closeIcon = wrapper.find('svg')
  238. expect(closeIcon.exists()).toBe(true)
  239. const svg = mount(Delete).find('svg').element
  240. expect(closeIcon.element.innerHTML).toBe(svg.innerHTML)
  241. })
  242. test('should render draggable prop', async () => {
  243. const wrapper = mount(
  244. <Dialog modelValue={true} draggable={true}>
  245. {AXIOM}
  246. </Dialog>
  247. )
  248. await nextTick()
  249. await rAF()
  250. await nextTick()
  251. expect(wrapper.find('.is-draggable').exists()).toBe(true)
  252. })
  253. })
  254. describe('accessibility', () => {
  255. test('title attribute should set aria-label', async () => {
  256. const title = 'Hello World'
  257. const wrapper = mount(
  258. <Dialog modelValue={true} title={title}>
  259. {AXIOM}
  260. </Dialog>
  261. )
  262. await nextTick()
  263. const dialog = wrapper.find('[role="dialog"]')
  264. expect(dialog.attributes()['aria-label']).toBe(title)
  265. expect(dialog.attributes()['aria-labelledby']).toBeFalsy()
  266. })
  267. test('missing title attribute should point to header slot content', async () => {
  268. const wrapper = mount(
  269. <Dialog
  270. modelValue={true}
  271. v-slots={{
  272. header: ({
  273. titleId,
  274. titleClass,
  275. }: {
  276. titleId: string
  277. titleClass: string
  278. }) => <h5 id={titleId} class={titleClass} />,
  279. }}
  280. >
  281. {AXIOM}
  282. </Dialog>
  283. )
  284. await nextTick()
  285. const dialog = wrapper.find('[role="dialog"]')
  286. const dialogTitle = wrapper.find('.el-dialog__title')
  287. expect(dialog.attributes()['aria-label']).toBeFalsy()
  288. expect(dialog.attributes()['aria-labelledby']).toBe(
  289. dialogTitle.attributes().id
  290. )
  291. })
  292. test('aria-describedby should point to modal body', async () => {
  293. const wrapper = mount(<Dialog modelValue={true}>{AXIOM}</Dialog>)
  294. await nextTick()
  295. const dialog = wrapper.find('[role="dialog"]')
  296. const dialogBody = wrapper.find('.el-dialog__body')
  297. expect(dialog.attributes()['aria-describedby']).toBe(
  298. dialogBody.attributes().id
  299. )
  300. })
  301. })
  302. })