cenfun / monocart-coverage-reports

A code coverage tool to generate native V8 reports or Istanbul reports.
MIT License
69 stars 6 forks source link

[Question] untested jsx file shows all 0 #72

Closed stevez closed 1 month ago

stevez commented 1 month ago

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

cenfun commented 1 month ago

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

stevez commented 1 month ago

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?

cenfun commented 1 month ago

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

stevez commented 1 month ago

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

stevez commented 1 month ago

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

stevez commented 1 month ago

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

cenfun commented 1 month ago

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

stevez commented 1 month ago

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

cenfun commented 1 month ago

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.

stevez commented 1 month ago

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

cenfun commented 1 month ago

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

stevez commented 1 month ago

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?

cenfun commented 1 month ago

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.

cenfun commented 1 month ago

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

stevez commented 1 month ago

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

cenfun commented 1 month ago

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

cenfun commented 1 month ago

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');
    }
};