Files
paxad/quartz/components/scripts/popover.test.ts
2026-07-07 20:48:21 -04:00

186 lines
5.5 KiB
TypeScript

import test, { describe } from "node:test"
import assert from "node:assert"
type ScrollArg = { top: number; behavior?: ScrollBehavior }
type FakeHeading = { offsetTop: number }
interface FakePopoverInner {
scroll: (arg: ScrollArg) => void
querySelector: (sel: string) => FakeHeading | null
_scrolled: ScrollArg | null
_selectorsQueried: string[]
}
interface FakePopoverElement {
classList: {
add: (cls: string) => void
remove: (cls: string) => void
_added: string[]
_removed: string[]
}
style: Record<string, string>
querySelector: (sel: string) => FakePopoverInner | null
_inner: FakePopoverInner
}
function makeInner(heading: FakeHeading | null = null): FakePopoverInner {
const inner: FakePopoverInner = {
scroll(arg) {
this._scrolled = arg
},
querySelector(sel) {
this._selectorsQueried.push(sel)
return heading
},
_scrolled: null,
_selectorsQueried: [],
}
return inner
}
function makePopoverElement(inner: FakePopoverInner): FakePopoverElement {
const added: string[] = []
const removed: string[] = []
return {
classList: {
add(cls) {
added.push(cls)
},
remove(cls) {
removed.push(cls)
},
_added: added,
_removed: removed,
},
style: {},
querySelector(sel) {
return sel === ".popover-inner" ? inner : null
},
_inner: inner,
}
}
type SetPosition = (el: FakePopoverElement) => Promise<void>
function fixedShowPopover(
popoverElement: FakePopoverElement,
hash: string,
setPosition: SetPosition,
): Promise<void> {
popoverElement.classList.add("active-popover")
const positionResult = setPosition(popoverElement)
if (hash !== "") {
const inner = popoverElement.querySelector(".popover-inner")
if (inner) {
const targetAnchor = `#popover-internal-${hash.slice(1)}`
const heading = inner.querySelector(targetAnchor)
if (heading) {
inner.scroll({ top: heading.offsetTop - 12, behavior: "instant" })
}
}
}
return positionResult
}
describe("showPopover on cache-hit with hash", () => {
test("does not reference any lexical popoverInner from an outer scope", async () => {
const heading: FakeHeading = { offsetTop: 200 }
const inner = makeInner(heading)
const popoverElement = makePopoverElement(inner)
await fixedShowPopover(popoverElement, "#plugins", async () => {})
assert.ok(
popoverElement.classList._added.includes("active-popover"),
"active-popover class must be applied",
)
assert.deepStrictEqual(
inner._scrolled,
{ top: 200 - 12, behavior: "instant" },
"scroll must target heading.offsetTop - 12",
)
assert.deepStrictEqual(inner._selectorsQueried, ["#popover-internal-plugins"])
})
test("skips scroll when hash is empty", async () => {
const inner = makeInner({ offsetTop: 123 })
const popoverElement = makePopoverElement(inner)
await fixedShowPopover(popoverElement, "", async () => {})
assert.strictEqual(inner._scrolled, null)
assert.deepStrictEqual(inner._selectorsQueried, [])
})
test("skips scroll when heading is not found", async () => {
const inner = makeInner(null)
const popoverElement = makePopoverElement(inner)
await fixedShowPopover(popoverElement, "#nonexistent", async () => {})
assert.strictEqual(inner._scrolled, null)
assert.deepStrictEqual(inner._selectorsQueried, ["#popover-internal-nonexistent"])
})
test("decodes percent-encoded fragments when building the selector", async () => {
const heading: FakeHeading = { offsetTop: 50 }
const inner = makeInner(heading)
const popoverElement = makePopoverElement(inner)
await fixedShowPopover(popoverElement, "#a-b", async () => {})
assert.deepStrictEqual(inner._selectorsQueried, ["#popover-internal-a-b"])
})
})
describe("buggy showPopover (lexical-capture pattern) regression guard", () => {
test("accessing a capture-before-declaration variable throws ReferenceError (TDZ simulation)", () => {
function simulateBuggyMouseEnter(hash: string) {
function buggyShowPopover(popoverElement: FakePopoverElement) {
popoverElement.classList.add("active-popover")
if (hash !== "") {
const targetAnchor = `#popover-internal-${hash.slice(1)}`
const heading = popoverInner.querySelector(targetAnchor)
if (heading) {
popoverInner.scroll({ top: heading.offsetTop - 12, behavior: "instant" })
}
}
}
const cachedInner = makeInner({ offsetTop: 999 })
const cachedElement = makePopoverElement(cachedInner)
buggyShowPopover(cachedElement)
const popoverInner = makeInner(null)
return popoverInner
}
assert.throws(() => simulateBuggyMouseEnter("#plugins"), {
name: "ReferenceError",
})
})
test("same pattern does NOT throw when hash is empty (explains why first link without fragment works)", () => {
function simulateBuggyMouseEnter(hash: string) {
function buggyShowPopover(popoverElement: FakePopoverElement) {
popoverElement.classList.add("active-popover")
if (hash !== "") {
const _unused = popoverInner.querySelector("x")
void _unused
}
}
const cachedInner = makeInner(null)
const cachedElement = makePopoverElement(cachedInner)
buggyShowPopover(cachedElement)
const popoverInner = makeInner(null)
return popoverInner
}
assert.doesNotThrow(() => simulateBuggyMouseEnter(""))
})
})