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

Version 1.0.22 is breaking all of our unit tests #80

Closed
kaiyoma opened this issue Feb 13, 2024 · 50 comments
Closed

Version 1.0.22 is breaking all of our unit tests #80

kaiyoma opened this issue Feb 13, 2024 · 50 comments

Comments

@kaiyoma
Copy link

kaiyoma commented Feb 13, 2024

If I upgrade from 1.0.20 to 1.0.22, all of our component unit tests start failing because elements are no longer visible. I think it has to be this change causing the problems: 28723d1

@saperdadsk Do you know why this would be the case?

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

This is essentially what I'm seeing now in our component unit tests:

TestingLibraryElementError: Unable to find an element by ...

Ignored nodes: comments, script, style
<body>
  <div>
    <div
      style="height: 200px; width: 200px;"
    >
      <div
        style="overflow: visible; width: 0px;"
      />
    </div>
  </div>
</body>

AutoSizer is rendering an empty shell with zero width, and none of its children are appearing.

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

Some more potentially valuable data points:

  • the commit message for the possibly offending commit says that tests were not included 😬
  • the same commit message references jsdom, but not everyone uses it (we use happy-dom for these tests)

@saperdadsk
Copy link
Contributor

saperdadsk commented Feb 13, 2024

Hmmm... @kaiyoma it's not obvious to me how that change would cause your tests to fail - it should only come into effect if window.getComputedStyle was previously returning an empty string. Could your tests previously have ignored an error coming from the lib?

If you put a breakpoint in the relevant code, what do you see window.getComputedStyle(this._parentNode) return for style.padding?

Tests were not included for the specific issue fixed by change, but existing tests did pass...

@saperdadsk
Copy link
Contributor

(The mention of JSDOM is the commit message was in reference to the JSDOM version used for testing in this repo - these changes "should" not care what library you use to render your component)

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

I'm happy to help, but I think I need more guidance here. Where exactly am I supposed to put that call? If I put it right before our AutoSizer call, I get this:

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

If I put it inside the render function inside AutoSizer, nothing ever prints.

@saperdadsk
Copy link
Contributor

saperdadsk commented Feb 13, 2024

@kaiyoma I would put a breakpoint at the beginning of the function _onResize inside the AutoSizer library, and step untl you get to

        // Guard against AutoSizer component being removed from the DOM immediately after being added.
        // This can result in invalid style values which can result in NaN values if we don't handle them.
        // See issue #150 for more context.

        const style = window.getComputedStyle(this._parentNode) || {};

which is where the change was.

@saperdadsk
Copy link
Contributor

(Specifically, inside the code in the file in (likely) node_modules/react-virtualized-auto-sizer/dist that your code is consuming)

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

Hmm, I'm having trouble getting that to work. We use ESM, so I added statements to the ESM file, but they're not working. I tried adding statements to the CJS file too, but that's also not working.

Got it to work after restarting Vitest. Here's what I see when I console log the style variable:

CSSStyleDeclaration { parentRule: null }

@saperdadsk
Copy link
Contributor

@kaiyoma What does style.paddingLeft return? If it's "", then you probably were ignoring the error before... Or maybe happy-dom allows for setting NaN values to style props and just ignores them during render or something?

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

Yes, style.paddingLeft is the empty string. We're not ignoring any errors in our unit tests though; any errors would cause our tests to fail. I'm more inclined to think that this is due to a difference between happy-dom and jsdom.

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

Hmm, even if I switch to jsdom, I'm still seeing basically the same kind of failure. (Actually, the markup looks worse and more incomplete.)

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

If I manually edit the AutoSizer library and change the 4 || operators back to ??, then my unit tests go back to passing, so it's definitely that change.

@bvaughn
Copy link
Owner

bvaughn commented Feb 13, 2024

If I manually edit the AutoSizer library and change the 4 || operators back to ??, then my unit tests go back to passing, so it's definitely that change.

That is surprising to me.

@kaiyoma What does style.paddingLeft return? If it's "", then you probably were ignoring the error before... Or maybe happy-dom allows for setting NaN values to style props and just ignores them during render or something?

This would also be my assumption, based on the other things mentioned in this thread.

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

@bvaughn At this point, I'm actually seeing the same behavior between happy-dom and jsdom. Both fail with this recent change, and both pass without the change.

@bvaughn
Copy link
Owner

bvaughn commented Feb 13, 2024

Can you package this up in a small runnable repro and share it here?

@saperdadsk
Copy link
Contributor

@kaiyoma What version of react-dom are your tests running with, and do you run with development mode on? I think the message I was seeing was from react-dom (i.e https://github.com/facebook/react/blob/dc3178151b8cb0359e5c36b8ed57458b807ae8e3/packages/react-dom-bindings/src/shared/warnValidStyle.js#L115). I wonder if your tests are suppressing the message or something?

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

Can you package this up in a small runnable repro and share it here?

Not quickly/easily. This is a large repo of proprietary code, so I would have to spend a non-trivial amount of time whittling it down.

@bvaughn
Copy link
Owner

bvaughn commented Feb 13, 2024

Can you package this up in a small runnable repro and share it here?

Not quickly/easily. This is a large repo of proprietary code, so I would have to spend a non-trivial amount of time whittling it down.

That's fair. I think we'd probably be able to help a lot more efficiently if we could view the "failing" behavior too. (Often times the reason for a failure is in some external dependency or configuration.)

Alternately you could just pin to the previous version of this library!

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

@saperdadsk We run 17.0.2. I'm not sure if we have development mode on ... whatever the default is, I guess?

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

Alternately you could just pin to the previous version of this library!

Yes, that's always our fallback when we run into issues with npm libraries.

@saperdadsk
Copy link
Contributor

@kaiyoma In your working version, what does the render function to AutoSizer get called with?

@bvaughn Arguably, this might just mean the change was a behavior change for components that are not attached to the DOM - i.e previously, since https://github.com/bvaughn/react-virtualized-auto-sizer/blob/master/src/AutoSizer.ts#L116C1-L133C1 only checks for the literal 0 for bailing out, NaN values were being allowed and the children function would be called with NaNs - now, the values will actually get set to zero, so that check is being hit.

@saperdadsk
Copy link
Contributor

It's ugly to say that Autosizer's contract is "Sometimes the height/width might be NaN and you'll get a warning from react, but your component will still render", but I guess fixing that is technically a breaking change....?

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

@saperdadsk I can't tell, because the render function doesn't seem to get called at all. If I put a console log inside, it never prints out anything to the screen.

@saperdadsk
Copy link
Contributor

@kaiyoma In your working version?

@bvaughn
Copy link
Owner

bvaughn commented Feb 13, 2024

@bvaughn Arguably, this might just mean the change was a behavior change for components that are not attached to the DOM

It's ugly to say that Autosizer's contract is "Sometimes the height/width might be NaN and you'll get a warning from react, but your component will still render", but I guess fixing that is technically a breaking change....?

This seems like a Semver "gray area" topic. I tend to think of it as a bug fix, but maybe it is a breaking change. (To be honest, I don't feel like I fully understanding it yet.)

@kaiyoma
Copy link
Author

kaiyoma commented Feb 13, 2024

@kaiyoma In your working version?

Ah, sorry. In the working version, I think we get passed NaN, which is the bug I reported here a while back: #69

@saperdadsk
Copy link
Contributor

@bvaughn Let me state the previous bug and the fix as I see it:

Consider the following HTML page:

<html>
<head>
<style>
</style>
</head>
<body>
	<div class='wrapper'></div>
	
	<script>
		const wrapper = document.querySelector('.wrapper');
		console.log('Attached', `"${window.getComputedStyle(wrapper).paddingLeft}"`);
		
		const detachedWrapper = document.createElement('div');
		console.log('Detached', `"${window.getComputedStyle(detachedWrapper).paddingLeft}"`);
		
		// Not at all an expert on the spec, but for reference:
		// See https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle , which specifies the getComputedStyle should return an empty https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle if the node is detached, and and https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface end up specifying that that a missing property is returned as an empty string 
	</script>
</body>
</html>

This will log

Attached "0px"
Detached ""

Before my change

  • If Autosizer was rendered inside wrapper, it would bail out and not render its children (since it determines that the height/width are the value 0).
  • if Autosizer was rendered inside detachedWrapper, it would not bail out (since it determines the height/width are NaN) and would render children, passing the value NaN as the width and height (which is the issue logged in Is it possible to test AutoSizer-powered components in unit tests? #69)

After my change

  • If Autosizer was rendered inside either wrapper or detachedWrapper, it would bail out and not render its children

At least in JSDOM, it looks like the handling of detached elements is not consistent with that.

@saperdadsk
Copy link
Contributor

saperdadsk commented Feb 13, 2024

Messing around a bit more, I think I can write a test that shows my behavior change:

it("should not render children for elements with empty computed styles", () => {
    mockOffsetSize(0, 0);

    renderHelper({
      excludeWrapperStyling: true,
      height: 0,
      width: 0,
    });

    expect(container.textContent).toEqual("");
  });

(Test will fail with

    Expected: ""
    Received: "width:NaN, height:NaN, foo:456, bar:123"

without my change)

(Not quite a repro of the bug I was encountering, but at least tests the same lines). I can probably open a PR to add that test, if you still think this behavior change is reasonable)

@bvaughn
Copy link
Owner

bvaughn commented Feb 13, 2024

I think the change made in 28723d1 is both reasonable and a bug fix.

I think the test you show above would pass before and after your change though?

Edit I just wrote that test locally, and it does pass before and after you commit.

Edit 2 Note that I wasn't accounting for the param you implied, excludeWrapperStyling. Implementing it that causes the test to pass/fail.

@bvaughn
Copy link
Owner

bvaughn commented Feb 13, 2024

I am getting ready to go on vacation until Monday! I don't really have the mental space to figure this out right now. If you'd like to post a PR or a proposal, I can look at it when I'm back. :) I'm going to step away until then.

saperdadsk added a commit to saperdadsk/react-virtualized-auto-sizer that referenced this issue Feb 23, 2024
This adds a test for the fix for bvaughn#79 - while that situation can appear in more situations than the one tested here (nodes being detached from the DOM, etc), it at least manifests in our test environment if we don't apply any styling to the parent node.

Relates to bvaughn#80, so if there is a decision to change the behavior, there is at least a test of _some_ kind
@saperdadsk
Copy link
Contributor

Opened a PR with a test case. Thinking on this issue, I'm leaning towards leaving this change in place with no further changes from the lib side. I can't really think of any change that would allow tests to work as before without detrimentally affecting production code (e.g checking for detached node status or something).

@bvaughn
Copy link
Owner

bvaughn commented Feb 24, 2024

Sorry for the churn and confusion, @kaiyoma.

I'm inclined to leave this change in place as well though. Thanks for digging into this, @saperdadsk.

@bvaughn bvaughn closed this as completed Feb 24, 2024
@kaiyoma
Copy link
Author

kaiyoma commented Feb 24, 2024 via email

@bvaughn
Copy link
Owner

bvaughn commented Feb 24, 2024

No. It works inside of unit tests. I have unit tests inside this project, and I use autosizer in other projects that have unit tests.

I’m not sure what is going on in your specific test because you didnt share a repro.

@kaiyoma
Copy link
Author

kaiyoma commented Feb 24, 2024

Okay, I'll try to work on a repro next week.

@anambl
Copy link

anambl commented Feb 28, 2024

Hi all! I am seeing the exact same issue as @kaiyoma.
I'm trying to understand in @saperdadsk 's comment here, specifically this point

If Autosizer was rendered inside wrapper, it would bail out and not render its children (since it determines that the height/width are the value 0).

How does the padding value relate to the height/width?

I have noticed that if I add a padding on the wrapper where AutoSizer is rendered then it will work, but I don't really understand why that is. In my case the padding is an empty string actually (not entirely sure how as I just simply never provided a padding to it) so for me the change in this PR means that before it used to work with the empty string whereas now it will default to 0.
And before the upgrade, I was also seeing the NaN height/width exactly as described in this issue.

@bvaughn
Copy link
Owner

bvaughn commented Feb 28, 2024

And before the upgrade, I was also seeing the NaN height/width exactly as described in #69.

Sounds like the update improved things for you then?

@anambl
Copy link

anambl commented Feb 29, 2024

And before the upgrade, I was also seeing the NaN height/width exactly as described in #69.

Sounds like the update improved things for you then?

I'm not seeing that warning anymore which is great, but now the component doesn't render at all anymore in unit tests so not quite 😅

As I said, it only renders if I add a padding to the element where AutoSizer is rendered. How does the padding value relate to the height/width? Why does no padding assume the height/width are 0?

And just to clarify, I'm only having this problem in unit tests 🤔

@bvaughn
Copy link
Owner

bvaughn commented Feb 29, 2024

This component does not render children if their height or width is 0px (bc there would be nothing to display, so it just bails out).

In order to calculate the available height and width, it measures the parent’s height and width and then subtracts padding. If the remainder is 0, it bails out.

Previously, the check would cause padding to be parsed as NaN in some cases, which broke the 0 comparison and caused it to not bail out when it should. Now that is fixed.

My guess is that your test is “failing” now bc the auto sizer’s parent component has 0 height or width. It only happened to “work” before because of the NaN bug.

Fix the size of the container auto sizer is in to fix your test.

@anambl
Copy link

anambl commented Feb 29, 2024

This component does not render children if their height or width is 0px (bc there would be nothing to display, so it just bails out).

My guess is that your test is “failing” now bc the auto sizer’s parent component has 0 height or width. It only happened to “work” before because of the NaN bug.

In the first sentence - by "their" did you mean the parent's size?

I'm not very good at CSS, but I think the parent container's size can be 0 and visually its children could still be visible provided their size is not 0.

Either way, I understand why the fix was good, but I do still think there's something not quite right. Adding a width and height explicitly to the parent of autosizer as an inline style doesn't fix the issue, where as adding a padding does. That doesn't seem right to me. I'm also a bit concerned that it only fails in unit tests, not sure why that is.

For now, I'll pin the version to the one that was working for me, thanks for replying :)

@bvaughn
Copy link
Owner

bvaughn commented Feb 29, 2024

I'm not very good at CSS, but I think the parent container's size can be 0 and visually its children could still be visible provided their size is not 0.

This component isn't trying to mimic or support all of the CSS spec. It's intended for a specific use case: When you want a React component to render into the full width and/or height of its container. If you want overflow: visible then you want to either use something else, or put auto sizer inside of the component that overflows.

Adding a width and height explicitly to the parent of autosizer as an inline style doesn't fix the issue, where as adding a padding does. That doesn't seem right to me. I'm also a bit concerned that it only fails in unit tests, not sure why that is.

I've asked for a repro above on this issue. I'll ask again now. Share your code and I'll look.

@anambl
Copy link

anambl commented Feb 29, 2024

This component isn't trying to mimic or support all of the CSS spec.

That's fine, as I said, adding the height and width didn't solve my problem.

I've asked for a repro above on this issue. I'll ask again now. Share your code and I'll look.

I don't have the time right now to put together a small clean example, but the repo I'm working on is public if you want to have a look at that, otherwise I'll try to create a smaller example in the following weeks.

I raised a PR here with the bump: Skyscanner/backpack#3260
AutoSizer is used here: https://github.com/Skyscanner/backpack/blob/main/packages/bpk-component-scrollable-calendar/src/BpkScrollableCalendarGridList.tsx#L165

@bvaughn
Copy link
Owner

bvaughn commented Feb 29, 2024

I'll likely wait for a smaller repro. That project looks pretty big and I don't have a lot of time to support this package.

@bvaughn
Copy link
Owner

bvaughn commented Mar 1, 2024

I had a few minutes this evening so I thought I would take a look. Good grief npm install takes a long time on that project.

Checking out your branch, installing dependencies, and running npm run test fails with:

✖ 235 problems (168 errors, 67 warnings)

Next I tried npm run jest– which actually runs tests but shows a lot of failures. Then to compare, I ran the same script on the main branch– and it had the same number of failures:

Test Suites: 99 failed, 190 passed, 289 total
Tests:       20 failed, 857 passed, 877 total

I guess this is why I ask for smaller repros.

@anambl
Copy link

anambl commented Mar 1, 2024

I had a few minutes this evening so I thought I would take a look. Good grief npm install takes a long time on that project.

Checking out your branch, installing dependencies, and running npm run test fails with:

✖ 235 problems (168 errors, 67 warnings)

Next I tried npm run jest– which actually runs tests but shows a lot of failures. Then to compare, I ran the same script on the main branch– and it had the same number of failures:

Test Suites: 99 failed, 190 passed, 289 total
Tests:       20 failed, 857 passed, 877 total

I guess this is why I ask for smaller repros.

We have a bunch of icons and styles that need to be built first, there's 2 commands you need to run before running the tests.

@bvaughn
Copy link
Owner

bvaughn commented Mar 6, 2024

Version 1.0.24 adds a new optional prop, doNotBailOutOnEmptyChildren, which can be used to override the default behavior of not rendering children when either width or height are 0

@pbrzosko
Copy link

@bvaughn @saperdadsk I have analyzed why this issue started to occur. If I am not mistaken, the problem was that before, when paddings were "", height and width were NaNs. Thus the code never went into height === 0 or width === 0 thus it didn't bail out because of them being 0. After that was fixed, it properly bails out.

@bvaughn
Copy link
Owner

bvaughn commented Mar 11, 2024

Yeah that pretty much aligns with this comment #80 (comment)

@pbrzosko
Copy link

Right... 🤦 At least that is now more clear than when I read it 😄

@bvaughn
Copy link
Owner

bvaughn commented Mar 12, 2024

Haha, no worries ☺️

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

5 participants