import { Parser } from '../../blocks'; import { Renderer } from '../../../html/renderer'; import { LinkNode } from '../../node'; import { pos } from '../../__test__/helper.spec'; import { parseUrlLink, parseEmailLink } from '../autoLinks'; describe('parseUrlLink()', () => { // https://github.github.com/gfm/#extended-www-autolink // https://github.github.com/gfm/#extended-url-autolink it('domain not preceeded by www is invalid', () => { expect(parseUrlLink('nhn.com')).toEqual([]); expect(parseUrlLink('ui.toast.com')).toEqual([]); }); it('domain preceeded by www with less than 2 periods(.) is invalid', () => { expect(parseUrlLink('www.nhn')).toEqual([]); }); it('domain preceeded by www is valid', () => { expect(parseUrlLink('www.nhn.com')).toEqual([ { text: 'www.nhn.com', url: `http://www.nhn.com`, range: [0, 10], }, ]); expect(parseUrlLink('Visit www.nhn.com Now!')).toEqual([ { text: 'www.nhn.com', url: `http://www.nhn.com`, range: [6, 16], }, ]); }); it('domain preceeded by http(s):// is valid', () => { expect(parseUrlLink('http://nhn.com')).toEqual([ { text: 'http://nhn.com', url: `http://nhn.com`, range: [0, 13], }, ]); expect(parseUrlLink('https://nhn.com')).toEqual([ { text: 'https://nhn.com', url: `https://nhn.com`, range: [0, 14], }, ]); }); it('zero or more non-space non-< characters may follow', () => { expect(parseUrlLink('www.nhn.com/help { const pairs = [ ['www.nhn.com/?help?', 'www.nhn.com/?help'], ['www.nhn.com/!help!', 'www.nhn.com/!help'], ['www.nhn.com/,help,', 'www.nhn.com/,help'], ['www.nhn.com/.help.', 'www.nhn.com/.help'], ['www.nhn.com/:help:', 'www.nhn.com/:help'], ['www.nhn.com/*help*', 'www.nhn.com/*help'], ['www.nhn.com/~help~', 'www.nhn.com/~help'], ['http://nhn.com/~help~', 'http://nhn.com/~help'], ['https://nhn.com/~help~', 'https://nhn.com/~help'], ]; pairs.forEach(([input, text]) => { expect(parseUrlLink(input)![0].text).toBe(text); }); }); it('trailing closing parens without matching opening parens are excluded', () => { const pairs = [ ['www.nhn.com/(ui)', 'www.nhn.com/(ui)'], ['www.nhn.com/(ui))', 'www.nhn.com/(ui)'], ['(www.nhn.com/(ui))', 'www.nhn.com/(ui)'], ['(www.nhn.com/((ui))', 'www.nhn.com/((ui))'], ['(www.nhn.com/(ui)', 'www.nhn.com/(ui)'], ['(www.nhn.com/)))(ui))', 'www.nhn.com/)))(ui)'], ['(http://nhn.com/)))(ui))', 'http://nhn.com/)))(ui)'], ['(https://nhn.com/)))(ui))', 'https://nhn.com/)))(ui)'], ]; pairs.forEach(([input, text]) => { expect(parseUrlLink(input)![0].text).toBe(text); }); }); it('trailing entity-like pattern (&xxx;) are excluded', () => { const pairs = [ ['www.nhn.com/ui&editor;grid', 'www.nhn.com/ui&editor;grid'], ['www.nhn.com/ui&grid;', 'www.nhn.com/ui'], ['www.nhn.com/ui&?grid;', 'www.nhn.com/ui&?grid;'], ['http://nhn.com/ui&?grid;', 'http://nhn.com/ui&?grid;'], ['https://nhn.com/ui&?grid;', 'https://nhn.com/ui&?grid;'], ]; pairs.forEach(([input, text]) => { expect(parseUrlLink(input)![0].text).toBe(text); }); }); it('should handle multiple occurrences', () => { expect(parseUrlLink('Hello www.nhn.com and http://toast.com')).toEqual([ { text: 'www.nhn.com', url: 'http://www.nhn.com', range: [6, 16], }, { text: 'http://toast.com', url: 'http://toast.com', range: [22, 37], }, ]); }); }); describe('parseEmailLink', () => { it('simple example', () => { expect(parseEmailLink('ui@toast.com')).toEqual([ { text: 'ui@toast.com', url: 'mailto:ui@toast.com', range: [0, 11], }, ]); expect(parseEmailLink('Hello ui@toast.com guys')).toEqual([ { text: 'ui@toast.com', url: 'mailto:ui@toast.com', range: [6, 17], }, ]); }); it('+ can occur before the @, but not after.', () => { expect(parseEmailLink('ui@to+ast.com')).toEqual([]); expect(parseEmailLink('u+i@toast.com')).toEqual([ { text: 'u+i@toast.com', url: 'mailto:u+i@toast.com', range: [0, 12], }, ]); }); it('trailing dash(-) and underscore(_) are invalid, trailing dot(.) is excluded ', () => { const pairs = [ ['a.b-c_d@a.b', 'a.b-c_d@a.b'], ['a.b-c_d@a.b.', 'a.b-c_d@a.b'], ]; const invalids = ['a.b-c_d@a.b-', 'a.b-c_d@a.b_']; pairs.forEach(([input, text]) => { expect(parseEmailLink(input)![0].text).toBe(text); }); invalids.forEach((input) => { expect(parseEmailLink(input)).toEqual([]); }); }); it('should handle multiple occurrences', () => { expect(parseEmailLink('Hello ui@toast.com and file@toast.com')).toEqual([ { text: 'ui@toast.com', url: 'mailto:ui@toast.com', range: [6, 17], }, { text: 'file@toast.com', url: 'mailto:file@toast.com', range: [23, 36], }, ]); }); }); describe('custom autolink parser', () => { const renderer = new Renderer(); const reader = new Parser({ extendedAutolinks: (content) => { const regex = /\d{3}/g; const result = []; let matched; while ((matched = regex.exec(content))) { const { index } = matched; const text = matched[0]; const range: [number, number] = [index, index + text.length - 1]; const url = `num:${text}`; result.push({ text, url, range }); } return result; }, }); it('should parse custom pattern', () => { const root = reader.parse('A 111 B 222'); const para = root.firstChild!; const link1 = para.firstChild!.next!; const link2 = link1.next!.next!; expect(link1).toMatchObject({ destination: 'num:111', extendedAutolink: true, sourcepos: pos(1, 3, 1, 5), firstChild: { literal: '111', }, }); expect(link2).toMatchObject({ destination: 'num:222', extendedAutolink: true, sourcepos: pos(1, 9, 1, 11), firstChild: { literal: '222', }, }); expect(renderer.render(root)).toBe( '

A 111 B 222

\n' ); }); }); // https://github.github.com/gfm/#example-621 describe('GFM Examples', () => { const reader = new Parser({ extendedAutolinks: true }); const renderer = new Renderer(); it('621', () => { const root = reader.parse('www.commonmark.org'); const link = root.firstChild!.firstChild as LinkNode; const linkText = link.firstChild!; expect(link).toMatchObject({ type: 'link', destination: 'http://www.commonmark.org', extendedAutolink: true, sourcepos: pos(1, 1, 1, 18), }); expect(linkText).toMatchObject({ literal: 'www.commonmark.org', sourcepos: pos(1, 1, 1, 18), }); const html = renderer.render(root); expect(html).toBe('

www.commonmark.org

\n'); }); it('622', () => { const root = reader.parse('Visit www.commonmark.org/help for more information.'); const text1 = root.firstChild!.firstChild!; const link = text1.next as LinkNode; const linkText = link.firstChild!; const text2 = link.next!; expect(text1.literal).toBe('Visit '); expect(link).toMatchObject({ type: 'link', extendedAutolink: true, destination: 'http://www.commonmark.org/help', sourcepos: pos(1, 7, 1, 29), }); expect(linkText.literal).toBe('www.commonmark.org/help'); expect(linkText.sourcepos).toEqual(pos(1, 7, 1, 29)); expect(text2.literal).toBe(' for more information.'); expect(text2.sourcepos).toEqual(pos(1, 30, 1, 51)); const html = renderer.render(root); expect(html).toBe( '

Visit www.commonmark.org/help for more information.

\n' ); }); const examples = [ { no: 623, input: ['Visit www.commonmark.org.\n\n', 'Visit www.commonmark.org/a.b.'].join(''), output: [ '

Visit www.commonmark.org.

\n', '

Visit www.commonmark.org/a.b.

\n', ].join(''), }, { no: 624, input: [ 'www.google.com/search?q=Markup+(business)\n\n', 'www.google.com/search?q=Markup+(business)))\n\n', '(www.google.com/search?q=Markup+(business))\n\n', '(www.google.com/search?q=Markup+(business)', ].join(''), output: [ '

', 'www.google.com/search?q=Markup+(business)

\n', '

', 'www.google.com/search?q=Markup+(business)))

\n', '

(', 'www.google.com/search?q=Markup+(business))

\n', '

(', 'www.google.com/search?q=Markup+(business)

\n', ].join(''), }, { no: 625, input: 'www.google.com/search?q=(business))+ok', output: [ '

', 'www.google.com/search?q=(business))+ok

\n', ].join(''), }, { no: 626, input: [ 'www.google.com/search?q=commonmark&hl=en\n\n', 'www.google.com/search?q=commonmark&hl;', ].join(''), output: [ '

', 'www.google.com/search?q=commonmark&hl=en

\n', '

', 'www.google.com/search?q=commonmark&hl;

\n', ].join(''), }, { no: 627, input: 'www.commonmark.org/hewww.commonmark.org/he<lp

\n', }, { no: 628, input: [ 'http://commonmark.org\n\n', '(Visit https://encrypted.google.com/search?q=Markup+(business))', ].join(''), output: [ '

http://commonmark.org

\n', '

(Visit ', 'https://encrypted.google.com/search?q=Markup+(business))

\n', ].join(''), }, { no: 629, input: 'foo@bar.baz', output: '

foo@bar.baz

\n', }, { no: 630, input: `hello@mail+xyz.example isn't valid, but hello+xyz@mail.example is.`, output: [ `

hello@mail+xyz.example isn't valid, but `, `hello+xyz@mail.example is.

\n`, ].join(''), }, { no: 631, input: ['a.b-c_d@a.b\n\n', 'a.b-c_d@a.b.\n\n', 'a.b-c_d@a.b-\n\n', 'a.b-c_d@a.b_'].join(''), output: [ '

a.b-c_d@a.b

\n', '

a.b-c_d@a.b.

\n', '

a.b-c_d@a.b-

\n', '

a.b-c_d@a.b_

\n', ].join(''), }, ]; examples.forEach(({ no, input, output }) => { it(String(no), () => { const root = reader.parse(input); const html = renderer.render(root); expect(html).toBe(output); }); }); });