diff --git a/package.json b/package.json index 5cf5586..f860ff0 100644 --- a/package.json +++ b/package.json @@ -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 ", "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" } -} +} \ No newline at end of file diff --git a/test/index.js b/test/index.js index 425d436..fd104de 100644 --- a/test/index.js +++ b/test/index.js @@ -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'); }); -}); +}); \ No newline at end of file diff --git a/test/setup.js b/test/setup.js new file mode 100644 index 0000000..7bf6e8d --- /dev/null +++ b/test/setup.js @@ -0,0 +1,154 @@ +import { JSDOM } from 'jsdom'; + +// Create a more complete JSDOM environment +const dom = new JSDOM(` + + + + +
+ + +`, { + 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; + } + }; +} \ No newline at end of file