1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

feat/matugen3 (#771)

* added matugen 3 terminal templates and logic

fixed version check and light terminal check

refactored json generation

fixed syntax

keep tmp debug

fixed file outputs

fixed syntax issues and implicit passing

added debug stderr output

* moved calls to matugen after template is built correctly

added --json hex

disabled debug message

cleaned up code into modular functions, re-added second full matugen call

fixed args

added shift

commented vs code section

debug changes

* arg format fixes

fixed json import flag

fixed string quotation

fix arg order

* cleaned up

fix cfg naming

* removed mt2.0 templates and refactored worker

removed/replaced matugen 2 templates

fix formatter diffs + consistent styling

* fixed last json output

* fixed syntax error

* vs code templates

* matugen: inject all stock/custom theme colors as overrides
- also some general architectural changes

* dank16: remove vscode enrich option

---------

Co-authored-by: bbedward
This commit is contained in:
Saurabh
2025-11-27 08:34:53 +11:00
committed by GitHub
parent 85704e3947
commit de8f2e6a68
19 changed files with 1187 additions and 929 deletions

View File

@@ -46,3 +46,9 @@ packages:
outpkg: mocks_evdev
interfaces:
EvdevDevice:
github.com/AvengeMedia/DankMaterialShell/core/internal/version:
config:
dir: "internal/mocks/version"
outpkg: mocks_version
interfaces:
VersionFetcher:

View File

@@ -2,7 +2,6 @@ package main
import (
"fmt"
"os"
"strings"
"github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
@@ -26,7 +25,6 @@ func init() {
dank16Cmd.Flags().Bool("alacritty", false, "Output in Alacritty terminal format")
dank16Cmd.Flags().Bool("ghostty", false, "Output in Ghostty terminal format")
dank16Cmd.Flags().Bool("wezterm", false, "Output in Wezterm terminal format")
dank16Cmd.Flags().String("vscode-enrich", "", "Enrich existing VSCode theme file with terminal colors")
dank16Cmd.Flags().String("background", "", "Custom background color")
dank16Cmd.Flags().String("contrast", "dps", "Contrast algorithm: dps (Delta Phi Star, default) or wcag")
}
@@ -44,7 +42,6 @@ func runDank16(cmd *cobra.Command, args []string) {
isAlacritty, _ := cmd.Flags().GetBool("alacritty")
isGhostty, _ := cmd.Flags().GetBool("ghostty")
isWezterm, _ := cmd.Flags().GetBool("wezterm")
vscodeEnrich, _ := cmd.Flags().GetString("vscode-enrich")
background, _ := cmd.Flags().GetString("background")
contrastAlgo, _ := cmd.Flags().GetString("contrast")
@@ -65,18 +62,7 @@ func runDank16(cmd *cobra.Command, args []string) {
colors := dank16.GeneratePalette(primaryColor, opts)
if vscodeEnrich != "" {
data, err := os.ReadFile(vscodeEnrich)
if err != nil {
log.Fatalf("Error reading file: %v", err)
}
enriched, err := dank16.EnrichVSCodeTheme(data, colors)
if err != nil {
log.Fatalf("Error enriching theme: %v", err)
}
fmt.Println(string(enriched))
} else if isJson {
if isJson {
fmt.Print(dank16.GenerateJSON(colors))
} else if isKitty {
fmt.Print(dank16.GenerateKittyTheme(colors))

View File

@@ -1,7 +1,6 @@
package dank16
import (
"encoding/json"
"math"
"testing"
)
@@ -373,79 +372,6 @@ func TestGeneratePalette(t *testing.T) {
}
}
func TestEnrichVSCodeTheme(t *testing.T) {
colors := GeneratePalette("#625690", PaletteOptions{IsLight: false})
baseTheme := map[string]interface{}{
"name": "Test Theme",
"type": "dark",
"colors": map[string]interface{}{
"editor.background": "#000000",
},
}
themeJSON, err := json.Marshal(baseTheme)
if err != nil {
t.Fatalf("Failed to marshal base theme: %v", err)
}
result, err := EnrichVSCodeTheme(themeJSON, colors)
if err != nil {
t.Fatalf("EnrichVSCodeTheme failed: %v", err)
}
var enriched map[string]interface{}
if err := json.Unmarshal(result, &enriched); err != nil {
t.Fatalf("Failed to unmarshal result: %v", err)
}
colorsMap, ok := enriched["colors"].(map[string]interface{})
if !ok {
t.Fatal("colors is not a map")
}
terminalColors := []string{
"terminal.ansiBlack",
"terminal.ansiRed",
"terminal.ansiGreen",
"terminal.ansiYellow",
"terminal.ansiBlue",
"terminal.ansiMagenta",
"terminal.ansiCyan",
"terminal.ansiWhite",
"terminal.ansiBrightBlack",
"terminal.ansiBrightRed",
"terminal.ansiBrightGreen",
"terminal.ansiBrightYellow",
"terminal.ansiBrightBlue",
"terminal.ansiBrightMagenta",
"terminal.ansiBrightCyan",
"terminal.ansiBrightWhite",
}
for i, key := range terminalColors {
if val, ok := colorsMap[key]; !ok {
t.Errorf("Missing terminal color: %s", key)
} else if val != colors[i] {
t.Errorf("%s = %s, expected %s", key, val, colors[i])
}
}
if colorsMap["editor.background"] != "#000000" {
t.Error("Original theme colors should be preserved")
}
}
func TestEnrichVSCodeThemeInvalidJSON(t *testing.T) {
colors := GeneratePalette("#625690", PaletteOptions{IsLight: false})
invalidJSON := []byte("{invalid json")
_, err := EnrichVSCodeTheme(invalidJSON, colors)
if err == nil {
t.Error("Expected error for invalid JSON, got nil")
}
}
func TestRoundTripConversion(t *testing.T) {
testColors := []string{"#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff", "#625690", "#808080"}

View File

@@ -1,250 +0,0 @@
package dank16
import (
"encoding/json"
"fmt"
)
type VSCodeTheme struct {
Schema string `json:"$schema"`
Name string `json:"name"`
Type string `json:"type"`
Colors map[string]string `json:"colors"`
TokenColors []VSCodeTokenColor `json:"tokenColors"`
SemanticHighlighting bool `json:"semanticHighlighting"`
SemanticTokenColors map[string]VSCodeTokenSetting `json:"semanticTokenColors"`
}
type VSCodeTokenColor struct {
Scope interface{} `json:"scope"`
Settings VSCodeTokenSetting `json:"settings"`
}
type VSCodeTokenSetting struct {
Foreground string `json:"foreground,omitempty"`
FontStyle string `json:"fontStyle,omitempty"`
}
func updateTokenColor(tc interface{}, scopeToColor map[string]string) {
tcMap, ok := tc.(map[string]interface{})
if !ok {
return
}
scopes, ok := tcMap["scope"].([]interface{})
if !ok {
return
}
settings, ok := tcMap["settings"].(map[string]interface{})
if !ok {
return
}
isYaml := hasScopeContaining(scopes, "yaml")
for _, scope := range scopes {
scopeStr, ok := scope.(string)
if !ok {
continue
}
if scopeStr == "string" && isYaml {
continue
}
if applyColorToScope(settings, scope, scopeToColor) {
break
}
}
}
func applyColorToScope(settings map[string]interface{}, scope interface{}, scopeToColor map[string]string) bool {
scopeStr, ok := scope.(string)
if !ok {
return false
}
newColor, exists := scopeToColor[scopeStr]
if !exists {
return false
}
settings["foreground"] = newColor
return true
}
func hasScopeContaining(scopes []interface{}, substring string) bool {
for _, scope := range scopes {
scopeStr, ok := scope.(string)
if !ok {
continue
}
for i := 0; i <= len(scopeStr)-len(substring); i++ {
if scopeStr[i:i+len(substring)] == substring {
return true
}
}
}
return false
}
func EnrichVSCodeTheme(themeData []byte, colors []string) ([]byte, error) {
var theme map[string]interface{}
if err := json.Unmarshal(themeData, &theme); err != nil {
return nil, err
}
colorsMap, ok := theme["colors"].(map[string]interface{})
if !ok {
colorsMap = make(map[string]interface{})
theme["colors"] = colorsMap
}
bg := colors[0]
isLight := false
if len(bg) == 7 && bg[0] == '#' {
r, g, b := 0, 0, 0
fmt.Sscanf(bg[1:], "%02x%02x%02x", &r, &g, &b)
luminance := (0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)) / 255.0
isLight = luminance > 0.5
}
if isLight {
theme["type"] = "light"
} else {
theme["type"] = "dark"
}
colorsMap["terminal.ansiBlack"] = colors[0]
colorsMap["terminal.ansiRed"] = colors[1]
colorsMap["terminal.ansiGreen"] = colors[2]
colorsMap["terminal.ansiYellow"] = colors[3]
colorsMap["terminal.ansiBlue"] = colors[4]
colorsMap["terminal.ansiMagenta"] = colors[5]
colorsMap["terminal.ansiCyan"] = colors[6]
colorsMap["terminal.ansiWhite"] = colors[7]
colorsMap["terminal.ansiBrightBlack"] = colors[8]
colorsMap["terminal.ansiBrightRed"] = colors[9]
colorsMap["terminal.ansiBrightGreen"] = colors[10]
colorsMap["terminal.ansiBrightYellow"] = colors[11]
colorsMap["terminal.ansiBrightBlue"] = colors[12]
colorsMap["terminal.ansiBrightMagenta"] = colors[13]
colorsMap["terminal.ansiBrightCyan"] = colors[14]
colorsMap["terminal.ansiBrightWhite"] = colors[15]
tokenColors, ok := theme["tokenColors"].([]interface{})
if ok {
scopeToColor := map[string]string{
"comment": colors[8],
"punctuation.definition.comment": colors[8],
"keyword": colors[5],
"storage.type": colors[13],
"storage.modifier": colors[5],
"variable": colors[15],
"variable.parameter": colors[7],
"meta.object-literal.key": colors[4],
"meta.property.object": colors[4],
"variable.other.property": colors[4],
"constant.other.symbol": colors[12],
"constant.numeric": colors[12],
"constant.language": colors[12],
"constant.character": colors[3],
"entity.name.type": colors[12],
"support.type": colors[13],
"entity.name.class": colors[12],
"entity.name.function": colors[2],
"support.function": colors[2],
"support.class": colors[15],
"support.variable": colors[15],
"variable.language": colors[12],
"entity.name.tag.yaml": colors[12],
"string.unquoted.plain.out.yaml": colors[15],
"string.unquoted.yaml": colors[15],
"string": colors[3],
}
for i, tc := range tokenColors {
updateTokenColor(tc, scopeToColor)
tokenColors[i] = tc
}
yamlRules := []VSCodeTokenColor{
{
Scope: "entity.name.tag.yaml",
Settings: VSCodeTokenSetting{Foreground: colors[12]},
},
{
Scope: []string{"string.unquoted.plain.out.yaml", "string.unquoted.yaml"},
Settings: VSCodeTokenSetting{Foreground: colors[15]},
},
}
for _, rule := range yamlRules {
tokenColors = append(tokenColors, rule)
}
theme["tokenColors"] = tokenColors
}
if semanticTokenColors, ok := theme["semanticTokenColors"].(map[string]interface{}); ok {
updates := map[string]string{
"variable": colors[15],
"variable.readonly": colors[12],
"property": colors[4],
"function": colors[2],
"method": colors[2],
"type": colors[12],
"class": colors[12],
"typeParameter": colors[13],
"enumMember": colors[12],
"string": colors[3],
"number": colors[12],
"comment": colors[8],
"keyword": colors[5],
"operator": colors[15],
"parameter": colors[7],
"namespace": colors[15],
}
for key, color := range updates {
if existing, ok := semanticTokenColors[key].(map[string]interface{}); ok {
existing["foreground"] = color
} else {
semanticTokenColors[key] = map[string]interface{}{
"foreground": color,
}
}
}
} else {
semanticTokenColors := make(map[string]interface{})
updates := map[string]string{
"variable": colors[7],
"variable.readonly": colors[12],
"property": colors[4],
"function": colors[2],
"method": colors[2],
"type": colors[12],
"class": colors[12],
"typeParameter": colors[13],
"enumMember": colors[12],
"string": colors[3],
"number": colors[12],
"comment": colors[8],
"keyword": colors[5],
"operator": colors[15],
"parameter": colors[7],
"namespace": colors[15],
}
for key, color := range updates {
semanticTokenColors[key] = map[string]interface{}{
"foreground": color,
}
}
theme["semanticTokenColors"] = semanticTokenColors
}
return json.MarshalIndent(theme, "", " ")
}

View File

@@ -0,0 +1,144 @@
// Code generated by mockery v2.53.5. DO NOT EDIT.
package mocks_version
import mock "github.com/stretchr/testify/mock"
// MockVersionFetcher is an autogenerated mock type for the VersionFetcher type
type MockVersionFetcher struct {
mock.Mock
}
type MockVersionFetcher_Expecter struct {
mock *mock.Mock
}
func (_m *MockVersionFetcher) EXPECT() *MockVersionFetcher_Expecter {
return &MockVersionFetcher_Expecter{mock: &_m.Mock}
}
// GetCurrentVersion provides a mock function with given fields: dmsPath
func (_m *MockVersionFetcher) GetCurrentVersion(dmsPath string) (string, error) {
ret := _m.Called(dmsPath)
if len(ret) == 0 {
panic("no return value specified for GetCurrentVersion")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
return rf(dmsPath)
}
if rf, ok := ret.Get(0).(func(string) string); ok {
r0 = rf(dmsPath)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(dmsPath)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockVersionFetcher_GetCurrentVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCurrentVersion'
type MockVersionFetcher_GetCurrentVersion_Call struct {
*mock.Call
}
// GetCurrentVersion is a helper method to define mock.On call
// - dmsPath string
func (_e *MockVersionFetcher_Expecter) GetCurrentVersion(dmsPath interface{}) *MockVersionFetcher_GetCurrentVersion_Call {
return &MockVersionFetcher_GetCurrentVersion_Call{Call: _e.mock.On("GetCurrentVersion", dmsPath)}
}
func (_c *MockVersionFetcher_GetCurrentVersion_Call) Run(run func(dmsPath string)) *MockVersionFetcher_GetCurrentVersion_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
func (_c *MockVersionFetcher_GetCurrentVersion_Call) Return(_a0 string, _a1 error) *MockVersionFetcher_GetCurrentVersion_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockVersionFetcher_GetCurrentVersion_Call) RunAndReturn(run func(string) (string, error)) *MockVersionFetcher_GetCurrentVersion_Call {
_c.Call.Return(run)
return _c
}
// GetLatestVersion provides a mock function with given fields: dmsPath
func (_m *MockVersionFetcher) GetLatestVersion(dmsPath string) (string, error) {
ret := _m.Called(dmsPath)
if len(ret) == 0 {
panic("no return value specified for GetLatestVersion")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
return rf(dmsPath)
}
if rf, ok := ret.Get(0).(func(string) string); ok {
r0 = rf(dmsPath)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(dmsPath)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockVersionFetcher_GetLatestVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestVersion'
type MockVersionFetcher_GetLatestVersion_Call struct {
*mock.Call
}
// GetLatestVersion is a helper method to define mock.On call
// - dmsPath string
func (_e *MockVersionFetcher_Expecter) GetLatestVersion(dmsPath interface{}) *MockVersionFetcher_GetLatestVersion_Call {
return &MockVersionFetcher_GetLatestVersion_Call{Call: _e.mock.On("GetLatestVersion", dmsPath)}
}
func (_c *MockVersionFetcher_GetLatestVersion_Call) Run(run func(dmsPath string)) *MockVersionFetcher_GetLatestVersion_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
func (_c *MockVersionFetcher_GetLatestVersion_Call) Return(_a0 string, _a1 error) *MockVersionFetcher_GetLatestVersion_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockVersionFetcher_GetLatestVersion_Call) RunAndReturn(run func(string) (string, error)) *MockVersionFetcher_GetLatestVersion_Call {
_c.Call.Return(run)
return _c
}
// NewMockVersionFetcher creates a new instance of MockVersionFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockVersionFetcher(t interface {
mock.TestingT
Cleanup(func())
}) *MockVersionFetcher {
mock := &MockVersionFetcher{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -18,28 +18,27 @@ type VersionInfo struct {
HasUpdate bool
}
func GetCurrentDMSVersion() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
// VersionFetcher is an interface for fetching version information
type VersionFetcher interface {
GetCurrentVersion(dmsPath string) (string, error)
GetLatestVersion(dmsPath string) (string, error)
}
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
return "", fmt.Errorf("DMS not installed")
}
originalDir, err := os.Getwd()
if err != nil {
return "", err
}
defer os.Chdir(originalDir)
if err := os.Chdir(dmsPath); err != nil {
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
}
// DefaultVersionFetcher is the default implementation that uses git/curl
type DefaultVersionFetcher struct{}
func (d *DefaultVersionFetcher) GetCurrentVersion(dmsPath string) (string, error) {
if _, err := os.Stat(filepath.Join(dmsPath, ".git")); err == nil {
originalDir, err := os.Getwd()
if err != nil {
return "", err
}
defer os.Chdir(originalDir)
if err := os.Chdir(dmsPath); err != nil {
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
}
tagCmd := exec.Command("git", "describe", "--exact-match", "--tags", "HEAD")
if tagOutput, err := tagCmd.Output(); err == nil {
return strings.TrimSpace(string(tagOutput)), nil
@@ -65,21 +64,14 @@ func GetCurrentDMSVersion() (string, error) {
return "unknown", nil
}
func GetLatestDMSVersion() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
originalDir, err := os.Getwd()
if err != nil {
return "", err
}
defer os.Chdir(originalDir)
func (d *DefaultVersionFetcher) GetLatestVersion(dmsPath string) (string, error) {
if _, err := os.Stat(filepath.Join(dmsPath, ".git")); err == nil {
originalDir, err := os.Getwd()
if err != nil {
return "", err
}
defer os.Chdir(originalDir)
if err := os.Chdir(dmsPath); err != nil {
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
}
@@ -154,13 +146,54 @@ func GetLatestDMSVersion() (string, error) {
return result.TagName, nil
}
// defaultFetcher is used by the public functions
var defaultFetcher VersionFetcher = &DefaultVersionFetcher{}
func GetCurrentDMSVersion() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
return "", fmt.Errorf("DMS not installed")
}
return defaultFetcher.GetCurrentVersion(dmsPath)
}
func GetLatestDMSVersion() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
return defaultFetcher.GetLatestVersion(dmsPath)
}
func GetDMSVersionInfo() (*VersionInfo, error) {
current, err := GetCurrentDMSVersion()
return GetDMSVersionInfoWithFetcher(defaultFetcher)
}
func GetDMSVersionInfoWithFetcher(fetcher VersionFetcher) (*VersionInfo, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to get home directory: %w", err)
}
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
return nil, fmt.Errorf("DMS not installed")
}
current, err := fetcher.GetCurrentVersion(dmsPath)
if err != nil {
return nil, err
}
latest, err := GetLatestDMSVersion()
latest, err := fetcher.GetLatestVersion(dmsPath)
if err != nil {
return nil, fmt.Errorf("failed to get latest version: %w", err)
}

View File

@@ -5,6 +5,8 @@ import (
"os/exec"
"path/filepath"
"testing"
mocks_version "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/version"
)
func TestCompareVersions(t *testing.T) {
@@ -33,36 +35,107 @@ func TestCompareVersions(t *testing.T) {
}
func TestGetDMSVersionInfo_Structure(t *testing.T) {
homeDir, err := os.UserHomeDir()
if err != nil {
t.Skip("Cannot get home directory")
}
// Create a temp directory with a fake DMS installation
tempDir := t.TempDir()
dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
os.MkdirAll(dmsPath, 0755)
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
t.Skip("DMS not installed, skipping version info test")
}
// Create a .git directory to simulate git installation
os.MkdirAll(filepath.Join(dmsPath, ".git"), 0755)
info, err := GetDMSVersionInfo()
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)
os.Setenv("HOME", tempDir)
// Create mock fetcher
mockFetcher := mocks_version.NewMockVersionFetcher(t)
mockFetcher.EXPECT().GetCurrentVersion(dmsPath).Return("v0.1.0", nil)
mockFetcher.EXPECT().GetLatestVersion(dmsPath).Return("v0.1.1", nil)
info, err := GetDMSVersionInfoWithFetcher(mockFetcher)
if err != nil {
t.Fatalf("GetDMSVersionInfo() failed: %v", err)
t.Fatalf("GetDMSVersionInfoWithFetcher() failed: %v", err)
}
if info == nil {
t.Fatal("GetDMSVersionInfo() returned nil")
t.Fatal("GetDMSVersionInfoWithFetcher() returned nil")
}
if info.Current == "" {
t.Error("Current version is empty")
if info.Current != "v0.1.0" {
t.Errorf("Current version = %s, expected v0.1.0", info.Current)
}
if info.Latest == "" {
t.Error("Latest version is empty")
if info.Latest != "v0.1.1" {
t.Errorf("Latest version = %s, expected v0.1.1", info.Latest)
}
if !info.HasUpdate {
t.Error("HasUpdate should be true when current != latest")
}
if !info.IsTag {
t.Error("IsTag should be true for v0.1.0")
}
t.Logf("Current: %s, Latest: %s, HasUpdate: %v", info.Current, info.Latest, info.HasUpdate)
}
func TestGetDMSVersionInfo_BranchVersion(t *testing.T) {
tempDir := t.TempDir()
dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
os.MkdirAll(dmsPath, 0755)
os.MkdirAll(filepath.Join(dmsPath, ".git"), 0755)
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)
os.Setenv("HOME", tempDir)
mockFetcher := mocks_version.NewMockVersionFetcher(t)
mockFetcher.EXPECT().GetCurrentVersion(dmsPath).Return("master@abc1234", nil)
mockFetcher.EXPECT().GetLatestVersion(dmsPath).Return("master@def5678", nil)
info, err := GetDMSVersionInfoWithFetcher(mockFetcher)
if err != nil {
t.Fatalf("GetDMSVersionInfoWithFetcher() failed: %v", err)
}
if !info.IsBranch {
t.Error("IsBranch should be true for branch@commit format")
}
if !info.IsGit {
t.Error("IsGit should be true for branch@commit format")
}
if !info.HasUpdate {
t.Error("HasUpdate should be true when commits differ")
}
}
func TestGetDMSVersionInfo_NoUpdate(t *testing.T) {
tempDir := t.TempDir()
dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
os.MkdirAll(dmsPath, 0755)
os.MkdirAll(filepath.Join(dmsPath, ".git"), 0755)
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)
os.Setenv("HOME", tempDir)
mockFetcher := mocks_version.NewMockVersionFetcher(t)
mockFetcher.EXPECT().GetCurrentVersion(dmsPath).Return("v0.1.0", nil)
mockFetcher.EXPECT().GetLatestVersion(dmsPath).Return("v0.1.0", nil)
info, err := GetDMSVersionInfoWithFetcher(mockFetcher)
if err != nil {
t.Fatalf("GetDMSVersionInfoWithFetcher() failed: %v", err)
}
if info.HasUpdate {
t.Error("HasUpdate should be false when current == latest")
}
}
func TestGetCurrentDMSVersion_NotInstalled(t *testing.T) {
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)