//! test suite for bevy
//!
//! See [`AppTest`].
use std::{
    fmt::Debug,
    ops::{Deref, DerefMut},
};

use bevy::prelude::*;

/// Test suite for bevy.
/// 
/// This suite makes available useful common assertions about the state of the
/// bevy app.
///
/// Create a [`Self::new`]
///
/// ```rust
/// use bevy_assert::AppTest;
///
/// let suite = AppTest::new();
/// ```
///
/// or from [`App`].
///
/// ```rust
/// use bevy::prelude::*;
/// use bevy_assert::AppTest;
///
/// let suite: AppTest = App::new().into();
/// ```
///
/// [`AppTest`] dereferences to [`App`] so you can construct the bevy state
/// normally by calling all the [`App`] methods directly.
///
/// ```rust
/// use bevy::{prelude::*, state::app::StatesPlugin};
/// use bevy_assert::AppTest;
///
/// let mut t = AppTest::new();
/// t.add_plugins(StatesPlugin);
/// ```
///
/// The method variants with `_r` suffix will return a Result instead of
/// panicking.
///
#[derive(Default)]
pub struct AppTest {
    app: App,
}

impl AppTest {
    /// Creates a new test suite from an empty app.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Asserts that a state is present in the app and is the specified one.
    ///
    /// # Panics
    ///
    /// Panics if assertion fails.
    pub fn assert_state<S: States>(&self, s: &S) {
        self.assert_state_r(s).unwrap();
    }

    /// Asserts that a state is present in the app and is the specified one.
    ///
    /// Use this variant for a Result instead of panicking.
    ///
    /// # Errors
    ///
    /// - If the state is not in the app.
    /// - If the state is not the specified one.
    pub fn assert_state_r<S: States + Debug>(&self, s: &S) -> Result<(), String> {
        let app_state = self
            .app
            .world()
            .get_resource::<State<S>>()
            .ok_or("State not found".to_string())?
            .get();
        if app_state != s {
            return Err(format!("State was not equal: {app_state:?} != {s:?}"));
        }
        Ok(())
    }
}

impl From<App> for AppTest {
    fn from(value: App) -> Self {
        Self { app: value }
    }
}

impl Deref for AppTest {
    type Target = App;

    fn deref(&self) -> &Self::Target {
        &self.app
    }
}

impl DerefMut for AppTest {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.app
    }
}
