* fix mobile mention issue #15616 * add state != nil * clear previous text when clear mentions * fix: after selected mention user, and type @ not working * bump version
This commit is contained in:
parent
83ad76637a
commit
8608aecdb4
|
@ -10,6 +10,10 @@ import (
|
|||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/status-im/status-go/logutils"
|
||||
|
||||
"github.com/status-im/status-go/api/multiformat"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
)
|
||||
|
@ -160,7 +164,7 @@ func (ms *MentionState) String() string {
|
|||
}
|
||||
atIdxsStr += fmt.Sprintf("%+v", entry)
|
||||
}
|
||||
return fmt.Sprintf("MentionState{AtSignIdx: %d, AtIdxs: [%s], MentionEnd: %d, PreviousText: %q, NewText: %q, Start: %d, End: %d}",
|
||||
return fmt.Sprintf("MentionState{AtSignIdx: %d, AtIdxs: [%s], MentionEnd: %d, PreviousText: %q, NewText: %s, Start: %d, End: %d}",
|
||||
ms.AtSignIdx, atIdxsStr, ms.MentionEnd, ms.PreviousText, *ms.NewText, ms.Start, ms.End)
|
||||
}
|
||||
|
||||
|
@ -169,6 +173,7 @@ type ChatMentionContext struct {
|
|||
InputSegments []InputSegment
|
||||
MentionSuggestions map[string]*MentionableUser
|
||||
MentionState *MentionState
|
||||
PreviousText string // user input text before the last change
|
||||
NewText string
|
||||
}
|
||||
|
||||
|
@ -189,12 +194,14 @@ type MentionManager struct {
|
|||
mentionContexts map[string]*ChatMentionContext
|
||||
*Messenger
|
||||
mentionableUserGetter
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewMentionManager(m *Messenger) *MentionManager {
|
||||
mm := &MentionManager{
|
||||
mentionContexts: make(map[string]*ChatMentionContext),
|
||||
Messenger: m,
|
||||
logger: logutils.ZapLogger().Named("MentionManager"),
|
||||
}
|
||||
mm.mentionableUserGetter = mm
|
||||
return mm
|
||||
|
@ -296,25 +303,24 @@ func (m *MentionManager) CheckMentions(chatID, text string) (string, error) {
|
|||
return newText, nil
|
||||
}
|
||||
|
||||
func (m *MentionManager) OnTextInput(chatID string, state *MentionState) (*ChatMentionContext, error) {
|
||||
if state == nil {
|
||||
return nil, fmt.Errorf("mention[OnTextInput] state should not be nil")
|
||||
}
|
||||
func (m *MentionManager) OnChangeText(chatID, text string) (*ChatMentionContext, error) {
|
||||
ctx := m.getChatMentionContext(chatID)
|
||||
previousState := ctx.MentionState
|
||||
var newState *MentionState
|
||||
if previousState == nil {
|
||||
newState = state
|
||||
} else {
|
||||
previousState.PreviousText = state.PreviousText
|
||||
previousState.NewText = state.NewText
|
||||
previousState.Start = state.Start
|
||||
previousState.End = state.End
|
||||
newState = previousState
|
||||
diff := diffText(ctx.PreviousText, text)
|
||||
if diff == nil {
|
||||
return ctx, nil
|
||||
}
|
||||
newState.AtIdxs = calcAtIdxs(newState)
|
||||
ctx.MentionState = newState
|
||||
return ctx, nil
|
||||
ctx.PreviousText = text
|
||||
if ctx.MentionState == nil {
|
||||
ctx.MentionState = &MentionState{}
|
||||
}
|
||||
ctx.MentionState.PreviousText = diff.previousText
|
||||
ctx.MentionState.NewText = &diff.newText
|
||||
ctx.MentionState.Start = diff.start
|
||||
ctx.MentionState.End = diff.end
|
||||
|
||||
ctx.MentionState.AtIdxs = calcAtIdxs(ctx.MentionState)
|
||||
m.logger.Debug("OnChangeText", zap.String("chatID", chatID), zap.Any("state", ctx.MentionState))
|
||||
return m.CalculateSuggestions(chatID, text)
|
||||
}
|
||||
|
||||
func (m *MentionManager) RecheckAtIdxs(chatID string, text string, publicKey string) (*ChatMentionContext, error) {
|
||||
|
@ -338,6 +344,7 @@ func (m *MentionManager) CalculateSuggestions(chatID, text string) (*ChatMention
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.logger.Debug("CalculateSuggestions", zap.String("chatID", chatID), zap.String("text", text), zap.Int("num of mentionable user", len(mentionableUsers)))
|
||||
|
||||
m.calculateSuggestions(chatID, text, mentionableUsers)
|
||||
|
||||
|
@ -371,12 +378,12 @@ func (m *MentionManager) calculateSuggestions(chatID string, text string, mentio
|
|||
} else {
|
||||
end = state.Start
|
||||
}
|
||||
atSignIdx := LastIndexOf(text, charAtSign, state.Start)
|
||||
tr := []rune(text)
|
||||
searchedText := strings.ToLower(string(tr[atSignIdx+1 : end]))
|
||||
atSignIdx := lastIndexOf(text, charAtSign, state.End)
|
||||
searchedText := strings.ToLower(subs(text, atSignIdx+1, end))
|
||||
m.logger.Debug("calculateSuggestions", zap.Int("atSignIdx", atSignIdx), zap.String("searchedText", searchedText), zap.String("text", text), zap.Any("state", state))
|
||||
|
||||
var suggestions map[string]*MentionableUser
|
||||
if atSignIdx <= state.Start && end-atSignIdx <= 100 {
|
||||
if (atSignIdx <= state.Start && end-atSignIdx <= 100) || text[len(text)-1] == charAtSign[0] {
|
||||
suggestions = getUserSuggestions(mentionableUsers, searchedText, -1)
|
||||
}
|
||||
|
||||
|
@ -418,6 +425,7 @@ func (m *MentionManager) ClearMentions(chatID string) {
|
|||
ctx.MentionState = nil
|
||||
ctx.InputSegments = nil
|
||||
ctx.NewText = ""
|
||||
ctx.PreviousText = ""
|
||||
m.clearSuggestions(chatID)
|
||||
}
|
||||
|
||||
|
@ -430,7 +438,7 @@ func (m *MentionManager) HandleSelectionChange(chatID, text string, start int, e
|
|||
func (m *MentionManager) handleSelectionChange(chatID, text string, start int, end int, mentionableUsers map[string]*MentionableUser) {
|
||||
ctx := m.getChatMentionContext(chatID)
|
||||
state := ctx.MentionState
|
||||
if len(state.AtIdxs) > 0 {
|
||||
if state != nil && len(state.AtIdxs) > 0 {
|
||||
var atIdx *AtIndexEntry
|
||||
for _, idx := range state.AtIdxs {
|
||||
if start >= idx.From && end-1 <= idx.To {
|
||||
|
@ -449,6 +457,7 @@ func (m *MentionManager) handleSelectionChange(chatID, text string, start int, e
|
|||
m.clearSuggestions(chatID)
|
||||
}
|
||||
}
|
||||
ctx.PreviousText = text
|
||||
}
|
||||
|
||||
func (m *MentionManager) ToInputField(chatID, text string) (*ChatMentionContext, error) {
|
||||
|
@ -982,14 +991,13 @@ func calculateInput(text string, idxs []*AtIndexEntry) []InputSegment {
|
|||
if len(idxs) == 0 {
|
||||
return []InputSegment{{Type: Text, Value: text}}
|
||||
}
|
||||
tr := []rune(text)
|
||||
idxCount := len(idxs)
|
||||
lastFrom := idxs[idxCount-1].From
|
||||
|
||||
var result []InputSegment
|
||||
|
||||
if idxs[0].From != 0 {
|
||||
result = append(result, InputSegment{Type: Text, Value: string(tr[:idxs[0].From])})
|
||||
result = append(result, InputSegment{Type: Text, Value: subs(text, 0, idxs[0].From)})
|
||||
}
|
||||
|
||||
for _, entry := range idxs {
|
||||
|
@ -999,19 +1007,49 @@ func calculateInput(text string, idxs []*AtIndexEntry) []InputSegment {
|
|||
mentioned := entry.Mentioned
|
||||
|
||||
if mentioned && nextAtIdx != intUnknown {
|
||||
result = append(result, InputSegment{Type: Mention, Value: string(tr[from : to+1])})
|
||||
result = append(result, InputSegment{Type: Text, Value: string(tr[to+1 : nextAtIdx])})
|
||||
result = append(result, InputSegment{Type: Mention, Value: subs(text, from, to+1)})
|
||||
result = append(result, InputSegment{Type: Text, Value: subs(text, to+1, nextAtIdx)})
|
||||
} else if mentioned && lastFrom == from {
|
||||
result = append(result, InputSegment{Type: Mention, Value: string(tr[from : to+1])})
|
||||
result = append(result, InputSegment{Type: Text, Value: string(tr[to+1:])})
|
||||
result = append(result, InputSegment{Type: Mention, Value: subs(text, from, to+1)})
|
||||
result = append(result, InputSegment{Type: Text, Value: subs(text, to+1)})
|
||||
} else {
|
||||
result = append(result, InputSegment{Type: Text, Value: string(tr[from : to+1])})
|
||||
result = append(result, InputSegment{Type: Text, Value: subs(text, from, to+1)})
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func subs(s string, start int, end ...int) string {
|
||||
tr := []rune(s)
|
||||
e := len(tr)
|
||||
|
||||
if len(end) > 0 {
|
||||
e = end[0]
|
||||
}
|
||||
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
|
||||
if e > len(tr) {
|
||||
e = len(tr)
|
||||
}
|
||||
|
||||
if e < 0 {
|
||||
e = 0
|
||||
}
|
||||
|
||||
if start > e {
|
||||
start, e = e, start
|
||||
if e > len(tr) {
|
||||
e = len(tr)
|
||||
}
|
||||
}
|
||||
|
||||
return string(tr[start:e])
|
||||
}
|
||||
|
||||
func isValidTerminatingCharacter(c rune) bool {
|
||||
switch c {
|
||||
case '\t': // tab
|
||||
|
@ -1152,9 +1190,9 @@ func toInfo(inputSegments []InputSegment) *MentionState {
|
|||
return state
|
||||
}
|
||||
|
||||
// LastIndexOf returns the index of the last occurrence of substr in s starting from index start.
|
||||
// lastIndexOf returns the index of the last occurrence of substr in s starting from index start.
|
||||
// If substr is not present in s, it returns -1.
|
||||
func LastIndexOf(s, substr string, start int) int {
|
||||
func lastIndexOf(s, substr string, start int) int {
|
||||
if start < 0 {
|
||||
return -1
|
||||
}
|
||||
|
@ -1186,3 +1224,68 @@ func reverse(r []rune) string {
|
|||
}
|
||||
return string(r)
|
||||
}
|
||||
|
||||
type TextDiff struct {
|
||||
previousText string
|
||||
newText string // we always set it to empty if it's a delete operation
|
||||
start int
|
||||
end int
|
||||
}
|
||||
|
||||
// hasCommonCharSequence checks if str1 has a common character sequence with str2.
|
||||
// It iterates through both strings and compares their characters one by one.
|
||||
// The function returns true if all characters in str1 can be found in str2 in the same order, but not necessarily consecutively.
|
||||
// This is helpful for determining if there is an insertion or deletion operation between two strings.
|
||||
func hasCommonCharSequence(str1, str2 []rune) bool {
|
||||
i, j := 0, 0
|
||||
for i < len(str1) && j < len(str2) {
|
||||
if str1[i] == str2[j] {
|
||||
i++
|
||||
}
|
||||
j++
|
||||
}
|
||||
return i == len(str1)
|
||||
}
|
||||
|
||||
func diffText(oldText, newText string) *TextDiff {
|
||||
if oldText == newText {
|
||||
return nil
|
||||
}
|
||||
t1 := []rune(oldText)
|
||||
t2 := []rune(newText)
|
||||
oldLen := len(t1)
|
||||
newLen := len(t2)
|
||||
if oldLen == 0 {
|
||||
return &TextDiff{previousText: oldText, newText: newText, start: 0, end: 0}
|
||||
}
|
||||
if newLen == 0 {
|
||||
return &TextDiff{previousText: oldText, newText: "", start: 0, end: oldLen}
|
||||
}
|
||||
|
||||
// if we reach here, t1 and t2 are not empty
|
||||
start := 0
|
||||
for start < oldLen && start < newLen && t1[start] == t2[start] {
|
||||
start++
|
||||
}
|
||||
|
||||
oldEnd, newEnd := oldLen, newLen
|
||||
for oldEnd > start && newEnd > start && t1[oldEnd-1] == t2[newEnd-1] {
|
||||
oldEnd--
|
||||
newEnd--
|
||||
}
|
||||
|
||||
diff := &TextDiff{previousText: oldText, start: start}
|
||||
if hasCommonCharSequence(t1, t2) { // is just a insert operation
|
||||
diff.end = start
|
||||
diff.newText = string(t2[start:newEnd])
|
||||
} else {
|
||||
diff.end = newEnd
|
||||
if oldLen > newLen {
|
||||
diff.end = oldEnd
|
||||
}
|
||||
if !hasCommonCharSequence(t2, t1) { // is not a delete operation
|
||||
diff.newText = string(t2[start:diff.end])
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/status-im/status-go/logutils"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -157,6 +160,8 @@ func TestReplaceMentions(t *testing.T) {
|
|||
{"code case 2", "` @user2 `", "` @user2 `"},
|
||||
{"code case 3", "``` @user2 ```", "``` @user2 ```"},
|
||||
{"code case 4", "` ` @user2 ``", "` ` @0xpk2 ``"},
|
||||
|
||||
{"double @", "@ @user2", "@ @0xpk2"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -338,18 +343,254 @@ func TestToInputField(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSubs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
start int
|
||||
end int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Normal case",
|
||||
input: "Hello, world!",
|
||||
start: 0,
|
||||
end: 5,
|
||||
expected: "Hello",
|
||||
},
|
||||
{
|
||||
name: "Start index out of range (negative)",
|
||||
input: "Hello, world!",
|
||||
start: -5,
|
||||
end: 5,
|
||||
expected: "Hello",
|
||||
},
|
||||
{
|
||||
name: "End index out of range",
|
||||
input: "Hello, world!",
|
||||
start: 7,
|
||||
end: 50,
|
||||
expected: "world!",
|
||||
},
|
||||
{
|
||||
name: "Start index greater than end index",
|
||||
input: "Hello, world!",
|
||||
start: 10,
|
||||
end: 5,
|
||||
expected: ", wor",
|
||||
},
|
||||
{
|
||||
name: "Both indices out of range",
|
||||
input: "Hello, world!",
|
||||
start: -5,
|
||||
end: 50,
|
||||
expected: "Hello, world!",
|
||||
},
|
||||
{
|
||||
name: "Start index negative, end index out of range",
|
||||
input: "Hello, world!",
|
||||
start: -10,
|
||||
end: 15,
|
||||
expected: "Hello, world!",
|
||||
},
|
||||
{
|
||||
name: "Start index negative, end index within range",
|
||||
input: "Hello, world!",
|
||||
start: -10,
|
||||
end: 5,
|
||||
expected: "Hello",
|
||||
},
|
||||
{
|
||||
name: "Start index negative, end index negative",
|
||||
input: "Hello, world!",
|
||||
start: -10,
|
||||
end: -5,
|
||||
expected: "",
|
||||
},
|
||||
|
||||
{
|
||||
name: "Start index zero, end index zero",
|
||||
input: "Hello, world!",
|
||||
start: 0,
|
||||
end: 0,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "Start index positive, end index zero",
|
||||
input: "Hello, world!",
|
||||
start: 3,
|
||||
end: 0,
|
||||
expected: "Hel",
|
||||
},
|
||||
{
|
||||
name: "Start index equal to input length",
|
||||
input: "Hello, world!",
|
||||
start: 13,
|
||||
end: 15,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "End index negative",
|
||||
input: "Hello, world!",
|
||||
start: 5,
|
||||
end: -5,
|
||||
expected: "Hello",
|
||||
},
|
||||
{
|
||||
name: "Start and end indices equal and negative",
|
||||
input: "Hello, world!",
|
||||
start: -3,
|
||||
end: -3,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "Start index greater than input length",
|
||||
input: "Hello, world!",
|
||||
start: 15,
|
||||
end: 20,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "End index equal to input length",
|
||||
input: "Hello, world!",
|
||||
start: 0,
|
||||
end: 13,
|
||||
expected: "Hello, world!",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := subs(tc.input, tc.start, tc.end)
|
||||
if actual != tc.expected {
|
||||
t.Errorf("Test case '%s': expected '%s', got '%s'", tc.name, tc.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLastIndexOf(t *testing.T) {
|
||||
atSignIdx := LastIndexOf("@", charAtSign, 0)
|
||||
atSignIdx := lastIndexOf("@", charAtSign, 0)
|
||||
require.Equal(t, 0, atSignIdx)
|
||||
|
||||
atSignIdx = LastIndexOf("@@", charAtSign, 1)
|
||||
atSignIdx = lastIndexOf("@@", charAtSign, 1)
|
||||
require.Equal(t, 1, atSignIdx)
|
||||
|
||||
//at-sign-idx 0 text @t searched-text t start 2 end 2 new-text
|
||||
atSignIdx = LastIndexOf("@t", charAtSign, 2)
|
||||
atSignIdx = lastIndexOf("@t", charAtSign, 2)
|
||||
require.Equal(t, 0, atSignIdx)
|
||||
}
|
||||
|
||||
func TestDiffText(t *testing.T) {
|
||||
testCases := []struct {
|
||||
oldText string
|
||||
newText string
|
||||
expected *TextDiff
|
||||
}{
|
||||
{
|
||||
oldText: "",
|
||||
newText: "A",
|
||||
expected: &TextDiff{
|
||||
start: 0,
|
||||
end: 0,
|
||||
previousText: "",
|
||||
newText: "A",
|
||||
},
|
||||
},
|
||||
{
|
||||
oldText: "A",
|
||||
newText: "Ab",
|
||||
expected: &TextDiff{
|
||||
start: 1,
|
||||
end: 1,
|
||||
previousText: "A",
|
||||
newText: "b",
|
||||
},
|
||||
},
|
||||
{
|
||||
oldText: "Ab",
|
||||
newText: "Abc",
|
||||
expected: &TextDiff{
|
||||
start: 2,
|
||||
end: 2,
|
||||
previousText: "Ab",
|
||||
newText: "c",
|
||||
},
|
||||
},
|
||||
{
|
||||
oldText: "Abc",
|
||||
newText: "Ac",
|
||||
expected: &TextDiff{
|
||||
start: 1,
|
||||
end: 2,
|
||||
previousText: "Abc",
|
||||
newText: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
oldText: "Ac",
|
||||
newText: "Adc",
|
||||
expected: &TextDiff{
|
||||
start: 1,
|
||||
end: 1,
|
||||
previousText: "Ac",
|
||||
newText: "d",
|
||||
},
|
||||
},
|
||||
{
|
||||
oldText: "Adc",
|
||||
newText: "Ad ee c",
|
||||
expected: &TextDiff{
|
||||
start: 2,
|
||||
end: 2,
|
||||
previousText: "Adc",
|
||||
newText: " ee ",
|
||||
},
|
||||
},
|
||||
{
|
||||
oldText: "Ad ee c",
|
||||
newText: "A fff d ee c",
|
||||
expected: &TextDiff{
|
||||
start: 1,
|
||||
end: 1,
|
||||
previousText: "Ad ee c",
|
||||
newText: " fff ",
|
||||
},
|
||||
},
|
||||
{
|
||||
oldText: "A fff d ee c",
|
||||
newText: " fff d ee c",
|
||||
expected: &TextDiff{
|
||||
start: 0,
|
||||
end: 1,
|
||||
previousText: "A fff d ee c",
|
||||
newText: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
oldText: " fff d ee c",
|
||||
newText: " fffee c",
|
||||
expected: &TextDiff{
|
||||
start: 4,
|
||||
end: 7,
|
||||
previousText: " fff d ee c",
|
||||
newText: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
oldText: "abc",
|
||||
newText: "abc",
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
for i, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%d", i+1), func(t *testing.T) {
|
||||
diff := diffText(tc.oldText, tc.newText)
|
||||
require.Equal(t, tc.expected, diff)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type MockMentionableUserGetter struct {
|
||||
mentionableUserMap map[string]*MentionableUser
|
||||
}
|
||||
|
@ -362,67 +603,127 @@ func (m *MockMentionableUserGetter) getMentionableUser(chatID string, pk string)
|
|||
return m.mentionableUserMap[pk], nil
|
||||
}
|
||||
|
||||
func TestMentionSuggestion(t *testing.T) {
|
||||
mentionableUserMap, chatID, mentionManager := setupMentionSuggestionTest()
|
||||
func TestMentionSuggestionCases(t *testing.T) {
|
||||
mentionableUserMap, chatID, mentionManager := setupMentionSuggestionTest(nil)
|
||||
|
||||
testCases := []struct {
|
||||
newText string
|
||||
prevText string
|
||||
start int
|
||||
end int
|
||||
inputText string
|
||||
expectedSize int
|
||||
}{
|
||||
{"@", "", 0, 0, "@", len(mentionableUserMap)},
|
||||
{"u", "", 1, 1, "@u", len(mentionableUserMap)},
|
||||
{"2", "", 2, 2, "@u2", 1},
|
||||
{"3", "", 3, 3, "@u23", 0},
|
||||
{"", "3", 3, 4, "@u2", 1},
|
||||
{"@", len(mentionableUserMap)},
|
||||
{"@u", len(mentionableUserMap)},
|
||||
{"@u2", 1},
|
||||
{"@u23", 0},
|
||||
{"@u2", 1},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
input := MentionState{PreviousText: tc.prevText, NewText: &tc.newText, Start: tc.start, End: tc.end}
|
||||
_, err := mentionManager.OnTextInput(chatID, &input)
|
||||
require.NoError(t, err)
|
||||
ctx, err := mentionManager.CalculateSuggestions(chatID, tc.inputText)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedSize, len(ctx.MentionSuggestions))
|
||||
t.Logf("Input: %+v, MentionState:%+v, InputSegments:%+v\n", input, ctx.MentionState, ctx.InputSegments)
|
||||
for i, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%d", i+1), func(t *testing.T) {
|
||||
_, err := mentionManager.OnChangeText(chatID, tc.inputText)
|
||||
require.NoError(t, err)
|
||||
ctx, err := mentionManager.CalculateSuggestions(chatID, tc.inputText)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedSize, len(ctx.MentionSuggestions))
|
||||
t.Logf("Input: %+v, MentionState:%+v, InputSegments:%+v\n", tc.inputText, ctx.MentionState, ctx.InputSegments)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMentionSuggestionWithSpecialCharacters(t *testing.T) {
|
||||
mentionableUserMap, chatID, mentionManager := setupMentionSuggestionTest()
|
||||
func TestMentionSuggestionSpecialInputModeForAndroid(t *testing.T) {
|
||||
mentionableUserMap, chatID, mentionManager := setupMentionSuggestionTest(nil)
|
||||
|
||||
testCases := []struct {
|
||||
inputText string
|
||||
expectedSize int
|
||||
}{
|
||||
{"A", 0},
|
||||
{"As", 0},
|
||||
{"Asd", 0},
|
||||
{"Asd@", len(mentionableUserMap)},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%d", i+1), func(t *testing.T) {
|
||||
ctx, err := mentionManager.OnChangeText(chatID, tc.inputText)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedSize, len(ctx.MentionSuggestions))
|
||||
t.Logf("Input: %+v, MentionState:%+v, InputSegments:%+v\n", tc.inputText, ctx.MentionState, ctx.InputSegments)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMentionSuggestionSpecialChars(t *testing.T) {
|
||||
mentionableUserMap, chatID, mentionManager := setupMentionSuggestionTest(nil)
|
||||
|
||||
testCases := []struct {
|
||||
newText string
|
||||
prevText string
|
||||
start int
|
||||
end int
|
||||
inputText string
|
||||
expectedSize int
|
||||
calculateSuggestion bool
|
||||
}{
|
||||
{"'", "", 0, 0, "'", 0, false},
|
||||
{"‘", "", 0, 1, "‘", 0, true},
|
||||
{"@", "", 1, 1, "‘@", len(mentionableUserMap), true},
|
||||
{"'", 0, false},
|
||||
{"‘", 0, true},
|
||||
{"‘@", len(mentionableUserMap), true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
input := MentionState{PreviousText: tc.prevText, NewText: &tc.newText, Start: tc.start, End: tc.end}
|
||||
ctx, err := mentionManager.OnTextInput(chatID, &input)
|
||||
ctx, err := mentionManager.OnChangeText(chatID, tc.inputText)
|
||||
require.NoError(t, err)
|
||||
if tc.calculateSuggestion {
|
||||
ctx, err = mentionManager.CalculateSuggestions(chatID, tc.inputText)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedSize, len(ctx.MentionSuggestions))
|
||||
}
|
||||
t.Logf("Input: %+v, MentionState:%+v, InputSegments:%+v\n", input, ctx.MentionState, ctx.InputSegments)
|
||||
t.Logf("Input: %+v, MentionState:%+v, InputSegments:%+v\n", tc.inputText, ctx.MentionState, ctx.InputSegments)
|
||||
}
|
||||
}
|
||||
|
||||
func setupMentionSuggestionTest() (map[string]*MentionableUser, string, *MentionManager) {
|
||||
mentionableUserMap := getMentionableUserMap()
|
||||
func TestMentionSuggestionAtSignSpaceCases(t *testing.T) {
|
||||
mentionableUserMap, chatID, mentionManager := setupMentionSuggestionTest(map[string]*MentionableUser{
|
||||
"0xpk1": {
|
||||
primaryName: "User Number One",
|
||||
Contact: &Contact{
|
||||
ID: "0xpk1",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
inputText string
|
||||
expectedSize int
|
||||
calculateSuggestion bool
|
||||
}{
|
||||
{"@", len(mentionableUserMap), true},
|
||||
{"@ ", 0, true},
|
||||
{"@ @", len(mentionableUserMap), true},
|
||||
}
|
||||
|
||||
var ctx *ChatMentionContext
|
||||
var err error
|
||||
for _, tc := range testCases {
|
||||
ctx, err = mentionManager.OnChangeText(chatID, tc.inputText)
|
||||
require.NoError(t, err)
|
||||
t.Logf("After OnChangeText, Input: %+v, MentionState:%+v, InputSegments:%+v\n", tc.inputText, ctx.MentionState, ctx.InputSegments)
|
||||
if tc.calculateSuggestion {
|
||||
ctx, err = mentionManager.CalculateSuggestions(chatID, tc.inputText)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedSize, len(ctx.MentionSuggestions))
|
||||
t.Logf("After CalculateSuggestions, Input: %+v, MentionState:%+v, InputSegments:%+v\n", tc.inputText, ctx.MentionState, ctx.InputSegments)
|
||||
}
|
||||
}
|
||||
require.Len(t, ctx.InputSegments, 3)
|
||||
require.Equal(t, Mention, ctx.InputSegments[0].Type)
|
||||
require.Equal(t, "@ @", ctx.InputSegments[0].Value)
|
||||
require.Equal(t, Text, ctx.InputSegments[1].Type)
|
||||
require.Equal(t, "@", ctx.InputSegments[1].Value)
|
||||
require.Equal(t, Text, ctx.InputSegments[2].Type)
|
||||
require.Equal(t, "@", ctx.InputSegments[2].Value)
|
||||
}
|
||||
|
||||
func setupMentionSuggestionTest(mentionableUserMapInput map[string]*MentionableUser) (map[string]*MentionableUser, string, *MentionManager) {
|
||||
mentionableUserMap := mentionableUserMapInput
|
||||
if mentionableUserMap == nil {
|
||||
mentionableUserMap = getDefaultMentionableUserMap()
|
||||
}
|
||||
|
||||
for _, u := range mentionableUserMap {
|
||||
addSearchablePhrases(u)
|
||||
|
@ -442,12 +743,13 @@ func setupMentionSuggestionTest() (map[string]*MentionableUser, string, *Mention
|
|||
Messenger: &Messenger{
|
||||
allChats: allChats,
|
||||
},
|
||||
logger: logutils.ZapLogger().Named("MentionManager"),
|
||||
}
|
||||
|
||||
return mentionableUserMap, chatID, mentionManager
|
||||
}
|
||||
|
||||
func getMentionableUserMap() map[string]*MentionableUser {
|
||||
func getDefaultMentionableUserMap() map[string]*MentionableUser {
|
||||
return map[string]*MentionableUser{
|
||||
"0xpk1": {
|
||||
primaryName: "User Number One",
|
||||
|
|
|
@ -1302,18 +1302,14 @@ func (api *PublicAPI) ChatMentionCheckMentions(chatID, text string) (string, err
|
|||
return api.service.messenger.GetMentionsManager().CheckMentions(chatID, text)
|
||||
}
|
||||
|
||||
func (api *PublicAPI) ChatMentionOnTextInput(chatID string, state *protocol.MentionState) (*protocol.ChatMentionContext, error) {
|
||||
return api.service.messenger.GetMentionsManager().OnTextInput(chatID, state)
|
||||
func (api *PublicAPI) ChatMentionOnChangeText(chatID, text string) (*protocol.ChatMentionContext, error) {
|
||||
return api.service.messenger.GetMentionsManager().OnChangeText(chatID, text)
|
||||
}
|
||||
|
||||
func (api *PublicAPI) ChatMentionRecheckAtIdxs(chatID string, text string, publicKey string) (*protocol.ChatMentionContext, error) {
|
||||
return api.service.messenger.GetMentionsManager().RecheckAtIdxs(chatID, text, publicKey)
|
||||
}
|
||||
|
||||
func (api *PublicAPI) ChatMentionCalculateSuggestions(chatID, text string) (*protocol.ChatMentionContext, error) {
|
||||
return api.service.messenger.GetMentionsManager().CalculateSuggestions(chatID, text)
|
||||
}
|
||||
|
||||
func (api *PublicAPI) ChatMentionNewInputTextWithMention(chatID, text, primaryName string) *protocol.ChatMentionContext {
|
||||
return api.service.messenger.GetMentionsManager().NewInputTextWithMention(chatID, text, primaryName)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue