CommonDialog.spec.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { flushPromises } from '@vue/test-utils'
  3. import { afterAll, beforeAll } from 'vitest'
  4. import { renderComponent } from '#tests/support/components/index.ts'
  5. import CommonDialog from '../CommonDialog.vue'
  6. import { getDialogMeta, openDialog } from '../useDialog.ts'
  7. const html = String.raw
  8. describe('visuals for common dialog', () => {
  9. beforeAll(() => {
  10. const app = document.createElement('div')
  11. app.id = 'app'
  12. document.body.appendChild(app)
  13. })
  14. beforeEach(() => {
  15. const { dialogsOptions } = getDialogMeta()
  16. dialogsOptions.set('dialog', {
  17. name: 'dialog',
  18. component: vi.fn().mockResolvedValue({}),
  19. refocus: true,
  20. })
  21. })
  22. afterAll(() => {
  23. document.body.innerHTML = ''
  24. })
  25. it('rendering with header title and content', () => {
  26. const view = renderComponent(CommonDialog, {
  27. props: {
  28. name: 'dialog',
  29. headerTitle: 'Some Title',
  30. },
  31. slots: {
  32. default: 'Content Slot',
  33. },
  34. })
  35. expect(view.getByText('Some Title')).toBeInTheDocument()
  36. expect(view.getByText('Content Slot')).toBeInTheDocument()
  37. expect(view.getByLabelText('Close dialog')).toBeInTheDocument()
  38. })
  39. it('can render header title as slot', () => {
  40. const view = renderComponent(CommonDialog, {
  41. props: {
  42. name: 'dialog',
  43. },
  44. slots: {
  45. header: 'Some Title',
  46. },
  47. })
  48. expect(view.getByText('Some Title')).toBeInTheDocument()
  49. })
  50. it('can close dialog with keyboard and clicks', async () => {
  51. const view = renderComponent(CommonDialog, {
  52. props: {
  53. name: 'dialog',
  54. },
  55. global: {
  56. stubs: {
  57. teleport: true,
  58. },
  59. },
  60. })
  61. await flushPromises()
  62. await view.events.keyboard('{Escape}')
  63. const emitted = view.emitted()
  64. expect(emitted.close).toHaveLength(1)
  65. expect(emitted.close[0]).toEqual([undefined])
  66. await view.events.click(view.getByLabelText('Close dialog'))
  67. expect(emitted.close).toHaveLength(2)
  68. await view.events.click(view.getByRole('button', { name: 'OK' }))
  69. expect(emitted.close).toHaveLength(3)
  70. expect(emitted.close[2]).toEqual([false])
  71. })
  72. it('rendering different footer button content', () => {
  73. const view = renderComponent(CommonDialog, {
  74. props: {
  75. name: 'dialog',
  76. headerTitle: 'Some Title',
  77. footerActionOptions: {
  78. actionLabel: 'Yes, continue',
  79. },
  80. },
  81. slots: {
  82. default: 'Content Slot',
  83. },
  84. })
  85. expect(
  86. view.getByRole('button', { name: 'Yes, continue' }),
  87. ).toBeInTheDocument()
  88. })
  89. it('has an accessible name', async () => {
  90. const view = renderComponent(CommonDialog, {
  91. props: {
  92. headerTitle: 'foobar',
  93. name: 'dialog',
  94. },
  95. })
  96. expect(view.getByRole('dialog')).toHaveAccessibleName('foobar')
  97. })
  98. it('traps focus inside the dialog', async () => {
  99. const externalForm = document.createElement('form')
  100. externalForm.innerHTML = html`
  101. <input data-test-id="form_input" type="text" />
  102. <select data-test-id="form_select" type="text" />
  103. `
  104. document.body.appendChild(externalForm)
  105. const view = renderComponent(CommonDialog, {
  106. props: {
  107. name: 'dialog',
  108. },
  109. slots: {
  110. default: html`
  111. <input data-test-id="input" type="text" />
  112. <div data-test-id="div" tabindex="0" />
  113. <select data-test-id="select">
  114. <option value="1">1</option>
  115. </select>
  116. `,
  117. },
  118. })
  119. view.getByTestId('input').focus()
  120. expect(view.getByTestId('input')).toHaveFocus()
  121. await view.events.keyboard('{Tab}')
  122. expect(view.getByTestId('div')).toHaveFocus()
  123. await view.events.keyboard('{Tab}')
  124. expect(view.getByTestId('select')).toHaveFocus()
  125. await view.events.keyboard('{Tab}')
  126. expect(view.getByRole('button', { name: 'Cancel & Go Back' })).toHaveFocus()
  127. await view.events.keyboard('{Tab}')
  128. expect(view.getByRole('button', { name: 'OK' })).toHaveFocus()
  129. await view.events.keyboard('{Tab}')
  130. expect(view.getByLabelText('Close dialog')).toHaveFocus()
  131. await view.events.keyboard('{Tab}')
  132. expect(view.getByTestId('input')).toHaveFocus()
  133. })
  134. it('autofocuses the first focusable element', async () => {
  135. const view = renderComponent(CommonDialog, {
  136. props: {
  137. name: 'dialog',
  138. },
  139. slots: {
  140. default: html`
  141. <div data-test-id="div" tabindex="0" />
  142. <select data-test-id="select">
  143. <option value="1">1</option>
  144. </select>
  145. `,
  146. },
  147. })
  148. await flushPromises()
  149. expect(view.getByTestId('div')).toHaveFocus()
  150. })
  151. it('focuses close, if there is nothing focusable in dialog', async () => {
  152. const view = renderComponent(CommonDialog, {
  153. props: {
  154. name: 'dialog',
  155. hideFooter: true,
  156. },
  157. })
  158. await flushPromises()
  159. expect(view.getByLabelText('Close dialog')).toHaveFocus()
  160. })
  161. it('refocuses element that opened dialog', async () => {
  162. const button = document.createElement('button')
  163. button.setAttribute('aria-haspopup', 'dialog')
  164. button.setAttribute('aria-controls', 'dialog-dialog')
  165. button.setAttribute('data-test-id', 'button')
  166. document.body.appendChild(button)
  167. button.focus()
  168. expect(button).toHaveFocus()
  169. await openDialog('dialog', {})
  170. const view = renderComponent(CommonDialog, {
  171. props: {
  172. name: 'dialog',
  173. hideFooter: true,
  174. },
  175. })
  176. await flushPromises()
  177. expect(view.getByLabelText('Close dialog')).toHaveFocus()
  178. await view.events.keyboard('{Escape}')
  179. expect(button).toHaveFocus()
  180. })
  181. })