stencil
stencil copied to clipboard
bug: proxyCustomElement causing infinite loops with react testing library
Prerequisites
- [X] I have read the Contributing Guidelines.
- [X] I agree to follow the Code of Conduct.
- [X] I have searched for existing issues that already report this problem, without success.
Stencil Version
2.17.0
Current Behavior
Rendering multiple Stencil components wrapped as React components causes an PrettyFormatPluginError
error when used with @testing-library/react
. This error only reproduces in a test environment.
Expected Behavior
I expect there to be no errors when running the test.
Steps to Reproduce
- Clone the attached repo.
- Use the following script to install dependencies, build projects, and pack local dependencies:
cd sample-component-library && npm i && npm run build && npm pack
cd ../sample-component-library-react && npm i && npm i ../sample-component-library/sample-component-library-0.0.1.tgz && npm run build && npm pack
cd ../sample-app && npm i && npm i ../sample-component-library-react/component-library-react-0.0.1.tgz
This script does the following things:
a) Builds and packs the base Stencil component library in sample-component-library
b) Uses the packed sample-component-library
to build React wrappers for the Stencil components in sample-component-library-react
.
c) Uses the packed sample-component-library-react
to consume the React wrappers in a React application named sample-app
.
- In
sample-app
, runnpm run test
. Observe that the following error is logged:
RangeError: Invalid string length
at Array.join (<anonymous>)
6 | render(<App />);
7 | const linkElement = screen.getByText(/learn react/i);
> 8 | screen.debug();
| ^
9 | expect(linkElement).toBeInTheDocument();
10 | });
11 |
at printChildren (node_modules/@testing-library/dom/dist/DOMElementFilter.js:52:4)
at Object.serialize (node_modules/@testing-library/dom/dist/DOMElementFilter.js:139:75)
at printPlugin (node_modules/pretty-format/build/index.js:330:16)
at printer (node_modules/pretty-format/build/index.js:379:12)
at node_modules/@testing-library/dom/dist/DOMElementFilter.js:44:79
at Array.map (<anonymous>)
at printChildren (node_modules/@testing-library/dom/dist/DOMElementFilter.js:43:89)
at Object.serialize (node_modules/@testing-library/dom/dist/DOMElementFilter.js:139:75)
at printPlugin (node_modules/pretty-format/build/index.js:330:16)
at printer (node_modules/pretty-format/build/index.js:379:12)
at node_modules/@testing-library/dom/dist/DOMElementFilter.js:44:79
at Array.map (<anonymous>)
at printChildren (node_modules/@testing-library/dom/dist/DOMElementFilter.js:43:89)
at Object.serialize (node_modules/@testing-library/dom/dist/DOMElementFilter.js:139:75)
at printPlugin (node_modules/pretty-format/build/index.js:330:16)
at printer (node_modules/pretty-format/build/index.js:379:12)
at node_modules/@testing-library/dom/dist/DOMElementFilter.js:44:79
at Array.map (<anonymous>)
at printChildren (node_modules/@testing-library/dom/dist/DOMElementFilter.js:43:89)
at Object.serialize (node_modules/@testing-library/dom/dist/DOMElementFilter.js:139:75)
at printPlugin (node_modules/pretty-format/build/index.js:330:16)
at Object.format (node_modules/pretty-format/build/index.js:567:16)
at prettyDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:75:37)
at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:88:20)
at Object.debug (node_modules/@testing-library/dom/dist/screen.js:36:167)
at Object.<anonymous> (src/App.test.js:8:10)
at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)
at runJest (node_modules/@jest/core/build/runJest.js:404:19)
I did more debugging in sample-app/node_modules/sample-component-library/dist/components/my-component.js
and found that the following changes "resolve" the issue. In reality, this just causes the relevant Stencil code to never get loaded, but it seems that something in the proxyCustomElement
call could be causing this.
- import { proxyCustomElement, HTMLElement, h } from '@stencil/core/internal/client';
+ import { HTMLElement, h } from '@stencil/core/internal/client';
function format(first, middle, last) {
return (first || '') + (middle ? ` ${middle}` : '') + (last ? ` ${last}` : '');
}
const myComponentCss = ":host{display:block}";
- const MyComponent$1 = /*@__PURE__*/ proxyCustomElement(class extends HTMLElement {
+ const MyComponent$1 = /*@__PURE__*/ class extends HTMLElement {
constructor() {
super();
- this.__registerHost();
- this.__attachShadow();
}
getText() {
return format(this.first, this.middle, this.last);
}
render() {
return h("div", null, "Hello, World! I'm ", this.getText());
}
static get style() { return myComponentCss; }
- }, [1, "my-component", {
- "first": [1],
- "middle": [1],
- "last": [1]
- }]);
+ };
function defineCustomElement$1() {
if (typeof customElements === "undefined") {
return;
}
const components = ["my-component"];
components.forEach(tagName => { switch (tagName) {
case "my-component":
if (!customElements.get(tagName)) {
customElements.define(tagName, MyComponent$1);
}
break;
} });
}
const MyComponent = MyComponent$1;
const defineCustomElement = defineCustomElement$1;
export { MyComponent, defineCustomElement };
Code Reproduction URL
https://github.com/vincenthongzy/stencil-repro
Additional Information
This issue was discovered by an Ionic Framework user. There is more context in https://github.com/ionic-team/ionic-framework/issues/25334
There may be some cases where infinite recursion could happen in the pretty printing package (https://github.com/ionic-team/ionic-framework/issues/25334#issuecomment-1134959100), so it could also be the case that Stencil is triggering a bug in the pretty printing package instead.
I've confirmed that this is a bug, although it's not clear yet the root cause is here. I don't know if this is a bug in an external library, a result of wrapping components using the react output targets, etc. Labeling as a bug to get this ingested into our backlog
Hey there @rwaskiewicz, I am happy to report that the problem is due a bug in nwsapi
used by jsdom
. After manually updating it to the latest version, the infinite loop bug is not occurring anymore.
We can now close this bug ticket as it can be fixed by doing npm i nwsapi
and confirming that it is at least v2.2.1.
@vincenthongzy I updated nwsapi to 2.2.2, but the problem was not resolved. Is there any other workaround?
"@ionic/core": "6.2.8" "@ionic/react": "6.2.8" "@ionic/react-router": "6.2.8" "@stencil/core": "2.18.0" "nwsapi": "2.2.2"
Any update here? It's been quiet for a while.
I tried the nwsapi upgrade, and it did not solve the issue.
An additional observation: the Invalid string length
error appears when using Node 16, but not on Node 14
This critical issue still exists. A possible solution was suggested in the linked issue: https://github.com/ionic-team/ionic-framework/issues/25334#issuecomment-1467515273
Hello, @rwaskiewicz. How do you think there are any blockers that stop this pull request from merge? According to the previous comment, it should solve the problem.
https://github.com/ionic-team/stencil/compare/main...staff0rd:stencil:pretty-print
Hello all,
I just published a dev build of Stencil with a possible fix for this issue. You can install it like this:
npm install --save-dev @stencil/[email protected]
it would be a tremendous help if you could try that out and report back whether that fixes the issue for you - I hope it does!
Hello all,
I just published a dev build of Stencil with a possible fix for this issue. You can install it like this:
npm install --save-dev @stencil/[email protected]
it would be a tremendous help if you could try that out and report back whether that fixes the issue for you - I hope it does!
For me, this fix did not solve the problem. But imho, I think that the problem does not seem to look like an infinite loop.
If you want to reproduce it, then it's very easy to do so. It is necessary to create an blank ionic application (from tabs template) according to the instructions from the official tutorial. After that, add the following test to the project:
test('renders without crashing', async () => {
const { baseElement, findByText } = render(<IonButton>Tab 3</IonButton>);
// const { baseElement, findByText } = render(<App />);
expect(baseElement).toBeDefined();
await findByText('Tab 5');
});
In this case, the test will fail, and that is ok. However, instead of the printed html, there will be a long json text on the screen.
If you want to get an even more strange error, uncomment the line where App is used instead of IonButton. The planned result will be so big that json printing will be replaced by the problem of html dumping. In this case, the error will look similar to the infinite loop problem
Did not solve for me, although I'm not certain I am including it correctly.
Test that causes bad serialization looks like this:
import { IonSkeletonText } from '@ionic/react'
import { waitForIonicReact } from '@ionic/react-test-utils'
import { render } from '@testing-library/react'
describe('SkeletonForm', () => {
it('should render a skeleton form', async () => {
const { debug } = render(<IonSkeletonText />)
await waitForIonicReact()
debug()
})
})
To integrate the above fix, I'm:
-
npm install --save-dev @stencil/[email protected]
in a local clone of@ionic/react
-
npm run build
in@ionic/react
-
npm link @ionic/react
in the project with the test
The fix for this issue has been included in today's v3.2.1 release of Stencil. As a result, I'm going to close this issue.
Please note that if you are encountering this issue while using the Ionic Framework, a new patch release of the Ionic Framework will need to be released that uses Stencil v3.2.1 in order to be resolved on the Framework side (see issue https://github.com/ionic-team/ionic-framework/issues/25334).
Unfortunately we are still running into this issue, even after building our component library with v3.2.1 release of Stencil.
I checked that the inlined class expression is no longer anonymous as described in PR #4188 and it looks like it should look:
const LdInputMessage$1 = /*@__PURE__*/ proxyCustomElement(class LdInputMessage extends HTMLElement {
However, the issue still occurs in a specific test case. I haven't been able to figure out the crucial difference to other test cases / components yet.
I'm attaching a link to an application on Stackblitz which contains a test that leads to the above issue, if you uncomment the last two lines of the second test: https://stackblitz.com/github/emdgroup-liquid/liquid-sandbox-react-tailwind?file=src%2FApp.test.tsx%3AL34 Here is the repo if you'd like to run it locally: https://github.com/emdgroup-liquid/liquid-sandbox-react-tailwind
It would be great, if you could have a second look at this issue @alicewriteswrongs @rwaskiewicz. 🙏
@borisdiakur Thanks! Can you please create a new issue with your reproduction case there for us to properly track it?
@rwaskiewicz Yes, I will. Or maybe not. 😅 I actually managed to dig a little further in the last hour, by removing all components from the app. This resulted in a different but seemingly related issue (Maximum call stack size exceeded in picocolors used by vitest for printing error messages). The actual error message (quite huge - see screenshot) says that the testing library is not able to find the element with text that I'm looking for in the test.

Since I'm not even using web components in the test right now, I think this issue is not related to the one documented above (although at first sight it seemed so).
TLDR: For now I think we're good and I won't need to open a new issue (at least not for Stencil). However, if I put the Web Components back in place, and run into a similar problem again, I will create a new one.
Thanks a lot 🙏
Hello.
We've a complete design system developed using stencil 2.x and a consumer found out this issue. I've seen this is already fixed for v3 and our next version will be released using stencil 3.x but in the meantime would be very beneficial to have the fix in v2.x.
Looking into the commit message I understand there is no breaking change, so I would appreciate it very much if this could be done.
I can provide a PR if you agree on that
Hey @oscargm, I just opened a PR (#4435) to port the fix to v2 and published a dev build based on that change - if you have a spare minute you could try this out in your project and confirm that it fixes the bug:
npm i --save-dev @stencil/[email protected]
Hey @alicewriteswrongs sorry taking so much time to answer.
I tried this package and the issue is still happening, there might be other things in v3 that help fixing it. Anyway, there is a workaround changing the way to query the elements and we'll release the new version soon using stencil v3 so this will be fixed from our side.
Thanks again for providing the test package to try this out!