From 4371857168a9f260aebe477b0d5f2a11f85234f7 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Sun, 21 Dec 2025 07:17:05 +0000 Subject: [PATCH] appview/pages: use robust handle regex from indigo/syntax Change-Id: vtyvsnwqkrwrksmnloppspuxtuvvvoym should cut down the false positives a bit. tests are included. Signed-off-by: oppiliappan --- appview/pages/markup/extension/atlink.go | 6 +- appview/pages/markup/markdown_test.go | 121 +++++++++++++++++++++++ 2 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 appview/pages/markup/markdown_test.go diff --git a/appview/pages/markup/extension/atlink.go b/appview/pages/markup/extension/atlink.go index 444a0678..005f4955 100644 --- a/appview/pages/markup/extension/atlink.go +++ b/appview/pages/markup/extension/atlink.go @@ -35,7 +35,7 @@ func (n *AtNode) Kind() ast.NodeKind { return KindAt } -var atRegexp = regexp.MustCompile(`(^|\s|\()(@)([a-zA-Z0-9.-]+)(\b)`) +var atRegexp = regexp.MustCompile(`(^|\s|\()(@)([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\b)`) var markdownLinkRegexp = regexp.MustCompile(`(?ms)\[.*\]\(.*\)`) type atParser struct{} @@ -57,10 +57,6 @@ func (s *atParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) return nil } - if !util.IsSpaceRune(block.PrecendingCharacter()) { - return nil - } - // Check for all links in the markdown to see if the handle found is inside one linksIndexes := markdownLinkRegexp.FindAllIndex(block.Source(), -1) for _, linkMatch := range linksIndexes { diff --git a/appview/pages/markup/markdown_test.go b/appview/pages/markup/markdown_test.go new file mode 100644 index 00000000..73c2442e --- /dev/null +++ b/appview/pages/markup/markdown_test.go @@ -0,0 +1,121 @@ +package markup + +import ( + "bytes" + "testing" +) + +func TestAtExtension_Rendering(t *testing.T) { + tests := []struct { + name string + markdown string + expected string + }{ + { + name: "renders simple at mention", + markdown: "Hello @user.tngl.sh!", + expected: `

Hello @user.tngl.sh!

`, + }, + { + name: "renders multiple at mentions", + markdown: "Hi @alice.tngl.sh and @bob.example.com", + expected: `

Hi @alice.tngl.sh and @bob.example.com

`, + }, + { + name: "renders at mention in parentheses", + markdown: "Check this out (@user.tngl.sh)", + expected: `

Check this out (@user.tngl.sh)

`, + }, + { + name: "does not render email", + markdown: "Contact me at test@example.com", + expected: `

Contact me at test@example.com

`, + }, + { + name: "renders at mention with hyphen", + markdown: "Follow @user-name.tngl.sh", + expected: `

Follow @user-name.tngl.sh

`, + }, + { + name: "renders at mention with numbers", + markdown: "@user123.test456.social", + expected: `

@user123.test456.social

`, + }, + { + name: "at mention at start of line", + markdown: "@user.tngl.sh is cool", + expected: `

@user.tngl.sh is cool

`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + md := NewMarkdown() + + var buf bytes.Buffer + if err := md.Convert([]byte(tt.markdown), &buf); err != nil { + t.Fatalf("failed to convert markdown: %v", err) + } + + result := buf.String() + if result != tt.expected+"\n" { + t.Errorf("expected:\n%s\ngot:\n%s", tt.expected, result) + } + }) + } +} + +func TestAtExtension_WithOtherMarkdown(t *testing.T) { + tests := []struct { + name string + markdown string + contains string + }{ + { + name: "at mention with bold", + markdown: "**Hello @user.tngl.sh**", + contains: `Hello @user.tngl.sh`, + }, + { + name: "at mention with italic", + markdown: "*Check @user.tngl.sh*", + contains: `Check @user.tngl.sh`, + }, + { + name: "at mention in list", + markdown: "- Item 1\n- @user.tngl.sh\n- Item 3", + contains: `@user.tngl.sh`, + }, + { + name: "at mention in link", + markdown: "[@regnault.dev](https://regnault.dev)", + contains: `@regnault.dev`, + }, + { + name: "at mention in link again", + markdown: "[check out @regnault.dev](https://regnault.dev)", + contains: `check out @regnault.dev`, + }, + { + name: "at mention in link again, multiline", + markdown: "[\ncheck out @regnault.dev](https://regnault.dev)", + contains: "\ncheck out @regnault.dev", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + md := NewMarkdown() + + var buf bytes.Buffer + if err := md.Convert([]byte(tt.markdown), &buf); err != nil { + t.Fatalf("failed to convert markdown: %v", err) + } + + result := buf.String() + if !bytes.Contains([]byte(result), []byte(tt.contains)) { + t.Errorf("expected output to contain:\n%s\ngot:\n%s", tt.contains, result) + } + }) + } +} -- 2.43.0