1 package d2oracle_test
2
3 import (
4 "fmt"
5 "io"
6 "io/fs"
7 "os"
8 "path/filepath"
9 "strconv"
10 "strings"
11 "testing"
12
13 "oss.terrastruct.com/util-go/assert"
14 "oss.terrastruct.com/util-go/diff"
15 "oss.terrastruct.com/util-go/go2"
16 "oss.terrastruct.com/util-go/xjson"
17
18 "oss.terrastruct.com/d2/d2compiler"
19 "oss.terrastruct.com/d2/d2format"
20 "oss.terrastruct.com/d2/d2graph"
21 "oss.terrastruct.com/d2/d2oracle"
22 "oss.terrastruct.com/d2/d2target"
23 )
24
25
26
27
28 func TestCreate(t *testing.T) {
29 t.Parallel()
30
31 testCases := []struct {
32 boardPath []string
33 name string
34 text string
35 key string
36
37 expKey string
38 expErr string
39 exp string
40 assertions func(t *testing.T, g *d2graph.Graph)
41 }{
42 {
43 name: "base",
44 text: ``,
45 key: `square`,
46
47 expKey: `square`,
48 exp: `square
49 `,
50 assertions: func(t *testing.T, g *d2graph.Graph) {
51 if len(g.Objects) != 1 {
52 t.Fatalf("expected 1 objects: %#v", g.Objects)
53 }
54 if g.Objects[0].ID != "square" {
55 t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
56 }
57 if g.Objects[0].Label.MapKey.Value.Unbox() != nil {
58 t.Fatalf("expected g.Objects[0].Label.Node.Value.Unbox() == nil: %#v", g.Objects[0].Label.MapKey.Value)
59 }
60 if d2format.Format(g.Objects[0].Label.MapKey.Key) != "square" {
61 t.Fatalf("expected g.Objects[0].Label.Node.Key to be square: %#v", g.Objects[0].Label.MapKey.Key)
62 }
63 },
64 },
65 {
66 name: "gen_key_suffix",
67 text: `"x "
68 `,
69 key: `"x "`,
70
71 expKey: `x 2`,
72 exp: `"x "
73 x 2
74 `,
75 assertions: func(t *testing.T, g *d2graph.Graph) {
76 if len(g.Objects) != 2 {
77 t.Fatalf("unexpected objects length: %#v", g.Objects)
78 }
79 if g.Objects[1].ID != `x 2` {
80 t.Fatalf("bad object ID: %#v", g.Objects[1])
81 }
82 },
83 },
84 {
85 name: "nested",
86 text: ``,
87 key: `b.c.square`,
88
89 expKey: `b.c.square`,
90 exp: `b.c.square
91 `,
92 assertions: func(t *testing.T, g *d2graph.Graph) {
93 if len(g.Objects) != 3 {
94 t.Fatalf("unexpected objects length: %#v", g.Objects)
95 }
96 if g.Objects[2].AbsID() != "b.c.square" {
97 t.Fatalf("bad absolute ID: %#v", g.Objects[2].AbsID())
98 }
99 if d2format.Format(g.Objects[2].Label.MapKey.Key) != "b.c.square" {
100 t.Fatalf("bad mapkey: %#v", g.Objects[2].Label.MapKey.Key)
101 }
102 if g.Objects[2].Label.MapKey.Value.Unbox() != nil {
103 t.Fatalf("expected nil mapkey value: %#v", g.Objects[2].Label.MapKey.Value)
104 }
105 },
106 },
107 {
108 name: "gen_key",
109 text: `square`,
110 key: `square`,
111
112 expKey: `square 2`,
113 exp: `square
114 square 2
115 `,
116 assertions: func(t *testing.T, g *d2graph.Graph) {
117 if len(g.Objects) != 2 {
118 t.Fatalf("expected 2 objects: %#v", g.Objects)
119 }
120 if g.Objects[1].ID != "square 2" {
121 t.Fatalf("expected g.Objects[1].ID to be square 2: %#v", g.Objects[1])
122 }
123 if g.Objects[1].Label.MapKey.Value.Unbox() != nil {
124 t.Fatalf("expected g.Objects[1].Label.Node.Value.Unbox() == nil: %#v", g.Objects[1].Label.MapKey.Value)
125 }
126 if d2format.Format(g.Objects[1].Label.MapKey.Key) != "square 2" {
127 t.Fatalf("expected g.Objects[1].Label.Node.Key to be square 2: %#v", g.Objects[1].Label.MapKey.Key)
128 }
129 },
130 },
131 {
132 name: "gen_key_nested",
133 text: `x.y.z.square`,
134 key: `x.y.z.square`,
135
136 expKey: `x.y.z.square 2`,
137 exp: `x.y.z.square
138 x.y.z.square 2
139 `,
140 assertions: func(t *testing.T, g *d2graph.Graph) {
141 if len(g.Objects) != 5 {
142 t.Fatalf("unexpected objects length: %#v", g.Objects)
143 }
144 if g.Objects[4].ID != "square 2" {
145 t.Fatalf("unexpected object id: %#v", g.Objects[4])
146 }
147 },
148 },
149 {
150 name: "scope",
151 text: `x.y.z: {
152 }`,
153 key: `x.y.z.square`,
154
155 expKey: `x.y.z.square`,
156 exp: `x.y.z: {
157 square
158 }
159 `,
160 assertions: func(t *testing.T, g *d2graph.Graph) {
161 if len(g.Objects) != 4 {
162 t.Fatalf("expected 4 objects: %#v", g.Objects)
163 }
164 if g.Objects[3].ID != "square" {
165 t.Fatalf("expected g.Objects[3].ID to be square: %#v", g.Objects[3])
166 }
167 if g.Objects[3].Label.MapKey.Value.Unbox() != nil {
168 t.Fatalf("expected g.Objects[3].Label.Node.Value.Unbox() == nil: %#v", g.Objects[3].Label.MapKey.Value)
169 }
170 if d2format.Format(g.Objects[3].Label.MapKey.Key) != "square" {
171 t.Fatalf("expected g.Objects[3].Label.Node.Key to be square: %#v", g.Objects[3].Label.MapKey.Key)
172 }
173 },
174 },
175 {
176 name: "gen_key_scope",
177 text: `x.y.z: {
178 square
179 }`,
180 key: `x.y.z.square`,
181
182 expKey: `x.y.z.square 2`,
183 exp: `x.y.z: {
184 square
185 square 2
186 }
187 `,
188 assertions: func(t *testing.T, g *d2graph.Graph) {
189 if len(g.Objects) != 5 {
190 t.Fatalf("expected 5 objects: %#v", g.Objects)
191 }
192 if g.Objects[4].ID != "square 2" {
193 t.Fatalf("expected g.Objects[4].ID to be square 2: %#v", g.Objects[4])
194 }
195 if g.Objects[4].Label.MapKey.Value.Unbox() != nil {
196 t.Fatalf("expected g.Objects[4].Label.Node.Value.Unbox() == nil: %#v", g.Objects[4].Label.MapKey.Value)
197 }
198 if d2format.Format(g.Objects[4].Label.MapKey.Key) != "square 2" {
199 t.Fatalf("expected g.Objects[4].Label.Node.Key to be square 2: %#v", g.Objects[4].Label.MapKey.Key)
200 }
201 },
202 },
203 {
204 name: "gen_key_n",
205 text: `x.y.z: {
206 square
207 square 2
208 square 3
209 square 4
210 square 5
211 square 6
212 square 7
213 square 8
214 square 9
215 square 10
216 }`,
217 key: `x.y.z.square`,
218
219 expKey: `x.y.z.square 11`,
220 exp: `x.y.z: {
221 square
222 square 2
223 square 3
224 square 4
225 square 5
226 square 6
227 square 7
228 square 8
229 square 9
230 square 10
231 square 11
232 }
233 `,
234 assertions: func(t *testing.T, g *d2graph.Graph) {
235 if len(g.Objects) != 14 {
236 t.Fatalf("expected 14 objects: %#v", g.Objects)
237 }
238 if g.Objects[13].ID != "square 11" {
239 t.Fatalf("expected g.Objects[13].ID to be square 11: %#v", g.Objects[13])
240 }
241 if d2format.Format(g.Objects[13].Label.MapKey.Key) != "square 11" {
242 t.Fatalf("expected g.Objects[13].Label.Node.Key to be square 11: %#v", g.Objects[13].Label.MapKey.Key)
243 }
244 },
245 },
246 {
247 name: "edge",
248 text: ``,
249 key: `x -> y`,
250
251 expKey: `(x -> y)[0]`,
252 exp: `x -> y
253 `,
254 assertions: func(t *testing.T, g *d2graph.Graph) {
255 if len(g.Objects) != 2 {
256 t.Fatalf("expected 2 objects: %#v", g.Objects)
257 }
258 if len(g.Edges) != 1 {
259 t.Fatalf("expected 1 edge: %#v", g.Edges)
260 }
261 if g.Edges[0].Src.ID != "x" {
262 t.Fatalf("expected g.Edges[0].Src.ID == x: %#v", g.Edges[0].Src.ID)
263 }
264 if g.Edges[0].Dst.ID != "y" {
265 t.Fatalf("expected g.Edges[0].Dst.ID == y: %#v", g.Edges[0].Dst.ID)
266 }
267 },
268 },
269 {
270 name: "edge_nested",
271 text: ``,
272 key: `container.(x -> y)`,
273
274 expKey: `container.(x -> y)[0]`,
275 exp: `container.(x -> y)
276 `,
277 assertions: func(t *testing.T, g *d2graph.Graph) {
278 if len(g.Objects) != 3 {
279 t.Fatalf("unexpected objects: %#v", g.Objects)
280 }
281 if len(g.Edges) != 1 {
282 t.Fatalf("unexpected edges: %#v", g.Edges)
283 }
284 },
285 },
286 {
287 name: "edge_scope",
288 text: `container: {
289 }`,
290 key: `container.(x -> y)`,
291
292 expKey: `container.(x -> y)[0]`,
293 exp: `container: {
294 x -> y
295 }
296 `,
297 assertions: func(t *testing.T, g *d2graph.Graph) {
298 if len(g.Objects) != 3 {
299 t.Fatalf("expected 3 objects: %#v", g.Objects)
300 }
301 },
302 },
303 {
304 name: "edge_scope_flat",
305 text: `container: {
306 }`,
307 key: `container.x -> container.y`,
308
309 expKey: `container.(x -> y)[0]`,
310 exp: `container: {
311 x -> y
312 }
313 `,
314 assertions: func(t *testing.T, g *d2graph.Graph) {
315 if len(g.Objects) != 3 {
316 t.Fatalf("expected 3 objects: %#v", g.Objects)
317 }
318 },
319 },
320 {
321 name: "edge_scope_nested",
322 text: `x.y`,
323 key: `x.y.z -> x.y.q`,
324
325 expKey: `x.y.(z -> q)[0]`,
326 exp: `x.y: {
327 z -> q
328 }
329 `,
330 assertions: func(t *testing.T, g *d2graph.Graph) {
331 if len(g.Objects) != 4 {
332 t.Fatalf("unexpected objects: %#v", g.Objects)
333 }
334 if len(g.Edges) != 1 {
335 t.Fatalf("unexpected edges: %#v", g.Edges)
336 }
337 },
338 },
339 {
340 name: "edge_unique",
341 text: `x -> y
342 hello.(x -> y)
343 hello.(x -> y)
344 `,
345 key: `hello.(x -> y)`,
346
347 expKey: `hello.(x -> y)[2]`,
348 exp: `x -> y
349 hello.(x -> y)
350 hello.(x -> y)
351 hello.(x -> y)
352 `,
353 assertions: func(t *testing.T, g *d2graph.Graph) {
354 if len(g.Objects) != 5 {
355 t.Fatalf("expected 5 objects: %#v", g.Objects)
356 }
357 if len(g.Edges) != 4 {
358 t.Fatalf("expected 4 edges: %#v", g.Edges)
359 }
360 },
361 },
362 {
363 name: "container",
364 text: `b`,
365 key: `b.q`,
366
367 expKey: `b.q`,
368 exp: `b: {
369 q
370 }
371 `,
372 assertions: func(t *testing.T, g *d2graph.Graph) {
373 if len(g.Objects) != 2 {
374 t.Fatalf("expected 2 objects: %#v", g.Objects)
375 }
376 },
377 },
378 {
379 name: "container_edge",
380 text: `b`,
381 key: `b.x -> b.y`,
382
383 expKey: `b.(x -> y)[0]`,
384 exp: `b: {
385 x -> y
386 }
387 `,
388 assertions: func(t *testing.T, g *d2graph.Graph) {
389 if len(g.Objects) != 3 {
390 t.Fatalf("expected 3 objects: %#v", g.Objects)
391 }
392 },
393 },
394 {
395 name: "container_edge_label",
396 text: `b: zoom`,
397 key: `b.x -> b.y`,
398
399 expKey: `b.(x -> y)[0]`,
400 exp: `b: zoom {
401 x -> y
402 }
403 `,
404 assertions: func(t *testing.T, g *d2graph.Graph) {
405 if len(g.Objects) != 3 {
406 t.Fatalf("expected 3 objects: %#v", g.Objects)
407 }
408 },
409 },
410 {
411 name: "make_scope_multiline",
412
413 text: `rawr: {shape: circle}
414 `,
415 key: `rawr.orange`,
416
417 expKey: `rawr.orange`,
418 exp: `rawr: {
419 shape: circle
420 orange
421 }
422 `,
423 },
424 {
425 name: "make_scope_multiline_spacing_1",
426
427 text: `before
428 rawr: {shape: circle}
429 after
430 `,
431 key: `rawr.orange`,
432
433 expKey: `rawr.orange`,
434 exp: `before
435 rawr: {
436 shape: circle
437 orange
438 }
439 after
440 `,
441 },
442 {
443 name: "make_scope_multiline_spacing_2",
444
445 text: `before
446
447 rawr: {shape: circle}
448
449 after
450 `,
451 key: `rawr.orange`,
452
453 expKey: `rawr.orange`,
454 exp: `before
455
456 rawr: {
457 shape: circle
458 orange
459 }
460
461 after
462 `,
463 },
464 {
465 name: "layers-basic",
466
467 text: `a
468
469 layers: {
470 x: {
471 a
472 }
473 }
474 `,
475 key: `b`,
476 boardPath: []string{"x"},
477
478 expKey: `b`,
479 exp: `a
480
481 layers: {
482 x: {
483 a
484 b
485 }
486 }
487 `,
488 },
489 {
490 name: "layers-edge",
491
492 text: `a
493
494 layers: {
495 x: {
496 a
497 }
498 }
499 `,
500 key: `a -> b`,
501 boardPath: []string{"x"},
502
503 expKey: `(a -> b)[0]`,
504 exp: `a
505
506 layers: {
507 x: {
508 a
509 a -> b
510 }
511 }
512 `,
513 },
514 {
515 name: "layers-edge-duplicate",
516
517 text: `a -> b
518
519 layers: {
520 x: {
521 a -> b
522 }
523 }
524 `,
525 key: `a -> b`,
526 boardPath: []string{"x"},
527
528 expKey: `(a -> b)[1]`,
529 exp: `a -> b
530
531 layers: {
532 x: {
533 a -> b
534 a -> b
535 }
536 }
537 `,
538 },
539 {
540 name: "scenarios-basic",
541
542 text: `a
543 b
544
545 scenarios: {
546 x: {
547 a
548 }
549 }
550 `,
551 key: `c`,
552 boardPath: []string{"x"},
553
554 expKey: `c`,
555 exp: `a
556 b
557
558 scenarios: {
559 x: {
560 a
561 c
562 }
563 }
564 `,
565 },
566 {
567 name: "scenarios-edge",
568
569 text: `a
570 b
571
572 scenarios: {
573 x: {
574 a
575 }
576 }
577 `,
578 key: `a -> b`,
579 boardPath: []string{"x"},
580
581 expKey: `(a -> b)[0]`,
582 exp: `a
583 b
584
585 scenarios: {
586 x: {
587 a
588 a -> b
589 }
590 }
591 `,
592 },
593 {
594 name: "scenarios-edge-inherited",
595
596 text: `a -> b
597
598 scenarios: {
599 x: {
600 a
601 }
602 }
603 `,
604 key: `a -> b`,
605 boardPath: []string{"x"},
606
607 expKey: `(a -> b)[1]`,
608 exp: `a -> b
609
610 scenarios: {
611 x: {
612 a
613 a -> b
614 }
615 }
616 `,
617 },
618 {
619 name: "steps-basic",
620
621 text: `a
622 d
623
624 steps: {
625 x: {
626 b
627 }
628 }
629 `,
630 key: `c`,
631 boardPath: []string{"x"},
632
633 expKey: `c`,
634 exp: `a
635 d
636
637 steps: {
638 x: {
639 b
640 c
641 }
642 }
643 `,
644 },
645 {
646 name: "steps-edge",
647
648 text: `a
649 d
650
651 steps: {
652 x: {
653 b
654 }
655 }
656 `,
657 key: `d -> b`,
658 boardPath: []string{"x"},
659
660 expKey: `(d -> b)[0]`,
661 exp: `a
662 d
663
664 steps: {
665 x: {
666 b
667 d -> b
668 }
669 }
670 `,
671 },
672 {
673 name: "steps-conflict",
674
675 text: `a
676 d
677
678 steps: {
679 x: {
680 b
681 }
682 }
683 `,
684 key: `d`,
685 boardPath: []string{"x"},
686
687 expKey: `d 2`,
688 exp: `a
689 d
690
691 steps: {
692 x: {
693 b
694 d 2
695 }
696 }
697 `,
698 },
699 }
700
701 for _, tc := range testCases {
702 tc := tc
703 t.Run(tc.name, func(t *testing.T) {
704 t.Parallel()
705
706 var newKey string
707 et := editTest{
708 text: tc.text,
709 testFunc: func(g *d2graph.Graph) (*d2graph.Graph, error) {
710 var err error
711 g, newKey, err = d2oracle.Create(g, tc.boardPath, tc.key)
712 return g, err
713 },
714
715 exp: tc.exp,
716 expErr: tc.expErr,
717 assertions: func(t *testing.T, g *d2graph.Graph) {
718 if newKey != tc.expKey {
719 t.Fatalf("expected %q but got %q", tc.expKey, newKey)
720 }
721 if tc.assertions != nil {
722 tc.assertions(t, g)
723 }
724 },
725 }
726 et.run(t)
727 })
728 }
729 }
730
731 func TestSet(t *testing.T) {
732 t.Parallel()
733
734 testCases := []struct {
735 boardPath []string
736 name string
737 text string
738 fsTexts map[string]string
739 key string
740 tag *string
741 value *string
742
743 expErr string
744 exp string
745 assertions func(t *testing.T, g *d2graph.Graph)
746 }{
747 {
748 name: "base",
749 text: ``,
750 key: `square`,
751
752 exp: `square
753 `,
754 assertions: func(t *testing.T, g *d2graph.Graph) {
755 if len(g.Objects) != 1 {
756 t.Fatalf("expected 1 objects: %#v", g.Objects)
757 }
758 if g.Objects[0].ID != "square" {
759 t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
760 }
761 if g.Objects[0].Label.MapKey.Value.Unbox() != nil {
762 t.Fatalf("expected g.Objects[0].Label.Node.Value.Unbox() == nil: %#v", g.Objects[0].Label.MapKey.Value)
763 }
764 if d2format.Format(g.Objects[0].Label.MapKey.Key) != "square" {
765 t.Fatalf("expected g.Objects[0].Label.Node.Key to be square: %#v", g.Objects[0].Label.MapKey.Key)
766 }
767 },
768 },
769 {
770 name: "edge",
771 text: `x -> y: one`,
772 key: `(x -> y)[0]`,
773 value: go2.Pointer(`two`),
774
775 exp: `x -> y: two
776 `,
777 assertions: func(t *testing.T, g *d2graph.Graph) {
778 if len(g.Objects) != 2 {
779 t.Fatalf("expected 2 objects: %#v", g.Objects)
780 }
781 if len(g.Edges) != 1 {
782 t.Fatalf("expected 1 edge: %#v", g.Edges)
783 }
784 if g.Edges[0].Src.ID != "x" {
785 t.Fatalf("expected g.Edges[0].Src.ID == x: %#v", g.Edges[0].Src.ID)
786 }
787 if g.Edges[0].Dst.ID != "y" {
788 t.Fatalf("expected g.Edges[0].Dst.ID == y: %#v", g.Edges[0].Dst.ID)
789 }
790 if g.Edges[0].Label.Value != "two" {
791 t.Fatalf("expected g.Edges[0].Label.Value == two: %#v", g.Edges[0].Label.Value)
792 }
793 },
794 },
795 {
796 name: "shape",
797 text: `square`,
798 key: `square.shape`,
799 value: go2.Pointer(`square`),
800
801 exp: `square: {shape: square}
802 `,
803 assertions: func(t *testing.T, g *d2graph.Graph) {
804 if len(g.Objects) != 1 {
805 t.Fatalf("expected 1 objects: %#v", g.Objects)
806 }
807 if g.Objects[0].ID != "square" {
808 t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
809 }
810 if g.Objects[0].Shape.Value != d2target.ShapeSquare {
811 t.Fatalf("expected g.Objects[0].Shape.Value == square: %#v", g.Objects[0].Shape.Value)
812 }
813 },
814 },
815 {
816 name: "replace_shape",
817 text: `square.shape: square`,
818 key: `square.shape`,
819 value: go2.Pointer(`circle`),
820
821 exp: `square.shape: circle
822 `,
823 assertions: func(t *testing.T, g *d2graph.Graph) {
824 if len(g.Objects) != 1 {
825 t.Fatalf("expected 1 objects: %#v", g.Objects)
826 }
827 if g.Objects[0].ID != "square" {
828 t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
829 }
830 if g.Objects[0].Shape.Value != d2target.ShapeCircle {
831 t.Fatalf("expected g.Objects[0].Shape.Value == circle: %#v", g.Objects[0].Shape.Value)
832 }
833 },
834 },
835 {
836 name: "new_style",
837 text: `square
838 `,
839 key: `square.style.opacity`,
840 value: go2.Pointer(`0.2`),
841 exp: `square: {style.opacity: 0.2}
842 `,
843 assertions: func(t *testing.T, g *d2graph.Graph) {
844 if len(g.AST.Nodes) != 1 {
845 t.Fatal(g.AST)
846 }
847 if len(g.Objects) != 1 {
848 t.Fatalf("expected 1 object but got %#v", len(g.Objects))
849 }
850 f, err := strconv.ParseFloat(g.Objects[0].Style.Opacity.Value, 64)
851 if err != nil || f != 0.2 {
852 t.Fatalf("expected g.Objects[0].Map.Nodes[0].MapKey.Value.Number.Value.Float64() == 0.2: %#v", f)
853 }
854 },
855 },
856 {
857 name: "inline_style",
858 text: `square: {style.opacity: 0.2}
859 `,
860 key: `square.style.fill`,
861 value: go2.Pointer(`red`),
862 exp: `square: {
863 style.opacity: 0.2
864 style.fill: red
865 }
866 `,
867 assertions: func(t *testing.T, g *d2graph.Graph) {
868 if len(g.AST.Nodes) != 1 {
869 t.Fatal(g.AST)
870 }
871 },
872 },
873 {
874 name: "expanded_map_style",
875 text: `square: {
876 style: {
877 opacity: 0.1
878 }
879 }
880 `,
881 key: `square.style.opacity`,
882 value: go2.Pointer(`0.2`),
883 exp: `square: {
884 style: {
885 opacity: 0.2
886 }
887 }
888 `,
889 assertions: func(t *testing.T, g *d2graph.Graph) {
890 if len(g.AST.Nodes) != 1 {
891 t.Fatal(g.AST)
892 }
893 if len(g.AST.Nodes[0].MapKey.Value.Map.Nodes) != 1 {
894 t.Fatalf("expected 1 node within square but got %v", len(g.AST.Nodes[0].MapKey.Value.Map.Nodes))
895 }
896 f, err := strconv.ParseFloat(g.Objects[0].Style.Opacity.Value, 64)
897 if err != nil || f != 0.2 {
898 t.Fatal(err, f)
899 }
900 },
901 },
902 {
903 name: "replace_style",
904 text: `square.style.opacity: 0.1
905 `,
906 key: `square.style.opacity`,
907 value: go2.Pointer(`0.2`),
908 exp: `square.style.opacity: 0.2
909 `,
910 assertions: func(t *testing.T, g *d2graph.Graph) {
911 if len(g.AST.Nodes) != 1 {
912 t.Fatal(g.AST)
913 }
914 f, err := strconv.ParseFloat(g.Objects[0].Style.Opacity.Value, 64)
915 if err != nil || f != 0.2 {
916 t.Fatal(err, f)
917 }
918 },
919 },
920 {
921 name: "replace_style_edgecase",
922 text: `square.style.fill: orange
923 `,
924 key: `square.style.opacity`,
925 value: go2.Pointer(`0.2`),
926 exp: `square.style.fill: orange
927 square.style.opacity: 0.2
928 `,
929 assertions: func(t *testing.T, g *d2graph.Graph) {
930 if len(g.AST.Nodes) != 2 {
931 t.Fatal(g.AST)
932 }
933 f, err := strconv.ParseFloat(g.Objects[0].Style.Opacity.Value, 64)
934 if err != nil || f != 0.2 {
935 t.Fatal(err, f)
936 }
937 },
938 },
939 {
940 name: "set_position",
941 text: `square
942 `,
943 key: `square.top`,
944 value: go2.Pointer(`200`),
945 exp: `square: {top: 200}
946 `,
947 },
948 {
949 name: "replace_position",
950 text: `square: {
951 width: 100
952 top: 32
953 left: 44
954 }
955 `,
956 key: `square.top`,
957 value: go2.Pointer(`200`),
958 exp: `square: {
959 width: 100
960 top: 200
961 left: 44
962 }
963 `,
964 },
965 {
966 name: "set_dimensions",
967 text: `square
968 `,
969 key: `square.width`,
970 value: go2.Pointer(`200`),
971 exp: `square: {width: 200}
972 `,
973 },
974 {
975 name: "replace_dimensions",
976 text: `square: {
977 width: 100
978 }
979 `,
980 key: `square.width`,
981 value: go2.Pointer(`200`),
982 exp: `square: {
983 width: 200
984 }
985 `,
986 },
987 {
988 name: "set_tooltip",
989 text: `square
990 `,
991 key: `square.tooltip`,
992 value: go2.Pointer(`y`),
993 exp: `square: {tooltip: y}
994 `,
995 },
996 {
997 name: "replace_tooltip",
998 text: `square: {
999 tooltip: x
1000 }
1001 `,
1002 key: `square.tooltip`,
1003 value: go2.Pointer(`y`),
1004 exp: `square: {
1005 tooltip: y
1006 }
1007 `,
1008 },
1009 {
1010 name: "replace_link",
1011 text: `square: {
1012 link: https://google.com
1013 }
1014 `,
1015 key: `square.link`,
1016 value: go2.Pointer(`https://apple.com`),
1017 exp: `square: {
1018 link: https://apple.com
1019 }
1020 `,
1021 },
1022 {
1023 name: "replace_arrowhead",
1024 text: `x -> y: {
1025 target-arrowhead.shape: diamond
1026 }
1027 `,
1028 key: `(x -> y)[0].target-arrowhead.shape`,
1029 value: go2.Pointer(`circle`),
1030 exp: `x -> y: {
1031 target-arrowhead.shape: circle
1032 }
1033 `,
1034 },
1035 {
1036 name: "replace_arrowhead_map",
1037 text: `x -> y: {
1038 target-arrowhead: {
1039 shape: diamond
1040 }
1041 }
1042 `,
1043 key: `(x -> y)[0].target-arrowhead.shape`,
1044 value: go2.Pointer(`circle`),
1045 exp: `x -> y: {
1046 target-arrowhead: {
1047 shape: circle
1048 }
1049 }
1050 `,
1051 },
1052 {
1053 name: "replace_edge_style_map",
1054 text: `x -> y: {
1055 style: {
1056 stroke-dash: 3
1057 }
1058 }
1059 `,
1060 key: `(x -> y)[0].style.stroke-dash`,
1061 value: go2.Pointer(`4`),
1062 exp: `x -> y: {
1063 style: {
1064 stroke-dash: 4
1065 }
1066 }
1067 `,
1068 },
1069 {
1070 name: "replace_edge_style",
1071 text: `x -> y: {
1072 style.stroke-width: 1
1073 style.stroke-dash: 4
1074 }
1075 `,
1076 key: `(x -> y)[0].style.stroke-dash`,
1077 value: go2.Pointer(`3`),
1078 exp: `x -> y: {
1079 style.stroke-width: 1
1080 style.stroke-dash: 3
1081 }
1082 `,
1083 },
1084 {
1085 name: "set_fill_pattern",
1086 text: `square`,
1087 key: `square.style.fill-pattern`,
1088 value: go2.Pointer(`grain`),
1089 exp: `square: {style.fill-pattern: grain}
1090 `,
1091 },
1092 {
1093 name: "replace_fill_pattern",
1094 text: `square: {
1095 style.fill-pattern: lines
1096 }
1097 `,
1098 key: `square.style.fill-pattern`,
1099 value: go2.Pointer(`grain`),
1100 exp: `square: {
1101 style.fill-pattern: grain
1102 }
1103 `,
1104 },
1105 {
1106 name: "classes-style",
1107 text: `classes: {
1108 a: {
1109 style.fill: red
1110 }
1111 }
1112 b.class: a
1113 `,
1114 key: `b.style.fill`,
1115 value: go2.Pointer(`green`),
1116 exp: `classes: {
1117 a: {
1118 style.fill: red
1119 }
1120 }
1121 b.class: a
1122 b.style.fill: green
1123 `,
1124 },
1125 {
1126 name: "dupe-classes-style",
1127 text: `classes: {
1128 a: {
1129 style.fill: red
1130 }
1131 }
1132 b.class: a
1133 b.style.fill: red
1134 `,
1135 key: `b.style.fill`,
1136 value: go2.Pointer(`green`),
1137 exp: `classes: {
1138 a: {
1139 style.fill: red
1140 }
1141 }
1142 b.class: a
1143 b.style.fill: green
1144 `,
1145 },
1146 {
1147 name: "unapplied-classes-style",
1148 text: `classes: {
1149 a: {
1150 style.fill: red
1151 }
1152 }
1153 b.style.fill: red
1154 `,
1155 key: `b.style.fill`,
1156 value: go2.Pointer(`green`),
1157 exp: `classes: {
1158 a: {
1159 style.fill: red
1160 }
1161 }
1162 b.style.fill: green
1163 `,
1164 },
1165 {
1166 name: "unapplied-classes-style-2",
1167 text: `classes: {
1168 a: {
1169 style.fill: red
1170 }
1171 }
1172 b
1173 `,
1174 key: `b.style.fill`,
1175 value: go2.Pointer(`green`),
1176 exp: `classes: {
1177 a: {
1178 style.fill: red
1179 }
1180 }
1181 b: {style.fill: green}
1182 `,
1183 },
1184 {
1185 name: "label_unset",
1186 text: `square: "Always try to do things in chronological order; it's less confusing that way."
1187 `,
1188 key: `square.label`,
1189 value: nil,
1190
1191 exp: `square
1192 `,
1193 assertions: func(t *testing.T, g *d2graph.Graph) {
1194 if len(g.Objects) != 1 {
1195 t.Fatalf("expected 1 objects: %#v", g.Objects)
1196 }
1197 if g.Objects[0].ID != "square" {
1198 t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
1199 }
1200 if g.Objects[0].Shape.Value == d2target.ShapeSquare {
1201 t.Fatalf("expected g.Objects[0].Shape.Value == square: %#v", g.Objects[0].Shape.Value)
1202 }
1203 },
1204 },
1205 {
1206 name: "label",
1207 text: `square`,
1208 key: `square.label`,
1209 value: go2.Pointer(`Always try to do things in chronological order; it's less confusing that way.`),
1210
1211 exp: `square: "Always try to do things in chronological order; it's less confusing that way."
1212 `,
1213 assertions: func(t *testing.T, g *d2graph.Graph) {
1214 if len(g.Objects) != 1 {
1215 t.Fatalf("expected 1 objects: %#v", g.Objects)
1216 }
1217 if g.Objects[0].ID != "square" {
1218 t.Fatalf("expected g.Objects[0].ID to be square: %#v", g.Objects[0])
1219 }
1220 if g.Objects[0].Shape.Value == d2target.ShapeSquare {
1221 t.Fatalf("expected g.Objects[0].Shape.Value == square: %#v", g.Objects[0].Shape.Value)
1222 }
1223 },
1224 },
1225 {
1226 name: "label_replace",
1227 text: `square: I am deeply CONCERNED and I want something GOOD for BREAKFAST!`,
1228 key: `square`,
1229 value: go2.Pointer(`Always try to do things in chronological order; it's less confusing that way.`),
1230
1231 exp: `square: "Always try to do things in chronological order; it's less confusing that way."
1232 `,
1233 assertions: func(t *testing.T, g *d2graph.Graph) {
1234 if len(g.AST.Nodes) != 1 {
1235 t.Fatal(g.AST)
1236 }
1237 if len(g.Objects) != 1 {
1238 t.Fatal(g.Objects)
1239 }
1240 if g.Objects[0].ID != "square" {
1241 t.Fatal(g.Objects[0])
1242 }
1243 if g.Objects[0].Label.Value == "I am deeply CONCERNED and I want something GOOD for BREAKFAST!" {
1244 t.Fatal(g.Objects[0].Label.Value)
1245 }
1246 },
1247 },
1248 {
1249 name: "map_key_missing",
1250 text: `a -> b`,
1251 key: `a`,
1252 value: go2.Pointer(`Never offend people with style when you can offend them with substance.`),
1253
1254 exp: `a -> b
1255 a: Never offend people with style when you can offend them with substance.
1256 `,
1257 assertions: func(t *testing.T, g *d2graph.Graph) {
1258 if len(g.Objects) != 2 {
1259 t.Fatalf("expected 2 objects: %#v", g.Objects)
1260 }
1261 if len(g.Edges) != 1 {
1262 t.Fatalf("expected 1 edge: %#v", g.Edges)
1263 }
1264 },
1265 },
1266 {
1267 name: "nested_alex",
1268 text: `this: {
1269 label: do
1270 test -> here: asdf
1271 }`,
1272 key: `this.here`,
1273 value: go2.Pointer(`How much of their influence on you is a result of your influence on them?
1274 A conference is a gathering of important people who singly can do nothing`),
1275
1276 exp: `this: {
1277 label: do
1278 test -> here: asdf
1279 here: "How much of their influence on you is a result of your influence on them?\nA conference is a gathering of important people who singly can do nothing"
1280 }
1281 `,
1282 assertions: func(t *testing.T, g *d2graph.Graph) {
1283 if len(g.Objects) != 3 {
1284 t.Fatalf("expected 3 objects: %#v", g.Objects)
1285 }
1286 if len(g.Edges) != 1 {
1287 t.Fatalf("expected 1 edge: %#v", g.Edges)
1288 }
1289 },
1290 },
1291 {
1292 name: "label_primary",
1293 text: `oreo: {
1294 q -> z
1295 }`,
1296 key: `oreo`,
1297 value: go2.Pointer(`QOTD: "It's been Monday all week today."`),
1298
1299 exp: `oreo: 'QOTD: "It''s been Monday all week today."' {
1300 q -> z
1301 }
1302 `,
1303 assertions: func(t *testing.T, g *d2graph.Graph) {
1304 if len(g.Objects) != 3 {
1305 t.Fatalf("expected 3 objects: %#v", g.Objects)
1306 }
1307 if len(g.Edges) != 1 {
1308 t.Fatalf("expected 1 edge: %#v", g.Edges)
1309 }
1310 },
1311 },
1312 {
1313 name: "edge_index_nested",
1314 text: `oreo: {
1315 q -> z
1316 }`,
1317 key: `(oreo.q -> oreo.z)[0]`,
1318 value: go2.Pointer(`QOTD`),
1319
1320 exp: `oreo: {
1321 q -> z: QOTD
1322 }
1323 `,
1324 assertions: func(t *testing.T, g *d2graph.Graph) {
1325 if len(g.Objects) != 3 {
1326 t.Fatalf("expected 3 objects: %#v", g.Objects)
1327 }
1328 if len(g.Edges) != 1 {
1329 t.Fatalf("expected 1 edge: %#v", g.Edges)
1330 }
1331 },
1332 },
1333 {
1334 name: "edge_index_case",
1335 text: `Square: {
1336 Square -> Square 2
1337 }
1338 z: {
1339 x -> y
1340 }
1341 `,
1342 key: `Square.(Square -> Square 2)[0]`,
1343 value: go2.Pointer(`two`),
1344
1345 exp: `Square: {
1346 Square -> Square 2: two
1347 }
1348 z: {
1349 x -> y
1350 }
1351 `,
1352 assertions: func(t *testing.T, g *d2graph.Graph) {
1353 if len(g.Objects) != 6 {
1354 t.Fatalf("expected 6 objects: %#v", g.Objects)
1355 }
1356 if len(g.Edges) != 2 {
1357 t.Fatalf("expected 2 edges: %#v", g.Edges)
1358 }
1359 if g.Edges[0].Label.Value != "two" {
1360 t.Fatalf("expected g.Edges[0].Label.Value == two: %#v", g.Edges[0].Label.Value)
1361 }
1362 },
1363 },
1364 {
1365 name: "icon",
1366 text: `meow
1367 `,
1368 key: `meow.icon`,
1369 value: go2.Pointer(`https://icons.terrastruct.com/essentials/087-menu.svg`),
1370
1371 exp: `meow: {icon: https://icons.terrastruct.com/essentials/087-menu.svg}
1372 `,
1373 assertions: func(t *testing.T, g *d2graph.Graph) {
1374 if len(g.Objects) != 1 {
1375 t.Fatal(g.Objects)
1376 }
1377 if g.Objects[0].Icon.String() != "https://icons.terrastruct.com/essentials/087-menu.svg" {
1378 t.Fatal(g.Objects[0].Icon.String())
1379 }
1380 },
1381 },
1382 {
1383 name: "edge_chain",
1384 text: `oreo: {
1385 q -> z -> p: wsup
1386 }`,
1387 key: `(oreo.q -> oreo.z)[0]`,
1388 value: go2.Pointer(`QOTD:
1389 "It's been Monday all week today."`),
1390
1391 exp: `oreo: {
1392 q -> z -> p: wsup
1393 (q -> z)[0]: "QOTD:\n \"It's been Monday all week today.\""
1394 }
1395 `,
1396 assertions: func(t *testing.T, g *d2graph.Graph) {
1397 if len(g.Objects) != 4 {
1398 t.Fatalf("expected 4 objects: %#v", g.Objects)
1399 }
1400 if len(g.Edges) != 2 {
1401 t.Fatalf("expected 2 edges: %#v", g.Edges)
1402 }
1403 },
1404 },
1405 {
1406 name: "edge_nested_label_set",
1407 text: `oreo: {
1408 q -> z: wsup
1409 }`,
1410 key: `(oreo.q -> oreo.z)[0].label`,
1411 value: go2.Pointer(`yo`),
1412
1413 exp: `oreo: {
1414 q -> z: yo
1415 }
1416 `,
1417 assertions: func(t *testing.T, g *d2graph.Graph) {
1418 if len(g.Objects) != 3 {
1419 t.Fatalf("expected 3 objects: %#v", g.Objects)
1420 }
1421 if len(g.Edges) != 1 {
1422 t.Fatalf("expected 1 edge: %#v", g.Edges)
1423 }
1424 if g.Edges[0].Src.ID != "q" {
1425 t.Fatal(g.Edges[0].Src.ID)
1426 }
1427 },
1428 },
1429 {
1430 name: "shape_nested_style_set",
1431 text: `x
1432 `,
1433 key: `x.style.opacity`,
1434 value: go2.Pointer(`0.4`),
1435
1436 exp: `x: {style.opacity: 0.4}
1437 `,
1438 },
1439 {
1440 name: "edge_nested_style_set",
1441 text: `oreo: {
1442 q -> z: wsup
1443 }
1444 `,
1445 key: `(oreo.q -> oreo.z)[0].style.opacity`,
1446 value: go2.Pointer(`0.4`),
1447
1448 exp: `oreo: {
1449 q -> z: wsup {style.opacity: 0.4}
1450 }
1451 `,
1452 assertions: func(t *testing.T, g *d2graph.Graph) {
1453 assert.JSON(t, 3, len(g.Objects))
1454 assert.JSON(t, 1, len(g.Edges))
1455 assert.JSON(t, "q", g.Edges[0].Src.ID)
1456 assert.JSON(t, "0.4", g.Edges[0].Style.Opacity.Value)
1457 },
1458 },
1459 {
1460 name: "edge_chain_append_style",
1461 text: `x -> y -> z
1462 `,
1463 key: `(x -> y)[0].style.animated`,
1464 value: go2.Pointer(`true`),
1465
1466 exp: `x -> y -> z
1467 (x -> y)[0].style.animated: true
1468 `,
1469 },
1470 {
1471 name: "edge_chain_existing_style",
1472 text: `x -> y -> z
1473 (y -> z)[0].style.opacity: 0.4
1474 `,
1475 key: `(y -> z)[0].style.animated`,
1476 value: go2.Pointer(`true`),
1477
1478 exp: `x -> y -> z
1479 (y -> z)[0].style.opacity: 0.4
1480 (y -> z)[0].style.animated: true
1481 `,
1482 },
1483 {
1484 name: "edge_key_and_key",
1485 text: `a
1486 a.b -> a.c
1487 `,
1488 key: `a.(b -> c)[0].style.animated`,
1489 value: go2.Pointer(`true`),
1490
1491 exp: `a
1492 a.b -> a.c: {style.animated: true}
1493 `,
1494 },
1495 {
1496 name: "edge_label",
1497 text: `a -> b: "yo"
1498 `,
1499 key: `(a -> b)[0].style.animated`,
1500 value: go2.Pointer(`true`),
1501
1502 exp: `a -> b: "yo" {style.animated: true}
1503 `,
1504 },
1505 {
1506 name: "edge_append_style",
1507 text: `x -> y
1508 `,
1509 key: `(x -> y)[0].style.animated`,
1510 value: go2.Pointer(`true`),
1511
1512 exp: `x -> y: {style.animated: true}
1513 `,
1514 },
1515 {
1516 name: "edge_set_arrowhead",
1517 text: `x -> y
1518 `,
1519 key: `(x -> y)[0].target-arrowhead.shape`,
1520 value: go2.Pointer(`diamond`),
1521
1522 exp: `x -> y: {target-arrowhead.shape: diamond}
1523 `,
1524 },
1525 {
1526 name: "edge_replace_arrowhead",
1527 text: `x -> y: {target-arrowhead.shape: circle}
1528 `,
1529 key: `(x -> y)[0].target-arrowhead.shape`,
1530 value: go2.Pointer(`diamond`),
1531
1532 exp: `x -> y: {target-arrowhead.shape: diamond}
1533 `,
1534 },
1535 {
1536 name: "edge_replace_arrowhead_indexed",
1537 text: `x -> y
1538 (x -> y)[0].target-arrowhead.shape: circle
1539 `,
1540 key: `(x -> y)[0].target-arrowhead.shape`,
1541 value: go2.Pointer(`diamond`),
1542
1543 exp: `x -> y
1544 (x -> y)[0].target-arrowhead.shape: diamond
1545 `,
1546 },
1547 {
1548 name: "edge_merge_arrowhead",
1549 text: `x -> y: {
1550 target-arrowhead: {
1551 label: 1
1552 }
1553 }
1554 `,
1555 key: `(x -> y)[0].target-arrowhead.shape`,
1556 value: go2.Pointer(`diamond`),
1557
1558 exp: `x -> y: {
1559 target-arrowhead: {
1560 label: 1
1561 shape: diamond
1562 }
1563 }
1564 `,
1565 },
1566 {
1567 name: "edge_merge_style",
1568 text: `x -> y: {
1569 style: {
1570 opacity: 0.4
1571 }
1572 }
1573 `,
1574 key: `(x -> y)[0].style.animated`,
1575 value: go2.Pointer(`true`),
1576
1577 exp: `x -> y: {
1578 style: {
1579 opacity: 0.4
1580 animated: true
1581 }
1582 }
1583 `,
1584 },
1585 {
1586 name: "edge_flat_merge_arrowhead",
1587 text: `x -> y -> z
1588 (x -> y)[0].target-arrowhead.shape: diamond
1589 `,
1590 key: `(x -> y)[0].target-arrowhead.shape`,
1591 value: go2.Pointer(`circle`),
1592
1593 exp: `x -> y -> z
1594 (x -> y)[0].target-arrowhead.shape: circle
1595 `,
1596 },
1597 {
1598 name: "edge_index_merge_style",
1599 text: `x -> y -> z
1600 (x -> y)[0].style.opacity: 0.4
1601 `,
1602 key: `(x -> y)[0].style.opacity`,
1603 value: go2.Pointer(`0.5`),
1604
1605 exp: `x -> y -> z
1606 (x -> y)[0].style.opacity: 0.5
1607 `,
1608 },
1609 {
1610 name: "edge_chain_nested_set",
1611 text: `oreo: {
1612 q -> z -> p: wsup
1613 }`,
1614 key: `(oreo.q -> oreo.z)[0].style.opacity`,
1615 value: go2.Pointer(`0.4`),
1616
1617 exp: `oreo: {
1618 q -> z -> p: wsup
1619 (q -> z)[0].style.opacity: 0.4
1620 }
1621 `,
1622 assertions: func(t *testing.T, g *d2graph.Graph) {
1623 if len(g.Objects) != 4 {
1624 t.Fatalf("expected 4 objects: %#v", g.Objects)
1625 }
1626 if len(g.Edges) != 2 {
1627 t.Fatalf("expected 2 edges: %#v", g.Edges)
1628 }
1629 if g.Edges[0].Src.ID != "q" {
1630 t.Fatal(g.Edges[0].Src.ID)
1631 }
1632 if g.Edges[0].Style.Opacity.Value != "0.4" {
1633 t.Fatal(g.Edges[0].Style.Opacity.Value)
1634 }
1635 },
1636 },
1637 {
1638 name: "block_string_oneline",
1639
1640 text: ``,
1641 key: `x`,
1642 tag: go2.Pointer("md"),
1643 value: go2.Pointer(`|||what's up|||`),
1644
1645 exp: `x: ||||md |||what's up||| ||||
1646 `,
1647 },
1648 {
1649 name: "block_string_multiline",
1650
1651 text: ``,
1652 key: `x`,
1653 tag: go2.Pointer("md"),
1654 value: go2.Pointer(`# header
1655 He has not acquired a fortune; the fortune has acquired him.
1656 He has not acquired a fortune; the fortune has acquired him.`),
1657
1658 exp: `x: |md
1659 # header
1660 He has not acquired a fortune; the fortune has acquired him.
1661 He has not acquired a fortune; the fortune has acquired him.
1662 |
1663 `,
1664 },
1665
1666
1685
1686
1699
1700 {
1701 name: "errors/bad_tag",
1702
1703 text: `x.icon: hello
1704 `,
1705 key: "x.icon",
1706 tag: go2.Pointer("one two"),
1707 value: go2.Pointer(`three
1708 four
1709 five
1710 six
1711 `),
1712
1713 expErr: `failed to set "x.icon" to "one two" "\"three\\nfour\\nfive\\nsix\\n\"": spaces are not allowed in blockstring tags`,
1714 },
1715 {
1716 name: "layers-usable-ref-style",
1717
1718 text: `a
1719
1720 layers: {
1721 x: {
1722 a
1723 }
1724 }
1725 `,
1726 key: `a.style.opacity`,
1727 value: go2.Pointer(`0.2`),
1728 boardPath: []string{"x"},
1729
1730 exp: `a
1731
1732 layers: {
1733 x: {
1734 a: {style.opacity: 0.2}
1735 }
1736 }
1737 `,
1738 },
1739 {
1740 name: "layers-unusable-ref-style",
1741
1742 text: `a
1743
1744 layers: {
1745 x: {
1746 b
1747 }
1748 }
1749 `,
1750 key: `a.style.opacity`,
1751 value: go2.Pointer(`0.2`),
1752 boardPath: []string{"x"},
1753
1754 exp: `a
1755
1756 layers: {
1757 x: {
1758 b
1759 a.style.opacity: 0.2
1760 }
1761 }
1762 `,
1763 },
1764 {
1765 name: "scenarios-usable-ref-style",
1766
1767 text: `a: outer
1768
1769 scenarios: {
1770 x: {
1771 a: inner
1772 }
1773 }
1774 `,
1775 key: `a.style.opacity`,
1776 value: go2.Pointer(`0.2`),
1777 boardPath: []string{"x"},
1778
1779 exp: `a: outer
1780
1781 scenarios: {
1782 x: {
1783 a: inner {style.opacity: 0.2}
1784 }
1785 }
1786 `,
1787 },
1788 {
1789 name: "scenarios-nested-usable-ref-style",
1790
1791 text: `a: {
1792 b: outer
1793 }
1794
1795 scenarios: {
1796 x: {
1797 a: {
1798 b: inner
1799 }
1800 }
1801 }
1802 `,
1803 key: `a.b.style.opacity`,
1804 value: go2.Pointer(`0.2`),
1805 boardPath: []string{"x"},
1806
1807 exp: `a: {
1808 b: outer
1809 }
1810
1811 scenarios: {
1812 x: {
1813 a: {
1814 b: inner {style.opacity: 0.2}
1815 }
1816 }
1817 }
1818 `,
1819 },
1820 {
1821 name: "scenarios-unusable-ref-style",
1822
1823 text: `a
1824
1825 scenarios: {
1826 x: {
1827 b
1828 }
1829 }
1830 `,
1831 key: `a.style.opacity`,
1832 value: go2.Pointer(`0.2`),
1833 boardPath: []string{"x"},
1834
1835 exp: `a
1836
1837 scenarios: {
1838 x: {
1839 b
1840 a.style.opacity: 0.2
1841 }
1842 }
1843 `,
1844 },
1845 {
1846 name: "scenarios-label-primary",
1847
1848 text: `a: {
1849 style.opacity: 0.2
1850 }
1851
1852 scenarios: {
1853 x: {
1854 a: {
1855 style.opacity: 0.3
1856 }
1857 }
1858 }
1859 `,
1860 key: `a`,
1861 value: go2.Pointer(`b`),
1862 boardPath: []string{"x"},
1863
1864 exp: `a: {
1865 style.opacity: 0.2
1866 }
1867
1868 scenarios: {
1869 x: {
1870 a: b {
1871 style.opacity: 0.3
1872 }
1873 }
1874 }
1875 `,
1876 },
1877 {
1878 name: "scenarios-label-primary-missing",
1879
1880 text: `a: {
1881 style.opacity: 0.2
1882 }
1883
1884 scenarios: {
1885 x: {
1886 b
1887 }
1888 }
1889 `,
1890 key: `a`,
1891 value: go2.Pointer(`b`),
1892 boardPath: []string{"x"},
1893
1894 exp: `a: {
1895 style.opacity: 0.2
1896 }
1897
1898 scenarios: {
1899 x: {
1900 b
1901 a: b
1902 }
1903 }
1904 `,
1905 },
1906 {
1907 name: "scenarios-edge-set",
1908
1909 text: `a -> b
1910
1911 scenarios: {
1912 x: {
1913 c
1914 }
1915 }
1916 `,
1917 key: `(a -> b)[0].style.opacity`,
1918 value: go2.Pointer(`0.2`),
1919 boardPath: []string{"x"},
1920
1921 exp: `a -> b
1922
1923 scenarios: {
1924 x: {
1925 c
1926 (a -> b)[0].style.opacity: 0.2
1927 }
1928 }
1929 `,
1930 },
1931 {
1932 name: "scenarios-existing-edge-set",
1933
1934 text: `a -> b
1935
1936 scenarios: {
1937 x: {
1938 a -> b
1939 c
1940 }
1941 }
1942 `,
1943 key: `(a -> b)[1].style.opacity`,
1944 value: go2.Pointer(`0.2`),
1945 boardPath: []string{"x"},
1946
1947 exp: `a -> b
1948
1949 scenarios: {
1950 x: {
1951 a -> b: {style.opacity: 0.2}
1952 c
1953 }
1954 }
1955 `,
1956 },
1957 {
1958 name: "scenarios-arrowhead",
1959
1960 text: `a -> b: {
1961 target-arrowhead.shape: triangle
1962 }
1963 x -> y
1964
1965 scenarios: {
1966 x: {
1967 (a -> b)[0]: {
1968 target-arrowhead.shape: circle
1969 }
1970 c -> d
1971 }
1972 }
1973 `,
1974 key: `(a -> b)[0].target-arrowhead.shape`,
1975 value: go2.Pointer(`diamond`),
1976 boardPath: []string{"x"},
1977
1978 exp: `a -> b: {
1979 target-arrowhead.shape: triangle
1980 }
1981 x -> y
1982
1983 scenarios: {
1984 x: {
1985 (a -> b)[0]: {
1986 target-arrowhead.shape: diamond
1987 }
1988 c -> d
1989 }
1990 }
1991 `,
1992 },
1993 {
1994 name: "import/1",
1995
1996 text: `x: {
1997 ...@meow.x
1998 y
1999 }
2000 `,
2001 fsTexts: map[string]string{
2002 "meow": `x: {
2003 style.fill: blue
2004 }
2005 `,
2006 },
2007 key: `x.style.stroke`,
2008 value: go2.Pointer(`red`),
2009 exp: `x: {
2010 ...@meow.x
2011 y
2012 style.stroke: red
2013 }
2014 `,
2015 },
2016 {
2017 name: "import/2",
2018
2019 text: `x: {
2020 ...@meow.x
2021 y
2022 }
2023 `,
2024 fsTexts: map[string]string{
2025 "meow": `x: {
2026 style.fill: blue
2027 }
2028 `,
2029 },
2030 key: `x.style.fill`,
2031 value: go2.Pointer(`red`),
2032 exp: `x: {
2033 ...@meow.x
2034 y
2035 style.fill: red
2036 }
2037 `,
2038 },
2039 {
2040 name: "import/3",
2041
2042 text: `x: {
2043 ...@meow.x
2044 y
2045 style.fill: red
2046 }
2047 `,
2048 fsTexts: map[string]string{
2049 "meow": `x: {
2050 style.fill: blue
2051 }
2052 `,
2053 },
2054 key: `x.style.fill`,
2055 value: go2.Pointer(`yellow`),
2056 exp: `x: {
2057 ...@meow.x
2058 y
2059 style.fill: yellow
2060 }
2061 `,
2062 },
2063 }
2064
2065 for _, tc := range testCases {
2066 tc := tc
2067 t.Run(tc.name, func(t *testing.T) {
2068 t.Parallel()
2069
2070 et := editTest{
2071 text: tc.text,
2072 fsTexts: tc.fsTexts,
2073 testFunc: func(g *d2graph.Graph) (*d2graph.Graph, error) {
2074 return d2oracle.Set(g, tc.boardPath, tc.key, tc.tag, tc.value)
2075 },
2076
2077 exp: tc.exp,
2078 expErr: tc.expErr,
2079 assertions: tc.assertions,
2080 }
2081 et.run(t)
2082 })
2083 }
2084 }
2085
2086 func TestReconnectEdge(t *testing.T) {
2087 t.Parallel()
2088
2089 testCases := []struct {
2090 name string
2091 boardPath []string
2092 text string
2093 edgeKey string
2094 newSrc string
2095 newDst string
2096
2097 expErr string
2098 exp string
2099 assertions func(t *testing.T, g *d2graph.Graph)
2100 }{
2101 {
2102 name: "basic",
2103 text: `a
2104 b
2105 c
2106 a -> b
2107 `,
2108 edgeKey: `(a -> b)[0]`,
2109 newDst: "c",
2110 exp: `a
2111 b
2112 c
2113 a -> c
2114 `,
2115 },
2116 {
2117 name: "src",
2118 text: `a
2119 b
2120 c
2121 a -> b
2122 `,
2123 edgeKey: `(a -> b)[0]`,
2124 newSrc: "c",
2125 exp: `a
2126 b
2127 c
2128 c -> b
2129 `,
2130 },
2131 {
2132 name: "both",
2133 text: `a
2134 b
2135 c
2136 a -> b
2137 `,
2138 edgeKey: `(a -> b)[0]`,
2139 newSrc: "b",
2140 newDst: "a",
2141 exp: `a
2142 b
2143 c
2144 b -> a
2145 `,
2146 },
2147 {
2148 name: "contained",
2149 text: `a.x -> a.y
2150 a.z`,
2151 edgeKey: `a.(x -> y)[0]`,
2152 newDst: "a.z",
2153 exp: `a.x -> a.z
2154 a.y
2155 a.z
2156 `,
2157 },
2158 {
2159 name: "scope_outer",
2160 text: `a: {
2161 x -> y
2162 }
2163 b`,
2164 edgeKey: `(a.x -> a.y)[0]`,
2165 newDst: "b",
2166 exp: `a: {
2167 x -> _.b
2168 y
2169 }
2170 b
2171 `,
2172 },
2173 {
2174 name: "scope_inner",
2175 text: `a: {
2176 x -> y
2177 z: {
2178 b
2179 }
2180 }`,
2181 edgeKey: `(a.x -> a.y)[0]`,
2182 newDst: "a.z.b",
2183 exp: `a: {
2184 x -> z.b
2185 y
2186
2187 z: {
2188 b
2189 }
2190 }
2191 `,
2192 },
2193 {
2194 name: "loop",
2195 text: `a -> a
2196 b`,
2197 edgeKey: `(a -> a)[0]`,
2198 newDst: "b",
2199 exp: `a -> b
2200 b
2201 `,
2202 },
2203 {
2204 name: "preserve_old_obj",
2205 text: `a -> b
2206 (a -> b)[0].style.stroke: red
2207 c`,
2208 edgeKey: `(a -> b)[0]`,
2209 newSrc: "a",
2210 newDst: "c",
2211 exp: `a -> c
2212 b
2213 (a -> c)[0].style.stroke: red
2214 c
2215 `,
2216 },
2217 {
2218 name: "middle_chain",
2219 text: `a -> b -> c
2220 x`,
2221 edgeKey: `(a -> b)[0]`,
2222 newDst: "x",
2223 exp: `b -> c
2224 a -> x
2225 x
2226 `,
2227 },
2228 {
2229 name: "middle_chain_src",
2230 text: `a -> b -> c
2231 x`,
2232 edgeKey: `(b -> c)[0]`,
2233 newSrc: "x",
2234 exp: `a -> b
2235 x -> c
2236 x
2237 `,
2238 },
2239 {
2240 name: "middle_chain_both",
2241 text: `a -> b -> c -> d
2242 x`,
2243 edgeKey: `(b -> c)[0]`,
2244 newSrc: "x",
2245 newDst: "x",
2246 exp: `a -> b
2247 c -> d
2248 x -> x
2249 x
2250 `,
2251 },
2252 {
2253 name: "middle_chain_first",
2254 text: `a -> b -> c -> d
2255 x`,
2256 edgeKey: `(a -> b)[0]`,
2257 newSrc: "x",
2258 exp: `a
2259 x -> b -> c -> d
2260 x
2261 `,
2262 },
2263 {
2264 name: "middle_chain_last",
2265 text: `a -> b -> c -> d
2266 x`,
2267 edgeKey: `(c -> d)[0]`,
2268 newDst: "x",
2269 exp: `a -> b -> c -> x
2270 d
2271 x
2272 `,
2273 },
2274
2275 {
2276 name: "in_chain_3",
2277
2278 text: `a -> b -> a -> c
2279 `,
2280 edgeKey: "(a -> b)[0]",
2281 newDst: "c",
2282
2283 exp: `b -> a -> c
2284 a -> c
2285 `,
2286 },
2287 {
2288 name: "in_chain_4",
2289
2290 text: `a -> c -> a -> c
2291 b
2292 `,
2293 edgeKey: "(a -> c)[0]",
2294 newDst: "b",
2295
2296 exp: `c -> a -> c
2297 a -> b
2298 b
2299 `,
2300 },
2301 {
2302 name: "indexed_ref",
2303 text: `a -> b
2304 x
2305 (a -> b)[0].style.stroke: red
2306 `,
2307 edgeKey: `(a -> b)[0]`,
2308 newDst: "x",
2309 exp: `a -> x
2310 b
2311 x
2312 (a -> x)[0].style.stroke: red
2313 `,
2314 },
2315 {
2316 name: "reverse",
2317 text: `a -> b
2318 `,
2319 edgeKey: `(a -> b)[0]`,
2320 newSrc: "b",
2321 newDst: "a",
2322 exp: `b -> a
2323 `,
2324 },
2325 {
2326 name: "second_index",
2327 text: `a -> b: {
2328 style.stroke: blue
2329 }
2330 a -> b: {
2331 style.stroke: red
2332 }
2333 x
2334 `,
2335 edgeKey: `(a -> b)[1]`,
2336 newDst: "x",
2337 exp: `a -> b: {
2338 style.stroke: blue
2339 }
2340 a -> x: {
2341 style.stroke: red
2342 }
2343 x
2344 `,
2345 },
2346 {
2347 name: "nonexistant_edge",
2348 text: `a -> b
2349 `,
2350 edgeKey: `(b -> a)[0]`,
2351 newDst: "a",
2352 expErr: "edge not found",
2353 },
2354 {
2355 name: "nonexistant_obj",
2356 text: `a -> b
2357 `,
2358 edgeKey: `(a -> b)[0]`,
2359 newDst: "x",
2360 expErr: "newDst not found",
2361 },
2362 {
2363 name: "layers-basic",
2364 text: `a
2365
2366 layers: {
2367 x: {
2368 b
2369 c
2370 a -> b
2371 }
2372 }
2373 `,
2374 boardPath: []string{"x"},
2375 edgeKey: `(a -> b)[0]`,
2376 newDst: "c",
2377 exp: `a
2378
2379 layers: {
2380 x: {
2381 b
2382 c
2383 a -> c
2384 }
2385 }
2386 `,
2387 },
2388 {
2389 name: "scenarios-basic",
2390 text: `a
2391
2392 scenarios: {
2393 x: {
2394 b
2395 c
2396 a -> b
2397 }
2398 }
2399 `,
2400 boardPath: []string{"x"},
2401 edgeKey: `(a -> b)[0]`,
2402 newDst: "c",
2403 exp: `a
2404
2405 scenarios: {
2406 x: {
2407 b
2408 c
2409 a -> c
2410 }
2411 }
2412 `,
2413 },
2414 {
2415 name: "scenarios-outer-scope",
2416 text: `a
2417
2418 scenarios: {
2419 x: {
2420 d -> b
2421 }
2422 }
2423 `,
2424 boardPath: []string{"x"},
2425 edgeKey: `(d -> b)[0]`,
2426 newDst: "a",
2427 exp: `a
2428
2429 scenarios: {
2430 x: {
2431 d -> a
2432 b
2433 }
2434 }
2435 `,
2436 },
2437 {
2438 name: "scenarios-chain",
2439 text: `a -> b -> c
2440
2441 scenarios: {
2442 x: {
2443 d
2444 }
2445 }
2446 `,
2447 boardPath: []string{"x"},
2448 edgeKey: `(a -> b)[0]`,
2449 newDst: "d",
2450 expErr: `operation would modify AST outside of given scope`,
2451 },
2452 }
2453
2454 for _, tc := range testCases {
2455 tc := tc
2456 t.Run(tc.name, func(t *testing.T) {
2457 t.Parallel()
2458
2459 et := editTest{
2460 text: tc.text,
2461 testFunc: func(g *d2graph.Graph) (*d2graph.Graph, error) {
2462 var newSrc *string
2463 var newDst *string
2464 if tc.newSrc != "" {
2465 newSrc = &tc.newSrc
2466 }
2467 if tc.newDst != "" {
2468 newDst = &tc.newDst
2469 }
2470 return d2oracle.ReconnectEdge(g, tc.boardPath, tc.edgeKey, newSrc, newDst)
2471 },
2472
2473 exp: tc.exp,
2474 expErr: tc.expErr,
2475 assertions: tc.assertions,
2476 }
2477 et.run(t)
2478 })
2479 }
2480 }
2481
2482 func TestRename(t *testing.T) {
2483 t.Parallel()
2484
2485 testCases := []struct {
2486 name string
2487 boardPath []string
2488
2489 text string
2490 fsTexts map[string]string
2491 key string
2492 newName string
2493
2494 expErr string
2495 exp string
2496 assertions func(t *testing.T, g *d2graph.Graph)
2497 }{
2498 {
2499 name: "flat",
2500
2501 text: `nerve-gift-earther
2502 `,
2503 key: `nerve-gift-earther`,
2504 newName: `---`,
2505
2506 exp: `"---"
2507 `,
2508 assertions: func(t *testing.T, g *d2graph.Graph) {
2509 if len(g.Objects) != 1 {
2510 t.Fatalf("expected one object: %#v", g.Objects)
2511 }
2512 if g.Objects[0].ID != `"---"` {
2513 t.Fatalf("unexpected object id: %q", g.Objects[0].ID)
2514 }
2515 },
2516 },
2517 {
2518 name: "generated",
2519
2520 text: `Square
2521 `,
2522 key: `Square`,
2523 newName: `Square`,
2524
2525 exp: `Square
2526 `,
2527 },
2528 {
2529 name: "generated-conflict",
2530
2531 text: `Square
2532 Square 2
2533 `,
2534 key: `Square 2`,
2535 newName: `Square`,
2536
2537 exp: `Square
2538 Square 2
2539 `,
2540 },
2541 {
2542 name: "near",
2543
2544 text: `x: {
2545 near: y
2546 }
2547 y
2548 `,
2549 key: `y`,
2550 newName: `z`,
2551
2552 exp: `x: {
2553 near: z
2554 }
2555 z
2556 `,
2557 },
2558 {
2559 name: "conflict",
2560
2561 text: `lalal
2562 la
2563 `,
2564 key: `lalal`,
2565 newName: `la`,
2566
2567 exp: `la 2
2568 la
2569 `,
2570 },
2571 {
2572 name: "conflict 2",
2573
2574 text: `1.2.3: {
2575 4
2576 5
2577 }
2578 `,
2579 key: "1.2.3.4",
2580 newName: "5",
2581
2582 exp: `1.2.3: {
2583 5 2
2584 5
2585 }
2586 `,
2587 },
2588 {
2589 name: "conflict_with_dots",
2590
2591 text: `"a.b"
2592 y
2593 `,
2594 key: "y",
2595 newName: "a.b",
2596
2597 exp: `"a.b"
2598 "a.b 2"
2599 `,
2600 },
2601 {
2602 name: "conflict_with_numbers",
2603
2604 text: `1
2605 Square
2606 `,
2607 key: `Square`,
2608 newName: `1`,
2609
2610 exp: `1
2611 1 2
2612 `,
2613 },
2614 {
2615 name: "nested",
2616
2617 text: `x.y.z.q.nerve-gift-earther
2618 x.y.z.q: {
2619 nerve-gift-earther
2620 }
2621 `,
2622 key: `x.y.z.q.nerve-gift-earther`,
2623 newName: `nerve-gift-jingler`,
2624
2625 exp: `x.y.z.q.nerve-gift-jingler
2626 x.y.z.q: {
2627 nerve-gift-jingler
2628 }
2629 `,
2630 assertions: func(t *testing.T, g *d2graph.Graph) {
2631 if len(g.Objects) != 5 {
2632 t.Fatalf("expected five objects: %#v", g.Objects)
2633 }
2634 if g.Objects[4].AbsID() != "x.y.z.q.nerve-gift-jingler" {
2635 t.Fatalf("unexpected object absolute id: %q", g.Objects[4].AbsID())
2636 }
2637 },
2638 },
2639 {
2640 name: "edges",
2641
2642 text: `q.z -> p.k -> q.z -> l.a -> q.z
2643 q: {
2644 q -> + -> z
2645 z: label
2646 }
2647 `,
2648 key: `q.z`,
2649 newName: `%%%`,
2650
2651 exp: `q.%%% -> p.k -> q.%%% -> l.a -> q.%%%
2652 q: {
2653 q -> + -> %%%
2654 %%%: label
2655 }
2656 `,
2657 assertions: func(t *testing.T, g *d2graph.Graph) {
2658 if len(g.Objects) != 8 {
2659 t.Fatalf("expected eight objects: %#v", g.Objects)
2660 }
2661 if g.Objects[1].AbsID() != "q.%%%" {
2662 t.Fatalf("unexpected object absolute ID: %q", g.Objects[1].AbsID())
2663 }
2664 },
2665 },
2666 {
2667 name: "container",
2668
2669 text: `ok.q.z -> p.k -> ok.q.z -> l.a -> ok.q.z
2670 ok.q: {
2671 q -> + -> z
2672 z: label
2673 }
2674 ok: {
2675 q: {
2676 i
2677 }
2678 }
2679 (ok.q.z -> p.k)[0]: "furbling, v.:"
2680 more.(ok.q.z -> p.k): "furbling, v.:"
2681 `,
2682 key: `ok.q`,
2683 newName: `<gosling>`,
2684
2685 exp: `ok."<gosling>".z -> p.k -> ok."<gosling>".z -> l.a -> ok."<gosling>".z
2686 ok."<gosling>": {
2687 q -> + -> z
2688 z: label
2689 }
2690 ok: {
2691 "<gosling>": {
2692 i
2693 }
2694 }
2695 (ok."<gosling>".z -> p.k)[0]: "furbling, v.:"
2696 more.(ok.q.z -> p.k): "furbling, v.:"
2697 `,
2698 assertions: func(t *testing.T, g *d2graph.Graph) {
2699 if len(g.Objects) != 16 {
2700 t.Fatalf("expected 16 objects: %#v", g.Objects)
2701 }
2702 if g.Objects[2].AbsID() != `ok."<gosling>".z` {
2703 t.Fatalf("unexpected object absolute ID: %q", g.Objects[1].AbsID())
2704 }
2705 },
2706 },
2707 {
2708 name: "complex_edge_1",
2709
2710 text: `a.b.(x -> y).style.animated
2711 `,
2712 key: "a.b",
2713 newName: "ooo",
2714
2715 exp: `a.ooo.(x -> y).style.animated
2716 `,
2717 assertions: func(t *testing.T, g *d2graph.Graph) {
2718 if len(g.Objects) != 4 {
2719 t.Fatalf("expected 4 objects: %#v", g.Objects)
2720 }
2721 if len(g.Edges) != 1 {
2722 t.Fatalf("expected 1 edge: %#v", g.Edges)
2723 }
2724 },
2725 },
2726 {
2727 name: "complex_edge_2",
2728
2729 text: `a.b.(x -> y).style.animated
2730 `,
2731 key: "a.b.x",
2732 newName: "papa",
2733
2734 exp: `a.b.(papa -> y).style.animated
2735 `,
2736 assertions: func(t *testing.T, g *d2graph.Graph) {
2737 if len(g.Objects) != 4 {
2738 t.Fatalf("expected 4 objects: %#v", g.Objects)
2739 }
2740 if len(g.Edges) != 1 {
2741 t.Fatalf("expected 1 edge: %#v", g.Edges)
2742 }
2743 },
2744 },
2745
2766 {
2767 name: "arrows",
2768
2769 text: `x -> y
2770 `,
2771 key: "(x -> y)[0]",
2772 newName: "(x <- y)[0]",
2773
2774 exp: `x <- y
2775 `,
2776 assertions: func(t *testing.T, g *d2graph.Graph) {
2777 if len(g.Objects) != 2 {
2778 t.Fatalf("expected 2 objects: %#v", g.Objects)
2779 }
2780 if len(g.Edges) != 1 {
2781 t.Fatalf("expected 1 edge: %#v", g.Edges)
2782 }
2783 if !g.Edges[0].SrcArrow || g.Edges[0].DstArrow {
2784 t.Fatalf("expected src arrow and no dst arrow: %#v", g.Edges[0])
2785 }
2786 },
2787 },
2788 {
2789 name: "arrows_complex",
2790
2791 text: `a.b.(x -- y).style.animated
2792 `,
2793 key: "a.b.(x -- y)[0]",
2794 newName: "(x <-> y)[0]",
2795
2796 exp: `a.b.(x <-> y).style.animated
2797 `,
2798 assertions: func(t *testing.T, g *d2graph.Graph) {
2799 if len(g.Objects) != 4 {
2800 t.Fatalf("expected 4 objects: %#v", g.Objects)
2801 }
2802 if len(g.Edges) != 1 {
2803 t.Fatalf("expected 1 edge: %#v", g.Edges)
2804 }
2805 if !g.Edges[0].SrcArrow || !g.Edges[0].DstArrow {
2806 t.Fatalf("expected src arrow and dst arrow: %#v", g.Edges[0])
2807 }
2808 },
2809 },
2810 {
2811 name: "arrows_chain",
2812
2813 text: `x -> y -> z -> q
2814 `,
2815 key: "(x -> y)[0]",
2816 newName: "(x <-> y)[0]",
2817
2818 exp: `x <-> y -> z -> q
2819 `,
2820 assertions: func(t *testing.T, g *d2graph.Graph) {
2821 if len(g.Objects) != 4 {
2822 t.Fatalf("expected 4 objects: %#v", g.Objects)
2823 }
2824 if len(g.Edges) != 3 {
2825 t.Fatalf("expected 3 edges: %#v", g.Edges)
2826 }
2827 if !g.Edges[0].SrcArrow || !g.Edges[0].DstArrow {
2828 t.Fatalf("expected src arrow and dst arrow: %#v", g.Edges[0])
2829 }
2830 },
2831 },
2832 {
2833 name: "arrows_trim_common",
2834
2835 text: `x.(x -> y -> z -> q)
2836 `,
2837 key: "(x.x -> x.y)[0]",
2838 newName: "(x.x <-> x.y)[0]",
2839
2840 exp: `x.(x <-> y -> z -> q)
2841 `,
2842 assertions: func(t *testing.T, g *d2graph.Graph) {
2843 if len(g.Objects) != 5 {
2844 t.Fatalf("expected 5 objects: %#v", g.Objects)
2845 }
2846 if len(g.Edges) != 3 {
2847 t.Fatalf("expected 3 edges: %#v", g.Edges)
2848 }
2849 if !g.Edges[0].SrcArrow || !g.Edges[0].DstArrow {
2850 t.Fatalf("expected src arrow and dst arrow: %#v", g.Edges[0])
2851 }
2852 },
2853 },
2854 {
2855 name: "arrows_trim_common_2",
2856
2857 text: `x.x -> x.y -> x.z -> x.q)
2858 `,
2859 key: "(x.x -> x.y)[0]",
2860 newName: "(x.x <-> x.y)[0]",
2861
2862 exp: `x.x <-> x.y -> x.z -> x.q)
2863 `,
2864 assertions: func(t *testing.T, g *d2graph.Graph) {
2865 if len(g.Objects) != 5 {
2866 t.Fatalf("expected 5 objects: %#v", g.Objects)
2867 }
2868 if len(g.Edges) != 3 {
2869 t.Fatalf("expected 3 edges: %#v", g.Edges)
2870 }
2871 if !g.Edges[0].SrcArrow || !g.Edges[0].DstArrow {
2872 t.Fatalf("expected src arrow and dst arrow: %#v", g.Edges[0])
2873 }
2874 },
2875 },
2876
2877 {
2878 name: "errors/empty_key",
2879
2880 text: ``,
2881 key: "",
2882
2883 expErr: `failed to rename "" to "": empty map key: ""`,
2884 },
2885 {
2886 name: "errors/nonexistent",
2887
2888 text: ``,
2889 key: "1.2.3.4",
2890 newName: "bic",
2891
2892 expErr: `failed to rename "1.2.3.4" to "bic": key does not exist`,
2893 },
2894
2895 {
2896 name: "errors/reserved_keys",
2897
2898 text: `x.icon: hello
2899 `,
2900 key: "x.icon",
2901 newName: "near",
2902 expErr: `failed to rename "x.icon" to "near": cannot rename to reserved keyword: "near"`,
2903 },
2904 {
2905 name: "layers-basic",
2906
2907 text: `x
2908
2909 layers: {
2910 y: {
2911 a
2912 }
2913 }
2914 `,
2915 boardPath: []string{"y"},
2916 key: "a",
2917 newName: "b",
2918
2919 exp: `x
2920
2921 layers: {
2922 y: {
2923 b
2924 }
2925 }
2926 `,
2927 },
2928 {
2929 name: "scenarios-basic",
2930
2931 text: `x
2932
2933 scenarios: {
2934 y: {
2935 a
2936 }
2937 }
2938 `,
2939 boardPath: []string{"y"},
2940 key: "a",
2941 newName: "b",
2942
2943 exp: `x
2944
2945 scenarios: {
2946 y: {
2947 b
2948 }
2949 }
2950 `,
2951 },
2952 {
2953 name: "scenarios-conflict",
2954
2955 text: `x
2956
2957 scenarios: {
2958 y: {
2959 a
2960 }
2961 }
2962 `,
2963 boardPath: []string{"y"},
2964 key: "a",
2965 newName: "x",
2966
2967 exp: `x
2968
2969 scenarios: {
2970 y: {
2971 x 2
2972 }
2973 }
2974 `,
2975 },
2976 {
2977 name: "scenarios-scope-err",
2978
2979 text: `x
2980
2981 scenarios: {
2982 y: {
2983 a
2984 }
2985 }
2986 `,
2987 boardPath: []string{"y"},
2988 key: "x",
2989 newName: "b",
2990
2991 expErr: `failed to rename "x" to "b": operation would modify AST outside of given scope`,
2992 },
2993 }
2994
2995 for _, tc := range testCases {
2996 tc := tc
2997 t.Run(tc.name, func(t *testing.T) {
2998 t.Parallel()
2999
3000 et := editTest{
3001 text: tc.text,
3002 fsTexts: tc.fsTexts,
3003 testFunc: func(g *d2graph.Graph) (*d2graph.Graph, error) {
3004 objectsBefore := len(g.Objects)
3005 var err error
3006 g, _, err = d2oracle.Rename(g, tc.boardPath, tc.key, tc.newName)
3007 if err == nil {
3008 objectsAfter := len(g.Objects)
3009 if objectsBefore != objectsAfter {
3010 t.Log(d2format.Format(g.AST))
3011 return nil, fmt.Errorf("rename cannot destroy or create objects: found %d objects before and %d objects after", objectsBefore, objectsAfter)
3012 }
3013 }
3014
3015 return g, err
3016 },
3017
3018 exp: tc.exp,
3019 expErr: tc.expErr,
3020 assertions: tc.assertions,
3021 }
3022 et.run(t)
3023 })
3024 }
3025 }
3026
3027 func TestMove(t *testing.T) {
3028 t.Parallel()
3029
3030 testCases := []struct {
3031 skip bool
3032 name string
3033 boardPath []string
3034
3035 text string
3036 fsTexts map[string]string
3037 key string
3038 newKey string
3039 includeDescendants bool
3040
3041 expErr string
3042 exp string
3043 assertions func(t *testing.T, g *d2graph.Graph)
3044 }{
3045 {
3046 name: "basic",
3047
3048 text: `a
3049 `,
3050 key: `a`,
3051 newKey: `b`,
3052
3053 exp: `b
3054 `,
3055 assertions: func(t *testing.T, g *d2graph.Graph) {
3056 assert.JSON(t, len(g.Objects), 1)
3057 assert.JSON(t, g.Objects[0].ID, "b")
3058 },
3059 },
3060 {
3061 name: "basic_nested",
3062
3063 text: `a: {
3064 b
3065 }
3066 `,
3067 key: `a.b`,
3068 newKey: `a.c`,
3069
3070 exp: `a: {
3071 c
3072 }
3073 `,
3074 assertions: func(t *testing.T, g *d2graph.Graph) {
3075 assert.JSON(t, len(g.Objects), 2)
3076 assert.JSON(t, g.Objects[1].ID, "c")
3077 },
3078 },
3079 {
3080 name: "duplicate",
3081
3082 text: `a: {
3083 b: {
3084 shape: cylinder
3085 }
3086 }
3087
3088 a: {
3089 b: {
3090 shape: cylinder
3091 }
3092 }
3093 `,
3094 key: `a.b`,
3095 newKey: `b`,
3096
3097 exp: `a
3098
3099 a
3100 b: {
3101 shape: cylinder
3102 }
3103 `,
3104 },
3105 {
3106 name: "duplicate_generated",
3107
3108 text: `x
3109 x 2
3110 x 3: {
3111 x 3
3112 x 4
3113 }
3114 x 4
3115 y
3116 `,
3117 key: `x 3`,
3118 newKey: `y.x 3`,
3119
3120 exp: `x
3121 x 2
3122
3123 x 3
3124 x 5
3125
3126 x 4
3127 y: {
3128 x 3
3129 }
3130 `,
3131 },
3132 {
3133 name: "rename_2",
3134
3135 text: `a: {
3136 b 2
3137 y 2
3138 }
3139 b 2
3140 x
3141 `,
3142 key: `a`,
3143 newKey: `x.a`,
3144
3145 exp: `b
3146 y 2
3147
3148 b 2
3149 x: {
3150 a
3151 }
3152 `,
3153 },
3154 {
3155 name: "parentheses",
3156
3157 text: `x -> y (z)
3158 z: ""
3159 `,
3160 key: `"y (z)"`,
3161 newKey: `z.y (z)`,
3162
3163 exp: `x -> z.y (z)
3164 z: ""
3165 `,
3166 },
3167 {
3168 name: "middle_container_generated_conflict",
3169
3170 text: `a.Square.Text 3 -> a.Square.Text 2
3171
3172 a.Square -> a.Text
3173
3174 a: {
3175 Text
3176 Square: {
3177 Text 2
3178 Text 3
3179 }
3180 Square
3181
3182 Text 2
3183 }
3184 `,
3185 key: `a.Square`,
3186 newKey: `Square`,
3187
3188 exp: `a.Text 3 -> a.Text 4
3189
3190 Square -> a.Text
3191
3192 a: {
3193 Text
3194
3195 Text 4
3196 Text 3
3197
3198 Text 2
3199 }
3200 Square
3201 `,
3202 },
3203 {
3204 name: "into_container_existing_map",
3205
3206 text: `a: {
3207 b
3208 }
3209 c
3210 `,
3211 key: `c`,
3212 newKey: `a.c`,
3213
3214 exp: `a: {
3215 b
3216 c
3217 }
3218 `,
3219 assertions: func(t *testing.T, g *d2graph.Graph) {
3220 assert.JSON(t, len(g.Objects), 3)
3221 assert.JSON(t, "a", g.Objects[0].ID)
3222 assert.JSON(t, 2, len(g.Objects[0].Children))
3223 },
3224 },
3225 {
3226 name: "into_container_with_flat_keys",
3227
3228 text: `a
3229 c: {
3230 style.opacity: 0.4
3231 style.fill: "#FFFFFF"
3232 style.stroke: "#FFFFFF"
3233 }
3234 `,
3235 key: `c`,
3236 newKey: `a.c`,
3237
3238 exp: `a: {
3239 c: {
3240 style.opacity: 0.4
3241 style.fill: "#FFFFFF"
3242 style.stroke: "#FFFFFF"
3243 }
3244 }
3245 `,
3246 },
3247 {
3248 name: "into_container_nonexisting_map",
3249
3250 text: `a
3251 c
3252 `,
3253 key: `c`,
3254 newKey: `a.c`,
3255
3256 exp: `a: {
3257 c
3258 }
3259 `,
3260 assertions: func(t *testing.T, g *d2graph.Graph) {
3261 assert.JSON(t, len(g.Objects), 2)
3262 assert.JSON(t, "a", g.Objects[0].ID)
3263 assert.JSON(t, 1, len(g.Objects[0].Children))
3264 },
3265 },
3266 {
3267 name: "basic_out_of_container",
3268
3269 text: `a: {
3270 b
3271 }
3272 `,
3273 key: `a.b`,
3274 newKey: `b`,
3275
3276 exp: `a
3277 b
3278 `,
3279 assertions: func(t *testing.T, g *d2graph.Graph) {
3280 assert.JSON(t, len(g.Objects), 2)
3281 assert.JSON(t, "a", g.Objects[0].ID)
3282 assert.JSON(t, 0, len(g.Objects[0].Children))
3283 },
3284 },
3285 {
3286 name: "out_of_newline_container",
3287
3288 text: `"a\n": {
3289 b
3290 }
3291 `,
3292 key: `"a\n".b`,
3293 newKey: `b`,
3294
3295 exp: `"a\n"
3296 b
3297 `,
3298 },
3299 {
3300 name: "partial_slice",
3301
3302 text: `a: {
3303 b
3304 }
3305 a.b
3306 `,
3307 key: `a.b`,
3308 newKey: `b`,
3309
3310 exp: `a
3311 b
3312 `,
3313 },
3314 {
3315 name: "partial_edge_slice",
3316
3317 text: `a: {
3318 b
3319 }
3320 a.b -> c
3321 `,
3322 key: `a.b`,
3323 newKey: `b`,
3324
3325 exp: `a
3326 b -> c
3327 b
3328 `,
3329 },
3330 {
3331 name: "full_edge_slice",
3332
3333 text: `a: {
3334 b: {
3335 c
3336 }
3337 b.c -> d
3338 }
3339 a.b.c -> a.d
3340 `,
3341 key: `a.b.c`,
3342 newKey: `c`,
3343
3344 exp: `a: {
3345 b
3346 _.c -> d
3347 }
3348 c -> a.d
3349 c
3350 `,
3351 },
3352 {
3353 name: "full_slice",
3354
3355 text: `a: {
3356 b: {
3357 c
3358 }
3359 b.c
3360 }
3361 a.b.c
3362 `,
3363 key: `a.b.c`,
3364 newKey: `c`,
3365
3366 exp: `a: {
3367 b
3368 }
3369 c
3370 `,
3371 },
3372 {
3373 name: "slice_style",
3374
3375 text: `a: {
3376 b
3377 }
3378 a.b.icon: https://icons.terrastruct.com/essentials/142-target.svg
3379 `,
3380 key: `a.b`,
3381 newKey: `b`,
3382
3383 exp: `a
3384 a
3385 b
3386 b.icon: https://icons.terrastruct.com/essentials/142-target.svg
3387 `,
3388 },
3389 {
3390 name: "between_containers",
3391
3392 text: `a: {
3393 b
3394 }
3395 c
3396 `,
3397 key: `a.b`,
3398 newKey: `c.b`,
3399
3400 exp: `a
3401 c: {
3402 b
3403 }
3404 `,
3405 assertions: func(t *testing.T, g *d2graph.Graph) {
3406 assert.JSON(t, len(g.Objects), 3)
3407 assert.JSON(t, "a", g.Objects[0].ID)
3408 assert.JSON(t, 0, len(g.Objects[0].Children))
3409 assert.JSON(t, "c", g.Objects[1].ID)
3410 assert.JSON(t, 1, len(g.Objects[1].Children))
3411 },
3412 },
3413 {
3414 name: "hoist_container_children",
3415
3416 text: `a: {
3417 b
3418 c
3419 }
3420 d
3421 `,
3422 key: `a`,
3423 newKey: `d.a`,
3424
3425 exp: `b
3426 c
3427
3428 d: {
3429 a
3430 }
3431 `,
3432 },
3433 {
3434 name: "middle_container",
3435
3436 text: `x: {
3437 y: {
3438 z
3439 }
3440 }
3441 `,
3442 key: `x.y`,
3443 newKey: `y`,
3444
3445 exp: `x: {
3446 z
3447 }
3448 y
3449 `,
3450 },
3451 {
3452
3453 name: "extend_stationary_path",
3454
3455 text: `a.b
3456 a: {
3457 b
3458 c
3459 }
3460 `,
3461 key: `a.b`,
3462 newKey: `a.c.b`,
3463
3464 exp: `a.c.b
3465 a: {
3466 c: {
3467 b
3468 }
3469 }
3470 `,
3471 assertions: func(t *testing.T, g *d2graph.Graph) {
3472 assert.JSON(t, len(g.Objects), 3)
3473 },
3474 },
3475 {
3476 name: "extend_map",
3477
3478 text: `a.b: {
3479 e
3480 }
3481 a: {
3482 b
3483 c
3484 }
3485 `,
3486 key: `a.b`,
3487 newKey: `a.c.b`,
3488
3489 exp: `a: {
3490 e
3491 }
3492 a: {
3493 c: {
3494 b
3495 }
3496 }
3497 `,
3498 },
3499 {
3500 name: "into_container_with_flat_style",
3501
3502 text: `x.style.border-radius: 5
3503 y
3504 `,
3505 key: `y`,
3506 newKey: `x.y`,
3507
3508 exp: `x: {
3509 style.border-radius: 5
3510 y
3511 }
3512 `,
3513 },
3514 {
3515 name: "flat_between_containers",
3516
3517 text: `a.b
3518 c
3519 `,
3520 key: `a.b`,
3521 newKey: `c.b`,
3522
3523 exp: `a
3524 c: {
3525 b
3526 }
3527 `,
3528 assertions: func(t *testing.T, g *d2graph.Graph) {
3529 assert.JSON(t, len(g.Objects), 3)
3530 },
3531 },
3532 {
3533 name: "underscore-connection",
3534
3535 text: `a: {
3536 b
3537
3538 _.c.d -> b
3539 }
3540
3541 c: {
3542 d
3543 }
3544 `,
3545 key: `a.b`,
3546 newKey: `c.b`,
3547
3548 exp: `a: {
3549 _.c.d -> _.c.b
3550 }
3551
3552 c: {
3553 d
3554 b
3555 }
3556 `,
3557 },
3558
3559 {
3560 name: "nested-underscore-move-out",
3561 text: `guitar: {
3562 books: {
3563 _._.pipe
3564 }
3565 }
3566 `,
3567 key: `pipe`,
3568 newKey: `guitar.pipe`,
3569
3570 exp: `guitar: {
3571 books
3572 pipe
3573 }
3574 `,
3575 },
3576 {
3577 name: "flat_middle_container",
3578
3579 text: `a.b.c
3580 d
3581 `,
3582 key: `a.b`,
3583 newKey: `d.b`,
3584
3585 exp: `a.c
3586 d: {
3587 b
3588 }
3589 `,
3590 assertions: func(t *testing.T, g *d2graph.Graph) {
3591 assert.JSON(t, len(g.Objects), 4)
3592 },
3593 },
3594 {
3595 name: "flat_merge",
3596
3597 text: `a.b
3598 c.d: meow
3599 `,
3600 key: `a.b`,
3601 newKey: `c.b`,
3602
3603 exp: `a
3604 c: {
3605 d: meow
3606 b
3607 }
3608 `,
3609 assertions: func(t *testing.T, g *d2graph.Graph) {
3610 assert.JSON(t, len(g.Objects), 4)
3611 },
3612 },
3613 {
3614 name: "flat_reparent_with_value",
3615 text: `a.b: "yo"
3616 `,
3617 key: `a.b`,
3618 newKey: `b`,
3619
3620 exp: `a
3621 b: "yo"
3622 `,
3623 },
3624 {
3625 name: "flat_reparent_with_map_value",
3626 text: `a.b: {
3627 shape: hexagon
3628 }
3629 `,
3630 key: `a.b`,
3631 newKey: `b`,
3632
3633 exp: `a
3634 b: {
3635 shape: hexagon
3636 }
3637 `,
3638 },
3639 {
3640 name: "flat_reparent_with_mixed_map_value",
3641 text: `a.b: {
3642 # this is reserved
3643 shape: hexagon
3644 # this is not
3645 c
3646 }
3647 `,
3648 key: `a.b`,
3649 newKey: `b`,
3650
3651 exp: `a: {
3652 # this is not
3653 c
3654 }
3655 b: {
3656 # this is reserved
3657 shape: hexagon
3658 }
3659 `,
3660 },
3661 {
3662 name: "flat_style",
3663
3664 text: `a.style.opacity: 0.4
3665 a.style.fill: black
3666 b
3667 `,
3668 key: `a`,
3669 newKey: `b.a`,
3670
3671 exp: `b: {
3672 a.style.opacity: 0.4
3673 a.style.fill: black
3674 }
3675 `,
3676 },
3677 {
3678 name: "flat_nested_merge",
3679
3680 text: `a.b.c.d.e
3681 p.q.b.m.o
3682 `,
3683 key: `a.b.c`,
3684 newKey: `p.q.z`,
3685
3686 exp: `a.b.d.e
3687 p.q: {
3688 b.m.o
3689 z
3690 }
3691 `,
3692 },
3693 {
3694
3695 name: "flat_nested_merge_multiple_refs",
3696
3697 text: `a: {
3698 b.c.d
3699 e.f
3700 e.g
3701 }
3702 a.b.c
3703 a.b.c.q
3704 `,
3705 key: `a.e`,
3706 newKey: `a.b.c.e`,
3707
3708 exp: `a: {
3709 b.c: {
3710 d
3711 e
3712 }
3713 f
3714 g
3715 }
3716 a.b.c
3717 a.b.c.q
3718 `,
3719 },
3720 {
3721
3722 skip: true,
3723
3724 name: "less_nested_map",
3725
3726 text: `a: {
3727 b: {
3728 c
3729 }
3730 }
3731 a.b.c: {
3732 d
3733 }
3734 e
3735 `,
3736 key: `e`,
3737 newKey: `a.b.c.e`,
3738
3739 exp: `a: {
3740 b: {
3741 c
3742 }
3743 }
3744 a.b.c: {
3745 d
3746 e
3747 }
3748 `,
3749 },
3750 {
3751 name: "invalid-near",
3752
3753 text: `x: {
3754 near: y
3755 }
3756 y
3757 `,
3758 key: `y`,
3759 newKey: `x.y`,
3760
3761 exp: `x: {
3762 near: y
3763 y
3764 }
3765 `,
3766 expErr: `failed to move: "y" to "x.y": failed to recompile:
3767 x: {
3768 near: x.y
3769 y
3770 }
3771
3772 d2/testdata/d2oracle/TestMove/invalid-near.d2:2:9: near keys cannot be set to an descendant`,
3773 },
3774 {
3775 name: "near",
3776
3777 text: `x: {
3778 near: y
3779 }
3780 a
3781 y
3782 `,
3783 key: `y`,
3784 newKey: `a.y`,
3785
3786 exp: `x: {
3787 near: a.y
3788 }
3789 a: {
3790 y
3791 }
3792 `,
3793 },
3794 {
3795 name: "flat_near",
3796
3797 text: `x.near: y
3798 a
3799 y
3800 `,
3801 key: `y`,
3802 newKey: `a.y`,
3803
3804 exp: `x.near: a.y
3805 a: {
3806 y
3807 }
3808 `,
3809 },
3810 {
3811 name: "container_near",
3812
3813 text: `x: {
3814 y: {
3815 near: x.a.b.z
3816 }
3817 a.b.z
3818 }
3819 y
3820 `,
3821 key: `x.a.b`,
3822 newKey: `y.a`,
3823
3824 exp: `x: {
3825 y: {
3826 near: x.a.z
3827 }
3828 a.z
3829 }
3830 y: {
3831 a
3832 }
3833 `,
3834 },
3835 {
3836 name: "nhooyr_one",
3837
3838 text: `a: {
3839 b.c
3840 }
3841 d
3842 `,
3843 key: `a.b`,
3844 newKey: `d.q`,
3845
3846 exp: `a: {
3847 c
3848 }
3849 d: {
3850 q
3851 }
3852 `,
3853 },
3854 {
3855 name: "nhooyr_two",
3856
3857 text: `a: {
3858 b.c -> meow
3859 }
3860 d: {
3861 x
3862 }
3863 `,
3864 key: `a.b`,
3865 newKey: `d.b`,
3866
3867 exp: `a: {
3868 c -> meow
3869 }
3870 d: {
3871 x
3872 b
3873 }
3874 `,
3875 },
3876 {
3877 name: "unique_name",
3878
3879 text: `a: {
3880 b
3881 }
3882 a.b
3883 c: {
3884 b
3885 }
3886 `,
3887 key: `c.b`,
3888 newKey: `a.b`,
3889
3890 exp: `a: {
3891 b
3892 b 2
3893 }
3894 a.b
3895 c
3896 `,
3897 },
3898 {
3899 name: "unique_name_with_references",
3900
3901 text: `a: {
3902 b
3903 }
3904 d -> c.b
3905 c: {
3906 b
3907 }
3908 `,
3909 key: `c.b`,
3910 newKey: `a.b`,
3911
3912 exp: `a: {
3913 b
3914 b 2
3915 }
3916 d -> a.b 2
3917 c
3918 `,
3919 },
3920 {
3921 name: "map_transplant",
3922
3923 text: `a: {
3924 b
3925 style: {
3926 opacity: 0.4
3927 }
3928 c
3929 label: "yo"
3930 }
3931 d
3932 `,
3933 key: `a`,
3934 newKey: `d.a`,
3935
3936 exp: `b
3937
3938 c
3939
3940 d: {
3941 a: {
3942 style: {
3943 opacity: 0.4
3944 }
3945
3946 label: "yo"
3947 }
3948 }
3949 `,
3950 },
3951 {
3952 name: "map_with_label",
3953
3954 text: `a: "yo" {
3955 c
3956 }
3957 d
3958 `,
3959 key: `a`,
3960 newKey: `d.a`,
3961
3962 exp: `c
3963
3964 d: {
3965 a: "yo"
3966 }
3967 `,
3968 },
3969 {
3970 name: "underscore_merge",
3971
3972 text: `a: {
3973 _.b: "yo"
3974 }
3975 b: "what"
3976 c
3977 `,
3978 key: `b`,
3979 newKey: `c.b`,
3980
3981 exp: `a
3982
3983 c: {
3984 b: "yo"
3985 b: "what"
3986 }
3987 `,
3988 },
3989 {
3990 name: "underscore_children",
3991
3992 text: `a: {
3993 _.b
3994 }
3995 b
3996 `,
3997 key: `b`,
3998 newKey: `c`,
3999
4000 exp: `a: {
4001 _.c
4002 }
4003 c
4004 `,
4005 },
4006 {
4007 name: "underscore_transplant",
4008
4009 text: `a: {
4010 b: {
4011 _.c
4012 }
4013 }
4014 `,
4015 key: `a.c`,
4016 newKey: `c`,
4017
4018 exp: `a: {
4019 b
4020 }
4021 c
4022 `,
4023 },
4024 {
4025 name: "underscore_split",
4026
4027 text: `a: {
4028 b: {
4029 _.c.f
4030 }
4031 }
4032 `,
4033 key: `a.c`,
4034 newKey: `c`,
4035
4036 exp: `a: {
4037 b: {
4038 _.f
4039 }
4040 }
4041 c
4042 `,
4043 },
4044 {
4045 name: "underscore_edge_container_1",
4046
4047 text: `a: {
4048 _.b -> c
4049 }
4050 `,
4051 key: `b`,
4052 newKey: `a.b`,
4053
4054 exp: `a: {
4055 b -> c
4056 }
4057 `,
4058 },
4059 {
4060 name: "underscore_edge_container_2",
4061
4062 text: `a: {
4063 _.b -> c
4064 }
4065 `,
4066 key: `b`,
4067 newKey: `a.c.b`,
4068
4069 exp: `a: {
4070 c.b -> c
4071 }
4072 `,
4073 },
4074 {
4075 name: "underscore_edge_container_3",
4076
4077 text: `a: {
4078 _.b -> c
4079 }
4080 `,
4081 key: `b`,
4082 newKey: `d`,
4083
4084 exp: `a: {
4085 _.d -> c
4086 }
4087 `,
4088 },
4089 {
4090 name: "underscore_edge_container_4",
4091
4092 text: `a: {
4093 _.b -> c
4094 }
4095 `,
4096 key: `b`,
4097 newKey: `a.f`,
4098
4099 exp: `a: {
4100 f -> c
4101 }
4102 `,
4103 },
4104 {
4105 name: "underscore_edge_container_5",
4106
4107 text: `a: {
4108 _.b -> _.c
4109 }
4110 `,
4111 key: `b`,
4112 newKey: `c.b`,
4113
4114 exp: `a: {
4115 _.c.b -> _.c
4116 }
4117 `,
4118 },
4119 {
4120 name: "underscore_edge_container_6",
4121
4122 text: `x: {
4123 _.y.a -> _.y.b
4124 }
4125 `,
4126 key: `y`,
4127 newKey: `x.y`,
4128 includeDescendants: true,
4129
4130 exp: `x: {
4131 y.a -> y.b
4132 }
4133 `,
4134 },
4135 {
4136 name: "underscore_edge_split",
4137
4138 text: `a: {
4139 b: {
4140 _.c.f -> yo
4141 }
4142 }
4143 `,
4144 key: `a.c`,
4145 newKey: `c`,
4146
4147 exp: `a: {
4148 b: {
4149 _.f -> yo
4150 }
4151 }
4152 c
4153 `,
4154 },
4155 {
4156 name: "underscore_split_out",
4157
4158 text: `a: {
4159 b: {
4160 _.c.f
4161 }
4162 c: {
4163 e
4164 }
4165 }
4166 `,
4167 key: `a.c.f`,
4168 newKey: `a.c.e.f`,
4169
4170 exp: `a: {
4171 b: {
4172 _.c
4173 }
4174 c: {
4175 e: {
4176 f
4177 }
4178 }
4179 }
4180 `,
4181 },
4182 {
4183 name: "underscore_edge_children",
4184
4185 text: `a: {
4186 _.b -> c
4187 }
4188 b
4189 `,
4190 key: `b`,
4191 newKey: `c`,
4192
4193 exp: `a: {
4194 _.c -> c
4195 }
4196 c
4197 `,
4198 },
4199 {
4200 name: "move_container_children",
4201
4202 text: `b: {
4203 p
4204 q
4205 }
4206 a
4207 d
4208 `,
4209 key: `b`,
4210 newKey: `d.b`,
4211
4212 exp: `p
4213 q
4214
4215 a
4216 d: {
4217 b
4218 }
4219 `,
4220 },
4221 {
4222 name: "move_container_conflict_children",
4223
4224 text: `x: {
4225 a
4226 b
4227 }
4228 a
4229 d
4230 `,
4231 key: `x`,
4232 newKey: `d.x`,
4233
4234 exp: `a 2
4235 b
4236
4237 a
4238 d: {
4239 x
4240 }
4241 `,
4242 },
4243 {
4244 name: "edge_conflict",
4245
4246 text: `x.y.a -> x.y.b
4247 y
4248 `,
4249 key: `x`,
4250 newKey: `y.x`,
4251
4252 exp: `y 2.a -> y 2.b
4253 y: {
4254 x
4255 }
4256 `,
4257 },
4258 {
4259 name: "edge_basic",
4260
4261 text: `a -> b
4262 `,
4263 key: `a`,
4264 newKey: `c`,
4265
4266 exp: `c -> b
4267 `,
4268 },
4269 {
4270 name: "edge_nested_basic",
4271
4272 text: `a: {
4273 b -> c
4274 }
4275 `,
4276 key: `a.b`,
4277 newKey: `a.d`,
4278
4279 exp: `a: {
4280 d -> c
4281 }
4282 `,
4283 },
4284 {
4285 name: "edge_into_container",
4286
4287 text: `a: {
4288 d
4289 }
4290 b -> c
4291 `,
4292 key: `b`,
4293 newKey: `a.b`,
4294
4295 exp: `a: {
4296 d
4297 }
4298 a.b -> c
4299 `,
4300 },
4301 {
4302 name: "edge_out_of_container",
4303
4304 text: `a: {
4305 b -> c
4306 }
4307 `,
4308 key: `a.b`,
4309 newKey: `b`,
4310
4311 exp: `a: {
4312 _.b -> c
4313 }
4314 `,
4315 },
4316 {
4317 name: "connected_nested",
4318
4319 text: `x -> y.z
4320 `,
4321 key: `y.z`,
4322 newKey: `z`,
4323
4324 exp: `x -> z
4325 y
4326 `,
4327 },
4328 {
4329 name: "chain_connected_nested",
4330
4331 text: `y.z -> x -> y.z
4332 `,
4333 key: `y.z`,
4334 newKey: `z`,
4335
4336 exp: `z -> x -> z
4337 y
4338 `,
4339 },
4340 {
4341 name: "chain_connected_nested_no_extra_create",
4342
4343 text: `y.b -> x -> y.z
4344 `,
4345 key: `y.z`,
4346 newKey: `z`,
4347
4348 exp: `y.b -> x -> z
4349 `,
4350 },
4351 {
4352 name: "edge_across_containers",
4353
4354 text: `a: {
4355 b -> c
4356 }
4357 d
4358 `,
4359 key: `a.b`,
4360 newKey: `d.b`,
4361
4362 exp: `a: {
4363 _.d.b -> c
4364 }
4365 d
4366 `,
4367 },
4368 {
4369 name: "move_out_of_edge",
4370
4371 text: `a.b.c -> d.e.f
4372 `,
4373 key: `a.b`,
4374 newKey: `q`,
4375
4376 exp: `a.c -> d.e.f
4377 q
4378 `,
4379 },
4380 {
4381 name: "move_out_of_nested_edge",
4382
4383 text: `a.b.c -> d.e.f
4384 `,
4385 key: `a.b`,
4386 newKey: `d.e.q`,
4387
4388 exp: `a.c -> d.e.f
4389 d.e: {
4390 q
4391 }
4392 `,
4393 },
4394 {
4395 name: "append_multiple_styles",
4396
4397 text: `a: {
4398 style: {
4399 opacity: 0.4
4400 }
4401 }
4402 a: {
4403 style: {
4404 fill: "red"
4405 }
4406 }
4407 d
4408 `,
4409 key: `a`,
4410 newKey: `d.a`,
4411
4412 exp: `d: {
4413 a: {
4414 style: {
4415 opacity: 0.4
4416 }
4417 }
4418 a: {
4419 style: {
4420 fill: "red"
4421 }
4422 }
4423 }
4424 `,
4425 },
4426 {
4427 name: "move_into_key_with_value",
4428
4429 text: `a: meow
4430 b
4431 `,
4432 key: `b`,
4433 newKey: `a.b`,
4434
4435 exp: `a: meow {
4436 b
4437 }
4438 `,
4439 },
4440 {
4441 name: "gnarly_1",
4442
4443 text: `a.b.c -> d.e.f
4444 b: meow {
4445 p: "eyy"
4446 q
4447 p.p -> q.q
4448 }
4449 b.p.x -> d
4450 `,
4451 key: `b`,
4452 newKey: `d.b`,
4453
4454 exp: `a.b.c -> d.e.f
4455 d: {
4456 b: meow
4457 }
4458 p: "eyy"
4459 q
4460 p.p -> q.q
4461
4462 p.x -> d
4463 `,
4464 },
4465 {
4466 name: "reuse_map",
4467
4468 text: `a: {
4469 b: {
4470 hey
4471 }
4472 b.yo
4473 }
4474 k
4475 `,
4476 key: `k`,
4477 newKey: `a.b.k`,
4478
4479 exp: `a: {
4480 b: {
4481 hey
4482 k
4483 }
4484 b.yo
4485 }
4486 `,
4487 },
4488 {
4489
4490
4491
4492
4493
4494
4495
4496 skip: true,
4497 name: "merge_nested_flat",
4498
4499 text: `a: {
4500 b.c
4501 b.d
4502 b.e.g
4503 }
4504 k
4505 `,
4506 key: `k`,
4507 newKey: `a.b.k`,
4508
4509 exp: `a: {
4510 b.c
4511 b.d
4512 b.e.g
4513 b.k
4514 }
4515 `,
4516 },
4517 {
4518 name: "merge_nested_maps",
4519
4520 text: `a: {
4521 b.c
4522 b.d
4523 b.e.g
4524 b.d: {
4525 o
4526 }
4527 }
4528 k
4529 `,
4530 key: `k`,
4531 newKey: `a.b.k`,
4532
4533 exp: `a: {
4534 b.c
4535 b.d
4536 b.e.g
4537 b: {
4538 d: {
4539 o
4540 }
4541 k
4542 }
4543 }
4544 `,
4545 },
4546 {
4547 name: "merge_reserved",
4548
4549 text: `a: {
4550 b.c
4551 b.label: "yo"
4552 b.label: "hi"
4553 b.e.g
4554 }
4555 k
4556 `,
4557 key: `k`,
4558 newKey: `a.b.k`,
4559
4560 exp: `a: {
4561 b.c
4562 b.label: "yo"
4563 b.label: "hi"
4564 b: {
4565 e.g
4566 k
4567 }
4568 }
4569 `,
4570 },
4571 {
4572 name: "multiple_nesting_levels",
4573
4574 text: `a: {
4575 b: {
4576 c
4577 c.g
4578 }
4579 b.c.d
4580 x
4581 }
4582 a.b.c.f
4583 `,
4584 key: `a.x`,
4585 newKey: `a.b.c.x`,
4586
4587 exp: `a: {
4588 b: {
4589 c
4590 c: {
4591 g
4592 x
4593 }
4594 }
4595 b.c.d
4596 }
4597 a.b.c.f
4598 `,
4599 },
4600 {
4601 name: "edge_chain_basic",
4602
4603 text: `a -> b -> c
4604 `,
4605 key: `a`,
4606 newKey: `d`,
4607
4608 exp: `d -> b -> c
4609 `,
4610 },
4611 {
4612 name: "edge_chain_into_container",
4613
4614 text: `a -> b -> c
4615 d
4616 `,
4617 key: `a`,
4618 newKey: `d.a`,
4619
4620 exp: `d.a -> b -> c
4621 d
4622 `,
4623 },
4624 {
4625 name: "edge_chain_out_container",
4626
4627 text: `a: {
4628 b -> c -> d
4629 }
4630 `,
4631 key: `a.c`,
4632 newKey: `c`,
4633
4634 exp: `a: {
4635 b -> _.c -> d
4636 }
4637 `,
4638 },
4639 {
4640 name: "edge_chain_circular",
4641
4642 text: `a: {
4643 b -> c -> b
4644 }
4645 `,
4646 key: `a.b`,
4647 newKey: `b`,
4648
4649 exp: `a: {
4650 _.b -> c -> _.b
4651 }
4652 `,
4653 },
4654 {
4655 name: "container_multiple_refs_with_underscore",
4656
4657 text: `a
4658 b: {
4659 _.a
4660 }
4661 `,
4662 key: `a`,
4663 newKey: `b.a`,
4664
4665 exp: `b: {
4666 a
4667 }
4668 `,
4669 },
4670 {
4671 name: "container_conflicts_generated",
4672 text: `Square 2: "" {
4673 Square: ""
4674 }
4675 Square: ""
4676 Square 3
4677 `,
4678 key: `Square 2`,
4679 newKey: `Square 3.Square 2`,
4680
4681 exp: `Square 2: ""
4682
4683 Square: ""
4684 Square 3: {
4685 Square 2: ""
4686 }
4687 `,
4688 },
4689 {
4690 name: "include_descendants_flat_1",
4691 text: `x.y
4692 z
4693 `,
4694 key: `x`,
4695 newKey: `z.x`,
4696 includeDescendants: true,
4697
4698 exp: `z: {
4699 x.y
4700 }
4701 `,
4702 },
4703 {
4704 name: "include_descendants_flat_2",
4705 text: `a.x.y
4706 a.z
4707 `,
4708 key: `a.x`,
4709 newKey: `a.z.x`,
4710 includeDescendants: true,
4711
4712 exp: `a
4713 a.z: {
4714 x.y
4715 }
4716 `,
4717 },
4718 {
4719 name: "include_descendants_flat_3",
4720 text: `a.x.y
4721 a.z
4722 `,
4723 key: `a.x`,
4724 newKey: `x`,
4725 includeDescendants: true,
4726
4727 exp: `a
4728 a.z
4729 x.y
4730 `,
4731 },
4732 {
4733 name: "include_descendants_flat_4",
4734 text: `a.x.y
4735 a.z
4736 `,
4737 key: `a.x.y`,
4738 newKey: `y`,
4739 includeDescendants: true,
4740
4741 exp: `a.x
4742 a.z
4743 y
4744 `,
4745 },
4746 {
4747 name: "include_descendants_map_1",
4748 text: `x: {
4749 y
4750 }
4751 z
4752 `,
4753 key: `x`,
4754 newKey: `z.x`,
4755 includeDescendants: true,
4756
4757 exp: `z: {
4758 x: {
4759 y
4760 }
4761 }
4762 `,
4763 },
4764 {
4765 name: "include_descendants_map_2",
4766 text: `x: {
4767 y: {
4768 c
4769 }
4770 y.b
4771 }
4772 x.y.b
4773 z
4774 `,
4775 key: `x.y`,
4776 newKey: `a`,
4777 includeDescendants: true,
4778
4779 exp: `x
4780 x
4781 z
4782 a: {
4783 c
4784 }
4785 a.b
4786 `,
4787 },
4788 {
4789 name: "include_descendants_grandchild",
4790 text: `x: {
4791 y.a
4792 y: {
4793 b
4794 }
4795 }
4796 z
4797 `,
4798 key: `x`,
4799 newKey: `z.x`,
4800 includeDescendants: true,
4801
4802 exp: `z: {
4803 x: {
4804 y.a
4805 y: {
4806 b
4807 }
4808 }
4809 }
4810 `,
4811 },
4812 {
4813 name: "include_descendants_sql",
4814 text: `x: {
4815 shape: sql_table
4816 a: b
4817 }
4818 z
4819 `,
4820 key: `x`,
4821 newKey: `z.x`,
4822 includeDescendants: true,
4823
4824 exp: `z: {
4825 x: {
4826 shape: sql_table
4827 a: b
4828 }
4829 }
4830 `,
4831 },
4832 {
4833 name: "include_descendants_edge_child",
4834 text: `x: {
4835 a -> b
4836 }
4837 z
4838 `,
4839 key: `x`,
4840 newKey: `z.x`,
4841 includeDescendants: true,
4842
4843 exp: `z: {
4844 x: {
4845 a -> b
4846 }
4847 }
4848 `,
4849 },
4850 {
4851 name: "include_descendants_edge_ref_1",
4852 text: `x
4853 z
4854 x.a -> x.b
4855 `,
4856 key: `x`,
4857 newKey: `z.x`,
4858 includeDescendants: true,
4859
4860 exp: `z: {
4861 x
4862 }
4863 z.x.a -> z.x.b
4864 `,
4865 },
4866 {
4867 name: "include_descendants_edge_ref_2",
4868 text: `x -> y.z
4869 `,
4870 key: `y.z`,
4871 newKey: `z`,
4872 includeDescendants: true,
4873
4874 exp: `x -> z
4875 y
4876 `,
4877 },
4878 {
4879 name: "include_descendants_edge_ref_3",
4880 text: `x -> y.z.a
4881 `,
4882 key: `y.z`,
4883 newKey: `z`,
4884 includeDescendants: true,
4885
4886 exp: `x -> z.a
4887 y
4888 `,
4889 },
4890 {
4891 name: "include_descendants_edge_ref_4",
4892 text: `x -> y.z.a
4893 b
4894 `,
4895 key: `y.z`,
4896 newKey: `b.z`,
4897 includeDescendants: true,
4898
4899 exp: `x -> b.z.a
4900 b
4901 y
4902 `,
4903 },
4904 {
4905 name: "include_descendants_edge_ref_5",
4906 text: `foo: {
4907 x -> y.z.a
4908 b
4909 }
4910 `,
4911 key: `foo.y.z`,
4912 newKey: `foo.b.z`,
4913 includeDescendants: true,
4914
4915 exp: `foo: {
4916 x -> b.z.a
4917 b
4918 y
4919 }
4920 `,
4921 },
4922 {
4923 name: "include_descendants_edge_ref_6",
4924 text: `x -> y
4925 z
4926 `,
4927 key: `y`,
4928 newKey: `z.y`,
4929 includeDescendants: true,
4930
4931 exp: `x -> z.y
4932 z
4933 `,
4934 },
4935 {
4936 name: "include_descendants_edge_ref_7",
4937 text: `d.t -> d.np.s
4938 `,
4939 key: `d.np.s`,
4940 newKey: `d.s`,
4941 includeDescendants: true,
4942
4943 exp: `d.t -> d.s
4944 d.np
4945 `,
4946 },
4947 {
4948 name: "include_descendants_nested_1",
4949 text: `y.z
4950 b
4951 `,
4952 key: `y.z`,
4953 newKey: `b.z`,
4954 includeDescendants: true,
4955
4956 exp: `y
4957 b: {
4958 z
4959 }
4960 `,
4961 },
4962 {
4963 name: "include_descendants_nested_2",
4964 text: `y.z
4965 y.b
4966 `,
4967 key: `y.z`,
4968 newKey: `y.b.z`,
4969 includeDescendants: true,
4970
4971 exp: `y
4972 y.b: {
4973 z
4974 }
4975 `,
4976 },
4977 {
4978 name: "include_descendants_underscore",
4979 text: `github.code -> local.dev
4980
4981 github: {
4982 _.local.dev -> _.aws.workflows
4983 _.aws: {
4984 workflows
4985 }
4986 }
4987 `,
4988 key: `aws.workflows`,
4989 newKey: `github.workflows`,
4990 includeDescendants: true,
4991
4992 exp: `github.code -> local.dev
4993
4994 github: {
4995 _.local.dev -> workflows
4996 _.aws
4997 workflows
4998 }
4999 `,
5000 },
5001 {
5002 name: "include_descendants_underscore_2",
5003 text: `a: {
5004 b: {
5005 _.c
5006 }
5007 }
5008 `,
5009 key: `a.b`,
5010 newKey: `b`,
5011 includeDescendants: true,
5012
5013 exp: `a
5014 b: {
5015 _.a.c
5016 }
5017 `,
5018 },
5019 {
5020 name: "include_descendants_underscore_3",
5021 text: `a: {
5022 b: {
5023 _.c -> d
5024 _.c -> _.d
5025 }
5026 }
5027 `,
5028 key: `a.b`,
5029 newKey: `b`,
5030 includeDescendants: true,
5031
5032 exp: `a
5033 b: {
5034 _.a.c -> d
5035 _.a.c -> _.a.d
5036 }
5037 `,
5038 },
5039 {
5040 name: "include_descendants_edge_ref_underscore",
5041 text: `x
5042 z
5043 x.a -> x.b
5044 b: {
5045 _.x.a -> _.x.b
5046 }
5047 `,
5048 key: `x`,
5049 newKey: `z.x`,
5050 includeDescendants: true,
5051
5052 exp: `z: {
5053 x
5054 }
5055 z.x.a -> z.x.b
5056 b: {
5057 _.z.x.a -> _.z.x.b
5058 }
5059 `,
5060 },
5061 {
5062 name: "include_descendants_near",
5063 text: `x.y
5064 z
5065 a.near: x.y
5066 `,
5067 key: `x`,
5068 newKey: `z.x`,
5069 includeDescendants: true,
5070
5071 exp: `z: {
5072 x.y
5073 }
5074 a.near: z.x.y
5075 `,
5076 },
5077 {
5078 name: "include_descendants_conflict",
5079 text: `x.y
5080 z.x
5081 `,
5082 key: `x`,
5083 newKey: `z.x`,
5084 includeDescendants: true,
5085
5086 exp: `z: {
5087 x
5088 x 2.y
5089 }
5090 `,
5091 },
5092 {
5093 name: "include_descendants_non_conflict",
5094 text: `x.y
5095 z.x
5096 y
5097 `,
5098 key: `x`,
5099 newKey: `z.x`,
5100 includeDescendants: true,
5101
5102 exp: `z: {
5103 x
5104 x 2.y
5105 }
5106 y
5107 `,
5108 },
5109 {
5110 name: "nested_reserved_2",
5111 text: `A.B.C.shape: circle
5112 `,
5113 key: `A.B.C`,
5114 newKey: `C`,
5115
5116 exp: `A.B
5117 C.shape: circle
5118 `,
5119 },
5120 {
5121 name: "nested_reserved_3",
5122 text: `A.B.C.shape: circle
5123 A.B: {
5124 C
5125 D
5126 }
5127 `,
5128 key: `A.B.C`,
5129 newKey: `A.B.D.C`,
5130
5131 exp: `A.B
5132 A.B: {
5133 D: {
5134 C.shape: circle
5135 C
5136 }
5137 }
5138 `,
5139 },
5140 {
5141 name: "include_descendants_nested_reserved_2",
5142 text: `A.B.C.shape: circle
5143 `,
5144 key: `A.B.C`,
5145 newKey: `C`,
5146 includeDescendants: true,
5147
5148 exp: `A.B
5149 C.shape: circle
5150 `,
5151 },
5152 {
5153 name: "include_descendants_nested_reserved_3",
5154 text: `A.B.C.shape: circle
5155 `,
5156 key: `A.B`,
5157 newKey: `C`,
5158 includeDescendants: true,
5159
5160 exp: `A
5161 C.C.shape: circle
5162 `,
5163 },
5164 {
5165 name: "include_descendants_move_out",
5166 text: `a.b: {
5167 c: {
5168 d
5169 }
5170 }
5171 `,
5172 key: `a.b`,
5173 newKey: `b`,
5174 includeDescendants: true,
5175
5176 exp: `a
5177 b: {
5178 c: {
5179 d
5180 }
5181 }
5182 `,
5183 },
5184 {
5185 name: "include_descendants_underscore_regression",
5186 text: `x: {
5187 _.a
5188 }
5189 a
5190 `,
5191 key: `a`,
5192 newKey: `x.a`,
5193 includeDescendants: true,
5194
5195 exp: `x: {
5196 a
5197 }
5198 `,
5199 },
5200 {
5201 name: "include_descendants_underscore_regression_2",
5202 text: `x: {
5203 _.a.b
5204 }
5205 `,
5206 key: `a`,
5207 newKey: `x.a`,
5208 includeDescendants: true,
5209
5210 exp: `x: {
5211 a.b
5212 }
5213 `,
5214 },
5215 {
5216 name: "layers-basic",
5217
5218 text: `a
5219
5220 layers: {
5221 x: {
5222 b
5223 c
5224 }
5225 }
5226 `,
5227 key: `c`,
5228 newKey: `b.c`,
5229 boardPath: []string{"x"},
5230
5231 exp: `a
5232
5233 layers: {
5234 x: {
5235 b: {
5236 c
5237 }
5238 }
5239 }
5240 `,
5241 },
5242 {
5243 name: "scenarios-out-of-scope",
5244
5245 text: `a
5246
5247 scenarios: {
5248 x: {
5249 b
5250 c
5251 }
5252 }
5253 `,
5254 key: `a`,
5255 newKey: `b.a`,
5256 boardPath: []string{"x"},
5257
5258 expErr: `failed to move: "a" to "b.a": operation would modify AST outside of given scope`,
5259 },
5260 }
5261
5262 for _, tc := range testCases {
5263 if tc.skip {
5264 continue
5265 }
5266 tc := tc
5267 t.Run(tc.name, func(t *testing.T) {
5268 t.Parallel()
5269
5270 et := editTest{
5271 text: tc.text,
5272 fsTexts: tc.fsTexts,
5273 testFunc: func(g *d2graph.Graph) (*d2graph.Graph, error) {
5274 objectsBefore := len(g.Objects)
5275 var err error
5276 g, err = d2oracle.Move(g, tc.boardPath, tc.key, tc.newKey, tc.includeDescendants)
5277 if err == nil {
5278 objectsAfter := len(g.Objects)
5279 if objectsBefore != objectsAfter {
5280 t.Log(d2format.Format(g.AST))
5281 return nil, fmt.Errorf("move cannot destroy or create objects: found %d objects before and %d objects after", objectsBefore, objectsAfter)
5282 }
5283 }
5284 return g, err
5285 },
5286
5287 exp: tc.exp,
5288 expErr: tc.expErr,
5289 assertions: tc.assertions,
5290 }
5291 et.run(t)
5292 })
5293 }
5294 }
5295
5296 func TestDelete(t *testing.T) {
5297 t.Parallel()
5298
5299 testCases := []struct {
5300 name string
5301 boardPath []string
5302
5303 text string
5304 fsTexts map[string]string
5305 key string
5306
5307 expErr string
5308 exp string
5309 assertions func(t *testing.T, g *d2graph.Graph)
5310 }{
5311 {
5312 name: "flat",
5313
5314 text: `nerve-gift-earther
5315 `,
5316 key: `nerve-gift-earther`,
5317
5318 exp: ``,
5319 assertions: func(t *testing.T, g *d2graph.Graph) {
5320 if len(g.Objects) != 0 {
5321 t.Fatalf("expected zero objects: %#v", g.Objects)
5322 }
5323 },
5324 },
5325 {
5326 name: "edge_identical_child",
5327
5328 text: `x.x.y.z -> x.y.b
5329 `,
5330 key: `x`,
5331
5332 exp: `x.y.z -> y.b
5333 `,
5334 },
5335 {
5336 name: "duplicate_generated",
5337
5338 text: `x
5339 x 2
5340 x 3: {
5341 x 3
5342 x 4
5343 }
5344 x 4
5345 y
5346 `,
5347 key: `x 3`,
5348 exp: `x
5349 x 2
5350
5351 x 3
5352 x 5
5353
5354 x 4
5355 y
5356 `,
5357 },
5358 {
5359 name: "table_refs",
5360
5361 text: `a: {
5362 shape: sql_table
5363 b
5364 }
5365 c: {
5366 shape: sql_table
5367 d
5368 }
5369
5370 a.b
5371 a.b -> c.d
5372 `,
5373 key: `a`,
5374
5375 exp: `c: {
5376 shape: sql_table
5377 d
5378 }
5379 c.d
5380 `,
5381 },
5382 {
5383 name: "class_refs",
5384
5385 text: `a: {
5386 shape: class
5387 b: int
5388 }
5389
5390 a.b
5391 `,
5392 key: `a`,
5393
5394 exp: ``,
5395 },
5396 {
5397 name: "edge_both_identical_childs",
5398
5399 text: `x.x.y.z -> x.x.b
5400 `,
5401 key: `x`,
5402
5403 exp: `x.y.z -> x.b
5404 `,
5405 },
5406 {
5407 name: "edge_conflict",
5408
5409 text: `x.y.a -> x.y.b
5410 y
5411 `,
5412 key: `x`,
5413
5414 exp: `y 2.a -> y 2.b
5415 y
5416 `,
5417 },
5418 {
5419 name: "underscore_remove",
5420
5421 text: `x: {
5422 _.y
5423 _.a -> _.b
5424 _.c -> d
5425 }
5426 `,
5427 key: `x`,
5428
5429 exp: `y
5430 a -> b
5431 c -> d
5432 `,
5433 },
5434 {
5435 name: "underscore_no_conflict",
5436
5437 text: `x: {
5438 y: {
5439 _._.z
5440 }
5441 z
5442 }
5443 `,
5444 key: `x.y`,
5445
5446 exp: `x: {
5447 _.z
5448
5449 z
5450 }
5451 `,
5452 },
5453 {
5454 name: "nested_underscore_update",
5455
5456 text: `guitar: {
5457 books: {
5458 _._.pipe
5459 }
5460 }
5461 `,
5462 key: `guitar`,
5463
5464 exp: `books: {
5465 _.pipe
5466 }
5467 `,
5468 },
5469 {
5470 name: "only-underscore",
5471
5472 text: `guitar: {
5473 books: {
5474 _._.pipe
5475 }
5476 }
5477 `,
5478 key: `pipe`,
5479
5480 exp: `guitar: {
5481 books
5482 }
5483 `,
5484 },
5485 {
5486 name: "only-underscore-nested",
5487
5488 text: `guitar: {
5489 books: {
5490 _._.pipe: {
5491 a
5492 }
5493 }
5494 }
5495 `,
5496 key: `pipe`,
5497
5498 exp: `guitar: {
5499 books
5500 }
5501 a
5502 `,
5503 },
5504 {
5505 name: "node_in_edge",
5506
5507 text: `x -> y -> z -> q -> p
5508 z.ok: {
5509 what's up
5510 }
5511 `,
5512 key: `z`,
5513
5514 exp: `x -> y
5515 q -> p
5516 ok: {
5517 what's up
5518 }
5519 `,
5520 assertions: func(t *testing.T, g *d2graph.Graph) {
5521 if len(g.Objects) != 6 {
5522 t.Fatalf("expected 6 objects: %#v", g.Objects)
5523 }
5524 if len(g.Edges) != 2 {
5525 t.Fatalf("expected two edges: %#v", g.Edges)
5526 }
5527 },
5528 },
5529 {
5530 name: "node_in_edge_last",
5531
5532 text: `x -> y -> z -> q -> a.b.p
5533 a.b.p: {
5534 what's up
5535 }
5536 `,
5537 key: `a.b.p`,
5538
5539 exp: `x -> y -> z -> q
5540 a.b: {
5541 what's up
5542 }
5543 `,
5544 assertions: func(t *testing.T, g *d2graph.Graph) {
5545 if len(g.Objects) != 7 {
5546 t.Fatalf("expected 7 objects: %#v", g.Objects)
5547 }
5548 if len(g.Edges) != 3 {
5549 t.Fatalf("expected three edges: %#v", g.Edges)
5550 }
5551 },
5552 },
5553 {
5554 name: "children",
5555
5556 text: `p: {
5557 what's up
5558 x -> y
5559 }
5560 `,
5561 key: `p`,
5562
5563 exp: `what's up
5564 x -> y
5565 `,
5566 assertions: func(t *testing.T, g *d2graph.Graph) {
5567 if len(g.Objects) != 3 {
5568 t.Fatalf("expected 3 objects: %#v", g.Objects)
5569 }
5570 if len(g.Edges) != 1 {
5571 t.Fatalf("expected 1 edge: %#v", g.Edges)
5572 }
5573 },
5574 },
5575 {
5576 name: "hoist_children",
5577
5578 text: `a: {
5579 b: {
5580 c
5581 }
5582 }
5583 `,
5584 key: `a.b`,
5585
5586 exp: `a: {
5587 c
5588 }
5589 `,
5590 },
5591 {
5592 name: "hoist_edge_children",
5593
5594 text: `a: {
5595 b
5596 c -> d
5597 }
5598 `,
5599 key: `a`,
5600
5601 exp: `b
5602 c -> d
5603 `,
5604 },
5605 {
5606 name: "children_conflicts",
5607
5608 text: `p: {
5609 x
5610 }
5611 x
5612 `,
5613 key: `p`,
5614
5615 exp: `x 2
5616
5617 x
5618 `,
5619 },
5620 {
5621 name: "edge_map_style",
5622
5623 text: `x -> y: { style.stroke: red }
5624 `,
5625 key: `(x -> y)[0].style.stroke`,
5626
5627 exp: `x -> y
5628 `,
5629 },
5630 {
5631
5632 name: "breakup_arrowhead",
5633
5634 text: `x -> y: {
5635 target-arrowhead.shape: diamond
5636 }
5637 (x -> y)[0].source-arrowhead: {
5638 shape: diamond
5639 }
5640 `,
5641 key: `x`,
5642
5643 exp: `y
5644 `,
5645 },
5646 {
5647 name: "arrowhead",
5648
5649 text: `x -> y: {
5650 target-arrowhead.shape: diamond
5651 }
5652 `,
5653 key: `(x -> y)[0].target-arrowhead`,
5654
5655 exp: `x -> y
5656 `,
5657 },
5658 {
5659 name: "arrowhead_shape",
5660
5661 text: `x -> y: {
5662 target-arrowhead.shape: diamond
5663 }
5664 `,
5665 key: `(x -> y)[0].target-arrowhead.shape`,
5666
5667 exp: `x -> y
5668 `,
5669 },
5670 {
5671 name: "arrowhead_label",
5672
5673 text: `x -> y: {
5674 target-arrowhead.shape: diamond
5675 target-arrowhead.label: 1
5676 }
5677 `,
5678 key: `(x -> y)[0].target-arrowhead.label`,
5679
5680 exp: `x -> y: {
5681 target-arrowhead.shape: diamond
5682 }
5683 `,
5684 },
5685 {
5686 name: "arrowhead_map",
5687
5688 text: `x -> y: {
5689 target-arrowhead: {
5690 shape: diamond
5691 }
5692 }
5693 `,
5694 key: `(x -> y)[0].target-arrowhead.shape`,
5695
5696 exp: `x -> y
5697 `,
5698 },
5699 {
5700 name: "edge-only-style",
5701
5702 text: `x -> y: {
5703 style.stroke: red
5704 }
5705 `,
5706 key: `(x -> y)[0].style.stroke`,
5707
5708 exp: `x -> y
5709 `,
5710 },
5711 {
5712 name: "edge_key_style",
5713
5714 text: `x -> y
5715 (x -> y)[0].style.stroke: red
5716 `,
5717 key: `(x -> y)[0].style.stroke`,
5718
5719 exp: `x -> y
5720 `,
5721 },
5722 {
5723 name: "nested_edge_key_style",
5724
5725 text: `a: {
5726 x -> y
5727 }
5728 a.(x -> y)[0].style.stroke: red
5729 `,
5730 key: `a.(x -> y)[0].style.stroke`,
5731
5732 exp: `a: {
5733 x -> y
5734 }
5735 `,
5736 },
5737 {
5738 name: "multiple_flat_style",
5739
5740 text: `x.style.opacity: 0.4
5741 x.style.fill: red
5742 `,
5743 key: `x.style.fill`,
5744
5745 exp: `x.style.opacity: 0.4
5746 `,
5747 },
5748 {
5749 name: "edge_flat_style",
5750
5751 text: `A -> B
5752 A.style.stroke-dash: 5
5753 `,
5754 key: `A`,
5755
5756 exp: `B
5757 `,
5758 },
5759 {
5760 name: "flat_reserved",
5761
5762 text: `A -> B
5763 A.style.stroke-dash: 5
5764 `,
5765 key: `A.style.stroke-dash`,
5766
5767 exp: `A -> B
5768 `,
5769 },
5770 {
5771 name: "singular_flat_style",
5772
5773 text: `x.style.fill: red
5774 `,
5775 key: `x.style.fill`,
5776
5777 exp: `x
5778 `,
5779 },
5780 {
5781 name: "nested_flat_style",
5782
5783 text: `x: {
5784 style.fill: red
5785 }
5786 `,
5787 key: `x.style.fill`,
5788
5789 exp: `x
5790 `,
5791 },
5792 {
5793 name: "multiple_map_styles",
5794
5795 text: `x: {
5796 style: {
5797 opacity: 0.4
5798 fill: red
5799 }
5800 }
5801 `,
5802 key: `x.style.fill`,
5803
5804 exp: `x: {
5805 style: {
5806 opacity: 0.4
5807 }
5808 }
5809 `,
5810 },
5811 {
5812 name: "singular_map_style",
5813
5814 text: `x: {
5815 style: {
5816 fill: red
5817 }
5818 }
5819 `,
5820 key: `x.style.fill`,
5821
5822 exp: `x
5823 `,
5824 },
5825 {
5826 name: "delete_near",
5827
5828 text: `x: {
5829 near: y
5830 }
5831 y
5832 `,
5833 key: `x.near`,
5834
5835 exp: `x
5836 y
5837 `,
5838 },
5839 {
5840 name: "delete_container_of_near",
5841
5842 text: `direction: down
5843 first input -> start game -> game loop
5844
5845 game loop: {
5846 direction: down
5847 input -> increase bird top velocity
5848
5849 move bird -> move pipes -> render
5850
5851 render -> no collision -> wait 16 milliseconds -> move bird
5852 render -> collision detected -> game over
5853 no collision.near: game loop.collision detected
5854 }
5855 `,
5856 key: `game loop`,
5857
5858 exp: `direction: down
5859 first input -> start game
5860
5861 input -> increase bird top velocity
5862
5863 move bird -> move pipes -> render
5864
5865 render -> no collision -> wait 16 milliseconds -> move bird
5866 render -> collision detected -> game over
5867 no collision.near: collision detected
5868 `,
5869 },
5870 {
5871 name: "delete_tooltip",
5872
5873 text: `x: {
5874 tooltip: yeah
5875 }
5876 `,
5877 key: `x.tooltip`,
5878
5879 exp: `x
5880 `,
5881 },
5882 {
5883 name: "delete_link",
5884
5885 text: `x.link: https://google.com
5886 `,
5887 key: `x.link`,
5888
5889 exp: `x
5890 `,
5891 },
5892 {
5893 name: "delete_icon",
5894
5895 text: `y.x: {
5896 link: https://google.com
5897 icon: https://google.com/memes.jpeg
5898 }
5899 `,
5900 key: `y.x.icon`,
5901
5902 exp: `y.x: {
5903 link: https://google.com
5904 }
5905 `,
5906 },
5907 {
5908 name: "delete_redundant_flat_near",
5909
5910 text: `x
5911
5912 y
5913 `,
5914 key: `x.near`,
5915
5916 exp: `x
5917
5918 y
5919 `,
5920 },
5921 {
5922 name: "delete_needed_flat_near",
5923
5924 text: `x.near: y
5925 y
5926 `,
5927 key: `x.near`,
5928
5929 exp: `x
5930 y
5931 `,
5932 },
5933 {
5934 name: "children_no_self_conflict",
5935
5936 text: `x: {
5937 x
5938 }
5939 `,
5940 key: `x`,
5941
5942 exp: `x
5943 `,
5944 },
5945 {
5946 name: "near",
5947
5948 text: `x: {
5949 near: y
5950 }
5951 y
5952 `,
5953 key: `y`,
5954
5955 exp: `x
5956 `,
5957 },
5958 {
5959 name: "container_near",
5960
5961 text: `x: {
5962 y: {
5963 near: x.z
5964 }
5965 z
5966 a: {
5967 near: x.z
5968 }
5969 }
5970 `,
5971 key: `x`,
5972
5973 exp: `y: {
5974 near: z
5975 }
5976 z
5977 a: {
5978 near: z
5979 }
5980 `,
5981 },
5982 {
5983 name: "multi_near",
5984
5985 text: `Starfish: {
5986 API
5987 Bluefish: {
5988 near: Starfish.API
5989 }
5990 Yo: {
5991 near: Blah
5992 }
5993 }
5994 Blah
5995 `,
5996 key: `Starfish`,
5997
5998 exp: `API
5999 Bluefish: {
6000 near: API
6001 }
6002 Yo: {
6003 near: Blah
6004 }
6005
6006 Blah
6007 `,
6008 },
6009 {
6010 name: "children_nested_conflicts",
6011
6012 text: `p: {
6013 x: {
6014 y
6015 }
6016 }
6017 x
6018 `,
6019 key: `p`,
6020
6021 exp: `x 2: {
6022 y
6023 }
6024
6025 x
6026 `,
6027 },
6028 {
6029 name: "children_referenced_conflicts",
6030
6031 text: `p: {
6032 x
6033 }
6034 x
6035
6036 p.x: "hi"
6037 `,
6038 key: `p`,
6039
6040 exp: `x 2
6041
6042 x
6043
6044 x 2: "hi"
6045 `,
6046 },
6047 {
6048 name: "children_flat_conflicts",
6049
6050 text: `p.x
6051 x
6052
6053 p.x: "hi"
6054 `,
6055 key: `p`,
6056
6057 exp: `x 2
6058 x
6059
6060 x 2: "hi"
6061 `,
6062 },
6063 {
6064 name: "children_edges_flat_conflicts",
6065
6066 text: `p.x -> p.y -> p.z
6067 x
6068 z
6069
6070 p.x: "hi"
6071 p.z: "ey"
6072 `,
6073 key: `p`,
6074
6075 exp: `x 2 -> y -> z 2
6076 x
6077 z
6078
6079 x 2: "hi"
6080 z 2: "ey"
6081 `,
6082 },
6083 {
6084 name: "children_nested_referenced_conflicts",
6085
6086 text: `p: {
6087 x.y
6088 }
6089 x
6090
6091 p.x: "hi"
6092 p.x.y: "hey"
6093 `,
6094 key: `p`,
6095
6096 exp: `x 2.y
6097
6098 x
6099
6100 x 2: "hi"
6101 x 2.y: "hey"
6102 `,
6103 },
6104 {
6105 name: "children_edge_conflicts",
6106
6107 text: `p: {
6108 x -> y
6109 }
6110 x
6111
6112 p.x: "hi"
6113 `,
6114 key: `p`,
6115
6116 exp: `x 2 -> y
6117
6118 x
6119
6120 x 2: "hi"
6121 `,
6122 },
6123 {
6124 name: "children_multiple_conflicts",
6125
6126 text: `p: {
6127 x -> y
6128 x
6129 y
6130 }
6131 x
6132 y
6133
6134 p.x: "hi"
6135 `,
6136 key: `p`,
6137
6138 exp: `x 2 -> y 2
6139 x 2
6140 y 2
6141
6142 x
6143 y
6144
6145 x 2: "hi"
6146 `,
6147 },
6148 {
6149 name: "multi_path_map_conflict",
6150
6151 text: `x.y: {
6152 z
6153 }
6154 x: {
6155 z
6156 }
6157 `,
6158 key: `x.y`,
6159
6160 exp: `x: {
6161 z 2
6162 }
6163 x: {
6164 z
6165 }
6166 `,
6167 },
6168 {
6169 name: "multi_path_map_no_conflict",
6170
6171 text: `x.y: {
6172 z
6173 }
6174 x: {
6175 z
6176 }
6177 `,
6178 key: `x`,
6179
6180 exp: `y: {
6181 z
6182 }
6183
6184 z
6185 `,
6186 },
6187 {
6188 name: "children_scope",
6189
6190 text: `x.q: {
6191 p: {
6192 what's up
6193 x -> y
6194 }
6195 }
6196 `,
6197 key: `x.q.p`,
6198
6199 exp: `x.q: {
6200 what's up
6201 x -> y
6202 }
6203 `,
6204 assertions: func(t *testing.T, g *d2graph.Graph) {
6205 if len(g.Objects) != 5 {
6206 t.Fatalf("expected 5 objects: %#v", g.Objects)
6207 }
6208 if len(g.Edges) != 1 {
6209 t.Fatalf("expected 1 edge: %#v", g.Edges)
6210 }
6211 },
6212 },
6213 {
6214 name: "children_order",
6215
6216 text: `c: {
6217 before
6218 y: {
6219 congo
6220 }
6221 after
6222 }
6223 `,
6224 key: `c.y`,
6225
6226 exp: `c: {
6227 before
6228
6229 congo
6230
6231 after
6232 }
6233 `,
6234 assertions: func(t *testing.T, g *d2graph.Graph) {
6235 if len(g.Objects) != 4 {
6236 t.Fatalf("expected 4 objects: %#v", g.Objects)
6237 }
6238 },
6239 },
6240 {
6241 name: "edge_first",
6242
6243 text: `l.p.d: {x -> p -> y -> z}
6244 `,
6245 key: `l.p.d.(x -> p)[0]`,
6246
6247 exp: `l.p.d: {x; p -> y -> z}
6248 `,
6249 assertions: func(t *testing.T, g *d2graph.Graph) {
6250 if len(g.Objects) != 7 {
6251 t.Fatalf("expected 7 objects: %#v", g.Objects)
6252 }
6253 if len(g.Edges) != 2 {
6254 t.Fatalf("unexpected edges: %#v", g.Objects)
6255 }
6256 },
6257 },
6258 {
6259 name: "multiple_flat_middle_container",
6260
6261 text: `a.b.c
6262 a.b.d
6263 `,
6264 key: `a.b`,
6265
6266 exp: `a.c
6267 a.d
6268 `,
6269 },
6270 {
6271 name: "edge_middle",
6272
6273 text: `l.p.d: {x -> y -> z -> q -> p}
6274 `,
6275 key: `l.p.d.(z -> q)[0]`,
6276
6277 exp: `l.p.d: {x -> y -> z; q -> p}
6278 `,
6279 assertions: func(t *testing.T, g *d2graph.Graph) {
6280 if len(g.Objects) != 8 {
6281 t.Fatalf("expected 8 objects: %#v", g.Objects)
6282 }
6283 if len(g.Edges) != 3 {
6284 t.Fatalf("expected three edges: %#v", g.Edges)
6285 }
6286 },
6287 },
6288 {
6289 name: "edge_last",
6290
6291 text: `l.p.d: {x -> y -> z -> q -> p}
6292 `,
6293 key: `l.p.d.(q -> p)[0]`,
6294
6295 exp: `l.p.d: {x -> y -> z -> q; p}
6296 `,
6297 assertions: func(t *testing.T, g *d2graph.Graph) {
6298 if len(g.Objects) != 8 {
6299 t.Fatalf("expected 8 objects: %#v", g.Objects)
6300 }
6301 if len(g.Edges) != 3 {
6302 t.Fatalf("expected three edges: %#v", g.Edges)
6303 }
6304 },
6305 },
6306 {
6307 name: "key_with_edges",
6308
6309 text: `hello.meow -> hello.bark
6310 `,
6311 key: `hello.(meow -> bark)[0]`,
6312
6313 exp: `hello.meow
6314 hello.bark
6315 `,
6316 assertions: func(t *testing.T, g *d2graph.Graph) {
6317 if len(g.Objects) != 3 {
6318 t.Fatalf("expected three objects: %#v", g.Objects)
6319 }
6320 if len(g.Edges) != 0 {
6321 t.Fatalf("expected zero edges: %#v", g.Edges)
6322 }
6323 },
6324 },
6325 {
6326 name: "key_with_edges_2",
6327
6328 text: `hello.meow -> hello.bark
6329 `,
6330 key: `hello.meow`,
6331
6332 exp: `hello.bark
6333 `,
6334 assertions: func(t *testing.T, g *d2graph.Graph) {
6335 if len(g.Objects) != 2 {
6336 t.Fatalf("expected 2 objects: %#v", g.Objects)
6337 }
6338 },
6339 },
6340 {
6341 name: "key_with_edges_3",
6342
6343 text: `hello.(meow -> bark)
6344 `,
6345 key: `hello.meow`,
6346
6347 exp: `hello.bark
6348 `,
6349 assertions: func(t *testing.T, g *d2graph.Graph) {
6350 if len(g.Objects) != 2 {
6351 t.Fatalf("expected 2 objects: %#v", g.Objects)
6352 }
6353 },
6354 },
6355 {
6356 name: "key_with_edges_4",
6357
6358 text: `hello.(meow -> bark)
6359 `,
6360 key: `(hello.meow -> hello.bark)[0]`,
6361
6362 exp: `hello.meow
6363 hello.bark
6364 `,
6365 assertions: func(t *testing.T, g *d2graph.Graph) {
6366 if len(g.Objects) != 3 {
6367 t.Fatalf("expected three objects: %#v", g.Objects)
6368 }
6369 if len(g.Edges) != 0 {
6370 t.Fatalf("expected zero edges: %#v", g.Edges)
6371 }
6372 },
6373 },
6374 {
6375 name: "nested",
6376
6377 text: `a.b.c.d
6378 `,
6379 key: `a.b`,
6380
6381 exp: `a.c.d
6382 `,
6383 assertions: func(t *testing.T, g *d2graph.Graph) {
6384 if len(g.Objects) != 3 {
6385 t.Fatalf("expected 3 objects: %#v", g.Objects)
6386 }
6387 },
6388 },
6389 {
6390 name: "nested_2",
6391
6392 text: `a.b.c.d
6393 `,
6394 key: `a.b.c.d`,
6395
6396 exp: `a.b.c
6397 `,
6398 assertions: func(t *testing.T, g *d2graph.Graph) {
6399 if len(g.Objects) != 3 {
6400 t.Fatalf("expected 3 objects: %#v", g.Objects)
6401 }
6402 },
6403 },
6404 {
6405 name: "order_1",
6406
6407 text: `x -> p -> y -> z
6408 `,
6409 key: `p`,
6410
6411 exp: `x
6412 y -> z
6413 `,
6414 assertions: func(t *testing.T, g *d2graph.Graph) {
6415 if len(g.Objects) != 3 {
6416 t.Fatalf("expected 3 objects: %#v", g.Objects)
6417 }
6418 },
6419 },
6420 {
6421 name: "order_2",
6422
6423 text: `p -> y -> z
6424 `,
6425 key: `y`,
6426
6427 exp: `p
6428 z
6429 `,
6430 assertions: func(t *testing.T, g *d2graph.Graph) {
6431 if len(g.Objects) != 2 {
6432 t.Fatalf("expected 2 objects: %#v", g.Objects)
6433 }
6434 },
6435 },
6436 {
6437 name: "order_3",
6438
6439 text: `y -> p -> y -> z
6440 `,
6441 key: `y`,
6442
6443 exp: `p
6444 z
6445 `,
6446 assertions: func(t *testing.T, g *d2graph.Graph) {
6447 if len(g.Objects) != 2 {
6448 t.Fatalf("expected 2 objects: %#v", g.Objects)
6449 }
6450 },
6451 },
6452 {
6453 name: "order_4",
6454
6455 text: `y -> p
6456 `,
6457 key: `p`,
6458
6459 exp: `y
6460 `,
6461 assertions: func(t *testing.T, g *d2graph.Graph) {
6462 if len(g.Objects) != 1 {
6463 t.Fatalf("expected 1 object: %#v", g.Objects)
6464 }
6465 },
6466 },
6467 {
6468 name: "order_5",
6469
6470 text: `x: {
6471 a -> b -> c
6472 q -> p
6473 }
6474 `,
6475 key: `x.a`,
6476
6477 exp: `x: {
6478 b -> c
6479 q -> p
6480 }
6481 `,
6482 assertions: func(t *testing.T, g *d2graph.Graph) {
6483 if len(g.Objects) != 5 {
6484 t.Fatalf("expected 5 objects: %#v", g.Objects)
6485 }
6486 },
6487 },
6488 {
6489 name: "order_6",
6490
6491 text: `x: {
6492 lol
6493 }
6494 x.p.q.z
6495 `,
6496 key: `x.p.q.z`,
6497
6498 exp: `x: {
6499 lol
6500 }
6501 x.p.q
6502 `,
6503 assertions: func(t *testing.T, g *d2graph.Graph) {
6504 if len(g.Objects) != 4 {
6505 t.Fatalf("expected 4 objects: %#v", g.Objects)
6506 }
6507 },
6508 },
6509 {
6510 name: "order_7",
6511
6512 text: `x: {
6513 lol
6514 }
6515 x.p.q.more
6516 x.p.q.z
6517 `,
6518 key: `x.p.q.z`,
6519
6520 exp: `x: {
6521 lol
6522 }
6523 x.p.q.more
6524 `,
6525 assertions: func(t *testing.T, g *d2graph.Graph) {
6526 if len(g.Objects) != 5 {
6527 t.Fatalf("expected 5 objects: %#v", g.Objects)
6528 }
6529 },
6530 },
6531 {
6532 name: "order_8",
6533
6534 text: `x -> y
6535 bark
6536 y -> x
6537 zebra
6538 x -> q
6539 kang
6540 `,
6541 key: `x`,
6542
6543 exp: `bark
6544 y
6545
6546 zebra
6547 q
6548
6549 kang
6550 `,
6551 assertions: func(t *testing.T, g *d2graph.Graph) {
6552 if len(g.Objects) != 5 {
6553 t.Fatalf("expected 5 objects: %#v", g.Objects)
6554 }
6555 },
6556 },
6557 {
6558 name: "empty_map",
6559
6560 text: `c: {
6561 y: {
6562 congo
6563 }
6564 }
6565 `,
6566 key: `c.y.congo`,
6567
6568 exp: `c: {
6569 y
6570 }
6571 `,
6572 assertions: func(t *testing.T, g *d2graph.Graph) {
6573 if len(g.Objects) != 2 {
6574 t.Fatalf("expected 2 objects: %#v", g.Objects)
6575 }
6576 },
6577 },
6578 {
6579 name: "edge_common",
6580
6581 text: `x.a -> x.y
6582 `,
6583 key: "x",
6584
6585 exp: `a -> y
6586 `,
6587 assertions: func(t *testing.T, g *d2graph.Graph) {
6588 if len(g.Objects) != 2 {
6589 t.Fatalf("expected 2 objects: %#v", g.Objects)
6590 }
6591 if len(g.Edges) != 1 {
6592 t.Fatalf("unexpected edges: %#v", g.Edges)
6593 }
6594 },
6595 },
6596 {
6597 name: "edge_common_2",
6598
6599 text: `x.(a -> y)
6600 `,
6601 key: "x",
6602
6603 exp: `a -> y
6604 `,
6605 assertions: func(t *testing.T, g *d2graph.Graph) {
6606 if len(g.Objects) != 2 {
6607 t.Fatalf("expected 2 objects: %#v", g.Objects)
6608 }
6609 if len(g.Edges) != 1 {
6610 t.Fatalf("unexpected edges: %#v", g.Edges)
6611 }
6612 },
6613 },
6614 {
6615 name: "edge_common_3",
6616
6617 text: `x.(a -> y)
6618 `,
6619 key: "(x.a -> x.y)[0]",
6620
6621 exp: `x.a
6622 x.y
6623 `,
6624 assertions: func(t *testing.T, g *d2graph.Graph) {
6625 if len(g.Objects) != 3 {
6626 t.Fatalf("expected 3 objects: %#v", g.Objects)
6627 }
6628 if len(g.Edges) != 0 {
6629 t.Fatalf("unexpected edges: %#v", g.Edges)
6630 }
6631 },
6632 },
6633 {
6634 name: "edge_common_4",
6635
6636 text: `x.a -> x.y
6637 `,
6638 key: "x.(a -> y)[0]",
6639
6640 exp: `x.a
6641 x.y
6642 `,
6643 assertions: func(t *testing.T, g *d2graph.Graph) {
6644 if len(g.Objects) != 3 {
6645 t.Fatalf("expected 3 objects: %#v", g.Objects)
6646 }
6647 if len(g.Edges) != 0 {
6648 t.Fatalf("unexpected edges: %#v", g.Edges)
6649 }
6650 },
6651 },
6652 {
6653 name: "edge_decrement",
6654
6655 text: `a -> b
6656 a -> b
6657 a -> b
6658 a -> b
6659 a -> b
6660 (a -> b)[0]: zero
6661 (a -> b)[1]: one
6662 (a -> b)[2]: two
6663 (a -> b)[3]: three
6664 (a -> b)[4]: four
6665 `,
6666 key: `(a -> b)[2]`,
6667
6668 exp: `a -> b
6669 a -> b
6670
6671 a -> b
6672 a -> b
6673 (a -> b)[0]: zero
6674 (a -> b)[1]: one
6675
6676 (a -> b)[2]: three
6677 (a -> b)[3]: four
6678 `,
6679 },
6680 {
6681 name: "shape_class",
6682 text: `D2 Parser: {
6683 shape: class
6684
6685 # Default visibility is + so no need to specify.
6686 +reader: io.RuneReader
6687 readerPos: d2ast.Position
6688
6689 # Private field.
6690 -lookahead: "[]rune"
6691
6692 # Protected field.
6693 # We have to escape the # to prevent the line from being parsed as a comment.
6694 \#lookaheadPos: d2ast.Position
6695
6696 +peek(): (r rune, eof bool)
6697 rewind()
6698 commit()
6699
6700 \#peekn(n int): (s string, eof bool)
6701 }
6702
6703 "github.com/terrastruct/d2parser.git" -> D2 Parser
6704 `,
6705 key: `D2 Parser`,
6706
6707 exp: `"github.com/terrastruct/d2parser.git"
6708 `,
6709 },
6710
6711 {
6712 name: "shape_sql_table",
6713
6714 text: `cloud: {
6715 disks: {
6716 shape: sql_table
6717 id: int {constraint: primary_key}
6718 }
6719 blocks: {
6720 shape: sql_table
6721 id: int {constraint: primary_key}
6722 disk: int {constraint: foreign_key}
6723 blob: blob
6724 }
6725 blocks.disk -> disks.id
6726
6727 AWS S3 Vancouver -> disks
6728 }
6729 `,
6730 key: "cloud.blocks",
6731
6732 exp: `cloud: {
6733 disks: {
6734 shape: sql_table
6735 id: int {constraint: primary_key}
6736 }
6737 disks.id
6738
6739 AWS S3 Vancouver -> disks
6740 }
6741 `,
6742 },
6743 {
6744 name: "nested_reserved",
6745
6746 text: `x.y.z: {
6747 label: Sweet April showers do spring May flowers.
6748 icon: bingo
6749 near: x.y.jingle
6750 shape: parallelogram
6751 style: {
6752 stroke: red
6753 }
6754 }
6755 x.y.jingle
6756 `,
6757 key: "x.y.z",
6758
6759 exp: `x.y
6760 x.y.jingle
6761 `,
6762 },
6763 {
6764 name: "only_delete_obj_reserved",
6765
6766 text: `A: {style.stroke: "#000e3d"}
6767 B
6768 A -> B: {style.stroke: "#2b50c2"}
6769 `,
6770 key: `A.style.stroke`,
6771 exp: `A
6772 B
6773 A -> B: {style.stroke: "#2b50c2"}
6774 `,
6775 },
6776 {
6777 name: "only_delete_edge_reserved",
6778
6779 text: `A: {style.stroke: "#000e3d"}
6780 B
6781 A -> B: {style.stroke: "#2b50c2"}
6782 `,
6783 key: `(A->B)[0].style.stroke`,
6784 exp: `A: {style.stroke: "#000e3d"}
6785 B
6786 A -> B
6787 `,
6788 },
6789 {
6790 name: "width",
6791
6792 text: `x: {
6793 width: 200
6794 }
6795 `,
6796 key: `x.width`,
6797
6798 exp: `x
6799 `,
6800 },
6801 {
6802 name: "left",
6803
6804 text: `x: {
6805 left: 200
6806 }
6807 `,
6808 key: `x.left`,
6809
6810 exp: `x
6811 `,
6812 },
6813 {
6814 name: "conflicts_generated",
6815 text: `Text 4
6816 Square: {
6817 Text 4: {
6818 Text 2
6819 }
6820 Text
6821 }
6822 `,
6823 key: `Square`,
6824
6825 exp: `Text 4
6826
6827 Text 2: {
6828 Text 2
6829 }
6830 Text
6831 `,
6832 },
6833 {
6834 name: "conflicts_generated_continued",
6835 text: `Text 4
6836
6837 Text: {
6838 Text 2
6839 }
6840 Text 2
6841 `,
6842 key: `Text`,
6843
6844 exp: `Text 4
6845
6846 Text
6847
6848 Text 2
6849 `,
6850 },
6851 {
6852 name: "conflicts_generated_3",
6853 text: `x: {
6854 Square 2
6855 Square 3
6856 }
6857
6858 Square 2
6859 Square
6860 `,
6861 key: `x`,
6862
6863 exp: `Square 4
6864 Square 3
6865
6866 Square 2
6867 Square
6868 `,
6869 },
6870 {
6871 name: "drop_value",
6872 text: `a.b.c: "c label"
6873 `,
6874 key: `a.b.c`,
6875
6876 exp: `a.b
6877 `,
6878 },
6879 {
6880 name: "drop_value_with_primary",
6881 text: `a.b: hello {
6882 shape: circle
6883 }
6884 `,
6885 key: `a.b`,
6886
6887 exp: `a
6888 `,
6889 },
6890 {
6891 name: "save_map",
6892 text: `a.b: {
6893 shape: circle
6894 }
6895 `,
6896 key: `a`,
6897
6898 exp: `b: {
6899 shape: circle
6900 }
6901 `,
6902 },
6903 {
6904 name: "save_map_with_primary",
6905 text: `a.b: hello {
6906 shape: circle
6907 }
6908 `,
6909 key: `a`,
6910
6911 exp: `b: hello {
6912 shape: circle
6913 }
6914 `,
6915 },
6916 {
6917 name: "chaos_1",
6918
6919 text: `cm: {shape: cylinder}
6920 cm <-> cm: {source-arrowhead.shape: cf-one-required}
6921 mt: z
6922 cdpdxz
6923
6924 bymdyk: hdzuj {shape: class}
6925
6926 bymdyk <-> bymdyk
6927 cm
6928
6929 cm <-> bymdyk: {
6930 source-arrowhead.shape: cf-many-required
6931 target-arrowhead.shape: arrow
6932 }
6933 bymdyk <-> cdpdxz
6934
6935 bymdyk -> cm: nk {
6936 target-arrowhead.shape: diamond
6937 target-arrowhead.label: 1
6938 }
6939 `,
6940 key: `bymdyk`,
6941
6942 exp: `cm: {shape: cylinder}
6943 cm <-> cm: {source-arrowhead.shape: cf-one-required}
6944 mt: z
6945 cdpdxz
6946
6947 cm
6948 `,
6949 },
6950 {
6951 name: "layers-basic",
6952
6953 text: `a
6954
6955 layers: {
6956 x: {
6957 b
6958 c
6959 }
6960 }
6961 `,
6962 key: `c`,
6963 boardPath: []string{"x"},
6964
6965 exp: `a
6966
6967 layers: {
6968 x: {
6969 b
6970 }
6971 }
6972 `,
6973 },
6974 {
6975 name: "scenarios-basic",
6976
6977 text: `a
6978
6979 scenarios: {
6980 x: {
6981 b
6982 c
6983 }
6984 }
6985 `,
6986 key: `c`,
6987 boardPath: []string{"x"},
6988
6989 exp: `a
6990
6991 scenarios: {
6992 x: {
6993 b
6994 }
6995 }
6996 `,
6997 },
6998 {
6999 name: "scenarios-inherited",
7000
7001 text: `a
7002
7003 scenarios: {
7004 x: {
7005 b
7006 c
7007 }
7008 }
7009 `,
7010 key: `a`,
7011 boardPath: []string{"x"},
7012
7013 exp: `a
7014
7015 scenarios: {
7016 x: {
7017 b
7018 c
7019 a: null
7020 }
7021 }
7022 `,
7023 },
7024 {
7025 name: "scenarios-edge-inherited",
7026
7027 text: `a -> b
7028
7029 scenarios: {
7030 x: {
7031 b
7032 c
7033 }
7034 }
7035 `,
7036 key: `(a -> b)[0]`,
7037 boardPath: []string{"x"},
7038
7039 exp: `a -> b
7040
7041 scenarios: {
7042 x: {
7043 b
7044 c
7045 (a -> b)[0]: null
7046 }
7047 }
7048 `,
7049 },
7050 }
7051
7052 for _, tc := range testCases {
7053 tc := tc
7054 t.Run(tc.name, func(t *testing.T) {
7055 t.Parallel()
7056
7057 et := editTest{
7058 text: tc.text,
7059 fsTexts: tc.fsTexts,
7060 testFunc: func(g *d2graph.Graph) (*d2graph.Graph, error) {
7061 return d2oracle.Delete(g, tc.boardPath, tc.key)
7062 },
7063
7064 exp: tc.exp,
7065 expErr: tc.expErr,
7066 assertions: tc.assertions,
7067 }
7068 et.run(t)
7069 })
7070 }
7071 }
7072
7073 type editTest struct {
7074 text string
7075 fsTexts map[string]string
7076 testFunc func(*d2graph.Graph) (*d2graph.Graph, error)
7077
7078 exp string
7079 expErr string
7080 assertions func(*testing.T, *d2graph.Graph)
7081 }
7082
7083 func (tc editTest) run(t *testing.T) {
7084 d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
7085 tfs := testFS(make(map[string]*testF))
7086 for name, text := range tc.fsTexts {
7087 tfs[name] = &testF{content: text}
7088 }
7089 g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), &d2compiler.CompileOptions{
7090 FS: tfs,
7091 })
7092 if err != nil {
7093 t.Fatal(err)
7094 }
7095
7096 g, err = tc.testFunc(g)
7097 if tc.expErr != "" {
7098 if err == nil {
7099 t.Fatalf("expected error with: %q", tc.expErr)
7100 }
7101 ds, err := diff.Strings(tc.expErr, err.Error())
7102 if err != nil {
7103 t.Fatal(err)
7104 }
7105 if ds != "" {
7106 t.Fatalf("unexpected error: %s", ds)
7107 }
7108 } else if err != nil {
7109 t.Fatal(err)
7110 }
7111
7112 if tc.expErr == "" {
7113 if tc.assertions != nil {
7114 t.Run("assertions", func(t *testing.T) {
7115 tc.assertions(t, g)
7116 })
7117 }
7118
7119 newText := d2format.Format(g.AST)
7120 ds, err := diff.Strings(tc.exp, newText)
7121 if err != nil {
7122 t.Fatal(err)
7123 }
7124 if ds != "" {
7125 t.Fatalf("tc.exp != newText:\n%s", ds)
7126 }
7127 }
7128
7129 got := struct {
7130 Graph *d2graph.Graph `json:"graph"`
7131 Err string `json:"err"`
7132 }{
7133 Graph: g,
7134 Err: fmt.Sprintf("%#v", err),
7135 }
7136
7137 err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2oracle", t.Name()), got)
7138 assert.Success(t, err)
7139 }
7140
7141 func TestReconnectEdgeIDDeltas(t *testing.T) {
7142 t.Parallel()
7143
7144 testCases := []struct {
7145 name string
7146
7147 boardPath []string
7148 text string
7149 edge string
7150 newSrc string
7151 newDst string
7152
7153 exp string
7154 expErr string
7155 }{
7156 {
7157 name: "basic",
7158
7159 text: `a -> b
7160 x
7161 `,
7162 edge: "(a -> b)[0]",
7163 newDst: "x",
7164
7165 exp: `{
7166 "(a -> b)[0]": "(a -> x)[0]"
7167 }`,
7168 },
7169 {
7170 name: "both",
7171 text: `a
7172 b
7173 c
7174 a -> b
7175 `,
7176 edge: `(a -> b)[0]`,
7177 newSrc: "b",
7178 newDst: "a",
7179 exp: `{
7180 "(a -> b)[0]": "(b -> a)[0]"
7181 }`,
7182 },
7183 {
7184 name: "contained",
7185
7186 text: `a.x -> a.y
7187 a.z
7188 `,
7189 edge: "a.(x -> y)[0]",
7190 newDst: "a.z",
7191
7192 exp: `{
7193 "a.(x -> y)[0]": "a.(x -> z)[0]"
7194 }`,
7195 },
7196 {
7197 name: "second_index",
7198
7199 text: `a -> b
7200 a -> b
7201 c
7202 `,
7203 edge: "(a -> b)[1]",
7204 newDst: "c",
7205
7206 exp: `{
7207 "(a -> b)[1]": "(a -> c)[0]"
7208 }`,
7209 },
7210 {
7211 name: "old_sibling_decrement",
7212
7213 text: `a -> b
7214 a -> b
7215 c
7216 `,
7217 edge: "(a -> b)[0]",
7218 newDst: "c",
7219
7220 exp: `{
7221 "(a -> b)[0]": "(a -> c)[0]",
7222 "(a -> b)[1]": "(a -> b)[0]"
7223 }`,
7224 },
7225 {
7226 name: "new_sibling_increment",
7227
7228 text: `a -> b
7229 c -> b
7230 a -> b
7231 `,
7232 edge: "(c -> b)[0]",
7233 newSrc: "a",
7234
7235 exp: `{
7236 "(a -> b)[1]": "(a -> b)[2]",
7237 "(c -> b)[0]": "(a -> b)[1]"
7238 }`,
7239 },
7240 {
7241 name: "increment_and_decrement",
7242
7243 text: `a -> b
7244 c -> b
7245 c -> b
7246 a -> b
7247 `,
7248 edge: "(c -> b)[0]",
7249 newSrc: "a",
7250
7251 exp: `{
7252 "(a -> b)[1]": "(a -> b)[2]",
7253 "(c -> b)[0]": "(a -> b)[1]",
7254 "(c -> b)[1]": "(c -> b)[0]"
7255 }`,
7256 },
7257 {
7258 name: "in_chain",
7259
7260 text: `a -> b -> a -> b
7261 c
7262 `,
7263 edge: "(a -> b)[0]",
7264 newDst: "c",
7265
7266 exp: `{
7267 "(a -> b)[0]": "(a -> c)[0]",
7268 "(a -> b)[1]": "(a -> b)[0]"
7269 }`,
7270 },
7271 {
7272 name: "in_chain_2",
7273
7274 text: `a -> b -> a -> b
7275 c
7276 `,
7277 edge: "(a -> b)[1]",
7278 newDst: "c",
7279
7280 exp: `{
7281 "(a -> b)[1]": "(a -> c)[0]"
7282 }`,
7283 },
7284 {
7285 name: "in_chain_3",
7286
7287 text: `a -> b -> a -> c
7288 `,
7289 edge: "(a -> b)[0]",
7290 newDst: "c",
7291
7292 exp: `{
7293 "(a -> b)[0]": "(a -> c)[1]"
7294 }`,
7295 },
7296 {
7297 name: "in_chain_4",
7298
7299 text: `a -> c -> a -> c
7300 b
7301 `,
7302 edge: "(a -> c)[0]",
7303 newDst: "b",
7304
7305 exp: `{
7306 "(a -> c)[0]": "(a -> b)[0]",
7307 "(a -> c)[1]": "(a -> c)[0]"
7308 }`,
7309 },
7310 {
7311 name: "scenarios-outer-scope",
7312 text: `a
7313
7314 scenarios: {
7315 x: {
7316 d -> b
7317 }
7318 }
7319 `,
7320 boardPath: []string{"x"},
7321 edge: `(d -> b)[0]`,
7322 newDst: "a",
7323 exp: `{
7324 "(d -> b)[0]": "(d -> a)[0]"
7325 }`,
7326 },
7327 {
7328 name: "scenarios-second",
7329 text: `g
7330 a -> b
7331 d
7332
7333 scenarios: {
7334 x: {
7335 d -> b
7336 }
7337 }
7338 `,
7339 boardPath: []string{"x"},
7340 edge: `(d -> b)[0]`,
7341 newSrc: "a",
7342 exp: `{
7343 "(d -> b)[0]": "(a -> b)[1]"
7344 }`,
7345 },
7346 }
7347
7348 for _, tc := range testCases {
7349 tc := tc
7350 t.Run(tc.name, func(t *testing.T) {
7351 t.Parallel()
7352
7353 d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
7354 g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
7355 if err != nil {
7356 t.Fatal(err)
7357 }
7358
7359 var newSrc *string
7360 var newDst *string
7361 if tc.newSrc != "" {
7362 newSrc = &tc.newSrc
7363 }
7364 if tc.newDst != "" {
7365 newDst = &tc.newDst
7366 }
7367
7368 deltas, err := d2oracle.ReconnectEdgeIDDeltas(g, tc.boardPath, tc.edge, newSrc, newDst)
7369 if tc.expErr != "" {
7370 if err == nil {
7371 t.Fatalf("expected error with: %q", tc.expErr)
7372 }
7373 ds, err := diff.Strings(tc.expErr, err.Error())
7374 if err != nil {
7375 t.Fatal(err)
7376 }
7377 if ds != "" {
7378 t.Fatalf("unexpected error: %s", ds)
7379 }
7380 } else if err != nil {
7381 t.Fatal(err)
7382 }
7383
7384 if hasRepeatedValue(deltas) {
7385 t.Fatalf("deltas set more than one value equal to another: %s", string(xjson.Marshal(deltas)))
7386 }
7387
7388 ds, err := diff.Strings(tc.exp, string(xjson.Marshal(deltas)))
7389 if err != nil {
7390 t.Fatal(err)
7391 }
7392 if ds != "" {
7393 t.Fatalf("unexpected deltas: %s", ds)
7394 }
7395 })
7396 }
7397 }
7398
7399 func TestMoveIDDeltas(t *testing.T) {
7400 t.Parallel()
7401
7402 testCases := []struct {
7403 name string
7404
7405 text string
7406 key string
7407 newKey string
7408 includeDescendants bool
7409
7410 exp string
7411 expErr string
7412 }{
7413 {
7414 name: "rename",
7415
7416 text: `x
7417 `,
7418 key: "x",
7419 newKey: "y",
7420
7421 exp: `{
7422 "x": "y"
7423 }`,
7424 },
7425 {
7426 name: "rename_identical",
7427
7428 text: `Square
7429 `,
7430 key: "Square",
7431 newKey: "Square",
7432
7433 exp: `{}`,
7434 },
7435 {
7436 name: "children_no_self_conflict",
7437
7438 text: `x: {
7439 x
7440 }
7441 y
7442 `,
7443 key: `x`,
7444 newKey: `y.x`,
7445
7446 exp: `{
7447 "x": "y.x",
7448 "x.x": "x"
7449 }`,
7450 },
7451 {
7452 name: "into_container",
7453
7454 text: `x
7455 y
7456 x -> z
7457 `,
7458 key: "x",
7459 newKey: "y.x",
7460
7461 exp: `{
7462 "(x -> z)[0]": "(y.x -> z)[0]",
7463 "x": "y.x"
7464 }`,
7465 },
7466 {
7467 name: "out_container",
7468
7469 text: `x: {
7470 y
7471 }
7472 x.y -> z
7473 `,
7474 key: "x.y",
7475 newKey: "y",
7476
7477 exp: `{
7478 "(x.y -> z)[0]": "(y -> z)[0]",
7479 "x.y": "y"
7480 }`,
7481 },
7482 {
7483 name: "container_with_edge",
7484
7485 text: `x {
7486 a
7487 b
7488 a -> b
7489 }
7490 y
7491 `,
7492 key: "x",
7493 newKey: "y.x",
7494
7495 exp: `{
7496 "x": "y.x",
7497 "x.(a -> b)[0]": "(a -> b)[0]",
7498 "x.a": "a",
7499 "x.b": "b"
7500 }`,
7501 },
7502 {
7503 name: "out_conflict",
7504
7505 text: `x: {
7506 y
7507 }
7508 y
7509 x.y -> z
7510 `,
7511 key: "x.y",
7512 newKey: "y",
7513
7514 exp: `{
7515 "(x.y -> z)[0]": "(y 2 -> z)[0]",
7516 "x.y": "y 2"
7517 }`,
7518 },
7519 {
7520 name: "into_conflict",
7521
7522 text: `x: {
7523 y
7524 }
7525 y
7526 x.y -> z
7527 `,
7528 key: "y",
7529 newKey: "x.y",
7530
7531 exp: `{
7532 "y": "x.y 2"
7533 }`,
7534 },
7535 {
7536 name: "move_container",
7537
7538 text: `x: {
7539 a
7540 b
7541 }
7542 y
7543 x.a -> x.b
7544 x.a -> x.b
7545 `,
7546 key: "x",
7547 newKey: "y.x",
7548
7549 exp: `{
7550 "x": "y.x",
7551 "x.(a -> b)[0]": "(a -> b)[0]",
7552 "x.(a -> b)[1]": "(a -> b)[1]",
7553 "x.a": "a",
7554 "x.b": "b"
7555 }`,
7556 },
7557 {
7558 name: "conflicts",
7559
7560 text: `x: {
7561 a
7562 b
7563 }
7564 a
7565 y
7566 x.a -> x.b
7567 `,
7568 key: "x",
7569 newKey: "y.x",
7570
7571 exp: `{
7572 "x": "y.x",
7573 "x.(a -> b)[0]": "(a 2 -> b)[0]",
7574 "x.a": "a 2",
7575 "x.b": "b"
7576 }`,
7577 },
7578 {
7579 name: "container_conflicts_generated",
7580 text: `Square 2: "" {
7581 Square: ""
7582 }
7583 Square: ""
7584 Square 3
7585 `,
7586 key: `Square 2`,
7587 newKey: `Square 3.Square 2`,
7588
7589 exp: `{
7590 "Square 2": "Square 3.Square 2",
7591 "Square 2.Square": "Square 2"
7592 }`,
7593 },
7594 {
7595 name: "duplicate_generated",
7596
7597 text: `x
7598 x 2
7599 x 3: {
7600 x 3
7601 x 4
7602 }
7603 x 4
7604 y
7605 `,
7606 key: `x 3`,
7607 newKey: `y.x 3`,
7608
7609 exp: `{
7610 "x 3": "y.x 3",
7611 "x 3.x 3": "x 3",
7612 "x 3.x 4": "x 5"
7613 }`,
7614 },
7615 {
7616 name: "include_descendants_flat",
7617
7618 text: `x.y
7619 z
7620 `,
7621 key: `x`,
7622 newKey: `z.x`,
7623 includeDescendants: true,
7624
7625 exp: `{
7626 "x": "z.x",
7627 "x.y": "z.x.y"
7628 }`,
7629 },
7630 {
7631 name: "include_descendants_map",
7632
7633 text: `x: {
7634 y
7635 }
7636 z
7637 `,
7638 key: `x`,
7639 newKey: `z.x`,
7640 includeDescendants: true,
7641
7642 exp: `{
7643 "x": "z.x",
7644 "x.y": "z.x.y"
7645 }`,
7646 },
7647 {
7648 name: "include_descendants_conflict",
7649
7650 text: `x.y
7651 z.x
7652 `,
7653 key: `x`,
7654 newKey: `z.x`,
7655 includeDescendants: true,
7656
7657 exp: `{
7658 "x": "z.x 2",
7659 "x.y": "z.x 2.y"
7660 }`,
7661 },
7662 {
7663 name: "include_descendants_non_conflict",
7664
7665 text: `x.y
7666 z.x
7667 y
7668 `,
7669 key: `x`,
7670 newKey: `z.x`,
7671 includeDescendants: true,
7672
7673 exp: `{
7674 "x": "z.x 2",
7675 "x.y": "z.x 2.y"
7676 }`,
7677 },
7678 {
7679 name: "include_descendants_edge_ref",
7680 text: `x -> y.z
7681 `,
7682 key: `y.z`,
7683 newKey: `z`,
7684 includeDescendants: true,
7685
7686 exp: `{
7687 "(x -> y.z)[0]": "(x -> z)[0]",
7688 "y.z": "z"
7689 }`,
7690 },
7691 {
7692 name: "include_descendants_edge_ref_2",
7693 text: `x -> y.z
7694 `,
7695 key: `y.z`,
7696 newKey: `z`,
7697 includeDescendants: true,
7698
7699 exp: `{
7700 "(x -> y.z)[0]": "(x -> z)[0]",
7701 "y.z": "z"
7702 }`,
7703 },
7704 {
7705 name: "include_descendants_edge_ref_3",
7706 text: `x -> y.z.a
7707 `,
7708 key: `y.z`,
7709 newKey: `z`,
7710 includeDescendants: true,
7711
7712 exp: `{
7713 "(x -> y.z.a)[0]": "(x -> z.a)[0]",
7714 "y.z": "z",
7715 "y.z.a": "z.a"
7716 }`,
7717 },
7718 {
7719 name: "include_descendants_edge_ref_4",
7720 text: `x -> y.z.a
7721 b
7722 `,
7723 key: `y.z`,
7724 newKey: `b.z`,
7725 includeDescendants: true,
7726
7727 exp: `{
7728 "(x -> y.z.a)[0]": "(x -> b.z.a)[0]",
7729 "y.z": "b.z",
7730 "y.z.a": "b.z.a"
7731 }`,
7732 },
7733 {
7734 name: "include_descendants_underscore_2",
7735 text: `a: {
7736 b: {
7737 _.c
7738 }
7739 }
7740 `,
7741 key: `a.b`,
7742 newKey: `b`,
7743 includeDescendants: true,
7744
7745 exp: `{
7746 "a.b": "b"
7747 }`,
7748 },
7749 {
7750 name: "include_descendants_underscore_3",
7751 text: `a: {
7752 b: {
7753 _.c -> d
7754 _.c -> _.d
7755 }
7756 }
7757 `,
7758 key: `a.b`,
7759 newKey: `b`,
7760 includeDescendants: true,
7761
7762 exp: `{
7763 "a.(c -> b.d)[0]": "(a.c -> b.d)[0]",
7764 "a.b": "b",
7765 "a.b.d": "b.d"
7766 }`,
7767 },
7768 {
7769 name: "include_descendants_edge_ref_underscore",
7770 text: `x
7771 z
7772 x.a -> x.b
7773 b: {
7774 _.x.a -> _.x.b
7775 }
7776 `,
7777 key: `x`,
7778 newKey: `z.x`,
7779 includeDescendants: true,
7780
7781 exp: `{
7782 "x": "z.x",
7783 "x.(a -> b)[0]": "z.x.(a -> b)[0]",
7784 "x.(a -> b)[1]": "z.x.(a -> b)[1]",
7785 "x.a": "z.x.a",
7786 "x.b": "z.x.b"
7787 }`,
7788 },
7789 {
7790 name: "include_descendants_sql_table",
7791
7792 text: `x: {
7793 shape: sql_table
7794 a: b
7795 }
7796 z
7797 `,
7798 key: `x`,
7799 newKey: `z.x`,
7800 includeDescendants: true,
7801
7802 exp: `{
7803 "x": "z.x"
7804 }`,
7805 },
7806 }
7807
7808 for _, tc := range testCases {
7809 tc := tc
7810 t.Run(tc.name, func(t *testing.T) {
7811 t.Parallel()
7812
7813 d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
7814 g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
7815 if err != nil {
7816 t.Fatal(err)
7817 }
7818
7819 deltas, err := d2oracle.MoveIDDeltas(g, tc.key, tc.newKey, tc.includeDescendants)
7820 if tc.expErr != "" {
7821 if err == nil {
7822 t.Fatalf("expected error with: %q", tc.expErr)
7823 }
7824 ds, err := diff.Strings(tc.expErr, err.Error())
7825 if err != nil {
7826 t.Fatal(err)
7827 }
7828 if ds != "" {
7829 t.Fatalf("unexpected error: %s", ds)
7830 }
7831 } else if err != nil {
7832 t.Fatal(err)
7833 }
7834
7835 if hasRepeatedValue(deltas) {
7836 t.Fatalf("deltas set more than one value equal to another: %s", string(xjson.Marshal(deltas)))
7837 }
7838
7839 ds, err := diff.Strings(tc.exp, string(xjson.Marshal(deltas)))
7840 if err != nil {
7841 t.Fatal(err)
7842 }
7843 if ds != "" {
7844 t.Fatalf("unexpected deltas: %s", ds)
7845 }
7846 })
7847 }
7848 }
7849
7850 func TestDeleteIDDeltas(t *testing.T) {
7851 t.Parallel()
7852
7853 testCases := []struct {
7854 name string
7855
7856 boardPath []string
7857 text string
7858 key string
7859
7860 exp string
7861 expErr string
7862 }{
7863 {
7864 name: "delete_node",
7865
7866 text: `x.y.p -> x.y.q
7867 x.y.z.w.e.p.l
7868 x.y.z.1.2.3.4
7869 x.y.3.4.5.6
7870 x.y.3.4.6.7
7871 x.y.3.4.6.7 -> x.y.3.4.5.6
7872 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
7873 `,
7874 key: "x.y",
7875
7876 exp: `{
7877 "x.y.(p -> q)[0]": "x.(p -> q)[0]",
7878 "x.y.3": "x.3",
7879 "x.y.3.4": "x.3.4",
7880 "x.y.3.4.(6.7 -> 5.6)[0]": "x.3.4.(6.7 -> 5.6)[0]",
7881 "x.y.3.4.5": "x.3.4.5",
7882 "x.y.3.4.5.6": "x.3.4.5.6",
7883 "x.y.3.4.6": "x.3.4.6",
7884 "x.y.3.4.6.7": "x.3.4.6.7",
7885 "x.y.p": "x.p",
7886 "x.y.q": "x.q",
7887 "x.y.z": "x.z",
7888 "x.y.z.(w.e.p.l -> 1.2.3.4)[0]": "x.z.(w.e.p.l -> 1.2.3.4)[0]",
7889 "x.y.z.1": "x.z.1",
7890 "x.y.z.1.2": "x.z.1.2",
7891 "x.y.z.1.2.3": "x.z.1.2.3",
7892 "x.y.z.1.2.3.4": "x.z.1.2.3.4",
7893 "x.y.z.w": "x.z.w",
7894 "x.y.z.w.e": "x.z.w.e",
7895 "x.y.z.w.e.p": "x.z.w.e.p",
7896 "x.y.z.w.e.p.l": "x.z.w.e.p.l"
7897 }`,
7898 },
7899 {
7900 name: "children_no_self_conflict",
7901
7902 text: `x: {
7903 x
7904 }
7905 `,
7906 key: `x`,
7907
7908 exp: `{
7909 "x.x": "x"
7910 }`,
7911 },
7912 {
7913 name: "duplicate_generated",
7914
7915 text: `x
7916 x 2
7917 x 3: {
7918 x 3
7919 x 4
7920 }
7921 x 4
7922 y
7923 `,
7924 key: `x 3`,
7925 exp: `{
7926 "x 3.x 3": "x 3",
7927 "x 3.x 4": "x 5"
7928 }`,
7929 },
7930 {
7931 name: "nested-height",
7932
7933 text: `x: {
7934 a -> b
7935 height: 200
7936 }
7937 `,
7938 key: `x.height`,
7939
7940 exp: `null`,
7941 },
7942 {
7943 name: "edge-style",
7944
7945 text: `x <-> y: {
7946 target-arrowhead: circle
7947 source-arrowhead: diamond
7948 }
7949 `,
7950 key: `(x <-> y)[0].target-arrowhead`,
7951
7952 exp: `null`,
7953 },
7954 {
7955 name: "only-reserved",
7956 text: `guitar: {
7957 books: {
7958 _._.pipe: {
7959 a
7960 }
7961 }
7962 }
7963 `,
7964 key: `pipe`,
7965
7966 exp: `{
7967 "pipe.a": "a"
7968 }`,
7969 },
7970 {
7971 name: "delete_container_with_conflicts",
7972
7973 text: `x {
7974 a
7975 b
7976 }
7977 a
7978 b
7979 c
7980 x.a -> c
7981 `,
7982 key: "x",
7983
7984 exp: `{
7985 "(x.a -> c)[0]": "(a 2 -> c)[0]",
7986 "x.a": "a 2",
7987 "x.b": "b 2"
7988 }`,
7989 },
7990 {
7991 name: "multiword",
7992
7993 text: `Starfish: {
7994 API
7995 }
7996 Starfish.API
7997 `,
7998 key: "Starfish",
7999
8000 exp: `{
8001 "Starfish.API": "API"
8002 }`,
8003 },
8004 {
8005 name: "delete_container_with_edge",
8006
8007 text: `x {
8008 a
8009 b
8010 a -> b
8011 }
8012 `,
8013 key: "x",
8014
8015 exp: `{
8016 "x.(a -> b)[0]": "(a -> b)[0]",
8017 "x.a": "a",
8018 "x.b": "b"
8019 }`,
8020 },
8021 {
8022 name: "delete_edge_field",
8023
8024 text: `a -> b
8025 a -> b
8026 `,
8027 key: "(a -> b)[0].style.opacity",
8028
8029 exp: "null",
8030 },
8031 {
8032 name: "delete_edge",
8033
8034 text: `x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8035 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8036 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8037 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8038 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8039 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8040 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8041 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[0]: meow
8042 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[1]: meow
8043 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[2]: meow
8044 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[3]: meow
8045 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[4]: meow
8046 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[5]: meow
8047 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[6]: meow
8048 `,
8049 key: "(x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[1]",
8050
8051 exp: `{
8052 "x.y.z.(w.e.p.l -> 1.2.3.4)[2]": "x.y.z.(w.e.p.l -> 1.2.3.4)[1]",
8053 "x.y.z.(w.e.p.l -> 1.2.3.4)[3]": "x.y.z.(w.e.p.l -> 1.2.3.4)[2]",
8054 "x.y.z.(w.e.p.l -> 1.2.3.4)[4]": "x.y.z.(w.e.p.l -> 1.2.3.4)[3]",
8055 "x.y.z.(w.e.p.l -> 1.2.3.4)[5]": "x.y.z.(w.e.p.l -> 1.2.3.4)[4]",
8056 "x.y.z.(w.e.p.l -> 1.2.3.4)[6]": "x.y.z.(w.e.p.l -> 1.2.3.4)[5]"
8057 }`,
8058 },
8059 {
8060 name: "delete_generated_id_conflicts",
8061
8062 text: `Text 2: {
8063 Text
8064 Text 3
8065 }
8066 Text
8067 `,
8068 key: "Text 2",
8069
8070 exp: `{
8071 "Text 2.Text": "Text 2",
8072 "Text 2.Text 3": "Text 3"
8073 }`,
8074 },
8075 {
8076 name: "delete_generated_id_conflicts_2",
8077
8078 text: `Text 4
8079 Square: {
8080 Text 4: {
8081 Text 2
8082 }
8083 Text
8084 }
8085 `,
8086 key: "Square",
8087
8088 exp: `{
8089 "Square.Text": "Text",
8090 "Square.Text 4": "Text 2",
8091 "Square.Text 4.Text 2": "Text 2.Text 2"
8092 }`,
8093 },
8094 {
8095 name: "delete_generated_id_conflicts_2_continued",
8096
8097 text: `Text 4
8098
8099 Text: {
8100 Text 2
8101 }
8102 Text 2
8103 `,
8104 key: "Text",
8105
8106 exp: `{
8107 "Text.Text 2": "Text"
8108 }`,
8109 },
8110 {
8111 name: "conflicts_generated_3",
8112 text: `x: {
8113 Square 2
8114 Square 3
8115 }
8116
8117 Square 2
8118 Square
8119 `,
8120 key: `x`,
8121
8122 exp: `{
8123 "x.Square 2": "Square 4",
8124 "x.Square 3": "Square 3"
8125 }`,
8126 },
8127 {
8128 name: "scenarios-basic",
8129 text: `x
8130
8131 scenarios: {
8132 y: {
8133 a
8134 }
8135 }
8136 `,
8137 boardPath: []string{"y"},
8138 key: `a`,
8139
8140 exp: `{}`,
8141 },
8142 {
8143 name: "scenarios-parent",
8144 text: `x
8145
8146 scenarios: {
8147 y: {
8148 a.x
8149 }
8150 }
8151 `,
8152 boardPath: []string{"y"},
8153 key: `a`,
8154
8155 exp: `{
8156 "a.x": "x 2"
8157 }`,
8158 },
8159 {
8160 name: "layers-parent",
8161 text: `x
8162
8163 layers: {
8164 y: {
8165 a.x
8166 }
8167 }
8168 `,
8169 boardPath: []string{"y"},
8170 key: `a`,
8171
8172 exp: `{
8173 "a.x": "x"
8174 }`,
8175 },
8176 }
8177
8178 for _, tc := range testCases {
8179 tc := tc
8180 t.Run(tc.name, func(t *testing.T) {
8181 t.Parallel()
8182
8183 d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
8184 g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
8185 if err != nil {
8186 t.Fatal(err)
8187 }
8188
8189 deltas, err := d2oracle.DeleteIDDeltas(g, tc.boardPath, tc.key)
8190 if tc.expErr != "" {
8191 if err == nil {
8192 t.Fatalf("expected error with: %q", tc.expErr)
8193 }
8194 ds, err := diff.Strings(tc.expErr, err.Error())
8195 if err != nil {
8196 t.Fatal(err)
8197 }
8198 if ds != "" {
8199 t.Fatalf("unexpected error: %s", ds)
8200 }
8201 } else if err != nil {
8202 t.Fatal(err)
8203 }
8204
8205 if hasRepeatedValue(deltas) {
8206 t.Fatalf("deltas set more than one value equal to another: %s", string(xjson.Marshal(deltas)))
8207 }
8208
8209 ds, err := diff.Strings(tc.exp, string(xjson.Marshal(deltas)))
8210 if err != nil {
8211 t.Fatal(err)
8212 }
8213 if ds != "" {
8214 t.Fatalf("unexpected deltas: %s", ds)
8215 }
8216 })
8217 }
8218 }
8219
8220 func hasRepeatedValue(m map[string]string) bool {
8221 seen := make(map[string]struct{}, len(m))
8222 for _, v := range m {
8223 if _, ok := seen[v]; ok {
8224 return true
8225 }
8226 seen[v] = struct{}{}
8227 }
8228 return false
8229 }
8230
8231 func TestRenameIDDeltas(t *testing.T) {
8232 t.Parallel()
8233
8234 testCases := []struct {
8235 name string
8236
8237 boardPath []string
8238 text string
8239 key string
8240 newName string
8241
8242 exp string
8243 expErr string
8244 }{
8245 {
8246 name: "rename_node",
8247
8248 text: `x.y.p -> x.y.q
8249 x.y.z.w.e.p.l
8250 x.y.z.1.2.3.4
8251 x.y.3.4.5.6
8252 x.y.3.4.6.7
8253 x.y.3.4.6.7 -> x.y.3.4.5.6
8254 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8255 `,
8256 key: "x.y",
8257 newName: "papa",
8258
8259 exp: `{
8260 "x.y": "x.papa",
8261 "x.y.(p -> q)[0]": "x.papa.(p -> q)[0]",
8262 "x.y.3": "x.papa.3",
8263 "x.y.3.4": "x.papa.3.4",
8264 "x.y.3.4.(6.7 -> 5.6)[0]": "x.papa.3.4.(6.7 -> 5.6)[0]",
8265 "x.y.3.4.5": "x.papa.3.4.5",
8266 "x.y.3.4.5.6": "x.papa.3.4.5.6",
8267 "x.y.3.4.6": "x.papa.3.4.6",
8268 "x.y.3.4.6.7": "x.papa.3.4.6.7",
8269 "x.y.p": "x.papa.p",
8270 "x.y.q": "x.papa.q",
8271 "x.y.z": "x.papa.z",
8272 "x.y.z.(w.e.p.l -> 1.2.3.4)[0]": "x.papa.z.(w.e.p.l -> 1.2.3.4)[0]",
8273 "x.y.z.1": "x.papa.z.1",
8274 "x.y.z.1.2": "x.papa.z.1.2",
8275 "x.y.z.1.2.3": "x.papa.z.1.2.3",
8276 "x.y.z.1.2.3.4": "x.papa.z.1.2.3.4",
8277 "x.y.z.w": "x.papa.z.w",
8278 "x.y.z.w.e": "x.papa.z.w.e",
8279 "x.y.z.w.e.p": "x.papa.z.w.e.p",
8280 "x.y.z.w.e.p.l": "x.papa.z.w.e.p.l"
8281 }`,
8282 },
8283 {
8284 name: "rename_conflict",
8285
8286 text: `x
8287 y
8288 `,
8289 key: "x",
8290 newName: "y",
8291
8292 exp: `{
8293 "x": "y 2"
8294 }`,
8295 },
8296 {
8297 name: "generated-conflict",
8298
8299 text: `Square
8300 Square 2
8301 `,
8302 key: `Square 2`,
8303 newName: `Square`,
8304
8305 exp: `{}`,
8306 },
8307 {
8308 name: "rename_conflict_with_dots",
8309
8310 text: `"a.b"
8311 y
8312 `,
8313 key: "y",
8314 newName: "a.b",
8315
8316 exp: `{
8317 "y": "\"a.b 2\""
8318 }`,
8319 },
8320 {
8321 name: "rename_conflict_with_numbers",
8322
8323 text: `1
8324 Square
8325 `,
8326 key: `Square`,
8327 newName: `1`,
8328
8329 exp: `{
8330 "Square": "1 2"
8331 }`,
8332 },
8333 {
8334 name: "rename_identical",
8335
8336 text: `Square
8337 `,
8338 key: "Square",
8339 newName: "Square",
8340
8341 exp: `{}`,
8342 },
8343 {
8344 name: "rename_edge",
8345
8346 text: `x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8347 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8348 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8349 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8350 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8351 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8352 x.y.z.w.e.p.l -> x.y.z.1.2.3.4
8353 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[0]: meow
8354 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[1]: meow
8355 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[2]: meow
8356 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[3]: meow
8357 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[4]: meow
8358 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[5]: meow
8359 (x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[6]: meow
8360 `,
8361 key: "(x.y.z.w.e.p.l -> x.y.z.1.2.3.4)[1]",
8362 newName: "(x.y.z.w.e.p.l <-> x.y.z.1.2.3.4)[1]",
8363
8364 exp: `{
8365 "x.y.z.(w.e.p.l -> 1.2.3.4)[1]": "x.y.z.(w.e.p.l <-> 1.2.3.4)[1]"
8366 }`,
8367 },
8368 {
8369 name: "layers-basic",
8370
8371 text: `x
8372
8373 layers: {
8374 y: {
8375 a
8376 }
8377 }
8378 `,
8379 boardPath: []string{"y"},
8380 key: "a",
8381 newName: "b",
8382
8383 exp: `{
8384 "a": "b"
8385 }`,
8386 },
8387 {
8388 name: "scenarios-conflict",
8389
8390 text: `x
8391
8392 scenarios: {
8393 y: {
8394 a
8395 }
8396 }
8397 `,
8398 boardPath: []string{"y"},
8399 key: "a",
8400 newName: "x",
8401
8402 exp: `{
8403 "a": "x 2"
8404 }`,
8405 },
8406 }
8407
8408 for _, tc := range testCases {
8409 tc := tc
8410 t.Run(tc.name, func(t *testing.T) {
8411 t.Parallel()
8412
8413 d2Path := fmt.Sprintf("d2/testdata/d2oracle/%v.d2", t.Name())
8414 g, _, err := d2compiler.Compile(d2Path, strings.NewReader(tc.text), nil)
8415 if err != nil {
8416 t.Fatal(err)
8417 }
8418
8419 deltas, err := d2oracle.RenameIDDeltas(g, tc.boardPath, tc.key, tc.newName)
8420 if tc.expErr != "" {
8421 if err == nil {
8422 t.Fatalf("expected error with: %q", tc.expErr)
8423 }
8424 ds, err := diff.Strings(tc.expErr, err.Error())
8425 if err != nil {
8426 t.Fatal(err)
8427 }
8428 if ds != "" {
8429 t.Fatalf("unexpected error: %s", ds)
8430 }
8431 } else if err != nil {
8432 t.Fatal(err)
8433 }
8434
8435 if hasRepeatedValue(deltas) {
8436 t.Fatalf("deltas set more than one value equal to another: %s", string(xjson.Marshal(deltas)))
8437 }
8438
8439 ds, err := diff.Strings(tc.exp, string(xjson.Marshal(deltas)))
8440 if err != nil {
8441 t.Fatal(err)
8442 }
8443 if ds != "" {
8444 t.Fatalf("unexpected deltas: %s", ds)
8445 }
8446 })
8447 }
8448 }
8449
8450 type testF struct {
8451 content string
8452 readIndex int
8453 }
8454
8455 func (tf *testF) Close() error {
8456 return nil
8457 }
8458
8459 func (tf *testF) Read(p []byte) (int, error) {
8460 data := []byte(tf.content)
8461 if tf.readIndex >= len(data) {
8462 tf.readIndex = 0
8463 return 0, io.EOF
8464 }
8465 readBytes := copy(p, data[tf.readIndex:])
8466 tf.readIndex += readBytes
8467 return readBytes, nil
8468 }
8469
8470 func (tf *testF) Stat() (os.FileInfo, error) {
8471 return nil, nil
8472 }
8473
8474 type testFS map[string]*testF
8475
8476 func (tfs testFS) Open(name string) (fs.File, error) {
8477 for k := range tfs {
8478 if strings.HasSuffix(name[:len(name)-3], k) {
8479 return tfs[k], nil
8480 }
8481 }
8482 return nil, fs.ErrNotExist
8483 }
8484
View as plain text