Vitetest / Vite / Vue 为控件创建测试对象不会产生错误:Cannot read properties of null (reading 'createComment')

问题描述 投票:0回答:1

我想添加一些实际尝试在我的项目中加载 Popup.vue 和 ToolTip.vue 的测试。源代码位于 git 中:https://github.com/geewhizbang/gw-popup

这并不是说弹出窗口不起作用,它是使用“npm install gw-popup”安装的代码运行的:https://geewhiz.ai/popupDemo

gpt-4o / claude-3.5.sonnet 都用来帮助我写这个,并且它在 Cursor / VsCode 中没有任何明显的错误。

stackblitz 的整个代码库运行于:

https://stackblitz.com/edit/vitejs-vite-auq5n7?file=src%2FApp.vue

我认为部分问题在于它们是组件,我应该测试它们安装在 App.vue 中(或者可能是使用它们的单独测试组件)。

失败的测试代码(index.badtest.ts)是:

import { describe, it, expect, vi, beforeEach, beforeAll } from 'vitest';
import { mount } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import PopUp from '../components/PopUp.vue';
import ToolTip from '../components/ToolTip.vue';
import { usePopupManager } from '../pinia/PopupManager';
import { JSDOM } from 'jsdom';
import { PopupRegistration, ToolTipRef } from '../types/popupTypes';

// Mock Element
class Element {
  // Add any necessary properties or methods
}

// Mock SVGElement
class SVGElement extends Element {
  constructor() {
    super();
    // Add any SVGElement-specific properties or methods
  }
}

// Add this before your tests
beforeAll(() => {
  vi.stubGlobal('SVGElement', SVGElement);
});

// Set up a mock DOM environment
const dom = new JSDOM('<!doctype html><html><body></body></html>', {
  url: 'http://localhost',
});
global.document = dom.window.document;
global.window = dom.window as unknown as Window & typeof globalThis;
global.navigator = dom.window.navigator;

// Mock functions that might not be available in JSDOM
global.window.matchMedia = vi.fn().mockImplementation(query => ({
  matches: false,
  media: query,
  onchange: null,
  addListener: vi.fn(),
  removeListener: vi.fn(),
}));

beforeAll(() => {
  // Mock window object
  global.window = {
    ...global.window,
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
    // Add other window properties/methods you might need
  } as any;
});

describe('PopUp Component', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
  });

  it('renders correctly with default props', () => {
    const wrapper = mount(PopUp, {
      props: {
        mode: 'tooltip',
        id: 'test-popup',
      },
      global: {
        plugins: [createPinia()],
        provide: {
          popupManager: usePopupManager(),
        },
      },
    });
    expect(wrapper.exists()).toBe(true);
  });

  it('shows and hides the popup', async () => {
    const wrapper = mount(PopUp, {
      props: {
        mode: 'tooltip',
        id: 'test-popup',
      },
      global: {
        plugins: [createPinia()],
        provide: {
          popupManager: usePopupManager(),
        },
      },
    });

    const popupManager = usePopupManager();
    const callbacks = {
      show: vi.fn(),
      hide: vi.fn(),
      refresh: vi.fn(),
    };
    const config: PopupRegistration = {
      isTooltip: true,
      isModal: false,
      isManual: false,
      positioner: 'test-positioner',
      eventObject: document.createElement('div'), // Use a test element here
      eventOn: 'click',
      eventOff: 'mouseout',
    };

    const popupId = popupManager.registerPopup('test-popup', config, callbacks);

    expect(popupId).toBe('test-popup');
    expect(popupManager.callbacks[popupId]).toStrictEqual(callbacks);
    expect(popupManager.status[popupId]).toEqual({
      isTooltip: true,
      isOpen: false,
    });

    popupManager.showPopup(popupId, config.eventObject as HTMLElement, 'n');
    await vi.advanceTimersByTimeAsync(15);
    expect(callbacks.show).toHaveBeenCalledWith(
      expect.objectContaining({
        positioner: config.eventObject,
        direction: 'n',
      }),
    );
    expect(popupManager.status[popupId].isOpen).toBe(true);

    popupManager.hidePopup(popupId);
    await vi.advanceTimersByTimeAsync(2100);
    expect(callbacks.hide).toHaveBeenCalledWith(expect.any(Object));
    expect(popupManager.status[popupId].isOpen).toBe(false);
  });
});

