Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions editor/src/messages/tool/common_functionality/shapes/grid_shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,58 @@ fn calculate_isometric_x_position(y_spacing: f64, rad_a: f64, rad_b: f64) -> f64
let spacing_x = y_spacing / (rad_a.tan() + rad_b.tan());
spacing_x * 9.
}
#[cfg(test)]
mod tests {
use super::calculate_grid_params;
use glam::DVec2;

#[test]
fn grid_params_basic_rectangle() {
// Simple downward-right drag: translation = start, dimensions = raw/9, no angle
let (translation, dimensions, angle) = calculate_grid_params(DVec2::ZERO, DVec2::new(90., 90.), false, false, false);
assert_eq!(translation, DVec2::ZERO);
assert_eq!(dimensions, DVec2::splat(10.)); // 90/9 = 10
assert!(angle.is_none());
}

#[test]
fn grid_params_lock_ratio_forces_square_spacing() {
// Non-square drag (90x45) with lock_ratio: uses larger dim (90), dimensions = 90/9 = 10
let (_, dimensions, angle) = calculate_grid_params(DVec2::ZERO, DVec2::new(90., 45.), false, false, true);
assert_eq!(dimensions, DVec2::splat(10.));
assert!(angle.is_none());
}

#[test]
fn grid_params_center_doubles_dimensions_and_shifts_translation() {
// Center draw: dimensions doubled, translation shifted back by raw_dimensions
let (translation, dimensions, angle) = calculate_grid_params(DVec2::ZERO, DVec2::new(90., 90.), false, true, false);
assert_eq!(translation, DVec2::splat(-90.));
assert_eq!(dimensions, DVec2::splat(20.)); // 2 * 90/9 = 20
assert!(angle.is_none());
}

#[test]
fn grid_params_negative_drag_adjusts_translation() {
// Drag up-left from (100,100) to (10,10): translation must shift to (10,10)
let (translation, dimensions, angle) = calculate_grid_params(DVec2::splat(100.), DVec2::splat(10.), false, false, false);
assert!((translation.x - 10.).abs() < 1e-10, "Expected translation.x=10, got {}", translation.x);
assert!((translation.y - 10.).abs() < 1e-10, "Expected translation.y=10, got {}", translation.y);
Comment on lines +292 to +293
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For comparing floating-point vectors, it's cleaner to use abs_diff_eq from the glam crate. This allows you to assert the near-equality of the entire DVec2 in a single line, making the test more concise and readable.

Suggested change
assert!((translation.x - 10.).abs() < 1e-10, "Expected translation.x=10, got {}", translation.x);
assert!((translation.y - 10.).abs() < 1e-10, "Expected translation.y=10, got {}", translation.y);
assert!(translation.abs_diff_eq(DVec2::splat(10.), 1e-9), "Expected translation to be close to (10, 10), got {}", translation);

assert_eq!(dimensions, DVec2::splat(10.)); // (90,90)/9
assert!(angle.is_none());
}

#[test]
fn grid_params_isometric_produces_angle() {
// Isometric grid (no lock_ratio): angle is dynamically computed from drag
let (_, _, angle) = calculate_grid_params(DVec2::ZERO, DVec2::new(90., 90.), true, false, false);
assert!(angle.is_some(), "Isometric grid should return an angle");
}

#[test]
fn grid_params_isometric_lock_ratio_fixes_angle_at_30() {
// Isometric + lock_ratio: angle is standardized at 30 degrees
let (_, _, angle) = calculate_grid_params(DVec2::ZERO, DVec2::new(90., 90.), true, false, true);
assert_eq!(angle, Some(30.), "Isometric lock_ratio should fix angle at 30°");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,83 @@ impl Polygon {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}

#[cfg(test)]
mod test_polygon {
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
use crate::test_utils::test_prelude::*;
use graph_craft::document::value::TaggedValue;

/// Reads sides and radius from the first polygon node found in the document.
fn get_polygon_inputs(editor: &EditorTestUtils) -> Option<(u32, f64)> {
let document = editor.active_document();
document.metadata().all_layers().find_map(|layer| {
let inputs = NodeGraphLayer::new(layer, &document.network_interface)
.find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::regular_polygon::IDENTIFIER))?;
let Some(&TaggedValue::U32(sides)) = inputs.get(1).and_then(|i| i.as_value()) else {
return None;
};
let Some(&TaggedValue::F64(radius)) = inputs.get(2).and_then(|i| i.as_value()) else {
return None;
};
Some((sides, radius))
})
}

#[tokio::test]
async fn polygon_draw_simple() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;
editor.drag_tool(ToolType::Shape, 0., 0., 100., 100., ModifierKeys::empty()).await;

assert_eq!(editor.active_document().metadata().all_layers().count(), 1);
let (sides, radius) = get_polygon_inputs(&editor).expect("Polygon node should exist after draw");
assert!(sides >= 3, "Polygon should have at least 3 sides, got {sides}");
assert!((radius - 50.).abs() < 1., "Expected radius ≈ 50 for 100×100 drag, got {radius}");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The tolerance of 1.0 for this floating-point comparison seems quite large. While some tolerance is expected in integration tests due to floating-point arithmetic, a value this high might mask potential inaccuracies. Consider using a smaller tolerance, like 1e-9, to ensure the calculation is as precise as expected. This also applies to other similar assertions in this test module.

Suggested change
assert!((radius - 50.).abs() < 1., "Expected radius ≈ 50 for 100×100 drag, got {radius}");
assert!((radius - 50.).abs() < 1e-9, "Expected radius ≈ 50 for 100×100 drag, got {radius}");

}

