use bevy::prelude::*;
use bevy_kitty::prelude::*;
fn test_app(config: KittyConfig) -> App {
let mut app = App::new();
app.add_plugins(MinimalPlugins);
app.add_plugins(AssetPlugin::default());
app.init_asset::<Image>();
app.init_asset::<bevy::image::TextureAtlasLayout>();
app.init_resource::<ButtonInput<KeyCode>>();
app.add_plugins(KittyPlugin { config });
app
}
fn headless_config() -> KittyConfig {
KittyConfig {
virtual_size: UVec2::new(320, 180),
terminal_size: Some(TermSize {
cols: 80,
rows: 24,
xpixel: 640,
ypixel: 384,
}),
input: false,
text: false,
..default()
}
}
#[cfg(feature = "input")]
fn read_clicks(mut clicks: MessageReader<KittyClick>, mut seen: Local<u32>) {
for _ in clicks.read() {
*seen += 1;
}
}
#[test]
#[cfg(feature = "input")]
fn a_click_reader_works_even_when_input_is_disabled() {
let mut app = test_app(headless_config());
app.add_systems(Update, read_clicks);
app.update();
app.update();
}
#[test]
#[cfg(feature = "input")]
fn a_key_reader_works_even_when_input_is_disabled() {
fn read_keys(mut keys: MessageReader<KittyKey>) {
for _ in keys.read() {}
}
let mut app = test_app(headless_config());
app.add_systems(Update, read_keys);
app.update();
}
#[test]
fn the_config_is_available_as_a_resource() {
let app = test_app(headless_config());
let config = app.world().resource::<KittyConfig>();
assert_eq!(config.virtual_size, UVec2::new(320, 180));
}
#[test]
fn a_host_system_can_order_against_the_input_set() {
fn noop() {}
let mut app = test_app(headless_config());
app.add_systems(Update, noop.after(KittySet::Input));
app.update();
}
#[test]
fn a_host_system_can_order_against_the_render_set() {
fn noop() {}
let mut app = test_app(headless_config());
app.add_systems(PostUpdate, noop.after(KittySet::Render));
app.update();
}
#[test]
fn ticking_without_a_camera_does_not_panic() {
let mut app = test_app(headless_config());
for _ in 0..5 {
app.update();
}
}
#[test]
fn a_camera_gets_a_render_target_attached() {
use bevy::camera::RenderTarget;
let mut app = test_app(headless_config());
let cam = app.world_mut().spawn((Camera2d, KittyCamera)).id();
app.update();
app.update();
assert!(
app.world().entity(cam).contains::<RenderTarget>(),
"the KittyCamera should have been retargeted to an offscreen image"
);
}
#[test]
fn frame_mode_boots_too() {
let config = KittyConfig {
mode: KittyMode::Frame,
..headless_config()
};
let mut app = test_app(config);
app.world_mut().spawn((Camera2d, KittyCamera));
app.update();
}
#[cfg(feature = "ui")]
#[test]
fn the_kitty_camera_claims_bevy_uis_default_camera_slot() {
let mut app = test_app(headless_config());
let cam = app.world_mut().spawn((Camera2d, KittyCamera)).id();
app.update();
app.update();
assert!(
app.world()
.entity(cam)
.contains::<bevy::ui::IsDefaultUiCamera>(),
"the KittyCamera must claim bevy_ui's default camera slot, or every \
Val::Percent resolves against a zero viewport"
);
}
#[test]
fn bevy_really_does_put_a_white_image_behind_the_default_handle() {
let mut app = App::new();
app.add_plugins(AssetPlugin::default());
app.add_plugins(bevy::image::ImagePlugin::default());
let images = app.world().resource::<Assets<Image>>();
let default_img = images
.get(&Handle::<Image>::default())
.expect("Bevy should insert an Image at Handle::default()");
assert_eq!(
(default_img.width(), default_img.height()),
(1, 1),
"the default image is a 1x1 placeholder; if this grew, re-check the \
sprite pass's solid-tile dispatch"
);
assert!(
default_img.data.is_some(),
"the default image keeps CPU data, which is exactly why an \
Assets<Image> lookup cannot distinguish it from a real sprite"
);
}