salesforce / sfdx-lwc-jest

Run Jest against LWC components in SFDX workspace environment
MIT License
165 stars 82 forks source link

Tests stopped to work with v0.10.x and v0.11.x #202

Closed AllanOricil closed 1 year ago

AllanOricil commented 3 years ago

Description

Steps to Reproduce

We have too many components to paste here and I don't think I can open it publicly. Can we have a chat so I can show it to you? Just for an example this is one test that does not work with the latest package version:

it('should render Service Contract Welcome descriptive button on load', () => {
     const scsButton = element.shadowRoot.querySelectorAll('c-dce-descriptive-button[data-id="dbtn_scl"]');
     expect(scsButton.length).toBe(1);
});

Expected Results

Jest tests should work with v0.10.x to v0.11.x as they work with v0.9.2

Actual Results

Jest tests stopped working after upgrading @salesforce/sfdx-lwc-jest . It does not work with any 0.10.x or 0.11.x but it works with 0.9.2

Version

Possible Solution

No idea

pmdartus commented 3 years ago

Between version 0.9.2 and 0.11.0 we did bump jest from 25 to 26. Between the 2 versions, jsdom was bump to version 16.

We haven't seen any breakage internally at Salesforce with those 2 upgrades. @AllanOricil could you try isolating a test failing with the upgrade? This would greatly help the investigation.

AllanOricil commented 3 years ago

With this, you can see the problem. Using 0.9.2 everything is passing. When changing to anything above it, everything is failing.

c-dce-my-contracts-card component template

<template>
    <lightning-layout multiple-rows vertical-align="stretch">
        <lightning-layout-item size="12" small-device-size="12" medium-device-size="12" large-device-size="12"
              flexibility="auto" padding="around-large">
            <p data-id="p_lcws" class="slds-text-body_regular slds-text-color_default slds-var-p-bottom_medium">{label.LinkContAndWarrSumm}</p>
            <c-dce-group-descriptive-button>
                <c-dce-descriptive-button title={label.LinkSuppAgr} description={label.LinkSuppAgrDesc} url={urlLinkSupportAgreements} target="_self" icon="icon-link" data-id="dbtn_lsa" analytics-key="MyContracts:LinkSupportAgreement"></c-dce-descriptive-button>
                <c-dce-descriptive-button title={label.LinkPackSupp} description={label.LinkPackSuppDesc} url={urlLinkPackagedSupport} target="_self" icon="icon-link" data-id="dbtn_lps" analytics-key="MyContracts:LinkPackagedSupport"></c-dce-descriptive-button>
                <c-dce-descriptive-button title={label.LinkWarr} description={label.LinkWarrDesc} url={urlLinkWarranties} target="_self" icon="icon-link" data-id="dbtn_lw" analytics-key="MyContracts:LinkWarranty"></c-dce-descriptive-button>
                <c-dce-descriptive-button title={label.BatchLinkTool} description={label.BatchLinkToolDesc} url={urlBatchLinkTool} target="_self" icon="icon-link" data-id="dbtn_blt" analytics-key="MyContracts:BatchLinkTool"></c-dce-descriptive-button>
            </c-dce-group-descriptive-button>
        </lightning-layout-item>
        <lightning-layout-item size="12" small-device-size="12" medium-device-size="12" large-device-size="12"
              flexibility="auto" padding="around-large">
            <p class="slds-text-body_regular slds-text-color_default slds-var-p-bottom_medium">{label.ViewLkdShareSuppSumm}</p>
            <c-dce-group-descriptive-button>
                <c-dce-descriptive-button title={label.ViewContAndWarr} description={label.ViewContAndWarrDesc} url={urlViewContractsAndWarranties} target="_self" icon="icon-form-list-view" data-id="dbtn_vcw" analytics-key="MyContracts:Entitlements"></c-dce-descriptive-button>
                <c-dce-descriptive-button title={label.ReqSvcContCopy} description={label.ReqSvcContCopyDesc} url={urlRequestServiceWelcomeCopy} target="servicecontractletter" icon="icon-document" data-id="dbtn_scl" analytics-key="MyContracts:DontKnowYourContractID"></c-dce-descriptive-button>
            </c-dce-group-descriptive-button>
        </lightning-layout-item>
     </lightning-layout>
