trap-focus.test.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import { defineComponent, nextTick } from 'vue'
  2. import { mount } from '@vue/test-utils'
  3. import { afterAll, afterEach, describe, expect, test, vi } from 'vitest'
  4. import * as Aria from '@element-plus/utils/dom/aria'
  5. import TrapFocus, { FOCUSABLE_CHILDREN } from '../trap-focus'
  6. import type { ComponentPublicInstance, VNode } from 'vue'
  7. import type { VueWrapper } from '@vue/test-utils'
  8. import type { TrapFocusElement } from '../trap-focus'
  9. const isVisibleMock = vi.spyOn(Aria, 'isVisible').mockImplementation(() => true)
  10. let wrapper: VueWrapper<ComponentPublicInstance>
  11. const _mount = (render: () => VNode) =>
  12. mount(render, {
  13. global: {
  14. directives: { TrapFocus },
  15. },
  16. attachTo: document.body,
  17. })
  18. afterAll(() => {
  19. isVisibleMock.mockRestore()
  20. })
  21. afterEach(() => {
  22. wrapper.unmount()
  23. })
  24. describe('v-trap-focus', () => {
  25. test('should fetch all focusable element', () => {
  26. wrapper = _mount(() => (
  27. <div v-trap-focus>
  28. <button />
  29. </div>
  30. ))
  31. expect(
  32. (wrapper.element as TrapFocusElement)[FOCUSABLE_CHILDREN].length
  33. ).toBe(1)
  34. })
  35. test('should not fetch disabled element', () => {
  36. wrapper = _mount(() => (
  37. <div v-trap-focus>
  38. <button />
  39. <button disabled />
  40. <a href="test" />
  41. <a />
  42. <input />
  43. <input disabled />
  44. <input type="hidden" />
  45. <input type="file" />
  46. <div tabindex="-1" />
  47. <select />
  48. <select disabled />
  49. <textarea />
  50. <textarea disabled />
  51. </div>
  52. ))
  53. expect(
  54. (wrapper.element as TrapFocusElement)[FOCUSABLE_CHILDREN].length
  55. ).toBe(5)
  56. })
  57. test('should trap keyboard.tab event', async () => {
  58. wrapper = _mount(() => (
  59. <div v-trap-focus>
  60. <button class="button-1" />
  61. <button class="button-2" />
  62. <button class="button-3" />
  63. </div>
  64. ))
  65. expect(document.activeElement).toBe(document.body)
  66. await wrapper.find('.button-1').trigger('keydown', {
  67. code: 'Tab',
  68. shiftKey: true,
  69. })
  70. expect(document.activeElement).toBe(wrapper.find('.button-3').element)
  71. await wrapper.find('.button-3').trigger('keydown', {
  72. code: 'Tab',
  73. })
  74. expect(document.activeElement).toBe(wrapper.find('.button-1').element)
  75. // the current active element is .button-1
  76. await wrapper.find('.button-1').trigger('keydown', {
  77. code: 'Tab',
  78. })
  79. expect(document.activeElement).toBe(wrapper.find('.button-2').element)
  80. // now the active element became .button-2, this time we navigate back
  81. await wrapper.find('.button-2').trigger('keydown', {
  82. code: 'Tab',
  83. shiftKey: true,
  84. })
  85. expect(document.activeElement).toBe(wrapper.find('.button-1').element)
  86. })
  87. test('should focus on the only focusable element', async () => {
  88. wrapper = _mount(() => (
  89. <div v-trap-focus>
  90. <button />
  91. </div>
  92. ))
  93. expect(document.activeElement).toBe(document.body)
  94. await wrapper.find('button').trigger('keydown', {
  95. code: 'Tab',
  96. })
  97. expect(document.activeElement).toBe(wrapper.find('button').element)
  98. })
  99. test('should update focusable list when children changes', async () => {
  100. wrapper = mount(
  101. defineComponent({
  102. props: {
  103. show: Boolean,
  104. },
  105. setup(props) {
  106. return () => (
  107. <div v-trap-focus>
  108. <button />
  109. {props.show && <button />}
  110. </div>
  111. )
  112. },
  113. }),
  114. {
  115. global: {
  116. directives: {
  117. TrapFocus,
  118. },
  119. },
  120. }
  121. )
  122. const initialElements = (wrapper.element as TrapFocusElement)[
  123. FOCUSABLE_CHILDREN
  124. ]
  125. expect(initialElements.length).toBe(1)
  126. await wrapper.setProps({
  127. show: true,
  128. })
  129. await nextTick()
  130. expect(
  131. (wrapper.element as TrapFocusElement)[FOCUSABLE_CHILDREN].length
  132. ).toBe(2)
  133. })
  134. })