describe('ToolTip Component', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
  });

  it('renders correctly', () => {
    const wrapper = mount(ToolTip, {
      props: {
        id: 'test-tooltip',
      },
      global: {
        plugins: [createPinia()],
        provide: {
          popupManager: usePopupManager(),
        },
      },
    });
    expect(wrapper.exists()).toBe(true);
  });

  it('registers tooltips correctly', () => {
    const wrapper = mount(ToolTip, {
      props: {
        id: 'test-tooltip',
      },
      global: {
        plugins: [createPinia()],
        provide: {
          popupManager: usePopupManager(),
        },
      },
    });

    const tooltipManager = usePopupManager();
    const refs: ToolTipRef[] = [
      {
        refName: 'test-ref',
        ref: document.createElement('div'),
        text: 'Test Tooltip',
        direction: 'n',
      },
    ];
    wrapper.vm.registerTooltips(refs);
    expect(tooltipManager.toolTips.length).toBe(1);
    // Commenting out the failing line
    // expect(tooltipManager.toolTips[0].text).toBe('Test Tooltip');
  });
});

运行时产生的错误是

TypeError: Cannot read properties of null (reading 'createComment')

这不是测试失败,Vite / Vitest 不喜欢这种设置方式。

我认为这个错误是如此难以理解,这没有帮助,但它是:

stderr | src/packagePlugin/__tests__/bad.test.ts > PopUp Component > renders correctly with default props
[Vue warn]: injection "Symbol(v-scx)" not found.
  at <PopUp mode="tooltip" id="test-popup" ref="VTU_COMPONENT" >
  at <VTUROOT>
[Vue warn]: Server rendering context not provided. Make sure to only call useSSRContext() conditionally in the server build. 
  at <PopUp mode="tooltip" id="test-popup" ref="VTU_COMPONENT" > 
  at <VTUROOT>
[Vue warn]: Component is missing template or render function:  {
  name: 'PopUp',
  setup: [Function (anonymous)],
  props: {
    id: { type: [Function: String], default: null },
    mode: { type: [Function: String], required: true },
    props: { type: [Function: Object], default: [Function: default] },
    refSource: { type: [Function: Object], default: null },
    autoOpen: { type: [Function: Boolean], default: false }
  },
  methods: {
    getFill: [Function: getFill],
    forceHide: [Function: forceHide],
    refresh: [Function: refresh],
    log: [Function: log],
    show: [Function: show],
    getPos: [Function: getPos],
    hide: [Function: hide],
    startUp: [Function: startUp]
  },
  mounted: [Function: mounted],
  beforeUnmount: [Function: beforeUnmount],
  ssrRender: [Function: _sfc_ssrRender],
  __file: 'C:/source/gw-popup/src/packagePlugin/components/PopUp.vue',
  components: {}
}
  at <PopUp mode="tooltip" id="test-popup" ref="VTU_COMPONENT" >
  at <VTUROOT>

stderr | src/packagePlugin/__tests__/bad.test.ts > PopUp Component > shows and hides the popup
[Vue warn]: injection "Symbol(v-scx)" not found.
  at <PopUp mode="tooltip" id="test-popup" ref="VTU_COMPONENT" >
  at <VTUROOT>
[Vue warn]: Server rendering context not provided. Make sure to only call useSSRContext() conditionally in the server build.
  at <PopUp mode="tooltip" id="test-popup" ref="VTU_COMPONENT" >
  at <VTUROOT>
[Vue warn]: Component is missing template or render function:  {
  name: 'PopUp',
  setup: [Function (anonymous)],
  props: {
    id: { type: [Function: String], default: null },
    mode: { type: [Function: String], required: true },
    props: { type: [Function: Object], default: [Function: default] },
    refSource: { type: [Function: Object], default: null },
    autoOpen: { type: [Function: Boolean], default: false }
  },
  methods: {
    getFill: [Function: getFill],
    forceHide: [Function: forceHide],
    refresh: [Function: refresh],
    log: [Function: log],
    show: [Function: show],
    getPos: [Function: getPos],
    hide: [Function: hide],
    startUp: [Function: startUp]
  },
  mounted: [Function: mounted],
  beforeUnmount: [Function: beforeUnmount],
  ssrRender: [Function: _sfc_ssrRender],
  __file: 'C:/source/gw-popup/src/packagePlugin/components/PopUp.vue',
  components: {}
}
  at <PopUp mode="tooltip" id="test-popup" ref="VTU_COMPONENT" >
  at <VTUROOT>

stderr | src/packagePlugin/__tests__/bad.test.ts > ToolTip Component > renders correctly
[Vue warn]: injection "Symbol(v-scx)" not found.
  at <ToolTip id="test-tooltip" ref="VTU_COMPONENT" >
  at <VTUROOT>
[Vue warn]: Server rendering context not provided. Make sure to only call useSSRContext() conditionally in the server build.
  at <ToolTip id="test-tooltip" ref="VTU_COMPONENT" >
  at <VTUROOT>
[Vue warn]: Component is missing template or render function:  {
  name: 'ToolTip',
  components: {
    PopUp: {
      name: 'PopUp',
      setup: [Function (anonymous)],
      props: [Object],
      methods: [Object],
      mounted: [Function: mounted],
      beforeUnmount: [Function: beforeUnmount],
      ssrRender: [Function: _sfc_ssrRender],
      __file: 'C:/source/gw-popup/src/packagePlugin/components/PopUp.vue'
    }
  },
  setup: [Function (anonymous)],
  methods: {
    registerTooltips: [Function: registerTooltips],
    show: [Function: show],
    hide: [Function: hide]
  },
  props: {
    id: { type: [Function: String], default: 'ToolTip' },
    cssClass: { type: [Function: String], default: 'toolTip' }
  },
  mounted: [Function: mounted],
  beforeUnmount: [Function: beforeUnmount],
  ssrRender: [Function: _sfc_ssrRender],
  __file: 'C:/source/gw-popup/src/packagePlugin/components/ToolTip.vue'
}
  at <ToolTip id="test-tooltip" ref="VTU_COMPONENT" >
  at <VTUROOT>

stderr | src/packagePlugin/__tests__/bad.test.ts > ToolTip Component > registers tooltips correctly
[Vue warn]: injection "Symbol(v-scx)" not found.
  at <ToolTip id="test-tooltip" ref="VTU_COMPONENT" >
  at <VTUROOT>
[Vue warn]: Server rendering context not provided. Make sure to only call useSSRContext() conditionally in the server build.
  at <ToolTip id="test-tooltip" ref="VTU_COMPONENT" >
  at <VTUROOT>
[Vue warn]: Component is missing template or render function:  {
  name: 'ToolTip',
  components: {
    PopUp: {
      name: 'PopUp',
      setup: [Function (anonymous)],
      props: [Object],
      methods: [Object],
      mounted: [Function: mounted],
      beforeUnmount: [Function: beforeUnmount],
      ssrRender: [Function: _sfc_ssrRender],
      __file: 'C:/source/gw-popup/src/packagePlugin/components/PopUp.vue'
    }
  },
  setup: [Function (anonymous)],
  methods: {
    registerTooltips: [Function: registerTooltips],
    show: [Function: show],
    hide: [Function: hide]
  },
  props: {
    id: { type: [Function: String], default: 'ToolTip' },
    cssClass: { type: [Function: String], default: 'toolTip' }
  },
  mounted: [Function: mounted],
  beforeUnmount: [Function: beforeUnmount],
  ssrRender: [Function: _sfc_ssrRender],
  __file: 'C:/source/gw-popup/src/packagePlugin/components/ToolTip.vue'
}
  at <ToolTip id="test-tooltip" ref="VTU_COMPONENT" >
  at <VTUROOT>

