UNPKG

cypress-layout-inspector

Version:

Simple utility to provide layout testing functionality to Cypress

446 lines (432 loc) 14.2 kB
"use strict"; // src/utils/configure.ts var config = { excludePadding: false, threshold: 0 }; var configure = (opts) => { config = { ...config, ...opts }; }; var getConfiguration = (key) => config[key]; // src/utils/element.ts var getElement = (subject) => { if (Cypress.dom.isJquery(subject)) return subject; return cy.$$(subject); }; var unwrapElement = (element) => { return Cypress.dom.isJquery(element) ? element[0] : element; }; var removePadding = (properties, element) => { const el = unwrapElement(element); const { paddingTop, paddingRight, paddingBottom, paddingLeft } = getComputedStyle(el); return { width: properties.width - (parseFloat(paddingLeft) + parseFloat(paddingRight)), height: properties.height - (parseFloat(paddingTop) + parseFloat(paddingBottom)), top: properties.top + parseFloat(paddingTop), right: properties.right - parseFloat(paddingRight), bottom: properties.bottom - parseFloat(paddingBottom), left: properties.left + parseFloat(paddingLeft) }; }; var getElementProperties = (subject) => { const element = getElement(subject); const positions = Cypress.dom.getElementPositioning(element); let properties = { ...Cypress._.pick(positions, ["width", "height"]), ...Cypress._.pick(positions.fromElViewport, [ "top", "right", "bottom", "left" ]) }; if (getConfiguration("excludePadding")) { properties = removePadding(properties, element); } return properties; }; // src/assertions/alignment.ts var alignmentAssertions = ({ Assertion }) => { Assertion.addMethod("horizontallyAligned", function(element, edge = "all") { const [source, target] = [ getElementProperties(this._obj), getElementProperties(element) ]; const topAlignDiff = source.top - target.top; const bottomAlignDiff = -(source.bottom - target.bottom); let condition; switch (edge) { case "top": condition = topAlignDiff === 0; break; case "bottom": condition = bottomAlignDiff === 0; break; case "centered": condition = topAlignDiff - bottomAlignDiff === 0; break; case "all": default: condition = topAlignDiff === 0 && bottomAlignDiff === 0; } return this.assert( condition, `expected #{this} to be horizontally aligned ${edge} with ${element}`, `expected #{this} not to be horizontally aligned ${edge} with ${element}`, "" ); }); Assertion.addMethod("verticallyAligned", function(element, edge = "all") { const [source, target] = [ getElementProperties(this._obj), getElementProperties(element) ]; const leftAlignDiff = source.left - target.left; const rightAlignDiff = -(source.right - target.right); let condition; switch (edge) { case "left": condition = leftAlignDiff === 0; break; case "right": condition = rightAlignDiff === 0; break; case "centered": condition = leftAlignDiff - rightAlignDiff === 0; break; case "all": default: condition = leftAlignDiff === 0 && rightAlignDiff === 0; } return this.assert( condition, `expected #{this} to be vertically aligned ${edge} with ${element}`, `expected #{this} not to be vertically aligned ${edge} with ${element}`, "" ); }); }; var alignment_default = alignmentAssertions; // src/assertions/boundary.ts var boundaryAssertions = ({ Assertion }) => { Assertion.addMethod("overflowing", function(direction = "any") { const source = unwrapElement(this._obj); const overflowVertical = source.scrollHeight > source.clientHeight; const overflowHorizontal = source.scrollWidth > source.clientWidth; let condition; switch (direction) { case "horizontally": condition = overflowHorizontal; break; case "vertically": condition = overflowVertical; break; case "all": default: condition = overflowHorizontal || overflowVertical; } return this.assert( condition, `expected #{this} to be overflowing ${direction}`, `expected #{this} not to be overflowing ${direction}`, "" ); }); Assertion.addMethod("overlapping", function(element) { const [source, target] = [ getElementProperties(this._obj), getElementProperties(element) ]; const condition = !(source.right <= target.left || source.left >= target.right || source.bottom <= target.top || source.top >= target.bottom); return this.assert( condition, `expected #{this} to be overlapping with ${element}`, `expected #{this} not to be overlapping with ${element}`, "" ); }); }; var boundary_default = boundaryAssertions; // src/utils/assertions.ts var isWithinThreshold = (a, b) => { const threshold = getConfiguration("threshold"); return a >= b - threshold && a <= b + threshold; }; // src/assertions/position.ts var positionAssertions = ({ Assertion }, utils) => { Assertion.addMethod("rightOf", function(element, distance) { const [source, target] = [ getElementProperties(this._obj), getElementProperties(element) ]; const actual = source.left - target.right; if (distance === void 0) { return this.assert( actual >= 0, `expected #{this} to be right of ${element}`, `expected #{this} not to be right of ${element}`, "" ); } return this.assert( isWithinThreshold(actual, distance), `expected #{this} to be right of ${element} by #{exp}, but the value was #{act}`, `expected #{this} not to be right of ${element} by #{exp}, but the value was #{act}`, distance, actual ); }); Assertion.addMethod("leftOf", function(element, distance) { const [source, target] = [ getElementProperties(this._obj), getElementProperties(element) ]; const actual = target.left - source.right; if (distance === void 0) { return this.assert( actual >= 0, `expected #{this} to be left of ${element}`, `expected #{this} not to be left of ${element}`, "" ); } return this.assert( isWithinThreshold(actual, distance), `expected #{this} to be left of ${element} by #{exp}, but the value was #{act}`, `expected #{this} not to be left of ${element} by #{exp}, but the value was #{act}`, distance, actual ); }); Assertion.overwriteMethod( "above", (_super) => function(element, distance) { if (Cypress.dom.isJquery(this._obj)) { const [source, target] = [ getElementProperties(this._obj), getElementProperties(element) ]; const actual = target.top - source.bottom; if (distance === void 0) { return this.assert( actual >= 0, `expected #{this} to be above ${element}`, `expected #{this} not to be above ${element}`, "" ); } return this.assert( isWithinThreshold(actual, distance), `expected #{this} to be above ${element} by #{exp}, but the value was #{act}`, `expected #{this} not to be above ${element} by #{exp}, but the value was #{act}`, distance, actual ); } return _super.apply(this, arguments); } ); Assertion.overwriteMethod( "below", (_super) => function(element, distance) { if (Cypress.dom.isJquery(this._obj)) { const [source, target] = [ getElementProperties(this._obj), getElementProperties(element) ]; const actual = source.top - target.bottom; if (distance === void 0) { return this.assert( actual >= 0, `expected #{this} to be below ${element}`, `expected #{this} not to be below ${element}`, "" ); } return this.assert( isWithinThreshold(actual, distance), `expected #{this} to be below ${element} by #{exp}, but the value was #{act}`, `expected #{this} not to be below ${element} by #{exp}, but the value was #{act}`, distance, actual ); } return _super.apply(this, arguments); } ); Assertion.addMethod("inside", function(element, distances) { const [source, target] = [ getElementProperties(this._obj), getElementProperties(element) ]; const differences = { top: source.top - target.top, left: source.left - target.left, right: -(source.right - target.right), bottom: -(source.bottom - target.bottom) }; if (distances === void 0) { return this.assert( source.left >= target.left && source.right <= target.right && source.top >= target.top && source.bottom <= target.bottom, `expected #{this} to be inside of ${element}, but the value was #{act}`, `expected #{this} not to be inside of ${element}, but the value was #{act}`, "", utils.inspect(differences) ); } const condition = Object.keys(distances).every((key) => { const distance = distances[key]; const actual = differences[key]; return isWithinThreshold(actual, distance); }); return this.assert( condition, `expected #{this} to be inside of ${element} by #{exp}, but the value was #{act}`, `expected #{this} not to be inside of ${element} by #{exp}, but the value was #{act}`, utils.inspect(distances), utils.inspect(differences) ); }); }; var position_default = positionAssertions; // src/assertions/style.ts var styleAssertions = ({ Assertion }, utils) => { Assertion.addMethod("style", function(property, value) { const actual = this._obj.css(property); return this.assert( actual === value, `expected #{this} to have CSS property ${utils.inspect( property )} with the value of #{exp}, but the value was #{act}`, `expected #{this} not to have CSS property ${utils.inspect( property )} with the value #{exp}`, value, actual ); }); }; var style_default = styleAssertions; // src/assertions/index.ts chai.use(alignment_default); chai.use(boundary_default); chai.use(position_default); chai.use(style_default); // src/chainers/dimensions.ts var dimensionAssertions = ({ Assertion }, utils) => { Assertion.addChainableMethod( "width", function(measure) { const source = getElementProperties(this._obj); const actual = source.width; return this.assert( actual === measure, `expected #{this} to have width of #{exp}, but the value was #{act}`, `expected #{this} not to have width of #{exp}, but the value was #{act}`, measure, actual ); }, function() { utils.flag(this, "element.width", true); } ); Assertion.addChainableMethod( "height", function(measure) { const source = getElementProperties(this._obj); const actual = source.height; return this.assert( actual === measure, `expected #{this} to have height of #{exp}, but the value was #{act}`, `expected #{this} not to have height of #{exp}, but the value was #{act}`, measure, actual ); }, function() { utils.flag(this, "element.height", true); } ); }; var dimensions_default = dimensionAssertions; // src/chainers/index.ts chai.use(dimensions_default); // src/overwrites/dimensions.ts var dimensionsOverwrites = ({ Assertion }, utils) => { Assertion.overwriteMethod( "gt", (_super) => function(n) { const width = utils.flag(this, "element.width"); const height = utils.flag(this, "element.height"); if (width || height) { const source = getElementProperties(this._obj); let property = "width"; if (height) property = "height"; const actual = source[property]; return this.assert( actual > n, `expected #{this} to have ${property} greater than #{exp}, but the value was #{act}`, `expected #{this} not to have ${property} greater than #{exp}, but the value was #{act}`, n, actual ); } return _super.apply(this, arguments); } ); Assertion.overwriteMethod( "lt", (_super) => function(n) { const width = utils.flag(this, "element.width"); const height = utils.flag(this, "element.height"); if (width || height) { const source = getElementProperties(this._obj); let property = "width"; if (height) property = "height"; const actual = source[property]; return this.assert( actual < n, `expected #{this} to have ${property} less than #{exp}, but the value was #{act}`, `expected #{this} not to have ${property} less than #{exp}, but the value was #{act}`, n, actual ); } return _super.apply(this, arguments); } ); Assertion.overwriteMethod( "within", (_super) => function(start, finish) { const width = utils.flag(this, "element.width"); const height = utils.flag(this, "element.height"); if (width || height) { const source = getElementProperties(this._obj); let property = "width"; if (height) property = "height"; const actual = source[property]; return this.assert( actual >= start && actual <= finish, `expected #{this} to have ${property} within #{exp}, but the value was #{act}`, `expected #{this} not to have ${property} within #{exp}, but the value was #{act}`, `${start} .. ${finish}`, actual ); } return _super.apply(this, arguments); } ); }; var dimensions_default2 = dimensionsOverwrites; // src/overwrites/index.ts chai.use(dimensions_default2); // src/index.ts Cypress.Commands.add("configureLayoutInspector", (config2) => configure(config2));