我想添加一些实际尝试在我的项目中加载 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
首先,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();
});
});
我希望这是一个有用的答案,可以帮助任何遇到类似问题的人。