github.com/grahambrereton-form3/tilt@v0.10.18/internal/hud/webview/view_test.go (about)

     1  package webview
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io/ioutil"
     7  	"path/filepath"
     8  	"reflect"
     9  	"strings"
    10  	"testing"
    11  	"unicode"
    12  	"unicode/utf8"
    13  
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/stretchr/testify/assert"
    17  )
    18  
    19  // The view data model is not allowed to have any private properties,
    20  // because these properties need to be serialized to JSON for the web UI.
    21  func TestMarshalView(t *testing.T) {
    22  	assertCanMarshal(t, reflect.TypeOf(View{}), reflect.TypeOf(View{}))
    23  }
    24  
    25  func TestJSONRoundTrip(t *testing.T) {
    26  	// this file can be generated via
    27  	// curl localhost:10350/api/snapshot/aaaa | jq '.View' > view.json
    28  	testdataPath := filepath.Join("testdata", "view.json")
    29  	contents, err := ioutil.ReadFile(testdataPath)
    30  	assert.NoError(t, err)
    31  
    32  	// why is this 1.5 round trips instead of 1?
    33  	// go produces output where && is changed to \u0026. I couldn't find a convenient mechanism to generate
    34  	// view.json that matched this.
    35  
    36  	// deserialize into a map[string]interface so that it'll have everything and observed will, also
    37  	decoder := json.NewDecoder(bytes.NewBuffer(contents))
    38  	decoder.DisallowUnknownFields()
    39  	expected := make(map[string]interface{})
    40  	err = decoder.Decode(&expected)
    41  	require.NoError(t, err)
    42  
    43  	// round-trip through an instance of `View`
    44  	decoder = json.NewDecoder(bytes.NewBuffer(contents))
    45  	decoder.DisallowUnknownFields()
    46  	var view View
    47  	err = decoder.Decode(&view)
    48  	require.NoError(t, err)
    49  	b := bytes.NewBuffer(nil)
    50  	encoder := json.NewEncoder(b)
    51  	err = encoder.Encode(view)
    52  	require.NoError(t, err)
    53  
    54  	// now put it back into a `map[string]interface` so that we can compare with `expected`
    55  	decoder = json.NewDecoder(b)
    56  	observed := make(map[string]interface{})
    57  	err = decoder.Decode(&observed)
    58  	require.NoError(t, err)
    59  
    60  	require.Equal(t, expected, observed)
    61  }
    62  
    63  // v: the type to check.
    64  // owner: the owner of this field, for display purposes.
    65  func assertCanMarshal(t *testing.T, v reflect.Type, owner reflect.Type) {
    66  	// If this type does its own marshaling
    67  	var marshal *json.Marshaler
    68  	if v.Implements(reflect.TypeOf(marshal).Elem()) {
    69  		return
    70  	}
    71  
    72  	kind := v.Kind()
    73  	switch kind {
    74  	case reflect.Array, reflect.Slice, reflect.Ptr:
    75  		assertCanMarshal(t, v.Elem(), owner)
    76  	case reflect.Map:
    77  		assertCanMarshal(t, v.Elem(), owner)
    78  		assertCanMarshal(t, v.Key(), owner)
    79  	case reflect.Interface:
    80  		t.Errorf("View needs to be serializable. This type in the view don't make sense: %s in %s", v, owner)
    81  
    82  	case reflect.Chan, reflect.Func:
    83  		t.Errorf("View needs to be serializable. This type in the view don't make sense: %s in %s", v, owner)
    84  	case reflect.Struct:
    85  		for i := 0; i < v.NumField(); i++ {
    86  			field := v.Field(i)
    87  			if !isExported(field.Name) {
    88  				t.Errorf("All fields in the WebView need to be serializable to web. Unexported fields are forbidden: %s in %s",
    89  					field.Name, v)
    90  			}
    91  			tag := field.Tag.Get("json")
    92  			jsonName := strings.SplitN(tag, ",", 2)[0]
    93  			if !isValidJSONField(jsonName) {
    94  				t.Errorf("All fields in the WebView need to be serializable to valid lower-case JSON fields. Field name: %s. Json tag: %s",
    95  					field.Name, jsonName)
    96  			}
    97  
    98  			assertCanMarshal(t, field.Type, v)
    99  		}
   100  	}
   101  }
   102  
   103  func isExported(id string) bool {
   104  	r, _ := utf8.DecodeRuneInString(id)
   105  	return unicode.IsUpper(r)
   106  }
   107  
   108  func isValidJSONField(field string) bool {
   109  	if strings.Contains(field, "_") {
   110  		return false
   111  	}
   112  
   113  	r, _ := utf8.DecodeRuneInString(field)
   114  	return unicode.IsLower(r)
   115  }