feat(tui): add shared WizardState seeded from config and registry

This commit is contained in:
MDietermann
2026-04-13 21:48:12 +02:00
parent 777a3fe49b
commit 258b53b6a0
2 changed files with 158 additions and 0 deletions

86
internal/tui/state.go Normal file
View File

@@ -0,0 +1,86 @@
package tui
import (
"sort"
"github.com/MDietermann/ArduinoArchSetup/internal/config"
"github.com/MDietermann/ArduinoArchSetup/internal/module"
)
// Theme is a named colorscheme presented in the wizard. Plan 3 wires these
// names to actual theme modules; for now they are display-only strings.
type Theme struct {
ID string
Name string
}
// BuiltinThemes is the fixed list the wizard shows. Keep in sync with the
// themes shipped by Plan 3's theme module.
var BuiltinThemes = []Theme{
{ID: "gruvbox", Name: "Gruvbox Dark"},
{ID: "catppuccin", Name: "Catppuccin Mocha"},
{ID: "tokyonight", Name: "Tokyo Night"},
{ID: "nord", Name: "Nord"},
{ID: "dracula", Name: "Dracula"},
}
// ModuleChoice is one row in the module selector.
type ModuleChoice struct {
ID string
Name string
Description string
Selected bool
}
// WizardState is the payload the wizard fills in and the dashboard reads.
type WizardState struct {
ThemeID string
Modules []ModuleChoice
Parallelism int // advanced; reserved for a future concurrent engine
HaltOnError bool // advanced
Confirmed bool // true once the user hits Confirm on the last screen
}
// NewWizardState builds an initial state from the running config and the
// registered modules. Pre-ticks all modules except those disabled in the
// overlay, defaults the theme to the first built-in theme.
func NewWizardState(cfg config.Config, reg *module.Registry) WizardState {
disabled := map[string]bool{}
for _, id := range cfg.DisabledModules {
disabled[id] = true
}
mods := reg.All()
choices := make([]ModuleChoice, 0, len(mods))
for _, m := range mods {
meta := m.Meta()
choices = append(choices, ModuleChoice{
ID: meta.ID,
Name: meta.Name,
Description: meta.Description,
Selected: !disabled[meta.ID],
})
}
sort.Slice(choices, func(i, j int) bool { return choices[i].ID < choices[j].ID })
theme := BuiltinThemes[0].ID
if override, ok := cfg.ThemeOverrides["__active"]; ok && override != "" {
theme = override
}
return WizardState{
ThemeID: theme,
Modules: choices,
Parallelism: 1,
}
}
// SelectedIDs returns only the checked module IDs.
func (s WizardState) SelectedIDs() []string {
out := make([]string, 0, len(s.Modules))
for _, m := range s.Modules {
if m.Selected {
out = append(out, m.ID)
}
}
return out
}

View File

@@ -0,0 +1,72 @@
package tui
import (
"context"
"testing"
"github.com/MDietermann/ArduinoArchSetup/internal/config"
"github.com/MDietermann/ArduinoArchSetup/internal/module"
)
type fakeModule struct{ id, name, desc string }
func (f fakeModule) Meta() module.Meta {
return module.Meta{ID: f.id, Name: f.name, Description: f.desc}
}
func (f fakeModule) Detect(context.Context) (module.State, error) { return nil, nil }
func (f fakeModule) Plan(context.Context, module.State) (module.Plan, error) {
return module.Plan{}, nil
}
func (f fakeModule) Apply(context.Context, module.Plan, module.Reporter) error { return nil }
func (f fakeModule) Verify(context.Context) error { return nil }
func TestNewWizardStatePreTicksAllByDefault(t *testing.T) {
reg := module.NewRegistry()
_ = reg.Register(fakeModule{id: "a", name: "Alpha"})
_ = reg.Register(fakeModule{id: "b", name: "Bravo"})
s := NewWizardState(config.Config{}, reg)
if len(s.Modules) != 2 {
t.Fatalf("modules = %d", len(s.Modules))
}
for _, m := range s.Modules {
if !m.Selected {
t.Errorf("%s not selected", m.ID)
}
}
if s.ThemeID != BuiltinThemes[0].ID {
t.Errorf("theme = %q", s.ThemeID)
}
}
func TestNewWizardStateRespectsDisabled(t *testing.T) {
reg := module.NewRegistry()
_ = reg.Register(fakeModule{id: "a"})
_ = reg.Register(fakeModule{id: "b"})
s := NewWizardState(config.Config{DisabledModules: []string{"a"}}, reg)
for _, m := range s.Modules {
switch m.ID {
case "a":
if m.Selected {
t.Error("a should be unticked")
}
case "b":
if !m.Selected {
t.Error("b should be ticked")
}
}
}
}
func TestSelectedIDsOnlyReturnsChecked(t *testing.T) {
s := WizardState{Modules: []ModuleChoice{
{ID: "a", Selected: true},
{ID: "b", Selected: false},
{ID: "c", Selected: true},
}}
got := s.SelectedIDs()
if len(got) != 2 || got[0] != "a" || got[1] != "c" {
t.Fatalf("got %v", got)
}
}