Skip to content

Commit b171eeb

Browse files
0HyperCubeKeavon
andauthored
Add tests to the Ellipse, Artboard, and Fill tools (#2181)
* Add ellipse tests * Add tests for fill tool and re-enable some other tests * Code review * Fix Rust crate advisory --------- Co-authored-by: Keavon Chambers <[email protected]>
1 parent 1190e82 commit b171eeb

File tree

22 files changed

+1100
-239
lines changed

22 files changed

+1100
-239
lines changed

Cargo.lock

Lines changed: 3 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editor/src/application.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ impl Editor {
1515
Self { dispatcher: Dispatcher::new() }
1616
}
1717

18+
#[cfg(test)]
19+
pub(crate) fn new_local_executor() -> (Self, crate::node_graph_executor::NodeRuntime) {
20+
let (runtime, executor) = crate::node_graph_executor::NodeGraphExecutor::new_with_local_runtime();
21+
let dispatcher = Dispatcher::with_executor(executor);
22+
(Self { dispatcher }, runtime)
23+
}
24+
1825
pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Vec<FrontendMessage> {
1926
self.dispatcher.handle_message(message, true);
2027

editor/src/dispatcher.rs

Lines changed: 91 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ pub struct DispatcherMessageHandlers {
2626
workspace_message_handler: WorkspaceMessageHandler,
2727
}
2828

29+
impl DispatcherMessageHandlers {
30+
pub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {
31+
Self {
32+
portfolio_message_handler: PortfolioMessageHandler::with_executor(executor),
33+
..Default::default()
34+
}
35+
}
36+
}
37+
2938
/// For optimization, these are messages guaranteed to be redundant when repeated.
3039
/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
3140
/// In addition, these messages do not change any state in the backend (aside from caches).
@@ -53,6 +62,13 @@ impl Dispatcher {
5362
Self::default()
5463
}
5564

65+
pub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {
66+
Self {
67+
message_handlers: DispatcherMessageHandlers::with_executor(executor),
68+
..Default::default()
69+
}
70+
}
71+
5672
// If the deepest queues (higher index in queues list) are now empty (after being popped from) then remove them
5773
fn cleanup_queues(&mut self, leave_last: bool) {
5874
while self.message_queues.last().filter(|queue| queue.is_empty()).is_some() {
@@ -328,60 +344,48 @@ impl Dispatcher {
328344

329345
#[cfg(test)]
330346
mod test {
331-
use crate::application::Editor;
332-
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
333-
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
334-
use crate::messages::prelude::*;
335-
use crate::test_utils::EditorTestUtils;
336-
use graphene_core::raster::color::Color;
337-
338-
fn init_logger() {
339-
let _ = env_logger::builder().is_test(true).try_init();
340-
}
347+
pub use crate::test_utils::test_prelude::*;
341348

342349
/// Create an editor with three layers
343350
/// 1. A red rectangle
344351
/// 2. A blue shape
345352
/// 3. A green ellipse
346-
fn create_editor_with_three_layers() -> Editor {
347-
init_logger();
348-
let mut editor = Editor::create();
353+
async fn create_editor_with_three_layers() -> EditorTestUtils {
354+
let mut editor = EditorTestUtils::create();
349355

350-
editor.new_document();
356+
editor.new_document().await;
351357

352-
editor.select_primary_color(Color::RED);
353-
editor.draw_rect(100., 200., 300., 400.);
358+
editor.select_primary_color(Color::RED).await;
359+
editor.draw_rect(100., 200., 300., 400.).await;
354360

355-
editor.select_primary_color(Color::BLUE);
356-
editor.draw_polygon(10., 1200., 1300., 400.);
361+
editor.select_primary_color(Color::BLUE).await;
362+
editor.draw_polygon(10., 1200., 1300., 400.).await;
357363

358-
editor.select_primary_color(Color::GREEN);
359-
editor.draw_ellipse(104., 1200., 1300., 400.);
364+
editor.select_primary_color(Color::GREEN).await;
365+
editor.draw_ellipse(104., 1200., 1300., 400.).await;
360366

361367
editor
362368
}
363369

364-
// TODO: Fix text
365-
#[ignore]
366-
#[test]
367370
/// - create rect, shape and ellipse
368371
/// - copy
369372
/// - paste
370373
/// - assert that ellipse was copied
371-
fn copy_paste_single_layer() {
372-
let mut editor = create_editor_with_three_layers();
374+
#[tokio::test]
375+
async fn copy_paste_single_layer() {
376+
let mut editor = create_editor_with_three_layers().await;
373377

374-
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
375-
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
376-
editor.handle_message(PortfolioMessage::PasteIntoFolder {
377-
clipboard: Clipboard::Internal,
378-
parent: LayerNodeIdentifier::ROOT_PARENT,
379-
insert_index: 0,
380-
});
381-
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
378+
let layers_before_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
379+
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;
380+
editor
381+
.handle_message(PortfolioMessage::PasteIntoFolder {
382+
clipboard: Clipboard::Internal,
383+
parent: LayerNodeIdentifier::ROOT_PARENT,
384+
insert_index: 0,
385+
})
386+
.await;
382387

383-
let layers_before_copy = document_before_copy.metadata().all_layers().collect::<Vec<_>>();
384-
let layers_after_copy = document_after_copy.metadata().all_layers().collect::<Vec<_>>();
388+
let layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
385389

386390
assert_eq!(layers_before_copy.len(), 3);
387391
assert_eq!(layers_after_copy.len(), 4);
@@ -392,33 +396,30 @@ mod test {
392396
}
393397
}
394398

395-
// TODO: Fix text
396-
#[ignore]
397-
#[test]
398399
#[cfg_attr(miri, ignore)]
399400
/// - create rect, shape and ellipse
400401
/// - select shape
401402
/// - copy
402403
/// - paste
403404
/// - assert that shape was copied
404-
fn copy_paste_single_layer_from_middle() {
405-
let mut editor = create_editor_with_three_layers();
406-
407-
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
408-
let shape_id = document_before_copy.metadata().all_layers().nth(1).unwrap();
405+
#[tokio::test]
406+
async fn copy_paste_single_layer_from_middle() {
407+
let mut editor = create_editor_with_three_layers().await;
409408

410-
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![shape_id.to_node()] });
411-
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
412-
editor.handle_message(PortfolioMessage::PasteIntoFolder {
413-
clipboard: Clipboard::Internal,
414-
parent: LayerNodeIdentifier::ROOT_PARENT,
415-
insert_index: 0,
416-
});
409+
let layers_before_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
410+
let shape_id = editor.active_document().metadata().all_layers().nth(1).unwrap();
417411

418-
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
412+
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![shape_id.to_node()] }).await;
413+
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;
414+
editor
415+
.handle_message(PortfolioMessage::PasteIntoFolder {
416+
clipboard: Clipboard::Internal,
417+
parent: LayerNodeIdentifier::ROOT_PARENT,
418+
insert_index: 0,
419+
})
420+
.await;
419421

420-
let layers_before_copy = document_before_copy.metadata().all_layers().collect::<Vec<_>>();
421-
let layers_after_copy = document_after_copy.metadata().all_layers().collect::<Vec<_>>();
422+
let layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
422423

423424
assert_eq!(layers_before_copy.len(), 3);
424425
assert_eq!(layers_after_copy.len(), 4);
@@ -429,9 +430,6 @@ mod test {
429430
}
430431
}
431432

432-
// TODO: Fix text
433-
#[ignore]
434-
#[test]
435433
#[cfg_attr(miri, ignore)]
436434
/// - create rect, shape and ellipse
437435
/// - select ellipse and rect
@@ -440,36 +438,40 @@ mod test {
440438
/// - create another rect
441439
/// - paste
442440
/// - paste
443-
fn copy_paste_deleted_layers() {
444-
let mut editor = create_editor_with_three_layers();
445-
446-
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
447-
let mut layers = document_before_copy.metadata().all_layers();
448-
let rect_id = layers.next().expect("rectangle");
449-
let shape_id = layers.next().expect("shape");
450-
let ellipse_id = layers.next().expect("ellipse");
451-
452-
editor.handle_message(NodeGraphMessage::SelectedNodesSet {
453-
nodes: vec![rect_id.to_node(), ellipse_id.to_node()],
454-
});
455-
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
456-
editor.handle_message(NodeGraphMessage::DeleteSelectedNodes { delete_children: true });
457-
editor.draw_rect(0., 800., 12., 200.);
458-
editor.handle_message(PortfolioMessage::PasteIntoFolder {
459-
clipboard: Clipboard::Internal,
460-
parent: LayerNodeIdentifier::ROOT_PARENT,
461-
insert_index: 0,
462-
});
463-
editor.handle_message(PortfolioMessage::PasteIntoFolder {
464-
clipboard: Clipboard::Internal,
465-
parent: LayerNodeIdentifier::ROOT_PARENT,
466-
insert_index: 0,
467-
});
468-
469-
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
470-
471-
let layers_before_copy = document_before_copy.metadata().all_layers().collect::<Vec<_>>();
472-
let layers_after_copy = document_after_copy.metadata().all_layers().collect::<Vec<_>>();
441+
#[tokio::test]
442+
async fn copy_paste_deleted_layers() {
443+
let mut editor = create_editor_with_three_layers().await;
444+
assert_eq!(editor.active_document().metadata().all_layers().count(), 3);
445+
446+
let layers_before_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
447+
let rect_id = layers_before_copy[0];
448+
let shape_id = layers_before_copy[1];
449+
let ellipse_id = layers_before_copy[2];
450+
451+
editor
452+
.handle_message(NodeGraphMessage::SelectedNodesSet {
453+
nodes: vec![rect_id.to_node(), ellipse_id.to_node()],
454+
})
455+
.await;
456+
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;
457+
editor.handle_message(NodeGraphMessage::DeleteSelectedNodes { delete_children: true }).await;
458+
editor.draw_rect(0., 800., 12., 200.).await;
459+
editor
460+
.handle_message(PortfolioMessage::PasteIntoFolder {
461+
clipboard: Clipboard::Internal,
462+
parent: LayerNodeIdentifier::ROOT_PARENT,
463+
insert_index: 0,
464+
})
465+
.await;
466+
editor
467+
.handle_message(PortfolioMessage::PasteIntoFolder {
468+
clipboard: Clipboard::Internal,
469+
parent: LayerNodeIdentifier::ROOT_PARENT,
470+
insert_index: 0,
471+
})
472+
.await;
473+
474+
let layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
473475

474476
assert_eq!(layers_before_copy.len(), 3);
475477
assert_eq!(layers_after_copy.len(), 6);
@@ -498,8 +500,7 @@ mod test {
498500
panic!()
499501
};
500502

501-
init_logger();
502-
let mut editor = Editor::create();
503+
let mut editor = EditorTestUtils::create();
503504

504505
// UNCOMMENT THIS FOR RUNNING UNDER MIRI
505506
//
@@ -523,20 +524,13 @@ mod test {
523524
"Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)",
524525
);
525526

526-
let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
527+
let responses = editor.editor.handle_message(PortfolioMessage::OpenDocumentFile {
527528
document_name: document_name.into(),
528529
document_serialized_content,
529530
});
530531

531532
// Check if the graph renders
532-
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
533-
portfolio
534-
.executor
535-
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE, true)
536-
.expect("submit_node_graph_evaluation failed");
537-
crate::node_graph_executor::run_node_graph().await;
538-
let mut messages = VecDeque::new();
539-
editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render");
533+
editor.eval_graph().await;
540534

541535
for response in responses {
542536
// Check for the existence of the file format incompatibility warning dialog after opening the test file

editor/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ pub mod consts;
1212
pub mod dispatcher;
1313
pub mod messages;
1414
pub mod node_graph_executor;
15+
#[cfg(test)]
1516
pub mod test_utils;
1617
pub mod utility_traits;

editor/src/messages/portfolio/document/utility_types/network_interface.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5622,6 +5622,34 @@ impl NodeNetworkInterface {
56225622
self.force_set_upstream_to_chain(node_id, network_path);
56235623
}
56245624
}
5625+
5626+
pub fn iter_recursive(&self) -> NodesRecursiveIter<'_> {
5627+
NodesRecursiveIter {
5628+
stack: vec![&self.network],
5629+
current_slice: None,
5630+
}
5631+
}
5632+
}
5633+
5634+
pub struct NodesRecursiveIter<'a> {
5635+
stack: Vec<&'a NodeNetwork>,
5636+
current_slice: Option<std::collections::hash_map::Iter<'a, NodeId, DocumentNode>>,
5637+
}
5638+
5639+
impl<'a> Iterator for NodesRecursiveIter<'a> {
5640+
type Item = (NodeId, &'a DocumentNode);
5641+
fn next(&mut self) -> Option<Self::Item> {
5642+
loop {
5643+
if let Some((id, node)) = self.current_slice.as_mut().and_then(|iter| iter.next()) {
5644+
if let DocumentNodeImplementation::Network(network) = &node.implementation {
5645+
self.stack.push(network);
5646+
}
5647+
return Some((*id, node));
5648+
}
5649+
let network = self.stack.pop()?;
5650+
self.current_slice = Some(network.nodes.iter());
5651+
}
5652+
}
56255653
}
56265654

56275655
#[derive(PartialEq)]

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1160,7 +1160,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
11601160
}
11611161

11621162
impl PortfolioMessageHandler {
1163-
pub async fn introspect_node(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any>, IntrospectError> {
1163+
pub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {
1164+
Self { executor, ..Default::default() }
1165+
}
1166+
1167+
pub async fn introspect_node(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync>, IntrospectError> {
11641168
self.executor.introspect_node(node_path).await
11651169
}
11661170

editor/src/messages/tool/common_functionality/graph_modification_utils.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,15 @@ impl<'a> NodeGraphLayer<'a> {
394394
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
395395
}
396396

397+
/// Node id of a protonode if it exists in the layer's primary flow
398+
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: &'static str) -> Option<NodeId> {
399+
self.horizontal_layer_flow().find(move |node_id| {
400+
self.network_interface
401+
.implementation(node_id, &[])
402+
.is_some_and(move |implementation| *implementation == graph_craft::document::DocumentNodeImplementation::proto(protonode_identifier))
403+
})
404+
}
405+
397406
/// Find all of the inputs of a specific node within the layer's primary flow, up until the next layer is reached.
398407
pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> {
399408
self.horizontal_layer_flow()

0 commit comments

Comments
 (0)