Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jest throws error because of LWC restriction on accessing tagName #299

Closed
jeffhube opened this issue Jan 5, 2023 · 2 comments
Closed

Jest throws error because of LWC restriction on accessing tagName #299

jeffhube opened this issue Jan 5, 2023 · 2 comments

Comments

@jeffhube
Copy link

jeffhube commented Jan 5, 2023

Description

Jest, in certain cases, may attempt to print out a lightning component element. In doing so, it will attempt to access the tagName, which throws an error:

Usage of property `tagName` is disallowed because the component itself does not know which tagName will be used to create the element, therefore writing code that check for that value is error prone.

This error comes from https://github.com/salesforce/lwc/blob/61a89c3f41a211bcc27b4878f2f2d83b279e1ea6/packages/%40lwc/engine-core/src/framework/restrictions.ts#L272

I encountered this when attempting to assert that a method was not called, when it was in fact called, and a lightning component element was passed as an argument. Jest attempts to print out the arguments to the method that was not expected to be called. If one of those arguments is lightning component element, it attempts to access tagName and throws an error.

For reference, this is what Jest's output normally looks like, when the argument is "a string" rather than a lightning component element.

expect(jest.fn()).not.toBeCalled()

Expected number of calls: 0
Received number of calls: 1

1: "a string"

Steps to Reproduce

// simplified test case
import { createElement } from 'lwc';
import { foo } from 'c/util';
import MyComponent from 'c/myComponent';

jest.mock('c/util', () => ({ foo: jest.fn() }));

describe('c-my-component', () => {
    it('does not call foo', () => {
        const element = createElement('c-my-component', { is: MyComponent });
        document.body.appendChild(element);

        expect(foo).not.toBeCalled();
    });
});
<!-- HTML for component under test -->
<template></template>
// JS for component under test (myComponent)
import { LightningElement } from 'lwc';
import { foo } from 'c/util';

export default class MyComponent extends LightningElement {
    connectedCallback() {
        foo(this);
    }
}
// JS for helper component (util)
export function foo(bar) { }
// Jest config overrides
const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config');
module.exports = {
    ...jestConfig,
    modulePathIgnorePatterns: ['<rootDir>/.localdevserver']
};
# Command to repro
sfdx-lwc-jest -- --no-cache

Expected Results

The unit test fails with an error about how foo was called 1 time but was expected to be called 0 times.

Actual Results

The unit test fails with an error about accessing tagName.

 FAIL  force-app/main/default/lwc/myComponent/__tests__/myComponent.test.js
  c-my-component
    × does not call foo (25 ms)

  ● c-my-component › does not call foo

    Usage of property `tagName` is disallowed because the component itself does not know which tagName will be used to create the element, therefore writing code that check for that value is error prone.

      10 |         document.body.appendChild(element);
      11 |
    > 12 |         expect(foo).not.toBeCalled();
         |                         ^
at/build/index.js:602:22)
      at node_modules/.pnpm/[email protected]/node_modules/expect/build/spyMatchers.js:40:51
          at Array.map (<anonymous>)
      at printReceivedArgs (node_modules/.pnpm/[email protected]/node_modules/expect/build/spyMatchers.js:35:10)
      at node_modules/.pnpm/[email protected]/node_modules/expect/build/spyMatchers.js:376:41
          at Array.reduce (<anonymous>)
      at node_modules/.pnpm/[email protected]/node_modules/expect/build/spyMatchers.js:374:14
      at getMessage (node_modules/.pnpm/[email protected]/node_modules/expect/build/index.js:169:15)
      at processResult (node_modules/.pnpm/[email protected]/node_modules/expect/build/index.js:293:25)
      at Object.throwingMatcher [as toBeCalled] (node_modules/.pnpm/[email protected]/node_modules/expect/build/index.js:362:16)      
      at Object.toBeCalled (force-app/main/default/lwc/myComponent/__tests__/myComponent.test.js:12:25)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        3.99 s
Ran all test suites.

Version

  • @salesforce/sfdx-lwc-jest: 1.2.1
  • Node: 16.18.0

Additional context/Screenshots

Here are some details about how I encountered this. I have a utility component that contains a method for displaying an error as a toast.

export function errorToast(element, error) {
    element.dispatchEvent(new ShowToastEvent(/* ... */));
}

When a component wants to display an error toast, it calls errorToast, passing itself and the error.

import { errorToast } from 'c/util';
// ...
errorToast(this, error);

In a test, I want to confirm that the errorToast function was not called.

expect(errorToast).not.toHaveBeenCalled();
@nolanlawson
Copy link
Contributor

Thanks for opening the issue. This is currently by design.

However, there is a case to be made that we should not throw an error in dev mode where the same error would not be thrown in prod mode: salesforce/lwc#3245

As a quick fix, we can disable this check in Jest.

@nolanlawson
Copy link
Contributor

Fixed by salesforce/lwc#3245. Thanks for reporting!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants