Applique .gitattributes sur tous les fichiers existants. Élimine les différences fantômes entre WSL et Windows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
164 lines
6.5 KiB
TypeScript
Executable File
164 lines
6.5 KiB
TypeScript
Executable File
import { describe, it, expect } from 'vitest';
|
||
import { simulateStock } from '@domain/services/StockSimulator';
|
||
|
||
describe('simulateStock', () => {
|
||
it('stock vide → aucun croisement', () => {
|
||
const r = simulateStock({});
|
||
expect(r.crossings).toHaveLength(0);
|
||
expect(r.unusedStock).toHaveLength(0);
|
||
});
|
||
|
||
it('stock à zéro → aucun croisement', () => {
|
||
const r = simulateStock({ Rousse: { m: 0, f: 0 } });
|
||
expect(r.crossings).toHaveLength(0);
|
||
});
|
||
|
||
it('♂Rousse + ♀Dorée → Dorée et Rousse ×1 (config1)', () => {
|
||
const r = simulateStock({ Rousse: { m: 1, f: 0 }, Dorée: { m: 0, f: 1 } });
|
||
expect(r.crossings).toHaveLength(1);
|
||
expect(r.crossings[0]!.baby).toBe('Dorée et Rousse');
|
||
expect(r.crossings[0]!.count).toBe(1);
|
||
expect(r.crossings[0]!.pAMale).toBe(1); // ♂Rousse
|
||
expect(r.crossings[0]!.pAFemale).toBe(0);
|
||
expect(r.crossings[0]!.pBFemale).toBe(1); // ♀Dorée
|
||
expect(r.crossings[0]!.pBMale).toBe(0);
|
||
});
|
||
|
||
it('♀Rousse + ♂Dorée → Dorée et Rousse ×1 (config2)', () => {
|
||
const r = simulateStock({ Rousse: { m: 0, f: 1 }, Dorée: { m: 1, f: 0 } });
|
||
expect(r.crossings).toHaveLength(1);
|
||
expect(r.crossings[0]!.baby).toBe('Dorée et Rousse');
|
||
expect(r.crossings[0]!.count).toBe(1);
|
||
expect(r.crossings[0]!.pAMale).toBe(0);
|
||
expect(r.crossings[0]!.pAFemale).toBe(1); // ♀Rousse config2
|
||
});
|
||
|
||
it('♂♂Rousse + ♀♀Dorée (même sexe d\'un côté) → 2 bébés config1 seulement', () => {
|
||
const r = simulateStock({ Rousse: { m: 2, f: 0 }, Dorée: { m: 0, f: 2 } });
|
||
expect(r.crossings).toHaveLength(1);
|
||
expect(r.crossings[0]!.count).toBe(2);
|
||
expect(r.crossings[0]!.pAMale).toBe(2); // 2× ♂Rousse × ♀Dorée
|
||
expect(r.crossings[0]!.pAFemale).toBe(0); // pas de config2
|
||
});
|
||
|
||
it('les deux configs simultanées : 2♂/2♀ × 2♂/2♀ → 4 bébés', () => {
|
||
const r = simulateStock({ Rousse: { m: 2, f: 2 }, Dorée: { m: 2, f: 2 } });
|
||
const c = r.crossings.find(x => x.baby === 'Dorée et Rousse');
|
||
expect(c).toBeDefined();
|
||
expect(c!.count).toBe(4);
|
||
expect(c!.pAMale).toBe(2); // c1 = min(♂R=2, ♀D=2)
|
||
expect(c!.pAFemale).toBe(2); // c2 = min(♀R=2, ♂D=2)
|
||
});
|
||
|
||
it('distribution proportionnelle : 4♂/4♀ × 3 races → 4 bébés par croisement Gen2', () => {
|
||
const r = simulateStock({
|
||
Rousse: { m: 4, f: 4 },
|
||
Dorée: { m: 4, f: 4 },
|
||
Amande: { m: 4, f: 4 },
|
||
});
|
||
const gen2 = r.crossings.filter(c => c.gen === 2);
|
||
expect(gen2).toHaveLength(3);
|
||
for (const c of gen2) {
|
||
expect(c.count).toBe(4);
|
||
}
|
||
});
|
||
|
||
it('cascade complète Gen2→Gen3→Gen4→Gen5 avec 4♂/4♀ × 3 races', () => {
|
||
const r = simulateStock({
|
||
Rousse: { m: 4, f: 4 },
|
||
Dorée: { m: 4, f: 4 },
|
||
Amande: { m: 4, f: 4 },
|
||
});
|
||
|
||
// Gen2 : AmR×4, DorR×4, AmD×4 = 12
|
||
const gen2 = r.crossings.filter(c => c.gen === 2);
|
||
expect(gen2.reduce((s, c) => s + c.count, 0)).toBe(12);
|
||
|
||
// Gen3 : Ebène×2, Indigo×2 = 4
|
||
const gen3 = r.crossings.filter(c => c.gen === 3);
|
||
expect(gen3).toHaveLength(2);
|
||
expect(gen3.find(c => c.baby === 'Ebène')!.count).toBe(2);
|
||
expect(gen3.find(c => c.baby === 'Indigo')!.count).toBe(2);
|
||
|
||
// Gen4 : Ebène et Indigo×2
|
||
const gen4 = r.crossings.filter(c => c.gen === 4);
|
||
expect(gen4).toHaveLength(1);
|
||
expect(gen4[0]!.baby).toBe('Ebène et Indigo');
|
||
expect(gen4[0]!.count).toBe(2);
|
||
|
||
// Gen5 : Orchidée×2 (Pourpre ignoré car EI allocation = 0)
|
||
const gen5 = r.crossings.filter(c => c.gen === 5);
|
||
expect(gen5).toHaveLength(1);
|
||
expect(gen5[0]!.baby).toBe('Orchidée');
|
||
expect(gen5[0]!.count).toBe(2);
|
||
|
||
// Total : 12 + 4 + 2 + 2 = 20
|
||
const total = r.crossings.reduce((s, c) => s + c.count, 0);
|
||
expect(total).toBe(20);
|
||
});
|
||
|
||
it('allocation proportionnelle donne la priorité au second croisement quand le premier reçoit 0', () => {
|
||
// EI participe à Pourpre (1er) et Orchidée (2nd) en Gen5
|
||
// Avec 1♂/1♀ EI, alloc = floor(1/2) = 0 pour Pourpre → 0 bébés
|
||
// Orchidée : EI n'a plus qu'1 croisement restant → alloc = 1 → 2 bébés
|
||
const r = simulateStock({
|
||
'Ebène et Indigo': { m: 1, f: 1 },
|
||
'Amande et Rousse': { m: 1, f: 1 },
|
||
'Dorée et Rousse': { m: 1, f: 1 },
|
||
});
|
||
expect(r.crossings.find(c => c.baby === 'Pourpre')).toBeUndefined();
|
||
expect(r.crossings.find(c => c.baby === 'Orchidée')?.count).toBe(2);
|
||
});
|
||
|
||
it('race sans partenaire → stock inutilisé', () => {
|
||
const r = simulateStock({ Rousse: { m: 5, f: 5 } });
|
||
expect(r.crossings).toHaveLength(0);
|
||
expect(r.unusedStock).toHaveLength(1);
|
||
expect(r.unusedStock[0]!.race).toBe('Rousse');
|
||
expect(r.unusedStock[0]!.m).toBe(5);
|
||
expect(r.unusedStock[0]!.f).toBe(5);
|
||
});
|
||
|
||
it('stock restant après simulation correctement identifié', () => {
|
||
// 1♂ Rousse + 1♀ Dorée → 1 DorR. Rousse ♀ = 3 restantes
|
||
const r = simulateStock({ Rousse: { m: 1, f: 3 }, Dorée: { m: 0, f: 1 } });
|
||
expect(r.crossings).toHaveLength(1);
|
||
expect(r.crossings[0]!.count).toBe(1);
|
||
const leftR = r.unusedStock.find(u => u.race === 'Rousse');
|
||
expect(leftR).toBeDefined();
|
||
expect(leftR!.f).toBe(3);
|
||
});
|
||
|
||
it('bébés répartis ♂/♀ pour les générations suivantes', () => {
|
||
// 1♂/1♀ Rousse + 1♂/1♀ Dorée → 2 DorR (1♂, 1♀)
|
||
// 1♂/1♀ Amande + 1♂/1♀ Rousse_new? Non, Amande n'a pas de stock
|
||
const r = simulateStock({ Rousse: { m: 1, f: 1 }, Dorée: { m: 1, f: 1 } });
|
||
expect(r.crossings).toHaveLength(1);
|
||
expect(r.crossings[0]!.count).toBe(2);
|
||
// Bébés DorR dans unused : 1♂ + 1♀
|
||
const baby = r.unusedStock.find(u => u.race === 'Dorée et Rousse');
|
||
expect(baby).toBeDefined();
|
||
expect(baby!.m).toBe(1);
|
||
expect(baby!.f).toBe(1);
|
||
});
|
||
|
||
it('ne modifie pas l\'inventaire d\'entrée (immutabilité)', () => {
|
||
const inv = { Rousse: { m: 2, f: 2 }, Dorée: { m: 2, f: 2 } };
|
||
simulateStock(inv);
|
||
expect(inv.Rousse.m).toBe(2);
|
||
expect(inv.Rousse.f).toBe(2);
|
||
expect(inv.Dorée.m).toBe(2);
|
||
expect(inv.Dorée.f).toBe(2);
|
||
});
|
||
|
||
it('stock asymétrique : 3♂/1♀ Rousse + 1♂/3♀ Dorée', () => {
|
||
const r = simulateStock({ Rousse: { m: 3, f: 1 }, Dorée: { m: 1, f: 3 } });
|
||
const c = r.crossings.find(x => x.baby === 'Dorée et Rousse');
|
||
expect(c).toBeDefined();
|
||
// c1 = min(♂R=3, ♀D=3) = 3, c2 = min(♀R=1, ♂D=1) = 1 → bred = 4
|
||
expect(c!.count).toBe(4);
|
||
expect(c!.pAMale).toBe(3);
|
||
expect(c!.pAFemale).toBe(1);
|
||
});
|
||
});
|