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

TypeError: Cannot read properties of undefined (reading 'toLowerCase') #73

Closed
ewlsh opened this issue Jan 29, 2023 · 10 comments
Closed

Comments

@ewlsh
Copy link

ewlsh commented Jan 29, 2023

Hi 👋 I'm working on upgrading our codebase from Jest 26 to Jest 29 and alongside that we're upping our jsdom version and thus nwsapi. I've been struggling with this very odd error in some components and eventually tracked it down to the code nwsapi is generating. What seems to be happening is whenever a component contains duet date picker *ByRole selectors from React Testing Library start to fail. It seems to occur when jsdom evaluates a component within the duet date picker.

It seems that with this code generated by nwsapi, e.element&&e.type.toLowerCase() introduced here e.type is undefined for these duet components and thus the generated code throws an error. I changed this to be e.element&&e.type&&e.type.toLowerCase() locally and my tests are now passing again.

Duet does use custom web elements, but I'm not sure if that is relevant. Additionally it uses more complex selectors than most of our components, so could also be a factor.

The error:


    TypeError: Cannot read properties of undefined (reading 'toLowerCase')

      107 |
      108 |      const name = component.getTranslation('translation:common.submit');
    > 109 |     const submitButton = component.getByRole('button', {
          |                                    ^
      110 |         name: name,
      111 |     });
      112 |     userEvent.click(submitButton);

      at Array.Resolver (eval at compile (../node_modules/nwsapi/src/nwsapi.js:773:17), <anonymous>:3:83)
      at match_assert (../node_modules/nwsapi/src/nwsapi.js:1356:13)
      at Object._matches [as match] (../node_modules/nwsapi/src/nwsapi.js:1374:16)
      at exports.matchesDontThrow (../node_modules/jsdom/lib/jsdom/living/helpers/selectors.js:29:36)
      at matches (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:50:10)
      at ../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:35:18
          at Array.forEach (<anonymous>)
      at handleSheet (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:26:13)
          at Array.forEach (<anonymous>)
      at Object.exports.forEachMatchingSheetRuleOfElement (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:46:11)
      at getCascadedPropertyValue (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:62:11)
      at getSpecifiedValue (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:80:19)
      at getComputedValue (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:100:12)
      at getSpecifiedValue (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:89:12)
      at getComputedValue (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:100:12)
      at getSpecifiedValue (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:89:12)
      at getComputedValue (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:100:12)
      at getSpecifiedValue (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:89:12)
      at getComputedValue (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:100:12)
      at exports.getResolvedValue (../node_modules/jsdom/lib/jsdom/living/helpers/style-rules.js:111:10)
      at ../node_modules/jsdom/lib/jsdom/browser/Window.js:819:41
          at Array.forEach (<anonymous>)
      at getComputedStyleImplementation (../node_modules/jsdom/lib/jsdom/browser/Window.js:818:13)
      at isHidden (../node_modules/dom-accessibility-api/sources/accessible-name-and-description.ts:84:16)
      at computeTextAlternative (../node_modules/dom-accessibility-api/sources/accessible-name-and-description.ts:559:4)
      at computeTextAlternative (../node_modules/dom-accessibility-api/sources/accessible-name-and-description.ts:696:3)
      at computeAccessibleName (../node_modules/dom-accessibility-api/sources/accessible-name.ts:40:9)
      at ../node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/queries/role.js:132:82
          at Array.filter (<anonymous>)
      at queryAllByRole (../node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/queries/role.js:127:6)
      at ../node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/query-helpers.js:74:17
      at ../node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/query-helpers.js:52:17
      at getByRole (../node_modules/@testing-library/react/node_modules/@testing-library/dom/dist/query-helpers.js:95:19)

Code generated before change:

