#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::fs;
use bevy_xray_parser::graph;
use bevy_xray_parser::model::{StateGraph, StateKind};
use bevy_xray_parser::parser;
fn create_and_parse(files: &[(&str, &str)]) -> StateGraph {
let dir = tempfile::Builder::new()
.prefix("bevy_xray_test_")
.tempdir()
.expect("failed to create temp dir");
fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"test-crate\"\nversion = \"0.1.0\"\nedition = \"2024\"\n",
)
.expect("failed to write Cargo.toml");
let src = dir.path().join("src");
fs::create_dir_all(&src).expect("failed to create src dir");
for (name, content) in files {
let path = src.join(name);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("failed to create parent dir");
}
fs::write(&path, content).expect("failed to write source file");
}
parser::parse_workspace(dir.path()).expect("parse_workspace failed")
}
#[test]
fn parse_root_state() {
let graph = create_and_parse(&[(
"state.rs",
r#"
#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
enum AppState {
#[default]
Loading,
InGame,
Menu,
}
"#,
)]);
assert_eq!(graph.states.len(), 1);
assert_eq!(graph.states[0].name, "AppState");
assert!(matches!(graph.states[0].kind, StateKind::Root));
assert_eq!(graph.states[0].variants, vec!["Loading", "InGame", "Menu"]);
}
#[test]
fn parse_sub_state() {
let graph = create_and_parse(&[(
"state.rs",
r#"
#[derive(SubStates, Default, Debug, Clone, PartialEq, Eq, Hash)]
#[source(AppState = InGame)]
enum GameState {
#[default]
Playing,
Paused,
}
"#,
)]);
assert_eq!(graph.states.len(), 1);
assert_eq!(graph.states[0].name, "GameState");
assert!(matches!(graph.states[0].kind, StateKind::Sub { .. }));
}
#[test]
fn parse_plugin_impl() {
let graph = create_and_parse(&[(
"plugin.rs",
r#"
struct MyPlugin;
impl Plugin for MyPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, my_system.in_set(MySets::Core));
}
}
"#,
)]);
assert_eq!(graph.plugins.len(), 1);
assert_eq!(graph.plugins[0].name, "MyPlugin");
assert_eq!(graph.plugins[0].crate_name, "test-crate");
}
#[test]
fn parse_plugin_with_sub_plugins() {
let graph = create_and_parse(&[(
"plugin.rs",
r#"
struct ParentPlugin;
impl Plugin for ParentPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(ChildPlugin);
}
}
struct ChildPlugin;
impl Plugin for ChildPlugin {
fn build(&self, app: &mut App) {}
}
"#,
)]);
assert_eq!(graph.plugins.len(), 2);
let parent = graph
.plugins
.iter()
.find(|p| p.name == "ParentPlugin")
.expect("ParentPlugin");
assert!(parent.sub_plugins.contains(&"ChildPlugin".to_string()));
let tree = graph::build_plugin_tree(&graph);
assert_eq!(tree.len(), 1);
assert_eq!(tree[0].name, "ParentPlugin");
assert_eq!(tree[0].children.len(), 1);
assert_eq!(tree[0].children[0].name, "ChildPlugin");
}
#[test]
fn parse_system_set() {
let graph = create_and_parse(&[(
"sets.rs",
r#"
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
enum MySets {
Core,
Render,
}
"#,
)]);
assert_eq!(graph.system_sets.len(), 1);
assert_eq!(graph.system_sets[0].name, "MySets");
assert_eq!(graph.system_sets[0].variants, vec!["Core", "Render"]);
}
#[test]
fn parse_schedule_config() {
let graph = create_and_parse(&[(
"plugin.rs",
r#"
struct SetupPlugin;
impl Plugin for SetupPlugin {
fn build(&self, app: &mut App) {
app.configure_sets(
Update,
MySets::Core.run_if(in_state(AppState::InGame)),
);
}
}
"#,
)]);
assert!(!graph.schedule_configs.is_empty());
let config = &graph.schedule_configs[0];
assert_eq!(config.schedule, "Update");
assert!(config.system_sets.contains(&"MySets::Core".to_string()));
}
#[test]
fn parse_plugin_group() {
let graph = create_and_parse(&[(
"group.rs",
r#"
struct MyPlugins;
impl PluginGroup for MyPlugins {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
.add(FooPlugin)
.add(BarPlugin)
}
}
"#,
)]);
assert_eq!(graph.plugin_groups.len(), 1);
assert_eq!(graph.plugin_groups[0].name, "MyPlugins");
assert!(
graph.plugin_groups[0]
.plugins
.contains(&"FooPlugin".to_string())
);
assert!(
graph.plugin_groups[0]
.plugins
.contains(&"BarPlugin".to_string())
);
}
#[test]
fn parse_empty_workspace() {
let graph = create_and_parse(&[("main.rs", "fn main() {}")]);
assert!(graph.states.is_empty());
assert!(graph.plugins.is_empty());
assert!(graph.plugin_groups.is_empty());
assert!(graph.schedule_configs.is_empty());
assert!(graph.system_sets.is_empty());
}
#[test]
fn build_state_tree_integration() {
let graph = create_and_parse(&[(
"state.rs",
r#"
#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
enum AppState {
#[default]
Loading,
InGame,
}
#[derive(SubStates, Default, Debug, Clone, PartialEq, Eq, Hash)]
#[source(AppState = InGame)]
enum GameState {
#[default]
Playing,
Paused,
}
"#,
)]);
let tree = graph::build_state_tree(&graph);
assert_eq!(tree.len(), 1);
assert_eq!(tree[0].name, "AppState");
assert_eq!(tree[0].children.len(), 1);
assert_eq!(tree[0].children[0].name, "GameState");
}
#[test]
fn plugins_for_state_integration() {
let graph = create_and_parse(&[(
"plugin.rs",
r#"
struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.init_state::<AppState>();
app.add_systems(Update, my_system.run_if(in_state(AppState::InGame)));
}
}
"#,
)]);
let plugins = graph::plugins_for_state("AppState", &graph);
assert_eq!(plugins.len(), 1);
assert_eq!(plugins[0].name, "GamePlugin");
}