1use serde::{Deserialize, Serialize};
2
3use crate::io::NodeGraphKeyCode;
4use crate::runtime::connection::{CONNECT_EDGE_TRANSACTION_LABEL, ConnectEdgeRequest};
5use crate::runtime::delete::DELETE_SELECTION_TRANSACTION_LABEL;
6use crate::runtime::drag::NODE_DRAG_TRANSACTION_LABEL;
7use crate::runtime::drag::PointerGestureClaim;
8use crate::runtime::events::{
9 ConnectEnd, ConnectEndOutcome, ConnectStart, NodeDragEnd, NodeDragEndOutcome, NodeDragStart,
10 NodeDragUpdate, NodeGraphGestureEvent, NodeResizeEnd, NodeResizeEndOutcome, NodeResizeStart,
11 NodeResizeUpdate, ViewportMove, ViewportMoveEnd, ViewportMoveEndOutcome, ViewportMoveKind,
12 ViewportMoveStart,
13};
14use crate::runtime::measurement::NodeMeasurement;
15use crate::runtime::rendering::RenderingQueryResult;
16use crate::runtime::resize::NODE_RESIZE_TRANSACTION_LABEL;
17use crate::runtime::resize::NodePointerResizeRequest;
18use crate::runtime::selection::{NodePointerDownInput, SelectionBoxInput};
19use crate::runtime::viewport::{ViewportDragPanInput, ViewportGestureContext, ViewportTransform};
20use crate::runtime::xyflow::callbacks::{ConnectionChange, EdgeConnection};
21use jellyflow_core::core::{CanvasPoint, CanvasSize, EdgeId, GroupId, NodeId};
22use keyboard_types::Code as KeyCode;
23
24use super::action::{ConformanceAction, ConformanceLayoutFactsExpectation};
25use super::suite::ConformanceScenario;
26use super::trace::{ConformanceCallbackEvent, ConformanceTraceEvent, ConformanceViewChange};
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30#[serde(tag = "kind", content = "data", rename_all = "snake_case")]
31pub enum ConformanceBehavior {
32 NodeDragSession(ConformanceNodeDragSessionContract),
33 ConnectEdgeSession(ConformanceConnectEdgeSessionContract),
34 NodeResizeSession(ConformanceNodeResizeSessionContract),
35 SelectionBox(ConformanceSelectionBoxContract),
36 DeleteSelection(ConformanceDeleteSelectionContract),
37 DeleteSelectionDuringNodeDrag(ConformanceDeleteSelectionDuringNodeDragContract),
38 NodePointerDownSelection(ConformanceNodePointerDownSelectionContract),
39 ViewportDragPanSession(ConformanceViewportDragPanSessionContract),
40 RenderingQuery(ConformanceRenderingQueryContract),
41 LayoutFacts(ConformanceLayoutFactsContract),
42}
43
44impl ConformanceBehavior {
45 pub fn node_drag_session(contract: ConformanceNodeDragSessionContract) -> Self {
46 Self::NodeDragSession(contract)
47 }
48
49 pub fn connect_edge_session(contract: ConformanceConnectEdgeSessionContract) -> Self {
50 Self::ConnectEdgeSession(contract)
51 }
52
53 pub fn node_resize_session(contract: ConformanceNodeResizeSessionContract) -> Self {
54 Self::NodeResizeSession(contract)
55 }
56
57 pub fn selection_box(contract: ConformanceSelectionBoxContract) -> Self {
58 Self::SelectionBox(contract)
59 }
60
61 pub fn delete_selection(contract: ConformanceDeleteSelectionContract) -> Self {
62 Self::DeleteSelection(contract)
63 }
64
65 pub fn delete_selection_during_node_drag(
66 contract: ConformanceDeleteSelectionDuringNodeDragContract,
67 ) -> Self {
68 Self::DeleteSelectionDuringNodeDrag(contract)
69 }
70
71 pub fn node_pointer_down_selection(
72 contract: ConformanceNodePointerDownSelectionContract,
73 ) -> Self {
74 Self::NodePointerDownSelection(contract)
75 }
76
77 pub fn viewport_drag_pan_session(contract: ConformanceViewportDragPanSessionContract) -> Self {
78 Self::ViewportDragPanSession(contract)
79 }
80
81 pub fn rendering_query(contract: ConformanceRenderingQueryContract) -> Self {
82 Self::RenderingQuery(contract)
83 }
84
85 pub fn layout_facts(contract: ConformanceLayoutFactsContract) -> Self {
86 Self::LayoutFacts(contract)
87 }
88
89 pub(crate) fn actions(&self) -> Vec<ConformanceAction> {
90 match self {
91 Self::NodeDragSession(contract) => vec![contract.action()],
92 Self::ConnectEdgeSession(contract) => vec![contract.action()],
93 Self::NodeResizeSession(contract) => vec![contract.action()],
94 Self::SelectionBox(contract) => vec![contract.action()],
95 Self::DeleteSelection(contract) => vec![contract.action()],
96 Self::DeleteSelectionDuringNodeDrag(contract) => contract.actions(),
97 Self::NodePointerDownSelection(contract) => vec![contract.action()],
98 Self::ViewportDragPanSession(contract) => vec![contract.action()],
99 Self::RenderingQuery(contract) => vec![contract.action()],
100 Self::LayoutFacts(contract) => contract.actions(),
101 }
102 }
103
104 pub(crate) fn expected_trace(&self) -> Vec<ConformanceTraceEvent> {
105 match self {
106 Self::NodeDragSession(contract) => contract.expected_trace(),
107 Self::ConnectEdgeSession(contract) => contract.expected_trace(),
108 Self::NodeResizeSession(contract) => contract.expected_trace(),
109 Self::SelectionBox(contract) => contract.expected_trace(),
110 Self::DeleteSelection(contract) => contract.expected_trace(),
111 Self::DeleteSelectionDuringNodeDrag(contract) => contract.expected_trace(),
112 Self::NodePointerDownSelection(contract) => contract.expected_trace(),
113 Self::ViewportDragPanSession(contract) => contract.expected_trace(),
114 Self::RenderingQuery(contract) => contract.expected_trace(),
115 Self::LayoutFacts(contract) => contract.expected_trace(),
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122pub struct ConformanceNodeDragSessionContract {
123 pub primary: NodeId,
124 pub nodes: Vec<NodeId>,
125 pub start: CanvasPoint,
126 pub to: CanvasPoint,
127 pub commit_op_kinds: Vec<String>,
128}
129
130impl ConformanceNodeDragSessionContract {
131 pub fn new(primary: NodeId, start: CanvasPoint, to: CanvasPoint) -> Self {
132 Self {
133 primary,
134 nodes: vec![primary],
135 start,
136 to,
137 commit_op_kinds: vec!["set_node_pos".to_owned()],
138 }
139 }
140
141 pub fn with_nodes(mut self, nodes: impl IntoIterator<Item = NodeId>) -> Self {
142 self.nodes = nodes.into_iter().collect();
143 self
144 }
145
146 pub fn with_commit_op_kinds(
147 mut self,
148 op_kinds: impl IntoIterator<Item = impl Into<String>>,
149 ) -> Self {
150 self.commit_op_kinds = op_kinds.into_iter().map(Into::into).collect();
151 self
152 }
153
154 fn action(&self) -> ConformanceAction {
155 ConformanceAction::apply_node_drag_session(self.primary, self.start, self.to)
156 }
157
158 fn expected_trace(&self) -> Vec<ConformanceTraceEvent> {
159 let start = NodeDragStart {
160 primary: self.primary,
161 nodes: self.nodes.clone(),
162 pointer: self.start,
163 };
164 let update = NodeDragUpdate {
165 primary: self.primary,
166 nodes: self.nodes.clone(),
167 pointer: self.to,
168 };
169 let end = NodeDragEnd {
170 primary: self.primary,
171 nodes: self.nodes.clone(),
172 pointer: self.to,
173 outcome: NodeDragEndOutcome::Committed,
174 };
175
176 vec![
177 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::NodeDragStart(start.clone())),
178 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeDragStart(start)),
179 ConformanceTraceEvent::graph_commit(
180 Some(NODE_DRAG_TRANSACTION_LABEL),
181 self.commit_op_kinds.iter().map(String::as_str),
182 ),
183 ConformanceTraceEvent::callback(ConformanceCallbackEvent::GraphCommit {
184 label: Some(NODE_DRAG_TRANSACTION_LABEL.to_owned()),
185 }),
186 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeEdgeChanges {
187 nodes: self.nodes.len(),
188 edges: 0,
189 }),
190 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodesChange {
191 count: self.nodes.len(),
192 }),
193 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::NodeDragUpdate(update.clone())),
194 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeDrag(update)),
195 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::NodeDragEnd(end.clone())),
196 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeDragEnd(end)),
197 ]
198 }
199}
200
201#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
203pub struct ConformanceConnectEdgeSessionContract {
204 pub start: ConnectStart,
205 pub request: ConnectEdgeRequest,
206 pub connection: EdgeConnection,
207}
208
209impl ConformanceConnectEdgeSessionContract {
210 pub fn new(
211 start: ConnectStart,
212 request: ConnectEdgeRequest,
213 connection: EdgeConnection,
214 ) -> Self {
215 Self {
216 start,
217 request,
218 connection,
219 }
220 }
221
222 fn action(&self) -> ConformanceAction {
223 ConformanceAction::apply_connect_edge_session(self.start.clone(), self.request)
224 }
225
226 fn expected_trace(&self) -> Vec<ConformanceTraceEvent> {
227 let end = ConnectEnd {
228 kind: self.start.kind.clone(),
229 mode: self.start.mode,
230 target: Some(self.request.to),
231 outcome: ConnectEndOutcome::Committed,
232 };
233
234 vec![
235 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::ConnectStart(self.start.clone())),
236 ConformanceTraceEvent::callback(ConformanceCallbackEvent::ConnectStart(
237 self.start.clone(),
238 )),
239 ConformanceTraceEvent::graph_commit(Some(CONNECT_EDGE_TRANSACTION_LABEL), ["add_edge"]),
240 ConformanceTraceEvent::callback(ConformanceCallbackEvent::GraphCommit {
241 label: Some(CONNECT_EDGE_TRANSACTION_LABEL.to_owned()),
242 }),
243 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeEdgeChanges {
244 nodes: 0,
245 edges: 1,
246 }),
247 ConformanceTraceEvent::callback(ConformanceCallbackEvent::EdgesChange { count: 1 }),
248 ConformanceTraceEvent::callback(ConformanceCallbackEvent::ConnectionChange(
249 ConnectionChange::Connected(self.connection),
250 )),
251 ConformanceTraceEvent::callback(ConformanceCallbackEvent::Connect(self.connection)),
252 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::ConnectEnd(end.clone())),
253 ConformanceTraceEvent::callback(ConformanceCallbackEvent::ConnectEnd(end)),
254 ]
255 }
256}
257
258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
260pub struct ConformanceNodeResizeSessionContract {
261 pub request: NodePointerResizeRequest,
262 pub update: NodeResizeUpdate,
263 pub commit_op_kinds: Vec<String>,
264}
265
266impl ConformanceNodeResizeSessionContract {
267 pub fn new(request: NodePointerResizeRequest, update: NodeResizeUpdate) -> Self {
268 Self {
269 request,
270 update,
271 commit_op_kinds: vec!["set_node_size".to_owned()],
272 }
273 }
274
275 pub fn with_commit_op_kinds(
276 mut self,
277 op_kinds: impl IntoIterator<Item = impl Into<String>>,
278 ) -> Self {
279 self.commit_op_kinds = op_kinds.into_iter().map(Into::into).collect();
280 self
281 }
282
283 fn action(&self) -> ConformanceAction {
284 ConformanceAction::apply_node_pointer_resize_session(self.request)
285 }
286
287 fn expected_trace(&self) -> Vec<ConformanceTraceEvent> {
288 let start = NodeResizeStart {
289 node: self.request.node,
290 direction: self.request.direction,
291 pointer: self.request.start,
292 };
293 let end = NodeResizeEnd {
294 node: self.request.node,
295 direction: self.request.direction,
296 pointer: self.request.current,
297 outcome: NodeResizeEndOutcome::Committed,
298 };
299
300 vec![
301 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::NodeResizeStart(start.clone())),
302 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeResizeStart(start)),
303 ConformanceTraceEvent::graph_commit(
304 Some(NODE_RESIZE_TRANSACTION_LABEL),
305 self.commit_op_kinds.iter().map(String::as_str),
306 ),
307 ConformanceTraceEvent::callback(ConformanceCallbackEvent::GraphCommit {
308 label: Some(NODE_RESIZE_TRANSACTION_LABEL.to_owned()),
309 }),
310 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeEdgeChanges {
311 nodes: 1,
312 edges: 0,
313 }),
314 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodesChange { count: 1 }),
315 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::NodeResizeUpdate(
316 self.update.clone(),
317 )),
318 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeResize(
319 self.update.clone(),
320 )),
321 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::NodeResizeEnd(end.clone())),
322 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeResizeEnd(end)),
323 ]
324 }
325}
326
327#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
329pub struct ConformanceSelectionBoxContract {
330 pub input: SelectionBoxInput,
331 pub nodes: Vec<NodeId>,
332 pub edges: Vec<EdgeId>,
333 #[serde(default, skip_serializing_if = "Vec::is_empty")]
334 pub groups: Vec<GroupId>,
335}
336
337impl ConformanceSelectionBoxContract {
338 pub fn new(
339 input: SelectionBoxInput,
340 nodes: impl IntoIterator<Item = NodeId>,
341 edges: impl IntoIterator<Item = EdgeId>,
342 ) -> Self {
343 Self {
344 input,
345 nodes: nodes.into_iter().collect(),
346 edges: edges.into_iter().collect(),
347 groups: Vec::new(),
348 }
349 }
350
351 pub fn with_groups(mut self, groups: impl IntoIterator<Item = GroupId>) -> Self {
352 self.groups = groups.into_iter().collect();
353 self
354 }
355
356 fn action(&self) -> ConformanceAction {
357 ConformanceAction::apply_selection_box(self.input)
358 }
359
360 fn expected_trace(&self) -> Vec<ConformanceTraceEvent> {
361 selection_trace_events(&self.nodes, &self.edges, &self.groups)
362 }
363}
364
365fn selection_trace_events(
366 nodes: &[NodeId],
367 edges: &[EdgeId],
368 groups: &[GroupId],
369) -> Vec<ConformanceTraceEvent> {
370 vec![
371 ConformanceTraceEvent::selection(nodes.to_vec(), edges.to_vec(), groups.to_vec()),
372 ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewChange {
373 changes: vec![ConformanceViewChange::Selection {
374 nodes: nodes.to_vec(),
375 edges: edges.to_vec(),
376 groups: groups.to_vec(),
377 }],
378 }),
379 ConformanceTraceEvent::callback(ConformanceCallbackEvent::SelectionChange {
380 nodes: nodes.to_vec(),
381 edges: edges.to_vec(),
382 groups: groups.to_vec(),
383 }),
384 ]
385}
386
387fn usize_is_zero(value: &usize) -> bool {
388 *value == 0
389}
390
391fn default_delete_commit_op_kinds(nodes: usize, edges: usize) -> Vec<String> {
392 if nodes > 0 {
393 return vec!["remove_node".to_owned()];
394 }
395
396 if edges > 0 {
397 return vec!["remove_edge".to_owned()];
398 }
399
400 Vec::new()
401}
402
403#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
405pub struct ConformanceDeleteSelectionContract {
406 #[serde(default, skip_serializing_if = "Option::is_none")]
407 pub key: Option<NodeGraphKeyCode>,
408 pub nodes: usize,
409 pub edges: usize,
410 #[serde(default, skip_serializing_if = "usize_is_zero")]
411 pub groups: usize,
412 #[serde(default, skip_serializing_if = "usize_is_zero")]
413 pub sticky_notes: usize,
414 pub commit_op_kinds: Vec<String>,
415 #[serde(default, skip_serializing_if = "Vec::is_empty")]
416 pub disconnected: Vec<EdgeConnection>,
417}
418
419impl ConformanceDeleteSelectionContract {
420 pub fn new(nodes: usize, edges: usize) -> Self {
421 Self {
422 key: None,
423 nodes,
424 edges,
425 groups: 0,
426 sticky_notes: 0,
427 commit_op_kinds: default_delete_commit_op_kinds(nodes, edges),
428 disconnected: Vec::new(),
429 }
430 }
431
432 pub fn for_key(mut self, key: KeyCode) -> Self {
433 self.key = Some(NodeGraphKeyCode(key));
434 self
435 }
436
437 pub fn with_commit_op_kinds(
438 mut self,
439 op_kinds: impl IntoIterator<Item = impl Into<String>>,
440 ) -> Self {
441 self.commit_op_kinds = op_kinds.into_iter().map(Into::into).collect();
442 self
443 }
444
445 pub fn with_deleted_groups(mut self, groups: usize) -> Self {
446 self.groups = groups;
447 self
448 }
449
450 pub fn with_deleted_sticky_notes(mut self, sticky_notes: usize) -> Self {
451 self.sticky_notes = sticky_notes;
452 self
453 }
454
455 pub fn with_disconnected(
456 mut self,
457 disconnected: impl IntoIterator<Item = EdgeConnection>,
458 ) -> Self {
459 self.disconnected = disconnected.into_iter().collect();
460 self
461 }
462
463 fn action(&self) -> ConformanceAction {
464 match self.key {
465 Some(key) => ConformanceAction::apply_delete_selection_for_key(key.0),
466 None => ConformanceAction::apply_delete_selection(),
467 }
468 }
469
470 fn expected_trace(&self) -> Vec<ConformanceTraceEvent> {
471 let mut trace = vec![
472 ConformanceTraceEvent::graph_commit(
473 Some(DELETE_SELECTION_TRANSACTION_LABEL),
474 self.commit_op_kinds.iter().map(String::as_str),
475 ),
476 ConformanceTraceEvent::callback(ConformanceCallbackEvent::GraphCommit {
477 label: Some(DELETE_SELECTION_TRANSACTION_LABEL.to_owned()),
478 }),
479 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeEdgeChanges {
480 nodes: self.nodes,
481 edges: self.edges,
482 }),
483 ];
484
485 if self.nodes > 0 {
486 trace.push(ConformanceTraceEvent::callback(
487 ConformanceCallbackEvent::NodesChange { count: self.nodes },
488 ));
489 }
490
491 if self.edges > 0 {
492 trace.push(ConformanceTraceEvent::callback(
493 ConformanceCallbackEvent::EdgesChange { count: self.edges },
494 ));
495 }
496
497 for connection in self.disconnected.iter().copied() {
498 trace.push(ConformanceTraceEvent::callback(
499 ConformanceCallbackEvent::ConnectionChange(ConnectionChange::Disconnected(
500 connection,
501 )),
502 ));
503 trace.push(ConformanceTraceEvent::callback(
504 ConformanceCallbackEvent::Disconnect(connection),
505 ));
506 }
507
508 if self.nodes > 0 {
509 trace.push(ConformanceTraceEvent::callback(
510 ConformanceCallbackEvent::NodesDelete { count: self.nodes },
511 ));
512 }
513
514 if self.edges > 0 {
515 trace.push(ConformanceTraceEvent::callback(
516 ConformanceCallbackEvent::EdgesDelete { count: self.edges },
517 ));
518 }
519
520 if self.groups > 0 {
521 trace.push(ConformanceTraceEvent::callback(
522 ConformanceCallbackEvent::GroupsDelete { count: self.groups },
523 ));
524 }
525
526 if self.sticky_notes > 0 {
527 trace.push(ConformanceTraceEvent::callback(
528 ConformanceCallbackEvent::StickyNotesDelete {
529 count: self.sticky_notes,
530 },
531 ));
532 }
533
534 trace.push(ConformanceTraceEvent::callback(
535 ConformanceCallbackEvent::Delete {
536 nodes: self.nodes,
537 edges: self.edges,
538 groups: self.groups,
539 sticky_notes: self.sticky_notes,
540 },
541 ));
542 trace.extend(selection_trace_events(&[], &[], &[]));
543
544 trace
545 }
546}
547
548#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
550pub struct ConformanceDeleteSelectionDuringNodeDragContract {
551 pub start: NodeDragStart,
552 pub end: NodeDragEnd,
553 pub delete: ConformanceDeleteSelectionContract,
554}
555
556impl ConformanceDeleteSelectionDuringNodeDragContract {
557 pub fn new(
558 start: NodeDragStart,
559 end: NodeDragEnd,
560 delete: ConformanceDeleteSelectionContract,
561 ) -> Self {
562 Self { start, end, delete }
563 }
564
565 fn actions(&self) -> Vec<ConformanceAction> {
566 vec![
567 ConformanceAction::emit_gesture(NodeGraphGestureEvent::NodeDragStart(
568 self.start.clone(),
569 )),
570 self.delete.action(),
571 ConformanceAction::emit_gesture(NodeGraphGestureEvent::NodeDragEnd(self.end.clone())),
572 ]
573 }
574
575 fn expected_trace(&self) -> Vec<ConformanceTraceEvent> {
576 let mut trace = vec![
577 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::NodeDragStart(
578 self.start.clone(),
579 )),
580 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeDragStart(
581 self.start.clone(),
582 )),
583 ];
584 trace.extend(self.delete.expected_trace());
585 trace.extend([
586 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::NodeDragEnd(self.end.clone())),
587 ConformanceTraceEvent::callback(ConformanceCallbackEvent::NodeDragEnd(
588 self.end.clone(),
589 )),
590 ]);
591 trace
592 }
593}
594
595#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
597pub struct ConformanceNodePointerDownSelectionContract {
598 pub input: NodePointerDownInput,
599 pub expected_claim: PointerGestureClaim,
600 pub nodes: Vec<NodeId>,
601 pub edges: Vec<EdgeId>,
602 #[serde(default, skip_serializing_if = "Vec::is_empty")]
603 pub groups: Vec<GroupId>,
604}
605
606impl ConformanceNodePointerDownSelectionContract {
607 pub fn new(
608 input: NodePointerDownInput,
609 expected_claim: PointerGestureClaim,
610 nodes: impl IntoIterator<Item = NodeId>,
611 edges: impl IntoIterator<Item = EdgeId>,
612 ) -> Self {
613 Self {
614 input,
615 expected_claim,
616 nodes: nodes.into_iter().collect(),
617 edges: edges.into_iter().collect(),
618 groups: Vec::new(),
619 }
620 }
621
622 pub fn with_groups(mut self, groups: impl IntoIterator<Item = GroupId>) -> Self {
623 self.groups = groups.into_iter().collect();
624 self
625 }
626
627 fn action(&self) -> ConformanceAction {
628 ConformanceAction::apply_node_pointer_down_expect_claim(
629 self.input.node,
630 self.input.multi_selection_active,
631 self.input.screen_delta,
632 self.expected_claim,
633 )
634 }
635
636 fn expected_trace(&self) -> Vec<ConformanceTraceEvent> {
637 selection_trace_events(&self.nodes, &self.edges, &self.groups)
638 }
639}
640
641#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
643pub struct ConformanceViewportDragPanSessionContract {
644 pub context: ViewportGestureContext,
645 pub input: ViewportDragPanInput,
646 pub start: ViewportTransform,
647 pub end: ViewportTransform,
648}
649
650impl ConformanceViewportDragPanSessionContract {
651 pub fn new(
652 context: ViewportGestureContext,
653 input: ViewportDragPanInput,
654 start: ViewportTransform,
655 end: ViewportTransform,
656 ) -> Self {
657 Self {
658 context,
659 input,
660 start,
661 end,
662 }
663 }
664
665 fn action(&self) -> ConformanceAction {
666 ConformanceAction::apply_viewport_drag_pan_session(self.context, self.input)
667 }
668
669 fn expected_trace(&self) -> Vec<ConformanceTraceEvent> {
670 let start = ViewportMoveStart {
671 kind: ViewportMoveKind::PanDrag,
672 pan: self.start.pan,
673 zoom: self.start.zoom,
674 };
675 let update = ViewportMove {
676 kind: ViewportMoveKind::PanDrag,
677 pan: self.end.pan,
678 zoom: self.end.zoom,
679 };
680 let end = ViewportMoveEnd {
681 kind: ViewportMoveKind::PanDrag,
682 pan: self.end.pan,
683 zoom: self.end.zoom,
684 outcome: ViewportMoveEndOutcome::Ended,
685 };
686
687 vec![
688 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::ViewportMoveStart(start)),
689 ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewportMoveStart(start)),
690 ConformanceTraceEvent::viewport(self.end.pan, self.end.zoom),
691 ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewChange {
692 changes: vec![ConformanceViewChange::Viewport {
693 pan: self.end.pan,
694 zoom: self.end.zoom,
695 }],
696 }),
697 ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewportChange {
698 pan: self.end.pan,
699 zoom: self.end.zoom,
700 }),
701 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::ViewportMove(update)),
702 ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewportMove(update)),
703 ConformanceTraceEvent::gesture(NodeGraphGestureEvent::ViewportMoveEnd(end)),
704 ConformanceTraceEvent::callback(ConformanceCallbackEvent::ViewportMoveEnd(end)),
705 ]
706 }
707}
708
709#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
711pub struct ConformanceRenderingQueryContract {
712 pub viewport_size: CanvasSize,
713 pub expected: RenderingQueryResult,
714}
715
716impl ConformanceRenderingQueryContract {
717 pub fn new(viewport_size: CanvasSize, expected: RenderingQueryResult) -> Self {
718 Self {
719 viewport_size,
720 expected,
721 }
722 }
723
724 fn action(&self) -> ConformanceAction {
725 ConformanceAction::assert_rendering_query(self.viewport_size, self.expected.clone())
726 }
727
728 fn expected_trace(&self) -> Vec<ConformanceTraceEvent> {
729 Vec::new()
730 }
731}
732
733#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
735pub struct ConformanceLayoutFactsContract {
736 pub measurements: Vec<NodeMeasurement>,
737 pub viewport_size: CanvasSize,
738 pub expected: ConformanceLayoutFactsExpectation,
739}
740
741impl ConformanceLayoutFactsContract {
742 pub fn new(
743 measurements: impl IntoIterator<Item = NodeMeasurement>,
744 viewport_size: CanvasSize,
745 expected: ConformanceLayoutFactsExpectation,
746 ) -> Self {
747 Self {
748 measurements: measurements.into_iter().collect(),
749 viewport_size,
750 expected,
751 }
752 }
753
754 fn actions(&self) -> Vec<ConformanceAction> {
755 self.measurements
756 .iter()
757 .cloned()
758 .map(ConformanceAction::report_node_measurement)
759 .chain([ConformanceAction::assert_layout_facts(
760 self.viewport_size,
761 self.expected.clone(),
762 )])
763 .collect()
764 }
765
766 fn expected_trace(&self) -> Vec<ConformanceTraceEvent> {
767 Vec::new()
768 }
769}
770
771impl ConformanceScenario {
772 pub fn with_node_drag_session_contract(
773 self,
774 contract: ConformanceNodeDragSessionContract,
775 ) -> Self {
776 self.with_behavior(ConformanceBehavior::node_drag_session(contract))
777 }
778
779 pub fn with_connect_edge_session_contract(
780 self,
781 contract: ConformanceConnectEdgeSessionContract,
782 ) -> Self {
783 self.with_behavior(ConformanceBehavior::connect_edge_session(contract))
784 }
785
786 pub fn with_node_resize_session_contract(
787 self,
788 contract: ConformanceNodeResizeSessionContract,
789 ) -> Self {
790 self.with_behavior(ConformanceBehavior::node_resize_session(contract))
791 }
792
793 pub fn with_selection_box_contract(self, contract: ConformanceSelectionBoxContract) -> Self {
794 self.with_behavior(ConformanceBehavior::selection_box(contract))
795 }
796
797 pub fn with_delete_selection_contract(
798 self,
799 contract: ConformanceDeleteSelectionContract,
800 ) -> Self {
801 self.with_behavior(ConformanceBehavior::delete_selection(contract))
802 }
803
804 pub fn with_delete_selection_during_node_drag_contract(
805 self,
806 contract: ConformanceDeleteSelectionDuringNodeDragContract,
807 ) -> Self {
808 self.with_behavior(ConformanceBehavior::delete_selection_during_node_drag(
809 contract,
810 ))
811 }
812
813 pub fn with_node_pointer_down_selection_contract(
814 self,
815 contract: ConformanceNodePointerDownSelectionContract,
816 ) -> Self {
817 self.with_behavior(ConformanceBehavior::node_pointer_down_selection(contract))
818 }
819
820 pub fn with_viewport_drag_pan_session_contract(
821 self,
822 contract: ConformanceViewportDragPanSessionContract,
823 ) -> Self {
824 self.with_behavior(ConformanceBehavior::viewport_drag_pan_session(contract))
825 }
826
827 pub fn with_rendering_query_contract(
828 self,
829 contract: ConformanceRenderingQueryContract,
830 ) -> Self {
831 self.with_behavior(ConformanceBehavior::rendering_query(contract))
832 }
833
834 pub fn with_layout_facts_contract(self, contract: ConformanceLayoutFactsContract) -> Self {
835 self.with_behavior(ConformanceBehavior::layout_facts(contract))
836 }
837}