diff --git a/packages/canvas-polyfill/__test__/gamepad.test.mjs b/packages/canvas-polyfill/__test__/gamepad.test.mjs new file mode 100644 index 000000000..b9d39870d --- /dev/null +++ b/packages/canvas-polyfill/__test__/gamepad.test.mjs @@ -0,0 +1,58 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { fileURLToPath } from 'node:url'; +import { build } from 'esbuild'; + +// Like the native backends, the fake reports already-connected pads synchronously when monitoring starts. +const fakeGamepad = ` +const pad = { index: 0, id: 'Fake (STANDARD GAMEPAD)', connected: true }; +let started = false; +export const gamepads = { + addListener(listener) { + if (!started) { + started = true; + listener({ type: 'gamepadconnected', gamepad: pad }); + } + }, +}; +export function getGamepads() { + return started ? [pad, null, null, null] : [null, null, null, null]; +} +`; + +async function loadPolyfill() { + const entry = fileURLToPath(new URL('../gamepad.ts', import.meta.url)); + const { outputFiles } = await build({ + entryPoints: [entry], + bundle: true, + format: 'esm', + write: false, + platform: 'neutral', + plugins: [ + { + name: 'fake-canvas-gamepad', + setup(b) { + b.onResolve({ filter: /^@nativescript\/canvas-gamepad$/ }, () => ({ path: 'fake', namespace: 'fake' })); + b.onLoad({ filter: /.*/, namespace: 'fake' }, () => ({ contents: fakeGamepad, loader: 'js' })); + }, + }, + ], + }); + return import('data:text/javascript;base64,' + Buffer.from(outputFiles[0].text).toString('base64') + '#' + Math.random()); +} + +test('first gamepadconnected listener sees pads that were already connected', async () => { + const target = new EventTarget(); + globalThis.addEventListener = target.addEventListener.bind(target); + globalThis.removeEventListener = target.removeEventListener.bind(target); + globalThis.dispatchEvent = target.dispatchEvent.bind(target); + globalThis.window = globalThis; + + const { hookGamepadListeners } = await loadPolyfill(); + hookGamepadListeners(); + + const seen = []; + window.addEventListener('gamepadconnected', (e) => seen.push(e.gamepad.id)); + + assert.deepEqual(seen, ['Fake (STANDARD GAMEPAD)']); +}); diff --git a/packages/canvas-polyfill/gamepad.ts b/packages/canvas-polyfill/gamepad.ts index f23a38a98..ee86e4a3a 100644 --- a/packages/canvas-polyfill/gamepad.ts +++ b/packages/canvas-polyfill/gamepad.ts @@ -32,10 +32,12 @@ export function hookGamepadListeners() { continue; } const hooked = function (this: unknown, type: string, ...rest: unknown[]) { + const result = add.call(this, type, ...rest); + // Register first: starting the backend reports already-connected pads synchronously. if (type === 'gamepadconnected' || type === 'gamepaddisconnected') { gamepads(); } - return add.call(this, type, ...rest); + return result; }; hooked.__gamepadHook = true; host.addEventListener = hooked; diff --git a/packages/canvas-polyfill/package.json b/packages/canvas-polyfill/package.json index dfc6c6ad0..725cd242f 100644 --- a/packages/canvas-polyfill/package.json +++ b/packages/canvas-polyfill/package.json @@ -1,6 +1,6 @@ { "name": "@nativescript/canvas-polyfill", - "version": "3.0.0-alpha.18", + "version": "3.0.0-alpha.19", "description": "Polyfill for making NativeScript compatible with web libs like pixi.js, three.js, phaser.js, babylon.js, etc....", "main": "index", "typings": "global.d.ts",