» npm install jest-preset-angular
Jest Failing in Angular Project: Persistent "Cannot find module canvas.node" Despite Mocking
Jest unit test in Angular app is throwing "Jest encountered an unexpected token" error - Stack Overflow
[Bug]: npm install fails with latest jest 29.0.3
jestjs - Angular 14 and Jest (cannot run tests) - Stack Overflow
» npm install @angular-builders/jest
Hi everyone,
I'm struggling with a persistent Jest error in my Angular (v19) project running on macOS with pnpm, and I'm hoping someone might have encountered this before or have fresh ideas.
The Problem:
When I run pnpm jest, my tests fail immediately with:
FAIL src/app/app.component.spec.ts ● Test suite failed to run Cannot find module '../build/Release/canvas.node'
What I've Tried (Exhaustively):
I know the standard solution is to mock the canvas module, but it's not working. Here's everything I've done:
-
Mocking via
moduleNameMapper:-
Added the following to
jest.config.js:module.exports = { preset: 'jest-preset-angular', setupFilesAfterEnv: ['<rootDir>/src/setup.jest.ts'], moduleNameMapper: { '^canvas$': '<rootDir>/__mocks__/canvas.mock.js', '^src/(.*)$': '<rootDir>/src/$1', }, }; -
Created
__mocks__/canvas.mock.jsin the project root:module.exports = {};
-
-
Confirmed
canvasis NOT a direct dependency: It's not listed inpackage.json. -
Installed System Dependencies: Ran
brew install pkg-config cairo pango libpng jpeg giflib librsvgon macOS. -
Reinstalled Dependencies: Ran
rm -rf node_modules,pnpm installafter installing system deps. -
Cleared Caches: Used
pnpm jest --clearCacheand also cleared pnpm cache (pnpm cache clean) during deep clean. -
Explicit Mock in Setup: Added
jest.mock('canvas', () => ({}), { virtual: true });tosrc/setup.jest.ts. -
Forced Newer
jsdom: Used pnpm overrides inpackage.jsonto forcejsdom: "^22.1.0"and reinstalled. -
Deep Clean & Verbose Install: Did
rm -rf node_modules,rm pnpm-lock.yaml,pnpm cache clean, thenpnpm install --verbose.
Despite all this, the exact same error persists.
Relevant Versions:
-
Angular: 19.x
-
Jest: 29.7.0
-
jest-preset-angular: 14.5.4 -
jest-environment-jsdom: 29.7.0 (inferred) -
canvas(transitive): 3.1.0 (inferred) -
jsdom(transitive): 20.0.3 (inferred) -
OS: macOS
-
Package Manager: pnpm
Has anyone run into a situation where moduleNameMapper seems completely ignored for a transitive dependency loaded by jsdom? Any ideas what else could be interfering or alternative approaches I could try? Could it be a weird interaction between pnpm, Jest 29, and this older jsdom/canvas combo?
Thanks in advance for any suggestions!
UPDATE:
The most important change was to configure the transformIgnorePatterns as below:
"transformIgnorePatterns": [
"/node_modules/@protontech/(?!jsmimeparser).+\\.js$"
],
I recreated a project with angular 19, then changed the jest.config.ts to reflect what was provided in the jest-preset-angular latest documentation.
import type { Config } from 'jest';
import presets from 'jest-preset-angular/presets';
export default {
...presets.createCjsPreset({
tsconfig: 'tsconfig.spec.json',
}),
setupFilesAfterEnv: ['<rootDir>/src/setup-jest.ts'],
"transformIgnorePatterns": [
"/node_modules/@protontech/(?!jsmimeparser).+\\.js$"
],
} satisfies Config;
Then we can update the setup-jest.ts as below:
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
// import './jest-global-mocks';
setupZoneTestEnv();
Also adding tsconfig.spec.json for reference:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": ["jest", "node"]
},
"files": ["polyfills.ts"],
"include": ["**/*.spec.ts", "**/*.d.ts"]
}
Github Repo -> npm i --legacy-peer-deps -> npx jest --verbose (You can try your normal test command instead of this)
Seems like you are using a JS file and during conversion (Read as typescript) it is throwing an error, could you try:
import type {Config} from 'jest';
const config: Config = {
verbose: true,
preset: 'jest-preset-angular',
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
"transformIgnorePatterns": [
"node_modules/(?!@protontech)"
],
moduleNameMapper: {
'^src/(.*)$': '<rootDir>/src/$1',
'^app/(.*)$': '<rootDir>/src/app/$1',
'^assets/(.*)$': '<rootDir>/src/assets/$1',
'^environments/(.*)$': '<rootDir>/src/environments/$1',
},
};
Jest gives an error: "SyntaxError: Unexpected token export"
This configuration should resolve the "Unexpected token export" error:
import type { Config } from 'jest';
const config: Config = {
verbose: true,
preset: 'jest-preset-angular',
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
moduleNameMapper: {
'^src/(.*)$': '<rootDir>/src/$1',
'^app/(.*)$': '<rootDir>/src/app/$1',
'^assets/(.*)$': '<rootDir>/src/assets/$1',
'^environments/(.*)$': '<rootDir>/src/environments/$1',
},
transformIgnorePatterns: [
// This line tells Jest to transform the jsmimeparser module
'node_modules/(?!@protontech/jsmimeparser)'
],
transform: {
'^.+\\.(ts|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
};
export default config;
Make sure your tsconfig.spec.json includes the necessary compiler options for handling ES modules:
{
"compilerOptions": {
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}
So I was searching online for quite sometime then I came across this solution, hope it works for you as well.
In the jest.config.js file, if you are using the moduleDirectories config change it from moduleDirectories: ['node_modules', './'] to moduleDirectories: ['node_modules', __dirname]
There are couple of things you can do
1- Make sure you changed this in setup-jest.ts file
import 'jest-preset-angular/setup-jest'; //from
import 'jest-preset-angular/setup-jest.mjs'; //to
2- add mjs to module file extensions
moduleFileExtensions: ['ts', 'html', 'js', 'json', 'mjs'],
3- Map your ts config paths at the moduleNameMapper, you can add rootDir/ as prefix as a second parameter to pathsToModuleNameMapper if needed
const { compilerOptions } = require('$tsconfigFilePath'); // replace $tsconfigFilePath with the actual tsconfigFilePath;
const { pathsToModuleNameMapper } = require('ts-jest');
module.exports = {
moduleNameMapper: {
...pathsToModuleNameMapper(compilerOptions.paths),
tslib: 'tslib/tslib.es6.js',
}
}
4- This one is tricky if you are using ngrx replace the following, this might be fixed in future version, it really depends on which @nrwl/jest version you are using, mine as of now is 14.6.0
const nxPreset = require('@nrwl/jest/preset'); //from
const nxPreset = require('@nrwl/jest/preset').default; //to