</template>

c-dce-my-contracts-card component js

import { LightningElement, track } from 'lwc';
import LinkContAndWarrSumm from '@salesforce/label/c.DCEEntLinkContAndWarrSumm';
import LinkSuppAgr from '@salesforce/label/c.DCEEntLinkSuppAgr';
import LinkSuppAgrDesc from '@salesforce/label/c.DCEEntLinkSuppAgrDesc';
import LinkPackSupp from '@salesforce/label/c.DCEEntLinkPackSupp';
import LinkPackSuppDesc from '@salesforce/label/c.DCEEntLinkPackSuppDesc';
import LinkWarr from '@salesforce/label/c.DCEEntLinkWarr';
import LinkWarrDesc from '@salesforce/label/c.DCEEntLinkWarrDesc';
import BatchLinkTool from '@salesforce/label/c.DCEEntBatchLinkTool';
import BatchLinkToolDesc from '@salesforce/label/c.DCEEntBatchLinkToolDesc';
import ViewLkdShareSuppSumm from '@salesforce/label/c.DCEEntViewLkdShareSuppSumm';
import ViewContAndWarr from '@salesforce/label/c.DCEEntViewContAndWarr';
import ViewContAndWarrDesc from '@salesforce/label/c.DCEEntViewContAndWarrDesc';
import ReqSvcContCopy from '@salesforce/label/c.DCEEntReqSvcContCopy';
import ReqSvcContCopyDesc from '@salesforce/label/c.DCEEntReqSvcContCopyDesc';

export default class DceMyContractsCard extends LightningElement {

    label = {
        LinkContAndWarrSumm,
        LinkSuppAgr,
        LinkSuppAgrDesc,
        LinkPackSupp,
        LinkPackSuppDesc,
        LinkWarr,
        LinkWarrDesc,
        BatchLinkTool,
        BatchLinkToolDesc,
        ViewLkdShareSuppSumm,
        ViewContAndWarr,
        ViewContAndWarrDesc,
        ReqSvcContCopy,
        ReqSvcContCopyDesc
      };

