Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useSlots() make component not mountable #2033

Open
nemeros opened this issue Nov 18, 2022 · 5 comments
Open

useSlots() make component not mountable #2033

nemeros opened this issue Nov 18, 2022 · 5 comments

Comments

@nemeros
Copy link

nemeros commented Nov 18, 2022

Subject of the issue

Given a basic component, with setup script, if we use the function useSlots(), the component cannot be rendered with mount.

Steps to reproduce

I created a brand new project with create-vue@2, with typescript and vitest :

PS D:\dev\cfdp> npm create vue@2

Vue.js - The Progressive JavaScript Framework

√ Project name: ... vue-test2
√ Add TypeScript? ... No / Yes
√ Add JSX Support? ... No / Yes
√ Add Vue Router for Single Page Application development? ... No / Yes
√ Add Pinia for state management? ... No / Yes
√ Add Vitest for Unit Testing? ... No / Yes
√ Add Cypress for End-to-End testing? ... No / Yes
√ Add ESLint for code quality? ... No / Yes

Now the basic component, SlotComponent.vue :

<template>
  <div>
    <slot name="body"></slot>
  </div>
</template>
<script setup lang="ts">
import { useSlots } from 'vue';

const slots = useSlots();
</script>

The associated test, SlotComponent.spec.ts :

import { describe, expect, it } from 'vitest';
import { mount } from '@vue/test-utils';
import SlotComponent from './SlotComponent.vue'

describe('Slot test', () => {

  it('will fail', () => {
    const wrapper = mount(SlotComponent, {
      slots: {
        body: '<div>body</div>'
      }
    });

    expect(wrapper.text()).toContain('body');
  });
});

Expected behaviour

the test should pass, (it pass if i remove the code const slots = useSlots())

Actual behaviour

the test fail with the stacktrace below :

stderr | src/components/__tests__/SlotComponent.spec.ts > Slot test > will fail
[Vue warn]: useContext() called without active instance.
[Vue warn]: Error in setup: "TypeError: Cannot read properties of null (reading '_setupContext')"

found in

---> <SlotComponent> at D:/dev/cfdp/vue-test/src/components/__tests__/SlotComponent.vue
       <Root>
TypeError: Cannot read properties of null (reading '_setupContext')
    at getContext (D:/dev/cfdp/vue-test/node_modules/vue/dist/vue.runtime.esm.js:2589:15)        
    at Module.useSlots (D:/dev/cfdp/vue-test/node_modules/vue/dist/vue.runtime.esm.js:2567:12)   
    at setup (D:/dev/cfdp/vue-test/src/components/__tests__/SlotComponent.vue:8:41)
    at invokeWithErrorHandling (D:\dev\cfdp\vue-test\node_modules\vue\dist\vue.runtime.common.dev.js:2919:30)
    at initSetup (D:\dev\cfdp\vue-test\node_modules\vue\dist\vue.runtime.common.dev.js:2352:29)
    at initState (D:\dev\cfdp\vue-test\node_modules\vue\dist\vue.runtime.common.dev.js:5300:5)
    at VueComponent.Vue._init (D:\dev\cfdp\vue-test\node_modules\vue\dist\vue.runtime.common.dev.js:5615:9)
    at new VueComponent (D:\dev\cfdp\vue-test\node_modules\vue\dist\vue.runtime.common.dev.js:5750:18)
    at createComponentInstanceForVnode (D:\dev\cfdp\vue-test\node_modules\vue\dist\vue.runtime.common.dev.js:4490:12)
    at init (D:\dev\cfdp\vue-test\node_modules\vue\dist\vue.runtime.common.dev.js:4352:54)

 ❯ src/components/__tests__/SlotComponent.spec.ts (1)
   ❯ Slot test (1)
     × will fail
 ✓ src/components/__tests__/HelloWorld.spec.ts (1)

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
 FAIL  src/components/__tests__/SlotComponent.spec.ts > Slot test > will fail
