[Bug]: jest-preset-angular does not work well with monorepos with mixed hoisted dependencies
See original GitHub issueVersion
12.2.0
Steps to reproduce
Setup a monorepo with an angular project with several dependencies, some dependencies are hoisted to the root of the monorepo and others are not.
When you run jest with this preset, you will be getting errors like:
Unexpected value 'TranslateModule' imported by the module 'DynamicTestModule'. Please add an @NgModule annotation
TypeError: Cannot read properties of undefined (reading 'Tag')
Type AgGridModule does not have 'ɵmod' property
Some tests will pass and others will fail.
As it turns out, jest-preset-angular uses this code to call ngcc to compile the dependencies, but it only accounts for the node_modules directory where the ngcc was found in https://github.com/thymikee/jest-preset-angular/blob/main/src/utils/ngcc-jest-processor.ts#L17-L19
This has the unintended effect that depending on how the hoisting happened (moving the ngcc to the root node modules or the angular project node modules), some dependencies in the other node_modules directory would not get compiled.
Took a bit of digging through the code to identify this as the source of the problem.
I fixed it in my case by copying ngcc-jest-processor and modifying it to run the compiler on both node_modules directories.
It would be great if this issue could be solved directly in this project as well.
Expected behavior
I expect all the tests to pass
Actual behavior
Some tests pass, and others fail
Additional context
For my use case, I fixed it by copying the ngcc-jest-processor into my project and modifying it to call ngcc twice (one per node modules directory) and that resolved the issue for me.
/**
* Mainly copied from https://github.com/angular/angular-cli/blob/master/packages/ngtools/webpack/src/ngcc_processor.ts
* and adjusted to work with Jest
*/
// const { spawnSync } = require('child_process');
// const path = require('path');
import { spawnSync } from 'child_process';
import path from 'path';
const ANGULAR_COMPILER_CLI_PKG_NAME = `@angular${path.sep}compiler-cli`;
let ngccPath = '';
try {
ngccPath = require.resolve('@angular/compiler-cli/ngcc/main-ngcc.js');
} catch {
const compilerCliNgccPath = require.resolve('@angular/compiler-cli/ngcc');
ngccPath = path.resolve(
compilerCliNgccPath.substring(0, compilerCliNgccPath.lastIndexOf(path.sep)),
'main-ngcc.js'
);
}
function findNodeModulesDirectory() {
return ngccPath.substring(0, ngccPath.indexOf(ANGULAR_COMPILER_CLI_PKG_NAME));
}
const nodeModuleDirPath = findNodeModulesDirectory();
const runNgccJestProcessorForNodeModule = (tsconfigPath, nodeModuleDir) => {
if (nodeModuleDir) {
process.stdout.write(`\nngcc-jest-processor..: running ngcc for ${nodeModuleDir}\n`);
const ngccBaseArgs = [
ngccPath,
'--source' /** basePath */,
nodeModuleDir,
'--properties' /** propertiesToConsider */,
/**
* There are various properties: fesm2015, fesm5, es2015, esm2015, esm5, main, module, browser to choose from.
* Normally, Jest requires `umd`. If running Jest in ESM mode, Jest will require both `umd` + `esm2015`.
*/
...['es2015', 'main'],
'--first-only' /** compileAllFormats */,
'false', // make sure that `ngcc` runs on subfolders as well
'--async',
];
if (tsconfigPath) {
ngccBaseArgs.push(...['--tsconfig', tsconfigPath]);
}
// We spawn instead of using the API because:
// - NGCC Async uses clustering which is problematic when used via the API which means
// that we cannot setup multiple cluster masters with different options.
// - We will not be able to have concurrent builds otherwise Ex: App-Shell,
// as NGCC will create a lock file for both builds and it will cause builds to fails.
const { status, error } = spawnSync(process.execPath, ngccBaseArgs, {
stdio: ['inherit', process.stderr, process.stderr],
});
if (status !== 0) {
const errorMessage = error?.message ?? '';
throw new Error(
`${errorMessage} NGCC failed ${errorMessage ? ', see above' : ''}.`
);
}
} else {
console.log(
`Warning: Could not locate '@angular/compiler-cli' to run 'ngcc' automatically.` +
`Please make sure you are running 'ngcc-jest-processor.js' from root level of your project.` +
`'ngcc' must be run before running Jest`
);
}
};
export const runNgccJestProcessor = (
tsconfigPath
) => {
runNgccJestProcessorForNodeModule(tsconfigPath, path.resolve(__dirname, 'node_modules'));
runNgccJestProcessorForNodeModule(tsconfigPath, path.resolve(__dirname, '../../node_modules'));
};
Environment
System:
OS: macOS 12.2.1
CPU: (8) arm64 Apple M1 Pro
Binaries:
Node: 16.15.1 - ~/.nvm/versions/node/v16.15.1/bin/node
Yarn: 1.22.10 - /usr/local/bin/yarn
npm: 8.11.0 - ~/.nvm/versions/node/v16.15.1/bin/npm
Issue Analytics
- State:
- Created a year ago
- Comments:6
Top GitHub Comments
I get it now, one thing I want to clear up is: by default this preset doesn’t provide
ngcc
to run but you have to opt in, please check our preset https://github.com/thymikee/jest-preset-angular/blob/main/src/presets/index.tsOur
ngcc
script is to stimulate what angular cli does, but if it doesn’t fit your need, you shouldn’t use this preset’s ngcc script but should use the one from Angular itself. I think that would help you to move forward. There is anngcc
bin file which is shipped with angular cli.Please also take a look at our example https://github.com/thymikee/jest-preset-angular/tree/main/examples/example-app-yarn-workspace which might help.
Regarding to fix the ngcc script, feel free to submit your PR 😃