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

Getting Error Type 'typeof import("/home/quophyie/projects/helmet-issue/node_modules/helmet/index")' has no call signatures when running tests with jest, ts-jest when using ESM / ECMAScript Modules #441

Open
quophyie opened this issue Oct 12, 2023 · 12 comments

Comments

@quophyie
Copy link

quophyie commented Oct 12, 2023

Hi

I am getting the error below when I run tests with jest, ts-jest where a module uses helmet

Type 'typeof import("/home/quophyie/projects/helmet-issue/node_modules/helmet/index")' 

I am using ESM (ECMAScript modules) and node version 19.9.0 and helmet v7.0.0
The weird thing is that, the app works fine when I just run it with ts-node

Here is my setup (The github repo is helmut-issue-441 )
node-version
v19.9.0v

package.json

{
  "type": "module",
  "scripts": {
    "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest -i --passWithNoTests",
    "start": "ts-node --esm --project tsconfig.json --transpile-only main.ts"
  },
  "dependencies": {
    "cross-env": "^7.0.3",
    "express": "^4.18.2",
    "helmet": "^7.0.0",
    "ts-node": "^10.9.1"
  },
  "devDependencies": {
    "@types/jest": "^29.5.5",
    "jest": "^29.7.0",
    "ts-jest": "^29.1.1",
    "typescript": "^5.2.2"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "es2022",
    "lib": ["es2022", "dom"],
    "typeRoots": ["./node_modules/@types", "src/types"],
    "allowJs": true,
    "allowImportingTsExtensions": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strictPropertyInitialization": false,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "resolveJsonModule": true,
    "isolatedModules": false,
    "jsx": "react",
    "pretty": true,
    "sourceMap": true,
    "noEmit": true,
    "downlevelIteration": true,
    "outDir": "./build",
    "rootDir": "."
  },
  "include": ["./**/*.ts", "declarations/**/*"],
  "exclude": ["build/**/*", "node_modules"]
}

main.test.ts

import {jest} from '@jest/globals'
import helmet from 'helmet'

helmet();

describe('Helmet Issue 441', () => {
    it('fails on call to helmut() function', () => {
        expect(true).toBe(true);
    });
});

main.ts

import helmet from 'helmet';

helmet();
console.log('This is OK');
@quophyie quophyie changed the title Getting Error Type 'typeof import("/home/quophyie/projects/helmet-issue/node_modules/helmet/index")' has no call signatures when running tests with jest, ts-jest and ESM / ECMAScript Modules Getting Error Type 'typeof import("/home/quophyie/projects/helmet-issue/node_modules/helmet/index")' has no call signatures when running tests with jest, ts-jest when using ESM / ECMAScript Modules Oct 12, 2023
@EvanHahn
Copy link
Member

EvanHahn commented Oct 12, 2023 via email

@quophyie
Copy link
Author

Hi @EvanHahn
Thanks for the quick response

This only happens with helmet module only. Other modules are OK

jest, ts-jest and ts-node all use the same typescript config .

here is the jest.config.ts with the embedded ts-jest config

jest.config.ts

import type { JestConfigWithTsJest } from 'ts-jest';

const jestConfig: JestConfigWithTsJest = {
    testFailureExitCode: 1,
    moduleFileExtensions: ['ts', 'js', 'json'],
    extensionsToTreatAsEsm: ['.ts'],
    preset: 'ts-jest/presets/default-esm',
    transform: {
        '^.+\\.(ts|tsx)$': [
            'ts-jest',
            {
                useESM: true,
                tsconfig: 'tsconfig.json',
            },
        ],
    },
    testMatch: [
        '**/*.test.(ts|js)',
        '**/*.spec.(ts|js)'
    ],
    testPathIgnorePatterns: [
        '<rootDir>/build',
        '<rootDir>/node_modules/'
    ],
    testEnvironment: 'node',
};

export default jestConfig;

ts-node also uses the exact same tsconfig.json

Here is the ts-node start script in the package.json

"start": "ts-node --esm --project tsconfig.json --transpile-only main.ts"

@EvanHahn
Copy link
Member

Everything looks okay at a glance, but ESM + TypeScript + Jest often causes problems.

What's the full error you're seeing? What happens if you add // @ts-ignore before importing Helmet?

@quophyie
Copy link
Author

@EvanHahn
Here is the full console log

yarn run v1.22.19
warning package.json: No license field
$ cross-env NODE_OPTIONS=--experimental-vm-modules jest -i --passWithNoTests --detectOpenHandles --forceExit --runInBand
 FAIL  ./main.test.ts
  ● Test suite failed to run

    main.test.ts:4:1 - error TS2349: This expression is not callable.
      Type 'typeof import("/home/dman/projects/helmet-issue-441/node_modules/helmet/index")' has no call signatures.

    4 helmet();
      ~~~~~~

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.912 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

@quophyie
Copy link
Author

Hi @EvanHahn
I added the //@ts-ignore and that seems to have fixed it i.e.
in my main.test.ts

import {jest} from '@jest/globals'
import helmet from 'helmet'

//@ts-ignore
helmet();

describe('Helmet Issue 441', () => {
    it('fails on call to helmut() function', () => {
        expect(true).toBe(true);
    });
});

Thanks for the help

Would you know why the //@ts-ignore solves the issue?

quophyie pushed a commit to quophyie/helmet-issue-441 that referenced this issue Oct 13, 2023
@EvanHahn
Copy link
Member

It seems like ts-jest isn't pulling in the right type declarations for some reason. I don't know why.

// @ts-ignore simply tells TypeScript to ignore the next line. This is not a true fix, but a workaround for this problem.

Is there a way to see what type declaration file ts-jest is using? Maybe some verbose logging mode or something?

@Sharcoux
Copy link

I have the same problem. The type correctly points towards index.d.ts. However, the types are starting to behave correctly only if I do const helmet = require('helmet').default.

@EvanHahn
Copy link
Member

Awhile ago, I chatted with a TypeScript team member who endorsed the way Helmet exports its types. But I concede that it's complicated and it's possible I made a mistake somewhere.

Is it possible that ts-jest (or some sub-system) is getting confused, trying to treat an ES module as a CommonJS one? Or something like that?

@null-prophet
Copy link

when you use "moduleResolution": "node", it will work.

Any other resolution method will fail. I see this issue happening in the validator.js codebase too. I have to add in another default import.

When I change the moduleResolution to anything other than node helmet will not work.

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["ESNext"],
    "module": "ESNext",
    "outDir": "./dist",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "allowImportingTsExtensions": true
  },
  "exclude": [
    "node_modules"
  ],
  "extends": "../../packages/tsconfig/base.json",
  "include": ["."]
}
// base.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Default",
  "compilerOptions": {
    "composite": false,
    "declaration": true,
    "declarationMap": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "inlineSources": false,
    "isolatedModules": true,
    "moduleResolution": "node",
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "preserveWatchOutput": true,
    "skipLibCheck": true,
    "strict": true,
    "strictNullChecks": true
  },
  "exclude": ["node_modules"]
}

// package.json (abbreviated)
{
  "name": "api",
  "version": "0.0.1",
  "private": true,
  "type": "module",
  "scripts": {
    "start": "node dist/index.js",
    "dev": "nodemon",
    "build": "tsup",
    "clean": "rimraf dist",
    "typecheck": "tsc --noEmit",
    "lint": "eslint src/",
    "test": "DOTENV_CONFIG_PATH=.env.test jest --detectOpenHandles"
  },
  "jest": {
    "preset": "@repo/jest-presets/jest/node",
    "setupFiles": [
      "dotenv/config"
    ],
    "globalSetup": "<rootDir>/test/global-setup.ts",
    "globalTeardown": "<rootDir>/test/global-teardown.ts",
    "setupFilesAfterEnv": [
      "<rootDir>/test/setup-file.ts"
    ]
  },
  "dependencies": {
    "express": "^4.18.2",
    "helmet": "^7.1.0",
  },
  "devDependencies": {
    "esbuild": "^0.19.7",
    "esbuild-register": "^3.5.0",
    "eslint": "*",
    "jest": "^29.7.0",
    "jest-fetch-mock": "^3.0.3",
    "nodemon": "^3.0.2",
    "supertest": "^6.3.3",
    "ts-node": "^10.9.1",
    "tsup": "^8.0.1",
    "typescript": "^5.3.3"
  }
}

I'm just using a vanilla express js setup with the use(helmet()) and import helmet from 'helmet'

This a turbo monorepo using pnpm but the TS-Config is working fine with other packages.

doing the: const helmet = require('helmet').default works when I change moduleResolution to ESNext only.

@EvanHahn
Copy link
Member

To work with Node, I expect that "moduleResolution" needs to be set to "nodenext", "node10", or one of its aliases. According to the docs, you don't always get this by default.

@tbn-mm
Copy link

tbn-mm commented Jan 29, 2024

If I downgrade to 6.1.4, the error is gone.
The 6.1.5 commit: f8ae480

@EvanHahn
Copy link
Member

@tbn-mm Could you create a sample project that reproduces your issue?

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

No branches or pull requests

5 participants