101 lines
4.8 KiB
TypeScript
101 lines
4.8 KiB
TypeScript
import { ribbit, resetDOM } from './setup';
|
|
|
|
const lib = ribbit();
|
|
|
|
const macros = [
|
|
{
|
|
name: 'user',
|
|
toHTML: () => '<a href="/user">TestUser</a>',
|
|
},
|
|
{
|
|
name: 'npc',
|
|
toHTML: ({ keywords }: any) => {
|
|
const name = keywords.join(' ');
|
|
return '<a href="/NPC/' + name.replace(/ /g, '') + '">' + name + '</a>';
|
|
},
|
|
},
|
|
{
|
|
name: 'style',
|
|
toHTML: ({ keywords, content }: any) => '<div class="' + keywords.join(' ') + '">' + (content || '') + '</div>',
|
|
},
|
|
{
|
|
name: 'toc',
|
|
toHTML: ({ params }: any) => '<aside class="toc" data-depth="' + (params.depth || '3') + '"></aside>',
|
|
},
|
|
];
|
|
|
|
const editor = new lib.Editor({macros: macros});
|
|
const converter = editor.converter;
|
|
|
|
const H = (md: string) => converter.toHTML(md);
|
|
const M = (html: string) => converter.toMarkdown(html);
|
|
|
|
describe('Macros', () => {
|
|
describe('self-closing', () => {
|
|
it('bare name renders', () => expect(H('hello @user world')).toContain('<a href="/user">TestUser</a>'));
|
|
it('bare name wrapped', () => expect(H('hello @user world')).toContain('data-macro="user"'));
|
|
it('empty parens', () => expect(H('hello @user() world')).toContain('data-macro="user"'));
|
|
it('keywords', () => expect(H('@npc(Goblin King)')).toContain('Goblin King'));
|
|
it('keywords in data attr', () => expect(H('@npc(Goblin King)')).toContain('data-keywords="Goblin King"'));
|
|
it('params', () => expect(H('@toc(depth="2")')).toContain('data-param-depth="2"'));
|
|
});
|
|
|
|
describe('unknown macros', () => {
|
|
it('renders error', () => expect(H('@bogus')).toContain('ribbit-error'));
|
|
it('shows name', () => expect(H('@bogus')).toContain('@bogus'));
|
|
it('block error', () => expect(H('@bogus(args\ncontent\n)')).toContain('ribbit-error'));
|
|
});
|
|
|
|
it('email not matched', () => expect(H('user@example.com')).toBe('<p>user@example.com</p>'));
|
|
|
|
describe('block macros', () => {
|
|
it('content processed', () => expect(H('@style(box\n**bold**\n)')).toContain('<strong>bold</strong>'));
|
|
it('wrapped with data-macro', () => expect(H('@style(box\ncontent\n)')).toContain('data-macro="style"'));
|
|
it('keywords in data attr', () => expect(H('@style(box center\ncontent\n)')).toContain('data-keywords="box center"'));
|
|
});
|
|
|
|
describe('verbatim', () => {
|
|
it('skips markdown', () => expect(H('@style(box verbatim\n**bold**\n)')).toContain('**bold**'));
|
|
it('no strong tag', () => expect(H('@style(box verbatim\n**bold**\n)')).not.toContain('<strong>'));
|
|
it('escapes html', () => expect(H('@style(box verbatim\n<b>tag</b>\n)')).toContain('<b>'));
|
|
it('preserves newlines', () => expect(H('@style(box verbatim\nline1\nline2\n)')).toContain('line1<br>'));
|
|
it('data-verbatim set', () => expect(H('@style(box verbatim\ncontent\n)')).toContain('data-verbatim="true"'));
|
|
it('keyword stripped from data-keywords', () => {
|
|
const html = H('@style(box verbatim\ncontent\n)');
|
|
expect(html).toContain('data-keywords="box"');
|
|
const verbatimKeywordPattern = /data-keywords="[^"]*verbatim/;
|
|
expect(html).not.toMatch(verbatimKeywordPattern);
|
|
});
|
|
});
|
|
|
|
describe('nesting', () => {
|
|
it('inline inside bold', () => expect(H('**@npc(Goblin King)**')).toContain('<strong>'));
|
|
it('block contains list', () => expect(H('@style(box\n- item 1\n- item 2\n)')).toContain('<ul>'));
|
|
it('inline inside block', () => expect(H('@style(box\nhello @user world\n)')).toContain('data-macro="user"'));
|
|
});
|
|
|
|
describe('fenced code protection', () => {
|
|
it('not in code block', () => expect(H('```\n@user\n```')).not.toContain('data-macro'));
|
|
it('literal in code block', () => expect(H('```\n@user\n```')).toContain('@user'));
|
|
it('not in inline code', () => expect(H('`@user`')).not.toContain('data-macro'));
|
|
});
|
|
|
|
describe('generic round-trip via data- attributes', () => {
|
|
it('inline macro', () => expect(M(H('hello @user world'))).toBe('hello @user world'));
|
|
it('inline with keywords', () => expect(M(H('@npc(Goblin King)'))).toBe('@npc(Goblin King)'));
|
|
it('inline with params', () => expect(M(H('@toc(depth="2")'))).toBe('@toc(depth="2")'));
|
|
it('block macro', () => {
|
|
const md = '@style(box\n**bold** content\n)';
|
|
const result = M(H(md)).trim();
|
|
expect(result).toContain('@style(box');
|
|
expect(result).toContain('**bold** content');
|
|
expect(result).toContain(')');
|
|
});
|
|
it('verbatim round-trip preserves keyword', () => {
|
|
const md = '@style(box verbatim\n<b>literal</b>\n)';
|
|
const result = M(H(md)).trim();
|
|
expect(result).toContain('@style(box verbatim');
|
|
});
|
|
});
|
|
});
|