#![cfg(all(feature = "serde", feature = "rand"))]
use contract_bridge::auction::{Auction, Call, IllegalCall, RelativeVulnerability};
use contract_bridge::deck::Deck;
use contract_bridge::{
Bid, Card, Contract, FullDeal, Hand, Holding, Level, Penalty, Rank, Seat, SeatFlags, Strain,
Suit,
};
const fn bid_call(level: u8, strain: Strain) -> Call {
Call::Bid(Bid::new(level, strain))
}
fn roundtrip<T>(value: &T) -> serde_json::Result<()>
where
T: serde::Serialize + serde::de::DeserializeOwned + core::fmt::Debug + PartialEq,
{
let json = serde_json::to_string(value)?;
let parsed: T = serde_json::from_str(&json)?;
assert_eq!(&parsed, value, "round-trip mismatch for {json}");
Ok(())
}
#[test]
fn strain_json_is_variant_name() -> serde_json::Result<()> {
assert_eq!(serde_json::to_string(&Strain::Spades)?, "\"Spades\"");
assert_eq!(serde_json::to_string(&Strain::Notrump)?, "\"Notrump\"");
for &s in &Strain::ASC {
roundtrip(&s)?;
}
Ok(())
}
#[test]
fn suit_json_roundtrip() -> serde_json::Result<()> {
for &s in &Suit::ASC {
roundtrip(&s)?;
}
Ok(())
}
#[test]
fn seat_json_roundtrip() -> serde_json::Result<()> {
for &s in &Seat::ALL {
roundtrip(&s)?;
}
Ok(())
}
#[test]
fn rank_json_is_number() -> serde_json::Result<()> {
assert_eq!(serde_json::to_string(&Rank::A)?, "14");
for n in 2..=14 {
roundtrip(&Rank::new(n))?;
}
Ok(())
}
#[test]
fn level_json_is_number() -> serde_json::Result<()> {
assert_eq!(serde_json::to_string(&Level::new(4))?, "4");
for n in 1..=7 {
roundtrip(&Level::new(n))?;
}
Ok(())
}
#[test]
fn penalty_json_roundtrip() -> serde_json::Result<()> {
for p in [Penalty::Undoubled, Penalty::Doubled, Penalty::Redoubled] {
roundtrip(&p)?;
}
Ok(())
}
#[test]
fn card_json_is_string() -> serde_json::Result<()> {
let c = Card {
suit: Suit::Spades,
rank: Rank::A,
};
assert_eq!(serde_json::to_string(&c)?, "\"♠A\"");
roundtrip(&c)
}
#[test]
fn bid_json_is_string() -> serde_json::Result<()> {
let b = Bid::new(3, Strain::Notrump);
assert_eq!(serde_json::to_string(&b)?, "\"3NT\"");
roundtrip(&b)
}
#[test]
fn contract_json_is_string() -> serde_json::Result<()> {
let c = Contract::new(4, Strain::Hearts, Penalty::Doubled);
assert_eq!(serde_json::to_string(&c)?, "\"4♥x\"");
roundtrip(&c)
}
#[test]
fn holding_json_is_string() -> anyhow::Result<()> {
let h: Holding = "AKQJT".parse()?;
assert_eq!(serde_json::to_string(&h)?, "\"AKQJT\"");
roundtrip(&h)?;
Ok(())
}
#[test]
fn hand_json_is_string() -> anyhow::Result<()> {
let h: Hand = "AKQJ.T98.765.432".parse()?;
assert_eq!(serde_json::to_string(&h)?, "\"AKQJ.T98.765.432\"");
roundtrip(&h)?;
Ok(())
}
#[test]
fn full_deal_json_is_pbn_string() -> anyhow::Result<()> {
let d: FullDeal =
"N:AKQJT98765432... .AKQJT98765432.. ..AKQJT98765432. ...AKQJT98765432".parse()?;
let json = serde_json::to_string(&d)?;
assert!(json.starts_with("\"N:"), "unexpected: {json}");
roundtrip(&d)?;
Ok(())
}
#[test]
fn seat_flags_json_roundtrip() -> serde_json::Result<()> {
roundtrip(&SeatFlags::NS)?;
roundtrip(&SeatFlags::EW)?;
roundtrip(&SeatFlags::ALL)?;
roundtrip(&(SeatFlags::NORTH | SeatFlags::SOUTH))
}
#[test]
fn call_json_is_string() -> Result<(), serde_json::Error> {
assert_eq!(serde_json::to_string(&Call::Pass)?, "\"P\"");
assert_eq!(serde_json::to_string(&Call::Double)?, "\"X\"");
assert_eq!(serde_json::to_string(&Call::Redouble)?, "\"XX\"");
assert_eq!(
serde_json::to_string(&bid_call(3, Strain::Notrump))?,
"\"3NT\"",
);
for call in [
Call::Pass,
Call::Double,
Call::Redouble,
bid_call(1, Strain::Spades),
bid_call(7, Strain::Clubs),
] {
roundtrip(&call)?;
}
Ok(())
}
#[test]
fn auction_json_is_string() -> anyhow::Result<()> {
let mut auction = Auction::new();
for call in [
Call::Pass,
bid_call(1, Strain::Spades),
bid_call(2, Strain::Hearts),
Call::Double,
Call::Pass,
Call::Pass,
Call::Pass,
] {
auction.try_push(call)?;
}
assert_eq!(serde_json::to_string(&auction)?, "\"P 1♠ 2♥ X P P P\"");
roundtrip(&auction)?;
roundtrip(&Auction::new())?;
Ok(())
}
#[test]
fn relative_vulnerability_json_roundtrip() -> Result<(), serde_json::Error> {
for v in [
RelativeVulnerability::NONE,
RelativeVulnerability::WE,
RelativeVulnerability::THEY,
RelativeVulnerability::ALL,
] {
roundtrip(&v)?;
}
Ok(())
}
#[test]
fn deck_json_is_string() -> anyhow::Result<()> {
roundtrip(&Deck::ALL)?;
roundtrip(&Deck::EMPTY)?;
let deck: Deck = "AKQJ.T98.765.432".parse()?;
assert_eq!(serde_json::to_string(&deck)?, "\"AKQJ.T98.765.432\"");
roundtrip(&deck)?;
Ok(())
}
#[test]
fn illegal_call_roundtrip() -> Result<(), serde_json::Error> {
roundtrip(&IllegalCall::AfterFinalPass)?;
Ok(())
}