...

Source file src/oss.terrastruct.com/d2/d2renderers/d2svg/dark_theme/dark_theme_test.go

Documentation: oss.terrastruct.com/d2/d2renderers/d2svg/dark_theme

     1  package dark_theme_test
     2  
     3  import (
     4  	"context"
     5  	"encoding/xml"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"testing"
    11  
    12  	"cdr.dev/slog"
    13  
    14  	tassert "github.com/stretchr/testify/assert"
    15  
    16  	"oss.terrastruct.com/util-go/assert"
    17  	"oss.terrastruct.com/util-go/diff"
    18  	"oss.terrastruct.com/util-go/go2"
    19  
    20  	"oss.terrastruct.com/d2/d2graph"
    21  	"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
    22  	"oss.terrastruct.com/d2/d2lib"
    23  	"oss.terrastruct.com/d2/d2renderers/d2fonts"
    24  	"oss.terrastruct.com/d2/d2renderers/d2svg"
    25  	"oss.terrastruct.com/d2/lib/log"
    26  	"oss.terrastruct.com/d2/lib/textmeasure"
    27  )
    28  
    29  func TestDarkTheme(t *testing.T) {
    30  	t.Parallel()
    31  
    32  	tcs := []testCase{
    33  		{
    34  			name: "basic",
    35  			script: `a -> b
    36  `,
    37  		},
    38  		{
    39  			name: "child to child",
    40  			script: `winter.snow -> summer.sun
    41  		`,
    42  		},
    43  		{
    44  			name: "animated",
    45  			script: `winter.snow -> summer.sun -> trees -> winter.snow: { style.animated: true }
    46  		`,
    47  		},
    48  		{
    49  			name: "connection label",
    50  			script: `a -> b: hello
    51  		`,
    52  		},
    53  		{
    54  			name: "twitter",
    55  			script: `timeline mixer: "" {
    56    explanation: |md
    57      ## **Timeline mixer**
    58      - Inject ads, who-to-follow, onboarding
    59      - Conversation module
    60      - Cursoring,pagination
    61      - Tweat deduplication
    62      - Served data logging
    63    |
    64  }
    65  People discovery: "People discovery \nservice"
    66  admixer: Ad mixer {
    67    style.fill: "#cba6f7"
    68    style.font-color: "#000000"
    69  }
    70  
    71  onboarding service: "Onboarding \nservice"
    72  timeline mixer -> People discovery
    73  timeline mixer -> onboarding service
    74  timeline mixer -> admixer
    75  container0: "" {
    76    graphql
    77    comment
    78    tlsapi
    79  }
    80  container0.graphql: GraphQL\nFederated Strato Column {
    81    shape: image
    82    icon: https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/GraphQL_Logo.svg/1200px-GraphQL_Logo.svg.png
    83  }
    84  container0.comment: |md
    85    ## Tweet/user content hydration, visibility filtering
    86  |
    87  container0.tlsapi: TLS-API (being deprecated)
    88  container0.graphql -> timeline mixer
    89  timeline mixer <- container0.tlsapi
    90  twitter fe: "Twitter Frontend " {
    91    icon: https://icons.terrastruct.com/social/013-twitter-1.svg
    92    shape: image
    93  }
    94  twitter fe -> container0.graphql: iPhone web
    95  twitter fe -> container0.tlsapi: HTTP Android
    96  web: Web {
    97    icon: https://icons.terrastruct.com/azure/Web%20Service%20Color/App%20Service%20Domains.svg
    98    shape: image
    99  }
   100  
   101  Iphone: {
   102    icon: 'https://ss7.vzw.com/is/image/VerizonWireless/apple-iphone-12-64gb-purple-53017-mjn13ll-a?$device-lg$'
   103    shape: image
   104  }
   105  Android: {
   106    icon: https://cdn4.iconfinder.com/data/icons/smart-phones-technologies/512/android-phone.png
   107    shape: image
   108  }
   109  
   110  web -> twitter fe
   111  timeline scorer: "Timeline\nScorer" {
   112    style.fill: "#fab387"
   113    style.font-color: "#000000"
   114  }
   115  home ranker: Home Ranker
   116  
   117  timeline service: Timeline Service
   118  timeline mixer -> timeline scorer: Thrift RPC
   119  timeline mixer -> home ranker: {
   120    style.stroke-dash: 4
   121    style.stroke: "#000E3D"
   122  }
   123  timeline mixer -> timeline service
   124  home mixer: Home mixer {
   125    # style.fill: "#c1a2f3"
   126  }
   127  container0.graphql -> home mixer: {
   128    style.stroke-dash: 4
   129    style.stroke: "#000E3D"
   130  }
   131  home mixer -> timeline scorer
   132  home mixer -> home ranker: {
   133    style.stroke-dash: 4
   134    style.stroke: "#000E3D"
   135  }
   136  home mixer -> timeline service
   137  manhattan 2: Manhattan
   138  gizmoduck: Gizmoduck
   139  socialgraph: Social graph
   140  tweetypie: Tweety Pie
   141  home mixer -> manhattan 2
   142  home mixer -> gizmoduck
   143  home mixer -> socialgraph
   144  home mixer -> tweetypie
   145  Iphone -> twitter fe
   146  Android -> twitter fe
   147  prediction service2: Prediction Service {
   148    shape: image
   149    icon: https://cdn-icons-png.flaticon.com/512/6461/6461819.png
   150  }
   151  home scorer: Home Scorer {
   152    style.fill: "#eba0ac"
   153    style.font-color: "#000000"
   154  }
   155  manhattan: Manhattan
   156  memcache: Memcache {
   157    icon: https://d1q6f0aelx0por.cloudfront.net/product-logos/de041504-0ddb-43f6-b89e-fe04403cca8d-memcached.png
   158  }
   159  
   160  fetch: Fetch {
   161    style.multiple: true
   162    shape: step
   163  }
   164  feature: Feature {
   165    style.multiple: true
   166    shape: step
   167  }
   168  scoring: Scoring {
   169    style.multiple: true
   170    shape: step
   171  }
   172  fetch -> feature
   173  feature -> scoring
   174  
   175  prediction service: Prediction Service {
   176    shape: image
   177    icon: https://cdn-icons-png.flaticon.com/512/6461/6461819.png
   178  }
   179  scoring -> prediction service
   180  fetch -> container2.crmixer
   181  
   182  home scorer -> manhattan: ""
   183  
   184  home scorer -> memcache: ""
   185  home scorer -> prediction service2
   186  home ranker -> home scorer
   187  home ranker -> container2.crmixer: Candidate Fetch
   188  container2: "" {
   189    style.stroke: "#b4befe"
   190    style.fill: "#000000"
   191    crmixer: CrMixer {
   192      style.fill: "#11111b"
   193  	style.font-color: "#cdd6f4"
   194    }
   195    earlybird: EarlyBird
   196    utag: Utag
   197    space: Space
   198    communities: Communities
   199  }
   200  etc: ...etc
   201  
   202  home scorer -> etc: Feature Hydration
   203  
   204  feature -> manhattan
   205  feature -> memcache
   206  feature -> etc: Candidate sources
   207  		`,
   208  		},
   209  		{
   210  			name: "all_shapes",
   211  			script: `
   212  rectangle: {shape: "rectangle"}
   213  square: {shape: "square"}
   214  page: {shape: "page"}
   215  parallelogram: {shape: "parallelogram"}
   216  document: {shape: "document"}
   217  cylinder: {shape: "cylinder"}
   218  queue: {shape: "queue"}
   219  package: {shape: "package"}
   220  step: {shape: "step"}
   221  callout: {shape: "callout"}
   222  stored_data: {shape: "stored_data"}
   223  person: {shape: "person"}
   224  diamond: {shape: "diamond"}
   225  oval: {shape: "oval"}
   226  circle: {shape: "circle"}
   227  hexagon: {shape: "hexagon"}
   228  cloud: {shape: "cloud"}
   229  
   230  rectangle -> square -> page
   231  parallelogram -> document -> cylinder
   232  queue -> package -> step
   233  callout -> stored_data -> person
   234  diamond -> oval -> circle
   235  hexagon -> cloud
   236  `,
   237  		},
   238  		{
   239  			name: "sql_tables",
   240  			script: `users: {
   241  	shape: sql_table
   242  	id: int
   243  	name: string
   244  	email: string
   245  	password: string
   246  	last_login: datetime
   247  }
   248  
   249  products: {
   250  	shape: sql_table
   251  	id: int
   252  	price: decimal
   253  	sku: string
   254  	name: string
   255  }
   256  
   257  orders: {
   258  	shape: sql_table
   259  	id: int
   260  	user_id: int
   261  	product_id: int
   262  }
   263  
   264  shipments: {
   265  	shape: sql_table
   266  	id: int
   267  	order_id: int
   268  	tracking_number: string {constraint: primary_key}
   269  	status: string
   270  }
   271  
   272  users.id <-> orders.user_id
   273  products.id <-> orders.product_id
   274  shipments.order_id <-> orders.id`,
   275  		},
   276  		{
   277  			name: "class",
   278  			script: `manager: BatchManager {
   279    shape: class
   280    -num: int
   281    -timeout: int
   282    -pid
   283  
   284    +getStatus(): Enum
   285    +getJobs(): "Job[]"
   286    +setTimeout(seconds int)
   287  }
   288  `,
   289  		},
   290  		{
   291  			name: "arrowheads",
   292  			script: `
   293  a: ""
   294  b: ""
   295  a.1 -- b.1: none
   296  a.2 <-> b.2: arrow {
   297  	source-arrowhead.shape: arrow
   298  	target-arrowhead.shape: arrow
   299  }
   300  a.3 <-> b.3: triangle {
   301  	source-arrowhead.shape: triangle
   302  	target-arrowhead.shape: triangle
   303  }
   304  a.4 <-> b.4: diamond {
   305  	source-arrowhead.shape: diamond
   306  	target-arrowhead.shape: diamond
   307  }
   308  a.5 <-> b.5: diamond filled {
   309  	source-arrowhead: {
   310  		shape: diamond
   311  		style.filled: true
   312  	}
   313  	target-arrowhead: {
   314  		shape: diamond
   315  		style.filled: true
   316  	}
   317  }
   318  a.6 <-> b.6: cf-many {
   319  	source-arrowhead.shape: cf-many
   320  	target-arrowhead.shape: cf-many
   321  }
   322  a.7 <-> b.7: cf-many-required {
   323  	source-arrowhead.shape: cf-many-required
   324  	target-arrowhead.shape: cf-many-required
   325  }
   326  a.8 <-> b.8: cf-one {
   327  	source-arrowhead.shape: cf-one
   328  	target-arrowhead.shape: cf-one
   329  }
   330  a.9 <-> b.9: cf-one-required {
   331  	source-arrowhead.shape: cf-one-required
   332  	target-arrowhead.shape: cf-one-required
   333  }
   334  `,
   335  		},
   336  		{
   337  			name: "opacity",
   338  			script: `x.style.opacity: 0.4
   339  y: |md
   340    linux: because a PC is a terrible thing to waste
   341  | {
   342  	style.opacity: 0.4
   343  }
   344  x -> a: {
   345    label: You don't have to know how the computer works,\njust how to work the computer.
   346    style.opacity: 0.4
   347  }
   348  users: {
   349  	shape: sql_table
   350  	last_login: datetime
   351  	style.opacity: 0.4
   352  }
   353  `,
   354  		},
   355  		{
   356  			name: "overlay",
   357  			script: `bright: {
   358  	style.stroke: "#000"
   359  	style.font-color: "#000"
   360  	style.fill: "#fff"
   361  }
   362  normal: {
   363  	style.stroke: "#000"
   364  	style.font-color: "#000"
   365  	style.fill: "#ccc"
   366  }
   367  dark: {
   368  	style.stroke: "#000"
   369  	style.font-color: "#fff"
   370  	style.fill: "#555"
   371  }
   372  darker: {
   373  	style.stroke: "#000"
   374  	style.font-color: "#fff"
   375  	style.fill: "#000"
   376  }
   377  `,
   378  		},
   379  		{
   380  			name: "code",
   381  			script: `code: |go
   382  func main() {
   383    panic("TODO")
   384  }
   385  |
   386  
   387  text: |md
   388  Five is a sufficiently close approximation to infinity.
   389  |
   390  unknown: |asdf
   391  Don't hit me!!  I'm in the Twilight Zone!!!
   392  |
   393  code -- unknown
   394  `,
   395  		},
   396  	}
   397  	runa(t, tcs)
   398  }
   399  
   400  type testCase struct {
   401  	name   string
   402  	script string
   403  	skip   bool
   404  }
   405  
   406  func runa(t *testing.T, tcs []testCase) {
   407  	for _, tc := range tcs {
   408  		tc := tc
   409  		t.Run(tc.name, func(t *testing.T) {
   410  			if tc.skip {
   411  				t.Skip()
   412  			}
   413  			t.Parallel()
   414  
   415  			run(t, tc)
   416  		})
   417  	}
   418  }
   419  
   420  func run(t *testing.T, tc testCase) {
   421  	ctx := context.Background()
   422  	ctx = log.WithTB(ctx, t, nil)
   423  	ctx = log.Leveled(ctx, slog.LevelDebug)
   424  
   425  	ruler, err := textmeasure.NewRuler()
   426  	if !tassert.Nil(t, err) {
   427  		return
   428  	}
   429  
   430  	renderOpts := &d2svg.RenderOpts{
   431  		ThemeID: go2.Pointer(int64(200)),
   432  	}
   433  	layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
   434  		return d2dagrelayout.DefaultLayout, nil
   435  	}
   436  
   437  	diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{
   438  		Ruler:          ruler,
   439  		LayoutResolver: layoutResolver,
   440  		FontFamily:     go2.Pointer(d2fonts.HandDrawn),
   441  	}, renderOpts)
   442  	if !tassert.Nil(t, err) {
   443  		return
   444  	}
   445  
   446  	dataPath := filepath.Join("testdata", strings.TrimPrefix(t.Name(), "TestDarkTheme/"))
   447  	pathGotSVG := filepath.Join(dataPath, "dark_theme.got.svg")
   448  
   449  	svgBytes, err := d2svg.Render(diagram, renderOpts)
   450  	assert.Success(t, err)
   451  	err = os.MkdirAll(dataPath, 0755)
   452  	assert.Success(t, err)
   453  	err = ioutil.WriteFile(pathGotSVG, svgBytes, 0600)
   454  	assert.Success(t, err)
   455  	defer os.Remove(pathGotSVG)
   456  
   457  	var xmlParsed interface{}
   458  	err = xml.Unmarshal(svgBytes, &xmlParsed)
   459  	assert.Success(t, err)
   460  
   461  	err = diff.Testdata(filepath.Join(dataPath, "dark_theme"), ".svg", svgBytes)
   462  	assert.Success(t, err)
   463  }
   464  

View as plain text