tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

mode_test.js (7092B)


      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(/&/g, '&amp;').replace(/</g, '&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), line);
    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 })();