Incorrect Jest coverage
Opened this issue Β· 83 comments
When using @swc/jest to transpile ts to commonjs and running jest --coverage certain branches are shown as not covered (console logging in these branches show that tests do run the code path). Using babel to transpile and run the tests shows the correct coverage.
I added this to my Jest configuration in package.json which seemed to help.
"jest": {
"collectCoverage": true,
"transform": {
"^.+\\.(t|j)sx?$": [
"@swc/jest",
{
"sourceMaps": true
}
]
}
}@pspeter3 Thank you for your code snippet, it helps, but now there is another problem:
It looks like not covered code highlights are not displayed correctly.
There is an example of coverage report from project with:
jest@27.3.1@swc/core@1.2.118@swc/jest@0.2.11
the yellow highlight here says that the comma is supposedly not covered
@krutoo ran into the same issues and guessed it is caused by the conversion to older JS syntax. We could fix these false-positives by targeting a newer ES version.
transform: {
'^.+\\.(t|j)sx?$': [
'@swc/jest',
{
jsc: {
target: 'es2021',
},
sourceMaps: true,
},
],
},
Another reproduction here: typescript-eslint/tslint-to-eslint-config#1367
I ended up only needing target: "es2021". Thanks!
I think swc reusing span for various places may cause this.
Can you try https://sokra.github.io/source-map-visualization/#custom by manually invoking swc?
@kdy1 does this help? Maybe the Object.keys(_node).forEach produces the mismatch? Jest complains that this line is not covered: export * as node from './BodyNodesBuilder';
Edit: tested with
@swc/jest0.2.20jest27.5.1
@bobaaaaa Can you share some code? I think the test file would be enough, as it does not have good sourcemap.
Oh... Maybe assumption about monotonic increment of source map position can be the cause.
I'll add it to my tasklist
@kdy1 You can find it here: https://gist.github.com/bobaaaaa/3649b3a7e6312793a257bf67c500128a
Let me know if something is missing.
(thx for investigating β€οΈ)
You need sourceMaps: true or sourceMaps: "inline".
I verified that it's working
@kdy1 Hm, I tested both sourceMaps in .swcrc and as an option in the jest.config.json. Both did not work for me. Even with target: "es2021".
Hmm... Source maps are valid, maybe emitted tokens without sourcemap entry can be the cause I guess?
Some sourcemap libraries have bugs related to it.
@kdy1 I updated the gist with the generated .js + .js.map files: https://gist.github.com/bobaaaaa/3649b3a7e6312793a257bf67c500128a
I'm not sure why does jest can't understand the sourcemap.
I need to dig into jest to know the source map library it uses...
Can you try the latest version of @swc/core? (v1.2.155)
Patches in #4007 are very likely to fix this issue.
@kdy1 I tested the new version. Unfortunately, its still not fixed :(
Tested with es2020 & es2021 + sourceMaps: true & sourceMaps: "inline". I updated all files in my gist build with v1.2.155:
https://gist.github.com/bobaaaaa/3649b3a7e6312793a257bf67c500128a
(Keep in mind, this is not a huge blocker/issue for us. Still thx for looking into this)
Can you try the latest version? (v1.2.156)
There was a bug fix for a module that only contains export * from './foo' and such module is super common, so I think it can be the cause of this.
Can confirm same issue with vitest + swc with all recommended configs and latest version (v1.2.156)
I changed the milestone, thanks!
I triggered publishing of the new version
I see you fixed this in v1.2.158, but the problem persists after I update, is it my configuration problem?
transform: {
'^.+\\.(t|j)sx?$': [
'@swc/jest',
{
jsc: {
target: 'es2021',
},
sourceMaps: true,
},
],
},
Edit: tested with
@nestjs7.6.15@swc/jest0.2.20@swc/core1.2.158jest26.6.3
In my case all decorator occurrences in code are marked as uncovered branches
Only way to get proper coverage report is to use tsc for test compilation
My reproduction repo for reference: https://github.com/wight554/blog-template/tree/swc-test
Coverage report is included in coverage folder (see controllers)
---------------------------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------------------------------|---------|----------|---------|---------|-------------------
All files | 99.61 | 67.21 | 100 | 99.61 |
server/auth | 100 | 100 | 100 | 100 |
AuthController.ts | 100 | 100 | 100 | 100 |
AuthService.ts | 100 | 100 | 100 | 100 |
server/auth/guards | 80 | 100 | 100 | 80 |
JwtAuthGuard.ts | 80 | 100 | 100 | 80 | 1
LocalAuthGuard.ts | 80 | 100 | 100 | 80 | 1
server/comment | 100 | 37.5 | 100 | 100 |
CommentController.ts | 100 | 37.5 | 100 | 100 | 19,25-26,33
CommentService.ts | 100 | 100 | 100 | 100 |
server/comment/dto | 100 | 100 | 100 | 100 |
CreateCommentDto.ts | 100 | 100 | 100 | 100 |
UpdateCommentDto.ts | 100 | 100 | 100 | 100 |
server/constants | 100 | 100 | 100 | 100 |
controllers.ts | 100 | 100 | 100 | 100 |
server/crypto | 100 | 100 | 100 | 100 |
CryptoService.ts | 100 | 100 | 100 | 100 |
server/decorators | 100 | 100 | 100 | 100 |
UserDecorator.ts | 100 | 100 | 100 | 100 |
server/enums | 100 | 100 | 100 | 100 |
MongoError.ts | 100 | 100 | 100 | 100 |
server/interceptors | 97.56 | 100 | 100 | 97.56 |
...ooseClassSerializerInterceptor.ts | 97.56 | 100 | 100 | 97.56 | 1
server/post | 100 | 41.17 | 100 | 100 |
PostController.ts | 100 | 41.17 | 100 | 100 | ...58-59,66,73-76
PostService.ts | 100 | 100 | 100 | 100 |
server/post/dto | 100 | 100 | 100 | 100 |
CreatePostDto.ts | 100 | 100 | 100 | 100 |
UpdatePostDto.ts | 100 | 100 | 100 | 100 |
server/user | 100 | 50 | 100 | 100 |
UserController.ts | 100 | 50 | 100 | 100 | 28,31,37-40
UserService.ts | 100 | 100 | 100 | 100 |
server/user/dto | 100 | 100 | 100 | 100 |
CreateUserDto.ts | 100 | 100 | 100 | 100 |
UpdateUserDto.ts | 100 | 100 | 100 | 100 |
src | 100 | 100 | 100 | 100 |
app.tsx | 100 | 100 | 100 | 100 |
logo.tsx | 100 | 100 | 100 | 100 |
---------------------------------------|---------|----------|---------|---------|-------------------
tsc reports 100% for all
yes, can confirm. This is still not fixed. But /* istanbul ignore next */ now works :)
still not fixed in @swc/core 1.2.159 if it was meant to be fixed
yes, can confirm. This is still not fixed. But
/* istanbul ignore next */now works :)
Do you find that while this works, it's not quite as well as babel-jest? For example I cannot ignore a whole file or large function blocks that have nested functions.
I'm able to ignore these files in collectCoverageFrom so not a blocker, but something I have observed.
I think it's related to recent changes of module passes.
I'm working on this but this seems like a quite tricky issue
I improved sourcemap a bit with #5569
Current status:
Still not sure how coverage tools count execution counts, though
Implementation-wise?
By rewriting the code so that this:
// line 1
const q = 42;
// line 2
const x = q + 5;
// line 3
const z = x * 2;becomes:
globalLinesCounter[1]++;
const q = 42;
globalLinesCounter[2]++
const x = q + 5;
globalLinesCounter[3]++;
const z = x * 2;ie, literally incrementing per-line counters in a global object
@kdy1 I tested the new version. Unfortunately, its still not fixed :(
Tested with
es2020&es2021+sourceMaps: true&sourceMaps: "inline". I updated all files in my gist build withv1.2.155: https://gist.github.com/bobaaaaa/3649b3a7e6312793a257bf67c500128a(Keep in mind, this is not a huge blocker/issue for us. Still thx for looking into this)
Hey @kdy1 my reported issue here is now fixed for me with "@swc/jest": "0.2.22"
@kdy1 Unfortunately I still get wrong (false-positive) coverage reports on 1.3.4 π’
nyc/istanbul
c8
.swcrc
{
"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true
},
"transform": {
"decoratorMetadata": true
}
},
"sourceMaps": true
}.tsconfig
{
"extends": "@tsconfig/node16",
"compilerOptions": {
"baseUrl": ".",
"resolveJsonModule": true,
"outDir": "build",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"paths": {
"@/*": ["src/*"],
"@modules/*": ["src/modules/*"],
"@test/*": ["test/*"]
}
}
}
@kdy1 Can I help you somehow with this? I mean, I probably won't fix it as I have no knowledge about how the process works, but maybe some kind of minimal reproduction repo would help? π€
Yes, it will definitely help. Ideally if I can invoke jest to get coverage for single file, I can test/ensure that jest reports 100% coverage
@kdy1 Here it is: https://github.com/sarneeh/swc-project-swc-issues-3854
When I was creating it I noticed that the false-positives go away when I turn off decoratorMetadata option (which is unfortunately required when using dependency injection frameworks like typedi). Hopefully that will help you somehow π
The clue about decoratorMetadata really helps! I already fixed codegen and it means some transform is dropping source map information, and now I know which pass is doing so. Thank you!
I've noticed that jest coverage will report code branches inside helper functions as being uncovered lines unless the externalHelpers:true options is specified.
jsc: {
externalHelpers: true,
},
Adding this option improved my test coverage significantly since the helper code is no longer included in the coverage scan. I'm not sure if anyone else has encountered this?
I opened a PR to add this option in @swc-node/jest but it probably needs further investigation/discussion swc-project/swc-node#673
I had the same issue. But when I changed sourceMaps: true to sourceMaps: "inline", jest started to collect coverage correctly.
My config is:
// jest.config.js
const config = {
transform: {
"^.+\\.(t|j)sx?$": ["@swc/jest", { ...swcConfig, sourceMaps: "inline" }],
},
}
Ofc, Adding the option to .swcrc works as well.
Hope this helps people.
Iβm actually using @swc-node/jest which already sets sourcemaps to inline by default.
I had the same issue. But when I changed
sourceMaps: truetosourceMaps: "inline", jest started to collect coverage correctly.My config is:
// jest.config.js const config = { transform: { "^.+\\.(t|j)sx?$": ["@swc/jest", { ...swcConfig, sourceMaps: "inline" }], }, }Ofc, Adding the option to
.swcrcworks as well.Hope this helps people.
I had same issue. Could you show swcConfig at here? @tkeiyama
Iβm actually using @swc-node/jest which already sets sourcemaps to inline by default.
This is true... :|
I had same issue. Could you show swcConfig at here?
Sure. @960590968
Here are my config files for testing. And I'm using React.
Basically, swcConfig is configs from .swcrc
jest.config.js
const { readFileSync } = require("fs");
const swcConfig = JSON.parse(readFileSync(`${__dirname}/.swcrc`, "utf-8"));
/** @type {import('jest').Config} */
const config = {
rootDir: ".",
collectCoverage: true,
collectCoverageFrom: [
"src/**/*.{ts,tsx}",
"!src/**/*.stories.tsx",
],
coverageThreshold: {
global: {
statements: 90,
branches: 90,
functions: 90,
lines: 90,
},
},
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
transform: {
"^.+\\.(t|j)sx?$": ["@swc/jest", { ...swcConfig }],
},
testEnvironment: "jsdom",
testMatch: ["<rootDir>/src/**/*.test.{ts,tsx}"],
};
module.exports = config;jest.setup.js
import "@testing-library/jest-dom";.swcrc
{
"sourceMaps": "inline",
"minify": true,
"jsc": {
"parser": {
"syntax": "typescript",
"jsx": true
},
"transform": {
"react": {
"runtime": "automatic"
}
}
}
}Dependencies
// package.json
{
...
"dependencies": {
"clsx": "^1.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@storybook/addon-essentials": "^6.5.13",
"@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-vite": "^0.2.5",
"@storybook/react": "^6.5.13",
"@swc/core": "^1.3.20",
"@swc/jest": "^0.2.23",
"@testing-library/dom": "^8.19.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.2.3",
"@types/node": "^18.11.9",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"autoprefixer": "^10.4.13",
"dprint": "^0.33.0",
"husky": "^8.0.0",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"lint-staged": "^13.0.4",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.19",
"storybook-addon-themes": "^6.1.0",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.3"
}
}I've tested with "sourceMaps": "inline" but didn't work.
.swcrc:
{
"sourceMaps": "inline",
"jsc": {
"target": "es2021",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"keepClassNames": true,
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
}
}
}
package.json:
"devDependencies": {
...
"@swc/core": "^1.3.20",
"@swc/jest": "^0.2.23"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": ".",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "@swc/jest"
},
"collectCoverageFrom": [
"src/**/*.{ts,js}"
],
"moduleNameMapper": {
"^@app/(.*)$": "<rootDir>/src/$1",
"^@test/(.*)$": "<rootDir>/test/$1"
},
"coverageDirectory": "./coverage",
"coverageReporters": [
"json-summary",
"text",
"lcov"
],
"coverageThreshold": {
"global": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 100
}
},
"testEnvironment": "node"
},
"engines": {
"node": "^16.16.0"
}
}
same as @klutzer here, same jest configuration.
Might be related to decorators as previously
btw, not sure its 100% related, but in regards to ignore comments, this is the generated code I see:
function myFn(type) {
- /* istanbul ignore next */
- cov_shygh21on().f[5]++;
- cov_shygh21on().s[14]++;
- return `${type}Tag`;
- }Useing coverageProvider: 'v8' will probably solve the problem.
Solution funded at stackoverflow https://stackoverflow.com/a/74851858/8770040
@Krzywy14 that completely drops the coverage percentage - did you have any success with it?
My specific problem seems to have been resolved by swc-project/swc-node#673, for what it's worth.
@Krzywy14 that completely drops the coverage percentage - did you have any success with it?
danr-za
IMO coverage percentage is better than when i used babel.
I check that on few files. With babel, even when all function/lines has been coverage, still not showing 100% coverage. When change to "v8" looks like coverage have really 100%.
"coverageProvider": "v8" used with:
"@swc-node/jest": "^1.5.5",
"@swc/core": "^1.3.23",And I forgot. In transform remove ts from regex
"transform": {
"\\.(tsx|jsx|js)$": [
"@swc-node/jest",
{
"dynamicImport": true
}
]
},well, exactly the opposite on my end here - I had 100% with babel, once I switched to swc I had around 99%, but with coverageProvider: v8 I even have around 86% π€·
Did someone try target: es2022. without decorators, and with externalHelpers: true?
swc has an enormous amount of transforms, and to fix this, I have to narrow down the pass which is producing the wrong source map.
@kdy1 we use decorators a lot in our project, so its a must in our case
Since the project does not use decorators, setting decorators false and externalHelpers true fixed the coverage issues
I tried:
externalHelpers: truedecorators: falsetarget: "es2021"sourceMaps: true- in different combinations.
Unfortunately, it does not help to restore the coverage and even also makes it worse.
Same with ts-node and mocha (((
For my NestJS project, I also noticed that test coverage dropped quite a bit when switching from ts-jest to @swc/jest.
Digging into it, it seems to be related to how the Typescript metadata for constructors are transpiled.
Take this constructor:
constructor(
@Inject(AppService)
private readonly appService: AppService,
) {}
The output of swc for the design:paramtypes metadata for the constructor is:
_ts_metadata("design:paramtypes", [
typeof _appservice.AppService === "undefined" ? Object : _appservice.AppService
])
Notice the typeof ternary. This gets flagged as an "uncovered branch" by istanbul, the
coverage calculator used by jest.
Compare this to how ts-jest outputs this:
__metadata("design:paramtypes", [app_service_1.AppService])
No ternary, so no impact to code coverage.
I created a workaround that tells istanbul to ignore these lines that incorrectly impact code coverage. It inserts an /* istanbul ignore next */ comment in the necessary places, resulting in transpiled code like this:
_ts_metadata("design:paramtypes", [
/* istanbul ignore next */typeof _appservice.AppService === "undefined" ? Object : _appservice.AppService
])
I setup this repo with instructions to reproduce: https://github.com/rogisolorzano/nest-swc-coverage
The workaround I'm using is in create-swc-transformer.js
Curious if there are any thoughts on this and potential solutions that don't involve having to insert /* istanbul ignore next */ into the transpiled output by swc?
I saw mention of using @swc-node/jest vs @swc/jest what are the differences?
I'm using NestJS as well and this seems to be related to decorators, so removing the legacyDecorator and/or decoratorMetadata is not an approach I can test.
This seems to be very related to an issue ts-jest was/is having here, maybe there's some insight to be taken from there? If they did resolve it I think it was via a similar method as rogisolorzano provided which is adding istanbul ignore comments.
I think I've tried all configuration permutations listed here so far without any luck, as well as installing @swc-node/jest & @swc/helpers and trying it as the jest transformer. One thing to note to keep you sane is that you may need to run npx jest --clearCache between configuration changes it seems before running your next jest coverage run (I noticed removing the transform decorators in my swcrc config broke tests, and still showed they were broken after reverting everything ?!).
Potentially Related Issues on Other Projects
Ultimately it sounds like source mapping can help some scenarios, changing the target in other scenarios, but specific decorators don't seem to care about either of those in some scenarios like Angular and NestJS commonly it seems.
I've tested here with vitest+swc in a Nestjs project and the issue still happens. I can't apply a transformer as @rogisolorzano suggested (I guess transformers are just for Jest).
There's also an issue in istanbul related to the same problem: istanbuljs/istanbuljs#70
I took some time to checkout the related "fix for decorator coverage" that I linked above. It does seem they took the same approach as @rogisolorzano π
I'm not sure if this project has a way to implement a "pre-processor" but maybe that's something @rogisolorzano could open a PR for is this is how folks seem to be getting around this. Otherwise it sounds like there needs to be a major rework on the system to implement so that it doesn't require a ternary (or is this an underlying transpilation implementation issue of these frameworks with how the decorator is written?).
PS: Thanks @rogisolorzano for the minimal reproduction and digging deep into this.
@klutzer are you not using Jest? I believe that's the scope of this issue, but I believe the coverage issue is not directly related to jest but the coverage tool [istanbul].
I'm still getting wrong coverage results when using @swc/jest...
i'm using vitest instead of jest, but i'm dealing with the same coverage issues outlined above in my nestjs project. i ended up creating a custom plugin that adds /* c8 ignore next */ comments to constructor parameters in my source code. adding comments to the transformed code didn't work for me (this may or may not have something to do with using v8 as my coverage provider, but i'm not sure)
my vitest.config.ts:
/**
* @file Vitest Configuration
* @module config/vitest
* @see https://vitest.dev/config/
*/
import { DECORATOR_REGEX } from '@flex-development/decorator-regex'
import pathe from '@flex-development/pathe'
import { cast, split, type Nullable } from '@flex-development/tutils'
import swc from '@swc/core'
import ci from 'is-ci'
import tsconfigpaths from 'vite-tsconfig-paths'
import GithubActionsReporter from 'vitest-github-actions-reporter'
import {
defineConfig,
type UserConfig,
type UserConfigExport
} from 'vitest/config'
import { BaseSequencer, type WorkspaceSpec } from 'vitest/node'
import tsconfig from './tsconfig.json' assert { type: 'json' }
/**
* Vitest configuration export.
*
* @const {UserConfigExport} config
*/
const config: UserConfigExport = defineConfig((): UserConfig => {
/**
* [`lint-staged`][1] check.
*
* [1]: https://github.com/okonet/lint-staged
*
* @const {boolean} LINT_STAGED
*/
const LINT_STAGED: boolean = !!Number.parseInt(process.env.LINT_STAGED ?? '0')
return {
define: {},
plugins: [
{
enforce: 'pre',
name: 'decorators',
/**
* Transforms source `code` containing decorators.
*
* @see https://github.com/swc-project/swc/issues/3854
*
* @param {string} code - Source code
* @param {string} id - Module id of source code
* @return {Promise<Nullable<{ code: string }>>} Transform result
*/
async transform(
code: string,
id: string
): Promise<Nullable<{ code: string }>> {
// do nothing if source code does not contain decorators
DECORATOR_REGEX.lastIndex = 0
if (!DECORATOR_REGEX.test(code)) return null
/**
* Regular expression used to match constructor parameters.
*
* @see https://regex101.com/r/kTq0JK
*
* @const {RegExp} CONSTRUCTOR_PARAMS_REGEX
*/
const CONSTRUCTOR_PARAMS_REGEX: RegExp =
/(?<=constructor\(\s*)([^\n)].+?)(?=\n? *?\) ?{)/gs
// add "/* c8 ignore next */" before constructor parameters
for (const [match] of code.matchAll(CONSTRUCTOR_PARAMS_REGEX)) {
code = code.replace(match, (params: string): string => {
return split(params, '\n').reduce((acc, param) => {
return acc.replace(
param,
param.replace(/(\S)/, '/* c8 ignore next */ $1')
)
}, params)
})
}
return {
code: (
await swc.transform(code, {
configFile: false,
filename: id,
inlineSourcesContent: true,
jsc: {
keepClassNames: true,
parser: {
decorators: true,
dynamicImport: true,
syntax: 'typescript',
tsx: pathe.extname(id) === '.tsx'
},
target: cast<swc.JscTarget>(tsconfig.compilerOptions.target),
transform: {
decoratorMetadata:
tsconfig.compilerOptions.emitDecoratorMetadata,
legacyDecorator: true,
useDefineForClassFields:
tsconfig.compilerOptions.useDefineForClassFields
}
},
sourceMaps: 'inline',
swcrc: false
})
).code
}
}
},
tsconfigpaths({ projects: [pathe.resolve('tsconfig.json')] })
],
test: {
allowOnly: !ci,
benchmark: {},
chaiConfig: {
includeStack: true,
showDiff: true,
truncateThreshold: 0
},
clearMocks: true,
coverage: {
all: !LINT_STAGED,
clean: true,
cleanOnRerun: true,
exclude: [
'**/__mocks__/**',
'**/__tests__/**',
'**/index.ts',
'src/interfaces/',
'src/metadata/',
'src/types/'
],
extension: ['.ts'],
include: ['src'],
provider: 'v8',
reporter: [...(ci ? [] : (['html'] as const)), 'lcovonly', 'text'],
reportsDirectory: './coverage',
skipFull: false
},
environment: 'node',
environmentOptions: {},
globalSetup: [],
globals: true,
hookTimeout: 10 * 1000,
include: [
`**/__tests__/*.${LINT_STAGED ? '{spec,spec-d}' : 'spec'}.{ts,tsx}`
],
isolate: true,
mockReset: true,
outputFile: { json: './__tests__/report.json' },
passWithNoTests: true,
reporters: [
'json',
'verbose',
ci ? new GithubActionsReporter() : './__tests__/reporters/notifier.ts'
],
/**
* Stores snapshots next to `file`'s directory.
*
* @param {string} file - Path to test file
* @param {string} extension - Snapshot extension
* @return {string} Custom snapshot path
*/
resolveSnapshotPath(file: string, extension: string): string {
return pathe.resolve(
pathe.resolve(pathe.dirname(pathe.dirname(file)), '__snapshots__'),
pathe.basename(file).replace(/\.spec.tsx?/, '') + extension
)
},
restoreMocks: true,
root: process.cwd(),
sequence: {
sequencer: class Sequencer extends BaseSequencer {
/**
* Determines test file execution order.
*
* @public
* @override
* @async
*
* @param {WorkspaceSpec[]} specs - Workspace spec objects
* @return {Promise<WorkspaceSpec[]>} `files` sorted
*/
public override async sort(
specs: WorkspaceSpec[]
): Promise<WorkspaceSpec[]> {
return (await super.sort(specs)).sort(([, file1], [, file2]) => {
return file1.localeCompare(file2)
})
}
}
},
setupFiles: ['./__tests__/setup/index.ts'],
silent: false,
singleThread: true,
slowTestThreshold: 3000,
snapshotFormat: {
callToJSON: true,
min: false,
printBasicPrototype: false,
printFunctionName: true
},
testTimeout: 10 * 1000,
typecheck: {
allowJs: false,
checker: 'tsc',
ignoreSourceErrors: false,
include: ['**/__tests__/*.spec-d.ts'],
tsconfig: pathe.resolve('tsconfig.typecheck.json')
},
unstubEnvs: true,
unstubGlobals: true
}
}
})
export default configOur issue was that components that were using emotion were being tested but not captured in the coverage reporting. Using sourceMaps: 'inline' was all it took to get coverage back up to where it was.
@kdy1, would @rogisolorzano's suggestion work for a definitive solution in @swc/jest? Personally, this bug prevents me from migrating from ts-jest to @swc/jest in some projects due to a false drop in code coverage, which affects code quality analyzers like SonarQube. I believe that other people may be blocked from migrating for the same reason.
I'll give this another try tomorrow. Thanks!
fwiw, i created a simple plugin to fix this in my projects, as @rogisolorzano did not work for me (probably because I am using v8 for coverage like @unicornware). it works for the specific problem @rogisolorzano described, but can't say it's a complete solution. also my first foray into Rust so constructive feedback is welcome.
https://github.com/bdentino/swc-plugin-fix-decorator-metadata-cov
Okay, about the ternary case, tsc ignores isolatedModules: true in tsconfig.json and that's why it can generate app_service_1.AppService instead of a ternary. Under isolatedModules: true, the behavior of SWC is correct. I'll try adjusting source map to patch coverage.
It seems like the default coverage provider is not good at reading source maps emitted by SWC.
+"coverageProvider": "v8"
- Expr::Cond(CondExpr {
- span: DUMMY_SP,
- test: check_object_existed(Box::new(member_expr.clone())),
- cons: Box::new(quote_ident!("Object").into()),
- alt: Box::new(member_expr),
- })
+ member_expr
Oh. I need to generate less source map. I fixed it for "coverageProvider": "v8".
the above fixed the coverage issue, but unfortunately with "coverageProvider": "v8" my tests crash half way through with heap out of memory error. also "v8" seems lot (maybe 2x) slower than with default coverage provider
I was on "@swc/core": "1.6.13",
The solution of @rogisolorzano solved it for us and was plug and play.
The nice part is that their implementation doesnt forget to forward config to underlying module (useful if you customize it). Ex:
transform: {
'^.+\\.ts$': [
'../configs/create-swc-transformer.js',
{ ...config }, // <-- this is the content of our swcrc file
],
Our tools and configs: @swc/jest 0.2.36, @swc/core 1.6.13, jest 29.7.0, targetting commonjs.
Note that our swc config doesn't use "sourceMaps: true" (false coverage errors not related to this discussion).
The issue still persists. I had to go back to ts-jest as it was having a huge impact on my coverage. Here we use NestJs too.
"@swc/cli": "^0.5.2",
"@swc/core": "^1.9.3",
"@swc/jest": "^0.2.37",
update:
I got the correct coverage by migrating to the node-specific swc.
"@swc/cli": "^0.6.0",
"@swc/core": "^1.11.7",
"@swc/jest": "^0.2.37",
->
"@swc-node/jest": "^1.8.12",
"@swc-node/register": "^1.10.9",
"@swc/core": "^1.11.7",
"@swc/helpers": "^0.5.15",
I didn't get any different behavior between @swc-node/jest and ts-jest.
I'm encountering the same issue in NestJS.
Currently, I'm using @swc/jest only for running tests locally during development, while still relying on ts-jest for test coverage.
jest.config.json
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": "../..",
"testRegex": "test/unit/.*\\.*spec\\.ts$",
"testEnvironment": "node",
"transform": {
"^.+\\.(t|j)s?$": ["@swc/jest"]
},
"clearMocks": true,
"resetMocks": true,
"restoreMocks": true,
"verbose": true,
"testTimeout": 10000
}
jest.coverage.json
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": "../..",
"testRegex": "test/unit/.*\\.*spec\\.ts$",
"testEnvironment": "node",
"transform": {
"^.+\\.(t|j)s?$": ["ts-jest"]
},
"clearMocks": true,
"resetMocks": true,
"restoreMocks": true,
"verbose": true,
"testTimeout": 10000,
"maxWorkers": "50%",
"collectCoverage": true,
"collectCoverageFrom": ["src/**/*.(t|j)s", "!dist/**/*.(t|j)s"],
"coveragePathIgnorePatterns": ["^.+\\.(module|dto).ts$", "^.+\\main.ts$"],
"coverageDirectory": "coverage",
"coverageThreshold": {
"global": {
"branches": 90,
"functions": 90,
"lines": 90,
"statements": 90
}
},
"coverageReporters": ["text", "json", "cobertura"]
}
Estou enfrentando o mesmo problema no NestJS.
Atualmente, estou usando
@swc/jestapenas para executar testes localmente durante o desenvolvimento, mas ainda contando comts-jesta cobertura de testes.brincadeira.config.json
{ "moduleFileExtensions": ["js", "json", "ts"], "rootDir": "../..", "testRegex": "test/unit/.*\\.*spec\\.ts$", "testEnvironment": "node", "transform": { "^.+\\.(t|j)s?$": ["@swc/jest"] }, "clearMocks": true, "resetMocks": true, "restoreMocks": true, "verbose": true, "testTimeout": 10000 }brincadeira.cobertura.json
{ "moduleFileExtensions": ["js", "json", "ts"], "rootDir": "../..", "testRegex": "test/unit/.*\\.*spec\\.ts$", "testEnvironment": "node", "transform": { "^.+\\.(t|j)s?$": ["ts-jest"] }, "clearMocks": true, "resetMocks": true, "restoreMocks": true, "verbose": true, "testTimeout": 10000, "maxWorkers": "50%", "collectCoverage": true, "collectCoverageFrom": ["src/**/*.(t|j)s", "!dist/**/*.(t|j)s"], "coveragePathIgnorePatterns": ["^.+\\.(module|dto).ts$", "^.+\\main.ts$"], "coverageDirectory": "coverage", "coverageThreshold": { "global": { "branches": 90, "functions": 90, "lines": 90, "statements": 90 } }, "coverageReporters": ["text", "json", "cobertura"] }
test what I mentioned above
There is an issue with coverageProvider: 'v8' after node 20.10.0:
nodejs/node#51251
Switching to coverageProvider: 'babel' solved the problem for me.
I have also been running into issues with the accuracy of Jest coverage results, particularly the maximum potential coverage changing depending on what number of tests being run. I found the issue was related to module.type in @swc/jest was being set to "commonjs" for the process method and "es6" for the asyncProcess method. Hacking the @swc/jest module to set both instances to "commonjs" made the values reliably consistent.
I have posted about this issue in more detail on the SWC pkgs project









