test(floating): exhaustive parity coverage — +2 bugs (getScale basis, window scrollbar)

Drives the dual-system comparison suites to 487 cases (350 synthetic + 137 real-
DOM, all green) by implementing the gaps a coverage-enumeration audit
(floating-parity-coverage-gaps, 9 agents) found untested. Two more real bugs
surfaced and fixed:

- rects.ts getRectRelativeToOffsetParent passed the ELEMENT's own scale as the
  getBoundingClientRect basis; floating-ui always uses the offsetParent's. They
  diverge on an anisotropically-scaled or bordered offsetParent (getScale's
  fallback rounds the two boxes differently) → sub-pixel reference shift. Now
  passes offsetParent as the basis (the anisotropic-scale tests caught it).
- rects.ts omitted floating-ui's setLeftRTLScrollbarOffset for a window
  offsetParent (offsets.x = getWindowScrollBarX(documentElement)). ~0 on a normal
  LTR document, but non-zero under a left-side scrollbar / writing-mode:vertical-rl
  → wrong flip decision (the writing-mode test caught it). Also ports the
  getViewportRect scrollbar-gutter correction + SCROLLBAR_MAX, and the isTopLayer
  short-circuit in getClippingRect (native popover / modal <dialog> escape clip).

New coverage (synthetic): flip bestFit/fallbackStrategy/crossAxis-alignment/
multi-step, limitShift offset-object/axis-toggles/origin-side, size single-axis
shift + symmetric shrink, offset crossAxis under RTL, hide boundary + numeric
offsets, arrow padding clamp, the reset loop (bare-true preserve + MAX_RESET_COUNT),
padding-object × offsetScale, shift data delta.

New coverage (real-DOM): document scrollbar, reference-clip ≠ floating-clip
(altBoundary), anisotropic + scaled-and-scrolled offsetParent, scaled clipping
ancestor, multi-element collisionBoundary, different offsetParents, non-default
flip options, modal <dialog>, body-as-scroller, nested table chain, transformed
<html> scrolled, thick asymmetric border, writing-mode vertical-rl, shadow
crossing, floating in two scrollers, fixed×scaled sweep.

Documented as deliberate non-features (out of the frozen consumer API): derivable
function options, virtual-anchor contextElement, rootBoundary:'document',
visualViewport pinch-zoom, deep cross-iframe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
menubar-v4-safe
dev 3 months ago
parent a2500e9e31
commit f204b55741

@ -137,27 +137,40 @@ engine.test.ts✅ 13-case parity guard: runMiddleware ↔ @floating-ui/core, ide
- `floating.svelte.ts` selects own/fui factories via a loosely-typed factory set +
branches `autoUpdate`. `@floating-ui` stays the default for A/B (P4 deletes it).
## Verification done — comparison suites + 9 bugs found & fixed
## Verification done — comparison suites + 11 bugs found & fixed
Two parity suites assert **own engine == `@floating-ui`** API output (no removal, no
migration; flag stays OFF). Together they found 9 real bugs (4 critical, in the P1
DOM-read layer); all fixed and re-verified.
migration; flag stays OFF). They found 11 real bugs total (4 critical, in the P1
DOM-read layer); all fixed and re-verified. **487 comparison cases, all green.**
- **Math/middleware — `engine/engine.test.ts` (232 cases, green).** `runMiddleware`
- **Math/middleware — `engine/engine.test.ts` (350 cases, green).** `runMiddleware`
(the real loop) vs `@floating-ui/core`'s `computePosition`, both fed identical
synthetic rects (a synthetic floating-ui `platform` mirrors `snapshotOf`). Full matrix:
12 placements × 7 edge positions (full chain) + flip-at-edges + shift (main/cross ×
limiter × edges) + arrow (+ tiny-ref alignmentOffset) + size (each side ± shift) +
hide (across clip) + extremes (oversized floating/ref, zero-size, fractional, negative
padding, huge offset) + scroll/scale/rtl variants. Pixel-identical.
- **Real-DOM/full-pipeline — `engine/engine-dom.svelte.test.ts` (60 cases, green;
runs in the `client` chromium project).** BOTH systems' `computePosition` on the SAME
real elements — this is the ONLY suite that exercises `rects.ts` + `clipping.ts`
(the synthetic suite injects rects). Limit scenarios: all placements, viewport-edge
flip/shift, nested scroll container, transformed ancestor, **CSS-scaled offsetParent**,
fixed strategy, scrolled page, individual-transform (`translate`/`scale`/`rotate`)
containing block, static-table-cell offsetParent, **`position:fixed` escaping a scroll
container**, arrow + size.
synthetic rects (a synthetic floating-ui `platform` mirrors `snapshotOf`). Covers every
middleware branch: 12 placements × 7 edge positions (full chain); flip (edges,
fallbackPlacements, fallbackAxisSideDirection, crossAxis true/false/alignment,
fallbackStrategy bestFit/initialPlacement, flipAlignment, multi-step oscillation, the
bestFit "nothing fits" reduce); shift (main/cross × limitShift incl. offset object /
axis toggles / origin-side); offset (number/negative/crossAxis/alignmentAxis, RTL
mirror, the post-flip re-run); arrow (centering, tiny-ref alignmentOffset, the
largestPossiblePadding clamp); size (each side, aligned ± rtl, single-axis shift,
noShift shrink); hide (referenceHidden + escaped + numeric offsets + exact boundary);
the reset loop (bare-true preserves coords, MAX_RESET_COUNT cap, placement reset);
per-side padding × offsetScale; extremes (oversized/zero/fractional/neg-padding).
- **Real-DOM/full-pipeline — `engine/engine-dom.svelte.test.ts` (137 cases, green;
`client` chromium project).** BOTH systems' `computePosition` on the SAME real elements
— the ONLY suite exercising `rects.ts` + `clipping.ts` (synthetic injects rects). Limit
scenarios: all placements; viewport-edge flip/shift; single + deeply-nested scroll
containers; floating inside 2 scrollers; transformed / **CSS-scaled** (isotropic +
**anisotropic**) / **scaled-and-scrolled** offsetParent; **scaled clipping ancestor**;
fixed strategy (+ fixed×scaled sweep); scrolled page; **transformed `<html>` on a
scrolled page**; individual-transform containing block; static-table-cell + **nested
table chain**; `position:fixed` escaping a scroll container; native-popover + modal-
`<dialog>` top-layer clip escape; **reference clip ≠ floating clip (altBoundary)**;
multi-element collisionBoundary; **reference & floating under different offsetParents**;
virtual `Measurable` anchor; **RTL** + **writing-mode vertical-rl**; shadow DOM
(same-level + crossing); sticky ancestor; visible scrollbar; **document scrollbar /
body-as-scroller**; thick asymmetric offsetParent border; body/html transform; body
margin; arrow (all placements) + size.
**Bugs found & fixed (all confirmed independently by the adversarial audit workflow
`floating-engine-parity-audit`, 25 agents):**
@ -186,8 +199,38 @@ DOM-read layer); all fixed and re-verified.
9. **`getOverflowAncestors` (LOW)** — include `win.visualViewport` so `auto-update`
binds zoom listeners (filtered out of the clip math).
Audit also classified the documented simplifications (arrow clientSize, size no-reset,
single-document iframe/clipping, the `handle-scroll` non-emit) as intentional — not bugs.
The **coverage-gap audit** (`floating-parity-coverage-gaps`, 9 agents — enumerates every
floating-ui branch NOT yet tested, then I implemented the tractable ones) drove the test
counts to 487 and surfaced 2 MORE real bugs:
10. **`getScale` basis (MED)** — `getRectRelativeToOffsetParent` passed the element's own
scale instead of the offsetParent's (floating-ui's 4th arg is always the offsetParent).
They differ on an anisotropically-scaled or bordered offsetParent (getScale's fallback
rounds the two boxes differently) → a sub-pixel reference shift. Fixed: always pass
`offsetParent` as the scale basis.
11. **window-offsetParent scrollbar offset (MED)** — getRect omitted floating-ui's
`setLeftRTLScrollbarOffset` (`offsets.x = getWindowScrollBarX(documentElement)`) for a
window offsetParent. ~0 on a normal LTR doc (so it passed for a year) but non-zero on a
left-side scrollbar / `writing-mode: vertical-rl` → wrong flip decision. Also added
floating-ui's `getViewportRect` scrollbar-gutter correction + the `SCROLLBAR_MAX` cap +
`isTopLayer` short-circuit in `getClippingRect`.
Audit classified the documented simplifications as intentional — not bugs: arrow clientSize
(`rects.floating` vs `offsetParent.clientWidth` — identical for the borderless wrapper),
size no-reset, single-document iframe/clipping, the `handle-scroll` non-emit.
**Deliberate non-features (out of the frozen consumer API — NOT bugs, do not "fix"):**
- **Derivable (function) middleware options** — floating-ui resolves `offset((state)=>…)`
via `evaluate`; our factories take static values. Every soma call site passes static
options, so this is intentionally unsupported (adding `evaluate` everywhere for an unused
feature would bloat the engine).
- **Virtual-anchor `contextElement` scale unwrap** — floating-ui divides a virtual rect by
its `contextElement`'s scale; the frozen `Measurable` type is `{ getBoundingClientRect }`
with no `contextElement`, so soma's `customAnchor` never carries one.
- **`rootBoundary: 'document'`** (`getDocumentRect`) — `floating.svelte` never sets
`rootBoundary`; only `'viewport'` is reachable.
- **visualViewport pinch-zoom offset** — handled in code (the `isWebKit` gate) but not
scriptable in headless chromium, so unverified by test.
- **Deep cross-iframe** offset accumulation — single-document fidelity (header-documented).
### ⚠️ Preview-tool caveat for the broad browser A/B (read before P2/P3)

