From cc3aaced1bd18cbbb3cad30d1c15f1b8dfd09e1c Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Wed, 11 Mar 2026 08:29:09 +0000 Subject: [PATCH] feat(04-04): wire up restart button click handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added restart button click event listener in Game constructor - Click handler calls restart() method to reset game - Null check for restart button element (defensive programming) - Tests verify click handler registration and restart() invocation - Tests verify graceful handling of missing button element 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/__tests__/Game.test.ts | 70 ++++++++++++++++++++++++++++++++++++++ src/game/Game.ts | 8 +++++ 2 files changed, 78 insertions(+) diff --git a/src/__tests__/Game.test.ts b/src/__tests__/Game.test.ts index 20170aa..4a0c5b1 100644 --- a/src/__tests__/Game.test.ts +++ b/src/__tests__/Game.test.ts @@ -522,4 +522,74 @@ describe('Game', () => { expect(mockScoreDisplay.textContent).toBe('Score: 0'); }); }); + + describe('restart button click handler', () => { + let mockRestartButton: any; + let mockOverlay: any; + let mockScoreDisplay: any; + let mockPreviousScoreDisplay: any; + + beforeEach(() => { + mockRestartButton = { + addEventListener: vi.fn(), + }; + mockOverlay = { style: { display: '' }, textContent: '' }; + mockScoreDisplay = { style: {}, textContent: '' }; + mockPreviousScoreDisplay = { style: { display: 'none' }, textContent: '' }; + + vi.stubGlobal('document', { + getElementById: vi.fn((id: string) => { + if (id === 'game') return mockCanvas; + if (id === 'score-display') return mockScoreDisplay; + if (id === 'previous-score-display') return mockPreviousScoreDisplay; + if (id === 'game-over-overlay') return mockOverlay; + if (id === 'restart-button') return mockRestartButton; + return null; + }), + }); + + game = new Game(); + }); + + it('should register click handler on restart button in constructor', () => { + // Verify addEventListener was called for restart button + expect(mockRestartButton.addEventListener).toHaveBeenCalledWith('click', expect.any(Function)); + }); + + it('should call restart() when restart button is clicked', () => { + // Get the click handler that was registered + const clickHandler = mockRestartButton.addEventListener.mock.calls.find( + (call: any[]) => call[0] === 'click' + )[1]; + + // Mock the restart method + const restartSpy = vi.spyOn(game, 'restart'); + + // Simulate button click + clickHandler(); + + // Verify restart was called + expect(restartSpy).toHaveBeenCalledTimes(1); + }); + + it('should find restart button element by ID', () => { + // Verify document.getElementById was called for restart-button + expect(document.getElementById).toHaveBeenCalledWith('restart-button'); + }); + + it('should handle null restart button gracefully', () => { + // Test with null restart button + vi.stubGlobal('document', { + getElementById: vi.fn((id: string) => { + if (id === 'game') return mockCanvas; + return null; // All other elements return null + }), + }); + + // Should not throw error + expect(() => { + game = new Game(); + }).not.toThrow(); + }); + }); }); diff --git a/src/game/Game.ts b/src/game/Game.ts index 65fc43d..aaef3c3 100644 --- a/src/game/Game.ts +++ b/src/game/Game.ts @@ -125,6 +125,14 @@ export class Game { this.events.on('tile:cleared', () => { this.checkWinCondition(); }); + + // Setup restart button handler + const restartButton = document.getElementById('restart-button'); + if (restartButton) { + restartButton.addEventListener('click', () => { + this.restart(); + }); + } } /**