salesforce / sfdx-lwc-jest

Run Jest against LWC components in SFDX workspace environment
MIT License
164 stars 81 forks source link

flushpromises not working #109

Closed AllanOricil closed 4 years ago

AllanOricil commented 4 years ago

Description

I have a similar problem like the one described in this post. In my case, I have a wired method which fills a track property that is used in a if:true/if:false. After emiting the mock data, the component renders the if:false={property} even when the property has a value.

Steps to Reproduce

Use this guy example. Mine is similar. https://salesforce.stackexchange.com/questions/280315/lightning-web-component-unit-testing-with-get-record-and-conditional-rendering/283233#283233

jodarove commented 4 years ago

Hi @AllanOricil , in the example you mentioned does not seems that is registering the test wire adapter: const getRecordWireAdapter = registerLdsTestWireAdapter(getRecord);

You can look a complete example in: https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.unit_testing_using_wire_utility

If that does not solves your case, can you share it with us?

AllanOricil commented 4 years ago

Ok, disregard his example. The guy forgot to register the wire adapter. Our code, it is completly fine. We have everything that is needed and it does not work.

muenzpraeger commented 4 years ago

The flushPromises method is only relevant when using a Promise. The wire adapter is not a promise, so that won't fit your case.

It would be good to see your markup and code (anonymized if needed), otherwise it's a shot in the dark.

AllanOricil commented 4 years ago
<template>
   <template if:true={i18n}> 
      <lightning-input type="toggle" label={i18n.SPECIFIC_PRODUCTS} name="input4" message-toggle-active="On" message-toggle-inactive="Off" onchange={handleChange} checked={toggleOids} disabled={disableToggle}></lightning-input> 
      <lightning-tabset>
         <lightning-tab label={i18n.TOP_RESOLUTIONS_TRUE}>
            <template if:false={isDataAvailable}>
               <template if:false={topResolutionDocuments}>
                  <lightning-tile>
                     <div class="slds-p-top_large medium-height">
                        <lightning-spinner size="small" variant="brand"></lightning-spinner>
                     </div>
                  </lightning-tile>
               </template>
               <template if:true={topResolutionDocuments}>
                  <lightning-tile>
                     <div class="slds-grid slds-grid_vertical">
                        <template for:each={topResolutionDocuments} for:item="doc"> 
                           <div key={doc.kmdocid} class="slds-col slds-p-bottom_small slds-size_12-of-12">
                              <div>{doc.kmdoctypedetails} - <lightning-formatted-date-time value={doc.t_date} year="numeric" month="short" day="2-digit"></lightning-formatted-date-time></div>
                              <lightning-formatted-url value={doc.kmurl}  label={doc.title} target="_blank" ></lightning-formatted-url>
                           </div>
                        </template>
                     </div>
                     <template if:true={viewMoreDocs}>  
                        <lightning-formatted-url  value={topResolutionsViewAllUrl} label={i18n.VIEW_MORE}></lightning-formatted-url> 
                     </template>
                  </lightning-tile>
               </template>
            </template>
         </lightning-tab>
         <lightning-tab label={i18n.SECURITY_BULLETINS_TITLE}>
            <template if:false={isDataAvailable}>
               <template if:false={securityBullletins}>
                  <lightning-tile>
                     <div class="slds-p-top_large medium-height">
                        <lightning-spinner size="small" variant="brand"></lightning-spinner>
                     </div>
                  </lightning-tile>
               </template>
               <template if:true={securityBullletins}>
                  <lightning-tile>
                     <div class="slds-grid slds-grid_vertical">
                        <template for:each={securityBullletins} for:item="doc"> 
                           <div key={doc.kmdocid} class="slds-col slds-p-bottom_small slds-size_12-of-12">
                              <div>{doc.kmdoctypedetails} - <lightning-formatted-date-time value={doc.t_date} year="numeric" month="short" day="2-digit"></lightning-formatted-date-time></div>
                              <lightning-formatted-url value={doc.kmurl}  label={doc.title} target="_blank" ></lightning-formatted-url>
                           </div>
                        </template>
                     </div>
                     <template if:true={viewMoreSecB}>  
                        <lightning-formatted-url  value={securityBulletinViewAllUrl} label={i18n.VIEW_MORE} ></lightning-formatted-url> 
                     </template>
                  </lightning-tile>
               </template>
            </template>
         </lightning-tab>
      </lightning-tabset>
      <template if:true={isDataAvailable}>
         <div class="slds-grid slds-grid_align-left">
            <div class="slds-col slds-grid medium-height">
               <div class="slds-col slds-size_1-of_12 slds-m-right_xx-small">
                  <lightning-icon icon-name="utility:warning" class="warning-icon" size="small"></lightning-icon>
               </div>
               <div class="slds-col slds-size_11-of_12">
                  <p id="gsd-label-url-paragraph">
                     <template if:true={richTextMsg}>
                        <lightning-formatted-rich-text value={infoMsg}></lightning-formatted-rich-text>
                     </template>
                     <template if:false={richTextMsg}>
                        {infoMsg}<lightning-button variant="base" label="Turn off 'Specific to My Products'" title="Turn off 'Specific to My Products'" onclick={turnOffToggle} class="slds-m-left_x-small"></lightning-button>{infoMsg1}
                     </template>
                  </p>
               </div>
            </div>
         </div>
      </template>  
   </template>  
</template>
import { LightningElement, track, wire, api } from 'lwc';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';

import getTopResolutions from '@salesforce/apex/GSDCEPTopResolutionsController.getTopResolutions';
import getSecurityBulletins from '@salesforce/apex/GSDCEPTopResolutionsController.getSecurityBulletins';
import getGSDCEPProperties from '@salesforce/apex/GSDCEPTopResolutionsController.getGSDCEPProperties';

import TOP_RESOLUTIONS_TRUE from '@salesforce/label/c.TopResolutionstrue';
import VIEW_MORE from '@salesforce/label/c.viewMore';
import SECURITY_BULLETINS_TITLE from '@salesforce/label/c.securityBulletinTitle';
import SPECIFIC_PRODUCTS from '@salesforce/label/c.specificProducts';
import NO_PRODUCTS_LINKED from '@salesforce/label/c.noProductsLinked';
import ERROR_NO_PRODUCTS from '@salesforce/label/c.errorNoProducts';
import gsdCepTopResolutionsCss from '@salesforce/resourceUrl/gsdCepTopResolutions';
import gsdCepProductOids from '@salesforce/resourceUrl/gsdCepProductOids';
export default class GsdCepTopResolutions extends LightningElement {

    @track 
    topResolutionDocuments;

    @track 
    securityBullletins;

    @track 
    viewMoreDocs = false;

    @track 
    viewMoreSecB = false;

    @api 
    productOids = null;

    @track 
    toggleOids = false;

    @track 
    disableToggle = true;

    @track 
    toggleOidsLabel;

    @track 
    infoMsg = null;

    @track 
    infoMsg1 = null;

    @track 
    richTextMsg = true;

    @track 
    topResolutionsViewAllUrl = "/connect/s/search?toggle=OFF#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctopissue=[TopResolutionstrue]";;

    @track
    securityBulletinViewAllUrl = "/connect/s/search?toggle=OFF#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctypedetails=[cv5500009]";;

    @track
    portalHomeUrl;

    i18n = {
        TOP_RESOLUTIONS_TRUE,
        VIEW_MORE,
        SECURITY_BULLETINS_TITLE,
        SPECIFIC_PRODUCTS,
        NO_PRODUCTS_LINKED,
        ERROR_NO_PRODUCTS
    };

    connectedCallback() {
        Promise.all([
            loadScript(this, gsdCepProductOids),
            loadStyle(this, gsdCepTopResolutionsCss)
        ])
        .then(() => {
            console.log("script loaded ");
            window.apiWrapper.EntitlementApi.callApis('lwc', () => {
                console.log("no products: ", 'OFF');
                this.productOids = null;
                //this.toggleOids = false;
                this.disableToggle = false;
                this.toggleOidsLabel = 'OFF';
                this.topResolutionsViewAllUrl = "/connect/s/search?toggle=OFF#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctopissue=[TopResolutionstrue]";
                this.securityBulletinViewAllUrl = "/connect/s/search?toggle=OFF#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctypedetails=[cv5500009]";
            }, (result) => {
                console.log("product oids: ", result);
                this.productOids = result;
                //this.toggleOids = true;
                this.disableToggle = false;
                this.toggleOidsLabel = 'ON';
                this.topResolutionsViewAllUrl = "/connect/s/search?toggle=ON#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctopissue=[TopResolutionstrue]";
                this.securityBulletinViewAllUrl = "/connect/s/search?toggle=ON#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctypedetails=[cv5500009]";   
            }, (error) => {
                console.log("error with multiple oids ", error);
                this.productOids = null;
                //this.toggleOids = false;
                this.disableToggle = false;
                this.toggleOidsLabel = "ERROR";
            }) ;
        })
        .catch(error => {
             console.log("error in loading scripts ", error);
        })
    }

    @wire(getTopResolutions, { productOids: '$productOids', toggleOids: '$toggleOids' })
    wireTopResolutions({error, data}) {
        if(data != undefined && data.length > 0){
            if(data.length > 3) {
                this.topResolutionDocuments = data.slice(0,3);
                this.viewMoreDocs = true;
            } else {
                this.topResolutionDocuments = data;
                this.viewMoreDocs = false;
            }
        }else {
            console.log("top resolutions error: ", error);
            this.viewMoreDocs = false;
        }
    } 

    @wire(getSecurityBulletins, { productOids: '$productOids', toggleOids: '$toggleOids' })
    wireSecurityBulletins({error, data}) {
        if(data != undefined && data.length > 0){
            if(data.length > 3) {
                this.securityBullletins = data.slice(0,3);
                this.viewMoreSecB = true;
            } else {
                this.securityBullletins = data;
                this.viewMoreSecB = false;    
            }
        }else {
            console.log("security bulletins error: ", error);
            this.viewMoreSecB = false;
        }
    }

    @wire(getGSDCEPProperties)
    fetchPortalHomeUrl({error, data}){
        if(data){
            this.portalHomeUrl = data[0].portalHomeUrl__c;
            console.log('PORTAL URL: ' + this.portalHomeUrl);
        }else if(error){
            console.log("could not get the portalHomeUrl__c value: ", error);
        }
    }

    handleChange(event) {
        this.toggleOids = event.target.checked;
        if (event.target.checked) {
            if (this.infoMsg === null && this.productOids === null) {
                if (this.toggleOidsLabel === 'ERROR') {
                    let msg = this.i18n.ERROR_NO_PRODUCTS.split("{BUTTON}");
                    this.infoMsg = msg[0];
                    this.infoMsg1 = msg[1];
                    this.richTextMsg = false;
                } else {
                    this.infoMsg = this.i18n.NO_PRODUCTS_LINKED.replace("{URL}", this.portalHomeUrl);               
                    this.infoMsg = this.i18n.NO_PRODUCTS_LINKED.replace("{URL}", "https://portal-ext-itg-h4.itcs.hpe.com/portal/site/hpsc/aae/home");   
                    this.richTextMsg = true;             
                }
            } else {
                this.topResolutionsViewAllUrl = "/connect/s/search?toggle=ON#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctopissue=[TopResolutionstrue]";
                this.securityBulletinViewAllUrl = "/connect/s/search?toggle=ON#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctypedetails=[cv5500009]";
            }
        } else {
            this.infoMsg = null;
            this.topResolutionsViewAllUrl = "/connect/s/search?toggle=OFF#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctopissue=[TopResolutionstrue]";
            this.securityBulletinViewAllUrl = "/connect/s/search?toggle=OFF#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctypedetails=[cv5500009]";
        }
    }

    turnOffToggle() {
        this.toggleOids = false;
        this.infoMsg = null;
        this.infoMsg1 = null;
        this.topResolutionsViewAllUrl = "/connect/s/search?toggle=OFF#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctopissue=[TopResolutionstrue]";
        this.securityBulletinViewAllUrl = "/connect/s/search?toggle=OFF#t=Documents&sort=%40hpescuniversaldate%20descending&layout=table&f:@kmdoctypedetails=[cv5500009]";        
    }

    get isDataAvailable() {
        return this.infoMsg != null;
    }

}
import { createElement } from 'lwc';
import { registerApexTestWireAdapter } from '@salesforce/lwc-jest';
import GsdCepTopResolutions from 'c/gsdCepTopResolutions';
import getTopResolutions from '@salesforce/apex/GSDCEPTopResolutionsController.getTopResolutions';
import getSecurityBulletins from '@salesforce/apex/GSDCEPTopResolutionsController.getSecurityBulletins';
import TopResolutionstrue from '@salesforce/label/c.TopResolutionstrue';
import viewMore from '@salesforce/label/c.viewMore';
import securityBulletinTitle from '@salesforce/label/c.securityBulletinTitle';
import specificProducts from '@salesforce/label/c.specificProducts';

// Realistic data with a list of top resolutions
const mockTopResolutionstList = require('./data/topResolutionsList.json');

// Realistic data with a list of security bulletins - 2 items only
const mockSecurityBulletinsList = require('./data/securityBulletinsList.json');

// An empty list of records to verify the component does something reasonable
// when there is no data to display
const mockTopResolutionsListNoRecords = require('./data/topResolutionsListNoData.json');

const apiResponse = require('./data/apiResponse.json');

// Register as Apex wire adapter. Some tests verify that provisioned values trigger desired behavior.
const topResolutionsListAdapter = registerApexTestWireAdapter(getTopResolutions);
const securityBulletinsListAdapter = registerApexTestWireAdapter(getSecurityBulletins);

jest.mock('@salesforce/label/c.TopResolutionstrue', () => {
    return { default: "Top Resolutions" };
}, { virtual : true });

jest.mock('@salesforce/label/c.viewMore', () => {
    return { default: "View All" };
}, { virtual : true });

