Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect coverage when mocked apex function and copyright placed before its import #320

Open
framocma opened this issue Jun 28, 2023 · 9 comments
Labels
bug Something isn't working

Comments

@framocma
Copy link

framocma commented Jun 28, 2023

Description

First of all, my apologies if the issue description lacks enough details or it is not thorough enough as it is the first issue I report via Github. After upgrading some dependencies in the company's repo it was noticed a degradation in the function's coverage due to a new function added to the calculation that turns out to be coming from the Apex function being imported. It occurs when this function is mocked in the test class and the JS class under testing has a copyright before the import (which is mandatory in our company's policy).

Steps to Reproduce

/**
 * @copyright 2023 FranM.
 */

import { getCreateComponent } from 'jest-utils';
import dummySayHi from '@salesforce/apex/CoverageIssueWithCopyrightAndApex.dummySayHi';
import coverageWithApexModal from 'c/coverageWithApexModal';

jest.mock('@salesforce/apex/CoverageIssueWithCopyrightAndApex.dummySayHi', () => ({ default: jest.fn() }), { virtual: true });

const createComponent = getCreateComponent(coverageWithApexModal, 'c-coverage-with-apex-modal');

describe('coverageWithApexModal component test', () => {
	describe('apply clicked', () => {
		it('controller is called', () => {
			//Given
			const { component } = createComponent();
			const applyBtn = component.shadowRoot.querySelector('.apply-btn');

			//When
			applyBtn.click();

			//Then
			expect(dummySayHi).toHaveBeenCalledTimes(1);
		});
	});
});
<!-- HTML for component under test -->
<template>
	<div class="slds-is-relative">
		<lightning-button label="apply" class="apply-btn slds-m-left_x-small" onclick={handleSayHi}></lightning-button>
	</div>
</template>
// JS for component under test
/**
 * @copyright 2023 FranM.
 */

import { LightningElement } from 'lwc';
import dummySayHi from '@salesforce/apex/CoverageIssueWithCopyrightAndApex.dummySayHi';

const NAME = 'Peter';

export default class CoverageWithApex extends LightningElement {
	handleSayHi() {
		const result = dummySayHi(NAME);
		console.log(result);
	}
}
public with sharing class CoverageIssueWithCopyrightAndApex {
	@AuraEnabled(cacheable=true)
	public static String dummySayHi(String name) {
		return 'Hi, ' + name;
	}
}
const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config');
const { setupFilesAfterEnv } = jestConfig;

module.exports = {
	...jestConfig,
	setupFilesAfterEnv: [...setupFilesAfterEnv, './jest.setup.js'],
	clearMocks: true,
	roots: ['<rootDir>/force-app/main'],
	transformIgnorePatterns: ['/node_modules/(?!(.*@salesforce/sfdx-lwc-jest/src/lightning-stubs)/)'],
	moduleDirectories: ['<rootDir>/node_modules', '<rootDir>/force-app/test/jest-utils'],
	moduleNameMapper: {
		'^lightning/actions$': '<rootDir>/force-app/main/common/test/jest-mocks/actions',
	},
	collectCoverageFrom: ['force-app/main/**/lwc/**/*.js', '!**/__tests__/**', '!**/__mocks__/**'],
	coverageThreshold: {
		global: {
			statements: 98.84,
			branches: 96.39,
			functions: 99.47,
			lines: 98.9,
		},
	},
	reporters: [
		'default',
		[
			'jest-junit',
			{
				suiteName: 'LWC Tests',
				outputDirectory: 'test-results/jest',
				outputName: './jest-result.xml',
				classNameTemplate: '{classname}',
				titleTemplate: '{title}',
				ancestorSeparator: ' → ',
			},
		],
	],
};
# Command to repro
npm run lwc-jest --coverage

Expected Results

Function coverage to be 100%

Actual Results

Function coverage is 66.66%

Version

  • @salesforce/sfdx-lwc-jest: 1.2.1
  • Node: 18.15.0

Additional context/Screenshots

Screenshot 2023-06-28 at 14 20 56 Screenshot 2023-06-28 at 14 24 11
@nolanlawson nolanlawson added the bug Something isn't working label Jul 6, 2023
@nolanlawson
Copy link
Contributor

W-13720527

@BatemanVO
Copy link

BatemanVO commented Jul 7, 2023

Our team was on v1.3.0 and just updated to v1.4.1 today, but began encountering the same issue sporadically with any comments whatsoever before import statements. It doesn't occur on all files / tests, but after updating to the latest version, a significant number of them started reporting that line 1 was uncovered, which was just the opening to a comment block.

Moving the comments beneath all import statements resolves the issue.

Here is a sample file that is affected:

// Used by the VNextComponent Visualforce Page to handle logic needed after the VNext Canvas App has been rendered
import { LightningElement, api, track } from 'lwc';
import { showError } from 'c/lwcUtilities';
import stampConversationId from '@salesforce/apex/VNextComponentController.stampConversationId';
import leaveConversation from '@salesforce/apex/VNextComponentController.leaveConversation';

export default class Vnext extends LightningElement {
    // @api properties passed from the expected parent VF Lightning Out page
    @api publish;
    @api subscribe;
    @api recordId;
    @api partnerId;
    @track isAttemptingToLeaveConversation = false;
    @track hasLeftConversation = false;

    connectedCallback() {
        this.subscribe([{
            name: 'WorkspaceEvent',
            onData: event => {
                if (!event.payload) { return; }

                if (event.payload.messageCategory === 'ConversationCreated' && event.payload.conversationId) {
                    stampConversationId({
                        recordId: this.recordId,
                        conversationId: event.payload.conversationId
                    })
                        .catch(this.showError.bind(this));
                }
            }
        }]);
    }
    handleLeaveConversation() {
        this.isAttemptingToLeaveConversation = true;
        leaveConversation({
            partnerId: this.partnerId,
            recordId: this.recordId
        }).then(() => {
            this.dispatchEvent(new CustomEvent('toastemulation', {
                detail: {
                    title: 'Conversation Ended',
                    variant: 'success',
                    type: 'success',
                    message: 'Conversation successfully left'
                }
            }));
            this.hasLeftConversation = true;
        }).catch(this.showError.bind(this))
            .finally(() => this.isAttemptingToLeaveConversation = false);
    }
    showError(error) {
        this.dispatchEvent(new CustomEvent('toastemulation', {
            detail: {
                variant: 'error',
                type: 'error',
                mode: 'sticky',
                message: showError.call(this, error),
                title: 'Error'
            }
        }));
    }
}

Moving the comment from line 1 to line 5, beneath the import statements, fixes the coverage issue.

Here is a sample file that is not affected:

// Reusable button used as part of the caseConsoleButtonPanel component. For buttons that don't need complex logic and
// should just always update a record, this component can be used to handle those updates.
import { LightningElement, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { updateRecord } from 'lightning/uiRecordApi';
import { showError } from 'c/lwcUtilities';

export default class RecordUpdateButton extends LightningElement {
    @api recordId;
    @api label;
    @api variant;
    @api recordInput;

    /**
     * Handler for when the button is clicked. Calls the Apex method associated with this LWC and, if no errors are thrown,
     * shows a success Toast and fires a refresh event.
     */
    handleClick() {
        this.dispatchEvent(new CustomEvent('showspinner'));
        updateRecord(this.recordInput)
            .then(() => {
                this.dispatchEvent(new CustomEvent('refresh'));
                this.dispatchEvent(new ShowToastEvent({
                    title: 'Success',
                    variant: 'success',
                    message: 'Record updated'
                }));
            })
            .catch(showError.bind(this))
            .finally(() => this.dispatchEvent(new CustomEvent('hidespinner')));
    }
}

So as noted in the original issue, it appears to be related to files that import Apex functions.

sfdx-lwc-jest v1.4.1
Node v18.9.0
OS Windows 10

@nolanlawson
Copy link
Contributor

I wonder if this would be fixed with a Jest update. Currently sfdx-lwc-jest is on Jest v27 (latest is v29).

@nolanlawson
Copy link
Contributor

Could someone who is having this issue please provide a GitHub repo that we can use to test? From the bug description, it's hard to tell where to put which files (and with which filenames) in order to reproduce the issue. Thank you!

@BatemanVO
Copy link

BatemanVO commented Aug 10, 2023

@nolanlawson Here is a link to a barebones repo where I can reproduce the issue.

Here is the coverage reported with the comment left at the top of the file:
with-comment-at-top
uncovered-line

Here is the coverage reported if I move the comment two lines down, beneath the import statements:
with-comment-below-imports

EDIT: As an additional note, if I uninstall 1.4.1 and reinstall to the latest version, 2.2.0, I still get the same issue.

@nolanlawson
Copy link
Contributor

Thanks a lot @BatemanVO! With your repo (using npm install && npx lwc-jest --coverage, then opening coverage/lcov-report/index.html), I am able to repro:

Screenshot 2023-08-10 at 11 05 41 AM

Unfortunately, it looks like upgrading to Jest v29 does not resolve the issue:

Screenshot 2023-08-10 at 11 19 29 AM

This will require some more digging to figure out what's going on.

@zoranmilovanovic
Copy link

Same issue is present in our sfdx project. I created also similar git repository where it can be tested https://github.com/zoranmilovanovic/jest-coverage-issue

Is there any update on this issue?

@BatemanVO
Copy link

Just updated a couple of our repos to sfdx-lwc-jest 4.0.1 today and still encountering the same issue. Some files that were not affected with version 3.0.1 were affected after updating to 4.0.1, but I didn't see anything significant between the ones affected in 4.0.1, but not 3.0.1, compared to the ones that were affected in 3.0.1 - it still seems to be tied to importing apex methods.

@BatemanVO
Copy link

BatemanVO commented Jun 17, 2024

Still seeing the issue with sfdx-lwc-jest 5.0.0 as of today, though have noticed a slight change in behavior.

The coverage tool still reports comments before import statements as uncovered, but instead of always reporting line 1 as uncovered, the line reported is the last comment using //. For example:

// Some comment
/* Start of a comment block
Some comments
End of a comment block */

Will report line 2 as uncovered, whereas:

// A comment
// Another comment
// Yet another comment

will report line 3 as uncovered.

EDIT, 10/16/2024: Still occurring as of 5.1.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants