From 46202ee2e398ffd49febe6684f5c6a8648da600d Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 1 Nov 2026 17:58:03 +0100 Subject: v4.0.1: introduce Command/View and port roulette to the shared boundary --- tests/client_core.rs | 130 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 4 deletions(-) (limited to 'tests') diff --git a/tests/client_core.rs b/tests/client_core.rs index b0bba6f..695dcb1 100644 --- a/tests/client_core.rs +++ b/tests/client_core.rs @@ -10,7 +10,10 @@ use zagan_bet::client::core::{ClientCore, Effect, InteractiveCommand, Key}; use zagan_bet::client::games::response::{ parse_blackjack_response, parse_crash_response, parse_spin_response, }; +use zagan_bet::client::games::roulette::{Cursor, Direction}; use zagan_bet::client::ui::command_names; +use zagan_bet::client::view::{Command, RoulettePhase, View}; +use zagan_bet::roulette::BetTarget; use zagan_bet::crash::CrashState; const WIN3: &str = "CCCCLLCLODGSDGS"; @@ -36,7 +39,8 @@ impl Screen { for effect in effects { match effect { Effect::Print(lines) => self.lines.extend(lines.iter().cloned()), - Effect::ReplaceRegion(lines) => { + Effect::Render(view) => { + let lines = zagan_bet::client::tui::render_view(view); if let Some(start) = self.region_start { self.lines.truncate(start); } else { @@ -52,14 +56,15 @@ impl Screen { self.region_start = None; self.region_len = 0; } - Effect::AnimateRoulette { final_lines, .. } => { + Effect::AnimateRoulette { view, .. } => { + let lines = zagan_bet::client::tui::render_view(view); if let Some(start) = self.region_start { self.lines.truncate(start); } else { self.region_start = Some(self.lines.len()); } - self.lines.extend(final_lines.iter().cloned()); - self.region_len = final_lines.len(); + self.lines.extend(lines.iter().cloned()); + self.region_len = lines.len(); } Effect::SetPrompt { text, cursor_col } => { self.prompt = text.clone(); @@ -1198,3 +1203,120 @@ fn history_rejects_a_bad_count_without_sending() { .iter() .any(|line| line.contains("Usage: history [1-50]"))); } + +#[test] +fn the_roulette_view_reflects_the_cursor_and_bets() { + let mut h = Harness::new(); + h.enter_roulette(); + + let View::Roulette(view) = h.core.view() else { + panic!("expected a roulette view"); + }; + assert_eq!(view.cursor, Cursor::Lattice { x: 1, y: 5 }); + assert_eq!( + view.cursor.target(), + Some(BetTarget::Straight { number: 1 }) + ); + assert!(view.bets.is_empty()); + assert_eq!(view.chip, 10); + assert_eq!(view.phase, RoulettePhase::Placing); + + h.core.command(Command::MoveCursor(Direction::Up)); + h.core.command(Command::MoveCursor(Direction::Up)); + let effects = h.core.command(Command::PlaceBet); + h.apply(effects); + + let View::Roulette(view) = h.core.view() else { + panic!("expected a roulette view"); + }; + assert_eq!( + view.cursor.target(), + Some(BetTarget::Straight { number: 2 }) + ); + assert_eq!(view.bets.total(), 10); + assert_eq!( + view.bets.amount_on(&BetTarget::Straight { number: 2 }), + 10 + ); +} + +#[test] +fn clicking_a_bet_target_does_not_move_the_cursor() { + let mut h = Harness::new(); + h.enter_roulette(); + + let effects = h.core.command(Command::PlaceBetAt(BetTarget::Split { a: 1, b: 4 })); + h.apply(effects); + + let View::Roulette(view) = h.core.view() else { + panic!("expected a roulette view"); + }; + assert_eq!(view.bets.amount_on(&BetTarget::Split { a: 1, b: 4 }), 10); + assert_eq!( + view.cursor.target(), + Some(BetTarget::Straight { number: 1 }), + "a click places a chip without dragging the keyboard cursor" + ); +} + +#[test] +fn the_roulette_view_reports_the_result() { + let mut h = Harness::new(); + h.enter_roulette(); + h.core.command(Command::PlaceBet); + + h.key(Key::Char(' ')); + let bets = r#"{"chips":[{"target":{"bet":"straight","number":1},"amount":10}]}"#; + h.reply(&format!("ROULETTE_RESULT s1 360 10 1350 1 red {bets}")); + + let View::Roulette(view) = h.core.view() else { + panic!("expected a roulette view"); + }; + assert_eq!(view.phase, RoulettePhase::Result); + assert_eq!(view.number, Some(1)); + assert_eq!(view.color.as_deref(), Some("red")); + assert_eq!(view.winnings, Some(360)); + assert_eq!(view.balance, Some(1350)); + assert!(view.error.is_none()); +} + +#[test] +fn the_chip_size_is_a_command() { + let mut h = Harness::new(); + h.enter_roulette(); + + h.core.command(Command::AdjustChip(10)); + let View::Roulette(view) = h.core.view() else { + panic!("expected a roulette view"); + }; + assert_eq!(view.chip, 20); + + h.core.command(Command::SetChip(5)); + let View::Roulette(view) = h.core.view() else { + panic!("expected a roulette view"); + }; + assert_eq!(view.chip, 10, "the chip never goes below 10"); +} + +#[test] +fn unported_games_still_reach_the_frontend_as_text() { + let mut h = Harness::new(); + h.enter_slots("10"); + + match h.core.view() { + View::Text(lines) => assert_eq!(lines.len(), 10), + other => panic!("expected the text bridge, got {other:?}"), + } +} + +#[test] +fn leaving_through_a_command_clears_the_screen() { + let mut h = Harness::new(); + h.enter_roulette(); + + let effects = h.core.command(Command::Leave); + h.apply(effects); + + assert!(!h.core.is_in_game()); + assert!(h.screen.lines.iter().any(|line| line == "Game exited.")); +} -- cgit v1.2.3