jest.mock('@salesforce/label/c.securityBulletinTitle', () => {
    return { default: "Security Bulletins" };
}, { virtual : true });

jest.mock('@salesforce/label/c.specificProducts', () => {
    return { default: "Specific to My Products" };
}, { virtual : true });

jest.mock('EntitlementApi.callEntitlementApi', () => {
    let apiResponseTmp =  require('./data/apiResponse.json');
    return Promise.resolve({ ok: true, json: () => Promise.resolve(apiResponseTmp.multipleProducts), jsonResponse: apiResponseTmp.multipleProducts })
}, { virtual : true });

jest.mock(
    'lightning/platformResourceLoader',
    () => {
        return {
            loadScript() {
                return new Promise((resolve, reject) => {
                    require('../../../staticresources/gsdCepProductOids');
                    resolve();
                });
            },
            loadStyle() {
                return new Promise((resolve, reject) => {
                    global.elementResizeDetectorMaker = "success";//require('../../../staticresources/elementResizeDetector');
                    resolve();
                });
            }
        };
    },
    { virtual: true }
);

describe('c-topResolutions-component', () => {

    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);
        }
        // Prevent data saved on mocks from leaking between tests
        jest.clearAllMocks();
    });

        // Helper function to mock a resolved fetch call.
    function mockFetch(data1, data2) {
        return jest
            .fn()
            .mockImplementationOnce(() =>
                Promise.resolve({ ok: true, json: () => Promise.resolve(data1), jsonResponse: data1 })
            )
            .mockImplementationOnce(() =>
                Promise.resolve({ ok: true, json: () => Promise.resolve(data2), jsonResponse: data2 })
            );
    }

    // Helper function to wait until the microtask queue is empty.
    // This is needed for promise timing.
    function flushPromises() {
        // eslint-disable-next-line no-undef
        return new Promise(resolve => setImmediate(resolve));
    }

    it('Test Wire Method to get TopResolutions', () => {
        fetch = global.fetch = mockFetch(apiResponse.multipleProducts, apiResponse.multipleOids);
        const WIRE_PARAMETER = { productOids: '465619,465674,465616', toggleOids: false};
        const element = createElement('c-topResolutions-component', {
            is: GsdCepTopResolutions,
        });
        document.body.appendChild(element);

        // Emit data from @wire
        topResolutionsListAdapter.emit(mockTopResolutionstList);
        securityBulletinsListAdapter.emit(mockSecurityBulletinsList);

        return flushPromises().then(() => {
            // THIS IS ALSO FAILING
            /*expect(topResolutionsListAdapter.getLastConfig()).toEqual(
                WIRE_PARAMETER
            );*/
            // Query lightning-tab field element

            const tabFields = element.shadowRoot.querySelectorAll('lightning-tab');
            // Top Resolutions and Security Bulletins
            expect(tabFields.length).toBe(2);
            expect(tabFields[0].label).toEqual("Top Resolutions");
            expect(tabFields[1].label).toEqual("Security Bulletins");

            const lightningTile = tabFields[0].childNodes[0];
            const topResolutionsGrid = lightningTile.childNodes[0];
            const spinner = topResolutionsGrid.querySelector('lightning-spinner');

            //IT FAILS HERE
            expect(spinner).toBeNull();
            } 
        } );

    });
});
AllanOricil commented 4 years ago

JEST OUTPUT FOR THIS TEST image

AllanOricil commented 4 years ago

I put a "expect(spinner).toBeNull()" to make the test fail on purpose. The test on line 134 is passing if I remove the line 132. In summary, the spinner is rendered even after I emit mock data for the wire that fills the "topResolutionDocuments" property

muenzpraeger commented 4 years ago

Awesome how much is going on in your component. Love it!

I've a bit limited time, but I'm wondering a) what the error message for the failing getLastConfig is and b) if it passes if you move away from your flushPromises approach and just check in a standard Promise.resolve().

Also what sfdx-lwc-jest version are you using?

AllanOricil commented 4 years ago

My component is a mess. I know. That is why I used that guy's code as an example hahaha We are gonna fix that later using the best practices you suggest in the recipes.

a) image

b) Same result!

I isntalled sfdx-lwc-jest yesterday using this page https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.unit_testing_using_jest_installation

image

The only difference is that I am using "scripts": { "test:unit": "lwc-jest", "test:unit:watch": "lwc-jest --watch" },

and not "scripts": { "test:unit": "sfdx-lwc-jest", "test:unit:watch": "sfdx-lwc-jest --watch" }

AllanOricil commented 4 years ago

Hi guys. Somehow after issuing the following commands the error changed saying that I was missing one wire Adapter.

image

image

After I added the missing wire adapter I was able to continue my tests.