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

Unit tests break when using @headless/ui-package #54286

Closed
1 task done
weyert opened this issue Aug 19, 2023 · 7 comments · Fixed by #54695
Closed
1 task done

Unit tests break when using @headless/ui-package #54286

weyert opened this issue Aug 19, 2023 · 7 comments · Fixed by #54695
Labels
bug Issue was opened via the bug report template. locked Testing Related to testing with Next.js.

Comments

@weyert
Copy link

weyert commented Aug 19, 2023

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.6.0: Wed Jul  5 22:22:05 PDT 2023; root:xnu-8796.141.3~6/RELEASE_ARM64_T6000
    Binaries:
      Node: 18.14.0
      npm: 8.19.3
      Yarn: 1.22.19
      pnpm: 8.6.12
    Relevant Packages:
      next: 13.4.19
      eslint-config-next: 13.4.19
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 4.9.5
    Next.js Config:
      output: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

Jest (next/jest)

Link to the code that reproduces this issue or a replay of the bug

https://github.com/weyert/nextjs-jest-modularize-imports

To Reproduce

  1. Create Next.js application
  2. Create component which uses import { Dialog, Transition } from '@headlessui/react'
  3. Write unit tests that renders the componet
  4. Run unit tests
  5. Note the the test failing (see error below)

Describe the Bug

When you try to run the unit tests you can get an error after upgrading to 13.4.9:

 FAIL  src/__tests__/pages/CallbackPage.test.tsx
  ● Test suite failed to run

    Cannot find module 'modularize-import-loader?name=Dialog&join=./components/dialog/dialog!@headlessui/react' from 'src/layouts/SiteLayout.tsx'

    Require stack:
      src/layouts/SiteLayout.tsx
      src/layouts/getLayoutComponent.tsx
      src/pages/auth/callback.tsx
      src/__tests__/pages/CallbackPage.test.tsx

      21 | import { useAuth } from '@/auth/usercontext/useAuth'
      22 |
    > 23 | interface SiteLayoutProps extends Omit<LayoutProps, 'Layout'> {
         |                ^
      24 |   children: React.ReactNode
      25 |   showCategoriesList?: boolean
      26 | }

      at Resolver._throwModNotFoundError (../../node_modules/.pnpm/jest-resolve@29.6.2/node_modules/jest-resolve/build/resolver.js:427:11)
      at Object.<anonymous> (src/layouts/SiteLayout.tsx:23:16)
      at Object.<anonymous> (src/layouts/getLayoutComponent.tsx:11:21)
      at Object.<anonymous> (src/pages/apps/auth/callback.tsx:37:29)
      at Object.<anonymous> (src/__tests__/pages/CallbackPage.test.tsx:13:19)

Expected Behavior

I would expect unit tests to pass when not making any code changes but only upgrading to a newer version of Next when no breaking changes are listed in the release notes

Which browser are you using? (if relevant)

Edge

How are you deploying your application? (if relevant)

Other platform

@weyert weyert added the bug Issue was opened via the bug report template. label Aug 19, 2023
@github-actions github-actions bot added the Testing Related to testing with Next.js. label Aug 19, 2023
@weyert
Copy link
Author

weyert commented Aug 19, 2023

I have added a repro here: https://github.com/weyert/nextjs-jest-modularize-imports
When you run npm run build everything is fine:

❯ npm run build

> ui@0.0.0 build
> next build

- warn You have enabled experimental feature (externalDir) in next.config.js.
- warn Experimental features are not covered by semver, and may cause unexpected or broken application behavior. Use at your own risk.

- info Linting and checking validity of types  
- info Creating an optimized production build  
- info Compiled successfully
- info Collecting page data  
- info Generating static pages (2/2)
- info Finalizing page optimization  

Route (pages)                              Size     First Load JS
┌ λ /                                      15 kB            95 kB
├   /_app                                  0 B              80 kB
└ ○ /404                                   182 B          80.1 kB
+ First Load JS shared by all              80 kB
  ├ chunks/framework-63157d71ad419e09.js   45.2 kB
  ├ chunks/main-594acc6ec9f44333.js        33.4 kB
  ├ chunks/pages/_app-e057d3ea87146833.js  444 B
  └ chunks/webpack-0b5d8249fb15f5f3.js     939 B

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)

But when you try to run tests its failing with the following error:

❯ npm run test 

> ui@0.0.0 test
> env TZ=Etc/UTC jest --runInBand

