/**
* Copyright © 2016-2019 Threema GmbH (https://threema.ch/).
*
* This file is part of Threema Web.
*
* Threema Web is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
* General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Threema Web. If not, see .
*/
// tslint:disable:max-line-length
import {markify, parse, tokenize, TokenType} from '../../src/markup_parser';
describe('Markup Parser', () => {
describe('tokenizer', () => {
it('simple', function() {
const text = 'hello *there*!';
const tokens = tokenize(text);
expect(tokens).toEqual([
{ kind: TokenType.Text, value: 'hello ' },
{ kind: TokenType.Asterisk },
{ kind: TokenType.Text, value: 'there' },
{ kind: TokenType.Asterisk },
{ kind: TokenType.Text, value: '!' },
]);
});
it('nested', function() {
const text = 'this is *_nested_*!';
const tokens = tokenize(text);
expect(tokens).toEqual([
{ kind: TokenType.Text, value: 'this is ' },
{ kind: TokenType.Asterisk },
{ kind: TokenType.Underscore },
{ kind: TokenType.Text, value: 'nested' },
{ kind: TokenType.Underscore },
{ kind: TokenType.Asterisk },
{ kind: TokenType.Text, value: '!' },
]);
});
it('ignore if not along boundary', function() {
const text = 'this*is_not~at-boundary';
const tokens = tokenize(text);
expect(tokens).toEqual([
{ kind: TokenType.Text, value: 'this*is_not~at-boundary' },
]);
});
it('ignore markup in URLs', function() {
const text = 'ignore if *in* a link: https://example.com/pic_-_a.jpg';
const tokens = tokenize(text);
expect(tokens).toEqual([
{ kind: TokenType.Text, value: 'ignore if ' },
{ kind: TokenType.Asterisk },
{ kind: TokenType.Text, value: 'in' },
{ kind: TokenType.Asterisk },
{ kind: TokenType.Text, value: ' a link: https://example.com/pic_-_a.jpg' },
]);
});
it('with newlines', function() {
const text = 'hello\n*world*\n';
const tokens = tokenize(text);
expect(tokens).toEqual([
{ kind: TokenType.Text, value: 'hello' },
{ kind: TokenType.Newline },
{ kind: TokenType.Asterisk },
{ kind: TokenType.Text, value: 'world' },
{ kind: TokenType.Asterisk },
{ kind: TokenType.Newline },
]);
});
});
describe('parser', () => {
it('simple text without formatting', () => {
const tokens = [{ kind: TokenType.Text, value: 'hello world' }];
const html = parse(tokens);
expect(html).toEqual('hello world');
});
it('simple bold text', () => {
const tokens = [
{ kind: TokenType.Text, value: 'hello ' },
{ kind: TokenType.Asterisk },
{ kind: TokenType.Text, value: 'bold' },
{ kind: TokenType.Asterisk },
];
const html = parse(tokens);
expect(html).toEqual('hello bold');
});
it('simple italic text', () => {
const tokens = [
{ kind: TokenType.Text, value: 'hello ' },
{ kind: TokenType.Underscore },
{ kind: TokenType.Text, value: 'italic' },
{ kind: TokenType.Underscore },
];
const html = parse(tokens);
expect(html).toEqual('hello italic');
});
it('simple strikethrough text', () => {
const tokens = [
{ kind: TokenType.Text, value: 'hello ' },
{ kind: TokenType.Tilde },
{ kind: TokenType.Text, value: 'strikethrough' },
{ kind: TokenType.Tilde },
];
const html = parse(tokens);
expect(html).toEqual('hello strikethrough');
});
it('correct nesting', () => {
const tokens = [
{ kind: TokenType.Text, value: 'hello ' },
{ kind: TokenType.Asterisk },
{ kind: TokenType.Text, value: 'bold and ' },
{ kind: TokenType.Underscore },
{ kind: TokenType.Text, value: 'italic' },
{ kind: TokenType.Underscore },
{ kind: TokenType.Asterisk },
];
const html = parse(tokens);
expect(html).toEqual('hello bold and italic');
});
it('incorrect nesting', () => {
const tokens = [
{ kind: TokenType.Asterisk },
{ kind: TokenType.Text, value: 'hi ' },
{ kind: TokenType.Underscore },
{ kind: TokenType.Text, value: 'there' },
{ kind: TokenType.Asterisk },
{ kind: TokenType.Underscore },
];
const html = parse(tokens);
expect(html).toEqual('hi _there_');
});
});
function testPatterns(cases) {
for (const testcase of cases) {
const input = testcase[0];
const expected = testcase[1];
expect(markify(input)).toEqual(expected);
}
}
describe('markify', () => {
it('detects bold text', () => {
testPatterns([
['*bold text (not italic)*',
'bold text (not italic)'],
]);
});
it('detects italic text', () => {
testPatterns([
['This text is not italic.',
'This text is not italic.'],
['_This text is italic._',
'This text is italic.'],
['This text is _partially_ italic',
'This text is partially italic'],
['This text has _two_ _italic_ bits',
'This text has two italic bits'],
]);
});
it('detects strikethrough text', () => {
testPatterns([
['so ~strikethrough~', 'so strikethrough'],
]);
});
it('detects mixed markup', () => {
testPatterns([
['*bold text with _italic_ *',
'bold text with italic '],
['*part bold,* _part italic_',
'part bold, part italic'],
['_italic text with *bold* _',
'italic text with bold '],
]);
});
it('is applied on word boundaries', () => {
testPatterns([
['(*bold*)',
'(bold)'],
['¡*Threema* es fantástico!',
'¡Threema es fantástico!'],
['«_great_ service»',
'«great service»'],
['"_great_" service',
'"great" service'],
['*bold*…',
'bold…'],
['_Threema_',
'Threema'],
]);
});
it('is only applied on word boundaries', () => {
testPatterns([
['so not_really_italic',
'so not_really_italic'],
['invalid*bold*stuff',
'invalid*bold*stuff'],
['no~strike~through',
'no~strike~through'],
['*bold_but_no~strike~through*',
'bold_but_no~strike~through'],
['<_< >_>',
'<_< >_>'],
['_Threema_',
'_Threema_'],
]);
});
it('does not break URLs', () => {
testPatterns([
['https://en.wikipedia.org/wiki/Java_class_file *nice*',
'https://en.wikipedia.org/wiki/Java_class_file nice'],
['https://example.com/_output_/',
'https://example.com/_output_/'],
['https://example.com/*output*/',
'https://example.com/*output*/'],
['https://example.com?_twitter_impression=true',
'https://example.com?_twitter_impression=true'],
['https://example.com?__twitter_impression=true',
'https://example.com?__twitter_impression=true'],
['https://example.com?___twitter_impression=true',
'https://example.com?___twitter_impression=true'],
['https://example.com/image_-_1.jpg',
'https://example.com/image_-_1.jpg'],
]);
});
it('ignores invalid markup', () => {
testPatterns([
['*invalid markup (do not parse)_', '*invalid markup (do not parse)_'],
['random *asterisk', 'random *asterisk'],
['***three asterisks', '***three asterisks'],
['***three asterisks*', '**three asterisks'],
]);
});
it('ignores markup with \\n (newline)', () => {
testPatterns([
['*First line\n and a new one. (do not parse)*', '*First line\n and a new one. (do not parse)*'],
['*\nbegins with linebreak. (do not parse)*', '*\nbegins with linebreak. (do not parse)*'],
['*Just some text. But it ends with newline (do not parse)\n*', '*Just some text. But it ends with newline (do not parse)\n*'],
]);
});
});
});