1use crate::{BindingId, CanvasRecordId};
2use indexmap::IndexSet;
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum CanvasRecordRelationKind {
8 Parent,
9 Group,
10 Binding,
11}
12
13#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
14pub enum CanvasRecordRelationKey<'a> {
15 Parent {
16 child: &'a CanvasRecordId,
17 parent: &'a CanvasRecordId,
18 },
19 Group {
20 group: &'a CanvasRecordId,
21 member: &'a CanvasRecordId,
22 },
23 Binding {
24 id: &'a BindingId,
25 },
26}
27
28#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
29pub struct CanvasRecordParentRelation {
30 pub child: CanvasRecordId,
31 pub parent: CanvasRecordId,
32}
33
34impl CanvasRecordParentRelation {
35 pub fn new(child: impl Into<CanvasRecordId>, parent: impl Into<CanvasRecordId>) -> Self {
36 Self {
37 child: child.into(),
38 parent: parent.into(),
39 }
40 }
41}
42
43#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
44pub struct CanvasRecordGroupRelation {
45 pub group: CanvasRecordId,
46 pub member: CanvasRecordId,
47}
48
49impl CanvasRecordGroupRelation {
50 pub fn new(group: impl Into<CanvasRecordId>, member: impl Into<CanvasRecordId>) -> Self {
51 Self {
52 group: group.into(),
53 member: member.into(),
54 }
55 }
56}
57
58#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
59pub struct CanvasRecordBindingRelation {
60 pub id: BindingId,
61 #[serde(default = "default_binding_kind")]
62 pub kind: String,
63 pub source: CanvasRecordId,
64 pub target: CanvasRecordId,
65}
66
67impl CanvasRecordBindingRelation {
68 pub fn new(
69 id: impl Into<BindingId>,
70 source: impl Into<CanvasRecordId>,
71 target: impl Into<CanvasRecordId>,
72 ) -> Self {
73 Self {
74 id: id.into(),
75 kind: default_binding_kind(),
76 source: source.into(),
77 target: target.into(),
78 }
79 }
80
81 pub fn with_kind(mut self, kind: impl Into<String>) -> Self {
82 self.kind = kind.into();
83 self
84 }
85}
86
87fn default_binding_kind() -> String {
88 "binding".to_string()
89}
90
91#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
92pub enum CanvasRecordRelation {
93 Parent(CanvasRecordParentRelation),
94 Group(CanvasRecordGroupRelation),
95 Binding(CanvasRecordBindingRelation),
96}
97
98impl CanvasRecordRelation {
99 pub fn kind(&self) -> CanvasRecordRelationKind {
100 match self {
101 Self::Parent(_) => CanvasRecordRelationKind::Parent,
102 Self::Group(_) => CanvasRecordRelationKind::Group,
103 Self::Binding(_) => CanvasRecordRelationKind::Binding,
104 }
105 }
106
107 pub fn relation_key(&self) -> CanvasRecordRelationKey<'_> {
108 match self {
109 Self::Parent(relation) => CanvasRecordRelationKey::Parent {
110 child: &relation.child,
111 parent: &relation.parent,
112 },
113 Self::Group(relation) => CanvasRecordRelationKey::Group {
114 group: &relation.group,
115 member: &relation.member,
116 },
117 Self::Binding(relation) => CanvasRecordRelationKey::Binding { id: &relation.id },
118 }
119 }
120}
121
122impl From<CanvasRecordParentRelation> for CanvasRecordRelation {
123 fn from(value: CanvasRecordParentRelation) -> Self {
124 Self::Parent(value)
125 }
126}
127
128impl From<CanvasRecordGroupRelation> for CanvasRecordRelation {
129 fn from(value: CanvasRecordGroupRelation) -> Self {
130 Self::Group(value)
131 }
132}
133
134impl From<CanvasRecordBindingRelation> for CanvasRecordRelation {
135 fn from(value: CanvasRecordBindingRelation) -> Self {
136 Self::Binding(value)
137 }
138}
139
140#[derive(Clone, Debug, Default, Serialize, Deserialize)]
141pub struct CanvasRecordRelations {
142 #[serde(default)]
143 parents: Vec<CanvasRecordParentRelation>,
144 #[serde(default)]
145 groups: Vec<CanvasRecordGroupRelation>,
146 #[serde(default)]
147 bindings: Vec<CanvasRecordBindingRelation>,
148}
149
150impl PartialEq for CanvasRecordRelations {
151 fn eq(&self, other: &Self) -> bool {
152 self.parents.len() == other.parents.len()
153 && self.groups.len() == other.groups.len()
154 && self.bindings.len() == other.bindings.len()
155 && self.parents.iter().all(|relation| {
156 other.contains_relation(&CanvasRecordRelation::Parent(relation.clone()))
157 })
158 && self.groups.iter().all(|relation| {
159 other.contains_relation(&CanvasRecordRelation::Group(relation.clone()))
160 })
161 && self.bindings.iter().all(|relation| {
162 other.contains_relation(&CanvasRecordRelation::Binding(relation.clone()))
163 })
164 }
165}
166
167impl Eq for CanvasRecordRelations {}
168
169impl CanvasRecordRelations {
170 pub fn builder() -> CanvasRecordRelationsBuilder {
171 CanvasRecordRelationsBuilder::new()
172 }
173
174 pub fn is_empty(&self) -> bool {
175 self.parents.is_empty() && self.groups.is_empty() && self.bindings.is_empty()
176 }
177
178 pub fn parents(&self) -> impl Iterator<Item = &CanvasRecordParentRelation> {
179 self.parents.iter()
180 }
181
182 pub fn groups(&self) -> impl Iterator<Item = &CanvasRecordGroupRelation> {
183 self.groups.iter()
184 }
185
186 pub fn bindings(&self) -> impl Iterator<Item = &CanvasRecordBindingRelation> {
187 self.bindings.iter()
188 }
189
190 pub fn binding(&self, id: &BindingId) -> Option<&CanvasRecordBindingRelation> {
191 self.bindings.iter().find(|relation| &relation.id == id)
192 }
193
194 pub fn parent_of(&self, child: &CanvasRecordId) -> Option<&CanvasRecordId> {
195 self.parents
196 .iter()
197 .find(|relation| &relation.child == child)
198 .map(|relation| &relation.parent)
199 }
200
201 pub fn children_of(&self, parent: &CanvasRecordId) -> impl Iterator<Item = &CanvasRecordId> {
202 self.parents
203 .iter()
204 .filter(move |relation| &relation.parent == parent)
205 .map(|relation| &relation.child)
206 }
207
208 pub fn members_of(&self, group: &CanvasRecordId) -> impl Iterator<Item = &CanvasRecordId> {
209 self.groups
210 .iter()
211 .filter(move |relation| &relation.group == group)
212 .map(|relation| &relation.member)
213 }
214
215 pub fn groups_for(&self, member: &CanvasRecordId) -> impl Iterator<Item = &CanvasRecordId> {
216 self.groups
217 .iter()
218 .filter(move |relation| &relation.member == member)
219 .map(|relation| &relation.group)
220 }
221
222 pub fn bindings_for(
223 &self,
224 record: &CanvasRecordId,
225 ) -> impl Iterator<Item = &CanvasRecordBindingRelation> {
226 self.bindings
227 .iter()
228 .filter(move |relation| &relation.source == record || &relation.target == record)
229 }
230
231 pub(crate) fn collect_related_records(
232 &self,
233 seeds: impl IntoIterator<Item = CanvasRecordId>,
234 mut can_include: impl FnMut(&CanvasRecordId) -> bool,
235 ) -> IndexSet<CanvasRecordId> {
236 let mut records = IndexSet::new();
237 let mut pending = Vec::new();
238
239 for record_id in seeds {
240 Self::insert_related_record(record_id, &mut records, &mut pending, &mut can_include);
241 }
242
243 while let Some(record_id) = pending.pop() {
244 for child in self.children_of(&record_id).cloned() {
245 Self::insert_related_record(child, &mut records, &mut pending, &mut can_include);
246 }
247 for member in self.members_of(&record_id).cloned() {
248 Self::insert_related_record(member, &mut records, &mut pending, &mut can_include);
249 }
250 }
251
252 records
253 }
254
255 pub(crate) fn collect_descendant_records(
256 &self,
257 seeds: impl IntoIterator<Item = CanvasRecordId>,
258 mut can_traverse: impl FnMut(&CanvasRecordId) -> bool,
259 ) -> IndexSet<CanvasRecordId> {
260 let mut records = IndexSet::new();
261 let mut pending = seeds
262 .into_iter()
263 .filter(|record_id| can_traverse(record_id))
264 .collect::<Vec<_>>();
265
266 while let Some(record_id) = pending.pop() {
267 for child in self.children_of(&record_id).cloned() {
268 Self::insert_related_record(child, &mut records, &mut pending, &mut can_traverse);
269 }
270 for member in self.members_of(&record_id).cloned() {
271 Self::insert_related_record(member, &mut records, &mut pending, &mut can_traverse);
272 }
273 }
274
275 records
276 }
277
278 pub fn contains_relation(&self, relation: &CanvasRecordRelation) -> bool {
279 match relation {
280 CanvasRecordRelation::Parent(relation) => {
281 self.parent_of(&relation.child) == Some(&relation.parent)
282 }
283 CanvasRecordRelation::Group(relation) => self.contains_group_relation(relation),
284 CanvasRecordRelation::Binding(relation) => self.binding(&relation.id) == Some(relation),
285 }
286 }
287
288 pub(crate) fn subset_for_records(
289 &self,
290 mut contains_record: impl FnMut(&CanvasRecordId) -> bool,
291 ) -> CanvasRecordRelations {
292 let mut relations = CanvasRecordRelationsBuilder::new();
293
294 for relation in self.parents() {
295 if contains_record(&relation.child) && contains_record(&relation.parent) {
296 relations.add_parent(relation.child.clone(), relation.parent.clone());
297 }
298 }
299
300 for relation in self.groups() {
301 if contains_record(&relation.group) && contains_record(&relation.member) {
302 relations.add_group_member(relation.group.clone(), relation.member.clone());
303 }
304 }
305
306 for relation in self.bindings() {
307 if contains_record(&relation.source) && contains_record(&relation.target) {
308 relations.add_binding(relation.clone());
309 }
310 }
311
312 relations.build()
313 }
314
315 pub(crate) fn set_parent(
316 &mut self,
317 child: CanvasRecordId,
318 parent: CanvasRecordId,
319 ) -> Option<CanvasRecordId> {
320 if let Some(relation) = self
321 .parents
322 .iter_mut()
323 .find(|relation| relation.child == child)
324 {
325 if relation.parent == parent {
326 return Some(parent);
327 }
328
329 let previous = std::mem::replace(&mut relation.parent, parent);
330 return Some(previous);
331 }
332
333 self.parents
334 .push(CanvasRecordParentRelation { child, parent });
335 None
336 }
337
338 pub(crate) fn clear_parent(&mut self, child: &CanvasRecordId) -> Option<CanvasRecordId> {
339 let index = self
340 .parents
341 .iter()
342 .position(|relation| &relation.child == child)?;
343 Some(self.parents.remove(index).parent)
344 }
345
346 pub(crate) fn add_to_group(&mut self, group: CanvasRecordId, member: CanvasRecordId) -> bool {
347 if self
348 .groups
349 .iter()
350 .any(|relation| relation.group == group && relation.member == member)
351 {
352 return false;
353 }
354
355 self.groups
356 .push(CanvasRecordGroupRelation { group, member });
357 true
358 }
359
360 pub(crate) fn remove_from_group(
361 &mut self,
362 group: &CanvasRecordId,
363 member: &CanvasRecordId,
364 ) -> bool {
365 let Some(index) = self
366 .groups
367 .iter()
368 .position(|relation| &relation.group == group && &relation.member == member)
369 else {
370 return false;
371 };
372 self.groups.remove(index);
373 true
374 }
375
376 pub(crate) fn set_binding(
377 &mut self,
378 binding: CanvasRecordBindingRelation,
379 ) -> Option<CanvasRecordBindingRelation> {
380 if let Some(relation) = self
381 .bindings
382 .iter_mut()
383 .find(|relation| relation.id == binding.id)
384 {
385 if *relation == binding {
386 return Some(binding);
387 }
388
389 let previous = std::mem::replace(relation, binding);
390 return Some(previous);
391 }
392
393 self.bindings.push(binding);
394 None
395 }
396
397 pub(crate) fn remove_binding(&mut self, id: &BindingId) -> Option<CanvasRecordBindingRelation> {
398 let index = self
399 .bindings
400 .iter()
401 .position(|relation| &relation.id == id)?;
402 Some(self.bindings.remove(index))
403 }
404
405 pub(crate) fn prune_missing_records(&mut self, existing: &IndexSet<CanvasRecordId>) -> bool {
406 let parent_count = self.parents.len();
407 self.parents.retain(|relation| {
408 existing.contains(&relation.child) && existing.contains(&relation.parent)
409 });
410
411 let group_count = self.groups.len();
412 self.groups.retain(|relation| {
413 existing.contains(&relation.group) && existing.contains(&relation.member)
414 });
415
416 let binding_count = self.bindings.len();
417 self.bindings.retain(|relation| {
418 existing.contains(&relation.source) && existing.contains(&relation.target)
419 });
420
421 self.parents.len() != parent_count
422 || self.groups.len() != group_count
423 || self.bindings.len() != binding_count
424 }
425
426 fn contains_group_relation(&self, relation: &CanvasRecordGroupRelation) -> bool {
427 self.groups
428 .iter()
429 .any(|existing| existing.group == relation.group && existing.member == relation.member)
430 }
431
432 fn insert_related_record<F>(
433 record_id: CanvasRecordId,
434 records: &mut IndexSet<CanvasRecordId>,
435 pending: &mut Vec<CanvasRecordId>,
436 can_include: &mut F,
437 ) where
438 F: FnMut(&CanvasRecordId) -> bool,
439 {
440 if !can_include(&record_id) {
441 return;
442 }
443 if records.insert(record_id.clone()) {
444 pending.push(record_id);
445 }
446 }
447}
448
449#[derive(Clone, Debug, Default, PartialEq)]
450pub struct CanvasRecordRelationsBuilder {
451 relations: CanvasRecordRelations,
452}
453
454impl CanvasRecordRelationsBuilder {
455 pub fn new() -> Self {
456 Self::default()
457 }
458
459 pub fn add_parent(
460 &mut self,
461 child: impl Into<CanvasRecordId>,
462 parent: impl Into<CanvasRecordId>,
463 ) -> &mut Self {
464 self.relations.set_parent(child.into(), parent.into());
465 self
466 }
467
468 pub fn add_group_member(
469 &mut self,
470 group: impl Into<CanvasRecordId>,
471 member: impl Into<CanvasRecordId>,
472 ) -> &mut Self {
473 self.relations.add_to_group(group.into(), member.into());
474 self
475 }
476
477 pub fn add_relation(&mut self, relation: impl Into<CanvasRecordRelation>) -> &mut Self {
478 match relation.into() {
479 CanvasRecordRelation::Parent(relation) => {
480 self.add_parent(relation.child, relation.parent);
481 }
482 CanvasRecordRelation::Group(relation) => {
483 self.add_group_member(relation.group, relation.member);
484 }
485 CanvasRecordRelation::Binding(relation) => {
486 self.add_binding(relation);
487 }
488 }
489 self
490 }
491
492 pub fn add_binding(&mut self, binding: CanvasRecordBindingRelation) -> &mut Self {
493 self.relations.set_binding(binding);
494 self
495 }
496
497 pub fn build(self) -> CanvasRecordRelations {
498 self.relations
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use crate::{BindingId, NodeId, ShapeId};
506 use indexmap::IndexSet;
507
508 #[test]
509 fn relation_defaults_are_empty() {
510 let relations = CanvasRecordRelations::default();
511
512 assert!(relations.is_empty());
513 assert_eq!(relations.parents().count(), 0);
514 assert_eq!(relations.groups().count(), 0);
515 assert_eq!(relations.bindings().count(), 0);
516 }
517
518 #[test]
519 fn collect_related_records_expands_descendants_only() {
520 let mut relations = CanvasRecordRelations::default();
521 let frame = CanvasRecordId::Shape(ShapeId::from("frame"));
522 let group = CanvasRecordId::Shape(ShapeId::from("group"));
523 let leaf = CanvasRecordId::Node(NodeId::from("leaf"));
524 let outside = CanvasRecordId::Node(NodeId::from("outside"));
525
526 relations.set_parent(group.clone(), frame.clone());
527 relations.add_to_group(group.clone(), leaf.clone());
528 relations.add_to_group(frame.clone(), outside.clone());
529
530 let records = relations.collect_related_records([frame.clone()], |_| true);
531
532 assert_eq!(records.len(), 4);
533 assert!(records.contains(&frame));
534 assert!(records.contains(&group));
535 assert!(records.contains(&leaf));
536 assert!(records.contains(&outside));
537 }
538
539 #[test]
540 fn parent_and_child_lookup_use_record_ids() {
541 let mut relations = CanvasRecordRelations::default();
542 let child = CanvasRecordId::Node(NodeId::from("child"));
543 let parent = CanvasRecordId::Shape(ShapeId::from("frame"));
544
545 assert_eq!(relations.set_parent(child.clone(), parent.clone()), None);
546
547 assert_eq!(relations.parent_of(&child), Some(&parent));
548 assert_eq!(
549 relations.children_of(&parent).cloned().collect::<Vec<_>>(),
550 vec![child]
551 );
552 }
553
554 #[test]
555 fn group_memberships_are_deduplicated() {
556 let mut relations = CanvasRecordRelations::default();
557 let group = CanvasRecordId::Shape(ShapeId::from("group"));
558 let member = CanvasRecordId::Node(NodeId::from("member"));
559
560 assert!(relations.add_to_group(group.clone(), member.clone()));
561 assert!(!relations.add_to_group(group.clone(), member.clone()));
562
563 assert_eq!(
564 relations.members_of(&group).cloned().collect::<Vec<_>>(),
565 vec![member.clone()]
566 );
567 assert_eq!(
568 relations.groups_for(&member).cloned().collect::<Vec<_>>(),
569 vec![group]
570 );
571 }
572
573 #[test]
574 fn relation_records_report_kind_and_identity() {
575 let child = CanvasRecordId::Node(NodeId::from("child"));
576 let parent = CanvasRecordId::Shape(ShapeId::from("frame"));
577 let parent_relation = CanvasRecordRelation::from(CanvasRecordParentRelation::new(
578 child.clone(),
579 parent.clone(),
580 ));
581 let group_relation = CanvasRecordRelation::from(CanvasRecordGroupRelation::new(
582 parent.clone(),
583 child.clone(),
584 ));
585 let binding_relation = CanvasRecordRelation::from(CanvasRecordBindingRelation::new(
586 "binding",
587 child.clone(),
588 parent.clone(),
589 ));
590
591 assert_eq!(parent_relation.kind(), CanvasRecordRelationKind::Parent);
592 assert_eq!(
593 parent_relation.relation_key(),
594 CanvasRecordRelationKey::Parent {
595 child: &child,
596 parent: &parent,
597 }
598 );
599 assert_eq!(group_relation.kind(), CanvasRecordRelationKind::Group);
600 assert_eq!(
601 group_relation.relation_key(),
602 CanvasRecordRelationKey::Group {
603 group: &parent,
604 member: &child,
605 }
606 );
607 assert_eq!(binding_relation.kind(), CanvasRecordRelationKind::Binding);
608 assert_eq!(
609 binding_relation.relation_key(),
610 CanvasRecordRelationKey::Binding {
611 id: &BindingId::from("binding"),
612 }
613 );
614 }
615
616 #[test]
617 fn relations_builder_constructs_structural_relation_sets() {
618 let child = CanvasRecordId::Node(NodeId::from("child"));
619 let parent = CanvasRecordId::Shape(ShapeId::from("frame"));
620 let mut builder = CanvasRecordRelations::builder();
621 builder
622 .add_parent(child.clone(), parent.clone())
623 .add_group_member(parent.clone(), child.clone());
624
625 let relations = builder.build();
626
627 assert_eq!(relations.parent_of(&child), Some(&parent));
628 assert!(relations.contains_relation(&CanvasRecordRelation::Group(
629 CanvasRecordGroupRelation::new(parent, child)
630 )));
631 }
632
633 #[test]
634 fn subset_for_records_filters_relation_facts_by_scope() {
635 let child = CanvasRecordId::Node(NodeId::from("child"));
636 let group = CanvasRecordId::Shape(ShapeId::from("group"));
637 let outsider = CanvasRecordId::Node(NodeId::from("outsider"));
638 let binding = CanvasRecordBindingRelation::new("binding", child.clone(), group.clone());
639 let mut relations = CanvasRecordRelations::default();
640 relations.set_parent(child.clone(), group.clone());
641 relations.add_to_group(group.clone(), child.clone());
642 relations.add_to_group(group.clone(), outsider.clone());
643 relations.set_binding(binding.clone());
644
645 let scope: IndexSet<CanvasRecordId> = IndexSet::from_iter([child.clone(), group.clone()]);
646 let subset = relations.subset_for_records(|record_id| scope.contains(record_id));
647
648 assert_eq!(subset.parent_of(&child), Some(&group));
649 assert_eq!(
650 subset.members_of(&group).cloned().collect::<Vec<_>>(),
651 vec![child.clone()]
652 );
653 assert_eq!(subset.binding(&BindingId::from("binding")), Some(&binding));
654 assert!(!subset.contains_relation(&CanvasRecordRelation::Group(
655 CanvasRecordGroupRelation::new(group, outsider)
656 )));
657 }
658
659 #[test]
660 fn relations_builder_accepts_unified_relation_records() {
661 let child = CanvasRecordId::Node(NodeId::from("child"));
662 let parent = CanvasRecordId::Shape(ShapeId::from("frame"));
663 let mut builder = CanvasRecordRelationsBuilder::new();
664 builder.add_relation(CanvasRecordParentRelation::new(
665 child.clone(),
666 parent.clone(),
667 ));
668 builder.add_relation(CanvasRecordGroupRelation::new(
669 parent.clone(),
670 child.clone(),
671 ));
672 builder.add_relation(CanvasRecordBindingRelation::new(
673 "binding",
674 child.clone(),
675 parent.clone(),
676 ));
677
678 let relations = builder.build();
679
680 assert!(relations.contains_relation(&CanvasRecordRelation::Parent(
681 CanvasRecordParentRelation::new(child.clone(), parent.clone())
682 )));
683 assert!(relations.contains_relation(&CanvasRecordRelation::Group(
684 CanvasRecordGroupRelation::new(parent.clone(), child.clone())
685 )));
686 assert!(relations.contains_relation(&CanvasRecordRelation::Binding(
687 CanvasRecordBindingRelation::new("binding", child, parent)
688 )));
689 }
690
691 #[test]
692 fn binding_relations_are_keyed_by_binding_id() {
693 let source = CanvasRecordId::Node(NodeId::from("source"));
694 let target = CanvasRecordId::Shape(ShapeId::from("target"));
695 let replacement_target = CanvasRecordId::Shape(ShapeId::from("replacement-target"));
696 let mut relations = CanvasRecordRelations::default();
697
698 assert_eq!(
699 relations.set_binding(CanvasRecordBindingRelation::new(
700 "binding",
701 source.clone(),
702 target.clone(),
703 )),
704 None
705 );
706 assert_eq!(
707 relations.set_binding(CanvasRecordBindingRelation::new(
708 "binding",
709 source.clone(),
710 replacement_target.clone(),
711 )),
712 Some(CanvasRecordBindingRelation::new(
713 "binding",
714 source.clone(),
715 target
716 ))
717 );
718
719 assert_eq!(
720 relations.bindings_for(&source).cloned().collect::<Vec<_>>(),
721 vec![CanvasRecordBindingRelation::new(
722 "binding",
723 source,
724 replacement_target
725 )]
726 );
727 assert_eq!(
728 relations.remove_binding(&BindingId::from("binding")),
729 Some(CanvasRecordBindingRelation::new(
730 "binding",
731 CanvasRecordId::Node(NodeId::from("source")),
732 CanvasRecordId::Shape(ShapeId::from("replacement-target")),
733 ))
734 );
735 assert!(relations.is_empty());
736 }
737
738 #[test]
739 fn relation_equality_uses_semantic_ordering() {
740 let group_a = CanvasRecordId::Shape(ShapeId::from("group-a"));
741 let group_b = CanvasRecordId::Shape(ShapeId::from("group-b"));
742 let member_a = CanvasRecordId::Node(NodeId::from("member-a"));
743 let member_b = CanvasRecordId::Node(NodeId::from("member-b"));
744
745 let mut left = CanvasRecordRelations::default();
746 left.set_parent(member_a.clone(), group_a.clone());
747 left.set_parent(member_b.clone(), group_b.clone());
748 left.add_to_group(group_a.clone(), member_a.clone());
749 left.add_to_group(group_b.clone(), member_b.clone());
750 left.set_binding(CanvasRecordBindingRelation::new(
751 "binding-a",
752 member_a.clone(),
753 group_a.clone(),
754 ));
755 left.set_binding(CanvasRecordBindingRelation::new(
756 "binding-b",
757 member_b.clone(),
758 group_b.clone(),
759 ));
760
761 let mut right = CanvasRecordRelations::default();
762 right.set_parent(member_b.clone(), group_b.clone());
763 right.set_parent(member_a.clone(), group_a.clone());
764 right.add_to_group(group_b.clone(), member_b.clone());
765 right.add_to_group(group_a.clone(), member_a.clone());
766 right.set_binding(CanvasRecordBindingRelation::new(
767 "binding-b",
768 member_b,
769 group_b,
770 ));
771 right.set_binding(CanvasRecordBindingRelation::new(
772 "binding-a",
773 member_a,
774 group_a,
775 ));
776
777 assert_eq!(left, right);
778 }
779
780 #[test]
781 fn prune_missing_records_removes_dangling_relations() {
782 let mut relations = CanvasRecordRelations::default();
783 let group = CanvasRecordId::Shape(ShapeId::from("group"));
784 let member = CanvasRecordId::Node(NodeId::from("member"));
785 let missing = CanvasRecordId::Node(NodeId::from("missing"));
786 relations.set_parent(member.clone(), group.clone());
787 relations.add_to_group(group.clone(), member.clone());
788 relations.add_to_group(group.clone(), missing.clone());
789 relations.set_binding(CanvasRecordBindingRelation::new(
790 "binding",
791 member.clone(),
792 missing,
793 ));
794
795 let existing = IndexSet::from_iter([group.clone(), member.clone()]);
796
797 assert!(relations.prune_missing_records(&existing));
798 assert_eq!(relations.parent_of(&member), Some(&group));
799 assert_eq!(
800 relations.members_of(&group).cloned().collect::<Vec<_>>(),
801 vec![member]
802 );
803 assert!(relations.bindings().next().is_none());
804 }
805}