@@ -1486,13 +1486,6 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14861486 expect.objectContaining({ id: "captureInputContent", paddingBottom: "2px" })
14871487 ]));
14881488 expect(compactBottomWhitespace.every((entry) => entry.bottomGap <= 4)).toBe(true);
1489- const setupContentFit = await page.locator("#gestureSetupContent, #captureInputContent").evaluateAll((contents) => (
1490- contents.map((content) => ({
1491- id: content.id,
1492- overflowDelta: content.scrollHeight - content.clientHeight
1493- }))
1494- ));
1495- expect(setupContentFit.every((entry) => entry.overflowDelta <= 2)).toBe(true);
14961489 const gestureDeviceBottomGaps = await page.locator("#inputMappingV2GestureList .input-mapping-v2__gesture-group").evaluateAll((groups) => (
14971490 groups.map((group) => {
14981491 const groupBox = group.getBoundingClientRect();
@@ -1517,7 +1510,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15171510 await page.keyboard.press("KeyP");
15181511 await expect(page.locator(".input-mapping-v2__mapping-card[data-input-mapping-tile-action-id='cancel']")).toContainText("Keyboard KeyP Press");
15191512 const cancelTileKeyPToken = page.locator(".input-mapping-v2__mapping-card[data-input-mapping-tile-action-id='cancel'] .input-mapping-v2__input-token[data-input-mapping-binding='KeyP']");
1520- await expect(cancelTileKeyPToken).toHaveText("Keyboard\nKeyP\nPress ");
1513+ await expect(cancelTileKeyPToken).toHaveText("Keyboard KeyP Press ");
15211514 await expect(cancelTileKeyPToken).toHaveClass(/is-selected-mapping-input/);
15221515 await expect(cancelTileKeyPToken).toHaveAttribute("aria-current", "true");
15231516 await expect(cancelTileKeyPToken).toHaveCSS("box-shadow", /rgba/);
@@ -1702,6 +1695,198 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17021695 }
17031696 });
17041697
1698+ test("sizes Input Mapping V2 columns and live-highlights mapped non-keyboard inputs", async ({ page }) => {
1699+ await page.setViewportSize({ width: 1600, height: 900 });
1700+ await page.addInitScript(() => {
1701+ window.__inputMappingV2MockGamepads = [
1702+ {
1703+ axes: [0, 0, 0, 0],
1704+ buttons: [{ pressed: false }, { pressed: false }],
1705+ connected: true,
1706+ id: "Arcade Flight Stick (Vendor: 1234 Product: abcd)",
1707+ index: 0,
1708+ mapping: "",
1709+ timestamp: 20
1710+ }
1711+ ];
1712+ Object.defineProperty(navigator, "getGamepads", {
1713+ configurable: true,
1714+ value: () => window.__inputMappingV2MockGamepads
1715+ });
1716+ });
1717+ const pageErrors = [];
1718+ page.on("pageerror", (error) => {
1719+ pageErrors.push(error.message);
1720+ });
1721+ const server = await openInputMappingV2(page);
1722+ try {
1723+ await expect(page.locator("body[data-tool-id='input-mapping-v2']")).toBeVisible();
1724+ await expect(page.locator(".input-mapping-v2__gamepad-capture-button[data-input-mapping-gamepad-index='0']")).toBeVisible();
1725+
1726+ const columnSizing = await page.locator(".input-mapping-v2.tool-starter.app-shell").evaluate((shell) => {
1727+ const leftPanel = shell.querySelector(".tool-starter__panel--left");
1728+ const centerPanel = shell.querySelector(".tool-starter__panel--center");
1729+ const leftAccordions = Array.from(leftPanel.querySelectorAll(":scope > .tool-starter__accordion"));
1730+ const centerAccordions = Array.from(centerPanel.querySelectorAll(":scope > .tool-starter__accordion"));
1731+ const actions = leftAccordions[0];
1732+ const devices = leftAccordions[1];
1733+ const actionHeader = actions.querySelector(".accordion-v2__header");
1734+ const actionContent = actions.querySelector("#actionSetupContent");
1735+ const gestures = centerAccordions[0];
1736+ const capture = centerAccordions[1];
1737+ const captured = centerAccordions[2];
1738+ const leftBox = leftPanel.getBoundingClientRect();
1739+ const centerBox = centerPanel.getBoundingClientRect();
1740+ const leftInnerBottom = leftBox.bottom - Number.parseFloat(getComputedStyle(leftPanel).paddingBottom || "0");
1741+ const centerInnerBottom = centerBox.bottom - Number.parseFloat(getComputedStyle(centerPanel).paddingBottom || "0");
1742+ return {
1743+ actionContentOverflow: Math.round(actionContent.scrollHeight - actionContent.clientHeight),
1744+ actionExpectedMax: Math.ceil(actionHeader.getBoundingClientRect().height + actionContent.scrollHeight + 8),
1745+ actionHeight: Math.round(actions.getBoundingClientRect().height),
1746+ capturedBottomDelta: Math.round(centerInnerBottom - captured.getBoundingClientRect().bottom),
1747+ capturedHeight: Math.round(captured.getBoundingClientRect().height),
1748+ devicesBottomDelta: Math.round(leftInnerBottom - devices.getBoundingClientRect().bottom),
1749+ devicesHeight: Math.round(devices.getBoundingClientRect().height),
1750+ setupHeight: Math.round(gestures.getBoundingClientRect().height + capture.getBoundingClientRect().height)
1751+ };
1752+ });
1753+ expect(columnSizing.actionHeight).toBeLessThanOrEqual(columnSizing.actionExpectedMax);
1754+ expect(columnSizing.actionContentOverflow).toBeLessThanOrEqual(1);
1755+ expect(columnSizing.devicesHeight).toBeGreaterThan(columnSizing.actionHeight);
1756+ expect(Math.abs(columnSizing.devicesBottomDelta)).toBeLessThanOrEqual(2);
1757+ expect(columnSizing.capturedHeight).toBeGreaterThanOrEqual(300);
1758+ expect(columnSizing.capturedHeight).toBeGreaterThan(columnSizing.setupHeight / 2);
1759+ expect(Math.abs(columnSizing.capturedBottomDelta)).toBeLessThanOrEqual(2);
1760+
1761+ await expect(page.locator(".input-mapping-v2__device-card[data-input-mapping-device-id='touch']")).toContainText(/Touch capability|Touch capture/);
1762+ await expect(page.locator(".input-mapping-v2__device-card[data-input-mapping-device-id='pen']")).toContainText(/Pen capability|Pen capture/);
1763+ await expect(page.locator(".input-mapping-v2__device-card[data-input-mapping-device-id='flightStick']")).toContainText(/game controller capture|flight stick/i);
1764+ await expect(page.locator(".input-mapping-v2__device-card[data-input-mapping-device-id='vrController']")).toContainText(/WebXR|VR controller capture|not available/i);
1765+
1766+ await page.locator("#inputMappingV2ActionSelect").selectOption("cancel");
1767+ await page.locator("#inputMappingV2AddActionButton").click();
1768+ await page.locator(".input-mapping-v2__gesture-group", { hasText: "Keyboard" })
1769+ .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='KeyboardPress']")
1770+ .click();
1771+ await page.locator("#inputMappingV2CaptureKeyboardButton").click();
1772+ await page.keyboard.press("KeyP");
1773+
1774+ await page.locator(".input-mapping-v2__gesture-group", { hasText: "Mouse" })
1775+ .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='MouseClick']")
1776+ .click();
1777+ await page.locator("#inputMappingV2CaptureMouseButton").click();
1778+ await page.evaluate(() => {
1779+ window.dispatchEvent(new MouseEvent("mousedown", {
1780+ bubbles: true,
1781+ button: 2,
1782+ cancelable: true
1783+ }));
1784+ });
1785+
1786+ await page.locator(".input-mapping-v2__gesture-group", { hasText: "Mouse" })
1787+ .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='MouseWheelUp']")
1788+ .click();
1789+
1790+ await page.locator(".input-mapping-v2__gesture-group", { hasText: "Game Controller" })
1791+ .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='GameControllerButton']")
1792+ .click();
1793+ await page.locator(".input-mapping-v2__gamepad-capture-button[data-input-mapping-gamepad-index='0']").click();
1794+ await page.evaluate(() => {
1795+ window.__inputMappingV2MockGamepads[0] = {
1796+ ...window.__inputMappingV2MockGamepads[0],
1797+ buttons: [{ pressed: false }, { pressed: true }],
1798+ timestamp: 21
1799+ };
1800+ });
1801+ await expect(page.locator(".input-mapping-v2__gamepad-capture-button[data-input-mapping-gamepad-index='0']")).not.toHaveClass(/is-capturing/, { timeout: 2500 });
1802+ await page.evaluate(() => {
1803+ window.__inputMappingV2MockGamepads[0] = {
1804+ ...window.__inputMappingV2MockGamepads[0],
1805+ buttons: [{ pressed: false }, { pressed: false }],
1806+ timestamp: 22
1807+ };
1808+ });
1809+
1810+ const cancelTile = page.locator(".input-mapping-v2__mapping-card[data-input-mapping-tile-action-id='cancel']");
1811+ await expect(cancelTile).toContainText("Keyboard KeyP Press, Mouse Right Button Click, Mouse Wheel Up Wheel, Game Controller Button 1 Button");
1812+ await expect(cancelTile.locator(".input-mapping-v2__input-separator")).toHaveCount(3);
1813+ const tokenLayout = await cancelTile.locator(".input-mapping-v2__input-token").evaluateAll((tokens) => tokens.map((token) => ({
1814+ cardWidth: Math.round(token.closest(".input-mapping-v2__mapping-card").getBoundingClientRect().width),
1815+ text: token.textContent,
1816+ title: token.title,
1817+ whiteSpace: getComputedStyle(token).whiteSpace,
1818+ width: Math.round(token.getBoundingClientRect().width)
1819+ })));
1820+ expect(tokenLayout.map((entry) => entry.text)).toEqual([
1821+ "Keyboard KeyP Press",
1822+ "Mouse Right Button Click",
1823+ "Mouse Wheel Up Wheel",
1824+ "Game Controller Button 1 Button"
1825+ ]);
1826+ expect(tokenLayout.every((entry) => !entry.text.includes("\n"))).toBe(true);
1827+ expect(tokenLayout.every((entry) => entry.whiteSpace === "nowrap")).toBe(true);
1828+ expect(tokenLayout.every((entry) => entry.width <= entry.cardWidth)).toBe(true);
1829+ expect(tokenLayout.some((entry) => entry.title.includes("\n"))).toBe(true);
1830+
1831+ const keyToken = cancelTile.locator(".input-mapping-v2__input-token[data-input-mapping-binding='KeyP']");
1832+ await page.keyboard.down("KeyP");
1833+ await expect(keyToken).toHaveClass(/is-action-active/);
1834+ await page.keyboard.up("KeyP");
1835+ await expect(keyToken).not.toHaveClass(/is-action-active/);
1836+
1837+ const mouseToken = cancelTile.locator(".input-mapping-v2__input-token[data-input-mapping-binding='MouseButton2']");
1838+ await page.evaluate(() => {
1839+ window.dispatchEvent(new MouseEvent("mousedown", {
1840+ bubbles: true,
1841+ button: 2,
1842+ cancelable: true
1843+ }));
1844+ });
1845+ await expect(mouseToken).toHaveClass(/is-action-active/);
1846+ await page.evaluate(() => {
1847+ window.dispatchEvent(new MouseEvent("mouseup", {
1848+ bubbles: true,
1849+ button: 2,
1850+ cancelable: true
1851+ }));
1852+ });
1853+ await expect(mouseToken).not.toHaveClass(/is-action-active/);
1854+
1855+ const wheelToken = cancelTile.locator(".input-mapping-v2__input-token[data-input-mapping-binding='MouseWheelUp']");
1856+ await page.evaluate(() => {
1857+ window.dispatchEvent(new WheelEvent("wheel", {
1858+ bubbles: true,
1859+ cancelable: true,
1860+ deltaY: -120
1861+ }));
1862+ });
1863+ await expect(wheelToken).toHaveClass(/is-action-active/, { timeout: 1000 });
1864+
1865+ const gamepadToken = cancelTile.locator(".input-mapping-v2__input-token[data-input-mapping-binding='Pad0:Button1']");
1866+ await expect(gamepadToken).not.toHaveClass(/is-action-active/, { timeout: 2500 });
1867+ await page.evaluate(() => {
1868+ window.__inputMappingV2MockGamepads[0] = {
1869+ ...window.__inputMappingV2MockGamepads[0],
1870+ buttons: [{ pressed: false }, { pressed: true }],
1871+ timestamp: 23
1872+ };
1873+ });
1874+ await expect(gamepadToken).toHaveClass(/is-action-active/, { timeout: 2500 });
1875+ await page.evaluate(() => {
1876+ window.__inputMappingV2MockGamepads[0] = {
1877+ ...window.__inputMappingV2MockGamepads[0],
1878+ buttons: [{ pressed: false }, { pressed: false }],
1879+ timestamp: 24
1880+ };
1881+ });
1882+ await expect(gamepadToken).not.toHaveClass(/is-action-active/, { timeout: 2500 });
1883+ expect(pageErrors).toEqual([]);
1884+ } finally {
1885+ await workspaceV2CoverageReporter.stop(page);
1886+ await server.close();
1887+ }
1888+ });
1889+
17051890 test("launches Input Mapping V2 and captures keyboard mappings", async ({ page }) => {
17061891 await page.setViewportSize({ width: 1920, height: 900 });
17071892 const server = await openInputMappingV2(page);
@@ -1803,13 +1988,6 @@ test.describe("Workspace Manager V2 bootstrap", () => {
18031988 })
18041989 ));
18051990 expect(compactBottomWhitespace.every((entry) => entry.bottomGap <= 4)).toBe(true);
1806- const setupContentFit = await page.locator("#gestureSetupContent, #captureInputContent").evaluateAll((contents) => (
1807- contents.map((content) => ({
1808- id: content.id,
1809- overflowDelta: content.scrollHeight - content.clientHeight
1810- }))
1811- ));
1812- expect(setupContentFit.every((entry) => entry.overflowDelta <= 2)).toBe(true);
18131991 const gestureFlowLayout = await page.locator("#inputMappingV2GestureList").evaluate((container) => {
18141992 const wantedGroups = ["Keyboard", "Mouse", "Game Controller"];
18151993 const groupLayouts = wantedGroups.map((label) => {
0 commit comments