diff options
author | dwillist <dthornton@vmware.com> | 2021-01-20 17:30:33 -0500 |
---|---|---|
committer | dwillist <dthornton@vmware.com> | 2021-01-20 17:36:15 -0500 |
commit | 43c0b96bac08eec89875962c8f2a6da52f2a1a97 (patch) | |
tree | 5df4754c6033052bef4747f2cb92151b2d44257e | |
parent | a11e9c1cf23c379dcec0bb45721ec1bca4e2c717 (diff) |
add mock based viewmodel tests
Signed-off-by: dwillist <dthornton@vmware.com>
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | runtime/ui/app.go | 12 | ||||
-rw-r--r-- | runtime/ui/format/format.go | 2 | ||||
-rw-r--r-- | runtime/ui/viewmodels/fakes/fake_filter_model.go | 44 | ||||
-rw-r--r-- | runtime/ui/viewmodels/fakes/fake_layers_model.go | 120 | ||||
-rw-r--r-- | runtime/ui/viewmodels/fakes/fake_tree_cache.go | 34 | ||||
-rw-r--r-- | runtime/ui/viewmodels/fakes/fake_tree_model.go | 120 | ||||
-rw-r--r-- | runtime/ui/viewmodels/filter_view_model_test.go | 36 | ||||
-rw-r--r-- | runtime/ui/viewmodels/layer_view_model_test.go | 168 | ||||
-rw-r--r-- | runtime/ui/viewmodels/tree_view_model.go | 41 | ||||
-rw-r--r-- | runtime/ui/viewmodels/tree_view_model_test.go | 421 |
11 files changed, 986 insertions, 14 deletions
@@ -14,7 +14,7 @@ ci-unit-test: ci-static-analysis: go vet ./... - @! gofmt -s -l . 2>&1 | grep -vE '^\.git/' | grep -vE '^\.cache/' + gofmt -s -l . 2>&1 | grep -vE '^\.git/' | grep -vE '^\.cache/' golangci-lint run ci-install-go-tools: diff --git a/runtime/ui/app.go b/runtime/ui/app.go index 8090cf7..3d83a21 100644 --- a/runtime/ui/app.go +++ b/runtime/ui/app.go @@ -45,7 +45,8 @@ func newApp(app *tview.Application, analysis *image.AnalysisResult, cache filetr layerDetailsBox.SetVisibility(components.MinHeightVisibility(10)) //layerViewModel := viewmodels.NewLayersViewModel(analysis.Layers) - treeViewModel, err := viewmodels.NewTreeViewModel(cache, layerModel, filterViewModel) + cacheWrapper := CacheWrapper{Cache: &cache} + treeViewModel, err := viewmodels.NewTreeViewModel(&cacheWrapper, layerModel, filterViewModel) if err != nil { panic(err) } @@ -168,3 +169,12 @@ func Run(analysis *image.AnalysisResult, treeStack filetree.Comparer) error { logrus.Info("app run loop exited") return nil } + +// TODO move me to initialization package +type CacheWrapper struct { + Cache *filetree.Comparer +} + +func (c *CacheWrapper) GetTree(key filetree.TreeIndexKey) (viewmodels.TreeModel, error) { + return c.Cache.GetTree(key) +} diff --git a/runtime/ui/format/format.go b/runtime/ui/format/format.go index 9b79461..ebdc797 100644 --- a/runtime/ui/format/format.go +++ b/runtime/ui/format/format.go @@ -124,7 +124,7 @@ func boldReplace(s string) string { return s } -// TODO factor me out into a utils package along with my usage in the componenets package +// TODO factor this out into a utils package along with my usage in the componenets package func intMin(a, b int) int { if a < b { return a diff --git a/runtime/ui/viewmodels/fakes/fake_filter_model.go b/runtime/ui/viewmodels/fakes/fake_filter_model.go new file mode 100644 index 0000000..d2e2bd5 --- /dev/null +++ b/runtime/ui/viewmodels/fakes/fake_filter_model.go @@ -0,0 +1,44 @@ +package fakes + +import ( + "regexp" + "sync" +) + +type FilterModel struct { + GetFilterCall struct { + sync.Mutex + CallCount int + Returns struct { + Regexp *regexp.Regexp + } + Stub func() *regexp.Regexp + } + SetFilterCall struct { + sync.Mutex + CallCount int + Receives struct { + R *regexp.Regexp + } + Stub func(*regexp.Regexp) + } +} + +func (f *FilterModel) GetFilter() *regexp.Regexp { + f.GetFilterCall.Lock() + defer f.GetFilterCall.Unlock() + f.GetFilterCall.CallCount++ + if f.GetFilterCall.Stub != nil { + return f.GetFilterCall.Stub() + } + return f.GetFilterCall.Returns.Regexp +} +func (f *FilterModel) SetFilter(param1 *regexp.Regexp) { + f.SetFilterCall.Lock() + defer f.SetFilterCall.Unlock() + f.SetFilterCall.CallCount++ + f.SetFilterCall.Receives.R = param1 + if f.SetFilterCall.Stub != nil { + f.SetFilterCall.Stub(param1) + } +} diff --git a/runtime/ui/viewmodels/fakes/fake_layers_model.go b/runtime/ui/viewmodels/fakes/fake_layers_model.go new file mode 100644 index 0000000..9748249 --- /dev/null +++ b/runtime/ui/viewmodels/fakes/fake_layers_model.go @@ -0,0 +1,120 @@ +package fakes + +import ( + "fmt" + "sync" + + "github.com/wagoodman/dive/dive/filetree" + "github.com/wagoodman/dive/dive/image" + "github.com/wagoodman/dive/runtime/ui/viewmodels" +) + +type LayersModel struct { + GetCompareIndiciesCall struct { + sync.Mutex + CallCount int + Returns struct { + TreeIndexKey filetree.TreeIndexKey + } + Stub func() filetree.TreeIndexKey + } + GetCurrentLayerCall struct { + sync.Mutex + CallCount int + Returns struct { + Layer *image.Layer + } + Stub func() *image.Layer + } + GetModeCall struct { + sync.Mutex + CallCount int + Returns struct { + LayerCompareMode viewmodels.LayerCompareMode + } + Stub func() viewmodels.LayerCompareMode + } + GetPrintableLayersCall struct { + sync.Mutex + CallCount int + Returns struct { + StringerSlice []fmt.Stringer + } + Stub func() []fmt.Stringer + } + SetLayerIndexCall struct { + sync.Mutex + CallCount int + Receives struct { + Index int + } + Returns struct { + Bool bool + } + Stub func(int) bool + } + SwitchLayerModeCall struct { + sync.Mutex + CallCount int + Returns struct { + Error error + } + Stub func() error + } +} + +func (f *LayersModel) GetCompareIndicies() filetree.TreeIndexKey { + f.GetCompareIndiciesCall.Lock() + defer f.GetCompareIndiciesCall.Unlock() + f.GetCompareIndiciesCall.CallCount++ + if f.GetCompareIndiciesCall.Stub != nil { + return f.GetCompareIndiciesCall.Stub() + } + return f.GetCompareIndiciesCall.Returns.TreeIndexKey +} +func (f *LayersModel) GetCurrentLayer() *image.Layer { + f.GetCurrentLayerCall.Lock() + defer f.GetCurrentLayerCall.Unlock() + f.GetCurrentLayerCall.CallCount++ + if f.GetCurrentLayerCall.Stub != nil { + return f.GetCurrentLayerCall.Stub() + } + return f.GetCurrentLayerCall.Returns.Layer +} +func (f *LayersModel) GetMode() viewmodels.LayerCompareMode { + f.GetModeCall.Lock() + defer f.GetModeCall.Unlock() + f.GetModeCall.CallCount++ + if f.GetModeCall.Stub != nil { + return f.GetModeCall.Stub() + } + return f.GetModeCall.Returns.LayerCompareMode +} +func (f *LayersModel) GetPrintableLayers() []fmt.Stringer { + f.GetPrintableLayersCall.Lock() + defer f.GetPrintableLayersCall.Unlock() + f.GetPrintableLayersCall.CallCount++ + if f.GetPrintableLayersCall.Stub != nil { + return f.GetPrintableLayersCall.Stub() + } + return f.GetPrintableLayersCall.Returns.StringerSlice +} +func (f *LayersModel) SetLayerIndex(param1 int) bool { + f.SetLayerIndexCall.Lock() + defer f.SetLayerIndexCall.Unlock() + f.SetLayerIndexCall.CallCount++ + f.SetLayerIndexCall.Receives.Index = param1 + if f.SetLayerIndexCall.Stub != nil { + return f.SetLayerIndexCall.Stub(param1) + } + return f.SetLayerIndexCall.Returns.Bool +} +func (f *LayersModel) SwitchLayerMode() error { + f.SwitchLayerModeCall.Lock() + defer f.SwitchLayerModeCall.Unlock() + f.SwitchLayerModeCall.CallCount++ + if f.SwitchLayerModeCall.Stub != nil { + return f.SwitchLayerModeCall.Stub() + } + return f.SwitchLayerModeCall.Returns.Error +} diff --git a/runtime/ui/viewmodels/fakes/fake_tree_cache.go b/runtime/ui/viewmodels/fakes/fake_tree_cache.go new file mode 100644 index 0000000..f4c36ac --- /dev/null +++ b/runtime/ui/viewmodels/fakes/fake_tree_cache.go @@ -0,0 +1,34 @@ +package fakes + +import ( + "sync" + + "github.com/wagoodman/dive/dive/filetree" + "github.com/wagoodman/dive/runtime/ui/viewmodels" +) + +type TreeCache struct { + GetTreeCall struct { + sync.Mutex + CallCount int + Receives struct { + Key filetree.TreeIndexKey + } + Returns struct { + TreeModel viewmodels.TreeModel + Error error + } + Stub func(filetree.TreeIndexKey) (viewmodels.TreeModel, error) + } +} + +func (f *TreeCache) GetTree(param1 filetree.TreeIndexKey) (viewmodels.TreeModel, error) { + f.GetTreeCall.Lock() + defer f.GetTreeCall.Unlock() + f.GetTreeCall.CallCount++ + f.GetTreeCall.Receives.Key = param1 + if f.GetTreeCall.Stub != nil { + return f.GetTreeCall.Stub(param1) + } + return f.GetTreeCall.Returns.TreeModel, f.GetTreeCall.Returns.Error +} diff --git a/runtime/ui/viewmodels/fakes/fake_tree_model.go b/runtime/ui/viewmodels/fakes/fake_tree_model.go new file mode 100644 index 0000000..50aeaa9 --- /dev/null +++ b/runtime/ui/viewmodels/fakes/fake_tree_model.go @@ -0,0 +1,120 @@ +package fakes + +import ( + "sync" + + "github.com/wagoodman/dive/dive/filetree" +) + +type TreeModel struct { + RemovePathCall struct { + sync.Mutex + CallCount int + Receives struct { + Path string + } + Returns struct { + Error error + } + Stub func(string) error + } + StringBetweenCall struct { + sync.Mutex + CallCount int + Receives struct { + Start int + Stop int + ShowAttributes bool + } + Returns struct { + String string + } + Stub func(int, int, bool) string + } + VisibleSizeCall struct { + sync.Mutex + CallCount int + Returns struct { + Int int + } + Stub func() int + } + VisitDepthChildFirstCall struct { + sync.Mutex + CallCount int + Receives struct { + Visitor filetree.Visitor + Evaluator filetree.VisitEvaluator + } + Returns struct { + Error error + } + Stub func(filetree.Visitor, filetree.VisitEvaluator) error + } + VisitDepthParentFirstCall struct { + sync.Mutex + CallCount int + Receives struct { + Visitor filetree.Visitor + Evaluator filetree.VisitEvaluator + } + Returns struct { + Error error + } + Stub func(filetree.Visitor, filetree.VisitEvaluator) error + } +} + +func (f *TreeModel) RemovePath(param1 string) error { + f.RemovePathCall.Lock() + defer f.RemovePathCall.Unlock() + f.RemovePathCall.CallCount++ + f.RemovePathCall.Receives.Path = param1 + if f.RemovePathCall.Stub != nil { + return f.RemovePathCall.Stub(param1) + } + return f.RemovePathCall.Returns.Error +} +func (f *TreeModel) StringBetween(param1 int, param2 int, param3 bool) string { + f.StringBetweenCall.Lock() + defer f.StringBetweenCall.Unlock() + f.StringBetweenCall.CallCount++ + f.StringBetweenCall.Receives.Start = param1 + f.StringBetweenCall.Receives.Stop = param2 + f.StringBetweenCall.Receives.ShowAttributes = param3 + if f.StringBetweenCall.Stub != nil { + return f.StringBetweenCall.Stub(param1, param2, param3) + } + return f.StringBetweenCall.Returns.String +} +func (f *TreeModel) VisibleSize() int { + f.VisibleSizeCall.Lock() + defer f.VisibleSizeCall.Unlock() + f.VisibleSizeCall.CallCount++ + if f.VisibleSizeCall.Stub != nil { + return f.VisibleSizeCall.Stub() + } + return f.VisibleSizeCall.Returns.Int +} +func (f *TreeModel) VisitDepthChildFirst(param1 filetree.Visitor, param2 filetree.VisitEvaluator) error { + f.VisitDepthChildFirstCall.Lock() + defer f.VisitDepthChildFirstCall.Unlock() + f.VisitDepthChildFirstCall.CallCount++ + f.VisitDepthChildFirstCall.Receives.Visitor = param1 + f.VisitDepthChildFirstCall.Receives.Evaluator = param2 + if f.VisitDepthChildFirstCall.Stub != nil { + return f.VisitDepthChildFirstCall.Stub(param1, param2) + } + return f.VisitDepthChildFirstCall.Returns.Error +} +func (f *TreeModel) VisitDepthParentFirst(param1 filetree.Visitor, param2 filetree.VisitEvaluator) error { + f.VisitDepthParentFirstCall.Lock() + defer f.VisitDepthParentFirstCall.Unlock() + f.VisitDepthParentFirstCall.CallCount++ + f.VisitDepthParentFirstCall.Receives.Visitor = param1 + f.VisitDepthParentFirstCall.Receives.Evaluator = param2 + if f.VisitDepthParentFirstCall.Stub != nil { + return f.VisitDepthParentFirstCall.Stub(param1, param2) + } + return f.VisitDepthParentFirstCall.Returns.Error +} diff --git a/runtime/ui/viewmodels/filter_view_model_test.go b/runtime/ui/viewmodels/filter_view_model_test.go new file mode 100644 index 0000000..62d94f6 --- /dev/null +++ b/runtime/ui/viewmodels/filter_view_model_test.go @@ -0,0 +1,36 @@ +package viewmodels_test + +import ( + "github.com/wagoodman/dive/runtime/ui/viewmodels" + "regexp" + "testing" +) + +func TestFilterModel(t *testing.T) { + + testNilFilterView(t) + testReFilterView(t) +} + + +func testNilFilterView(t *testing.T) { + filterView := viewmodels.NewFilterViewModel(nil) + filter := filterView.GetFilter() + if filter != nil { + t.Errorf("expected nil got %#v", filter) + } +} + +func testReFilterView(t *testing.T) { + filterView := viewmodels.NewFilterViewModel(nil) + + r := regexp.MustCompile("some regex") + filterView.SetFilter(r) + filter := filterView.GetFilter() + if filter != r { + t.Errorf("expected %q got %#v", r, filter) + } + if filter.String() != "some regex" { + t.Errorf("expected 'some regex' got %s", filter.String()) + } +}
\ No newline at end of file diff --git a/runtime/ui/viewmodels/layer_view_model_test.go b/runtime/ui/viewmodels/layer_view_model_test.go new file mode 100644 index 0000000..0b500b2 --- /dev/null +++ b/runtime/ui/viewmodels/layer_view_model_test.go @@ -0,0 +1,168 @@ +package viewmodels_test + +import ( + "github.com/wagoodman/dive/dive/filetree" + "github.com/wagoodman/dive/dive/image" + "github.com/wagoodman/dive/runtime/ui/viewmodels" + "testing" +) + +func TestLayersViewModel(t *testing.T) { + testMode(t) + testIndicies(t) + testGetCurrentLayer(t) + + testGetPrintableLayers(t) +} + +func testMode(t *testing.T) { + lvm := viewmodels.NewLayersViewModel([]*image.Layer{}) + + curMode := lvm.GetMode() + if curMode != viewmodels.CompareSingleLayer { + t.Errorf("expected %v got %v", viewmodels.CompareSingleLayer, curMode) + } + + if err := lvm.SwitchLayerMode(); err != nil { + t.Errorf("expected 'nil' got %q", err) + } + + curMode = lvm.GetMode() + if curMode != viewmodels.CompareAllLayers { + t.Errorf("expected %v got %v", viewmodels.CompareAllLayers, curMode) + } +} + +func testIndicies(t *testing.T) { + testCompareSingleIndicies(t) + testCompareAllIndicies(t) +} +func testCompareSingleIndicies(t *testing.T) { + layers := []*image.Layer{ + { + Id: "some-id", + }, + { + Id: "some-id2", + }, + { + Id: "some-id3", + }, + } + + + lvm := viewmodels.NewLayersViewModel(layers) + + cmpIndex := lvm.GetCompareIndicies() + if cmpIndex != filetree.NewTreeIndexKey(0,0,0,0) { + t.Errorf("expected index key {0,0,0,0}, got %#v", cmpIndex) + } + + lvm.SetLayerIndex(2) + + cmpIndex = lvm.GetCompareIndicies() + if cmpIndex != filetree.NewTreeIndexKey(0,1,2,2) { + t.Errorf("expected index key {0,1,2,2}, got %#v", cmpIndex) + } +} + +func testCompareAllIndicies(t *testing.T) { + layers := []*image.Layer{ + { + Id: "some-id", + }, + { + Id: "some-id2", + }, + { + Id: "some-id3", + }, + } + + + lvm := viewmodels.NewLayersViewModel(layers) + err := lvm.SwitchLayerMode() + errorCheck(t, err) + + if lvm.GetMode() != viewmodels.CompareAllLayers { + t.Errorf("expected CompareAllLayers mode %d, got %d", viewmodels.CompareAllLayers, lvm.GetMode()) + } + + cmpIndex := lvm.GetCompareIndicies() + if cmpIndex != filetree.NewTreeIndexKey(0,0,1,0) { + t.Errorf("expected index key {0,0,0,0}, got %#v", cmpIndex) + } + + lvm.SetLayerIndex(2) + + cmpIndex = lvm.GetCompareIndicies() + if cmpIndex != filetree.NewTreeIndexKey(0,0,1,2) { + t.Errorf("expected index key {0,0,1,2}, got %#v", cmpIndex) + } +} + +func testGetCurrentLayer(t *testing.T) { + firstLayer := &image.Layer{ + Id: "some-id", + } + secondLayer := &image.Layer{ + Id: "some-id2", + } + layers := []*image.Layer{firstLayer, secondLayer} + lvm := viewmodels.NewLayersViewModel(layers) + + if lvm.GetCurrentLayer() != firstLayer { + t.Errorf("expected %#v, got %#v", *firstLayer, *(lvm.GetCurrentLayer())) + } + + lvm.SetLayerIndex(1) + + if lvm.GetCurrentLayer() != secondLayer { + t.Errorf("expected %#v, got %#v", *secondLayer, *(lvm.GetCurrentLayer())) + } +} + +func testGetPrintableLayers(t *testing.T) { + layers := []*image.Layer{ + { + Id: "some-id", + Index: 0, + Command: "layer1 cmd", + Size: 100, + Tree: nil, + Names: []string{"name1", "name2"}, + Digest: "digest:layer1", + + }, + { + Id: "some-id2", + Index: 1, + Command: "layer2 cmd", + Size: 200, + Tree: nil, + Names: []string{"name3", "name4"}, + Digest: "digest:layer2", + }, + } + + lvm := viewmodels.NewLayersViewModel(layers) + printableLayers := lvm.GetPrintableLayers() + + if len(printableLayers) != 2 { + t.Errorf("expected 2 got %d", len(printableLayers)) + } + + expectedFirstLayer := " 100 B FROM some-id" + if printableLayers[0].String() != expectedFirstLayer { + t.Errorf("expected %s got %s", expectedFirstLayer, printableLayers[0].String()) + } + + expectedSecondLayer := " 200 B layer2 cmd" + if printableLayers[1].String() != expectedSecondLayer { + t.Errorf("expected %s got %s", expectedSecondLayer, printableLayers[1].String()) + } + + +} + + diff --git a/runtime/ui/viewmodels/tree_view_model.go b/runtime/ui/viewmodels/tree_view_model.go index 22ed392..52b35a4 100644 --- a/runtime/ui/viewmodels/tree_view_model.go +++ b/runtime/ui/viewmodels/tree_view_model.go @@ -9,11 +9,12 @@ import ( "github.com/wagoodman/dive/dive/image" ) +//go:generate faux --interface FilterModel --output fakes/fake_filter_model.go type FilterModel interface { SetFilter(r *regexp.Regexp) GetFilter() *regexp.Regexp } - +//go:generate faux --interface LayersModel --output fakes/fake_layers_model.go type LayersModel interface { SetLayerIndex(index int) bool GetCompareIndicies() filetree.TreeIndexKey @@ -23,16 +24,31 @@ type LayersModel interface { SwitchLayerMode() error } +//go:generate faux --interface TreeCache --output fakes/fake_tree_cache.go +type TreeCache interface { + GetTree(key filetree.TreeIndexKey) (TreeModel, error) +} + +//go:generate faux --interface TreeModel --output fakes/fake_tree_model.go +type TreeModel interface { + StringBetween(start, stop int, showAttributes bool) string + VisitDepthParentFirst(visitor filetree.Visitor, evaluator filetree.VisitEvaluator) error + VisitDepthChildFirst(visitor filetree.Visitor, evaluator filetree.VisitEvaluator) error + RemovePath(path string) error + VisibleSize() int + +} + type TreeViewModel struct { - currentTree *filetree.FileTree - cache filetree.Comparer + currentTree TreeModel + cache TreeCache hiddenDiffTypes []bool // Make this an interface that is composed with the FilterView FilterModel LayersModel } -func NewTreeViewModel(cache filetree.Comparer, lModel LayersModel, fModel FilterModel) (*TreeViewModel, error) { +func NewTreeViewModel(cache TreeCache, lModel LayersModel, fModel FilterModel) (*TreeViewModel, error) { curTreeIndex := filetree.NewTreeIndexKey(0, 0, 0, 0) tree, err := cache.GetTree(curTreeIndex) if err != nil { @@ -67,7 +83,7 @@ func (tvm *TreeViewModel) VisibleSize() int { func (tvm *TreeViewModel) SetFilter(filterRegex *regexp.Regexp) { tvm.FilterModel.SetFilter(filterRegex) - if err := tvm.FilterUpdate(); err != nil { + if err := tvm.filterUpdate(); err != nil { panic(err) } } @@ -77,16 +93,15 @@ func (tvm *TreeViewModel) SetFilter(filterRegex *regexp.Regexp) { // TODO: handle errors correctly func (tvm *TreeViewModel) ToggleHiddenFileType(filetype filetree.DiffType) bool { tvm.hiddenDiffTypes[filetype] = !tvm.hiddenDiffTypes[filetype] - if err := tvm.FilterUpdate(); err != nil { + if err := tvm.filterUpdate(); err != nil { //panic(err) return false } return true - } // TODO: maek this method private, cant think of a reason for this to be public -func (tvm *TreeViewModel) FilterUpdate() error { +func (tvm *TreeViewModel) filterUpdate() error { logrus.Debug("Updating filter!!!") // keep the t selection in parity with the current DiffType selection filter := tvm.GetFilter() @@ -100,15 +115,19 @@ func (tvm *TreeViewModel) FilterUpdate() error { } } + if len(node.Children) > 0 { + node.Data.ViewInfo.Hidden = true + } + if filter != nil && !node.Data.ViewInfo.Hidden { // hide nodes that do not match the current file filter regex (also don't unhide nodes that are already hidden) match := filter.FindString(node.Path()) - node.Data.ViewInfo.Hidden = len(match) == 0 + node.Data.ViewInfo.Hidden = len(match) != 0 } return nil }, nil) if err != nil { - logrus.Errorf("unable to propagate t model tree: %+v", err) + logrus.Errorf("error updating filter on current tree: %s", err) return err } @@ -159,7 +178,7 @@ func (tvm *TreeViewModel) setCurrentTree(key filetree.TreeIndexKey) error { } tvm.currentTree = newTree - if err := tvm.FilterUpdate(); err != nil { + if err := tvm.filterUpdate(); err != nil { return err } return nil diff --git a/runtime/ui/viewmodels/tree_view_model_test.go b/runtime/ui/viewmodels/tree_view_model_test.go new file mode 100644 index 0000000..2e95209 --- /dev/null +++ b/runtime/ui/viewmodels/tree_view_model_test.go @@ -0,0 +1,421 @@ +package viewmodels_test + +import ( + tar "archive/tar" + "github.com/wagoodman/dive/dive/filetree" + "github.com/wagoodman/dive/runtime/ui/viewmodels" + "github.com/wagoodman/dive/runtime/ui/viewmodels/fakes" + "os" + "regexp" + "testing" +) + +func TestTreeViewModel(t *testing.T) { + testStringBetween(t) + testVisitDepthParentFirst(t) + testVisitDepthChildFirst(t) + testRemovePath(t) + testVisibleSize(t) + testSetFilter(t) + testToggleHiddenFileType(t) + testSetLayerIndex(t) + testSwitchLayerMode(t) +} + +func testStringBetween(t *testing.T) { + fModel := &fakes.FilterModel{} + lModel := &fakes.LayersModel{} + tCache := &fakes.TreeCache{} + tModel := &fakes.TreeModel{} + tCache.GetTreeCall.Returns.TreeModel = tModel + expectedString := "the string between" + tModel.StringBetweenCall.Returns.String = expectedString + + tvm, err := viewmodels.NewTreeViewModel(tCache, lModel, fModel) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + out := tvm.StringBetween(1,2,true) + if out != expectedString { + t.Fatalf("expected: %s got: %s", expectedString, out) + } + + if tModel.StringBetweenCall.CallCount != 1 { + t.Error("expected StringBetween to be called on TreeModel") + } + + if tModel.StringBetweenCall.Receives.Start != 1 { + t.Fatalf("expected start to be passed through as 1, got %d", tModel.StringBetweenCall.Receives.Start) + } + + if tModel.StringBetweenCall.Receives.Stop != 2 { + t.Fatalf("expected start to be passed through as 2, got %d", tModel.StringBetweenCall.Receives.Stop) + } + + if !tModel.StringBetweenCall.Receives.ShowAttributes { + t.Fatalf("expected start to be passed through as true, got %t", tModel.StringBetweenCall.Receives.ShowAttributes) + } +} + +func testVisitDepthChildFirst(t *testing.T) { + fModel := &fakes.FilterModel{} + lModel := &fakes.LayersModel{} + tCache := &fakes.TreeCache{} + tModel := &fakes.TreeModel{} + tCache.GetTreeCall.Returns.TreeModel = tModel + + tvm, err := viewmodels.NewTreeViewModel(tCache, lModel, fModel) + errorCheck(t, err) + + visitor := func(*filetree.FileNode) error { return nil } + evaluator := func(*filetree.FileNode) bool { return true } + err = tvm.VisitDepthChildFirst(visitor, evaluator) + errorCheck(t, err) + + if tModel.VisitDepthChildFirstCall.CallCount != 1 { + t.Fatalf("unexpected number of calls on TreeModel, expected 1 got %d", tModel.VisitDepthParentFirstCall.CallCount) + } +} + +func testVisitDepthParentFirst(t *testing.T) { + fModel := &fakes.FilterModel{} + lModel := &fakes.LayersModel{} + tCache := &fakes.TreeCache{} + tModel := &fakes.TreeModel{} + tCache.GetTreeCall.Returns.TreeModel = tModel + + tvm, err := viewmodels.NewTreeViewModel(tCache, lModel, fModel) + errorCheck(t, err) + + visitor := func(*filetree.FileNode) error { return nil } + evaluator := func(*filetree.FileNode) bool { return true } + err = tvm.VisitDepthParentFirst(visitor, evaluator) + errorCheck(t, err) + + if tModel.VisitDepthParentFirstCall.CallCount != 1 { + t.Fatalf("unexpected number of calls on TreeModel, expected 1 got %d", tModel.VisitDepthParentFirstCall.CallCount) + } +} + +func testRemovePath(t *testing.T) { + fModel := &fakes.FilterModel{} + lModel := &fakes.LayersModel{} + tCache := &fakes.TreeCache{} + tModel := &fakes.TreeModel{} + tCache.GetTreeCall.Returns.TreeModel = tModel + + tvm, err := viewmodels.NewTreeViewModel(tCache, lModel, fModel) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + removePathArg := "/some/path" + err = tvm.RemovePath(removePathArg) + errorCheck(t, err) + + if removePathArg != tModel.RemovePathCall.Receives.Path { + t.Fatalf("expected: %s recieved: %s", removePathArg, tModel.RemovePathCall.Receives.Path) + } +} + +func testVisibleSize(t *testing.T) { + fModel := &fakes.FilterModel{} + lModel := &fakes.LayersModel{} + tCache := &fakes.TreeCache{} + tModel := &fakes.TreeModel{} + tCache.GetTreeCall.Returns.TreeModel = tModel + tModel.VisibleSizeCall.Returns.Int = 15 + + tvm, err := viewmodels.NewTreeViewModel(tCache, lModel, fModel) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + size := tvm.VisibleSize() + + if size != 15 { + t.Fatalf("expected %d got %d", 15, size) + } + + if tModel.VisibleSizeCall.CallCount != 1 { + t.Fatalf("expected VisibleSize to be called 1 times, got %d", tModel.VisibleSizeCall.CallCount) + } +} + +func testSetFilter(t *testing.T) { + fModel := viewmodels.NewFilterViewModel(nil) + lModel := &fakes.LayersModel{} + tCache := &fakes.TreeCache{} + tModel := filetree.NewFileTree() + _,_, err := tModel.AddPath("/dirA/dirB/file", filetree.FileInfo { + Path: "/dirA/dirB/file", + TypeFlag: tar.TypeReg, + Size: 100, + Mode: os.ModePerm, + Uid: 200, + Gid: 200, + IsDir: false, + }) + errorCheck(t,err) + + + _,_,err = tModel.AddPath("/dirA/dirC/other-thing", filetree.FileInfo { + Path: "/dirA/dirC/other-thing", + TypeFlag: tar.TypeReg, + Size: 1000, + Mode: os.ModePerm, + Uid: 200, + Gid: 200, + IsDir: false, + }) + errorCheck(t,err) + + _,_, err = tModel.AddPath("/dirA/dirB/other-file", filetree.FileInfo { + Path: "/dirA/dirB/other-file", + TypeFlag: tar.TypeReg, + Size: 1000, + Mode: os.ModePerm, + Uid: 200, + Gid: 200, + IsDir: false, + }) + errorCheck(t,err) + + tCache.GetTreeCall.Returns.TreeModel = tModel + + tvm, err := viewmodels.NewTreeViewModel(tCache, lModel, fModel) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + hiddenNodes, err := getHiddenNodes(tModel) + errorCheck(t, err) + if len(hiddenNodes) != 0 { + t.Fatalf("expected no nodes to be hidden, got %d", len(hiddenNodes)) + } + + r := regexp.MustCompile("other-file") + tvm.SetFilter(r) + + hiddenNodes, err = getHiddenNodes(tModel) + errorCheck(t, err) + if len(hiddenNodes) != 1 { + t.Fatalf("expected 1 to be hidden, got %d", len(hiddenNodes)) + } + if hiddenNodes[0].Name != "other-file" { + t.Fatalf("expected 'other-file' to be hidden, got %s", hiddenNodes[0].Name) + } + + // Check if directories where all children are hidden are hidden as well + r = regexp.MustCompile("file") + tvm.SetFilter(r) + + hiddenNodes, err = getHiddenNodes(tModel) + errorCheck(t, err) + if len(hiddenNodes) != 3 { + t.Fatalf("expected 3 nodes to be hidden, got %d", len(hiddenNodes)) + } + + hiddenNames := []string{} + for _, node := range hiddenNodes { + hiddenNames = append(hiddenNames, node.Name) + } + + if !containsString("other-file", hiddenNames) { + t.Fatalf("expected %#v to contain other-file", hiddenNames) + } + + if !containsString("file", hiddenNames) { + t.Fatalf("expected %#v to contain file", hiddenNames) + } + + if !containsString("dirB", hiddenNames) { + t.Fatalf("expected %#v to contain dirB", hiddenNames) + } +} + +func testToggleHiddenFileType(t *testing.T) { + fModel := viewmodels.NewFilterViewModel(nil) + lModel := &fakes.LayersModel{} + tCache := &fakes.TreeCache{} + tModel := filetree.NewFileTree() + _,_,err := tModel.AddPath("/dirA/file", filetree.FileInfo { + Path: "/dirA/file", + TypeFlag: tar.TypeReg, + Size: 100, + Mode: os.ModePerm, + Uid: 200, + Gid: 200, + IsDir: false, + }) + errorCheck(t,err) + + tModel.Root.Children["dirA"].Children["file"].Data.DiffType = filetree.Added + tCache.GetTreeCall.Returns.TreeModel = tModel + + tvm, err := viewmodels.NewTreeViewModel(tCache, lModel, fModel) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + hiddenNodes, err := getHiddenNodes(tModel) + errorCheck(t, err) + if len(hiddenNodes) != 0 { + t.Fatalf("expected no nodes to be hidden, got %d", len(hiddenNodes)) + } + + tvm.ToggleHiddenFileType(filetree.Added) + + hiddenNodes, err = getHiddenNodes(tModel) + errorCheck(t, err) + if len(hiddenNodes) != 2 { + t.Fatalf("expected 2 to be hidden, got %d", len(hiddenNodes)) + } + hiddenNames := []string{} + for _, node := range hiddenNodes { + hiddenNames = append(hiddenNames, node.Name) + } + + if !containsString("file", hiddenNames) { + t.Fatalf("expected 'file' to be hidden in %#v", hiddenNames) + } + + if !containsString("dirA", hiddenNames) { + t.Fatalf("expected 'file' to be hidden in %#v", hiddenNames) + } + +} + +func testSetLayerIndex(t *testing.T) { + fModel := &fakes.FilterModel{} + lModel := &fakes.LayersModel{} + tCache := &fakes.TreeCache{} + tModel := &a |