feat(eventDispatcher): add an event dispatcher to converter

This commit is contained in:
Estevão Soares dos Santos 2015-08-03 03:47:49 +01:00
parent ea6031a25f
commit 2734326e19
23 changed files with 232 additions and 66 deletions

BIN
dist/showdown.js vendored

Binary file not shown.

BIN
dist/showdown.js.map vendored

Binary file not shown.

BIN
dist/showdown.min.js vendored

Binary file not shown.

Binary file not shown.

View File

@ -38,18 +38,8 @@ showdown.Converter = function (converterOptions) {
*/ */
outputModifiers = [], outputModifiers = [],
/** listeners = {
* The parser Order };
* @private
* @type {string[]}
*/
parserOrder = [
'githubCodeBlocks',
'hashHTMLBlocks',
'stripLinkDefinitions',
'blockGamut',
'unescapeSpecialChars'
];
_constructor(); _constructor();
@ -128,6 +118,7 @@ showdown.Converter = function (converterOptions) {
for (var i = 0; i < ext.length; ++i) { for (var i = 0; i < ext.length; ++i) {
switch (ext[i].type) { switch (ext[i].type) {
case 'lang': case 'lang':
langExtensions.push(ext[i]); langExtensions.push(ext[i]);
break; break;
@ -135,12 +126,16 @@ showdown.Converter = function (converterOptions) {
case 'output': case 'output':
outputModifiers.push(ext[i]); outputModifiers.push(ext[i]);
break; break;
}
if (ext[i].hasOwnProperty(listeners)) {
for (var ln in ext[i].listeners) {
if (ext[i].listeners.hasOwnProperty(ln)) {
listen(ln, ext[i].listeners[ln]);
}
}
}
}
default:
// should never reach here
throw Error('Extension loader error: Type unrecognized!!!');
}
}
} }
/** /**
@ -175,6 +170,57 @@ showdown.Converter = function (converterOptions) {
} }
} }
/**
* Listen to an event
* @param {string} name
* @param {function} callback
*/
function listen(name, callback) {
if (!showdown.helper.isString(name)) {
throw Error('Invalid argument in converter.listen() method: name must be a string, but ' + typeof name + ' given');
}
if (typeof callback !== 'function') {
throw Error('Invalid argument in converter.listen() method: callback must be a function, but ' + typeof callback + ' given');
}
if (!listeners.hasOwnProperty(name)) {
listeners[name] = [];
}
listeners[name].push(callback);
}
/**
* Dispatch an event
* @private
* @param {string} evtName Event name
* @param {string} text Text
* @param {{}} options Converter Options
* @returns {string}
*/
this._dispatch = function dispatch (evtName, text, options) {
if (listeners.hasOwnProperty(evtName)) {
for (var ei = 0; ei < listeners[evtName].length; ++ei) {
var nText = listeners[evtName][ei](evtName, text, this, options);
if (nText && typeof nText !== 'undefined') {
text = nText;
}
}
}
return text;
};
/**
* Listen to an event
* @param {string} name
* @param {function} callback
* @returns {showdown.Converter}
*/
this.listen = function (name, callback) {
listen(name, callback);
return this;
};
/** /**
* Converts a markdown string into HTML * Converts a markdown string into HTML
* @param {string} text * @param {string} text
@ -227,11 +273,12 @@ showdown.Converter = function (converterOptions) {
text = showdown.subParser('runExtension')(ext, text, options, globals); text = showdown.subParser('runExtension')(ext, text, options, globals);
}); });
// Run all registered parsers // run the sub parsers
for (var i = 0; i < parserOrder.length; ++i) { text = showdown.subParser('githubCodeBlocks')(text, options, globals);
var name = parserOrder[i]; text = showdown.subParser('hashHTMLBlocks')(text, options, globals);
text = parsers[name](text, options, globals); text = showdown.subParser('stripLinkDefinitions')(text, options, globals);
} text = showdown.subParser('blockGamut')(text, options, globals);
text = showdown.subParser('unescapeSpecialChars')(text, options, globals);
// attacklab: Restore dollar signs // attacklab: Restore dollar signs
text = text.replace(/~D/g, '$$'); text = text.replace(/~D/g, '$$');

View File

@ -245,27 +245,57 @@ function validate(extension, name) {
type = ext.type = 'output'; type = ext.type = 'output';
} }
if (type !== 'lang' && type !== 'output') { if (type !== 'lang' && type !== 'output' && type !== 'listener') {
ret.valid = false; ret.valid = false;
ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang" or "output"'; ret.error = baseMsg + 'type ' + type + ' is not recognized. Valid values: "lang/language", "output/html" or "listener"';
return ret; return ret;
} }
if (type === 'listener') {
if (showdown.helper.isUndefined(ext.listeners)) {
ret.valid = false;
ret.error = baseMsg + '. Extensions of type "listener" must have a property called "listeners"';
return ret;
}
} else {
if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
ret.valid = false;
ret.error = baseMsg + type + ' extensions must define either a "regex" property or a "filter" method';
return ret;
}
}
if (ext.listeners) {
if (typeof ext.listeners !== 'object') {
ret.valid = false;
ret.error = baseMsg + '"listeners" property must be an object but ' + typeof ext.listeners + ' given';
return ret;
}
for (var ln in ext.listeners) {
if (ext.listeners.hasOwnProperty(ln)) {
if (typeof ext.listeners[ln] !== 'function') {
ret.valid = false;
ret.error = baseMsg + '"listeners" property must be an hash of [event name]: [callback]. listeners.' + ln +
' must be a function but ' + typeof ext.listeners[ln] + ' given';
return ret;
}
}
}
}
if (ext.filter) { if (ext.filter) {
if (typeof ext.filter !== 'function') { if (typeof ext.filter !== 'function') {
ret.valid = false; ret.valid = false;
ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given'; ret.error = baseMsg + '"filter" must be a function, but ' + typeof ext.filter + ' given';
return ret; return ret;
} }
} else if (ext.regex) { } else if (ext.regex) {
if (showdown.helper.isString(ext.regex)) { if (showdown.helper.isString(ext.regex)) {
ext.regex = new RegExp(ext.regex, 'g'); ext.regex = new RegExp(ext.regex, 'g');
} }
if (!ext.regex instanceof RegExp) { if (!ext.regex instanceof RegExp) {
ret.valid = false; ret.valid = false;
ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + ret.error = baseMsg + '"regex" property must either be a string or a RegExp object, but ' + typeof ext.regex + ' given';
typeof ext.regex + ' given';
return ret; return ret;
} }
if (showdown.helper.isUndefined(ext.replace)) { if (showdown.helper.isUndefined(ext.replace)) {
@ -273,17 +303,6 @@ function validate(extension, name) {
ret.error = baseMsg + '"regex" extensions must implement a replace string or function'; ret.error = baseMsg + '"regex" extensions must implement a replace string or function';
return ret; return ret;
} }
} else {
ret.valid = false;
ret.error = baseMsg + 'extensions must define either a "regex" property or a "filter" method';
return ret;
}
if (showdown.helper.isUndefined(ext.filter) && showdown.helper.isUndefined(ext.regex)) {
ret.valid = false;
ret.error = baseMsg + 'output extensions must define a filter property';
return ret;
} }
} }
return ret; return ret;

View File

@ -1,9 +1,11 @@
/** /**
* Turn Markdown link shortcuts into XHTML <a> tags. * Turn Markdown link shortcuts into XHTML <a> tags.
*/ */
showdown.subParser('anchors', function (text, config, globals) { showdown.subParser('anchors', function (text, options, globals) {
'use strict'; 'use strict';
text = globals.converter._dispatch('anchors.before', text, options);
var writeAnchorTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) { var writeAnchorTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
if (showdown.helper.isUndefined(m7)) { if (showdown.helper.isUndefined(m7)) {
m7 = ''; m7 = '';
@ -73,7 +75,7 @@ showdown.subParser('anchors', function (text, config, globals) {
)()()()() // pad remaining backreferences )()()()() // pad remaining backreferences
/g,_DoAnchors_callback); /g,_DoAnchors_callback);
*/ */
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag); text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)][ ]?(?:\n[ ]*)?\[(.*?)])()()()()/g, writeAnchorTag);
// //
// Next, inline-style links: [link text](url "optional title") // Next, inline-style links: [link text](url "optional title")
@ -106,7 +108,7 @@ showdown.subParser('anchors', function (text, config, globals) {
) )
/g,writeAnchorTag); /g,writeAnchorTag);
*/ */
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, text = text.replace(/(\[((?:\[[^\]]*]|[^\[\]])*)]\([ \t]*()<?(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,
writeAnchorTag); writeAnchorTag);
// //
@ -124,8 +126,8 @@ showdown.subParser('anchors', function (text, config, globals) {
)()()()()() // pad rest of backreferences )()()()()() // pad rest of backreferences
/g, writeAnchorTag); /g, writeAnchorTag);
*/ */
text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); text = text.replace(/(\[([^\[\]]+)])()()()()()/g, writeAnchorTag);
text = globals.converter._dispatch('anchors.after', text, options);
return text; return text;
}); });

