Skip to content

Add tests to the Ellipse, Artboard, and Fill tools #2181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 7, 2025
Merged
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
7 changes: 3 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions editor/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ impl Editor {
Self { dispatcher: Dispatcher::new() }
}

#[cfg(test)]
pub(crate) fn new_local_executor() -> (Self, crate::node_graph_executor::NodeRuntime) {
let (runtime, executor) = crate::node_graph_executor::NodeGraphExecutor::new_with_local_runtime();
let dispatcher = Dispatcher::with_executor(executor);
(Self { dispatcher }, runtime)
}

pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Vec<FrontendMessage> {
self.dispatcher.handle_message(message, true);

Expand Down
188 changes: 91 additions & 97 deletions editor/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ pub struct DispatcherMessageHandlers {
workspace_message_handler: WorkspaceMessageHandler,
}

impl DispatcherMessageHandlers {
pub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {
Self {
portfolio_message_handler: PortfolioMessageHandler::with_executor(executor),
..Default::default()
}
}
}

/// For optimization, these are messages guaranteed to be redundant when repeated.
/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
/// In addition, these messages do not change any state in the backend (aside from caches).
Expand Down Expand Up @@ -53,6 +62,13 @@ impl Dispatcher {
Self::default()
}

pub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {
Self {
message_handlers: DispatcherMessageHandlers::with_executor(executor),
..Default::default()
}
}

// If the deepest queues (higher index in queues list) are now empty (after being popped from) then remove them
fn cleanup_queues(&mut self, leave_last: bool) {
while self.message_queues.last().filter(|queue| queue.is_empty()).is_some() {
Expand Down Expand Up @@ -328,60 +344,48 @@ impl Dispatcher {

#[cfg(test)]
mod test {
use crate::application::Editor;
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
use crate::test_utils::EditorTestUtils;
use graphene_core::raster::color::Color;

fn init_logger() {
let _ = env_logger::builder().is_test(true).try_init();
}
pub use crate::test_utils::test_prelude::*;

/// Create an editor with three layers
/// 1. A red rectangle
/// 2. A blue shape
/// 3. A green ellipse
fn create_editor_with_three_layers() -> Editor {
init_logger();
let mut editor = Editor::create();
async fn create_editor_with_three_layers() -> EditorTestUtils {
let mut editor = EditorTestUtils::create();

editor.new_document();
editor.new_document().await;

editor.select_primary_color(Color::RED);
editor.draw_rect(100., 200., 300., 400.);
editor.select_primary_color(Color::RED).await;
editor.draw_rect(100., 200., 300., 400.).await;

editor.select_primary_color(Color::BLUE);
editor.draw_polygon(10., 1200., 1300., 400.);
editor.select_primary_color(Color::BLUE).await;
editor.draw_polygon(10., 1200., 1300., 400.).await;

editor.select_primary_color(Color::GREEN);
editor.draw_ellipse(104., 1200., 1300., 400.);
editor.select_primary_color(Color::GREEN).await;
editor.draw_ellipse(104., 1200., 1300., 400.).await;

editor
}

// TODO: Fix text
#[ignore]
#[test]
/// - create rect, shape and ellipse
/// - copy
/// - paste
/// - assert that ellipse was copied
fn copy_paste_single_layer() {
let mut editor = create_editor_with_three_layers();
#[tokio::test]
async fn copy_paste_single_layer() {
let mut editor = create_editor_with_three_layers().await;

let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: 0,
});
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let layers_before_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;
editor
.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: 0,
})
.await;

let layers_before_copy = document_before_copy.metadata().all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata().all_layers().collect::<Vec<_>>();
let layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();

assert_eq!(layers_before_copy.len(), 3);
assert_eq!(layers_after_copy.len(), 4);
Expand All @@ -392,33 +396,30 @@ mod test {
}
}

// TODO: Fix text
#[ignore]
#[test]
#[cfg_attr(miri, ignore)]
/// - create rect, shape and ellipse
/// - select shape
/// - copy
/// - paste
/// - assert that shape was copied
fn copy_paste_single_layer_from_middle() {
let mut editor = create_editor_with_three_layers();

let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let shape_id = document_before_copy.metadata().all_layers().nth(1).unwrap();
#[tokio::test]
async fn copy_paste_single_layer_from_middle() {
let mut editor = create_editor_with_three_layers().await;

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

let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![shape_id.to_node()] }).await;
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;
editor
.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: 0,
})
.await;

let layers_before_copy = document_before_copy.metadata().all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata().all_layers().collect::<Vec<_>>();
let layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();

assert_eq!(layers_before_copy.len(), 3);
assert_eq!(layers_after_copy.len(), 4);
Expand All @@ -429,9 +430,6 @@ mod test {
}
}

// TODO: Fix text
#[ignore]
#[test]
#[cfg_attr(miri, ignore)]
/// - create rect, shape and ellipse
/// - select ellipse and rect
Expand All @@ -440,36 +438,40 @@ mod test {
/// - create another rect
/// - paste
/// - paste
fn copy_paste_deleted_layers() {
let mut editor = create_editor_with_three_layers();

let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let mut layers = document_before_copy.metadata().all_layers();
let rect_id = layers.next().expect("rectangle");
let shape_id = layers.next().expect("shape");
let ellipse_id = layers.next().expect("ellipse");

editor.handle_message(NodeGraphMessage::SelectedNodesSet {
nodes: vec![rect_id.to_node(), ellipse_id.to_node()],
});
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
editor.handle_message(NodeGraphMessage::DeleteSelectedNodes { delete_children: true });
editor.draw_rect(0., 800., 12., 200.);
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: 0,
});
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: 0,
});

let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();

let layers_before_copy = document_before_copy.metadata().all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata().all_layers().collect::<Vec<_>>();
#[tokio::test]
async fn copy_paste_deleted_layers() {
let mut editor = create_editor_with_three_layers().await;
assert_eq!(editor.active_document().metadata().all_layers().count(), 3);

let layers_before_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
let rect_id = layers_before_copy[0];
let shape_id = layers_before_copy[1];
let ellipse_id = layers_before_copy[2];

editor
.handle_message(NodeGraphMessage::SelectedNodesSet {
nodes: vec![rect_id.to_node(), ellipse_id.to_node()],
})
.await;
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;
editor.handle_message(NodeGraphMessage::DeleteSelectedNodes { delete_children: true }).await;
editor.draw_rect(0., 800., 12., 200.).await;
editor
.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: 0,
})
.await;
editor
.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: 0,
})
.await;

let layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();

assert_eq!(layers_before_copy.len(), 3);
assert_eq!(layers_after_copy.len(), 6);
Expand Down Expand Up @@ -498,8 +500,7 @@ mod test {
panic!()
};

init_logger();
let mut editor = Editor::create();
let mut editor = EditorTestUtils::create();

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

let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
let responses = editor.editor.handle_message(PortfolioMessage::OpenDocumentFile {
document_name: document_name.into(),
document_serialized_content,
});

// Check if the graph renders
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
portfolio
.executor
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE, true)
.expect("submit_node_graph_evaluation failed");
crate::node_graph_executor::run_node_graph().await;
let mut messages = VecDeque::new();
editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render");
editor.eval_graph().await;

for response in responses {
// Check for the existence of the file format incompatibility warning dialog after opening the test file
Expand Down
1 change: 1 addition & 0 deletions editor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ pub mod consts;
pub mod dispatcher;
pub mod messages;
pub mod node_graph_executor;
#[cfg(test)]
pub mod test_utils;
pub mod utility_traits;
Original file line number Diff line number Diff line change
Expand Up @@ -5622,6 +5622,34 @@ impl NodeNetworkInterface {
self.force_set_upstream_to_chain(node_id, network_path);
}
}

pub fn iter_recursive(&self) -> NodesRecursiveIter<'_> {
NodesRecursiveIter {
stack: vec![&self.network],
current_slice: None,
}
}
}

pub struct NodesRecursiveIter<'a> {
stack: Vec<&'a NodeNetwork>,
current_slice: Option<std::collections::hash_map::Iter<'a, NodeId, DocumentNode>>,
}

impl<'a> Iterator for NodesRecursiveIter<'a> {
type Item = (NodeId, &'a DocumentNode);
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some((id, node)) = self.current_slice.as_mut().and_then(|iter| iter.next()) {
if let DocumentNodeImplementation::Network(network) = &node.implementation {
self.stack.push(network);
}
return Some((*id, node));
}
let network = self.stack.pop()?;
self.current_slice = Some(network.nodes.iter());
}
}
}

#[derive(PartialEq)]
Expand Down
6 changes: 5 additions & 1 deletion editor/src/messages/portfolio/portfolio_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1160,7 +1160,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}

impl PortfolioMessageHandler {
pub async fn introspect_node(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any>, IntrospectError> {
pub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {
Self { executor, ..Default::default() }
}

pub async fn introspect_node(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync>, IntrospectError> {
self.executor.introspect_node(node_path).await
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,15 @@ impl<'a> NodeGraphLayer<'a> {
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
}

/// Node id of a protonode if it exists in the layer's primary flow
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: &'static str) -> Option<NodeId> {
self.horizontal_layer_flow().find(move |node_id| {
self.network_interface
.implementation(node_id, &[])
.is_some_and(move |implementation| *implementation == graph_craft::document::DocumentNodeImplementation::proto(protonode_identifier))
})
}

/// Find all of the inputs of a specific node within the layer's primary flow, up until the next layer is reached.
pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> {
self.horizontal_layer_flow()
Expand Down
Loading