1use crate::doc_ng::{register::Applied, write_order::WriteOrder};
7
8pub trait Entity {
9 type Update;
10 type Init;
11 type Id: Eq + std::hash::Hash + Copy;
12 fn from_init(init: &Self::Init, order: WriteOrder) -> Self;
15 fn apply(&mut self, update: &Self::Update, order: WriteOrder) -> Applied;
16 fn max_order(&self) -> WriteOrder;
22 fn invert(&self, update: &Self::Update) -> Self::Update;
28}
29
30macro_rules! entity {
38 (
39 $(#[$meta:meta])*
40 pub struct $entity:ident(init $init:ident, update $update:ident, id $id:ty) {
41 registers { $( $(#[$rmeta:meta])* $rvar:ident => $rfield:ident : $rty:ty ),* $(,)? }
42 namespaces { $( $nvar:ident => $nfield:ident : $nty:ty ),* $(,)? }
43 constants { $( $(#[$cmeta:meta])* $cfield:ident : $cty:ty ),* $(,)? }
44 }
45 ) => {
46 $(#[$meta])*
47 #[derive(Clone, serde::Serialize)]
48 pub struct $entity {
49 $( $(#[$rmeta])* pub $rfield: $crate::doc_ng::register::Register<$rty>, )*
50 $( pub $nfield: $nty, )*
51 $( $(#[$cmeta])* pub $cfield: $cty, )*
52 }
53
54 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
55 pub struct $init {
56 $( $(#[$rmeta])* pub $rfield: $rty, )*
57 $( pub $nfield: <$nty as $crate::doc_ng::entity::Entity>::Init, )*
58 $( $(#[$cmeta])* pub $cfield: $cty, )*
59 }
60
61 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
62 pub enum $update {
63 $( $rvar($rty), )*
64 $( $nvar(<$nty as $crate::doc_ng::entity::Entity>::Update), )*
65 }
66
67 impl $crate::doc_ng::entity::Entity for $entity {
68 type Init = $init;
69 type Update = $update;
70 type Id = $id;
71 fn from_init(
72 init: &Self::Init,
73 order: $crate::doc_ng::write_order::WriteOrder,
74 ) -> Self {
75 Self {
76 $( $rfield: $crate::doc_ng::register::Register::new(&init.$rfield, order), )*
77 $(
78 $nfield: <$nty as $crate::doc_ng::entity::Entity>::from_init(
79 &init.$nfield,
80 order,
81 ),
82 )*
83 $( $cfield: init.$cfield, )*
84 }
85 }
86 fn apply(
87 &mut self,
88 update: &Self::Update,
89 order: $crate::doc_ng::write_order::WriteOrder,
90 ) -> $crate::doc_ng::register::Applied {
91 match update {
92 $( $update::$rvar(value) => self.$rfield.apply(value, order), )*
93 $( $update::$nvar(value) => self.$nfield.apply(value, order), )*
94 }
95 }
96 fn max_order(&self) -> $crate::doc_ng::write_order::WriteOrder {
97 [
98 $( self.$rfield.order(), )*
99 $( self.$nfield.max_order(), )*
100 ]
101 .into_iter()
102 .fold(
103 $crate::doc_ng::write_order::WriteOrder::BOTTOM,
104 $crate::doc_ng::write_order::WriteOrder::max,
105 )
106 }
107 fn invert(&self, update: &Self::Update) -> Self::Update {
108 match update {
109 $( $update::$rvar(_) => $update::$rvar(self.$rfield.as_ref().clone()), )*
110 $( $update::$nvar(value) => $update::$nvar(self.$nfield.invert(value)), )*
111 }
112 }
113 }
114 };
115}
116pub(crate) use entity;
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::doc_ng::fixtures::{block_id, pin_id, route_id};
122 use crate::doc_ng::{
123 block_model::{
124 Block, BlockInit, BlockUpdate, Comment, CommentInit, CommentUpdate, Icon, Image,
125 ImageInit, ImageUpdate, LabelInit, LabelUpdate, Live, Pin, PinInit, PinUpdate, Route,
126 RouteInit, RouteLabel, RouteLabelInit, RouteLabelUpdate, RouteUpdate, Text, TextInit,
127 TextUpdate,
128 },
129 geometry::{
130 FracVal, GridPoint, GridRect, GridSize, ScreenPoint, ScreenRect, ScreenSize, Waypoint,
131 },
132 hash::{AssetHash, AssetKind, Hasher},
133 rev::Rev,
134 values::{LabelSide, PinDir, Role},
135 write_order::Seq,
136 };
137
138 fn at(rev: u64) -> WriteOrder {
139 WriteOrder::new(Rev::new(rev), Seq::new(0))
140 }
141
142 fn rect(x: i32, y: i32, w: u32, h: u32) -> GridRect {
143 GridRect {
144 top_left: GridPoint { x, y },
145 size: GridSize { w, h },
146 }
147 }
148
149 fn screen_rect(x: f32, y: f32) -> ScreenRect {
150 ScreenRect {
151 top_left: ScreenPoint {
152 x: FracVal::from(x),
153 y: FracVal::from(y),
154 },
155 size: ScreenSize {
156 w: FracVal::from(8.0),
157 h: FracVal::from(8.0),
158 },
159 }
160 }
161
162 fn asset(bytes: &[u8]) -> AssetHash {
163 let mut hasher = Hasher::<AssetKind>::new();
164 hasher.update(bytes);
165 hasher.finalize()
166 }
167
168 fn label_init(name: &str) -> LabelInit {
171 LabelInit {
172 name: name.into(),
173 side: LabelSide::Bottom,
174 offset: FracVal::from(1.5),
175 hidden: true,
176 }
177 }
178
179 #[test]
180 fn a_block_from_init_carries_every_value_at_the_create_order() {
181 let icon = Icon {
182 asset: asset(b"icon"),
183 rect: screen_rect(1.0, 2.0),
184 };
185 let init = BlockInit {
186 parent: block_id(1),
187 rect: rect(2, 3, 4, 5),
188 locked: true,
189 title: label_init("title"),
190 type_label: label_init("type"),
191 icon: icon.clone(),
192 };
193 let block = Block::from_init(&init, at(7));
194
195 assert_eq!(*block.parent.as_ref(), block_id(1));
196 assert_eq!(*block.rect.as_ref(), rect(2, 3, 4, 5));
197 assert!(*block.locked.as_ref());
198 assert_eq!(block.title.name.as_ref().as_str(), "title");
199 assert_eq!(*block.title.side.as_ref(), LabelSide::Bottom);
200 assert_eq!(*block.title.offset.as_ref(), FracVal::from(1.5));
201 assert!(*block.title.hidden.as_ref());
202 assert_eq!(block.type_label.name.as_ref().as_str(), "type");
203 assert_eq!(*block.icon.as_ref(), icon);
204
205 for order in [
206 block.parent.order(),
207 block.rect.order(),
208 block.locked.order(),
209 block.title.name.order(),
210 block.title.side.order(),
211 block.title.offset.order(),
212 block.title.hidden.order(),
213 block.type_label.name.order(),
214 block.type_label.side.order(),
215 block.type_label.offset.order(),
216 block.type_label.hidden.order(),
217 block.icon.order(),
218 ] {
219 assert_eq!(order, at(7));
220 }
221 }
222
223 fn distinct_pin() -> PinInit {
226 PinInit {
227 owner: block_id(1),
228 name: "clk".into(),
229 type_name: "clock".into(),
230 tag: "t0".into(),
231 tag_hidden: true,
232 rect: rect(1, 1, 2, 2),
233 dir: PinDir::Output,
234 pin_accent: Role::Accent3,
235 port_accent: Role::Accent4,
236 port_pin_accent: Role::Accent5,
237 flip_lr: true,
238 }
239 }
240
241 #[test]
242 fn a_pin_from_init_carries_every_value_at_the_create_order() {
243 let init = distinct_pin();
244 let pin = Pin::from_init(&init, at(9));
245
246 assert_eq!(*pin.owner.as_ref(), block_id(1));
247 assert_eq!(pin.name.as_ref().as_str(), "clk");
248 assert_eq!(pin.type_name.as_ref().as_str(), "clock");
249 assert_eq!(pin.tag.as_ref().as_str(), "t0");
250 assert!(*pin.tag_hidden.as_ref());
251 assert_eq!(*pin.rect.as_ref(), rect(1, 1, 2, 2));
252 assert_eq!(*pin.dir.as_ref(), PinDir::Output);
253 assert_eq!(*pin.pin_accent.as_ref(), Role::Accent3);
254 assert_eq!(*pin.port_accent.as_ref(), Role::Accent4);
255 assert_eq!(*pin.port_pin_accent.as_ref(), Role::Accent5);
256 assert!(*pin.flip_lr.as_ref());
257
258 for order in [
259 pin.owner.order(),
260 pin.name.order(),
261 pin.type_name.order(),
262 pin.tag.order(),
263 pin.tag_hidden.order(),
264 pin.rect.order(),
265 pin.dir.order(),
266 pin.pin_accent.order(),
267 pin.port_accent.order(),
268 pin.port_pin_accent.order(),
269 pin.flip_lr.order(),
270 ] {
271 assert_eq!(order, at(9));
272 }
273 }
274
275 #[test]
276 fn a_route_from_init_carries_its_values_and_endpoint_constants() {
277 let waypoints = vec![Waypoint {
278 pos: GridPoint { x: 3, y: 4 },
279 locked: true,
280 }];
281 let init = RouteInit {
282 owner: block_id(1),
283 name: "net7".into(),
284 from: pin_id(2),
285 to: pin_id(3),
286 role: Role::Accent2,
287 waypoints: waypoints.clone(),
288 };
289 let route = Route::from_init(&init, at(4));
290
291 assert_eq!(*route.owner.as_ref(), block_id(1));
292 assert_eq!(route.name.as_ref().as_str(), "net7");
293 assert_eq!(route.from, pin_id(2));
294 assert_eq!(route.to, pin_id(3));
295 assert_eq!(*route.role.as_ref(), Role::Accent2);
296 assert_eq!(*route.waypoints.as_ref(), waypoints);
297
298 for order in [
299 route.owner.order(),
300 route.name.order(),
301 route.role.order(),
302 route.waypoints.order(),
303 ] {
304 assert_eq!(order, at(4));
305 }
306 }
307
308 #[test]
309 fn the_small_kinds_from_init_carry_their_values_at_the_create_order() {
310 let route_label = RouteLabel::from_init(
311 &RouteLabelInit {
312 owner: route_id(1),
313 pos: FracVal::from(0.25),
314 },
315 at(2),
316 );
317 assert_eq!(*route_label.owner.as_ref(), route_id(1));
318 assert_eq!(*route_label.pos.as_ref(), FracVal::from(0.25));
319 assert_eq!(route_label.owner.order(), at(2));
320 assert_eq!(route_label.pos.order(), at(2));
321
322 let text = Text::from_init(
323 &TextInit {
324 owner: block_id(1),
325 text: "note".into(),
326 pos: GridPoint { x: 5, y: 6 },
327 role: Role::Accent1,
328 },
329 at(3),
330 );
331 assert_eq!(text.text.as_ref().as_str(), "note");
332 assert_eq!(*text.pos.as_ref(), GridPoint { x: 5, y: 6 });
333 for order in [
334 text.owner.order(),
335 text.text.order(),
336 text.pos.order(),
337 text.role.order(),
338 ] {
339 assert_eq!(order, at(3));
340 }
341
342 let comment = Comment::from_init(
343 &CommentInit {
344 owner: block_id(1),
345 rect: rect(0, 0, 3, 3),
346 role: Role::Accent6,
347 title: label_init("comment"),
348 },
349 at(5),
350 );
351 assert_eq!(comment.title.name.as_ref().as_str(), "comment");
352 for order in [
353 comment.owner.order(),
354 comment.rect.order(),
355 comment.role.order(),
356 comment.title.name.order(),
357 comment.title.side.order(),
358 comment.title.offset.order(),
359 comment.title.hidden.order(),
360 ] {
361 assert_eq!(order, at(5));
362 }
363
364 let image = Image::from_init(
365 &ImageInit {
366 owner: block_id(1),
367 asset: asset(b"png"),
368 rect: screen_rect(3.0, 4.0),
369 },
370 at(6),
371 );
372 assert_eq!(*image.asset.as_ref(), asset(b"png"));
373 for order in [image.owner.order(), image.asset.order(), image.rect.order()] {
374 assert_eq!(order, at(6));
375 }
376 }
377
378 #[test]
382 fn namespace_descent_lands_in_the_named_label() {
383 let init = BlockInit {
384 parent: block_id(1),
385 rect: rect(0, 0, 1, 1),
386 locked: false,
387 title: label_init("title"),
388 type_label: label_init("type"),
389 icon: Icon::default(),
390 };
391 let mut block = Block::from_init(&init, at(1));
392
393 let applied = block.apply(
394 &BlockUpdate::Title(LabelUpdate::Name("renamed".into())),
395 at(2),
396 );
397 assert_eq!(applied, Applied::Won);
398 assert_eq!(block.title.name.as_ref().as_str(), "renamed");
399 assert_eq!(
400 block.type_label.name.as_ref().as_str(),
401 "type",
402 "the twin namespace must be untouched"
403 );
404 assert_eq!(
405 block.title.side.order(),
406 at(1),
407 "sibling registers in the named namespace must be untouched"
408 );
409 }
410
411 #[test]
412 fn apply_lands_each_update_in_its_register() {
413 let mut pin = Pin::from_init(
414 &PinInit {
415 owner: block_id(1),
416 name: "d0".into(),
417 type_name: String::new(),
418 tag: String::new(),
419 tag_hidden: false,
420 rect: rect(0, 0, 1, 1),
421 dir: PinDir::Input,
422 pin_accent: Role::Accent0,
423 port_accent: Role::Accent0,
424 port_pin_accent: Role::Accent0,
425 flip_lr: false,
426 },
427 at(1),
428 );
429 assert_eq!(
430 pin.apply(&PinUpdate::Dir(PinDir::InOut), at(2)),
431 Applied::Won
432 );
433 assert_eq!(*pin.dir.as_ref(), PinDir::InOut);
434 assert_eq!(
435 pin.name.order(),
436 at(1),
437 "untouched registers keep their order"
438 );
439
440 let mut route = Route::from_init(
441 &RouteInit {
442 owner: block_id(1),
443 name: String::new(),
444 from: pin_id(2),
445 to: pin_id(3),
446 role: Role::Accent0,
447 waypoints: Vec::new(),
448 },
449 at(1),
450 );
451 let dragged = vec![Waypoint {
452 pos: GridPoint { x: 9, y: 9 },
453 locked: false,
454 }];
455 assert_eq!(
456 route.apply(&RouteUpdate::Waypoints(dragged.clone()), at(2)),
457 Applied::Won
458 );
459 assert_eq!(*route.waypoints.as_ref(), dragged);
460 }
461
462 #[test]
465 fn a_stale_update_is_refused_and_the_value_stands() {
466 let init = BlockInit {
467 parent: block_id(1),
468 rect: rect(2, 3, 4, 5),
469 locked: false,
470 title: label_init("title"),
471 type_label: label_init("type"),
472 icon: Icon::default(),
473 };
474 let mut block = Block::from_init(&init, at(5));
475
476 let applied = block.apply(&BlockUpdate::Rect(rect(9, 9, 9, 9)), at(3));
477 assert_eq!(applied, Applied::LostToNewer);
478 assert_eq!(*block.rect.as_ref(), rect(2, 3, 4, 5));
479 assert_eq!(block.rect.order(), at(5));
480 }
481
482 fn the_walk_raises_max_order<E: Entity>(entity: &mut E, updates: Vec<E::Update>)
489 where
490 E::Update: std::fmt::Debug,
491 {
492 assert_eq!(
493 entity.max_order(),
494 at(1),
495 "the walk starts at the create order"
496 );
497 for (ndx, update) in updates.into_iter().enumerate() {
498 let order = at(2 + ndx as u64);
499 assert_eq!(
500 entity.apply(&update, order),
501 Applied::Won,
502 "the walk write {update:?} must land"
503 );
504 assert_eq!(
505 entity.max_order(),
506 order,
507 "the register behind {update:?} must join the max_order fold"
508 );
509 }
510 }
511
512 fn pin_fixture(order: WriteOrder) -> Pin {
513 Pin::from_init(
514 &PinInit {
515 owner: block_id(1),
516 name: "d0".into(),
517 type_name: String::new(),
518 tag: String::new(),
519 tag_hidden: false,
520 rect: rect(0, 0, 1, 1),
521 dir: PinDir::Input,
522 pin_accent: Role::Accent0,
523 port_accent: Role::Accent0,
524 port_pin_accent: Role::Accent0,
525 flip_lr: false,
526 },
527 order,
528 )
529 }
530
531 #[test]
532 fn max_order_walks_every_block_register() {
533 let mut block = Block::from_init(
534 &BlockInit {
535 parent: block_id(1),
536 rect: rect(0, 0, 1, 1),
537 locked: false,
538 title: label_init("title"),
539 type_label: label_init("type"),
540 icon: Icon::default(),
541 },
542 at(1),
543 );
544 the_walk_raises_max_order(
545 &mut block,
546 vec![
547 BlockUpdate::Parent(block_id(9)),
548 BlockUpdate::Rect(rect(9, 9, 9, 9)),
549 BlockUpdate::Locked(true),
550 BlockUpdate::Title(LabelUpdate::Name("renamed".into())),
551 BlockUpdate::Title(LabelUpdate::Side(LabelSide::Center)),
552 BlockUpdate::Title(LabelUpdate::Offset(FracVal::from(2.0))),
553 BlockUpdate::Title(LabelUpdate::Hidden(true)),
554 BlockUpdate::TypeLabel(LabelUpdate::Name("renamed".into())),
555 BlockUpdate::TypeLabel(LabelUpdate::Side(LabelSide::Center)),
556 BlockUpdate::TypeLabel(LabelUpdate::Offset(FracVal::from(2.0))),
557 BlockUpdate::TypeLabel(LabelUpdate::Hidden(true)),
558 BlockUpdate::Icon(Icon::default()),
559 ],
560 );
561 }
562
563 #[test]
564 fn max_order_walks_every_pin_register() {
565 let mut pin = pin_fixture(at(1));
566 the_walk_raises_max_order(
567 &mut pin,
568 vec![
569 PinUpdate::Owner(block_id(9)),
570 PinUpdate::Name("clk".into()),
571 PinUpdate::TypeName("clock".into()),
572 PinUpdate::Tag("t1".into()),
573 PinUpdate::TagHidden(true),
574 PinUpdate::Rect(rect(9, 9, 9, 9)),
575 PinUpdate::Dir(PinDir::Output),
576 PinUpdate::PinAccent(Role::Accent1),
577 PinUpdate::PortAccent(Role::Accent2),
578 PinUpdate::PortPinAccent(Role::Accent3),
579 PinUpdate::FlipLR(true),
580 ],
581 );
582 }
583
584 #[test]
585 fn max_order_walks_the_small_kinds_registers() {
586 let mut route = Route::from_init(
587 &RouteInit {
588 owner: block_id(1),
589 name: String::new(),
590 from: pin_id(2),
591 to: pin_id(3),
592 role: Role::Accent0,
593 waypoints: Vec::new(),
594 },
595 at(1),
596 );
597 the_walk_raises_max_order(
598 &mut route,
599 vec![
600 RouteUpdate::Owner(block_id(9)),
601 RouteUpdate::Name("net".into()),
602 RouteUpdate::Role(Role::Accent1),
603 RouteUpdate::Waypoints(Vec::new()),
604 ],
605 );
606
607 let mut route_label = RouteLabel::from_init(
608 &RouteLabelInit {
609 owner: route_id(1),
610 pos: FracVal::from(0.25),
611 },
612 at(1),
613 );
614 the_walk_raises_max_order(
615 &mut route_label,
616 vec![
617 RouteLabelUpdate::Owner(route_id(9)),
618 RouteLabelUpdate::Pos(FracVal::from(0.5)),
619 ],
620 );
621
622 let mut text = Text::from_init(
623 &TextInit {
624 owner: block_id(1),
625 text: "note".into(),
626 pos: GridPoint { x: 5, y: 6 },
627 role: Role::Accent1,
628 },
629 at(1),
630 );
631 the_walk_raises_max_order(
632 &mut text,
633 vec![
634 TextUpdate::Owner(block_id(9)),
635 TextUpdate::Text("edited".into()),
636 TextUpdate::Pos(GridPoint { x: 9, y: 9 }),
637 TextUpdate::Role(Role::Accent2),
638 ],
639 );
640
641 let mut comment = Comment::from_init(
642 &CommentInit {
643 owner: block_id(1),
644 rect: rect(0, 0, 3, 3),
645 role: Role::Accent6,
646 title: label_init("comment"),
647 },
648 at(1),
649 );
650 the_walk_raises_max_order(
651 &mut comment,
652 vec![
653 CommentUpdate::Owner(block_id(9)),
654 CommentUpdate::Rect(rect(9, 9, 9, 9)),
655 CommentUpdate::Role(Role::Accent1),
656 CommentUpdate::Title(LabelUpdate::Name("renamed".into())),
657 CommentUpdate::Title(LabelUpdate::Side(LabelSide::Center)),
658 CommentUpdate::Title(LabelUpdate::Offset(FracVal::from(2.0))),
659 CommentUpdate::Title(LabelUpdate::Hidden(true)),
660 ],
661 );
662
663 let mut image = Image::from_init(
664 &ImageInit {
665 owner: block_id(1),
666 asset: asset(b"png"),
667 rect: screen_rect(3.0, 4.0),
668 },
669 at(1),
670 );
671 the_walk_raises_max_order(
672 &mut image,
673 vec![
674 ImageUpdate::Owner(block_id(9)),
675 ImageUpdate::Asset(asset(b"jpg")),
676 ImageUpdate::Rect(screen_rect(9.0, 9.0)),
677 ],
678 );
679 }
680
681 #[test]
685 fn presence_and_inner_writes_raise_a_lives_max_order() {
686 let mut live = Live::new(pin_fixture(at(1)), at(1));
687 assert_eq!(live.max_order(), at(1));
688
689 assert_eq!(live.delete(at(2)), Applied::Won);
690 assert_eq!(live.max_order(), at(2), "a delete raises the max");
691 assert_eq!(live.restore(at(3)), Applied::Won);
692 assert_eq!(live.max_order(), at(3), "a restore raises the max");
693 assert_eq!(
694 live.apply_update(&PinUpdate::Dir(PinDir::InOut), at(4)),
695 Applied::Won
696 );
697 assert_eq!(live.max_order(), at(4), "an inner write raises the max");
698 }
699
700 #[test]
705 fn invert_reads_the_register_each_variant_targets() {
706 let pin = Pin::from_init(&distinct_pin(), at(1));
707
708 assert!(matches!(
709 pin.invert(&PinUpdate::Owner(block_id(9))),
710 PinUpdate::Owner(was) if was == block_id(1)
711 ));
712 assert!(matches!(
713 pin.invert(&PinUpdate::Name("d0".into())),
714 PinUpdate::Name(was) if was == "clk"
715 ));
716 assert!(matches!(
717 pin.invert(&PinUpdate::TypeName("reset".into())),
718 PinUpdate::TypeName(was) if was == "clock"
719 ));
720 assert!(matches!(
721 pin.invert(&PinUpdate::Tag("t9".into())),
722 PinUpdate::Tag(was) if was == "t0"
723 ));
724 assert!(matches!(
725 pin.invert(&PinUpdate::TagHidden(false)),
726 PinUpdate::TagHidden(true)
727 ));
728 assert!(matches!(
729 pin.invert(&PinUpdate::Rect(rect(9, 9, 9, 9))),
730 PinUpdate::Rect(was) if was == rect(1, 1, 2, 2)
731 ));
732 assert!(matches!(
733 pin.invert(&PinUpdate::Dir(PinDir::InOut)),
734 PinUpdate::Dir(PinDir::Output)
735 ));
736 assert!(matches!(
737 pin.invert(&PinUpdate::PinAccent(Role::Accent0)),
738 PinUpdate::PinAccent(Role::Accent3)
739 ));
740 assert!(matches!(
741 pin.invert(&PinUpdate::PortAccent(Role::Accent0)),
742 PinUpdate::PortAccent(Role::Accent4)
743 ));
744 assert!(matches!(
745 pin.invert(&PinUpdate::PortPinAccent(Role::Accent0)),
746 PinUpdate::PortPinAccent(Role::Accent5)
747 ));
748 assert!(matches!(
749 pin.invert(&PinUpdate::FlipLR(false)),
750 PinUpdate::FlipLR(true)
751 ));
752 }
753
754 #[test]
758 fn invert_descends_into_the_named_namespace() {
759 let block = Block::from_init(
760 &BlockInit {
761 parent: block_id(1),
762 rect: rect(0, 0, 1, 1),
763 locked: false,
764 title: label_init("title"),
765 type_label: label_init("type"),
766 icon: Icon::default(),
767 },
768 at(1),
769 );
770
771 assert!(matches!(
772 block.invert(&BlockUpdate::Title(LabelUpdate::Name("renamed".into()))),
773 BlockUpdate::Title(LabelUpdate::Name(was)) if was == "title"
774 ));
775 assert!(matches!(
776 block.invert(&BlockUpdate::TypeLabel(LabelUpdate::Name("renamed".into()))),
777 BlockUpdate::TypeLabel(LabelUpdate::Name(was)) if was == "type"
778 ));
779 }
780
781 #[test]
784 fn an_inverse_restores_the_displaced_value_as_a_new_write() {
785 let mut pin = Pin::from_init(&distinct_pin(), at(1));
786 let inverse = pin.invert(&PinUpdate::Tag("t9".into()));
787
788 assert_eq!(pin.apply(&PinUpdate::Tag("t9".into()), at(2)), Applied::Won);
789 assert_eq!(pin.tag.as_ref().as_str(), "t9");
790
791 assert_eq!(pin.apply(&inverse, at(3)), Applied::Won);
792 assert_eq!(pin.tag.as_ref().as_str(), "t0");
793 assert_eq!(pin.tag.order(), at(3), "the undo is the latest write");
794 }
795
796 #[test]
799 fn untouched_entities_tie_at_their_creation_order() {
800 let one = Live::new(pin_fixture(at(7)), at(7));
801 let other = Live::new(pin_fixture(at(7)), at(7));
802 assert_eq!(one.max_order(), other.max_order());
803 assert_eq!(one.max_order(), at(7));
804 }
805}