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

fix: #29093 for elements with length in rem #29224

Conversation

senpl
Copy link

@senpl senpl commented Mar 29, 2024

Additional details

Change was necessary because previous visibility check version works only with integer values for visibility. Current fix provide it with getting real width.
In theory it now fixes issue when visible element throws error as hidden/invisible or without width/height.
Still how visibility should behave when grid is used is not really clear, so there might be cases that this check is not perfect. Still I guess it's better than it was before.

Steps to test

e2e or component test

describe('Repro', () => {
    it('Skeleton Fail without changes', () => {
        cy.viewport('macbook-16');
        cy.visit('https://www.skeleton.dev/docs/get-started');
        cy.get('#sidebar-left > div > section').click();
      });
  })

How has the user experience changed?

User no longer has to use {force: true} fix.

PR Tasks

@CLAassistant
Copy link

CLAassistant commented Mar 29, 2024

CLA assistant check
All committers have signed the CLA.

@cypress-app-bot
Copy link
Collaborator

@senpl senpl marked this pull request as ready for review March 29, 2024 13:13
@jennifer-shehane
Copy link
Member

@senpl Thanks! Can you add a test around this new behavior?

There is a Contributing Guide that covers how to contribute and get Cypress running locally in generally here: https://github.com/cypress-io/cypress/blob/develop/.github/CONTRIBUTING.md

Here's an example of another issue's test code:

To run the tests:

  • within cypress root, run yarn
  • run yarn workspace @packages/driver cypress:open
  • click on the test you're writing to run it within Cypress

Instructions for running the driver tests can always be found here: https://github.com/cypress-io/cypress/blob/develop/packages/driver/README.md

Copy link
Member

@jennifer-shehane jennifer-shehane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I manually tested the change against the 3 examples this is expected to fix. It does fix them.

There are some other tests failing however, so it looks like this logic has broken some other visibility checks:
Screenshot 2024-04-11 at 4 01 24 PM

I had some concern around performance of this, and whether this would mean slower performance. Running benchmarks against offsetWidth vs getBoundingClientRect seem to indicate that getBoundingClientRect is actually faster.

Before

Screenshot 2024-04-11 at 3 18 51 PM

After

Screenshot 2024-04-11 at 3 18 51 PM

@senpl senpl requested a review from jennifer-shehane May 20, 2024 06:17
@jennifer-shehane jennifer-shehane dismissed their stale review May 20, 2024 13:45

Dismissing previous review