      @track urlLinkSupportAgreements = '/portal/site/hpsc/aae/linkSupportAgreements/?spf_p.tpst=aaeLinkObligations&spf_p.prp_aaeLinkObligations=wsrp-navigationalState%3Drender%253DoblLinkSupportAgreement';
      @track urlLinkPackagedSupport = '/portal/site/hpsc/aae/linkCarePacks/?spf_p.tpst=aaeLinkObligations&spf_p.prp_aaeLinkObligations=wsrp-navigationalState%3Drender%253DoblLinkHPCarePacks';
      @track urlLinkWarranties = '/portal/site/hpsc/aae/linkWarranties/?spf_p.tpst=aaeLinkObligations&spf_p.prp_aaeLinkObligations=wsrp-navigationalState%3Drender%253DoblLinkWarranties';
      @track urlBatchLinkTool = '/portal/site/hpsc/aae/batchLink';
      @track urlViewContractsAndWarranties = '/connect/s/contracts';
      @track urlRequestServiceWelcomeCopy = 'https://hpvertica.secure.force.com/MySalesOpNewSite/COCaseRequestForm?RequestType=Fixed%2FFlexible+Support+Services';

c-dce-descriptive-button jest mock

import { LightningElement, track, api } from 'lwc';
export default class DceDescriptiveButton extends LightningElement {
    @api url;
    @api title;
    @api description;
    @api target = "_self";
    @api icon;
    @api analyticsKey;
    @track state = false;
}

c-dce-descriptive-group-button jest mock

import { LightningElement } from 'lwc';

export default class DceDesignDescriptiveGroupButton extends LightningElement {
}

c-dce-my-contracts-card jest tests

import { createElement } from 'lwc';
import dceMyContractsCard from 'c/dceMyContractsCard';

const linkout_urls = 
    {
        'Link_Support_Agreements__c': '/portal/site/hpsc/aae/linkSupportAgreements/?spf_p.tpst=aaeLinkObligations&spf_p.prp_aaeLinkObligations=wsrp-navigationalState%3Drender%253DoblLinkSupportAgreement', 
        'Link_Packaged_Support__c': '/portal/site/hpsc/aae/linkCarePacks/?spf_p.tpst=aaeLinkObligations&spf_p.prp_aaeLinkObligations=wsrp-navigationalState%3Drender%253DoblLinkHPCarePacks',
        'Link_Warranties__c': '/portal/site/hpsc/aae/linkWarranties/?spf_p.tpst=aaeLinkObligations&spf_p.prp_aaeLinkObligations=wsrp-navigationalState%3Drender%253DoblLinkWarranties',
        'Batch_Link_Tool__c': '/portal/site/hpsc/aae/batchLink',
        'View_Contracts_and_Warranties__c': '/connect/s/contracts',
        'Request_Service_Welcome_Copy__c': 'https://hpvertica.secure.force.com/MySalesOpNewSite/COCaseRequestForm?RequestType=Fixed%2FFlexible+Support+Services'
    };

let element;

jest.mock("@salesforce/label/c.DCEEntLinkContAndWarrSumm", () => {
    return { default: "Link contracts and warranties to your HPE Support Center account to access entitled services. If batch linking, you will need to submit a CSV file to the Batch Link tool." };
}, { virtual: true });

jest.mock("@salesforce/label/c.DCEEntLinkSuppAgr", () => {
    return { default: "Link a Support Agreement" };
}, { virtual: true });

jest.mock("@salesforce/label/c.DCEEntLinkSuppAgrDesc", () => {
    return { default: "Requires SAID and SAR" };
}, { virtual: true });

describe('c-dce-my-contracts-card', () => {
    afterEach(() => {
        // The jsdom instance is shared across test cases in a single file so reset the DOM
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
    });

    beforeEach(() => {
        // Create initial element
        element = createElement('c-dce-my-contracts-card', {
            is: dceMyContractsCard
        });
        document.body.appendChild(element);
    });

    it('CUSTOM LABEL TEST: Custom label was mocked - p element', () => {

        const divs = element.shadowRoot.querySelectorAll(
            'p[data-id="p_lcws"]'
        );
        expect(divs.length).toBe(1);
        expect(divs[0].textContent).toBe("Link contracts and warranties to your HPE Support Center account to access entitled services. If batch linking, you will need to submit a CSV file to the Batch Link tool.");
    });

    it('CUSTOM LABEL TEST: Custom label was mocked - c-dce-descriptive-button component', () => {

        const lsaButton = element.shadowRoot.querySelectorAll('c-dce-descriptive-button[data-id="dbtn_lsa"]');
        expect(lsaButton.length).toBe(1);
        expect(lsaButton[0].title).toBe("Link a Support Agreement");
        expect(lsaButton[0].description).toBe("Requires SAID and SAR");
    });

    it('Render Link a Support Agreement descriptive button on load', () => {

        const lsaButton = element.shadowRoot.querySelectorAll('c-dce-descriptive-button[data-id="dbtn_lsa"]');
        expect(lsaButton.length).toBe(1);
    });

    it('Navigate to Link a Support Agreement page on click of descriptive button', () => {
        let INPUT_URL = linkout_urls['Link_Support_Agreements__c'];

        const linkSAButton = element.shadowRoot.querySelector(
            'c-dce-descriptive-button[data-id="dbtn_lsa"]'
        );
        linkSAButton.click();

        return Promise.resolve().then(() => {
            expect(linkSAButton.target).toBe('_self');
            expect(linkSAButton.url).toBe(INPUT_URL);
        });
    });

    it('Render Link a Packaged Support descriptive button on load', () => {

        const lpsButton = element.shadowRoot.querySelectorAll('c-dce-descriptive-button[data-id="dbtn_lps"]');
        expect(lpsButton.length).toBe(1);
    });

    it('Navigate to Link a Packaged Support page on click of descriptive button', () => {
        let INPUT_URL = linkout_urls['Link_Packaged_Support__c'];

        const linkPSButton = element.shadowRoot.querySelector(
            'c-dce-descriptive-button[data-id="dbtn_lps"]'
        );
        linkPSButton.click();

        return Promise.resolve().then(() => {
            expect(linkPSButton.target).toBe('_self');
            expect(linkPSButton.url).toBe(INPUT_URL);
        });
    });

    it('Render Link a Warranty descriptive button on load', () => {

        const lwButton = element.shadowRoot.querySelectorAll('c-dce-descriptive-button[data-id="dbtn_lw"]');
        expect(lwButton.length).toBe(1);
    });

    it('Navigate to Link a Warranty page on click of descriptive button', () => {
        let INPUT_URL = linkout_urls['Link_Warranties__c'];

        const linkWButton = element.shadowRoot.querySelector(
            'c-dce-descriptive-button[data-id="dbtn_lw"]'
        );
        linkWButton.click();

        return Promise.resolve().then(() => {
            expect(linkWButton.target).toBe('_self');
            expect(linkWButton.url).toBe(INPUT_URL);
        });
    });

    it('Render Bulk Link Tool descriptive button on load', () => {

        const bltButton = element.shadowRoot.querySelectorAll('c-dce-descriptive-button[data-id="dbtn_blt"]');
        expect(bltButton.length).toBe(1);
    });

    it('Navigate to Bulk Link Tool page on click of descriptive button', () => {
        let INPUT_URL = linkout_urls['Batch_Link_Tool__c'];

        const linkLBTButton = element.shadowRoot.querySelector(
            'c-dce-descriptive-button[data-id="dbtn_blt"]'
        );
        linkLBTButton.click();

        return Promise.resolve().then(() => {
            expect(linkLBTButton.target).toBe('_self');
            expect(linkLBTButton.url).toBe(INPUT_URL);
        });
    });

    it('Render View My contracts and Warranties descriptive button on load', () => {

        const vcwButton = element.shadowRoot.querySelectorAll('c-dce-descriptive-button[data-id="dbtn_vcw"]');
        expect(vcwButton.length).toBe(1);
    });

    it('Navigate to View My contracts and Warranties page on click of descriptive button', () => {
        const INPUT_URL = linkout_urls['View_Contracts_and_Warranties__c'];

        const vcwButton = element.shadowRoot.querySelector(
            'c-dce-descriptive-button[data-id="dbtn_vcw"]'
        );
        vcwButton.click();

        return Promise.resolve().then(() => {
            expect(vcwButton.target).toBe('_self');
            expect(vcwButton.url).toBe(INPUT_URL);
        });
    });

    it('Render Service Contract Welcome descriptive button on load', () => {

        const scsButton = element.shadowRoot.querySelectorAll('c-dce-descriptive-button[data-id="dbtn_scl"]');
        expect(scsButton.length).toBe(1);
    });

    it('Navigate to Service Contract Welcome letter page on click of descriptive button', () => {
        const INPUT_URL = linkout_urls['Request_Service_Welcome_Copy__c'];

        const welcomeButton = element.shadowRoot.querySelector(
            'c-dce-descriptive-button[data-id="dbtn_scl"]'
        );
        welcomeButton.click();

        return Promise.resolve().then(() => {
            expect(welcomeButton.target).toBe('servicecontractletter');
            expect(welcomeButton.url).toBe(INPUT_URL);
        });
    });
});
AllanOricil commented 3 years ago

You can remove all the other tests and use this one to understand what is going on between the versions

it('Render Link a Packaged Support descriptive button on load', () => {

        const lpsButton = element.shadowRoot.querySelectorAll('c-dce-descriptive-button[data-id="dbtn_lps"]');
        expect(lpsButton.length).toBe(1);
    });
AllanOricil commented 3 years ago

@pmdartus did the coments help to debug the problem?

pmdartus commented 3 years ago

I am unfortunately not able to reproduce the issue with the content you shared. Could you please create a small SFDX project with clear reproduction steps?

AllanOricil commented 3 years ago

@pmdartus I just shared with you a private repo where you can reproduce the error.

pmdartus commented 3 years ago

I spent some time extracting a minimal reproduction step out of the repo you shared. The minimal repro can be found here (update commit).

It appears that between versions 0.9.2 and 0.10.0 that slotted content on components that don't have slots are not accessible anymore.

<template>
  <c-card>
    <button></button>
  </c-card>
</template>

In the example above, prior 0.10.0, the <button> can always be query selected from the shadow root. After the 0.10.0 upgrade the <button> can only be selected if the <c-card> has a <slot> in its template. This is a bug in the LWC engine renders slotted content.

const btns = elm.shadowRoot.querySelectorAll('button'); 
console.log(btns.length); // In `0.9.4` -> `1`, In `0.10.0` -> `0` 

Workaround: To preserve the existing behavior, the <c-card> has to have a <slot> in its template.

FYI @AllanOricil

yippie commented 3 years ago

This is breaking a lot of things for us and putting us in a bind.

We want to use a summer 21 feature (not even lwc) so we have to bump our project version to 52. This means we can't stay on 9.2 because of the API check is enforced. And now 25% of our tests just broke because of this.

My component has a slot (actually 3) and it still isn't rendering the content

AllanOricil commented 3 years ago

I actually did not have time to test @pmdartus workaround. It would be good if you could share an isolated project @yippie

yippie commented 3 years ago

I will get you a project shortly but with some testing it seems that if the slot is heavily nested, then it doesn't render it. If I move the slot to the root of the template, then it renders the content.

yippie commented 3 years ago

I withdraw my issue. having an explicit tag does work. I found that my issue with it not rendering was my own bad code.

When trying to reproduce, I did confirm that if there is no slot tag, nothing renders, but if you add a slot tag, it renders as expected (very easy to see if you use snapshots)

OleJoMan commented 2 years ago

@AllanOricil Hi, did you resolve this issue? We have the same issue with custom components with . On version 0.9 all works fine. From version 0.10 jest cannot find elements that we pass inside the slot in the child custom component. In my local environment test components with the same hierarchy works fine on all versions.

telegram-cloud-photo-size-2-5244917116329637712-y in work repo jest cannot find div in parent component on 4th line

AllanOricil commented 2 years ago

@AllanOricil Hi, did you resolve this issue? We have the same issue with custom components with . On version 0.9 all works fine. From version 0.10 jest cannot find elements that we pass inside the slot in the child custom component. In my local environment test components with the same hierarchy works fine on all versions.

telegram-cloud-photo-size-2-5244917116329637712-y in work repo jest cannot find div in parent component on 4th line

I never tried the proposed workaround, so I won't be able to help you. But I have been using sfdx-lwc-jest@1.0.1 and everything is working fine. I don't think it will fix the problem as @pmdartus explained it is a bug in the lwc engine. I suggest you to stay using 0.9 and disable the API checking in case you are using one of the last salesforce apis.

dxenonb commented 2 years ago

Is this related to this issue?

I have a tests for a custom input and aria attributes:

expect(input.getAttribute('aria-invalid')).toBe('true');
// worked previously: const errorEl = element.shadowRoot.querySelector(`#${errorElId}`);
// works now:
const errorEl = element.shadowRoot.querySelector(`p.help`);
expect(errorEl).not.toBeNull();
expect(errorEl.id + ' ; ' + errorElId).toStrictEqual('err');
// ...

It looks like the querySelector is failing on selecting by ID? but not when selecting by other things (p.help). The last line above confirms that the IDs are still the same, only the select is failing:

image

(My HTML essentially looks like the following; and the transformed global ID errorElId is fetched successfully from the input element's aria attribute)

<input aria-errormessage="error" ...>
<p id="error" class="help">...</p>
AllanOricil commented 2 years ago

@dxenonb does not seem related to the problem I had