import { describe, it } from 'mocha' import { expect } from 'chai' import { tl } from '@mtcute/tl' import { HtmlMessageEntityParser, html } from '../src' import { MessageEntity, RawString } from '@mtcute/client' import bigInt from 'big-integer' const createEntity = ( type: T, offset: number, length: number, additional?: Omit< tl.FindByName, '_' | 'offset' | 'length' > ): tl.TypeMessageEntity => { return { _: type, offset, length, ...(additional ?? {}), } as any // idc really, its not that important } const createEntities = (entities: tl.TypeMessageEntity[]): MessageEntity[] => { return entities .map((it) => MessageEntity._parse(it)) .filter((it) => it !== null) as MessageEntity[] } describe('HtmlMessageEntityParser', () => { const parser = new HtmlMessageEntityParser() describe('unparse', () => { const test = ( text: string, entities: tl.TypeMessageEntity[], expected: string, _parser = parser ): void => { expect(_parser.unparse(text, createEntities(entities))).eq(expected) } it('should return the same text if there are no entities or text', () => { test('', [], '') test('some text', [], 'some text') }) it('should handle , , , tags', () => { test( 'plain bold italic underline strikethrough plain', [ createEntity('messageEntityBold', 6, 4), createEntity('messageEntityItalic', 11, 6), createEntity('messageEntityUnderline', 18, 9), createEntity('messageEntityStrike', 28, 13), ], 'plain bold italic underline strikethrough plain' ) }) it('should handle ,
, 
tags', () => { test( 'plain code pre blockquote plain', [ createEntity('messageEntityCode', 6, 4), createEntity('messageEntityPre', 11, 3), createEntity('messageEntityBlockquote', 15, 10), ], 'plain code
pre
blockquote
plain' ) }) it('should handle links and text mentions', () => { test( 'plain https://google.com google @durov Pavel Durov mail@mail.ru plain', [ createEntity('messageEntityUrl', 6, 18), createEntity('messageEntityTextUrl', 25, 6, { url: 'https://google.com', }), createEntity('messageEntityMention', 32, 6), createEntity('messageEntityMentionName', 39, 11, { userId: 36265675, }), createEntity('messageEntityEmail', 51, 12), ], 'plain https://google.com google @durov Pavel Durov mail@mail.ru plain' ) }) it('should handle language in
', () => {
            test(
                'plain console.log("Hello, world!") some code plain',
                [
                    createEntity('messageEntityPre', 6, 28, {
                        language: 'javascript',
                    }),
                    createEntity('messageEntityPre', 35, 9, { language: '' }),
                ],
                'plain 
console.log("Hello, world!")
some code
plain' ) }) it('should support entities on the edges', () => { test( 'Hello, world', [ createEntity('messageEntityBold', 0, 5), createEntity('messageEntityBold', 7, 5), ], 'Hello, world' ) }) it('should clamp out-of-range entities', () => { test( 'Hello, world', [ createEntity('messageEntityBold', -2, 7), createEntity('messageEntityBold', 7, 10), ], 'Hello, world' ) }) it('should ignore entities outside the length', () => { test( 'Hello, world', [createEntity('messageEntityBold', 50, 5)], 'Hello, world' ) }) it('should support entities followed by each other', () => { test( 'plain Hello, world plain', [ createEntity('messageEntityBold', 6, 6), createEntity('messageEntityItalic', 12, 6), ], 'plain Hello, world plain' ) }) it('should support nested entities', () => { test( 'Welcome to the gym zone!', [ createEntity('messageEntityItalic', 0, 24), createEntity('messageEntityBold', 15, 8), ], 'Welcome to the gym zone!' ) }) it('should support nested entities with the same edges', () => { test( 'Welcome to the gym zone!', [ createEntity('messageEntityItalic', 0, 24), createEntity('messageEntityBold', 15, 9), ], 'Welcome to the gym zone!' ) test( 'Welcome to the gym zone!', [ createEntity('messageEntityBold', 0, 24), createEntity('messageEntityItalic', 15, 9), ], 'Welcome to the gym zone!' ) test( 'Welcome to the gym zone!', [ createEntity('messageEntityItalic', 0, 24), createEntity('messageEntityBold', 0, 7), ], 'Welcome to the gym zone!' ) test( 'Welcome to the gym zone!', [ createEntity('messageEntityItalic', 0, 24), createEntity('messageEntityBold', 0, 24), ], 'Welcome to the gym zone!' ) }) it('should support overlapping entities', () => { test( 'Welcome to the gym zone!', [ createEntity('messageEntityItalic', 0, 14), createEntity('messageEntityBold', 8, 10), ], 'Welcome to the gym zone!' ) test( 'plain bold bold-italic bold-italic-underline underline plain', [ createEntity('messageEntityBold', 6, 38), createEntity('messageEntityItalic', 11, 33), createEntity('messageEntityUnderline', 23, 31), ], 'plain bold bold-italic bold-italic-underline underline plain' ) test( 'plain bold bold-italic bold-italic-underline italic-underline underline plain', [ createEntity('messageEntityBold', 6, 38), createEntity('messageEntityItalic', 11, 50), createEntity('messageEntityUnderline', 23, 48), ], // not the most efficient way (in the second part we could do ......), but whatever 'plain bold bold-italic bold-italic-underline italic-underline underline plain' ) }) it('should properly handle emojis', () => { test( "best flower: 🌸. don't you even doubt it.", [ createEntity('messageEntityItalic', 0, 11), createEntity('messageEntityBold', 13, 2), createEntity('messageEntityItalic', 17, 5), ], "best flower: 🌸. don't you even doubt it." ) }) it('should escape special symbols', () => { test( '<&> < & > <&>', [createEntity('messageEntityBold', 4, 5)], '<&> < & > <&>' ) }) it('should work with custom syntax highlighter', () => { const parser = new HtmlMessageEntityParser({ syntaxHighlighter: (code, lang) => `lang: ${lang}
${code}`, }) test( 'plain console.log("Hello, world!") some code plain', [ createEntity('messageEntityPre', 6, 28, { language: 'javascript', }), createEntity('messageEntityPre', 35, 9, { language: '' }), ], 'plain
lang: javascript
console.log("Hello, world!")
lang: 
some code
plain', parser ) }) }) describe('parse', () => { const test = ( text: string, expectedEntities: tl.TypeMessageEntity[], expectedText: string ): void => { const [_text, entities] = parser.parse(text) expect(_text).eql(expectedText) expect(entities).eql(expectedEntities) } it('should handle , , , tags', () => { test( 'plain bold italic underline strikethrough plain', [ createEntity('messageEntityBold', 6, 4), createEntity('messageEntityItalic', 11, 6), createEntity('messageEntityUnderline', 18, 9), createEntity('messageEntityStrike', 28, 13), ], 'plain bold italic underline strikethrough plain' ) }) it('should handle ,
, 
tags', () => { test( 'plain code
pre
blockquote
plain', [ createEntity('messageEntityCode', 6, 4), createEntity('messageEntityPre', 11, 3, { language: '' }), createEntity('messageEntityBlockquote', 15, 10), ], 'plain code pre blockquote plain' ) }) it('should handle links and text mentions', () => { test( 'plain https://google.com google @durov Pavel Durov plain', [ createEntity('messageEntityTextUrl', 25, 6, { url: 'https://google.com', }), createEntity('messageEntityMentionName', 39, 11, { userId: 36265675, }), ], 'plain https://google.com google @durov Pavel Durov plain' ) test( 'user', [ createEntity('inputMessageEntityMentionName', 0, 4, { userId: { _: 'inputUser', userId: 1234567, accessHash: bigInt('aabbccddaabbccdd', 16), }, }), ], 'user' ) }) it('should handle language in
', () => {
            test(
                'plain 
console.log("Hello, world!")
some code
plain', [ createEntity('messageEntityPre', 6, 28, { language: 'javascript', }), createEntity('messageEntityPre', 35, 9, { language: '' }), ], 'plain console.log("Hello, world!") some code plain' ) }) it('should support entities on the edges', () => { test( 'Hello, world', [ createEntity('messageEntityBold', 0, 5), createEntity('messageEntityBold', 7, 5), ], 'Hello, world' ) }) it('should return empty array if there are no entities', () => { test('Hello, world', [], 'Hello, world') }) it('should support entities followed by each other', () => { test( 'plain Hello, world plain', [ createEntity('messageEntityBold', 6, 6), createEntity('messageEntityItalic', 12, 6), ], 'plain Hello, world plain' ) }) it('should support nested entities', () => { test( 'Welcome to the gym zone!', [ createEntity('messageEntityBold', 15, 8), createEntity('messageEntityItalic', 0, 24), ], 'Welcome to the gym zone!' ) }) it('should support nested entities with the same edges', () => { test( 'Welcome to the gym zone!', [ createEntity('messageEntityBold', 15, 9), createEntity('messageEntityItalic', 0, 24), ], 'Welcome to the gym zone!' ) test( 'Welcome to the gym zone!', [ createEntity('messageEntityItalic', 15, 9), createEntity('messageEntityBold', 0, 24), ], 'Welcome to the gym zone!' ) test( 'Welcome to the gym zone!', [ createEntity('messageEntityBold', 0, 7), createEntity('messageEntityItalic', 0, 24), ], 'Welcome to the gym zone!' ) test( 'Welcome to the gym zone!', [ createEntity('messageEntityBold', 0, 24), createEntity('messageEntityItalic', 0, 24), ], 'Welcome to the gym zone!' ) }) it('should properly handle emojis', () => { test( "best flower: 🌸. don't you even doubt it.", [ createEntity('messageEntityItalic', 0, 11), createEntity('messageEntityBold', 13, 2), createEntity('messageEntityItalic', 17, 5), ], "best flower: 🌸. don't you even doubt it." ) }) it('should handle non-escaped special symbols', () => { test( '<&> < & > <&>', [createEntity('messageEntityBold', 4, 5)], '<&> < & > <&>' ) }) it('should unescape special symbols', () => { test( '<&> < & > <&> link', [ createEntity('messageEntityBold', 4, 5), createEntity('messageEntityTextUrl', 14, 4, { url: '/?a="hello"&b', }), ], '<&> < & > <&> link' ) }) it('should ignore other tags', () => { test('', [], 'alert(1)') }) it('should ignore empty urls', () => { test('link link', [], 'link link') }) }) describe('template', () => { it('should work as a tagged template literal', () => { const unsafeString = '<&>' expect(html`${unsafeString}`).eq('<&>') expect(html`${unsafeString} text`).eq('<&> text') expect(html`text ${unsafeString}`).eq('text <&>') expect(html`${unsafeString}`).eq('<&>') }) it('should skip with RawString', () => { const unsafeString2 = '<&>' const unsafeString = new RawString('<&>') expect(html`${unsafeString}`).eq('<&>') expect(html`${unsafeString} ${unsafeString2}`).eq('<&> <&>') expect(html`${unsafeString} text`).eq('<&> text') expect(html`text ${unsafeString}`).eq('text <&>') expect(html`${unsafeString}`).eq('<&>') expect(html`${unsafeString} ${unsafeString2}`).eq('<&> <&>') }) }) })