Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -16,3 +16,5 @@ egui = "0.34"
|
||||
rstest = "0.23"
|
||||
proptest = "1.5"
|
||||
approx = "0.5"
|
||||
proptest = "1.5"
|
||||
approx = "0.5"
|
||||
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,241 @@
|
||||
use hcie_protocol::tools::{TextAlignment, TextOrientation, TextEffect};
|
||||
use hcie_text::TextRenderer;
|
||||
|
||||
/// Integration tests for hcie-text text rendering.
|
||||
///
|
||||
/// Covers: font loading, font management, rasterization baseline,
|
||||
/// error handling for missing fonts, fallback behavior,
|
||||
/// create_text_layer and refresh_text_layer.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: load a built-in font for testing
|
||||
// ---------------------------------------------------------------------------
|
||||
fn setup_renderer() -> (TextRenderer, bool) {
|
||||
let mut renderer = TextRenderer::new();
|
||||
let font_data = include_bytes!("../tests/fixtures/Inter-Regular.ttf");
|
||||
let ok = renderer.load_font("Inter", font_data).is_ok();
|
||||
(renderer, ok)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Font management
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn new_renderer_has_no_fonts() {
|
||||
let renderer = TextRenderer::new();
|
||||
assert!(renderer.get_fonts().is_empty(), "new renderer should have no fonts");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_font_succeeds_with_valid_data() {
|
||||
let mut renderer = TextRenderer::new();
|
||||
// embed a small TTF for testing — must exist at this path
|
||||
let font_data = include_bytes!("../tests/fixtures/Inter-Regular.ttf");
|
||||
let result = renderer.load_font("Inter", font_data);
|
||||
assert!(result.is_ok(), "loading a valid TTF should succeed: {:?}", result.err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_font_fails_with_invalid_data() {
|
||||
let mut renderer = TextRenderer::new();
|
||||
let result = renderer.load_font("Invalid", &[0u8, 1, 2, 3]);
|
||||
assert!(result.is_err(), "loading garbage data as a font should fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_font_returns_true_after_loading() {
|
||||
let mut renderer = TextRenderer::new();
|
||||
let font_data = include_bytes!("../tests/fixtures/Inter-Regular.ttf");
|
||||
if renderer.load_font("Inter", font_data).is_ok() {
|
||||
assert!(renderer.has_font("Inter"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_font_returns_false_for_unloaded() {
|
||||
let renderer = TextRenderer::new();
|
||||
assert!(!renderer.has_font("NonexistentFont"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_fonts_returns_loaded_names() {
|
||||
let mut renderer = TextRenderer::new();
|
||||
let font_data = include_bytes!("../tests/fixtures/Inter-Regular.ttf");
|
||||
if renderer.load_font("MyFont", font_data).is_ok() {
|
||||
let fonts = renderer.get_fonts();
|
||||
assert!(fonts.contains(&"MyFont".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Text rasterization (baseline — if font is available)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn rasterize_text_returns_valid_output() {
|
||||
let (mut renderer, font_ok) = setup_renderer();
|
||||
if !font_ok {
|
||||
eprintln!("Skipping: test font not available");
|
||||
return;
|
||||
}
|
||||
|
||||
let result = renderer.rasterize_text(
|
||||
"Hello",
|
||||
"Inter",
|
||||
24.0,
|
||||
[0, 0, 0, 255], // black
|
||||
0.0, 0.0, // x, y
|
||||
0.0, // angle
|
||||
TextAlignment::Left,
|
||||
TextOrientation::Horizontal,
|
||||
&[], // no effects
|
||||
true, // anti-alias
|
||||
);
|
||||
|
||||
assert!(result.is_ok(), "rasterize should succeed: {:?}", result.err());
|
||||
|
||||
let (pixels, w, h, _ox, _oy, _uw, _uh) = result.unwrap();
|
||||
assert!(w > 0, "rasterized width should be > 0");
|
||||
assert!(h > 0, "rasterized height should be > 0");
|
||||
assert_eq!(pixels.len(), (w * h * 4) as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rasterize_empty_string_produces_no_pixels_or_minimal() {
|
||||
let (mut renderer, font_ok) = setup_renderer();
|
||||
if !font_ok { return; }
|
||||
|
||||
let result = renderer.rasterize_text(
|
||||
"", "Inter", 24.0, [0, 0, 0, 255],
|
||||
0.0, 0.0, 0.0, TextAlignment::Left,
|
||||
TextOrientation::Horizontal, &[], true,
|
||||
);
|
||||
|
||||
// Empty string may return an error or a 0-size buffer; either is acceptable
|
||||
match result {
|
||||
Ok((pixels, w, h, _, _, _, _)) => {
|
||||
assert!(w == 0 || h == 0 || pixels.is_empty(),
|
||||
"empty string should produce trivial output: {}x{}", w, h);
|
||||
}
|
||||
Err(_) => { /* empty string may legitimately fail */ }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rasterize_returns_different_output_for_different_sizes() {
|
||||
let (mut renderer, font_ok) = setup_renderer();
|
||||
if !font_ok { return; }
|
||||
|
||||
let small = renderer.rasterize_text(
|
||||
"A", "Inter", 12.0, [0, 0, 0, 255],
|
||||
0.0, 0.0, 0.0, TextAlignment::Left,
|
||||
TextOrientation::Horizontal, &[], true,
|
||||
).unwrap();
|
||||
let large = renderer.rasterize_text(
|
||||
"A", "Inter", 48.0, [0, 0, 0, 255],
|
||||
0.0, 0.0, 0.0, TextAlignment::Left,
|
||||
TextOrientation::Horizontal, &[], true,
|
||||
).unwrap();
|
||||
|
||||
// Larger font size should produce taller output
|
||||
assert!(large.2 >= small.2, "larger font should produce taller output");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Color handling
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn rasterize_text_applies_color() {
|
||||
let (mut renderer, font_ok) = setup_renderer();
|
||||
if !font_ok { return; }
|
||||
|
||||
let result = renderer.rasterize_text(
|
||||
"X", "Inter", 24.0, [255, 0, 0, 255], // red
|
||||
0.0, 0.0, 0.0, TextAlignment::Left,
|
||||
TextOrientation::Horizontal, &[], true,
|
||||
);
|
||||
|
||||
if let Ok((pixels, w, h, _, _, _, _)) = result {
|
||||
if w > 0 && h > 0 {
|
||||
// At least some pixels should have non-zero red channel
|
||||
let has_red = pixels.iter().step_by(4).any(|&r| r > 0);
|
||||
assert!(has_red, "text rasterized in red should have non-zero R channel");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// create_text_layer
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn create_text_layer_produces_valid_layer() {
|
||||
let (mut renderer, font_ok) = setup_renderer();
|
||||
if !font_ok { return; }
|
||||
|
||||
let result = renderer.create_text_layer(
|
||||
"Hello", "Inter", 24.0, [0, 0, 0, 255],
|
||||
10.0, 20.0, 0.0, TextAlignment::Left,
|
||||
TextOrientation::Horizontal, &[],
|
||||
);
|
||||
|
||||
assert!(result.is_ok(), "create_text_layer should succeed: {:?}", result.err());
|
||||
let layer = result.unwrap();
|
||||
assert!(layer.width > 0);
|
||||
assert!(layer.height > 0);
|
||||
assert!(layer.pixels.len() >= 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_text_layer_fails_for_missing_font() {
|
||||
let mut renderer = TextRenderer::new();
|
||||
let result = renderer.create_text_layer(
|
||||
"Hello", "NonExistentFont", 24.0, [0, 0, 0, 255],
|
||||
0.0, 0.0, 0.0, TextAlignment::Left,
|
||||
TextOrientation::Horizontal, &[],
|
||||
);
|
||||
assert!(result.is_err(), "should fail when font is not loaded");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Error handling
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn rasterize_fails_for_missing_font() {
|
||||
let mut renderer = TextRenderer::new();
|
||||
let result = renderer.rasterize_text(
|
||||
"Hi", "AbsentFont", 12.0, [0; 4],
|
||||
0.0, 0.0, 0.0, TextAlignment::Left,
|
||||
TextOrientation::Horizontal, &[], true,
|
||||
);
|
||||
assert!(result.is_err(), "rasterize with missing font should fail");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// refresh_text_layer
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn refresh_text_layer_does_not_panic() {
|
||||
let (mut renderer, font_ok) = setup_renderer();
|
||||
if !font_ok { return; }
|
||||
|
||||
let mut layer = match renderer.create_text_layer(
|
||||
"Test", "Inter", 16.0, [0, 0, 0, 255],
|
||||
0.0, 0.0, 0.0, TextAlignment::Left,
|
||||
TextOrientation::Horizontal, &[],
|
||||
) {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
eprintln!("Skipping refresh test: cannot create layer: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let result = renderer.refresh_text_layer(&mut layer);
|
||||
assert!(result.is_ok() || result.is_err());
|
||||
// Either outcome is valid depending on the layer's state
|
||||
}
|
||||
Reference in New Issue
Block a user