- warn You have enabled experimental feature (externalDir) in next.config.js.
- warn Experimental features are not covered by semver, and may cause unexpected or broken application behavior. Use at your own risk.

 FAIL  src/layouts/__tests__/SiteLayout.test.tsx
  ● Test suite failed to run

    Cannot find module 'modularize-import-loader?name=Dialog&join=./components/dialog/dialog!@headlessui/react' from 'src/layouts/SiteLayout.tsx'

    Require stack:
      src/layouts/SiteLayout.tsx
      src/layouts/__tests__/SiteLayout.test.tsx

      21 |   const navIsOpen = true
      22 |   const setNavIsOpen = (value: boolean) => {
    > 23 |     console.log(`SiteLayout.setNavIsOpen() called value=${value}`)
         |                ^
      24 |   }
      25 |
      26 |   return (

      at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/resolver.js:427:11)
      at Object.<anonymous> (src/layouts/SiteLayout.tsx:23:16)
      at Object.<anonymous> (src/layouts/__tests__/SiteLayout.test.tsx:7:21)

 FAIL  src/__tests__/pages/StartPage.test.tsx
  ● Test suite failed to run

    Cannot find module 'modularize-import-loader?name=Dialog&join=./components/dialog/dialog!@headlessui/react' from 'src/layouts/SiteLayout.tsx'

    Require stack:
      src/layouts/SiteLayout.tsx
      src/pages/index.tsx
      src/__tests__/pages/StartPage.test.tsx

      21 |   const navIsOpen = true
      22 |   const setNavIsOpen = (value: boolean) => {
    > 23 |     console.log(`SiteLayout.setNavIsOpen() called value=${value}`)
         |                ^
      24 |   }
      25 |
      26 |   return (

      at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/resolver.js:427:11)
      at Object.<anonymous> (src/layouts/SiteLayout.tsx:23:16)
      at Object.<anonymous> (src/pages/index.tsx:21:60)
      at Object.<anonymous> (src/__tests__/pages/StartPage.test.tsx:6:55)

Test Suites: 2 failed, 2 total
Tests:       0 total
Snapshots:   0 total
Time:        0.937 s
Ran all test suites.

@kodiakhq kodiakhq bot closed this as completed in #54695 Aug 30, 2023
kodiakhq bot pushed a commit that referenced this issue Aug 30, 2023
)

## Initial Pass

The initial pass applies to all user code. If an import (`foo`) matches one of the packages from our list, say:

```js
// user code

import { a } from 'foo'
import { otherThings } from './code'
```

The initial pass transforms it into:

```js
// user code

import { a } from '__barrel_optimize__?names=a!=!foo'
import { otherThings } from './code'
```

## Barrel Optimizations

This `__barrel_optimize__` loader is used to optimize the imports of "barrel" files that have many
re-exports. Currently, both Node.js and Webpack have to enter all of these
submodules even if we only need a few of them.

For example, say a file `foo.js` with the following contents:

```js
  export { a } from './a'
  export { b } from './b'
  export { c } from './c'
  ...
```

If the user imports `a` only, this loader will accept the `names` option to
be `['a']`. Then, it request the `__barrel_transform__` SWC loader to load
`foo.js` (via `this.loadModule` Webpack API) and receive the following info:

```js
  export const __next_private_export_map__ = '[["./a","a","a"],["./b","b","b"],["./c","c","c"],...]'
```

The export map, generated by SWC, is a JSON that represents the exports of
that module, their original file, and their original name (since you can do
`export { a as b }`).

Then, this loader can safely remove all the exports that are not needed and
re-export the ones from `names`:

```js
  export { a } from './a'
```

That's the basic situation and also the happy path.



## Wildcard Exports

For wildcard exports (e.g. `export * from './a'`), it becomes a bit more complicated.
Say `foo.js` with the following contents:

```js
  export * from './a'
  export * from './b'
  export * from './c'
  ...
```

If the user imports `bar` from it, SWC can never know which files are going to be
exporting `bar`. So, we have to keep all the wildcard exports and do the same
process recursively. This loader will return the following output:

```js
  export * from '__barrel_optimize__?names=bar&wildcard!=!./a'
  export * from '__barrel_optimize__?names=bar&wildcard!=!./b'
  export * from '__barrel_optimize__?names=bar&wildcard!=!./c'
  ...
```

The "!=!" tells Webpack to use the same loader to process './a', './b', and './c'.
After the recursive process, the "inner loaders" will either return an empty string
or:

```js
  export * from './target'
```

Where `target` is the file that exports `bar`.



## Non-Barrel Files

If the file is not a barrel, we can't apply any optimizations. That's because
we can't easily remove things from the file. For example, say `foo.js` with:

```js
  const v = 1
  export function b () {
    return v
  }
```

If the user imports `b` only, we can't remove the `const v = 1` even though
the file is side-effect free. In these caes, this loader will simply re-export
`foo.js`:

```js
  export * from './foo'
```

Besides these cases, this loader also carefully handles the module cache so
SWC won't analyze the same file twice, and no instance of the same file will
be accidentally created as different instances.

---

- [x] Disable this loader for Jest
- [x] Add tests for SWC loader caching
- [x] Add tests for external modules
- [x] Add tests for pages
- [x] Add tests for recursive `export *`s

---

Closes #54038, closes #54286.
@LukasGerm
Copy link

I have the same issue. Is it fixed already? Can still reproduce in 1.7.17

@weyert
Copy link
Author

weyert commented Sep 7, 2023

Works for me in the last canary version

@ganicus
Copy link

ganicus commented Sep 13, 2023

What's the workable solution here?

@davecarlson
Copy link
Contributor

What's the workable solution here?

Waiting for 13.4.20 to be released! It's been fixed for weeks on canary, but for whatever reasons, we've had no new production release for over 3 weeks, which is annoying as there's a ton of fixes since .19, and we dont want to run a canary. ( as such we are currently stuck on .12 .....)

@t-alex-lopez
Copy link

Cannot find module 'modularize-import-loader?name=Tab&join=./components/tabs/tabs!@headlessui/react' from 'src/components_data/devtools/PantomathDevtools.tsx'

@github-actions
Copy link
Contributor

github-actions bot commented Oct 5, 2023

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot added the locked label Oct 5, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 5, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Issue was opened via the bug report template. locked Testing Related to testing with Next.js.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants