///
/**
* Test Pattern: Battleship Multiplayer Lobby
*
* Tests the lobby data model and actions:
* - Initial state (defaults)
* - Player 1 joining
* - Player 2 joining
* - Game state transition to "playing" when both players join
* - Reset functionality
*
* Run: deno task ct test packages/patterns/battleship/multiplayer/lobby.test.tsx --verbose
*/
import { action, computed, pattern, Writable } from "commontools";
import BattleshipLobby from "./lobby.tsx";
import {
createInitialShots,
type GameState,
INITIAL_GAME_STATE,
type PlayerData,
type ShotsState,
} from "./schemas.tsx";
export default pattern(() => {
// Create Writable cells with initial values for the lobby state
const player1Cell = Writable.of(null);
const player2Cell = Writable.of(null);
const shotsCell = Writable.of(createInitialShots());
const gameStateCell = Writable.of(INITIAL_GAME_STATE);
// Instantiate the lobby pattern with properly initialized cells
const lobby = BattleshipLobby({
gameName: "Battleship",
player1: player1Cell,
player2: player2Cell,
shots: shotsCell,
gameState: gameStateCell,
});
// ==========================================================================
// Actions using exported Stream handlers
// ==========================================================================
// Player 1 joins with name "Alice"
const action_join_player1 = action(() => {
lobby.joinPlayer1.send({ name: "Alice" });
});
// Player 2 joins with name "Bob"
const action_join_player2 = action(() => {
lobby.joinPlayer2.send({ name: "Bob" });
});
// Reset the lobby
const action_reset = action(() => {
lobby.reset.send();
});
// Try to join with empty name (should be ignored)
const action_join_empty_name = action(() => {
lobby.joinPlayer1.send({ name: "" });
});
// ==========================================================================
// Initial State Assertions
// ==========================================================================
// Game name defaults to "Battleship"
const assert_initial_game_name = computed(
() => lobby.gameName === "Battleship",
);
// Both players are initially null
const assert_initial_player1_null = computed(() => lobby.player1 === null);
const assert_initial_player2_null = computed(() => lobby.player2 === null);
// Game state phase is "waiting"
const assert_initial_phase_waiting = computed(
() => lobby.gameState.phase === "waiting",
);
// Game state currentTurn is 1 initially
const assert_initial_current_turn = computed(
() => lobby.gameState.currentTurn === 1,
);
// Game state winner is null initially
const assert_initial_winner_null = computed(
() => lobby.gameState.winner === null,
);
// ==========================================================================
// After Player 1 Joins Assertions
// ==========================================================================
// Player 1 has joined with correct name
const assert_player1_name = computed(() => lobby.player1?.name === "Alice");
// Player 1 has ships assigned
const assert_player1_has_ships = computed(
() =>
lobby.player1 !== null &&
Array.isArray(lobby.player1.ships) &&
lobby.player1.ships.length === 5,
);
// Player 1 has a color
const assert_player1_has_color = computed(
() =>
lobby.player1 !== null &&
typeof lobby.player1.color === "string" &&
lobby.player1.color.length > 0,
);
// Player 2 is still null
const assert_player2_still_null = computed(() => lobby.player2 === null);
// Game state is still waiting (only 1 player joined)
const assert_still_waiting_one_player = computed(
() => lobby.gameState.phase === "waiting",
);
// ==========================================================================
// After Player 2 Joins Assertions
// ==========================================================================
// Player 2 has joined with correct name
const assert_player2_name = computed(() => lobby.player2?.name === "Bob");
// Player 2 has ships assigned
const assert_player2_has_ships = computed(
() =>
lobby.player2 !== null &&
Array.isArray(lobby.player2.ships) &&
lobby.player2.ships.length === 5,
);
// Game state phase changes to "playing"
const assert_phase_playing = computed(
() => lobby.gameState.phase === "playing",
);
// Game state currentTurn is set to 1 when game starts
const assert_current_turn_player1 = computed(
() => lobby.gameState.currentTurn === 1,
);
// ==========================================================================
// After Reset Assertions
// ==========================================================================
// Both players are back to null
const assert_reset_player1_null = computed(() => lobby.player1 === null);
const assert_reset_player2_null = computed(() => lobby.player2 === null);
// Game state returns to "waiting"
const assert_reset_phase_waiting = computed(
() => lobby.gameState.phase === "waiting",
);
// Winner is null after reset
const assert_reset_winner_null = computed(
() => lobby.gameState.winner === null,
);
// ==========================================================================
// Edge Case: Empty name should be ignored
// ==========================================================================
const assert_empty_name_ignored = computed(() => lobby.player1 === null);
// ==========================================================================
// Test Sequence
// ==========================================================================
return {
tests: [
// === Initial State Tests ===
{ assertion: assert_initial_game_name },
{ assertion: assert_initial_player1_null },
{ assertion: assert_initial_player2_null },
{ assertion: assert_initial_phase_waiting },
{ assertion: assert_initial_current_turn },
{ assertion: assert_initial_winner_null },
// === Edge case: Empty name ignored ===
{ action: action_join_empty_name },
{ assertion: assert_empty_name_ignored },
// === Player 1 Joins ===
{ action: action_join_player1 },
{ assertion: assert_player1_name },
{ assertion: assert_player1_has_ships },
{ assertion: assert_player1_has_color },
{ assertion: assert_player2_still_null },
{ assertion: assert_still_waiting_one_player },
// === Player 2 Joins ===
{ action: action_join_player2 },
{ assertion: assert_player2_name },
{ assertion: assert_player2_has_ships },
{ assertion: assert_phase_playing },
{ assertion: assert_current_turn_player1 },
// === Reset ===
{ action: action_reset },
{ assertion: assert_reset_player1_null },
{ assertion: assert_reset_player2_null },
{ assertion: assert_reset_phase_waiting },
{ assertion: assert_reset_winner_null },
],
lobby,
};
});