/** * Helper to test CodeMirror highlighting modes. It pretty prints output of the * highlighter and can check against expected styles. * * See test.html in the stex mode for examples. */ ModeTest = {}; ModeTest.modeOptions = {}; ModeTest.modeName = CodeMirror.defaults.mode; /* keep track of results for printSummary */ ModeTest.testCount = 0; ModeTest.passes = 0; /** * Run a test; prettyprints the results using document.write(). * * @param name Name of test * @param text String to highlight. * @param expected Expected styles and tokens: Array(style, token, [style, token,...]) * @param modeName * @param modeOptions * @param expectedFail */ ModeTest.testMode = function(name, text, expected, modeName, modeOptions, expectedFail) { ModeTest.testCount += 1; if (!modeName) modeName = ModeTest.modeName; if (!modeOptions) modeOptions = ModeTest.modeOptions; var mode = CodeMirror.getMode(modeOptions, modeName); if (expected.length < 0) { throw "must have text for test (" + name + ")"; } if (expected.length % 2 != 0) { throw "must have text for test (" + name + ") plus expected (style, token) pairs"; } return test( modeName + "_" + name, function(){ return ModeTest.compare(text, expected, mode); }, expectedFail ); } ModeTest.compare = function (text, expected, mode) { var expectedOutput = []; for (var i = 0; i < expected.length; i += 2) { var sty = expected[i]; if (sty && sty.indexOf(" ")) sty = sty.split(' ').sort().join(' '); expectedOutput.push(sty, expected[i + 1]); } var observedOutput = ModeTest.highlight(text, mode); var pass, passStyle = ""; pass = ModeTest.highlightOutputsEqual(expectedOutput, observedOutput); passStyle = pass ? 'mt-pass' : 'mt-fail'; ModeTest.passes += pass ? 1 : 0; var s = ''; if (pass) { s += '
'; s += '
' + ModeTest.htmlEscape(text) + '
'; s += '
'; s += ModeTest.prettyPrintOutputTable(observedOutput); s += '
'; s += '
'; return s; } else { s += '
'; s += '
' + ModeTest.htmlEscape(text) + '
'; s += '
'; s += 'expected:'; s += ModeTest.prettyPrintOutputTable(expectedOutput); s += 'observed:'; s += ModeTest.prettyPrintOutputTable(observedOutput); s += '
'; s += '
'; throw s; } } /** * Emulation of CodeMirror's internal highlight routine for testing. Multi-line * input is supported. * * @param string to highlight * * @param mode the mode that will do the actual highlighting * * @return array of [style, token] pairs */ ModeTest.highlight = function(string, mode) { var state = mode.startState() var lines = string.replace(/\r\n/g,'\n').split('\n'); var st = [], pos = 0; for (var i = 0; i < lines.length; ++i) { var line = lines[i], newLine = true; var stream = new CodeMirror.StringStream(line); if (line == "" && mode.blankLine) mode.blankLine(state); /* Start copied code from CodeMirror.highlight */ while (!stream.eol()) { var style = mode.token(stream, state), substr = stream.current(); if (style && style.indexOf(" ") > -1) style = style.split(' ').sort().join(' '); stream.start = stream.pos; if (pos && st[pos-2] == style && !newLine) { st[pos-1] += substr; } else if (substr) { st[pos++] = style; st[pos++] = substr; } // Give up when line is ridiculously long if (stream.pos > 5000) { st[pos++] = null; st[pos++] = this.text.slice(stream.pos); break; } newLine = false; } } return st; } /** * Compare two arrays of output from ModeTest.highlight. * * @param o1 array of [style, token] pairs * * @param o2 array of [style, token] pairs * * @return boolean; true iff outputs equal */ ModeTest.highlightOutputsEqual = function(o1, o2) { if (o1.length != o2.length) return false; for (var i = 0; i < o1.length; ++i) if (o1[i] != o2[i]) return false; return true; } /** * Print tokens and corresponding styles in a table. Spaces in the token are * replaced with 'interpunct' dots (·). * * @param output array of [style, token] pairs * * @return html string */ ModeTest.prettyPrintOutputTable = function(output) { var s = ''; s += ''; for (var i = 0; i < output.length; i += 2) { var style = output[i], val = output[i+1]; s += ''; } s += ''; for (var i = 0; i < output.length; i += 2) { s += ''; } s += '
' + '' + ModeTest.htmlEscape(val).replace(/ /g,'·') + '' + '
' + output[i] + '
'; return s; } /** * Print how many tests have run so far and how many of those passed. */ ModeTest.printSummary = function() { ModeTest.runTests(ModeTest.displayTest); document.write(ModeTest.passes + ' passes for ' + ModeTest.testCount + ' tests'); } /** * Basic HTML escaping. */ ModeTest.htmlEscape = function(str) { str = str.toString(); return str.replace(/[<&]/g, function(str) {return str == "&" ? "&" : "<";}); }