vitest.setup.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. import domMatchers, {
  3. type TestingLibraryMatchers,
  4. } from '@testing-library/jest-dom/matchers'
  5. import { configure } from '@testing-library/vue'
  6. import * as matchers from 'vitest-axe/matchers'
  7. import { expect } from 'vitest'
  8. import 'vitest-axe/extend-expect'
  9. import { ServiceWorkerHelper } from '#shared/utils/testSw.ts'
  10. import * as assertions from './support/assertions/index.ts'
  11. global.__ = (source) => {
  12. return source
  13. }
  14. window.sw = new ServiceWorkerHelper()
  15. configure({
  16. testIdAttribute: 'data-test-id',
  17. asyncUtilTimeout: process.env.CI ? 30_000 : 1_000,
  18. })
  19. Object.defineProperty(window, 'fetch', {
  20. value: (path: string) => {
  21. throw new Error(`calling fetch on ${path}`)
  22. },
  23. writable: true,
  24. configurable: true,
  25. })
  26. class DOMRectList {
  27. length = 0
  28. // eslint-disable-next-line class-methods-use-this
  29. item = () => null;
  30. // eslint-disable-next-line class-methods-use-this
  31. [Symbol.iterator] = () => {
  32. //
  33. }
  34. }
  35. Object.defineProperty(Node.prototype, 'getClientRects', {
  36. value: new DOMRectList(),
  37. })
  38. Object.defineProperty(Element.prototype, 'scroll', { value: vi.fn() })
  39. Object.defineProperty(Element.prototype, 'scrollBy', { value: vi.fn() })
  40. Object.defineProperty(Element.prototype, 'scrollIntoView', { value: vi.fn() })
  41. require.extensions['.css'] = () => ({})
  42. vi.stubGlobal('requestAnimationFrame', (cb: () => void) => {
  43. setTimeout(cb, 0)
  44. })
  45. vi.stubGlobal('scrollTo', vi.fn())
  46. vi.stubGlobal('matchMedia', (media: string) => ({
  47. matches: false,
  48. media,
  49. onchange: null,
  50. addEventListener: vi.fn(),
  51. removeEventListener: vi.fn(),
  52. }))
  53. vi.mock(
  54. '#shared/components/CommonNotifications/useNotifications.ts',
  55. async () => {
  56. const { useNotifications: originalUseNotifications } =
  57. await vi.importActual<any>(
  58. '#shared/components/CommonNotifications/useNotifications.ts',
  59. )
  60. let notifications: any
  61. const useNotifications = () => {
  62. if (notifications) return notifications
  63. const result = originalUseNotifications()
  64. notifications = {
  65. notify: vi.fn(result.notify),
  66. notifications: result.notifications,
  67. removeNotification: vi.fn(result.removeNotification),
  68. clearAllNotifications: vi.fn(result.clearAllNotifications),
  69. hasErrors: vi.fn(result.hasErrors),
  70. }
  71. return notifications
  72. }
  73. return {
  74. useNotifications,
  75. default: useNotifications,
  76. }
  77. },
  78. )
  79. // don't rely on tiptap, because it's not supported in JSDOM
  80. vi.mock(
  81. '#shared/components/Form/fields/FieldEditor/FieldEditorInput.vue',
  82. async () => {
  83. const { computed, defineComponent } = await import('vue')
  84. const component = defineComponent({
  85. name: 'FieldEditorInput',
  86. props: { context: { type: Object, required: true } },
  87. setup(props) {
  88. const value = computed({
  89. get: () => props.context._value,
  90. set: (value) => {
  91. props.context.node.input(value)
  92. },
  93. })
  94. return { value, name: props.context.node.name, id: props.context.id }
  95. },
  96. template: `<textarea :id="id" :name="name" v-model="value" />`,
  97. })
  98. return { __esModule: true, default: component }
  99. },
  100. )
  101. // mock vueuse because of CommonDialog, it uses usePointerSwipe
  102. // that is not supported in JSDOM
  103. vi.mock('@vueuse/core', async () => {
  104. const mod = await vi.importActual<typeof import('@vueuse/core')>(
  105. '@vueuse/core',
  106. )
  107. return {
  108. ...mod,
  109. usePointerSwipe: vi
  110. .fn()
  111. .mockReturnValue({ distanceY: 0, isSwiping: false }),
  112. }
  113. })
  114. beforeEach((context) => {
  115. context.skipConsole = false
  116. if (!vi.isMockFunction(console.warn)) {
  117. vi.spyOn(console, 'warn').mockClear()
  118. } else {
  119. vi.mocked(console.warn).mockClear()
  120. }
  121. if (!vi.isMockFunction(console.error)) {
  122. vi.spyOn(console, 'error').mockClear()
  123. } else {
  124. vi.mocked(console.error).mockClear()
  125. }
  126. })
  127. afterEach((context) => {
  128. // we don't import it from `renderComponent`, because renderComponent may not be called
  129. // and it doesn't make sense to import everything from it
  130. if ('cleanupComponents' in globalThis) {
  131. globalThis.cleanupComponents()
  132. }
  133. if (context.skipConsole !== true) {
  134. expect(
  135. console.warn,
  136. 'there were no warning during test',
  137. ).not.toHaveBeenCalled()
  138. expect(
  139. console.error,
  140. 'there were no errors during test',
  141. ).not.toHaveBeenCalled()
  142. }
  143. })
  144. // Import the matchers for accessibility testing with aXe.
  145. expect.extend(matchers)
  146. expect.extend(assertions)
  147. expect.extend(domMatchers)
  148. expect.extend({
  149. // allow aria-disabled in toBeDisabled
  150. toBeDisabled(received, ...args) {
  151. if (received instanceof Element) {
  152. const attr = received.getAttribute('aria-disabled')
  153. if (!this.isNot && attr === 'true') {
  154. return { pass: true, message: () => '' }
  155. }
  156. if (this.isNot && attr === 'true') {
  157. // pass will be reversed and it will fail
  158. return { pass: true, message: () => 'should not have "aria-disabled"' }
  159. }
  160. }
  161. return (domMatchers.toBeDisabled as any).call(this, received, ...args)
  162. },
  163. })
  164. process.on('uncaughtException', (e) => console.log('Uncaught Exception', e))
  165. process.on('unhandledRejection', (e) => console.log('Unhandled Rejection', e))
  166. declare module 'vitest' {
  167. interface TestContext {
  168. skipConsole: boolean
  169. }
  170. // eslint-disable-next-line @typescript-eslint/no-empty-interface
  171. interface Assertion<T> extends TestingLibraryMatchers<null, T> {}
  172. }
  173. declare global {
  174. function cleanupComponents(): void
  175. }