vitest.setup.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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. vi.hoisted(() => {
  12. globalThis.__ = (source) => {
  13. return source
  14. }
  15. })
  16. window.sw = new ServiceWorkerHelper()
  17. configure({
  18. testIdAttribute: 'data-test-id',
  19. asyncUtilTimeout: process.env.CI ? 30_000 : 1_000,
  20. })
  21. Object.defineProperty(window, 'fetch', {
  22. value: (path: string) => {
  23. throw new Error(`calling fetch on ${path}`)
  24. },
  25. writable: true,
  26. configurable: true,
  27. })
  28. class DOMRectList {
  29. length = 0
  30. // eslint-disable-next-line class-methods-use-this
  31. item = () => null;
  32. // eslint-disable-next-line class-methods-use-this
  33. [Symbol.iterator] = () => {
  34. //
  35. }
  36. }
  37. Object.defineProperty(Node.prototype, 'getClientRects', {
  38. value: new DOMRectList(),
  39. })
  40. Object.defineProperty(Element.prototype, 'scroll', { value: vi.fn() })
  41. Object.defineProperty(Element.prototype, 'scrollBy', { value: vi.fn() })
  42. Object.defineProperty(Element.prototype, 'scrollIntoView', { value: vi.fn() })
  43. const descriptor = Object.getOwnPropertyDescriptor(
  44. HTMLImageElement.prototype,
  45. 'src',
  46. )!
  47. Object.defineProperty(HTMLImageElement.prototype, 'src', {
  48. set(value) {
  49. descriptor.set?.call(this, value)
  50. this.dispatchEvent(new Event('load'))
  51. },
  52. get: descriptor.get,
  53. })
  54. Object.defineProperty(HTMLCanvasElement.prototype, 'getContext', {
  55. value: function getContext() {
  56. return {
  57. drawImage: (img: HTMLImageElement) => {
  58. this.__image_src = img.src
  59. },
  60. }
  61. },
  62. })
  63. Object.defineProperty(HTMLCanvasElement.prototype, 'toDataURL', {
  64. value: function toDataURL() {
  65. return this.__image_src
  66. },
  67. })
  68. // Mock IntersectionObserver feature by injecting it into the global namespace.
  69. // More info here: https://vitest.dev/guide/mocking.html#globals
  70. const IntersectionObserverMock = vi.fn(() => ({
  71. disconnect: vi.fn(),
  72. observe: vi.fn(),
  73. takeRecords: vi.fn(),
  74. unobserve: vi.fn(),
  75. }))
  76. globalThis.IntersectionObserver = IntersectionObserverMock as any
  77. require.extensions['.css'] = () => ({})
  78. globalThis.requestAnimationFrame = (cb) => {
  79. setTimeout(cb, 0)
  80. return 0
  81. }
  82. globalThis.scrollTo = vi.fn<any>()
  83. globalThis.matchMedia = (media: string) => ({
  84. matches: false,
  85. media,
  86. onchange: null,
  87. addListener: vi.fn(),
  88. removeListener: vi.fn(),
  89. dispatchEvent: vi.fn(),
  90. addEventListener: vi.fn(),
  91. removeEventListener: vi.fn(),
  92. })
  93. vi.mock(
  94. '#shared/components/CommonNotifications/useNotifications.ts',
  95. async () => {
  96. const { useNotifications: originalUseNotifications } =
  97. await vi.importActual<any>(
  98. '#shared/components/CommonNotifications/useNotifications.ts',
  99. )
  100. let notifications: any
  101. const useNotifications = () => {
  102. if (notifications) return notifications
  103. const result = originalUseNotifications()
  104. notifications = {
  105. notify: vi.fn(result.notify),
  106. notifications: result.notifications,
  107. removeNotification: vi.fn(result.removeNotification),
  108. clearAllNotifications: vi.fn(result.clearAllNotifications),
  109. hasErrors: vi.fn(result.hasErrors),
  110. }
  111. return notifications
  112. }
  113. return {
  114. useNotifications,
  115. default: useNotifications,
  116. }
  117. },
  118. )
  119. // don't rely on tiptap, because it's not supported in JSDOM
  120. vi.mock(
  121. '#shared/components/Form/fields/FieldEditor/FieldEditorInput.vue',
  122. async () => {
  123. const { computed, defineComponent } = await import('vue')
  124. const component = defineComponent({
  125. name: 'FieldEditorInput',
  126. props: { context: { type: Object, required: true } },
  127. setup(props) {
  128. const value = computed({
  129. get: () => props.context._value,
  130. set: (value) => {
  131. props.context.node.input(value)
  132. },
  133. })
  134. return { value, name: props.context.node.name, id: props.context.id }
  135. },
  136. template: `<textarea :id="id" :name="name" v-model="value" />`,
  137. })
  138. return { __esModule: true, default: component }
  139. },
  140. )
  141. // mock vueuse because of CommonDialog, it uses usePointerSwipe
  142. // that is not supported in JSDOM
  143. vi.mock('@vueuse/core', async () => {
  144. const mod =
  145. await vi.importActual<typeof import('@vueuse/core')>('@vueuse/core')
  146. return {
  147. ...mod,
  148. usePointerSwipe: vi
  149. .fn()
  150. .mockReturnValue({ distanceY: 0, isSwiping: false }),
  151. }
  152. })
  153. beforeEach((context) => {
  154. context.skipConsole = false
  155. if (!vi.isMockFunction(console.warn)) {
  156. vi.spyOn(console, 'warn').mockClear()
  157. } else {
  158. vi.mocked(console.warn).mockClear()
  159. }
  160. if (!vi.isMockFunction(console.error)) {
  161. vi.spyOn(console, 'error').mockClear()
  162. } else {
  163. vi.mocked(console.error).mockClear()
  164. }
  165. })
  166. afterEach((context) => {
  167. // we don't import it from `renderComponent`, because renderComponent may not be called
  168. // and it doesn't make sense to import everything from it
  169. if ('cleanupComponents' in globalThis) {
  170. globalThis.cleanupComponents()
  171. }
  172. if (context.skipConsole !== true) {
  173. expect(
  174. console.warn,
  175. 'there were no warning during test',
  176. ).not.toHaveBeenCalled()
  177. expect(
  178. console.error,
  179. 'there were no errors during test',
  180. ).not.toHaveBeenCalled()
  181. }
  182. })
  183. // Import the matchers for accessibility testing with aXe.
  184. expect.extend(matchers)
  185. expect.extend(assertions)
  186. expect.extend(domMatchers)
  187. expect.extend({
  188. // allow aria-disabled in toBeDisabled
  189. toBeDisabled(received, ...args) {
  190. if (received instanceof Element) {
  191. const attr = received.getAttribute('aria-disabled')
  192. if (!this.isNot && attr === 'true') {
  193. return { pass: true, message: () => '' }
  194. }
  195. if (this.isNot && attr === 'true') {
  196. // pass will be reversed and it will fail
  197. return { pass: true, message: () => 'should not have "aria-disabled"' }
  198. }
  199. }
  200. return (domMatchers.toBeDisabled as any).call(this, received, ...args)
  201. },
  202. })
  203. process.on('uncaughtException', (e) => console.log('Uncaught Exception', e))
  204. process.on('unhandledRejection', (e) => console.log('Unhandled Rejection', e))
  205. declare module 'vitest' {
  206. interface TestContext {
  207. skipConsole: boolean
  208. }
  209. // eslint-disable-next-line @typescript-eslint/no-empty-interface
  210. interface Assertion<T> extends TestingLibraryMatchers<null, T> {}
  211. }
  212. declare global {
  213. function cleanupComponents(): void
  214. }