Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
62
crates/domain/tests/common/mod.rs
Normal file
62
crates/domain/tests/common/mod.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use domain::{Color, Dimensions, DomainError, Point2D, ScreensaverScene, Sprite, Vector2D};
|
||||
|
||||
pub const LOGO: Dimensions = size(200, 100);
|
||||
pub const ROOMY: Dimensions = size(800, 600);
|
||||
pub const TINY: Dimensions = size(50, 50);
|
||||
|
||||
pub const SNUG: Dimensions = size(LOGO.width() + 2, LOGO.height() + 2);
|
||||
pub const EDGE_ONLY: Dimensions = size(LOGO.width() + 2, ROOMY.height());
|
||||
|
||||
pub const SPEED: Vector2D = velocity(3.0, 2.0);
|
||||
pub const RED: Color = Color {
|
||||
r: 255,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 255,
|
||||
};
|
||||
|
||||
pub const fn size(width: u32, height: u32) -> Dimensions {
|
||||
match Dimensions::new(width, height) {
|
||||
Some(size) => size,
|
||||
None => panic!("test dimensions must be non-zero"),
|
||||
}
|
||||
}
|
||||
|
||||
const fn velocity(dx: f32, dy: f32) -> Vector2D {
|
||||
match Vector2D::new(dx, dy) {
|
||||
Some(velocity) => velocity,
|
||||
None => panic!("test velocity must be finite"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn point(x: f32, y: f32) -> Point2D {
|
||||
Point2D::new(x, y).expect("test point must be finite")
|
||||
}
|
||||
|
||||
pub fn try_scene(logo: Dimensions, bounds: Dimensions) -> Result<ScreensaverScene, DomainError> {
|
||||
ScreensaverScene::new(logo, SPEED, RED, bounds)
|
||||
}
|
||||
|
||||
pub fn scene_in(bounds: Dimensions) -> ScreensaverScene {
|
||||
try_scene(LOGO, bounds).expect("LOGO must fit the test bounds")
|
||||
}
|
||||
|
||||
pub fn advance(scene: &mut ScreensaverScene, bounds: Dimensions, frames: u32) {
|
||||
for _ in 0..frames {
|
||||
scene.tick(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sprite_count(scene: &ScreensaverScene) -> usize {
|
||||
scene.sprites().count()
|
||||
}
|
||||
|
||||
pub fn logo_sprite(scene: &ScreensaverScene) -> Sprite {
|
||||
scene.sprites().next().expect("the logo is always present")
|
||||
}
|
||||
|
||||
pub fn bottom_right(bounds: Dimensions) -> Point2D {
|
||||
point(bounds.width() as f32, bounds.height() as f32)
|
||||
}
|
||||
37
crates/domain/tests/construction.rs
Normal file
37
crates/domain/tests/construction.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
mod common;
|
||||
|
||||
use common::{LOGO, ROOMY, scene_in, size, sprite_count, try_scene};
|
||||
use domain::{Dimensions, Point2D, Vector2D};
|
||||
|
||||
#[test]
|
||||
fn dimensions_reject_zero_extents() {
|
||||
assert!(Dimensions::new(0, 10).is_none());
|
||||
assert!(Dimensions::new(10, 0).is_none());
|
||||
assert!(Dimensions::new(10, 10).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn points_reject_non_finite_coordinates() {
|
||||
assert!(Point2D::new(f32::NAN, 0.0).is_none());
|
||||
assert!(Point2D::new(0.0, f32::INFINITY).is_none());
|
||||
assert!(Point2D::new(-1.0, 1.0).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vectors_reject_non_finite_components() {
|
||||
assert!(Vector2D::new(f32::NAN, 1.0).is_none());
|
||||
assert!(Vector2D::new(1.0, f32::NEG_INFINITY).is_none());
|
||||
assert!(Vector2D::new(0.0, 0.0).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_logo_must_fit_inside_the_bounds() {
|
||||
assert!(try_scene(LOGO, LOGO).is_ok());
|
||||
assert!(try_scene(size(LOGO.width() + 1, LOGO.height()), LOGO).is_err());
|
||||
assert!(try_scene(size(LOGO.width(), LOGO.height() + 1), LOGO).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_scene_holds_only_the_logo() {
|
||||
assert_eq!(sprite_count(&scene_in(ROOMY)), 1);
|
||||
}
|
||||
75
crates/domain/tests/motion.rs
Normal file
75
crates/domain/tests/motion.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
mod common;
|
||||
|
||||
use common::{
|
||||
EDGE_ONLY, ROOMY, SNUG, TINY, advance, bottom_right, logo_sprite, scene_in, sprite_count,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn hitting_a_corner_bursts_sparkles_at_that_corner() {
|
||||
let mut scene = scene_in(SNUG);
|
||||
|
||||
advance(&mut scene, SNUG, 1);
|
||||
|
||||
let corner = bottom_right(SNUG);
|
||||
|
||||
assert!(scene.sprites().any(|sprite| sprite.position() == corner));
|
||||
assert!(sprite_count(&scene) > 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hitting_a_single_edge_recolours_but_does_not_burst() {
|
||||
let mut scene = scene_in(EDGE_ONLY);
|
||||
let before = logo_sprite(&scene).color();
|
||||
|
||||
advance(&mut scene, EDGE_ONLY, 1);
|
||||
|
||||
assert_ne!(logo_sprite(&scene).color(), before);
|
||||
assert_eq!(sprite_count(&scene), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sparkles_expire_so_the_scene_reaches_a_steady_size() {
|
||||
let mut scene = scene_in(SNUG);
|
||||
|
||||
advance(&mut scene, SNUG, 64);
|
||||
let steady = sprite_count(&scene);
|
||||
advance(&mut scene, SNUG, 512);
|
||||
|
||||
assert_eq!(sprite_count(&scene), steady);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_sparkle_buffer_holds_the_worst_case_without_dropping_any() {
|
||||
let mut scene = scene_in(SNUG);
|
||||
|
||||
advance(&mut scene, SNUG, 256);
|
||||
|
||||
assert_eq!(sprite_count(&scene), 12 * 9 + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounds_shrinking_below_the_logo_neither_recolours_nor_bursts() {
|
||||
let mut scene = scene_in(ROOMY);
|
||||
let before = logo_sprite(&scene).color();
|
||||
|
||||
advance(&mut scene, TINY, 64);
|
||||
|
||||
assert_eq!(logo_sprite(&scene).color(), before);
|
||||
assert_eq!(sprite_count(&scene), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_logo_never_leaves_the_bounds() {
|
||||
let mut scene = scene_in(ROOMY);
|
||||
|
||||
for _ in 0..2048 {
|
||||
advance(&mut scene, ROOMY, 1);
|
||||
|
||||
let logo = logo_sprite(&scene);
|
||||
let (position, size) = (logo.position(), logo.dimensions());
|
||||
|
||||
assert!(position.x() >= 0.0 && position.y() >= 0.0);
|
||||
assert!(position.x() + size.width() as f32 <= ROOMY.width() as f32);
|
||||
assert!(position.y() + size.height() as f32 <= ROOMY.height() as f32);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user