setup.components.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import { afterEach, vi } from 'vitest';
  2. import { cleanup } from '@testing-library/react';
  3. import i18next from 'i18next';
  4. import { initReactI18next } from 'react-i18next';
  5. import enUS from '../../../internal/web/translation/en-US.json';
  6. // RTL sets this from a global beforeAll, which never runs with `globals: false`.
  7. (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
  8. vi.mock('persian-calendar-suite', () => ({
  9. PersianDateTimePicker: () => null,
  10. }));
  11. if (typeof globalThis.localStorage === 'undefined') {
  12. const store = new Map<string, string>();
  13. const storage = {
  14. getItem: (k: string) => (store.has(k) ? store.get(k)! : null),
  15. setItem: (k: string, v: string) => {
  16. store.set(k, String(v));
  17. },
  18. removeItem: (k: string) => {
  19. store.delete(k);
  20. },
  21. clear: () => {
  22. store.clear();
  23. },
  24. key: (i: number) => Array.from(store.keys())[i] ?? null,
  25. get length() {
  26. return store.size;
  27. },
  28. } as Storage;
  29. Object.defineProperty(globalThis, 'localStorage', { value: storage, configurable: true });
  30. Object.defineProperty(globalThis, 'sessionStorage', { value: storage, configurable: true });
  31. }
  32. if (!window.matchMedia) {
  33. window.matchMedia = ((query: string) => ({
  34. matches: false,
  35. media: query,
  36. onchange: null,
  37. addListener: () => {},
  38. removeListener: () => {},
  39. addEventListener: () => {},
  40. removeEventListener: () => {},
  41. dispatchEvent: () => false,
  42. })) as unknown as typeof window.matchMedia;
  43. }
  44. if (typeof globalThis.ResizeObserver === 'undefined') {
  45. globalThis.ResizeObserver = class {
  46. observe() {}
  47. unobserve() {}
  48. disconnect() {}
  49. } as unknown as typeof ResizeObserver;
  50. }
  51. if (!Element.prototype.scrollIntoView) {
  52. Element.prototype.scrollIntoView = () => {};
  53. }
  54. // jsdom does not implement pseudo-element styles or Range geometry. Ant
  55. // Design and CodeMirror use these APIs for layout, so supply harmless test
  56. // fallbacks instead of emitting noisy "Not implemented" errors.
  57. const nativeGetComputedStyle = window.getComputedStyle.bind(window);
  58. window.getComputedStyle = ((element: Element) =>
  59. nativeGetComputedStyle(element)) as typeof window.getComputedStyle;
  60. if (!Range.prototype.getClientRects) {
  61. Range.prototype.getClientRects = () => [] as unknown as DOMRectList;
  62. }
  63. if (!i18next.isInitialized) {
  64. void i18next.use(initReactI18next).init({
  65. lng: 'en-US',
  66. fallbackLng: 'en-US',
  67. resources: { 'en-US': { translation: enUS } },
  68. interpolation: { escapeValue: false, prefix: '{', suffix: '}' },
  69. returnNull: false,
  70. });
  71. }
  72. afterEach(async () => {
  73. cleanup();
  74. document.body.innerHTML = '';
  75. /*
  76. * React 19 defers passive-effect flushes onto a macrotask (setImmediate),
  77. * whose callback reads `window.event`. If one is still queued when vitest
  78. * tears down the jsdom environment, it fires after `window` is gone and
  79. * throws "window is not defined". Drain a few macrotask ticks here so any
  80. * pending callback runs while `window` still exists. Several ticks are used
  81. * because a microtask resolving mid-drain (rc-trigger/AntD) can queue a new
  82. * one behind the first.
  83. */
  84. for (let i = 0; i < 3; i += 1) {
  85. await new Promise((resolve) => setTimeout(resolve, 0));
  86. }
  87. });
  88. import { HttpUtil, Msg } from '@/utils';
  89. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  90. vi.spyOn(HttpUtil, 'post').mockResolvedValue({ success: true, obj: {} } as any);
  91. vi.spyOn(HttpUtil, 'get').mockImplementation(
  92. async (url: string) => new Msg(true, '', url.includes('/panel/api/inbounds/options') ? [] : {}),
  93. );