move tests

This commit is contained in:
2026-06-07 00:33:16 -05:00
parent ea79dc20f9
commit 5ea7e4ec4e
4 changed files with 229 additions and 248 deletions
+11
View File
@@ -150,3 +150,14 @@ impl TryFrom<&str> for Archetype {
}
}
}
#[cfg(test)]
mod tests {
use super::Archetype;
#[test]
fn directional_crate_default_glyph_tiles() {
assert_eq!(Archetype::HCrate.default_glyph().tile, 29);
assert_eq!(Archetype::VCrate.default_glyph().tile, 18);
}
}
+175
View File
@@ -309,3 +309,178 @@ impl Board {
id
}
}
#[cfg(test)]
pub(crate) mod tests {
use std::collections::{BTreeMap, HashMap};
use color::Rgba8;
use crate::archetype::Archetype;
use crate::glyph::Glyph;
use crate::object_def::ObjectDef;
use crate::script::Direction;
use crate::utils::{ObjectId, Player, Solid};
use super::Board;
/// Builds an all-empty `w×h` board with the given player position, objects,
/// and `(name, source)` scripts. Assigns sequential ids (1..=n) to objects.
pub(crate) fn open_board(
w: usize,
h: usize,
player: (i32, i32),
objects: Vec<ObjectDef>,
scripts: &[(&str, &str)],
) -> Board {
let mut object_map: BTreeMap<ObjectId, ObjectDef> = BTreeMap::new();
let mut next_object_id: ObjectId = 1;
for o in objects {
object_map.insert(next_object_id, o);
next_object_id += 1;
}
Board {
name: "test".into(),
width: w,
height: h,
cells: vec![(Archetype::Empty.default_glyph(), Archetype::Empty); w * h],
floor: vec![Archetype::Empty.default_glyph(); w * h],
floor_spec: None,
player: Player { x: player.0, y: player.1 },
objects: object_map,
next_object_id,
portals: Vec::new(),
font: None,
zoom: 1,
scripts: scripts.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect::<HashMap<_,_>>(),
board_script_name: None,
load_errors: Vec::new(),
}
}
/// Stamps a crate cell onto a board.
pub(crate) fn crate_at(board: &mut Board, x: usize, y: usize) {
*board.get_mut(x, y) = (Archetype::Crate.default_glyph(), Archetype::Crate);
}
/// Stamps a wall cell onto a board.
pub(crate) fn wall_at(board: &mut Board, x: usize, y: usize) {
*board.get_mut(x, y) = (Archetype::Wall.default_glyph(), Archetype::Wall);
}
/// Stamps an arbitrary archetype cell onto a board.
pub(crate) fn stamp(board: &mut Board, x: usize, y: usize, arch: Archetype) {
*board.get_mut(x, y) = (arch.default_glyph(), arch);
}
#[test]
fn solid_at_reports_wall_object_and_empty() {
let mut board = open_board(4, 1, (3, 0), vec![], &[]);
board.cells[1] = (Archetype::Wall.default_glyph(), Archetype::Wall);
board.add_object(ObjectDef::new(2, 0));
assert!(board.solid_at(0, 0).is_none());
assert!(board.is_passable(0, 0));
match board.solid_at(1, 0) {
Some(Solid::Cell(Archetype::Wall)) => {}
other => panic!("expected Solid::Cell(Wall), got {:?}", other.is_some()),
}
assert!(!board.is_passable(1, 0));
match board.solid_at(2, 0) {
Some(Solid::Object(id)) => assert_eq!((board.objects[&id].x, board.objects[&id].y), (2, 0)),
_ => panic!("expected Solid::Object"),
}
assert!(!board.is_passable(2, 0));
}
#[test]
fn non_solid_object_does_not_block() {
let mut obj = ObjectDef::new(1, 0);
obj.solid = false;
let board = open_board(3, 1, (0, 0), vec![obj], &[]);
assert!(board.solid_at(1, 0).is_none());
assert!(board.is_passable(1, 0));
}
#[test]
fn solid_at_reports_player() {
let board = open_board(3, 1, (1, 0), vec![], &[]);
match board.solid_at(1, 0) {
Some(Solid::Player) => {}
_ => panic!("expected Solid::Player at the player's cell"),
}
assert!(!board.is_passable(1, 0));
}
#[test]
fn in_bounds_checks_grid_boundaries() {
let board = open_board(3, 2, (0, 0), vec![], &[]);
assert!(board.in_bounds((0, 0)));
assert!(board.in_bounds((2, 1)));
assert!(!board.in_bounds((-1, 0)));
assert!(!board.in_bounds((0, -1)));
assert!(!board.in_bounds((3, 0)));
assert!(!board.in_bounds((0, 2)));
}
#[test]
fn can_push_is_read_only_and_correct() {
let mut board = open_board(3, 1, (0, 0), vec![], &[]);
crate_at(&mut board, 1, 0);
assert!(board.can_push(1, 0, Direction::East));
assert_eq!(board.get(1, 0).1, Archetype::Crate); // no mutation
assert_eq!(board.get(2, 0).1, Archetype::Empty);
let mut board = open_board(3, 1, (0, 0), vec![], &[]);
crate_at(&mut board, 1, 0);
wall_at(&mut board, 2, 0);
assert!(!board.can_push(1, 0, Direction::East));
}
#[test]
fn push_into_player_pushes_player() {
// Crate shoved east into the player slides the player along into open space.
let mut board = open_board(4, 1, (2, 0), vec![], &[]);
crate_at(&mut board, 1, 0);
assert!(board.can_push(1, 0, Direction::East));
board.push(1, 0, Direction::East);
assert_eq!(board.get(1, 0).1, Archetype::Empty);
assert_eq!(board.get(2, 0).1, Archetype::Crate);
assert_eq!((board.player.x, board.player.y), (3, 0));
}
#[test]
fn push_into_player_blocked_by_wall() {
// Player backed against a wall: push has nowhere to go, nothing moves.
let mut board = open_board(4, 1, (2, 0), vec![], &[]);
crate_at(&mut board, 1, 0);
wall_at(&mut board, 3, 0);
assert!(!board.can_push(1, 0, Direction::East));
board.push(1, 0, Direction::East); // no-op
assert_eq!(board.get(1, 0).1, Archetype::Crate);
assert_eq!((board.player.x, board.player.y), (2, 0));
}
#[test]
fn glyph_at_uses_floor_for_empty_and_grid_for_solid() {
// Player parked at (2,0) so it doesn't overlap either asserted cell.
let mut board = open_board(3, 1, (2, 0), vec![], &[]);
let floor_glyph = Glyph {
tile: '.' as u32,
fg: Rgba8 { r: 10, g: 20, b: 30, a: 255 },
bg: Rgba8 { r: 1, g: 2, b: 3, a: 255 },
};
board.floor = vec![floor_glyph; 3];
wall_at(&mut board, 0, 0);
assert_eq!(board.glyph_at(0, 0), Archetype::Wall.default_glyph());
assert_eq!(board.glyph_at(1, 0), floor_glyph);
}
#[test]
fn fresh_board_is_valid_and_reports_errors() {
let mut board = open_board(1, 1, (0, 0), vec![], &[]);
assert!(board.is_valid());
board.report_error("something went wrong");
assert!(!board.is_valid());
assert_eq!(board.load_errors.len(), 1);
}
}
+21 -248
View File
@@ -189,14 +189,13 @@ fn step_object(board: &mut Board, id: ObjectId, dir: Direction) -> Option<Object
#[cfg(test)]
mod tests {
use color::Rgba8;
use std::collections::BTreeMap;
use std::time::Duration;
use crate::archetype::Archetype;
use crate::board::Board;
use crate::glyph::Glyph;
use crate::board::tests::{crate_at, open_board, stamp, wall_at};
use crate::object_def::ObjectDef;
use crate::utils::{ObjectId, Player, Pushable, Solid};
use crate::utils::Player;
use super::*;
@@ -234,49 +233,6 @@ mod tests {
o
}
/// Builds an all-empty (passable) `w×h` board with the given player position,
/// objects, and `(name, source)` scripts — room for movement, unlike the 1×1
/// `board_with_object`.
fn open_board(
w: usize,
h: usize,
player: (i32, i32),
objects: Vec<ObjectDef>,
scripts: &[(&str, &str)],
) -> Board {
// Assign sequential ids (1..=n) just like the map loader, so tests that
// index by id can rely on first-object == id 1.
let mut object_map: BTreeMap<ObjectId, ObjectDef> = BTreeMap::new();
let mut next_object_id: ObjectId = 1;
for o in objects {
object_map.insert(next_object_id, o);
next_object_id += 1;
}
Board {
name: "test".into(),
width: w,
height: h,
cells: vec![(Archetype::Empty.default_glyph(), Archetype::Empty); w * h],
floor: vec![Archetype::Empty.default_glyph(); w * h],
floor_spec: None,
player: Player {
x: player.0,
y: player.1,
},
objects: object_map,
next_object_id,
portals: Vec::new(),
font: None,
zoom: 1,
scripts: scripts
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect(),
board_script_name: None,
load_errors: Vec::new(),
}
}
/// Flattens each log line into a single string for easy assertions.
fn log_texts(game: &GameState) -> Vec<String> {
game.log
@@ -286,81 +242,26 @@ mod tests {
}
#[test]
fn solid_at_reports_wall_object_and_empty() {
// A 4×1 board: empty floor, a wall, an object, and the player parked at (3,0)
// (kept off the asserted cells, since the player is itself solid).
let mut board = open_board(4, 1, (3, 0), vec![], &[]);
board.cells[1] = (Archetype::Wall.default_glyph(), Archetype::Wall);
// A solid object on the otherwise-empty cell (2, 0).
board.add_object(ObjectDef::new(2, 0)); // solid by default
// Empty floor: nothing solid.
assert!(board.solid_at(0, 0).is_none());
assert!(board.is_passable(0, 0));
// Wall cell: the grid archetype is the solid.
match board.solid_at(1, 0) {
Some(Solid::Cell(Archetype::Wall)) => {}
other => panic!("expected Solid::Cell(Wall), got {:?}", other.is_some()),
}
assert!(!board.is_passable(1, 0));
// Object cell: the solid object shadows the empty floor under it.
match board.solid_at(2, 0) {
Some(Solid::Object(id)) => {
let o = &board.objects[&id];
assert_eq!((o.x, o.y), (2, 0))
},
_ => panic!("expected Solid::Object"),
}
assert!(!board.is_passable(2, 0));
}
#[test]
fn non_solid_object_does_not_block() {
// A non-solid object sits on empty floor: the cell stays passable.
let mut obj = ObjectDef::new(1, 0);
obj.solid = false;
let board = open_board(3, 1, (0, 0), vec![obj], &[]);
assert!(board.solid_at(1, 0).is_none());
assert!(board.is_passable(1, 0));
}
/// Stamps a crate cell onto an otherwise-open board.
fn crate_at(board: &mut Board, x: usize, y: usize) {
*board.get_mut(x, y) = (Archetype::Crate.default_glyph(), Archetype::Crate);
}
/// Stamps a wall cell onto an otherwise-open board.
fn wall_at(board: &mut Board, x: usize, y: usize) {
*board.get_mut(x, y) = (Archetype::Wall.default_glyph(), Archetype::Wall);
}
#[test]
fn in_bounds_checks_grid_boundaries() {
let board = open_board(3, 2, (0, 0), vec![], &[]);
assert!(board.in_bounds((0, 0)));
assert!(board.in_bounds((2, 1)));
assert!(!board.in_bounds((-1, 0)));
assert!(!board.in_bounds((0, -1)));
assert!(!board.in_bounds((3, 0))); // x == width
assert!(!board.in_bounds((0, 2))); // y == height
}
#[test]
fn can_push_is_read_only_and_correct() {
// Crate with empty space beyond: pushable, and the board is left untouched.
let mut board = open_board(3, 1, (0, 0), vec![], &[]);
fn pushing_a_crate_reveals_the_floor_underneath() {
// The cell a crate is pushed off of becomes Empty and glyph_at shows floor.
// After the push the player is at (1,0); check the cell the player vacated
// (0,0) — it is Empty and must reveal the floor glyph, not black-on-black.
use color::Rgba8;
use crate::glyph::Glyph;
let mut board = open_board(4, 1, (0, 0), vec![], &[]);
let floor_glyph = Glyph {
tile: ',' as u32,
fg: Rgba8 { r: 40, g: 60, b: 40, a: 255 },
bg: Rgba8 { r: 5, g: 10, b: 5, a: 255 },
};
board.floor = vec![floor_glyph; 4];
crate_at(&mut board, 1, 0);
assert!(board.can_push(1, 0, Direction::East));
assert_eq!(board.get(1, 0).1, Archetype::Crate); // no mutation
assert_eq!(board.get(2, 0).1, Archetype::Empty);
// Crate against a wall: not pushable.
let mut board = open_board(3, 1, (0, 0), vec![], &[]);
crate_at(&mut board, 1, 0);
wall_at(&mut board, 2, 0);
assert!(!board.can_push(1, 0, Direction::East));
let mut game = GameState::new(board);
game.try_move(Direction::East);
let b = game.board();
assert_eq!(b.get(2, 0).1, Archetype::Crate);
assert_eq!(b.get(1, 0).1, Archetype::Empty);
assert_eq!(b.glyph_at(0, 0), floor_glyph);
}
#[test]
@@ -460,24 +361,6 @@ mod tests {
assert_eq!(b.get(3, 0).1, Archetype::Crate); // crate pushed to (3,0)
}
#[test]
fn pushable_allows_only_its_axis() {
use Direction::*;
for d in [North, South, East, West] {
assert!(!Pushable::No.allows(d));
assert!(Pushable::Any.allows(d));
}
assert!(Pushable::Horizontal.allows(East) && Pushable::Horizontal.allows(West));
assert!(!Pushable::Horizontal.allows(North) && !Pushable::Horizontal.allows(South));
assert!(Pushable::Vertical.allows(North) && Pushable::Vertical.allows(South));
assert!(!Pushable::Vertical.allows(East) && !Pushable::Vertical.allows(West));
}
/// Stamps an archetype cell onto an otherwise-open board.
fn stamp(board: &mut Board, x: usize, y: usize, arch: Archetype) {
*board.get_mut(x, y) = (arch.default_glyph(), arch);
}
#[test]
fn hcrate_pushes_east_but_not_north() {
// Pushing east: the HCrate slides.
@@ -524,21 +407,6 @@ mod tests {
assert_eq!(b.get(1, 0).1, Archetype::VCrate); // didn't move
}
#[test]
fn fresh_board_is_valid_and_reports_errors() {
let mut board = open_board(1, 1, (0, 0), vec![], &[]);
assert!(board.is_valid());
board.report_error("something went wrong");
assert!(!board.is_valid());
assert_eq!(board.load_errors.len(), 1);
}
#[test]
fn directional_crate_default_glyph_tiles() {
assert_eq!(Archetype::HCrate.default_glyph().tile, 29);
assert_eq!(Archetype::VCrate.default_glyph().tile, 18);
}
#[test]
fn init_runs_only_on_run_init_not_at_construction() {
let board = board_with_object(
@@ -916,101 +784,6 @@ mod tests {
assert!(log_texts(&game).iter().any(|t| t == "bumped by -1"));
}
#[test]
fn glyph_at_uses_floor_for_empty_and_grid_for_solid() {
// A board with a distinctive floor glyph; empty cells show the floor, while
// a wall keeps drawing its own grid glyph (floor ignored under solids).
// Player parked at (2,0) so it doesn't overlap either asserted cell.
let mut board = open_board(3, 1, (2, 0), vec![], &[]);
let floor_glyph = Glyph {
tile: '.' as u32,
fg: Rgba8 {
r: 10,
g: 20,
b: 30,
a: 255,
},
bg: Rgba8 {
r: 1,
g: 2,
b: 3,
a: 255,
},
};
board.floor = vec![floor_glyph; 3];
wall_at(&mut board, 0, 0);
assert_eq!(board.glyph_at(0, 0), Archetype::Wall.default_glyph()); // solid: grid glyph
assert_eq!(board.glyph_at(1, 0), floor_glyph); // empty: floor glyph
}
#[test]
fn pushing_a_crate_reveals_the_floor_underneath() {
// The cell a crate is pushed off of becomes Empty, so its `glyph_at`
// should show the floor glyph rather than black-on-black.
let mut board = open_board(4, 1, (0, 0), vec![], &[]);
let floor_glyph = Glyph {
tile: ',' as u32,
fg: Rgba8 {
r: 40,
g: 60,
b: 40,
a: 255,
},
bg: Rgba8 {
r: 5,
g: 10,
b: 5,
a: 255,
},
};
board.floor = vec![floor_glyph; 4];
crate_at(&mut board, 1, 0);
let mut game = GameState::new(board);
game.try_move(Direction::East); // player at (0,0) pushes the crate east
let b = game.board();
assert_eq!(b.get(2, 0).1, Archetype::Crate); // crate moved one east
// After the push the player is at (1,0); check the cell the player vacated
// (0,0) — it is Empty and must reveal the floor glyph, not black-on-black.
assert_eq!(b.get(1, 0).1, Archetype::Empty); // crate's old cell is empty
assert_eq!(b.glyph_at(0, 0), floor_glyph); // player's old cell shows floor
}
#[test]
fn solid_at_reports_player() {
// The player's own cell is solid (and thus impassable to movers).
let board = open_board(3, 1, (1, 0), vec![], &[]);
match board.solid_at(1, 0) {
Some(Solid::Player) => {}
_ => panic!("expected Solid::Player at the player's cell"),
}
assert!(!board.is_passable(1, 0));
}
#[test]
fn push_into_player_pushes_player() {
// Crate shoved east into the player slides the player along into open space.
let mut board = open_board(4, 1, (2, 0), vec![], &[]);
crate_at(&mut board, 1, 0);
assert!(board.can_push(1, 0, Direction::East));
board.push(1, 0, Direction::East);
assert_eq!(board.get(1, 0).1, Archetype::Empty); // crate left its cell
assert_eq!(board.get(2, 0).1, Archetype::Crate); // crate took the player's old cell
assert_eq!((board.player.x, board.player.y), (3, 0)); // player shoved east
}
#[test]
fn push_into_player_blocked_by_wall() {
// Player backed against a wall: the push has nowhere to go, so nothing moves
// and the player is never overlapped.
let mut board = open_board(4, 1, (2, 0), vec![], &[]);
crate_at(&mut board, 1, 0);
wall_at(&mut board, 3, 0);
assert!(!board.can_push(1, 0, Direction::East));
board.push(1, 0, Direction::East); // no-op
assert_eq!(board.get(1, 0).1, Archetype::Crate);
assert_eq!((board.player.x, board.player.y), (2, 0));
}
#[test]
fn object_push_into_player() {
// A scripted object moving into the player pushes the player when there's room.
+22
View File
@@ -111,3 +111,25 @@ pub struct Player {
/// Row position (0-indexed, increasing downward).
pub y: i32,
}
#[cfg(test)]
mod tests {
use crate::script::Direction;
use super::Pushable;
#[test]
fn pushable_allows_only_its_axis() {
for d in [Direction::North, Direction::South, Direction::East, Direction::West] {
assert!(!Pushable::No.allows(d));
assert!(Pushable::Any.allows(d));
}
assert!(Pushable::Horizontal.allows(Direction::East));
assert!(Pushable::Horizontal.allows(Direction::West));
assert!(!Pushable::Horizontal.allows(Direction::North));
assert!(!Pushable::Horizontal.allows(Direction::South));
assert!(Pushable::Vertical.allows(Direction::North));
assert!(Pushable::Vertical.allows(Direction::South));
assert!(!Pushable::Vertical.allows(Direction::East));
assert!(!Pushable::Vertical.allows(Direction::West));
}
}