mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
feat(extension): hover chain node fix
This commit is contained in:
@@ -1150,6 +1150,107 @@ describe("record-capture semantic", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not let unrelated topbar hovers or menu pass-through items own an avatar menu", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-08-07T08:08:45.000Z"));
|
||||
try {
|
||||
document.body.innerHTML = `
|
||||
<button type="button" aria-label="Star">Star</button>
|
||||
<div class="tg-avatar tg-avatar--img tg-avatar--shape-hexagon">
|
||||
<img class="tg-avatar__image" />
|
||||
</div>
|
||||
<ul class="user-menu dropdown-menu">
|
||||
<li><a href="/u/me">My profile</a></li>
|
||||
<li><a href="/dashboard/groups">My groups</a></li>
|
||||
</ul>
|
||||
`;
|
||||
const star = document.querySelector("button")!;
|
||||
const avatar = document.querySelector(".tg-avatar")!;
|
||||
const image = document.querySelector("img")!;
|
||||
const menu = document.querySelector(".user-menu")!;
|
||||
const profile = document.querySelector('a[href="/u/me"]')!;
|
||||
const groups = document.querySelector('a[href="/dashboard/groups"]')!;
|
||||
let menuRect = { left: 0, top: 0, width: 0, height: 0 };
|
||||
let profileRect = { left: 0, top: 0, width: 0, height: 0 };
|
||||
let groupsRect = { left: 0, top: 0, width: 0, height: 0 };
|
||||
mockRect(star, { left: 760, top: 8, width: 60, height: 32 });
|
||||
mockRect(avatar, { left: 900, top: 8, width: 32, height: 32 });
|
||||
mockRect(image, { left: 900, top: 8, width: 32, height: 32 });
|
||||
vi.spyOn(menu, "getBoundingClientRect").mockImplementation(
|
||||
() =>
|
||||
({
|
||||
x: menuRect.left,
|
||||
y: menuRect.top,
|
||||
top: menuRect.top,
|
||||
left: menuRect.left,
|
||||
right: menuRect.left + menuRect.width,
|
||||
bottom: menuRect.top + menuRect.height,
|
||||
width: menuRect.width,
|
||||
height: menuRect.height,
|
||||
toJSON: () => ({}),
|
||||
}) as DOMRect,
|
||||
);
|
||||
vi.spyOn(profile, "getBoundingClientRect").mockImplementation(
|
||||
() =>
|
||||
({
|
||||
x: profileRect.left,
|
||||
y: profileRect.top,
|
||||
top: profileRect.top,
|
||||
left: profileRect.left,
|
||||
right: profileRect.left + profileRect.width,
|
||||
bottom: profileRect.top + profileRect.height,
|
||||
width: profileRect.width,
|
||||
height: profileRect.height,
|
||||
toJSON: () => ({}),
|
||||
}) as DOMRect,
|
||||
);
|
||||
vi.spyOn(groups, "getBoundingClientRect").mockImplementation(
|
||||
() =>
|
||||
({
|
||||
x: groupsRect.left,
|
||||
y: groupsRect.top,
|
||||
top: groupsRect.top,
|
||||
left: groupsRect.left,
|
||||
right: groupsRect.left + groupsRect.width,
|
||||
bottom: groupsRect.top + groupsRect.height,
|
||||
width: groupsRect.width,
|
||||
height: groupsRect.height,
|
||||
toJSON: () => ({}),
|
||||
}) as DOMRect,
|
||||
);
|
||||
mockHoverStyle([star, avatar, image, profile, groups]);
|
||||
const capture = startRecordCapture("rec-hover-avatar-menu-pass-through", (step) =>
|
||||
steps.push(step),
|
||||
);
|
||||
|
||||
mouseOver(star);
|
||||
mouseOver(avatar);
|
||||
mouseOver(image);
|
||||
menuRect = { left: 820, top: 48, width: 160, height: 96 };
|
||||
profileRect = { left: 830, top: 56, width: 120, height: 28 };
|
||||
groupsRect = { left: 830, top: 88, width: 120, height: 28 };
|
||||
vi.runOnlyPendingTimers();
|
||||
mouseOver(profile);
|
||||
mouseOver(groups);
|
||||
click(groups);
|
||||
capture.dispose();
|
||||
|
||||
expect(steps.map((s) => s.op)).toEqual(["hover", "click"]);
|
||||
expect(steps[0]).toMatchObject({
|
||||
op: "hover",
|
||||
target: { tag: "img", role: "img", name: "image" },
|
||||
});
|
||||
expect(steps[1]).toMatchObject({
|
||||
op: "click",
|
||||
target: { role: "link", name: "My groups" },
|
||||
});
|
||||
expect(JSON.stringify(steps)).not.toContain("Star");
|
||||
expect(JSON.stringify(steps)).not.toContain("My profile");
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("does not record hover before an unrelated page click", () => {
|
||||
document.body.innerHTML = `
|
||||
<button type="button" aria-label="Open user navigation menu">avatar</button>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { isHoverSurfaceCandidateElement, isLikelyHoverSurfaceOwner } from "../record-hover-surface";
|
||||
|
||||
function mockRect(
|
||||
el: Element,
|
||||
rect: { left: number; top: number; width: number; height: number },
|
||||
): void {
|
||||
vi.spyOn(el, "getBoundingClientRect").mockReturnValue({
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
right: rect.left + rect.width,
|
||||
bottom: rect.top + rect.height,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
toJSON: () => ({}),
|
||||
});
|
||||
}
|
||||
|
||||
describe("record-hover-surface", () => {
|
||||
it("recognizes popover containers without treating topbar wrappers or menu items as surfaces", () => {
|
||||
document.body.innerHTML = `
|
||||
<ul class="header-menu tw-flex navbar-app__authentication-menu">
|
||||
<li class="tg-popover">
|
||||
<div class="tg-avatar tg-avatar--img"></div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tg-popover__popper">
|
||||
<div class="tg-popover__content">
|
||||
<div class="tg-popover__content-body">
|
||||
<li class="header-menu__item">
|
||||
<a class="header-dropdown-menu__link" href="/u/me">My profile</a>
|
||||
</li>
|
||||
<li class="header-menu__item">
|
||||
<a class="header-dropdown-menu__link" href="/dashboard/groups">My groups</a>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
expect(isHoverSurfaceCandidateElement(document.querySelector(".header-menu")!)).toBe(false);
|
||||
expect(isHoverSurfaceCandidateElement(document.querySelector(".tg-popover")!)).toBe(false);
|
||||
expect(isHoverSurfaceCandidateElement(document.querySelector(".header-menu__item")!)).toBe(
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
isHoverSurfaceCandidateElement(document.querySelector(".header-dropdown-menu__link")!),
|
||||
).toBe(false);
|
||||
expect(isHoverSurfaceCandidateElement(document.querySelector(".tg-popover__popper")!)).toBe(
|
||||
true,
|
||||
);
|
||||
expect(isHoverSurfaceCandidateElement(document.querySelector(".tg-popover__content")!)).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
isHoverSurfaceCandidateElement(document.querySelector(".tg-popover__content-body")!),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("does not treat elements covered by an open surface as the surface owner", () => {
|
||||
document.body.innerHTML = `
|
||||
<button class="avatar">image</button>
|
||||
<a class="fork" href="/fork">Fork</a>
|
||||
<button class="clone">Clone & Download</button>
|
||||
<div class="tg-popover__content-body"></div>
|
||||
`;
|
||||
const avatar = document.querySelector(".avatar")!;
|
||||
const fork = document.querySelector(".fork")!;
|
||||
const clone = document.querySelector(".clone")!;
|
||||
const surface = document.querySelector(".tg-popover__content-body")!;
|
||||
mockRect(avatar, { left: 1340, top: 11, width: 26, height: 26 });
|
||||
mockRect(fork, { left: 1275, top: 56, width: 69, height: 28 });
|
||||
mockRect(clone, { left: 1159, top: 111, width: 180, height: 30 });
|
||||
mockRect(surface, { left: 1111, top: 44, width: 240, height: 496 });
|
||||
|
||||
expect(isLikelyHoverSurfaceOwner(avatar, surface)).toBe(true);
|
||||
expect(isLikelyHoverSurfaceOwner(fork, surface)).toBe(false);
|
||||
expect(isLikelyHoverSurfaceOwner(clone, surface)).toBe(false);
|
||||
});
|
||||
|
||||
it("does not treat fixed navigation containers as plain floating surfaces", () => {
|
||||
document.body.innerHTML = `
|
||||
<header class="navbar-app">
|
||||
<a href="/fork">Fork</a>
|
||||
<button>image</button>
|
||||
</header>
|
||||
<div class="plain-floating"></div>
|
||||
`;
|
||||
const navbar = document.querySelector(".navbar-app")!;
|
||||
const floating = document.querySelector(".plain-floating")!;
|
||||
mockRect(navbar, { left: 0, top: 0, width: 1386, height: 51 });
|
||||
mockRect(floating, { left: 1111, top: 44, width: 240, height: 496 });
|
||||
vi.spyOn(window, "getComputedStyle").mockImplementation((el) => {
|
||||
const style = {
|
||||
cursor: "",
|
||||
display: "block",
|
||||
pointerEvents: "auto",
|
||||
position: el === navbar ? "fixed" : "absolute",
|
||||
visibility: "visible",
|
||||
} as CSSStyleDeclaration;
|
||||
return style;
|
||||
});
|
||||
|
||||
expect(isHoverSurfaceCandidateElement(navbar)).toBe(false);
|
||||
expect(isHoverSurfaceCandidateElement(floating)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user