Address some visibility issues (#4421)

* Add failing test case for visible element within overflow hidden then position absolute element.

Addresses #4395

* Write failing test case for when parent is flex and overflow hidden with el outside bounds

Addresses #4161

* Wrote failing test for visibility outside of clip-path

Addresses #1178

* Add failing tests for transform scale

Addresses https://github.com/cypress-io/cypress/issues/723

* Add failing test for backfact-visibility hidden example

* cs -> js fixes

* Add more specific error when el is not element

* Always return as visible when checking html or body

* Add comments + rename methods to be more exact

* Add case for isHidden when visibility is collapse

* Add failing test cases for visibility issues

* Add comment about how ensureVisibility works under the hood

* Add checks and tests for opacity: 0 to be hidden

* Simplify if/case statements to be more readable

* Expand check for offset parents to also check children of ancestor for positioning attributes

close #4395
close #755

* Fix issue where els with parents with absolute position inside overflow hidden would be calculated as not visible

* comment out opacity checks for patch release

* Add more tests around new visibility assertions

- Add case to make sure display none is not on the option or optgroup
itself

* Fix failing assertion - where el was undefined when looking for visibiliyt

* remove commented out code involving opacity 😬
This commit is contained in:
Jennifer Shehane
2019-06-19 14:51:33 +06:30
committed by GitHub
parent 60318a7f0f
commit e2205f9263
4 changed files with 617 additions and 272 deletions
+4
View File
@@ -114,6 +114,10 @@ create = (state, expect) ->
ensureVisibility = (subject, onFail) ->
cmd = state("current").get("name")
# We overwrite the filter(":visible") in jquery
# packages/driver/src/config/jquery.coffee#L51
# So that this effectively calls our logic
# for $dom.isVisible aka !$dom.isHidden
if not (subject.length is subject.filter(":visible").length)
reason = $dom.getReasonIsHidden(subject)
node = $dom.stringify(subject)
+45 -4
View File
@@ -317,10 +317,22 @@ const isSelect = (el) => {
return getTagName(el) === 'select'
}
const isOption = (el) => {
return getTagName(el) === 'option'
}
const isOptgroup = (el) => {
return getTagName(el) === 'optgroup'
}
const isBody = (el) => {
return getTagName(el) === 'body'
}
const isHTML = (el) => {
return getTagName(el) === 'html'
}
const isSvg = function (el) {
try {
return 'ownerSVGElement' in el
@@ -390,6 +402,10 @@ const isAncestor = ($el, $maybeAncestor) => {
return $el.parents().index($maybeAncestor) >= 0
}
const isChild = ($el, $maybeChild) => {
return $el.children().index($maybeChild) >= 0
}
const isSelector = ($el, selector) => {
return $el.is(selector)
}
@@ -559,10 +575,26 @@ const getFirstFocusableEl = ($el) => {
return getFirstFocusableEl($el.parent())
}
const getFirstFixedOrStickyPositionParent = ($el) => {
// return null if we're at body/html
const getFirstParentWithTagName = ($el, tagName) => {
// return null if we're at body/html/document
// cuz that means nothing has fixed position
if (!$el || $el.is('body,html')) {
if (!$el[0] || !tagName || $el.is('body,html') || $document.isDocument($el)) {
return null
}
// if we are the matching element return ourselves
if (getTagName($el[0]) === tagName) {
return $el
}
// else recursively continue to walk up the parent node chain
return getFirstParentWithTagName($el.parent(), tagName)
}
const getFirstFixedOrStickyPositionParent = ($el) => {
// return null if we're at body/html/document
// cuz that means nothing has fixed position
if (!$el || $el.is('body,html') || $document.isDocument($el)) {
return null
}
@@ -691,7 +723,6 @@ const getFirstDeepestElement = (elements, index = 0) => {
}
return $current
}
// short form css-inlines the element
@@ -773,6 +804,8 @@ module.exports = {
isAncestor,
isChild,
isScrollable,
isTextLike,
@@ -783,8 +816,14 @@ module.exports = {
isSame,
isOption,
isOptgroup,
isBody,
isHTML,
isInput,
isTextarea,
@@ -815,6 +854,8 @@ module.exports = {
getFirstDeepestElement,
getFirstParentWithTagName,
getFirstFixedOrStickyPositionParent,
getFirstStickyPositionParent,
+212 -161
View File
@@ -1,16 +1,3 @@
/* eslint-disable
no-case-declarations,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS104: Avoid inline assignments
* DS204: Change includes calls to have a more natural evaluation order
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const _ = require('lodash')
const $jquery = require('./jquery')
@@ -22,56 +9,86 @@ const fixedOrAbsoluteRe = /(fixed|absolute)/
const OVERFLOW_PROPS = ['hidden', 'scroll', 'auto']
//# WARNING:
//# developer beware. visibility is a sink hole
//# that leads to sheer madness. you should
//# avoid this file before its too late.
const isVisible = (el) => {
return !isHidden(el, 'isVisible()')
}
//# TODO: we should prob update dom
//# to be passed in $utils as a dependency
//# because of circular references
const isHidden = function (el, name) {
// TODO: we should prob update dom
// to be passed in $utils as a dependency
// because of circular references
const isHidden = (el, name = 'isHidden()') => {
if (!$elements.isElement(el)) {
if (name == null) {
name = 'isHidden()'
}
throw new Error(`Cypress.dom.${name} must be passed a basic DOM element.`)
throw new Error(`Cypress.dom.${name} failed because it requires a DOM element. The subject received was: '${el}'`)
}
const $el = $jquery.wrap(el)
//# in Cypress-land we consider the element hidden if
//# either its offsetHeight or offsetWidth is 0 because
//# it is impossible for the user to interact with this element
//# offsetHeight / offsetWidth includes the ef
return elHasNoEffectiveWidthOrHeight($el) ||
// the body and html are always visible
if ($elements.isBody(el) || $elements.isHTML(el)) {
return false // is visible
}
//# additionally if the effective visibility of the element
//# is hidden (which includes any parent nodes) then the user
//# cannot interact with this element and thus it is hidden
elHasVisibilityHidden($el) ||
// an option is considered visible if its parent select is visible
if ($elements.isOption(el) || $elements.isOptgroup(el)) {
// they could have just set to hide the option
if (elHasDisplayNone($el)) {
return true
}
//# we do some calculations taking into account the parents
//# to see if its hidden by a parent
elIsHiddenByAncestors($el) ||
// if its parent select is visible, then it's not hidden
const $select = $elements.getFirstParentWithTagName($el, 'select')
//# if this is a fixed element check if its covered
(
elIsFixed($el) ?
elIsNotElementFromPoint($el)
:
//# else check if el is outside the bounds
//# of its ancestors overflow
elIsOutOfBoundsOfAncestorsOverflow($el)
)
// check $select.length here first
// they may have not put the option into a select el,
// in which case it will fall through to regular visibility logic
if ($select && $select.length) {
// if the select is hidden, the options in it are visible too
return isHidden($select[0], name)
}
}
// in Cypress-land we consider the element hidden if
// either its offsetHeight or offsetWidth is 0 because
// it is impossible for the user to interact with this element
if (elHasNoEffectiveWidthOrHeight($el)) {
return true // is hidden
}
// additionally if the effective visibility of the element
// is hidden (which includes any parent nodes) then the user
// cannot interact with this element and thus it is hidden
if (elHasVisibilityHiddenOrCollapse($el)) {
return true // is hidden
}
// we do some calculations taking into account the parents
// to see if its hidden by a parent
if (elIsHiddenByAncestors($el)) {
return true // is hidden
}
if (elOrAncestorIsFixed($el)) {
return elIsNotElementFromPoint($el)
}
// else check if el is outside the bounds
// of its ancestors overflow
return elIsOutOfBoundsOfAncestorsOverflow($el)
}
const elHasNoEffectiveWidthOrHeight = ($el) => {
// Is the element's CSS width OR height, including any borders,
// padding, and vertical scrollbars (if rendered) less than 0?
//
// elOffsetWidth:
// If the element is hidden (for example, by setting style.display
// on the element or one of its ancestors to "none"), then 0 is returned.
// $el[0].getClientRects().length:
// For HTML <area> elements, SVG elements that do not render anything themselves,
// display:none elements, and generally any elements that are not directly rendered,
// an empty list is returned.
return (elOffsetWidth($el) <= 0) || (elOffsetHeight($el) <= 0) || ($el[0].getClientRects().length <= 0)
}
@@ -87,51 +104,70 @@ const elOffsetHeight = ($el) => {
return $el[0].offsetHeight
}
const elHasVisibilityHiddenOrCollapse = ($el) => {
return elHasVisibilityHidden($el) || elHasVisibilityCollapse($el)
}
const elHasVisibilityHidden = ($el) => {
return $el.css('visibility') === 'hidden'
}
const elHasVisibilityCollapse = ($el) => {
return $el.css('visibility') === 'collapse'
}
const elHasDisplayNone = ($el) => {
return $el.css('display') === 'none'
}
const elHasOverflowHidden = function ($el) {
let needle
const cssOverflow = [$el.css('overflow'), $el.css('overflow-y'), $el.css('overflow-x')]
return (needle = 'hidden', [$el.css('overflow'), $el.css('overflow-y'), $el.css('overflow-x')].includes(needle))
return cssOverflow.includes('hidden')
}
const elHasPositionRelative = ($el) => {
return $el.css('position') === 'relative'
}
const elHasClippableOverflow = function ($el) {
const o = $el.css('overflow')
const oy = $el.css('overflow-y')
const ox = $el.css('overflow-x')
const elHasPositionAbsolute = ($el) => {
return $el.css('position') === 'absolute'
}
return OVERFLOW_PROPS.includes(o) || OVERFLOW_PROPS.includes(oy) || OVERFLOW_PROPS.includes(ox)
const elHasClippableOverflow = function ($el) {
return OVERFLOW_PROPS.includes($el.css('overflow')) ||
OVERFLOW_PROPS.includes($el.css('overflow-y')) ||
OVERFLOW_PROPS.includes($el.css('overflow-x'))
}
const canClipContent = function ($el, $ancestor) {
//# can't clip without clippable overflow
// can't clip without overflow properties
if (!elHasClippableOverflow($ancestor)) {
return false
}
const $offsetParent = $jquery.wrap($el[0].offsetParent)
// the closest parent with position relative, absolute, or fixed
const $offsetParent = $jquery.wrap($el.offsetParent()[0])
//# even if overflow is clippable, if an ancestor of the ancestor is the
//# element's offset parent, the ancestor will not clip the element
//# unless the element is position relative
// even if ancestors' overflow is clippable, if the element's offset parent
// is a parent of the ancestor, the ancestor will not clip the element
// unless the element is position relative
if (!elHasPositionRelative($el) && $elements.isAncestor($ancestor, $offsetParent)) {
return false
}
// even if ancestors' overflow is clippable, if the element's offset parent
// is a child of the ancestor, the ancestor will not clip the element
// unless the ancestor has position absolute
if (elHasPositionAbsolute($offsetParent) && $elements.isChild($ancestor, $offsetParent)) {
return false
}
return true
}
const elIsFixed = function ($el) {
const elOrAncestorIsFixed = function ($el) {
const $stickyOrFixedEl = $elements.getFirstFixedOrStickyPositionParent($el)
if ($stickyOrFixedEl) {
@@ -153,9 +189,9 @@ const elAtCenterPoint = function ($el) {
}
const elDescendentsHavePositionFixedOrAbsolute = function ($parent, $child) {
//# create an array of all elements between $parent and $child
//# including child but excluding parent
//# and check if these have position fixed|absolute
// create an array of all elements between $parent and $child
// including child but excluding parent
// and check if these have position fixed|absolute
const $els = $child.parentsUntil($parent).add($child)
return _.some($els.get(), (el) => {
@@ -164,30 +200,26 @@ const elDescendentsHavePositionFixedOrAbsolute = function ($parent, $child) {
}
const elIsNotElementFromPoint = function ($el) {
//# if we have a fixed position element that means
//# it is fixed 'relative' to the viewport which means
//# it MUST be available with elementFromPoint because
//# that is also relative to the viewport
// if we have a fixed position element that means
// it is fixed 'relative' to the viewport which means
// it MUST be available with elementFromPoint because
// that is also relative to the viewport
const $elAtPoint = elAtCenterPoint($el)
//# if the element at point is not a descendent
//# of our $el then we know it's being covered or its
//# not visible
// if the element at point is not a descendent
// of our $el then we know it's being covered or its
// not visible
return !$elements.isDescendent($el, $elAtPoint)
}
const elIsOutOfBoundsOfAncestorsOverflow = function ($el, $ancestor) {
if ($ancestor == null) {
$ancestor = $el.parent()
}
//# no ancestor, not out of bounds!
const elIsOutOfBoundsOfAncestorsOverflow = function ($el, $ancestor = $el.parent()) {
// no ancestor, not out of bounds!
if (!$ancestor) {
return false
}
//# if we've reached the top parent, which is document
//# then we're in bounds all the way up, return false
// if we've reached the top parent, which is document
// then we're in bounds all the way up, return false
if ($ancestor.is('body,html') || $document.isDocument($ancestor)) {
return false
}
@@ -197,18 +229,18 @@ const elIsOutOfBoundsOfAncestorsOverflow = function ($el, $ancestor) {
if (canClipContent($el, $ancestor)) {
const ancestorProps = $coordinates.getElementPositioning($ancestor)
//# target el is out of bounds
// target el is out of bounds
if (
//# target el is to the right of the ancestor's visible area
// target el is to the right of the ancestor's visible area
(elProps.fromWindow.left > (ancestorProps.width + ancestorProps.fromWindow.left)) ||
//# target el is to the left of the ancestor's visible area
// target el is to the left of the ancestor's visible area
((elProps.fromWindow.left + elProps.width) < ancestorProps.fromWindow.left) ||
//# target el is under the ancestor's visible area
// target el is under the ancestor's visible area
(elProps.fromWindow.top > (ancestorProps.height + ancestorProps.fromWindow.top)) ||
//# target el is above the ancestor's visible area
// target el is above the ancestor's visible area
((elProps.fromWindow.top + elProps.height) < ancestorProps.fromWindow.top)
) {
return true
@@ -218,151 +250,170 @@ const elIsOutOfBoundsOfAncestorsOverflow = function ($el, $ancestor) {
return elIsOutOfBoundsOfAncestorsOverflow($el, $ancestor.parent())
}
const elIsHiddenByAncestors = function ($el, $origEl) {
//# store the original $el
if ($origEl == null) {
$origEl = $el
}
//# walk up to each parent until we reach the body
//# if any parent has an effective offsetHeight of 0
//# and its set overflow: hidden then our child element
//# is effectively hidden
//# -----UNLESS------
//# the parent or a descendent has position: absolute|fixed
const elIsHiddenByAncestors = function ($el, $origEl = $el) {
// walk up to each parent until we reach the body
// if any parent has an effective offsetHeight of 0
// and its set overflow: hidden then our child element
// is effectively hidden
// -----UNLESS------
// the parent or a descendent has position: absolute|fixed
const $parent = $el.parent()
//# stop if we've reached the body or html
//# in case there is no body
//# or if parent is the document which can
//# happen if we already have an <html> element
// stop if we've reached the body or html
// in case there is no body
// or if parent is the document which can
// happen if we already have an <html> element
if ($parent.is('body,html') || $document.isDocument($parent)) {
return false
}
if (elHasOverflowHidden($parent) && elHasNoEffectiveWidthOrHeight($parent)) {
//# if any of the elements between the parent and origEl
//# have fixed or position absolute
// if any of the elements between the parent and origEl
// have fixed or position absolute
return !elDescendentsHavePositionFixedOrAbsolute($parent, $origEl)
}
//# continue to recursively walk up the chain until we reach body or html
// continue to recursively walk up the chain until we reach body or html
return elIsHiddenByAncestors($parent, $origEl)
}
const parentHasNoOffsetWidthOrHeightAndOverflowHidden = function ($el) {
//# if we've walked all the way up to body or html then return false
// if we've walked all the way up to body or html then return false
if (!$el.length || $el.is('body,html')) {
return false
}
//# if we have overflow hidden and no effective width or height
// if we have overflow hidden and no effective width or height
if (elHasOverflowHidden($el) && elHasNoEffectiveWidthOrHeight($el)) {
return $el
}
//# continue walking
// continue walking
return parentHasNoOffsetWidthOrHeightAndOverflowHidden($el.parent())
}
const parentHasDisplayNone = function ($el) {
//# if we have no $el or we've walked all the way up to document
//# then return false
// if we have no $el or we've walked all the way up to document
// then return false
if (!$el.length || $document.isDocument($el)) {
return false
}
//# if we have display none then return the $el
// if we have display none then return the $el
if (elHasDisplayNone($el)) {
return $el
}
//# continue walking
// continue walking
return parentHasDisplayNone($el.parent())
}
const parentHasVisibilityNone = function ($el) {
//# if we've walked all the way up to document then return false
const parentHasVisibilityHidden = function ($el) {
// if we've walked all the way up to document then return false
if (!$el.length || $document.isDocument($el)) {
return false
}
//# if we have display none then return the $el
// if we have display none then return the $el
if (elHasVisibilityHidden($el)) {
return $el
}
//# continue walking
return parentHasVisibilityNone($el.parent())
// continue walking
return parentHasVisibilityHidden($el.parent())
}
const getReasonIsHidden = function ($el) {
//# TODO: need to add in the reason an element
//# is hidden when its fixed position and its
//# either being covered or there is no el
const parentHasVisibilityCollapse = function ($el) {
// if we've walked all the way up to document then return false
if (!$el.length || $document.isDocument($el)) {
return false
}
let width
let height
// if we have display none then return the $el
if (elHasVisibilityCollapse($el)) {
return $el
}
// continue walking
return parentHasVisibilityCollapse($el.parent())
}
/* eslint-disable no-cond-assign */
const getReasonIsHidden = function ($el) {
// TODO: need to add in the reason an element
// is hidden when its fixed position and its
// either being covered or there is no el
const node = $elements.stringify($el, 'short')
let width = elOffsetWidth($el)
let height = elOffsetHeight($el)
let $parent
let parentNode
const node = $elements.stringify($el, 'short')
//# returns the reason in human terms why an element is considered not visible
switch (false) {
case !elHasDisplayNone($el):
return `This element '${node}' is not visible because it has CSS property: 'display: none'`
// returns the reason in human terms why an element is considered not visible
if (elHasDisplayNone($el)) {
return `This element '${node}' is not visible because it has CSS property: 'display: none'`
}
case !($parent = parentHasDisplayNone($el.parent())):
parentNode = $elements.stringify($parent, 'short')
if ($parent = parentHasDisplayNone($el.parent())) {
parentNode = $elements.stringify($parent, 'short')
return `This element '${node}' is not visible because its parent '${parentNode}' has CSS property: 'display: none'`
return `This element '${node}' is not visible because its parent '${parentNode}' has CSS property: 'display: none'`
}
case !($parent = parentHasVisibilityNone($el.parent())):
parentNode = $elements.stringify($parent, 'short')
if ($parent = parentHasVisibilityHidden($el.parent())) {
parentNode = $elements.stringify($parent, 'short')
return `This element '${node}' is not visible because its parent '${parentNode}' has CSS property: 'visibility: hidden'`
return `This element '${node}' is not visible because its parent '${parentNode}' has CSS property: 'visibility: hidden'`
}
case !elHasVisibilityHidden($el):
return `This element '${node}' is not visible because it has CSS property: 'visibility: hidden'`
if ($parent = parentHasVisibilityCollapse($el.parent())) {
parentNode = $elements.stringify($parent, 'short')
case !elHasNoOffsetWidthOrHeight($el):
width = elOffsetWidth($el)
height = elOffsetHeight($el)
return `This element '${node}' is not visible because its parent '${parentNode}' has CSS property: 'visibility: collapse'`
}
return `This element '${node}' is not visible because it has an effective width and height of: '${width} x ${height}' pixels.`
if (elHasVisibilityHidden($el)) {
return `This element '${node}' is not visible because it has CSS property: 'visibility: hidden'`
}
case !($parent = parentHasNoOffsetWidthOrHeightAndOverflowHidden($el.parent())):
parentNode = $elements.stringify($parent, 'short')
width = elOffsetWidth($parent)
height = elOffsetHeight($parent)
if (elHasVisibilityCollapse($el)) {
return `This element '${node}' is not visible because it has CSS property: 'visibility: collapse'`
}
return `This element '${node}' is not visible because its parent '${parentNode}' has CSS property: 'overflow: hidden' and an effective width and height of: '${width} x ${height}' pixels.`
if (elHasNoOffsetWidthOrHeight($el)) {
return `This element '${node}' is not visible because it has an effective width and height of: '${width} x ${height}' pixels.`
}
default:
//# nested else --___________--
if (elIsFixed($el)) {
if (elIsNotElementFromPoint($el)) {
//# show the long element here
const covered = $elements.stringify(elAtCenterPoint($el))
if ($parent = parentHasNoOffsetWidthOrHeightAndOverflowHidden($el.parent())) {
parentNode = $elements.stringify($parent, 'short')
width = elOffsetWidth($parent)
height = elOffsetHeight($parent)
return `\
return `This element '${node}' is not visible because its parent '${parentNode}' has CSS property: 'overflow: hidden' and an effective width and height of: '${width} x ${height}' pixels.`
}
// nested else --___________--
if (elOrAncestorIsFixed($el)) {
if (elIsNotElementFromPoint($el)) {
// show the long element here
const covered = $elements.stringify(elAtCenterPoint($el))
return `\
This element '${node}' is not visible because it has CSS property: 'position: fixed' and its being covered by another element:
${covered}\
`
}
} else {
if (elIsOutOfBoundsOfAncestorsOverflow($el)) {
return `This element '${node}' is not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: 'hidden', 'scroll' or 'auto'`
}
}
return `Cypress could not determine why this element '${node}' is not visible.`
}
} else {
if (elIsOutOfBoundsOfAncestorsOverflow($el)) {
return `This element '${node}' is not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: 'hidden', 'scroll' or 'auto'`
}
}
return `Cypress could not determine why this element '${node}' is not visible.`
}
/* eslint-enable no-cond-assign */
module.exports = {
isVisible,
@@ -13,7 +13,7 @@ describe "src/cypress/dom/visibility", ->
fn = ->
$dom.isHidden(null)
expect(fn).to.throw("Cypress.dom.isHidden() must be passed a basic DOM element.")
expect(fn).to.throw("Cypress.dom.isHidden() failed because it requires a DOM element. The subject received was: \'null\'")
context "isVisible", ->
it "exposes isVisible", ->
@@ -21,9 +21,9 @@ describe "src/cypress/dom/visibility", ->
it "throws when not passed a DOM element", ->
fn = ->
$dom.isVisible(null)
$dom.isVisible("form")
expect(fn).to.throw("Cypress.dom.isVisible() must be passed a basic DOM element.")
expect(fn).to.throw("Cypress.dom.isVisible() failed because it requires a DOM element. The subject received was: \'form\'")
context "#isScrollable", ->
beforeEach ->
@@ -116,9 +116,62 @@ describe "src/cypress/dom/visibility", ->
@$visHidden = add "<ul style='visibility: hidden;'></ul>"
@$parentVisHidden = add "<div class='invis' style='visibility: hidden;'><button>parent visibility: hidden</button></div>"
@$displayNone = add "<button style='display: none'>display: none</button>"
@$btnOpacity = add "<button style='opacity: 0'>opacity: 0</button>"
@$inputHidden = add "<input type='hidden' value='abcdef'>"
@$btnOpacity = add "<button style='opacity: 0;'>opacity: 0</button>"
@$divNoWidth = add "<div style='width: 0; height: 100px;'>width: 0</div>"
@$divNoHeight = add "<div style='width: 50px; height: 0px;'>height: 0</div>"
@$optionInSelect = add """
<select>
<option>Naruto</option>
</select>
"""
@$optgroupInSelect = add """
<select>
<optgroup label='Shinobi'>
<option>Naruto</option>
</optgroup>
</select>
"""
@$optionInHiddenSelect = add """
<select style='display: none'>
<option>Sasuke</option>
</select>
"""
@$optionOutsideSelect = add """
<div style='display: none'>
<option id='option-hidden'>Sasuke</option>
</div>
<div>
<option id='option-visible'>Naruto</option>
</div>
"""
@$optionHiddenInSelect = add """
<select>
<option>--Select--</option>
<option id="hidden-opt" style='display: none'>Sakura</option>
</select>
"""
@$tableVisCollapse = add """
<table>
<tr>
<td>Naruto</td>
<td class='collapse' style='visibility: collapse;'>Sasuke</td>
<td>Sakura</td>
</tr>
<tr class='collapse' style='visibility: collapse;'>
<td>Kaguya</td>
<td><span id='collapse-span'>Madara</span></td>
<td>Orochimaro</td>
</tr>
</table>
"""
@$parentNoWidth = add """
<div style='width: 0; height: 100px; overflow: hidden;'>
<div style='height: 500px; width: 500px;'>
@@ -238,6 +291,21 @@ describe "src/cypress/dom/visibility", ->
</div>
"""
@$elOutOfParentWithFlexAndOverflowHiddenBounds = add """
<div style="display: flex; overflow: hidden;">
<div id="red" style="flex: 0 0 80%; background-color: red;">red</div>
<div id="green" style="flex: 0 0 80%; background-color: green;">green</div>
<div id="blue" style="background-color: blue;">blue</div>
</div>
"""
@$elOutOfParentWithOverflowHiddenBoundsButCloserPositionAbsoluteParent = add """
<div style="border: 1px solid red; width: 200px; height: 200px; overflow: hidden;">
<div style="position: absolute; left: 300px; border: 1px solid blue; width: 200px; height: 200px;">
<span style="border: 1px solid green;">Hello</span>
</div>
"""
@$elOutOfAncestorOverflowAutoBounds = add """
<div style='width: 100px; height: 100px; overflow: auto;'>
<div style='width: 1000px; position: relative;'>
@@ -318,8 +386,41 @@ describe "src/cypress/dom/visibility", ->
</div>
"""
add """
<div id="ancestorTransformMakesElOutOfBoundsOfAncestor" style='margin-left: 100px; overflow: hidden; width: 100px;'>
@$parentWithClipPathAbsolutePositionElOutsideClipPath = add """
<div style="position: absolute; clip-path: polygon(0 0, 0 0, 0 0, 0 0);">
<span>clip-path</span>
</div>
"""
@$parentWithClipPathAbsolutePositionElInsideClipPath = add """
<div style="position: absolute; clip-path: circle(100%);">
<span>clip-path</span>
</div>
"""
@$parentWithTransformScaleElOutsideScale = add """
<div style="transform: scale(0,0)">
<span>TRANSFORMERS</span>
</div>
"""
@$parentWithTransformScaleElInsideScale = add """
<div style="transform: scale(1,1)">
<span>TRANSFORMERS</span>
</div>
"""
@$parentsWithBackfaceVisibilityHidden = add """
<div style="position: absolute; width: 200px; height: 260px; background: red; backface-visibility: hidden;">
<span id="front">front</span>
</div>
<div style="position: absolute; width: 200px; height: 260px; background: blue; backface-visibility: hidden; transform: rotateY(180deg);">
<span id="back" >back</span>
</div>
"""
@$ancestorTransformMakesElOutOfBoundsOfAncestor = add """
<div style='margin-left: 100px; overflow: hidden; width: 100px;'>
<div style='transform: translateX(-100px); width: 200px;'>
<div style='width: 100px;'>
<span>out of ancestor's bounds due to ancestor translate</span>
@@ -328,13 +429,13 @@ describe "src/cypress/dom/visibility", ->
</div>
"""
add """
<div id="ancestorTransformMakesElInBoundsOfAncestor" style='margin-left: 100px; overflow: hidden; width: 100px;'>
@$ancestorTransformMakesElInBoundsOfAncestor = add """
<div style='margin-left: 100px; overflow: hidden; width: 100px;'>
<div style='transform: translateX(-100px); width: 300px;'>
<div style='display: inline-block; width: 100px;'>
<span>out of ancestor's bounds due to ancestor translate</span>
</div>
<div style='display: inline-block; width: 100px;'>
<div id='inbounds' style='display: inline-block; width: 100px;'>
<span>in ancestor's bounds due to ancestor translate</span>
</div>
</div>
@@ -346,144 +447,286 @@ describe "src/cypress/dom/visibility", ->
# its factored in (window vs viewport) calculations
scrollThisIntoView.get(1).scrollIntoView()
it "is hidden if .css(visibility) is hidden", ->
expect(@$visHidden.is(":hidden")).to.be.true
expect(@$visHidden.is(":visible")).to.be.false
describe "html or body", ->
it "is visible if html", ->
expect(cy.$$("html").is(":hidden")).to.be.false
expect(cy.$$("html").is(":visible")).to.be.true
expect(@$visHidden).to.be.hidden
expect(@$visHidden).to.not.be.visible
expect(cy.$$("html")).not.to.be.hidden
expect(cy.$$("html")).to.be.visible
cy.wrap(@$visHidden).should("be.hidden")
cy.wrap(@$visHidden).should("not.be.visible")
cy.wrap(cy.$$("html")).should("not.be.hidden")
cy.wrap(cy.$$("html")).should("be.visible")
it "is hidden if parents have .css(visibility) hidden", ->
expect(@$parentVisHidden.find("button").is(":hidden")).to.be.true
expect(@$parentVisHidden.find("button").is(":visible")).to.be.false
it "is visible if body", ->
expect(cy.$$("body").is(":hidden")).to.be.false
expect(cy.$$("body").is(":visible")).to.be.true
expect(@$parentVisHidden.find("button")).to.be.hidden
expect(@$parentVisHidden.find("button")).to.not.be.visible
expect(cy.$$("body")).not.to.be.hidden
expect(cy.$$("body")).to.be.visible
cy.wrap(@$parentVisHidden).find("button").should("be.hidden")
cy.wrap(@$parentVisHidden).find("button").should("not.be.visible")
cy.wrap(cy.$$("body")).should("not.be.hidden")
cy.wrap(cy.$$("body")).should("be.visible")
it "is visible if opacity is 0", ->
expect(@$btnOpacity.is(":hidden")).to.be.false
expect(@$btnOpacity.is(":visible")).to.be.true
it "is visible if display none on body or html", ->
cy.$$("html").css("display", "none")
cy.$$("body").css("display", "none")
expect(@$btnOpacity).not.to.be.hidden
expect(@$btnOpacity).to.be.visible
expect(cy.$$("html")).not.to.be.hidden
expect(cy.$$("html")).to.be.visible
cy.wrap(@$btnOpacity).should("not.be.hidden")
cy.wrap(@$btnOpacity).should("be.visible")
expect(cy.$$("body")).not.to.be.hidden
expect(cy.$$("body")).to.be.visible
it "is hidden if offsetWidth is 0", ->
expect(@$divNoWidth.is(":hidden")).to.be.true
expect(@$divNoWidth.is(":visible")).to.be.false
describe "css visibility", ->
it "is hidden if .css(visibility) is hidden", ->
expect(@$visHidden.is(":hidden")).to.be.true
expect(@$visHidden.is(":visible")).to.be.false
expect(@$divNoWidth).to.be.hidden
expect(@$divNoWidth).to.not.be.visible
expect(@$visHidden).to.be.hidden
expect(@$visHidden).to.not.be.visible
cy.wrap(@$divNoWidth).should("be.hidden")
cy.wrap(@$divNoWidth).should("not.be.visible")
cy.wrap(@$visHidden).should("be.hidden")
cy.wrap(@$visHidden).should("not.be.visible")
it "is hidden if parent has overflow: hidden and no width", ->
expect(@$parentNoWidth.find("span")).to.be.hidden
expect(@$parentNoWidth.find("span")).to.not.be.visible
it "is hidden if parents have .css(visibility) hidden", ->
expect(@$parentVisHidden.find("button").is(":hidden")).to.be.true
expect(@$parentVisHidden.find("button").is(":visible")).to.be.false
it "is hidden if parent has overflow: hidden and no height", ->
expect(@$parentNoHeight.find("span")).to.be.hidden
expect(@$parentNoHeight.find("span")).to.not.be.visible
expect(@$parentVisHidden.find("button")).to.be.hidden
expect(@$parentVisHidden.find("button")).to.not.be.visible
it "is visible when parent has positive dimensions even with overflow hidden", ->
expect(@$parentWithWidthHeightNoOverflow.find("span")).to.be.visible
expect(@$parentWithWidthHeightNoOverflow.find("span")).to.not.be.hidden
cy.wrap(@$parentVisHidden).find("button").should("be.hidden")
cy.wrap(@$parentVisHidden).find("button").should("not.be.visible")
it "is visible if child has position: absolute", ->
expect(@$childPosAbs.find("span")).to.be.visible
expect(@$childPosAbs.find("span")).not.be.hidden
it "is hidden if visibility collapse", ->
expect(@$tableVisCollapse.find("td.collapse")).to.be.hidden
expect(@$tableVisCollapse.find("td.collapse")).to.not.be.visible
it "is visible if child has position: fixed", ->
expect(@$childPosFixed.find("button")).to.be.visible
expect(@$childPosFixed.find("button")).not.to.be.hidden
expect(@$tableVisCollapse.find("tr.collapse")).to.be.hidden
expect(@$tableVisCollapse.find("tr.collapse")).to.not.be.visible
it "is visible if descendent from parent has position: fixed", ->
expect(@$descendentPosFixed.find("button")).to.be.visible
expect(@$descendentPosFixed.find("button")).not.to.be.hidden
expect(@$tableVisCollapse.find("tr.collapse td")).to.be.hidden
expect(@$tableVisCollapse.find("tr.collapse td")).to.not.be.visible
it "is visible if has position: fixed and descendent is found", ->
expect(@$descendantInPosFixed.find("#descendantInPosFixed")).to.be.visible
expect(@$descendantInPosFixed.find("#descendantInPosFixed")).not.to.be.hidden
it "is hidden if parent has visibility collapse", ->
expect(@$tableVisCollapse.find("tr.collapse td")).to.be.hidden
expect(@$tableVisCollapse.find("tr.collapse td")).to.not.be.visible
it "is hidden if position: fixed and covered up", ->
expect(@$coveredUpPosFixed.find("#coveredUpPosFixed")).to.be.hidden
expect(@$coveredUpPosFixed.find("#coveredUpPosFixed")).not.to.be.visible
expect(@$tableVisCollapse.find("#collapse-span")).to.be.hidden
expect(@$tableVisCollapse.find("#collapse-span")).to.not.be.visible
it "is hidden if position: fixed and off screent", ->
expect(@$offScreenPosFixed).to.be.hidden
expect(@$offScreenPosFixed).not.to.be.visible
it "is hidden if input type hidden", ->
expect(@$inputHidden.is(":hidden")).to.be.true
expect(@$inputHidden.is(":visible")).to.be.false
it "is visible if descendent from parent has position: absolute", ->
expect(@$descendentPosAbs.find("span")).to.be.visible
expect(@$descendentPosAbs.find("span")).to.not.be.hidden
expect(@$inputHidden).to.be.hidden
expect(@$inputHidden).to.not.be.visible
it "is hidden if only the parent has position absolute", ->
expect(@$parentPosAbs.find("span")).to.be.hidden
expect(@$parentPosAbs.find("span")).to.not.be.visible
cy.wrap(@$inputHidden).should("be.hidden")
cy.wrap(@$inputHidden).should("not.be.visible")
it "is visible when parent doesnt have overflow hidden", ->
expect(@$parentNoWidthHeightOverflowAuto.find("span")).to.be.visible
expect(@$parentNoWidthHeightOverflowAuto.find("span")).to.not.be.hidden
describe "option and optgroup", ->
it "is visible if option in visible select", ->
expect(@$optionInSelect.find('option').is(":hidden")).to.be.false
expect(@$optionInSelect.find('option').is(":visible")).to.be.true
it "is hidden when parent overflow hidden and out of bounds to left", ->
expect(@$elOutOfParentBoundsToLeft.find("span")).to.be.hidden
expect(@$optionInSelect.find('option')).not.to.be.hidden
expect(@$optionInSelect.find('option')).to.be.visible
it "is hidden when parent overflow hidden and out of bounds to right", ->
expect(@$elOutOfParentBoundsToRight.find("span")).to.be.hidden
cy.wrap(@$optionInSelect.find('option')).should("not.be.hidden")
cy.wrap(@$optionInSelect.find('option')).should("be.visible")
it "is hidden when parent overflow hidden and out of bounds above", ->
expect(@$elOutOfParentBoundsAbove.find("span")).to.be.hidden
it "is visible if optgroup in visible select", ->
expect(@$optgroupInSelect.find('optgroup').is(":hidden")).to.be.false
expect(@$optgroupInSelect.find('optgroup').is(":visible")).to.be.true
it "is hidden when parent overflow hidden and out of bounds below", ->
expect(@$elOutOfParentBoundsBelow.find("span")).to.be.hidden
expect(@$optgroupInSelect.find('optgroup')).not.to.be.hidden
expect(@$optgroupInSelect.find('optgroup')).to.be.visible
it "is hidden when parent overflow-y hidden and out of bounds", ->
expect(@$elOutOfParentWithOverflowYHiddenBounds.find("span")).to.be.hidden
cy.wrap(@$optgroupInSelect.find('optgroup')).should("not.be.hidden")
cy.wrap(@$optgroupInSelect.find('optgroup')).should("be.visible")
it "is hidden when parent overflow-x hidden and out of bounds", ->
expect(@$elOutOfParentWithOverflowXHiddenBounds.find("span")).to.be.hidden
it "is hidden if option in hidden select", ->
expect(@$optionInHiddenSelect.find('option').is(":hidden")).to.be.true
expect(@$optionInHiddenSelect.find('option').is(":visible")).to.be.false
it "is hidden when parent is wide and ancestor is overflow auto", ->
expect(@$elOutOfAncestorOverflowAutoBounds.find("span")).to.be.hidden
expect(@$optionInHiddenSelect.find('option')).to.be.hidden
expect(@$optionInHiddenSelect.find('option')).not.to.be.visible
it "is hidden when parent overflow scroll and out of bounds", ->
expect(@$elOutOfScrollingParentBounds.find("span")).to.be.hidden
cy.wrap(@$optionInHiddenSelect.find('option')).should("be.hidden")
cy.wrap(@$optionInHiddenSelect.find('option')).should("not.be.visible")
it "is hidden when parent absolutely positioned and overflow hidden and out of bounds", ->
expect(@$elOutOfPosAbsParentsBounds.find("span")).to.be.hidden
it "is hidden if option is display none", ->
expect(@$optionHiddenInSelect.find('#hidden-opt').is(":hidden")).to.be.true
expect(@$optionHiddenInSelect.find('#hidden-opt').is(":visible")).to.be.false
it "is visible when parent absolutely positioned and overflow hidden and not out of bounds", ->
expect(@$elInPosAbsParentsBounds.find("span")).to.be.visible
expect(@$optionHiddenInSelect.find('#hidden-opt')).to.be.hidden
expect(@$optionHiddenInSelect.find('#hidden-opt')).not.to.be.visible
it "is visible when parent overflow hidden and not out of bounds", ->
expect(@$elInParentBounds.find("span")).to.be.visible
cy.wrap(@$optionHiddenInSelect.find('#hidden-opt')).should("be.hidden")
cy.wrap(@$optionHiddenInSelect.find('#hidden-opt')).should("not.be.visible")
it "is visible when ancestor is overflow hidden but more distant ancestor is the offset parent", ->
expect(@$elIsOutOfBoundsOfAncestorsOverflowButWithinRelativeAncestor.find("span")).to.be.visible
it "follows regular visibility logic if option outside of select", ->
expect(@$optionOutsideSelect.find('#option-hidden').is(":hidden")).to.be.true
expect(@$optionOutsideSelect.find('#option-hidden')).to.be.hidden
cy.wrap(@$optionOutsideSelect.find('#option-hidden')).should("be.hidden")
it "is hidden when relatively positioned outside ancestor with overflow hidden", ->
expect(@$elIsRelativeAndOutOfBoundsOfAncestorOverflow.find("span")).to.be.hidden
expect(@$optionOutsideSelect.find('#option-visible').is(":visible")).to.be.true
expect(@$optionOutsideSelect.find('#option-visible')).to.be.visible
cy.wrap(@$optionOutsideSelect.find('#option-visible')).should("be.visible")
describe "opacity visible", ->
it "is visible if opacity is 0", ->
expect(@$btnOpacity.is(":hidden")).to.be.false
expect(@$btnOpacity.is(":visible")).to.be.true
it "is visible when el is relatively positioned outside ancestor that does not hide overflow", ->
expect(@$elIsRelativeAndOutOfBoundsOfAncestorButAncestorShowsOverflow.find("span")).to.be.visible
expect(@$btnOpacity).not.to.be.hidden
expect(@$btnOpacity).to.be.visible
it "is visible when parent is relatively positioned out of bounds but el is relatively positioned back in bounds", ->
expect(@$parentOutOfBoundsButElInBounds.find("span")).to.be.visible
cy.wrap(@$btnOpacity).should("not.be.hidden")
cy.wrap(@$btnOpacity).should("be.visible")
it "is hidden when out of ancestor's bounds due to ancestor's transform", ->
cy.get("#ancestorTransformMakesElOutOfBoundsOfAncestor span").should("be.hidden")
describe "width and height", ->
it "is hidden if offsetWidth is 0", ->
expect(@$divNoWidth.is(":hidden")).to.be.true
expect(@$divNoWidth.is(":visible")).to.be.false
it "is visible when in ancestor's bounds due to ancestor's transform", ->
cy.get("#ancestorTransformMakesElInBoundsOfAncestor span").eq(1).should("be.visible")
expect(@$divNoWidth).to.be.hidden
expect(@$divNoWidth).to.not.be.visible
cy.wrap(@$divNoWidth).should("be.hidden")
cy.wrap(@$divNoWidth).should("not.be.visible")
it "is hidden if parent has overflow: hidden and no width", ->
expect(@$parentNoWidth.find("span")).to.be.hidden
expect(@$parentNoWidth.find("span")).to.not.be.visible
it "is hidden if parent has overflow: hidden and no height", ->
expect(@$parentNoHeight.find("span")).to.be.hidden
expect(@$parentNoHeight.find("span")).to.not.be.visible
it "is visible when parent has positive dimensions even with overflow hidden", ->
expect(@$parentWithWidthHeightNoOverflow.find("span")).to.be.visible
expect(@$parentWithWidthHeightNoOverflow.find("span")).to.not.be.hidden
describe "css position", ->
it "is visible if child has position: absolute", ->
expect(@$childPosAbs.find("span")).to.be.visible
expect(@$childPosAbs.find("span")).not.be.hidden
it "is visible if child has position: fixed", ->
expect(@$childPosFixed.find("button")).to.be.visible
expect(@$childPosFixed.find("button")).not.to.be.hidden
it "is visible if descendent from parent has position: fixed", ->
expect(@$descendentPosFixed.find("button")).to.be.visible
expect(@$descendentPosFixed.find("button")).not.to.be.hidden
it "is visible if has position: fixed and descendent is found", ->
expect(@$descendantInPosFixed.find("#descendantInPosFixed")).to.be.visible
expect(@$descendantInPosFixed.find("#descendantInPosFixed")).not.to.be.hidden
it "is hidden if position: fixed and covered up", ->
expect(@$coveredUpPosFixed.find("#coveredUpPosFixed")).to.be.hidden
expect(@$coveredUpPosFixed.find("#coveredUpPosFixed")).not.to.be.visible
it "is hidden if position: fixed and off screent", ->
expect(@$offScreenPosFixed).to.be.hidden
expect(@$offScreenPosFixed).not.to.be.visible
it "is visible if descendent from parent has position: absolute", ->
expect(@$descendentPosAbs.find("span")).to.be.visible
expect(@$descendentPosAbs.find("span")).to.not.be.hidden
it "is hidden if only the parent has position absolute", ->
expect(@$parentPosAbs.find("span")).to.be.hidden
expect(@$parentPosAbs.find("span")).to.not.be.visible
describe "css overflow", ->
it "is visible when parent doesnt have overflow hidden", ->
expect(@$parentNoWidthHeightOverflowAuto.find("span")).to.be.visible
expect(@$parentNoWidthHeightOverflowAuto.find("span")).to.not.be.hidden
it "is hidden when parent overflow hidden and out of bounds to left", ->
expect(@$elOutOfParentBoundsToLeft.find("span")).to.be.hidden
it "is hidden when parent overflow hidden and out of bounds to right", ->
expect(@$elOutOfParentBoundsToRight.find("span")).to.be.hidden
it "is hidden when parent overflow hidden and out of bounds above", ->
expect(@$elOutOfParentBoundsAbove.find("span")).to.be.hidden
it "is hidden when parent overflow hidden and out of bounds below", ->
expect(@$elOutOfParentBoundsBelow.find("span")).to.be.hidden
it "is hidden when parent overflow-y hidden and out of bounds", ->
expect(@$elOutOfParentWithOverflowYHiddenBounds.find("span")).to.be.hidden
it "is hidden when parent overflow-x hidden and out of bounds", ->
expect(@$elOutOfParentWithOverflowXHiddenBounds.find("span")).to.be.hidden
it "is visible when parent overflow hidden but el in a closer parent with position absolute", ->
expect(@$elOutOfParentWithOverflowHiddenBoundsButCloserPositionAbsoluteParent.find("span")).to.be.visible
it "is hidden when parent flex and overflow hidden and el out of bounds", ->
expect(@$elOutOfParentWithFlexAndOverflowHiddenBounds.find("#red")).to.be.visible
expect(@$elOutOfParentWithFlexAndOverflowHiddenBounds.find("#green")).to.be.visible
expect(@$elOutOfParentWithFlexAndOverflowHiddenBounds.find("#blue")).to.be.hidden
it "is hidden when parent is wide and ancestor is overflow auto", ->
expect(@$elOutOfAncestorOverflowAutoBounds.find("span")).to.be.hidden
it "is hidden when parent overflow scroll and out of bounds", ->
expect(@$elOutOfScrollingParentBounds.find("span")).to.be.hidden
it "is hidden when parent absolutely positioned and overflow hidden and out of bounds", ->
expect(@$elOutOfPosAbsParentsBounds.find("span")).to.be.hidden
it "is visible when parent absolutely positioned and overflow hidden and not out of bounds", ->
expect(@$elInPosAbsParentsBounds.find("span")).to.be.visible
it "is visible when parent overflow hidden and not out of bounds", ->
expect(@$elInParentBounds.find("span")).to.be.visible
it "is visible when ancestor is overflow hidden but more distant ancestor is the offset parent", ->
expect(@$elIsOutOfBoundsOfAncestorsOverflowButWithinRelativeAncestor.find("span")).to.be.visible
it "is hidden when relatively positioned outside ancestor with overflow hidden", ->
expect(@$elIsRelativeAndOutOfBoundsOfAncestorOverflow.find("span")).to.be.hidden
it "is visible when el is relatively positioned outside ancestor that does not hide overflow", ->
expect(@$elIsRelativeAndOutOfBoundsOfAncestorButAncestorShowsOverflow.find("span")).to.be.visible
it "is visible when parent is relatively positioned out of bounds but el is relatively positioned back in bounds", ->
expect(@$parentOutOfBoundsButElInBounds.find("span")).to.be.visible
describe "css clip-path", ->
it.skip "is hidden when outside of parents clip-path", ->
expect(@$parentWithClipPathAbsolutePositionElOutsideClipPath.find("span")).to.be.hidden
it "is visible when inside of parents clip-path", ->
expect(@$parentWithClipPathAbsolutePositionElInsideClipPath.find("span")).to.be.visible
describe "css transform", ->
it.skip "is hidden when outside parents transform scale", ->
expect(@$parentWithTransformScaleElOutsideScale.find("span")).to.be.hidden
it "is visible when inside of parents transform scale", ->
expect(@$parentWithTransformScaleElInsideScale.find("span")).to.be.visible
it "is hidden when out of ancestor's bounds due to ancestor's transform", ->
expect(@$ancestorTransformMakesElOutOfBoundsOfAncestor.find("span")).to.be.hidden
it "is visible when in ancestor's bounds due to ancestor's transform", ->
expect(@$ancestorTransformMakesElInBoundsOfAncestor.find("#inbounds")).to.be.visible
describe "css backface-visibility", ->
it "is visible when backface not visible", ->
expect(@$parentsWithBackfaceVisibilityHidden.find("#front")).to.be.visible
it.skip "is hidden when backface visible", ->
expect(@$parentsWithBackfaceVisibilityHidden.find("#back")).to.be.hidden
describe "#getReasonIsHidden", ->
beforeEach ->
@@ -502,6 +745,12 @@ describe "src/cypress/dom/visibility", ->
it "has parent with 'visibility: hidden'", ->
@reasonIs @$parentVisHidden.find("button"), "This element '<button>' is not visible because its parent '<div.invis>' has CSS property: 'visibility: hidden'"
it "has 'visibility: collapse'", ->
@reasonIs @$tableVisCollapse.find("td.collapse"), "This element '<td.collapse>' is not visible because it has CSS property: 'visibility: collapse'"
it "has parent with 'visibility: collapse'", ->
@reasonIs @$tableVisCollapse.find("tr.collapse td:first"), "This element '<td>' is not visible because its parent '<tr.collapse>' has CSS property: 'visibility: collapse'"
it "has effective zero width", ->
@reasonIs @$divNoWidth, "This element '<div>' is not visible because it has an effective width and height of: '0 x 100' pixels."