Merge branch 'matrix'
diff --git a/src/commands/math/LatexCommandInput.js b/src/commands/math/LatexCommandInput.js
index 19dfc82..bae3718 100644
--- a/src/commands/math/LatexCommandInput.js
+++ b/src/commands/math/LatexCommandInput.js
@@ -74,7 +74,7 @@
 
     var latex = this.ends[L].latex();
     if (!latex) latex = ' ';
-    var cmd = LatexCmds[latex];
+    var cmd = LatexCmds[latex] || Environments[latex];
     if (cmd) {
       cmd = cmd(latex);
       if (this._replacedFragment) cmd.replaces(this._replacedFragment);
diff --git a/src/commands/math/commands.js b/src/commands/math/commands.js
index 68838bb..3e7eb7a 100644
--- a/src/commands/math/commands.js
+++ b/src/commands/math/commands.js
@@ -862,3 +862,497 @@
     ;
   };
 });
+
+// LaTeX environments
+// Environments are delimited by an opening \begin{} and a closing
+// \end{}. Everything inside those tags will be formatted in a
+// special manner depending on the environment type.
+var Environments = {};
+
+LatexCmds.begin = P(MathCommand, function(_, super_) {
+  _.parser = function() {
+    var string = Parser.string;
+    var regex = Parser.regex;
+    return string('{')
+      .then(regex(/^[a-z]+/i))
+      .skip(string('}'))
+      .then(function (env) {
+          return (Environments[env] ?
+            Environments[env]().parser() :
+            Parser.fail('unknown environment type: '+env)
+          ).skip(string('\\end{'+env+'}'));
+      })
+    ;
+  };
+});
+
+var Environment = P(MathCommand, function(_, super_) {
+  _.template = [['\\begin{', '}'], ['\\end{', '}']];
+  _.wrappers = function () {
+    return [
+      _.template[0].join(this.environment),
+      _.template[1].join(this.environment)
+    ];
+  };
+});
+
+var Matrix =
+Environments.matrix = P(Environment, function(_, super_) {
+
+  var delimiters = {
+    column: '&',
+    row: '\\\\'
+  };
+  _.parentheses = {
+    left: null,
+    right: null
+  };
+  _.environment = 'matrix';
+
+  _.reflow = function() {
+    var blockjQ = this.jQ.children('table');
+
+    var height = blockjQ.outerHeight()/+blockjQ.css('fontSize').slice(0,-2);
+
+    var parens = this.jQ.children('.mq-paren');
+    if (parens.length) {
+      scale(parens, min(1 + .2*(height - 1), 1.2), 1.05*height);
+    }
+  };
+  _.latex = function() {
+    var latex = '';
+    var row;
+
+    this.eachChild(function (cell) {
+      if (typeof row !== 'undefined') {
+        latex += (row !== cell.row) ?
+          delimiters.row :
+          delimiters.column;
+      }
+      row = cell.row;
+      latex += cell.latex();
+    });
+
+    return this.wrappers().join(latex);
+  };
+  _.html = function() {
+    var cells = [], trs = '', i=0, row;
+
+    function parenHtml(paren) {
+      return (paren) ?
+          '<span class="mq-scaled mq-paren">'
+        +   paren
+        + '</span>' : '';
+    }
+
+    // Build <tr><td>.. structure from cells
+    this.eachChild(function (cell) {
+      if (row !== cell.row) {
+        row = cell.row;
+        trs += '<tr>$tds</tr>';
+        cells[row] = [];
+      }
+      cells[row].push('<td>&'+(i++)+'</td>');
+    });
+
+    this.htmlTemplate =
+        '<span class="mq-matrix mq-non-leaf">'
+      +   parenHtml(this.parentheses.left)
+      +   '<table class="mq-non-leaf">'
+      +     trs.replace(/\$tds/g, function () {
+              return cells.shift().join('');
+            })
+      +   '</table>'
+      +   parenHtml(this.parentheses.right)
+      + '</span>'
+    ;
+
+    return super_.html.call(this);
+  };
+  // Create default 4-cell matrix
+  _.createBlocks = function() {
+    this.blocks = [
+      MatrixCell(0, this),
+      MatrixCell(0, this),
+      MatrixCell(1, this),
+      MatrixCell(1, this)
+    ];
+  };
+  _.parser = function() {
+    var self = this;
+    var optWhitespace = Parser.optWhitespace;
+    var string = Parser.string;
+
+    return optWhitespace
+    .then(string(delimiters.column)
+      .or(string(delimiters.row))
+      .or(latexMathParser.block))
+    .many()
+    .skip(optWhitespace)
+    .then(function(items) {
+      var blocks = [];
+      var row = 0;
+      self.blocks = [];
+
+      function addCell() {
+        self.blocks.push(MatrixCell(row, self, blocks));
+        blocks = [];
+      }
+
+      for (var i=0; i<items.length; i+=1) {
+        if (items[i] instanceof MathBlock) {
+          blocks.push(items[i]);
+        } else {
+          addCell();
+          if (items[i] === delimiters.row) row+=1;
+        }
+      }
+      addCell();
+      self.autocorrect();
+      return Parser.succeed(self);
+    });
+  };
+  // Relink all the cells after parsing
+  _.finalizeTree = function() {
+    var table = this.jQ.find('table');
+    table.toggleClass('mq-rows-1', table.find('tr').length === 1);
+    this.relink();
+  };
+  // Enter the matrix at the top or bottom row if updown is configured.
+  _.getEntryPoint = function(dir, cursor, updown) {
+    if (updown === 'up') {
+      if (dir === L) {
+        return this.blocks[this.rowSize - 1];
+      } else {
+        return this.blocks[0];
+      }
+    } else { // updown === 'down'
+      if (dir === L) {
+        return this.blocks[this.blocks.length - 1];
+      } else {
+        return this.blocks[this.blocks.length - this.rowSize];
+      }
+    }
+  };
+  // Exit the matrix at the first and last columns if updown is configured.
+  _.atExitPoint = function(dir, cursor) {
+      // Which block are we in?
+      var i = this.blocks.indexOf(cursor.parent);
+      if (dir === L) {
+        // If we're on the left edge and moving left, we should exit.
+        return i % this.rowSize === 0;
+      } else {
+        // If we're on the right edge and moving right, we should exit.
+        return (i + 1) % this.rowSize === 0;
+      }
+  };
+  _.moveTowards = function(dir, cursor, updown) {
+    var entryPoint = updown && this.getEntryPoint(dir, cursor, updown);
+    cursor.insAtDirEnd(-dir, entryPoint || this.ends[-dir]);
+  };
+
+  // Set up directional pointers between cells
+  _.relink = function() {
+    var blocks = this.blocks;
+    var rows = [];
+    var row, column, cell;
+
+    // The row size will be used by other functions down the track.
+    // Begin by assuming we're a one-row matrix, and we'll overwrite this if we find another row.
+    this.rowSize = blocks.length;
+
+    // Use a for loop rather than eachChild
+    // as we're still making sure children()
+    // is set up properly
+    for (var i=0; i<blocks.length; i+=1) {
+      cell = blocks[i];
+      if (row !== cell.row) {
+        if (cell.row === 1) {
+          // We've just finished iterating the first row.
+          this.rowSize = column;
+        }
+        row = cell.row;
+        rows[row] = [];
+        column = 0;
+      }
+      rows[row][column] = cell;
+
+      // Set up horizontal linkage
+      cell[R] = blocks[i+1];
+      cell[L] = blocks[i-1];
+
+      // Set up vertical linkage
+      if (rows[row-1] && rows[row-1][column]) {
+        cell.upOutOf = rows[row-1][column];
+        rows[row-1][column].downOutOf = cell;
+      }
+
+      column+=1;
+    }
+
+    // set start and end blocks of matrix
+    this.ends[L] = blocks[0];
+    this.ends[R] = blocks[blocks.length-1];
+  };
+  // Ensure consistent row lengths
+  _.autocorrect = function(rows) {
+    var lengths = [], rows = [];
+    var blocks = this.blocks;
+    var maxLength, shortfall, position, row, i;
+
+    for (i=0; i<blocks.length; i+=1) {
+      row = blocks[i].row;
+      rows[row] = rows[row] || [];
+      rows[row].push(blocks[i]);
+      lengths[row] = rows[row].length;
+    }
+
+    maxLength = Math.max.apply(null, lengths);
+    if (maxLength !== Math.min.apply(null, lengths)) {
+      // Pad shorter rows to correct length
+      for (i=0; i<rows.length; i+=1) {
+        shortfall = maxLength - rows[i].length;
+        while (shortfall) {
+          position = maxLength*i + rows[i].length;
+          blocks.splice(position, 0, MatrixCell(i, this));
+          shortfall-=1;
+        }
+      }
+      this.relink();
+    }
+  };
+  // Deleting a cell will also delete the current row and
+  // column if they are empty, and relink the matrix.
+  _.deleteCell = function(currentCell) {
+    var rows = [], columns = [], myRow = [], myColumn = [];
+    var blocks = this.blocks, row, column;
+
+    // Create arrays for cells in the current row / column
+    this.eachChild(function (cell) {
+      if (row !== cell.row) {
+        row = cell.row;
+        rows[row] = [];
+        column = 0;
+      }
+      columns[column] = columns[column] || [];
+      columns[column].push(cell);
+      rows[row].push(cell);
+
+      if (cell === currentCell) {
+        myRow = rows[row];
+        myColumn = columns[column];
+      }
+
+      column+=1;
+    });
+
+    function isEmpty(cells) {
+      var empties = [];
+      for (var i=0; i<cells.length; i+=1) {
+        if (cells[i].isEmpty()) empties.push(cells[i]);
+      }
+      return empties.length === cells.length;
+    }
+
+    function remove(cells) {
+      for (var i=0; i<cells.length; i+=1) {
+        if (blocks.indexOf(cells[i]) > -1) {
+          cells[i].remove();
+          blocks.splice(blocks.indexOf(cells[i]), 1);
+        }
+      }
+    }
+
+    if (isEmpty(myRow) && myColumn.length > 1) {
+      row = rows.indexOf(myRow);
+      // Decrease all following row numbers
+      this.eachChild(function (cell) {
+        if (cell.row > row) cell.row-=1;
+      });
+      // Dispose of cells and remove <tr>
+      remove(myRow);
+      this.jQ.find('tr').eq(row).remove();
+    }
+    if (isEmpty(myColumn) && myRow.length > 1) {
+      remove(myColumn);
+    }
+    this.finalizeTree();
+  };
+  _.addRow = function(afterCell) {
+    var previous = [], newCells = [], next = [];
+    var newRow = $('<tr></tr>'), row = afterCell.row;
+    var columns = 0, block, column;
+
+    this.eachChild(function (cell) {
+      // Cache previous rows
+      if (cell.row <= row) {
+        previous.push(cell);
+      }
+      // Work out how many columns
+      if (cell.row === row) {
+        if (cell === afterCell) column = columns;
+        columns+=1;
+      }
+      // Cache cells after new row
+      if (cell.row > row) {
+        cell.row+=1;
+        next.push(cell);
+      }
+    });
+
+    // Add new cells, one for each column
+    for (var i=0; i<columns; i+=1) {
+      block = MatrixCell(row+1);
+      block.parent = this;
+      newCells.push(block);
+
+      // Create cell <td>s and add to new row
+      block.jQ = $('<td class="mq-empty">')
+        .attr(mqBlockId, block.id)
+        .appendTo(newRow);
+    }
+
+    // Insert the new row
+    this.jQ.find('tr').eq(row).after(newRow);
+    this.blocks = previous.concat(newCells, next);
+    return newCells[column];
+  };
+  _.addColumn = function(afterCell) {
+    var rows = [], newCells = [];
+    var column, block;
+
+    // Build rows array and find new column index
+    this.eachChild(function (cell) {
+      rows[cell.row] = rows[cell.row] || [];
+      rows[cell.row].push(cell);
+      if (cell === afterCell) column = rows[cell.row].length;
+    });
+
+    // Add new cells, one for each row
+    for (var i=0; i<rows.length; i+=1) {
+      block = MatrixCell(i);
+      block.parent = this;
+      newCells.push(block);
+      rows[i].splice(column, 0, block);
+
+      block.jQ = $('<td class="mq-empty">')
+        .attr(mqBlockId, block.id);
+    }
+
+    // Add cell <td> elements in correct positions
+    this.jQ.find('tr').each(function (i) {
+      $(this).find('td').eq(column-1).after(rows[i][column].jQ);
+    });
+
+    // Flatten the rows array-of-arrays
+    this.blocks = [].concat.apply([], rows);
+    return newCells[afterCell.row];
+  };
+  _.insert = function(method, afterCell) {
+    var cellToFocus = this[method](afterCell);
+    this.cursor = this.cursor || this.parent.cursor;
+    this.finalizeTree();
+    this.bubble('reflow').cursor.insAtRightEnd(cellToFocus);
+  };
+  _.backspace = function(cell, dir, cursor, finalDeleteCallback) {
+    var dirwards = cell[dir];
+    if (cell.isEmpty()) {
+      this.deleteCell(cell);
+      while (dirwards &&
+        dirwards[dir] &&
+        this.blocks.indexOf(dirwards) === -1) {
+          dirwards = dirwards[dir];
+      }
+      if (dirwards) {
+        cursor.insAtDirEnd(-dir, dirwards);
+      }
+      if (this.blocks.length === 1 && this.blocks[0].isEmpty()) {
+        finalDeleteCallback();
+        this.finalizeTree();
+      }
+      this.bubble('edited');
+    }
+  };
+});
+
+Environments.pmatrix = P(Matrix, function(_, super_) {
+  _.environment = 'pmatrix';
+  _.parentheses = {
+    left: '(',
+    right: ')'
+  };
+});
+
+Environments.bmatrix = P(Matrix, function(_, super_) {
+  _.environment = 'bmatrix';
+  _.parentheses = {
+    left: '[',
+    right: ']'
+  };
+});
+
+Environments.Bmatrix = P(Matrix, function(_, super_) {
+  _.environment = 'Bmatrix';
+  _.parentheses = {
+    left: '{',
+    right: '}'
+  };
+});
+
+Environments.vmatrix = P(Matrix, function(_, super_) {
+  _.environment = 'vmatrix';
+  _.parentheses = {
+    left: '|',
+    right: '|'
+  };
+});
+
+Environments.Vmatrix = P(Matrix, function(_, super_) {
+  _.environment = 'Vmatrix';
+  _.parentheses = {
+    left: '&#8214;',
+    right: '&#8214;'
+  };
+});
+
+// Replacement for mathblocks inside matrix cells
+// Adds matrix-specific keyboard commands
+var MatrixCell = P(MathBlock, function(_, super_) {
+  _.init = function(row, parent, replaces) {
+    super_.init.call(this);
+    this.row = row;
+    if (parent) {
+      this.adopt(parent, parent.ends[R], 0);
+    }
+    if (replaces) {
+      for (var i=0; i<replaces.length; i++) {
+        replaces[i].children().adopt(this, this.ends[R], 0);
+      }
+    }
+  };
+  _.keystroke = function(key, e, ctrlr) {
+    switch (key) {
+    case 'Shift-Spacebar':
+      e.preventDefault();
+      return this.parent.insert('addColumn', this);
+      break;
+    case 'Shift-Enter':
+    return this.parent.insert('addRow', this);
+      break;
+    }
+    return super_.keystroke.apply(this, arguments);
+  };
+  _.deleteOutOf = function(dir, cursor) {
+    var self = this, args = arguments;
+    this.parent.backspace(this, dir, cursor, function () {
+      // called when last cell gets deleted
+      return super_.deleteOutOf.apply(self, args);
+    });
+  };
+  _.moveOutOf = function(dir, cursor, updown) {
+    var atExitPoint = updown && this.parent.atExitPoint(dir, cursor);
+    // Step out of the matrix if we've moved past an edge column
+    if (!atExitPoint && this[dir]) cursor.insAtDirEnd(-dir, this[dir]);
+    else cursor.insDirOf(dir, this.parent);
+  };
+});
diff --git a/src/css/math.less b/src/css/math.less
index dfa53e2..f170de6 100644
--- a/src/css/math.less
+++ b/src/css/math.less
@@ -416,4 +416,29 @@
       }
     }
   }
