...

Source file src/oss.terrastruct.com/d2/d2format/format_test.go

Documentation: oss.terrastruct.com/d2/d2format

     1  package d2format_test
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	"oss.terrastruct.com/util-go/assert"
     9  
    10  	"oss.terrastruct.com/d2/d2format"
    11  	"oss.terrastruct.com/d2/d2parser"
    12  )
    13  
    14  func TestPrint(t *testing.T) {
    15  	t.Parallel()
    16  
    17  	testCases := []struct {
    18  		name string
    19  		in   string
    20  		exp  string
    21  	}{
    22  		{
    23  			name: "basic",
    24  			in: `
    25  x  ->  y
    26  `,
    27  			exp: `x -> y
    28  `,
    29  		},
    30  
    31  		{
    32  			name: "complex",
    33  			in: `
    34  sql_example   :   sql_example   {
    35  board  : {
    36  shape:   sql_table
    37  id: int {constraint: primary_key}
    38  frame: int {constraint: foreign_key}
    39  diagram: int {constraint: foreign_key}
    40  board_objects:   jsonb
    41  last_updated:  		timestamp with time zone
    42  last_thumbgen: timestamp with time zone
    43  dsl				: text
    44    }
    45  
    46    # Normal.
    47    board.diagram -> diagrams.id
    48  
    49    # Self referential.
    50    diagrams.id   -> diagrams.representation
    51  
    52    # SrcArrow test.
    53    diagrams.id <-   views .  diagram
    54    diagrams.id <-> steps . diagram
    55  
    56    diagrams: {
    57      shape: sql_table
    58      id: {type: int  ; constraint: primary_key}
    59      representation: {type: jsonb}
    60    }
    61  
    62    views: {
    63      shape: sql_table
    64      id: {type: int; constraint: primary_key}
    65      representation: {type: jsonb}
    66      diagram: int {constraint: foreign_key}
    67  }
    68  
    69    steps: 						{
    70  		shape: sql_table
    71  id: {  type: int; constraint: primary_key  }
    72  representation: {  type: jsonb  }
    73  diagram: int {constraint: foreign_key}
    74    }
    75    meow <- diagrams.id
    76  }
    77  
    78  D2 AST Parser {
    79    shape: class
    80  
    81       +prevRune  : rune
    82    prevColumn  : int
    83  
    84    +eatSpace(eatNewlines bool): (rune, error)
    85      unreadRune()
    86  
    87    		\#scanKey(r rune): (k Key, _ error)
    88  }
    89  
    90  """dmaskkldsamkld """
    91  
    92  
    93  """
    94  
    95  dmaskdmasl
    96  mdlkasdaskml
    97  daklsmdakms
    98  
    99  """
   100  
   101  bs: |
   102  dmasmdkals
   103  dkmsamdklsa
   104  |
   105  bs2: | mdsalldkams|
   106  
   107  y-->q: meow
   108  x->y->z
   109  
   110  meow: {
   111  x: |` + "`" + `
   112  meow
   113  meow
   114  ` + "`" + `| {
   115  }
   116  }
   117  
   118  
   119  "meow\t": ok
   120  `,
   121  			exp: `sql_example: sql_example {
   122    board: {
   123      shape: sql_table
   124      id: int {constraint: primary_key}
   125      frame: int {constraint: foreign_key}
   126      diagram: int {constraint: foreign_key}
   127      board_objects: jsonb
   128      last_updated: timestamp with time zone
   129      last_thumbgen: timestamp with time zone
   130      dsl: text
   131    }
   132  
   133    # Normal.
   134    board.diagram -> diagrams.id
   135  
   136    # Self referential.
   137    diagrams.id -> diagrams.representation
   138  
   139    # SrcArrow test.
   140    diagrams.id <- views.diagram
   141    diagrams.id <-> steps.diagram
   142  
   143    diagrams: {
   144      shape: sql_table
   145      id: {type: int; constraint: primary_key}
   146      representation: {type: jsonb}
   147    }
   148  
   149    views: {
   150      shape: sql_table
   151      id: {type: int; constraint: primary_key}
   152      representation: {type: jsonb}
   153      diagram: int {constraint: foreign_key}
   154    }
   155    meow <- diagrams.id
   156    
   157    steps: {
   158      shape: sql_table
   159      id: {type: int; constraint: primary_key}
   160      representation: {type: jsonb}
   161      diagram: int {constraint: foreign_key}
   162    }
   163  }
   164  
   165  D2 AST Parser: {
   166    shape: class
   167  
   168    +prevRune: rune
   169    prevColumn: int
   170  
   171    +eatSpace(eatNewlines bool): (rune, error)
   172    unreadRune()
   173  
   174    \#scanKey(r rune): (k Key, _ error)
   175  }
   176  
   177  """ dmaskkldsamkld """
   178  
   179  """
   180  
   181  dmaskdmasl
   182  mdlkasdaskml
   183  daklsmdakms
   184  
   185  """
   186  
   187  bs: |md
   188    dmasmdkals
   189    dkmsamdklsa
   190  |
   191  bs2: |md mdsalldkams |
   192  
   193  y -> q: meow
   194  x -> y -> z
   195  
   196  meow: {
   197    x: |` + "`" + `md
   198      meow
   199      meow
   200    ` + "`" + `|
   201  }
   202  
   203  "meow\t": ok
   204  `,
   205  		},
   206  
   207  		{
   208  			name: "block_comment",
   209  			in: `
   210  """
   211  D2 AST Parser2: {
   212    shape: class
   213  
   214    reader: io.RuneReader
   215    readerPos: d2ast.Position
   216  
   217    lookahead: "[]rune"
   218    lookaheadPos: d2ast.Position
   219  
   220    peek() (r rune, eof bool)
   221    -rewind(): ()
   222    +commit()
   223    \#peekn(n int) (s string, eof bool)
   224  }
   225  """
   226  `,
   227  			exp: `"""
   228  D2 AST Parser2: {
   229    shape: class
   230  
   231    reader: io.RuneReader
   232    readerPos: d2ast.Position
   233  
   234    lookahead: "[]rune"
   235    lookaheadPos: d2ast.Position
   236  
   237    peek() (r rune, eof bool)
   238    -rewind(): ()
   239    +commit()
   240    \#peekn(n int) (s string, eof bool)
   241  }
   242  """
   243  `,
   244  		},
   245  
   246  		{
   247  			name: "block_string_indent",
   248  			in: `
   249  parent: {
   250  example_code: |` + "`" + `go
   251  package fs
   252  
   253  type FS interface {
   254  	Open(name string) (File, error)
   255  }
   256  
   257  type File interface {
   258  	Stat() (FileInfo, error)
   259  	Read([]byte) (int, error)
   260  	Close() error
   261  }
   262  
   263  var (
   264  	ErrInvalid    = errInvalid()    // "invalid argument"
   265  	ErrPermission = errPermission() // "permission denied"
   266  	ErrExist      = errExist()      // "file already exists"
   267  	ErrNotExist   = errNotExist()   // "file does not exist"
   268  	ErrClosed     = errClosed()     // "file already closed"
   269  )
   270  ` + "`" + `|}`,
   271  			exp: `parent: {
   272    example_code: |` + "`" + `go
   273      package fs
   274  
   275      type FS interface {
   276      	Open(name string) (File, error)
   277      }
   278  
   279      type File interface {
   280      	Stat() (FileInfo, error)
   281      	Read([]byte) (int, error)
   282      	Close() error
   283      }
   284  
   285      var (
   286      	ErrInvalid    = errInvalid()    // "invalid argument"
   287      	ErrPermission = errPermission() // "permission denied"
   288      	ErrExist      = errExist()      // "file already exists"
   289      	ErrNotExist   = errNotExist()   // "file does not exist"
   290      	ErrClosed     = errClosed()     // "file already closed"
   291      )
   292    ` + "`" + `|
   293  }
   294  `,
   295  		},
   296  
   297  		{
   298  			// This one we test that the common indent is stripped before the correct indent is
   299  			// applied.
   300  			name: "block_string_indent_2",
   301  			in: `
   302  parent: {
   303  example_code: |` + "`" + `go
   304  	package fs
   305  
   306  	type FS interface {
   307  		Open(name string) (File, error)
   308  	}
   309  
   310  	type File interface {
   311  		Stat() (FileInfo, error)
   312  		Read([]byte) (int, error)
   313  		Close() error
   314  	}
   315  
   316  	var (
   317  		ErrInvalid    = errInvalid()    // "invalid argument"
   318  		ErrPermission = errPermission() // "permission denied"
   319  		ErrExist      = errExist()      // "file already exists"
   320  		ErrNotExist   = errNotExist()   // "file does not exist"
   321  		ErrClosed     = errClosed()     // "file already closed"
   322  	)
   323  ` + "`" + `|}`,
   324  			exp: `parent: {
   325    example_code: |` + "`" + `go
   326      package fs
   327  
   328      type FS interface {
   329      	Open(name string) (File, error)
   330      }
   331  
   332      type File interface {
   333      	Stat() (FileInfo, error)
   334      	Read([]byte) (int, error)
   335      	Close() error
   336      }
   337  
   338      var (
   339      	ErrInvalid    = errInvalid()    // "invalid argument"
   340      	ErrPermission = errPermission() // "permission denied"
   341      	ErrExist      = errExist()      // "file already exists"
   342      	ErrNotExist   = errNotExist()   // "file does not exist"
   343      	ErrClosed     = errClosed()     // "file already closed"
   344      )
   345    ` + "`" + `|
   346  }
   347  `,
   348  		},
   349  
   350  		{
   351  			// This one we test that the common indent is stripped before the correct indent is
   352  			// applied even when there's too much indent.
   353  			name: "block_string_indent_3",
   354  			in: `
   355  																		parent: {
   356  																		example_code: |` + "`" + `go
   357  																			package fs
   358  
   359  																			type FS interface {
   360  																				Open(name string) (File, error)
   361  																			}
   362  
   363  																			type File interface {
   364  																				Stat() (FileInfo, error)
   365  																				Read([]byte) (int, error)
   366  																				Close() error
   367  																			}
   368  
   369  																			var (
   370  																				ErrInvalid    = errInvalid()    // "invalid argument"
   371  																				ErrPermission = errPermission() // "permission denied"
   372  																				ErrExist      = errExist()      // "file already exists"
   373  																				ErrNotExist   = errNotExist()   // "file does not exist"
   374  																				ErrClosed     = errClosed()     // "file already closed"
   375  																			)
   376  ` + "`" + `|}`,
   377  			exp: `parent: {
   378    example_code: |` + "`" + `go
   379      package fs
   380  
   381      type FS interface {
   382      	Open(name string) (File, error)
   383      }
   384  
   385      type File interface {
   386      	Stat() (FileInfo, error)
   387      	Read([]byte) (int, error)
   388      	Close() error
   389      }
   390  
   391      var (
   392      	ErrInvalid    = errInvalid()    // "invalid argument"
   393      	ErrPermission = errPermission() // "permission denied"
   394      	ErrExist      = errExist()      // "file already exists"
   395      	ErrNotExist   = errNotExist()   // "file does not exist"
   396      	ErrClosed     = errClosed()     // "file already closed"
   397      )
   398    ` + "`" + `|
   399  }
   400  `,
   401  		},
   402  
   403  		{
   404  			// This one has 3 space indent and whitespace only lines.
   405  			name: "block_string_uneven_indent",
   406  			in: `
   407  parent: {
   408     example_code: |` + "`" + `go
   409     	package fs
   410  
   411     	type FS interface {
   412     		Open(name string) (File, error)
   413     	}
   414  
   415     	type File interface {
   416     		Stat() (FileInfo, error)
   417     		Read([]byte) (int, error)
   418     		Close() error
   419     	}
   420  
   421     	var (
   422     		ErrInvalid    = errInvalid()    // "invalid argument"
   423     		ErrPermission = errPermission() // "permission denied"
   424     		ErrExist      = errExist()      // "file already exists"
   425     		ErrNotExist   = errNotExist()   // "file does not exist"
   426     		ErrClosed     = errClosed()     // "file already closed"
   427     	)
   428  ` + "`" + `|}`,
   429  			exp: `parent: {
   430    example_code: |` + "`" + `go
   431      package fs
   432  
   433      type FS interface {
   434      	Open(name string) (File, error)
   435      }
   436  
   437      type File interface {
   438      	Stat() (FileInfo, error)
   439      	Read([]byte) (int, error)
   440      	Close() error
   441      }
   442  
   443      var (
   444      	ErrInvalid    = errInvalid()    // "invalid argument"
   445      	ErrPermission = errPermission() // "permission denied"
   446      	ErrExist      = errExist()      // "file already exists"
   447      	ErrNotExist   = errNotExist()   // "file does not exist"
   448      	ErrClosed     = errClosed()     // "file already closed"
   449      )
   450    ` + "`" + `|
   451  }
   452  `,
   453  		},
   454  
   455  		{
   456  			// This one has 3 space indent and large whitespace only lines.
   457  			name: "block_string_uneven_indent_2",
   458  			in: `
   459  parent: {
   460     example_code: |` + "`" + `go
   461     	package fs
   462  
   463     	type FS interface {
   464     		Open(name string) (File, error)
   465     	}
   466  
   467  ` + "`" + `|}`,
   468  			exp: `parent: {
   469    example_code: |` + "`" + `go
   470      package fs
   471  
   472      type FS interface {
   473      	Open(name string) (File, error)
   474      }
   475  
   476    ` + "`" + `|
   477  }
   478  `,
   479  		},
   480  
   481  		{
   482  			name: "block_comment_indent",
   483  			in: `
   484  parent: {
   485  """
   486  hello
   487  """ }`,
   488  			exp: `parent: {
   489    """
   490    hello
   491    """
   492  }
   493  `,
   494  		},
   495  
   496  		{
   497  			name: "scalars",
   498  			in: `x: null
   499  y: true
   500  z: 343`,
   501  			exp: `x: null
   502  y: true
   503  z: 343
   504  `,
   505  		},
   506  
   507  		{
   508  			name: "substitution",
   509  			in:   `x: ${ok}; y: [...${yes}]`,
   510  			exp: `x: ${ok}; y: [...${yes}]
   511  `,
   512  		},
   513  
   514  		{
   515  			name: "line_comment_block",
   516  			in: `# wsup
   517  # hello
   518  # The Least Successful Collector`,
   519  			exp: `# wsup
   520  # hello
   521  # The Least Successful Collector
   522  `,
   523  		},
   524  
   525  		{
   526  			name: "inline_comment",
   527  			in: `hello: x # soldier
   528  more`,
   529  			exp: `hello: x # soldier
   530  more
   531  `,
   532  		},
   533  
   534  		{
   535  			name: "array_one_line",
   536  			in:   `a: [1;2;3;4]`,
   537  			exp: `a: [1; 2; 3; 4]
   538  `,
   539  		},
   540  		{
   541  			name: "array",
   542  			in: `a: [
   543  hi # Fraud is the homage that force pays to reason.
   544  1
   545  2
   546  
   547  3
   548  4
   549  5; 6; 7
   550  	]`,
   551  			exp: `a: [
   552    hi # Fraud is the homage that force pays to reason.
   553    1
   554    2
   555  
   556    3
   557    4
   558    5
   559    6
   560    7
   561  ]
   562  `,
   563  		},
   564  
   565  		{
   566  			name: "ampersand",
   567  			in:   `&scenario: red`,
   568  			exp: `&scenario: red
   569  `,
   570  		},
   571  
   572  		{
   573  			name: "complex_edge",
   574  			in:   `pre.(src -> dst -> more)[3].post`,
   575  			exp: `pre.(src -> dst -> more)[3].post
   576  `,
   577  		},
   578  		{
   579  			name: "edge_index_glob",
   580  			in:   `(x -> y)[*]`,
   581  			exp: `(x -> y)[*]
   582  `,
   583  		},
   584  		{
   585  			name: "bidirectional",
   586  			in:   `x<>y`,
   587  			exp: `x <-> y
   588  `,
   589  		},
   590  		{
   591  			name: "empty_map",
   592  			in: `x: {}
   593  `,
   594  			exp: `x
   595  `,
   596  		},
   597  		{
   598  			name: "leading_space_comments",
   599  			in: `# foo
   600  #   foobar
   601  #     baz
   602  x  ->  y
   603  #foo
   604  y
   605  `,
   606  			exp: `# foo
   607  #   foobar
   608  #     baz
   609  x -> y
   610  # foo
   611  y
   612  `,
   613  		},
   614  		{
   615  			name: "less_than_edge#955",
   616  			in: `
   617  x <= y
   618  `,
   619  			exp: `x <- = y
   620  `,
   621  		},
   622  		{
   623  			name: "import/1",
   624  			in: `
   625  x: @file.d2
   626  `,
   627  			exp: `x: @file
   628  `,
   629  		},
   630  		{
   631  			name: "import/2",
   632  			in: `
   633  x: @file."d2"
   634  `,
   635  			exp: `x: @file."d2"
   636  `,
   637  		},
   638  		{
   639  			name: "import/3",
   640  			in: `
   641  x: @./file
   642  `,
   643  			exp: `x: @file
   644  `,
   645  		},
   646  		{
   647  			name: "import/4",
   648  			in: `
   649  x: @../file
   650  `,
   651  			exp: `x: @../file
   652  `,
   653  		},
   654  		{
   655  			name: "import/4",
   656  			in: `
   657  x: @"x/../file"
   658  `,
   659  			exp: `x: @file
   660  `,
   661  		},
   662  		{
   663  			name: "layers_scenarios_steps_bottom_simple",
   664  			in: `layers: {
   665    b: {
   666  		e
   667      scenarios: {
   668        p: { 
   669          x 
   670        }
   671      }
   672    }
   673  	steps: {
   674  		a
   675  	}
   676  }
   677  `,
   678  			exp: `layers: {
   679    b: {
   680      e
   681      
   682      scenarios: {
   683        p: {
   684          x
   685        }
   686      }
   687    }
   688    
   689    steps: {
   690      a
   691    }
   692  }
   693  `,
   694  		},
   695  		{
   696  			name: "layers_scenarios_steps_bottom_complex",
   697  			in: `a
   698  
   699  scenarios: {
   700  
   701  
   702  
   703  	scenario-1: {
   704  		steps: {
   705  			step-1: {
   706          Test
   707  			}
   708  			step-2
   709  		}
   710  		non-step
   711  	}
   712  }
   713  
   714  layers: {
   715    Test super nested: {
   716  		base-layer
   717  		layers: {
   718  			layers: {
   719  				grand-child-layer: {
   720  					grand-child-board
   721  				}
   722  			}
   723  			layer-board
   724  		}
   725  		last-layer
   726  	}
   727  }
   728  b
   729  steps: {
   730  	1: {
   731  		step-1-content
   732  	}
   733  }
   734  
   735  
   736  scenarios: {
   737  	scenario-2: {
   738  		scenario-2-content
   739  	}
   740  }
   741  
   742  c
   743  d
   744  
   745  only-layers: {
   746  	layers: {
   747  		X
   748  		Y
   749  	}
   750  	layers: {
   751  		Z
   752  	}
   753  }
   754  `,
   755  			exp: `a
   756  b
   757  
   758  c
   759  d
   760  
   761  only-layers: {
   762    layers: {
   763      X
   764      Y
   765    }
   766    
   767    layers: {
   768      Z
   769    }
   770  }
   771  
   772  layers: {
   773    Test super nested: {
   774      base-layer
   775      last-layer
   776      
   777      layers: {
   778        layer-board
   779        
   780        layers: {
   781          grand-child-layer: {
   782            grand-child-board
   783          }
   784        }
   785      }
   786    }
   787  }
   788  
   789  scenarios: {
   790    scenario-1: {
   791      non-step
   792      
   793      steps: {
   794        step-1: {
   795          Test
   796        }
   797        step-2
   798      }
   799    }
   800  }
   801  
   802  scenarios: {
   803    scenario-2: {
   804      scenario-2-content
   805    }
   806  }
   807  
   808  steps: {
   809    1: {
   810      step-1-content
   811    }
   812  }
   813  `,
   814  		},
   815  		{
   816  			name: "substitution_mid_string",
   817  			in: `vars: {
   818    test: hello
   819  }
   820  
   821  mybox: {
   822    label: prefix${test}suffix
   823  }
   824  `,
   825  			exp: `vars: {
   826    test: hello
   827  }
   828  
   829  mybox: {
   830    label: prefix${test}suffix
   831  }
   832  `,
   833  		},
   834  	}
   835  
   836  	for _, tc := range testCases {
   837  		tc := tc
   838  		t.Run(tc.name, func(t *testing.T) {
   839  			t.Parallel()
   840  
   841  			ast, err := d2parser.Parse(fmt.Sprintf("%s.d2", t.Name()), strings.NewReader(tc.in), nil)
   842  			if err != nil {
   843  				t.Fatal(err)
   844  			}
   845  			assert.String(t, tc.exp, d2format.Format(ast))
   846  		})
   847  	}
   848  }
   849  
   850  func TestEdge(t *testing.T) {
   851  	t.Parallel()
   852  
   853  	mk, err := d2parser.ParseMapKey(`(x -> y)[0]`)
   854  	if err != nil {
   855  		t.Fatal(err)
   856  	}
   857  	if len(mk.Edges) != 1 {
   858  		t.Fatalf("expected one edge: %#v", mk.Edges)
   859  	}
   860  
   861  	assert.String(t, `x -> y`, d2format.Format(mk.Edges[0]))
   862  	assert.String(t, `[0]`, d2format.Format(mk.EdgeIndex))
   863  }
   864  

View as plain text