diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 00000000..88e17e8b --- /dev/null +++ b/src/api.ts @@ -0,0 +1,58 @@ +import peg from 'pegjs'; +import { MfmNode, MfmPlainNode } from './node'; +import { stringifyNode, stringifyTree } from './util'; + +const parser: peg.Parser = require('./parser'); + +export function parse(input: string): MfmNode[] { + const nodes = parser.parse(input, { startRule: 'fullParser' }); + return nodes; +} + +export function parsePlain(input: string): MfmPlainNode[] { + const nodes = parser.parse(input, { startRule: 'plainParser' }); + return nodes; +} + +export function toString(tree: MfmNode[]): string +export function toString(node: MfmNode): string +export function toString(node: MfmNode | MfmNode[]): string { + if (Array.isArray(node)) { + return stringifyTree(node); + } + else { + return stringifyNode(node); + } +} + +export function inspect(tree: MfmNode[], action: (node: MfmNode) => void): void { + for (const node of tree) { + action(node); + if (node.children != null) { + inspect(node.children, action); + } + } +} + +export function extract(nodes: MfmNode[], type: (MfmNode['type'] | MfmNode['type'][])): MfmNode[] { + function predicate(node: MfmNode, type: (MfmNode['type'] | MfmNode['type'][])): boolean { + if (Array.isArray(type)) { + return (type.some(i => i == node.type)); + } + else { + return (type == node.type); + } + } + + const dest = [] as MfmNode[]; + for (const node of nodes) { + if (predicate(node, type)) { + dest.push(node); + } + if (node.children != null) { + dest.push(...extract(node.children, type)); + } + } + + return dest; +} diff --git a/src/index.ts b/src/index.ts index 7c47787c..292d5ace 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,60 +1,10 @@ -import peg from 'pegjs'; -import { MfmNode, MfmPlainNode } from './node'; -import { stringifyNode, stringifyTree } from './util'; -const parser: peg.Parser = require('./parser'); - -export function parse(input: string): MfmNode[] { - const nodes = parser.parse(input, { startRule: 'fullParser' }); - return nodes; -} - -export function parsePlain(input: string): MfmPlainNode[] { - const nodes = parser.parse(input, { startRule: 'plainParser' }); - return nodes; -} - -export function toString(tree: MfmNode[]): string -export function toString(node: MfmNode): string -export function toString(node: MfmNode | MfmNode[]): string { - if (Array.isArray(node)) { - return stringifyTree(node); - } - else { - return stringifyNode(node); - } -} - -export function inspect(tree: MfmNode[], action: (node: MfmNode) => void): void { - for (const node of tree) { - action(node); - if (node.children != null) { - inspect(node.children, action); - } - } -} - -export function extract(nodes: MfmNode[], type: (MfmNode['type'] | MfmNode['type'][])): MfmNode[] { - function predicate(node: MfmNode, type: (MfmNode['type'] | MfmNode['type'][])): boolean { - if (Array.isArray(type)) { - return (type.some(i => i == node.type)); - } - else { - return (type == node.type); - } - } - - const dest = [] as MfmNode[]; - for (const node of nodes) { - if (predicate(node, type)) { - dest.push(node); - } - if (node.children != null) { - dest.push(...extract(node.children, type)); - } - } - - return dest; -} +export { + parse, + parsePlain, + toString, + inspect, + extract +} from './api'; export { NodeType } from './node'; diff --git a/test/api.ts b/test/api.ts new file mode 100644 index 00000000..910cd3cd --- /dev/null +++ b/test/api.ts @@ -0,0 +1,58 @@ +import assert from 'assert'; +import { extract, inspect, parse, toString } 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 './node'; + +describe('API', () => { + describe('toString', () => { + it('basic', () => { + const input = +`before +
+Hello [tada everynyan! 🎉] + +I'm @ai, A bot of misskey! + +https://github.com/syuilo/ai +
+after`; + assert.strictEqual(toString(parse(input)), input); + }); + }); + + describe('inspect', () => { + it('replace text', () => { + const input = 'good morning [tada everynyan!]'; + const result = parse(input); + inspect(result, node => { + if (node.type == 'text') { + node.props.text = node.props.text.replace(/good morning/g, 'hello'); + } + }); + assert.strictEqual(toString(result), 'hello [tada everynyan!]'); + }); + }); + + describe('extract', () => { + it('basic', () => { + const nodes = parse('@hoge @piyo @bebeyo'); + const expect = [ + MENTION('hoge', null, '@hoge'), + MENTION('piyo', null, '@piyo'), + MENTION('bebeyo', null, '@bebeyo') + ]; + assert.deepStrictEqual(extract(nodes, 'mention'), expect); + }); + + it('nested', () => { + const nodes = parse('abc:hoge:[tada 123 @hoge :foo:]:piyo:'); + const expect = [ + EMOJI_CODE('hoge'), + EMOJI_CODE('foo'), + EMOJI_CODE('piyo') + ]; + assert.deepStrictEqual(extract(nodes, 'emojiCode'), expect); + }); + }); +}); diff --git a/test/main.ts b/test/main.ts deleted file mode 100644 index 7ec63038..00000000 --- a/test/main.ts +++ /dev/null @@ -1,564 +0,0 @@ -import assert from 'assert'; -import { extract, inspect, parse, parsePlain, toString } from '../built/index'; -import { createNode } from '../built/util'; -import { - TEXT, CENTER, FN, UNI_EMOJI, MENTION, EMOJI_CODE, HASHTAG, N_URL, BOLD, SMALL, ITALIC, STRIKE, QUOTE, MATH_BLOCK, SEARCH, CODE_BLOCK, LINK -} from './node'; - -describe('text', () => { - it('basic', () => { - const input = 'abc'; - const output = [TEXT('abc')]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('quote', () => { - it('single', () => { - const input = '> abc'; - const output = [ - QUOTE([ - TEXT('abc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('multiple', () => { - const input = ` -> abc -> 123 -`; - const output = [ - QUOTE([ - TEXT('abc\n123') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - - it('with block (center)', () => { - const input = ` ->
-> a ->
-`; - const output = [ - QUOTE([ - CENTER([ - TEXT('a') - ]) - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - - it('with block (center, mention)', () => { - const input = ` ->
-> I'm @ai, An bot of misskey! ->
-`; - const output = [ - QUOTE([ - CENTER([ - TEXT('I\'m '), - MENTION('ai', null, '@ai'), - TEXT(', An bot of misskey!'), - ]) - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('search', () => { - describe('basic', () => { - it('Search', () => { - const input = 'MFM 書き方 123 Search'; - const output = [ - createNode('search', { - query: 'MFM 書き方 123', - content: input - }) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('[Search]', () => { - const input = 'MFM 書き方 123 [Search]'; - const output = [ - createNode('search', { - query: 'MFM 書き方 123', - content: input - }) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('search', () => { - const input = 'MFM 書き方 123 search'; - const output = [ - createNode('search', { - query: 'MFM 書き方 123', - content: input - }) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('[search]', () => { - const input = 'MFM 書き方 123 [search]'; - const output = [ - createNode('search', { - query: 'MFM 書き方 123', - content: input - }) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('検索', () => { - const input = 'MFM 書き方 123 検索'; - const output = [ - createNode('search', { - query: 'MFM 書き方 123', - content: input - }) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('[検索]', () => { - const input = 'MFM 書き方 123 [検索]'; - const output = [ - createNode('search', { - query: 'MFM 書き方 123', - content: input - }) - ]; - assert.deepStrictEqual(parse(input), output); - }); - }); - it('with text', () => { - const input = 'abc\nhoge piyo bebeyo 検索\n123'; - const output = [ - TEXT('abc'), - SEARCH('hoge piyo bebeyo', 'hoge piyo bebeyo 検索'), - TEXT('123') - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('code block', () => { - it('basic', () => { - const input = '```\nabc\n```'; - const output = [CODE_BLOCK('abc', null)]; - assert.deepStrictEqual(parse(input), output); - }); - it('multi line', () => { - const input = '```\na\nb\nc\n```'; - const output = [CODE_BLOCK('a\nb\nc', null)]; - assert.deepStrictEqual(parse(input), output); - }); - it('basic (lang)', () => { - const input = '```js\nconst a = 1;\n```'; - const output = [CODE_BLOCK('const a = 1;', 'js')]; - assert.deepStrictEqual(parse(input), output); - }); - it('with text', () => { - const input = 'abc\n```\nconst abc = 1;\n```\n123'; - const output = [ - TEXT('abc'), - CODE_BLOCK('const abc = 1;', null), - TEXT('123') - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('mathBlock', () => { - it('basic', () => { - const input = '123\n\\[math1\\]\nabc\n\\[math2\\]'; - const output = [ - TEXT('123'), - MATH_BLOCK('math1'), - TEXT('abc'), - MATH_BLOCK('math2') - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('case of no matched', () => { - const input = '\\[aaa\\]\\[bbb\\]'; - const output = [ - TEXT('\\[aaa\\]\\[bbb\\]') - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('center', () => { - it('single text', () => { - const input = '
abc
'; - const output = [ - CENTER([ - TEXT('abc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('multiple text', () => { - const input = 'before\n
\nabc\n123\n\npiyo\n
\nafter'; - const output = [ - TEXT('before'), - CENTER([ - TEXT('abc\n123\n\npiyo') - ]), - TEXT('after') - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('emoji code', () => { - it('basic', () => { - const input = ':abc:'; - const output = [EMOJI_CODE('abc')]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('unicode emoji', () => { - it('basic', () => { - const input = '今起きた😇'; - const output = [TEXT('今起きた'), UNI_EMOJI('😇')]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('big', () => { - it('basic', () => { - const input = '***abc***'; - const output = [ - FN('tada', { }, [ - TEXT('abc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('内容にはインライン構文を利用できる', () => { - const input = '***123**abc**123***'; - const output = [ - FN('tada', { }, [ - TEXT('123'), - BOLD([ - TEXT('abc') - ]), - TEXT('123') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('内容は改行できる', () => { - const input = '***123\n**abc**\n123***'; - const output = [ - FN('tada', { }, [ - TEXT('123\n'), - BOLD([ - TEXT('abc') - ]), - TEXT('\n123') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('bold', () => { - it('basic', () => { - const input = '**abc**'; - const output = [ - BOLD([ - TEXT('abc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('内容にはインライン構文を利用できる', () => { - const input = '**123~~abc~~123**'; - const output = [ - BOLD([ - TEXT('123'), - STRIKE([ - TEXT('abc') - ]), - TEXT('123') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('内容は改行できる', () => { - const input = '**123\n~~abc~~\n123**'; - const output = [ - BOLD([ - TEXT('123\n'), - STRIKE([ - TEXT('abc') - ]), - TEXT('\n123') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('small', () => { - it('basic', () => { - const input = 'abc'; - const output = [ - SMALL([ - TEXT('abc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('内容にはインライン構文を利用できる', () => { - const input = 'abc**123**abc'; - const output = [ - SMALL([ - TEXT('abc'), - BOLD([ - TEXT('123') - ]), - TEXT('abc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('内容は改行できる', () => { - const input = 'abc\n**123**\nabc'; - const output = [ - SMALL([ - TEXT('abc\n'), - BOLD([ - TEXT('123') - ]), - TEXT('\nabc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('italic 1', () => { - it('basic', () => { - const input = 'abc'; - const output = [ - ITALIC([ - TEXT('abc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('内容にはインライン構文を利用できる', () => { - const input = 'abc**123**abc'; - const output = [ - ITALIC([ - TEXT('abc'), - BOLD([ - TEXT('123') - ]), - TEXT('abc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); - it('内容は改行できる', () => { - const input = 'abc\n**123**\nabc'; - const output = [ - ITALIC([ - TEXT('abc\n'), - BOLD([ - TEXT('123') - ]), - TEXT('\nabc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('italic 2', () => { - it('basic', () => { - const input = '*abc*'; - const output = [ - ITALIC([ - TEXT('abc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -// strike - -// inlineCode - -// mathInline - -// mention - -describe('hashtag', () => { - it('and unicode emoji', () => { - const input = '#️⃣abc123#abc'; - const output = [UNI_EMOJI('#️⃣'), TEXT('abc123'), HASHTAG('abc')]; - assert.deepStrictEqual(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('.') - ]; - assert.deepStrictEqual(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('.') - ]; - assert.deepStrictEqual(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('.') - ]; - assert.deepStrictEqual(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('.') - ]; - assert.deepStrictEqual(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('.') - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -describe('fn', () => { - it('basic', () => { - const input = '[tada abc]'; - const output = [ - FN('tada', { }, [ - TEXT('abc') - ]) - ]; - assert.deepStrictEqual(parse(input), output); - }); -}); - -it('composite', () => { - const input = -`before -
-Hello [tada everynyan! 🎉] - -I'm @ai, A bot of misskey! - -https://github.com/syuilo/ai -
-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') - ]; - assert.deepStrictEqual(parse(input), output); -}); - -describe('toString API', () => { - it('basic', () => { - const input = -`before -
-Hello [tada everynyan! 🎉] - -I'm @ai, A bot of misskey! - -https://github.com/syuilo/ai -
-after`; - assert.strictEqual(toString(parse(input)), input); - }); -}); - -describe('inspect API', () => { - it('replace text', () => { - const input = 'good morning [tada everynyan!]'; - const result = parse(input); - inspect(result, node => { - if (node.type == 'text') { - node.props.text = node.props.text.replace(/good morning/g, 'hello'); - } - }); - assert.strictEqual(toString(result), 'hello [tada everynyan!]'); - }); -}); - -describe('extract API', () => { - it('basic', () => { - const nodes = parse('@hoge @piyo @bebeyo'); - const expect = [ - MENTION('hoge', null, '@hoge'), - MENTION('piyo', null, '@piyo'), - MENTION('bebeyo', null, '@bebeyo') - ]; - assert.deepStrictEqual(extract(nodes, 'mention'), expect); - }); - - it('nested', () => { - const nodes = parse('abc:hoge:[tada 123 @hoge :foo:]:piyo:'); - const expect = [ - EMOJI_CODE('hoge'), - EMOJI_CODE('foo'), - EMOJI_CODE('piyo') - ]; - assert.deepStrictEqual(extract(nodes, 'emojiCode'), expect); - }); -}); diff --git a/test/node.ts b/test/node.ts index 7116e9a4..8f9918d5 100644 --- a/test/node.ts +++ b/test/node.ts @@ -1,26 +1,24 @@ import { - MfmBold, MfmCenter, MfmCodeBlock, MfmEmojiCode, MfmFn, MfmHashtag, MfmInline, - MfmInlineCode, MfmItalic, MfmLink, MfmMathBlock, MfmMathInline, MfmMention, - MfmNode, MfmQuote, MfmSearch, MfmSmall, MfmStrike, MfmText, MfmUnicodeEmoji, MfmUrl + MfmFn, MfmInline, MfmNode, NodeType } from '../built'; + +export const QUOTE = (children: MfmNode[]): NodeType<'quote'> => { return { type:'quote', children }; }; +export const SEARCH = (query: string, content: string): NodeType<'search'> => { return { type:'search', props: { query, content } }; }; +export const CODE_BLOCK = (code: string, lang: string | null): NodeType<'blockCode'> => { return { type:'blockCode', props: { code, lang } }; }; +export const MATH_BLOCK = (formula: string): NodeType<'mathBlock'> => { return { type:'mathBlock', props: { formula } }; }; +export const CENTER = (children: MfmInline[]): NodeType<'center'> => { return { type:'center', children }; }; -export const QUOTE = (children: MfmNode[]): MfmQuote => { return { type:'quote', children }; }; -export const SEARCH = (query: string, content: string): MfmSearch => { return { type:'search', props: { query, content } }; }; -export const CODE_BLOCK = (code: string, lang: string | null): MfmCodeBlock => { return { type:'blockCode', props: { code, lang } }; }; -export const MATH_BLOCK = (formula: string): MfmMathBlock => { return { type:'mathBlock', props: { formula } }; }; -export const CENTER = (children: MfmInline[]): MfmCenter => { return { type:'center', children }; }; - -export const BOLD = (children: MfmInline[]): MfmBold => { return { type:'bold', children }; }; -export const SMALL = (children: MfmInline[]): MfmSmall => { return { type:'small', children }; }; -export const ITALIC = (children: MfmInline[]): MfmItalic => { return { type:'italic', children }; }; -export const STRIKE = (children: MfmInline[]): MfmStrike => { return { type:'strike', children }; }; -export const INLINE_CODE = (code: string): MfmInlineCode => { return { type:'inlineCode', props: { code } }; }; -export const MATH_INLINE = (formula: string): MfmMathInline => { return { type:'mathInline', props: { formula } }; }; -export const MENTION = (username: string, host: string | null, acct: string): MfmMention => { return { type:'mention', props: { username, host, acct } }; }; -export const HASHTAG = (value: string): MfmHashtag => { return { type:'hashtag', props: { hashtag: value } }; }; -export const N_URL = (value: string): MfmUrl => { return { type:'url', props: { url: value } }; }; -export const LINK = (silent: boolean, url: string, children: MfmInline[]): MfmLink => { return { type:'link', props: { silent, url }, children }; }; -export const EMOJI_CODE = (name: string): MfmEmojiCode => { return { type:'emojiCode', props: { name: name } }; }; -export const FN = (name: string, args: MfmFn['props']['args'], children: MfmFn['children']): MfmFn => { return { type:'fn', props: { name, args }, children }; }; -export const UNI_EMOJI = (value: string): MfmUnicodeEmoji => { return { type:'unicodeEmoji', props: { emoji: value } }; }; -export const TEXT = (value: string): MfmText => { return { type:'text', props: { text: value } }; }; +export const BOLD = (children: MfmInline[]): NodeType<'bold'> => { return { type:'bold', children }; }; +export const SMALL = (children: MfmInline[]): NodeType<'small'> => { return { type:'small', children }; }; +export const ITALIC = (children: MfmInline[]): NodeType<'italic'> => { return { type:'italic', children }; }; +export const STRIKE = (children: MfmInline[]): NodeType<'strike'> => { return { type:'strike', children }; }; +export const INLINE_CODE = (code: string): NodeType<'inlineCode'> => { return { type:'inlineCode', props: { code } }; }; +export const MATH_INLINE = (formula: string): NodeType<'mathInline'> => { return { type:'mathInline', props: { formula } }; }; +export const MENTION = (username: string, host: string | null, acct: string): NodeType<'mention'> => { return { type:'mention', props: { username, host, acct } }; }; +export const HASHTAG = (value: string): NodeType<'hashtag'> => { return { type:'hashtag', props: { hashtag: value } }; }; +export const N_URL = (value: string): NodeType<'url'> => { return { type:'url', props: { url: value } }; }; +export const LINK = (silent: boolean, url: string, children: MfmInline[]): NodeType<'link'> => { return { type:'link', props: { silent, url }, children }; }; +export const EMOJI_CODE = (name: string): NodeType<'emojiCode'> => { return { type:'emojiCode', props: { name: name } }; }; +export const FN = (name: string, args: MfmFn['props']['args'], children: MfmFn['children']): NodeType<'fn'> => { return { type:'fn', props: { name, args }, children }; }; +export const UNI_EMOJI = (value: string): NodeType<'unicodeEmoji'> => { return { type:'unicodeEmoji', props: { emoji: value } }; }; +export const TEXT = (value: string): NodeType<'text'> => { return { type:'text', props: { text: value } }; }; diff --git a/test/parser.ts b/test/parser.ts new file mode 100644 index 00000000..45cf536c --- /dev/null +++ b/test/parser.ts @@ -0,0 +1,526 @@ +import assert from 'assert'; +import { parse } from '../built/index'; +import { createNode } from '../built/util'; +import { + TEXT, CENTER, FN, UNI_EMOJI, MENTION, EMOJI_CODE, HASHTAG, N_URL, BOLD, SMALL, ITALIC, STRIKE, QUOTE, MATH_BLOCK, SEARCH, CODE_BLOCK, LINK +} from './node'; + +describe('parser', () => { + describe('text', () => { + it('普通のテキストを入力すると1つのテキストノードが返される', () => { + const input = 'abc'; + const output = [TEXT('abc')]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('quote', () => { + it('1行の引用ブロックを使用できる', () => { + const input = '> abc'; + const output = [ + QUOTE([ + TEXT('abc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('複数行の引用ブロックを使用できる', () => { + const input = ` +> abc +> 123 +`; + const output = [ + QUOTE([ + TEXT('abc\n123') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('引用ブロックはブロックをネストできる', () => { + const input = ` +>
+> a +>
+`; + const output = [ + QUOTE([ + CENTER([ + TEXT('a') + ]) + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('引用ブロックはインライン構文を含んだブロックをネストできる', () => { + const input = ` +>
+> I'm @ai, An bot of misskey! +>
+`; + const output = [ + QUOTE([ + CENTER([ + TEXT('I\'m '), + MENTION('ai', null, '@ai'), + TEXT(', An bot of misskey!'), + ]) + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('search', () => { + describe('検索構文を使用できる', () => { + it('Search', () => { + const input = 'MFM 書き方 123 Search'; + const output = [ + createNode('search', { + query: 'MFM 書き方 123', + content: input + }) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('[Search]', () => { + const input = 'MFM 書き方 123 [Search]'; + const output = [ + createNode('search', { + query: 'MFM 書き方 123', + content: input + }) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('search', () => { + const input = 'MFM 書き方 123 search'; + const output = [ + createNode('search', { + query: 'MFM 書き方 123', + content: input + }) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('[search]', () => { + const input = 'MFM 書き方 123 [search]'; + const output = [ + createNode('search', { + query: 'MFM 書き方 123', + content: input + }) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('検索', () => { + const input = 'MFM 書き方 123 検索'; + const output = [ + createNode('search', { + query: 'MFM 書き方 123', + content: input + }) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('[検索]', () => { + const input = 'MFM 書き方 123 [検索]'; + const output = [ + createNode('search', { + query: 'MFM 書き方 123', + content: input + }) + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + it('ブロックの前後にあるテキストが正しく解釈される', () => { + const input = 'abc\nhoge piyo bebeyo 検索\n123'; + const output = [ + TEXT('abc'), + SEARCH('hoge piyo bebeyo', 'hoge piyo bebeyo 検索'), + TEXT('123') + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('code block', () => { + it('コードブロックを使用できる', () => { + const input = '```\nabc\n```'; + const output = [CODE_BLOCK('abc', null)]; + assert.deepStrictEqual(parse(input), output); + }); + it('コードブロックには複数行のコードを入力できる', () => { + const input = '```\na\nb\nc\n```'; + const output = [CODE_BLOCK('a\nb\nc', null)]; + assert.deepStrictEqual(parse(input), output); + }); + it('コードブロックは言語を指定できる', () => { + const input = '```js\nconst a = 1;\n```'; + const output = [CODE_BLOCK('const a = 1;', 'js')]; + assert.deepStrictEqual(parse(input), output); + }); + it('ブロックの前後にあるテキストが正しく解釈される', () => { + const input = 'abc\n```\nconst abc = 1;\n```\n123'; + const output = [ + TEXT('abc'), + CODE_BLOCK('const abc = 1;', null), + TEXT('123') + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('mathBlock', () => { + it('1行の数式ブロックを使用できる', () => { + const input = '\\[math1\\]'; + const output = [ + MATH_BLOCK('math1') + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('ブロックの前後にあるテキストが正しく解釈される', () => { + const input = 'abc\n\\[math1\\]\n123'; + const output = [ + TEXT('abc'), + MATH_BLOCK('math1'), + TEXT('123') + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('行末以外に閉じタグがある場合はマッチしない', () => { + const input = '\\[aaa\\]after'; + const output = [ + TEXT('\\[aaa\\]after') + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('行頭以外に開始タグがある場合はマッチしない', () => { + const input = 'before\\[aaa\\]'; + const output = [ + TEXT('before\\[aaa\\]') + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('center', () => { + it('single text', () => { + const input = '
abc
'; + const output = [ + CENTER([ + TEXT('abc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('multiple text', () => { + const input = 'before\n
\nabc\n123\n\npiyo\n
\nafter'; + const output = [ + TEXT('before'), + CENTER([ + TEXT('abc\n123\n\npiyo') + ]), + TEXT('after') + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('emoji code', () => { + it('basic', () => { + const input = ':abc:'; + const output = [EMOJI_CODE('abc')]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('unicode emoji', () => { + it('basic', () => { + const input = '今起きた😇'; + const output = [TEXT('今起きた'), UNI_EMOJI('😇')]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('big', () => { + it('basic', () => { + const input = '***abc***'; + const output = [ + FN('tada', { }, [ + TEXT('abc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('内容にはインライン構文を利用できる', () => { + const input = '***123**abc**123***'; + const output = [ + FN('tada', { }, [ + TEXT('123'), + BOLD([ + TEXT('abc') + ]), + TEXT('123') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('内容は改行できる', () => { + const input = '***123\n**abc**\n123***'; + const output = [ + FN('tada', { }, [ + TEXT('123\n'), + BOLD([ + TEXT('abc') + ]), + TEXT('\n123') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('bold', () => { + it('basic', () => { + const input = '**abc**'; + const output = [ + BOLD([ + TEXT('abc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('内容にはインライン構文を利用できる', () => { + const input = '**123~~abc~~123**'; + const output = [ + BOLD([ + TEXT('123'), + STRIKE([ + TEXT('abc') + ]), + TEXT('123') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('内容は改行できる', () => { + const input = '**123\n~~abc~~\n123**'; + const output = [ + BOLD([ + TEXT('123\n'), + STRIKE([ + TEXT('abc') + ]), + TEXT('\n123') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('small', () => { + it('basic', () => { + const input = 'abc'; + const output = [ + SMALL([ + TEXT('abc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('内容にはインライン構文を利用できる', () => { + const input = 'abc**123**abc'; + const output = [ + SMALL([ + TEXT('abc'), + BOLD([ + TEXT('123') + ]), + TEXT('abc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('内容は改行できる', () => { + const input = 'abc\n**123**\nabc'; + const output = [ + SMALL([ + TEXT('abc\n'), + BOLD([ + TEXT('123') + ]), + TEXT('\nabc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('italic 1', () => { + it('basic', () => { + const input = 'abc'; + const output = [ + ITALIC([ + TEXT('abc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('内容にはインライン構文を利用できる', () => { + const input = 'abc**123**abc'; + const output = [ + ITALIC([ + TEXT('abc'), + BOLD([ + TEXT('123') + ]), + TEXT('abc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + it('内容は改行できる', () => { + const input = 'abc\n**123**\nabc'; + const output = [ + ITALIC([ + TEXT('abc\n'), + BOLD([ + TEXT('123') + ]), + TEXT('\nabc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('italic 2', () => { + it('basic', () => { + const input = '*abc*'; + const output = [ + ITALIC([ + TEXT('abc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + // strike + + // inlineCode + + // mathInline + + // mention + + describe('hashtag', () => { + it('and unicode emoji', () => { + const input = '#️⃣abc123#abc'; + const output = [UNI_EMOJI('#️⃣'), TEXT('abc123'), HASHTAG('abc')]; + assert.deepStrictEqual(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('.') + ]; + assert.deepStrictEqual(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('.') + ]; + assert.deepStrictEqual(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('.') + ]; + assert.deepStrictEqual(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('.') + ]; + assert.deepStrictEqual(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('.') + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + describe('fn', () => { + it('basic', () => { + const input = '[tada abc]'; + const output = [ + FN('tada', { }, [ + TEXT('abc') + ]) + ]; + assert.deepStrictEqual(parse(input), output); + }); + }); + + it('composite', () => { + const input = +`before +
+Hello [tada everynyan! 🎉] + +I'm @ai, A bot of misskey! + +https://github.com/syuilo/ai +
+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') + ]; + assert.deepStrictEqual(parse(input), output); + }); +});