+
+  .mq-matrix {
+    vertical-align: middle;
+    margin-left: 0.1em;
+    margin-right: 0.1em;
+
+    table {
+      width: auto;
+      border-bottom: none;
+      border-spacing: 3px;
+      border-collapse: separate;
+      &.mq-rows-1 { /* better alignment when there's just one row */
+        vertical-align: middle;
+        margin-bottom: 1px;
+      }
+    }
+
+    td {
+      border: none;
+      width: auto; /* defensive resets */
+      padding: 0.1em 0.3em;
+      vertical-align: baseline;
+    }
+  }
+
 }
diff --git a/src/publicapi.js b/src/publicapi.js
index 63cd46f..c8fedf4 100644
--- a/src/publicapi.js
+++ b/src/publicapi.js
@@ -173,7 +173,7 @@
       var ctrlr = this.__controller.notify(), cursor = ctrlr.cursor;
       if (/^\\[a-z]+$/i.test(cmd)) {
         cmd = cmd.slice(1);
-        var klass = LatexCmds[cmd];
+        var klass = LatexCmds[cmd] || Environments[cmd];
         if (klass) {
           cmd = klass(cmd);
           if (cursor.selection) cmd.replaces(cursor.replaceSelection());
diff --git a/test/matrix.html b/test/matrix.html
new file mode 100644
index 0000000..fd3175f
--- /dev/null
+++ b/test/matrix.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>

+<html>

+<head>

+

+<title>MathQuill Matrix Test</title>

+

+<link rel="stylesheet" type="text/css" href="../build/mathquill.css">

+<script type="text/javascript" src="support/jquery-1.7.2.js"></script>

+<script type="text/javascript" src="../build/mathquill.js"></script>

+</head>

+<body>

+

+<h3>MathQuill Matrix Test</h3>

+<span id="example">

+\begin{Bmatrix}1&amp;2\\x&amp;y\end{Bmatrix}

+</span>

+

+<script>

+MathQuill = MathQuill.getInterface(1);

+instance = MathQuill.MathField(document.getElementById('example'));

+console.log(instance.latex());

+</script>

+

+</body>

+</html>

diff --git a/test/unit/latex.test.js b/test/unit/latex.test.js
index e6e4096..afa3a32 100644
--- a/test/unit/latex.test.js
+++ b/test/unit/latex.test.js
@@ -137,6 +137,18 @@
     assertParsesLatex('\\square ');
   });
 
+  test('matrices', function() {
+    assertParsesLatex('\\begin{matrix}x\\end{matrix}');
+    assertParsesLatex('\\begin{pmatrix}x\\end{pmatrix}');
+    assertParsesLatex('\\begin{Bmatrix}x\\end{Bmatrix}');
+    assertParsesLatex('\\begin{vmatrix}x&y\\\\1&2\\end{vmatrix}');
+    assertParsesLatex('\\begin{bmatrix}x&y&z&123&x^2\\\\23&s&\\sin \\theta &1&x\\\\e&h&a&1&y\\end{bmatrix}');
+
+    // Adds missing cells
+    assertParsesLatex('\\begin{Vmatrix}x&y\\\\1\\end{Vmatrix}', '\\begin{Vmatrix}x&y\\\\1&\\end{Vmatrix}');
+    assertParsesLatex('\\begin{Vmatrix}x\\\\x&y\\\\x\\end{Vmatrix}', '\\begin{Vmatrix}x&\\\\x&y\\\\x&\\end{Vmatrix}');
+  });
+
   suite('public API', function() {
     var mq;
     setup(function() {
diff --git a/test/unit/typing.test.js b/test/unit/typing.test.js
index c77fc90..bcc073f 100644
--- a/test/unit/typing.test.js
+++ b/test/unit/typing.test.js
@@ -1107,4 +1107,125 @@
       assertLatex('\\times');
     });
   });
+
+  suite('Matrices', function() {
+    test('add matrix via mq.write', function() {
+      mq.write('\\begin{matrix}x&y\\\\1&2\\end{matrix}');
+      assertLatex('\\begin{matrix}x&y\\\\1&2\\end{matrix}');
+    });
+
+    test('key bindings add rows and columns to matrix', function() {
+      mq.write('\\begin{matrix}x\\end{matrix}').keystroke('Left');
+
+      mq.keystroke('Shift-Spacebar');
+      assertLatex('\\begin{matrix}x&\\end{matrix}');
+
+      mq.keystroke('Shift-Enter');
+      assertLatex('\\begin{matrix}x&\\\\&\\end{matrix}');
+    });
+
+    test('key sequence populates matrix', function() {
+      mq.write('\\begin{matrix}x&\\\\&\\end{matrix}')
+        .keystroke('Left Left').typedText('a')
+        .keystroke('Right').typedText('b')
+        .keystroke('Up').typedText('y');
+
+      assertLatex('\\begin{matrix}x&y\\\\a&b\\end{matrix}');
+    });
+
+    test('cursor keys navigate around matrix', function() {
+      mq.write('\\begin{matrix}&&\\\\&&\\\\&&\\end{matrix}');
+
+      mq.keystroke('Left Left Left').typedText('a')
+        .keystroke('Up').typedText('b')
+        .keystroke('Right').typedText('c')
+        .keystroke('Down').typedText('d');
+
+      assertLatex('\\begin{matrix}&&\\\\b&c&\\\\a&d&\\end{matrix}');
+    });
+
+    test('passes over matrices when leftRightIntoCmdGoes is set to up', function() {
+      mq.config({ leftRightIntoCmdGoes: 'up' });
+
+      // 1 2 3
+      // 4 5 6
+      // 7 8 9
+      mq.write('\\begin{matrix}1&2&3\\\\4&5&6\\\\7&8&9\\end{matrix}');
+
+      mq.keystroke('Left Left Left Left Left Left Left').typedText('a')
+        .keystroke('Right Right Right Right Right Right Right').typedText('b')
+        .keystroke('Left Left Left Left').typedText('c');
+
+      // It should've entered the top of the matrix and exited at either end, leading to
+      //   1  2c 3
+      // a 4  5  6 b
+      //   7  8  9
+      assertLatex('a\\begin{matrix}1&2c&3\\\\4&5&6\\\\7&8&9\\end{matrix}b');
+    });
+
+    test('passes under matrices when leftRightIntoCmdGoes is set to down', function() {
+      mq.config({ leftRightIntoCmdGoes: 'down' });
+
+      // 1 2 3
+      // 4 5 6
+      // 7 8 9
+      mq.write('\\begin{matrix}1&2&3\\\\4&5&6\\\\7&8&9\\end{matrix}');
+
+      mq.keystroke('Left Left Left Left Left Left Left').typedText('a')
+        .keystroke('Right Right Right Right Right Right Right').typedText('b')
+        .keystroke('Left Left Left Left').typedText('c');
+
+      // It should've entered the bottom of the matrix and exited at either end, leading to
+      //   1  2  3
+      // a 4  5  6 b
+      //   7  8c 9
+      assertLatex('a\\begin{matrix}1&2&3\\\\4&5&6\\\\7&8c&9\\end{matrix}b');
+    });
+
+    test('exits out of matrices on their edges when leftRightIntoCmdGoes is set', function() {
+      mq.config({ leftRightIntoCmdGoes: 'up' });
+
+      // 1 2 3
+      // 4 5 6
+      // 7 8 9
+      mq.write('\\begin{matrix}1&2&3\\\\4&5&6\\\\7&8&9\\end{matrix}');
+
+      mq.keystroke('Left Left Left Down').typedText('a')
+        .keystroke('Right Right Right').typedText('b')
+
+      // It should've entered the top of the matrix and exited out the side, leading to
+      // 1  2  3
+      // 4  5a 6 b
+      // 7  8  9
+      assertLatex('\\begin{matrix}1&2&3\\\\4&5a&6\\\\7&8&9\\end{matrix}b');
+    });
+
+    test('delete key removes empty matrix row/column', function() {
+      mq.write('\\begin{matrix}a&&b\\\\&c&d\\\\&e&f\\end{matrix}');
+
+      // Row is not yet deleted as there was content
+      mq.keystroke('Left Backspace Left');
+      assertLatex('\\begin{matrix}a&&b\\\\&c&d\\\\&e&\\end{matrix}');
+
+      // Row is now deleted (delete e, then row)
+      mq.keystroke('Backspace Backspace');
+      assertLatex('\\begin{matrix}a&&b\\\\&c&d\\end{matrix}');
+
+      // Column is now deleted (delete c, then column)
+      mq.keystroke('Left Left Backspace Backspace');
+      assertLatex('\\begin{matrix}a&b\\\\&d\\end{matrix}');
+    });
+
+    test('brackets are scaled immediately', function() {
+      mq.write('\\begin{bmatrix}x\\end{bmatrix}');
+      function bracketHeight() {
+        return $(mq.el()).find('.mq-matrix .mq-paren.mq-scaled')[0].getBoundingClientRect().height;
+      }
+      var height = bracketHeight();
+      mq.keystroke('Left Shift-Enter');
+
+      assert.ok(bracketHeight() > height,
+        'matrix bracket height should be increased when new row is added');
+    });
+  });
 });
