Archive milestone artifacts: - milestones/v1.0-ROADMAP.md (full roadmap archive) - milestones/v1.0-REQUIREMENTS.md (26/26 requirements complete) - milestones/v1.0-phases/ (8 phase directories with plans, summaries, verifications) Updated: - PROJECT.md: full evolution review, all consolidation requirements validated - ROADMAP.md: collapsed to milestone summary with archive link - STATE.md: reset for next milestone - MILESTONES.md: created with stats and accomplishments - RETROSPECTIVE.md: created with lessons learned Deleted: - REQUIREMENTS.md (archived, fresh for next milestone) 8 phases, 17 plans, 34 tasks, 84 tests added, 6 days
14 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-queue-config-player-tests | 02 | execute | 1 |
|
true |
|
|
Purpose: Config tests verify roundtrip fidelity and validation rules, which are essential before any config format changes. Player logic tests characterize the volume conversion math and state mapping as a safety net for any future player refactoring.
Output: 6 test files — 5 for config/sub-configs (~8-10 tests) and 1 for player (~5-6 tests).
<execution_context> @/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md @/home/caleb/.config/opencode/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.mdFrom backend/config/config.go:
type Config struct {
ctx context.Context // unexported
logger *slog.Logger // unexported
filePath string // unexported — set by NewConfig or manually for tests
Library *library.Config `toml:"Library"`
Theme *theme.Config `toml:"Theme"`
Window *WindowConfig `toml:"Window"`
TrackList *tracklist.Config `toml:"TrackList"`
Favorites *favorites.Config `toml:"Favorites"`
}
func NewConfig(logger *slog.Logger) (*Config, error) // reads from system config dir — NOT usable in tests
func (c *Config) Validate() error // delegates to sub-configs
func (c *Config) Load() error // reads from c.filePath
func (c *Config) Save() error // writes to c.filePath with 0o644
func (c *Config) applyDefaults() // unexported — fills nil sub-configs
From backend/theme/config.go:
type BackgroundShade string // "darker", "dark", "light"
type Config struct { AccentColor string; BackgroundShade BackgroundShade }
func (c *Config) ApplyDefaults()
func (c *Config) Validate() error // checks hex color regex + shade enum
const DefaultAccentColor = "#ffd43b"
const DefaultBackgroundShade = BackgroundDark
From backend/tracklist/config.go:
type ColumnID string // 16 valid values
type Column struct { ID ColumnID }
type Config struct { Columns []Column }
func (c *Config) ApplyDefaults()
func (c *Config) Validate() error // checks valid IDs + no duplicates
var DefaultColumns = []Column{{ColTrackName}, {ColArtistName}, {ColTrackLength}}
From backend/favorites/config.go:
type IconStyle string // "heart", "star"
type Config struct { PlaylistID int64; IconStyle; PinDefault bool }
func (c *Config) ApplyDefaults()
func (c *Config) Validate() error // checks icon style enum
const DefaultIconStyle = IconHeart
From backend/library/config.go:
type ScanConcurrency string // "auto", "ssd", "hdd"
type Directory string
type Config struct { DirectoryPath Directory; ScanConcurrency }
func (c *Config) Validate() error // checks dir exists on filesystem + mode enum
const DefaultScanConcurrency = ScanConcurrencyAuto
From backend/player/volume.go:
type UserVolume int // 0-100
type Volume float64 // -5 to 0
const MinUserVol UserVolume = 0, MaxUserVol = 100, DefaultUserVol = 50
const MinVol Volume = -5, MaxVol = 0
func (uv UserVolume) ToVolume() Volume
func (v Volume) ToUserVolume() UserVolume
func clampVolume(v UserVolume) UserVolume // unexported
From backend/player/player.go:
type State string
const Playing State = "playing", Paused = "paused", Stopped = "stopped"
func stateToMediaControls(s State) mediacontrols.PlaybackState // unexported
From backend/mediacontrols/mediacontrols.go:
type PlaybackState int
const StateStopped PlaybackState = 0, StatePlaying = 1, StatePaused = 2
From backend/config/window.go:
type WindowConfig struct { Width int; Height int }
func NewDefaultWindowConfig() *WindowConfig // returns &WindowConfig{Width: 1024, Height: 768}
backend/theme/config_test.go (package theme) — ~3 tests:
TestThemeConfig_Validate_ValidValues— table-driven: valid hex colors ("#fff", "#ffd43b", "#000000") with valid shades ("darker", "dark", "light") all passTestThemeConfig_Validate_InvalidHexColor— table-driven: invalid colors ("fff", "#gg0000", "#12345", "red", "") all return error containing "invalid hex color"TestThemeConfig_Validate_InvalidBackgroundShade— shade "neon" returns error containing "unknown background shade"TestThemeConfig_ApplyDefaults— verify zero-value Config gets DefaultAccentColor and DefaultBackgroundShade
backend/tracklist/config_test.go (package tracklist) — ~3 tests:
TestTrackListConfig_Validate_ValidColumns— valid column IDs passTestTrackListConfig_Validate_UnknownColumnID— unknown ID returns error containing "unknown track-list column ID"TestTrackListConfig_Validate_DuplicateColumn— duplicate ID returns error containing "duplicate column ID"TestTrackListConfig_ApplyDefaults— verify zero-value Config gets DefaultColumns
backend/favorites/config_test.go (package favorites) — ~2-3 tests:
TestFavoritesConfig_Validate_ValidIconStyles— table-driven: "heart", "star" both passTestFavoritesConfig_Validate_InvalidIconStyle— "diamond" returns error containing "unknown favorites icon style"TestFavoritesConfig_ApplyDefaults— verify zero-value gets DefaultIconStyle
backend/library/config_test.go (package library) — ~3-4 tests:
TestLibraryConfig_Validate_ValidDirectory— use t.TempDir() as directory, all scan concurrency modes ("auto", "ssd", "hdd") passTestLibraryConfig_Validate_NonexistentDirectory— "/nonexistent/path/xyz" returns errorTestLibraryConfig_Validate_InvalidScanConcurrency— "turbo" returns error containing "unknown scan concurrency"TestLibraryConfig_Validate_EmptyDirectory— empty DirectoryPath with valid scan concurrency passes (no dir check when empty)TestLibraryConfig_ApplyDefaults— verify zero-value ScanConcurrency gets DefaultScanConcurrency
backend/config/config_test.go (package config) — ~3-4 tests:
-
TestConfig_LoadSave_Roundtrip— The critical roundtrip test:- Create Config struct directly with
filePathset tofilepath.Join(t.TempDir(), "config.toml") - Set all sub-configs to non-default values: theme accent "#ff0000", shade "light", tracklist columns with 5 columns, favorites icon "star", library directory set to a second t.TempDir(), library scan concurrency "ssd", window 800x600
- Call applyDefaults() then Save()
- Create NEW Config struct with same filePath, call Load()
- Verify ALL fields match the original values
Note: Set
loggertoslog.Default()on the Config struct for both instances.
- Create Config struct directly with
-
TestConfig_Load_MissingFile— Config with filePath pointing to nonexistent file. Load() should create the file with defaults (current behavior). Verify file exists after Load(). -
TestConfig_Validate_ComposesSubConfigErrors— Config with invalid theme (bad hex) AND invalid tracklist (unknown column) returns an error. Verify both error messages are present (errors.Join behavior). -
TestConfig_ApplyDefaults_NilSubConfigs— Config with all nil sub-configs, call applyDefaults(), verify all sub-configs are non-nil with sensible defaults.
For the roundtrip test, import sub-config packages: theme, tracklist, favorites, library. Access unexported fields (filePath, logger) directly since this is an internal test (package config). cd backend && go test -race -count=1 ./config/ ./theme/ ./tracklist/ ./favorites/ ./library/ -run "TestTheme|TestTrackList|TestFavorites|TestLibrary|TestConfig" -v 2>&1 | tail -40 5 test files exist covering: theme hex+shade validation, tracklist column validation, favorites icon validation, library dir+concurrency validation, and config load/save roundtrip. All pass with -race.
Task 2: Player volume and state mapping tests backend/player/volume_test.go Create volume_test.go in the player package (internal, package player). Follow established conventions: t.Parallel(), table-driven subtests, stdlib testing only.Write these test functions:
-
TestUserVolume_ToVolume— table-driven with cases:UserVolume Expected Volume 0 (MinUserVol) -5.0 (MinVol) 100 (MaxUserVol) 0.0 (MaxVol) 50 (DefaultUserVol) -2.5 (midpoint) 25 -3.75 75 -1.25 For each: verify uv.ToVolume()matches expected within a tolerance of 0.001 (use math.Abs for float comparison). -
TestVolume_ToUserVolume— table-driven with inverse cases:Volume Expected UserVolume -5.0 (MinVol) 0 (MinUserVol) 0.0 (MaxVol) 100 (MaxUserVol) -2.5 50 -3.75 25 -1.25 75 For each: verify v.ToUserVolume()matches expected exactly (int comparison). -
TestUserVolume_ToVolume_OutOfRange— table-driven: values outside [0,100] like -1, 101, 200, -50. Verify ToVolume() returns zero-value Volume (0.0) per current implementation (theifguard fails, returns uninitializednewVol). -
TestVolume_ToUserVolume_OutOfRange— values outside [-5,0] like -6.0, 1.0, -10.0. Verify ToUserVolume() returns zero-value UserVolume (0) per current implementation. -
TestUserVolume_ToVolume_Roundtrip— for every UserVolume from 0 to 100, convert to Volume and back. Verify roundtrip matches original value. This is the characterization test — if the math changes, this breaks. -
TestClampVolume— table-driven:Input Expected -10 0 (MinUserVol) 0 0 50 50 100 100 150 100 (MaxUserVol) -
TestStateToMediaControls— table-driven:State Expected PlaybackState Playing mediacontrols.StatePlaying (1) Paused mediacontrols.StatePaused (2) Stopped mediacontrols.StateStopped (0) State("unknown") mediacontrols.StateStopped (0) — default case
Import "yellowjacket/backend/mediacontrols" for the PlaybackState constants. Use math for float comparison tolerance.
cd backend && go test -race -count=1 -run "TestUserVolume|TestVolume|TestClamp|TestState" ./player/ -v 2>&1 | tail -20
volume_test.go has ~7 tests covering ToVolume/ToUserVolume conversion at all boundaries, out-of-range behavior, full roundtrip 0-100, clamp, and state mapping. All pass with -race.
<success_criteria>
- 5 config test files exist covering all sub-config validators + composed Config
- Config load/save roundtrip preserves all non-default values
- Missing config file handled gracefully
- volume_test.go exists with ~7 tests for volume conversion + state mapping
- ToVolume/ToUserVolume roundtrip is verified for all values 0-100
- All tests pass with
go test -race</success_criteria>