4

I've been looking to start unit testing the Lightning Web Component I've created with LWC Jest and I am running into some issues in creating my component that is making use of the NavigationMixin module. The unit test is failing to create the component when it reaches the NavigationMixin.GenerateUrl with the following error:

TypeError: Cannot read property 'then' of undefined

      64 |                     "title" : this.linkTitles.shift(),
      65 |                     "location" : pageRef,
    > 66 |                     "url" : this[NavigationMixin.GenerateUrl](pageRef).then(url => {this.links[i].url = url;}),
         |                             ^
      67 |                     "description" : this.linkDescriptions.shift()

I've tried wrapping the 'expect' assertion in a Promise statement as suggested here due to the asynchronous nature of NavigationMixin.GenerateUrl but had no luck. Tried to implement some of the jest.js mocking examples as well but didn't have much luck.

The following is the unit test and Javascript involved. Full component source is here.

Unit Test:

// createElement is what we'll use to create our component under test
import { createElement } from 'lwc';
// helpfulLinks is the component being tested
import helpfulLinks from 'c/helpfulLinks';

describe('Helpful Links Component', () => {
    it('is chained to another component', () => {
        // Given
        const expectedClassName = 'slds-grid slds-wrap slds-color__background_gray-1 slds-box pull-up-margin';
        // When
        const element = createElement('c-helpful-links', { is: helpfulLinks });
        element.linkIcons = 'doctype:pdf;doctype:flash';
        element.linkTitles = 'Sample Title; Sample Title';
        element.linkUrlsOrIds = '/;/';
        element.linkDescriptions = 'Sample Description;Sample Description';
        element.chainLists = true;
        document.body.appendChild(element);
        const className = element.shadowRoot.querySelector('div[data-id=linksList]').className;
        // Then
        expect(className).resolves.toBe(expectedClassName);
    });
});

Javascript for the Component

import { LightningElement, api, track } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';

export default class HelpfulLinks extends NavigationMixin(LightningElement) {
    //All of the settings that can be defined for the component
    @api title;
    @api navigationOptions;
    @api showTitle;
    @api chainLists;
    @api
    get linkIcons() {
        return this._linkIcons;
    }
    set linkIcons(value) {
        this._linkIcons = value.split(";");
        //Use this value to compare the lengths across all other settings
        this._linkSettingsCount = this._linkIcons.length;
    }
    @api
    get linkTitles() {
        return this._linkTitles;
    }
    set linkTitles(value) {
        this._linkTitles = value.split(";");
    }
    @api
    get linkUrlsOrIds() {
        return this._linkUrlsOrIds;
    }
    set linkUrlsOrIds(value) {
        this._linkUrlsOrIds = value.split(";");
    }
    @api
    get linkDescriptions() {
        return this._linkDescriptions;
    }
    set linkDescriptions(value) {
        this._linkDescriptions = value.split(";");
    }

    //Holds the constructed links to be rendered. Needs to be tracked for the URL display to work.
    @track
    links = [];

    //Used to return an error back to the user
    errorOccurred = false;
    errorMessage = '';

    renderedCallback() {
        //With the DOM being rendered, check whether the lists should be chained
        if(this.chainLists) {
            this.template.querySelector('div[data-id=linksList]').className = this.template.querySelector('div[data-id=linksList]').className + ' pull-up-margin';
        }
    }

    connectedCallback() {
        //Check that all the link settings are the same length
        if([this.linkIcons, this.linkTitles, this.linkUrlsOrIds, this.linkDescriptions].every(this.linkLengthSettingsMatch, this)) {
            //Set the links
            for(let i = 0; i < this._linkSettingsCount; i++) {
                let pageRef = this.constructPageRef(this.linkUrlsOrIds.shift());
                this.links.push({
                    "icon" : this.linkIcons.shift(),
                    "title" : this.linkTitles.shift(),
                    "location" : pageRef,
                    "url" : this[NavigationMixin.GenerateUrl](pageRef).then(url => {this.links[i].url = url;}),
                    "description" : this.linkDescriptions.shift()
                });
            }
        }
        else {
            //Set the error
            this.errorOccurred = true;
            this.errorMessage = 'Please ensure that when you fill out the icons, titles, urls and descriptions that they all have the same amount of semi colons.';
        }
    }

    navigateToTarget(event) {
        //Stop the event's default behavior.
        event.preventDefault();
        //Stop the event from bubbling up in the DOM.
        event.stopPropagation();
        //Navigate to the record page.
        this[NavigationMixin.Navigate](this.links[event.target.dataset.recordcount].location);
    }

    constructPageRef(urlOrId) {
        switch(this.navigationOptions) {
            case "URL":
                return {
                    type: 'standard__webPage',
                    attributes: {
                        url: urlOrId
                    }
                };
            case "Salesforce File":
                return {
                    type: 'standard__namedPage',
                    attributes: {
                        pageName: 'filePreview'
                    },
                    state : {
                        recordIds: urlOrId
                    }
                };
            case "Record Page":
                return {
                    type: 'standard__recordPage',
                    attributes: {
                        recordId: urlOrId,
                        actionName: 'view'
                    }
                };
            default:
                //As these switch values are guaranteed, this should never happen
                return null;
        }
    }

    //Utility for checking setting lengths
    linkLengthSettingsMatch(setting) {
        return setting.length === this._linkSettingsCount;
    }
}

Any assistance on this would be greatly appreciated. Thanks!

2 Answers 2

7

The default mock implementation for NavigationMixin doesn't provide a promise.

You'll have to create your own mock implementation, either globally for your project, or specific to your component, which then works for your use case. As it's not about testing NavigationMixin you should run the latter approach.

0

I had this same issue, and it turned out to be an issue with my JEST installation. If you debug your jest unit test do you find navigation.NavigationMixin or _navigation.NavigationMixin defined? If so, you probably have the same issue. Make sure you have a jest.config.js file with the following:

const { jestConfig } = require('@salesforce/lwc-jest/config');
module.exports = {
    ...jestConfig,
    moduleNameMapper: {   
        '/^(c)\/(.+)$':'<rootDir>/force-app/main/default/lwc/$2/$2'
    },
    testPathIgnorePatterns: [
        '<rootDir>/node_modules/',
        '<rootDit>/test/specs/'
    ]
}

Also, in your tests import:

import { registerTestWireAdapter } from '@salesforce/lwc-jest';
import { CurrentPageReference } from 'lightning/navigation';

And before your describe use:

 registerTestWireAdapter(CurrentPageReference);

I'm able to use NavigationMixin with a promise in my test context this way, without the need to create a custom mock impolementation

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .