browser/devtools/sourceeditor/test/cm_mode_test.js

changeset 0
6474c204b198
     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('&', '&amp;').replace('<', '&lt;');
    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 +})();

mercurial