@@ -1772,6 +1772,13 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17721772 .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='MouseWheelUp']")
17731773 .click();
17741774 await page.locator("#inputMappingV2CaptureMouseButton").click();
1775+ await page.evaluate(() => {
1776+ window.dispatchEvent(new WheelEvent("wheel", {
1777+ bubbles: true,
1778+ cancelable: true,
1779+ deltaY: -120
1780+ }));
1781+ });
17751782
17761783 await page.locator(".input-mapping-v2__gesture-group", { hasText: "Game Controller" })
17771784 .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='GameControllerButton']")
@@ -1950,7 +1957,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
19501957 await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='MouseButton0']")).toHaveText("Mouse, Left Button, Click");
19511958
19521959 await page.locator(".input-mapping-v2__gesture-group", { hasText: "Game Controller" })
1953- .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='GameControllerButton ']")
1960+ .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='GameControllerStick ']")
19541961 .click();
19551962 expect(await captureDisabledState()).toEqual({
19561963 gamepads: [false],
@@ -1983,14 +1990,14 @@ test.describe("Workspace Manager V2 bootstrap", () => {
19831990 await expect(fireTile.locator(".input-mapping-v2__input-token")).toHaveText([
19841991 "Keyboard, KeyH, Hold",
19851992 "Mouse, Left Button, Click",
1986- "Game Controller, Axis 0+, Button "
1993+ "Game Controller, Axis 0+, Stick "
19871994 ]);
19881995 await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='KeyH:KeyboardHold']")).toHaveText("Keyboard, KeyH, Hold");
19891996 await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='MouseButton0']")).toHaveText("Mouse, Left Button, Click");
1990- await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='Pad0:Axis0+:GameControllerButton ']")).toHaveText("Game Controller, Axis 0+, Button ");
1997+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='Pad0:Axis0+']")).toHaveText("Game Controller, Axis 0+, Stick ");
19911998 await expect(fireTile.locator(".input-mapping-v2__input-token[data-input-mapping-binding='KeyH:KeyboardHold']")).toHaveAttribute("title", "Keyboard\nKeyH\nHold");
19921999 await expect(fireTile.locator(".input-mapping-v2__input-token[data-input-mapping-binding='MouseButton0']")).toHaveAttribute("title", "Mouse\nMouse Left Button");
1993- await expect(fireTile.locator(".input-mapping-v2__input-token[data-input-mapping-binding='Pad0:Axis0+:GameControllerButton ']")).toHaveAttribute("title", /Arcade Flight Stick[\s\S]*Axis 0\+[\s\S]*Button /);
2000+ await expect(fireTile.locator(".input-mapping-v2__input-token[data-input-mapping-binding='Pad0:Axis0+']")).toHaveAttribute("title", /Arcade Flight Stick[\s\S]*Axis 0\+/);
19942001 const tokenMetrics = await fireTile.locator(".input-mapping-v2__input-token").evaluateAll((tokens) => tokens.map((token) => ({
19952002 cardWidth: Math.round(token.closest(".input-mapping-v2__mapping-card").getBoundingClientRect().width),
19962003 flexBasis: getComputedStyle(token).flexBasis,
@@ -2090,7 +2097,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
20902097 key: "Control"
20912098 }));
20922099 });
2093- await expect(page.locator("#inputMappingV2CaptureMessage")).toContainText(/Combo capture recorded Keyboard ControlLeft\. Press one more /);
2100+ await expect(page.locator("#inputMappingV2CaptureMessage")).toContainText(/Combo capture recorded Keyboard ControlLeft\. Waiting for second input\. /);
20942101 await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='Combo:ControlLeft+KeyS']")).toHaveCount(0);
20952102 await expect(page.locator("#inspectorOutput")).not.toContainText('"binding": "Combo:ControlLeft+KeyS"');
20962103 await expect.poll(() => fireTile.evaluate((card) => card.scrollTop)).toBe(selectedScrollTop);
@@ -2130,6 +2137,208 @@ test.describe("Workspace Manager V2 bootstrap", () => {
21302137 }
21312138 });
21322139
2140+ test("honors Input Mapping V2 gesture-specific capture sessions", async ({ page }) => {
2141+ await page.setViewportSize({ width: 1600, height: 900 });
2142+ await page.addInitScript(() => {
2143+ const buttons = Array.from({ length: 16 }, () => ({ pressed: false }));
2144+ window.__inputMappingV2MockGamepads = [{
2145+ axes: [0, 0, 0, 0],
2146+ buttons,
2147+ connected: true,
2148+ id: "Standard Controller",
2149+ index: 0,
2150+ mapping: "standard",
2151+ timestamp: 70
2152+ }];
2153+ Object.defineProperty(navigator, "getGamepads", {
2154+ configurable: true,
2155+ value: () => window.__inputMappingV2MockGamepads
2156+ });
2157+ });
2158+ const pageErrors = [];
2159+ page.on("pageerror", (error) => {
2160+ pageErrors.push(error.message);
2161+ });
2162+ const server = await openInputMappingV2(page);
2163+ try {
2164+ await expect(page.locator("body[data-tool-id='input-mapping-v2']")).toBeVisible();
2165+ await expect(page.locator(".input-mapping-v2__gamepad-capture-button[data-input-mapping-gamepad-index='0']")).toBeVisible();
2166+
2167+ await page.locator("#inputMappingV2ActionSelect").selectOption("cancel");
2168+ await page.locator("#inputMappingV2AddActionButton").click();
2169+ await page.locator(".input-mapping-v2__gesture-group", { hasText: "Keyboard" })
2170+ .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='KeyboardRelease']")
2171+ .click();
2172+ await page.locator("#inputMappingV2CaptureKeyboardButton").click();
2173+ await page.keyboard.down("KeyP");
2174+ await expect(page.locator("#inputMappingV2CaptureMessage")).toContainText("Keyboard Release recorded KeyP. Waiting for release.");
2175+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='KeyP:KeyboardRelease']")).toHaveCount(0);
2176+ await expect(page.locator("#inputMappingV2CaptureKeyboardButton")).toHaveClass(/is-capturing/);
2177+ await page.keyboard.up("KeyP");
2178+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='KeyP:KeyboardRelease']")).toHaveText("Keyboard, KeyP, Release");
2179+ await expect(page.locator("#inputMappingV2CaptureKeyboardButton")).not.toHaveClass(/is-capturing/);
2180+
2181+ await page.locator("#inputMappingV2ActionSelect").selectOption("confirm");
2182+ await page.locator("#inputMappingV2AddActionButton").click();
2183+ await page.locator(".input-mapping-v2__gesture-group", { hasText: "Keyboard" })
2184+ .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='KeyboardCombo']")
2185+ .click();
2186+ await page.locator("#inputMappingV2CaptureKeyboardButton").click();
2187+ await page.evaluate(() => {
2188+ window.dispatchEvent(new KeyboardEvent("keydown", {
2189+ bubbles: true,
2190+ cancelable: true,
2191+ code: "ControlLeft",
2192+ ctrlKey: true,
2193+ key: "Control"
2194+ }));
2195+ });
2196+ await expect(page.locator("#inputMappingV2CaptureMessage")).toContainText("Combo capture recorded Keyboard ControlLeft. Waiting for second input.");
2197+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='Combo:ControlLeft+KeyS']")).toHaveCount(0);
2198+ await expect(page.locator("#inputMappingV2CaptureKeyboardButton")).toHaveClass(/is-capturing/);
2199+ await page.evaluate(() => {
2200+ window.dispatchEvent(new KeyboardEvent("keydown", {
2201+ bubbles: true,
2202+ cancelable: true,
2203+ code: "KeyS",
2204+ ctrlKey: true,
2205+ key: "s"
2206+ }));
2207+ });
2208+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='Combo:ControlLeft+KeyS']")).toHaveText("Combo, Ctrl + S");
2209+ await expect(page.locator("#inputMappingV2CaptureKeyboardButton")).not.toHaveClass(/is-capturing/);
2210+
2211+ await page.locator("#inputMappingV2ActionSelect").selectOption("fire");
2212+ await page.locator("#inputMappingV2AddActionButton").click();
2213+ await page.locator(".input-mapping-v2__gesture-group", { hasText: "Mouse" })
2214+ .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='MouseDoubleClick']")
2215+ .click();
2216+ await page.locator("#inputMappingV2CaptureMouseButton").click();
2217+ await page.evaluate(() => {
2218+ window.dispatchEvent(new WheelEvent("wheel", {
2219+ bubbles: true,
2220+ cancelable: true,
2221+ deltaY: -120
2222+ }));
2223+ });
2224+ await expect(page.locator("#statusLog")).toHaveValue(/WARN Mouse Double Click capture expects mouse button input; wheel input ignored\./);
2225+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='MouseButton0:MouseDoubleClick']")).toHaveCount(0);
2226+ await page.evaluate(() => {
2227+ window.dispatchEvent(new MouseEvent("mousedown", {
2228+ bubbles: true,
2229+ button: 0,
2230+ cancelable: true
2231+ }));
2232+ });
2233+ await expect(page.locator("#inputMappingV2CaptureMessage")).toContainText("Mouse Double Click recorded first click on Left Button. Waiting for second click.");
2234+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='MouseButton0:MouseDoubleClick']")).toHaveCount(0);
2235+ await page.evaluate(() => {
2236+ window.dispatchEvent(new MouseEvent("mousedown", {
2237+ bubbles: true,
2238+ button: 0,
2239+ cancelable: true
2240+ }));
2241+ });
2242+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='MouseButton0:MouseDoubleClick']")).toHaveText("Mouse, Left Button, Double Click");
2243+ await expect(page.locator("#inputMappingV2CaptureMouseButton")).not.toHaveClass(/is-capturing/);
2244+
2245+ await page.locator("#inputMappingV2ActionSelect").selectOption("jump");
2246+ await page.locator("#inputMappingV2AddActionButton").click();
2247+ await page.locator(".input-mapping-v2__gesture-group", { hasText: "Mouse" })
2248+ .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='MouseWheelUp']")
2249+ .click();
2250+ await page.locator("#inputMappingV2CaptureMouseButton").click();
2251+ await page.evaluate(() => {
2252+ window.dispatchEvent(new MouseEvent("mousedown", {
2253+ bubbles: true,
2254+ button: 0,
2255+ cancelable: true
2256+ }));
2257+ });
2258+ await expect(page.locator("#statusLog")).toHaveValue(/WARN Mouse Wheel Up capture expects wheel input; mouse click ignored\./);
2259+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='MouseWheelUp']")).toHaveCount(0);
2260+ await page.evaluate(() => {
2261+ window.dispatchEvent(new WheelEvent("wheel", {
2262+ bubbles: true,
2263+ cancelable: true,
2264+ deltaY: 120
2265+ }));
2266+ });
2267+ await expect(page.locator("#statusLog")).toHaveValue(/WARN Mouse Wheel Up capture expects Wheel Up; Wheel Down was ignored\./);
2268+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='MouseWheelUp']")).toHaveCount(0);
2269+ await page.evaluate(() => {
2270+ window.dispatchEvent(new WheelEvent("wheel", {
2271+ bubbles: true,
2272+ cancelable: true,
2273+ deltaY: -120
2274+ }));
2275+ });
2276+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='MouseWheelUp']")).toHaveText("Mouse, Wheel Up, Wheel");
2277+ await expect(page.locator("#inputMappingV2CaptureMouseButton")).not.toHaveClass(/is-capturing/);
2278+
2279+ await page.locator("#inputMappingV2ActionSelect").selectOption("menu");
2280+ await page.locator("#inputMappingV2AddActionButton").click();
2281+ await page.locator(".input-mapping-v2__gesture-group", { hasText: "Game Controller" })
2282+ .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='GameControllerTrigger']")
2283+ .click();
2284+ await page.evaluate(() => {
2285+ window.__inputMappingV2MockGamepads[0] = {
2286+ ...window.__inputMappingV2MockGamepads[0],
2287+ buttons: window.__inputMappingV2MockGamepads[0].buttons.map((button, index) => ({ pressed: index === 0 })),
2288+ timestamp: 71
2289+ };
2290+ });
2291+ await page.locator(".input-mapping-v2__gamepad-capture-button[data-input-mapping-gamepad-index='0']").click();
2292+ await expect(page.locator("#statusLog")).toHaveValue(/WARN Game Controller Trigger capture expects trigger input; A is button input\./, { timeout: 2500 });
2293+ await expect(page.locator(".input-mapping-v2__mapping-card[data-input-mapping-tile-action-id='menu'] .input-mapping-v2__input-token")).toHaveCount(0);
2294+ await page.evaluate(() => {
2295+ window.__inputMappingV2MockGamepads[0] = {
2296+ ...window.__inputMappingV2MockGamepads[0],
2297+ buttons: window.__inputMappingV2MockGamepads[0].buttons.map((button, index) => ({ pressed: index === 6 })),
2298+ timestamp: 72
2299+ };
2300+ });
2301+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='Pad0:Button6']")).toHaveText("Game Controller, LT, Trigger", { timeout: 2500 });
2302+ await expect(page.locator(".input-mapping-v2__gamepad-capture-button[data-input-mapping-gamepad-index='0']")).not.toHaveClass(/is-capturing/);
2303+
2304+ await page.evaluate(() => {
2305+ window.__inputMappingV2MockGamepads[0] = {
2306+ ...window.__inputMappingV2MockGamepads[0],
2307+ buttons: window.__inputMappingV2MockGamepads[0].buttons.map(() => ({ pressed: false })),
2308+ timestamp: 73
2309+ };
2310+ });
2311+ await page.locator("#inputMappingV2ActionSelect").selectOption("start");
2312+ await page.locator("#inputMappingV2AddActionButton").click();
2313+ await page.locator(".input-mapping-v2__gesture-group", { hasText: "Game Controller" })
2314+ .locator(".input-mapping-v2__gesture-button[data-input-mapping-gesture-binding='GameControllerButton']")
2315+ .click();
2316+ await page.evaluate(() => {
2317+ window.__inputMappingV2MockGamepads[0] = {
2318+ ...window.__inputMappingV2MockGamepads[0],
2319+ buttons: window.__inputMappingV2MockGamepads[0].buttons.map((button, index) => ({ pressed: index === 6 })),
2320+ timestamp: 74
2321+ };
2322+ });
2323+ await page.locator(".input-mapping-v2__gamepad-capture-button[data-input-mapping-gamepad-index='0']").click();
2324+ await expect(page.locator("#statusLog")).toHaveValue(/WARN Game Controller Button capture expects button input; LT is trigger input\./, { timeout: 2500 });
2325+ await expect(page.locator(".input-mapping-v2__mapping-card[data-input-mapping-tile-action-id='start'] .input-mapping-v2__input-token")).toHaveCount(0);
2326+ await page.evaluate(() => {
2327+ window.__inputMappingV2MockGamepads[0] = {
2328+ ...window.__inputMappingV2MockGamepads[0],
2329+ buttons: window.__inputMappingV2MockGamepads[0].buttons.map((button, index) => ({ pressed: index === 1 })),
2330+ timestamp: 75
2331+ };
2332+ });
2333+ await expect(page.locator(".input-mapping-v2__input-token[data-input-mapping-binding='Pad0:Button1']")).toHaveText("Game Controller, B, Button", { timeout: 2500 });
2334+ await expect(page.locator(".input-mapping-v2__gamepad-capture-button[data-input-mapping-gamepad-index='0']")).not.toHaveClass(/is-capturing/);
2335+ expect(pageErrors).toEqual([]);
2336+ } finally {
2337+ await workspaceV2CoverageReporter.stop(page);
2338+ await server.close();
2339+ }
2340+ });
2341+
21332342 test("launches Input Mapping V2 and captures keyboard mappings", async ({ page }) => {
21342343 await page.setViewportSize({ width: 1920, height: 900 });
21352344 const server = await openInputMappingV2(page);
0 commit comments