diff --git a/pkg/models/mentions_test.go b/pkg/models/mentions_test.go index 4d8065807..a0c797ea9 100644 --- a/pkg/models/mentions_test.go +++ b/pkg/models/mentions_test.go @@ -117,6 +117,8 @@ func TestSendingMentionNotification(t *testing.T) { task, err := GetTaskByIDSimple(s, 32) require.NoError(t, err) + project, err := GetProjectSimpleByID(s, task.ProjectID) + require.NoError(t, err) tc := &TaskComment{ Comment: `

Lorem Ipsum @user1 @user2 @user3 @user4 @user5 @user6

`, TaskID: 32, // user2 has access to the project that task belongs to @@ -127,6 +129,7 @@ func TestSendingMentionNotification(t *testing.T) { Doer: u, Task: &task, Comment: tc, + Project: project, } _, err = notifyMentionedUsers(s, &task, tc.Comment, n) @@ -171,6 +174,8 @@ func TestSendingMentionNotification(t *testing.T) { task, err := GetTaskByIDSimple(s, 32) require.NoError(t, err) + project, err := GetProjectSimpleByID(s, task.ProjectID) + require.NoError(t, err) tc := &TaskComment{ Comment: `

Lorem Ipsum @user2

`, TaskID: 32, // user2 has access to the project that task belongs to @@ -181,6 +186,7 @@ func TestSendingMentionNotification(t *testing.T) { Doer: u, Task: &task, Comment: tc, + Project: project, } _, err = notifyMentionedUsers(s, &task, tc.Comment, n) diff --git a/pkg/notifications/mail_test.go b/pkg/notifications/mail_test.go index 6328d1c91..125a13cd0 100644 --- a/pkg/notifications/mail_test.go +++ b/pkg/notifications/mail_test.go @@ -134,7 +134,7 @@ This is a line assertHTMLContainsDarkModeSupport(t, mailopts.HTMLMessage) // Check for expected content - assert.Contains(t, mailopts.HTMLMessage, `

This is a line

`) + assert.Contains(t, mailopts.HTMLMessage, `

This is a line

`) assert.Contains(t, mailopts.HTMLMessage, `Hi there,`) // Verify no action button is present @@ -224,8 +224,8 @@ This is a footer line assertHTMLContainsDarkModeSupport(t, mailopts.HTMLMessage) // Check for content - assert.Contains(t, mailopts.HTMLMessage, `

This is a line

`) - assert.Contains(t, mailopts.HTMLMessage, `

This is a footer line

`) + assert.Contains(t, mailopts.HTMLMessage, `

This is a line

`) + assert.Contains(t, mailopts.HTMLMessage, `This is a footer line`) // Verify no action button assert.NotContains(t, mailopts.HTMLMessage, `class="email-button"`) @@ -277,7 +277,7 @@ This is a footer line assert.Contains(t, mailopts.HTMLMessage, `href="https://example.com"`) // Check for footer - assert.Contains(t, mailopts.HTMLMessage, `

This is a footer line

`) + assert.Contains(t, mailopts.HTMLMessage, `This is a footer line`) }) t.Run("with thread ID", func(t *testing.T) { mail := NewMail(). @@ -488,3 +488,224 @@ This is a footer line assert.Contains(t, mailopts.HTMLMessage, `attention`) }) } + +func TestConversationalMail(t *testing.T) { + t.Run("Conversational flag", func(t *testing.T) { + mail := NewMail(). + From("test@example.com"). + To("test@otherdomain.com"). + Subject("Testmail"). + Conversational(). + Line("This is a conversational message") + + assert.True(t, mail.IsConversational()) + }) + + t.Run("Default is not conversational", func(t *testing.T) { + mail := NewMail(). + From("test@example.com"). + To("test@otherdomain.com"). + Subject("Testmail"). + Line("This is a formal message") + + assert.False(t, mail.IsConversational()) + }) + + t.Run("Conversational template selection", func(t *testing.T) { + mail := NewMail(). + From("test@example.com"). + To("test@otherdomain.com"). + Subject("Testmail"). + Conversational(). + Line("This is a conversational message"). + Action("View Task", "https://example.com/task/123") + + mailopts, err := RenderMail(mail, "en") + require.NoError(t, err) + + // Should not contain greeting section + assert.NotContains(t, mailopts.HTMLMessage, "

\n\t\t\n\t

") + + // Should use conversational styling + assert.Contains(t, mailopts.HTMLMessage, "background: #f6f8fa") + assert.Contains(t, mailopts.HTMLMessage, "font-family: -apple-system") + + // Should NOT have logo (completely removed) + assert.NotContains(t, mailopts.HTMLMessage, "logo.png") + assert.NotContains(t, mailopts.HTMLMessage, "Vikunja") + + // Should have inline action link with arrow + assert.Contains(t, mailopts.HTMLMessage, "View Task →") + assert.Contains(t, mailopts.HTMLMessage, "color: #0969da") + + // Should not have the formal button styling + assert.NotContains(t, mailopts.HTMLMessage, "background-color: #1973ff") + assert.NotContains(t, mailopts.HTMLMessage, "width:280px") + + // Plain text should not have greeting + assert.NotContains(t, mailopts.Message, "Hi there,") + assert.Contains(t, mailopts.Message, "This is a conversational message") + }) + + t.Run("Formal template still works", func(t *testing.T) { + mail := NewMail(). + From("test@example.com"). + To("test@otherdomain.com"). + Subject("Testmail"). + Greeting("Hi there,"). + Line("This is a formal message"). + Action("View Task", "https://example.com/task/123") + + mailopts, err := RenderMail(mail, "en") + require.NoError(t, err) + + // Should contain greeting + assert.Contains(t, mailopts.HTMLMessage, "Hi there,") + + // Should use formal styling + assert.Contains(t, mailopts.HTMLMessage, "background: #f3f4f6") + assert.Contains(t, mailopts.HTMLMessage, "font-family: 'Open Sans'") + assert.Contains(t, mailopts.HTMLMessage, "width: 600px") + assert.Contains(t, mailopts.HTMLMessage, "height: 75px") + + // Should HAVE logo in formal emails + assert.Contains(t, mailopts.HTMLMessage, "logo.png") + assert.Contains(t, mailopts.HTMLMessage, "Vikunja") + + // Should have formal button styling + assert.Contains(t, mailopts.HTMLMessage, "background-color: #1973ff") + assert.Contains(t, mailopts.HTMLMessage, "width:280px") + + // Should not have conversational arrow + assert.NotContains(t, mailopts.HTMLMessage, "View Task →") + + // Plain text should have greeting + assert.Contains(t, mailopts.Message, "Hi there,") + }) + + t.Run("Conversational without action", func(t *testing.T) { + mail := NewMail(). + From("test@example.com"). + To("test@otherdomain.com"). + Subject("Testmail"). + Conversational(). + Line("This is a conversational message without action") + + mailopts, err := RenderMail(mail, "en") + require.NoError(t, err) + + // Should use conversational styling + assert.Contains(t, mailopts.HTMLMessage, "background: #f6f8fa") + + // Should not have action section + assert.NotContains(t, mailopts.HTMLMessage, "border-top: 1px solid #e5e7eb") + }) + + t.Run("Conversational with footer", func(t *testing.T) { + mail := NewMail(). + From("test@example.com"). + To("test@otherdomain.com"). + Subject("Testmail"). + Conversational(). + Line("This is a conversational message"). + FooterLine("This is a footer line") + + mailopts, err := RenderMail(mail, "en") + require.NoError(t, err) + + // Should have footer with conversational styling + assert.Contains(t, mailopts.HTMLMessage, "color: #656d76") + assert.Contains(t, mailopts.HTMLMessage, "font-size: 12px") + assert.Contains(t, mailopts.HTMLMessage, "This is a footer line") + }) + + t.Run("Conversational header line format", func(t *testing.T) { + action := "testuser left a comment" + taskURL := "https://example.com/task/123" + projectTitle := "Test Project" + taskTitle := "Test Task" + taskIdentifier := "#1" + + headerLine := CreateConversationalHeader("", action, taskURL, projectTitle, taskIdentifier, taskTitle) + + // Should contain action with username + assert.Contains(t, headerLine, "testuser left a comment") + + // Should contain task link with identifier and GitHub blue color + assert.Contains(t, headerLine, `This is the comment body

"). + Action("View Task", "https://example.com/task/123") + + mailOpts, err := RenderMail(mail, "en") + require.NoError(t, err) + + // Header should be in its own section + assert.Contains(t, mailOpts.HTMLMessage, `color: #57606a`) + assert.Contains(t, mailOpts.HTMLMessage, `testuser left a comment`) + + // Body content should be separate from the header + assert.Contains(t, mailOpts.HTMLMessage, `This is the comment body`) + + // Plain text should contain the header text + assert.Contains(t, mailOpts.Message, "testuser left a comment") + }) + + t.Run("Conversational header line only (no body)", func(t *testing.T) { + header := CreateConversationalHeader("", "testuser assigned you", "https://example.com/task/123", "Project", "#1", "Task") + mail := NewMail(). + Conversational(). + Subject("Test"). + HeaderLine(header). + Action("View Task", "https://example.com/task/123") + + mailOpts, err := RenderMail(mail, "en") + require.NoError(t, err) + + // Header should render + assert.Contains(t, mailOpts.HTMLMessage, `testuser assigned you`) + + // Should not have the content section when there are no intro/outro lines + assert.NotContains(t, mailOpts.HTMLMessage, `padding: 20px; padding-bottom: 0; color: #24292f`) + }) + + t.Run("Translation system integration", func(t *testing.T) { + // Test that translation keys are properly structured + // This verifies the translation keys exist and are accessible + + // Test action translations + headerLine1 := CreateConversationalHeader("", "John left a comment", "https://example.com", "Project", "#1", "Task") + assert.Contains(t, headerLine1, "John left a comment") + + headerLine2 := CreateConversationalHeader("", "Jane assigned you", "https://example.com", "Project", "#2", "Task") + assert.Contains(t, headerLine2, "Jane assigned you") + + // Verify header structure is maintained + assert.Contains(t, headerLine1, `color: #0969da`) + assert.Contains(t, headerLine1, "(Project > Task) #1") + }) +}