TypeError: Cannot read properties of null (reading '_setupContext')
 ❯ getContext node_modules/vue/dist/vue.runtime.esm.js:2589:15
    2587|     }
    2588|     var vm = currentInstance;
    2589|     return vm._setupContext || (vm._setupContext = createSetupContext(vm));
       |               ^
    2590| }
    2591| /**
 ❯ Module.useSlots node_modules/vue/dist/vue.runtime.esm.js:2567:12
 ❯ setup src/components/__tests__/SlotComponent.vue:8:41
 ❯ invokeWithErrorHandling node_modules/vue/dist/vue.runtime.common.dev.js:2919:30
 ❯ initSetup node_modules/vue/dist/vue.runtime.common.dev.js:2352:29
 ❯ initState node_modules/vue/dist/vue.runtime.common.dev.js:5300:5
 ❯ VueComponent.Vue._init node_modules/vue/dist/vue.runtime.common.dev.js:5615:9
 ❯ new VueComponent node_modules/vue/dist/vue.runtime.common.dev.js:5750:18
 ❯ createComponentInstanceForVnode node_modules/vue/dist/vue.runtime.common.dev.js:4490:12
 ❯ init node_modules/vue/dist/vue.runtime.common.dev.js:4352:54

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files  1 failed | 1 passed (2)
     Tests  1 failed | 1 passed (2)
      Time  3.70s (in thread 64ms, 5780.13%)

Annex :

my package.json :

{
  "name": "vue-test",
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "run-p type-check build-only",
    "preview": "vite preview --port 4173",
    "test:unit": "vitest --environment jsdom",
    "build-only": "vite build",
    "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false"
  },
  "dependencies": {
    "vue": "^2.7.7"
  },
  "devDependencies": {
    "@types/jsdom": "^16.2.14",
    "@types/node": "^16.11.45",
    "@vitejs/plugin-legacy": "^2.0.0",
    "@vitejs/plugin-vue2": "^1.1.2",
    "@vitejs/plugin-vue2-jsx": "^1.0.2",
    "@vue/test-utils": "^1.3.0",
    "@vue/tsconfig": "^0.1.3",
    "jsdom": "^20.0.0",
    "npm-run-all": "^4.1.5",
    "terser": "^5.14.2",
    "typescript": "~4.7.4",
    "vite": "^3.0.2",
    "vitest": "^0.18.1",
    "vue-template-compiler": "^2.7.7",
    "vue-tsc": "^0.38.8"
  }
}
@nemeros nemeros closed this as completed Nov 18, 2022
@nemeros nemeros reopened this Nov 18, 2022
@treardon17
Copy link

treardon17 commented Nov 28, 2022

In addition to useSlots, this also happens with useAttrs, useListeners, and getCurrentInstance (using "vue": "~2.7.13" and "@vue/test-utils" : "1.3.x"). I cannot run unit tests while using script setup in combination with any one of those methods. Currently using vitest 0.23.4 as the test runner. Also worth noting this happens with mount as well as shallowMount.

@rossinek
Copy link

Quick workaround for vitest (fixes useListeners usage):

// tests setup

vi.mock('vue', async () => {
  const actualModule = await vi.importActual('vue');
  return {
    ...actualModule,
    useListeners: actualModule.default.useListeners,
  };
});

@piktur
Copy link
Contributor

piktur commented Jul 5, 2023

@rossinek how does this solve the issue? This mock does not change the implementation of useListeners. The private module variable currentInstance referenced in getContext will still be undefined.

Unless mocking prevents inconsistent module resolution. Whilst debugging I've noticed both vitest vue/dist/vue.js and vue/dist/vue.esm.js are loaded.

@rossinek
Copy link

rossinek commented Jul 5, 2023

@piktur thank for taking a look at this. It was a long time ago and TBH I don't remember why it works and currently I don't have the capacity to debug it once again but I created a little reproduction for you if you want to dive in. To see the difference run:

npm run test src/components/HelloWorld.broken.spec.js
# vs
npm run test src/components/HelloWorld.spec.js

@tobi-fis
Copy link

tobi-fis commented Feb 5, 2024

Any news on this? Some component logic I need to test relies on useSlots and I still get Cannot read properties of null (reading 'setupContext') when running the test.

Update for anyone struggling with this:
The solution was to put the call to useSlots() into the top-level of your components' script setup block (or setup function). I had placed it inside a function before, heres the before/after example:

Wrong:

<script setup>
  const foo = () => {
     const accordionItems = useSlots().accordions?.();  
     # other logic...
  }
</script>

Correct:

<script setup>
  const accordionItems = useSlots().accordions?.();

  const foo = () => {
   # other logic...
  }
</script>

Currently using:

  • vitest: 1.2.2
  • vite: 5.0.10
  • vue: 3.4.4
  • vue test utils: 2.4.4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants