package main import ( "testing" "github.com/stretchr/testify/require" ) func TestMarkdownEntities(t *testing.T) { testCases := []struct { name string markdown string text string entities []TelegramEntity }{ { name: "double asterisks give bold", markdown: "a **b** c", text: "a b c", entities: []TelegramEntity{ { Type: "bold", Offset: 2, Length: 1, }, }, }, { name: "double underscores give bold", markdown: "__bold__", text: "bold", entities: []TelegramEntity{ { Type: "bold", Offset: 0, Length: 4, }, }, }, { name: "single asterisk gives italic", markdown: "an *odd* word", text: "an odd word", entities: []TelegramEntity{ { Type: "italic", Offset: 3, Length: 3, }, }, }, { name: "single underscore gives italic", markdown: "an _odd_ word", text: "an odd word", entities: []TelegramEntity{ { Type: "italic", Offset: 3, Length: 3, }, }, }, { name: "double tildes give strikethrough", markdown: "~~old~~ new", text: "old new", entities: []TelegramEntity{ { Type: "strikethrough", Offset: 0, Length: 3, }, }, }, { name: "backticks give inline code with no entity inside", markdown: "run `go **test**` now", text: "run go **test** now", entities: []TelegramEntity{ { Type: "code", Offset: 4, Length: 11, }, }, }, { name: "double backticks hold a backtick", markdown: "``a ` b``", text: "a ` b", entities: []TelegramEntity{ { Type: "code", Offset: 0, Length: 5, }, }, }, { name: "link gives a text link", markdown: "see [the docs](https://example.com/a_b) here", text: "see the docs here", entities: []TelegramEntity{ { Type: "text_link", Offset: 4, Length: 8, URL: "https://example.com/a_b", }, }, }, { name: "bold inside a link nests", markdown: "[a **b** c](https://example.com)", text: "a b c", entities: []TelegramEntity{ { Type: "text_link", Offset: 0, Length: 5, URL: "https://example.com", }, { Type: "bold", Offset: 2, Length: 1, }, }, }, { name: "code block with a language gives pre with the language", markdown: "Commit:\n```text\nFix the **bug**\n\nBody\n```\nDone.", text: "Commit:\nFix the **bug**\n\nBody\nDone.", entities: []TelegramEntity{ { Type: "pre", Offset: 8, Length: 21, Language: "text", }, }, }, { name: "code block without a language gives pre", markdown: "```\nls -l\n```", text: "ls -l", entities: []TelegramEntity{ { Type: "pre", Offset: 0, Length: 5, }, }, }, { name: "heading becomes bold with its inline marks", markdown: "## The `plan`\nnext", text: "The plan\nnext", entities: []TelegramEntity{ { Type: "bold", Offset: 0, Length: 8, }, { Type: "code", Offset: 4, Length: 4, }, }, }, { name: "list, quote and table stay as text", markdown: "- one\n* two\n1. three\n> quote\n| a | b |\n|---|---|", text: "- one\n* two\n1. three\n> quote\n| a | b |\n|---|---|", }, { name: "asterisk of a list item with italic later stays", markdown: "* item with *stress*", text: "* item with stress", entities: []TelegramEntity{ { Type: "italic", Offset: 12, Length: 6, }, }, }, { name: "unclosed marks stay as text", markdown: "**open, `tick, [link](, ~~gone and *star", text: "**open, `tick, [link](, ~~gone and *star", }, { name: "unclosed code fence stays as text", markdown: "```go\nfmt.Println()", text: "```go\nfmt.Println()", }, { name: "snake case words stay as text", markdown: "set snake_case_words and max_tokens_count", text: "set snake_case_words and max_tokens_count", }, { name: "multiplication stays as text", markdown: "2 * 3 * 4", text: "2 * 3 * 4", }, { name: "mark does not cross a line break", markdown: "**a\nb**", text: "**a\nb**", }, { name: "backslash makes a mark plain", markdown: `\*not italic\* and C:\Users`, text: `*not italic* and C:\Users`, }, { name: "character outside the BMP moves the offset by two", markdown: "\U0001F600 **b**", text: "\U0001F600 b", entities: []TelegramEntity{ { Type: "bold", Offset: 3, Length: 1, }, }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { text, entities := telegramMarkdownEntities(tc.markdown) require.Equal(t, tc.text, text) require.Equal(t, tc.entities, entities) }) } } func TestEscapeTags(t *testing.T) { testCases := []struct { name string markdown string escaped string }{ { name: "tag in prose shows as text", markdown: "Use a
tag for the rest.", escaped: `Use a \
tag for the rest.`, }, { name: "tag in inline code stays as written", markdown: "Use `
` and `` a.", escaped: "Use `
` and `` a.", }, { name: "tag in a code block stays as written", markdown: "

\n\n```html\n

\n```\n\n

", escaped: "\\

\n\n```html\n

\n```\n\n\\

", }, { name: "code block that does not close stays as written", markdown: "a \n```\n

", escaped: "a \\\n```\n
", }, { name: "tag in a table cell shows as text", markdown: "| Tag | Use |\n|-----|-----|\n|
| `
` |", escaped: "| Tag | Use |\n|-----|-----|\n| \\
| `
` |", }, { name: "autolink shows as text", markdown: "See .", escaped: `See \.`, }, { name: "backtick run that opens no code stays as text", markdown: "a ``b` ", escaped: "a ``b` \\", }, { name: "escaped bracket stays as written", markdown: `a \ and \\`, escaped: `a \ and \\\`, }, { name: "dollar, pipe, and equals signs stay as written", markdown: "$5 or $10, a || b, ==c==", escaped: "$5 or $10, a || b, ==c==", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { require.Equal(t, tc.escaped, telegramEscapeTags(tc.markdown)) }) } }