if(e.element&&e.type.toLowerCase()=="::before"){e=e.element;n=e;while((e=e.parentElement)){if((/(^|\s)duet-date(\s|$)/.test(e.getAttribute("class")))){r=true;}}e=n;}
if(e.element&&e.type.toLowerCase()=="::after"){e=e.element;n=e;while((e=e.parentElement)){if((/(^|\s)duet-date(\s|$)/.test(e.getAttribute("class")))){r=true;}}e=n;}
if(e.element&&e.type.toLowerCase()=="::-webkit-input-placeholder"){e=e.element;if((/(^|\s)duet-date__input(\s|$)/.test(e.getAttribute("class")))){r=true;}}
if(e.element&&e.type.toLowerCase()=="::before"){e=e.element;if(!true){if((/(^|\s)duet-date__day(\s|$)/.test(e.getAttribute("class")))){r=true;}}}
if(e.element&&e.type.toLowerCase()=="::before"){e=e.element;if((/(^|\s)is-today(\s|$)/.test(e.getAttribute("class")))){if((/(^|\s)duet-date__day(\s|$)/.test(e.getAttribute("class")))){r=true;}}}
if(e.element&&e.type.toLowerCase()=="::before"){e=e.element;if(!s.match(".is-today",e)){if(((/^true$/).test(e.getAttribute&&e.getAttribute("aria-disabled"))==true)){if((/(^|\s)duet-date__day(\s|$)/.test(e.getAttribute("class")))){r=true;}}}}
if(e.element&&e.type.toLowerCase()=="::before"){e=e.element;if((/(^|\s)is-outside(\s|$)/.test(e.getAttribute("class")))){if((/(^|\s)duet-date__day(\s|$)/.test(e.getAttribute("class")))){r=true;}}}

Code generated after change:

if(e.element&&e.type&&e.type.toLowerCase()=="::before"){e=e.element;n=e;while((e=e.parentElement)){if((/(^|\s)duet-date(\s|$)/.test(e.getAttribute("class")))){r=true;}}e=n;}
if(e.element&&e.type&&e.type.toLowerCase()=="::after"){e=e.element;n=e;while((e=e.parentElement)){if((/(^|\s)duet-date(\s|$)/.test(e.getAttribute("class")))){r=true;}}e=n;}
if(e.element&&e.type&&e.type.toLowerCase()=="::-webkit-input-placeholder"){e=e.element;if((/(^|\s)duet-date__input(\s|$)/.test(e.getAttribute("class")))){r=true;}}
if(e.element&&e.type&&e.type.toLowerCase()=="::before"){e=e.element;if(!true){if((/(^|\s)duet-date__day(\s|$)/.test(e.getAttribute("class")))){r=true;}}}
if(e.element&&e.type&&e.type.toLowerCase()=="::before"){e=e.element;if((/(^|\s)is-today(\s|$)/.test(e.getAttribute("class")))){if((/(^|\s)duet-date__day(\s|$)/.test(e.getAttribute("class")))){r=true;}}}
if(e.element&&e.type&&e.type.toLowerCase()=="::before"){e=e.element;if(!s.match(".is-today",e)){if(((/^true$/).test(e.getAttribute&&e.getAttribute("aria-disabled"))==true)){if((/(^|\s)duet-date__day(\s|$)/.test(e.getAttribute("class")))){r=true;}}}}
if(e.element&&e.type&&e.type.toLowerCase()=="::before"){e=e.element;if((/(^|\s)is-outside(\s|$)/.test(e.getAttribute("class")))){if((/(^|\s)duet-date__day(\s|$)/.test(e.getAttribute("class")))){r=true;}}}
@dperini
Copy link
Owner

dperini commented Jan 29, 2023

@ewlsh
great finding ... these kind of bugs might be affecting other selectors too.
I will have this fixed asap together with others I am currently working on.
Thank you for your help and for reporting the error.

dperini added a commit that referenced this issue Apr 6, 2023
@dperini
Copy link
Owner

dperini commented Apr 6, 2023

@ewlsh
the issue should be resolved by the above commit soo I am closing this.
Feel free to reopen in case you still have problems, remember to send a reproducible test case.

@dperini dperini closed this as completed Apr 6, 2023
@jas-chen
Copy link

Hi @dperini I still encounter this issue after upgrading to v2.2.3. Here is the reproducible test case, the issue is caused by the element property on the custom element.
https://github.com/jas-chen/nwsapi-issue-73

@dperini
Copy link
Owner

dperini commented Apr 14, 2023

@jas-chen
Thank you for reporting this issue. I will have a deeper look to this.
While at it, can you also repeat the same test using the latest 2.2.4 release ?
Release 2.2.3 was a failure and I will probably remove it from npm in a few days.

@dperini
Copy link
Owner

dperini commented Apr 14, 2023

@jas-chen & @ewlsh
here is a simplified test case as an attempt to triage this bug in the right repository, it is the same test case as posted above, I just got rid of React and Jest dependencies and left the simplest native JavaScript code to reproduce this bug.
Here is the HTML/JavaScript code with which I performed my test:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>customElements and nwsapi</title>
<script src="src/nwsapi.js"></script>
<script>
window.onload = () => {

  class MyElement extends HTMLElement {
    static properties = {
      element: {},
    };

    render() {
      return this.element;
    }
  }

  customElements.define("my-element", MyElement);

  const style = document.createElement("style");
  style.innerHTML = `
      body::before {
        content: '';
      }`;

  document.head.appendChild(style);

  document.body.innerHTML = '<my-element element="hello"></my-element>';

  const myElement = document.querySelector("my-element");
  // this replaces the "expect" line in the original test
  console.log(myElement.attributes['element'].value);
  console.log(window.getComputedStyle(myElement));

  // these are additional queries to ensure nwsapi works
  console.log(document.querySelector("my-element"));
  console.log(document.querySelector("my-element[element]").attributes);
  console.log(document.querySelector("my-element[element]").getAttribute('element'));
};
</script>
</head>
<body>

</body>
</html>

Please copy the above code and paste it in the console and check if the results are correct.
If that is the case, I guess we should start looking for the issue in another place.

Thank you for the help with this.

@dperini dperini reopened this Apr 14, 2023
@jas-chen
Copy link

@dperini

can you also repeat the same test using the latest 2.2.4 release ?

Yes, the issue is still there after upgrading to 2.2.4.

https://github.com/jas-chen/nwsapi-issue-73/blob/d47f2c03cd1f00ccfefeac2b1289dfda8c8b92ff/package-lock.json#L856

Please copy the above code and paste it in the console and check if the results are correct.

The result is not correct, myElement.element should have value 'hello', also the error TypeError: Cannot read properties of undefined (reading 'toLowerCase') is not thrown.

Here is the code that I consider to be correct (note: the API of my-element is changed, the attribute element is replaced with the element property).

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>customElements and nwsapi</title>
<script src="src/nwsapi.js"></script>
<script>
window.onload = () => {
  class MyElement extends HTMLElement {
    get element() {
      return "hello";
    }
  
    render() {
      return this.element;
    }
  }

  customElements.define("my-element", MyElement);

  const style = document.createElement("style");
  style.innerHTML = `
      body::before {
        content: '';
      }`;

  document.head.appendChild(style);

  document.body.innerHTML = '<my-element></my-element>';

  const myElement = document.querySelector("my-element");

  if (myElement.element !== "hello") {
    throw new Error(`myElement.element should have value 'hello'`);
  }

  // should throw TypeError: Cannot read properties of undefined (reading 'toLowerCase')
  console.log(window.getComputedStyle(myElement));
};
</script>
</head>
<body>

</body>
</html>

@dperini
Copy link
Owner

dperini commented Apr 15, 2023

@jas-chen & @ewlsh
the results of the tests executed with "nwsapi" and native qSA are the same, that tells me "nwsapi" is ok but I don't know the code you are using nor am I familiar with your environment so there might be quirks I did not consider.

If you can setup a minimal native JavaScript/DOM code showing different behavior compared to that of "nwsapi", then I can dig further in the issue and try to fix my code else I should give up and only rely on your input.

The scope of "nwsapi" is mimic and replicate the functionality of the native browsers qSA, in browsers and headless environments like "jsdom".

Looking forward for a reproducible test case, without dependencies, showing the issue in "nwsapi".
Thank you for your help and availability.

@dperini
Copy link
Owner

dperini commented Apr 15, 2023

@jas-chen & @ewlsh
I forgot to tell that in case we do not discover the reason of the issue or if we are unable to fix it, I am open to consider adding that small extra check that @ewlsh mentioned in his first report, thus changing:

e.element&&e.type.toLowerCase()

with an extra check as suggested:

e.element&&e.type&&e.type.toLowerCase()

ensuring first that this change doesn't affect other functionalities or produces other issues.

@jas-chen
Copy link

@dperini
Sorry I didn't test the HTML code in the browser, it seems that only breaks in jsdom.

I made another code that should produce the issue in the browser (tested with Chrome).

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <script src="https://unpkg.com/[email protected]/src/nwsapi.js"></script>
</head>
<body>
  <script>
    class MyElement extends HTMLElement {
      get element() {
        return "hello";
      }
    
      render() {
        return this.element;
      }
    }

    customElements.define("my-element", MyElement);

    document.body.innerHTML = '<my-element></my-element>';

    function match() {
      return document.querySelector("my-element").matches('my-element::after');
    }

    // this works
    console.log(match());

    NW.Dom.install();

    // throws `TypeError: Cannot read properties of undefined (reading 'toLowerCase')`
    console.log(match());
  </script>
</body>
</html>

@dperini dperini closed this as completed Jun 7, 2023
@jas-chen
Copy link

jas-chen commented Jun 7, 2023

@dperini please correct me if I am wrong, but I don't see this issue fixed?

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

No branches or pull requests

3 participants