message-box.test.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. // @ts-nocheck
  2. import { markRaw } from 'vue'
  3. import { mount } from '@vue/test-utils'
  4. import { afterEach, describe, expect, it, test, vi } from 'vitest'
  5. import { rAF } from '@element-plus/test-utils/tick'
  6. import { triggerNativeCompositeClick } from '@element-plus/test-utils/composite-click'
  7. import { QuestionFilled as QuestionFilledIcon } from '@element-plus/icons-vue'
  8. import MessageBox from '../src/messageBox'
  9. import { ElMessageBox } from '..'
  10. const selector = '.el-overlay'
  11. const QuestionFilled = markRaw(QuestionFilledIcon)
  12. vi.mock('@element-plus/utils/error', () => ({
  13. debugWarn: vi.fn(),
  14. }))
  15. const _mount = (invoker: () => void) => {
  16. return mount(
  17. {
  18. template: '<div></div>',
  19. mounted() {
  20. invoker()
  21. },
  22. },
  23. {
  24. attachTo: 'body',
  25. }
  26. )
  27. }
  28. describe('MessageBox', () => {
  29. afterEach(async () => {
  30. MessageBox.close()
  31. document.body.innerHTML = ''
  32. await rAF()
  33. })
  34. test('create and close', async () => {
  35. MessageBox({
  36. type: 'success',
  37. title: '消息',
  38. message: '这是一段内容',
  39. customStyle: {
  40. width: '100px',
  41. },
  42. })
  43. const msgbox: HTMLElement = document.querySelector(selector)
  44. expect(msgbox).toBeDefined()
  45. await rAF()
  46. expect(
  47. msgbox.querySelector('.el-message-box__title span').textContent
  48. ).toEqual('消息')
  49. expect(
  50. msgbox.querySelector('.el-message-box__message').querySelector('p')
  51. .textContent
  52. ).toEqual('这是一段内容')
  53. /** custom inline style */
  54. expect(
  55. (msgbox.querySelector('.el-message-box') as HTMLElement).style.width
  56. ).toEqual('100px')
  57. MessageBox.close()
  58. await rAF()
  59. expect(msgbox.style.display).toEqual('none')
  60. })
  61. test('invoke with strings', () => {
  62. MessageBox({ title: '消息', message: '这是一段内容' })
  63. const msgbox = document.querySelector(selector)
  64. expect(msgbox).toBeDefined()
  65. })
  66. test('custom icon', async () => {
  67. MessageBox({
  68. type: 'warning',
  69. icon: QuestionFilled,
  70. message: '这是一段内容',
  71. })
  72. await rAF()
  73. const icon = document.querySelector('.el-message-box__status')
  74. expect(icon.classList.contains('el-icon')).toBe(true)
  75. const svg = mount(QuestionFilled).find('svg').element
  76. expect(icon.querySelector('svg').innerHTML).toBe(svg.innerHTML)
  77. })
  78. test('html string', async () => {
  79. MessageBox({
  80. title: 'html string',
  81. dangerouslyUseHTMLString: true,
  82. message: '<strong>html string</strong>',
  83. })
  84. await rAF()
  85. const message = document.querySelector('.el-message-box__message strong')
  86. expect(message.textContent).toEqual('html string')
  87. })
  88. test('distinguish cancel and close', async () => {
  89. let msgAction = ''
  90. const invoker = () => {
  91. MessageBox({
  92. title: '消息',
  93. message: '这是一段内容',
  94. distinguishCancelAndClose: true,
  95. callback: (action) => {
  96. msgAction = action
  97. },
  98. })
  99. }
  100. _mount(invoker)
  101. await rAF()
  102. const btn = document.querySelector(
  103. '.el-message-box__close'
  104. ) as HTMLButtonElement
  105. btn.click()
  106. await rAF()
  107. expect(msgAction).toEqual('close')
  108. })
  109. test('alert', async () => {
  110. MessageBox.alert('这是一段内容', {
  111. title: '标题名称',
  112. type: 'warning',
  113. })
  114. await rAF()
  115. await triggerNativeCompositeClick(document.querySelector(selector))
  116. await rAF()
  117. const msgbox: HTMLElement = document.querySelector(selector)
  118. expect(msgbox.style.display).toEqual('')
  119. expect(msgbox.querySelector('.el-icon-warning')).toBeDefined()
  120. })
  121. test('confirm', async () => {
  122. MessageBox.confirm('这是一段内容', {
  123. title: '标题名称',
  124. type: 'warning',
  125. })
  126. await rAF()
  127. const btn = document
  128. .querySelector(selector)
  129. .querySelector('.el-button--primary') as HTMLButtonElement
  130. btn.click()
  131. await rAF()
  132. const msgbox: HTMLElement = document.querySelector(selector)
  133. expect(msgbox).toBe(null)
  134. })
  135. test('autofocus', async () => {
  136. MessageBox.alert('这是一段内容', {
  137. autofocus: false,
  138. title: '标题名称',
  139. })
  140. await rAF()
  141. const btnElm = document.querySelector(
  142. '.el-message-box__btns .el-button--primary'
  143. )
  144. const haveFocus = btnElm.isSameNode(document.activeElement)
  145. expect(haveFocus).toBe(false)
  146. })
  147. test('prompt', async () => {
  148. MessageBox.prompt('这是一段内容', {
  149. title: '标题名称',
  150. inputPattern: /test/,
  151. inputErrorMessage: 'validation failed',
  152. })
  153. await rAF()
  154. const inputElm = document
  155. .querySelector(selector)
  156. .querySelector('.el-message-box__input')
  157. const haveFocus = inputElm
  158. .querySelector('input')
  159. .isSameNode(document.activeElement)
  160. expect(inputElm).toBeDefined()
  161. expect(haveFocus).toBe(true)
  162. })
  163. test('prompt: focus on textarea', async () => {
  164. MessageBox.prompt('这是一段内容', {
  165. inputType: 'textarea',
  166. title: '标题名称',
  167. })
  168. await rAF()
  169. const textareaElm = document
  170. .querySelector(selector)
  171. .querySelector('textarea')
  172. const haveFocus = textareaElm.isSameNode(document.activeElement)
  173. expect(haveFocus).toBe(true)
  174. })
  175. test('callback', async () => {
  176. let msgAction = ''
  177. MessageBox({
  178. title: '消息',
  179. message: '这是一段内容',
  180. callback: (action) => {
  181. msgAction = action
  182. },
  183. })
  184. await rAF()
  185. const closeBtn = document.querySelector(
  186. '.el-message-box__close'
  187. ) as HTMLButtonElement
  188. closeBtn.click()
  189. await rAF()
  190. expect(msgAction).toEqual('cancel')
  191. })
  192. test('beforeClose', async () => {
  193. let msgAction = ''
  194. MessageBox({
  195. callback: (action) => {
  196. msgAction = action
  197. },
  198. title: '消息',
  199. message: '这是一段内容',
  200. beforeClose: (_, __, done) => {
  201. done()
  202. },
  203. })
  204. await rAF()
  205. ;(
  206. document.querySelector(
  207. '.el-message-box__btns .el-button--primary'
  208. ) as HTMLButtonElement
  209. ).click()
  210. await rAF()
  211. expect(msgAction).toEqual('confirm')
  212. })
  213. describe('promise', () => {
  214. test('resolve', async () => {
  215. let msgAction = ''
  216. MessageBox.confirm('此操作将永久删除该文件, 是否继续?', '提示').then(
  217. (action) => {
  218. msgAction = action
  219. }
  220. )
  221. await rAF()
  222. const btn = document.querySelector(
  223. '.el-message-box__btns .el-button--primary'
  224. ) as HTMLButtonElement
  225. btn.click()
  226. await rAF()
  227. expect(msgAction).toEqual('confirm')
  228. })
  229. test('reject', async () => {
  230. let msgAction = ''
  231. MessageBox.confirm('此操作将永久删除该文件, 是否继续?', '提示').catch(
  232. (action) => {
  233. msgAction = action
  234. }
  235. )
  236. await rAF()
  237. const btn = document.querySelector('.el-message-box__btns .el-button')
  238. ;(btn as HTMLButtonElement).click()
  239. await rAF()
  240. expect(msgAction).toEqual('cancel')
  241. })
  242. })
  243. describe('context inheritance', () => {
  244. it('should globally inherit context correctly', () => {
  245. expect(ElMessageBox._context).toBe(null)
  246. const testContext = {
  247. config: {
  248. globalProperties: {},
  249. },
  250. _context: {},
  251. }
  252. ElMessageBox.install?.(testContext as any)
  253. expect(ElMessageBox._context).not.toBe(null)
  254. expect(ElMessageBox._context).toBe(testContext._context)
  255. // clean up
  256. ElMessageBox._context = null
  257. })
  258. })
  259. describe('append to', () => {
  260. it('should append to body if parameter is not provided', () => {
  261. MessageBox({
  262. title: 'append to test',
  263. message: 'append to test',
  264. })
  265. const msgbox: HTMLElement = document.querySelector(`body > ${selector}`)
  266. expect(msgbox).toBeDefined()
  267. })
  268. it('should append to body if element does not exist', () => {
  269. MessageBox({
  270. title: 'append to test',
  271. message: 'append to test',
  272. appendTo: '.not-existing-selector',
  273. })
  274. const msgbox: HTMLElement = document.querySelector(`body > ${selector}`)
  275. expect(msgbox).toBeDefined()
  276. })
  277. it('should append to HtmlElement provided', () => {
  278. const htmlElement = document.createElement('div')
  279. document.body.appendChild(htmlElement)
  280. MessageBox({
  281. title: 'append to test',
  282. message: 'append to test',
  283. appendTo: htmlElement,
  284. })
  285. const msgbox: HTMLElement = htmlElement.querySelector(selector)
  286. expect(msgbox).toBeDefined()
  287. })
  288. it('should append to selector provided', () => {
  289. const htmlElement = document.createElement('div')
  290. htmlElement.className = 'custom-html-element'
  291. document.body.appendChild(htmlElement)
  292. MessageBox({
  293. title: 'append to test',
  294. message: 'append to test',
  295. appendTo: '.custom-html-element',
  296. })
  297. const msgbox: HTMLElement = htmlElement.querySelector(selector)
  298. expect(msgbox).toBeDefined()
  299. })
  300. })
  301. describe('accessibility', () => {
  302. test('title attribute should set aria-label', async () => {
  303. const title = 'Hello World'
  304. MessageBox({
  305. type: 'success',
  306. title,
  307. message: '这是一段内容',
  308. })
  309. await rAF()
  310. const msgbox: HTMLElement = document.querySelector(selector)!
  311. const msgboxDialog = msgbox?.querySelector('[role="dialog"]')!
  312. expect(msgboxDialog.getAttribute('aria-label')).toBe(title)
  313. expect(msgboxDialog.getAttribute('aria-labelledby')).toBeFalsy()
  314. })
  315. test('aria-describedby should point to modal body when not prompt', async () => {
  316. MessageBox({
  317. type: 'success',
  318. message: '这是一段内容',
  319. })
  320. await rAF()
  321. const msgbox: HTMLElement = document.querySelector(selector)!
  322. const msgboxDialog = msgbox.querySelector('[role="dialog"]')!
  323. const msgboxContent = msgboxDialog.querySelector(
  324. '.el-message-box__content'
  325. )!
  326. expect(msgboxDialog.getAttribute('aria-describedby')).toBe(
  327. msgboxContent.getAttribute('id')
  328. )
  329. })
  330. test('aria-describedby should not be used when prompt; label attached to input', async () => {
  331. const message = '这是一段内容'
  332. MessageBox.prompt(message, {
  333. type: 'success',
  334. })
  335. await rAF()
  336. const msgbox: HTMLElement = document.querySelector(selector)!
  337. const msgboxDialog = msgbox.querySelector('[role="dialog"]')!
  338. const label = msgboxDialog.querySelector('label')!
  339. const input = msgboxDialog.querySelector('input')!
  340. expect(msgboxDialog.getAttribute('aria-describedby')).toBeFalsy()
  341. expect(label.getAttribute('for')).toBe(input.getAttribute('id'))
  342. expect(label.textContent).toBe(message)
  343. })
  344. })
  345. })