//! Tests for the characters a world holds: the stage-taking line, who the //! record puts on stage, and the character's own log. use super::*; use crate::campaign::{Campaign, GameTime}; use std::fs; use tempfile::TempDir; /// A fresh campaign in a temp directory. fn campaign() -> (Campaign, TempDir) { let dir = TempDir::new().unwrap(); let opened = Campaign::open(dir.path()).unwrap(); (opened, dir) } fn time(text: &str) -> GameTime { GameTime::parse(text).unwrap() } /// Writes a sheet for `slug` in the world at `world`, so the character /// counts as one. fn write_sheet(world: &Path, slug: &str, contents: &str) { let sheet = sheet_path(world, slug); fs::create_dir_all(sheet.parent().unwrap()).unwrap(); fs::write(sheet, contents).unwrap(); } // --- The stage-taking line ------------------------------------------------ #[test] fn taking_the_stage_writes_the_event_line_to_the_campaign_log() { let (campaign, dir) = campaign(); campaign .take_stage("sister-maren", time("#d1-0000")) .unwrap(); let text = fs::read_to_string(dir.path().join("campaign-log/0001.md")).unwrap(); assert_eq!( text, "#d1-0000 - [[characters/sister-maren]] takes the stage.\n" ); } #[test] fn taking_the_stage_reads_back_as_the_character_on_stage() { let (campaign, _dir) = campaign(); campaign .take_stage("sister-maren", time("#d1-0000")) .unwrap(); assert_eq!( campaign.on_stage().unwrap().as_deref(), Some("sister-maren") ); } #[test] fn taking_the_stage_leaves_the_transcript_alone() { let (campaign, _dir) = campaign(); campaign .take_stage("sister-maren", time("#d1-0000")) .unwrap(); assert!(campaign.transcript_entries().unwrap().is_empty()); } #[test] fn taking_the_stage_twice_at_the_same_moment_is_allowed() { let (campaign, _dir) = campaign(); campaign .take_stage("sister-maren", time("#d1-0000")) .unwrap(); campaign .take_stage("brother-kel", time("#d1-0000")) .unwrap(); assert_eq!(campaign.on_stage().unwrap().as_deref(), Some("brother-kel")); } #[test] fn nobody_is_on_stage_when_the_log_holds_no_stage_event() { let (campaign, _dir) = campaign(); campaign .mark(time("#d1-0830"), "The party leaves the inn.", None) .unwrap(); assert_eq!(campaign.on_stage().unwrap(), None); } #[test] fn the_last_stage_event_across_day_files_wins() { let (campaign, _dir) = campaign(); campaign .take_stage("sister-maren", time("#d1-0000")) .unwrap(); campaign .mark(time("#d1-0830"), "The party leaves the inn.", None) .unwrap(); campaign .take_stage("brother-kel", time("#d2-0900")) .unwrap(); campaign .mark(time("#d3-1000"), "The road turns north.", None) .unwrap(); assert_eq!(campaign.on_stage().unwrap().as_deref(), Some("brother-kel")); } #[test] fn a_stage_event_in_an_older_day_still_answers() { let (campaign, _dir) = campaign(); campaign .take_stage("sister-maren", time("#d1-0000")) .unwrap(); campaign .mark(time("#d4-1000"), "The road turns north.", None) .unwrap(); assert_eq!( campaign.on_stage().unwrap().as_deref(), Some("sister-maren") ); } #[test] fn narration_that_only_looks_like_the_stage_event_names_nobody() { let (campaign, _dir) = campaign(); campaign .mark( time("#d1-0830"), "Everyone watches as [[characters/sister-maren]] takes the stage. The room quiets.", None, ) .unwrap(); assert_eq!(campaign.on_stage().unwrap(), None); } #[test] fn a_line_naming_no_slug_names_nobody() { let (campaign, _dir) = campaign(); campaign .mark(time("#d1-0830"), "[[characters/]] takes the stage.", None) .unwrap(); assert_eq!(campaign.on_stage().unwrap(), None); } #[test] fn a_line_whose_slug_holds_a_path_names_nobody() { let (campaign, _dir) = campaign(); campaign .mark( time("#d1-0830"), "[[characters/sister-maren/log]] takes the stage.", None, ) .unwrap(); assert_eq!(campaign.on_stage().unwrap(), None); } // --- The characters in a world ------------------------------------------- #[test] fn a_world_with_no_characters_directory_holds_no_characters() { let (campaign, _dir) = campaign(); assert!(campaign.characters().is_empty()); } #[test] fn every_directory_with_a_sheet_is_a_character_in_sorted_order() { let (campaign, dir) = campaign(); write_sheet( dir.path(), "sister-maren", "---\nhp: 22\n---\n\nA cleric.\n", ); write_sheet( dir.path(), "brother-kel", "---\nhp: 30\n---\n\nA fighter.\n", ); assert_eq!(campaign.characters(), vec!["brother-kel", "sister-maren"]); } #[test] fn a_directory_with_no_sheet_is_not_a_character() { let (campaign, dir) = campaign(); write_sheet( dir.path(), "sister-maren", "---\nhp: 22\n---\n\nA cleric.\n", ); fs::create_dir_all(dir.path().join("characters/half-built")).unwrap(); fs::write(dir.path().join("characters/loose.md"), "not a character").unwrap(); assert_eq!(campaign.characters(), vec!["sister-maren"]); } #[test] fn a_characters_sheet_is_the_entry_file_in_its_directory() { let (campaign, dir) = campaign(); assert_eq!( campaign.character_sheet("sister-maren"), dir.path().join("characters/sister-maren/entry.md") ); } // --- The character's own log --------------------------------------------- #[test] fn a_character_event_writes_a_line_to_the_characters_log() { let (campaign, dir) = campaign(); campaign .log_character_event( "sister-maren", time("#d1-0830"), "hp: 22 → 15, the ghoul's claws find her shoulder", ) .unwrap(); let text = fs::read_to_string(dir.path().join("characters/sister-maren/log/0001.md")).unwrap(); assert_eq!( text, "#d1-0830 - hp: 22 → 15, the ghoul's claws find her shoulder\n" ); } #[test] fn character_events_partition_by_day() { let (campaign, dir) = campaign(); campaign .log_character_event("sister-maren", time("#d1-0830"), "hp: 22 → 15") .unwrap(); campaign .log_character_event("sister-maren", time("#d1-2200"), "hp: 15 → 22, a long rest") .unwrap(); campaign .log_character_event("sister-maren", time("#d2-0900"), "level: 3 → 4") .unwrap(); let log = dir.path().join("characters/sister-maren/log"); assert_eq!( fs::read_to_string(log.join("0001.md")).unwrap(), "#d1-0830 - hp: 22 → 15\n#d1-2200 - hp: 15 → 22, a long rest\n" ); assert_eq!( fs::read_to_string(log.join("0002.md")).unwrap(), "#d2-0900 - level: 3 → 4\n" ); } #[test] fn a_character_event_leaves_the_campaign_log_alone() { let (campaign, _dir) = campaign(); campaign .log_character_event("sister-maren", time("#d1-0830"), "hp: 22 → 15") .unwrap(); assert!(campaign.log_entries().unwrap().is_empty()); } #[test] fn a_character_event_reports_a_log_it_cannot_write() { let (campaign, dir) = campaign(); fs::write(dir.path().join("characters"), "not a directory").unwrap(); let error = campaign .log_character_event("sister-maren", time("#d1-0830"), "hp: 22 → 15") .unwrap_err(); assert!(error.contains("cannot create")); }