@ -11,6 +11,7 @@ import {
createCoords,
getBoundingClientRect,
getScale,
getWindowScrollBarX,
isContainingBlock,
isWebKit,
rectToClientRect
@ -18,10 +19,30 @@ import {
const { max, min } = Math
// Most scrollbars are 15–18px; cap the correction so unusual styles can't
// produce a wild value. Matches floating-ui's SCROLLBAR_MAX.
const SCROLLBAR_MAX = 25
function getComputed(el: Element, win: Window): CSSStyleDeclaration {
return win.getComputedStyle(el)
}
/** Is the element in the top layer (native `popover` / `<dialog>` modal)? Such an
* element escapes all scroll-container clipping — only the viewport bounds it.
* Mirrors floating-ui's `isTopLayer`. */
function isTopLayer(el: Element): boolean {
try {
if (el.matches(':popover-open')) return true
} catch {
// :popover-open unsupported — fall through.
}
try {
return el.matches(':modal')
} catch {
return false
}
}
/** Does the element clip its overflowing content (scroll/clip/hidden)? */
export function isOverflowElement(el: Element, win: Window): boolean {
const { overflow, overflowX, overflowY, display } = getComputed(el, win)
@ -139,6 +160,28 @@ function getViewportRect(el: Element, strategy: Strategy, win: Window): Rect {
y = vv.offsetTop
}
}
// `scrollbar-gutter: stable` + `overflow: hidden` on <html> reserves gutter
// space that html.clientWidth counts but isn't visually usable. Mirror
// floating-ui's correction (a no-op in the common no-gutter case). A left-side
// scrollbar instead widens the usable area by its width.
const windowScrollbarX = getWindowScrollBarX(html, win)
if (windowScrollbarX <= 0) {
const doc = html.ownerDocument
const body = doc.body
const bodyStyles = getComputed(body, win)
const bodyMarginInline =
doc.compatMode === 'CSS1Compat'
? parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight) || 0
: 0
const clippingStableScrollbarWidth = Math.abs(html.clientWidth - body.clientWidth - bodyMarginInline)
if (clippingStableScrollbarWidth <= SCROLLBAR_MAX) {
width -= clippingStableScrollbarWidth
}
} else if (windowScrollbarX <= SCROLLBAR_MAX) {
width += windowScrollbarX
}
return { width, height, x, y }
}
@ -178,7 +221,11 @@ export function getClippingRect(
win: Window
): Rect {
const ancestors: (Element | 'viewport')[] =
boundary === 'clippingAncestors' ? getClippingElementAncestors(element, win) : [...boundary]
boundary === 'clippingAncestors'
? isTopLayer(element)
? []
: getClippingElementAncestors(element, win)
: [...boundary]
ancestors.push('viewport')
const first = clipOf(element, ancestors[0], strategy, win)

@ -23,12 +23,15 @@ import {
arrow as fuiArrow,
computePosition as fuiComputePosition,
flip as fuiFlip,
hide as fuiHide,
offset as fuiOffset,
shift as fuiShift,
size as fuiSize,
type Middleware as FuiMiddleware
} from '@floating-ui/dom'
import { hide as ownHide } from './middleware'
import type { Middleware } from './types'
import type { Measurable } from '../types'
import type { Placement, Strategy } from '../placement'
const dom = createActiveDom()
@ -50,13 +53,17 @@ afterEach(() => {
for (const el of trash) el.remove()
trash.length = 0
window.scrollTo(0, 0)
document.documentElement.removeAttribute('dir')
document.body.removeAttribute('dir')
document.body.style.cssText = ''
document.documentElement.style.cssText = ''
})
// Run both engines on the same elements + equivalent middleware specs.
type Built = { own: Middleware[]; fui: FuiMiddleware[] }
async function bothPositions(
anchor: HTMLElement,
anchor: Measurable | HTMLElement,
floating: HTMLElement,
mw: Built,
placement: Placement,
@ -386,6 +393,870 @@ describe('real-DOM parity — fixed floating escaping a scroll container (clip d
}
})
describe('real-DOM parity — virtual anchor (Measurable)', () => {
for (const placement of ['bottom', 'top-start', 'right', 'left-end'] as Placement[]) {
it(placement, async () => {
// A virtual reference (context-menu pattern): only getBoundingClientRect.
const rect = { x: 380, y: 280, width: 4, height: 4, top: 280, left: 380, right: 384, bottom: 284 }
const virtual: Measurable = { getBoundingClientRect: () => rect as DOMRect }
const floating = box({ position: 'absolute', width: '200px', height: '120px' })
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(virtual, floating, mw, placement)
assertSame(`virtual ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — RTL document', () => {
for (const placement of ['bottom-start', 'bottom-end', 'top-start', 'right-start'] as Placement[]) {
it(placement, async () => {
document.documentElement.setAttribute('dir', 'rtl')
const anchor = box({
position: 'absolute',
left: '420px',
top: '300px',
width: '90px',
height: '34px'
})
const floating = box({ position: 'absolute', width: '200px', height: '120px' })
const mw: Built = {
own: [ownOffset({ mainAxis: 8, alignmentAxis: 10 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8, alignmentAxis: 10 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`rtl ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — shadow DOM', () => {
for (const placement of ['bottom', 'top-start', 'right'] as Placement[]) {
it(placement, async () => {
const host = box({ position: 'relative' })
const root = host.attachShadow({ mode: 'open' })
const anchor = document.createElement('div')
Object.assign(anchor.style, {
position: 'absolute',
left: '300px',
top: '260px',
width: '90px',
height: '34px'
})
const floating = document.createElement('div')
Object.assign(floating.style, { position: 'absolute', width: '200px', height: '120px' })
root.appendChild(anchor)
root.appendChild(floating)
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`shadow ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — deeply nested scroll containers', () => {
for (const [s1, s2] of [
[0, 0],
[80, 40],
[160, 120]
]) {
it(`outerScroll=${s1} innerScroll=${s2}`, async () => {
const outer = box({
position: 'absolute',
left: '80px',
top: '80px',
width: '420px',
height: '300px',
overflow: 'auto',
border: '1px solid'
})
const outerTall = box({ height: '900px', width: '700px', position: 'relative' }, outer)
const inner = box(
{
position: 'absolute',
left: '60px',
top: '120px',
width: '260px',
height: '180px',
overflow: 'auto',
border: '1px solid'
},
outerTall
)
const innerTall = box({ height: '600px', width: '500px', position: 'relative' }, inner)
const anchor = box(
{ position: 'absolute', left: '40px', top: '220px', width: '90px', height: '34px' },
innerTall
)
const floating = box({ position: 'absolute', width: '200px', height: '120px' })
outer.scrollTop = s1
inner.scrollTop = s2
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 4 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 4 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, 'bottom')
assertSame(`nested2 ${s1}/${s2}`, own, fui)
})
}
})
describe('real-DOM parity — hide (referenceHidden) in a scroll container', () => {
for (const scroll of [0, 260, 520]) {
it(`scrollTop=${scroll}`, async () => {
const scroller = box({
position: 'absolute',
left: '120px',
top: '120px',
width: '300px',
height: '200px',
overflow: 'auto',
border: '1px solid'
})
const tall = box({ height: '900px', width: '500px', position: 'relative' }, scroller)
const anchor = box(
{ position: 'absolute', left: '40px', top: '120px', width: '90px', height: '34px' },
tall
)
const floating = box({ position: 'absolute', width: '200px', height: '120px' })
scroller.scrollTop = scroll
const own = await ownComputePosition(anchor, floating, {
placement: 'bottom',
strategy: 'absolute',
middleware: [ownOffset({ mainAxis: 8 }), ownHide({ strategy: 'referenceHidden' })],
dom
})
const fui = await fuiComputePosition(anchor, floating, {
placement: 'bottom',
strategy: 'absolute',
middleware: [fuiOffset({ mainAxis: 8 }), fuiHide({ strategy: 'referenceHidden' })]
})
const oh = (own.middlewareData.hide ?? {}) as { referenceHidden?: boolean }
const fh = (fui.middlewareData.hide ?? {}) as { referenceHidden?: boolean }
expect(!!oh.referenceHidden, `hide@${scroll}`).toBe(!!fh.referenceHidden)
})
}
})
describe('real-DOM parity — arrow across all placements (real layout)', () => {
for (const placement of PLACEMENTS) {
it(placement, async () => {
const anchor = box({
position: 'absolute',
left: '420px',
top: '320px',
width: '110px',
height: '38px'
})
const floating = box({ position: 'absolute', width: '220px', height: '150px' })
const arrowEl = box({ position: 'absolute', width: '14px', height: '14px' }, floating)
const { own, fui } = await bothPositions(
anchor,
floating,
{
own: [ownOffset({ mainAxis: 10 }), ownArrow({ element: arrowEl, padding: 8 })],
fui: [fuiOffset({ mainAxis: 10 }), fuiArrow({ element: arrowEl, padding: 8 })]
},
placement
)
assertSame(`arrow ${placement}`, own, fui)
const oa = own.middlewareData.arrow as { x?: number; y?: number }
const fa = fui.middlewareData.arrow as { x?: number; y?: number }
expect(Math.abs((oa.x ?? oa.y ?? 0) - (fa.x ?? fa.y ?? 0)), `arrow center ${placement}`).toBeLessThanOrEqual(TOL)
})
}
})
describe('real-DOM parity — transform on body/html ancestor', () => {
for (const target of ['body', 'html'] as const) {
it(`transform on <${target}>`, async () => {
const el = target === 'body' ? document.body : document.documentElement
el.style.transform = 'translateZ(0)'
const anchor = box({
position: 'absolute',
left: '380px',
top: '300px',
width: '90px',
height: '34px'
})
const floating = box({ position: 'absolute', width: '200px', height: '120px' })
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, 'bottom')
assertSame(`transform-${target}`, own, fui)
})
}
})
describe('real-DOM parity — body margin (non-zero document origin)', () => {
it('body margin 24px', async () => {
document.body.style.margin = '24px'
const anchor = box({
position: 'absolute',
left: '380px',
top: '300px',
width: '90px',
height: '34px'
})
const floating = box({ position: 'absolute', width: '200px', height: '120px' })
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, 'bottom')
assertSame('body-margin', own, fui)
})
})
describe('real-DOM parity — sticky ancestor', () => {
for (const scroll of [0, 100]) {
it(`sticky scrollTop=${scroll}`, async () => {
const scroller = box({
position: 'absolute',
left: '100px',
top: '100px',
width: '320px',
height: '240px',
overflow: 'auto',
border: '1px solid'
})
const sticky = box({ position: 'sticky', top: '0px', width: '300px', height: '60px' }, scroller)
const tall = box({ height: '700px', width: '300px' }, scroller)
void tall
const anchor = box(
{ position: 'absolute', left: '40px', top: '10px', width: '90px', height: '34px' },
sticky
)
const floating = box({ position: 'absolute', width: '180px', height: '110px' })
scroller.scrollTop = scroll
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, 'bottom')
assertSame(`sticky ${scroll}`, own, fui)
})
}
})
describe('real-DOM parity — visible scrollbar (clientWidth < offsetWidth)', () => {
it('scroll container with a forced scrollbar', async () => {
const scroller = box({
position: 'absolute',
left: '120px',
top: '120px',
width: '320px',
height: '240px',
overflow: 'scroll',
border: '2px solid'
})
const tall = box({ height: '900px', width: '900px', position: 'relative' }, scroller)
const anchor = box(
{ position: 'absolute', left: '40px', top: '60px', width: '90px', height: '34px' },
tall
)
const floating = box({ position: 'absolute', width: '200px', height: '120px' })
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 4 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 4 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, 'bottom')
assertSame('scrollbar', own, fui)
})
})
describe('real-DOM parity — explicit collisionBoundary element', () => {
for (const placement of ['bottom', 'right'] as Placement[]) {
it(placement, async () => {
const boundary = box({
position: 'absolute',
left: '150px',
top: '150px',
width: '380px',
height: '260px',
border: '1px solid'
})
const anchor = box(
{ position: 'absolute', left: '260px', top: '200px', width: '90px', height: '34px' },
boundary
)
const floating = box({ position: 'absolute', width: '200px', height: '130px' })
const mw: Built = {
own: [
ownOffset({ mainAxis: 8 }),
ownFlip({ boundary: [boundary], altBoundary: true }),
ownShift({ boundary: [boundary], altBoundary: true, padding: 4 })
],
fui: [
fuiOffset({ mainAxis: 8 }),
fuiFlip({ boundary: [boundary], altBoundary: true }),
fuiShift({ boundary: [boundary], altBoundary: true, padding: 4 })
]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`boundary ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — top-layer floating (native popover) escapes clipping', () => {
for (const placement of ['bottom', 'top'] as Placement[]) {
it(placement, async () => {
const scroller = box({
position: 'absolute',
left: '120px',
top: '120px',
width: '300px',
height: '200px',
overflow: 'auto',
border: '1px solid'
})
const tall = box({ height: '700px', width: '500px', position: 'relative' }, scroller)
const anchor = box(
{ position: 'absolute', left: '40px', top: '90px', width: '90px', height: '34px' },
tall
)
// Native popover floating element — top layer, so the clipping walk must
// short-circuit (isTopLayer) and bound it to the viewport, not the scroller.
const floating = box({ position: 'fixed', width: '200px', height: '140px' }, scroller)
floating.setAttribute('popover', 'manual')
if (typeof floating.showPopover !== 'function') return // unsupported → skip body
floating.showPopover()
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement, 'fixed')
assertSame(`top-layer ${placement}`, own, fui)
floating.hidePopover()
})
}
})
// ════════ Coverage-gap sweep (real-DOM) ══════════════════════════════════════
describe('real-DOM parity — document scrollbar present (viewport width)', () => {
for (const placement of ['right', 'bottom-end', 'top-end'] as Placement[]) {
it(placement, async () => {
const spacer = box({ position: 'absolute', top: '0', left: '0', width: '1px', height: '3000px' })
void spacer
const anchor = box({
position: 'absolute',
left: `${document.documentElement.clientWidth - 120}px`,
top: '300px',
width: '90px',
height: '34px'
})
const floating = box({ position: 'absolute', width: '200px', height: '120px' })
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 0 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 0 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`scrollbar-vp ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — reference clip ≠ floating clip (altBoundary)', () => {
for (const scroll of [0, 240]) {
it(`altBoundary flip scrollTop=${scroll}`, async () => {
const scroller = box({
position: 'absolute',
left: '120px',
top: '120px',
width: '300px',
height: '200px',
overflow: 'auto',
border: '1px solid'
})
const tall = box({ height: '900px', width: '500px', position: 'relative' }, scroller)
const anchor = box(
{ position: 'absolute', left: '40px', top: '120px', width: '90px', height: '34px' },
tall
)
// floating lives in body (its own clip = viewport); altBoundary makes
// flip/shift read the REFERENCE's clip (the scroller).
const floating = box({ position: 'absolute', width: '200px', height: '160px' })
scroller.scrollTop = scroll
const mw: Built = {
own: [
ownOffset({ mainAxis: 8 }),
ownFlip({ altBoundary: true }),
ownShift({ altBoundary: true, padding: 4 })
],
fui: [
fuiOffset({ mainAxis: 8 }),
fuiFlip({ altBoundary: true }),
fuiShift({ altBoundary: true, padding: 4 })
]
}
const { own, fui } = await bothPositions(anchor, floating, mw, 'bottom')
assertSame(`altBoundary ${scroll}`, own, fui)
})
}
})
describe('real-DOM parity — anisotropic scale offsetParent', () => {
for (const placement of ['bottom', 'right', 'top-start', 'bottom-end'] as Placement[]) {
it(placement, async () => {
const wrapper = box({
position: 'relative',
transform: 'scale(1.4, 0.7)',
transformOrigin: 'top left',
left: '180px',
top: '160px',
width: '420px',
height: '340px',
border: '1px solid'
})
const anchor = box(
{ position: 'absolute', left: '120px', top: '130px', width: '90px', height: '34px' },
wrapper
)
const floating = box({ position: 'absolute', width: '200px', height: '120px' }, wrapper)
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`aniso ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — scaled AND scrolled offsetParent', () => {
for (const placement of ['bottom', 'right'] as Placement[]) {
it(placement, async () => {
const scroller = box({
position: 'relative',
transform: 'scale(1.3)',
transformOrigin: 'top left',
overflow: 'auto',
left: '120px',
top: '120px',
width: '300px',
height: '220px',
border: '1px solid'
})
const tall = box({ height: '700px', width: '700px', position: 'relative' }, scroller)
const anchor = box(
{ position: 'absolute', left: '60px', top: '180px', width: '90px', height: '34px' },
tall
)
const floating = box({ position: 'absolute', width: '200px', height: '120px' }, scroller)
scroller.scrollTop = 120
scroller.scrollLeft = 40
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`scaled+scroll ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — multi-element collisionBoundary', () => {
for (const placement of ['bottom', 'right'] as Placement[]) {
it(placement, async () => {
const a = box({ position: 'absolute', left: '150px', top: '150px', width: '400px', height: '260px', border: '1px solid' })
const b = box({ position: 'absolute', left: '300px', top: '120px', width: '260px', height: '320px', border: '1px solid' })
const anchor = box({ position: 'absolute', left: '360px', top: '230px', width: '90px', height: '34px' })
const floating = box({ position: 'absolute', width: '200px', height: '130px' })
const mw: Built = {
own: [
ownOffset({ mainAxis: 8 }),
ownFlip({ boundary: [a, b], altBoundary: true }),
ownShift({ boundary: [a, b], altBoundary: true, padding: 4 })
],
fui: [
fuiOffset({ mainAxis: 8 }),
fuiFlip({ boundary: [a, b], altBoundary: true }),
fuiShift({ boundary: [a, b], altBoundary: true, padding: 4 })
]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`multi-boundary ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — reference and floating under DIFFERENT offsetParents', () => {
for (const placement of ['bottom', 'right', 'top-start'] as Placement[]) {
it(placement, async () => {
const wrapperA = box({
position: 'relative',
transform: 'translateZ(0)',
left: '160px',
top: '120px',
width: '300px',
height: '220px',
border: '1px solid'
})
const anchor = box(
{ position: 'absolute', left: '120px', top: '120px', width: '90px', height: '34px' },
wrapperA
)
const wrapperB = box({
position: 'relative',
transform: 'translateZ(0)',
left: '560px',
top: '380px',
width: '320px',
height: '260px',
border: '1px solid'
})
const floating = box({ position: 'absolute', width: '200px', height: '120px' }, wrapperB)
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`cross-op ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — non-default flip options (real overflow)', () => {
const VARIANTS: { name: string; own: () => Middleware; fui: () => FuiMiddleware }[] = [
{ name: 'crossAxis-alignment', own: () => ownFlip({ crossAxis: 'alignment' }), fui: () => fuiFlip({ crossAxis: 'alignment' }) },
{ name: 'fallbackAxisSideDirection-start', own: () => ownFlip({ fallbackAxisSideDirection: 'start' }), fui: () => fuiFlip({ fallbackAxisSideDirection: 'start' }) },
{ name: 'flipAlignment-false', own: () => ownFlip({ flipAlignment: false }), fui: () => fuiFlip({ flipAlignment: false }) }
]
for (const v of VARIANTS) {
it(v.name, async () => {
const scroller = box({
position: 'absolute',
left: '120px',
top: '420px',
width: '300px',
height: '200px',
overflow: 'auto',
border: '1px solid'
})
const tall = box({ height: '700px', width: '500px', position: 'relative' }, scroller)
const anchor = box(
{ position: 'absolute', left: '40px', top: '120px', width: '90px', height: '34px' },
tall
)
const floating = box({ position: 'absolute', width: '200px', height: '260px' })
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), v.own(), ownShift({ padding: 4 })],
fui: [fuiOffset({ mainAxis: 8 }), v.fui(), fuiShift({ padding: 4 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, 'bottom-start')
assertSame(`flipopt ${v.name}`, own, fui)
})
}
})
describe('real-DOM parity — modal <dialog> top-layer escapes clipping', () => {
for (const placement of ['bottom', 'top'] as Placement[]) {
it(placement, async () => {
const scroller = box({
position: 'absolute',
left: '120px',
top: '120px',
width: '300px',
height: '200px',
overflow: 'auto',
border: '1px solid'
})
const tall = box({ height: '700px', width: '500px', position: 'relative' }, scroller)
const anchor = box(
{ position: 'absolute', left: '40px', top: '90px', width: '90px', height: '34px' },
tall
)
const dialog = document.createElement('dialog')
Object.assign(dialog.style, { width: '200px', height: '140px', margin: '0', padding: '0', border: '0' })
scroller.appendChild(dialog)
if (typeof dialog.showModal !== 'function') return
dialog.showModal()
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, dialog, mw, placement, 'fixed')
assertSame(`modal ${placement}`, own, fui)
dialog.close()
})
}
})
describe('real-DOM parity — body as scroller (filtered from clip walk)', () => {
it('body overflow:auto', async () => {
document.body.style.cssText = 'overflow:auto;'
const spacer = box({ position: 'absolute', top: '0', left: '0', width: '2000px', height: '2000px' })
void spacer
const anchor = box({ position: 'absolute', left: '380px', top: '300px', width: '90px', height: '34px' })
const floating = box({ position: 'absolute', width: '200px', height: '120px' })
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, 'bottom')
assertSame('body-scroller', own, fui)
})
})
describe('real-DOM parity — nested static table chain (multi-skip)', () => {
it('table→td→table→td offsetParent skip', async () => {
const outer = box({
position: 'relative',
left: '160px',
top: '160px',
width: '600px',
height: '360px',
border: '1px solid'
})
const t1 = document.createElement('table')
const tb1 = document.createElement('tbody')
const r1 = document.createElement('tr')
const c1 = document.createElement('td')
c1.style.padding = '20px'
const t2 = document.createElement('table')
const tb2 = document.createElement('tbody')
const r2 = document.createElement('tr')
const c2 = document.createElement('td')
c2.style.padding = '20px'
t1.appendChild(tb1)
tb1.appendChild(r1)
r1.appendChild(c1)
c1.appendChild(t2)
t2.appendChild(tb2)
tb2.appendChild(r2)
r2.appendChild(c2)
outer.appendChild(t1)
const anchor = box(
{ position: 'absolute', left: '20px', top: '20px', width: '90px', height: '34px' },
c2
)
const floating = box({ position: 'absolute', width: '200px', height: '120px' }, c2)
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, 'bottom')
assertSame('table-chain', own, fui)
})
})
describe('real-DOM parity — transformed <html> on a scrolled page', () => {
it('html translate + scroll', async () => {
document.documentElement.style.transform = 'translate(30px, 50px)'
const spacer = box({ position: 'absolute', top: '0', left: '0', width: '1px', height: '2500px' })
void spacer
const anchor = box({
position: 'absolute',
left: '380px',
top: '1400px',
width: '90px',
height: '34px'
})
const floating = box({ position: 'absolute', width: '200px', height: '120px' })
window.scrollTo(0, 1000)
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, 'bottom')
assertSame('html-transform-scroll', own, fui)
})
})
describe('real-DOM parity — thick asymmetric offsetParent border (scrolled)', () => {
for (const placement of ['bottom', 'right'] as Placement[]) {
it(placement, async () => {
const scroller = box({
position: 'relative',
left: '120px',
top: '120px',
width: '320px',
height: '240px',
overflow: 'auto',
borderLeft: '20px solid',
borderTop: '8px solid',
borderRight: '2px solid',
borderBottom: '2px solid'
})
const tall = box({ height: '700px', width: '700px', position: 'relative' }, scroller)
const anchor = box(
{ position: 'absolute', left: '60px', top: '180px', width: '90px', height: '34px' },
tall
)
const floating = box({ position: 'absolute', width: '200px', height: '120px' }, scroller)
scroller.scrollTop = 120
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`thick-border ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — writing-mode vertical-rl', () => {
for (const placement of ['bottom-start', 'right'] as Placement[]) {
it(placement, async () => {
document.documentElement.style.writingMode = 'vertical-rl'
const anchor = box({
position: 'absolute',
left: '420px',
top: '300px',
width: '90px',
height: '34px'
})
const floating = box({ position: 'absolute', width: '200px', height: '120px' })
const mw: Built = {
own: [ownOffset({ mainAxis: 8, alignmentAxis: 10 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8, alignmentAxis: 10 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`writing-mode ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — shadow boundary crossing (anchor light, floating shadow)', () => {
for (const placement of ['bottom', 'right'] as Placement[]) {
it(placement, async () => {
const anchor = box({
position: 'absolute',
left: '380px',
top: '300px',
width: '90px',
height: '34px'
})
const host = box({ position: 'relative' })
const root = host.attachShadow({ mode: 'open' })
const floating = document.createElement('div')
Object.assign(floating.style, { position: 'absolute', width: '200px', height: '120px' })
root.appendChild(floating)
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`shadow-cross ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — floating inside two nested scroll containers', () => {
for (const [s1, s2] of [
[0, 0],
[100, 60]
]) {
it(`outer=${s1} inner=${s2}`, async () => {
const outer = box({
position: 'absolute',
left: '80px',
top: '80px',
width: '440px',
height: '320px',
overflow: 'auto',
border: '1px solid'
})
const outerTall = box({ height: '900px', width: '760px', position: 'relative' }, outer)
const inner = box(
{
position: 'absolute',
left: '40px',
top: '60px',
width: '300px',
height: '220px',
overflow: 'auto',
border: '1px solid'
},
outerTall
)
const innerTall = box({ height: '700px', width: '600px', position: 'relative' }, inner)
const anchor = box(
{ position: 'absolute', left: '40px', top: '120px', width: '90px', height: '34px' },
innerTall
)
// floating ALSO inside the inner scroller → its clip = inner ∩ outer ∩ vp.
const floating = box({ position: 'absolute', width: '180px', height: '120px' }, innerTall)
outer.scrollTop = s1
inner.scrollTop = s2
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 4 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 4 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, 'bottom')
assertSame(`floating-2scroll ${s1}/${s2}`, own, fui)
})
}
})
describe('real-DOM parity — fixed × scaled containing block sweep', () => {
for (const placement of ['bottom', 'top-start', 'right', 'left-end'] as Placement[]) {
it(placement, async () => {
const wrapper = box({
position: 'relative',
transform: 'scale(1.25)',
transformOrigin: 'top left',
left: '180px',
top: '160px',
width: '420px',
height: '320px',
border: '1px solid'
})
const anchor = box(
{ position: 'absolute', left: '120px', top: '120px', width: '90px', height: '34px' },
wrapper
)
const floating = box({ position: 'fixed', width: '200px', height: '120px' }, wrapper)
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 6 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 6 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement, 'fixed')
assertSame(`fixed-scaled ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — scaled clipping ancestor (getInnerBoundingClientRect scale)', () => {
for (const placement of ['bottom', 'right'] as Placement[]) {
it(placement, async () => {
const scroller = box({
position: 'relative',
transform: 'scale(1.25)',
transformOrigin: 'top left',
overflow: 'auto',
left: '120px',
top: '120px',
width: '300px',
height: '220px',
border: '1px solid'
})
const tall = box({ height: '700px', width: '600px', position: 'relative' }, scroller)
const anchor = box(
{ position: 'absolute', left: '60px', top: '150px', width: '90px', height: '34px' },
tall
)
// floating inside the scaled scroller → the scaled clip rect bounds it.
const floating = box({ position: 'absolute', width: '200px', height: '160px' }, tall)
const mw: Built = {
own: [ownOffset({ mainAxis: 8 }), ownFlip(), ownShift({ padding: 4 })],
fui: [fuiOffset({ mainAxis: 8 }), fuiFlip(), fuiShift({ padding: 4 })]
}
const { own, fui } = await bothPositions(anchor, floating, mw, placement)
assertSame(`scaled-clip ${placement}`, own, fui)
})
}
})
describe('real-DOM parity — arrow + size in real layout', () => {
it('arrow centering (bottom) matches', async () => {
const anchor = box({

@ -829,3 +829,638 @@ describe('parity — coordinate-space variants (scroll · scale · rtl)', () =>
}
}
})
// ── offset — every form (number, negative, crossAxis, alignmentAxis) ──────────
describe('parity — offset forms', () => {
const base: Scenario = {
reference: { x: 400, y: 300, width: 90, height: 32 },
floatingDims: { width: 200, height: 120 },
clip: VIEWPORT
}
const FORMS: { name: string; opt: number | { mainAxis?: number; crossAxis?: number; alignmentAxis?: number | null } }[] = [
{ name: 'number 12', opt: 12 },
{ name: 'number 0', opt: 0 },
{ name: 'negative -10', opt: -10 },
{ name: 'mainAxis only', opt: { mainAxis: 14 } },
{ name: 'crossAxis only', opt: { crossAxis: 18 } },
{ name: 'main+cross', opt: { mainAxis: 8, crossAxis: -12 } },
{ name: 'alignmentAxis', opt: { mainAxis: 8, alignmentAxis: 16 } },
{ name: 'negative alignmentAxis', opt: { mainAxis: 8, alignmentAxis: -16 } }
]
for (const placement of ['top', 'bottom-start', 'right-end', 'left'] as Placement[]) {
for (const form of FORMS) {
it(`${placement} · ${form.name}`, async () => {
const { own, fui } = await compare(base, placement, (kind) =>
[kind === 'own' ? ownOffset(form.opt) : fuiOffset(form.opt)].filter(Boolean)
)
assertParity(`offset ${placement}/${form.name}`, own, fui)
})
}
}
})
// ── shift — crossAxis-only, both axes, limitShift with offsets ────────────────
describe('parity — shift axis + limiter variants', () => {
const scenario: Scenario = {
reference: { x: 900, y: 720, width: 90, height: 32 },
floatingDims: { width: 200, height: 140 },
clip: VIEWPORT
}
const VARIANTS = [
{ name: 'cross-only', main: false, cross: true, limiter: undefined as undefined | (() => unknown) },
{ name: 'both-axes', main: true, cross: true, limiter: undefined },
{ name: 'limit main', main: true, cross: false, limit: true },
{ name: 'limit both', main: true, cross: true, limit: true },
{ name: 'limit offset', main: true, cross: true, limit: true, limitOffset: 10 }
]
for (const placement of ['bottom', 'right'] as Placement[]) {
for (const v of VARIANTS as { name: string; main: boolean; cross: boolean; limit?: boolean; limitOffset?: number }[]) {
it(`${placement} · ${v.name}`, async () => {
const { own, fui } = await compare(scenario, placement, (kind) => {
const o = kind === 'own'
const limiter = v.limit
? o
? ownLimitShift({ offset: v.limitOffset })
: fuiLimitShift({ offset: v.limitOffset })
: undefined
const opts = { mainAxis: v.main, crossAxis: v.cross, limiter, padding: 4 }
return [
o ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
o ? ownShift(opts as any) : fuiShift(opts as any)
].filter(Boolean)
})
assertParity(`shift ${placement}/${v.name}`, own, fui)
})
}
}
})
// ── flip — fallbackPlacements, fallbackAxisSideDirection, crossAxis, strategy ──
describe('parity — flip option variants', () => {
const scenario: Scenario = {
reference: { x: 410, y: 730, width: 90, height: 32 },
floatingDims: { width: 220, height: 160 },
clip: VIEWPORT
}
const VARIANTS: { name: string; opt: Record<string, unknown> }[] = [
{ name: 'explicit fallbackPlacements', opt: { fallbackPlacements: ['right', 'left', 'top'] } },
{ name: 'fallbackAxisSideDirection start', opt: { fallbackAxisSideDirection: 'start' } },
{ name: 'fallbackAxisSideDirection end', opt: { fallbackAxisSideDirection: 'end' } },
{ name: 'crossAxis false', opt: { crossAxis: false } },
{ name: 'crossAxis alignment', opt: { crossAxis: 'alignment' } },
{ name: 'mainAxis false', opt: { mainAxis: false } },
{ name: 'fallbackStrategy initialPlacement', opt: { fallbackStrategy: 'initialPlacement' } },
{ name: 'flipAlignment false', opt: { flipAlignment: false } }
]
for (const placement of ['bottom', 'bottom-start', 'right'] as Placement[]) {
for (const v of VARIANTS) {
it(`${placement} · ${v.name}`, async () => {
const { own, fui } = await compare(scenario, placement, (kind) =>
[
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
kind === 'own' ? ownFlip(v.opt as any) : fuiFlip(v.opt as any)
].filter(Boolean)
)
assertParity(`flip ${placement}/${v.name}`, own, fui)
})
}
}
})
// ── flip cross-axis — getAlignmentSides when the reference outsizes floating ───
describe('parity — flip cross-axis with reference wider/taller than floating', () => {
for (const placement of ['bottom-start', 'bottom-end', 'right-start', 'left-end'] as Placement[]) {
it(placement, async () => {
const scenario: Scenario = {
// reference larger than floating on the alignment axis flips
// getAlignmentSides' mainAlignmentSide.
reference: { x: 60, y: 60, width: 320, height: 240 },
floatingDims: { width: 120, height: 90 },
clip: VIEWPORT
}
const { own, fui } = await compare(scenario, placement, (kind) =>
[
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
kind === 'own' ? ownFlip({ crossAxis: true }) : fuiFlip({ crossAxis: true })
].filter(Boolean)
)
assertParity(`flip-cross ${placement}`, own, fui)
})
}
})
// ── hide — escaped strategy ───────────────────────────────────────────────────
describe('parity — hide escaped strategy', () => {
const CASES = [
{ name: 'inside', ref: { x: 410, y: 350 }, fx: 410, fy: 350 },
{ name: 'floating-escaped', ref: { x: 410, y: 350 }, fx: 410, fy: 1300 }
]
for (const c of CASES) {
it(c.name, async () => {
const scenario: Scenario = {
reference: { x: c.ref.x, y: c.ref.y, width: 90, height: 32 },
floatingDims: { width: 180, height: 100 },
clip: VIEWPORT
}
const { own, fui } = await compare(scenario, 'bottom', (kind) =>
[
kind === 'own'
? ownHide({ strategy: 'escaped', padding: 0 })
: fuiHide({ strategy: 'escaped', padding: 0 })
].filter(Boolean)
)
const oe = (own.middlewareData.hide ?? {}) as { escaped?: boolean }
const fe = (fui.middlewareData.hide ?? {}) as { escaped?: boolean }
expect(!!oe.escaped, `${c.name} escaped`).toBe(!!fe.escaped)
})
}
})
// ── size — aligned placements + rtl widthSide branch ──────────────────────────
describe('parity — size on aligned placements (+rtl)', () => {
for (const placement of [
'bottom-start',
'bottom-end',
'top-start',
'right-start',
'right-end',
'left-end'
] as Placement[]) {
for (const rtl of [false, true]) {
it(`${placement} rtl=${rtl}`, async () => {
const scenario: Scenario = {
reference: { x: 300, y: 300, width: 90, height: 32 },
floatingDims: { width: 360, height: 320 },
clip: VIEWPORT,
rtl
}
let ownAW = 0,
ownAH = 0,
fuiAW = 0,
fuiAH = 0
await compare(scenario, placement, (kind) => {
const apply = (a: { availableWidth: number; availableHeight: number }) => {
if (kind === 'own') {
ownAW = a.availableWidth
ownAH = a.availableHeight
} else {
fuiAW = a.availableWidth
fuiAH = a.availableHeight
}
}
return [
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
kind === 'own' ? ownSize({ padding: 0, apply }) : fuiSize({ padding: 0, apply })
].filter(Boolean)
})
expect(ownAW, `${placement} rtl=${rtl} AW`).toBeCloseTo(fuiAW, 4)
expect(ownAH, `${placement} rtl=${rtl} AH`).toBeCloseTo(fuiAH, 4)
})
}
}
})
// ── arrow — alignmentOffset reset interacting with offset + flip ──────────────
describe('parity — arrow alignmentOffset reset + offset/flip short-circuit', () => {
for (const placement of ['bottom-start', 'bottom-end', 'right-start', 'left-start'] as Placement[]) {
it(placement, async () => {
// Tiny reference + large arrow padding triggers arrow's shouldAddOffset
// (alignmentOffset + reset). offset must then short-circuit and flip skip.
const scenario: Scenario = {
reference: { x: 420, y: 360, width: 14, height: 14 },
floatingDims: { width: 260, height: 170 },
clip: VIEWPORT,
arrowDims: { width: 20, height: 20 }
}
const { own, fui } = await compare(scenario, placement, (kind, ctx) =>
[
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
kind === 'own' ? ownFlip({ padding: 0 }) : fuiFlip({ padding: 0 }),
kind === 'own'
? ownArrow({ element: ctx.arrowEl as Element, padding: 20 })
: fuiArrow({ element: ctx.arrowEl as never, padding: 20 })
].filter(Boolean)
)
assertParity(`arrow-reset ${placement}`, own, fui, { arrow: true })
})
}
})
// ── collisionPadding object (per-side) ────────────────────────────────────────
describe('parity — per-side collisionPadding', () => {
const scenario: Scenario = {
reference: { x: 410, y: 720, width: 90, height: 32 },
floatingDims: { width: 220, height: 150 },
clip: VIEWPORT
}
for (const placement of ['bottom', 'top', 'right'] as Placement[]) {
it(placement, async () => {
const pad = { top: 20, right: 8, bottom: 30, left: 12 }
const { own, fui } = await compare(scenario, placement, (kind) =>
[
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
kind === 'own'
? ownShift({ mainAxis: true, crossAxis: true, padding: pad })
: fuiShift({ mainAxis: true, crossAxis: true, padding: pad }),
kind === 'own' ? ownFlip({ padding: pad }) : fuiFlip({ padding: pad })
].filter(Boolean)
)
assertParity(`padding-obj ${placement}`, own, fui)
})
}
})
// ════════ Coverage-gap sweep (from the parity-coverage-gaps audit) ════════════
// flip — terminal bestFit when nothing fits + fallbackStrategy divergence.
describe('parity — flip bestFit / fallbackStrategy (nothing fits)', () => {
const scenario: Scenario = {
reference: { x: 120, y: 140, width: 60, height: 24 },
floatingDims: { width: 280, height: 280 },
clip: { x: 0, y: 0, width: 300, height: 300 }
}
for (const strat of ['bestFit', 'initialPlacement'] as const) {
it(`fallbackStrategy ${strat}`, async () => {
const { own, fui } = await compare(scenario, 'bottom', (kind) =>
[
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
kind === 'own'
? ownFlip({ fallbackStrategy: strat, padding: 0 })
: fuiFlip({ fallbackStrategy: strat, padding: 0 })
].filter(Boolean)
)
assertParity(`bestFit ${strat}`, own, fui)
})
}
})
// flip crossAxis 'alignment' guard — must NOT leave the axis when main fits.
describe('parity — flip crossAxis alignment vs true', () => {
const scenario: Scenario = {
reference: { x: 410, y: 8, width: 90, height: 32 },
floatingDims: { width: 200, height: 140 },
clip: VIEWPORT
}
for (const cross of ['alignment', true] as const) {
it(`crossAxis ${cross}`, async () => {
const { own, fui } = await compare(scenario, 'bottom-start', (kind) =>
[
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
kind === 'own'
? ownFlip({ crossAxis: cross, padding: 0 })
: fuiFlip({ crossAxis: cross, padding: 0 })
].filter(Boolean)
)
assertParity(`flip-cross-${cross}`, own, fui)
})
}
})
// flip — multi-step oscillation (≥3 placements tried; overflowsData accumulates).
describe('parity — flip multi-step', () => {
for (const placement of ['bottom-start', 'top-end', 'right-start'] as Placement[]) {
it(placement, async () => {
const scenario: Scenario = {
reference: { x: 30, y: 720, width: 90, height: 60 },
floatingDims: { width: 240, height: 200 },
clip: { x: 0, y: 0, width: 360, height: 800 }
}
const { own, fui } = await compare(scenario, placement, (kind) =>
[
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
kind === 'own' ? ownFlip({ padding: 0 }) : fuiFlip({ padding: 0 })
].filter(Boolean)
)
assertParity(`flip-multi ${placement}`, own, fui)
})
}
})
// size — single-axis shift (the exact soma avoidCollisions config) + noShift shrink.
describe('parity — size single-axis shift + symmetric shrink', () => {
const CASES = [
{ name: 'mainAxis-shift', placement: 'bottom' as Placement, ref: { x: 410, y: 600 }, dims: { width: 200, height: 360 }, shift: true },
{ name: 'no-shift-shrink', placement: 'bottom' as Placement, ref: { x: 60, y: 600 }, dims: { width: 380, height: 360 }, shift: false }
]
for (const c of CASES) {
it(c.name, async () => {
const scenario: Scenario = {
reference: { x: c.ref.x, y: c.ref.y, width: 90, height: 32 },
floatingDims: c.dims,
clip: VIEWPORT
}
let oAW = 0,
oAH = 0,
fAW = 0,
fAH = 0
await compare(scenario, c.placement, (kind) => {
const apply = (a: { availableWidth: number; availableHeight: number }) => {
if (kind === 'own') {
oAW = a.availableWidth
oAH = a.availableHeight
} else {
fAW = a.availableWidth
fAH = a.availableHeight
}
}
return [
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
c.shift
? kind === 'own'
? ownShift({ mainAxis: true, crossAxis: false, padding: 0 })
: fuiShift({ mainAxis: true, crossAxis: false, padding: 0 })
: undefined,
kind === 'own' ? ownSize({ padding: 0, apply }) : fuiSize({ padding: 0, apply })
].filter(Boolean)
})
expect(oAW, `${c.name} AW`).toBeCloseTo(fAW, 4)
expect(oAH, `${c.name} AH`).toBeCloseTo(fAH, 4)
})
}
})
// limitShift — offset object, axis toggles, origin-side cross-axis clamp.
describe('parity — limitShift option variants', () => {
const scenario: Scenario = {
reference: { x: 60, y: 60, width: 90, height: 32 },
floatingDims: { width: 220, height: 150 },
clip: VIEWPORT
}
const VARIANTS = [
{ name: 'offset-object', opt: { offset: { mainAxis: 12, crossAxis: 6 } } },
{ name: 'mainAxis-false', opt: { mainAxis: false } },
{ name: 'crossAxis-false', opt: { crossAxis: false } }
]
for (const placement of ['top-start', 'left-start'] as Placement[]) {
for (const v of VARIANTS) {
it(`${placement} · ${v.name}`, async () => {
const { own, fui } = await compare(scenario, placement, (kind) => {
const o = kind === 'own'
return [
o ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
o
? ownShift({ mainAxis: true, crossAxis: true, limiter: ownLimitShift(v.opt), padding: 0 })
: fuiShift({ mainAxis: true, crossAxis: true, limiter: fuiLimitShift(v.opt), padding: 0 })
].filter(Boolean)
})
assertParity(`limit ${placement}/${v.name}`, own, fui)
})
}
}
})
// offset — crossAxis number on a vertical placement under RTL (the mirror).
describe('parity — offset crossAxis under RTL', () => {
for (const placement of ['bottom', 'top', 'bottom-start'] as Placement[]) {
it(placement, async () => {
const scenario: Scenario = {
reference: { x: 400, y: 300, width: 90, height: 32 },
floatingDims: { width: 200, height: 120 },
clip: VIEWPORT,
rtl: true
}
const { own, fui } = await compare(scenario, placement, (kind) =>
[
kind === 'own'
? ownOffset({ mainAxis: 8, crossAxis: 24 })
: fuiOffset({ mainAxis: 8, crossAxis: 24 })
].filter(Boolean)
)
assertParity(`offset-rtl-cross ${placement}`, own, fui)
})
}
})
// offset — re-runs after flip changes placement (the alignmentOffset short-circuit
// must NOT fire when the placement differs).
it('parity — offset re-runs after flip changes placement (arrow alignmentOffset)', async () => {
const scenario: Scenario = {
reference: { x: 420, y: 740, width: 14, height: 14 },
floatingDims: { width: 260, height: 170 },
clip: VIEWPORT,
arrowDims: { width: 20, height: 20 }
}
const { own, fui } = await compare(scenario, 'bottom-start', (kind, ctx) =>
[
kind === 'own'
? ownOffset({ mainAxis: 8, alignmentAxis: 4 })
: fuiOffset({ mainAxis: 8, alignmentAxis: 4 }),
kind === 'own' ? ownFlip({ padding: 0 }) : fuiFlip({ padding: 0 }),
kind === 'own'
? ownArrow({ element: ctx.arrowEl as Element, padding: 18 })
: fuiArrow({ element: ctx.arrowEl as never, padding: 18 })
].filter(Boolean)
)
assertParity('offset-after-flip', own, fui, { arrow: true })
})
// hide — exact-boundary referenceHidden + numeric referenceHiddenOffsets.
describe('parity — hide boundary + offsets', () => {
const CASES = [
{ name: 'just-out-right', ref: { x: 1000, y: 100, width: 80, height: 30 } },
{ name: 'asym-out', ref: { x: -30, y: 790, width: 80, height: 30 } }
]
for (const c of CASES) {
it(c.name, async () => {
const scenario: Scenario = {
reference: c.ref,
floatingDims: { width: 180, height: 100 },
clip: VIEWPORT
}
const { own, fui } = await compare(scenario, 'bottom', (kind) =>
[
kind === 'own'
? ownHide({ strategy: 'referenceHidden', padding: 0 })
: fuiHide({ strategy: 'referenceHidden', padding: 0 })
].filter(Boolean)
)
const oh = (own.middlewareData.hide ?? {}) as {
referenceHidden?: boolean
referenceHiddenOffsets?: Record<string, number>
}
const fh = (fui.middlewareData.hide ?? {}) as {
referenceHidden?: boolean
referenceHiddenOffsets?: Record<string, number>
}
expect(!!oh.referenceHidden, `${c.name} hidden`).toBe(!!fh.referenceHidden)
for (const side of ['top', 'right', 'bottom', 'left'] as const) {
expect(oh.referenceHiddenOffsets?.[side], `${c.name} off.${side}`).toBeCloseTo(
fh.referenceHiddenOffsets?.[side] as number,
4
)
}
})
}
})
// shift — the data.x/data.y delta payload (consumers read it).
it('parity — shift data delta', async () => {
const scenario: Scenario = {
reference: { x: 900, y: 100, width: 80, height: 30 },
floatingDims: { width: 200, height: 100 },
clip: VIEWPORT
}
const { own, fui } = await compare(scenario, 'bottom', (kind) =>
[
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
kind === 'own'
? ownShift({ mainAxis: true, crossAxis: true, padding: 0 })
: fuiShift({ mainAxis: true, crossAxis: true, padding: 0 })
].filter(Boolean)
)
const od = own.middlewareData.shift as { x?: number; y?: number }
const fd = fui.middlewareData.shift as { x?: number; y?: number }
expect(od.x, 'shift.x').toBeCloseTo(fd.x as number, 4)
expect(od.y, 'shift.y').toBeCloseTo(fd.y as number, 4)
})
// arrow — largestPossiblePadding clamp engaging (small floating, big padding).
it('parity — arrow largestPossiblePadding clamp', async () => {
const scenario: Scenario = {
reference: { x: 410, y: 350, width: 80, height: 30 },
floatingDims: { width: 40, height: 40 },
clip: VIEWPORT,
arrowDims: { width: 12, height: 12 }
}
const { own, fui } = await compare(scenario, 'bottom', (kind, ctx) =>
[
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
kind === 'own'
? ownArrow({ element: ctx.arrowEl as Element, padding: 30 })
: fuiArrow({ element: ctx.arrowEl as never, padding: 30 })
].filter(Boolean)
)
assertParity('arrow-clamp', own, fui, { arrow: true })
})
// reset loop — bare-true reset preserves mutated coords (custom middleware).
it('parity — bare-true reset preserves running coords', async () => {
const scenario: Scenario = {
reference: { x: 200, y: 200, width: 80, height: 30 },
floatingDims: { width: 160, height: 90 },
clip: VIEWPORT
}
const ownProbe = { seen: NaN }
const ownMw: Middleware[] = [
{
name: 'bump',
options: {},
fn(s) {
const d = s.middlewareData.bump as { done?: boolean } | undefined
if (d?.done) return {}
return { x: s.x + 37, data: { done: true }, reset: true }
}
},
{
name: 'probe',
options: {},
fn(s) {
ownProbe.seen = s.x
return {}
}
}
]
const own = runMiddleware('bottom', 'absolute', ownMw, snapshotOf(scenario))
let fuiSeen = NaN
const fui = await fuiComputePosition({} as never, {} as never, {
placement: 'bottom',
strategy: 'absolute',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
platform: platformOf(scenario, {}, {}) as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
middleware: [
{
name: 'bump',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fn(s: any) {
if (s.middlewareData.bump?.done) return {}
return { x: s.x + 37, data: { done: true }, reset: true }
}
},
{
name: 'probe',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fn(s: any) {
fuiSeen = s.x
return {}
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
] as any
})
expect(ownProbe.seen, 'probe sees mutated x').toBeCloseTo(fuiSeen, 4)
expect(own.x, 'final x').toBeCloseTo(fui.x, 4)
})
// reset loop — MAX_RESET_COUNT bail-out lands on the same placement.
it('parity — MAX_RESET_COUNT cap settles identically', async () => {
const scenario: Scenario = {
reference: { x: 200, y: 200, width: 80, height: 30 },
floatingDims: { width: 160, height: 90 },
clip: VIEWPORT
}
const own = runMiddleware('bottom', 'absolute', [
{
name: 'osc',
options: {},
fn(s) {
return { reset: { placement: s.placement === 'bottom' ? 'top' : 'bottom' } }
}
}
], snapshotOf(scenario))
const fui = await fuiComputePosition({} as never, {} as never, {
placement: 'bottom',
strategy: 'absolute',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
platform: platformOf(scenario, {}, {}) as any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
middleware: [
{
name: 'osc',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fn(s: any) {
return { reset: { placement: s.placement === 'bottom' ? 'top' : 'bottom' } }
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
] as any
})
expect(own.placement, 'cap placement').toBe(fui.placement)
expect(own.x, 'cap x').toBeCloseTo(fui.x, 4)
expect(own.y, 'cap y').toBeCloseTo(fui.y, 4)
})
// per-side padding object combined with non-unit offsetScale.
describe('parity — padding object × offsetScale', () => {
for (const placement of ['bottom', 'right'] as Placement[]) {
it(placement, async () => {
const scenario: Scenario = {
reference: { x: 410, y: 700, width: 90, height: 32 },
floatingDims: { width: 220, height: 150 },
clip: VIEWPORT,
offsetScale: { x: 1.25, y: 0.9 }
}
const pad = { top: 16, right: 6, bottom: 24, left: 10 }
const { own, fui } = await compare(scenario, placement, (kind) =>
[
kind === 'own' ? ownOffset({ mainAxis: 8 }) : fuiOffset({ mainAxis: 8 }),
kind === 'own'
? ownShift({ mainAxis: true, crossAxis: true, padding: pad })
: fuiShift({ mainAxis: true, crossAxis: true, padding: pad }),
kind === 'own' ? ownFlip({ padding: pad }) : fuiFlip({ padding: pad })
].filter(Boolean)
)
assertParity(`pad-scale ${placement}`, own, fui)
})
}
})

@ -94,7 +94,7 @@ function getNodeScroll(el: Element | Window): Coords {
/** Horizontal offset of the document scrollbar (non-zero only when the scrollbar
* sits on the left, e.g. some RTL systems). Mirrors floating-ui. */
function getWindowScrollBarX(element: Element, win: Window, rect?: Rect): number {
export function getWindowScrollBarX(element: Element, win: Window, rect?: Rect): number {
const leftScroll = getNodeScroll(element).x
if (!rect) return getBoundingClientRect(getDocumentElement(element), win).x + leftScroll
return rect.x + leftScroll
@ -209,7 +209,12 @@ function getRectRelativeToOffsetParent(
const documentElement = getDocumentElement(offsetParent)
const isFixed = strategy === 'fixed'
const rect = getBoundingClientRect(el, win, true, isFixed ? offsetParent : undefined)
// Scale basis is ALWAYS the offsetParent (matches floating-ui), not the
// element's own scale. They differ when the offsetParent is anisotropically
// scaled or carries a border (getScale's fallback rounds differently for the
// two boxes) — using the element's scale shifted the reference rect by a
// sub-pixel on such parents.
const rect = getBoundingClientRect(el, win, true, offsetParent)
let scroll: Coords = createCoords(0)
const offsets: Coords = createCoords(0)
@ -221,8 +226,16 @@ function getRectRelativeToOffsetParent(
const offsetRect = getBoundingClientRect(offsetParent, win, true)
offsets.x = offsetRect.x + offsetParent.clientLeft
offsets.y = offsetRect.y + offsetParent.clientTop
} else if (documentElement) {
// Window offsetParent: account for a left-side document scrollbar (RTL
// systems, or a vertical writing-mode that flips the inline origin).
// ~0 on a normal LTR document, so this was silently fine until exercised.
offsets.x = getWindowScrollBarX(documentElement, win)
}
}
if (isFixed && !isOffsetParentAnElement && documentElement) {
offsets.x = getWindowScrollBarX(documentElement, win)
}
const htmlOffset =
documentElement && !isOffsetParentAnElement && !isFixed

Loading…
Cancel
Save

Powered by TurnKey Linux.