@jennifer-shehane jennifer-shehane removed their request for review May 20, 2024 13:45
@mschile mschile requested a review from cacieprins May 21, 2024 15:51
@@ -117,10 +117,10 @@ describe('FileRow', () => {
cy.contains(changesRequiredDescription).should('be.visible')
cy.get('pre').should('have.length', 2)

cy.get('.shiki').should('be.visible')
cy.get('div.rounded > div.text-left.cursor-text').should('be.visible')
Copy link
Contributor

@cacieprins cacieprins May 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @senpl ,

There are two .shiki elements in this component. One of them is always hidden, and one of them that should show/hide on click. The hidden state for this element is caused by a parent element with maxHeight of 0, and overflow:hidden.

I do agree that the .shiki selector here should be more specific. I edited it to assert the expected visibility state of both elements:

    // A FileRow with status=valid should not be initially open 
    cy.get('[data-cy=valid] .shiki').should('not.be.visible')
    // A FileRow with status=changes should be initially open
    cy.get('[data-cy=changes] .shiki').should('be.visible')

    // Clicking the header should collapse the shiki code view in the FileRow 
    cy.contains('cypress/integration/command.js').click()

    cy.get('[data-cy=valid] .shiki').should('not.be.visible')
    cy.get('[data-cy=changes] .shiki').should('not.be.visible')

Unfortunately, while this test passes on the develop, it does not pass with the visibility changes in this branch. Can you look into why?

Thank you!

@senpl senpl requested a review from cacieprins May 29, 2024 07:43
Copy link
Contributor

@cacieprins cacieprins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the issue being tested in the fixture added with this PR is related to display: contents, rather than rem units.

This seems like a separate issue from #29093 and #28638

For fixing the display:contents issue directly, I recommend checking for this css rule in canClipContent, as elements with display:contents cannot clip content. I've opened an issue to track that here: #29605

The rem issue should have a fixture that uses rem units. I do believe there is a useful fix here regarding them and using getClientBoundingRect() to calculate float dimensions rather than int dimensions. Fixing the rem issue and the display: contents issue should be separated into two different pull requests. But disregarding certain combinations of clipping ancestors will cause many more issues, giving lots of false positives.

Thank you so much for contributing!


if (elHasPositionAbsolute($el) && (ancestorProps.width === 0 || ancestorProps.height === 0)) {
return elIsOutOfBoundsOfAncestorsOverflow($el, getParent($ancestor))
if ((elHasPositionAbsolute($el) || elHasBlockStyle($el)) && (ancestorProps.width === 0 || ancestorProps.height === 0)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block elements that have ancestors that hide them via overflow clipping will assert as visible even if they are not.

For example, with this DOM:

<div style="overflow: hidden; width: 0; height: 0">
  <div style="height:100px; width:100px;">
    <div id="hidden" style="height: 100px; width: 100px;">This content is hidden</div>
  </div>
</div>

this assertion will fail:

cy.get('#hidden').should('not.be.visible')

However, this DOM:

<div style="overflow: hidden; width: 0; height: 0">
  <div style="height:100px; width:100px;">
    <span id="hidden">This content is hidden</div>
  </div>
</div>

Will succeed with the same assertion.

As will this DOM:

<div style="overflow: hidden; width: 0; height: 0">
  <div id="hidden">This content is hidden</div>
</div>

Removing the elHasBlockStyle portion of this if block should resolve the issue.

To verify, you can add additional test cases in visibility.cy.ts for parent vs. grandparent visibility.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified. elHasBlockStyle removed. Test in visibility.cy.ts added.

packages/driver/cypress/e2e/dom/visibility.cy.ts Outdated Show resolved Hide resolved
// no ancestor, not out of bounds!
// if we've reached the top parent, which is not a normal DOM el
// then we're in bounds all the way up, return false
if (isUndefinedOrHTMLBodyDoc($ancestor)) {
return false
}

//fix for 28638
if (elHasPositionRelative($el) && elHasPositionAbsolute($ancestor)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relative elements can be clipped by absolute ancestors that have height or width of 0 and overflow:hidden. canClipContent (L#230) checks for various edge cases with offset parents before comparing the dimensions of an element and their ancestor to determine if the element is actually obscured by clipping.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to canClipContent. This is just first working fix. Assumption is: when El has position relative and it's ancestor Absolute than this can not be clipped. But if this assumption is not exactly correct then more complicated check could be done. It's just better then existing version for this case.

@@ -308,47 +322,54 @@ const elIsNotElementFromPoint = function ($el) {
return true
}

const elIsOutOfBoundsOfAncestorsOverflow = function ($el, $ancestor = getParent($el)) {
const elIsOutOfBoundsOfAncestorsOverflow = function ($el, el: HTMLElement, $ancestor = getParent($el)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$el.get(0) and el refer to the same element - let's remove the extraneous argument to make this function easier to follow. For getting rid of the implicit any on $el, JQuery<HTMLElement> works.

Copy link
Author

@senpl senpl Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed. I guess it was just naive aproach to pass pipeline limitations in hugeJS file and not calculating jQuery when it was necessery. Still probably not needed and not that optimal anyway. Suggested getBoundingCalculation is probably better optimalisation anyway.

const elOffsetWidth = ($el) => {
return $el[0].offsetWidth
const elClientHeight = ($el) => {
return $el[0].getBoundingClientRect().height
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each call to .getBoundingClientRect() forces a layout/reflow of the browser. Calling this many times for each element/ancestor comparison in a complicated DOM may cause performance issues.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed into function. So result could be stored.

}

.contents {
display: contents
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what is causing Cypress to not properly determine that #sidebar-left is not hidden in the fixture. display: contents causes the element to not generate its own box, which prevents overflow:hidden from being applied as expected - the element is effectively replaced by its children, and its box model (and overflow settings) should be disregarded. See: https://drafts.csswg.org/css-display/#valdef-display-contents.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's true. Check moved to canClip.

@cacieprins cacieprins self-requested a review June 4, 2024 17:10
Copy link
Contributor

@cacieprins cacieprins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the latest changes!

The next step for this PR is to decide which issue you would like it to fix. We like to keep PRs narrowly focused on one issue at a time.

If you wish to fix the display: contents issue (#29605) in this PR, please remove any changes that are unnecessary to fix it. I think the only change necessary to fix this is the addition of the CSS rule check in canClipContent. If you wish to continue fixing any rem/em issues, please open a second PR to address it

If you wish to fix issues that are arising due to rounding dimensions with rem and em units, please add a test case for that in this PR and remove any changes that are unnecessary to fix that specific test case. If you wish, open a second PR to address the display:contents issue.

Thank you again!

@senpl
Copy link
Author

senpl commented Jun 14, 2024

I choose second approach, added tests and tried to demonstrate need of this change. Still it's not that simple because it's only fix for not correctly detected as visible element that are with display: contents so they are detected as with 0 width without it. Anyway this change is needed in some cases. I just not found good test to demonstrate it yet.

@senpl
Copy link
Author

senpl commented Jul 1, 2024

This changes are now part of other PR where they are needed to work. When their are accepted that one will no longer be needed.

@senpl senpl closed this Jul 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants