diff --git a/src/__tests__/types.test.ts b/src/__tests__/types.test.ts new file mode 100644 index 0000000..f7afae6 --- /dev/null +++ b/src/__tests__/types.test.ts @@ -0,0 +1,128 @@ +// Tests for src/types/index.ts +// Test 1: TilePosition type has row and col as numbers +// Test 2: Tile interface has id, type, position, cleared +// Test 3: GameEvents type defines event names and payloads + +import { describe, it, expect } from 'vitest'; +import type { TilePosition, Tile, GameEvents } from '../types'; + +describe('Type Definitions', () => { + describe('TilePosition', () => { + it('should accept object with row and col as numbers', () => { + const position: TilePosition = { row: 5, col: 10 }; + expect(position.row).toBe(5); + expect(position.col).toBe(10); + }); + + it('should allow row and col to be 0', () => { + const position: TilePosition = { row: 0, col: 0 }; + expect(position.row).toBe(0); + expect(position.col).toBe(0); + }); + }); + + describe('Tile', () => { + it('should have id as string', () => { + const tile: Tile = { + id: 'tile-0-0', + type: 0, + position: { row: 0, col: 0 }, + cleared: false, + }; + expect(typeof tile.id).toBe('string'); + }); + + it('should have type as number (0-15 for emoji index)', () => { + const tile: Tile = { + id: 'tile-5-5', + type: 7, + position: { row: 5, col: 5 }, + cleared: false, + }; + expect(typeof tile.type).toBe('number'); + expect(tile.type).toBeGreaterThanOrEqual(0); + expect(tile.type).toBeLessThanOrEqual(15); + }); + + it('should have position as TilePosition', () => { + const tile: Tile = { + id: 'tile-1-2', + type: 3, + position: { row: 1, col: 2 }, + cleared: false, + }; + expect(tile.position).toHaveProperty('row'); + expect(tile.position).toHaveProperty('col'); + }); + + it('should have cleared as boolean', () => { + const tile: Tile = { + id: 'tile-2-3', + type: 5, + position: { row: 2, col: 3 }, + cleared: true, + }; + expect(typeof tile.cleared).toBe('boolean'); + expect(tile.cleared).toBe(true); + }); + }); + + describe('GameEvents', () => { + it('should define game:start event with void payload', () => { + // Type check - if this compiles, the type is correct + type StartPayload = GameEvents['game:start']; + const payload: StartPayload = undefined; + expect(payload).toBeUndefined(); + }); + + it('should define game:tick event with deltaTime', () => { + type TickPayload = GameEvents['game:tick']; + const payload: TickPayload = { deltaTime: 16.67 }; + expect(payload.deltaTime).toBe(16.67); + }); + + it('should define tile:selected event with tile, row, col', () => { + type SelectedPayload = GameEvents['tile:selected']; + const tile: Tile = { + id: 'test', + type: 0, + position: { row: 0, col: 0 }, + cleared: false, + }; + const payload: SelectedPayload = { tile, row: 0, col: 0 }; + expect(payload.tile).toBe(tile); + expect(payload.row).toBe(0); + expect(payload.col).toBe(0); + }); + + it('should define tile:cleared event with tile', () => { + type ClearedPayload = GameEvents['tile:cleared']; + const tile: Tile = { + id: 'test', + type: 1, + position: { row: 1, col: 1 }, + cleared: true, + }; + const payload: ClearedPayload = { tile }; + expect(payload.tile).toBe(tile); + }); + + it('should define game:score event with points', () => { + type ScorePayload = GameEvents['game:score']; + const payload: ScorePayload = { points: 100 }; + expect(payload.points).toBe(100); + }); + + it('should define game:over event with won boolean', () => { + type OverPayload = GameEvents['game:over']; + const payload: OverPayload = { won: true }; + expect(payload.won).toBe(true); + }); + + it('should define error event with Error', () => { + type ErrorPayload = GameEvents['error']; + const payload: ErrorPayload = new Error('Test error'); + expect(payload).toBeInstanceOf(Error); + }); + }); +}); diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..ae40ed4 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,34 @@ +// src/types/index.ts - Shared type definitions +// This file contains all TypeScript interfaces and types used throughout the game. + +/** + * Represents a position in the grid + */ +export interface TilePosition { + row: number; + col: number; +} + +/** + * Represents a single tile in the game grid + */ +export interface Tile { + id: string; + type: number; // 0-15 for emoji index + position: TilePosition; + cleared: boolean; +} + +/** + * Maps event names to their payload types + * Used for type-safe event emission and handling + */ +export interface GameEvents { + 'game:start': void; + 'game:tick': { deltaTime: number }; + 'tile:selected': { tile: Tile; row: number; col: number }; + 'tile:cleared': { tile: Tile }; + 'game:score': { points: number }; + 'game:over': { won: boolean }; + 'error': Error; +}