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

Cannot test <Link> outside of a Page on Next v9.5.0 (Enzyme + Jest) #15543

Closed
evantahler opened this issue Jul 27, 2020 · 17 comments · Fixed by #15604
Closed

Cannot test <Link> outside of a Page on Next v9.5.0 (Enzyme + Jest) #15543

evantahler opened this issue Jul 27, 2020 · 17 comments · Fixed by #15604
Assignees
Milestone

Comments

@evantahler
Copy link
Contributor

evantahler commented Jul 27, 2020

Bug report

Describe the bug

As of Next v9.5.0, Components which use the <Link> component cannot be tested in isolation. However, Pages which use <Link> can be tested fine.

To Reproduce

With this component:

// file: components/commonLink.tsx
import Link from "next/link";

export default function CommonLink() {
  return (
    <>
      <Link href="/other/page">
        <a>Click Me!</a>
      </Link>
    </>
  );
}

And this Test

// file: __tests__/components/commonLink.tsx
import { mount } from "enzyme";
import Component from "../../components/commonLink";

describe("navigation", () => {
  let wrapper;

  beforeEach(() => {
    wrapper = mount(<Component />);
  });

  afterEach(() => {
    wrapper.unmount();
  });

  test("renders", async () => {
    const html = wrapper.html();
    expect(html).toContain('href="/other/page">Click Me</a>');
  });
});

Produce the error:

    Error: Uncaught [TypeError: Cannot read property 'pathname' of null]
        at reportException (/Users/evan/workspace/grouparoo/grouparoo/core/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:62:24)
        at innerInvokeEventListeners (/Users/evan/workspace/grouparoo/grouparoo/core/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:333:9)
        at invokeEventListeners (/Users/evan/workspace/grouparoo/grouparoo/core/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)

...

        at Link (/Users/evan/workspace/grouparoo/grouparoo/core/node_modules/next/client/link.tsx:176:14)

...


    The above error occurred in the <Link> component:
        in Link (at commonLink.tsx:6)
        in CommonLink (created by WrapperComponent)
        in WrapperComponent

    Consider adding an error boundary to your tree to customize error handling behavior.
    Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

      at logCapturedError (../node_modules/react-dom/cjs/react-dom.development.js:19527:21)
      at logError (../node_modules/react-dom/cjs/react-dom.development.js:19564:5)
      at update.callback (../node_modules/react-dom/cjs/react-dom.development.js:20708:5)
...

Expected behavior

The test should pass

In older versions of Next.js, this test passed.

Screenshots

N/A

System information

  • OS: OSX
  • Browser N/A
  • Version of Next.js: v9.5.0
  • Version of Node.js: v12, v14
@evantahler
Copy link
Contributor Author

Updated the stack trace above to include the more interesting line number:

        at Link (/Users/evan/workspace/grouparoo/grouparoo/core/node_modules/next/client/link.tsx:176:14)

@evantahler evantahler changed the title Cannot test <Link> outside of a page on Next v9.5.0 (Enzyme + Jest) Cannot test <Link> outside of a Page on Next v9.5.0 (Enzyme + Jest) Jul 27, 2020
@evantahler
Copy link
Contributor Author

This error might be interpreted as: Without a page, the Next Router doesn't load, and therefore router.pathname cannot be used. https://github.com/vercel/next.js/blob/canary/packages/next/client/link.tsx#L176

@gavinsharp
Copy link

Ran into this as well. My (hopefully temporary) workaround was to add a Jest module mock for next/link (__mocks__/next/link.js) that returns a plain <a>.

@stefvhuynh
Copy link

ran into this as well but not in a test. i see it when i upgrade and run the app.

@evantahler
Copy link
Contributor Author

That's a good hack @gavinsharp!

@Timer Timer added good first issue Easy to fix issues, good for newcomers kind: bug labels Jul 28, 2020
@sagar-gavhane
Copy link

sagar-gavhane commented Jul 28, 2020

