Skip to content
Snippets Groups Projects
parser.ts 16.3 KiB
Newer Older
import assert from 'assert';
marihachi's avatar
marihachi committed
import * as mfm from '../built/index';
import {
	TEXT, CENTER, FN, UNI_EMOJI, MENTION, EMOJI_CODE, HASHTAG, N_URL, BOLD, SMALL, ITALIC, STRIKE, QUOTE, MATH_BLOCK, SEARCH, CODE_BLOCK, LINK
} from '../built/index';
describe('PlainParser', () => {
	describe('text', () => {
		it('basic', () => {
			const input = 'abc';
			const output = [TEXT('abc')];
			assert.deepStrictEqual(mfm.parsePlain(input), output);
		});

		it('ignore hashtag', () => {
			const input = 'abc#abc';
			const output = [TEXT('abc#abc')];
			assert.deepStrictEqual(mfm.parsePlain(input), output);
		});

		it('keycap number sign', () => {
			const input = 'abc#️⃣abc';
			const output = [TEXT('abc'), UNI_EMOJI('#️⃣'), TEXT('abc')];
			assert.deepStrictEqual(mfm.parsePlain(input), output);
		});
	});
});

describe('FullParser', () => {
	describe('text', () => {
marihachi's avatar
marihachi committed
		it('普通のテキストを入力すると1つのテキストノードが返される', () => {
			const input = 'abc';
			const output = [TEXT('abc')];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('quote', () => {
marihachi's avatar
marihachi committed
		it('1行の引用ブロックを使用できる', () => {
			const input = '> abc';
			const output = [
				QUOTE([
					TEXT('abc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
		it('複数行の引用ブロックを使用できる', () => {
			const input = `
> abc
> 123
`;
			const output = [
				QUOTE([
					TEXT('abc\n123')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
		it('引用ブロックはブロックをネストできる', () => {
			const input = `
> <center>
> a
> </center>
`;
			const output = [
				QUOTE([
					CENTER([
						TEXT('a')
					])
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
		it('引用ブロックはインライン構文を含んだブロックをネストできる', () => {
			const input = `
> <center>
> I'm @ai, An bot of misskey!
> </center>
`;
			const output = [
				QUOTE([
					CENTER([
						TEXT('I\'m '),
						MENTION('ai', null, '@ai'),
						TEXT(', An bot of misskey!'),
					])
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
		it('複数行の引用ブロックでは空行を含めることができる', () => {
			const input = `
> abc
>
> 123
`;
			const output = [
				QUOTE([
					TEXT('abc\n\n123')
				])
			];
			assert.deepStrictEqual(mfm.parse(input), output);
		});
		it('1行の引用ブロックを空行にはできない', () => {
			const input = `> `;
			const output = [
				TEXT('> ')
			];
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('search', () => {
marihachi's avatar
marihachi committed
		describe('検索構文を使用できる', () => {
			it('Search', () => {
				const input = 'MFM 書き方 123 Search';
				const output = [
					SEARCH('MFM 書き方 123', input)
marihachi's avatar
marihachi committed
				assert.deepStrictEqual(mfm.parse(input), output);
			});
			it('[Search]', () => {
				const input = 'MFM 書き方 123 [Search]';
				const output = [
					SEARCH('MFM 書き方 123', input)
marihachi's avatar
marihachi committed
				assert.deepStrictEqual(mfm.parse(input), output);
			});
			it('search', () => {
				const input = 'MFM 書き方 123 search';
				const output = [
					SEARCH('MFM 書き方 123', input)
marihachi's avatar
marihachi committed
				assert.deepStrictEqual(mfm.parse(input), output);
			});
			it('[search]', () => {
				const input = 'MFM 書き方 123 [search]';
				const output = [
					SEARCH('MFM 書き方 123', input)
marihachi's avatar
marihachi committed
				assert.deepStrictEqual(mfm.parse(input), output);
			});
			it('検索', () => {
				const input = 'MFM 書き方 123 検索';
				const output = [
					SEARCH('MFM 書き方 123', input)
marihachi's avatar
marihachi committed
				assert.deepStrictEqual(mfm.parse(input), output);
			});
			it('[検索]', () => {
				const input = 'MFM 書き方 123 [検索]';
				const output = [
					SEARCH('MFM 書き方 123', input)
marihachi's avatar
marihachi committed
				assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
		it('ブロックの前後にあるテキストが正しく解釈される', () => {
			const input = 'abc\nhoge piyo bebeyo 検索\n123';
			const output = [
				TEXT('abc'),
				SEARCH('hoge piyo bebeyo', 'hoge piyo bebeyo 検索'),
				TEXT('123')
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('code block', () => {
marihachi's avatar
marihachi committed
		it('コードブロックを使用できる', () => {
			const input = '```\nabc\n```';
			const output = [CODE_BLOCK('abc', null)];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
		it('コードブロックには複数行のコードを入力できる', () => {
			const input = '```\na\nb\nc\n```';
			const output = [CODE_BLOCK('a\nb\nc', null)];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
		it('コードブロックは言語を指定できる', () => {
			const input = '```js\nconst a = 1;\n```';
			const output = [CODE_BLOCK('const a = 1;', 'js')];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
		it('ブロックの前後にあるテキストが正しく解釈される', () => {
			const input = 'abc\n```\nconst abc = 1;\n```\n123';
			const output = [
				TEXT('abc'),
				CODE_BLOCK('const abc = 1;', null),
				TEXT('123')
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('mathBlock', () => {
marihachi's avatar
marihachi committed
		it('1行の数式ブロックを使用できる', () => {
			const input = '\\[math1\\]';
			const output = [
				MATH_BLOCK('math1')
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
		});
		it('ブロックの前後にあるテキストが正しく解釈される', () => {
			const input = 'abc\n\\[math1\\]\n123';
			const output = [
				TEXT('abc'),
marihachi's avatar
marihachi committed
				MATH_BLOCK('math1'),
				TEXT('123')
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
		});
		it('行末以外に閉じタグがある場合はマッチしない', () => {
			const input = '\\[aaa\\]after';
			const output = [
				TEXT('\\[aaa\\]after')
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
		it('行頭以外に開始タグがある場合はマッチしない', () => {
			const input = 'before\\[aaa\\]';
			const output = [
marihachi's avatar
marihachi committed
				TEXT('before\\[aaa\\]')
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('center', () => {
		it('single text', () => {
			const input = '<center>abc</center>';
			const output = [
				CENTER([
					TEXT('abc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
		it('multiple text', () => {
			const input = 'before\n<center>\nabc\n123\n\npiyo\n</center>\nafter';
			const output = [
				TEXT('before'),
				CENTER([
					TEXT('abc\n123\n\npiyo')
				]),
				TEXT('after')
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('emoji code', () => {
		it('basic', () => {
			const input = ':abc:';
			const output = [EMOJI_CODE('abc')];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('unicode emoji', () => {
		it('basic', () => {
			const input = '今起きた😇';
			const output = [TEXT('今起きた'), UNI_EMOJI('😇')];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);

		it('keycap number sign', () => {
			const input = 'abc#️⃣123';
			const output = [TEXT('abc'), UNI_EMOJI('#️⃣'), TEXT('123')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('big', () => {
		it('basic', () => {
			const input = '***abc***';
			const output = [
				FN('tada', { }, [
					TEXT('abc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
		it('内容にはインライン構文を利用できる', () => {
			const input = '***123**abc**123***';
			const output = [
				FN('tada', { }, [
					TEXT('123'),
					BOLD([
						TEXT('abc')
					]),
					TEXT('123')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
		it('内容は改行できる', () => {
			const input = '***123\n**abc**\n123***';
			const output = [
				FN('tada', { }, [
					TEXT('123\n'),
					BOLD([
						TEXT('abc')
					]),
					TEXT('\n123')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('bold', () => {
		it('basic', () => {
			const input = '**abc**';
			const output = [
				BOLD([
					TEXT('abc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
		it('内容にはインライン構文を利用できる', () => {
			const input = '**123~~abc~~123**';
			const output = [
				BOLD([
					TEXT('123'),
					STRIKE([
						TEXT('abc')
					]),
					TEXT('123')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
		it('内容は改行できる', () => {
			const input = '**123\n~~abc~~\n123**';
			const output = [
				BOLD([
					TEXT('123\n'),
					STRIKE([
						TEXT('abc')
					]),
					TEXT('\n123')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('small', () => {
		it('basic', () => {
			const input = '<small>abc</small>';
			const output = [
				SMALL([
					TEXT('abc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
		it('内容にはインライン構文を利用できる', () => {
			const input = '<small>abc**123**abc</small>';
			const output = [
				SMALL([
					TEXT('abc'),
					BOLD([
						TEXT('123')
					]),
					TEXT('abc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
		it('内容は改行できる', () => {
			const input = '<small>abc\n**123**\nabc</small>';
			const output = [
				SMALL([
					TEXT('abc\n'),
					BOLD([
						TEXT('123')
					]),
					TEXT('\nabc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
	describe('italic tag', () => {
		it('basic', () => {
			const input = '<i>abc</i>';
			const output = [
				ITALIC([
					TEXT('abc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
		it('内容にはインライン構文を利用できる', () => {
			const input = '<i>abc**123**abc</i>';
			const output = [
				ITALIC([
					TEXT('abc'),
					BOLD([
						TEXT('123')
					]),
					TEXT('abc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
		it('内容は改行できる', () => {
			const input = '<i>abc\n**123**\nabc</i>';
			const output = [
				ITALIC([
					TEXT('abc\n'),
					BOLD([
						TEXT('123')
					]),
					TEXT('\nabc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed
	describe('italic alt 1', () => {
		it('basic', () => {
			const input = '*abc*';
			const output = [
				ITALIC([
					TEXT('abc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed

		it('basic 2', () => {
			const input = 'before *abc* after';
			const output = [
				TEXT('before '),
				ITALIC([
					TEXT('abc')
				]),
				TEXT(' after')
			];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('ignore a italic syntax if the before char is neither a space nor an LF', () => {
			const input = 'before*abc*after';
			const output = [TEXT('before*abc*after')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('italic alt 2', () => {
		it('basic', () => {
			const input = '_abc_';
			const output = [
				ITALIC([
					TEXT('abc')
				])
			];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('basic 2', () => {
			const input = 'before _abc_ after';
			const output = [
				TEXT('before '),
				ITALIC([
					TEXT('abc')
				]),
				TEXT(' after')
			];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('ignore a italic syntax if the before char is neither a space nor an LF', () => {
			const input = 'before_abc_after';
			const output = [TEXT('before_abc_after')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	// strike

	// inlineCode

	// mathInline

marihachi's avatar
marihachi committed
	describe('mention', () => {
		it('basic', () => {
			const input = '@abc';
			const output = [MENTION('abc', null, '@abc')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('basic 2', () => {
			const input = 'before @abc after';
			const output = [TEXT('before '), MENTION('abc', null, '@abc'), TEXT(' after')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('basic remote', () => {
			const input = '@abc@misskey.io';
			const output = [MENTION('abc', 'misskey.io', '@abc@misskey.io')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('basic remote 2', () => {
			const input = 'before @abc@misskey.io after';
			const output = [TEXT('before '), MENTION('abc', 'misskey.io', '@abc@misskey.io'), TEXT(' after')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('basic remote 3', () => {
			const input = 'before\n@abc@misskey.io\nafter';
			const output = [TEXT('before\n'), MENTION('abc', 'misskey.io', '@abc@misskey.io'), TEXT('\nafter')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('ignore format of mail address', () => {
			const input = 'abc@example.com';
			const output = [TEXT('abc@example.com')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('hashtag', () => {
		it('basic', () => {
marihachi's avatar
marihachi committed
			const input = '#abc';
			const output = [HASHTAG('abc')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('basic 2', () => {
			const input = 'before #abc after';
			const output = [TEXT('before '), HASHTAG('abc'), TEXT(' after')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('with keycap number sign', () => {
			const input = '#️⃣abc123 #abc';
			const output = [UNI_EMOJI('#️⃣'), TEXT('abc123 '), HASHTAG('abc')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('with keycap number sign 2', () => {
			const input = `abc
#️⃣abc`;
			const output = [TEXT('abc\n'), UNI_EMOJI('#️⃣'), TEXT('abc')];
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('ignore a hashtag if the before char is neither a space nor an LF', () => {
			const input = 'abc#abc';
			const output = [TEXT('abc#abc')];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('url', () => {
		it('basic', () => {
			const input = 'official instance: https://misskey.io/@ai.';
			const output = [
				TEXT('official instance: '),
				N_URL('https://misskey.io/@ai'),
				TEXT('.')
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('link', () => {
		it('basic', () => {
			const input = '[official instance](https://misskey.io/@ai).';
			const output = [
				LINK(false, 'https://misskey.io/@ai', [
					TEXT('official instance')
				]),
				TEXT('.')
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('silent flag', () => {
			const input = '?[official instance](https://misskey.io/@ai).';
			const output = [
				LINK(true, 'https://misskey.io/@ai', [
					TEXT('official instance')
				]),
				TEXT('.')
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('do not yield url node even if label is recognisable as a url', () => {
			const input = 'official instance: [https://misskey.io/@ai](https://misskey.io/@ai).';
			const output = [
				TEXT('official instance: '),
				LINK(false, 'https://misskey.io/@ai', [
					TEXT('https://misskey.io/@ai')
				]),
				TEXT('.')
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});

		it('do not yield link node even if label is recognisable as a link', () => {
			const input = 'official instance: [[https://misskey.io/@ai](https://misskey.io/@ai)](https://misskey.io/@ai).';
			const output = [
				TEXT('official instance: '),
				LINK(false, 'https://misskey.io/@ai', [
					TEXT('[https://misskey.io/@ai](https://misskey.io/@ai)')
				]),
				TEXT('.')
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	describe('fn', () => {
		it('basic', () => {
			const input = '[tada abc]';
			const output = [
				FN('tada', { }, [
					TEXT('abc')
				])
			];
marihachi's avatar
marihachi committed
			assert.deepStrictEqual(mfm.parse(input), output);
marihachi's avatar
marihachi committed

		it('with a string argument', () => {
			const input = '[spin.speed=1.1s a]';
			const output = [
				FN('spin', { speed: '1.1s' }, [
					TEXT('a')
				])
			];
			assert.deepStrictEqual(mfm.parse(input), output);
		});
	});

	it('composite', () => {
		const input =
`before
<center>
Hello [tada everynyan! 🎉]

I'm @ai, A bot of misskey!

https://github.com/syuilo/ai
</center>
after`;
		const output = [
			TEXT('before'),
			CENTER([
				TEXT('Hello '),
				FN('tada', { }, [
					TEXT('everynyan! '),
					UNI_EMOJI('🎉')
				]),
				TEXT('\n\nI\'m '),
				MENTION('ai', null, '@ai'),
				TEXT(', A bot of misskey!\n\n'),
				N_URL('https://github.com/syuilo/ai')
			]),
			TEXT('after')
		];
marihachi's avatar
marihachi committed
		assert.deepStrictEqual(mfm.parse(input), output);