cenfun/monocart-coverage-reports

[Question] untested jsx file shows all 0

Closed this issue · 17 comments

I found in the Lcov html and text output the untested jsx react component file will show 0 lines, 0 statements, 0 branches, 0 functions

But in v8 html file it will show the total bytes and uncovered bytes, and all other values are 0 except lines

I wonder if we need to manually call onEntry() to manually compile it but I don't know how to do it

No, I don't think it's necessary.

You may try collectCoverageFrom option if you are using Jest, I thought it includes all untested files

An array of glob patterns indicating a set of files for which coverage information should be collected. If a file matches the specified glob pattern, coverage information will be collected for it even if no tests exist for this file and it's never required in the test suite.

see https://jestjs.io/docs/configuration#collectcoveragefrom-array

I did, the thing is the file extension is .js for jsx files. When I run the test using jest's own report, the untested files has no issue.

And I found the coverage result between your report vs jest report are quite different, could you explain what the reasons could be and how to resolve the differences?

Can you provide an example shows where the differences?
And which one do you think better as you expected?

Let me find a better examples from the GitHub, so far I feel jest report more consistent, but I don't understand its v8 statements is 6x than Istanbul

I forked the nex.js to here: https://github.com/stevez/next.js/tree/monocart/examples/with-jest

  1. clone the repo
  2. checkout branch monocart

npm run test -> get jest native v8 coverage
npm run test:monocart -> get the v8 coverage from monocart

here is the differences
istanbul coverage
Screenshot 2024-09-09 at 10 00 53 PM

jest v8
Screenshot 2024-09-09 at 10 01 46 PM

monocart v8
Screenshot 2024-09-09 at 10 08 11 PM

you will see the differences

you can tell from monocart v8, _app.tsx and layout.tsx, the uncovered line# are empty
while in jest v8, they have line numbers.
I think this will cause the difference of the coverage result

There is a sourcemap issue in pages/home/index.tsx, Im tring to fix it.

That will make sense, since I found the same issue in your Istanbul report

The sourcemap issue should be fixed. please try monocart-coverage-reports@2.10.4
Back to talk about the differences, I personally think Monocart should be better, especially for JSX.
And there is a branch coverage comparison here:
https://github.com/stereobooster/test-coverage-calculation
If there are any new issues, please feel free to provide the steps to reproduce them.

Thank you, unfortunately when I upgraded the latest monocart-coverage-report, I got the same result for the same test repo: _app.tsx and layout.tsx are still all 0

Monocart simply follows the strategy of nyc (Istanbul official), which is just set the coverage for untested files to 0.
But for Jest, it will recompile untested files and generate coverage like for lines.
If we need to do the same thing for the untested files and the onEntry() hook is used for this purpose.

Actually, Next.js is a full-stack framework which includes both front-end and back-end code, we should also consider using E2E test like Playwright, then the _app.tsx and layout.tsx could be easy covered.
see example repo: https://github.com/cenfun/nextjs-with-playwright

I believe nyc will also calculate untested file lines, otherwise the coverage data will not be accurate.
Could you provide the examples how to use onEntry() to generate the untested file values?

There is a onEntry example but for TS:
https://github.com/cenfun/monocart-coverage-reports?#unparsable-source
I don't know how to compile JSX.
The untested files could be any formats, we can not calculate the coverage if it's not the native JS.

I wrote a example for it, it compile untested JSX with @swc/core

const swc = require("@swc/core");

...
onEntry: async (entry) => {
        // transform untested files
        const filename = path.basename(entry.url);
        if (['_app.tsx', 'layout.tsx'].includes(filename)) {
            const { code, map } = await swc.transform(entry.source, {
                // Some options cannot be specified in .swcrc
                filename,
                sourceMaps: true,
                // Input files are treated as module by default.
                isModule: true,
            
                // All options below can be configured via .swcrc
                jsc: {
                    parser: {
                        syntax: "typescript",
                        jsx: true
                    },
                    transform: {}
                }
            });
            entry.source = code;
            entry.sourceMap = JSON.parse(map);
        }
    },
...

I found a problem is we don't know which one is untested file on calling onEntry

We are closer, what if we use swc/babel compile all files first and then load them using onEntry()?

I figure out how to compile only for untested files, working in progress.

Please try monocart-coverage-reports@2.10.5 with new option all.transformer which will transform untested files

// mcr.config.unit.js
const path = require("path");
const swc = require("@swc/core");

module.exports = {
    // logging: 'debug',
    sourceMap: true,

    name: 'Unit Coverage Report',
    outputDir: './coverage/unit-monocart',

    reports: [
        'lcov',
        'json',
        'text',
        'text-summary',
        'v8',
        'raw'
    ],

    sourcePath: (filePath, info)=> {
        if (!filePath.includes('/') && info.distFile) {
            return `${path.dirname(info.distFile)}/${filePath}`;
        }
        return filePath;
    },

    all: {
        dir: ['./app', './pages'],
        filter: {
            // exclude files
            '**/__tests__/**': false,
            '**/*.test.*': false,
            '**/*.ts': true,
            '**/*.tsx': true
        },
        transformer: async (entry) => {

            // transform untested files
            const { code, map } = await swc.transform(entry.source, {
                // Some options cannot be specified in .swcrc
                filename: path.basename(entry.url),
                sourceMaps: true,
                // Input files are treated as module by default.
                isModule: true,
            
                // All options below can be configured via .swcrc
                jsc: {
                    parser: {
                        syntax: "typescript",
                        jsx: true
                    },
                    transform: {}
                }
            });
            entry.source = code;
            entry.sourceMap = JSON.parse(map);

        }
    },


    onEnd: () => {
        console.log('onEnd unit test');
    }
};