I'm getting same issue after upgrading to v9.5.0. This error is also getting for @testing-library/react.

    Error: Uncaught [TypeError: Cannot read property 'pathname' of null]
        at reportException (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:62:24)
        at innerInvokeEventListeners (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:346:9)
        at invokeEventListeners (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:281:3)
        at HTMLUnknownElementImpl._dispatch (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:228:9)
        at HTMLUnknownElementImpl.dispatchEvent (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:101:17)
        at HTMLUnknownElement.dispatchEvent (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:207:34)
        at Object.invokeGuardedCallbackDev (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:237:16)
        at invokeGuardedCallback (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:292:31)
        at beginWork$1 (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:23203:7)
        at performUnitOfWork (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:22157:12) TypeError: Cannot read property 'pathname' of null
        at Link (/Users/sagar/fabric/copilot-web-xpm/node_modules/next/client/link.tsx:176:14)
        at renderWithHooks (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:14803:18)
        at mountIndeterminateComponent (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:17482:13)
        at beginWork (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:18596:16)
        at HTMLUnknownElement.callCallback (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:188:14)
        at innerInvokeEventListeners (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:330:27)
        at invokeEventListeners (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:281:3)
        at HTMLUnknownElementImpl._dispatch (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:228:9)
        at HTMLUnknownElementImpl.dispatchEvent (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:101:17)
        at HTMLUnknownElement.dispatchEvent (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:207:34)
        at Object.invokeGuardedCallbackDev (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:237:16)
        at invokeGuardedCallback (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:292:31)
        at beginWork$1 (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:23203:7)
        at performUnitOfWork (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:22157:12)
        at workLoopSync (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:22130:22)
        at performSyncWorkOnRoot (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:21756:9)
        at scheduleUpdateOnFiber (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:21188:7)
        at updateContainer (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:24373:3)
        at /Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:24758:7
        at unbatchedUpdates (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:21903:12)
        at legacyRenderSubtreeIntoContainer (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:24757:5)
        at Object.render (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:24840:10)
        at /Users/sagar/fabric/copilot-web-xpm/node_modules/@testing-library/react/dist/pure.js:100:25
        at batchedUpdates$1 (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:21856:12)
        at act (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom-test-utils.development.js:929:14)
        at render (/Users/sagar/fabric/copilot-web-xpm/node_modules/@testing-library/react/dist/pure.js:96:26)
        at customRender (/Users/sagar/fabric/copilot-web-xpm/lib/testUtils/testUtils.js:28:3)
        at Object.it (/Users/sagar/fabric/copilot-web-xpm/components/SidebarNavigation/SidebarNavigation.test.js:45:45)
        at Object.asyncJestTest (/Users/sagar/fabric/copilot-web-xpm/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:100:37)
        at resolve (/Users/sagar/fabric/copilot-web-xpm/node_modules/jest-jasmine2/build/queueRunner.js:47:12)
        at new Promise (<anonymous>)
        at mapper (/Users/sagar/fabric/copilot-web-xpm/node_modules/jest-jasmine2/build/queueRunner.js:30:19)
        at promise.then (/Users/sagar/fabric/copilot-web-xpm/node_modules/jest-jasmine2/build/queueRunner.js:77:41)
        at process._tickCallback (internal/process/next_tick.js:68:7)

      at VirtualConsole.on.e (node_modules/jsdom/lib/jsdom/virtual-console.js:29:45)
      at reportException (node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:28)
      at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:346:9)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:281:3)
      at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:228:9)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:101:17)

@djeusette
Copy link

I also run into that issue when building an app. I had to downgrade to 9.4.4 to make it work.

@carloscuesta
Copy link

This could be easily fixed by providing a simple mock to the next/link component:

jest.mock('next/link', () => 'a')

carloscuesta added a commit to carloscuesta/carloscuesta.me that referenced this issue Jul 28, 2020
@thebuilder
Copy link

This is also an issue when using Storybook (or similar) to render components outside the page context.

Right now i've fixed it by creating a custom <RouterContext.Provider> to wrap the tests/stories.

.storybook/preview.js

import React from 'react'
import { addDecorator } from '@storybook/react'
import { linkTo } from '@storybook/addon-links'
import { RouterContext } from 'next/dist/next-server/lib/router-context'
import { startCase } from 'lodash'

addDecorator((storyFn) => (
  <RouterContext.Provider
    value={{
      pathname: '/',
      basePath: '',
      push: (url, as) => {
        if (as) linkTo('Routes', as !== '/' ? startCase(as) : 'Index')()
        return Promise.resolve(true)
      },
      replace: (url, as) => {
        if (as) linkTo('Routes', as !== '/' ? startCase(as) : 'Index')()
        return Promise.resolve(true)
      },
      reload: () => {},
      prefetch: () => {},
    }}
  >
    {storyFn()}
  </RouterContext.Provider>
))

For Testing Library I had to start using a custom render method, that wraps the application with the <RouterContext>.

@iicdii
Copy link
Contributor

iicdii commented Jul 28, 2020

It works fine in v9.4.5-canary.28. But this issue happen from v9.4.5-canary.29 to v9.5.0.
So I checked out v9.4.5-canary.29 change log, I think one of the commit below is causing a problem.

carloscuesta added a commit to carloscuesta/carloscuesta.me that referenced this issue Jul 28, 2020
* ⬆️ Bump next from 9.4.4 to 9.5.0

