refactor: move V1 code into v1/ subdirectory

- git mv cmd/ internal/ pkg/ test/ go.mod go.sum Makefile build.sh docs/ v1/
- Create v1/README.md with V1 documentation
- Update root README for V1 + V2 structure
- V1 still builds (cd v1 && go build ./cmd/hostkeeper) and 105 tests pass
- Root is now clean for V2 development
This commit is contained in:
swanadiva
2026-07-07 11:56:27 +07:00
parent 8ebdebedc8
commit 847989df75
68 changed files with 58 additions and 116 deletions
+95
View File
@@ -0,0 +1,95 @@
package tui_test
import (
"testing"
"time"
"git.tukangketik.id/swanadiva/hostkeeper/pkg/tui"
)
func TestErrorBannerShowHide(t *testing.T) {
banner := tui.NewErrorBanner(tui.SevError)
// Initially not visible
if banner.IsVisible() {
t.Error("Banner should not be visible initially")
}
// Show the banner
banner.Show("Test Error", "Something went wrong", "Check logs", "Restart app")
if !banner.IsVisible() {
t.Error("Banner should be visible after Show()")
}
// Verify content
if banner.Title != "Test Error" {
t.Errorf("Title = %q, want %q", banner.Title, "Test Error")
}
if banner.Detail != "Something went wrong" {
t.Errorf("Detail = %q, want %q", banner.Detail, "Something went wrong")
}
if len(banner.Hints) != 2 {
t.Errorf("Hints has %d items, want 2", len(banner.Hints))
}
// Hide the banner
banner.Hide()
if banner.IsVisible() {
t.Error("Banner should not be visible after Hide()")
}
}
func TestErrorBannerAutoDismiss(t *testing.T) {
banner := tui.NewErrorBanner(tui.SevWarning)
banner.AutoDismiss = true
banner.DismissAfter = 100 * time.Millisecond
banner.Show("Test Warning", "Something")
if !banner.IsVisible() {
t.Error("Banner should be visible after Show()")
}
// Wait for auto-dismiss
time.Sleep(150 * time.Millisecond)
banner.Update()
if banner.IsVisible() {
t.Error("Banner should be auto-dismissed after delay")
}
}
func TestErrorBannerSeverity(t *testing.T) {
tests := []struct {
severity tui.ErrorSeverity
name string
}{
{tui.SevError, "error"},
{tui.SevWarning, "warning"},
{tui.SevInfo, "info"},
}
for _, tt := range tests {
banner := tui.NewErrorBanner(tt.severity)
if banner.Severity != tt.severity {
t.Errorf("NewErrorBanner(%v).Severity = %v, want %v", tt.name, banner.Severity, tt.severity)
}
}
}
func TestErrorBannerView(t *testing.T) {
banner := tui.NewErrorBanner(tui.SevError)
banner.Show("Error Title", "Error detail", "Hint 1", "Hint 2")
// Test that View returns non-empty string
output := banner.View(80)
if output == "" {
t.Error("View() returned empty string")
}
// Test that View returns empty string when not visible
banner.Hide()
output = banner.View(80)
if output != "" {
t.Error("View() should return empty string when not visible")
}
}
+92
View File
@@ -0,0 +1,92 @@
package tui_test
import (
"testing"
"git.tukangketik.id/swanadiva/hostkeeper/pkg/tui"
)
// 5.1 WrapFooter short — single line
func TestWrapFooterShort(t *testing.T) {
result := tui.WrapFooter("short footer", 80)
if result != "short footer" {
t.Errorf("WrapFooter short = %q, want %q", result, "short footer")
}
}
// 5.2 WrapFooter long — multi-line
func TestWrapFooterLong(t *testing.T) {
// Footer longer than 30 chars should wrap
footer := "key1 key2 key3 key4 key5 key6 key7 key8 key9 key10"
result := tui.WrapFooter(footer, 30)
if result == footer {
t.Error("WrapFooter should wrap long footer")
}
}
// 5.3 WrapFooter empty
func TestWrapFooterEmpty(t *testing.T) {
result := tui.WrapFooter("", 80)
if result != "" {
t.Errorf("WrapFooter empty = %q, want %q", result, "")
}
}
// 5.4 ClampWidth over max
func TestClampWidthOver(t *testing.T) {
result := tui.ClampWidth(200, 80)
if result > 74 { // 80 - 6 (boxOverhead)
t.Errorf("ClampWidth(200, 80) = %d, should be <= 74", result)
}
}
// 5.5 ClampWidth under min
func TestClampWidthUnder(t *testing.T) {
result := tui.ClampWidth(5, 80)
if result < 20 {
t.Errorf("ClampWidth(5, 80) = %d, should be >= 20", result)
}
}
// 5.6 ClampWidth in range
func TestClampWidthInRange(t *testing.T) {
result := tui.ClampWidth(50, 80)
if result != 50 {
t.Errorf("ClampWidth(50, 80) = %d, want 50", result)
}
}
// 5.7 TruncateStr short
func TestTruncateStrShort(t *testing.T) {
result := tui.TruncateStr("hello", 20)
if result != "hello" {
t.Errorf("TruncateStr short = %q, want %q", result, "hello")
}
}
// 5.8 TruncateStr long
func TestTruncateStrLong(t *testing.T) {
result := tui.TruncateStr("this is a very long string that should be truncated", 10)
if result == "this is a very long string that should be truncated" {
t.Error("TruncateStr should truncate long string")
}
if len(result) > 12 { // 11 content + 1 ellipsis
t.Errorf("TruncateStr result too long: %d chars", len(result))
}
}
// 5.9 TruncateStr empty
func TestTruncateStrEmpty(t *testing.T) {
result := tui.TruncateStr("", 20)
if result != "" {
t.Errorf("TruncateStr empty = %q, want %q", result, "")
}
}
// 5.10 TruncateStr unicode
func TestTruncateStrUnicode(t *testing.T) {
result := tui.TruncateStr("こんにちは世界", 5)
if result == "こんにちは世界" {
t.Error("TruncateStr should truncate unicode string")
}
}
+71
View File
@@ -0,0 +1,71 @@
package tui_test
import (
"testing"
"git.tukangketik.id/swanadiva/hostkeeper/pkg/tui"
)
func TestGetTheme(t *testing.T) {
// Test getting existing themes
tests := []struct {
name string
expected string
}{
{"dark", "dark"},
{"light", "light"},
{"dracula", "dracula"},
}
for _, tt := range tests {
theme := tui.GetTheme(tt.name)
if theme.Name != tt.expected {
t.Errorf("GetTheme(%q) = %q, want %q", tt.name, theme.Name, tt.expected)
}
}
// Test getting non-existing theme defaults to dark
theme := tui.GetTheme("nonexistent")
if theme.Name != "dark" {
t.Errorf("GetTheme(nonexistent) = %q, want %q", theme.Name, "dark")
}
}
func TestSetTheme(t *testing.T) {
// Set theme to light
tui.SetTheme("light")
active := tui.GetActiveTheme()
if active.Name != "light" {
t.Errorf("After SetTheme(light), GetActiveTheme() = %q, want %q", active.Name, "light")
}
// Set theme back to dark
tui.SetTheme("dark")
active = tui.GetActiveTheme()
if active.Name != "dark" {
t.Errorf("After SetTheme(dark), GetActiveTheme() = %q, want %q", active.Name, "dark")
}
}
func TestGetActiveTheme(t *testing.T) {
// Default should be dark
active := tui.GetActiveTheme()
if active.Name != "dark" {
t.Errorf("GetActiveTheme() = %q, want %q", active.Name, "dark")
}
}
func TestThemeRegistry(t *testing.T) {
// Test that all themes are registered
expectedThemes := []string{"dark", "light", "dracula"}
for _, name := range expectedThemes {
if _, ok := tui.Themes[name]; !ok {
t.Errorf("Theme %q not found in Themes map", name)
}
}
// Test theme count
if len(tui.Themes) != 3 {
t.Errorf("Themes map has %d entries, want 3", len(tui.Themes))
}
}