package tui import ( "fmt" "strings" "testing" "charm.land/bubbles/v2/list" tea "charm.land/bubbletea/v2" "gitgud.io/mike/mpv-manager/internal/fileops" "gitgud.io/mike/mpv-manager/pkg/config" "gitgud.io/mike/mpv-manager/pkg/constants" "gitgud.io/mike/mpv-manager/pkg/installer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestErrorViewEnterPerformsAdvertisedExit(t *testing.T) { m := NewModel() m.state = StateError updated, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) require.Same(t, m, updated) require.NotNil(t, cmd) _, ok := cmd().(tea.QuitMsg) assert.True(t, ok) } func TestScreenshotTextInputRejectsConfigInjectionWithoutLeavingEditor(t *testing.T) { m := NewModel() m.state = StateScreenshotTemplateEdit m.selectedScreenshotOption = "screenshot-template" m.screenshotTextInput.SetValue("shot%n\nvo=null") updated, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) m = updated.(*Model) assert.Nil(t, cmd) assert.Equal(t, StateScreenshotTemplateEdit, m.state) assert.Contains(t, m.errorMessage, "Invalid value") assert.Contains(t, m.screenshotTextInputView(), "Invalid value") } func TestHotkeyPresetApplyCannotBeStartedTwice(t *testing.T) { sandboxConfigHome(t) m := NewModel() m.setupHotkeysPresetList() m.state = StateHotkeysPreset path := writeInputConf(t, "x cycle pause\n") locked, release := make(chan struct{}), make(chan struct{}) lockResult := make(chan error, 1) go func() { lockResult <- fileops.WithLock(path, func() error { close(locked); <-release; return nil }) }() <-locked updated, first := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) m = updated.(*Model) require.NotNil(t, first) assert.True(t, m.presetApplyInFlight) assert.Equal(t, "Applying preset…", m.presetStatusMessage) updated, second := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) m = updated.(*Model) assert.Nil(t, second) assert.True(t, m.presetApplyInFlight) originalGuard := m.operationCommitGuard originalResult := m.installDone updated, escape := m.Update(tea.KeyPressMsg{Code: tea.KeyEsc}) m = updated.(*Model) assert.Nil(t, escape) assert.Equal(t, StateHotkeysPreset, m.state) assert.Same(t, originalGuard, m.operationCommitGuard) // Even an externally changed screen cannot admit another worker into the // one owned stream slot before the preset result is consumed. m.state = StateMainMenu updated, admission := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) m = updated.(*Model) assert.Nil(t, admission) assert.Equal(t, originalResult, m.installDone) assert.Same(t, originalGuard, m.operationCommitGuard) close(release) require.NoError(t, <-lockResult) for m.operationActive { updated, _ = m.Update(m.nextOperationStreamCmd()()) m = updated.(*Model) } assert.False(t, m.presetApplyInFlight) assert.Contains(t, m.presetStatusMessage, "Applied preset") } func TestPackageInfoRowExplainsExternalUpdateAction(t *testing.T) { m := NewModel() m.state = StateUpdates m.updateList.SetItems([]list.Item{updateItem{ AppName: "MPV", UpdateType: "package-info", PackageManager: "Flatpak", }}) updated, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) m = updated.(*Model) assert.Nil(t, cmd) assert.Equal(t, StateUpdates, m.state) assert.Contains(t, m.statusMessage, "Flatpak") } func TestWrappedPostInstallMessageIsAControlFlowSuccess(t *testing.T) { m := newRecordingModel(t) m.installOperationType = OpTypeConfig wrapped := fmt.Errorf("post-install notice: %w", installer.PostInstallMessage{Message: "restart MPV"}) m = sendCmdDone(t, m, wrapped) assert.True(t, m.installSuccess) assert.NoError(t, m.installError) assert.Equal(t, "restart MPV", m.postInstallMessage) } func TestViewportWrapperDoesNotDisappearBeforeFirstResize(t *testing.T) { m := NewModel() m.errorMessage = "failure" view := m.errorView() assert.True(t, strings.Contains(view, "Installation Failed"), "view was %q", view) } func TestSharedScreenshotValidationMatchesTUIKeys(t *testing.T) { assert.NoError(t, config.ValidateMPVConfigValue(constants.ConfigKeyScreenshotDirectory, `C:\Users\me\Pictures`)) assert.Error(t, config.ValidateMPVConfigValue(constants.ConfigKeyScreenshotTemplate, "shot%n\nvo=null")) }