diff --git a/test/visual.html b/test/visual.html
index 3d091f5..d8518b7 100644
--- a/test/visual.html
+++ b/test/visual.html
@@ -178,9 +178,9 @@
 <table id="dynamic-reflow">

 <tr><th colspan=3><code>MQ(...).reflow()</code>

 <tr>

-  <td><span>\sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + \binom{n}{k}</span>

-  <td><span>\sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + \binom{n}{k}</span>

-  <td><span>\sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + \binom{n}{k}</span>

+  <td><span>\sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + \binom{n}{k} + \begin{bmatrix}x&amp;y\\1&amp;2\end{bmatrix}</span>

+  <td><span>\sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + \binom{n}{k} + \begin{bmatrix}x&amp;y\\1&amp;2\end{bmatrix}</span>

+  <td><span>\sqrt{ \left ( \frac{x^2 + y^2}{2} \right ) } + \binom{n}{k} + \begin{bmatrix}x&amp;y\\1&amp;2\end{bmatrix}</span>

 </table>

 

 <h3>Static LaTeX rendering (<code>.mathquill-static-math</code>) tests</h3>

@@ -212,11 +212,15 @@
 <tr><td><span class="mathquill-static-math">\vec x + \tilde x + \vec A + \tilde A + \vec{abcd} + \tilde{abcd}</span><td><span>\vec x + \tilde x + \vec A + \tilde A + \vec{abcd} + \tilde{abcd}</span>^M

 <tr><td><span class="mathquill-static-math">\int _{\phi =0}^{2\pi }\int _{\theta =0}^{\pi }\int _{r=0}^{\infty }f(r,\theta ,\phi )r^2\sin \theta drd\theta d\phi </span><td><span>\int _{\phi =0}^{2\pi }\int _{\theta =0}^{\pi }\int _{r=0}^{\infty }f(r,\theta ,\phi )r^2\sin \theta drd\theta d\phi </span>

 <tr><td><span class="mathquill-static-math">\int_0^{\frac{\frac{1}{2}}{3}} \int_0^{\frac{1}{\frac{2}{3}}} \int_0^{\frac{1}{\frac{2}{\frac{3}{\frac{4}{5}}}}}</span><td><span>\int_0^{\frac{\frac{1}{2}}{3}} \int_0^{\frac{1}{\frac{2}{3}}} \int_0^{\frac{1}{\frac{2}{\frac{3}{\frac{4}{5}}}}}</span>

+<<<<<<< HEAD

 <tr><td><span class="mathquill-static-math">\overline{abc}</span><td><span>\overline{abc}</span>

 <tr><td><span class="mathquill-static-math">\overleftarrow{abc}</span><td><span>\overleftarrow{abc}</span>

 <tr><td><span class="mathquill-static-math">\overrightarrow{abc}</span><td><span>\overrightarrow{abc}</span>

 <tr><td><span class="mathquill-static-math">\overleftrightarrow{abc}</span><td><span>\overleftrightarrow{abc}</span>

 <tr><td><span class="mathquill-static-math">\overarc{abc}</span><td><span>\overarc{abc}</span>

+=======

+<tr><td><span class="mathquill-static-math">\begin{Vmatrix}x&amp;y\\1&amp;2\end{Vmatrix}</span><td><span>\begin{Vmatrix}x&amp;y\\1&amp;2\end{Vmatrix}</span>

+>>>>>>> matrix

 <tr><td colspan=2><span id="sixes"></span>

 <script>

 $(function() {