Skip to content

Commit ebeb2f5

Browse files
committed
Add tests
1 parent 98d4d73 commit ebeb2f5

File tree

9 files changed

+1075
-0
lines changed

9 files changed

+1075
-0
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright (c) 2025 Alexander Farber
3+
* SPDX-License-Identifier: MIT
4+
*
5+
* This file is part of the pixi-questions project (https://github.com/afarber/pixi-questions)
6+
*/
7+
8+
import { describe, it, expect, vi, beforeEach } from 'vitest';
9+
import { Container } from 'pixi.js';
10+
11+
// Mock the container classes to avoid circular dependencies
12+
vi.mock('./Hand.js', () => ({
13+
Hand: class MockHand extends Container {
14+
constructor() {
15+
super();
16+
this._isHand = true;
17+
}
18+
}
19+
}));
20+
21+
vi.mock('./Table.js', () => ({
22+
Table: class MockTable extends Container {
23+
constructor() {
24+
super();
25+
this._isTable = true;
26+
}
27+
}
28+
}));
29+
30+
vi.mock('./Left.js', () => ({
31+
Left: class MockLeft extends Container {
32+
constructor() {
33+
super();
34+
this._isLeft = true;
35+
}
36+
}
37+
}));
38+
39+
vi.mock('./Right.js', () => ({
40+
Right: class MockRight extends Container {
41+
constructor() {
42+
super();
43+
this._isRight = true;
44+
}
45+
}
46+
}));
47+
48+
import { Card } from './Card.js';
49+
import { Hand } from './Hand.js';
50+
import { Table } from './Table.js';
51+
import { Left } from './Left.js';
52+
import { Right } from './Right.js';
53+
54+
// Create a mock sprite sheet
55+
const createMockSpriteSheet = () => ({
56+
textures: {
57+
'AS': { width: 188, height: 263 },
58+
'KH': { width: 188, height: 263 },
59+
'7D': { width: 188, height: 263 },
60+
'TD': { width: 188, height: 263 },
61+
'9C': { width: 188, height: 263 },
62+
'QS': { width: 188, height: 263 },
63+
'JH': { width: 188, height: 263 },
64+
'8S': { width: 188, height: 263 },
65+
}
66+
});
67+
68+
describe('Card', () => {
69+
let spriteSheet;
70+
71+
beforeEach(() => {
72+
spriteSheet = createMockSpriteSheet();
73+
});
74+
75+
describe('Static Methods', () => {
76+
describe('isValidCard', () => {
77+
it('returns true for valid keys like "AS", "7H", "TD"', () => {
78+
expect(Card.isValidCard('AS')).toBe(true);
79+
expect(Card.isValidCard('7H')).toBe(true);
80+
expect(Card.isValidCard('TD')).toBe(true);
81+
expect(Card.isValidCard('KC')).toBe(true);
82+
expect(Card.isValidCard('QD')).toBe(true);
83+
expect(Card.isValidCard('JS')).toBe(true);
84+
expect(Card.isValidCard('9H')).toBe(true);
85+
expect(Card.isValidCard('8C')).toBe(true);
86+
});
87+
88+
it('returns false for invalid keys like "1S", "AX", "ZZ"', () => {
89+
expect(Card.isValidCard('1S')).toBe(false);
90+
expect(Card.isValidCard('AX')).toBe(false);
91+
expect(Card.isValidCard('ZZ')).toBe(false);
92+
expect(Card.isValidCard('6H')).toBe(false);
93+
expect(Card.isValidCard('2D')).toBe(false);
94+
expect(Card.isValidCard('')).toBe(false);
95+
expect(Card.isValidCard('A')).toBe(false);
96+
});
97+
});
98+
99+
describe('compareCards', () => {
100+
it('sorts by suit order: Spades, Diamonds, Clubs, Hearts', () => {
101+
const cardS = new Card(spriteSheet, 'AS');
102+
const cardD = new Card(spriteSheet, 'TD');
103+
const cardC = new Card(spriteSheet, '9C');
104+
const cardH = new Card(spriteSheet, 'KH');
105+
106+
const cards = [cardH, cardC, cardD, cardS];
107+
cards.sort(Card.compareCards);
108+
109+
expect(cards[0].textureKey).toBe('AS');
110+
expect(cards[1].textureKey).toBe('TD');
111+
expect(cards[2].textureKey).toBe('9C');
112+
expect(cards[3].textureKey).toBe('KH');
113+
});
114+
115+
it('sorts by rank within same suit: A, K, Q, J, T, 9, 8, 7', () => {
116+
const cardA = new Card(spriteSheet, 'AS');
117+
const cardQ = new Card(spriteSheet, 'QS');
118+
const card8 = new Card(spriteSheet, '8S');
119+
120+
// Test Spades: A, Q, 8
121+
const spades = [card8, cardQ, cardA];
122+
spades.sort(Card.compareCards);
123+
expect(spades[0].textureKey).toBe('AS');
124+
expect(spades[1].textureKey).toBe('QS');
125+
expect(spades[2].textureKey).toBe('8S');
126+
});
127+
});
128+
});
129+
130+
describe('Instance Methods', () => {
131+
it('isParentHand() returns true when card is in Hand container', () => {
132+
const hand = new Hand();
133+
const card = new Card(spriteSheet, 'AS');
134+
hand.addChild(card);
135+
expect(card.isParentHand()).toBe(true);
136+
});
137+
138+
it('isParentHand() returns false when card is in Table container', () => {
139+
const table = new Table();
140+
const card = new Card(spriteSheet, 'AS');
141+
table.addChild(card);
142+
expect(card.isParentHand()).toBe(false);
143+
});
144+
145+
it('isParentTable() returns true when card is in Table container', () => {
146+
const table = new Table();
147+
const card = new Card(spriteSheet, 'AS');
148+
table.addChild(card);
149+
expect(card.isParentTable()).toBe(true);
150+
});
151+
152+
it('isParentLeft() returns true when card is in Left container', () => {
153+
const left = new Left();
154+
const card = new Card(spriteSheet, 'AS');
155+
left.addChild(card);
156+
expect(card.isParentLeft()).toBe(true);
157+
});
158+
159+
it('isParentRight() returns true when card is in Right container', () => {
160+
const right = new Right();
161+
const card = new Card(spriteSheet, 'AS');
162+
right.addChild(card);
163+
expect(card.isParentRight()).toBe(true);
164+
});
165+
});
166+
});
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright (c) 2025 Alexander Farber
3+
* SPDX-License-Identifier: MIT
4+
*
5+
* This file is part of the pixi-questions project (https://github.com/afarber/pixi-questions)
6+
*/
7+
8+
import { describe, it, expect, vi, beforeEach } from 'vitest';
9+
import { Container, Sprite } from 'pixi.js';
10+
11+
// Mock the circular dependencies
12+
vi.mock('./Hand.js', () => ({
13+
Hand: class MockHand extends Container {}
14+
}));
15+
16+
vi.mock('./Table.js', () => ({
17+
Table: class MockTable extends Container {}
18+
}));
19+
20+
vi.mock('./Left.js', () => ({
21+
Left: class MockLeft extends Container {}
22+
}));
23+
24+
vi.mock('./Right.js', () => ({
25+
Right: class MockRight extends Container {}
26+
}));
27+
28+
import { CardContainer } from './CardContainer.js';
29+
import { Card } from './Card.js';
30+
31+
// Concrete implementation for testing the abstract CardContainer
32+
class TestCardContainer extends CardContainer {
33+
constructor(screen) {
34+
super(screen);
35+
this._maxCards = 3;
36+
}
37+
38+
_repositionCards() {
39+
const cards = this.children.filter((child) => child instanceof Card);
40+
cards.forEach((card, index) => {
41+
card.x = index * 100;
42+
card.y = 100;
43+
card.baseX = card.x;
44+
card.baseY = card.y;
45+
card.angle = 0;
46+
});
47+
}
48+
}
49+
50+
// Create a mock sprite sheet
51+
const createMockSpriteSheet = () => ({
52+
textures: {
53+
'AS': { width: 188, height: 263 },
54+
'KH': { width: 188, height: 263 },
55+
'7D': { width: 188, height: 263 },
56+
'TD': { width: 188, height: 263 },
57+
}
58+
});
59+
60+
describe('CardContainer', () => {
61+
let spriteSheet;
62+
let screen;
63+
let container;
64+
65+
beforeEach(() => {
66+
spriteSheet = createMockSpriteSheet();
67+
screen = { width: 720, height: 720 };
68+
container = new TestCardContainer(screen);
69+
});
70+
71+
describe('_getCardCount', () => {
72+
it('returns 0 for empty container', () => {
73+
expect(container._getCardCount()).toBe(0);
74+
});
75+
76+
it('counts only Card children, not other sprites', () => {
77+
// Add a Card
78+
container.addCard(spriteSheet, 'AS', null, null, null);
79+
// Add a non-Card child (plain Sprite)
80+
const sprite = new Sprite();
81+
container.addChild(sprite);
82+
83+
expect(container._getCardCount()).toBe(1);
84+
expect(container.children.length).toBe(2);
85+
});
86+
});
87+
88+
describe('addCard', () => {
89+
it('returns true when container has space', () => {
90+
const result = container.addCard(spriteSheet, 'AS', null, null, null);
91+
expect(result).toBe(true);
92+
});
93+
94+
it('returns false when container is at max capacity', () => {
95+
container.addCard(spriteSheet, 'AS', null, null, null);
96+
container.addCard(spriteSheet, 'KH', null, null, null);
97+
container.addCard(spriteSheet, '7D', null, null, null);
98+
// Container is now at max capacity (3)
99+
const result = container.addCard(spriteSheet, 'TD', null, null, null);
100+
expect(result).toBe(false);
101+
});
102+
103+
it('creates card with correct textureKey', () => {
104+
container.addCard(spriteSheet, 'KH', null, null, null);
105+
const cards = container.children.filter((child) => child instanceof Card);
106+
expect(cards.length).toBe(1);
107+
expect(cards[0].textureKey).toBe('KH');
108+
});
109+
110+
it('with null startPos places card without animation', () => {
111+
container.addCard(spriteSheet, 'AS', null, null, null);
112+
const cards = container.children.filter((child) => child instanceof Card);
113+
const card = cards[0];
114+
// Card should be at position set by _repositionCards (0, 100 for first card)
115+
expect(card.x).toBe(0);
116+
expect(card.y).toBe(100);
117+
});
118+
119+
it('with startPos animates card to final position', () => {
120+
const startPos = { x: 500, y: 500 };
121+
container.addCard(spriteSheet, 'AS', startPos, 45, 0.5);
122+
const cards = container.children.filter((child) => child instanceof Card);
123+
const card = cards[0];
124+
// Card starts at startPos (before animation completes)
125+
expect(card.x).toBe(500);
126+
expect(card.y).toBe(500);
127+
expect(card.angle).toBe(45);
128+
expect(card.alpha).toBe(0.5);
129+
});
130+
});
131+
132+
describe('removeCard', () => {
133+
it('decreases card count by 1', () => {
134+
container.addCard(spriteSheet, 'AS', null, null, null);
135+
container.addCard(spriteSheet, 'KH', null, null, null);
136+
expect(container._getCardCount()).toBe(2);
137+
138+
const cards = container.children.filter((child) => child instanceof Card);
139+
container.removeCard(cards[0]);
140+
expect(container._getCardCount()).toBe(1);
141+
});
142+
});
143+
});

0 commit comments

Comments
 (0)