From 0f196c141a827ca3f52dd4769602d34ea6c9ec41 Mon Sep 17 00:00:00 2001 From: KHeo Date: Thu, 19 Dec 2019 09:53:53 +0900 Subject: [PATCH 1/3] Transform visibility when parent height/width is set. --- packages/driver/src/dom/visibility.js | 37 +++++++++++-------- .../integration/dom/visibility_spec.js | 28 ++++++++++++-- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/packages/driver/src/dom/visibility.js b/packages/driver/src/dom/visibility.js index 64d20333356d..1ca206d8cdb2 100644 --- a/packages/driver/src/dom/visibility.js +++ b/packages/driver/src/dom/visibility.js @@ -123,7 +123,7 @@ const elHasVisibilityHidden = ($el) => { return $el.css('visibility') === 'hidden' } -const numberRegex = /-?[0-9]+(?:\.[0-9]+)?/g +const numberRegex = /-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/g // This is a simplified version of backface culling. // https://en.wikipedia.org/wiki/Back-face_culling // @@ -169,12 +169,10 @@ const elIsHiddenByTransform = ($el) => { const style = window.getComputedStyle(el) const transform = style.getPropertyValue('transform') - // Test scaleZ(0) - // width or height of getBoundingClientRect aren't 0 when scaleZ(0). - // But it is invisible. - // Experiment -> https://codepen.io/sainthkh/pen/LYYQGpm - // That's why we're checking transfomation matrix here. - // + if (transform === 'none') { + return false + } + // To understand how this part works, // you need to understand tranformation matrix first. // Matrix is hard to explain with only text. So, check these articles. @@ -185,19 +183,28 @@ const elIsHiddenByTransform = ($el) => { if (transform.startsWith('matrix3d')) { const m3d = transform.substring(8).match(numberRegex) - // Z Axis values - if (+m3d[2] === 0 && +m3d[6] === 0 && +m3d[10] === 0) { + // Test scale to 0 + if ((+m3d[0] === 0 && +m3d[4] === 0 && +m3d[8] === 0) || // X Axis + (+m3d[1] === 0 && +m3d[5] === 0 && +m3d[9] === 0) || // Y Axis + (+m3d[2] === 0 && +m3d[6] === 0 && +m3d[10] === 0)) { // Z Axis return true } + + // Test rotate 90deg + const defaultNormal = [0, 0, -1] + const elNormal = findNormal(m3d) + // Simplified dot product. + // [0] and [1] are always 0 + const dot = defaultNormal[2] * elNormal[2] + + return Math.abs(dot) <= 1e-10 } - // Other cases - if (transform !== 'none') { - const { width, height } = el.getBoundingClientRect() + const m = transform.match(numberRegex) - if (width === 0 || height === 0) { - return true - } + if ((+m[0] === 0 && +m[2] === 0) || // X Axis + (+m[1] === 0 && +m[3] === 0)) { // Y Axis + return true } return false diff --git a/packages/driver/test/cypress/integration/dom/visibility_spec.js b/packages/driver/test/cypress/integration/dom/visibility_spec.js index 4bbcb1f28e3b..13f95544fbbe 100644 --- a/packages/driver/test/cypress/integration/dom/visibility_spec.js +++ b/packages/driver/test/cypress/integration/dom/visibility_spec.js @@ -801,11 +801,11 @@ describe('src/cypress/dom/visibility', () => { }) describe('css transform', () => { - describe('element visibility by css transform', () => { - const add = (el) => { - return $(el).appendTo(cy.$$('body')) - } + const add = (el) => { + return $(el).appendTo(cy.$$('body')) + } + describe('element visibility by css transform', () => { it('is visible when an element is translated a bit', () => { const el = add(`
Translated
`) @@ -899,6 +899,26 @@ describe('src/cypress/dom/visibility', () => { }) }) + describe('when parent height/width is set', () => { + it('is visible when transform is not 0, but height is 0', () => { + const el = add('

Text

') + + expect(el.find('#tr-p-0')).to.be.visible + }) + + it('is visible when transform is not 0, but width is 0', () => { + const el = add('

Test

') + + expect(el.find('#tr-p-1')).to.be.visible + }) + + it('is invisible when transform is 0, but height is not 0', () => { + const el = add('

Test

') + + expect(el.find('#tr-p-2')).to.be.hidden + }) + }) + it('is hidden when outside parents transform scale', function () { expect(this.$parentWithTransformScaleElOutsideScale.find('span')).to.be.hidden }) From e2765040fc5e35634144c1f38fec37b8ca2cd51d Mon Sep 17 00:00:00 2001 From: KHeo Date: Thu, 19 Dec 2019 17:36:32 +0900 Subject: [PATCH 2/3] width: 0 or height: 0 + transform != 'none' => visible. --- packages/driver/src/dom/visibility.js | 26 ++++++++++++++----- .../integration/dom/visibility_spec.js | 18 ++++++++++--- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/packages/driver/src/dom/visibility.js b/packages/driver/src/dom/visibility.js index 1ca206d8cdb2..5cd90afd8f94 100644 --- a/packages/driver/src/dom/visibility.js +++ b/packages/driver/src/dom/visibility.js @@ -100,7 +100,17 @@ const elHasNoEffectiveWidthOrHeight = ($el) => { // 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) + // From https://github.com/cypress-io/cypress/issues/5974, + // we learned that when an element has non-'none' transform style value like "translate(0, 0)", + // it is visible even with `height: 0` or `width: 0`. + // That's why we're checking `transform === 'none'` together with elOffsetWidth/Height. + + const style = elComputedStyle($el) + const transform = style.getPropertyValue('transform') + + return (elOffsetWidth($el) <= 0 && transform === 'none') || + (elOffsetHeight($el) <= 0 && transform === 'none') || + ($el[0].getClientRects().length <= 0) } const elHasNoOffsetWidthOrHeight = ($el) => { @@ -123,6 +133,12 @@ const elHasVisibilityHidden = ($el) => { return $el.css('visibility') === 'hidden' } +const elComputedStyle = ($el) => { + const el = $el[0] + + return getComputedStyle(el) +} + const numberRegex = /-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/g // This is a simplified version of backface culling. // https://en.wikipedia.org/wiki/Back-face_culling @@ -131,8 +147,7 @@ const numberRegex = /-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/g // and default normal vector, (0, 0, 1) // When dot product of them are >= 0, item is visible. const elIsBackface = ($el) => { - const el = $el[0] - const style = getComputedStyle(el) + const style = elComputedStyle($el) const backface = style.getPropertyValue('backface-visibility') const backfaceInvisible = backface === 'hidden' const transform = style.getPropertyValue('transform') @@ -163,10 +178,7 @@ const elHasVisibilityCollapse = ($el) => { // This function checks 2 things that can happen: scale and rotate const elIsHiddenByTransform = ($el) => { - // We need to see the final calculation of the element. - const el = $el[0] - - const style = window.getComputedStyle(el) + const style = elComputedStyle($el) const transform = style.getPropertyValue('transform') if (transform === 'none') { diff --git a/packages/driver/test/cypress/integration/dom/visibility_spec.js b/packages/driver/test/cypress/integration/dom/visibility_spec.js index 13f95544fbbe..945a2833c11d 100644 --- a/packages/driver/test/cypress/integration/dom/visibility_spec.js +++ b/packages/driver/test/cypress/integration/dom/visibility_spec.js @@ -899,20 +899,32 @@ describe('src/cypress/dom/visibility', () => { }) }) - describe('when parent height/width is set', () => { + describe('when height/width is set', () => { it('is visible when transform is not 0, but height is 0', () => { + const el = add('
Text
') + + expect(el).to.be.visible + }) + + it('is visible when transform is not 0, but width is 0', () => { + const el = add('

Text

') + + expect(el).to.be.visible + }) + + it('is visible when parent transform is not 0, but height is 0', () => { const el = add('

Text

') expect(el.find('#tr-p-0')).to.be.visible }) - it('is visible when transform is not 0, but width is 0', () => { + it('is visible when parent transform is not 0, but width is 0', () => { const el = add('

Test

') expect(el.find('#tr-p-1')).to.be.visible }) - it('is invisible when transform is 0, but height is not 0', () => { + it('is invisible when parent transform is 0, but height is not 0', () => { const el = add('

Test

') expect(el.find('#tr-p-2')).to.be.hidden From c729573e37d900b13c58722224242a4589137a84 Mon Sep 17 00:00:00 2001 From: KHeo Date: Tue, 24 Dec 2019 16:26:58 +0900 Subject: [PATCH 3/3] Refactor transform checker functions. --- packages/driver/src/dom/visibility.js | 52 +++++++++++++++++++-------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/packages/driver/src/dom/visibility.js b/packages/driver/src/dom/visibility.js index 5cd90afd8f94..805cf62c8f3b 100644 --- a/packages/driver/src/dom/visibility.js +++ b/packages/driver/src/dom/visibility.js @@ -193,35 +193,57 @@ const elIsHiddenByTransform = ($el) => { // https://en.wikipedia.org/wiki/Rotation_matrix#In_three_dimensions // if (transform.startsWith('matrix3d')) { - const m3d = transform.substring(8).match(numberRegex) + const matrix3d = transform.substring(8).match(numberRegex) - // Test scale to 0 - if ((+m3d[0] === 0 && +m3d[4] === 0 && +m3d[8] === 0) || // X Axis - (+m3d[1] === 0 && +m3d[5] === 0 && +m3d[9] === 0) || // Y Axis - (+m3d[2] === 0 && +m3d[6] === 0 && +m3d[10] === 0)) { // Z Axis + if (is3DMatrixScaledTo0(matrix3d)) { return true } - // Test rotate 90deg - const defaultNormal = [0, 0, -1] - const elNormal = findNormal(m3d) - // Simplified dot product. - // [0] and [1] are always 0 - const dot = defaultNormal[2] * elNormal[2] - - return Math.abs(dot) <= 1e-10 + return isElementOrthogonalWithView(matrix3d) } const m = transform.match(numberRegex) - if ((+m[0] === 0 && +m[2] === 0) || // X Axis - (+m[1] === 0 && +m[3] === 0)) { // Y Axis + if (is2DMatrixScaledTo0(m)) { + return true + } + + return false +} + +const is3DMatrixScaledTo0 = (m3d) => { + const xAxisScaledTo0 = +m3d[0] === 0 && +m3d[4] === 0 && +m3d[8] === 0 + const yAxisScaledTo0 = +m3d[1] === 0 && +m3d[5] === 0 && +m3d[9] === 0 + const zAxisScaledTo0 = +m3d[2] === 0 && +m3d[6] === 0 && +m3d[10] === 0 + + if (xAxisScaledTo0 || yAxisScaledTo0 || zAxisScaledTo0) { + return true + } + + return false +} + +const is2DMatrixScaledTo0 = (m) => { + const xAxisScaledTo0 = +m[0] === 0 && +m[2] === 0 + const yAxisScaledTo0 = +m[1] === 0 && +m[3] === 0 + + if (xAxisScaledTo0 || yAxisScaledTo0) { return true } return false } +const isElementOrthogonalWithView = (matrix3d) => { + const defaultNormal = [0, 0, -1] + const elNormal = findNormal(matrix3d) + // Simplified dot product. + // [0] and [1] are always 0 + const dot = defaultNormal[2] * elNormal[2] + + return Math.abs(dot) <= 1e-10 +} + const elHasDisplayNone = ($el) => { return $el.css('display') === 'none' }