0

I would like to add some tests that actually try to load Popup.vue and ToolTip.vue in my project. The source code is in git at: https://github.com/geewhizbang/gw-popup

It's not that the popup doesn't work, it is running using the 'npm install gw-popup' installed code at: https://geewhiz.ai/popupDemo

gpt-4o / claude-3.5.sonnet both were used to help me write this, and it doesn't have any overt errors in Cursor / VsCode.

stackblitz has the entire code base running at:

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

I think part of the problem is that they are components, and I should test them installed in App.vue (or perhaps a separate test component that uses them).

the test code that fails (index.badtest.ts) is:

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');
  });
});

The error generated when it runs is

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

And it's not the test failing, Vite / Vitest doesn't like the way this is setup.

I thought the error was so incomprehensible it wouldn't help, but here it is:

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
6
  • Please, provide relevant code in the question in addition to the links, it needs to be understandable if they become unavailable. "doesn't like the way this is setup." - can you clarify? "The error generated when it runs is" - please, provide the whole error including call stack. The repo doesn't contain "createComment", it's unclear where it comes from Commented Sep 28, 2024 at 11:39
  • I just tried to post the other code, and went past the limit, so I can't do it. PopUp.vue and ToolTip.vue are components. They are used in App.vue, and there is a pinia store PopupManager.ts being used to control the popups. The statement about Vite / Vitest being what is causing the error stands on it's own. It is not the test failing, it is the test failing to even run. Commented Sep 28, 2024 at 18:22
  • I added a link to a Stackblitz copy of the control library Commented Sep 28, 2024 at 18:59
  • Thanks. I see, it appears that createComment belongs to Vitest. But you didn't answer this, "The error generated when it runs is" - please, provide the whole error including call stack. Commented Sep 28, 2024 at 20:29
  • I've added the errors from the Terminal to the question Commented Sep 29, 2024 at 16:56

1 Answer 1

0

first of all vite wasn't setup correctly. I needed to add vitest.config.ts at the root of the project:

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()],
});

After this, debugging was much more productive because it was fixing actual errors instead of it not working at all. So the tests now work, with very minor changes from what I posted earlier.

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();
  });
});

I hope this is a useful answer and helps anybody out with similar problems.

Sign up to request clarification or add additional context in comments.

1 Comment

I got the main answer from here, but it took some different searches to come up with it: stackoverflow.com/questions/77428712/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.