Overflow

Button with overflowing text

<button id="click">Click This Button</button>
<button id="big-button">Click That Button</button>
#click {
  max-width: 60px;
  max-height: 30px;
}

Let's confirm the first button overflows it 60x30 pixel boundary, while the second button "grows" to enclose the entire text. First, let's write a utility function that returns true if the DOM element is overflowing its client dimensions.

const isOverflown = ({
  clientWidth,
  clientHeight,
  scrollWidth,
  scrollHeight,
}) => {
  return scrollHeight > clientHeight || scrollWidth > clientWidth
}

Now let's add a new Chai method that checks the current jQuery subject to be overflowing or now.

chai.use((_chai, utils) => {
  // use "function" syntax to make sure when Chai
  // calls it, the "this" object points at Chai

  function overflowing() {
    if (!Cypress.dom.isJquery(this._obj)) {
      throw new Error('Expected a jQuery object')
    }

    // the _obj should be a jQuery object
    this.assert(
      isOverflown(this._obj[0]),
      'expected element to overflow',
      'expected element not to overflow',
    )
  }
  _chai.Assertion.addMethod('overflow', overflowing)
})

Finally, let's use the new assertion method using implicit should('overflow') and its negative should('not.overflow') variants.

cy.get('#click').should('overflow')
cy.get('#big-button').should('not.overflow')

Let's confirm the assertion correctly fails for non-jQuery or empty objects. Here are some skipped examples.

cy.wrap(null).should('not.overflow')
cy.get('#does-not-exist').should('not.overflow')

Overflowing dialog container

In the example below the element might overflow not its immediate parent container, but a <dialog> element. Thus we need to check the elements' bounding rectangles.

📺 Watch this recipe explained in the video Custom Assertion Catches Elements Overflowing The Containeropen in new window.

<dialog open id="terms-dialog">
  <div>
    <p>Line 1: This dialog has a constrained height.</p>
    <p>Line 2: Additional copy creates vertical overflow.</p>
    <p>Line 3: Users can still scroll to read all lines.</p>
    <p>
      Line 4: This line intentionally pushes the content down.
    </p>
    <p>Line 5: Final line in this short text block.</p>
    <div>
      <button>Get 10% Off</button>
    </div>
  </div>
</dialog>
#terms-dialog {
  width: 280px;
  height: 110px;
  overflow-y: auto;
  overflow-x: visible;
}
#terms-dialog button {
  position: absolute;
  /* change these numbers to move the button around */
  top: 10px;
  left: 10px;
}
// checks if the first rectangle is completely inside
// the second bounding rectangle
const isOverflown = (rect, parentRect) => {
  return (
    rect.left < parentRect.left ||
    rect.right > parentRect.right ||
    rect.top < parentRect.top ||
    rect.bottom > parentRect.bottom
  )
}
chai.use((_chai, utils) => {
  // use "function" syntax to make sure when Chai
  // calls it, the "this" object points at Chai

  function overflowing(containerSelector = 'body') {
    if (!Cypress.dom.isJquery(this._obj)) {
      throw new Error('Expected a jQuery object')
    }

    // find the parent container using the given selector
    // and jQuery method "parents"
    const container = this._obj.parents(containerSelector)
    // get both bounding rectangles
    const parentContainer = container[0].getBoundingClientRect()
    const rect = this._obj[0].getBoundingClientRect()

    this.assert(
      isOverflown(rect, parentContainer),
      `expected element to overflow ${containerSelector}`,
      `expected element not to overflow ${containerSelector}`,
    )
  }
  _chai.Assertion.addMethod('overflow', overflowing)
})

Let's confirm that every button is visible inside the dialog element.

cy.get('#terms-dialog')
  .should('be.visible')
  .within(() => {
    cy.get('button').each(($el) => {
      // every button should be within the dialog container
      cy.wrap($el).should('not.overflow', 'dialog')
    })
  })