openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

mode_test.min.js (7084B)


      1 /**
      2  * Helper to test CodeMirror highlighting modes. It pretty prints output of the
      3  * highlighter and can check against expected styles.
      4  *
      5  * Mode tests are registered by calling test.mode(testName, mode,
      6  * tokens), where mode is a mode object as returned by
      7  * CodeMirror.getMode, and tokens is an array of lines that make up
      8  * the test.
      9  *
     10  * These lines are strings, in which styled stretches of code are
     11  * enclosed in brackets `[]`, and prefixed by their style. For
     12  * example, `[keyword if]`. Brackets in the code itself must be
     13  * duplicated to prevent them from being interpreted as token
     14  * boundaries. For example `a[[i]]` for `a[i]`. If a token has
     15  * multiple styles, the styles must be separated by ampersands, for
     16  * example `[tag&error </hmtl>]`.
     17  *
     18  * See the test.js files in the css, markdown, gfm, and stex mode
     19  * directories for examples.
     20  */
     21 (function() {
     22   function findSingle(str, pos, ch) {
     23     for (;;) {
     24       var found = str.indexOf(ch, pos);
     25       if (found == -1) return null;
     26       if (str.charAt(found + 1) != ch) return found;
     27       pos = found + 2;
     28     }
     29   }
     30 
     31   var styleName = /[\w&-_]+/g;
     32   function parseTokens(strs) {
     33     var tokens = [], plain = "";
     34     for (var i = 0; i < strs.length; ++i) {
     35       if (i) plain += "\n";
     36       var str = strs[i], pos = 0;
     37       while (pos < str.length) {
     38         var style = null, text;
     39         if (str.charAt(pos) == "[" && str.charAt(pos+1) != "[") {
     40           styleName.lastIndex = pos + 1;
     41           var m = styleName.exec(str);
     42           style = m[0].replace(/&/g, " ");
     43           var textStart = pos + style.length + 2;
     44           var end = findSingle(str, textStart, "]");
     45           if (end == null) throw new Error("Unterminated token at " + pos + " in '" + str + "'" + style);
     46           text = str.slice(textStart, end);
     47           pos = end + 1;
     48         } else {
     49           var end = findSingle(str, pos, "[");
     50           if (end == null) end = str.length;
     51           text = str.slice(pos, end);
     52           pos = end;
     53         }
     54         text = text.replace(/\[\[|\]\]/g, function(s) {return s.charAt(0);});
     55         tokens.push({style: style, text: text});
     56         plain += text;
     57       }
     58     }
     59     return {tokens: tokens, plain: plain};
     60   }
     61 
     62   test.mode = function(name, mode, tokens, modeName) {
     63     var data = parseTokens(tokens);
     64     return test((modeName || mode.name) + "_" + name, function() {
     65       return compare(data.plain, data.tokens, mode);
     66     });
     67   };
     68 
     69   function esc(str) {
     70     return str.replace('&', '&amp;').replace('<', '&lt;').replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
     71   }
     72 
     73   function compare(text, expected, mode) {
     74 
     75     var expectedOutput = [];
     76     for (var i = 0; i < expected.length; ++i) {
     77       var sty = expected[i].style;
     78       if (sty && sty.indexOf(" ")) sty = sty.split(' ').sort().join(' ');
     79       expectedOutput.push({style: sty, text: expected[i].text});
     80     }
     81 
     82     var observedOutput = highlight(text, mode);
     83 
     84     var s = "";
     85     var diff = highlightOutputsDifferent(expectedOutput, observedOutput);
     86     if (diff != null) {
     87       s += '<div class="mt-test mt-fail">';
     88       s +=   '<pre>' + esc(text) + '</pre>';
     89       s +=   '<div class="cm-s-default">';
     90       s += 'expected:';
     91       s +=   prettyPrintOutputTable(expectedOutput, diff);
     92       s += 'observed: [<a onclick="this.parentElement.className+=\' mt-state-unhide\'">display states</a>]';
     93       s +=   prettyPrintOutputTable(observedOutput, diff);
     94       s +=   '</div>';
     95       s += '</div>';
     96     }
     97     if (observedOutput.indentFailures) {
     98       for (var i = 0; i < observedOutput.indentFailures.length; i++)
     99         s += "<div class='mt-test mt-fail'>" + esc(observedOutput.indentFailures[i]) + "</div>";
    100     }
    101     if (s) throw new Failure(s);
    102   }
    103 
    104   function stringify(obj) {
    105     function replacer(key, obj) {
    106       if (typeof obj == "function") {
    107         var m = obj.toString().match(/function\s*[^\s(]*/);
    108         return m ? m[0] : "function";
    109       }
    110       return obj;
    111     }
    112     if (window.JSON && JSON.stringify)
    113       return JSON.stringify(obj, replacer, 2);
    114     return "[unsupported]";  // Fail safely if no native JSON.
    115   }
    116 
    117   function highlight(string, mode) {
    118     var state = mode.startState();
    119 
    120     var lines = string.replace(/\r\n/g,'\n').split('\n');
    121     var st = [], pos = 0;
    122     for (var i = 0; i < lines.length; ++i) {
    123       var line = lines[i], newLine = true;
    124       if (mode.indent) {
    125         var ws = line.match(/^\s*/)[0];
    126         var indent = mode.indent(state, line.slice(ws.length));
    127         if (indent != CodeMirror.Pass && indent != ws.length)
    128           (st.indentFailures || (st.indentFailures = [])).push(
    129             "Indentation of line " + (i + 1) + " is " + indent + " (expected " + ws.length + ")");
    130       }
    131       var stream = new CodeMirror.StringStream(line, 4, {
    132         lookAhead: function(n) { return lines[i + n] }
    133       });
    134       if (line == "" && mode.blankLine) mode.blankLine(state);
    135       /* Start copied code from CodeMirror.highlight */
    136       while (!stream.eol()) {
    137         for (var j = 0; j < 10 && stream.start >= stream.pos; j++)
    138           var compare = mode.token(stream, state);
    139         if (j == 10)
    140           throw new Failure("Failed to advance the stream." + stream.string + " " + stream.pos);
    141         var substr = stream.current();
    142         if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' ');
    143         stream.start = stream.pos;
    144         if (pos && st[pos-1].style == compare && !newLine) {
    145           st[pos-1].text += substr;
    146         } else if (substr) {
    147           st[pos++] = {style: compare, text: substr, state: stringify(state)};
    148         }
    149         // Give up when line is ridiculously long
    150         if (stream.pos > 5000) {
    151           st[pos++] = {style: null, text: this.text.slice(stream.pos)};
    152           break;
    153         }
    154         newLine = false;
    155       }
    156     }
    157 
    158     return st;
    159   }
    160 
    161   function highlightOutputsDifferent(o1, o2) {
    162     var minLen = Math.min(o1.length, o2.length);
    163     for (var i = 0; i < minLen; ++i)
    164       if (o1[i].style != o2[i].style || o1[i].text != o2[i].text) return i;
    165     if (o1.length > minLen || o2.length > minLen) return minLen;
    166   }
    167 
    168   function prettyPrintOutputTable(output, diffAt) {
    169     var s = '<table class="mt-output">';
    170     s += '<tr>';
    171     for (var i = 0; i < output.length; ++i) {
    172       var style = output[i].style, val = output[i].text;
    173       s +=
    174       '<td class="mt-token"' + (i == diffAt ? " style='background: pink'" : "") + '>' +
    175         '<span class="cm-' + esc(String(style)) + '">' +
    176         esc(val.replace(/ /g,'\xb7')) +  // ยท MIDDLE DOT
    177         '</span>' +
    178         '</td>';
    179     }
    180     s += '</tr><tr>';
    181     for (var i = 0; i < output.length; ++i) {
    182       s += '<td class="mt-style"><span>' + (output[i].style || null) + '</span></td>';
    183     }
    184     if(output[0].state) {
    185       s += '</tr><tr class="mt-state-row" title="State AFTER each token">';
    186       for (var i = 0; i < output.length; ++i) {
    187         s += '<td class="mt-state"><pre>' + esc(output[i].state) + '</pre></td>';
    188       }
    189     }
    190     s += '</tr></table>';
    191     return s;
    192   }
    193 })();