stencil icon indicating copy to clipboard operation
stencil copied to clipboard

bug: proxyCustomElement causing infinite loops with react testing library

Open liamdebeasi opened this issue 2 years ago • 1 comments

Prerequisites

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

  1. Clone the attached repo.
  2. 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.

  1. In sample-app, run npm 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.

liamdebeasi avatar Jun 23 '22 14:06 liamdebeasi

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

rwaskiewicz avatar Jun 24 '22 19:06 rwaskiewicz

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 avatar Aug 18 '22 14:08 vincenthongzy

@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"

h-sakano avatar Sep 24 '22 07:09 h-sakano

Any update here? It's been quiet for a while.

I tried the nwsapi upgrade, and it did not solve the issue.

tonypatton avatar Nov 02 '22 21:11 tonypatton

An additional observation: the Invalid string length error appears when using Node 16, but not on Node 14

luchsamapparat avatar Dec 21 '22 08:12 luchsamapparat

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

vmstarchenko avatar Mar 15 '23 10:03 vmstarchenko

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

vmstarchenko avatar Mar 17 '23 23:03 vmstarchenko

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!

alicewriteswrongs avatar Mar 22 '23 15:03 alicewriteswrongs

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

vmstarchenko avatar Mar 22 '23 23:03 vmstarchenko

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:

  1. npm install --save-dev @stencil/[email protected] in a local clone of @ionic/react
  2. npm run build in @ionic/react
  3. npm link @ionic/react in the project with the test

staff0rd avatar Mar 24 '23 00:03 staff0rd

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).

rwaskiewicz avatar Apr 10 '23 18:04 rwaskiewicz

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 avatar Apr 14 '23 09:04 borisdiakur

@borisdiakur Thanks! Can you please create a new issue with your reproduction case there for us to properly track it?

rwaskiewicz avatar Apr 14 '23 11:04 rwaskiewicz

@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.

Screenshot 2023-04-14 at 13 58 22

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 🙏

borisdiakur avatar Apr 14 '23 12:04 borisdiakur

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

oscargm avatar May 29 '23 11:05 oscargm

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]

alicewriteswrongs avatar May 31 '23 14:05 alicewriteswrongs

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!

oscargm avatar Jun 21 '23 08:06 oscargm