Merge pull request #10 from LeonardoTrapani/notifications-refactor

internal refactor of notifications
This commit is contained in:
Leonardo Trapani
2026-01-02 23:31:21 +01:00
committed by GitHub
7 changed files with 261 additions and 393 deletions
+13 -17
View File
@@ -12,6 +12,7 @@ import (
"github.com/leonardotrapani/hyprvoice/internal/bus"
"github.com/leonardotrapani/hyprvoice/internal/config"
"github.com/leonardotrapani/hyprvoice/internal/daemon"
"github.com/leonardotrapani/hyprvoice/internal/notify"
"github.com/spf13/cobra"
)
@@ -361,23 +362,18 @@ func runInteractiveConfig() error {
input := strings.TrimSpace(strings.ToLower(scanner.Text()))
if input == "y" || input == "yes" {
fmt.Println()
// Get current/default values for display
recTitle, recBody := cfg.GetRecordingStarted()
transTitle, transBody := cfg.GetTranscribing()
reloadTitle, reloadBody := cfg.GetConfigReloaded()
cancelTitle, cancelBody := cfg.GetOperationCancelled()
abortRecBody := cfg.GetRecordingAborted()
abortInjBody := cfg.GetInjectionAborted()
// Get resolved values (user config merged with defaults)
msgs := cfg.Notifications.Messages.Resolve()
// Recording Started
fmt.Println(" Recording Started notification:")
fmt.Printf(" Title (current: %s): ", recTitle)
fmt.Printf(" Title (current: %s): ", msgs[notify.MsgRecordingStarted].Title)
if scanner.Scan() {
if t := strings.TrimSpace(scanner.Text()); t != "" {
cfg.Notifications.Messages.RecordingStarted.Title = t
}
}
fmt.Printf(" Body (current: %s): ", recBody)
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgRecordingStarted].Body)
if scanner.Scan() {
if b := strings.TrimSpace(scanner.Text()); b != "" {
cfg.Notifications.Messages.RecordingStarted.Body = b
@@ -387,13 +383,13 @@ func runInteractiveConfig() error {
// Transcribing
fmt.Println(" Transcribing notification:")
fmt.Printf(" Title (current: %s): ", transTitle)
fmt.Printf(" Title (current: %s): ", msgs[notify.MsgTranscribing].Title)
if scanner.Scan() {
if t := strings.TrimSpace(scanner.Text()); t != "" {
cfg.Notifications.Messages.Transcribing.Title = t
}
}
fmt.Printf(" Body (current: %s): ", transBody)
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgTranscribing].Body)
if scanner.Scan() {
if b := strings.TrimSpace(scanner.Text()); b != "" {
cfg.Notifications.Messages.Transcribing.Body = b
@@ -403,13 +399,13 @@ func runInteractiveConfig() error {
// Config Reloaded
fmt.Println(" Config Reloaded notification:")
fmt.Printf(" Title (current: %s): ", reloadTitle)
fmt.Printf(" Title (current: %s): ", msgs[notify.MsgConfigReloaded].Title)
if scanner.Scan() {
if t := strings.TrimSpace(scanner.Text()); t != "" {
cfg.Notifications.Messages.ConfigReloaded.Title = t
}
}
fmt.Printf(" Body (current: %s): ", reloadBody)
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgConfigReloaded].Body)
if scanner.Scan() {
if b := strings.TrimSpace(scanner.Text()); b != "" {
cfg.Notifications.Messages.ConfigReloaded.Body = b
@@ -419,13 +415,13 @@ func runInteractiveConfig() error {
// Operation Cancelled
fmt.Println(" Operation Cancelled notification:")
fmt.Printf(" Title (current: %s): ", cancelTitle)
fmt.Printf(" Title (current: %s): ", msgs[notify.MsgOperationCancelled].Title)
if scanner.Scan() {
if t := strings.TrimSpace(scanner.Text()); t != "" {
cfg.Notifications.Messages.OperationCancelled.Title = t
}
}
fmt.Printf(" Body (current: %s): ", cancelBody)
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgOperationCancelled].Body)
if scanner.Scan() {
if b := strings.TrimSpace(scanner.Text()); b != "" {
cfg.Notifications.Messages.OperationCancelled.Body = b
@@ -435,7 +431,7 @@ func runInteractiveConfig() error {
// Recording Aborted (body only)
fmt.Println(" Recording Aborted notification:")
fmt.Printf(" Body (current: %s): ", abortRecBody)
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgRecordingAborted].Body)
if scanner.Scan() {
if b := strings.TrimSpace(scanner.Text()); b != "" {
cfg.Notifications.Messages.RecordingAborted.Body = b
@@ -445,7 +441,7 @@ func runInteractiveConfig() error {
// Injection Aborted (body only)
fmt.Println(" Injection Aborted notification:")
fmt.Printf(" Body (current: %s): ", abortInjBody)
fmt.Printf(" Body (current: %s): ", msgs[notify.MsgInjectionAborted].Body)
if scanner.Scan() {
if b := strings.TrimSpace(scanner.Text()); b != "" {
cfg.Notifications.Messages.InjectionAborted.Body = b
+28 -42
View File
@@ -5,10 +5,12 @@ import (
"log"
"os"
"path/filepath"
"reflect"
"time"
"github.com/BurntSushi/toml"
"github.com/leonardotrapani/hyprvoice/internal/injection"
"github.com/leonardotrapani/hyprvoice/internal/notify"
"github.com/leonardotrapani/hyprvoice/internal/recording"
"github.com/leonardotrapani/hyprvoice/internal/transcriber"
)
@@ -64,52 +66,36 @@ type MessagesConfig struct {
InjectionAborted MessageConfig `toml:"injection_aborted"`
}
func (c *Config) GetRecordingStarted() (title, body string) {
m := c.Notifications.Messages.RecordingStarted
if m.Title == "" && m.Body == "" {
return "Hyprvoice", "Recording Started"
}
return m.Title, m.Body
}
// Resolve merges user config with defaults from MessageDefs
func (m *MessagesConfig) Resolve() map[notify.MessageType]notify.Message {
result := make(map[notify.MessageType]notify.Message)
func (c *Config) GetTranscribing() (title, body string) {
m := c.Notifications.Messages.Transcribing
if m.Title == "" && m.Body == "" {
return "Hyprvoice", "Recording Ended... Transcribing"
// Build toml tag → field index map
v := reflect.ValueOf(m).Elem()
t := v.Type()
tagToField := make(map[string]int)
for i := 0; i < t.NumField(); i++ {
tagToField[t.Field(i).Tag.Get("toml")] = i
}
return m.Title, m.Body
}
func (c *Config) GetConfigReloaded() (title, body string) {
m := c.Notifications.Messages.ConfigReloaded
if m.Title == "" && m.Body == "" {
return "Hyprvoice", "Config Reloaded"
for _, def := range notify.MessageDefs {
msg := notify.Message{
Title: def.DefaultTitle,
Body: def.DefaultBody,
IsError: def.IsError,
}
if idx, ok := tagToField[def.ConfigKey]; ok {
userMsg := v.Field(idx).Interface().(MessageConfig)
if userMsg.Title != "" {
msg.Title = userMsg.Title
}
if userMsg.Body != "" {
msg.Body = userMsg.Body
}
}
result[def.Type] = msg
}
return m.Title, m.Body
}
func (c *Config) GetOperationCancelled() (title, body string) {
m := c.Notifications.Messages.OperationCancelled
if m.Title == "" && m.Body == "" {
return "Hyprvoice", "Operation Cancelled"
}
return m.Title, m.Body
}
func (c *Config) GetRecordingAborted() string {
m := c.Notifications.Messages.RecordingAborted
if m.Body == "" {
return "Recording Aborted"
}
return m.Body
}
func (c *Config) GetInjectionAborted() string {
m := c.Notifications.Messages.InjectionAborted
if m.Body == "" {
return "Injection Aborted"
}
return m.Body
return result
}
func (c *Config) ToRecordingConfig() recording.Config {
+39 -127
View File
@@ -5,6 +5,8 @@ import (
"path/filepath"
"testing"
"time"
"github.com/leonardotrapani/hyprvoice/internal/notify"
)
// createTestConfig returns a valid configuration for testing
@@ -1229,142 +1231,52 @@ func TestConfig_Validate_GroqTranslation_RejectsTurbo(t *testing.T) {
}
}
func TestConfig_MessageGetters_Defaults(t *testing.T) {
config := createTestConfig()
func TestMessagesConfig_Resolve_Defaults(t *testing.T) {
cfg := createTestConfig()
msgs := cfg.Notifications.Messages.Resolve()
t.Run("GetRecordingStarted returns defaults", func(t *testing.T) {
title, body := config.GetRecordingStarted()
if title != "Hyprvoice" {
t.Errorf("GetRecordingStarted() title = %q, want %q", title, "Hyprvoice")
}
if body != "Recording Started" {
t.Errorf("GetRecordingStarted() body = %q, want %q", body, "Recording Started")
}
})
t.Run("GetTranscribing returns defaults", func(t *testing.T) {
title, body := config.GetTranscribing()
if title != "Hyprvoice" {
t.Errorf("GetTranscribing() title = %q, want %q", title, "Hyprvoice")
}
if body != "Recording Ended... Transcribing" {
t.Errorf("GetTranscribing() body = %q, want %q", body, "Recording Ended... Transcribing")
}
})
t.Run("GetConfigReloaded returns defaults", func(t *testing.T) {
title, body := config.GetConfigReloaded()
if title != "Hyprvoice" {
t.Errorf("GetConfigReloaded() title = %q, want %q", title, "Hyprvoice")
}
if body != "Config Reloaded" {
t.Errorf("GetConfigReloaded() body = %q, want %q", body, "Config Reloaded")
}
})
t.Run("GetOperationCancelled returns defaults", func(t *testing.T) {
title, body := config.GetOperationCancelled()
if title != "Hyprvoice" {
t.Errorf("GetOperationCancelled() title = %q, want %q", title, "Hyprvoice")
}
if body != "Operation Cancelled" {
t.Errorf("GetOperationCancelled() body = %q, want %q", body, "Operation Cancelled")
}
})
t.Run("GetRecordingAborted returns default", func(t *testing.T) {
body := config.GetRecordingAborted()
if body != "Recording Aborted" {
t.Errorf("GetRecordingAborted() = %q, want %q", body, "Recording Aborted")
}
})
t.Run("GetInjectionAborted returns default", func(t *testing.T) {
body := config.GetInjectionAborted()
if body != "Injection Aborted" {
t.Errorf("GetInjectionAborted() = %q, want %q", body, "Injection Aborted")
}
})
// Check defaults are applied
if msgs[notify.MsgRecordingStarted].Title != "Hyprvoice" {
t.Errorf("MsgRecordingStarted title = %q, want %q", msgs[notify.MsgRecordingStarted].Title, "Hyprvoice")
}
if msgs[notify.MsgRecordingStarted].Body != "Recording Started" {
t.Errorf("MsgRecordingStarted body = %q, want %q", msgs[notify.MsgRecordingStarted].Body, "Recording Started")
}
if msgs[notify.MsgTranscribing].Body != "Recording Ended... Transcribing" {
t.Errorf("MsgTranscribing body = %q, want %q", msgs[notify.MsgTranscribing].Body, "Recording Ended... Transcribing")
}
if msgs[notify.MsgRecordingAborted].IsError != true {
t.Errorf("MsgRecordingAborted IsError = %v, want true", msgs[notify.MsgRecordingAborted].IsError)
}
}
func TestConfig_MessageGetters_Custom(t *testing.T) {
config := createTestConfig()
config.Notifications.Messages = MessagesConfig{
func TestMessagesConfig_Resolve_CustomOverrides(t *testing.T) {
cfg := createTestConfig()
cfg.Notifications.Messages = MessagesConfig{
RecordingStarted: MessageConfig{
Title: "",
Body: "🎤",
},
Transcribing: MessageConfig{
Title: "",
Body: "⏳",
},
ConfigReloaded: MessageConfig{
Title: "",
Body: "🔧",
},
OperationCancelled: MessageConfig{
Title: "Custom",
Body: "Cancelled!",
Title: "Custom Title",
Body: "Custom Body",
},
RecordingAborted: MessageConfig{
Body: "Recording stopped",
},
InjectionAborted: MessageConfig{
Body: "Inject failed",
Body: "Custom Abort",
},
}
t.Run("GetRecordingStarted returns custom emoji", func(t *testing.T) {
title, body := config.GetRecordingStarted()
if title != "" {
t.Errorf("GetRecordingStarted() title = %q, want %q", title, "")
}
if body != "🎤" {
t.Errorf("GetRecordingStarted() body = %q, want %q", body, "🎤")
}
})
msgs := cfg.Notifications.Messages.Resolve()
t.Run("GetTranscribing returns custom emoji", func(t *testing.T) {
title, body := config.GetTranscribing()
if title != "" {
t.Errorf("GetTranscribing() title = %q, want %q", title, "")
}
if body != "⏳" {
t.Errorf("GetTranscribing() body = %q, want %q", body, "⏳")
}
})
// Custom values should override defaults
if msgs[notify.MsgRecordingStarted].Title != "Custom Title" {
t.Errorf("MsgRecordingStarted title = %q, want %q", msgs[notify.MsgRecordingStarted].Title, "Custom Title")
}
if msgs[notify.MsgRecordingStarted].Body != "Custom Body" {
t.Errorf("MsgRecordingStarted body = %q, want %q", msgs[notify.MsgRecordingStarted].Body, "Custom Body")
}
if msgs[notify.MsgRecordingAborted].Body != "Custom Abort" {
t.Errorf("MsgRecordingAborted body = %q, want %q", msgs[notify.MsgRecordingAborted].Body, "Custom Abort")
}
t.Run("GetConfigReloaded returns custom emoji", func(t *testing.T) {
title, body := config.GetConfigReloaded()
if title != "" {
t.Errorf("GetConfigReloaded() title = %q, want %q", title, "")
}
if body != "🔧" {
t.Errorf("GetConfigReloaded() body = %q, want %q", body, "🔧")
}
})
t.Run("GetOperationCancelled returns custom values", func(t *testing.T) {
title, body := config.GetOperationCancelled()
if title != "Custom" {
t.Errorf("GetOperationCancelled() title = %q, want %q", title, "Custom")
}
if body != "Cancelled!" {
t.Errorf("GetOperationCancelled() body = %q, want %q", body, "Cancelled!")
}
})
t.Run("GetRecordingAborted returns custom value", func(t *testing.T) {
body := config.GetRecordingAborted()
if body != "Recording stopped" {
t.Errorf("GetRecordingAborted() = %q, want %q", body, "Recording stopped")
}
})
t.Run("GetInjectionAborted returns custom value", func(t *testing.T) {
body := config.GetInjectionAborted()
if body != "Inject failed" {
t.Errorf("GetInjectionAborted() = %q, want %q", body, "Inject failed")
}
})
// Non-customized messages should still have defaults
if msgs[notify.MsgTranscribing].Title != "Hyprvoice" {
t.Errorf("MsgTranscribing title = %q, want %q", msgs[notify.MsgTranscribing].Title, "Hyprvoice")
}
}
+10 -17
View File
@@ -32,19 +32,15 @@ type Daemon struct {
func New() (*Daemon, error) {
configMgr, err := config.NewManager()
conf := configMgr.GetConfig()
if err != nil {
return nil, fmt.Errorf("failed to create config manager: %w", err)
}
conf := configMgr.GetConfig()
ctx, cancel := context.WithCancel(context.Background())
n := notify.GetNotifierBasedOnConfig(conf)
d := &Daemon{
notifier: n,
notifier: notify.NewNotifier(conf.Notifications.Type, conf.Notifications.Messages.Resolve()),
configMgr: configMgr,
ctx: ctx,
cancel: cancel,
@@ -58,12 +54,12 @@ func (d *Daemon) onConfigReload() {
d.stopPipeline()
conf := d.configMgr.GetConfig()
title, body := conf.GetConfigReloaded()
d.notifier.Notify(title, body)
d.mu.Lock()
d.notifier = notify.GetNotifierBasedOnConfig(conf)
d.notifier = notify.NewNotifier(conf.Notifications.Type, conf.Notifications.Messages.Resolve())
d.mu.Unlock()
d.notifier.Send(notify.MsgConfigReloaded)
}
func (d *Daemon) status() pipeline.Status {
@@ -192,13 +188,12 @@ func (d *Daemon) toggle() {
d.pipeline = p
d.mu.Unlock()
title, body := conf.GetRecordingStarted()
go d.notifier.Notify(title, body)
go d.notifier.Send(notify.MsgRecordingStarted)
go d.monitorPipelineErrors(p)
case pipeline.Recording:
d.stopPipeline()
go d.notifier.Error(conf.GetRecordingAborted())
go d.notifier.Send(notify.MsgRecordingAborted)
case pipeline.Transcribing:
d.mu.RLock()
@@ -210,12 +205,11 @@ func (d *Daemon) toggle() {
} else {
d.mu.RUnlock()
}
title, body := conf.GetTranscribing()
go d.notifier.Notify(title, body)
go d.notifier.Send(notify.MsgTranscribing)
case pipeline.Injecting:
d.stopPipeline()
go d.notifier.Error(conf.GetInjectionAborted())
go d.notifier.Send(notify.MsgInjectionAborted)
}
}
@@ -225,8 +219,7 @@ func (d *Daemon) cancelPipeline() {
log.Printf("Daemon: Cancel requested but pipeline is idle, ignoring")
default:
d.stopPipeline()
title, body := d.configMgr.GetConfig().GetOperationCancelled()
go d.notifier.Notify(title, body)
go d.notifier.Send(notify.MsgOperationCancelled)
}
}
+39
View File
@@ -0,0 +1,39 @@
package notify
// MessageType identifies a notification event
type MessageType int
const (
MsgRecordingStarted MessageType = iota
MsgTranscribing
MsgConfigReloaded
MsgOperationCancelled
MsgRecordingAborted
MsgInjectionAborted
)
// MessageDef defines a message type with its config key and defaults
type MessageDef struct {
Type MessageType
ConfigKey string // TOML key under [notifications.messages]
DefaultTitle string
DefaultBody string
IsError bool // error notifications use critical urgency, no custom title
}
// MessageDefs is the single source of truth for all notification messages
var MessageDefs = []MessageDef{
{MsgRecordingStarted, "recording_started", "Hyprvoice", "Recording Started", false},
{MsgTranscribing, "transcribing", "Hyprvoice", "Recording Ended... Transcribing", false},
{MsgConfigReloaded, "config_reloaded", "Hyprvoice", "Config Reloaded", false},
{MsgOperationCancelled, "operation_cancelled", "Hyprvoice", "Operation Cancelled", false},
{MsgRecordingAborted, "recording_aborted", "", "Recording Aborted", true},
{MsgInjectionAborted, "injection_aborted", "", "Injection Aborted", true},
}
// Message is a resolved message ready for display
type Message struct {
Title string
Body string
IsError bool
}
+55 -32
View File
@@ -1,63 +1,86 @@
package notify
import (
"github.com/leonardotrapani/hyprvoice/internal/config"
"log"
"os/exec"
)
type Notifier interface {
Error(msg string)
Notify(title, message string)
Send(mt MessageType)
Error(msg string) // for dynamic errors (e.g., pipeline errors)
}
type Desktop struct{}
func (d Desktop) RecordingStarted() {
d.Notify("Hyprvoice", "Recording Started")
// NewNotifier creates a notifier based on type with resolved messages
func NewNotifier(notifType string, messages map[MessageType]Message) Notifier {
switch notifType {
case "desktop":
return NewDesktop(messages)
case "log":
return NewLog(messages)
default:
return &Nop{}
}
}
func (d Desktop) Transcribing() {
d.Notify("Hyprvoice", "Transcribing...")
type Desktop struct {
messages map[MessageType]Message
}
func (Desktop) Error(msg string) {
func NewDesktop(messages map[MessageType]Message) *Desktop {
return &Desktop{messages: messages}
}
func (d *Desktop) Send(mt MessageType) {
msg, ok := d.messages[mt]
if !ok {
return
}
if msg.IsError {
d.Error(msg.Body)
return
}
d.notify(msg.Title, msg.Body)
}
func (d *Desktop) Error(msg string) {
cmd := exec.Command("notify-send", "-a", "Hyprvoice", "-u", "critical", "Hyprvoice Error", msg)
if err := cmd.Run(); err != nil {
log.Printf("Failed to send error notification: %v", err)
}
}
func (Desktop) Notify(title, message string) {
cmd := exec.Command("notify-send", "-a", "Hyprvoice", title, message)
func (d *Desktop) notify(title, body string) {
cmd := exec.Command("notify-send", "-a", "Hyprvoice", title, body)
if err := cmd.Run(); err != nil {
log.Printf("Failed to send notification: %v", err)
}
}
type Log struct{}
func (l Log) Error(msg string) {
l.Notify("Hyprvoice Error", msg)
type Log struct {
messages map[MessageType]Message
}
func (Log) Notify(title, message string) {
log.Printf("%s: %s", title, message)
func NewLog(messages map[MessageType]Message) *Log {
return &Log{messages: messages}
}
func (l *Log) Send(mt MessageType) {
msg, ok := l.messages[mt]
if !ok {
return
}
if msg.IsError {
l.Error(msg.Body)
return
}
log.Printf("%s: %s", msg.Title, msg.Body)
}
func (l *Log) Error(msg string) {
log.Printf("Hyprvoice Error: %s", msg)
}
type Nop struct{}
func (Nop) Error(msg string) {}
func (Nop) Notify(title, message string) {}
func GetNotifierBasedOnConfig(c *config.Config) Notifier {
switch c.Notifications.Type {
case "desktop":
return Desktop{}
case "log":
return Log{}
case "none":
return Nop{}
}
return Nop{}
}
func (Nop) Send(mt MessageType) {}
func (Nop) Error(msg string) {}
+77 -158
View File
@@ -2,202 +2,121 @@ package notify
import (
"testing"
"github.com/leonardotrapani/hyprvoice/internal/config"
)
func TestDesktop_Notify(t *testing.T) {
desktop := Desktop{}
// Test normal notification
desktop.Notify("Test Title", "Test Message")
// Test error notification
desktop.Error("Test Error Message")
// Test specific methods
desktop.RecordingStarted()
desktop.Transcribing()
func testMessages() map[MessageType]Message {
return map[MessageType]Message{
MsgRecordingStarted: {Title: "Hyprvoice", Body: "Recording Started", IsError: false},
MsgTranscribing: {Title: "Hyprvoice", Body: "Transcribing", IsError: false},
MsgConfigReloaded: {Title: "Hyprvoice", Body: "Config Reloaded", IsError: false},
MsgOperationCancelled: {Title: "Hyprvoice", Body: "Operation Cancelled", IsError: false},
MsgRecordingAborted: {Title: "", Body: "Recording Aborted", IsError: true},
MsgInjectionAborted: {Title: "", Body: "Injection Aborted", IsError: true},
}
}
func TestLog_Notify(t *testing.T) {
logNotifier := Log{}
func TestDesktop_Send(t *testing.T) {
desktop := NewDesktop(testMessages())
// Test normal notification
logNotifier.Notify("Test Title", "Test Message")
// Test Send for different message types (won't actually send, just verify no panic)
desktop.Send(MsgRecordingStarted)
desktop.Send(MsgTranscribing)
desktop.Send(MsgRecordingAborted) // error type
}
// Test error notification
func TestDesktop_Error(t *testing.T) {
desktop := NewDesktop(testMessages())
desktop.Error("Test Error Message")
}
func TestLog_Send(t *testing.T) {
logNotifier := NewLog(testMessages())
logNotifier.Send(MsgRecordingStarted)
logNotifier.Send(MsgRecordingAborted) // error type
}
func TestLog_Error(t *testing.T) {
logNotifier := NewLog(testMessages())
logNotifier.Error("Test Error Message")
}
func TestNop_Notify(t *testing.T) {
func TestNop_Send(t *testing.T) {
nop := Nop{}
nop.Send(MsgRecordingStarted)
nop.Send(MsgRecordingAborted)
}
// Test that these methods don't panic
nop.Notify("Test Title", "Test Message")
func TestNop_Error(t *testing.T) {
nop := Nop{}
nop.Error("Test Error Message")
}
func TestGetNotifierBasedOnConfig(t *testing.T) {
func TestNewNotifier(t *testing.T) {
msgs := testMessages()
tests := []struct {
name string
config *config.Config
expected string
name string
notifType string
expectType string
}{
{
name: "desktop notification type",
config: &config.Config{
Notifications: config.NotificationsConfig{
Type: "desktop",
},
},
expected: "desktop",
},
{
name: "log notification type",
config: &config.Config{
Notifications: config.NotificationsConfig{
Type: "log",
},
},
expected: "log",
},
{
name: "none notification type",
config: &config.Config{
Notifications: config.NotificationsConfig{
Type: "none",
},
},
expected: "nop",
},
{
name: "unknown notification type",
config: &config.Config{
Notifications: config.NotificationsConfig{
Type: "unknown",
},
},
expected: "nop",
},
{"desktop", "desktop", "*notify.Desktop"},
{"log", "log", "*notify.Log"},
{"none", "none", "*notify.Nop"},
{"unknown", "unknown", "*notify.Nop"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
notifier := GetNotifierBasedOnConfig(tt.config)
notifier := NewNotifier(tt.notifType, msgs)
// Test the notifier by calling its methods
notifier.Notify("Test", "Message")
// Test the notifier works
notifier.Send(MsgRecordingStarted)
notifier.Error("Error")
// Check the type by testing behavior
switch tt.expected {
case "desktop":
// Desktop notifier should not panic
if desktop, ok := notifier.(Desktop); ok {
desktop.RecordingStarted()
desktop.Transcribing()
}
case "log":
// Log notifier should not panic
if logNotifier, ok := notifier.(Log); ok {
logNotifier.Notify("Test", "Message")
logNotifier.Error("Error")
}
case "nop":
// Nop notifier should not panic
if nop, ok := notifier.(Nop); ok {
nop.Notify("Test", "Message")
nop.Error("Error")
}
}
})
}
}
func TestDesktop_Methods(t *testing.T) {
desktop := Desktop{}
// Test RecordingStarted method
desktop.RecordingStarted()
// Test Transcribing method
desktop.Transcribing()
// Test Error method
desktop.Error("Test error")
// Test Notify method
desktop.Notify("Test Title", "Test Message")
}
func TestLog_Methods(t *testing.T) {
logNotifier := Log{}
// Test Error method
logNotifier.Error("Test error")
// Test Notify method
logNotifier.Notify("Test Title", "Test Message")
}
func TestNop_Methods(t *testing.T) {
nop := Nop{}
// Test Error method
nop.Error("Test error")
// Test Notify method
nop.Notify("Test Title", "Test Message")
}
func TestNotifierInterface(t *testing.T) {
msgs := testMessages()
// Test that all notifiers implement the Notifier interface
var notifier Notifier
// Test Desktop
notifier = Desktop{}
notifier.Notify("Test", "Message")
notifier = NewDesktop(msgs)
notifier.Send(MsgRecordingStarted)
notifier.Error("Error")
// Test Log
notifier = Log{}
notifier.Notify("Test", "Message")
notifier = NewLog(msgs)
notifier.Send(MsgRecordingStarted)
notifier.Error("Error")
// Test Nop
notifier = Nop{}
notifier.Notify("Test", "Message")
notifier = &Nop{}
notifier.Send(MsgRecordingStarted)
notifier.Error("Error")
}
func TestNotificationTypes(t *testing.T) {
// Test different notification configurations
configs := []*config.Config{
{
Notifications: config.NotificationsConfig{
Type: "desktop",
},
},
{
Notifications: config.NotificationsConfig{
Type: "log",
},
},
{
Notifications: config.NotificationsConfig{
Type: "none",
},
},
func TestMessageDefs(t *testing.T) {
// Verify MessageDefs contains expected entries
if len(MessageDefs) != 6 {
t.Errorf("Expected 6 MessageDefs, got %d", len(MessageDefs))
}
for _, cfg := range configs {
t.Run("type_"+cfg.Notifications.Type, func(t *testing.T) {
notifier := GetNotifierBasedOnConfig(cfg)
// Test that the notifier works
notifier.Notify("Test Title", "Test Message")
notifier.Error("Test Error")
})
// Verify each has required fields
for _, def := range MessageDefs {
if def.ConfigKey == "" {
t.Errorf("MessageDef type %d has empty ConfigKey", def.Type)
}
if def.DefaultBody == "" {
t.Errorf("MessageDef type %d has empty DefaultBody", def.Type)
}
}
}
func TestSend_UnknownMessageType(t *testing.T) {
msgs := testMessages()
desktop := NewDesktop(msgs)
// Should not panic with unknown message type
desktop.Send(MessageType(999))
}