#[tokio::test]
async fn polygon_draw_non_square_uses_shorter_dimension() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;
// Drag wider than tall: dimensions = (100, 60), radius = shorter/2 = 30
editor.drag_tool(ToolType::Shape, 0., 0., 100., 60., ModifierKeys::empty()).await;

let (_, radius) = get_polygon_inputs(&editor).expect("Polygon node should exist");
assert!((radius - 30.).abs() < 1., "Expected radius ≈ 30 for 100×60 drag, got {radius}");
}

#[tokio::test]
async fn polygon_draw_shift_lock_ratio() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;
// SHIFT forces equal dimensions — a 100×60 drag becomes 100×100
editor.drag_tool(ToolType::Shape, 0., 0., 100., 60., ModifierKeys::SHIFT).await;

let (_, radius) = get_polygon_inputs(&editor).expect("Polygon node should exist");
assert!((radius - 50.).abs() < 1., "Expected radius ≈ 50 with SHIFT lock ratio on 100×60 drag, got {radius}");
}

#[tokio::test]
async fn polygon_default_six_sides() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test name polygon_default_six_sides is misleading as the test asserts that the default number of sides is 5. To improve clarity and prevent confusion, the test name should be updated to reflect the actual assertion.

Suggested change
async fn polygon_default_six_sides() {
async fn polygon_default_five_sides() {

let mut editor = EditorTestUtils::create();
editor.new_document().await;
editor.drag_tool(ToolType::Shape, 0., 0., 100., 100., ModifierKeys::empty()).await;

let (sides, _) = get_polygon_inputs(&editor).expect("Polygon node should exist");
assert_eq!(sides, 5, "Default polygon should have 5 sides");
}

#[tokio::test]
async fn polygon_cancel_rmb_no_layer() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;
editor.drag_tool_cancel_rmb(ToolType::Shape).await;

assert_eq!(
editor.active_document().metadata().all_layers().count(),
0,
"RMB-cancelled polygon should not create a layer"
);
}
}
98 changes: 98 additions & 0 deletions editor/src/messages/tool/common_functionality/shapes/star_shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,101 @@ impl Star {
}
}
}