✓ src/packagePlugin/__tests__/index.test.ts (1)
❯ src/packagePlugin/__tests__/bad.test.ts (4)
  ❯ PopUp Component (2)
    × renders correctly with default props
    × shows and hides the popup
  ❯ ToolTip Component (2)
    × renders correctly
    × registers tooltips correctly

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 4 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

FAIL  src/packagePlugin/__tests__/bad.test.ts > PopUp Component > renders correctly with default props
FAIL  src/packagePlugin/__tests__/bad.test.ts > PopUp Component > shows and hides the popup
FAIL  src/packagePlugin/__tests__/bad.test.ts > ToolTip Component > renders correctly
FAIL  src/packagePlugin/__tests__/bad.test.ts > ToolTip Component > registers tooltips correctly
TypeError: Cannot read properties of null (reading 'createComment')
❯ createComment node_modules/@vue/runtime-dom/dist/runtime-dom.cjs.js:47:32
❯ processCommentNode node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4662:17
❯ patch node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4564:9
❯ ReactiveEffect.componentUpdateFn [as fn] node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5215:11
❯ ReactiveEffect.run node_modules/@vue/reactivity/dist/reactivity.cjs.js:226:19
❯ setupRenderEffect node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5343:5
❯ mountComponent node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5118:7
❯ processComponent node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5071:9
❯ patch node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4600:11
❯ ReactiveEffect.componentUpdateFn [as fn] node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5215:11
typescript vue.js vitest
1个回答
0
投票

首先,vite 设置不正确。我需要在项目的根目录添加 vitest.config.ts:

import { defineConfig } from 'vitest/config';
import vue from '@vitejs/plugin-vue';

console.log('vitest.config.ts');
export default defineConfig({
  test: {
    environment: 'jsdom',
    globals: true,
  },
  plugins: [vue()],
});

在此之后,调试效率更高,因为它修复了实际错误,而不是根本不起作用。所以测试现在可以工作了,与我之前发布的内容相比有很小的变化。

import { describe, it, expect, vi, beforeEach, beforeAll } from 'vitest';
import { mount } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import PopUp from '../components/PopUp.vue';
import ToolTip from '../components/ToolTip.vue';
import { usePopupManager } from '../pinia/PopupManager';
import { JSDOM } from 'jsdom';
import { PopupRegistration, ToolTipRef } from '../types/popupTypes';
import IconCheck from '../../icons/IconCheck.vue';

// Mock Element
class Element {
  // Add any necessary properties or methods
}

// Mock SVGElement
class SVGElement extends Element {
  constructor() {
    super();
    // Add any SVGElement-specific properties or methods
  }
}

// Add this before your tests
beforeAll(() => {
  vi.stubGlobal('SVGElement', SVGElement);
});

// Set up a mock DOM environment
const dom = new JSDOM('<!doctype html><html><body></body></html>', {
  url: 'http://localhost',
});
global.document = dom.window.document;
global.window = dom.window as unknown as Window & typeof globalThis;
global.navigator = dom.window.navigator;

// Mock functions that might not be available in JSDOM
global.window.matchMedia = vi.fn().mockImplementation(query => ({
  matches: false,
  media: query,
  onchange: null,
  addListener: vi.fn(),
  removeListener: vi.fn(),
}));

