summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordwillist <dthornton@vmware.com>2021-01-20 17:30:33 -0500
committerdwillist <dthornton@vmware.com>2021-01-20 17:36:15 -0500
commit43c0b96bac08eec89875962c8f2a6da52f2a1a97 (patch)
tree5df4754c6033052bef4747f2cb92151b2d44257e
parenta11e9c1cf23c379dcec0bb45721ec1bca4e2c717 (diff)
add mock based viewmodel tests
Signed-off-by: dwillist <dthornton@vmware.com>
-rw-r--r--Makefile2
-rw-r--r--runtime/ui/app.go12
-rw-r--r--runtime/ui/format/format.go2
-rw-r--r--runtime/ui/viewmodels/fakes/fake_filter_model.go44
-rw-r--r--runtime/ui/viewmodels/fakes/fake_layers_model.go120
-rw-r--r--runtime/ui/viewmodels/fakes/fake_tree_cache.go34
-rw-r--r--runtime/ui/viewmodels/fakes/fake_tree_model.go120
-rw-r--r--runtime/ui/viewmodels/filter_view_model_test.go36
-rw-r--r--runtime/ui/viewmodels/layer_view_model_test.go168
-rw-r--r--runtime/ui/viewmodels/tree_view_model.go41
-rw-r--r--runtime/ui/viewmodels/tree_view_model_test.go421
11 files changed, 986 insertions, 14 deletions
diff --git a/Makefile b/Makefile
index d106cd9..8ba418f 100644
--- a/Makefile
+++ b/Makefile
@@ -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 := &fakes.TreeModel{}
+ tCache.GetTreeCall.Returns.TreeModel = tModel
+
+ tvm, err := viewmodels.NewTreeViewModel(tCache, lModel, fModel)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+
+ testIndex := 10
+ tvm.SetLayerIndex(testIndex)
+ if lModel.SetLayerIndexCall.Receives.Index != testIndex {
+ t.Fatalf("expected index to be %d, got %d", testIndex, lModel.SetLayerIndexCall.Receives.Index)
+ }
+}
+
+func testSwitchLayerMode(t *testing.T) {
+ fModel := &fakes.FilterModel{}
+ lModel := &fakes.LayersModel{}
+ tCache := &fakes.TreeCache{}
+ firstTreeModel := filetree.NewFileTree()
+ _, _, err := firstTreeModel.AddPath("/collapsed-dir/collapsed-file", filetree.FileInfo {
+ Path: "/collapsed-dir/collapsed-file",
+ TypeFlag: tar.TypeReg,
+ Size: 100,
+ Mode: os.ModePerm,
+ Uid: 200,
+ Gid: 200,
+ IsDir: false,
+ })
+ errorCheck(t,err)
+
+ // Second tree has no collapsed or hidden values set
+ secondTreeModel := firstTreeModel.Copy()
+
+ firstTreeModel.Root.Children["collapsed-dir"].Data.ViewInfo.Collapsed = true
+
+ _,_, err = secondTreeModel.AddPath("/visible/visible-file", filetree.FileInfo {
+ Path: "/visible/visible-file",
+ TypeFlag: tar.TypeReg,