-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
280 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
{ | ||
"name": "hud-gamepad", | ||
"name": "@npm-packages-collection/npm-hud-gamepad", | ||
"description": "A Heads Up Display (HUD) for Gamepads, Keyboards, and more", | ||
"author": "Eugene Yevhen Andruszczenko <[email protected]>", | ||
"contributors": [ | ||
|
@@ -20,7 +20,7 @@ | |
"heads-up-display" | ||
], | ||
"publishConfig": { | ||
"registry": "https://registry.npmjs.org/" | ||
"registry": "https://npm.pkg.github.com/" | ||
}, | ||
"engines": { | ||
"node": "20.x", | ||
|
@@ -47,7 +47,7 @@ | |
"dev:npm": "nodemon --watch . --exec 'echo Watching files...' && npm run dev:example", | ||
"dev:pack": "npm pack && mv hud-gamepad-*.tgz hud-gamepad.tgz", | ||
"dev:example": "npm run example:setup", | ||
"test": "mocha" | ||
"test": "mocha --require ./test/setup.js" | ||
}, | ||
"postinstall": "npm install -g live-server && npm run dev", | ||
"devDependencies": { | ||
|
@@ -65,4 +65,4 @@ | |
"directories": { | ||
"test": "test" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,135 @@ | ||
import { expect } from 'chai'; | ||
import sinon from 'sinon'; | ||
import { JSDOM } from 'jsdom'; | ||
import { GamePad } from '../index.js'; | ||
import { GamePad } from '../src/index.js'; | ||
|
||
describe('GamePad', () => { | ||
let handler, callbackSpy, window; | ||
let mockContext, spies; | ||
|
||
// Setup the JSDOM environment and the callback spy before each test | ||
beforeEach(() => { | ||
const dom = new JSDOM(); | ||
window = dom.window; | ||
global.window = window; | ||
// Get the canvas and context | ||
const canvas = document.getElementById('gamepad'); | ||
mockContext = canvas.getContext('2d'); | ||
|
||
callbackSpy = sinon.spy(); | ||
}) | ||
// Create and store spies | ||
spies = { | ||
clearRect: sinon.replace(mockContext, 'clearRect', sinon.fake()), | ||
beginPath: sinon.replace(mockContext, 'beginPath', sinon.fake()), | ||
arc: sinon.replace(mockContext, 'arc', sinon.fake()), | ||
fill: sinon.replace(mockContext, 'fill', sinon.fake()), | ||
stroke: sinon.replace(mockContext, 'stroke', sinon.fake()), | ||
closePath: sinon.replace(mockContext, 'closePath', sinon.fake()), | ||
fillText: sinon.replace(mockContext, 'fillText', sinon.fake()), | ||
setTransform: sinon.replace(mockContext, 'setTransform', sinon.fake()) | ||
}; | ||
}); | ||
|
||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
|
||
it('should create a new GamePad instance', () => { | ||
const gamePad = new GamePad(); | ||
expect(gamePad).to.be.an.instanceof(GamePad); | ||
it.skip('should initialize GamePad with the given configuration', async () => { | ||
const config = { | ||
canvas: 'gamepad', | ||
joystick: true, | ||
buttons: [{ name: 'A', color: 'red', key: 'KeyA' }] | ||
}; | ||
|
||
await GamePad.setup(config); | ||
|
||
// Wait for any post-setup operations | ||
await new Promise(resolve => setTimeout(resolve, 0)); | ||
|
||
// Verify that at least some canvas operations occurred | ||
const anyCanvasOperation = Object.values(spies).some(spy => spy.called); | ||
expect(anyCanvasOperation, 'Expected at least one canvas operation').to.be.true; | ||
}); | ||
|
||
it.skip('should draw the gamepad on the canvas', async () => { | ||
await GamePad.setup({ | ||
canvas: 'gamepad', | ||
joystick: true | ||
}); | ||
|
||
// Wait for any post-setup operations | ||
await new Promise(resolve => setTimeout(resolve, 0)); | ||
|
||
GamePad.draw(); | ||
|
||
// Verify canvas operations occurred | ||
const drawOperations = [ | ||
spies.clearRect, | ||
spies.beginPath, | ||
spies.arc, | ||
spies.fill, | ||
spies.closePath | ||
]; | ||
|
||
const anyDrawOperation = drawOperations.some(spy => spy.called); | ||
expect(anyDrawOperation, 'Expected at least one draw operation').to.be.true; | ||
}); | ||
|
||
it('should handle events and return the current state of the gamepad', async () => { | ||
await GamePad.setup({ | ||
canvas: 'gamepad', | ||
joystick: true | ||
}); | ||
|
||
const state = GamePad.events({ | ||
left: true | ||
}); | ||
|
||
expect(state).to.be.an('object'); | ||
}); | ||
|
||
it('should observe and return the current state of the gamepad', async () => { | ||
await GamePad.setup({ | ||
canvas: 'gamepad', | ||
joystick: true | ||
}); | ||
|
||
const state = GamePad.observe(); | ||
expect(state).to.be.an('object'); | ||
}); | ||
|
||
it('should call observer function when state changes', async () => { | ||
const observer = sinon.spy(); | ||
|
||
await GamePad.setup({ | ||
canvas: 'gamepad', | ||
joystick: true, | ||
observerFunction: observer | ||
}); | ||
|
||
GamePad.events({ left: true }); | ||
expect(observer.called).to.be.true; | ||
}); | ||
|
||
it('should handle joystick movement', async () => { | ||
await GamePad.setup({ | ||
canvas: 'gamepad', | ||
joystick: true | ||
}); | ||
|
||
const touchEvent = new Event('touchstart'); | ||
touchEvent.touches = [{ | ||
identifier: 1, | ||
pageX: 100, | ||
pageY: 100 | ||
}]; | ||
|
||
GamePad.events(touchEvent); | ||
const state = GamePad.observe(); | ||
expect(state).to.have.any.keys('x-axis', 'y-axis', 'x-dir', 'y-dir'); | ||
}); | ||
|
||
it('should handle button presses', async () => { | ||
await GamePad.setup({ | ||
canvas: 'gamepad', | ||
buttons: [{ name: 'a', key: 'x' }] | ||
}); | ||
|
||
GamePad.events({ x: true }); | ||
const state = GamePad.observe(); | ||
expect(state).to.have.property('a'); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import { JSDOM } from 'jsdom'; | ||
|
||
// Create a more complete JSDOM environment | ||
const dom = new JSDOM(` | ||
<!DOCTYPE html> | ||
<html> | ||
<body> | ||
<canvas id="gamepad"></canvas> | ||
<div class="HudGamePadObserver"></div> | ||
</body> | ||
</html> | ||
`, { | ||
url: 'http://localhost', | ||
pretendToBeVisual: true, | ||
}); | ||
|
||
// Set up globals | ||
global.window = dom.window; | ||
global.document = dom.window.document; | ||
global.navigator = { | ||
userAgent: 'node.js', | ||
}; | ||
|
||
// Mock window properties | ||
global.window.innerWidth = 1024; | ||
global.window.innerHeight = 768; | ||
|
||
// Mock requestAnimationFrame | ||
global.requestAnimationFrame = (callback) => setTimeout(callback, 0); | ||
global.cancelAnimationFrame = (id) => clearTimeout(id); | ||
|
||
// Mock ResizeObserver | ||
global.ResizeObserver = class ResizeObserver { | ||
constructor(callback) { | ||
this.callback = callback; | ||
} | ||
observe() { this.callback([{ contentRect: { width: 1024, height: 768 } }]); } | ||
unobserve() {} | ||
disconnect() {} | ||
}; | ||
|
||
// Mock fetch | ||
global.fetch = async () => ({ | ||
ok: true, | ||
text: async () => '' | ||
}); | ||
|
||
// Mock DOMMatrix | ||
class DOMMatrix { | ||
constructor() { | ||
this.a = 1; this.b = 0; this.c = 0; this.d = 1; this.e = 0; this.f = 0; | ||
} | ||
} | ||
|
||
// Create mock context | ||
const createMockContext = () => { | ||
let currentPath = new Path2D(); | ||
|
||
return { | ||
canvas: { | ||
width: 1024, | ||
height: 768, | ||
style: {}, | ||
getBoundingClientRect: () => ({ | ||
left: 0, | ||
top: 0, | ||
width: 1024, | ||
height: 768 | ||
}) | ||
}, | ||
_transform: new DOMMatrix(), | ||
_stack: [], | ||
fillStyle: '', | ||
strokeStyle: '', | ||
lineWidth: 1, | ||
font: '', | ||
textAlign: 'left', | ||
textBaseline: 'top', | ||
|
||
clearRect: function() { return true; }, | ||
fillRect: function() { return true; }, | ||
beginPath: function() { currentPath = new Path2D(); return true; }, | ||
closePath: function() { return true; }, | ||
arc: function() { return true; }, | ||
fill: function() { return true; }, | ||
stroke: function() { return true; }, | ||
scale: function(x, y) { | ||
this._transform.a *= x; | ||
this._transform.d *= y; | ||
return true; | ||
}, | ||
roundRect: function() { return this; }, | ||
fillText: function() { return true; }, | ||
moveTo: function() { return true; }, | ||
lineTo: function() { return true; }, | ||
getImageData: () => ({ data: new Uint8ClampedArray(4) }), | ||
putImageData: function() { return true; }, | ||
drawImage: function() { return true; }, | ||
|
||
save: function() { | ||
this._stack.push({ ...this._transform }); | ||
return true; | ||
}, | ||
|
||
restore: function() { | ||
if (this._stack.length) { | ||
this._transform = this._stack.pop(); | ||
} | ||
return true; | ||
}, | ||
|
||
getTransform: function() { | ||
return this._transform; | ||
}, | ||
|
||
setTransform: function(a, b, c, d, e, f) { | ||
if (arguments.length === 1) { | ||
Object.assign(this._transform, a); | ||
} else { | ||
Object.assign(this._transform, { a, b, c, d, e, f }); | ||
} | ||
return true; | ||
} | ||
}; | ||
}; | ||
|
||
// Mock HTMLCanvasElement | ||
global.window.HTMLCanvasElement.prototype.getContext = function() { | ||
return createMockContext(); | ||
}; | ||
|
||
// Mock DOMMatrix globally | ||
global.DOMMatrix = DOMMatrix; | ||
|
||
// Mock Path2D | ||
global.Path2D = class Path2D {}; | ||
|
||
// Mock CanvasRenderingContext2D | ||
global.CanvasRenderingContext2D = class CanvasRenderingContext2D { | ||
constructor() { | ||
return createMockContext(); | ||
} | ||
}; | ||
|
||
// Event constructor polyfill | ||
if (!global.Event) { | ||
global.Event = class Event { | ||
constructor(type) { | ||
this.type = type; | ||
this.pageX = 0; | ||
this.pageY = 0; | ||
} | ||
}; | ||
} |