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

Test fails if useNuxtApp used #744

Closed
martinsjek opened this issue Jan 29, 2024 · 5 comments
Closed

Test fails if useNuxtApp used #744

martinsjek opened this issue Jan 29, 2024 · 5 comments

Comments

@martinsjek
Copy link

martinsjek commented Jan 29, 2024

Environment

  • Operating System: Linux
  • Node Version: v18.19.0
  • Nuxt Version: 3.9.3
  • CLI Version: 3.10.0
  • Nitro Version: 2.8.1
  • Package Manager: [email protected]
  • Builder: -
  • Build Modules: -

Reproduction

https://stackblitz.com/edit/github-vy6psx-sn3lfr

Describe the bug

[Vue warn]: Cannot mutate <script setup> binding "nuxtApp" from Options API. at <MountSuspendedComponent > at <MountSuspendedHelper> at <Anonymous ref="VTU_COMPONENT" > at <VTUROOT> [Vue warn]: Unhandled error during execution of render function at <MountSuspendedComponent > at <MountSuspendedHelper> at <Anonymous ref="VTU_COMPONENT" > at <VTUROOT>

I get this error message whenever I try to use useNudxtApp() in a component.
Before the merge of nuxt-vitest and @nuxt/text-utils, this worked fine.

Additional context

Tests can be ran with: npm run test ./pages/tests/index.spec.ts

Logs

No response

@kabalage
Copy link

kabalage commented Jul 4, 2024

I have the same issue. If I rename nuxtApp to anything else, it works: const _nuxtApp = useNuxtApp(). But that is not ideal, I would have to rename it to something weird in hundreds of places in our app.

Edit:
I found a very similar issue: frontegg/frontegg-vue#348
And that is not even related to Nuxt. I have no clue why the renaming solves it.

@erinalahorlbeck
Copy link

erinalahorlbeck commented Aug 8, 2024

I'm seeing this same issue in my most basic test:

<!-- File: /layouts/mount-suspended.vue -->
<template>
  <div>
    <div>
      <h1>{{ isHydrated }}</h1>
      <NuxtPage />
    </div>
  </div>
</template>

<script setup>
import { useNuxtApp } from '#app'

const nuxtApp = useNuxtApp()
const isHydrated = ref(nuxtApp.isHydrating)
</script>

And in my Vitest test:

// File: /layouts/tests/mount-suspended.test.js

// @vitest-environment nuxt
import { mountSuspended } from '@nuxt/test-utils/runtime'
import layoutToMountSuspended from '@/layouts/mount-suspended'

describe('mountSuspended experiment', () => {
  let wrapper

  beforeEach(async () => {
    wrapper = await mountSuspended(layoutToMountSuspended, {
      global: {
        stubs: ['NuxtPage'],
      },
    })
  })

  it('is a component', () => {
    expect(wrapper.vm).toBeTruthy()
  })
})

This test fails with the same error as above, but for me I have to not only rename the constant to something other than nuxtApp, but I also have to alias the useNuxtApp import itself:

import { useNuxtApp as whatever } from '#app'

const bobLoblaw = whatever()
const isHydrated = ref(bobLoblaw.isHydrating)

Now my test runs successfully. I'm guessing there's some sort of auto-import issue that scans files for those exact names. (I'd like to add that I've also seen issues in the past, even if an automatic Nuxt import appears only in a commented section. It still reads the comments and causes issues. So watch out for commented out code too!

Thanks @kabalage. This has been one of the most perplexing and difficult-to-solve issues I've come across in a long time.

EDIT: All that said, I hope this issue will still be addressed. Changing the alias/name throughout all of our files doesn't seem like a very tenable solution, nor is this issue documented anywhere except here.

@erinalahorlbeck
Copy link

erinalahorlbeck commented Aug 8, 2024

I'd like to add that I'm seeing the same error for other imports as well, such as useRoute or useRouter from vue-router when using mountSuspended():

[Vue warn]: Cannot mutate <script setup> binding "useRouter" from Options API.

I don't know how mountSuspended works under the hood but it seems to be using the Options API and is attempting to mutate certain imports unless they get aliased to a different name. This is highly problematic.

Update: Sometimes the issue is that the test simply times out after 10 seconds, and renaming useRouter to aliasedUseRouter fixes it.

@erinalahorlbeck
Copy link

I've been researching this problem some more, and I found another workaround that doesn't require aliasing useNuxtApp (or useRoute or useRouter). The other "solution" is to just remove the import statements entirely and rely on them as auto-imports. If I do that I can get my most basic tests to pass, without having to rename/alias every import.

However, this is also problematic because we've been using

import { useRoute, useRouter } from 'vue-router'

throughout our app, and when we remove this and instead rely on Nuxt's auto-import feature, the auto-import for the route/router is coming from Nuxt instead of vue-router, and that means you have to mock it with mockNuxtImport() instead of vi.mock(). That would mean updating a TON of tests on our end, so this solution isn't ideal either.

Nevertheless I'm posting this here since people are going to need every workaround they can for this issue until it is addressed.

@kabalage
Copy link

kabalage commented Nov 5, 2024

So I tried debugging the issue and I made some progress on this.

To debug I ran vitest with --inspect-brk --no-file-parallelism and used chrome://inspect to attach to the process.

I looked for the warning thrown by vue: "Cannot mutate <script setup> binding ..."

I found it in runtime-core.cjs, set a breakpoint for the line, and let the debugger resume. (Source: https://github.com/vuejs/core/blob/v3.5.12/packages/runtime-core/src/componentPublicInstance.ts#L546)

Inspecting the runtime objects, I figured out that anything defined in the setup of the Nuxt root component cannot be overriden.

So basically none of these variable names work in script setup components when mounting via mountSuspended:

[
  "IslandRenderer",
  "nuxtApp",
  "onResolve",
  "url",
  "SingleRenderer",
  "results",
  "error",
  "abortRender",
  "islandContext",
  "defineAsyncComponent",
  "onErrorCaptured",
  "onServerPrefetch",
  "provide",
  "useNuxtApp",
  "isNuxtError",
  "showError",
  "useError",
  "useRoute",
  "useRouter",
  "PageRouteSymbol",
  "AppComponent",
  "ErrorComponent",
  "componentIslands"
]

From inspecting the callstack, this where the warning happens:
https://github.com/nuxt/test-utils/blob/v3.14.4/src/runtime-utils/mount.ts#L128

I'm not exactly sure how this cloning/proxying is done in mountSuspended, but my guess is we should not merge the setupState of the root component with the setup state of the component being mounted. Or prefix the variables in nuxt-root.vue so it's not causing collisions.

Sorry to ping you @danielroe, I know you must be getting many notifications, but I think this needs some of your attention. What direction should I take here? I tried the latter locally, that works, but nuxt-root.vue becomes quite ugly and it's a test-utils concern anyways.

(#871 and #986 are both the same issue as this.)

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

3 participants