use markdown parsing to identify mentions (#8139)

Этот коммит содержится в:
Chris
2018-01-23 13:48:20 -06:00
коммит произвёл Harrison Healey
родитель b34384dbad
Коммит 3dad632043
2 изменённых файлов: 334 добавлений и 388 удалений

Просмотреть файл

@@ -10,7 +10,6 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"path/filepath" "path/filepath"
"regexp"
"sort" "sort"
"strings" "strings"
"time" "time"
@@ -20,6 +19,7 @@ import (
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
"github.com/mattermost/mattermost-server/utils/markdown"
"github.com/nicksnyder/go-i18n/i18n" "github.com/nicksnyder/go-i18n/i18n"
) )
@@ -71,8 +71,8 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
} else { } else {
keywords := a.GetMentionKeywordsInChannel(profileMap, post.Type != model.POST_HEADER_CHANGE && post.Type != model.POST_PURPOSE_CHANGE) keywords := a.GetMentionKeywordsInChannel(profileMap, post.Type != model.POST_HEADER_CHANGE && post.Type != model.POST_PURPOSE_CHANGE)
var potentialOtherMentions []string m := GetExplicitMentions(post.Message, keywords)
mentionedUserIds, potentialOtherMentions, hereNotification, channelNotification, allNotification = GetExplicitMentions(post.Message, keywords) mentionedUserIds, hereNotification, channelNotification, allNotification = m.MentionedUserIds, m.HereMentioned, m.ChannelMentioned, m.AllMentioned
// get users that have comment thread mentions enabled // get users that have comment thread mentions enabled
if len(post.RootId) > 0 && parentPostList != nil { if len(post.RootId) > 0 && parentPostList != nil {
@@ -89,8 +89,8 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
delete(mentionedUserIds, post.UserId) delete(mentionedUserIds, post.UserId)
} }
if len(potentialOtherMentions) > 0 { if len(m.OtherPotentialMentions) > 0 {
if result := <-a.Srv.Store.User().GetProfilesByUsernames(potentialOtherMentions, team.Id); result.Err == nil { if result := <-a.Srv.Store.User().GetProfilesByUsernames(m.OtherPotentialMentions, team.Id); result.Err == nil {
outOfChannelMentions := result.Data.([]*model.User) outOfChannelMentions := result.Data.([]*model.User)
if channel.Type != model.CHANNEL_GROUP { if channel.Type != model.CHANNEL_GROUP {
a.Go(func() { a.Go(func() {
@@ -788,125 +788,133 @@ func (a *App) sendOutOfChannelMentions(sender *model.User, post *model.Post, cha
return nil return nil
} }
type ExplicitMentions struct {
// MentionedUserIds contains a key for each user mentioned by keyword.
MentionedUserIds map[string]bool
// OtherPotentialMentions contains a list of strings that looked like mentions, but didn't have
// a corresponding keyword.
OtherPotentialMentions []string
// HereMentioned is true if the message contained @here.
HereMentioned bool
// AllMentioned is true if the message contained @all.
AllMentioned bool
// ChannelMentioned is true if the message contained @channel.
ChannelMentioned bool
}
// Given a message and a map mapping mention keywords to the users who use them, returns a map of mentioned // Given a message and a map mapping mention keywords to the users who use them, returns a map of mentioned
// users and a slice of potential mention users not in the channel and whether or not @here was mentioned. // users and a slice of potential mention users not in the channel and whether or not @here was mentioned.
func GetExplicitMentions(message string, keywords map[string][]string) (map[string]bool, []string, bool, bool, bool) { func GetExplicitMentions(message string, keywords map[string][]string) *ExplicitMentions {
mentioned := make(map[string]bool) ret := &ExplicitMentions{
potentialOthersMentioned := make([]string, 0) MentionedUserIds: make(map[string]bool),
}
systemMentions := map[string]bool{"@here": true, "@channel": true, "@all": true} systemMentions := map[string]bool{"@here": true, "@channel": true, "@all": true}
hereMentioned := false
allMentioned := false
channelMentioned := false
addMentionedUsers := func(ids []string) { addMentionedUsers := func(ids []string) {
for _, id := range ids { for _, id := range ids {
mentioned[id] = true ret.MentionedUserIds[id] = true
} }
} }
message = removeCodeFromMessage(message) processText := func(text string) {
for _, word := range strings.FieldsFunc(text, func(c rune) bool {
// Split on any whitespace or punctuation that can't be part of an at mention or emoji pattern
return !(c == ':' || c == '.' || c == '-' || c == '_' || c == '@' || unicode.IsLetter(c) || unicode.IsNumber(c))
}) {
isMention := false
for _, word := range strings.FieldsFunc(message, func(c rune) bool { // skip word with format ':word:' with an assumption that it is an emoji format only
// Split on any whitespace or punctuation that can't be part of an at mention or emoji pattern if word[0] == ':' && word[len(word)-1] == ':' {
return !(c == ':' || c == '.' || c == '-' || c == '_' || c == '@' || unicode.IsLetter(c) || unicode.IsNumber(c)) continue
}) { }
isMention := false
// skip word with format ':word:' with an assumption that it is an emoji format only if word == "@here" {
if word[0] == ':' && word[len(word)-1] == ':' { ret.HereMentioned = true
continue }
}
if word == "@here" { if word == "@channel" {
hereMentioned = true ret.ChannelMentioned = true
} }
if word == "@channel" { if word == "@all" {
channelMentioned = true ret.AllMentioned = true
} }
if word == "@all" { // Non-case-sensitive check for regular keys
allMentioned = true if ids, match := keywords[strings.ToLower(word)]; match {
} addMentionedUsers(ids)
isMention = true
}
// Non-case-sensitive check for regular keys // Case-sensitive check for first name
if ids, match := keywords[strings.ToLower(word)]; match { if ids, match := keywords[word]; match {
addMentionedUsers(ids) addMentionedUsers(ids)
isMention = true isMention = true
} }
// Case-sensitive check for first name if isMention {
if ids, match := keywords[word]; match { continue
addMentionedUsers(ids) }
isMention = true
}
if isMention { if strings.ContainsAny(word, ".-:") {
continue // This word contains a character that may be the end of a sentence, so split further
} splitWords := strings.FieldsFunc(word, func(c rune) bool {
return c == '.' || c == '-' || c == ':'
})
if strings.ContainsAny(word, ".-:") { for _, splitWord := range splitWords {
// This word contains a character that may be the end of a sentence, so split further if splitWord == "@here" {
splitWords := strings.FieldsFunc(word, func(c rune) bool { ret.HereMentioned = true
return c == '.' || c == '-' || c == ':' }
})
for _, splitWord := range splitWords { if splitWord == "@all" {
if splitWord == "@here" { ret.AllMentioned = true
hereMentioned = true }
}
if splitWord == "@all" { if splitWord == "@channel" {
allMentioned = true ret.ChannelMentioned = true
} }
if splitWord == "@channel" { // Non-case-sensitive check for regular keys
channelMentioned = true if ids, match := keywords[strings.ToLower(splitWord)]; match {
} addMentionedUsers(ids)
}
// Non-case-sensitive check for regular keys // Case-sensitive check for first name
if ids, match := keywords[strings.ToLower(splitWord)]; match { if ids, match := keywords[splitWord]; match {
addMentionedUsers(ids) addMentionedUsers(ids)
} } else if _, ok := systemMentions[splitWord]; !ok && strings.HasPrefix(splitWord, "@") {
username := splitWord[1:]
// Case-sensitive check for first name ret.OtherPotentialMentions = append(ret.OtherPotentialMentions, username)
if ids, match := keywords[splitWord]; match { }
addMentionedUsers(ids)
} else if _, ok := systemMentions[splitWord]; !ok && strings.HasPrefix(splitWord, "@") {
username := splitWord[1:]
potentialOthersMentioned = append(potentialOthersMentioned, username)
} }
} }
}
if _, ok := systemMentions[word]; !ok && strings.HasPrefix(word, "@") { if _, ok := systemMentions[word]; !ok && strings.HasPrefix(word, "@") {
username := word[1:] username := word[1:]
potentialOthersMentioned = append(potentialOthersMentioned, username) ret.OtherPotentialMentions = append(ret.OtherPotentialMentions, username)
}
} }
} }
return mentioned, potentialOthersMentioned, hereMentioned, channelMentioned, allMentioned buf := ""
} markdown.Inspect(message, func(node interface{}) bool {
text, ok := node.(*markdown.Text)
if !ok {
processText(buf)
buf = ""
return true
}
buf += text.Text
return false
})
processText(buf)
// Matches a line containing only ``` and a potential language definition, any number of lines not containing ```, return ret
// and then either a line containing only ``` or the end of the text
var codeBlockPattern = regexp.MustCompile("(?m)^[^\\S\n]*[\\`~]{3}.*$[\\s\\S]+?(^[^\\S\n]*[`~]{3}$|\\z)")
// Matches a backquote, either some text or any number of non-empty lines, and then a final backquote
var inlineCodePattern = regexp.MustCompile("(?m)\\`+(?:.+?|.*?\n(.*?\\S.*?\n)*.*?)\\`+")
// Strips pre-formatted text and code blocks from a Markdown string by replacing them with whitespace
func removeCodeFromMessage(message string) string {
if strings.Contains(message, "```") || strings.Contains(message, "~~~") {
message = codeBlockPattern.ReplaceAllString(message, "")
}
// Replace with a space to prevent cases like "user`code`name" from turning into "username"
if strings.Contains(message, "`") {
message = inlineCodePattern.ReplaceAllString(message, " ")
}
return message
} }
// Given a map of user IDs to profiles, returns a list of mention // Given a map of user IDs to profiles, returns a list of mention

Просмотреть файл

@@ -7,6 +7,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
) )
@@ -82,147 +84,229 @@ func TestGetExplicitMentions(t *testing.T) {
id2 := model.NewId() id2 := model.NewId()
id3 := model.NewId() id3 := model.NewId()
// not mentioning anybody for name, tc := range map[string]struct {
message := "this is a message" Message string
keywords := map[string][]string{} Keywords map[string][]string
if mentions, potential, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 0 || len(potential) != 0 { Expected *ExplicitMentions
t.Fatal("shouldn't have mentioned anybody or have any potencial mentions") }{
} "Nobody": {
Message: "this is a message",
// mentioning a user that doesn't exist Keywords: map[string][]string{},
message = "this is a message for @user" Expected: &ExplicitMentions{},
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 0 { },
t.Fatal("shouldn't have mentioned user that doesn't exist") "NonexistentUser": {
} Message: "this is a message for @user",
Expected: &ExplicitMentions{
// mentioning one person OtherPotentialMentions: []string{"user"},
keywords = map[string][]string{"@user": {id1}} },
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] { },
t.Fatal("should've mentioned @user") "OnePerson": {
} Message: "this is a message for @user",
Keywords: map[string][]string{"@user": {id1}},
// mentioning one person without an @mention Expected: &ExplicitMentions{
message = "this is a message for @user" MentionedUserIds: map[string]bool{
keywords = map[string][]string{"this": {id1}} id1: true,
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] { },
t.Fatal("should've mentioned this") },
} },
"OnePersonWithoutAtMention": {
// mentioning multiple people with one word Message: "this is a message for @user",
message = "this is a message for @user" Keywords: map[string][]string{"this": {id1}},
keywords = map[string][]string{"@user": {id1, id2}} Expected: &ExplicitMentions{
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] { MentionedUserIds: map[string]bool{
t.Fatal("should've mentioned two users with @user") id1: true,
} },
OtherPotentialMentions: []string{"user"},
// mentioning only one of multiple people },
keywords = map[string][]string{"@user": {id1}, "@mention": {id2}} },
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] { "MultiplePeopleWithOneWord": {
t.Fatal("should've mentioned @user and not @mention") Message: "this is a message for @user",
} Keywords: map[string][]string{"@user": {id1, id2}},
Expected: &ExplicitMentions{
// mentioning multiple people with multiple words MentionedUserIds: map[string]bool{
message = "this is an @mention for @user" id1: true,
keywords = map[string][]string{"@user": {id1}, "@mention": {id2}} id2: true,
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] { },
t.Fatal("should've mentioned two users with @user and @mention") },
} },
"OneOfMultiplePeople": {
// mentioning @channel (not a special case, but it's good to double check) Message: "this is a message for @user",
message = "this is an message for @channel" Keywords: map[string][]string{"@user": {id1}, "@mention": {id2}},
keywords = map[string][]string{"@channel": {id1, id2}} Expected: &ExplicitMentions{
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] { MentionedUserIds: map[string]bool{
t.Fatal("should've mentioned two users with @channel") id1: true,
} },
},
// mentioning @all (not a special case, but it's good to double check) },
message = "this is an message for @all" "MultiplePeopleWithMultipleWords": {
keywords = map[string][]string{"@all": {id1, id2}} Message: "this is an @mention for @user",
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] { Keywords: map[string][]string{"@user": {id1}, "@mention": {id2}},
t.Fatal("should've mentioned two users with @all") Expected: &ExplicitMentions{
} MentionedUserIds: map[string]bool{
id1: true,
// mentioning user.period without mentioning user (PLT-3222) id2: true,
message = "user.period doesn't complicate things at all by including periods in their username" },
keywords = map[string][]string{"user.period": {id1}, "user": {id2}} },
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] { },
t.Fatal("should've mentioned user.period and not user") "Channel": {
} Message: "this is an message for @channel",
Keywords: map[string][]string{"@channel": {id1, id2}},
// mentioning a potential out of channel user Expected: &ExplicitMentions{
message = "this is an message for @potential and @user" MentionedUserIds: map[string]bool{
keywords = map[string][]string{"@user": {id1}} id1: true,
if mentions, potential, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || len(potential) != 1 { id2: true,
t.Fatal("should've mentioned user and have a potential not in channel") },
} ChannelMentioned: true,
},
// words in inline code shouldn't trigger mentions },
message = "`this shouldn't mention @channel at all`" "All": {
keywords = map[string][]string{} Message: "this is an message for @all",
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 0 { Keywords: map[string][]string{"@all": {id1, id2}},
t.Fatal("@channel in inline code shouldn't cause a mention") Expected: &ExplicitMentions{
} MentionedUserIds: map[string]bool{
id1: true,
// words in code blocks shouldn't trigger mentions id2: true,
message = "```\nthis shouldn't mention @channel at all\n```" },
keywords = map[string][]string{} AllMentioned: true,
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 0 { },
t.Fatal("@channel in code block shouldn't cause a mention") },
} "UserWithPeriod": {
Message: "user.period doesn't complicate things at all by including periods in their username",
// Markdown-formatted text that isn't code should trigger mentions Keywords: map[string][]string{"user.period": {id1}, "user": {id2}},
message = "*@aaa @bbb @ccc*" Expected: &ExplicitMentions{
keywords = map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}} MentionedUserIds: map[string]bool{
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 3 || !mentions[id1] || !mentions[id2] || !mentions[id3] { id1: true,
t.Fatal("should've mentioned all 3 users", mentions) },
} },
},
message = "**@aaa @bbb @ccc**" "PotentialOutOfChannelUser": {
keywords = map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}} Message: "this is an message for @potential and @user",
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 3 || !mentions[id1] || !mentions[id2] || !mentions[id3] { Keywords: map[string][]string{"@user": {id1}},
t.Fatal("should've mentioned all 3 users") Expected: &ExplicitMentions{
} MentionedUserIds: map[string]bool{
id1: true,
message = "~~@aaa @bbb @ccc~~" },
keywords = map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}} OtherPotentialMentions: []string{"potential"},
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 3 || !mentions[id1] || !mentions[id2] || !mentions[id3] { },
t.Fatal("should've mentioned all 3 users") },
} "InlineCode": {
Message: "`this shouldn't mention @channel at all`",
message = "### @aaa" Keywords: map[string][]string{},
keywords = map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}} Expected: &ExplicitMentions{},
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] || mentions[id3] { },
t.Fatal("should've only mentioned aaa") "FencedCodeBlock": {
} Message: "```\nthis shouldn't mention @channel at all\n```",
Keywords: map[string][]string{},
message = "> @aaa" Expected: &ExplicitMentions{},
keywords = map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}} },
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] || mentions[id3] { "Emphasis": {
t.Fatal("should've only mentioned aaa") Message: "*@aaa @bbb @ccc*",
} Keywords: map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}},
Expected: &ExplicitMentions{
message = ":smile:" MentionedUserIds: map[string]bool{
keywords = map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}} id1: true,
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) == 1 || mentions[id1] { id2: true,
t.Fatal("should not mentioned smile") id3: true,
} },
},
message = "smile" },
keywords = map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}} "StrongEmphasis": {
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] || mentions[id3] { Message: "**@aaa @bbb @ccc**",
t.Fatal("should've only mentioned smile") Keywords: map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}},
} Expected: &ExplicitMentions{
MentionedUserIds: map[string]bool{
message = ":smile" id1: true,
keywords = map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}} id2: true,
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] || mentions[id3] { id3: true,
t.Fatal("should've only mentioned smile") },
} },
},
message = "smile:" "Strikethrough": {
keywords = map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}} Message: "~~@aaa @bbb @ccc~~",
if mentions, _, _, _, _ := GetExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] || mentions[id3] { Keywords: map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}},
t.Fatal("should've only mentioned smile") Expected: &ExplicitMentions{
MentionedUserIds: map[string]bool{
id1: true,
id2: true,
id3: true,
},
},
},
"Heading": {
Message: "### @aaa",
Keywords: map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}},
Expected: &ExplicitMentions{
MentionedUserIds: map[string]bool{
id1: true,
},
},
},
"BlockQuote": {
Message: "> @aaa",
Keywords: map[string][]string{"@aaa": {id1}, "@bbb": {id2}, "@ccc": {id3}},
Expected: &ExplicitMentions{
MentionedUserIds: map[string]bool{
id1: true,
},
},
},
"Emoji": {
Message: ":smile:",
Keywords: map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}},
Expected: &ExplicitMentions{},
},
"NotEmoji": {
Message: "smile",
Keywords: map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}},
Expected: &ExplicitMentions{
MentionedUserIds: map[string]bool{
id1: true,
},
},
},
"UnclosedEmoji": {
Message: ":smile",
Keywords: map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}},
Expected: &ExplicitMentions{
MentionedUserIds: map[string]bool{
id1: true,
},
},
},
"UnopenedEmoji": {
Message: "smile:",
Keywords: map[string][]string{"smile": {id1}, "smiley": {id2}, "smiley_cat": {id3}},
Expected: &ExplicitMentions{
MentionedUserIds: map[string]bool{
id1: true,
},
},
},
"IndentedCodeBlock": {
Message: " this shouldn't mention @channel at all",
Keywords: map[string][]string{},
Expected: &ExplicitMentions{},
},
"LinkTitle": {
Message: `[foo](this "shouldn't mention @channel at all")`,
Keywords: map[string][]string{},
Expected: &ExplicitMentions{},
},
"MalformedInlineCode": {
Message: "`this should mention @channel``",
Keywords: map[string][]string{},
Expected: &ExplicitMentions{
ChannelMentioned: true,
},
},
} {
t.Run(name, func(t *testing.T) {
m := GetExplicitMentions(tc.Message, tc.Keywords)
if tc.Expected.MentionedUserIds == nil {
tc.Expected.MentionedUserIds = make(map[string]bool)
}
assert.EqualValues(t, tc.Expected, m)
})
} }
} }
@@ -268,170 +352,24 @@ func TestGetExplicitMentionsAtHere(t *testing.T) {
} }
for message, shouldMention := range cases { for message, shouldMention := range cases {
if _, _, hereMentioned, _, _ := GetExplicitMentions(message, nil); hereMentioned && !shouldMention { if m := GetExplicitMentions(message, nil); m.HereMentioned && !shouldMention {
t.Fatalf("shouldn't have mentioned @here with \"%v\"", message) t.Fatalf("shouldn't have mentioned @here with \"%v\"", message)
} else if !hereMentioned && shouldMention { } else if !m.HereMentioned && shouldMention {
t.Fatalf("should've have mentioned @here with \"%v\"", message) t.Fatalf("should've mentioned @here with \"%v\"", message)
} }
} }
// mentioning @here and someone // mentioning @here and someone
id := model.NewId() id := model.NewId()
if mentions, potential, hereMentioned, _, _ := GetExplicitMentions("@here @user @potential", map[string][]string{"@user": {id}}); !hereMentioned { if m := GetExplicitMentions("@here @user @potential", map[string][]string{"@user": {id}}); !m.HereMentioned {
t.Fatal("should've mentioned @here with \"@here @user\"") t.Fatal("should've mentioned @here with \"@here @user\"")
} else if len(mentions) != 1 || !mentions[id] { } else if len(m.MentionedUserIds) != 1 || !m.MentionedUserIds[id] {
t.Fatal("should've mentioned @user with \"@here @user\"") t.Fatal("should've mentioned @user with \"@here @user\"")
} else if len(potential) > 1 { } else if len(m.OtherPotentialMentions) > 1 {
t.Fatal("should've potential mentions for @potential") t.Fatal("should've potential mentions for @potential")
} }
} }
func TestRemoveCodeFromMessage(t *testing.T) {
input := "this is regular text"
expected := input
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is text with\n```\na code block\n```\nin it"
expected = "this is text with\n\nin it"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is text with\n```javascript\na JS code block\n```\nin it"
expected = "this is text with\n\nin it"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is text with\n```java script?\na JS code block\n```\nin it"
expected = "this is text with\n\nin it"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is text with an empty\n```\n\n\n\n```\nin it"
expected = "this is text with an empty\n\nin it"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is text with\n```\ntwo\n```\ncode\n```\nblocks\n```\nin it"
expected = "this is text with\n\ncode\n\nin it"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is text with indented\n ```\ncode\n ```\nin it"
expected = "this is text with indented\n\nin it"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is text ending with\n```\nan unfinished code block"
expected = "this is text ending with\n"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is `code` in a sentence"
expected = "this is in a sentence"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is `two` things of `code` in a sentence"
expected = "this is things of in a sentence"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is `code with spaces` in a sentence"
expected = "this is in a sentence"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is `code\nacross multiple` lines"
expected = "this is lines"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is `code\non\nmany\ndifferent` lines"
expected = "this is lines"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is `\ncode on its own line\n` across multiple lines"
expected = "this is across multiple lines"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is `\n some more code \n` across multiple lines"
expected = "this is across multiple lines"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is `\ncode` on its own line"
expected = "this is on its own line"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is `code\n` on its own line"
expected = "this is on its own line"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is *italics mixed with `code in a way that has the code` take precedence*"
expected = "this is *italics mixed with take precedence*"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is code within a wo` `rd for some reason"
expected = "this is code within a wo rd for some reason"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is `not\n\ncode` because it has a blank line"
expected = input
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is `not\n \ncode` because it has a line with only whitespace"
expected = input
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is just `` two backquotes"
expected = input
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "these are ``multiple backquotes`` around code"
expected = "these are around code"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
input = "this is text with\n~~~\na code block\n~~~\nin it"
expected = "this is text with\n\nin it"
if actual := removeCodeFromMessage(input); actual != expected {
t.Fatalf("received incorrect output\n\nGot:\n%v\n\nExpected:\n%v\n", actual, expected)
}
}
func TestGetMentionKeywords(t *testing.T) { func TestGetMentionKeywords(t *testing.T) {
th := Setup() th := Setup()
defer th.TearDown() defer th.TearDown()