Bumps [next](https://github.com/vercel/next.js) from 9.4.4 to 9.5.0.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](vercel/next.js@v9.4.4...v9.5.0)

Signed-off-by: dependabot-preview[bot] <[email protected]>

* ✅ Mock next/Link

Related with vercel/next.js#15543

* 🚚 Move index/index.js to index.js

This is related with vercel/next.js#13699

* ✅ Improve Layout coverage

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Carlos Cuesta <[email protected]>
@Timer Timer added this to the iteration 6 milestone Jul 28, 2020
@Timer Timer added priority: p1 and removed good first issue Easy to fix issues, good for newcomers labels Jul 28, 2020
@ijjk ijjk self-assigned this Jul 28, 2020
@ijjk ijjk added the point: 2 label Jul 28, 2020
@kodiakhq kodiakhq bot closed this as completed in #15604 Jul 29, 2020
kodiakhq bot pushed a commit that referenced this issue Jul 29, 2020
This ensures rendering `next/link` doesn't fail without being nested under the router context.

Closes: #15543
@ijjk
Copy link
Member

ijjk commented Jul 29, 2020

Hi, this should be resolved in v9.5.1-canary.1 of Next.js specifically this PR #15604

@evantahler
Copy link
Contributor Author

Thank you for the super-fast turn around!

@Timer
Copy link
Member

Timer commented Jul 30, 2020

This was just fixed in 9.5.1, please upgrade!

@stovmascript
Copy link
Contributor

Hi @ijjk. I'm still having trouble with <Link> when rendered in a Material-UI v0 dropdown menu. The component gets rendered, thanks to your fix, but clicking the link gives me this error:

Unhandled Runtime Error
TypeError: Cannot read property 'push' of null

Source
../../client/link.tsx (141:9) @ linkClicked

  139 | 
  140 |   // replace state instead of push if prop is present
> 141 |   router[replace ? 'replace' : 'push'](href, as, { shallow }).then(
      |         ^
  142 |     (success: boolean) => {
  143 |       if (!success) return
  144 |       if (scroll) {

Granted, it works fine in the latest MUI (v4), but it also worked prior to Next.js v9.5. Unfortunately, we're stuck with MUI v0 for the time being.

These dropdown menus, popover components, etc. render somewhere outside the next root component, which I assume is why the router gets lost.

Is there any way I can help?

@ijjk
Copy link
Member

ijjk commented Aug 1, 2020

@stovmascript what is being tested when triggering onClick without the router being present? If you can provide a reproduction where triggering onClick for the Link component without the router context was working in a previous version but not in the current version we can take a closer look

LauraBeatris pushed a commit to LauraBeatris/next.js that referenced this issue Sep 1, 2020
This ensures rendering `next/link` doesn't fail without being nested under the router context.

Closes: vercel#15543
@Fi1osof
Copy link

Fi1osof commented Dec 12, 2020

This is also an issue when using Storybook (or similar) to render components outside the page context.

Right now i've fixed it by creating a custom <RouterContext.Provider> to wrap the tests/stories.

.storybook/preview.js

import React from 'react'
import { addDecorator } from '@storybook/react'
import { linkTo } from '@storybook/addon-links'
import { RouterContext } from 'next/dist/next-server/lib/router-context'
import { startCase } from 'lodash'

addDecorator((storyFn) => (
  <RouterContext.Provider
    value={{
      pathname: '/',
      basePath: '',
      push: (url, as) => {
        if (as) linkTo('Routes', as !== '/' ? startCase(as) : 'Index')()
        return Promise.resolve(true)
      },
      replace: (url, as) => {
        if (as) linkTo('Routes', as !== '/' ? startCase(as) : 'Index')()
        return Promise.resolve(true)
      },
      reload: () => {},
      prefetch: () => {},
    }}
  >
    {storyFn()}
  </RouterContext.Provider>
))

For Testing Library I had to start using a custom render method, that wraps the application with the <RouterContext>.

Fix for TypeScript
https://github.com/prisma-cms/prisma-cms.com/blob/6fc19fe342ec49460b58a8883db7c3ba157f545b/.storybook/preview.tsx

      <RouterContext.Provider
        value={{
          route: "/",
          pathname: '/',
          asPath: '/',
          query: {},
          basePath: '',
          push: (_url, as) => {
            if (as) {
              linkTo('Routes', as !== '/' ? startCase(as.toString()) : 'Index')()
            }
            return Promise.resolve(true)
          },
          replace: (_url, as) => {
            if (as) {
              linkTo('Routes', as !== '/' ? startCase(as.toString()) : 'Index')()
            }
            return Promise.resolve(true)
          },
          reload: () => { },
          prefetch: async () => { },
          back: () => { },
          beforePopState: () => { },
          isFallback: false,
          events: {} as MittEmitter,
        }}
      >
        {storyFn(context)}
      </RouterContext.Provider>

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.