add mock based viewmodel tests

Signed-off-by: dwillist <dthornton@vmware.com>
This commit is contained in:
dwillist 2021-01-20 17:30:33 -05:00
commit 43c0b96bac
11 changed files with 986 additions and 14 deletions

View file

@ -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:

View file

@ -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)
}

View file

@ -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

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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())
}
}

View file

@ -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())
}
}

View file

@ -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

View file

@ -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,
Size: 100,
Mode: os.ModePerm,
Uid: 200,
Gid: 200,
IsDir: false,
})
errorCheck(t,err)
collapsedNodes, err := getCollapsedNodes(secondTreeModel)
errorCheck(t, err)
if len(collapsedNodes) != 0 {
t.Fatalf("expected no nodes to be collapsed got %v", collapsedNodes)
}
key := filetree.NewTreeIndexKey(1,2,3,4)
tCache.GetTreeCall.Stub = func(k filetree.TreeIndexKey) (viewmodels.TreeModel, error) {
if k == key {
return secondTreeModel,nil
}
return firstTreeModel, nil
}
tvm, err := viewmodels.NewTreeViewModel(tCache, lModel, fModel)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
lModel.GetCompareIndiciesCall.Returns.TreeIndexKey = filetree.NewTreeIndexKey(1,2,3,4)
err = tvm.SwitchLayerMode()
errorCheck(t, err)
collapsedNodes, err = getCollapsedNodes(secondTreeModel)
errorCheck(t, err)
if len(collapsedNodes) != 1 {
t.Fatalf("expected no nodes to be collapsed got %v", collapsedNodes)
}
if collapsedNodes[0].Name != "collapsed-dir" {
t.Fatalf("expected 'collapsed-dir' to be collapsed got %s", collapsedNodes[0].Name)
}
}
func containsString(needle string, haystack []string) bool {
for _, s := range haystack {
if s == needle {
return true
}
}
return false
}
func getHiddenNodes(tModel *filetree.FileTree) ([]*filetree.FileNode, error) {
result := []*filetree.FileNode{}
err := tModel.VisitDepthParentFirst(func(node *filetree.FileNode) error {
if node.Data.ViewInfo.Hidden {
result = append(result, node)
}
return nil
}, nil)
return result, err
}
func getCollapsedNodes(tModel *filetree.FileTree) ([]*filetree.FileNode, error) {
result := []*filetree.FileNode{}
err := tModel.VisitDepthParentFirst(func(node *filetree.FileNode) error {
if node.Data.ViewInfo.Collapsed {
result = append(result, node)
}
return nil
},nil)
return result, err
}
func errorCheck(t *testing.T, err error) {
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
}