mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
fix(replay-ui): size overlay viewBox from hierarchy root bounds
Tap points are recorded in the hierarchy's coordinate space (iOS points, Android pixels, web CSS px) while screenshots are device pixels, so the overlay rendered at 1/3 position on iOS 3x screens. Derive the viewBox from the root element bounds; natural image size stays the fallback.
This commit is contained in:
1 parent
9958b0ddc8
commit
d84dc74b0b
3 files changed
+71
-2
No files matched your search
@@ -0,0 +1,36 @@
|
|||||||
|
import { describe, it, expect } from "bun:test";
|
||||||
|
import { deviceSpaceOf } from "../lib/device-space";
|
||||||
|
import type { Hierarchy } from "../types";
|
||||||
|
|
||||||
|
function hierarchyWithRoot(right: number, bottom: number): Hierarchy {
|
||||||
|
return {
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
bounds: { left: 0, top: 0, right, bottom },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("deviceSpaceOf", () => {
|
||||||
|
it("returns the root bounds extent", () => {
|
||||||
|
expect(deviceSpaceOf(hierarchyWithRoot(393, 852))).toEqual({
|
||||||
|
width: 393,
|
||||||
|
height: 852,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined without a hierarchy", () => {
|
||||||
|
expect(deviceSpaceOf(undefined)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined for an empty hierarchy", () => {
|
||||||
|
expect(deviceSpaceOf({ elements: [] })).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined for non-positive root bounds", () => {
|
||||||
|
expect(deviceSpaceOf(hierarchyWithRoot(0, 852))).toBeUndefined();
|
||||||
|
expect(deviceSpaceOf(hierarchyWithRoot(393, 0))).toBeUndefined();
|
||||||
|
expect(deviceSpaceOf(hierarchyWithRoot(-1, -1))).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import type { Hierarchy } from "../types";
|
||||||
|
|
||||||
|
// Tap points and resolved bounds in the trace share the hierarchy root's
|
||||||
|
// coordinate space (iOS points, Android pixels, web CSS px). Screenshots may
|
||||||
|
// be scaled (iOS 3x, web DPR>1), so the overlay viewBox must come from the
|
||||||
|
// root bounds, not the image's natural pixel size.
|
||||||
|
export function deviceSpaceOf(
|
||||||
|
hierarchy?: Hierarchy,
|
||||||
|
): { width: number; height: number } | undefined {
|
||||||
|
const root = hierarchy?.elements?.[0];
|
||||||
|
if (!root) return undefined;
|
||||||
|
const width = root.bounds.right;
|
||||||
|
const height = root.bounds.bottom;
|
||||||
|
if (!(width > 0) || !(height > 0)) return undefined;
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import Tabs, { type TabDefinition } from "../components/Tabs";
|
|||||||
import { useStep } from "../hooks/useStep";
|
import { useStep } from "../hooks/useStep";
|
||||||
import { useKeyboardNav } from "../hooks/useKeyboardNav";
|
import { useKeyboardNav } from "../hooks/useKeyboardNav";
|
||||||
import { useTheme } from "../hooks/useTheme";
|
import { useTheme } from "../hooks/useTheme";
|
||||||
|
import { deviceSpaceOf } from "../lib/device-space";
|
||||||
|
|
||||||
function basename(specPath: string): string {
|
function basename(specPath: string): string {
|
||||||
const index = specPath.lastIndexOf("/");
|
const index = specPath.lastIndexOf("/");
|
||||||
@@ -129,12 +130,21 @@ export default function RunDetail() {
|
|||||||
const witnessesBefore = currentStep?.witnesses;
|
const witnessesBefore = currentStep?.witnesses;
|
||||||
const witnessesAfter = nextStep?.witnesses ?? witnessesBefore;
|
const witnessesAfter = nextStep?.witnesses ?? witnessesBefore;
|
||||||
const exceptionsForStep = currentStep?.exceptions;
|
const exceptionsForStep = currentStep?.exceptions;
|
||||||
|
const beforeSpace = deviceSpaceOf(currentStep?.hierarchy);
|
||||||
|
const afterSpace = deviceSpaceOf(nextStep?.hierarchy ?? currentStep?.hierarchy);
|
||||||
|
|
||||||
const beforeTabs: TabDefinition[] = [
|
const beforeTabs: TabDefinition[] = [
|
||||||
{
|
{
|
||||||
id: "screenshot",
|
id: "screenshot",
|
||||||
label: "Screenshot",
|
label: "Screenshot",
|
||||||
content: <Screenshot src={beforeScreenshot} action={currentStep?.next_action} />,
|
content: (
|
||||||
|
<Screenshot
|
||||||
|
src={beforeScreenshot}
|
||||||
|
action={currentStep?.next_action}
|
||||||
|
deviceWidth={beforeSpace?.width}
|
||||||
|
deviceHeight={beforeSpace?.height}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "snapshots",
|
id: "snapshots",
|
||||||
@@ -194,7 +204,14 @@ export default function RunDetail() {
|
|||||||
{
|
{
|
||||||
id: "screenshot",
|
id: "screenshot",
|
||||||
label: "Screenshot",
|
label: "Screenshot",
|
||||||
content: <Screenshot src={afterScreenshot} action={undefined} />,
|
content: (
|
||||||
|
<Screenshot
|
||||||
|
src={afterScreenshot}
|
||||||
|
action={undefined}
|
||||||
|
deviceWidth={afterSpace?.width}
|
||||||
|
deviceHeight={afterSpace?.height}
|
||||||
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "snapshots",
|
id: "snapshots",
|
||||||
|
|||||||
Reference in new issue
Block a user