#![cfg(test)]
use super::shape::FreeformShapeWidget;
use super::types::{BubbleTailDirection, PathSegment, ShapePath};
use crate::core::{Color, Point, Rect};
use crate::event::EventHandler;
use crate::widget::{Widget, WidgetKind};
use std::sync::{Arc, Mutex};
#[test]
fn freeform_shape_creation_defaults() {
let shape = FreeformShapeWidget::new(Rect::new(10, 20, 200, 150), ShapePath::Heart);
assert_eq!(shape.path(), &ShapePath::Heart);
assert_eq!(shape.fill_color(), Color::rgb(200, 220, 255));
assert_eq!(shape.stroke_color(), Some(Color::rgb(80, 120, 200)));
assert_eq!(shape.stroke_width(), 2);
assert!(shape.base().is_visible());
assert!(shape.base().is_enabled());
}
#[test]
fn freeform_shape_set_path() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Heart);
let polygon =
ShapePath::Polygon(vec![Point::new(10, 10), Point::new(90, 10), Point::new(50, 90)]);
shape.set_path(polygon.clone());
assert_eq!(shape.path(), &polygon);
}
#[test]
fn freeform_shape_set_fill_color() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Heart);
shape.set_fill_color(Color::rgb(255, 0, 0));
assert_eq!(shape.fill_color(), Color::rgb(255, 0, 0));
}
#[test]
fn freeform_shape_set_stroke_color() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Heart);
shape.set_stroke_color(Some(Color::rgb(0, 255, 0)));
assert_eq!(shape.stroke_color(), Some(Color::rgb(0, 255, 0)));
shape.set_stroke_color(None);
assert_eq!(shape.stroke_color(), None);
}
#[test]
fn freeform_shape_set_stroke_width() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Heart);
shape.set_stroke_width(5);
assert_eq!(shape.stroke_width(), 5);
}
#[test]
fn freeform_shape_contains_heart_center() {
let shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Heart);
assert!(shape.contains(Point::new(50, 50)));
}
#[test]
fn freeform_shape_contains_heart_outside_rect() {
let shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Heart);
assert!(!shape.contains(Point::new(150, 150)));
}
#[test]
fn freeform_shape_contains_polygon_center() {
let triangle =
ShapePath::Polygon(vec![Point::new(0, 0), Point::new(100, 0), Point::new(50, 100)]);
let shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), triangle);
assert!(shape.contains(Point::new(50, 30)));
}
#[test]
fn freeform_shape_contains_rounded_rect() {
let shape =
FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::RoundedRect { radius: 10 });
assert!(shape.contains(Point::new(15, 15)));
assert!(!shape.contains(Point::new(200, 200)));
}
#[test]
fn freeform_shape_contains_zero_dimensions() {
let shape = FreeformShapeWidget::new(Rect::new(0, 0, 0, 0), ShapePath::Heart);
assert!(!shape.contains(Point::new(0, 0)));
}
#[test]
fn freeform_shape_rounded_rect_zero_radius_equal_rect() {
let shape =
FreeformShapeWidget::new(Rect::new(0, 0, 50, 50), ShapePath::RoundedRect { radius: 0 });
assert!(shape.contains(Point::new(25, 25)));
assert!(!shape.contains(Point::new(-1, 25)));
}
#[test]
fn freeform_shape_bubble_contains_body() {
let shape = FreeformShapeWidget::new(
Rect::new(10, 10, 100, 80),
ShapePath::Bubble { tail_direction: BubbleTailDirection::Right },
);
assert!(shape.contains(Point::new(60, 50)));
}
#[test]
fn freeform_shape_star_contains_center() {
let shape = FreeformShapeWidget::new(
Rect::new(0, 0, 100, 100),
ShapePath::Star { points: 5, inner_radius: 0.4 },
);
assert!(shape.contains(Point::new(50, 50)));
}
#[test]
fn freeform_shape_custom_path_empty() {
let shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Custom(vec![]));
assert!(!shape.contains(Point::new(50, 50)));
}
#[test]
fn freeform_shape_custom_path_triangle() {
let segs = vec![
PathSegment::MoveTo(Point::new(0, 0)),
PathSegment::LineTo(Point::new(100, 0)),
PathSegment::LineTo(Point::new(50, 100)),
PathSegment::Close,
];
let shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Custom(segs));
assert!(shape.contains(Point::new(50, 30)));
assert!(!shape.contains(Point::new(200, 200)));
}
#[test]
fn freeform_shape_geometry_delegation() {
let rect = Rect::new(5, 10, 300, 200);
let shape = FreeformShapeWidget::new(rect, ShapePath::Heart);
assert_eq!(shape.geometry(), rect);
assert_eq!(shape.position(), Point::new(5, 10));
assert_eq!(shape.size(), crate::core::Size::new(300, 200));
}
#[test]
fn freeform_shape_visibility() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Heart);
assert!(shape.is_visible());
shape.set_visible(false);
assert!(!shape.is_visible());
shape.set_visible(true);
assert!(shape.is_visible());
}
#[test]
fn freeform_shape_enabled_state() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Heart);
assert!(shape.is_enabled());
shape.set_enabled(false);
assert!(!shape.is_enabled());
}
#[test]
fn freeform_shape_kind() {
let shape = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Heart);
assert_eq!(shape.kind(), WidgetKind::FreeformShape);
}
#[test]
fn freeform_shape_ids_are_unique() {
let a = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Heart);
let b = FreeformShapeWidget::new(Rect::new(0, 0, 100, 100), ShapePath::Heart);
assert_ne!(a.id(), b.id());
}
#[test]
fn freeform_shape_clicked_signal_emits_on_click() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 200, 200), ShapePath::Heart);
let count = Arc::new(Mutex::new(0u32));
let sink = count.clone();
shape.clicked.connect(move || {
if let Ok(mut c) = sink.lock() {
*c += 1;
}
});
let press = crate::event::Event::MousePress { pos: Point::new(100, 100), button: 1 };
shape.handle_event(&press);
let release = crate::event::Event::MouseRelease { pos: Point::new(100, 100), button: 1 };
shape.handle_event(&release);
let got = *count.lock().unwrap();
assert_eq!(got, 1, "clicked signal should fire once on press+release inside");
}
#[test]
fn freeform_shape_click_outside_no_signal() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 200, 200), ShapePath::Heart);
let count = Arc::new(Mutex::new(0u32));
let sink = count.clone();
shape.clicked.connect(move || {
if let Ok(mut c) = sink.lock() {
*c += 1;
}
});
let press = crate::event::Event::MousePress { pos: Point::new(999, 999), button: 1 };
shape.handle_event(&press);
let release = crate::event::Event::MouseRelease { pos: Point::new(999, 999), button: 1 };
shape.handle_event(&release);
let got = *count.lock().unwrap();
assert_eq!(got, 0, "click should not fire for outside press+release");
}
#[test]
fn freeform_shape_hovered_signal_emits_on_mouse_move() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 200, 200), ShapePath::Heart);
let states = Arc::new(Mutex::new(Vec::<bool>::new()));
let sink = states.clone();
shape.hovered_changed.connect(move |h| {
if let Ok(mut s) = sink.lock() {
s.push(*h);
}
});
let ev = crate::event::Event::MouseMove { pos: Point::new(100, 100) };
shape.handle_event(&ev);
let ev2 = crate::event::Event::MouseMove { pos: Point::new(999, 999) };
shape.handle_event(&ev2);
let got = states.lock().ok().map(|g| g.clone()).unwrap_or_default();
assert_eq!(got, vec![true, false], "hovered_changed should fire true then false");
}
#[test]
fn freeform_shape_pressed_signal_emits_on_press() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 200, 200), ShapePath::Heart);
let states = Arc::new(Mutex::new(Vec::<bool>::new()));
let sink = states.clone();
shape.pressed_changed.connect(move |p| {
if let Ok(mut s) = sink.lock() {
s.push(*p);
}
});
let press = crate::event::Event::MousePress { pos: Point::new(100, 100), button: 1 };
shape.handle_event(&press);
let release = crate::event::Event::MouseRelease { pos: Point::new(100, 100), button: 1 };
shape.handle_event(&release);
let got = states.lock().ok().map(|g| g.clone()).unwrap_or_default();
assert_eq!(got, vec![true, false], "pressed_changed should fire true then false");
}
#[test]
fn freeform_shape_disabled_ignores_events() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 200, 200), ShapePath::Heart);
shape.set_enabled(false);
let count = Arc::new(Mutex::new(0u32));
let sink = count.clone();
shape.clicked.connect(move || {
if let Ok(mut c) = sink.lock() {
*c += 1;
}
});
let press = crate::event::Event::MousePress { pos: Point::new(100, 100), button: 1 };
shape.handle_event(&press);
let release = crate::event::Event::MouseRelease { pos: Point::new(100, 100), button: 1 };
shape.handle_event(&release);
let got = *count.lock().unwrap();
assert_eq!(got, 0, "disabled widget should not process events");
}
#[test]
fn freeform_shape_mouse_enter_and_leave() {
let mut shape = FreeformShapeWidget::new(Rect::new(0, 0, 200, 200), ShapePath::Heart);
let states = Arc::new(Mutex::new(Vec::<bool>::new()));
let sink = states.clone();
shape.hovered_changed.connect(move |h| {
if let Ok(mut s) = sink.lock() {
s.push(*h);
}
});
let enter = crate::event::Event::MouseEnter { pos: Point::new(100, 100) };
shape.handle_event(&enter);
let leave = crate::event::Event::MouseLeave { pos: Point::new(100, 100) };
shape.handle_event(&leave);
let got = states.lock().ok().map(|g| g.clone()).unwrap_or_default();
assert_eq!(got, vec![true, false], "MouseEnter/MouseLeave should fire hovered_changed");
}
#[test]
fn freeform_shape_bubble_tail_directions() {
for dir in &[
BubbleTailDirection::TopLeft,
BubbleTailDirection::TopRight,
BubbleTailDirection::BottomLeft,
BubbleTailDirection::BottomRight,
BubbleTailDirection::Left,
BubbleTailDirection::Right,
BubbleTailDirection::Top,
BubbleTailDirection::Bottom,
] {
let shape = FreeformShapeWidget::new(
Rect::new(0, 0, 80, 60),
ShapePath::Bubble { tail_direction: *dir },
);
assert!(shape.contains(Point::new(40, 30)), "center should be inside for {:?}", dir);
}
}
#[test]
fn freeform_shape_polygon_less_than_3_vertices() {
let shape = FreeformShapeWidget::new(
Rect::new(0, 0, 100, 100),
ShapePath::Polygon(vec![Point::new(0, 0), Point::new(100, 0)]),
);
assert!(!shape.contains(Point::new(50, 25)));
}
#[test]
fn freeform_shape_star_few_points() {
let shape = FreeformShapeWidget::new(
Rect::new(0, 0, 100, 100),
ShapePath::Star { points: 3, inner_radius: 0.5 },
);
assert!(shape.contains(Point::new(50, 50)));
}
#[test]
fn freeform_shape_path_enum_variants() {
let paths = [
ShapePath::Heart,
ShapePath::Star { points: 5, inner_radius: 0.4 },
ShapePath::Polygon(vec![Point::new(0, 0), Point::new(50, 0), Point::new(25, 50)]),
ShapePath::RoundedRect { radius: 8 },
ShapePath::Bubble { tail_direction: BubbleTailDirection::Top },
ShapePath::Custom(vec![
PathSegment::MoveTo(Point::new(0, 0)),
PathSegment::LineTo(Point::new(100, 0)),
PathSegment::Close,
]),
];
for i in 0..paths.len() {
for j in (i + 1)..paths.len() {
assert_ne!(paths[i], paths[j], "path variant {} should differ from {}", i, j);
}
}
}