13

Since the update to API 56.0 (Winter '23) Salesforce introduced a new set of components to help developers create modals with the Lightning Experience format in an easier way. The components added were the lightning-modal and its three children (header, body and footer).

A component that implements this new modal mode extends the LightningModal class and it implements the three sub-components on its body.

My problem is that I have content within the lightning-modal-body tags in my custom component, but those are not visible on a Jest test, apparently.

The component looks like this:

<template>
  <lightning-modal-header label={header}></lightning-modal-header>
  <lightning-modal-body>
    <template if:true={items}>
      <!-- sldsValidatorIgnoreNextLine -->
      <lightning-tree-grid key-field="id" columns={columns} data={lineItems}>
      </lightning-tree-grid>
    </template>
  </lightning-modal-body>
  <lightning-modal-footer>
    <lightning-button
      label={labels.Save}
      onclick={handleOk}
      data-id="save"
      variant="brand"
      class="slds-var-m-around_xxx-small"
      ><label>{labels.Save}</label></lightning-button
    >
  </lightning-modal-footer>
</template>

On the test, if I assert that the modal body exists, it works:

const modalBody = element.shadowRoot.querySelector("lightning-modal-body");
expect(modalBody).toBeTruthy();

But if I assert that the lightning-tree-grid exists within the body, it does not:

const treeGridComponent = element.shadowRoot.querySelector(
  "lightning-tree-grid"
);
expect(treeGridComponent).not.toBe(null);
    expect(received).not.toBe(expected) // Object.is equality

    Expected: not null

      63 |       "lightning-tree-grid"
      64 |     );
    > 65 |     expect(treeGridComponent).not.toBe(null);
         |                                   ^
      66 |   });
      67 | });
      68 |

So how does a test in this scenario should look like?

1
  • I really don't get why they required us to write an LWC that is the modal instead of being able to use the modal with some attributes and a body slot... I guess I'll find out when I try to use it (and will likely face the same issues you mention). Great question!
    – Phil W
    Commented Oct 19, 2022 at 20:22

4 Answers 4

10

Figured out the solution to this by following the concepts in the mocking trailhead. Essentially we just need to make mocks for these components and tell jest to use them.

1. Update sfdx-lwc-jest

Be on the version that supports Winter 23, for example 1.1.4

2. Make a directory to hold the mock components.

The trailhead uses something similar to force-app/test/jest-mocks/lightning

3. Create mock component .js and .html files for the lighting-modal-* components.

In the .html files include the following markup

<template><slot></slot></template>

And in the .js files just stub out the components

import { LightningElement } from 'lwc';

export default class ModalBody extends LightningElement {}

Your directory should have the following files:

/modalBody.html
/modalBody.js
/modalHeader.html
/modalHeader.js
/modalFooter.html
/modalFooter.js

4. Add the LightningModal stub

Create another file in the same directory called modal.js

The contents of this file I took from the lwc-recipes repo

Your directory should now have the following files:

/modal.js
/modalBody.html
/modalBody.js
/modalHeader.html
/modalHeader.js
/modalFooter.html
/modalFooter.js

5. Configure jest to use the mocks.

In your jest.config.js file modify, or create, your moduleNameMapper to look like this pointing to your stubs

  moduleNameMapper: {
    '^lightning/modal$': '<rootDir>/force-app/test/jest-mocks/lightning/modal',
    '^lightning/modalHeader$': '<rootDir>/force-app/test/jest-mocks/lightning/modalHeader',
    '^lightning/modalBody$': '<rootDir>/force-app/test/jest-mocks/lightning/modalBody',
    '^lightning/modalFooter$': '<rootDir>/force-app/test/jest-mocks/lightning/modalFooter',
  },

After completing the above I was able to successfully test the content between the lightning-modal-* tags. You may get a warning in the console like

  The following files share their name; please delete one of them:
    * <rootDir>/force-app/test/jest-mocks/lightning/modalBody.html
    * <rootDir>/force-app/test/jest-mocks/lightning/modalBody.js

I'm just ignoring it, the linked trailhead has duplicate .js/.html files.

2

For now, we do not have a way to test it, supposedly

LightningModal provides an .open() method which opens a modal and returns a promise that asynchronously resolves with the result of the user’s interaction with the modal. Each invocation of a modal component’s .open() method creates a unique instance of the modal. You can think of a modal as a self-contained application that starts from scratch when it opens. It displays the content you pass in through the .open() method or that you set within the modal's HTML template. - https://developer.salesforce.com/docs/component-library/bundle/lightning-modal/documentation

in other words, it is adding a new element to the DOM when the function .open() is fired:

Before opening:

Before opening modal

After opening:

After opening modal

ps.: the modal is a child node from lightning-overlay-container

Anyway, how we can "show" the modal? we need to call the .open() method from the implemented modal component, but when we are running tests it is not possible because we are mocking it, if you won't mock you will receive an error because it does not exist on your local machine: No mock

Following the mock created by lwc-recipes (https://github.com/trailheadapps/lwc-recipes/blob/main/force-app/test/jest-mocks/lightning/modal.js) the open method mock only does one thing, throws an error, that's why is necessary to also mock the .open() function as well ->

LightningModal.open = jest.fn().mockResolvedValue(YOUR STRING RETURN HERE);

Because of that, we do not actually have our modal on the screen, just their mock which doesn't have your lightning-tree-grid.

FYI here is their implementation of the modal, as you can see not even they have created the test for it (not until Today October 20 2022) https://github.com/trailheadapps/lwc-recipes/tree/main/force-app/main/default/lwc/myModal

4
  • you also can try to use the modalBody function from the modal.js, but something is missing there: /** * Query select a single element in lightning-modal-body * Usage: element.modalBody$('button') Returns first button * @param {string} selectors Ex: 'div', 'button, [data-button]' * @returns element */ @api modalBody$(selectors = '') { return select(this.template, LIGHTNING_MODAL_BODY, selectors); } Commented Oct 20, 2022 at 17:48
  • 1
    So the ideal scenario for testing this is to create a separate component to go between the body tags, and write the tests for that component separately? That's what I thought but wasn't sure. Commented Oct 20, 2022 at 20:01
  • 1
    for now, I would say yes, this is the best approach I can think of Commented Oct 21, 2022 at 0:38
  • I posted a new answer with a solution Commented Nov 9, 2022 at 21:01
1

@TemporaryFix's solution works, but following the docs for lightning-modal, we should use a slightly different approach. Use these methods on the modal.js stub for selecting DOM elements inside the modal's header, body, and footer:

  • modalHeader$
    • Equivalent to element.shadowRoot.querySelector("lightning-modal-header").querySelector
  • modalHeader$$
    • Equivalent to element.shadowRoot.querySelector("lightning-modal-header").querySelectorAll
  • modalBody$
    • Equivalent to element.shadowRoot.querySelector("lightning-modal-body").querySelector
  • modalBody$$
    • Equivalent to element.shadowRoot.querySelector("lightning-modal-body").querySelectorAll
  • modalFooter$
    • Equivalent to element.shadowRoot.querySelector("lightning-modal-footer").querySelector
  • modalFooter$$
    • Equivalent to element.shadowRoot.querySelector("lightning-modal-footer").querySelectorAll

I tried running my test on the modal component without any manual mocking and got the Cannot find module 'lightning/modal' error. But as soon as I set up my modal.js stub per @TemporaryFix's answer, the following worked:

expect(element.modalBody$("lightning-tree-grid")).toBeTruthy();

I did not have to create stub files for modalHeader, modalBody, or modalFooter, nor did I have to specify those files in my jest.config.js's moduleNameMapper.


Edit 12/19/2023: Depending on your package.json dependency versions related to LWC, it may still be necessary to stub out modalHeader, modalBody, and modalFooter as @TemporaryFix recommends. At the time I originally wrote this, I had recently used npm-check-updates to get the absolute latest versions of my dev environment dependencies. When I later had to revert due to issues with Prettier, I found that my Jest tests started throwing a Cannot find module 'lightning/modalHeader' error, so in the end I still had to stub those out. The test utility methods described above still worked, however.

0

I based my solution on the comment from @TemporaryFix, but instead of creating separate jest-mocks for the modal body/footer/header I only added a modal.js mock (copied from the recipe) and added a modal.html with slots:

<template> 
<slot></slot>
</template>

You must log in to answer this question.

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