Module '@foo' cannot be located within the ts-jest setup in a monorepository

My monorepo is set up with TypeScript, WebPack, and ts-jest. The build process is successful, but I'm running into issues with unit testing in the ./demo sub-project due to the error:

Cannot find module '@mlhaufe/graphics/adapters' or its corresponding type declarations.

<root>/tsconfig.json

{
    "compilerOptions": {
        "baseUrl": "./packages",
        "paths": {
            "@mlhaufe/graphics/*": [
                "lib/src/*"
            ]
        }
    },
    "references": [
        {
            "path": "./packages/lib"
        },
        {
            "path": "./packages/demo"
        }
    ],
    "files": [],
    "exclude": [
        "node_modules"
    ]
}

<root>/packages/demo/tsconfig.json

{
   ...
    "references": [
        {
            "path": "../lib"
        }
    ]

<root>/jest.config.mjs

import fs from 'fs';
import path from 'path';
import url from 'url';
import { pathsToModuleNameMapper } from 'ts-jest';
import tsconfig from './tsconfig.json' assert { type: 'json' };

const { compilerOptions } = tsconfig,
    __filename = url.fileURLToPath(import.meta.url),
    __dirname = path.dirname(__filename),
    packageNames = fs.readdirSync(path.resolve(__dirname, './packages'));

/** @type {import('jest').Config} */
export default {
    rootDir: compilerOptions.baseUrl,
    verbose: true,
    testPathIgnorePatterns: [
        '<rootDir>/node_modules/',
    ],
    reporters: [
        'default',
        ['jest-junit', { outputDirectory: './coverage' }]
    ],
    // <https://jestjs.io/docs/next/configuration#projects-arraystring--projectconfig>
    projects: packageNames.map((name) => ({
        displayName: name,
        transform: {
            '^.+\\.mts$': ['ts-jest', { useESM: true }]
        },
        moduleFileExtensions: ['js', 'mjs', 'mts'],
        roots: [`<rootDir>`],
        modulePaths: [compilerOptions.baseUrl],
        // required due to ts-jest limitation
        // <https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/#support-mts-extension>
        resolver: '<rootDir>/mjs-resolver.ts',
        // Used the path aliases in tsconfig.json
        moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
            prefix: '<rootDir>',
            useESM: true
        }),
        // moduleNameMapper: {
        //     '@mlhaufe/graphics/(.+)': '<rootDir>/packages/lib/src',
        //     '^(\\.\\.?/.*)\\.mjs$': ['$1.mts', '$0']
        // },
        testMatch: [`<rootDir>/packages/${name}/**/*.test.mts`],
        testPathIgnorePatterns: [`<rootDir>/packages/${name}/dist/`]
    }))
};

<root>/package.json

{
    ...
    "workspaces": [
        "packages/*"
    ],
    "engines": {
        "node": ">=16.0.0"
    ],
    "scripts": {
        "build": "npm run build --workspaces",
        "build:lib": "npm run build --workspace=packages/lib",
        "build:demo": "npm run build --workspace=packages/demo",
        "test": "jest --coverage",
        "test:lib": "jest --selectProjects=lib --coverage",
        "test:demo": "jest --selectProjects=demo --coverage",
        "serve:demo": "npm run serve --workspace=packages/demo"
    },
    ...
}

I'm facing an issue where ts-jest is unable to locate the module, even though webpack and TypeScript are functioning correctly. I'm struggling to understand the relationship between the settings outside of the projects property and those within. I initially thought they would be global and apply to all projects, but it seems that's not the case.

Any insights on this matter would be greatly appreciated. I haven't come across a consistent (and modern) resource detailing how to tackle this challenge.

Answer №1

After analyzing the issue, it appears that jest is resolving the local modules within a monorepo without transpiling them beforehand. The solution provided in this response is more of a workaround rather than a definitive fix.

My approach involves adjusting the import path to be recognized as a path import instead of an external package.

Here is a snippet from my jest.config.ts file:

const config: Config = {
  preset: 'ts-jest/presets/js-with-ts-esm',
  resolver: '<rootDir>/jest.resolve.cjs',
  // additional configuration details omitted for brevity
}

export default config

The contents of the jest.resolve.cjs file are as follows:

const fs = require('node:fs')
const path = require('node:path')
const resolver = require('ts-jest-resolver')

const localModules = new Map(
  fs.readdirSync(path.join(__dirname, 'packages'), { withFileTypes: true })
    .filter((dirent) => dirent.isDirectory())
    .map((dirent) => {
      const directory = path.join(__dirname, 'packages', dirent.name)
      const pkg = require(path.join(directory, 'package.json'))
      const main = path.join(directory, pkg.main)

      return [pkg.name, main]
    }),
)

/**
 * @param {string} path
 * @param {import('jest-resolve').ResolverOptions} options
 * @returns {string}
 */
module.exports = function resolve (path, options) {
  return resolver(localModules.get(path) ?? path, options)
}

In my specific case, all the local modules are located within the packages directory. You may need to adjust this path if your setup differs.

For those who are facing a similar challenge, I trust that you possess the expertise to comprehend the provided code without detailed explanations :)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Accessing information independent of Observable data in TypeScript

