Skip to content

Commit 2fc4896

Browse files
Add tests for the Freehand tool (#2599)
* Add tests for freehand tool * add test: line weight affects stroke width * refactor
1 parent 1a81e45 commit 2fc4896

File tree

2 files changed

+405
-0
lines changed

2 files changed

+405
-0
lines changed

editor/src/messages/tool/tool_messages/freehand_tool.rs

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,3 +340,383 @@ fn extend_path_with_next_segment(tool_data: &mut FreehandToolData, position: DVe
340340
tool_data.dragged = true;
341341
tool_data.end_point = Some((position, id));
342342
}
343+
344+
#[cfg(test)]
345+
mod test_freehand {
346+
use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, MouseKeys, ScrollDelta};
347+
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
348+
use crate::messages::tool::common_functionality::graph_modification_utils::get_stroke_width;
349+
use crate::messages::tool::tool_messages::freehand_tool::FreehandOptionsUpdate;
350+
use crate::test_utils::test_prelude::*;
351+
use glam::{DAffine2, DVec2};
352+
use graphene_core::vector::VectorData;
353+
354+
async fn get_vector_data(editor: &mut EditorTestUtils) -> Vec<(VectorData, DAffine2)> {
355+
let document = editor.active_document();
356+
let layers = document.metadata().all_layers();
357+
358+
layers
359+
.filter_map(|layer| {
360+
let vector_data = document.network_interface.compute_modified_vector(layer)?;
361+
let transform = document.metadata().transform_to_viewport(layer);
362+
Some((vector_data, transform))
363+
})
364+
.collect()
365+
}
366+
367+
fn verify_path_points(vector_data_list: &[(VectorData, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> {
368+
if vector_data_list.len() == 0 {
369+
return Err("No vector data found after drawing".to_string());
370+
}
371+
372+
let path_data = vector_data_list.iter().find(|(data, _)| data.point_domain.ids().len() > 0).ok_or("Could not find path data")?;
373+
374+
let (vector_data, transform) = path_data;
375+
let point_count = vector_data.point_domain.ids().len();
376+
let segment_count = vector_data.segment_domain.ids().len();
377+
378+
let actual_positions: Vec<DVec2> = vector_data
379+
.point_domain
380+
.ids()
381+
.iter()
382+
.filter_map(|&point_id| {
383+
let position = vector_data.point_domain.position_from_id(point_id)?;
384+
Some(transform.transform_point2(position))
385+
})
386+
.collect();
387+
388+
if segment_count != point_count - 1 {
389+
return Err(format!("Expected segments to be one less than points, got {} segments for {} points", segment_count, point_count));
390+
}
391+
392+
if point_count != expected_captured_points.len() {
393+
return Err(format!("Expected {} points, got {}", expected_captured_points.len(), point_count));
394+
}
395+
396+
for (i, (&expected, &actual)) in expected_captured_points.iter().zip(actual_positions.iter()).enumerate() {
397+
let distance = (expected - actual).length();
398+
if distance >= tolerance {
399+
return Err(format!("Point {} position mismatch: expected {:?}, got {:?} (distance: {})", i, expected, actual, distance));
400+
}
401+
}
402+
403+
Ok(())
404+
}
405+
406+
#[tokio::test]
407+
async fn test_freehand_transformed_artboard() {
408+
let mut editor = EditorTestUtils::create();
409+
editor.new_document().await;
410+
411+
editor.drag_tool(ToolType::Artboard, 0., 0., 500., 500., ModifierKeys::empty()).await;
412+
413+
let metadata = editor.active_document().metadata();
414+
let artboard = metadata.all_layers().next().unwrap();
415+
416+
editor
417+
.handle_message(GraphOperationMessage::TransformSet {
418+
layer: artboard,
419+
transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10.0, -5.0)),
420+
transform_in: TransformIn::Local,
421+
skip_rerender: false,
422+
})
423+
.await;
424+
425+
editor.select_tool(ToolType::Freehand).await;
426+
427+
let mouse_points = [DVec2::new(150.0, 100.0), DVec2::new(200.0, 150.0), DVec2::new(250.0, 130.0), DVec2::new(300.0, 170.0)];
428+
429+
// Expected points that will actually be captured by the tool
430+
let expected_captured_points = &mouse_points[1..];
431+
editor.drag_path(&mouse_points, ModifierKeys::empty()).await;
432+
433+
let vector_data_list = get_vector_data(&mut editor).await;
434+
verify_path_points(&vector_data_list, expected_captured_points, 1.0).expect("Path points verification failed");
435+
}
436+
437+
#[tokio::test]
438+
async fn test_extend_existing_path() {
439+
let mut editor = EditorTestUtils::create();
440+
editor.new_document().await;
441+
442+
let initial_points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)];
443+
444+
editor.select_tool(ToolType::Freehand).await;
445+
446+
let first_point = initial_points[0];
447+
editor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;
448+
editor.left_mousedown(first_point.x, first_point.y, ModifierKeys::empty()).await;
449+
450+
for &point in &initial_points[1..] {
451+
editor.move_mouse(point.x, point.y, ModifierKeys::empty(), MouseKeys::LEFT).await;
452+
}
453+
454+
let last_initial_point = initial_points[initial_points.len() - 1];
455+
editor
456+
.mouseup(
457+
EditorMouseState {
458+
editor_position: last_initial_point,
459+
mouse_keys: MouseKeys::empty(),
460+
scroll_delta: ScrollDelta::default(),
461+
},
462+
ModifierKeys::empty(),
463+
)
464+
.await;
465+
466+
let initial_vector_data = get_vector_data(&mut editor).await;
467+
assert!(!initial_vector_data.is_empty(), "No vector data found after initial drawing");
468+
469+
let (initial_data, transform) = &initial_vector_data[0];
470+
let initial_point_count = initial_data.point_domain.ids().len();
471+
let initial_segment_count = initial_data.segment_domain.ids().len();
472+
473+
assert!(initial_point_count >= 2, "Expected at least 2 points in initial path, found {}", initial_point_count);
474+
assert_eq!(
475+
initial_segment_count,
476+
initial_point_count - 1,
477+
"Expected {} segments in initial path, found {}",
478+
initial_point_count - 1,
479+
initial_segment_count
480+
);
481+
482+
let extendable_points = initial_data.extendable_points(false).collect::<Vec<_>>();
483+
assert!(!extendable_points.is_empty(), "No extendable points found in the path");
484+
485+
let endpoint_id = extendable_points[0];
486+
let endpoint_pos_option = initial_data.point_domain.position_from_id(endpoint_id);
487+
assert!(endpoint_pos_option.is_some(), "Could not find position for endpoint");
488+
489+
let endpoint_pos = endpoint_pos_option.unwrap();
490+
let endpoint_viewport_pos = transform.transform_point2(endpoint_pos);
491+
492+
assert!(endpoint_viewport_pos.is_finite(), "Endpoint position is not finite");
493+
494+
let extension_points = [DVec2::new(400.0, 200.0), DVec2::new(500.0, 100.0)];
495+
496+
let layer_node_id = {
497+
let document = editor.active_document();
498+
let layer = document.metadata().all_layers().next().unwrap();
499+
layer.to_node()
500+
};
501+
502+
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer_node_id] }).await;
503+
504+
editor.select_tool(ToolType::Freehand).await;
505+
506+
editor.move_mouse(endpoint_viewport_pos.x, endpoint_viewport_pos.y, ModifierKeys::empty(), MouseKeys::empty()).await;
507+
editor.left_mousedown(endpoint_viewport_pos.x, endpoint_viewport_pos.y, ModifierKeys::empty()).await;
508+
509+
for &point in &extension_points {
510+
editor.move_mouse(point.x, point.y, ModifierKeys::empty(), MouseKeys::LEFT).await;
511+
}
512+
513+
let last_extension_point = extension_points[extension_points.len() - 1];
514+
editor
515+
.mouseup(
516+
EditorMouseState {
517+
editor_position: last_extension_point,
518+
mouse_keys: MouseKeys::empty(),
519+
scroll_delta: ScrollDelta::default(),
520+
},
521+
ModifierKeys::empty(),
522+
)
523+
.await;
524+
525+
let extended_vector_data = get_vector_data(&mut editor).await;
526+
assert!(!extended_vector_data.is_empty(), "No vector data found after extension");
527+
528+
let (extended_data, _) = &extended_vector_data[0];
529+
let extended_point_count = extended_data.point_domain.ids().len();
530+
let extended_segment_count = extended_data.segment_domain.ids().len();
531+
532+
assert!(
533+
extended_point_count > initial_point_count,
534+
"Expected more points after extension, initial: {}, after extension: {}",
535+
initial_point_count,
536+
extended_point_count
537+
);
538+
539+
assert_eq!(
540+
extended_segment_count,
541+
extended_point_count - 1,
542+
"Expected segments to be one less than points, points: {}, segments: {}",
543+
extended_point_count,
544+
extended_segment_count
545+
);
546+
547+
let layer_count = {
548+
let document = editor.active_document();
549+
document.metadata().all_layers().count()
550+
};
551+
assert_eq!(layer_count, 1, "Expected only one layer after extending path");
552+
}
553+
554+
#[tokio::test]
555+
async fn test_append_to_selected_layer_with_shift() {
556+
let mut editor = EditorTestUtils::create();
557+
editor.new_document().await;
558+
559+
editor.select_tool(ToolType::Freehand).await;
560+
561+
let initial_points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)];
562+
563+
let first_point = initial_points[0];
564+
editor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;
565+
editor.left_mousedown(first_point.x, first_point.y, ModifierKeys::empty()).await;
566+
567+
for &point in &initial_points[1..] {
568+
editor.move_mouse(point.x, point.y, ModifierKeys::empty(), MouseKeys::LEFT).await;
569+
}
570+
571+
let last_initial_point = initial_points[initial_points.len() - 1];
572+
editor
573+
.mouseup(
574+
EditorMouseState {
575+
editor_position: last_initial_point,
576+
mouse_keys: MouseKeys::empty(),
577+
scroll_delta: ScrollDelta::default(),
578+
},
579+
ModifierKeys::empty(),
580+
)
581+
.await;
582+
583+
let initial_vector_data = get_vector_data(&mut editor).await;
584+
assert!(!initial_vector_data.is_empty(), "No vector data found after initial drawing");
585+
586+
let (initial_data, _) = &initial_vector_data[0];
587+
let initial_point_count = initial_data.point_domain.ids().len();
588+
let initial_segment_count = initial_data.segment_domain.ids().len();
589+
590+
let existing_layer_id = {
591+
let document = editor.active_document();
592+
let layer = document.metadata().all_layers().next().unwrap();
593+
layer
594+
};
595+
596+
editor
597+
.handle_message(NodeGraphMessage::SelectedNodesSet {
598+
nodes: vec![existing_layer_id.to_node()],
599+
})
600+
.await;
601+
602+
let second_path_points = [DVec2::new(400.0, 100.0), DVec2::new(500.0, 200.0), DVec2::new(600.0, 100.0)];
603+
604+
let first_second_point = second_path_points[0];
605+
editor.move_mouse(first_second_point.x, first_second_point.y, ModifierKeys::SHIFT, MouseKeys::empty()).await;
606+
607+
editor
608+
.mousedown(
609+
EditorMouseState {
610+
editor_position: first_second_point,
611+
mouse_keys: MouseKeys::LEFT,
612+
scroll_delta: ScrollDelta::default(),
613+
},
614+
ModifierKeys::SHIFT,
615+
)
616+
.await;
617+
618+
for &point in &second_path_points[1..] {
619+
editor.move_mouse(point.x, point.y, ModifierKeys::SHIFT, MouseKeys::LEFT).await;
620+
}
621+
622+
let last_second_point = second_path_points[second_path_points.len() - 1];
623+
editor
624+
.mouseup(
625+
EditorMouseState {
626+
editor_position: last_second_point,
627+
mouse_keys: MouseKeys::empty(),
628+
scroll_delta: ScrollDelta::default(),
629+
},
630+
ModifierKeys::SHIFT,
631+
)
632+
.await;
633+
634+
let final_vector_data = get_vector_data(&mut editor).await;
635+
assert!(!final_vector_data.is_empty(), "No vector data found after second drawing");
636+
637+
// Verify we still have only one layer
638+
let layer_count = {
639+
let document = editor.active_document();
640+
document.metadata().all_layers().count()
641+
};
642+
assert_eq!(layer_count, 1, "Expected only one layer after drawing with Shift key");
643+
644+
let (final_data, _) = &final_vector_data[0];
645+
let final_point_count = final_data.point_domain.ids().len();
646+
let final_segment_count = final_data.segment_domain.ids().len();
647+
648+
assert!(
649+
final_point_count > initial_point_count,
650+
"Expected more points after appending to layer, initial: {}, after append: {}",
651+
initial_point_count,
652+
final_point_count
653+
);
654+
655+
let expected_new_points = second_path_points.len();
656+
let expected_new_segments = expected_new_points - 1;
657+
658+
assert_eq!(
659+
final_point_count,
660+
initial_point_count + expected_new_points,
661+
"Expected {} total points after append",
662+
initial_point_count + expected_new_points
663+
);
664+
665+
assert_eq!(
666+
final_segment_count,
667+
initial_segment_count + expected_new_segments,
668+
"Expected {} total segments after append",
669+
initial_segment_count + expected_new_segments
670+
);
671+
}
672+
673+
#[tokio::test]
674+
async fn test_line_weight_affects_stroke_width() {
675+
let mut editor = EditorTestUtils::create();
676+
editor.new_document().await;
677+
678+
editor.select_tool(ToolType::Freehand).await;
679+
680+
let custom_line_weight = 5.0;
681+
editor
682+
.handle_message(ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::LineWeight(custom_line_weight))))
683+
.await;
684+
685+
let points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)];
686+
687+
let first_point = points[0];
688+
editor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;
689+
editor.left_mousedown(first_point.x, first_point.y, ModifierKeys::empty()).await;
690+
691+
for &point in &points[1..] {
692+
editor.move_mouse(point.x, point.y, ModifierKeys::empty(), MouseKeys::LEFT).await;
693+
}
694+
695+
let last_point = points[points.len() - 1];
696+
editor
697+
.mouseup(
698+
EditorMouseState {
699+
editor_position: last_point,
700+
mouse_keys: MouseKeys::empty(),
701+
scroll_delta: ScrollDelta::default(),
702+
},
703+
ModifierKeys::empty(),
704+
)
705+
.await;
706+
707+
let document = editor.active_document();
708+
let layer = document.metadata().all_layers().next().unwrap();
709+
710+
let stroke_width = get_stroke_width(layer, &document.network_interface);
711+
712+
assert!(stroke_width.is_some(), "Stroke width should be available on the created path");
713+
714+
assert_eq!(
715+
stroke_width.unwrap(),
716+
custom_line_weight,
717+
"Stroke width should match the custom line weight (expected {}, got {})",
718+
custom_line_weight,
719+
stroke_width.unwrap()
720+
);
721+
}
722+
}

0 commit comments

Comments
 (0)