assertEquals(1, EntityTools::mbStrlen('t')); $this->assertEquals(1, EntityTools::mbStrlen('ั')); $this->assertEquals(2, EntityTools::mbStrlen('๐Ÿ‘')); $this->assertEquals(4, EntityTools::mbStrlen('๐Ÿ‡บ๐Ÿ‡ฆ')); $this->assertEquals('st', EntityTools::mbSubstr('test', 2)); $this->assertEquals('aั', EntityTools::mbSubstr('aัaั', 2)); $this->assertEquals('a๐Ÿ‘', EntityTools::mbSubstr('a๐Ÿ‘a๐Ÿ‘', 3)); $this->assertEquals('๐Ÿ‡บ๐Ÿ‡ฆ', EntityTools::mbSubstr('๐Ÿ‡บ๐Ÿ‡ฆ๐Ÿ‡บ๐Ÿ‡ฆ', 4)); $this->assertEquals(['te', 'st'], EntityTools::mbStrSplit('test', 2)); $this->assertEquals(['aั', 'aั'], EntityTools::mbStrSplit('aัaั', 2)); $this->assertEquals(['a๐Ÿ‘', 'a๐Ÿ‘'], EntityTools::mbStrSplit('a๐Ÿ‘a๐Ÿ‘', 3)); $this->assertEquals(['๐Ÿ‡บ๐Ÿ‡ฆ', '๐Ÿ‡บ๐Ÿ‡ฆ'], EntityTools::mbStrSplit('๐Ÿ‡บ๐Ÿ‡ฆ๐Ÿ‡บ๐Ÿ‡ฆ', 4)); } private static function render(string $message, string $parse_mode): Entities { return match ($parse_mode) { 'html' => Entities::fromHtml($message), 'markdown' => Entities::fromMarkdown($message), }; } public function testEntities(): void { foreach ($this->provideEntities() as $params) { $this->testEntitiesInner(...$params); } } public function testUnclosed(): void { $this->expectExceptionMessage("Found unclosed markdown elements ]("); Entities::fromMarkdown('['); } public function testUnclosedLink(): void { $this->expectExceptionMessage("Unclosed ) opened @ pos 7!"); Entities::fromMarkdown('[test](https://google.com'); } public function testUnclosedCode(): void { $this->expectExceptionMessage('Unclosed ``` opened @ pos 3!'); Entities::fromMarkdown('```'); } public function testStandalone(): void { $test = Entities::fromMarkdown(']'); $this->assertEmpty($test->entities); $this->assertSame(']', $test->message); $test = Entities::fromMarkdown('!!'); $this->assertEmpty($test->entities); $this->assertSame('!!', $test->message); $test = Entities::fromMarkdown('|'); $this->assertEmpty($test->entities); $this->assertSame('|', $test->message); } private function testEntitiesInner(string $mode, string $html, string $bare, array $entities, ?string $htmlReverse = null): void { $result = self::render(message: $html, parse_mode: $mode); $this->assertEquals($bare, $result->message); $this->assertEquals($entities, $result->entities); if ( !\str_contains($html, 'tg://emoji') && !\str_contains($html, 'request(new Request( "https://api.telegram.org/bot{$token}/sendMessage?".\http_build_query([ 'chat_id'=> $dest, 'parse_mode'=> match ($mode) { 'markdown' => 'MarkdownV2', 'html' => 'html' }, 'text' => $html ]) ))->getBody()->buffer(), true); if (!isset($resultApi['result'])) { throw new AssertionError(\json_encode($resultApi)); } $entities = $resultApi['result']['entities'] ?? []; $entities = \array_map(function (array $e): array { if (isset($e['user'])) { $e['user'] = ['id' => $e['user']['id']]; } return $e; }, $entities); $this->assertEquals($bare, $resultApi['result']['text']); $this->assertEquals($entities, $entities); } if (\strtolower($mode) === 'html') { $this->assertEquals( \trim(\str_replace(['
', ' ', 'mention:'], ['
', ' ', 'tg://user?id='], $htmlReverse ?? $html)), $result->toHTML(true) ); $result = self::render(message: EntityTools::htmlEscape($html), parse_mode: $mode); $this->assertEquals($html, $result->message); $this->assertNoRelevantEntities($result->entities); } else { $result = self::render(message: EntityTools::markdownEscape($html), parse_mode: $mode); $this->assertEquals($html, $result->message); $this->assertNoRelevantEntities($result->entities); $result = self::render(message: "```\n".EntityTools::markdownCodeblockEscape($html)."\n```", parse_mode: $mode); $this->assertEquals($html, \rtrim($result->message)); $this->assertEquals([['offset' => 0, 'length' => EntityTools::mbStrlen($html), 'language' => '', 'type' => 'pre']], $result->entities); } } private function assertNoRelevantEntities(array $entities): void { $entities = \array_filter($entities, static fn (array $e) => !\in_array( $e['type'], ['url', 'email', 'phone_number', 'mention', 'bot_command'], true )); $this->assertEmpty($entities); } private function provideEntities(): array { return [ [ 'html', 'test', 'test', [ [ 'offset' => 0, 'length' => 4, 'type' => 'bold', ], ], ], [ 'html', 'test
test', "test\ntest", [ [ 'offset' => 0, 'length' => 4, 'type' => 'bold', ], ], ], [ 'html', 'test
test', "test\ntest", [ [ 'offset' => 0, 'length' => 4, 'type' => 'bold', ], ], ], [ 'html', '๐Ÿ‡บ๐Ÿ‡ฆ๐Ÿ‡บ๐Ÿ‡ฆ', '๐Ÿ‡บ๐Ÿ‡ฆ๐Ÿ‡บ๐Ÿ‡ฆ', [ [ 'offset' => 4, 'length' => 4, 'type' => 'bold', ], ], ], [ 'html', 'testtest ', 'testtest', [ [ 'offset' => 4, 'length' => 4, 'type' => 'bold', ], ], ], [ 'html', 'รจยปtesttest test', 'รจยปtesttest test', [ [ 'offset' => 6, 'length' => 4, 'type' => 'bold', ], ], ], [ 'html', 'test test', 'test test', [ [ 'offset' => 4, 'length' => 5, 'type' => 'bold', ], ], ], [ 'markdown', 'test* test*', 'test test', [ [ 'offset' => 4, 'length' => 5, 'type' => 'bold', ], ], ], [ 'html', 'test
test test
test
test strikethrough underline
blockquote
https://google.com daniil@daniil.it +39398172758722 @daniilgentili spoiler <b>not_bold</b>', "test\ntest test test test strikethrough underline blockquote https://google.com daniil@daniil.it +39398172758722 @daniilgentili spoiler not_bold", [ [ 'offset' => 0, 'length' => 4, 'type' => 'bold', ], [ 'offset' => 5, 'length' => 4, 'type' => 'italic', ], [ 'offset' => 10, 'length' => 4, 'type' => 'code', ], [ 'offset' => 15, 'length' => 4, 'language' => 'html', 'type' => 'pre', ], [ 'offset' => 20, 'length' => 4, 'url' => 'https://example.com/', 'type' => 'text_link', ], [ 'offset' => 25, 'length' => 13, 'type' => 'strikethrough', ], [ 'offset' => 39, 'length' => 9, 'type' => 'underline', ], [ 'offset' => 49, 'length' => 10, 'type' => 'block_quote', ], [ 'offset' => 127, 'length' => 7, 'type' => 'spoiler', ], ], 'test
test test
test
test strikethrough underline
blockquote
https://google.com daniil@daniil.it +39398172758722 @daniilgentili spoiler <b>not_bold</b>', ], [ 'markdown', 'test *bold _bold and italic_ bold*', 'test bold bold and italic bold', [ [ 'offset' => 10, 'length' => 15, 'type' => 'italic', ], [ 'offset' => 5, 'length' => 25, 'type' => 'bold', ], ], ], [ 'markdown', "a\nb\nc", "a\nb\nc", [], ], [ 'markdown', "a\n\nb\n\nc", "a\n\nb\n\nc", [], ], [ 'markdown', "a\n\n\nb\n\n\nc", "a\n\n\nb\n\n\nc", [], ], [ 'markdown', "a\n```php\n 2, 'length' => 17, 'type' => 'pre', 'language' => 'php', ], ], ], [ 'html', '\'"', '\'"', [ [ 'offset' => 0, 'length' => 2, 'type' => 'bold', ], ], ''"', ], [ 'html', 'mention1 mention2', 'mention1 mention2', [ [ 'offset' => 0, 'length' => 8, 'type' => 'text_mention', 'user' => ['id' => 101374607], ], [ 'offset' => 9, 'length' => 8, 'type' => 'text_mention', 'user' => ['id' => 101374607], ], ], ], [ 'html', 'mention1 mention2', 'mention1 mention2', [ [ 'offset' => 0, 'length' => 8, 'type' => 'text_mention', 'user' => ['id' => 101374607], ], [ 'offset' => 9, 'length' => 8, 'type' => 'text_mention', 'user' => ['id' => 101374607], ], ], ], [ 'markdown', '_a b c & " \' \_ \* \~ \\__', 'a b c & " \' _ * ~ _', [ [ 'offset' => 0, 'length' => 23, 'type' => 'italic', ], ], ], [ 'markdown', EntityTools::markdownEscape('\\ test testovich _*~'), '\\ test testovich _*~', [], ], [ 'markdown', "```\na_b\n".EntityTools::markdownCodeblockEscape('\\ ```').'```', "a_b\n\\ ```", [ [ 'offset' => 0, 'length' => 9, 'type' => 'pre', 'language' => '', ], ], ], [ 'markdown', '`c_d '.EntityTools::markdownCodeEscape('`').'`', 'c_d `', [ [ 'offset' => 0, 'length' => 5, 'type' => 'code', ], ], ], [ 'markdown', '[link ](https://google.com/)test', 'link test', [ [ 'offset' => 0, 'length' => 4, 'type' => 'text_link', 'url' => 'https://google.com/', ], ], ], [ 'markdown', '[link]('.EntityTools::markdownUrlEscape('https://transfer.sh/(/test/test.PNG,/test/test.MP4).zip').')', 'link', [ [ 'offset' => 0, 'length' => 4, 'type' => 'text_link', 'url' => 'https://transfer.sh/(/test/test.PNG,/test/test.MP4).zip', ], ], ], [ 'markdown', '[link]('.EntityTools::markdownUrlEscape('https://google.com/').')', 'link', [ [ 'offset' => 0, 'length' => 4, 'type' => 'text_link', 'url' => 'https://google.com/', ], ], ], [ 'markdown', '[link]('.EntityTools::markdownUrlEscape('https://google.com/?v=\\test').')', 'link', [ [ 'offset' => 0, 'length' => 4, 'type' => 'text_link', 'url' => 'https://google.com/?v=\\test', ], ], ], [ 'markdown', '[link ](https://google.com/)', 'link', [ [ 'offset' => 0, 'length' => 4, 'type' => 'text_link', 'url' => 'https://google.com/', ], ], ], [ 'markdown', '![link ](tg://emoji?id=5368324170671202286)', 'link', [ [ 'offset' => 0, 'length' => 4, 'type' => 'custom_emoji', 'custom_emoji_id' => 5368324170671202286, ], ], ], [ 'markdown', '[not a link]', '[not a link]', [], ], [ 'html', 'link test', 'link test', [ [ 'offset' => 0, 'length' => 4, 'type' => 'text_link', 'url' => 'https://google.com/', ], ], 'link test', ], [ 'html', 'link ', 'link', [ [ 'offset' => 0, 'length' => 4, 'type' => 'text_link', 'url' => 'https://google.com/', ], ], 'link ', ], [ 'markdown', 'test _italic_ *bold* __underlined__ ~strikethrough~ ```test pre``` `code` ||spoiler||', 'test italic bold underlined strikethrough pre code spoiler', [ [ 'offset' => 5, 'length' => 6, 'type' => 'italic', ], [ 'offset' => 12, 'length' => 4, 'type' => 'bold', ], [ 'offset' => 17, 'length' => 10, 'type' => 'underline', ], [ 'offset' => 28, 'length' => 13, 'type' => 'strikethrough', ], [ 'offset' => 42, 'length' => 4, 'type' => 'pre', 'language' => 'test', ], [ 'offset' => 47, 'length' => 4, 'type' => 'code', ], [ 'offset' => 52, 'length' => 7, 'type' => 'spoiler', ], ], ], [ 'markdown', '[special link]('.EntityTools::markdownUrlEscape('https://google.com/)').')', 'special link', [ [ 'offset' => 0, 'length' => 12, 'type' => 'text_link', 'url' => 'https://google.com/)', ], ], 'link ', ], [ 'markdown', '`'.EntityTools::markdownCodeEscape('``').'`', '``', [ [ 'offset' => 0, 'length' => 2, 'type' => 'code', ], ], '`\`\``', ], ]; } }