#[cfg(test)]
mod test_star {
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeType;
use crate::messages::tool::tool_messages::shape_tool::ShapeOptionsUpdate;
use crate::test_utils::test_prelude::*;
use graph_craft::document::value::TaggedValue;

/// Switch to Star shape type, then manually drag to avoid drag_tool re-selecting and resetting options.
async fn draw_star(editor: &mut EditorTestUtils, x1: f64, y1: f64, x2: f64, y2: f64, modifier_keys: ModifierKeys) {
editor.select_tool(ToolType::Shape).await;
editor
.handle_message(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Star),
})
.await;
editor.move_mouse(x1, y1, modifier_keys, MouseKeys::empty()).await;
editor.left_mousedown(x1, y1, modifier_keys).await;
editor.move_mouse(x2, y2, modifier_keys, MouseKeys::LEFT).await;
editor.left_mouseup(x2, y2, modifier_keys).await;
}

/// Returns (sides, outer_radius, inner_radius) from the first star node in the document.
fn get_star_inputs(editor: &EditorTestUtils) -> Option<(u32, f64, f64)> {
let document = editor.active_document();
document.metadata().all_layers().find_map(|layer| {
let inputs = NodeGraphLayer::new(layer, &document.network_interface)
.find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector_nodes::star::IDENTIFIER))?;
let Some(&TaggedValue::U32(sides)) = inputs.get(1).and_then(|i| i.as_value()) else {
return None;
};
let Some(&TaggedValue::F64(outer_radius)) = inputs.get(2).and_then(|i| i.as_value()) else {
return None;
};
let Some(&TaggedValue::F64(inner_radius)) = inputs.get(3).and_then(|i| i.as_value()) else {
return None;
};
Some((sides, outer_radius, inner_radius))
})
}

#[tokio::test]
async fn star_draw_simple() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;
draw_star(&mut editor, 0., 0., 100., 100., ModifierKeys::empty()).await;

assert_eq!(editor.active_document().metadata().all_layers().count(), 1);
let (sides, outer_radius, _) = get_star_inputs(&editor).expect("Star node should exist after draw");
assert!(sides >= 2, "Star should have at least 2 points, got {sides}");
assert!(outer_radius > 0., "Outer radius should be positive, got {outer_radius}");
}

#[tokio::test]
async fn star_inner_radius_is_half_outer() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;
draw_star(&mut editor, 0., 0., 100., 100., ModifierKeys::empty()).await;

let (_, outer_radius, inner_radius) = get_star_inputs(&editor).expect("Star node should exist");
assert!(
(inner_radius - outer_radius / 2.).abs() < 1e-10,
"Inner radius {inner_radius} should equal outer_radius/2 = {}",
outer_radius / 2.
);
}

#[tokio::test]
async fn star_draw_correct_outer_radius() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;
// 100x100 drag: dimensions=(100,100), x==y, radius = x/2 = 50
draw_star(&mut editor, 0., 0., 100., 100., ModifierKeys::empty()).await;

let (_, outer_radius, _) = get_star_inputs(&editor).expect("Star node should exist");
assert!((outer_radius - 50.).abs() < 1., "Expected outer radius ~50 for 100x100 drag, got {outer_radius}");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The tolerance of 1.0 for this floating-point comparison seems quite large. While some tolerance is expected in integration tests, a value this high might mask potential inaccuracies. Consider using a smaller tolerance, like 1e-9, to ensure the calculation is as precise as expected.

Suggested change
assert!((outer_radius - 50.).abs() < 1., "Expected outer radius ~50 for 100x100 drag, got {outer_radius}");
assert!((outer_radius - 50.).abs() < 1e-9, "Expected outer radius ~50 for 100x100 drag, got {outer_radius}");

}

#[tokio::test]
async fn star_cancel_rmb_no_layer() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;
editor.select_tool(ToolType::Shape).await;
editor
.handle_message(ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::ShapeType(ShapeType::Star),
})
.await;
editor.drag_tool_cancel_rmb(ToolType::Shape).await;

assert_eq!(
editor.active_document().metadata().all_layers().count(),
0,
"RMB-cancelled star should not create a layer"
);
}
}