text: describe('tableLayout', function () {
    var Cell = require('../src/cell');
    var layoutManager = require('../src/layout-manager');
    var layoutTable = layoutManager.layoutTable;
    var makeTableLayout = layoutManager.makeTableLayout;
    var addRowSpanCells = layoutManager.addRowSpanCells;
    var maxWidth = layoutManager.maxWidth;
    var fillInTable = layoutManager.fillInTable;
    var computeWidths = layoutManager.computeWidths;
    var computeHeights = layoutManager.computeHeights;
    var chai = require('chai');
    var expect = chai.expect;
    var kindOf = require('kind-of');
  
    it('simple 2x2 layout',function(){
      var actual = makeTableLayout([
        ['hello','goodbye'],
        ['hola','adios']
      ]);
  
      var expected = [
        ['hello','goodbye'],
        ['hola','adios']
      ];
  
      checkLayout(actual,expected);
    });
  
    it('cross table',function(){
      var actual = makeTableLayout([
        {'1.0':['yes','no']},
        {'2.0':['hello','goodbye']}
      ]);
  
      var expected = [
        ['1.0','yes','no'],
        ['2.0','hello','goodbye']
      ];
  
      checkLayout(actual,expected);
    });
  
    it('vertical table',function(){
      var actual = makeTableLayout([
        {'1.0':'yes'},
        {'2.0':'hello'}
      ]);
  
      var expected = [
        ['1.0','yes'],
        ['2.0','hello']
      ];
  
      checkLayout(actual,expected);
    });
  
    it('colSpan adds RowSpanCells to the right',function(){
      var actual = makeTableLayout([
        [{content:'hello',colSpan:2}],
        ['hola','adios']
      ]);
  
      var expected = [
        [{content:'hello',colSpan:2},null],
        ['hola','adios']
      ];
  
      checkLayout(actual,expected);
    });
  
    it('rowSpan adds RowSpanCell below',function(){
       var actual = makeTableLayout([
         [{content:'hello',rowSpan:2},'goodbye'],
         ['adios']
       ]);
  
      var expected = [
        ['hello'            , 'goodbye'],
        [{spannerFor:[0,0]} , 'adios']
      ];
  
      checkLayout(actual,expected);
    });
  
    it('rowSpan and cellSpan together',function(){
      var actual = makeTableLayout([
        [{content:'hello',rowSpan:2,colSpan:2},'goodbye'],
        ['adios']
      ]);
  
      var expected = [
        ['hello'            , null, 'goodbye'],
        [{spannerFor:[0,0]} , null, 'adios']
      ];
  
      checkLayout(actual,expected);
    });
  
    it('complex layout',function(){
      var actual = makeTableLayout([
        [{content:'hello',rowSpan:2,colSpan:2},{content:'yo',rowSpan:2,colSpan:2},'goodbye'],
        ['adios']
      ]);
  
      var expected = [
        ['hello'            , null, 'yo'               , null, 'goodbye'],
        [{spannerFor:[0,0]} , null, {spannerFor:[0,2]} , null, 'adios']
      ];
  
      checkLayout(actual,expected);
    });
  
    it('complex layout2',function(){
      var actual = makeTableLayout([
        ['a','b',                          {content:'c',rowSpan:3,colSpan:2},'d'],
        [{content:'e',rowSpan:2,colSpan:2}, 'f'],
        ['g']
      ]);
  
      var expected = [
        ['a',                 'b',   'c',                 null,  'd'],
        ['e',                 null,  {spannerFor:[0,2]},  null,  'f'],
        [{spannerFor:[1,0]},  null,  {spannerFor:[0,2]},  null,  'g']
      ];
  
      checkLayout(actual,expected);
    });
  
    it('stairstep spans',function(){
      var actual = makeTableLayout([
        [{content:'',rowSpan:2},''],
        [{content:'',rowSpan:2}],
        ['']
      ]);
  
      var expected = [
        [{content:'',rowSpan:2}, ''],
        [{spannerFor:[0,0]},{content:'',rowSpan:2}],
        ['',{spannerFor:[1,1]}]
      ];
  
      checkLayout(actual,expected);
    });
  
    describe('fillInTable',function(){
      function mc(opts,y,x){
        var cell = new Cell(opts);
        cell.x = x;
        cell.y = y;
        return cell;
      }
  
      it('will blank out individual cells',function(){
        var cells = [
          [mc('a',0,1)],
          [mc('b',1,0)]
        ];
        fillInTable(cells);
  
        checkLayout(cells,[
          ['', 'a'],
          ['b', '']
        ]);
      });
  
      it('will autospan to the right',function(){
        var cells = [
          [],
          [mc('a',1,1)]
        ];
        fillInTable(cells);
  
        checkLayout(cells,[
          [{content:'',colSpan:2}, null],
          ['', 'a']
        ]);
      });
  
      it('will autospan down',function(){
        var cells = [
          [ mc('a',0,1)],
          []
        ];
        fillInTable(cells);
        addRowSpanCells(cells);
  
        checkLayout(cells,[
          [{content:'',rowSpan:2}, 'a'],
          [{spannerFor:[0,0]}, '']
        ]);
      });
  
      it('will autospan right and down',function(){
        var cells = [
          [               mc('a',0,2)],
          [],
          [  mc('b',2,1)]
        ];
        fillInTable(cells);
        addRowSpanCells(cells);
  
        checkLayout(cells,[
          [{content:'',colSpan:2, rowSpan:2}, null, 'a'],
          [{spannerFor:[0,0]}, null, {content:'', colSpan:1, rowSpan:2}],
          ['','b',{spannerFor:[1,2]}]
        ]);
      });
    });
  
    describe('computeWidths',function() {
      function mc(y,x,desiredWidth, colSpan) {
        return {x:x,y:y,desiredWidth:desiredWidth,colSpan:colSpan};
      }
  
      it('finds the maximum desired width of each column', function () {
        var widths = [];
        var cells = [
          [mc(0,0,7), mc(0,1,3), mc(0,2,5)],
          [mc(1,0,8), mc(1,1,5), mc(1,2,2)],
          [mc(2,0,6), mc(2,1,9), mc(2,2,1)]
        ];
  
        computeWidths(widths, cells);
  
        expect(widths).to.eql([8, 9, 5]);
      });
  
      it('won\'t touch hard coded values', function () {
        var widths = [null, 3];
        var cells = [
          [mc(0,0,7), mc(0,1,3), mc(0,2,5)],
          [mc(1,0,8), mc(1,1,5), mc(1,2,2)],
          [mc(2,0,6), mc(2,1,9), mc(2,2,1)]
        ];
  
        computeWidths(widths, cells);
  
        expect(widths).to.eql([8, 3, 5]);
      });
  
      it('assumes undefined desiredWidth is 1', function () {
        var widths = [];
        var cells = [[{x:0,y:0}], [{x:0,y:1}], [{x:0,y:2}]];
        computeWidths(widths, cells);
        expect(widths).to.eql([1])
      });
  
      it('takes into account colSpan and wont over expand', function () {
        var widths = [];
        var cells = [
          [mc(0,0,10, 2),        mc(0,2,5)],
          [mc(1,0,5), mc(1,1,5), mc(1,2,2)],
          [mc(2,0,4), mc(2,1,2), mc(2,2,1)]
        ];
        computeWidths(widths, cells);
        expect(widths).to.eql([5, 5, 5]);
      });
  
      it('will expand rows involved in colSpan in a balanced way', function () {
        var widths = [];
        var cells = [
          [mc(0,0,13,2),          mc(0,2,5)],
          [mc(1,0,5), mc(1,1,5),  mc(1,2,2)],
          [mc(2,0,4), mc(2,1,2),  mc(2,2,1)]
        ];
        computeWidths(widths, cells);
        expect(widths).to.eql([6, 6, 5]);
      });
  
      it('expands across 3 cols', function () {
        var widths = [];
        var cells = [
          [mc(0,0,25,3)                        ],
          [mc(1,0,5),    mc(1,1,5),  mc(1,2,2) ],
          [mc(2,0,4),    mc(2,1,2),  mc(2,2,1) ]
        ];
        computeWidths(widths, cells);
        expect(widths).to.eql([9, 9, 5]);
      });
  
      it('multiple spans in same table', function () {
        var widths = [];
        var cells = [
          [mc(0,0,25,3)                        ],
          [mc(1,0,30,3)                        ],
          [mc(2,0,4),    mc(2,1,2),  mc(2,2,1) ]
        ];
        computeWidths(widths, cells);
        expect(widths).to.eql([11, 9, 8]);
      });
  
      it('spans will only edit uneditable tables',function(){
        var widths = [null, 3];
        var cells = [
          [mc(0,0,20,3)                         ],
          [mc(1,0,4),    mc(1,1,20),  mc(1,2,5) ]
        ];
        computeWidths(widths, cells);
        expect(widths).to.eql([7,3,8])
      });
  
      it('spans will only edit uneditable tables - first column uneditable',function(){
        var widths = [3];
        var cells = [
          [mc(0,0,20,3)                        ],
          [mc(1,0,4),    mc(1,1,3),  mc(1,2,5) ]
        ];
        computeWidths(widths, cells);
        expect(widths).to.eql([3,7,8])
      });
    });
      
    describe('computeHeights',function(){
      function mc(y,x,desiredHeight,colSpan){
        return {x:x,y:y,desiredHeight:desiredHeight,rowSpan:colSpan};
      }
  
      it('finds the maximum desired height of each row',function(){
        var heights = [];
        var cells = [
          [mc(0,0,7),  mc(0,1,3),  mc(0,2,5) ],
          [mc(1,0,8),  mc(1,1,5),  mc(1,2,2) ],
          [mc(2,0,6),  mc(2,1,9),  mc(2,2,1) ]
        ];
  
        computeHeights(heights,cells);
  
        expect(heights).to.eql([7,8,9]);
      });
  
      it('won\'t touch hard coded values',function(){
        var heights = [null,3];
        var cells = [
          [mc(0,0,7),  mc(0,1,3),  mc(0,2,5)],
          [mc(1,0,8),  mc(1,1,5),  mc(1,2,2)],
          [mc(2,0,6),  mc(2,1,9),  mc(2,2,1)]
        ];
  
        computeHeights(heights,cells);
  
        expect(heights).to.eql([7,3,9]);
      });
  
      it('assumes undefined desiredHeight is 1',function(){
        var heights = [];
        var cells = [[{x:0,y:0},{x:1,y:0},{x:2,y:0}]];
        computeHeights(heights,cells);
        expect(heights).to.eql([1])
      });
  
      it('takes into account rowSpan and wont over expand',function(){
        var heights = [];
        var cells = [
          [mc(0,0,10,2),  mc(0,1,5),  mc(0,2,2)],
          [               mc(1,1,5),  mc(1,2,2)],
          [mc(2,0,4),     mc(2,1,2),  mc(2,2,1)]
        ];
        computeHeights(heights,cells);
        expect(heights).to.eql([5,5,4]);
      });
  
      it('will expand rows involved in rowSpan in a balanced way',function(){
        var heights = [];
        var cells = [
          [mc(0,0,13,2), mc(0,1,5), mc(0,2,5)],
          [              mc(1,1,5), mc(1,2,2)],
          [mc(2,0,4),    mc(2,1,2), mc(2,2,1)]
        ];
        computeHeights(heights,cells);
        expect(heights).to.eql([6,6,4]);
      });
  
      it('expands across 3 rows',function(){
        var heights = [];
        var cells = [
          [mc(0,0,25,3), mc(0,1,5), mc(0,2,4)],
          [              mc(1,1,5), mc(1,2,2)],
          [              mc(2,1,2), mc(2,2,1)]
        ];
        computeHeights(heights,cells);
        expect(heights).to.eql([9,9,5]);
      });
  
      it('multiple spans in same table',function(){
        var heights = [];
        var cells = [
          [mc(0,0,25,3), mc(0,1,30,3), mc(0,2,4)],
          [                            mc(1,2,2)],
          [                            mc(2,2,1)]
        ];
        computeHeights(heights,cells);
        expect(heights).to.eql([11,9,8]);
      });
    });
  
    /**
     * Provides a shorthand for validating a table of cells.
     * To pass, both arrays must have the same dimensions, and each cell in `actualRows` must
     * satisfy the shorthand assertion of the corresponding location in `expectedRows`.
     *
     * Available Expectations Can Be:
     *
     *    * A `String` -  Must be a normal cell with contents equal to the String value.
     *    * `null` -  Must be a RowSpanCell
     *
     * Or an `Object` with any of the following properties (multiple properties allowed):
     *    * rowSpan:Number - Must be a normal cell with the given rowSpan.
     *    * colSpan:Number - Must be a normal cell with the given colSpan.
     *    * content:String - Must be a normal cell with the given content.
     *    * spannerFor:[row,col] - Must be a RowSpanCell delegating to the cell at the given coordinates.
     *
     * @param actualRows - the table of cells under test.
     * @param expectedRows - a table of shorthand assertions.
     */
  
    function checkLayout(actualTable,expectedTable){
      expectedTable.forEach(function(expectedRow,y){
        expectedRow.forEach(function(expectedCell,x){
          if(expectedCell !== null){
            var actualCell = findCell(actualTable,x,y);
            checkExpectation(actualCell,expectedCell,x,y,actualTable);
          }
        });
      });
    }
  
    function findCell(table,x,y){
      for(var i = 0; i < table.length; i++){
        var row = table[i];
        for(var j = 0; j < row.length; j++){
          var cell = row[j];
          if(cell.x === x && cell.y === y){
            return cell;
          }
        }
      }
    }
  
    function checkExpectation(actualCell,expectedCell,x,y,actualTable){
      if(kindOf(expectedCell) === 'string'){
        expectedCell = {content:expectedCell};
      }
      var address = '(' + y + ',' + x + ')';
      if(expectedCell.hasOwnProperty('content')){
        expect(actualCell, address).to.be.instanceOf(Cell);
        expect(actualCell.content,'content of ' + address).to.equal(expectedCell.content);
      }
      if(expectedCell.hasOwnProperty('rowSpan')){
        expect(actualCell, address).to.be.instanceOf(Cell);
        expect(actualCell.rowSpan, 'rowSpan of ' + address).to.equal(expectedCell.rowSpan);
      }
      if(expectedCell.hasOwnProperty('colSpan')){
        expect(actualCell, address).to.be.instanceOf(Cell);
        expect(actualCell.colSpan, 'colSpan of ' + address).to.equal(expectedCell.colSpan);
      }
      if(expectedCell.hasOwnProperty('spannerFor')){
        expect(actualCell, address).to.be.instanceOf(Cell.RowSpanCell);
        expect(actualCell.originalCell,address + 'originalCell should be a cell').to.be.instanceOf(Cell);
        expect(actualCell.originalCell,address + 'originalCell not right').to.equal(findCell(actualTable,
          expectedCell.spannerFor[1],
          expectedCell.spannerFor[0]
        ));
        //TODO: retest here x,y coords
      }
    }
  });
  