michael@0: /** michael@0: * Helper to test CodeMirror highlighting modes. It pretty prints output of the michael@0: * highlighter and can check against expected styles. michael@0: * michael@0: * Mode tests are registered by calling test.mode(testName, mode, michael@0: * tokens), where mode is a mode object as returned by michael@0: * CodeMirror.getMode, and tokens is an array of lines that make up michael@0: * the test. michael@0: * michael@0: * These lines are strings, in which styled stretches of code are michael@0: * enclosed in brackets `[]`, and prefixed by their style. For michael@0: * example, `[keyword if]`. Brackets in the code itself must be michael@0: * duplicated to prevent them from being interpreted as token michael@0: * boundaries. For example `a[[i]]` for `a[i]`. If a token has michael@0: * multiple styles, the styles must be separated by ampersands, for michael@0: * example `[tag&error ]`. michael@0: * michael@0: * See the test.js files in the css, markdown, gfm, and stex mode michael@0: * directories for examples. michael@0: */ michael@0: (function() { michael@0: function findSingle(str, pos, ch) { michael@0: for (;;) { michael@0: var found = str.indexOf(ch, pos); michael@0: if (found == -1) return null; michael@0: if (str.charAt(found + 1) != ch) return found; michael@0: pos = found + 2; michael@0: } michael@0: } michael@0: michael@0: var styleName = /[\w&-_]+/g; michael@0: function parseTokens(strs) { michael@0: var tokens = [], plain = ""; michael@0: for (var i = 0; i < strs.length; ++i) { michael@0: if (i) plain += "\n"; michael@0: var str = strs[i], pos = 0; michael@0: while (pos < str.length) { michael@0: var style = null, text; michael@0: if (str.charAt(pos) == "[" && str.charAt(pos+1) != "[") { michael@0: styleName.lastIndex = pos + 1; michael@0: var m = styleName.exec(str); michael@0: style = m[0].replace(/&/g, " "); michael@0: var textStart = pos + style.length + 2; michael@0: var end = findSingle(str, textStart, "]"); michael@0: if (end == null) throw new Error("Unterminated token at " + pos + " in '" + str + "'" + style); michael@0: text = str.slice(textStart, end); michael@0: pos = end + 1; michael@0: } else { michael@0: var end = findSingle(str, pos, "["); michael@0: if (end == null) end = str.length; michael@0: text = str.slice(pos, end); michael@0: pos = end; michael@0: } michael@0: text = text.replace(/\[\[|\]\]/g, function(s) {return s.charAt(0);}); michael@0: tokens.push(style, text); michael@0: plain += text; michael@0: } michael@0: } michael@0: return {tokens: tokens, plain: plain}; michael@0: } michael@0: michael@0: test.mode = function(name, mode, tokens, modeName) { michael@0: var data = parseTokens(tokens); michael@0: return test((modeName || mode.name) + "_" + name, function() { michael@0: return compare(data.plain, data.tokens, mode); michael@0: }); michael@0: }; michael@0: michael@0: function esc(str) { michael@0: return str.replace('&', '&').replace('<', '<'); michael@0: } michael@0: michael@0: function compare(text, expected, mode) { michael@0: michael@0: var expectedOutput = []; michael@0: for (var i = 0; i < expected.length; i += 2) { michael@0: var sty = expected[i]; michael@0: if (sty && sty.indexOf(" ")) sty = sty.split(' ').sort().join(' '); michael@0: expectedOutput.push(sty, expected[i + 1]); michael@0: } michael@0: michael@0: var observedOutput = highlight(text, mode); michael@0: michael@0: var s = ""; michael@0: var diff = highlightOutputsDifferent(expectedOutput, observedOutput); michael@0: if (diff != null) { michael@0: s += '
' + esc(text) + ''; michael@0: s += '
' + michael@0: '' + michael@0: esc(val.replace(/ /g,'\xb7')) + michael@0: '' + michael@0: ' | '; michael@0: } michael@0: s += '
' + (output[i] || null) + ' | '; michael@0: } michael@0: s += '