...

Source file src/oss.terrastruct.com/d2/d2oracle/edit_test.go

Documentation: oss.terrastruct.com/d2/d2oracle

     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  // TODO: make assertions less specific
    26  // TODO: move n objects and n edges assertions as fields on test instead of as callback
    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  		// TODO: pass
  1666  		/*
  1667  			{
  1668  				name: "oneline_constraint",
  1669  
  1670  				text: `My Table: {
  1671  					shape: sql_table
  1672  					column: int
  1673  				}
  1674  				`,
  1675  				key:   `My Table.column.constraint`,
  1676  				value: utils.Pointer("PK"),
  1677  
  1678  				exp: `My Table: {
  1679  					shape: sql_table
  1680  					column: int {constraint: PK}
  1681  				}
  1682  				`,
  1683  			},
  1684  		*/
  1685  		// TODO: pass
  1686  		/*
  1687  					{
  1688  						name: "oneline_style",
  1689  
  1690  						text: `foo: bar
  1691  			`,
  1692  						key:   `foo.style_fill`,
  1693  						value: utils.Pointer("red"),
  1694  
  1695  						exp: `foo: bar {style_fill: red}
  1696  			`,
  1697  					},
  1698  		*/
  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  		// These _3 and _4 match the delta tests
  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  		/* TODO: handle edge keys
  2746  				{
  2747  					name: "complex_edge_3",
  2748  
  2749  					text: `a.b.(x -> y).q.z
  2750  		`,
  2751  					key:     "a.b.(x -> y)[0].q",
  2752  					newName: "zoink",
  2753  
  2754  					exp: `a.b.(x -> y).zoink.z
  2755  		`,
  2756  					assertions: func(t *testing.T, g *d2graph.Graph) {
  2757  						if len(g.Objects) != 4 {
  2758  							t.Fatalf("expected 4 objects: %#v", g.Objects)
  2759  						}
  2760  						if len(g.Edges) != 1 {
  2761  							t.Fatalf("expected 1 edge: %#v", g.Edges)
  2762  						}
  2763  					},
  2764  				},
  2765  		*/
  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  			// a.b does not move from its scope, just extends path
  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  			// We open up only the most nested
  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  			// TODO
  3722  			skip: true,
  3723  			// Choose to move to a reference that is less nested but has an existing map
  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  			// TODO the heuristic for splitting open new maps should be only if the key has no existing maps and it also has either zero or one children. if it has two children or more then we should not be opening a map and just append the key at the most nested map.
  4490  			//       first loop over explicit references from first to last.
  4491  			//
  4492  			// explicit ref means its the leaf disregarding reserved fields.
  4493  			// implicit ref means there is a shape declared after the target element.
  4494  			//
  4495  			// then loop over the implicit references and only if there is no explicit ref do you need to add the implicit ref to the scope but only if appended == false (which would be set when looping through explicit refs).
  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  			// Just checks that removing an object removes the arrowhead field too
  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  		// TODO: delete disks.id as it's redundant
  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