package tui
import (
"strings"
"testing"
"github.com/standardbeagle/brummer/internal/logs"
"github.com/standardbeagle/brummer/internal/process"
"github.com/standardbeagle/brummer/pkg/events"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestViewConstants tests the view constants are defined
func TestViewConstants(t *testing.T) {
views := []View{
ViewScriptSelector,
ViewProcesses,
ViewLogs,
ViewErrors,
ViewURLs,
ViewSettings,
}
// Verify all views have different values
viewMap := make(map[View]bool)
for _, view := range views {
assert.False(t, viewMap[view], "Duplicate view value found: %v", view)
viewMap[view] = true
}
assert.Len(t, viewMap, len(views))
}
// TestModelCreation tests creating a new TUI model
func TestModelCreation(t *testing.T) {
// Create required dependencies
tempDir := t.TempDir()
eventBus := events.NewEventBus()
logStore := logs.NewStore(1000, eventBus)
defer logStore.Close()
processMgr, err := process.NewManager(tempDir, eventBus, false)
require.NoError(t, err)
// Test creating model with default view
model := NewModel(processMgr, logStore, eventBus, nil, nil, 7777, nil)
require.NotNil(t, model)
// Test creating model with specific view
modelWithView := NewModelWithView(processMgr, logStore, eventBus, nil, nil, 7777, ViewLogs, false, nil)
require.NotNil(t, modelWithView)
}
// TestModelViewSwitching tests switching between views
func TestModelViewSwitching(t *testing.T) {
tempDir := t.TempDir()
eventBus := events.NewEventBus()
logStore := logs.NewStore(1000, eventBus)
defer logStore.Close()
processMgr, err := process.NewManager(tempDir, eventBus, false)
require.NoError(t, err)
_ = NewModel(processMgr, logStore, eventBus, nil, nil, 7777, nil)
// Test view navigation
views := []View{
ViewScriptSelector,
ViewProcesses,
ViewLogs,
ViewErrors,
ViewURLs,
ViewSettings,
}
for _, targetView := range views {
// Switch to the view (we would normally do this through Update with key messages)
// For testing, we'll verify the view constants are accessible
assert.NotEmpty(t, string(targetView)) // Verify view is valid
}
}
// TestFilterValidation tests log filtering functionality
func TestFilterValidation(t *testing.T) {
testCases := []struct {
pattern string
valid bool
name string
}{
{"simple", true, "simple text"},
{"error.*", true, "regex pattern"},
{"[a-z]+", true, "character class"},
{"test|debug", true, "alternation"},
{"\\d+", true, "escape sequence"},
{"[invalid", false, "unclosed bracket"},
{"*invalid", false, "invalid regex"},
{"", true, "empty pattern"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Test if pattern would be valid for filtering
// In the real implementation, this would be handled by the logs package
if tc.valid {
assert.True(t, len(tc.pattern) >= 0) // Simple validation
}
})
}
}
// TestKeyMappings tests key mapping constants
func TestKeyMappings(t *testing.T) {
// Test that common key bindings are reasonable strings
commonKeys := []string{
"q", // quit
"tab", // switch views
"enter", // select
"esc", // back
"j", "k", // vim-style navigation
"up", "down", // arrow navigation
}
for _, key := range commonKeys {
// Verify keys are non-empty strings
assert.NotEmpty(t, key)
assert.True(t, len(key) > 0)
}
}
// TestSlashCommands tests slash command parsing
func TestSlashCommands(t *testing.T) {
testCases := []struct {
input string
expected bool
name string
}{
{"/show error", true, "show command"},
{"/hide debug", true, "hide command"},
{"/toggle-proxy", true, "toggle command"},
{"/clear", true, "clear command"},
{"/help", true, "help command"},
{"not a command", false, "regular text"},
{"/", false, "just slash"},
{"", false, "empty string"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
isCommand := strings.HasPrefix(tc.input, "/") && len(tc.input) > 1
assert.Equal(t, tc.expected, isCommand)
})
}
}
// TestErrorMessageFormatting tests error message display formatting
func TestErrorMessageFormatting(t *testing.T) {
testErrors := []struct {
message string
expected bool
name string
}{
{"Error: File not found", true, "standard error"},
{"WARNING: Deprecated function", true, "warning message"},
{"FAILED: Test case failed", true, "failure message"},
{"INFO: Process started", false, "info message"},
{"DEBUG: Variable value", false, "debug message"},
{"", false, "empty message"},
}
for _, tc := range testErrors {
t.Run(tc.name, func(t *testing.T) {
// Simple error detection based on keywords
isError := strings.Contains(strings.ToUpper(tc.message), "ERROR") ||
strings.Contains(strings.ToUpper(tc.message), "FAILED") ||
strings.Contains(strings.ToUpper(tc.message), "WARNING")
assert.Equal(t, tc.expected, isError)
})
}
}
// TestLogPriorityFiltering tests priority-based log filtering
func TestLogPriorityFiltering(t *testing.T) {
logEntries := []struct {
content string
isError bool
isHighPrio bool
description string
}{
{"Error: Failed to start", true, true, "error message"},
{"Warning: Deprecated API", false, true, "warning message"},
{"Build failed", false, true, "build failure"},
{"Test passed", false, false, "success message"},
{"Debug info", false, false, "debug message"},
{"Starting process", false, false, "info message"},
}
for _, entry := range logEntries {
t.Run(entry.description, func(t *testing.T) {
// Test priority detection logic
content := strings.ToLower(entry.content)
isHighPriority := entry.isError ||
strings.Contains(content, "error") ||
strings.Contains(content, "warning") ||
strings.Contains(content, "failed") ||
strings.Contains(content, "fatal")
assert.Equal(t, entry.isHighPrio, isHighPriority)
})
}
}
// TestProcessStatusFormatting tests process status display
func TestProcessStatusFormatting(t *testing.T) {
statuses := []struct {
status string
expected string
description string
}{
{"running", "🟢", "running indicator"},
{"stopped", "🔴", "stopped indicator"},
{"failed", "❌", "failed indicator"},
{"success", "✅", "success indicator"},
{"pending", "⏸️", "pending indicator"},
}
for _, status := range statuses {
t.Run(status.description, func(t *testing.T) {
// Test that we can map statuses to display indicators
var indicator string
switch status.status {
case "running":
indicator = "🟢"
case "stopped":
indicator = "🔴"
case "failed":
indicator = "❌"
case "success":
indicator = "✅"
case "pending":
indicator = "⏸️"
default:
indicator = "❓"
}
assert.Equal(t, status.expected, indicator)
})
}
}
// TestURLValidation tests URL parsing and validation
func TestURLValidation(t *testing.T) {
testURLs := []struct {
url string
valid bool
name string
}{
{"http://localhost:3000", true, "local HTTP"},
{"https://localhost:3000", true, "local HTTPS"},
{"http://127.0.0.1:8080", true, "IP address"},
{"https://example.com", true, "external HTTPS"},
{"ftp://example.com", false, "FTP protocol"},
{"not-a-url", false, "invalid URL"},
{"", false, "empty URL"},
{"localhost:3000", false, "missing protocol"},
}
for _, tc := range testURLs {
t.Run(tc.name, func(t *testing.T) {
// Simple URL validation
isValidHTTP := strings.HasPrefix(tc.url, "http://") ||
strings.HasPrefix(tc.url, "https://")
assert.Equal(t, tc.valid, isValidHTTP)
})
}
}
// TestConfigurationDisplay tests configuration display logic
func TestConfigurationDisplay(t *testing.T) {
configs := []struct {
setting string
value interface{}
name string
}{
{"mcp_port", 7777, "MCP port setting"},
{"proxy_port", 19888, "proxy port setting"},
{"debug_mode", false, "debug mode setting"},
{"preferred_package_manager", "npm", "package manager setting"},
}
for _, config := range configs {
t.Run(config.name, func(t *testing.T) {
// Test that we can format configuration values
assert.NotEmpty(t, config.setting)
assert.NotNil(t, config.value)
// Test value conversion to string for display
switch v := config.value.(type) {
case int:
assert.Greater(t, v, 0)
case bool:
// Boolean values are valid
case string:
assert.NotEmpty(t, v)
}
})
}
}
// TestHelpContent tests help content formatting
func TestHelpContent(t *testing.T) {
helpSections := []struct {
section string
content string
}{
{"Navigation", "Tab: Switch views, ↑/↓: Navigate items"},
{"Process Control", "s: Stop process, r: Restart process"},
{"Logs", "/: Search logs, p: Toggle priority filter"},
{"General", "q: Quit, ?: Show help"},
}
for _, section := range helpSections {
t.Run(section.section, func(t *testing.T) {
assert.NotEmpty(t, section.content)
assert.Contains(t, section.content, ":")
})
}
}
// TestColorTheme tests color theme constants
func TestColorTheme(t *testing.T) {
// Test that color-related functionality works
colors := []string{
"running", // green
"stopped", // red
"failed", // red
"success", // green
"pending", // yellow
"error", // red
"warning", // yellow
"info", // blue
}
for _, color := range colors {
t.Run(color+" color", func(t *testing.T) {
assert.NotEmpty(t, color)
// In a real implementation, these would map to actual color values
})
}
}