...

Source file src/oss.terrastruct.com/d2/d2exporter/export_test.go

Documentation: oss.terrastruct.com/d2/d2exporter

     1  package d2exporter_test
     2  
     3  import (
     4  	"context"
     5  	"path/filepath"
     6  	"strings"
     7  	"testing"
     8  
     9  	"cdr.dev/slog"
    10  
    11  	tassert "github.com/stretchr/testify/assert"
    12  
    13  	"oss.terrastruct.com/util-go/assert"
    14  	"oss.terrastruct.com/util-go/diff"
    15  	"oss.terrastruct.com/util-go/go2"
    16  
    17  	"oss.terrastruct.com/d2/d2compiler"
    18  	"oss.terrastruct.com/d2/d2exporter"
    19  	"oss.terrastruct.com/d2/d2graph"
    20  	"oss.terrastruct.com/d2/d2layouts"
    21  	"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
    22  	"oss.terrastruct.com/d2/d2lib"
    23  	"oss.terrastruct.com/d2/d2target"
    24  	"oss.terrastruct.com/d2/lib/geo"
    25  	"oss.terrastruct.com/d2/lib/log"
    26  	"oss.terrastruct.com/d2/lib/textmeasure"
    27  )
    28  
    29  type testCase struct {
    30  	name string
    31  	dsl  string
    32  
    33  	assertions func(t *testing.T, d *d2target.Diagram)
    34  }
    35  
    36  func TestExport(t *testing.T) {
    37  	t.Parallel()
    38  
    39  	t.Run("shape", testShape)
    40  	t.Run("connection", testConnection)
    41  	t.Run("label", testLabel)
    42  	t.Run("theme", testTheme)
    43  }
    44  
    45  func testShape(t *testing.T) {
    46  	tcs := []testCase{
    47  		{
    48  			name: "basic",
    49  			dsl: `x
    50  `,
    51  		},
    52  		{
    53  			name: "synonyms",
    54  			dsl: `x: {shape: circle}
    55  y: {shape: square}
    56  `,
    57  		},
    58  		{
    59  			name: "text_color",
    60  			dsl: `x: |md yo | { style.font-color: red }
    61  `,
    62  		},
    63  		{
    64  			name: "border-radius",
    65  			dsl: `Square: "" { style.border-radius: 5 }
    66  `,
    67  		},
    68  		{
    69  			name: "image_dimensions",
    70  
    71  			dsl: `hey: "" {
    72    icon: https://icons.terrastruct.com/essentials/004-picture.svg
    73    shape: image
    74  	width: 200
    75  	height: 230
    76  }
    77  `,
    78  			assertions: func(t *testing.T, d *d2target.Diagram) {
    79  				if d.Shapes[0].Width != 200 {
    80  					t.Fatalf("expected width 200, got %v", d.Shapes[0].Width)
    81  				}
    82  				if d.Shapes[0].Height != 230 {
    83  					t.Fatalf("expected height 230, got %v", d.Shapes[0].Height)
    84  				}
    85  			},
    86  		},
    87  		{
    88  			name: "sequence_group_position",
    89  
    90  			dsl: `hey {
    91    shape: sequence_diagram
    92  	a
    93  	b
    94    group: {
    95      a -> b
    96    }
    97  }
    98  `,
    99  			assertions: func(t *testing.T, d *d2target.Diagram) {
   100  				tassert.Equal(t, "hey.group", d.Shapes[3].ID)
   101  				tassert.Equal(t, "INSIDE_TOP_LEFT", d.Shapes[3].LabelPosition)
   102  			},
   103  		},
   104  	}
   105  
   106  	runa(t, tcs)
   107  }
   108  
   109  func testConnection(t *testing.T) {
   110  	tcs := []testCase{
   111  		{
   112  			name: "basic",
   113  			dsl: `x -> y
   114  `,
   115  		},
   116  		{
   117  			name: "stroke-dash",
   118  			dsl: `x -> y: { style.stroke-dash: 4 }
   119  `,
   120  		},
   121  		{
   122  			name: "arrowhead",
   123  			dsl: `x -> y: {
   124    source-arrowhead: If you've done six impossible things before breakfast, why not round it
   125    target-arrowhead: {
   126      label: A man with one watch knows what time it is.
   127      shape: diamond
   128      style.filled: true
   129    }
   130  }
   131  `,
   132  		},
   133  		{
   134  			// This is a regression test where a connection with stroke-dash of 0 on terrastruct flagship theme would have a diff color
   135  			// than a connection without stroke dash
   136  			name: "theme_stroke-dash",
   137  			dsl: `x -> y: { style.stroke-dash: 0 }
   138  x -> y
   139  `,
   140  		},
   141  	}
   142  
   143  	runa(t, tcs)
   144  }
   145  
   146  func testLabel(t *testing.T) {
   147  	tcs := []testCase{
   148  		{
   149  			name: "basic_shape",
   150  			dsl: `x: yo
   151  `,
   152  		},
   153  		{
   154  			name: "shape_font_color",
   155  			dsl: `x: yo { style.font-color: red }
   156  `,
   157  		},
   158  		{
   159  			name: "connection_font_color",
   160  			dsl: `x -> y: yo { style.font-color: red }
   161  `,
   162  		},
   163  	}
   164  
   165  	runa(t, tcs)
   166  }
   167  
   168  func testTheme(t *testing.T) {
   169  	tcs := []testCase{
   170  		{
   171  			name: "shape_without_bold",
   172  			dsl: `x: {
   173  	style.bold: false
   174  }
   175  `,
   176  		},
   177  		{
   178  			name: "shape_with_italic",
   179  			dsl: `x: {
   180  	style.italic: true
   181  }
   182  `,
   183  		},
   184  		{
   185  			name: "connection_without_italic",
   186  			dsl: `x -> y: asdf { style.italic: false }
   187  `,
   188  		},
   189  		{
   190  			name: "connection_with_italic",
   191  			dsl: `x -> y: asdf {
   192    style.italic: true
   193  }
   194  `,
   195  		},
   196  		{
   197  			name: "connection_with_bold",
   198  			dsl: `x -> y: asdf {
   199    style.bold: true
   200  }
   201  `,
   202  		},
   203  	}
   204  
   205  	runa(t, tcs)
   206  }
   207  
   208  func runa(t *testing.T, tcs []testCase) {
   209  	for _, tc := range tcs {
   210  		tc := tc
   211  		t.Run(tc.name, func(t *testing.T) {
   212  			t.Parallel()
   213  
   214  			run(t, tc)
   215  		})
   216  	}
   217  }
   218  
   219  func run(t *testing.T, tc testCase) {
   220  	ctx := context.Background()
   221  	ctx = log.WithTB(ctx, t, nil)
   222  	ctx = log.Leveled(ctx, slog.LevelDebug)
   223  
   224  	g, config, err := d2compiler.Compile("", strings.NewReader(tc.dsl), &d2compiler.CompileOptions{
   225  		UTF16Pos: true,
   226  	})
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  
   231  	ruler, err := textmeasure.NewRuler()
   232  	assert.JSON(t, nil, err)
   233  
   234  	err = g.SetDimensions(nil, ruler, nil)
   235  	assert.JSON(t, nil, err)
   236  
   237  	graphInfo := d2layouts.NestedGraphInfo(g.Root)
   238  	err = d2layouts.LayoutNested(ctx, g, graphInfo, d2dagrelayout.DefaultLayout, d2layouts.DefaultRouter)
   239  	if err != nil {
   240  		t.Fatal(err)
   241  	}
   242  
   243  	got, err := d2exporter.Export(ctx, g, nil)
   244  	if err != nil {
   245  		t.Fatal(err)
   246  	}
   247  	if got != nil {
   248  		got.Config = config
   249  	}
   250  
   251  	if tc.assertions != nil {
   252  		t.Run("assertions", func(t *testing.T) {
   253  			tc.assertions(t, got)
   254  		})
   255  	}
   256  
   257  	// This test is agnostic of layout changes
   258  	for i := range got.Shapes {
   259  		got.Shapes[i].Pos = d2target.Point{}
   260  		got.Shapes[i].Width = 0
   261  		got.Shapes[i].Height = 0
   262  		got.Shapes[i].LabelWidth = 0
   263  		got.Shapes[i].LabelHeight = 0
   264  		got.Shapes[i].LabelPosition = ""
   265  	}
   266  	for i := range got.Connections {
   267  		got.Connections[i].Route = []*geo.Point{}
   268  		got.Connections[i].LabelWidth = 0
   269  		got.Connections[i].LabelHeight = 0
   270  		got.Connections[i].LabelPosition = ""
   271  	}
   272  
   273  	err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2exporter", t.Name()), got)
   274  	assert.Success(t, err)
   275  }
   276  
   277  // TestHashID tests that 2 diagrams with different theme configs do not equal each other
   278  func TestHashID(t *testing.T) {
   279  	ctx := context.Background()
   280  	ctx = log.WithTB(ctx, t, nil)
   281  	ctx = log.Leveled(ctx, slog.LevelDebug)
   282  
   283  	aString := `
   284  vars: {
   285    d2-config: {
   286      theme-id: 3
   287    }
   288  }
   289  a -> b
   290  `
   291  
   292  	bString := `
   293  vars: {
   294    d2-config: {
   295      theme-id: 4
   296    }
   297  }
   298  a -> b
   299  `
   300  
   301  	da, err := compile(ctx, aString)
   302  	assert.JSON(t, nil, err)
   303  
   304  	db, err := compile(ctx, bString)
   305  	assert.JSON(t, nil, err)
   306  
   307  	hashA, err := da.HashID()
   308  	assert.JSON(t, nil, err)
   309  
   310  	hashB, err := db.HashID()
   311  	assert.JSON(t, nil, err)
   312  
   313  	assert.NotEqual(t, hashA, hashB)
   314  }
   315  
   316  func layoutResolver(engine string) (d2graph.LayoutGraph, error) {
   317  	return d2dagrelayout.DefaultLayout, nil
   318  }
   319  
   320  func compile(ctx context.Context, d2 string) (*d2target.Diagram, error) {
   321  	ruler, _ := textmeasure.NewRuler()
   322  	opts := &d2lib.CompileOptions{
   323  		Ruler:          ruler,
   324  		LayoutResolver: layoutResolver,
   325  		Layout:         go2.Pointer("dagre"),
   326  	}
   327  	d, _, e := d2lib.Compile(ctx, d2, opts, nil)
   328  	return d, e
   329  }
   330  

View as plain text