Bevy Forge 0.1.0: visual scene editor for Bevy 0.20 BSN
This commit is contained in:
@@ -0,0 +1,644 @@
|
||||
//! End-to-end tests for the `forge` binary.
|
||||
//!
|
||||
//! Scenes are produced through the core API so these tests exercise the real
|
||||
//! serialisation path instead of hand-written RON.
|
||||
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Output};
|
||||
|
||||
use bevy_forge_core::{ComponentData, Project, SceneDoc, SpriteData, Text2dData};
|
||||
use serde_json::Value;
|
||||
|
||||
fn forge(args: &[&str]) -> Output {
|
||||
Command::new(env!("CARGO_BIN_EXE_forge"))
|
||||
.args(args)
|
||||
.output()
|
||||
.expect("failed to spawn the forge binary")
|
||||
}
|
||||
|
||||
fn exit_code(output: &Output) -> i32 {
|
||||
output
|
||||
.status
|
||||
.code()
|
||||
.expect("the forge process was terminated by a signal")
|
||||
}
|
||||
|
||||
fn stdout(output: &Output) -> String {
|
||||
String::from_utf8_lossy(&output.stdout).into_owned()
|
||||
}
|
||||
|
||||
fn stderr(output: &Output) -> String {
|
||||
String::from_utf8_lossy(&output.stderr).into_owned()
|
||||
}
|
||||
|
||||
/// Create a minimal Bevy project (manifest + empty assets dir) in `dir`.
|
||||
fn create_project(dir: &Path) -> Project {
|
||||
fs::create_dir_all(dir.join("assets")).unwrap();
|
||||
fs::write(
|
||||
dir.join("Cargo.toml"),
|
||||
"[package]\nname = \"test-project\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nbevy = \"0.20\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
Project::open(dir).unwrap()
|
||||
}
|
||||
|
||||
fn save_scene(project: &Project, file: &str, document: &SceneDoc) -> PathBuf {
|
||||
let path = project.scenes_dir().join(file);
|
||||
project.save_scene(document, Some(&path)).unwrap();
|
||||
path
|
||||
}
|
||||
|
||||
/// A tiny scene with one textured sprite and one text entity.
|
||||
fn sprite_scene(name: &str, image: &str) -> SceneDoc {
|
||||
let mut document = SceneDoc::new(name);
|
||||
let player = document.add_entity("Player", None);
|
||||
document.add_component(
|
||||
player,
|
||||
ComponentData::Sprite(SpriteData {
|
||||
image: Some(image.to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
let label = document.add_entity("Label", None);
|
||||
document.add_component(
|
||||
label,
|
||||
ComponentData::Text2d(Text2dData {
|
||||
text: "Hello".to_string(),
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
document
|
||||
}
|
||||
|
||||
/// Run `forge new` and return the project root plus its demo scene path.
|
||||
fn scaffold_2d(dir: &Path) -> (PathBuf, PathBuf) {
|
||||
let output = forge(&["new", dir.to_str().unwrap()]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"forge new failed\nstdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
let project = Project::open(dir).unwrap();
|
||||
let scene = project.scenes_dir().join("demo.forge.ron");
|
||||
(project.root.clone(), scene)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Top-level CLI behaviour
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn help_lists_every_subcommand() {
|
||||
let output = forge(&["--help"]);
|
||||
assert_eq!(exit_code(&output), 0, "stderr: {}", stderr(&output));
|
||||
let text = stdout(&output);
|
||||
for subcommand in ["new", "validate", "export", "info", "scenes"] {
|
||||
assert!(
|
||||
text.contains(subcommand),
|
||||
"`{subcommand}` missing from --help output:\n{text}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_prints_the_package_version() {
|
||||
let output = forge(&["--version"]);
|
||||
assert_eq!(exit_code(&output), 0, "stderr: {}", stderr(&output));
|
||||
assert!(
|
||||
stdout(&output).contains(env!("CARGO_PKG_VERSION")),
|
||||
"unexpected version output: {}",
|
||||
stdout(&output)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_command_exits_with_a_usage_error() {
|
||||
let output = forge(&["frobnicate"]);
|
||||
assert_eq!(exit_code(&output), 2);
|
||||
assert!(!stderr(&output).is_empty());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// new
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn new_creates_a_valid_project() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = tmp.path().join("my-game");
|
||||
let output = forge(&["new", dir.to_str().unwrap()]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"forge new failed\nstdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
|
||||
for relative in [
|
||||
"Cargo.toml",
|
||||
"src/main.rs",
|
||||
"assets/.gitkeep",
|
||||
"scenes/demo.forge.ron",
|
||||
".gitignore",
|
||||
"README.md",
|
||||
] {
|
||||
assert!(dir.join(relative).is_file(), "missing {relative}");
|
||||
}
|
||||
|
||||
let manifest = fs::read_to_string(dir.join("Cargo.toml")).unwrap();
|
||||
assert!(
|
||||
manifest.contains("name = \"my-game\""),
|
||||
"manifest: {manifest}"
|
||||
);
|
||||
assert!(manifest.contains("bevy = \"0.20\""), "manifest: {manifest}");
|
||||
|
||||
let project = Project::open(&dir).unwrap();
|
||||
let scene_path = project.scenes_dir().join("demo.forge.ron");
|
||||
let document = project.load_scene(&scene_path).unwrap();
|
||||
assert!(
|
||||
!document.entities.is_empty(),
|
||||
"starter scene should contain entities"
|
||||
);
|
||||
assert!(
|
||||
!document.roots.is_empty(),
|
||||
"starter scene should have roots"
|
||||
);
|
||||
|
||||
let output = forge(&["validate", scene_path.to_str().unwrap()]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"scaffolded scene should validate\nstdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_refuses_a_non_empty_directory() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = tmp.path().join("occupied");
|
||||
fs::create_dir_all(&dir).unwrap();
|
||||
fs::write(dir.join("keep.txt"), "keep me").unwrap();
|
||||
|
||||
let output = forge(&["new", dir.to_str().unwrap()]);
|
||||
assert_eq!(exit_code(&output), 2);
|
||||
assert!(
|
||||
stderr(&output).contains("not empty"),
|
||||
"stderr: {}",
|
||||
stderr(&output)
|
||||
);
|
||||
assert!(!dir.join("Cargo.toml").exists());
|
||||
assert_eq!(fs::read_to_string(dir.join("keep.txt")).unwrap(), "keep me");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_fills_an_existing_empty_directory() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = tmp.path().join("already-there");
|
||||
fs::create_dir_all(&dir).unwrap();
|
||||
|
||||
let output = forge(&["new", dir.to_str().unwrap()]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"stdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
assert!(dir.join("Cargo.toml").is_file());
|
||||
assert!(dir.join("scenes/demo.forge.ron").is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_accepts_name_and_3d_template() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dir = tmp.path().join("space");
|
||||
let output = forge(&[
|
||||
"new",
|
||||
dir.to_str().unwrap(),
|
||||
"--name",
|
||||
"space-game",
|
||||
"--template",
|
||||
"3d",
|
||||
]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"stdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
|
||||
let manifest = fs::read_to_string(dir.join("Cargo.toml")).unwrap();
|
||||
assert!(
|
||||
manifest.contains("name = \"space-game\""),
|
||||
"manifest: {manifest}"
|
||||
);
|
||||
|
||||
let project = Project::open(&dir).unwrap();
|
||||
let document = project
|
||||
.load_scene(project.scenes_dir().join("demo.forge.ron"))
|
||||
.unwrap();
|
||||
assert!(
|
||||
document.stats().meshes > 0,
|
||||
"the 3D starter scene should contain mesh entities"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// validate
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn validate_accepts_a_good_scene_and_emits_json() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let (_root, scene) = scaffold_2d(&tmp.path().join("good-project"));
|
||||
|
||||
let output = forge(&["validate", scene.to_str().unwrap()]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"stdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
assert!(stdout(&output).contains("Summary:"));
|
||||
|
||||
let output = forge(&["validate", scene.to_str().unwrap(), "--json"]);
|
||||
assert_eq!(exit_code(&output), 0);
|
||||
let value: Value =
|
||||
serde_json::from_str(&stdout(&output)).expect("validate --json must emit JSON");
|
||||
assert_eq!(value["valid"], true);
|
||||
assert_eq!(value["errors"], 0);
|
||||
assert!(value["warnings"].is_u64());
|
||||
assert!(value["issues"].is_array());
|
||||
for issue in value["issues"].as_array().unwrap() {
|
||||
for key in ["severity", "entity", "field", "message"] {
|
||||
assert!(issue.get(key).is_some(), "issue missing `{key}`: {issue}");
|
||||
}
|
||||
assert!(["error", "warning", "info"].contains(&issue["severity"].as_str().unwrap()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_reports_parse_errors_with_exit_code_two() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let broken = tmp.path().join("broken.forge.ron");
|
||||
fs::write(&broken, "this is definitely not RON").unwrap();
|
||||
|
||||
let output = forge(&["validate", broken.to_str().unwrap()]);
|
||||
assert_eq!(exit_code(&output), 2);
|
||||
assert!(
|
||||
stderr(&output).contains("parse"),
|
||||
"stderr should explain the parse error: {}",
|
||||
stderr(&output)
|
||||
);
|
||||
assert!(stdout(&output).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_reports_a_missing_asset_as_a_validation_error() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project = create_project(tmp.path());
|
||||
let document = sprite_scene("Missing asset", "textures/missing.png");
|
||||
let scene = save_scene(&project, "missing.forge.ron", &document);
|
||||
|
||||
let output = forge(&["validate", scene.to_str().unwrap()]);
|
||||
assert_eq!(exit_code(&output), 1);
|
||||
let text = stdout(&output);
|
||||
assert!(text.contains("error"), "stdout: {text}");
|
||||
assert!(text.contains("textures/missing.png"), "stdout: {text}");
|
||||
assert!(text.contains("Summary:"), "stdout: {text}");
|
||||
|
||||
let output = forge(&["validate", scene.to_str().unwrap(), "--json"]);
|
||||
assert_eq!(exit_code(&output), 1);
|
||||
let value: Value = serde_json::from_str(&stdout(&output)).unwrap();
|
||||
assert_eq!(value["valid"], false);
|
||||
assert!(value["errors"].as_u64().unwrap() >= 1);
|
||||
let issues = value["issues"].as_array().unwrap();
|
||||
assert!(issues.iter().any(|issue| issue["message"]
|
||||
.as_str()
|
||||
.is_some_and(|message| message.contains("textures/missing.png"))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_uses_an_explicit_project_for_assets() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
// The scene lives outside the project; `--project` must still resolve assets.
|
||||
let project = create_project(&tmp.path().join("project"));
|
||||
let outside = tmp.path().join("outside");
|
||||
fs::create_dir_all(&outside).unwrap();
|
||||
let document = sprite_scene("Outside", "textures/hero.png");
|
||||
fs::create_dir_all(project.assets_dir.join("textures")).unwrap();
|
||||
fs::write(project.assets_dir.join("textures/hero.png"), b"png").unwrap();
|
||||
// Serialise inside the project, then move the file out of it.
|
||||
let saved = save_scene(&project, "outside.forge.ron", &document);
|
||||
let scene = outside.join("outside.forge.ron");
|
||||
fs::copy(&saved, &scene).unwrap();
|
||||
fs::remove_file(&saved).unwrap();
|
||||
|
||||
let output = forge(&[
|
||||
"validate",
|
||||
scene.to_str().unwrap(),
|
||||
"--project",
|
||||
project.root.to_str().unwrap(),
|
||||
]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"stdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// export
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn export_bsn_to_stdout_and_file() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project = create_project(tmp.path());
|
||||
let document = sprite_scene("Export scene", "textures/player.png");
|
||||
let scene = save_scene(&project, "export.forge.ron", &document);
|
||||
|
||||
let output = forge(&["export", scene.to_str().unwrap(), "--format", "bsn"]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"stdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
let stdout_code = stdout(&output);
|
||||
assert!(
|
||||
stdout_code.starts_with("use bevy::prelude::*;"),
|
||||
"generated code must start with the Bevy prelude import:\n{stdout_code}"
|
||||
);
|
||||
assert!(stdout_code.ends_with('\n'));
|
||||
assert!(
|
||||
stdout_code.contains("bsn!") || stdout_code.contains("bsn_list!"),
|
||||
"expected BSN macro code:\n{stdout_code}"
|
||||
);
|
||||
|
||||
let target = tmp.path().join("generated/nested/scene.rs");
|
||||
let output = forge(&[
|
||||
"export",
|
||||
scene.to_str().unwrap(),
|
||||
"--format",
|
||||
"bsn",
|
||||
"-o",
|
||||
target.to_str().unwrap(),
|
||||
]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"stdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
let written = fs::read_to_string(&target).expect("output file must exist");
|
||||
assert_eq!(written, stdout_code);
|
||||
assert!(
|
||||
target.parent().unwrap().is_dir(),
|
||||
"parent directories must be created"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_bsn_single_root_uses_the_bsn_macro() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project = create_project(tmp.path());
|
||||
let mut document = SceneDoc::new("Single root");
|
||||
let player = document.add_entity("Player", None);
|
||||
document.add_component(
|
||||
player,
|
||||
ComponentData::Sprite(SpriteData {
|
||||
image: Some("textures/player.png".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
);
|
||||
let scene = save_scene(&project, "single.forge.ron", &document);
|
||||
|
||||
let output = forge(&["export", scene.to_str().unwrap(), "--format", "bsn"]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"stdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
let code = stdout(&output);
|
||||
assert!(code.contains("bsn! {"), "code:\n{code}");
|
||||
assert!(!code.contains("bsn_list!"), "code:\n{code}");
|
||||
assert!(code.contains("-> impl Scene"), "code:\n{code}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_rust_format() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project = create_project(tmp.path());
|
||||
let document = sprite_scene("Export scene", "textures/player.png");
|
||||
let scene = save_scene(&project, "export.forge.ron", &document);
|
||||
|
||||
let output = forge(&["export", scene.to_str().unwrap(), "--format", "rust"]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"stdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
let code = stdout(&output);
|
||||
assert!(code.starts_with("use bevy::prelude::*;"), "code:\n{code}");
|
||||
assert!(
|
||||
code.contains("Commands"),
|
||||
"expected a Commands parameter:\n{code}"
|
||||
);
|
||||
assert!(
|
||||
code.contains("commands.spawn"),
|
||||
"expected imperative spawn code:\n{code}"
|
||||
);
|
||||
assert!(code.ends_with('\n'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_rejects_an_invalid_function_name() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project = create_project(tmp.path());
|
||||
let document = sprite_scene("Export scene", "textures/player.png");
|
||||
let scene = save_scene(&project, "export.forge.ron", &document);
|
||||
|
||||
let output = forge(&[
|
||||
"export",
|
||||
scene.to_str().unwrap(),
|
||||
"--format",
|
||||
"bsn",
|
||||
"--function",
|
||||
"not a valid name",
|
||||
]);
|
||||
assert_eq!(exit_code(&output), 1);
|
||||
assert!(
|
||||
stderr(&output).contains("identifier"),
|
||||
"stderr: {}",
|
||||
stderr(&output)
|
||||
);
|
||||
assert!(stdout(&output).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_reports_parse_errors_with_exit_code_two() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let broken = tmp.path().join("broken.forge.ron");
|
||||
fs::write(&broken, "not ron at all").unwrap();
|
||||
let target = tmp.path().join("never-written.rs");
|
||||
|
||||
let output = forge(&[
|
||||
"export",
|
||||
broken.to_str().unwrap(),
|
||||
"-o",
|
||||
target.to_str().unwrap(),
|
||||
]);
|
||||
assert_eq!(exit_code(&output), 2);
|
||||
assert!(
|
||||
!target.exists(),
|
||||
"no output must be written on a parse error"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// info
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn info_json_has_the_documented_fields() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project = create_project(tmp.path());
|
||||
let document = sprite_scene("Info scene", "textures/hero.png");
|
||||
let scene = save_scene(&project, "info.forge.ron", &document);
|
||||
|
||||
let output = forge(&["info", scene.to_str().unwrap(), "--json"]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"stdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
let value: Value = serde_json::from_str(&stdout(&output)).expect("info --json must emit JSON");
|
||||
assert_eq!(value["name"], "Info scene");
|
||||
assert_eq!(value["entities"], 2);
|
||||
assert_eq!(value["roots"], 2);
|
||||
assert!(value["path"]
|
||||
.as_str()
|
||||
.is_some_and(|path| Path::new(path).is_absolute()));
|
||||
assert_eq!(value["components"]["sprite"], 1);
|
||||
assert_eq!(value["components"]["text2d"], 1);
|
||||
let references = value["asset_references"].as_array().unwrap();
|
||||
assert!(references
|
||||
.iter()
|
||||
.any(|reference| reference["path"] == "textures/hero.png"));
|
||||
assert!(value["validation"].is_object());
|
||||
// A missing texture is an error, but `info` itself always succeeds.
|
||||
assert_eq!(value["validation"]["valid"], false);
|
||||
|
||||
let output = forge(&["info", scene.to_str().unwrap()]);
|
||||
assert_eq!(exit_code(&output), 0);
|
||||
let text = stdout(&output);
|
||||
assert!(text.contains("Scene:"));
|
||||
assert!(text.contains("Entities: 2"));
|
||||
assert!(text.contains("Validation:"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// scenes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn scenes_lists_absolute_paths_in_sorted_order() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project = create_project(tmp.path());
|
||||
save_scene(&project, "beta.forge.ron", &SceneDoc::new("Beta"));
|
||||
save_scene(&project, "alpha.forge.ron", &SceneDoc::new("Alpha"));
|
||||
|
||||
let output = forge(&["scenes", project.root.to_str().unwrap()]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"stdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
let lines: Vec<String> = stdout(&output).lines().map(ToOwned::to_owned).collect();
|
||||
let expected: Vec<String> = project
|
||||
.list_scenes()
|
||||
.iter()
|
||||
.map(|path| path.to_string_lossy().into_owned())
|
||||
.collect();
|
||||
assert_eq!(lines, expected);
|
||||
assert_eq!(lines.len(), 2);
|
||||
assert!(lines.iter().all(|line| Path::new(line).is_absolute()));
|
||||
assert!(lines[0].ends_with("alpha.forge.ron"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scenes_rejects_a_directory_that_is_not_a_bevy_project() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
fs::write(
|
||||
tmp.path().join("Cargo.toml"),
|
||||
"[package]\nname = \"not-bevy\"\nversion = \"0.1.0\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let output = forge(&["scenes", tmp.path().to_str().unwrap()]);
|
||||
assert_eq!(exit_code(&output), 2);
|
||||
assert!(
|
||||
stderr(&output).contains("not a Bevy project"),
|
||||
"stderr: {}",
|
||||
stderr(&output)
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Demo project integration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn demo_project_scene_validates_and_exports() {
|
||||
let demo = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples/demo-project");
|
||||
let scene = demo.join("scenes/garden.forge.ron");
|
||||
assert!(
|
||||
scene.is_file(),
|
||||
"demo project scene is missing: {}",
|
||||
scene.display()
|
||||
);
|
||||
|
||||
let output = forge(&["validate", scene.to_str().unwrap()]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"demo garden scene should validate\nstdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
|
||||
let output = forge(&["export", scene.to_str().unwrap(), "--format", "bsn"]);
|
||||
assert_eq!(
|
||||
exit_code(&output),
|
||||
0,
|
||||
"stdout: {}\nstderr: {}",
|
||||
stdout(&output),
|
||||
stderr(&output)
|
||||
);
|
||||
assert!(stdout(&output).starts_with("use bevy::prelude::*;"));
|
||||
|
||||
let output = forge(&["scenes", demo.to_str().unwrap()]);
|
||||
assert_eq!(exit_code(&output), 0);
|
||||
assert!(stdout(&output).contains("garden.forge.ron"));
|
||||
}
|
||||
Reference in New Issue
Block a user