cypress-layout-inspector
Version:
Simple utility to provide layout testing functionality to Cypress
446 lines (432 loc) • 14.2 kB
JavaScript
"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));