dive/runtime/ui/viewmodel/filetree_test.go
Alex Goodman d5e8a92968
Rework CI validation workflow and makefile (#460)
* rework CI validation workflow and makefile

* enable push

* fix job names

* fix license check

* fix snapshot builds

* fix acceptance tests

* fix linting

* disable pull request event

* rework windows runner caching

* disable release pipeline and add issue templates
2023-07-06 22:01:46 -04:00

399 lines
8.9 KiB
Go

package viewmodel
import (
"bytes"
"os"
"path/filepath"
"regexp"
"testing"
"github.com/fatih/color"
"github.com/sergi/go-diff/diffmatchpatch"
"github.com/wagoodman/dive/dive/filetree"
"github.com/wagoodman/dive/dive/image/docker"
"github.com/wagoodman/dive/runtime/ui/format"
)
const allowTestDataCapture = false
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func testCaseDataFilePath(name string) string {
return filepath.Join("testdata", name+".txt")
}
func helperLoadBytes(t *testing.T) []byte {
path := testCaseDataFilePath(t.Name())
theBytes, err := os.ReadFile(path)
if err != nil {
t.Fatalf("unable to load test data ('%s'): %+v", t.Name(), err)
}
return theBytes
}
func helperCaptureBytes(t *testing.T, data []byte) {
if !allowTestDataCapture {
t.Fatalf("cannot capture data in test mode: %s", t.Name())
}
path := testCaseDataFilePath(t.Name())
err := os.WriteFile(path, data, 0644)
if err != nil {
t.Fatalf("unable to save test data ('%s'): %+v", t.Name(), err)
}
}
func helperCheckDiff(t *testing.T, expected, actual []byte) {
if !bytes.Equal(expected, actual) {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(string(expected), string(actual), true)
t.Errorf(dmp.DiffPrettyText(diffs))
t.Errorf("%s: bytes mismatch", t.Name())
}
}
func assertTestData(t *testing.T, actualBytes []byte) {
path := testCaseDataFilePath(t.Name())
if !fileExists(path) {
if allowTestDataCapture {
helperCaptureBytes(t, actualBytes)
} else {
t.Fatalf("missing test data: %s", path)
}
}
expectedBytes := helperLoadBytes(t)
helperCheckDiff(t, expectedBytes, actualBytes)
}
func initializeTestViewModel(t *testing.T) *FileTree {
result := docker.TestAnalysisFromArchive(t, "../../../.data/test-docker-image.tar")
cache := filetree.NewComparer(result.RefTrees)
errors := cache.BuildCache()
if len(errors) > 0 {
t.Fatalf("%s: unable to build cache: %d errors", t.Name(), len(errors))
}
format.Selected = color.New(color.ReverseVideo, color.Bold).SprintFunc()
treeStack, failedPaths, err := filetree.StackTreeRange(result.RefTrees, 0, 0)
if len(failedPaths) > 0 {
t.Errorf("expected no filepath errors, got %d", len(failedPaths))
}
if err != nil {
t.Fatalf("%s: unable to stack trees: %v", t.Name(), err)
}
vm, err := NewFileTreeViewModel(treeStack, result.RefTrees, cache)
if err != nil {
t.Fatalf("%s: unable to create tree ViewModel: %+v", t.Name(), err)
}
return vm
}
func runTestCase(t *testing.T, vm *FileTree, width, height int, filterRegex *regexp.Regexp) {
err := vm.Update(filterRegex, width, height)
if err != nil {
t.Errorf("failed to update viewmodel: %v", err)
}
err = vm.Render()
if err != nil {
t.Errorf("failed to render viewmodel: %v", err)
}
assertTestData(t, vm.Buffer.Bytes())
}
func checkError(t *testing.T, err error, message string) {
if err != nil {
t.Errorf(message+": %+v", err)
}
}
func TestFileTreeGoCase(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 1000
vm.Setup(0, height)
vm.ShowAttributes = true
runTestCase(t, vm, width, height, nil)
}
func TestFileTreeNoAttributes(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 1000
vm.Setup(0, height)
vm.ShowAttributes = false
runTestCase(t, vm, width, height, nil)
}
func TestFileTreeRestrictedHeight(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 20
vm.Setup(0, height)
vm.ShowAttributes = false
runTestCase(t, vm, width, height, nil)
}
func TestFileTreeDirCollapse(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 100
vm.Setup(0, height)
vm.ShowAttributes = true
// collapse /bin
err := vm.ToggleCollapse(nil)
checkError(t, err, "unable to collapse /bin")
moved := vm.CursorDown()
if !moved {
t.Error("unable to cursor down")
}
moved = vm.CursorDown()
if !moved {
t.Error("unable to cursor down")
}
// collapse /etc
err = vm.ToggleCollapse(nil)
checkError(t, err, "unable to collapse /etc")
runTestCase(t, vm, width, height, nil)
}
func TestFileTreeDirCollapseAll(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 100
vm.Setup(0, height)
vm.ShowAttributes = true
err := vm.ToggleCollapseAll()
checkError(t, err, "unable to collapse all dir")
runTestCase(t, vm, width, height, nil)
}
func TestFileTreeSelectLayer(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 100
vm.Setup(0, height)
vm.ShowAttributes = true
// collapse /bin
err := vm.ToggleCollapse(nil)
checkError(t, err, "unable to collapse /bin")
// select the next layer, compareMode = layer
err = vm.SetTreeByLayer(0, 0, 1, 1)
if err != nil {
t.Errorf("unable to SetTreeByLayer: %v", err)
}
runTestCase(t, vm, width, height, nil)
}
func TestFileShowAggregateChanges(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 100
vm.Setup(0, height)
vm.ShowAttributes = true
// collapse /bin
err := vm.ToggleCollapse(nil)
checkError(t, err, "unable to collapse /bin")
// select the next layer, compareMode = layer
err = vm.SetTreeByLayer(0, 0, 1, 13)
checkError(t, err, "unable to SetTreeByLayer")
runTestCase(t, vm, width, height, nil)
}
func TestFileTreePageDown(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 10
vm.Setup(0, height)
vm.ShowAttributes = true
err := vm.Update(nil, width, height)
checkError(t, err, "unable to update")
err = vm.PageDown()
checkError(t, err, "unable to page down")
err = vm.PageDown()
checkError(t, err, "unable to page down")
err = vm.PageDown()
checkError(t, err, "unable to page down")
runTestCase(t, vm, width, height, nil)
}
func TestFileTreePageUp(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 10
vm.Setup(0, height)
vm.ShowAttributes = true
// these operations have a render step for intermediate results, which require at least one update to be done first
err := vm.Update(nil, width, height)
checkError(t, err, "unable to update")
err = vm.PageDown()
checkError(t, err, "unable to page down")
err = vm.PageDown()
checkError(t, err, "unable to page down")
err = vm.PageUp()
checkError(t, err, "unable to page up")
runTestCase(t, vm, width, height, nil)
}
func TestFileTreeDirCursorRight(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 100
vm.Setup(0, height)
vm.ShowAttributes = true
// collapse /bin
err := vm.ToggleCollapse(nil)
checkError(t, err, "unable to collapse /bin")
moved := vm.CursorDown()
if !moved {
t.Error("unable to cursor down")
}
moved = vm.CursorDown()
if !moved {
t.Error("unable to cursor down")
}
// collapse /etc
err = vm.ToggleCollapse(nil)
checkError(t, err, "unable to collapse /etc")
// expand /etc
err = vm.CursorRight(nil)
checkError(t, err, "unable to cursor right")
runTestCase(t, vm, width, height, nil)
}
func TestFileTreeFilterTree(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 1000
vm.Setup(0, height)
vm.ShowAttributes = true
regex, err := regexp.Compile("network")
if err != nil {
t.Errorf("could not create filter regex: %+v", err)
}
runTestCase(t, vm, width, height, regex)
}
func TestFileTreeHideAddedRemovedModified(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 100
vm.Setup(0, height)
vm.ShowAttributes = true
// collapse /bin
err := vm.ToggleCollapse(nil)
checkError(t, err, "unable to collapse /bin")
// select the 7th layer, compareMode = layer
err = vm.SetTreeByLayer(0, 0, 1, 7)
if err != nil {
t.Errorf("unable to SetTreeByLayer: %v", err)
}
// hide added files
vm.ToggleShowDiffType(filetree.Added)
// hide modified files
vm.ToggleShowDiffType(filetree.Modified)
// hide removed files
vm.ToggleShowDiffType(filetree.Removed)
runTestCase(t, vm, width, height, nil)
}
func TestFileTreeHideUnmodified(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 100
vm.Setup(0, height)
vm.ShowAttributes = true
// collapse /bin
err := vm.ToggleCollapse(nil)
checkError(t, err, "unable to collapse /bin")
// select the 7th layer, compareMode = layer
err = vm.SetTreeByLayer(0, 0, 1, 7)
if err != nil {
t.Errorf("unable to SetTreeByLayer: %v", err)
}
// hide unmodified files
vm.ToggleShowDiffType(filetree.Unmodified)
runTestCase(t, vm, width, height, nil)
}
func TestFileTreeHideTypeWithFilter(t *testing.T) {
vm := initializeTestViewModel(t)
width, height := 100, 100
vm.Setup(0, height)
vm.ShowAttributes = true
// collapse /bin
err := vm.ToggleCollapse(nil)
checkError(t, err, "unable to collapse /bin")
// select the 7th layer, compareMode = layer
err = vm.SetTreeByLayer(0, 0, 1, 7)
if err != nil {
t.Errorf("unable to SetTreeByLayer: %v", err)
}
// hide added files
vm.ToggleShowDiffType(filetree.Added)
regex, err := regexp.Compile("saved")
if err != nil {
t.Errorf("could not create filter regex: %+v", err)
}
runTestCase(t, vm, width, height, regex)
}