1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/sourceeditor/test/cm_mode_test.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,168 @@ 1.4 +/** 1.5 + * Helper to test CodeMirror highlighting modes. It pretty prints output of the 1.6 + * highlighter and can check against expected styles. 1.7 + * 1.8 + * Mode tests are registered by calling test.mode(testName, mode, 1.9 + * tokens), where mode is a mode object as returned by 1.10 + * CodeMirror.getMode, and tokens is an array of lines that make up 1.11 + * the test. 1.12 + * 1.13 + * These lines are strings, in which styled stretches of code are 1.14 + * enclosed in brackets `[]`, and prefixed by their style. For 1.15 + * example, `[keyword if]`. Brackets in the code itself must be 1.16 + * duplicated to prevent them from being interpreted as token 1.17 + * boundaries. For example `a[[i]]` for `a[i]`. If a token has 1.18 + * multiple styles, the styles must be separated by ampersands, for 1.19 + * example `[tag&error </hmtl>]`. 1.20 + * 1.21 + * See the test.js files in the css, markdown, gfm, and stex mode 1.22 + * directories for examples. 1.23 + */ 1.24 +(function() { 1.25 + function findSingle(str, pos, ch) { 1.26 + for (;;) { 1.27 + var found = str.indexOf(ch, pos); 1.28 + if (found == -1) return null; 1.29 + if (str.charAt(found + 1) != ch) return found; 1.30 + pos = found + 2; 1.31 + } 1.32 + } 1.33 + 1.34 + var styleName = /[\w&-_]+/g; 1.35 + function parseTokens(strs) { 1.36 + var tokens = [], plain = ""; 1.37 + for (var i = 0; i < strs.length; ++i) { 1.38 + if (i) plain += "\n"; 1.39 + var str = strs[i], pos = 0; 1.40 + while (pos < str.length) { 1.41 + var style = null, text; 1.42 + if (str.charAt(pos) == "[" && str.charAt(pos+1) != "[") { 1.43 + styleName.lastIndex = pos + 1; 1.44 + var m = styleName.exec(str); 1.45 + style = m[0].replace(/&/g, " "); 1.46 + var textStart = pos + style.length + 2; 1.47 + var end = findSingle(str, textStart, "]"); 1.48 + if (end == null) throw new Error("Unterminated token at " + pos + " in '" + str + "'" + style); 1.49 + text = str.slice(textStart, end); 1.50 + pos = end + 1; 1.51 + } else { 1.52 + var end = findSingle(str, pos, "["); 1.53 + if (end == null) end = str.length; 1.54 + text = str.slice(pos, end); 1.55 + pos = end; 1.56 + } 1.57 + text = text.replace(/\[\[|\]\]/g, function(s) {return s.charAt(0);}); 1.58 + tokens.push(style, text); 1.59 + plain += text; 1.60 + } 1.61 + } 1.62 + return {tokens: tokens, plain: plain}; 1.63 + } 1.64 + 1.65 + test.mode = function(name, mode, tokens, modeName) { 1.66 + var data = parseTokens(tokens); 1.67 + return test((modeName || mode.name) + "_" + name, function() { 1.68 + return compare(data.plain, data.tokens, mode); 1.69 + }); 1.70 + }; 1.71 + 1.72 + function esc(str) { 1.73 + return str.replace('&', '&').replace('<', '<'); 1.74 + } 1.75 + 1.76 + function compare(text, expected, mode) { 1.77 + 1.78 + var expectedOutput = []; 1.79 + for (var i = 0; i < expected.length; i += 2) { 1.80 + var sty = expected[i]; 1.81 + if (sty && sty.indexOf(" ")) sty = sty.split(' ').sort().join(' '); 1.82 + expectedOutput.push(sty, expected[i + 1]); 1.83 + } 1.84 + 1.85 + var observedOutput = highlight(text, mode); 1.86 + 1.87 + var s = ""; 1.88 + var diff = highlightOutputsDifferent(expectedOutput, observedOutput); 1.89 + if (diff != null) { 1.90 + s += '<div class="mt-test mt-fail">'; 1.91 + s += '<pre>' + esc(text) + '</pre>'; 1.92 + s += '<div class="cm-s-default">'; 1.93 + s += 'expected:'; 1.94 + s += prettyPrintOutputTable(expectedOutput, diff); 1.95 + s += 'observed:'; 1.96 + s += prettyPrintOutputTable(observedOutput, diff); 1.97 + s += '</div>'; 1.98 + s += '</div>'; 1.99 + } 1.100 + if (observedOutput.indentFailures) { 1.101 + for (var i = 0; i < observedOutput.indentFailures.length; i++) 1.102 + s += "<div class='mt-test mt-fail'>" + esc(observedOutput.indentFailures[i]) + "</div>"; 1.103 + } 1.104 + if (s) throw new Failure(s); 1.105 + } 1.106 + 1.107 + function highlight(string, mode) { 1.108 + var state = mode.startState() 1.109 + 1.110 + var lines = string.replace(/\r\n/g,'\n').split('\n'); 1.111 + var st = [], pos = 0; 1.112 + for (var i = 0; i < lines.length; ++i) { 1.113 + var line = lines[i], newLine = true; 1.114 + if (mode.indent) { 1.115 + var ws = line.match(/^\s*/)[0]; 1.116 + var indent = mode.indent(state, line.slice(ws.length)); 1.117 + if (indent != CodeMirror.Pass && indent != ws.length) 1.118 + (st.indentFailures || (st.indentFailures = [])).push( 1.119 + "Indentation of line " + (i + 1) + " is " + indent + " (expected " + ws.length + ")"); 1.120 + } 1.121 + var stream = new CodeMirror.StringStream(line); 1.122 + if (line == "" && mode.blankLine) mode.blankLine(state); 1.123 + /* Start copied code from CodeMirror.highlight */ 1.124 + while (!stream.eol()) { 1.125 + var compare = mode.token(stream, state), substr = stream.current(); 1.126 + if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' '); 1.127 + stream.start = stream.pos; 1.128 + if (pos && st[pos-2] == compare && !newLine) { 1.129 + st[pos-1] += substr; 1.130 + } else if (substr) { 1.131 + st[pos++] = compare; st[pos++] = substr; 1.132 + } 1.133 + // Give up when line is ridiculously long 1.134 + if (stream.pos > 5000) { 1.135 + st[pos++] = null; st[pos++] = this.text.slice(stream.pos); 1.136 + break; 1.137 + } 1.138 + newLine = false; 1.139 + } 1.140 + } 1.141 + 1.142 + return st; 1.143 + } 1.144 + 1.145 + function highlightOutputsDifferent(o1, o2) { 1.146 + var minLen = Math.min(o1.length, o2.length); 1.147 + for (var i = 0; i < minLen; ++i) 1.148 + if (o1[i] != o2[i]) return i >> 1; 1.149 + if (o1.length > minLen || o2.length > minLen) return minLen; 1.150 + } 1.151 + 1.152 + function prettyPrintOutputTable(output, diffAt) { 1.153 + var s = '<table class="mt-output">'; 1.154 + s += '<tr>'; 1.155 + for (var i = 0; i < output.length; i += 2) { 1.156 + var style = output[i], val = output[i+1]; 1.157 + s += 1.158 + '<td class="mt-token"' + (i == diffAt * 2 ? " style='background: pink'" : "") + '>' + 1.159 + '<span class="cm-' + esc(String(style)) + '">' + 1.160 + esc(val.replace(/ /g,'\xb7')) + 1.161 + '</span>' + 1.162 + '</td>'; 1.163 + } 1.164 + s += '</tr><tr>'; 1.165 + for (var i = 0; i < output.length; i += 2) { 1.166 + s += '<td class="mt-style"><span>' + (output[i] || null) + '</span></td>'; 1.167 + } 1.168 + s += '</table>'; 1.169 + return s; 1.170 + } 1.171 +})();