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!