2023-04-07 08:47:38 +00:00
|
|
|
|
package protocol
|
|
|
|
|
|
|
|
|
|
import (
|
2023-04-27 03:06:40 +00:00
|
|
|
|
"fmt"
|
2023-04-07 08:47:38 +00:00
|
|
|
|
"reflect"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
2023-05-15 12:15:10 +00:00
|
|
|
|
|
|
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
|
|
|
"github.com/status-im/status-go/logutils"
|
2023-04-07 08:47:38 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestRePosRegex(t *testing.T) {
|
|
|
|
|
testCases := []struct {
|
|
|
|
|
input string
|
|
|
|
|
expected bool
|
|
|
|
|
}{
|
|
|
|
|
{"@", true},
|
|
|
|
|
{"~", true},
|
|
|
|
|
{"\\", true},
|
|
|
|
|
{"*", true},
|
|
|
|
|
{"_", true},
|
|
|
|
|
{"\n", true},
|
|
|
|
|
{"`", true},
|
|
|
|
|
{"a", false},
|
|
|
|
|
{"#", false},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
|
actual := specialCharsRegex.MatchString(tc.input)
|
|
|
|
|
if actual != tc.expected {
|
|
|
|
|
t.Errorf("Unexpected match result for input '%s': expected=%v, actual=%v", tc.input, tc.expected, actual)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRePos(t *testing.T) {
|
|
|
|
|
// Test case 1: Empty string
|
|
|
|
|
s1 := ""
|
|
|
|
|
var want1 []specialCharLocation
|
|
|
|
|
if got1 := rePos(s1); !reflect.DeepEqual(got1, want1) {
|
|
|
|
|
t.Errorf("rePos(%q) = %v, want %v", s1, got1, want1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test case 2: Single match
|
|
|
|
|
s2 := "test@string"
|
|
|
|
|
want2 := []specialCharLocation{{4, "@"}}
|
|
|
|
|
if got2 := rePos(s2); !reflect.DeepEqual(got2, want2) {
|
|
|
|
|
t.Errorf("rePos(%q) = %v, want %v", s2, got2, want2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test case 3: Multiple matches
|
|
|
|
|
s3 := "this is a test string@with multiple@matches"
|
|
|
|
|
want3 := []specialCharLocation{{21, "@"}, {35, "@"}}
|
|
|
|
|
if got3 := rePos(s3); !reflect.DeepEqual(got3, want3) {
|
|
|
|
|
t.Errorf("rePos(%q) = %v, want %v", s3, got3, want3)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test case 4: No matches
|
|
|
|
|
s4 := "this is a test string with no matches"
|
|
|
|
|
var want4 []specialCharLocation
|
|
|
|
|
if got4 := rePos(s4); !reflect.DeepEqual(got4, want4) {
|
|
|
|
|
t.Errorf("rePos(%q) = %v, want %v", s4, got4, want4)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test case 5: Matches at the beginning and end
|
|
|
|
|
s5 := "@this is a test string@"
|
|
|
|
|
want5 := []specialCharLocation{{0, "@"}, {22, "@"}}
|
|
|
|
|
if got5 := rePos(s5); !reflect.DeepEqual(got5, want5) {
|
|
|
|
|
t.Errorf("rePos(%q) = %v, want %v", s5, got5, want5)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test case 6: special characters
|
|
|
|
|
s6 := "Привет @testm1 "
|
|
|
|
|
want6 := []specialCharLocation{{7, "@"}}
|
|
|
|
|
if got6 := rePos(s6); !reflect.DeepEqual(got6, want6) {
|
|
|
|
|
t.Errorf("rePos(%q) = %v, want %v", s6, got6, want6)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestReplaceMentions(t *testing.T) {
|
|
|
|
|
users := map[string]*MentionableUser{
|
|
|
|
|
"0xpk1": {
|
|
|
|
|
primaryName: "User Number One",
|
|
|
|
|
Contact: &Contact{
|
|
|
|
|
ID: "0xpk1",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"0xpk2": {
|
|
|
|
|
primaryName: "user2",
|
|
|
|
|
secondaryName: "User Number Two",
|
|
|
|
|
Contact: &Contact{
|
|
|
|
|
ID: "0xpk2",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"0xpk3": {
|
|
|
|
|
primaryName: "user3",
|
|
|
|
|
secondaryName: "User Number Three",
|
|
|
|
|
Contact: &Contact{
|
|
|
|
|
ID: "0xpk3",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
text string
|
|
|
|
|
expected string
|
|
|
|
|
}{
|
|
|
|
|
{"empty string", "", ""},
|
|
|
|
|
{"no text", "", ""},
|
|
|
|
|
{"incomlepte mention 1", "@", "@"},
|
|
|
|
|
{"incomplete mention 2", "@r", "@r"},
|
|
|
|
|
{"no mentions", "foo bar @buzz kek @foo", "foo bar @buzz kek @foo"},
|
|
|
|
|
{"starts with mention", "@User Number One", "@0xpk1"},
|
|
|
|
|
{"starts with mention, comma after mention", "@User Number One,", "@0xpk1,"},
|
|
|
|
|
{"starts with mention but no space after", "@User Number Onefoo", "@User Number Onefoo"},
|
|
|
|
|
{"starts with mention, some text after mention", "@User Number One foo", "@0xpk1 foo"},
|
|
|
|
|
{"starts with some text, then mention", "text @User Number One", "text @0xpk1"},
|
|
|
|
|
{"starts with some text, then mention, then more text", "text @User Number One foo", "text @0xpk1 foo"},
|
|
|
|
|
{"no space before mention", "text@User Number One", "text@0xpk1"},
|
|
|
|
|
{"two different mentions", "@User Number One @User Number two", "@0xpk1 @0xpk2"},
|
|
|
|
|
{"two different mentions, separated with comma", "@User Number One,@User Number two", "@0xpk1,@0xpk2"},
|
|
|
|
|
{"two different mentions inside text", "foo@User Number One bar @User Number two baz", "foo@0xpk1 bar @0xpk2 baz"},
|
|
|
|
|
{"ens mention", "@user2", "@0xpk2"},
|
|
|
|
|
{"multiple mentions", strings.Repeat("@User Number One @User Number two ", 1000), strings.Repeat("@0xpk1 @0xpk2 ", 1000)},
|
|
|
|
|
|
|
|
|
|
{"single * case 1", "*@user2*", "*@user2*"},
|
|
|
|
|
{"single * case 2", "*@user2 *", "*@0xpk2 *"},
|
|
|
|
|
{"single * case 3", "a*@user2*", "a*@user2*"},
|
|
|
|
|
{"single * case 4", "*@user2 foo*foo", "*@0xpk2 foo*foo"},
|
|
|
|
|
{"single * case 5", "a *@user2*", "a *@user2*"},
|
|
|
|
|
{"single * case 6", "*@user2 foo*", "*@user2 foo*"},
|
|
|
|
|
{"single * case 7", "@user2 *@user2 foo* @user2", "@0xpk2 *@user2 foo* @0xpk2"},
|
|
|
|
|
{"single * case 8", "*@user2 foo**@user2 foo*", "*@user2 foo**@user2 foo*"},
|
|
|
|
|
{"single * case 9", "*@user2 foo***@user2 foo* @user2", "*@user2 foo***@user2 foo* @0xpk2"},
|
|
|
|
|
|
|
|
|
|
{"double * case 1", "**@user2**", "**@user2**"},
|
|
|
|
|
{"double * case 2", "**@user2 **", "**@0xpk2 **"},
|
|
|
|
|
{"double * case 3", "a**@user2**", "a**@user2**"},
|
|
|
|
|
{"double * case 4", "**@user2 foo**foo", "**@user2 foo**foo"},
|
|
|
|
|
{"double * case 5", "a **@user2**", "a **@user2**"},
|
|
|
|
|
{"double * case 6", "**@user2 foo**", "**@user2 foo**"},
|
|
|
|
|
{"double * case 7", "@user2 **@user2 foo** @user2", "@0xpk2 **@user2 foo** @0xpk2"},
|
|
|
|
|
{"double * case 8", "**@user2 foo****@user2 foo**", "**@user2 foo****@user2 foo**"},
|
|
|
|
|
{"double * case 9", "**@user2 foo*****@user2 foo** @user2", "**@user2 foo*****@user2 foo** @0xpk2"},
|
|
|
|
|
|
|
|
|
|
{"tripple * case 1", "***@user2 foo***@user2 foo*", "***@user2 foo***@0xpk2 foo*"},
|
|
|
|
|
{"tripple ~ case 1", "~~~@user2 foo~~~@user2 foo~", "~~~@user2 foo~~~@user2 foo~"},
|
|
|
|
|
|
|
|
|
|
{"quote case 1", ">@user2", ">@user2"},
|
|
|
|
|
{"quote case 2", "\n>@user2", "\n>@user2"},
|
|
|
|
|
{"quote case 3", "\n> @user2 \n \n @user2", "\n> @user2 \n \n @0xpk2"},
|
|
|
|
|
{"quote case 4", ">@user2\n\n>@user2", ">@user2\n\n>@user2"},
|
|
|
|
|
{"quote case 5", "***hey\n\n>@user2\n\n@user2 foo***", "***hey\n\n>@user2\n\n@0xpk2 foo***"},
|
|
|
|
|
|
|
|
|
|
{"code case 1", "` @user2 `", "` @user2 `"},
|
|
|
|
|
{"code case 2", "` @user2 `", "` @user2 `"},
|
|
|
|
|
{"code case 3", "``` @user2 ```", "``` @user2 ```"},
|
|
|
|
|
{"code case 4", "` ` @user2 ``", "` ` @0xpk2 ``"},
|
2023-04-27 03:06:40 +00:00
|
|
|
|
|
|
|
|
|
{"double @", "@ @user2", "@ @0xpk2"},
|
2023-04-07 08:47:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
got := ReplaceMentions(tt.text, users)
|
|
|
|
|
if got != tt.expected {
|
|
|
|
|
t.Errorf("testing %q, ReplaceMentions(%q) got %q, expected %q", tt.name, tt.text, got, tt.expected)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetAtSignIdxs(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
text string
|
|
|
|
|
start int
|
|
|
|
|
want []int
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "no @ sign",
|
|
|
|
|
text: "hello world",
|
|
|
|
|
start: 0,
|
|
|
|
|
want: []int{},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "single @ sign",
|
|
|
|
|
text: "hello @world",
|
|
|
|
|
start: 0,
|
|
|
|
|
want: []int{6},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "multiple @ signs",
|
|
|
|
|
text: "@hello @world @again",
|
|
|
|
|
start: 0,
|
|
|
|
|
want: []int{0, 7, 14},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "start after first @ sign",
|
|
|
|
|
text: "hello @world",
|
|
|
|
|
start: 6,
|
|
|
|
|
want: []int{12},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "start after second @ sign",
|
|
|
|
|
text: "hello @world @again",
|
|
|
|
|
start: 8,
|
|
|
|
|
want: []int{14, 21},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "start after last @ sign",
|
|
|
|
|
text: "hello @world @again",
|
|
|
|
|
start: 15,
|
|
|
|
|
want: []int{21, 28},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
got := getAtSignIdxs(tt.text, tt.start)
|
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
|
|
|
t.Errorf("getAtSignIdxs() = %v, want %v", got, tt.want)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCalcAtIdxs(t *testing.T) {
|
|
|
|
|
newText := "@abc"
|
|
|
|
|
state := MentionState{
|
|
|
|
|
AtIdxs: []*AtIndexEntry{
|
|
|
|
|
{From: 0, To: 3, Checked: false},
|
|
|
|
|
},
|
|
|
|
|
NewText: &newText,
|
|
|
|
|
PreviousText: "",
|
|
|
|
|
Start: 0,
|
|
|
|
|
}
|
|
|
|
|
want := []*AtIndexEntry{
|
|
|
|
|
{From: 0, To: 0, Checked: false},
|
|
|
|
|
{From: 4, To: 7, Checked: false},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
got := calcAtIdxs(&state)
|
|
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
|
|
|
t.Errorf("calcAtIdxs() = %v, want %v", got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestToInfo(t *testing.T) {
|
|
|
|
|
newText := " "
|
|
|
|
|
t.Run("toInfo base case", func(t *testing.T) {
|
|
|
|
|
expected := &MentionState{
|
|
|
|
|
AtSignIdx: 2,
|
|
|
|
|
AtIdxs: []*AtIndexEntry{
|
|
|
|
|
{
|
|
|
|
|
Checked: true,
|
|
|
|
|
Mentioned: true,
|
|
|
|
|
From: 2,
|
|
|
|
|
To: 17,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
MentionEnd: 19,
|
|
|
|
|
PreviousText: "",
|
|
|
|
|
NewText: &newText,
|
|
|
|
|
Start: 18,
|
|
|
|
|
End: 18,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inputSegments := []InputSegment{
|
|
|
|
|
{Type: Text, Value: "H."},
|
|
|
|
|
{Type: Mention, Value: "@helpinghand.eth"},
|
|
|
|
|
{Type: Text, Value: " "},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actual := toInfo(inputSegments)
|
|
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(expected.AtIdxs, actual.AtIdxs) {
|
|
|
|
|
t.Errorf("Expected AtIdxs: %#v, but got: %#v", expected.AtIdxs, actual.AtIdxs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expected.AtIdxs = nil
|
|
|
|
|
actual.AtIdxs = nil
|
|
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
|
|
|
t.Errorf("Expected %#v, but got %#v", expected, actual)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestToInputField(t *testing.T) {
|
|
|
|
|
testCases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
input string
|
|
|
|
|
expected []InputSegment
|
|
|
|
|
}{
|
2023-05-15 12:15:10 +00:00
|
|
|
|
{
|
|
|
|
|
"only text",
|
|
|
|
|
"parse-text",
|
|
|
|
|
[]InputSegment{{Type: Text, Value: "parse-text"}},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"in the middle",
|
|
|
|
|
"hey @0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073 he",
|
|
|
|
|
[]InputSegment{
|
|
|
|
|
{Type: Text, Value: "hey "},
|
|
|
|
|
{Type: Mention, Value: "0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073"},
|
|
|
|
|
{Type: Text, Value: " he"},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"at the beginning",
|
|
|
|
|
"@0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073 he",
|
|
|
|
|
[]InputSegment{
|
|
|
|
|
{Type: Mention, Value: "0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073"},
|
|
|
|
|
{Type: Text, Value: " he"},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"at the end",
|
|
|
|
|
"hey @0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073",
|
|
|
|
|
[]InputSegment{
|
|
|
|
|
{Type: Text, Value: "hey "},
|
|
|
|
|
{Type: Mention, Value: "0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073"},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"invalid",
|
|
|
|
|
"invalid @0x04fBce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073",
|
|
|
|
|
[]InputSegment{
|
|
|
|
|
{Type: Text, Value: "invalid @0x04fBce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073"},
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-04-07 08:47:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
result := toInputField(tc.input)
|
|
|
|
|
if !reflect.DeepEqual(result, tc.expected) {
|
|
|
|
|
t.Errorf("Expected: %v, got: %v", tc.expected, result)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 03:06:40 +00:00
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-07 08:47:38 +00:00
|
|
|
|
func TestLastIndexOf(t *testing.T) {
|
2023-04-27 03:06:40 +00:00
|
|
|
|
atSignIdx := lastIndexOf("@", charAtSign, 0)
|
2023-04-07 08:47:38 +00:00
|
|
|
|
require.Equal(t, 0, atSignIdx)
|
|
|
|
|
|
2023-04-27 03:06:40 +00:00
|
|
|
|
atSignIdx = lastIndexOf("@@", charAtSign, 1)
|
2023-04-07 08:47:38 +00:00
|
|
|
|
require.Equal(t, 1, atSignIdx)
|
|
|
|
|
|
|
|
|
|
//at-sign-idx 0 text @t searched-text t start 2 end 2 new-text
|
2023-04-27 03:06:40 +00:00
|
|
|
|
atSignIdx = lastIndexOf("@t", charAtSign, 2)
|
2023-04-07 08:47:38 +00:00
|
|
|
|
require.Equal(t, 0, atSignIdx)
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 03:06:40 +00:00
|
|
|
|
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)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-07 08:47:38 +00:00
|
|
|
|
type MockMentionableUserGetter struct {
|
|
|
|
|
mentionableUserMap map[string]*MentionableUser
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *MockMentionableUserGetter) getMentionableUsers(chatID string) (map[string]*MentionableUser, error) {
|
|
|
|
|
return m.mentionableUserMap, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *MockMentionableUserGetter) getMentionableUser(chatID string, pk string) (*MentionableUser, error) {
|
|
|
|
|
return m.mentionableUserMap[pk], nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 03:06:40 +00:00
|
|
|
|
func TestMentionSuggestionCases(t *testing.T) {
|
2023-05-15 12:15:10 +00:00
|
|
|
|
mentionableUserMap, chatID, mentionManager := setupMentionSuggestionTest(t, nil)
|
2023-04-27 03:06:40 +00:00
|
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
|
inputText string
|
|
|
|
|
expectedSize int
|
|
|
|
|
}{
|
|
|
|
|
{"@", len(mentionableUserMap)},
|
|
|
|
|
{"@u", len(mentionableUserMap)},
|
|
|
|
|
{"@u2", 1},
|
|
|
|
|
{"@u23", 0},
|
|
|
|
|
{"@u2", 1},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-15 12:15:10 +00:00
|
|
|
|
func TestMentionSuggestionAfterToInputField(t *testing.T) {
|
|
|
|
|
mentionableUserMap, chatID, mentionManager := setupMentionSuggestionTest(t, nil)
|
|
|
|
|
_, err := mentionManager.ToInputField(chatID, "abc")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
ctx, err := mentionManager.OnChangeText(chatID, "@")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Equal(t, len(mentionableUserMap), len(ctx.MentionSuggestions))
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 03:06:40 +00:00
|
|
|
|
func TestMentionSuggestionSpecialInputModeForAndroid(t *testing.T) {
|
2023-05-15 12:15:10 +00:00
|
|
|
|
mentionableUserMap, chatID, mentionManager := setupMentionSuggestionTest(t, nil)
|
2023-04-07 08:47:38 +00:00
|
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
|
inputText string
|
|
|
|
|
expectedSize int
|
|
|
|
|
}{
|
2023-04-27 03:06:40 +00:00
|
|
|
|
{"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) {
|
2023-05-15 12:15:10 +00:00
|
|
|
|
mentionableUserMap, chatID, mentionManager := setupMentionSuggestionTest(t, nil)
|
2023-04-27 03:06:40 +00:00
|
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
|
inputText string
|
|
|
|
|
expectedSize int
|
|
|
|
|
calculateSuggestion bool
|
|
|
|
|
}{
|
|
|
|
|
{"'", 0, false},
|
|
|
|
|
{"‘", 0, true},
|
|
|
|
|
{"‘@", len(mentionableUserMap), true},
|
2023-04-07 08:47:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
2023-04-27 03:06:40 +00:00
|
|
|
|
ctx, err := mentionManager.OnChangeText(chatID, tc.inputText)
|
2023-04-07 08:47:38 +00:00
|
|
|
|
require.NoError(t, err)
|
2023-04-27 03:06:40 +00:00
|
|
|
|
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", tc.inputText, ctx.MentionState, ctx.InputSegments)
|
2023-04-07 08:47:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 03:06:40 +00:00
|
|
|
|
func TestMentionSuggestionAtSignSpaceCases(t *testing.T) {
|
2023-05-15 12:15:10 +00:00
|
|
|
|
mentionableUserMap, chatID, mentionManager := setupMentionSuggestionTest(t, map[string]*MentionableUser{
|
2023-04-27 03:06:40 +00:00
|
|
|
|
"0xpk1": {
|
|
|
|
|
primaryName: "User Number One",
|
|
|
|
|
Contact: &Contact{
|
|
|
|
|
ID: "0xpk1",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
2023-04-07 08:47:38 +00:00
|
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
|
inputText string
|
|
|
|
|
expectedSize int
|
|
|
|
|
calculateSuggestion bool
|
|
|
|
|
}{
|
2023-04-27 03:06:40 +00:00
|
|
|
|
{"@", len(mentionableUserMap), true},
|
|
|
|
|
{"@ ", 0, true},
|
|
|
|
|
{"@ @", len(mentionableUserMap), true},
|
2023-04-07 08:47:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 03:06:40 +00:00
|
|
|
|
var ctx *ChatMentionContext
|
|
|
|
|
var err error
|
2023-04-07 08:47:38 +00:00
|
|
|
|
for _, tc := range testCases {
|
2023-04-27 03:06:40 +00:00
|
|
|
|
ctx, err = mentionManager.OnChangeText(chatID, tc.inputText)
|
2023-04-07 08:47:38 +00:00
|
|
|
|
require.NoError(t, err)
|
2023-04-27 03:06:40 +00:00
|
|
|
|
t.Logf("After OnChangeText, Input: %+v, MentionState:%+v, InputSegments:%+v\n", tc.inputText, ctx.MentionState, ctx.InputSegments)
|
2023-04-07 08:47:38 +00:00
|
|
|
|
if tc.calculateSuggestion {
|
|
|
|
|
ctx, err = mentionManager.CalculateSuggestions(chatID, tc.inputText)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Equal(t, tc.expectedSize, len(ctx.MentionSuggestions))
|
2023-04-27 03:06:40 +00:00
|
|
|
|
t.Logf("After CalculateSuggestions, Input: %+v, MentionState:%+v, InputSegments:%+v\n", tc.inputText, ctx.MentionState, ctx.InputSegments)
|
2023-04-07 08:47:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-04-27 03:06:40 +00:00
|
|
|
|
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)
|
2023-04-07 08:47:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-15 12:15:10 +00:00
|
|
|
|
func setupMentionSuggestionTest(t *testing.T, mentionableUserMapInput map[string]*MentionableUser) (map[string]*MentionableUser, string, *MentionManager) {
|
2023-04-27 03:06:40 +00:00
|
|
|
|
mentionableUserMap := mentionableUserMapInput
|
|
|
|
|
if mentionableUserMap == nil {
|
|
|
|
|
mentionableUserMap = getDefaultMentionableUserMap()
|
|
|
|
|
}
|
2023-04-07 08:47:38 +00:00
|
|
|
|
|
|
|
|
|
for _, u := range mentionableUserMap {
|
|
|
|
|
addSearchablePhrases(u)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mockMentionableUserGetter := &MockMentionableUserGetter{
|
|
|
|
|
mentionableUserMap: mentionableUserMap,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
chatID := "0xchatID"
|
|
|
|
|
allChats := new(chatMap)
|
|
|
|
|
allChats.Store(chatID, &Chat{})
|
|
|
|
|
|
2023-05-15 12:15:10 +00:00
|
|
|
|
key, err := crypto.GenerateKey()
|
|
|
|
|
require.NoError(t, err)
|
2023-04-07 08:47:38 +00:00
|
|
|
|
mentionManager := &MentionManager{
|
|
|
|
|
mentionableUserGetter: mockMentionableUserGetter,
|
|
|
|
|
mentionContexts: make(map[string]*ChatMentionContext),
|
|
|
|
|
Messenger: &Messenger{
|
|
|
|
|
allChats: allChats,
|
2023-05-15 12:15:10 +00:00
|
|
|
|
identity: key,
|
2023-04-07 08:47:38 +00:00
|
|
|
|
},
|
2023-04-27 03:06:40 +00:00
|
|
|
|
logger: logutils.ZapLogger().Named("MentionManager"),
|
2023-04-07 08:47:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mentionableUserMap, chatID, mentionManager
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 03:06:40 +00:00
|
|
|
|
func getDefaultMentionableUserMap() map[string]*MentionableUser {
|
2023-04-07 08:47:38 +00:00
|
|
|
|
return map[string]*MentionableUser{
|
|
|
|
|
"0xpk1": {
|
|
|
|
|
primaryName: "User Number One",
|
|
|
|
|
Contact: &Contact{
|
|
|
|
|
ID: "0xpk1",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"0xpk2": {
|
|
|
|
|
primaryName: "u2",
|
|
|
|
|
secondaryName: "User Number Two",
|
|
|
|
|
Contact: &Contact{
|
|
|
|
|
ID: "0xpk2",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
"0xpk3": {
|
|
|
|
|
primaryName: "u3",
|
|
|
|
|
secondaryName: "User Number Three",
|
|
|
|
|
Contact: &Contact{
|
|
|
|
|
ID: "0xpk3",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|