svelte-jester icon indicating copy to clipboard operation
svelte-jester copied to clipboard

Unable to get Typescript working with many ES modules

Open therealpaulgg opened this issue 5 years ago • 31 comments

I have a typescript Svelte component that makes use of moment.JS, importing like so:

import moment from "moment

It works with my rollup compilation and development server flawlessly. However, when using svelte-jester and using preprocessing, it says that 'moment is not a function'.

Here is my jest configuration:

module.exports = {
    testEnvironment: "jsdom",
    transform: {
        "^.+\\.svelte$": [
            "svelte-jester",
            {
                preprocess: true
            }
        ],
        "^.+\\.js$": "babel-jest",
        "^.+\\.ts$": "ts-jest"
    },
    moduleFileExtensions: ["js", "ts", "svelte"],
    setupFilesAfterEnv: ["./jestSetup.ts"],
    transformIgnorePatterns: ["node_modules/(?!(svelte-typewriter|svelte-flatpickr)/)"],
    moduleNameMapper: {
        "\\.(css|less|scss)$": "identity-obj-proxy"
    },
    testPathIgnorePatterns: ["/lib/", "/node_modules/"]
}

jestSetup.ts:

import "@testing-library/jest-dom";

tsconfig.json (have tried many variants):

{
    "extends": "@tsconfig/svelte/tsconfig.json",
    "include": ["src/**/*", "src/types/*"],
    "exclude": ["node_modules/*", "__sapper__/*", "public/*"],
    "compilerOptions": {
        "types": ["jest", "node"],
        "allowSyntheticDefaultImports": true,
        "target": "ES2019"
    }
}

test output:


    TypeError: moment is not a function

      35 |                 dv[0] = flatpickr.formatDate(dv[0], "Y-m-d")
      36 |             }
    > 37 |             if (
         |             ^
      38 |                 typeof dv[1] != "string" &&
      39 |                 String(new Date(dv[1])) !== "Invalid Date"
      40 |             ) {

      at updateDate (src/components/Options.svelte:37:13)
      at Object.$$self.$$.update (src/components/Options.svelte:26:4)
      at init (node_modules/svelte/internal/index.js:1450:8)
      at new Options (src/components/Options.svelte:1815:3)
      at Array.create_default_slot_5 (src/App.svelte:25:12)
      at create_slot (node_modules/svelte/internal/index.js:65:29)
      at create_fragment (src/components/Sidebar.svelte:19:23)
      at init (node_modules/svelte/internal/index.js:1454:37)
      at new Sidebar (src/components/Sidebar.svelte:122:3)
      at create_fragment (src/App.svelte:377:12)
      at init (node_modules/svelte/internal/index.js:1454:37)
      at new App (src/App.svelte:510:3)
      at Object.render (node_modules/@testing-library/svelte/dist/pure.js:71:21)
      at Object.<anonymous> (tests/integration/Home.spec.ts:5:40)

This isn't the first time I have had trouble with transpiling. There have been issues with getting other Svelte components to work, problems with flatpickr, etc.

Please let me know if there is something I am doing wrong.

therealpaulgg avatar Aug 25 '20 18:08 therealpaulgg

I was able to get it to work by doing import * as moment from "moment" in Jest, but that makes my application not work. So clearly something is going on with the transpilation.

therealpaulgg avatar Aug 25 '20 18:08 therealpaulgg

Hey @therealpaulgg these types of issues come up a lot but they're always related to Jest, generally with how Jest resolves modules and transforms them. Jest only uses svelte-jester when it finds a file it doesn't understand such as .svelte and it then usessvelte-jester to transform that code to JS, but Jest is still responsible for resolving modules. By default Jest only resolves CommonJS by looking at the main key in package.json, and it's why import * as moment from "moment" works, and it's also why it fails resolving modules fails a lot.

Anyway ... that was to help you potentially debug in the future but in your case you're using TypeScript to resolve modules so ... the first thing I'd say is you can remove babel-jest and let ts-jest resolve modules:

transform: {
  // ...
  "^.+\\.(js|ts)$": "ts-jest"
}

The second is you should configure TypeScript to use the node module resolution strategy, there's a great section in the handbook about it.

"compilerOptions": {
  // ...
 "moduleResolution": "node"
}

Try these changes, if it doesn't still work let me know if you're receiving any new errors or the same.

mihar-22 avatar Aug 26 '20 00:08 mihar-22

Still having the same issue. I've changed my config (removed the extends svelte tsconfig portion, wasnt sure if that was messing with compilerOptions):

tsconfig.json:

{
    "include": ["src/**/*", "src/types/*"],
    "exclude": ["node_modules/*", "__sapper__/*", "public/*"],
    "compilerOptions": {
        "types": ["jest", "svelte"],
        "allowSyntheticDefaultImports": true,
        "target": "ES2019",
        "moduleResolution": "node",
        "allowJs": true,
        "importsNotUsedAsValues": "error",
        "isolatedModules": true,
        "sourceMap": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
    }
}

jest.config.js:

module.exports = {
    preset: "ts-jest",
    testEnvironment: "jsdom",
    transform: {
        "^.+\\.svelte$": [
            "svelte-jester",
            {
                preprocess: true
            }
        ],
        "^.+\\.(js|ts)$": "ts-jest"
    },
    moduleFileExtensions: ["js", "ts", "svelte"],
    setupFilesAfterEnv: ["./jestSetup.ts"],
    transformIgnorePatterns: [
        "node_modules/(?!(svelte-typewriter|svelte-flatpickr)/)"
    ],
    moduleNameMapper: {
        "\\.(css|less|scss)$": "identity-obj-proxy"
    },
    globals: {
        "ts-jest": {
            isolatedModules: true
        }
    },
    testPathIgnorePatterns: ["/lib/", "/node_modules/"]
}

Also worth noting I guess, since it's weird, I am using flatpickr as well for something using the format import flatpickr from "flatpickr". Tried doing import * as moment from "moment", so that stage of jest clears. and then I get (when doing flatpickr.parseDate(...)):

TypeError: Cannot read property 'parseDate' of undefined

therealpaulgg avatar Aug 26 '20 01:08 therealpaulgg

Oh there's few more steps to configure ts-jest, add allowJs: "true" to your tsconfig compilerOptions and then set the preset in jest.config.js to the following ts-jest/presets/js-with-ts. Let me know if it's still the same after that.

mihar-22 avatar Aug 26 '20 01:08 mihar-22

If you also want the original Svelte files when using Jest then it's a good idea to use jest-svelte-resolver.

mihar-22 avatar Aug 26 '20 01:08 mihar-22

Same deal 😕

also not totally how to use jest-svelte-resolver but it gave me an error basically immediately when parsing file:

export { default as Router } from "./Router.svelte";
    ^^^^^^

    SyntaxError: Unexpected token 'export

therealpaulgg avatar Aug 26 '20 14:08 therealpaulgg

That really sucks @therealpaulgg, so we don't go back and forth too much, when I have some time today I'll set up a project just like yours, and I'll see if I can get it working.

mihar-22 avatar Aug 27 '20 02:08 mihar-22

Hey @therealpaulgg I tried to recreate the issue but I can't. Everything works on my end. Can you give me an example of some file where you're trying the things that are failing? Some demo Svelte component with whatever you're importing and using would be awesome.

mihar-22 avatar Aug 28 '20 06:08 mihar-22

Yes in fact I can send you the entire project setup I'm using...

My test in test/integration is rather 'dumb' so to speak and doesn't really do a whole lot. It loads the entire App.svelte obviously to make sure everything works. I just added the DatePicker.svelte component which imports moment and flatpickr and things like that. There shouldn't be any import errors, but in its current state I doubt the test will pass, just kind of threw the code in there. As long as I can get to a point where the error isn't related to bad imports, that's great.

https://github.com/therealpaulgg/svelte-rollup-template

therealpaulgg avatar Aug 28 '20 08:08 therealpaulgg

Okay so first thing we now know is that moment has issues with Jest resolution, not sure why ... There is this hack to make it work. I'd like to recommend this library as an alternative: https://github.com/iamkun/dayjs. Same API but only 2KB.

Now need to figure out why Typewriter.svelte is not getting transformed.

mihar-22 avatar Aug 28 '20 09:08 mihar-22

So Jest always resolves based on the main key in the package.json and that's why svelte-flatpickr is resolved as a CJS module and it only works if we do import * as Flatpickr from 'svelte-flatpickr. You can always open up node_modules/{package}/package.json and have a look at the exports to see what's being resolved. If you look inside node_modules/svelte-flatpickr/package.json you'll see there is both a main and svelte export. Jest is defaulting to main. If we want the svelte file then we have to use a resolver like the one I linked earlier. Simply install it and add this line "resolver": "jest-svelte-resolver" in jest.config.js.

mihar-22 avatar Aug 28 '20 09:08 mihar-22

Finally, Typewriter was failing because you added node_modules to testPathIgnorePatterns in jest.config.js. This means Jest won't transform the file and ignores it, then at runtime it will fail because it doesn't understand .svelte files.

mihar-22 avatar Aug 28 '20 09:08 mihar-22

Overall here's what you can do:

  1. Run npm install @tsconfig/svelte and change your tsconfig to:
{
  "extends": "@tsconfig/svelte/tsconfig.json",
  "include": ["src/**"],
  "exclude": ["node_modules/**"],
  "compilerOptions": {
      "types": ["jest"],
      "allowJs": true,
      "allowSyntheticDefaultImports": true,
  }
}
  1. Change your jest.config.js to:
module.exports = {
    preset: "ts-jest/presets/js-with-ts",
    globals: {
      'ts-jest': {
         // Let's avoid type checking during tests (performance boost).
        diagnostics: false
      }
    },
    transform: {
        "^.+\\.svelte": [
            "svelte-jester",
            {
                preprocess: true
            }
        ],
        "^.+\\.(js|ts)": "ts-jest"
    },
    moduleFileExtensions: ["svelte", "js", "ts"],
    setupFilesAfterEnv: ["./jestSetup.ts"],
    transformIgnorePatterns: [
        "node_modules/(?!svelte-typewriter|svelte-flatpickr)/",
    ],
    moduleNameMapper: {
        "\\.(css|less|scss)$": "identity-obj-proxy"
    }
}
  1. Swap moment for dayjs.

  2. Do import * as Flatpickr from 'svelte-flatpickr or setup jest-resolver and do import Flatpickr from 'svelte-flatpickr.

mihar-22 avatar Aug 28 '20 09:08 mihar-22

Ok, thanks! tried all that. I am now getting dayjs is not a function. dunno if you were able to test that or not, but its somehow the exact same problem as moment.

therealpaulgg avatar Aug 28 '20 09:08 therealpaulgg

Ah it might be, I didn't check it and I cleaned up the project. Does import * as dayjs from 'dayjs' do the trick or same issue as moment where you lose the ability to call a function?

mihar-22 avatar Aug 28 '20 09:08 mihar-22

same problem as moment, my whole project complains when i do any import like that

therealpaulgg avatar Aug 28 '20 16:08 therealpaulgg

That hack I linked earlier might do the trick then if moment and dayjs are tricky:

import * as dayjs from "dayjs";
const dayjs = require("dayjs").default || require("dayjs");

mihar-22 avatar Aug 29 '20 00:08 mihar-22

Hey guys, is there any update on this? I'm also experiencing the same issue when I try to use the clsx module. The error is kinda similar: clsx is not a function. Have you @therealpaulgg solve your dayjs is not a function error?

ngavinsir avatar Feb 01 '21 15:02 ngavinsir

@ngavinsir unfortunately I don't recall this ever being solved for me. I have not worked on my project that required svelte in a while. Not sure if it is indicative of a problem with svelte-jester or something else.

therealpaulgg avatar Feb 01 '21 15:02 therealpaulgg

Ah, okay, thanks for your response :+1:

ngavinsir avatar Feb 01 '21 15:02 ngavinsir

Hi @mihar-22, I have the same issue as @therealpaulgg (except I use svelte-calendar, not svelte-flatpickr). following the instructions above, I got the svelte-calendar issue fixed by adding the resolver.

but, the solution about dayjs does not work: the trick const dayjs = require("dayjs").default || require("dayjs"); does fix the issue with jest, but the same code will cause a problem in browser with an error require is not defined, because require() does not exist in the browser/client-side JavaScript.

Do you have any workaround for dayjs? thanks

miaoz2001 avatar May 07 '21 11:05 miaoz2001

A nasty workaround for clsx:

  1. In your package.json:
  "jest": {
    ...
    "moduleNameMapper": {
      "^clsx$": [
        "<rootDir>/src/clsx.jest.js"
      ]
    }
  }
  1. Create a new file /src/clsx.jest.js
const clsx = require('clsx/dist/clsx.js')
module.exports.default = clsx

wgrabias avatar Jul 25 '21 22:07 wgrabias

Hey guys, is there any update on this? I'm also experiencing the same issue when I try to use the clsx module. The error is kinda similar: clsx is not a function. Have you @therealpaulgg solve your dayjs is not a function error?

Can you please retry with the latest version and jest 27+?

sebastianrothe avatar Sep 15 '21 19:09 sebastianrothe

Hi! I had the same problem, the hack I came up with is:

In my test setup script ( setupFilesAfterEnv: ["./jestSetup.ts"] in jest.config.js)

jest.mock('moment', () => ({ _esModule: true, default: jest.requireActual('moment'), }));

Hope it helps

bibizio avatar Nov 29 '21 07:11 bibizio

A nasty workaround for clsx:

  1. In your package.json:
  "jest": {
    ...
    "moduleNameMapper": {
      "^clsx$": [
        "<rootDir>/src/clsx.jest.js"
      ]
    }
  }
  1. Create a new file /src/clsx.jest.js
const clsx = require('clsx/dist/clsx.js')
module.exports.default = clsx

That's the only solution which works for me. I use it for my troubles with Flatpickr: In your package.json:

  moduleNameMapper: {
    "^flatpickr$": ["<rootDir>/src/flatpickr.jest.js"]
    }

and then you make a new file ''/src/flatpickr.jest.js:

const flatpickr = require('flatpickr/dist/flatpickr.js')
module.exports.default = flatpickr

Thank you @wgrabias

simeonTsonev avatar Jan 18 '22 11:01 simeonTsonev

It seems like TypeScript isn't the real culprit here. @sebastianrothe I've created a reproduction repository with the latest versions of Jest and svelte-jester to demonstrate that the issue persists.

What's confusing about this issue is that if one uses svelte-add-jest to add Jest to a SvelteKit application, such issues won't come up because this adder generates code that runs Jest in experimental ESM mode. It's all good, but then the user-event part of the Testing Library doesn't work, so it's a no-go zone for proper testing.

illright avatar Feb 18 '22 09:02 illright

We have some tests with click-events, also. What is not working on your side?

sebastianrothe avatar Feb 18 '22 09:02 sebastianrothe

@sebastianrothe never mind, turns out it's an issue with @testing-library/user-event (https://github.com/testing-library/user-event/issues/757#issuecomment-1009837483). I've created a reproduction of it in the esm branch of the same repository in case you'd like to take a look.

However, I would personally prefer not using the experimental ES module support. Is there any chance this could be fixed inside of svelte-jester to enable both modes?

illright avatar Feb 18 '22 10:02 illright

We do ship both versions (CJS and ESM). You may have to manually override the transformer in your jest.config:

transformer: "svelte-jester/dist/transformer.cjs",

sebastianrothe avatar Feb 18 '22 10:02 sebastianrothe

You may have to manually override the transformer in your jest.config:

This configuration didn't resolve the issue in the reproduction repository:

export default {
  testEnvironment: "jsdom",
  transform: {
    "^.+\\.js$": "babel-jest",
    "^.+\\.svelte$": "svelte-jester/dist/transformer.cjs"
  },
  moduleFileExtensions: ["js", "svelte"]
};

Did I understand you correctly?

illright avatar Feb 18 '22 10:02 illright