When attempting to send an HttpRequest in Typescript, I encountered an issue where the received data could not be stored outside of the subscribe function. Despite successfully saving the data within the subscribe block and being able to access it there, ...

Ionic 2: Unveiling the Flipclock Component

Can anyone provide guidance on integrating the Flipclock 24-hours feature into my Ionic 2 application? I'm unsure about the compatibility of the JavaScript library with Ionic 2 in typescript. I have searched for information on using Flipclock in Ionic ...

Transform a string into a class in Typescript/Angular

In my application, I've created a reusable modal popup component that takes a string as input and dynamically loads other components based on that input. This approach allows me to use the same modal popup component for multiple modals in the app inst ...

Steps for creating a TypeScript project with React Native

Hey there, I'm just starting out with react-native and I want to work on a project using VS Code. I'm familiar with initializing a project using the command "react-native init ProjectName", but it seems to generate files with a .js extension inst ...

Converting types to "any" and encountering the error message "There are two distinct types with the same name, but they are not related."

I am encountering some challenges while trying to use an NPM module that I developed along with its Typescript typings in another application. To simplify the examples, I will omit properties that are not relevant to the issue at hand. Within my module&ap ...

Steps to enable the submit button in angular

Here's the code snippet: SampleComponent.html <nz-radio-group formControlName="radiostatus" [(ngModel)]="radioValue" (ngModelChange)="onChangeStatus($event)"> <label nz-radio nzValue="passed">Passed</label> <label nz-rad ...

Arrange objects in dropdown menu to line up

I'm currently working on a dropdown menu and I have a specific requirement – the menu should always be split into two columns and be able to span multiple lines. However, I've encountered an issue where the columns are not aligned properly, cau ...

Tips for adjusting the dimensions of a map within the app.component.html

Within the code snippet below, my aim is to adjust the width and height of the map using the style tag shown here: <style> #map3 .map { width: 100%; height:90px; } </style> Despite trying various values for width an ...

Troubleshooting issue with Vue Class Component and Vuex-class causing ESLint error

I am interested in utilizing vuex-class to bind helpers for vuex and vue-class-component However, an error message is displayed: Error: Parsing error - Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class ...

The exported variable 'SAlert' is utilizing the name 'AlertInterface' from an external module

Utilizing the antd alert component (ts) with styled components import styled from 'styled-components'; import Alert from 'antd/es/alert'; export const SAlert = styled(Alert)` && { margin-bottom: 24px; border-radiu ...

Issue: Unable to locate a matching object '[object Object]' of type 'object'. NgFor can solely bind to data structures like Arrays and Iterables

I am facing an error that says "Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays." I am trying to create a Notification list but I can't figure out w ...

Encountering the error "Unable to access the 'user' property of an undefined object when working with Angular and Firebase

Exploring Firebase for the first time while attempting to configure email and Google authentication in an Angular (v5) application. While following a tutorial (), I encounter an error: ERROR TypeError: Cannot read property 'user' of undefined T ...

Tips for creating an interface in TypeScript that prevents access to uninitialized properties of a class

Interfaces still baffle me a bit. I understand that interfaces are typically used for public properties, but I want to create an interface that will prevent access to uninitialized properties. Currently, I am able to access this.material without any errors ...

Encountered an issue during the Jest test where the error message states 'Cannot call Class constructor Stack without using the keyword 'new''

I have encountered an issue with my Jest test for an AWS CDK configuration import { expect as expectCDK, matchTemplate, MatchStyle } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import { KmsMultiregionPrincipalKey } fro ...

Stop the transmission of ts files

Recently, I noticed that when using node JS with Angular-CLI, my .ts files are being transmitted to the client through HTTP. For example, accessing http://localhost/main.ts would deliver my main.ts file to the user. My understanding is that ts files are s ...

Set the GridToolbarQuickFilter text box to have an outlined style in Material UI v5

How can I customize the appearance of the GridToolbarQuickFilter textbox, such as outlining it? Ideally, I would like to accomplish this through theme.tsx, but I am open to any suggestions. https://i.stack.imgur.com/H1Ojj.png I have experimented with var ...

Typescript meets ESLint in perfect harmony

I have embarked on a mission to upgrade an older AWS CDK typescript application. It currently utilizes Jest+ESLint for local testing before deployment and AWS integration testing. While following the ESLint 8.0 documentation, I encountered a roadblock as ...

Exploring the possibility of integrating direct search functionality into the URL bar within an Angular application

One interesting feature I observed on GitHub is that after typing "github.com" in the URL bar, you can directly search by pressing the spacebar, which activates the "search mode." Here's how it looks like on Chrome: https://i.sstatic.net/XIgJu.png I ...

An error occurred while trying to set the property 'IS_CHECK' of an object that is undefined

I'm attempting to create a checkbox that, when selected, should also select everything else. I followed the code example provided in this tutorial for angular 2. However, I encountered an error: "ERROR TypeError: Cannot set property 'IS_CHECK&ap ...

Combining indexed types with template literals -- add a prefix to each key

Start with type A and transform it into type B by adding the prefix x to each key using Typescript's newest Template Literal Types feature: type A = { a: string; b: string; }; // Automatically generate this. type Prefixed = { xa: string; xb: ...