import { isRect, isRectMostlyAbove, isRectMostlyLeft, isRectMostlyBounded, isRectMostlyHBounded, isRectMostlyVBounded, rectsIntersect, rectContainersOther, } from './geom.js' // fix bug with jQuery 3 returning 0 height for elements in the IE's ['height', 'outerHeight'].forEach((methodName) => { let orig = $.fn[methodName] $.fn[methodName] = function () { // eslint-disable-line func-names if (!arguments.length && this.is('td')) { // eslint-disable-line prefer-rest-params return this[0].getBoundingClientRect().height } return orig.apply(this, arguments) // eslint-disable-line prefer-rest-params } }) export function getBoundingRects(els) { return $(els).map((i, node) => getBoundingRect(node)).get() } export function getBoundingRect(el) { el = $(el) return $.extend({}, el[0].getBoundingClientRect(), { node: el, // very useful for debugging }) } export function anyElsIntersect(els) { let rects = els.map((el) => el.getBoundingClientRect()) for (let i = 0; i < rects.length; i += 1) { for (let j = i + 1; j < rects.length; j += 1) { if (rectsIntersect(rects[i], rects[j])) { return [els[i], els[j]] } } } return false } export function anyElsObscured(els) { let rects = els.map((el) => el.getBoundingClientRect()) for (let i = 0; i < rects.length; i += 1) { for (let j = 0; j < rects.length; j += 1) { if (i !== j && rectContainersOther(rects[i], rects[j])) { return [els[i], els[j]] } } } return false } export function getLeadingBoundingRect(els, direction = 'ltr') { els = $(els) expect(els.length).toBeGreaterThan(0) let best = null els.each((i, node) => { const rect = getBoundingRect(node) if (!best) { best = rect } else if (direction === 'rtl') { if (rect.right > best.right) { best = rect } } else if (rect.left < best.left) { best = rect } }) return best } export function getTrailingBoundingRect(els, direction = 'ltr') { els = $(els) expect(els.length).toBeGreaterThan(0) let best = null els.each((i, node) => { const rect = getBoundingRect(node) if (!best) { best = rect } else if (direction === 'rtl') { if (rect.left < best.left) { best = rect } } else if (rect.right > best.right) { best = rect } }) return best } export function sortBoundingRects(els, direction = 'ltr') { els = $(els) // TODO: un-jquery-ify const rects = els.map((i, node) => getBoundingRect(node)).get() rects.sort((a, b) => { if (direction === 'rtl') { return b.right - a.right } return a.left - b.left }) return rects } // given an element, returns its bounding box. given a rect, returns the rect. function massageRect(input) { if (isRect(input)) { return input } return getBoundingRect(input) } // Jasmine Adapters // -------------------------------------------------------------------------------------------------- beforeEach(() => { jasmine.addMatchers({ toBeMostlyAbove() { return { compare(subject, other) { const result = { pass: isRectMostlyAbove(massageRect(subject), massageRect(other)), message: '' } if (!result.pass) { result.message = 'first rect is not mostly above the second' } return result }, } }, toBeMostlyBelow() { return { compare(subject, other) { const result = { pass: !isRectMostlyAbove(massageRect(subject), massageRect(other)), message: '' } if (!result.pass) { result.message = 'first rect is not mostly below the second' } return result }, } }, toBeMostlyLeftOf() { return { compare(subject, other) { const result = { pass: isRectMostlyLeft(massageRect(subject), massageRect(other)), message: '' } if (!result.pass) { result.message = 'first rect is not mostly left of the second' } return result }, } }, toBeMostlyRightOf() { return { compare(subject, other) { const result = { pass: !isRectMostlyLeft(massageRect(subject), massageRect(other)), message: '' } if (!result.pass) { result.message = 'first rect is not mostly right of the second' } return result }, } }, toBeMostlyBoundedBy() { return { compare(subject, other) { const result = { pass: isRectMostlyBounded(massageRect(subject), massageRect(other)), message: '' } if (!result.pass) { result.message = 'first rect is not mostly bounded by the second' } return result }, } }, toBeMostlyHBoundedBy() { return { compare(subject, other) { const result = { pass: isRectMostlyHBounded(massageRect(subject), massageRect(other)), message: '' } if (!result.pass) { result.message = 'first rect does not mostly horizontally bound the second' } return result }, } }, toBeMostlyVBoundedBy() { return { compare(subject, other) { const result = { pass: isRectMostlyVBounded(massageRect(subject), massageRect(other)), message: '' } if (!result.pass) { result.message = 'first rect does not mostly vertically bound the second' } return result }, } }, toBeBoundedBy() { return { compare(actual, expected) { let outer = massageRect(expected) let inner = massageRect(actual) let result = { message: '', pass: outer && inner && inner.left >= outer.left && inner.right <= outer.right && inner.top >= outer.top && inner.bottom <= outer.bottom, } if (!result.pass) { result.message = 'Element does not bound other element' } return result }, } }, toBeLeftOf() { return { compare(actual, expected) { let subjectBounds = massageRect(actual) let otherBounds = massageRect(expected) let result = { message: '', pass: subjectBounds && otherBounds && Math.round(subjectBounds.right) <= Math.round(otherBounds.left) + 2, // need to round because IE was giving weird fractions } if (!result.pass) { result.message = 'Element is not to the left of the other element' } return result }, } }, toBeRightOf() { return { compare(actual, expected) { let subjectBounds = massageRect(actual) let otherBounds = massageRect(expected) let result = { message: '', pass: subjectBounds && otherBounds && Math.round(subjectBounds.left) >= Math.round(otherBounds.right) - 2, // need to round because IE was giving weird fractions } if (!result.pass) { result.message = 'Element is not to the right of the other element' } return result }, } }, toBeAbove() { return { compare(actual, expected) { let subjectBounds = massageRect(actual) let otherBounds = massageRect(expected) let result = { message: '', pass: subjectBounds && otherBounds && Math.round(subjectBounds.bottom) <= Math.round(otherBounds.top) + 2, // need to round because IE was giving weird fractions } if (!result.pass) { result.message = 'Element is not above the other element' } return result }, } }, toBeBelow() { return { compare(actual, expected) { let subjectBounds = massageRect(actual) let otherBounds = massageRect(expected) let result = { message: '', pass: subjectBounds && otherBounds && Math.round(subjectBounds.top) >= Math.round(otherBounds.bottom) - 2, // need to round because IE was giving weird fractions } if (!result.pass) { result.message = 'Element is not below the other element' } return result }, } }, toIntersectWith() { return { compare(actual, expected) { let subjectBounds = massageRect(actual) let otherBounds = massageRect(expected) let result = { message: '', pass: subjectBounds && otherBounds && subjectBounds.right - 1 > otherBounds.left && subjectBounds.left + 1 < otherBounds.right && subjectBounds.bottom - 1 > otherBounds.top && subjectBounds.top + 1 < otherBounds.bottom, // +/-1 because of zoom } if (!result.pass) { result.message = 'Element does not intersect with other element' } return result }, } }, }) })