test(dashboard): align touch fixture with FloatingWindow (#2459)
## Summary - update the touch-resize browser fixture assertions for New Task's FloatingWindow migration - verify the shared nine touch targets, resize handle ID, and unified geometry persistence key ## Test plan - `corepack pnpm --filter @fusion/dashboard build` - `corepack pnpm --filter @fusion/dashboard exec vitest run --project dashboard-browser-touch --silent=passed-only --reporter=dot src/__tests__/task-modal-touch-resize-browser.test.ts` - `corepack pnpm check:changesets` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Updated tablet touch-resize coverage for task-detail to use the shared southeast resize handle and corrected expected hit-target counts. * Refreshed hit-target detection and drag target assertions after switching the test surface to the task-detail/floating window layout. * Updated persistence/geometry validations to compare the stored unified width/height against the resized panel dimensions. * Reworked desktop vs tablet assertions, including tighter overlay padding checks and standardized shared resize-handle sizing expectations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import i18n from "i18next";
|
||||
import { I18nextProvider, initReactI18next } from "react-i18next";
|
||||
@@ -6,8 +6,6 @@ import "./styles.css";
|
||||
import "./components/TaskDetailModal.css";
|
||||
import "./components/FloatingWindow.css";
|
||||
import { FloatingWindow } from "./components/FloatingWindow";
|
||||
import { useModalResizePersist } from "./hooks/useModalResizePersist";
|
||||
import { isTabletTouchViewport, useViewportMode } from "./hooks/useViewportMode";
|
||||
import { NewTaskModal } from "./components/NewTaskModal";
|
||||
import { AgentListModal } from "./components/AgentListModal";
|
||||
import { SetupWizardModal } from "./components/SetupWizardModal";
|
||||
@@ -18,16 +16,15 @@ const surface = params.get("surface") ?? "new-task";
|
||||
if (params.has("reset")) localStorage.clear();
|
||||
|
||||
/*
|
||||
FNXC:TaskModalResize 2026-07-26-16:00:
|
||||
The browser fixture seeds the production persistence path only for resize gestures that need
|
||||
headroom. It never supplies inline panel geometry, so density assertions continue to exercise
|
||||
TaskDetailModal.css's real tablet overlay and width rules.
|
||||
FNXC:ModalTouchGeometry 2026-07-26-20:08:
|
||||
Task Detail now uses FloatingWindow geometry in production. Seed its shared size-and-position payload
|
||||
only for resize gestures that need headroom; density assertions continue to use the default geometry.
|
||||
*/
|
||||
const detailSize = params.get("detailSize");
|
||||
if (detailSize) {
|
||||
const [width, height] = detailSize.split("x").map(Number);
|
||||
if (Number.isFinite(width) && Number.isFinite(height)) {
|
||||
localStorage.setItem("task-detail-modal-size", JSON.stringify({ width, height }));
|
||||
localStorage.setItem("floating-window:task-detail", JSON.stringify({ size: { width, height }, position: { x: 64, y: 64 } }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,15 +54,25 @@ window.fetch = async (input) => {
|
||||
};
|
||||
|
||||
function TaskDetailResizeHarness() {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const viewportMode = useViewportMode();
|
||||
const touchTargets = isTabletTouchViewport(viewportMode);
|
||||
useModalResizePersist(ref, true, "task-detail-modal-size", { touchTargets });
|
||||
return <div className="modal-overlay open">
|
||||
<div ref={ref} className={`modal modal-lg task-detail-modal${viewportMode === "tablet" ? " task-modal--tablet" : ""}${touchTargets ? " task-modal--touch-resize" : ""}`} data-testid="task-detail-modal">
|
||||
<div className="task-detail-content"><div className="modal-header">Task detail</div><div className="modal-body">Task detail body</div></div>
|
||||
return <FloatingWindow
|
||||
windowKey="task-detail-fixture"
|
||||
title="Task detail"
|
||||
onClose={() => undefined}
|
||||
hideHeader
|
||||
dragHandleSelector=".task-detail-content--embedded > .modal-header"
|
||||
className="floating-window--task-detail"
|
||||
defaultSize={{ width: 560, height: 480 }}
|
||||
minSize={{ width: 320, height: 240 }}
|
||||
persistGeometryKey="floating-window:task-detail"
|
||||
suspendGeometryPersistenceOnMobile
|
||||
layer="task-detail"
|
||||
testId="task-detail-modal-overlay"
|
||||
>
|
||||
<div className="task-detail-content task-detail-content--embedded">
|
||||
<div className="modal-header">Task detail</div>
|
||||
<div className="modal-body">Task detail body</div>
|
||||
</div>
|
||||
</div>;
|
||||
</FloatingWindow>;
|
||||
}
|
||||
|
||||
function FloatingWindowHarness() {
|
||||
|
||||
@@ -147,26 +147,32 @@ describe.runIf(executablePath)("Task modal tablet touch resize browser regressio
|
||||
await page.goto(`${baseUrl}app/task-modal-touch-resize-e2e-fixture.html?surface=task-detail&reset=1&detailSize=600x500`);
|
||||
await page.waitForTimeout(350);
|
||||
expect(await page.evaluate(() => window.scrollX === 0 && window.scrollY === 0)).toBe(true);
|
||||
expect(await page.evaluate(() => document.querySelectorAll("[data-resize-hit-target='true']").length)).toBe(1);
|
||||
expect(await page.evaluate(() => document.querySelectorAll("[data-resize-hit-target='true']").length)).toBe(9);
|
||||
await mkdir(screenshots, { recursive: true });
|
||||
if (width === 820) await page.screenshot({ path: path.join(screenshots, "tablet-before.png") });
|
||||
|
||||
const detailSelector = "[data-testid='task-detail-modal'] .modal-resize-grip";
|
||||
const detailPanel = "[data-testid='floating-window-task-detail-fixture']";
|
||||
const detailSelector = `${detailPanel} [data-testid='floating-window-resize-se']`;
|
||||
const detailPoint = await targetCenter(page, detailSelector);
|
||||
expect(await page.evaluate((point) => document.elementFromPoint(point.x, point.y)?.getAttribute("data-resize-hit-target"), detailPoint)).toBe("true");
|
||||
const detailBefore = await rect(page, "[data-testid='task-detail-modal']");
|
||||
const detailBefore = await rect(page, detailPanel);
|
||||
await touchDrag(cdp, detailPoint);
|
||||
await page.waitForTimeout(250);
|
||||
const detailAfter = await rect(page, "[data-testid='task-detail-modal']");
|
||||
const detailAfter = await rect(page, detailPanel);
|
||||
expect(detailAfter.width).toBeGreaterThan(detailBefore.width);
|
||||
expect(detailAfter.height).toBeGreaterThan(detailBefore.height);
|
||||
expect(detailAfter.width).toBeLessThanOrEqual(width);
|
||||
expect(detailAfter.height).toBeLessThanOrEqual(height);
|
||||
expect(await page.evaluate(() => localStorage.getItem("task-detail-modal-size"))).not.toBeNull();
|
||||
expect(detailAfter.width).toBeLessThanOrEqual(width - 32);
|
||||
expect(detailAfter.height).toBeLessThanOrEqual(height - 32);
|
||||
const persistedTaskDetailGeometry = await page.evaluate(() => {
|
||||
const raw = localStorage.getItem("floating-window:task-detail");
|
||||
return raw ? JSON.parse(raw) : null;
|
||||
});
|
||||
expect(persistedTaskDetailGeometry?.size.width).toBeCloseTo(detailAfter.width);
|
||||
expect(persistedTaskDetailGeometry?.size.height).toBeCloseTo(detailAfter.height);
|
||||
|
||||
await page.goto(`${baseUrl}app/task-modal-touch-resize-e2e-fixture.html?surface=new-task`);
|
||||
await page.waitForTimeout(350);
|
||||
expect(await page.evaluate(() => document.querySelectorAll("[data-resize-hit-target='true']").length)).toBe(8);
|
||||
expect(await page.evaluate(() => document.querySelectorAll("[data-resize-hit-target='true']").length)).toBe(9);
|
||||
const newTaskPanel = ".new-task-modal";
|
||||
const headerPoint = await targetCenter(page, "[data-testid='new-task-drag-handle']");
|
||||
const newTaskBeforeHeaderDrag = await rect(page, newTaskPanel);
|
||||
@@ -178,7 +184,7 @@ describe.runIf(executablePath)("Task modal tablet touch resize browser regressio
|
||||
expect(newTaskAfterHeaderDrag.width).toBe(newTaskBeforeHeaderDrag.width);
|
||||
expect(newTaskAfterHeaderDrag.height).toBe(newTaskBeforeHeaderDrag.height);
|
||||
|
||||
const newTaskTarget = "[data-testid='new-task-resize-se']";
|
||||
const newTaskTarget = "[data-testid='floating-window-resize-se']";
|
||||
const newTaskPoint = await targetCenter(page, newTaskTarget);
|
||||
expect(await page.evaluate((point) => document.elementFromPoint(point.x, point.y)?.getAttribute("data-resize-hit-target"), newTaskPoint)).toBe("true");
|
||||
const newTaskBeforeResize = await rect(page, newTaskPanel);
|
||||
@@ -189,8 +195,16 @@ describe.runIf(executablePath)("Task modal tablet touch resize browser regressio
|
||||
expect(newTaskAfterResize.height).toBeGreaterThan(newTaskBeforeResize.height);
|
||||
expect(newTaskAfterResize.width).toBeLessThanOrEqual(width - 32);
|
||||
expect(newTaskAfterResize.height).toBeLessThanOrEqual(height - 32);
|
||||
expect(await page.evaluate(() => localStorage.getItem("fusion:new-task-modal-size"))).not.toBeNull();
|
||||
expect(await page.evaluate(() => localStorage.getItem("fusion:new-task-modal-position"))).not.toBeNull();
|
||||
/*
|
||||
FNXC:ModalTouchGeometry 2026-07-26-19:51:
|
||||
The browser regression must prove FloatingWindow persisted usable resized geometry, not merely created the storage key.
|
||||
*/
|
||||
const persistedNewTaskGeometry = await page.evaluate(() => {
|
||||
const raw = localStorage.getItem("fusion:new-task-modal-geometry");
|
||||
return raw ? JSON.parse(raw) : null;
|
||||
});
|
||||
expect(persistedNewTaskGeometry?.size.width).toBeCloseTo(newTaskAfterResize.width);
|
||||
expect(persistedNewTaskGeometry?.size.height).toBeCloseTo(newTaskAfterResize.height);
|
||||
if (width === 820) await page.screenshot({ path: path.join(screenshots, "tablet-after.png") });
|
||||
await page.close();
|
||||
}, 30_000);
|
||||
@@ -283,15 +297,13 @@ describe.runIf(executablePath)("Task modal tablet touch resize browser regressio
|
||||
await desktop.goto(`${baseUrl}app/task-modal-touch-resize-e2e-fixture.html?surface=task-detail&reset=1`);
|
||||
await desktop.waitForTimeout(250);
|
||||
const desktopTaskDetail = {
|
||||
panel: await boxMetrics(desktop, "[data-testid='task-detail-modal']"),
|
||||
header: await boxMetrics(desktop, ".task-detail-modal .modal-header"),
|
||||
body: await boxMetrics(desktop, ".task-detail-modal .modal-body"),
|
||||
overlay: await boxMetrics(desktop, ".modal-overlay"),
|
||||
panel: await boxMetrics(desktop, "[data-testid='floating-window-task-detail-fixture']"),
|
||||
header: await boxMetrics(desktop, ".task-detail-content .modal-header"),
|
||||
body: await boxMetrics(desktop, ".task-detail-content .modal-body"),
|
||||
overlay: await boxMetrics(desktop, "[data-testid='task-detail-modal-overlay']"),
|
||||
handle: await boxMetrics(desktop, "[data-testid='floating-window-task-detail-fixture'] [data-testid='floating-window-resize-se']"),
|
||||
};
|
||||
// The desktop overlay is the intentional density baseline: 10vh top placement with no
|
||||
// panel-internal padding added by touch geometry.
|
||||
expect(parseFloat(desktopTaskDetail.overlay.paddingBlockStart)).toBeCloseTo(100);
|
||||
expect(desktopTaskDetail.panel.y).toBeCloseTo(parseFloat(desktopTaskDetail.overlay.paddingBlockStart));
|
||||
expect(parseFloat(desktopTaskDetail.overlay.paddingBlockStart)).toBe(0);
|
||||
await desktop.goto(`${baseUrl}app/task-modal-touch-resize-e2e-fixture.html?surface=new-task&reset=1`);
|
||||
await desktop.waitForTimeout(250);
|
||||
const desktopNewTask = {
|
||||
@@ -307,28 +319,28 @@ describe.runIf(executablePath)("Task modal tablet touch resize browser regressio
|
||||
await tablet.goto(`${baseUrl}app/task-modal-touch-resize-e2e-fixture.html?surface=task-detail&reset=1`);
|
||||
await tablet.waitForTimeout(250);
|
||||
const tabletTaskDetail = {
|
||||
panel: await boxMetrics(tablet, "[data-testid='task-detail-modal']"),
|
||||
header: await boxMetrics(tablet, ".task-detail-modal .modal-header"),
|
||||
body: await boxMetrics(tablet, ".task-detail-modal .modal-body"),
|
||||
overlay: await boxMetrics(tablet, ".modal-overlay"),
|
||||
grip: await boxMetrics(tablet, ".task-detail-modal .modal-resize-grip"),
|
||||
panel: await boxMetrics(tablet, "[data-testid='floating-window-task-detail-fixture']"),
|
||||
header: await boxMetrics(tablet, ".task-detail-content .modal-header"),
|
||||
body: await boxMetrics(tablet, ".task-detail-content .modal-body"),
|
||||
overlay: await boxMetrics(tablet, "[data-testid='task-detail-modal-overlay']"),
|
||||
handle: await boxMetrics(tablet, "[data-testid='floating-window-task-detail-fixture'] [data-testid='floating-window-resize-se']"),
|
||||
};
|
||||
// The production tablet breakpoint intentionally sizes the panel to 98vw and offsets it
|
||||
// by 6vh. Checking computed overlay padding plus the actual panel rect catches a painted
|
||||
// inset regression that the fixed overlay's own y=0 rect cannot observe.
|
||||
expect(parseFloat(tabletTaskDetail.overlay.paddingBlockStart)).toBeCloseTo(1024 * 0.06);
|
||||
expect(tabletTaskDetail.panel.y).toBeCloseTo(parseFloat(tabletTaskDetail.overlay.paddingBlockStart));
|
||||
expect(tabletTaskDetail.panel.width).toBeCloseTo(768 * 0.98, 1);
|
||||
expect(tabletTaskDetail.panel.x).toBeCloseTo((768 - tabletTaskDetail.panel.width) / 2, 1);
|
||||
/*
|
||||
FNXC:ModalTouchGeometry 2026-07-26-20:08:
|
||||
Task Detail and New Task share FloatingWindow's zero-inset tablet geometry while preserving
|
||||
desktop content density and 44px touch handles.
|
||||
*/
|
||||
expect(tabletTaskDetail.overlay.paddingBlockStart).toBe(desktopTaskDetail.overlay.paddingBlockStart);
|
||||
expect(tabletTaskDetail.panel.width).toBe(desktopTaskDetail.panel.width);
|
||||
expect(tabletTaskDetail.panel.x).toBeGreaterThanOrEqual(0);
|
||||
expect(tabletTaskDetail.overlay.paddingInlineStart).toBe(desktopTaskDetail.overlay.paddingInlineStart);
|
||||
expect(tabletTaskDetail.overlay.paddingInlineEnd).toBe(desktopTaskDetail.overlay.paddingInlineEnd);
|
||||
expect(tabletTaskDetail.panel.width).toBeLessThan(desktopTaskDetail.panel.width);
|
||||
expect(tabletTaskDetail.header.paddingBlockStart).toBe(desktopTaskDetail.header.paddingBlockStart);
|
||||
expect(tabletTaskDetail.header.paddingBlockEnd).toBe(desktopTaskDetail.header.paddingBlockEnd);
|
||||
expect(tabletTaskDetail.body.paddingBlockStart).toBe(desktopTaskDetail.body.paddingBlockStart);
|
||||
expect(tabletTaskDetail.body.paddingBlockEnd).toBe(desktopTaskDetail.body.paddingBlockEnd);
|
||||
expect(tabletTaskDetail.grip.width).toBe(44);
|
||||
expect(tabletTaskDetail.grip.height).toBe(44);
|
||||
expect(tabletTaskDetail.handle.width).toBe(44);
|
||||
expect(tabletTaskDetail.handle.height).toBe(44);
|
||||
|
||||
await tablet.goto(`${baseUrl}app/task-modal-touch-resize-e2e-fixture.html?surface=new-task&reset=1`);
|
||||
await tablet.waitForTimeout(250);
|
||||
@@ -337,7 +349,7 @@ describe.runIf(executablePath)("Task modal tablet touch resize browser regressio
|
||||
body: await boxMetrics(tablet, ".new-task-modal .modal-body"),
|
||||
overlay: await boxMetrics(tablet, "[data-testid='new-task-modal-overlay']"),
|
||||
panel: await boxMetrics(tablet, ".new-task-modal"),
|
||||
handle: await boxMetrics(tablet, "[data-testid='new-task-resize-se']"),
|
||||
handle: await boxMetrics(tablet, "[data-testid='floating-window-resize-se']"),
|
||||
};
|
||||
expect(tabletNewTask.header.paddingBlockStart).toBe(desktopNewTask.header.paddingBlockStart);
|
||||
expect(tabletNewTask.header.paddingBlockEnd).toBe(desktopNewTask.header.paddingBlockEnd);
|
||||
|
||||
Reference in New Issue
Block a user