use crate::builder::SimulationBuilder;
use crate::components::ElevatorPhase;
use crate::dispatch::scan::ScanDispatch;
use crate::events::Event;
use crate::stop::StopId;
use super::helpers::default_config;
fn build_sim() -> crate::sim::Simulation {
SimulationBuilder::from_config(default_config())
.dispatch(ScanDispatch::new())
.build()
.unwrap()
}
fn first_elevator(sim: &crate::sim::Simulation) -> crate::entity::EntityId {
sim.world().elevator_ids()[0]
}
#[test]
fn fresh_queue_is_empty() {
let sim = build_sim();
let elev = first_elevator(&sim);
assert_eq!(sim.destination_queue(elev), Some(&[][..]));
}
#[test]
fn dispatch_populates_queue() {
let mut sim = build_sim();
sim.spawn_rider_by_stop_id(StopId(1), StopId(2), 75.0)
.unwrap();
sim.step();
let elev = first_elevator(&sim);
let queue = sim.destination_queue(elev).unwrap();
assert!(
!queue.is_empty(),
"queue should contain the dispatched target (got {queue:?})"
);
}
#[test]
fn queue_pops_on_arrival() {
let mut sim = build_sim();
sim.spawn_rider_by_stop_id(StopId(0), StopId(2), 75.0)
.unwrap();
for _ in 0..2000 {
sim.step();
let elev = first_elevator(&sim);
let car = sim.world().elevator(elev).unwrap();
if !matches!(car.phase(), ElevatorPhase::MovingToStop(_))
&& sim.destination_queue(elev).is_some_and(<[_]>::is_empty)
{
break;
}
}
let elev = first_elevator(&sim);
assert!(sim.destination_queue(elev).unwrap().is_empty());
}
#[test]
fn push_destination_adds_to_back() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
let s1 = sim.stop_entity(StopId(1)).unwrap();
sim.push_destination(elev, s1).unwrap();
assert_eq!(sim.destination_queue(elev).unwrap(), &[s1]);
}
#[test]
fn push_destination_adjacent_dedup_back() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
let s1 = sim.stop_entity(StopId(1)).unwrap();
sim.push_destination(elev, s1).unwrap();
sim.push_destination(elev, s1).unwrap();
assert_eq!(sim.destination_queue(elev).unwrap(), &[s1]);
}
#[test]
fn push_destination_front_inserts_at_index_0() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
let s1 = sim.stop_entity(StopId(1)).unwrap();
let s2 = sim.stop_entity(StopId(2)).unwrap();
sim.push_destination(elev, s1).unwrap();
sim.push_destination_front(elev, s2).unwrap();
assert_eq!(sim.destination_queue(elev).unwrap(), &[s2, s1]);
}
#[test]
fn push_destination_front_adjacent_dedup() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
let s1 = sim.stop_entity(StopId(1)).unwrap();
sim.push_destination_front(elev, s1).unwrap();
sim.push_destination_front(elev, s1).unwrap();
assert_eq!(sim.destination_queue(elev).unwrap(), &[s1]);
}
#[test]
fn clear_destinations_empties_queue() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
let s0 = sim.stop_entity(StopId(0)).unwrap();
let s1 = sim.stop_entity(StopId(1)).unwrap();
let s2 = sim.stop_entity(StopId(2)).unwrap();
sim.push_destination(elev, s1).unwrap();
sim.push_destination(elev, s2).unwrap();
sim.push_destination(elev, s0).unwrap();
sim.clear_destinations(elev).unwrap();
assert!(sim.destination_queue(elev).unwrap().is_empty());
}
#[test]
fn imperative_push_drives_elevator() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
let s2 = sim.stop_entity(StopId(2)).unwrap();
sim.push_destination(elev, s2).unwrap();
let mut arrived = false;
for _ in 0..2000 {
sim.step();
for ev in sim.drain_events() {
if let Event::ElevatorArrived {
elevator, at_stop, ..
} = ev
&& elevator == elev
&& at_stop == s2
{
arrived = true;
}
}
if arrived {
break;
}
}
assert!(
arrived,
"elevator should have arrived at stop 2 via imperative queue"
);
}
#[test]
fn push_front_overrides_current_target() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
let s1 = sim.stop_entity(StopId(1)).unwrap();
let s2 = sim.stop_entity(StopId(2)).unwrap();
sim.push_destination(elev, s2).unwrap();
sim.step(); sim.step();
sim.push_destination_front(elev, s1).unwrap();
let mut arrived_at = None;
for _ in 0..2000 {
sim.step();
for ev in sim.drain_events() {
if let Event::ElevatorArrived {
elevator, at_stop, ..
} = ev
&& elevator == elev
{
arrived_at = Some(at_stop);
break;
}
}
if arrived_at.is_some() {
break;
}
}
assert_eq!(
arrived_at,
Some(s1),
"elevator should arrive at s1 first after push_front"
);
}
#[test]
fn destination_queued_event_fires_on_push() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
let s2 = sim.stop_entity(StopId(2)).unwrap();
sim.push_destination(elev, s2).unwrap();
sim.spawn_rider_by_stop_id(StopId(1), StopId(2), 75.0)
.unwrap();
sim.step();
let events = sim.drain_events();
let count = events
.iter()
.filter(|e| matches!(e, Event::DestinationQueued { .. }))
.count();
assert!(
count >= 2,
"expected at least 2 DestinationQueued events, got {count}"
);
}
#[test]
fn destination_queued_event_suppressed_on_dedup() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
let s1 = sim.stop_entity(StopId(1)).unwrap();
sim.push_destination(elev, s1).unwrap();
sim.push_destination(elev, s1).unwrap();
let events = sim.drain_events();
let count = events
.iter()
.filter(|e| matches!(e, Event::DestinationQueued { .. }))
.count();
assert_eq!(count, 1);
}
#[test]
fn snapshot_roundtrip_preserves_queue() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
let s0 = sim.stop_entity(StopId(0)).unwrap();
let s1 = sim.stop_entity(StopId(1)).unwrap();
let s2 = sim.stop_entity(StopId(2)).unwrap();
sim.push_destination(elev, s1).unwrap();
sim.push_destination(elev, s2).unwrap();
sim.push_destination(elev, s0).unwrap();
let snapshot = sim.snapshot();
let restored = snapshot.restore(None);
let new_elev = restored.world().elevator_ids()[0];
let restored_queue = restored.destination_queue(new_elev).unwrap();
assert_eq!(restored_queue.len(), 3);
}
#[test]
fn push_destination_errors_on_non_elevator() {
let mut sim = build_sim();
let s1 = sim.stop_entity(StopId(1)).unwrap();
let rider = sim
.spawn_rider_by_stop_id(StopId(0), StopId(2), 75.0)
.unwrap();
let result = sim.push_destination(rider, s1);
assert!(result.is_err());
}
#[test]
fn push_destination_errors_on_non_stop() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
let result = sim.push_destination(elev, elev);
assert!(result.is_err());
}
#[test]
fn redirect_via_push_front_updates_direction_indicators() {
let mut sim = build_sim();
let elev = first_elevator(&sim);
sim.spawn_rider_by_stop_id(StopId(1), StopId(2), 75.0)
.unwrap();
for _ in 0..20 {
sim.step();
if matches!(
sim.world()
.elevator(elev)
.map(crate::components::Elevator::phase),
Some(ElevatorPhase::MovingToStop(_))
) {
break;
}
}
assert_eq!(sim.elevator_going_up(elev), Some(true));
assert_eq!(sim.elevator_going_down(elev), Some(false));
let stop_0 = sim.stop_entity(StopId(0)).unwrap();
sim.push_destination_front(elev, stop_0).unwrap();
sim.step();
assert_eq!(
sim.elevator_going_up(elev),
Some(false),
"push_destination_front to a lower stop must clear going_up",
);
assert_eq!(sim.elevator_going_down(elev), Some(true));
}