[IMP] web: introduce position hook
This commit encapsulates the positioning logic of the Popover component
into a reusable hook. It also improves this logic by adding more
positioning directions and functionalities.
Before:
- only 4 directions (top, bottom, right, left : all middle aligned)
- the whole window viewport serves as a direction toggler
Now:
- 12 positions are available and are composed by 4 directions and 3
variants: {top,bottom,right,left}-{start,middle,end}
- the positioned element container is configurable. Default: window
Description:
When you use this hook, you make sure that your `popper` element (the
one you would like to position) is always placed at the given `position`
from the `reference` element. If positioning it so the `popper` element
is clipped off the `container` element, sensible fallback positions are
tried. If none is fully visible without being clipped off `container`,
the original position is used.
Usage:
usePosition(
reference, // [mandatory] HTMLElement or function giving HTMLElement
options // [optional] configure behaviour through options object
)
Options:
- popper (string): a t-ref reference. If none, component.el is used
- container (HTMLElement)
- margin (number)
- position (string): {top,bottom,right,left}-{start,middle,end}
Taskid-2578926
This commit is contained in:
@@ -1,80 +1,15 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { debounce, throttle } from "../utils/timing";
|
||||
import { usePosition } from "../position/position_hook";
|
||||
|
||||
const { Component } = owl;
|
||||
const { useExternalListener, useRef } = owl.hooks;
|
||||
|
||||
export class Popover extends Component {
|
||||
setup() {
|
||||
this.popoverRef = useRef("popover");
|
||||
|
||||
useExternalListener(document, "scroll", throttle(this.compute, 50), { capture: true });
|
||||
useExternalListener(window, "resize", debounce(this.compute, 250));
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.compute();
|
||||
}
|
||||
patched() {
|
||||
this.compute();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Computes the popover according to its props. This method will try to
|
||||
* position the popover as requested (according to the `position` props).
|
||||
* If the requested position does not fit the viewport, other positions will
|
||||
* be tried in a clockwise order starting a the requested position
|
||||
* (e.g. starting from left: top, right, bottom). If no position is found
|
||||
* that fits the viewport, "bottom" is used.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
compute() {
|
||||
const positioningData = this.constructor.computePositioningData(
|
||||
this.popoverRef.el,
|
||||
this.props.target
|
||||
);
|
||||
|
||||
const ORDERED_POSITIONS = ["top", "bottom", "left", "right"];
|
||||
// copy the default ordered position to avoid updating them in place
|
||||
const positionIndex = ORDERED_POSITIONS.indexOf(this.props.position);
|
||||
// check if the requested position fits the viewport; if not,
|
||||
// try all other positions and find one that does
|
||||
const position = ORDERED_POSITIONS.slice(positionIndex)
|
||||
.concat(ORDERED_POSITIONS.slice(0, positionIndex))
|
||||
.map((pos) => positioningData[pos])
|
||||
.find((pos) => {
|
||||
this.popoverRef.el.style.top = `${pos.top}px`;
|
||||
this.popoverRef.el.style.left = `${pos.left}px`;
|
||||
const rect = this.popoverRef.el.getBoundingClientRect();
|
||||
const html = document.documentElement;
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= (window.innerHeight || html.clientHeight) &&
|
||||
rect.right <= (window.innerWidth || html.clientWidth)
|
||||
);
|
||||
});
|
||||
|
||||
// remove all existing positioning classes
|
||||
for (const pos of ORDERED_POSITIONS) {
|
||||
this.popoverRef.el.classList.remove(`o_popover_${pos}`);
|
||||
}
|
||||
|
||||
if (position) {
|
||||
// apply the preferred found position that fits the viewport
|
||||
this.popoverRef.el.classList.add(`o_popover_${position.name}`);
|
||||
} else {
|
||||
// use the given `position` props because no position fits
|
||||
this.popoverRef.el.style.top = `${positioningData[this.props.position].top}px`;
|
||||
this.popoverRef.el.style.left = `${positioningData[this.props.position].left}px`;
|
||||
this.popoverRef.el.classList.add(`o_popover_${this.props.position}`);
|
||||
}
|
||||
usePosition(this.props.target, {
|
||||
margin: 16,
|
||||
position: this.props.position,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,66 +28,3 @@ Popover.props = {
|
||||
},
|
||||
target: HTMLElement,
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the expected positioning coordinates for each possible
|
||||
* positioning based on the target and popover sizes.
|
||||
* In particular the popover must not overflow the viewport in any
|
||||
* direction, it should actually stay at `margin` distance from the
|
||||
* border to look good.
|
||||
*
|
||||
* @param {HTMLElement} popoverElement The popover element
|
||||
* @param {HTMLElement} targetElement The target element, to which
|
||||
* the popover will be visually "bound"
|
||||
* @param {number} [margin=16] Minimal accepted margin from the border
|
||||
* of the viewport.
|
||||
* @returns {Object}
|
||||
*/
|
||||
Popover.computePositioningData = function (popoverElement, targetElement, margin = 16) {
|
||||
// set target position, possible position
|
||||
const boundingRectangle = targetElement.getBoundingClientRect();
|
||||
const targetTop = boundingRectangle.top;
|
||||
const targetLeft = boundingRectangle.left;
|
||||
const targetHeight = targetElement.offsetHeight;
|
||||
const targetWidth = targetElement.offsetWidth;
|
||||
const popoverHeight = popoverElement.offsetHeight;
|
||||
const popoverWidth = popoverElement.offsetWidth;
|
||||
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
const leftOffsetForVertical = Math.max(
|
||||
margin,
|
||||
Math.min(
|
||||
Math.round(targetLeft - (popoverWidth - targetWidth) / 2),
|
||||
windowWidth - popoverWidth - margin
|
||||
)
|
||||
);
|
||||
const topOffsetForHorizontal = Math.max(
|
||||
margin,
|
||||
Math.min(
|
||||
Math.round(targetTop - (popoverHeight - targetHeight) / 2),
|
||||
windowHeight - popoverHeight - margin
|
||||
)
|
||||
);
|
||||
return {
|
||||
top: {
|
||||
name: "top",
|
||||
top: Math.round(targetTop - popoverHeight),
|
||||
left: leftOffsetForVertical,
|
||||
},
|
||||
right: {
|
||||
name: "right",
|
||||
top: topOffsetForHorizontal,
|
||||
left: Math.round(targetLeft + targetWidth),
|
||||
},
|
||||
bottom: {
|
||||
name: "bottom",
|
||||
top: Math.round(targetTop + targetHeight),
|
||||
left: leftOffsetForVertical,
|
||||
},
|
||||
left: {
|
||||
name: "left",
|
||||
top: topOffsetForHorizontal,
|
||||
left: Math.round(targetLeft - popoverWidth),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -54,99 +54,98 @@
|
||||
$animation-time: 0.2s;
|
||||
max-width: 100vw;
|
||||
|
||||
position: fixed;
|
||||
top: 0; // will be updated in js
|
||||
left: 0; // but let's set it in the viewport in case of test/debug
|
||||
z-index: 1060;
|
||||
border: 1px solid $border-color;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
|
||||
|
||||
&_top {
|
||||
animation: $animation-time slide-top;
|
||||
margin-top: -$arrow-size;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -$arrow-size;
|
||||
border: $arrow-size solid transparent;
|
||||
border-top-color: $bg-color;
|
||||
}
|
||||
|
||||
&::before {
|
||||
margin-top: 1px;
|
||||
border-top-color: $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
&_right {
|
||||
animation: $animation-time slide-right;
|
||||
margin-left: $arrow-size;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 100%;
|
||||
&.o-popper-position {
|
||||
&--tm {
|
||||
animation: $animation-time slide-top;
|
||||
margin-top: -$arrow-size;
|
||||
border: $arrow-size solid transparent;
|
||||
border-right-color: $bg-color;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -$arrow-size;
|
||||
border: $arrow-size solid transparent;
|
||||
border-top-color: $bg-color;
|
||||
}
|
||||
|
||||
&::before {
|
||||
margin-top: 1px;
|
||||
border-top-color: $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
margin-right: 1px;
|
||||
border-right-color: $border-color;
|
||||
&--rm {
|
||||
animation: $animation-time slide-right;
|
||||
margin-left: $arrow-size;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 100%;
|
||||
margin-top: -$arrow-size;
|
||||
border: $arrow-size solid transparent;
|
||||
border-right-color: $bg-color;
|
||||
}
|
||||
|
||||
&::before {
|
||||
margin-right: 1px;
|
||||
border-right-color: $border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&_bottom {
|
||||
animation: $animation-time slide-bottom;
|
||||
margin-top: $arrow-size;
|
||||
&--bm {
|
||||
animation: $animation-time slide-bottom;
|
||||
margin-top: $arrow-size;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
&::before,
|
||||
&::after {
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -$arrow-size;
|
||||
border: $arrow-size solid transparent;
|
||||
border-bottom-color: $bg-color;
|
||||
}
|
||||
|
||||
&::before {
|
||||
margin-bottom: 1px;
|
||||
border-bottom-color: $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--lm {
|
||||
animation: $animation-time slide-left;
|
||||
margin-left: -$arrow-size;
|
||||
border: $arrow-size solid transparent;
|
||||
border-bottom-color: $bg-color;
|
||||
}
|
||||
|
||||
&::before {
|
||||
margin-bottom: 1px;
|
||||
border-bottom-color: $border-color;
|
||||
}
|
||||
}
|
||||
&::before,
|
||||
&::after {
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
margin-top: -$arrow-size;
|
||||
border: $arrow-size solid transparent;
|
||||
border-left-color: $bg-color;
|
||||
}
|
||||
|
||||
&_left {
|
||||
animation: $animation-time slide-left;
|
||||
margin-left: -$arrow-size;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
margin-top: -$arrow-size;
|
||||
border: $arrow-size solid transparent;
|
||||
border-left-color: $bg-color;
|
||||
}
|
||||
|
||||
&::before {
|
||||
margin-left: 1px;
|
||||
border-left-color: $border-color;
|
||||
&::before {
|
||||
margin-left: 1px;
|
||||
border-left-color: $border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<div role="tooltip" class="o_popover"
|
||||
t-att-class="props.popoverClass"
|
||||
t-on-popover-compute="compute"
|
||||
t-ref="popover"
|
||||
>
|
||||
<t t-slot="default" />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.o-popper-position {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { useEffect } from "@web/core/utils/hooks";
|
||||
import { debounce, throttle } from "../utils/timing";
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* popper?: string;
|
||||
* container?: HTMLElement;
|
||||
* margin?: number;
|
||||
* position?: Position;
|
||||
* }} Options
|
||||
*
|
||||
* @typedef {{
|
||||
* directions: (DirectionsDataKey)[];
|
||||
* variants: (VariantsDataKey)[];
|
||||
* get: (d?: DirectionsDataKey, v?: VariantsDataKey, containerRestricted?: boolean) => PositioningSolution | null;
|
||||
* }} Positioning
|
||||
*
|
||||
* @typedef {keyof DirectionsData} DirectionsDataKey
|
||||
* @typedef {{
|
||||
* t: number;
|
||||
* b: number;
|
||||
* l: number;
|
||||
* r: number;
|
||||
* }} DirectionsData
|
||||
*
|
||||
* @typedef {keyof VariantsData} VariantsDataKey
|
||||
* @typedef {{
|
||||
* vs: number;
|
||||
* vm: number;
|
||||
* ve: number;
|
||||
* hs: number;
|
||||
* hm: number;
|
||||
* he: number;
|
||||
* }} VariantsData
|
||||
*
|
||||
* @typedef {"top" | "left" | "bottom" | "right"} Direction
|
||||
* @typedef {Direction
|
||||
* | "top-start" | "top-middle" | "top-end"
|
||||
* | "left-start" | "left-middle" | "left-end"
|
||||
* | "right-start" | "right-middle" | "right-end"
|
||||
* | "bottom-start" | "bottom-middle" | "bottom-end"} Position
|
||||
*
|
||||
* @typedef {{ className: string, top: number, left: number }} PositioningSolution
|
||||
*/
|
||||
|
||||
const { hooks } = owl;
|
||||
const { useComponent, useExternalListener, useRef } = hooks;
|
||||
|
||||
const POPPER_CLASS = "o-popper-position";
|
||||
const DIRECTION_FLIP_ORDER = { top: "tbrl", right: "rltb", bottom: "btrl", left: "lrbt" };
|
||||
const VARIANT_FLIP_ORDER = { start: "sme", middle: "mse", end: "ems" };
|
||||
|
||||
/** @type {Options} */
|
||||
export const DEFAULTS = {
|
||||
margin: 0,
|
||||
position: "bottom",
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes positioning data used to determine each possible position
|
||||
* based on the reference, popper, and container sizes.
|
||||
* Particularly, a popper must not overflow the container in any direction,
|
||||
* it should actually stay at `margin` distance from the border to look good.
|
||||
*
|
||||
* @param {HTMLElement} reference
|
||||
* @param {HTMLElement} popper
|
||||
* @param {Options} options
|
||||
* @returns {Positioning} a positioning object containing:
|
||||
* - ascendingly sorted directions and variants
|
||||
* - a method returning style to apply to the popper for a given direction and variant
|
||||
*/
|
||||
export function computePositioning(reference, popper, options) {
|
||||
const { container, margin, position } = options;
|
||||
|
||||
// Retrieve directions and variants
|
||||
const [directionKey, variantKey = "middle"] = position.split("-");
|
||||
const directions = DIRECTION_FLIP_ORDER[directionKey];
|
||||
const variants = VARIANT_FLIP_ORDER[variantKey];
|
||||
|
||||
// Boxes
|
||||
const popBox = popper.getBoundingClientRect();
|
||||
const refBox = reference.getBoundingClientRect();
|
||||
const contBox = container.getBoundingClientRect();
|
||||
|
||||
// Compute positioning data
|
||||
/** @type {DirectionsData} */
|
||||
const directionsData = {
|
||||
t: refBox.top - popBox.height - margin,
|
||||
b: refBox.bottom + margin,
|
||||
r: refBox.right + margin,
|
||||
l: refBox.left - popBox.width - margin,
|
||||
};
|
||||
/** @type {VariantsData} */
|
||||
const variantsData = {
|
||||
vs: refBox.left,
|
||||
vm: refBox.left + refBox.width / 2 + -popBox.width / 2,
|
||||
ve: refBox.right - popBox.width,
|
||||
hs: refBox.top,
|
||||
hm: refBox.top + refBox.height / 2 + -popBox.height / 2,
|
||||
he: refBox.bottom - popBox.height,
|
||||
};
|
||||
|
||||
function get(d = directions[0], v = variants[0], containerRestricted = false) {
|
||||
const vertical = ["t", "b"].includes(d);
|
||||
const variantPrefix = vertical ? "v" : "h";
|
||||
const directionValue = directionsData[d];
|
||||
const variantValue = variantsData[variantPrefix + v];
|
||||
|
||||
if (containerRestricted) {
|
||||
const [directionSize, variantSize] = vertical
|
||||
? [popBox.height + margin, popBox.width]
|
||||
: [popBox.width, popBox.height + margin];
|
||||
const [directionMin, directionMax] = vertical
|
||||
? [contBox.top, contBox.bottom]
|
||||
: [contBox.left, contBox.right];
|
||||
const [variantMin, variantMax] = vertical
|
||||
? [contBox.left, contBox.right]
|
||||
: [contBox.top, contBox.bottom];
|
||||
|
||||
// Abort if outside container boundaries
|
||||
const directionOverflow =
|
||||
directionValue < directionMin || directionValue + directionSize > directionMax;
|
||||
const variantOverflow =
|
||||
variantValue < variantMin || variantValue + variantSize > variantMax;
|
||||
if (directionOverflow || variantOverflow) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const positioning = vertical
|
||||
? {
|
||||
top: directionValue,
|
||||
left: variantValue,
|
||||
}
|
||||
: {
|
||||
top: variantValue,
|
||||
left: directionValue,
|
||||
};
|
||||
return { ...positioning, className: `${POPPER_CLASS}--${d}${v}` };
|
||||
}
|
||||
|
||||
return {
|
||||
directions,
|
||||
variants,
|
||||
get,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to position the popper as requested (according to options).
|
||||
* If the requested position does not fit the container, other positions will be
|
||||
* tried in different direction and variant flip orders (depending on the requested position).
|
||||
* If no position is found that fits the container, the requested position stays used.
|
||||
*
|
||||
* When the final position is applied, a corresponding CSS class is also added to the popper.
|
||||
* This could be used to further styling.
|
||||
*
|
||||
* @param {HTMLElement} reference
|
||||
* @param {HTMLElement} popper
|
||||
* @param {Options} options
|
||||
*/
|
||||
function reposition(reference, popper, options) {
|
||||
options = {
|
||||
container: document.documentElement,
|
||||
...DEFAULTS,
|
||||
...options,
|
||||
};
|
||||
|
||||
// Reset all existing popper classes and only leave it as standalone
|
||||
for (const popperClass of popper.classList) {
|
||||
if (popperClass.startsWith(POPPER_CLASS)) {
|
||||
popper.classList.remove(popperClass);
|
||||
}
|
||||
}
|
||||
popper.classList.add(POPPER_CLASS);
|
||||
|
||||
// Compute positioning and find first match
|
||||
const positioning = computePositioning(reference, popper, options);
|
||||
for (const d of positioning.directions) {
|
||||
for (const v of positioning.variants) {
|
||||
const posData = positioning.get(d, v, true);
|
||||
if (!posData) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// From now, a position match have been found.
|
||||
// Apply styles
|
||||
const { className, top, left } = posData;
|
||||
popper.classList.add(className);
|
||||
popper.style.top = `${top}px`;
|
||||
popper.style.left = `${left}px`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// use the given `position` because no position fits
|
||||
const { className, top, left } = positioning.get();
|
||||
popper.classList.add(className);
|
||||
popper.style.top = `${top}px`;
|
||||
popper.style.left = `${left}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the `popper` element is always
|
||||
* placed at `position` from the `reference` element.
|
||||
* If doing so the `popper` element is clipped off `container`,
|
||||
* sensible fallback positions are tried.
|
||||
* If all of fallback positions are also clipped off `container`,
|
||||
* the original position is used.
|
||||
*
|
||||
* @param {HTMLElement | (()=>HTMLElement)} reference
|
||||
* @param {Options} options
|
||||
*/
|
||||
export function usePosition(reference, options) {
|
||||
options = { ...DEFAULTS, ...options };
|
||||
const { popper } = options;
|
||||
const popperRef = popper ? useRef(popper) : useComponent();
|
||||
const getReference = reference instanceof HTMLElement ? () => reference : reference;
|
||||
const update = () => {
|
||||
if (popperRef.el) {
|
||||
reposition(getReference(), popperRef.el, options);
|
||||
}
|
||||
};
|
||||
useEffect(update);
|
||||
useExternalListener(document, "scroll", throttle(update, 50), { capture: true });
|
||||
useExternalListener(window, "resize", debounce(update, 250));
|
||||
}
|
||||
@@ -2,31 +2,23 @@
|
||||
|
||||
import { Popover } from "@web/core/popover/popover";
|
||||
import { registerCleanup } from "../../helpers/cleanup";
|
||||
import { getFixture, patchWithCleanup } from "../../helpers/utils";
|
||||
import { getFixture } from "../../helpers/utils";
|
||||
|
||||
const { mount } = owl;
|
||||
|
||||
let fixture;
|
||||
let popoverTarget;
|
||||
|
||||
function computePositioningDataTest(popover, target) {
|
||||
const rect = target.getBoundingClientRect();
|
||||
const top = Math.floor(rect.top);
|
||||
const left = Math.floor(rect.left);
|
||||
return {
|
||||
top: { name: "top", top, left },
|
||||
bottom: { name: "bottom", top: top + 10, left },
|
||||
left: { name: "left", top: top + 10, left },
|
||||
right: { name: "right", top: top + 10, left: left + 10 },
|
||||
};
|
||||
}
|
||||
const positionClassMap = {
|
||||
top: "o-popper-position--tm",
|
||||
right: "o-popper-position--rm",
|
||||
bottom: "o-popper-position--bm",
|
||||
left: "o-popper-position--lm",
|
||||
};
|
||||
|
||||
function pointsTo(popover, element, position) {
|
||||
const hasCorrectClass = popover.classList.contains(`o_popover_${position}`);
|
||||
const expectedPosition = Popover.computePositioningData(popover, element)[position];
|
||||
const correctLeft = parseFloat(popover.style.left) === expectedPosition.left;
|
||||
const correctTop = parseFloat(popover.style.top) === expectedPosition.top;
|
||||
return hasCorrectClass && correctLeft && correctTop;
|
||||
function pointsTo(popover, position) {
|
||||
const hasCorrectClass = popover.classList.contains(positionClassMap[position]);
|
||||
return hasCorrectClass;
|
||||
}
|
||||
|
||||
QUnit.module("Popover", {
|
||||
@@ -40,10 +32,6 @@ QUnit.module("Popover", {
|
||||
registerCleanup(() => {
|
||||
fixture.removeChild(popoverTarget);
|
||||
});
|
||||
|
||||
patchWithCleanup(Popover, {
|
||||
computePositioningData: computePositioningDataTest,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -64,7 +52,7 @@ QUnit.test("popover is rendered nearby target (default)", async (assert) => {
|
||||
});
|
||||
|
||||
const popoverEl = fixture.querySelector(".o_popover");
|
||||
assert.ok(pointsTo(popoverEl, popoverTarget, "bottom"));
|
||||
assert.ok(pointsTo(popoverEl, "bottom"));
|
||||
popover.destroy();
|
||||
});
|
||||
|
||||
@@ -75,7 +63,7 @@ QUnit.test("popover is rendered nearby target (bottom)", async (assert) => {
|
||||
});
|
||||
|
||||
const popoverEl = fixture.querySelector(".o_popover");
|
||||
assert.ok(pointsTo(popoverEl, popoverTarget, "bottom"));
|
||||
assert.ok(pointsTo(popoverEl, "bottom"));
|
||||
popover.destroy();
|
||||
});
|
||||
|
||||
@@ -86,7 +74,7 @@ QUnit.test("popover is rendered nearby target (top)", async (assert) => {
|
||||
});
|
||||
|
||||
const popoverEl = fixture.querySelector(".o_popover");
|
||||
assert.ok(pointsTo(popoverEl, popoverTarget, "top"));
|
||||
assert.ok(pointsTo(popoverEl, "top"));
|
||||
popover.destroy();
|
||||
});
|
||||
|
||||
@@ -97,7 +85,7 @@ QUnit.test("popover is rendered nearby target (left)", async (assert) => {
|
||||
});
|
||||
|
||||
const popoverEl = fixture.querySelector(".o_popover");
|
||||
assert.ok(pointsTo(popoverEl, popoverTarget, "left"));
|
||||
assert.ok(pointsTo(popoverEl, "left"));
|
||||
popover.destroy();
|
||||
});
|
||||
|
||||
@@ -108,6 +96,6 @@ QUnit.test("popover is rendered nearby target (right)", async (assert) => {
|
||||
});
|
||||
|
||||
const popoverEl = fixture.querySelector(".o_popover");
|
||||
assert.ok(pointsTo(popoverEl, popoverTarget, "right"));
|
||||
assert.ok(pointsTo(popoverEl, "right"));
|
||||
popover.destroy();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,754 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { computePositioning, DEFAULTS, usePosition } from "@web/core/position/position_hook";
|
||||
import { registerCleanup } from "../helpers/cleanup";
|
||||
import { getFixture, nextTick, patchWithCleanup, triggerEvent } from "../helpers/utils";
|
||||
|
||||
const { Component, mount, tags } = owl;
|
||||
const { css, xml } = tags;
|
||||
let container;
|
||||
let reference;
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} popper
|
||||
* @param {import("@web/core/position/position_hook").Position} [position="bottom"]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isWellPositioned(popper, position = "bottom") {
|
||||
/** @type {import("@web/core/position/position_hook").Options} */
|
||||
const options = {
|
||||
...DEFAULTS,
|
||||
container,
|
||||
position,
|
||||
};
|
||||
const [direction, variant = "middle"] = position.split("-");
|
||||
const d = /** @type {import("@web/core/position/position_hook").DirectionsDataKey} */ (direction[0]);
|
||||
const v = /** @type {import("@web/core/position/position_hook").VariantsDataKey} */ (variant[0]);
|
||||
const posSolution = computePositioning(reference, popper, options).get(d, v);
|
||||
const hasCorrectClass = popper.classList.contains(posSolution.className);
|
||||
const correctLeft = parseFloat(popper.style.left) === posSolution.left;
|
||||
const correctTop = parseFloat(popper.style.top) === posSolution.top;
|
||||
return hasCorrectClass && correctLeft && correctTop;
|
||||
}
|
||||
|
||||
class TestComp extends Component {
|
||||
setup() {
|
||||
usePosition(reference, this.constructor.popperOptions);
|
||||
}
|
||||
}
|
||||
TestComp.style = css`
|
||||
#popper {
|
||||
background-color: cyan;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
}
|
||||
`;
|
||||
TestComp.template = xml`<div id="popper"/>`;
|
||||
/** @type {import("@web/core/position/position_hook").Options} */
|
||||
TestComp.popperOptions = {};
|
||||
|
||||
QUnit.module("usePosition Hook", {
|
||||
async beforeEach() {
|
||||
reference = document.createElement("div");
|
||||
reference.id = "reference";
|
||||
reference.style.backgroundColor = "yellow";
|
||||
reference.style.height = "50px";
|
||||
reference.style.width = "50px";
|
||||
|
||||
// Force container style, to make these tests independent of screen size
|
||||
container = document.createElement("div");
|
||||
container.id = "container";
|
||||
container.style.backgroundColor = "pink";
|
||||
container.style.height = "450px";
|
||||
container.style.width = "450px";
|
||||
container.style.display = "flex";
|
||||
container.style.alignItems = "center";
|
||||
container.style.justifyContent = "center";
|
||||
container.appendChild(reference);
|
||||
getFixture().prepend(container);
|
||||
registerCleanup(() => {
|
||||
getFixture().removeChild(container);
|
||||
});
|
||||
TestComp.popperOptions = { container };
|
||||
|
||||
patchWithCleanup(browser, { setTimeout: (func) => func() });
|
||||
},
|
||||
});
|
||||
|
||||
QUnit.test("default position is bottom", async (assert) => {
|
||||
const popper = await mount(TestComp, { target: container });
|
||||
assert.ok(isWellPositioned(popper.el, "bottom"));
|
||||
});
|
||||
|
||||
QUnit.test("can add margin", async (assert) => {
|
||||
let margin = 0;
|
||||
Object.assign(TestComp.popperOptions, { margin });
|
||||
let popper = await mount(TestComp, { target: container });
|
||||
const popBox1 = popper.el.getBoundingClientRect();
|
||||
popper.destroy();
|
||||
|
||||
margin = 20;
|
||||
Object.assign(TestComp.popperOptions, { margin });
|
||||
popper = await mount(TestComp, { target: container });
|
||||
const popBox2 = popper.el.getBoundingClientRect();
|
||||
popper.destroy();
|
||||
|
||||
assert.strictEqual(popBox1.top + margin, popBox2.top);
|
||||
});
|
||||
|
||||
QUnit.test("can use a t-ref as popper", async (assert) => {
|
||||
patchWithCleanup(TestComp, {
|
||||
popperOptions: { ...TestComp.popperOptions, popper: "popperRef" },
|
||||
template: xml`
|
||||
<div id="not-popper">
|
||||
<div id="popper" t-ref="popperRef"/>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
const comp = await mount(TestComp, { target: container });
|
||||
assert.notOk(isWellPositioned(comp.el));
|
||||
assert.ok(isWellPositioned(comp.el.querySelector("#popper")));
|
||||
});
|
||||
|
||||
const getPositionTest = (position, positionToCheck) => {
|
||||
return async (assert) => {
|
||||
Object.assign(TestComp.popperOptions, { position });
|
||||
const popper = await mount(TestComp, { target: container });
|
||||
positionToCheck = positionToCheck || position;
|
||||
assert.ok(isWellPositioned(popper.el, positionToCheck));
|
||||
};
|
||||
};
|
||||
QUnit.test("position top", getPositionTest("top"));
|
||||
QUnit.test("position left", getPositionTest("left"));
|
||||
QUnit.test("position bottom", getPositionTest("bottom"));
|
||||
QUnit.test("position right", getPositionTest("right"));
|
||||
QUnit.test("position top-start", getPositionTest("top-start"));
|
||||
QUnit.test("position top-middle", getPositionTest("top-middle"));
|
||||
QUnit.test("position top-end", getPositionTest("top-end"));
|
||||
QUnit.test("position left-start", getPositionTest("left-start"));
|
||||
QUnit.test("position left-middle", getPositionTest("left-middle"));
|
||||
QUnit.test("position left-end", getPositionTest("left-end"));
|
||||
QUnit.test("position bottom-start", getPositionTest("bottom-start"));
|
||||
QUnit.test("position bottom-middle", getPositionTest("bottom-middle"));
|
||||
QUnit.test("position bottom-end", getPositionTest("bottom-end"));
|
||||
QUnit.test("position right-start", getPositionTest("right-start"));
|
||||
QUnit.test("position right-middle", getPositionTest("right-middle"));
|
||||
QUnit.test("position right-end", getPositionTest("right-end"));
|
||||
QUnit.test("position top === top-middle", getPositionTest("top", "top-middle"));
|
||||
QUnit.test("position left === left-middle", getPositionTest("left", "left-middle"));
|
||||
QUnit.test("position bottom === bottom-middle", getPositionTest("bottom", "bottom-middle"));
|
||||
QUnit.test("position right === right-middle", getPositionTest("right", "right-middle"));
|
||||
|
||||
const CONTAINER_STYLE_MAP = {
|
||||
top: { alignItems: "flex-start" },
|
||||
bottom: { alignItems: "flex-end" },
|
||||
left: { justifyContent: "flex-start" },
|
||||
right: { justifyContent: "flex-end" },
|
||||
slimfit: { height: "100px", width: "100px" }, // height and width of popper
|
||||
h125: { height: "125px" }, // height of popper + 1/2 reference
|
||||
w125: { width: "125px" }, // width of popper + 1/2 reference
|
||||
};
|
||||
const getRepositionTest = (from, to, containerStyleChanges) => {
|
||||
return async (assert) => {
|
||||
Object.assign(TestComp.popperOptions, { position: from });
|
||||
const popper = await mount(TestComp, { target: container });
|
||||
assert.ok(isWellPositioned(popper.el, from), `points to ${from} direction`);
|
||||
|
||||
// Change container style and force update
|
||||
for (const styleToApply of containerStyleChanges.split(" ")) {
|
||||
Object.assign(container.style, CONTAINER_STYLE_MAP[styleToApply]);
|
||||
}
|
||||
triggerEvent(document, null, "scroll");
|
||||
await nextTick();
|
||||
assert.ok(isWellPositioned(popper.el, to), `points to ${to} direction`);
|
||||
};
|
||||
};
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from top-start to bottom-start",
|
||||
getRepositionTest("top-start", "bottom-start", "top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-start to bottom-middle",
|
||||
getRepositionTest("top-start", "bottom-middle", "top w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-start to bottom-end",
|
||||
getRepositionTest("top-start", "bottom-end", "top right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-start to right-start",
|
||||
getRepositionTest("top-start", "right-start", "h125 top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-start to right-middle",
|
||||
getRepositionTest("top-start", "right-middle", "h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-start to right-end",
|
||||
getRepositionTest("top-start", "right-end", "h125 bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-start to left-start",
|
||||
getRepositionTest("top-start", "left-start", "h125 right top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-start to left-middle",
|
||||
getRepositionTest("top-start", "left-middle", "h125 right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-start to left-end",
|
||||
getRepositionTest("top-start", "left-end", "h125 right bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-start to top-start",
|
||||
getRepositionTest("top-start", "top-start", "slimfit")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-start to top-middle",
|
||||
getRepositionTest("top-start", "top-middle", "w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-start to top-end",
|
||||
getRepositionTest("top-start", "top-end", "right")
|
||||
);
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from top-middle to bottom-start",
|
||||
getRepositionTest("top-middle", "bottom-start", "top left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-middle to bottom-middle",
|
||||
getRepositionTest("top-middle", "bottom-middle", "top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-middle to bottom-end",
|
||||
getRepositionTest("top-middle", "bottom-end", "top right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-middle to right-start",
|
||||
getRepositionTest("top-middle", "right-start", "h125 top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-middle to right-middle",
|
||||
getRepositionTest("top-middle", "right-middle", "h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-middle to right-end",
|
||||
getRepositionTest("top-middle", "right-end", "h125 bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-middle to left-start",
|
||||
getRepositionTest("top-middle", "left-start", "h125 right top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-middle to left-middle",
|
||||
getRepositionTest("top-middle", "left-middle", "h125 right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-middle to left-end",
|
||||
getRepositionTest("top-middle", "left-end", "h125 right bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-middle to top-start",
|
||||
getRepositionTest("top-middle", "top-start", "left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-middle to top-middle",
|
||||
getRepositionTest("top-middle", "top-middle", "slimfit")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-middle to top-end",
|
||||
getRepositionTest("top-middle", "top-end", "right")
|
||||
);
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from top-end to bottom-start",
|
||||
getRepositionTest("top-end", "bottom-start", "top left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-end to bottom-middle",
|
||||
getRepositionTest("top-end", "bottom-middle", "top w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-end to bottom-end",
|
||||
getRepositionTest("top-end", "bottom-end", "top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-end to right-start",
|
||||
getRepositionTest("top-end", "right-start", "h125 top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-end to right-middle",
|
||||
getRepositionTest("top-end", "right-middle", "h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-end to right-end",
|
||||
getRepositionTest("top-end", "right-end", "h125 bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-end to left-start",
|
||||
getRepositionTest("top-end", "left-start", "h125 right top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-end to left-middle",
|
||||
getRepositionTest("top-end", "left-middle", "h125 right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-end to left-end",
|
||||
getRepositionTest("top-end", "left-end", "h125 right bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-end to top-start",
|
||||
getRepositionTest("top-end", "top-start", "left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-end to top-middle",
|
||||
getRepositionTest("top-end", "top-middle", "w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from top-end to top-end",
|
||||
getRepositionTest("top-end", "top-end", "slimfit")
|
||||
);
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from left-start to bottom-start",
|
||||
getRepositionTest("left-start", "bottom-start", "w125 left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-start to bottom-middle",
|
||||
getRepositionTest("left-start", "bottom-middle", "w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-start to bottom-end",
|
||||
getRepositionTest("left-start", "bottom-end", "w125 right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-start to right-start",
|
||||
getRepositionTest("left-start", "right-start", "left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-start to right-middle",
|
||||
getRepositionTest("left-start", "right-middle", "left h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-start to right-end",
|
||||
getRepositionTest("left-start", "right-end", "left bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-start to left-start",
|
||||
getRepositionTest("left-start", "left-start", "slimfit")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-start to left-middle",
|
||||
getRepositionTest("left-start", "left-middle", "h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-start to left-end",
|
||||
getRepositionTest("left-start", "left-end", "bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-start to top-start",
|
||||
getRepositionTest("left-start", "top-start", "w125 bottom left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-start to top-middle",
|
||||
getRepositionTest("left-start", "top-middle", "w125 bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-start to top-end",
|
||||
getRepositionTest("left-start", "top-end", "w125 bottom right")
|
||||
);
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from left-middle to bottom-start",
|
||||
getRepositionTest("left-middle", "bottom-start", "w125 left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-middle to bottom-middle",
|
||||
getRepositionTest("left-middle", "bottom-middle", "w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-middle to bottom-end",
|
||||
getRepositionTest("left-middle", "bottom-end", "w125 right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-middle to right-start",
|
||||
getRepositionTest("left-middle", "right-start", "left top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-middle to right-middle",
|
||||
getRepositionTest("left-middle", "right-middle", "left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-middle to right-end",
|
||||
getRepositionTest("left-middle", "right-end", "left bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-middle to top-start",
|
||||
getRepositionTest("left-middle", "top-start", "w125 bottom left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-middle to top-middle",
|
||||
getRepositionTest("left-middle", "top-middle", "w125 bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-middle to top-end",
|
||||
getRepositionTest("left-middle", "top-end", "w125 bottom right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-middle to left-start",
|
||||
getRepositionTest("left-middle", "left-start", "top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-middle to left-middle",
|
||||
getRepositionTest("left-middle", "left-middle", "slimfit")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-middle to left-end",
|
||||
getRepositionTest("left-middle", "left-end", "bottom")
|
||||
);
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from left-end to bottom-start",
|
||||
getRepositionTest("left-end", "bottom-start", "w125 left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-end to bottom-middle",
|
||||
getRepositionTest("left-end", "bottom-middle", "w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-end to bottom-end",
|
||||
getRepositionTest("left-end", "bottom-end", "w125 right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-end to right-start",
|
||||
getRepositionTest("left-end", "right-start", "left top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-end to right-middle",
|
||||
getRepositionTest("left-end", "right-middle", "left h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-end to right-end",
|
||||
getRepositionTest("left-end", "right-end", "left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-end to top-start",
|
||||
getRepositionTest("left-end", "top-start", "w125 bottom left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-end to top-middle",
|
||||
getRepositionTest("left-end", "top-middle", "w125 bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-end to top-end",
|
||||
getRepositionTest("left-end", "top-end", "w125 bottom right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-end to left-start",
|
||||
getRepositionTest("left-end", "left-start", "top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-end to left-middle",
|
||||
getRepositionTest("left-end", "left-middle", "h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from left-end to left-end",
|
||||
getRepositionTest("left-end", "left-end", "slimfit")
|
||||
);
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to bottom-start",
|
||||
getRepositionTest("bottom-start", "bottom-start", "slimfit")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to bottom-middle",
|
||||
getRepositionTest("bottom-start", "bottom-middle", "w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to bottom-end",
|
||||
getRepositionTest("bottom-start", "bottom-end", "right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to right-start",
|
||||
getRepositionTest("bottom-start", "right-start", "h125 top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to right-middle",
|
||||
getRepositionTest("bottom-start", "right-middle", "h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to right-end",
|
||||
getRepositionTest("bottom-start", "right-end", "h125 bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to top-start",
|
||||
getRepositionTest("bottom-start", "top-start", "bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to top-middle",
|
||||
getRepositionTest("bottom-start", "top-middle", "bottom w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to top-end",
|
||||
getRepositionTest("bottom-start", "top-end", "bottom right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to left-start",
|
||||
getRepositionTest("bottom-start", "left-start", "h125 right top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to left-middle",
|
||||
getRepositionTest("bottom-start", "left-middle", "h125 right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-start to left-end",
|
||||
getRepositionTest("bottom-start", "left-end", "h125 right bottom")
|
||||
);
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to bottom-start",
|
||||
getRepositionTest("bottom-middle", "bottom-start", "left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to bottom-middle",
|
||||
getRepositionTest("bottom-middle", "bottom-middle", "slimfit")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to bottom-end",
|
||||
getRepositionTest("bottom-middle", "bottom-end", "right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to right-start",
|
||||
getRepositionTest("bottom-middle", "right-start", "h125 top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to right-middle",
|
||||
getRepositionTest("bottom-middle", "right-middle", "h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to right-end",
|
||||
getRepositionTest("bottom-middle", "right-end", "h125 bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to top-start",
|
||||
getRepositionTest("bottom-middle", "top-start", "bottom left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to top-middle",
|
||||
getRepositionTest("bottom-middle", "top-middle", "bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to top-end",
|
||||
getRepositionTest("bottom-middle", "top-end", "bottom right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to left-start",
|
||||
getRepositionTest("bottom-middle", "left-start", "h125 right top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to left-middle",
|
||||
getRepositionTest("bottom-middle", "left-middle", "h125 right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-middle to left-end",
|
||||
getRepositionTest("bottom-middle", "left-end", "h125 right bottom")
|
||||
);
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to bottom-start",
|
||||
getRepositionTest("bottom-end", "bottom-start", "left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to bottom-middle",
|
||||
getRepositionTest("bottom-end", "bottom-middle", "w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to bottom-end",
|
||||
getRepositionTest("bottom-end", "bottom-end", "slimfit")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to right-start",
|
||||
getRepositionTest("bottom-end", "right-start", "h125 top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to right-middle",
|
||||
getRepositionTest("bottom-end", "right-middle", "h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to right-end",
|
||||
getRepositionTest("bottom-end", "right-end", "h125 bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to top-start",
|
||||
getRepositionTest("bottom-end", "top-start", "bottom left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to top-middle",
|
||||
getRepositionTest("bottom-end", "top-middle", "bottom w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to top-end",
|
||||
getRepositionTest("bottom-end", "top-end", "bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to left-start",
|
||||
getRepositionTest("bottom-end", "left-start", "h125 right top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to left-middle",
|
||||
getRepositionTest("bottom-end", "left-middle", "h125 right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from bottom-end to left-end",
|
||||
getRepositionTest("bottom-end", "left-end", "h125 right bottom")
|
||||
);
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from right-start to bottom-start",
|
||||
getRepositionTest("right-start", "bottom-start", "w125 top left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-start to bottom-middle",
|
||||
getRepositionTest("right-start", "bottom-middle", "w125 top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-start to bottom-end",
|
||||
getRepositionTest("right-start", "bottom-end", "w125 top right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-start to right-start",
|
||||
getRepositionTest("right-start", "right-start", "slimfit")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-start to right-middle",
|
||||
getRepositionTest("right-start", "right-middle", "h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-start to right-end",
|
||||
getRepositionTest("right-start", "right-end", "bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-start to top-start",
|
||||
getRepositionTest("right-start", "top-start", "w125 left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-start to top-middle",
|
||||
getRepositionTest("right-start", "top-middle", "w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-start to top-end",
|
||||
getRepositionTest("right-start", "top-end", "w125 right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-start to left-start",
|
||||
getRepositionTest("right-start", "left-start", "right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-start to left-middle",
|
||||
getRepositionTest("right-start", "left-middle", "right h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-start to left-end",
|
||||
getRepositionTest("right-start", "left-end", "right bottom")
|
||||
);
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from right-middle to bottom-start",
|
||||
getRepositionTest("right-middle", "bottom-start", "w125 top left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-middle to bottom-middle",
|
||||
getRepositionTest("right-middle", "bottom-middle", "w125 top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-middle to bottom-end",
|
||||
getRepositionTest("right-middle", "bottom-end", "w125 top right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-middle to right-start",
|
||||
getRepositionTest("right-middle", "right-start", "top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-middle to right-middle",
|
||||
getRepositionTest("right-middle", "right-middle", "slimfit")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-middle to right-end",
|
||||
getRepositionTest("right-middle", "right-end", "bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-middle to left-start",
|
||||
getRepositionTest("right-middle", "left-start", "right top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-middle to left-middle",
|
||||
getRepositionTest("right-middle", "left-middle", "right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-middle to left-end",
|
||||
getRepositionTest("right-middle", "left-end", "right bottom")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-middle to top-start",
|
||||
getRepositionTest("right-middle", "top-start", "w125 left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-middle to top-middle",
|
||||
getRepositionTest("right-middle", "top-middle", "w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-middle to top-end",
|
||||
getRepositionTest("right-middle", "top-end", "w125 right")
|
||||
);
|
||||
// -----------------------------------------------------------------------------
|
||||
QUnit.test(
|
||||
"reposition from right-end to bottom-start",
|
||||
getRepositionTest("right-end", "bottom-start", "w125 top left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-end to bottom-middle",
|
||||
getRepositionTest("right-end", "bottom-middle", "w125 top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-end to bottom-end",
|
||||
getRepositionTest("right-end", "bottom-end", "w125 top right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-end to right-start",
|
||||
getRepositionTest("right-end", "right-start", "top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-end to right-middle",
|
||||
getRepositionTest("right-end", "right-middle", "h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-end to right-end",
|
||||
getRepositionTest("right-end", "right-end", "slimfit")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-end to left-start",
|
||||
getRepositionTest("right-end", "left-start", "right top")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-end to left-middle",
|
||||
getRepositionTest("right-end", "left-middle", "right h125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-end to left-end",
|
||||
getRepositionTest("right-end", "left-end", "right")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-end to top-start",
|
||||
getRepositionTest("right-end", "top-start", "w125 left")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-end to top-middle",
|
||||
getRepositionTest("right-end", "top-middle", "w125")
|
||||
);
|
||||
QUnit.test(
|
||||
"reposition from right-end to top-end",
|
||||
getRepositionTest("right-end", "top-end", "w125 right")
|
||||
);
|
||||
@@ -46,6 +46,9 @@ export function patchWithCleanup(obj, patchValue, options) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
export function getFixture() {
|
||||
if (QUnit.config.debug) {
|
||||
return document.body;
|
||||
|
||||
Reference in New Issue
Block a user