import { describe, it, expect } from 'vitest'; import * as fc from 'fast-check'; import { Block } from '../src/entities/Block.js'; import { GRID_GAP } from '../src/constants.js'; describe('Block', () => { describe('constructor', () => { it('initializes with correct properties', () => { const block = new Block(3, 2, 5, 40); expect(block.gridX).toBe(3); expect(block.gridY).toBe(2); expect(block.count).toBe(5); expect(block.size).toBe(40); expect(block.destroyed).toBe(false); }); }); describe('hit()', () => { it('decrements count by 1', () => { const block = new Block(0, 0, 5, 40); block.hit(); expect(block.count).toBe(4); }); it('returns false when count > 0 after hit', () => { const block = new Block(0, 0, 3, 40); expect(block.hit()).toBe(false); }); it('marks destroyed and returns true when count reaches 0', () => { const block = new Block(0, 0, 1, 40); expect(block.hit()).toBe(true); expect(block.destroyed).toBe(true); expect(block.count).toBe(0); }); it('marks destroyed when count goes below 0', () => { const block = new Block(0, 0, 0, 40); expect(block.hit()).toBe(true); expect(block.destroyed).toBe(true); expect(block.count).toBe(-1); }); }); describe('getRect()', () => { it('converts grid coordinates to pixel coordinates', () => { const size = 40; const block = new Block(2, 3, 5, size); const rect = block.getRect(); expect(rect.x).toBe(2 * (size + GRID_GAP) + GRID_GAP); expect(rect.y).toBe(3 * (size + GRID_GAP) + GRID_GAP); expect(rect.width).toBe(size); expect(rect.height).toBe(size); }); it('returns correct rect for origin block (0,0)', () => { const size = 50; const block = new Block(0, 0, 1, size); const rect = block.getRect(); expect(rect.x).toBe(GRID_GAP); expect(rect.y).toBe(GRID_GAP); expect(rect.width).toBe(size); expect(rect.height).toBe(size); }); }); describe('getColor()', () => { it('returns a valid hex color string', () => { const block = new Block(0, 0, 10, 40); const color = block.getColor(); expect(color).toMatch(/^#[0-9a-f]{6}$/); }); it('returns different colors for different counts', () => { const block1 = new Block(0, 0, 1, 40); const block2 = new Block(0, 0, 50, 40); expect(block1.getColor()).not.toBe(block2.getColor()); }); }); describe('isDestroyed()', () => { it('returns false when count > 0', () => { const block = new Block(0, 0, 5, 40); expect(block.isDestroyed()).toBe(false); }); it('returns true when count is 0', () => { const block = new Block(0, 0, 0, 40); expect(block.isDestroyed()).toBe(true); }); it('returns true when count is negative', () => { const block = new Block(0, 0, -1, 40); expect(block.isDestroyed()).toBe(true); }); }); describe('moveDown()', () => { it('increments gridY by 1', () => { const block = new Block(3, 2, 5, 40); block.moveDown(); expect(block.gridY).toBe(3); }); it('can be called multiple times', () => { const block = new Block(0, 0, 5, 40); block.moveDown(); block.moveDown(); block.moveDown(); expect(block.gridY).toBe(3); }); }); // Feature: ball-block-breaker, Property 7: 碰撞方块数字减1 // **Validates: Requirements 4.1** describe('Property 7: 碰撞方块数字减1', () => { it('hit() decrements count by exactly 1 for any block with count > 0', () => { fc.assert( fc.property( fc.integer({ min: 1, max: 10000 }), fc.integer({ min: 0, max: 6 }), fc.integer({ min: 0, max: 20 }), fc.constantFrom(30, 40, 50), (count, gridX, gridY, size) => { const block = new Block(gridX, gridY, count, size); const originalCount = block.count; block.hit(); expect(block.count).toBe(originalCount - 1); } ), { numRuns: 100 } ); }); }); });