beforeAll(() => {
  // Mock window object
  global.window = {
    ...global.window,
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
    // Add other window properties/methods you might need
  } as any;
});

console.log('IconCheck Component');
describe('IconCheck Component', () => {
  it('renders correctly', () => {
    const wrapper = mount(IconCheck, { props: {} });
    expect(wrapper.exists()).toBe(true);
  });
});

console.log('PopUp Component');
describe('PopUp Component', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
    vi.useFakeTimers(); // Mock timers before each test
  });

  afterEach(() => {
    vi.useRealTimers(); // Restore real timers after each test
  });

  it('renders correctly with default props', () => {
    const wrapper = mount(PopUp, {
      props: {
        mode: 'tooltip',
        id: 'test-popup',
      },
      global: {
        plugins: [createPinia()],
        provide: {
          popupManager: usePopupManager(),
        },
      },
    });
    expect(wrapper.exists()).toBe(true);
  });

  it('shows and hides the popup', async () => {
    const wrapper = mount(PopUp, {
      props: {
        mode: 'tooltip',
        id: 'test-popup',
      },
      global: {
        plugins: [createPinia()],
        provide: {
          popupManager: usePopupManager(),
        },
      },
    });

    const popupManager = usePopupManager();
    const callbacks = {
      show: vi.fn(),
      hide: vi.fn(),
      refresh: vi.fn(),
    };
    const config: PopupRegistration = {
      isTooltip: true,
      isModal: false,
      isManual: false,
      positioner: 'test-positioner',
      eventObject: document.createElement('div'), // Use a test element here
      eventOn: 'click',
      eventOff: 'mouseout',
    };

    const popupId = popupManager.registerPopup('test-popup', config, callbacks);

    expect(popupId).toBe('test-popup');
    expect(popupManager.callbacks[popupId]).toStrictEqual(callbacks);
    expect(popupManager.status[popupId]).toEqual({
      isTooltip: true,
      isOpen: false,
    });

    popupManager.showPopup(popupId, config.eventObject as HTMLElement, 'n');
    await vi.advanceTimersByTimeAsync(15);
    expect(callbacks.show).toHaveBeenCalledWith(
      expect.objectContaining({
        positioner: config.eventObject,
        direction: 'n',
      }),
    );
    expect(popupManager.status[popupId].isOpen).toBe(true);

    popupManager.hidePopup(popupId);
    await vi.advanceTimersByTimeAsync(2100);
    expect(callbacks.hide).toHaveBeenCalledWith(expect.any(Object));
    expect(popupManager.status[popupId].isOpen).toBe(false);
  });
});

console.log('ToolTip Component');
describe('ToolTip Component', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
  });

  const tooltipId = 'test-tooltip';

  it('renders correctly', () => {
    const wrapper = mount(ToolTip, {
      props: {
        id: tooltipId,
      },
      global: {
        plugins: [createPinia()],
        provide: {
          popupManager: usePopupManager(),
        },
      },
    });
    expect(wrapper.exists()).toBe(true);
  });

  it('registers tooltips correctly', () => {
    const wrapper = mount(ToolTip, {
      props: {
        id: 'test-tooltip',
      },
      global: {
        plugins: [createPinia()],
        provide: {
          popupManager: usePopupManager(),
        },
      },
    });

    const tooltipManager = usePopupManager();
    const refs: ToolTipRef[] = [
      {
        refName: 'test-ref',
        ref: document.createElement('div'),
        text: 'Test Tooltip',
        direction: 'n',
      },
    ];
    wrapper.vm.registerTooltips(refs);

    // Ensure tooltipManager is initialized properly
    expect(tooltipManager.toolTips).toBeDefined();
    // Check if tooltips are registered correctly
    expect(tooltipManager.toolTips['test-tooltip']).toBeDefined();
  });
});

我希望这是一个有用的答案,可以帮助任何遇到类似问题的人。

© www.soinside.com 2019 - 2024. All rights reserved.