memos/internal/markdown/parser/gfm_email_test.go

62 lines
3.5 KiB
Go

package parser
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func emailMatch(start, end int, address string) GFMEmailMatch {
return GFMEmailMatch{Start: start, End: end, Address: []byte(address)}
}
func TestFindGFMEmailMatches(t *testing.T) {
tests := []struct {
name string
source string
expected []GFMEmailMatch
}{
{name: "basic", source: "mail@example.com", expected: []GFMEmailMatch{emailMatch(0, 16, "mail@example.com")}},
{name: "slash starts a new local part", source: "#foo/bar_baz@example.com", expected: []GFMEmailMatch{emailMatch(5, 24, "bar_baz@example.com")}},
{name: "punctuation starts a new local part", source: "#foo!bar@example.com", expected: []GFMEmailMatch{emailMatch(5, 20, "bar@example.com")}},
{name: "hash starts a text-node email", source: "#foo@example.com", expected: []GFMEmailMatch{emailMatch(1, 16, "foo@example.com")}},
{name: "Unicode starts a new local part", source: "#中foo@example.com", expected: []GFMEmailMatch{emailMatch(4, 19, "foo@example.com")}},
{name: "emoji starts a new local part", source: "😀foo@example.com", expected: []GFMEmailMatch{emailMatch(4, 19, "foo@example.com")}},
{name: "trailing dot excluded", source: "mail@example.com.", expected: []GFMEmailMatch{emailMatch(0, 16, "mail@example.com")}},
{name: "invalid domain suffix", source: "mail@example.com_", expected: nil},
{name: "multiple", source: "a@b.co c@d.io", expected: []GFMEmailMatch{emailMatch(0, 6, "a@b.co"), emailMatch(7, 13, "c@d.io")}},
{name: "first email wins", source: "foo@bar.com@baz.example", expected: []GFMEmailMatch{emailMatch(0, 11, "foo@bar.com")}},
{
name: "adjacent emails do not overlap",
source: "foo@bar.com+abc@def.com",
expected: []GFMEmailMatch{emailMatch(0, 11, "foo@bar.com"), emailMatch(11, 23, "+abc@def.com")},
},
{name: "escaped local plus", source: `#foo\+bar@example.com`, expected: []GFMEmailMatch{emailMatch(1, 21, "foo+bar@example.com")}},
{name: "escaped local underscore", source: `#next\_item@example.com`, expected: []GFMEmailMatch{emailMatch(1, 23, "next_item@example.com")}},
{name: "escaped at and domain dot", source: `#foo\@example\.com`, expected: []GFMEmailMatch{emailMatch(1, 18, "foo@example.com")}},
{name: "numeric dot reference", source: `#foo.bar@example.com`, expected: []GFMEmailMatch{emailMatch(1, 24, "foo.bar@example.com")}},
{name: "named dot reference", source: `#foo.bar@example.com`, expected: []GFMEmailMatch{emailMatch(1, 27, "foo.bar@example.com")}},
{name: "numeric at reference", source: `#foo@example.com`, expected: []GFMEmailMatch{emailMatch(1, 20, "foo@example.com")}},
{name: "unknown reference stays literal", source: `#foo&bogus;bar@example.com`, expected: []GFMEmailMatch{emailMatch(11, 26, "bar@example.com")}},
{name: "allowed delimiter", source: "(mail@example.com", expected: []GFMEmailMatch{emailMatch(1, 17, "mail@example.com")}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, FindGFMEmailMatches([]byte(test.source)))
})
}
}
func TestMatchGFMEmailAtPreservesEscapedSourceRange(t *testing.T) {
source := []byte("first@example.com\n#foo\\+bar@example.com")
lowerBound := bytes.IndexByte(source, '\n') + 1
at := bytes.LastIndexByte(source, '@')
start, end, ok := MatchGFMEmailAt(source, at, lowerBound, len(source))
require.True(t, ok)
assert.Equal(t, lowerBound+1, start)
assert.Equal(t, len(source), end)
assert.Equal(t, `foo\+bar@example.com`, string(source[start:end]))
}