Jest throws error because of LWC restriction on accessing tagName
jeffhube opened this issue · 2 comments
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/expect@28.1.3/node_modules/expect/build/spyMatchers.js:40:51
at Array.map (<anonymous>)
at printReceivedArgs (node_modules/.pnpm/expect@28.1.3/node_modules/expect/build/spyMatchers.js:35:10)
at node_modules/.pnpm/expect@28.1.3/node_modules/expect/build/spyMatchers.js:376:41
at Array.reduce (<anonymous>)
at node_modules/.pnpm/expect@28.1.3/node_modules/expect/build/spyMatchers.js:374:14
at getMessage (node_modules/.pnpm/expect@28.1.3/node_modules/expect/build/index.js:169:15)
at processResult (node_modules/.pnpm/expect@28.1.3/node_modules/expect/build/index.js:293:25)
at Object.throwingMatcher [as toBeCalled] (node_modules/.pnpm/expect@28.1.3/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();
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.
Fixed by salesforce/lwc#3245. Thanks for reporting!