View File

@ -1,7 +1,7 @@
showdown.subParser('autoLinks', function (text, options) { showdown.subParser('autoLinks', function (text, options, globals) {
'use strict'; 'use strict';
//simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[-.+~:?#@!$&'()*,;=[\]\w]+)\b/gi, text = globals.converter._dispatch('autoLinks.before', text, options);
var simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)(?=\s|$)(?!["<>])/gi, var simpleURLRegex = /\b(((https?|ftp|dict):\/\/|www\.)[^'">\s]+\.[^'">\s]+)(?=\s|$)(?!["<>])/gi,
delimUrlRegex = /<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi, delimUrlRegex = /<(((https?|ftp|dict):\/\/|www\.)[^'">\s]+)>/gi,
@ -23,5 +23,7 @@ showdown.subParser('autoLinks', function (text, options) {
return showdown.subParser('encodeEmailAddress')(unescapedStr); return showdown.subParser('encodeEmailAddress')(unescapedStr);
} }
text = globals.converter._dispatch('autoLinks.after', text, options);
return text; return text;
}); });

View File

@ -5,6 +5,8 @@
showdown.subParser('blockGamut', function (text, options, globals) { showdown.subParser('blockGamut', function (text, options, globals) {
'use strict'; 'use strict';
text = globals.converter._dispatch('blockGamut.before', text, options);
text = showdown.subParser('headers')(text, options, globals); text = showdown.subParser('headers')(text, options, globals);
// Do Horizontal Rules: // Do Horizontal Rules:
@ -25,6 +27,7 @@ showdown.subParser('blockGamut', function (text, options, globals) {
text = showdown.subParser('hashHTMLBlocks')(text, options, globals); text = showdown.subParser('hashHTMLBlocks')(text, options, globals);
text = showdown.subParser('paragraphs')(text, options, globals); text = showdown.subParser('paragraphs')(text, options, globals);
return text; text = globals.converter._dispatch('blockGamut.after', text, options);
return text;
}); });

View File

@ -1,6 +1,7 @@
showdown.subParser('blockQuotes', function (text, options, globals) { showdown.subParser('blockQuotes', function (text, options, globals) {
'use strict'; 'use strict';
text = globals.converter._dispatch('blockQuotes.before', text, options);
/* /*
text = text.replace(/ text = text.replace(/
( // Wrap whole match in $1 ( // Wrap whole match in $1
@ -39,5 +40,7 @@ showdown.subParser('blockQuotes', function (text, options, globals) {
return showdown.subParser('hashBlock')('<blockquote>\n' + bq + '\n</blockquote>', options, globals); return showdown.subParser('hashBlock')('<blockquote>\n' + bq + '\n</blockquote>', options, globals);
}); });
text = globals.converter._dispatch('blockQuotes.after', text, options);
return text; return text;
}); });

View File

@ -4,6 +4,7 @@
showdown.subParser('codeBlocks', function (text, options, globals) { showdown.subParser('codeBlocks', function (text, options, globals) {
'use strict'; 'use strict';
text = globals.converter._dispatch('codeBlocks.before', text, options);
/* /*
text = text.replace(text, text = text.replace(text,
/(?:\n\n|^) /(?:\n\n|^)
@ -44,5 +45,6 @@ showdown.subParser('codeBlocks', function (text, options, globals) {
// attacklab: strip sentinel // attacklab: strip sentinel
text = text.replace(/~0/, ''); text = text.replace(/~0/, '');
text = globals.converter._dispatch('codeBlocks.after', text, options);
return text; return text;
}); });

View File

@ -23,9 +23,10 @@
* *
* ... type <code>`bar`</code> ... * ... type <code>`bar`</code> ...
*/ */
showdown.subParser('codeSpans', function (text) { showdown.subParser('codeSpans', function (text, options, globals) {
'use strict'; 'use strict';
text = globals.converter._dispatch('codeSpans.before', text, options);
//special case -> literal html code tag //special case -> literal html code tag
text = text.replace(/(<code[^><]*?>)([^]*?)<\/code>/g, function (wholeMatch, tag, c) { text = text.replace(/(<code[^><]*?>)([^]*?)<\/code>/g, function (wholeMatch, tag, c) {
c = c.replace(/^([ \t]*)/g, ''); // leading whitespace c = c.replace(/^([ \t]*)/g, ''); // leading whitespace
@ -56,5 +57,6 @@ showdown.subParser('codeSpans', function (text) {
} }
); );
text = globals.converter._dispatch('codeSpans.after', text, options);
return text; return text;
}); });

View File

@ -16,6 +16,8 @@ showdown.subParser('githubCodeBlocks', function (text, options, globals) {
return text; return text;
} }
text = globals.converter._dispatch('githubCodeBlocks.before', text, options);
text += '~0'; text += '~0';
text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function (wholeMatch, language, codeblock) { text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g, function (wholeMatch, language, codeblock) {
@ -34,6 +36,7 @@ showdown.subParser('githubCodeBlocks', function (text, options, globals) {
// attacklab: strip sentinel // attacklab: strip sentinel
text = text.replace(/~0/, ''); text = text.replace(/~0/, '');
return text; text = globals.converter._dispatch('githubCodeBlocks.after', text, options);
return text;
}); });

View File

@ -1,6 +1,8 @@
showdown.subParser('headers', function (text, options, globals) { showdown.subParser('headers', function (text, options, globals) {
'use strict'; 'use strict';
text = globals.converter._dispatch('headers.before', text, options);
var prefixHeader = options.prefixHeaderId, var prefixHeader = options.prefixHeaderId,
headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart), headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart),
@ -68,5 +70,6 @@ showdown.subParser('headers', function (text, options, globals) {
return title; return title;
} }
text = globals.converter._dispatch('headers.after', text, options);
return text; return text;
}); });

View File

@ -4,6 +4,8 @@
showdown.subParser('images', function (text, options, globals) { showdown.subParser('images', function (text, options, globals) {
'use strict'; 'use strict';
text = globals.converter._dispatch('images.before', text, options);
var inlineRegExp = /!\[(.*?)]\s?\([ \t]*()<?(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g, var inlineRegExp = /!\[(.*?)]\s?\([ \t]*()<?(\S+?)>?(?: =([*\d]+[A-Za-z%]{0,4})x([*\d]+[A-Za-z%]{0,4}))?[ \t]*(?:(['"])(.*?)\6[ \t]*)?\)/g,
referenceRegExp = /!\[(.*?)][ ]?(?:\n[ ]*)?\[(.*?)]()()()()()/g; referenceRegExp = /!\[(.*?)][ ]?(?:\n[ ]*)?\[(.*?)]()()()()()/g;
@ -70,5 +72,6 @@ showdown.subParser('images', function (text, options, globals) {
// Next, handle inline images: ![alt text](url =<width>x<height> "optional title") // Next, handle inline images: ![alt text](url =<width>x<height> "optional title")
text = text.replace(inlineRegExp, writeImageTag); text = text.replace(inlineRegExp, writeImageTag);
text = globals.converter._dispatch('images.after', text, options);
return text; return text;
}); });

View File

@ -1,6 +1,8 @@
showdown.subParser('italicsAndBold', function (text, options) { showdown.subParser('italicsAndBold', function (text, options, globals) {
'use strict'; 'use strict';
text = globals.converter._dispatch('italicsAndBold.before', text, options);
if (options.literalMidWordUnderscores) { if (options.literalMidWordUnderscores) {
//underscores //underscores
// Since we are consuming a \s character, we need to add it // Since we are consuming a \s character, we need to add it
@ -15,5 +17,7 @@ showdown.subParser('italicsAndBold', function (text, options) {
text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, '<strong>$2</strong>'); text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, '<strong>$2</strong>');
text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, '<em>$2</em>'); text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, '<em>$2</em>');
} }
text = globals.converter._dispatch('italicsAndBold.after', text, options);
return text; return text;
}); });

View File

@ -4,6 +4,7 @@
showdown.subParser('lists', function (text, options, globals) { showdown.subParser('lists', function (text, options, globals) {
'use strict'; 'use strict';
text = globals.converter._dispatch('lists.before', text, options);
/** /**
* Process the contents of a single ordered or unordered list, splitting it * Process the contents of a single ordered or unordered list, splitting it
* into individual list items. * into individual list items.
@ -156,5 +157,6 @@ showdown.subParser('lists', function (text, options, globals) {
// attacklab: strip sentinel // attacklab: strip sentinel
text = text.replace(/~0/, ''); text = text.replace(/~0/, '');
text = globals.converter._dispatch('lists.after', text, options);
return text; return text;
}); });

View File

@ -4,6 +4,7 @@
showdown.subParser('paragraphs', function (text, options, globals) { showdown.subParser('paragraphs', function (text, options, globals) {
'use strict'; 'use strict';
text = globals.converter._dispatch('paragraphs.before', text, options);
// Strip leading and trailing lines: // Strip leading and trailing lines:
text = text.replace(/^\n+/g, ''); text = text.replace(/^\n+/g, '');
text = text.replace(/\n+$/g, ''); text = text.replace(/\n+$/g, '');
@ -37,5 +38,6 @@ showdown.subParser('paragraphs', function (text, options, globals) {
} }
} }
text = globals.converter._dispatch('paragraphs.after', text, options);
return grafsOut.join('\n\n'); return grafsOut.join('\n\n');
}); });

View File

@ -5,6 +5,7 @@
showdown.subParser('spanGamut', function (text, options, globals) { showdown.subParser('spanGamut', function (text, options, globals) {
'use strict'; 'use strict';
text = globals.converter._dispatch('spanGamut.before', text, options);
text = showdown.subParser('codeSpans')(text, options, globals); text = showdown.subParser('codeSpans')(text, options, globals);
text = showdown.subParser('escapeSpecialCharsWithinTagAttributes')(text, options, globals); text = showdown.subParser('escapeSpecialCharsWithinTagAttributes')(text, options, globals);
text = showdown.subParser('encodeBackslashEscapes')(text, options, globals); text = showdown.subParser('encodeBackslashEscapes')(text, options, globals);
@ -25,6 +26,6 @@ showdown.subParser('spanGamut', function (text, options, globals) {
// Do hard breaks: // Do hard breaks:
text = text.replace(/ +\n/g, ' <br />\n'); text = text.replace(/ +\n/g, ' <br />\n');
text = globals.converter._dispatch('spanGamut.after', text, options);
return text; return text;
}); });

View File

@ -1,8 +1,10 @@
showdown.subParser('strikethrough', function (text, options) { showdown.subParser('strikethrough', function (text, options, globals) {
'use strict'; 'use strict';
if (options.strikethrough) { if (options.strikethrough) {
text = globals.converter._dispatch('strikethrough.before', text, options);
text = text.replace(/(?:~T){2}([^~]+)(?:~T){2}/g, '<del>$1</del>'); text = text.replace(/(?:~T){2}([^~]+)(?:~T){2}/g, '<del>$1</del>');
text = globals.converter._dispatch('strikethrough.after', text, options);
} }
return text; return text;

View File

@ -154,9 +154,11 @@ showdown.subParser('tables', function (text, options, globals) {
}; };
if (options.tables) { if (options.tables) {
text = globals.converter._dispatch('tables.before', text, options);
var tableParser = table(); var tableParser = table();
return tableParser.parse(text); text = tableParser.parse(text);
} else { text = globals.converter._dispatch('tables.after', text, options);
return text;
} }
return text;
}); });

View File

@ -27,7 +27,12 @@ describe('showdown.Converter', function () {
}); });
}); });
describe('setFlavor() github', function () { describe('setFlavor method', function () {
/**
* Test setFlavor('github')
*/
describe('github', function () {
var converter = new showdown.Converter(), var converter = new showdown.Converter(),
ghOpts = { ghOpts = {
omitExtraWLInCodeBlocks: true, omitExtraWLInCodeBlocks: true,
@ -54,6 +59,7 @@ describe('showdown.Converter', function () {
}); });
} }
}); });
});
describe('extension methods', function () { describe('extension methods', function () {
var extObjMock = { var extObjMock = {
@ -86,4 +92,41 @@ describe('showdown.Converter', function () {
showdown.resetExtensions(); showdown.resetExtensions();
}); });
}); });
describe('events', function () {
var events = [
'anchors',
'autoLinks',
'blockGamut',
'blockQuotes',
'codeBlocks',
'codeSpans',
'githubCodeBlocks',
'headers',
'images',
'italicsAndBold',
'lists',
'paragraph',
'spanGamut'
//'strikeThrough',
//'tables'
];
for (var i = 0; i < events.length; ++i) {
runListener(events[i] + '.before');
runListener(events[i] + '.after');
}
function runListener (name) {
it('should listen to ' + name, function () {
var converter = new showdown.Converter();
converter.listen(name, function (evtName, text, options) {
evtName.should.equal(name);
text.should.match(/^[\s\S]*foo[\s\S]*$/);
return text;
})
.makeHtml('foo');
});
}
});
}); });

View File

@ -46,6 +46,18 @@ describe('showdown.extension()', function () {
showdown.extension('foo').should.eql([extObjMock]); showdown.extension('foo').should.eql([extObjMock]);
showdown.resetExtensions(); showdown.resetExtensions();
}); });
it('a listener extension', function () {
showdown.extension('foo', {
type: 'listener',
listeners: {
foo: function (name, txt) {
return txt;
}
}
});
showdown.resetExtensions();
});
}); });
describe('should refuse to register', function () { describe('should refuse to register', function () {
@ -62,7 +74,7 @@ describe('showdown.extension()', function () {
type: 'foo' type: 'foo'
}); });
}; };
expect(fn).to.throw(/type .+? is not recognized\. Valid values: "lang" or "output"/); expect(fn).to.throw(/type .+? is not recognized\. Valid values: "lang\/language", "output\/html" or "listener"/);
}); });
it('an extension without regex or filter', function () { it('an extension without regex or filter', function () {
@ -73,6 +85,15 @@ describe('showdown.extension()', function () {
}; };
expect(fn).to.throw(/extensions must define either a "regex" property or a "filter" method/); expect(fn).to.throw(/extensions must define either a "regex" property or a "filter" method/);
}); });
it('a listener extension without a listeners property', function () {
var fn = function () {
showdown.extension('foo', {
type: 'listener'
});
};
expect(fn).to.throw(/Extensions of type "listener" must have a property called "listeners"/);
});
}); });
}); });