test: add combat happy-path e2e test; fix SpireCombatPage infinite render loop
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m22s

This commit is contained in:
2026-06-02 13:54:52 +02:00
parent f6f6ef4379
commit e71ba312fe
4 changed files with 341 additions and 7 deletions
@@ -127,19 +127,26 @@ export function SpireCombatPage() {
return base + floorBonus + randomVariation;
}, [currentFloor, seededRandom]);
// Track the last floor+totalRooms combo we generated a room for.
// Prevents infinite re-render loop: without this guard, the effect
// fires → setCurrentRoom → store update → re-render → tick advances
// currentFloor → effect fires → ... (loop).
// Generate initial room when floor or room count changes.
// Uses a ref guard to prevent infinite re-render loops.
// Generate room on mount and when floor changes.
// Uses a ref guard to prevent infinite re-render loops.
const lastGeneratedRef = useRef<string | null>(null);
useEffect(() => {
const key = `${currentFloor}:${totalRooms}`;
if (lastGeneratedRef.current === key) return; // already generated
if (lastGeneratedRef.current === key) return;
lastGeneratedRef.current = key;
setRoomsCleared(0);
const newRoom = generateSpireFloorState(currentFloor, 0, totalRooms);
setCurrentRoom(newRoom);
}, [currentFloor, totalRooms, setCurrentRoom]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentFloor, totalRooms]);
// Reset the generation guard when the component mounts
// (e.g. when re-entering spire mode after exit)
useEffect(() => {
lastGeneratedRef.current = null;
}, []);
const _handleRoomCleared = () => {
const nextRoomIndex = roomsCleared + 1;
@@ -1,6 +1,7 @@
'use client';
import { useCombatStore, usePrestigeStore, fmt } from '@/lib/game/stores';
import { useShallow } from 'zustand/react/shallow';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
@@ -32,7 +33,7 @@ export function SpireHeader({
isDescending,
}: SpireHeaderProps) {
const maxFloorReached = useCombatStore((s) => s.maxFloorReached);
const { insight } = usePrestigeStore((s) => ({ insight: s.insight }));
const insight = usePrestigeStore((s) => s.insight);
const guardian = getGuardianForFloor(currentFloor);
const isGuardian = isGuardianFloor(currentFloor);