<commit_before>var async = require('async');
var sinon = require('sinon');
var should = require('should');
var path = require('path');
var fs = require('fs');

var testutil = require('../test/testutil.js');
var blogModule = require('../model/blog.js');

var blogRouter = require('../routes/blog.js');

describe('routes/blog',function() {
  beforeEach(function(bddone){
    testutil.clearDB(bddone);
  })

  describe('renderBlogPreview',function() {
    it('should call next if blog id not exist',function(bddone) {
      blogModule.createNewBlog({title:"WN333"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = blog.id +1;
        var req = {};
        req.params = {};
        req.params.blog_id = newId;

        var res = {};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogPreview(req,res,next);
          }],
          function(err) {
            should.exist(err);
            should(next.called).be.true();
            should(res.render.called).be.false();
            bddone();
          }
        )
      })
    })
    it('should call next if blog name not exist',function(bddone) {
      blogModule.createNewBlog({title:"WN333"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = "WN332";
        var req = {};
        req.params = {};
        req.params.blog_id = newId;
        var res = {};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogPreview(req,res,next);
          }],
          function(err) {
            should.exist(err);
            should(next.called).be.true();
            should(res.render.called).be.false();
            bddone();
          }
        )
      })
    })
    it('should call next if blog exists twice',function(bddone) {
      blogModule.createNewBlog({name:"WN333"},function(err,blog) {
        should.not.exist(err);
        blogModule.createNewBlog({name:"WN333"},function(err,blog2) {
          should.not.exist(err);
          should(blog.id).not.equal(0);
          var newId = "WN333";
          var req = {};
          req.params = {};
          req.params.blog_id = newId;
          var res = {};

          async.series([
            function(callback) {
              res.render = sinon.spy(callback)
              next = sinon.spy(callback);
              blogRouter.renderBlogPreview(req,res,next);
            }],
            function(err) {
              should.exist(err);
              should(next.called).be.true();

              should(res.render.called).be.false();
              var call = next.firstCall;
              should(call.args[0].message).equal("Blog >WN333< exists twice");

              bddone();
            }
          )
        })
      })
    })
    it('should render a blog Preview',function(bddone) {
      blogModule.createNewBlog({name:"WN333",startDate:"2015-12-12T00:00:00",endDate:"2015-12-13T00:00:00"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = "WN333";
        var req = {};
        req.params = {};
        req.params.blog_id = newId;
        req.query = {};
        req.session = {articleReturnTo:"returnToUrlXX"};

        var res = {rendervar:{layout:"calculated layout"}};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogPreview(req,res,next);
          }],
          function(result) {
            should(result).equal('blogpreview');
            should(next.called).be.False();

            should(res.render.called).be.True();
            var call = res.render.firstCall;
            var v = call.args[1];

            should(v.preview).equal('
12.12.2015-13.12.2015
\n\n');
            should(v.blog.id).equal(blog.id);
            should(v.layout).equal("calculated layout");
            //should(v.articles.length).equal(0);
            should(v.lang).equal("DE");
            should(v.returnToUrl).equal("returnToUrlXX");

            bddone();
          }
        )
      })
    })
  })
  describe('renderBlogId',function() {
    it('should call next if blog id not exist',function(bddone) {
      blogModule.createNewBlog({title:"WN333"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = blog.id +1;
        var req = {};
        req.params = {};
        req.params.blog_id = newId;
        req.session = {};
        req.user = {};
        req.query = {};

        var res = {};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogId(req,res,next);
          }],
          function(err) {
            should.exist(err);
            should(next.called).be.true();
            should(res.render.called).be.false();
            bddone();
          }
        )
      })
    })
    it('should call next if blog name not exist',function(bddone) {
      blogModule.createNewBlog({title:"WN333"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = "WN332";
        var req = {};
        req.params = {};
        req.params.blog_id = newId;
        req.session = {};
        req.user = {};
        req.query = {};
        var res = {};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogId(req,res,next);
          }],
          function(err) {
            should.exist(err);
            should(next.called).be.true();
            should(res.render.called).be.false();
            bddone();
          }
        )
      })
    })
    it('should call next if blog exists twice',function(bddone) {
      blogModule.createNewBlog({name:"WN333"},function(err,blog) {
        should.not.exist(err);
        blogModule.createNewBlog({name:"WN333"},function(err,blog2) {
          should.not.exist(err);
          should(blog.id).not.equal(0);
          var newId = "WN333";
          var req = {};
          req.params = {};
          req.params.blog_id = newId;
          req.session = {};
          req.query = {};
          req.user = {};
          var res = {};

          async.series([
            function(callback) {
              res.render = sinon.spy(callback)
              next = sinon.spy(callback);
              blogRouter.renderBlogId(req,res,next);
            }],
            function(err) {
              should.exist(err);
              should(next.called).be.true();

              should(res.render.called).be.false();
              var call = next.firstCall;
              should(call.args[0].message).equal("Blog >WN333< exists twice");

              bddone();
            }
          )
        })
      })
    })
/*    it('should render a blog for edit',function(bddone) {
      blogModule.createNewBlog({name:"WN333",startDate:"2015-12-12T00:00:00",endDate:"2015-12-13T00:00:00"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = "WN333";
        var req = {};
        req.params = {};
        req.params.blog_id = newId;
        req.query = {};
        req.user = {};
        req.session = {articleReturnTo:"returnToUrlXX"};

        var res = {rendervar:{layout:"calculated layout"}};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogId(req,res,next);
          }],
          function(result) {
            should(result).equal('blog');
            should(next.called).be.False();

           //layout

                           //changes:changes,
                           //articles:articles,
                           //style:style,
                           //left_lang:options.left_lang,
                           //right_lang:options.right_lang,
                           //categories:blog.getCategories()});
            should(res.render.called).be.True();
            var call = res.render.firstCall;
            var v = call.args[1];

            should(v.blog.id).equal(blog.id);
            should(v.layout).equal("calculated layout");
            should(v.main_text).equal("
12.12.2015-13.12.2015
\n\n");

            console.dir(v.changes);

            bddone();
          }
        )
      })
    })  */
  })
 describe('Render Blog ID File Based',function() {
    beforeEach(function (bddone) {
      testutil.clearDB(bddone);
    })
    function doATest(filename) {

      it('should handle testfile '+filename,function (bddone) {
        var file =  path.resolve(__dirname,'data', filename);
        var data =  JSON.parse(fs.readFileSync(file));

        var blog;
        var md;
        var html;
        var articles;

        var res;

        async.series([
          function(done) {
            testutil.importData(data,done);
          },
          function callRenderBlogId(callback) {
            var req = {};
            req.params = {};
            req.params.blog_id = data.testBlogName;
            req.query = {};
            if (data.style) req.query.style = data.style;
            req.user = {};
            req.session = {articleReturnTo:"returnToUrlXX"};

            res = {rendervar:{layout:"calculated layout"}};
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogId(req,res,next);
          }
          ],
          function (call) {
            should(call).equal("blog");
            should(res.render.called).be.True();
            var call = res.render.firstCall;
            var v = call.args[1];

            should(v.blog.name).equal(data.testBlogName);
            should(v.layout).equal("calculated layout");
            should(v.main_text).equal(data.result.main_text);
            for (var i = 0;i<data.result.changes.length;i++) {
              should(v.change[i].user)=data.result.changes[i].user;
              should(v.change[i].from)=data.result.changes[i].from;
              should(v.change[i].to)=data.result.changes[i].to;
            }

            should(v.style).equal(data.result.style);
            should(v.left_lang).equal(data.result.left_lang);
            should(v.right_lang).equal(data.result.right_lang);

            bddone();
          }
        )
      })
    }
    testutil.generateTests("data",/^router.blog.renderBlogId.+json/,doATest);
  })
})<commit_msg>Disabled test temporary<commit_after>var async = require('async');
var sinon = require('sinon');
var should = require('should');
var path = require('path');
var fs = require('fs');

var testutil = require('../test/testutil.js');
var blogModule = require('../model/blog.js');

var blogRouter = require('../routes/blog.js');

describe('routes/blog',function() {
  beforeEach(function(bddone){
    testutil.clearDB(bddone);
  })

  describe('renderBlogPreview',function() {
    it('should call next if blog id not exist',function(bddone) {
      blogModule.createNewBlog({title:"WN333"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = blog.id +1;
        var req = {};
        req.params = {};
        req.params.blog_id = newId;

        var res = {};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogPreview(req,res,next);
          }],
          function(err) {
            should.exist(err);
            should(next.called).be.true();
            should(res.render.called).be.false();
            bddone();
          }
        )
      })
    })
    it('should call next if blog name not exist',function(bddone) {
      blogModule.createNewBlog({title:"WN333"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = "WN332";
        var req = {};
        req.params = {};
        req.params.blog_id = newId;
        var res = {};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogPreview(req,res,next);
          }],
          function(err) {
            should.exist(err);
            should(next.called).be.true();
            should(res.render.called).be.false();
            bddone();
          }
        )
      })
    })
    it('should call next if blog exists twice',function(bddone) {
      blogModule.createNewBlog({name:"WN333"},function(err,blog) {
        should.not.exist(err);
        blogModule.createNewBlog({name:"WN333"},function(err,blog2) {
          should.not.exist(err);
          should(blog.id).not.equal(0);
          var newId = "WN333";
          var req = {};
          req.params = {};
          req.params.blog_id = newId;
          var res = {};

          async.series([
            function(callback) {
              res.render = sinon.spy(callback)
              next = sinon.spy(callback);
              blogRouter.renderBlogPreview(req,res,next);
            }],
            function(err) {
              should.exist(err);
              should(next.called).be.true();

              should(res.render.called).be.false();
              var call = next.firstCall;
              should(call.args[0].message).equal("Blog >WN333< exists twice");

              bddone();
            }
          )
        })
      })
    })
    it('should render a blog Preview',function(bddone) {
      blogModule.createNewBlog({name:"WN333",startDate:"2015-12-12T00:00:00",endDate:"2015-12-13T00:00:00"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = "WN333";
        var req = {};
        req.params = {};
        req.params.blog_id = newId;
        req.query = {};
        req.session = {articleReturnTo:"returnToUrlXX"};

        var res = {rendervar:{layout:"calculated layout"}};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogPreview(req,res,next);
          }],
          function(result) {
            should(result).equal('blogpreview');
            should(next.called).be.False();

            should(res.render.called).be.True();
            var call = res.render.firstCall;
            var v = call.args[1];

            should(v.preview).equal('
12.12.2015-13.12.2015
\n\n');
            should(v.blog.id).equal(blog.id);
            should(v.layout).equal("calculated layout");
            //should(v.articles.length).equal(0);
            should(v.lang).equal("DE");
            should(v.returnToUrl).equal("returnToUrlXX");

            bddone();
          }
        )
      })
    })
  })
  describe('renderBlogId',function() {
    it('should call next if blog id not exist',function(bddone) {
      blogModule.createNewBlog({title:"WN333"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = blog.id +1;
        var req = {};
        req.params = {};
        req.params.blog_id = newId;
        req.session = {};
        req.user = {};
        req.query = {};

        var res = {};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogId(req,res,next);
          }],
          function(err) {
            should.exist(err);
            should(next.called).be.true();
            should(res.render.called).be.false();
            bddone();
          }
        )
      })
    })
    it('should call next if blog name not exist',function(bddone) {
      blogModule.createNewBlog({title:"WN333"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = "WN332";
        var req = {};
        req.params = {};
        req.params.blog_id = newId;
        req.session = {};
        req.user = {};
        req.query = {};
        var res = {};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogId(req,res,next);
          }],
          function(err) {
            should.exist(err);
            should(next.called).be.true();
            should(res.render.called).be.false();
            bddone();
          }
        )
      })
    })
    it('should call next if blog exists twice',function(bddone) {
      blogModule.createNewBlog({name:"WN333"},function(err,blog) {
        should.not.exist(err);
        blogModule.createNewBlog({name:"WN333"},function(err,blog2) {
          should.not.exist(err);
          should(blog.id).not.equal(0);
          var newId = "WN333";
          var req = {};
          req.params = {};
          req.params.blog_id = newId;
          req.session = {};
          req.query = {};
          req.user = {};
          var res = {};

          async.series([
            function(callback) {
              res.render = sinon.spy(callback)
              next = sinon.spy(callback);
              blogRouter.renderBlogId(req,res,next);
            }],
            function(err) {
              should.exist(err);
              should(next.called).be.true();

              should(res.render.called).be.false();
              var call = next.firstCall;
              should(call.args[0].message).equal("Blog >WN333< exists twice");

              bddone();
            }
          )
        })
      })
    })
/*    it('should render a blog for edit',function(bddone) {
      blogModule.createNewBlog({name:"WN333",startDate:"2015-12-12T00:00:00",endDate:"2015-12-13T00:00:00"},function(err,blog) {
        should.not.exist(err);
        should(blog.id).not.equal(0);
        var newId = "WN333";
        var req = {};
        req.params = {};
        req.params.blog_id = newId;
        req.query = {};
        req.user = {};
        req.session = {articleReturnTo:"returnToUrlXX"};

        var res = {rendervar:{layout:"calculated layout"}};

        async.series([
          function(callback) {
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogId(req,res,next);
          }],
          function(result) {
            should(result).equal('blog');
            should(next.called).be.False();

           //layout

                           //changes:changes,
                           //articles:articles,
                           //style:style,
                           //left_lang:options.left_lang,
                           //right_lang:options.right_lang,
                           //categories:blog.getCategories()});
            should(res.render.called).be.True();
            var call = res.render.firstCall;
            var v = call.args[1];

            should(v.blog.id).equal(blog.id);
            should(v.layout).equal("calculated layout");
            should(v.main_text).equal("
12.12.2015-13.12.2015
\n\n");

            console.dir(v.changes);

            bddone();
          }
        )
      })
    })  */
  })
 describe('Render Blog ID File Based',function() {
    beforeEach(function (bddone) {
      testutil.clearDB(bddone);
    })
    function doATest(filename) {

      it('should handle testfile '+filename,function (bddone) {
        var file =  path.resolve(__dirname,'data', filename);
        var data =  JSON.parse(fs.readFileSync(file));

        var blog;
        var md;
        var html;
        var articles;

        var res;

        async.series([
          function(done) {
            testutil.importData(data,done);
          },
          function callRenderBlogId(callback) {
            var req = {};
            req.params = {};
            req.params.blog_id = data.testBlogName;
            req.query = {};
            if (data.style) req.query.style = data.style;
            req.user = {};
            req.session = {articleReturnTo:"returnToUrlXX"};

            res = {rendervar:{layout:"calculated layout"}};
            res.render = sinon.spy(callback)
            next = sinon.spy(callback);
            blogRouter.renderBlogId(req,res,next);
          }
          ],
          function (call) {
            should(call).equal("blog");
            should(res.render.called).be.True();
            var call = res.render.firstCall;
            var v = call.args[1];

            should(v.blog.name).equal(data.testBlogName);
            should(v.layout).equal("calculated layout");
            should(v.main_text).equal(data.result.main_text);
            for (var i = 0;i<data.result.changes.length;i++) {
              should(v.change[i].user)=data.result.changes[i].user;
              should(v.change[i].from)=data.result.changes[i].from;
              should(v.change[i].to)=data.result.changes[i].to;
            }

            should(v.style).equal(data.result.style);
            should(v.left_lang).equal(data.result.left_lang);
            should(v.right_lang).equal(data.result.right_lang);

            bddone();
          }
        )
      })
    }
    //testutil.generateTests("data",/^router.blog.renderBlogId.+json/,doATest);
  })
})<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import _ from 'lodash';
import keys from './../kit/keymap';
import Row from './row';

import './styles.css';

class SpreadsheetTable extends React.PureComponent {
    constructor(props) {
        super(props);

        this.onGlobalKeyDown = this.onGlobalKeyDown.bind(this);
        this.onGlobalClick = this.onGlobalClick.bind(this);
        this.onCellClick = this.onCellClick.bind(this);
        this.onCellDoubleClick = this.onCellDoubleClick.bind(this);
        this.getCellClassName = this.getCellClassName.bind(this);
        this.startColumnResize = this.startColumnResize.bind(this);
        this.processColumnResize = this.processColumnResize.bind(this);
        this.onWindowResize = this.onWindowResize.bind(this);

        this.state = {
            disabledCells: this.getDisabledCells(this.props.rows, this.props.checkDisabledCell)
        };

        if (this.props.focusedCell) {
            this.state.activeCell = this.props.focusedCell;
            this.state.focusedCell = this.props.focusedCell;

            this.skipGlobalClick = true;
        }

        this.widthValues = {};
    }

    componentDidMount() {
        if (this.props.columnsResize) {
            this.freezeTable();

            window.addEventListener('resize', this.onWindowResize, false);
            document.addEventListener('mousemove', this.processColumnResize, false);
            document.addEventListener('mouseup', () => {
                this.th = null;
            }, false);
        }

        document.addEventListener('keydown', this.onGlobalKeyDown, false);
        document.addEventListener('click', this.onGlobalClick, false);
    }

    componentWillReceiveProps(newProps) {
        if (this.props.rows !== newProps.rows && newProps.checkDisabledCell) {
            const disabledCells = this.getDisabledCells(newProps.rows, newProps.checkDisabledCell);
            const newState = {
                disabledCells
            };

            if (_.find(disabledCells, this.state.activeCell)) {
                newState.activeCell = null;
            }
            if (_.find(disabledCells, this.state.focusedCell)) {
                newState.focusedCell = null;
            }

            this.setState(newState);
        }

        if (newProps.focusedCell) {
            const newActiveCell = newProps.focusedCell;

            this.setState({
                activeCell: newActiveCell,
                focusedCell: newActiveCell
            });
            this.skipGlobalClick = true;
        }

        if (newProps.blurFocus) {
            this.setState({
                focusedCell: null
            });
        }
    }

    componentDidUpdate() {
        this.freezeTable();

        document.removeEventListener('keydown', this.onGlobalKeyDown, false);
        document.addEventListener('keydown', this.onGlobalKeyDown, false);
    }

    componentWillUnmount() {
        if (this.props.columnsResize) {
            window.removeEventListener('resize', this.onWindowResize, false);
            document.removeEventListener('mousemove', this.processColumnResize, false);
            document.removeEventListener('mouseup', () => {
                this.th = null;
            }, false);
        }

        document.removeEventListener('keydown', this.onGlobalKeyDown, false);
        document.removeEventListener('click', this.onGlobalClick, false);
    }

    onWindowResize() {
        this.freezeTable();
    }

    freezeTable() {
        const table = this.tableElement;
        const cells = table.querySelectorAll('th');

        // сбрасываем прописанную ширину колонок, чтобы они подстроились под ширину
        // таблицы автоматически
        cells.forEach((cell) => {
            cell.style.width = 'auto';
        });

        table.style.width = '100%';

        // фиксируем ширины в style
        cells.forEach((cell) => {
            if (this.widthValues[cell.cellIndex]) {
                // если есть сохранённое значение, подстраиваем его под новую ширину таблицы
                // в процентном соотношении
                cell.style.width = Math.round(this.widthValues[cell.cellIndex] * table.offsetWidth
                        / this.widthValues.tableWidth) + 'px';
            } else {
                cell.style.width = cell.offsetWidth + 'px';
            }
        });
    }

    startColumnResize(e) {
        this.th = e.currentTarget.offsetParent;
        this.th.startOffset = this.th.offsetWidth - e.pageX;
        this.currentTh = this.th.nextSibling;
        this.currentTh.startOffset = document.body.offsetWidth - this.currentTh.offsetWidth -
            e.pageX;
    }

    processColumnResize(e, nextTh) {
        let direction;
        let sibling;
        const table = this.tableElement;

        if (this.currentCoords <= e.pageX) {
            direction = 'toRight';
        }

        if (this.th) {
            table.style.width = '100%';

            if (direction === 'toRight') {
                const diff = this.th.startOffset + e.pageX - parseInt(this.th.style.width, 10);

                if (this.currentCoords) {
                    sibling = nextTh || this.th.nextSibling;
                }

                if (parseInt(this.th.style.width, 10) + diff > 100 &&
                    parseInt(sibling.style.width, 10) - diff > 100) {
                    this.th.style.width = parseInt(this.th.style.width, 10) + diff + 'px';
                    sibling.style.width = parseInt(sibling.style.width, 10) - diff + 'px';

                    this.widthValues[this.th.cellIndex] = parseInt(this.th.style.width);
                    this.widthValues[sibling.cellIndex] = parseInt(sibling.style.width);
                } else {
                    let cell;

                    if (nextTh) {
                        cell = nextTh.nextSibling;
                    } else {
                        cell = this.th.nextSibling.nextSibling;
                    }

                    if (cell) {
                        this.processColumnResize(e, cell);
                    }
                }
            } else {
                if (this.currentCoords) {
                    sibling = nextTh || this.th;
                }

                const diff = document.body.offsetWidth - e.pageX - this.currentTh.startOffset -
                    this.currentTh.offsetWidth;

                if (parseInt(sibling.style.width, 10) - diff > 100 &&
                    parseInt(this.currentTh.style.width, 10) + diff > 100) {
                    sibling.style.width = parseInt(sibling.style.width, 10) - diff + 'px';
                    this.currentTh.style.width = parseInt(this.currentTh.style.width, 10) + diff + 'px';

                    this.widthValues[this.currentTh.cellIndex] = parseInt(this.currentTh.style.width);
                    this.widthValues[sibling.cellIndex] = parseInt(sibling.style.width);
                } else {
                    let cell;

                    if (nextTh) {
                        cell = nextTh.previousSibling;
                    } else {
                        cell = this.th.previousSibling;
                    }

                    if (cell) {
                        this.processColumnResize(e, cell);
                    }
                }
            }

            if (this.widthValues) {
                this.widthValues.tableWidth = table.offsetWidth;

                if (this.props.onColumnResize) {
                    this.props.onColumnResize(this.widthValues);
                }
            }
        }

        this.currentCoords = e.pageX;
    }

    getDisabledCells(rows, checkDisabledCell) {
        const disabledCells = [];

        if (checkDisabledCell) {
            rows.forEach((row, x) => {
                this.props.columns.forEach((column, y) => {
                    if (checkDisabledCell(row, column.id)) {
                        disabledCells.push({ x, y });
                    }
                });
            });
        }

        return disabledCells;
    }

    onGlobalKeyDown(e) {
        const block = this;
        const columnsCount = this.props.columns.length;
        const rowsCount = this.props.rows.length;

        let newActiveCell = this.state.activeCell;
        let newFocusedCell = this.state.focusedCell;

        if (this.state.activeCell) {
            const { x, y } = this.state.activeCell;

            newFocusedCell = this.state.activeCell;

            function moveRight({ x, y }) {
                if (y < columnsCount - 1) {
                    newActiveCell = { x, y: y + 1 };
                } else if (x < rowsCount - 1) {
                    newActiveCell = { x: x + 1, y: 0 };
                }
                newFocusedCell = null;

                if (_.find(block.state.disabledCells, newActiveCell)) {
                    moveRight(newActiveCell);
                }
            }

            function moveDown({ x, y }) {
                if (x < rowsCount - 1) {
                    newActiveCell = { x: x + 1, y };
                }
                newFocusedCell = null;

                if (_.find(block.state.disabledCells, newActiveCell)) {
                    moveDown(newActiveCell);
                }
            }

            function moveUp({ x, y }) {
                if (x > 0) {
                    newActiveCell = { x: x - 1, y };
                }
                newFocusedCell = null;

                if (_.find(block.state.disabledCells, newActiveCell)) {
                    moveUp(newActiveCell);
                }
            }

            function moveLeft({ x, y }) {
                if (y > 0) {
                    newActiveCell = { x, y: y - 1 };
                } else if (x > 0) {
                    newActiveCell = { x: x - 1, y: columnsCount - 1 };
                }
                newFocusedCell = null;

                if (_.find(block.state.disabledCells, newActiveCell)) {
                    moveLeft(newActiveCell);
                }
            }

            if (!this.state.focusedCell) {
                if (e.keyCode === keys.RIGHT) {
                    moveRight({ x, y });
                }

                if (e.keyCode === keys.LEFT) {
                    moveLeft({ x, y });
                }

                if (e.keyCode === keys.UP) {
                    e.preventDefault();
                    moveUp({ x, y });
                }

                if (e.keyCode === keys.DOWN) {
                    e.preventDefault();
                    moveDown({ x, y });
                }

                if (e.keyCode === keys.ALT) {
                    newFocusedCell = null;
                }
            }

            if (e.keyCode === keys.ENTER) {
                if (this.state.focusedCell) {
                    moveDown({ x, y });
                    e.target.blur();
                } else {
                    newFocusedCell = this.state.activeCell;
                }
            }

            if (e.keyCode === keys.TAB) {
                if (this.state.focusedCell) {
                    moveRight({ x, y });
                    e.target.blur();
                } else {
                    newFocusedCell = this.state.activeCell;
                }

                e.preventDefault();
            }

            if (e.keyCode === keys.ESC) {
                if (this.state.focusedCell) {
                    e.target.blur();
                    newFocusedCell = null;
                }
            }

            this.setState({
                activeCell: newActiveCell,
                focusedCell: newFocusedCell
            });
        }
    }

    onGlobalClick() {
        if (!this.skipGlobalClick) {
            this.setState({
                activeCell: null,
                focusedCell: null
            });
        } else {
            this.skipGlobalClick = false;
        }
    }

    onCellClick(x, y, row, columnId, e) {
        if (!_.find(this.state.disabledCells, { x, y })) {
            if (!e.skipCellClick && !_.isEqual(this.state.focusedCell, { x, y })) {
                this.setState({
                    focusedCell: e.target !== e.currentTarget ? { x, y } : null,
                    activeCell: { x, y }
                });
            }
        }

        if (this.props.onCellClick) {
            this.props.onCellClick(row, columnId);
        }

        this.skipGlobalClick = true;
    }

    onCellDoubleClick(x, y) {
        if (!_.find(this.state.disabledCells, { x, y })) {
            this.setState({
                activeCell: { x, y },
                focusedCell: { x, y }
            });
        }
    }

    getCellClassName(column, row, x, y) {
        return classnames(
            'SpreadsheetTable__cell',
            _.isEqual(this.state.activeCell, { x, y }) && 'SpreadsheetTable__cell_active',
            _.isEqual(this.state.focusedCell, { x, y }) && 'SpreadsheetTable__cell_focused',
            this.props.checkDisabledCell && this.props.checkDisabledCell(row, column.id)
                && 'SpreadsheetTable__cell_disabled'
        );
    }

    calculatePosition() {
        return this.props.offset + (this.props.first > 0 ? this.props.headerHeight : 0) + 'px';
    }

    renderResizer() {
        return (

        );
    }

    renderHeader() {
        const columns = this.props.columns;

        return (
            <thead
                style={{
                    height: this.props.headerHeight + 'px',
                    display: `${this.props.first > 0 ? 'none' : ''}`,
                }}
            >

                {
                    columns.map((column, i) => {
                        return (

                                {typeof column.title === 'string' ? column.title : column.title()}
                                {this.props.columnsResize && this.renderResizer()}

                        );
                    })
                }

        );
    }

    renderBody() {
        const rows = [].concat(_.slice(
            this.props.rows,
            this.props.first,
            this.props.last
        ));
        const columns = this.props.columns;
        let body;

        if (rows.length) {
            body = rows.map((row) => {
                return (
                    <Row
                        x={_.findIndex(this.props.rows, row)}
                        key={this.props.getRowKey(row)}
                        columns={columns}
                        row={row}
                        getCellClassName={this.getCellClassName}
                        onCellClick={this.onCellClick}
                        onCellDoubleClick={this.onCellDoubleClick}
                        activeCell={this.state.activeCell}
                        focusedCell={this.state.focusedCell}
                        disabledCells={this.state.disabledCells}
                        height={this.props.cellHeight}
                    />
                );
            });
        } else {
            body = (

                            {this.props.placeholder}

            );
        }

        return body;
    }

    render() {
        return (

 { this.tableElement = tableElement; }}
            >
                {this.renderHeader()}

                    {this.renderBody()}

        );
    }
}

export const propTypes = {
    columns: PropTypes.arrayOf(
        PropTypes.shape({
            id: PropTypes.string,
            title: PropTypes.func.isRequired,
            value: PropTypes.func.isRequired
        })
    ).isRequired,
    rows: PropTypes.arrayOf(PropTypes.any),
    getRowKey: PropTypes.func.isRequired,
    placeholder: PropTypes.string,
    checkDisabledCell: PropTypes.func,
    focusedCell: PropTypes.shape({
        x: PropTypes.number.isRequired,
        y: PropTypes.number.isRequired
    }),
    onCellClick: PropTypes.func,
    blurFocus: PropTypes.bool,

    // scroll
    headerHeight: PropTypes.number,
    cellHeight: PropTypes.number,
    first: PropTypes.number,
    last: PropTypes.number,
    offset: PropTypes.string,

    // resize
    columnsResize: PropTypes.bool,
    onColumnResize: PropTypes.func,
    columnsWidth: PropTypes.object
};

SpreadsheetTable.propTypes = propTypes;

export default SpreadsheetTable;
<commit_msg>Fix default props<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import _ from 'lodash';
import keys from './../kit/keymap';
import Row from './row';

import './styles.css';

class SpreadsheetTable extends React.PureComponent {
    constructor(props) {
        super(props);

        this.onGlobalKeyDown = this.onGlobalKeyDown.bind(this);
        this.onGlobalClick = this.onGlobalClick.bind(this);
        this.onCellClick = this.onCellClick.bind(this);
        this.onCellDoubleClick = this.onCellDoubleClick.bind(this);
        this.getCellClassName = this.getCellClassName.bind(this);
        this.startColumnResize = this.startColumnResize.bind(this);
        this.processColumnResize = this.processColumnResize.bind(this);
        this.onWindowResize = this.onWindowResize.bind(this);

        this.state = {
            disabledCells: this.getDisabledCells(this.props.rows, this.props.checkDisabledCell)
        };

        if (this.props.focusedCell) {
            this.state.activeCell = this.props.focusedCell;
            this.state.focusedCell = this.props.focusedCell;

            this.skipGlobalClick = true;
        }

        this.widthValues = {};
    }

    componentDidMount() {
        if (this.props.columnsResize) {
            this.freezeTable();

            window.addEventListener('resize', this.onWindowResize, false);
            document.addEventListener('mousemove', this.processColumnResize, false);
            document.addEventListener('mouseup', () => {
                this.th = null;
            }, false);
        }

        document.addEventListener('keydown', this.onGlobalKeyDown, false);
        document.addEventListener('click', this.onGlobalClick, false);
    }

    componentWillReceiveProps(newProps) {
        if (this.props.rows !== newProps.rows && newProps.checkDisabledCell) {
            const disabledCells = this.getDisabledCells(newProps.rows, newProps.checkDisabledCell);
            const newState = {
                disabledCells
            };

            if (_.find(disabledCells, this.state.activeCell)) {
                newState.activeCell = null;
            }
            if (_.find(disabledCells, this.state.focusedCell)) {
                newState.focusedCell = null;
            }

            this.setState(newState);
        }

        if (newProps.focusedCell) {
            const newActiveCell = newProps.focusedCell;

            this.setState({
                activeCell: newActiveCell,
                focusedCell: newActiveCell
            });
            this.skipGlobalClick = true;
        }

        if (newProps.blurFocus) {
            this.setState({
                focusedCell: null
            });
        }
    }

    componentDidUpdate() {
        this.freezeTable();

        document.removeEventListener('keydown', this.onGlobalKeyDown, false);
        document.addEventListener('keydown', this.onGlobalKeyDown, false);
    }

    componentWillUnmount() {
        if (this.props.columnsResize) {
            window.removeEventListener('resize', this.onWindowResize, false);
            document.removeEventListener('mousemove', this.processColumnResize, false);
            document.removeEventListener('mouseup', () => {
                this.th = null;
            }, false);
        }

        document.removeEventListener('keydown', this.onGlobalKeyDown, false);
        document.removeEventListener('click', this.onGlobalClick, false);
    }

    onWindowResize() {
        this.freezeTable();
    }

    freezeTable() {
        const table = this.tableElement;
        const cells = table.querySelectorAll('th');

        // сбрасываем прописанную ширину колонок, чтобы они подстроились под ширину
        // таблицы автоматически
        cells.forEach((cell) => {
            cell.style.width = 'auto';
        });

        table.style.width = '100%';

        // фиксируем ширины в style
        cells.forEach((cell) => {
            if (this.widthValues[cell.cellIndex]) {
                // если есть сохранённое значение, подстраиваем его под новую ширину таблицы
                // в процентном соотношении
                cell.style.width = Math.round(this.widthValues[cell.cellIndex] * table.offsetWidth
                        / this.widthValues.tableWidth) + 'px';
            } else {
                cell.style.width = cell.offsetWidth + 'px';
            }
        });
    }

    startColumnResize(e) {
        this.th = e.currentTarget.offsetParent;
        this.th.startOffset = this.th.offsetWidth - e.pageX;
        this.currentTh = this.th.nextSibling;
        this.currentTh.startOffset = document.body.offsetWidth - this.currentTh.offsetWidth -
            e.pageX;
    }

    processColumnResize(e, nextTh) {
        let direction;
        let sibling;
        const table = this.tableElement;

        if (this.currentCoords <= e.pageX) {
            direction = 'toRight';
        }

        if (this.th) {
            table.style.width = '100%';

            if (direction === 'toRight') {
                const diff = this.th.startOffset + e.pageX - parseInt(this.th.style.width, 10);

                if (this.currentCoords) {
                    sibling = nextTh || this.th.nextSibling;
                }

                if (parseInt(this.th.style.width, 10) + diff > 100 &&
                    parseInt(sibling.style.width, 10) - diff > 100) {
                    this.th.style.width = parseInt(this.th.style.width, 10) + diff + 'px';
                    sibling.style.width = parseInt(sibling.style.width, 10) - diff + 'px';

                    this.widthValues[this.th.cellIndex] = parseInt(this.th.style.width);
                    this.widthValues[sibling.cellIndex] = parseInt(sibling.style.width);
                } else {
                    let cell;

                    if (nextTh) {
                        cell = nextTh.nextSibling;
                    } else {
                        cell = this.th.nextSibling.nextSibling;
                    }

                    if (cell) {
                        this.processColumnResize(e, cell);
                    }
                }
            } else {
                if (this.currentCoords) {
                    sibling = nextTh || this.th;
                }

                const diff = document.body.offsetWidth - e.pageX - this.currentTh.startOffset -
                    this.currentTh.offsetWidth;

                if (parseInt(sibling.style.width, 10) - diff > 100 &&
                    parseInt(this.currentTh.style.width, 10) + diff > 100) {
                    sibling.style.width = parseInt(sibling.style.width, 10) - diff + 'px';
                    this.currentTh.style.width = parseInt(this.currentTh.style.width, 10) + diff + 'px';

                    this.widthValues[this.currentTh.cellIndex] = parseInt(this.currentTh.style.width);
                    this.widthValues[sibling.cellIndex] = parseInt(sibling.style.width);
                } else {
                    let cell;

                    if (nextTh) {
                        cell = nextTh.previousSibling;
                    } else {
                        cell = this.th.previousSibling;
                    }

                    if (cell) {
                        this.processColumnResize(e, cell);
                    }
                }
            }

            if (this.widthValues) {
                this.widthValues.tableWidth = table.offsetWidth;

                if (this.props.onColumnResize) {
                    this.props.onColumnResize(this.widthValues);
                }
            }
        }

        this.currentCoords = e.pageX;
    }

    getDisabledCells(rows, checkDisabledCell) {
        const disabledCells = [];

        if (checkDisabledCell) {
            rows.forEach((row, x) => {
                this.props.columns.forEach((column, y) => {
                    if (checkDisabledCell(row, column.id)) {
                        disabledCells.push({ x, y });
                    }
                });
            });
        }

        return disabledCells;
    }

    onGlobalKeyDown(e) {
        const block = this;
        const columnsCount = this.props.columns.length;
        const rowsCount = this.props.rows.length;

        let newActiveCell = this.state.activeCell;
        let newFocusedCell = this.state.focusedCell;

        if (this.state.activeCell) {
            const { x, y } = this.state.activeCell;

            newFocusedCell = this.state.activeCell;

            function moveRight({ x, y }) {
                if (y < columnsCount - 1) {
                    newActiveCell = { x, y: y + 1 };
                } else if (x < rowsCount - 1) {
                    newActiveCell = { x: x + 1, y: 0 };
                }
                newFocusedCell = null;

                if (_.find(block.state.disabledCells, newActiveCell)) {
                    moveRight(newActiveCell);
                }
            }

            function moveDown({ x, y }) {
                if (x < rowsCount - 1) {
                    newActiveCell = { x: x + 1, y };
                }
                newFocusedCell = null;

                if (_.find(block.state.disabledCells, newActiveCell)) {
                    moveDown(newActiveCell);
                }
            }

            function moveUp({ x, y }) {
                if (x > 0) {
                    newActiveCell = { x: x - 1, y };
                }
                newFocusedCell = null;

                if (_.find(block.state.disabledCells, newActiveCell)) {
                    moveUp(newActiveCell);
                }
            }

            function moveLeft({ x, y }) {
                if (y > 0) {
                    newActiveCell = { x, y: y - 1 };
                } else if (x > 0) {
                    newActiveCell = { x: x - 1, y: columnsCount - 1 };
                }
                newFocusedCell = null;

                if (_.find(block.state.disabledCells, newActiveCell)) {
                    moveLeft(newActiveCell);
                }
            }

            if (!this.state.focusedCell) {
                if (e.keyCode === keys.RIGHT) {
                    moveRight({ x, y });
                }

                if (e.keyCode === keys.LEFT) {
                    moveLeft({ x, y });
                }

                if (e.keyCode === keys.UP) {
                    e.preventDefault();
                    moveUp({ x, y });
                }

                if (e.keyCode === keys.DOWN) {
                    e.preventDefault();
                    moveDown({ x, y });
                }

                if (e.keyCode === keys.ALT) {
                    newFocusedCell = null;
                }
            }

            if (e.keyCode === keys.ENTER) {
                if (this.state.focusedCell) {
                    moveDown({ x, y });
                    e.target.blur();
                } else {
                    newFocusedCell = this.state.activeCell;
                }
            }

            if (e.keyCode === keys.TAB) {
                if (this.state.focusedCell) {
                    moveRight({ x, y });
                    e.target.blur();
                } else {
                    newFocusedCell = this.state.activeCell;
                }

                e.preventDefault();
            }

            if (e.keyCode === keys.ESC) {
                if (this.state.focusedCell) {
                    e.target.blur();
                    newFocusedCell = null;
                }
            }

            this.setState({
                activeCell: newActiveCell,
                focusedCell: newFocusedCell
            });
        }
    }

    onGlobalClick() {
        if (!this.skipGlobalClick) {
            this.setState({
                activeCell: null,
                focusedCell: null
            });
        } else {
            this.skipGlobalClick = false;
        }
    }

    onCellClick(x, y, row, columnId, e) {
        if (!_.find(this.state.disabledCells, { x, y })) {
            if (!e.skipCellClick && !_.isEqual(this.state.focusedCell, { x, y })) {
                this.setState({
                    focusedCell: e.target !== e.currentTarget ? { x, y } : null,
                    activeCell: { x, y }
                });
            }
        }

        if (this.props.onCellClick) {
            this.props.onCellClick(row, columnId);
        }

        this.skipGlobalClick = true;
    }

    onCellDoubleClick(x, y) {
        if (!_.find(this.state.disabledCells, { x, y })) {
            this.setState({
                activeCell: { x, y },
                focusedCell: { x, y }
            });
        }
    }

    getCellClassName(column, row, x, y) {
        return classnames(
            'SpreadsheetTable__cell',
            _.isEqual(this.state.activeCell, { x, y }) && 'SpreadsheetTable__cell_active',
            _.isEqual(this.state.focusedCell, { x, y }) && 'SpreadsheetTable__cell_focused',
            this.props.checkDisabledCell && this.props.checkDisabledCell(row, column.id)
                && 'SpreadsheetTable__cell_disabled'
        );
    }

    calculatePosition() {
        return this.props.offset + (this.props.first > 0 ? this.props.headerHeight : 0) + 'px';
    }

    renderResizer() {
        return (

        );
    }

    renderHeader() {
        const columns = this.props.columns;

        return (
            <thead
                style={{
                    height: this.props.headerHeight + 'px',
                    display: `${this.props.first > 0 ? 'none' : ''}`,
                }}
            >

                {
                    columns.map((column, i) => {
                        return (

                                {typeof column.title === 'string' ? column.title : column.title()}
                                {this.props.columnsResize && this.renderResizer()}

                        );
                    })
                }

        );
    }

    renderBody() {
        const rows = [].concat(_.slice(
            this.props.rows,
            this.props.first,
            this.props.last
        ));
        const columns = this.props.columns;
        let body;

        if (rows.length) {
            body = rows.map((row) => {
                return (
                    <Row
                        x={_.findIndex(this.props.rows, row)}
                        key={this.props.getRowKey(row)}
                        columns={columns}
                        row={row}
                        getCellClassName={this.getCellClassName}
                        onCellClick={this.onCellClick}
                        onCellDoubleClick={this.onCellDoubleClick}
                        activeCell={this.state.activeCell}
                        focusedCell={this.state.focusedCell}
                        disabledCells={this.state.disabledCells}
                        height={this.props.cellHeight}
                    />
                );
            });
        } else {
            body = (

                            {this.props.placeholder}

            );
        }

        return body;
    }

    render() {
        return (

 { this.tableElement = tableElement; }}
            >
                {this.renderHeader()}

                    {this.renderBody()}

        );
    }
}

export const propTypes = {
    columns: PropTypes.arrayOf(
        PropTypes.shape({
            id: PropTypes.string,
            title: PropTypes.func.isRequired,
            value: PropTypes.func.isRequired
        })
    ).isRequired,
    rows: PropTypes.arrayOf(PropTypes.any),
    getRowKey: PropTypes.func.isRequired,
    placeholder: PropTypes.string,
    checkDisabledCell: PropTypes.func,
    focusedCell: PropTypes.shape({
        x: PropTypes.number.isRequired,
        y: PropTypes.number.isRequired
    }),
    onCellClick: PropTypes.func,
    blurFocus: PropTypes.bool,

    // scroll
    headerHeight: PropTypes.number,
    cellHeight: PropTypes.number,
    first: PropTypes.number,
    last: PropTypes.number,
    offset: PropTypes.string,

    // resize
    columnsResize: PropTypes.bool,
    onColumnResize: PropTypes.func,
    columnsWidth: PropTypes.object
};

SpreadsheetTable.defaultProps = {
    blurFocus: false
};

SpreadsheetTable.propTypes = propTypes;

export default SpreadsheetTable;
<|endoftext|>


<commit_before>var jsdom = require("jsdom");
var test = require('tape');

jsdom.env({
  html:'

',
  features: { QuerySelector:true }, //you need query selector for D3 to work
  scripts: ["https://cid-harvard.github.io/vis-toolkit/js/d3.js",
    "https://cid-harvard.github.io/vis-toolkit/build/vistk.js"],
  done: function (err, window) {

    var d3 = window.d3;
    var vistk = window.vistk;

    d3.json("https://cid-harvard.github.io/vis-toolkit-datasets/data/diversification_ubiquity_hs4_1995_2012.json", function(error, countries) {

      var flat_years = vistk.utils.flatten_years(countries);

      visualization = vistk.viz()
        .params({
          dev: true,
          type: 'scatterplot',
          margin: {top: 10, right: 10, bottom: 30, left: 30},
          container: '#viz',
          data: flat_years,
          var_id: 'name',
          var_group: 'continent',
          var_color: 'continent',
          var_x: 'nb_products',
          var_y: 'avg_products',
          var_r: 'nb_products',
          var_share: "avg_products",
          share_cutoff: function(d) { return d.avg_products > 30; },
          var_text: 'name_2char',
          time: {
            var_time: "year",
            current_time: "1995",
            parse: function(d) { return d; }
          },
          x_scale: [{
            domain: [0, 700]
          }],
          items: [{
            attr: "name",
            marks: [{
              var_mark: '__aggregated',
              type: d3.scale.ordinal().domain([true, false]).range(["none", "circle"]),
              rotate: "0"
            }, {
              var_mark: '__selected',
              type: d3.scale.ordinal().domain([true, false]).range(["star", "none"]),
              rotate: "0"
            }, {
              var_mark: '__aggregated',
              type: d3.scale.ordinal().domain([true, false]).range(["circle", "none"]),
              var_r: "nb_products",
              var_fill: "nb_products",
              rotate: 0,
              var_share: "avg_products"
            }, {
              type: "text",
              rotate: "-30"
            }]
          }],
          ui: {
            default: true,
            options: ["country", "continent"]
          },
          selection: ["France"],
          highlight: ["France", "Germany"],
          title: "World Countries"
        });

      d3.select("#viz").call(visualization);

      test('we should have 120 circles', function (t) {

        t.plan(1);
        t.equal(d3.selectAll('circle')[0].length, 120);

      });

      test('if time changes, we should still have 120 circles', function (t) {

        visualization.params().time.current_time = 2012;
        d3.select("#viz").call(visualization);

        t.plan(2);
        t.equal(visualization.params().time.current_time, 2012);
        t.equal(d3.selectAll('circle')[0].length, 120);

      });

      test('if filter by continent Oceania only 3 circles left', function (t) {

        visualization.params().filter = ['Oceania'];
        d3.select("#viz").call(visualization);

        t.plan(2);
        t.deepEqual(visualization.params().filter, ['Oceania']);
        t.equal(d3.selectAll('circle')[0].length, 3);

      });

      test('if filter is reset, back to 120 circles', function (t) {

        visualization.params().filter = [];
        d3.select("#viz").call(visualization);

        t.plan(2);
        t.deepEqual(visualization.params().filter, []);
        t.equal(d3.selectAll('circle')[0].length, 120);

      });

    });

  }
});
<commit_msg>Progress on scatterplot tests<commit_after>var jsdom = require("jsdom");
var test = require('tape');

jsdom.env({
  html:'

',
  features: { QuerySelector:true }, //you need query selector for D3 to work
  scripts: ["https://cid-harvard.github.io/vis-toolkit/js/d3.js",
    "https://cid-harvard.github.io/vis-toolkit/build/vistk.js"],
  done: function (err, window) {

    var d3 = window.d3;
    var vistk = window.vistk;

    d3.json("https://cid-harvard.github.io/vis-toolkit-datasets/data/diversification_ubiquity_hs4_1995_2012.json", function(error, countries) {

      var flat_years = vistk.utils.flatten_years(countries);

      visualization = vistk.viz()
        .params({
          dev: true,
          type: 'scatterplot',
          margin: {top: 10, right: 10, bottom: 30, left: 30},
          container: '#viz',
          data: flat_years,
          var_id: 'name',
          var_group: 'continent',
          var_color: 'continent',
          var_x: 'nb_products',
          var_y: 'avg_products',
          var_r: 'nb_products',
          time: {
            var_time: "year",
            current_time: "1995",
            parse: function(d) { return d; }
          },
          x_scale: [{
            domain: [0, 700]
          }],
          items: [{
            marks: [{
              type: 'circle',
            }]
          }]
        });

      d3.select("#viz").call(visualization);

      test('we should have 120 circles', function (t) {

        t.plan(1);
        t.equal(d3.selectAll('circle')[0].length, 120);

      });

      test('if time changes, we should still have 120 circles', function (t) {

        visualization.params().time.current_time = 2012;
        d3.select("#viz").call(visualization);

        t.plan(2);
        t.equal(visualization.params().time.current_time, 2012);
        t.equal(d3.selectAll('circle')[0].length, 120);

      });

      test('if filter by continent Oceania only 3 circles left', function (t) {

        visualization.params().filter = ['Oceania'];
        d3.select("#viz").call(visualization);

        t.plan(2);
        t.deepEqual(visualization.params().filter, ['Oceania']);
        t.equal(d3.selectAll('circle')[0].length, 3);

      });

      test('if filter is reset, back to 120 circles', function (t) {

        visualization.params().filter = [];
        visualization.params().refresh = true;
        d3.select("#viz").call(visualization);

        t.plan(2);
        t.deepEqual(visualization.params().filter, []);
        t.equal(d3.selectAll('circle')[0].length, 120);

      });

      test('if aggregate by continent, only 120 circles', function (t) {

        visualization.params().filter = [];
        d3.select("#viz").call(visualization);

        t.plan(2);
        t.deepEqual(visualization.params().filter, []);
        t.equal(d3.selectAll('circle')[0].length, 120);

      });

    });

  }
});
<|endoftext|>


<commit_before>/* eslint-env mocha, jest */

import React from 'react';
import { shallow } from 'enzyme';

import DueDate from '../DueDate';

it('should render', () => {
  shallow(<DueDate />);
});
it('should be a <span />', () => {
  const dueDate = shallow(<DueDate />);
  expect(dueDate.is('span.DueDate')).toEqual(true);
});
it('should show icon if due date is close', () => {
  const dueDate = shallow(<DueDate due="today" />);
  expect(dueDate.find('small').length).toBeGreaterThan(0);
});
<commit_msg>Update Task DueDate unit test<commit_after>/* eslint-env mocha, jest */

import React from 'react';
import { shallow } from 'enzyme';

import DueDate from '../DueDate';

it('should render', () => {
  shallow(<DueDate />);
});
it('should be a <span />', () => {
  const dueDate = shallow(<DueDate />);
  expect(dueDate.is('span.DueDate')).toEqual(true);
});
it('should show icon if due date is tomorrow', () => {
  const dueDate = shallow(<DueDate due="tomorrow" />);
  expect(dueDate.find('small').length).toBeGreaterThan(0);
});
it('should show icon if due date is today', () => {
  const dueDate = shallow(<DueDate due="today" />);
  expect(dueDate.find('small').length).toBeGreaterThan(0);
});
<|endoftext|>


<commit_before>/* jshint node: true */

/**
 * Gulp Watch Task
 *
 * This is a very simple task that watches source directories for changes,
 * and re-runs build tasks accordingly.
 *
 * Note: gaze (the library that gulp.watch and gulp-watch use to do filesystem
 * watching) does _not_ detect new files nor deleted files, only modified files.
 * New versions of gaze will fix this, and updating gulp should just integrate
 * these fixes.
 *
 * Also note: we use gulp-watch here because we've always used it, and it logs
 * events in a nice way. We don't use gulp-watch to its full potential, and
 * could perhaps use gulp.watch if we wrote a custom logger (which is easy).
 *
 * @example
 *     gulp watch
 *
 * @package Esensi\Build
 * @author daniel <daniel@emersonmedia.com>
 * @author matt <matt@emersonmedia.com>
 * @copyright 2014 Emerson Media LP
 * @license https://github.com/esensi/build/blob/master/LICENSE.txt MIT License
 * @link http://www.emersonmedia.com
 *
 * @todo This task could one day make use of watchify for JavaScript, however this
 *       has been tried multiple times and watchify is REALLY hard to work with,
 *       so be warned...
 *
 * @todo This task could make good use of gulp-notify to alert on any errors. This
 *       will probably require a refactor of the build tasks, though.
 */

"use strict";

var gulp     = require('gulp');
var sequence = require('run-sequence');
var watch    = require('gulp-watch');
var config   = global.configOpts;

// Alias build:watch to watch
gulp.task('build:watch', ['watch']);

// Watch source directories for changes
// and run appropriate build subtask.
// Runs clean task first to prepare build.
gulp.task('watch', ['clean'], function()
{
    // Run build at start then begins watching
    gulp.start('build');

    // Watch scripts
    watch(config.scripts.watch, {'name': 'scripts'}, function()
    {
        // Scripts run clean:scripts before building
        gulp.start('build:scripts');
    });

    // Watch styles
    watch(config.styles.watch, {'name': 'styles'}, function()
    {
        // Styles run clean:styles before building
        gulp.start('build:styles');
    });

    // Watch images
    watch(config.images.watch, {'name': 'images'}, function()
    {
        // Use sequence to ensure synchronicity
        sequence(['clean:images', 'build:images']);
    });

    // Watch fonts
    watch(config.fonts.watch, {'name': 'fonts'}, function()
    {
        // Use sequence to ensure synchronicity
        sequence(['clean:fonts', 'build:fonts']);
    });

});
<commit_msg>Fixed missing config parameter.<commit_after>/* jshint node: true */

/**
 * Gulp Watch Task
 *
 * This is a very simple task that watches source directories for changes,
 * and re-runs build tasks accordingly.
 *
 * Note: gaze (the library that gulp.watch and gulp-watch use to do filesystem
 * watching) does _not_ detect new files nor deleted files, only modified files.
 * New versions of gaze will fix this, and updating gulp should just integrate
 * these fixes.
 *
 * Also note: we use gulp-watch here because we've always used it, and it logs
 * events in a nice way. We don't use gulp-watch to its full potential, and
 * could perhaps use gulp.watch if we wrote a custom logger (which is easy).
 *
 * @example
 *     gulp watch
 *
 * @package Esensi\Build
 * @author daniel <daniel@emersonmedia.com>
 * @author matt <matt@emersonmedia.com>
 * @copyright 2014 Emerson Media LP
 * @license https://github.com/esensi/build/blob/master/LICENSE.txt MIT License
 * @link http://www.emersonmedia.com
 *
 * @todo This task could one day make use of watchify for JavaScript, however this
 *       has been tried multiple times and watchify is REALLY hard to work with,
 *       so be warned...
 *
 * @todo This task could make good use of gulp-notify to alert on any errors. This
 *       will probably require a refactor of the build tasks, though.
 */

"use strict";

var gulp     = require('gulp');
var sequence = require('run-sequence');
var watch    = require('gulp-watch');
var config   = global.buildOptions;

// Alias build:watch to watch
gulp.task('build:watch', ['watch']);

// Watch source directories for changes
// and run appropriate build subtask.
// Runs clean task first to prepare build.
gulp.task('watch', ['clean'], function()
{
    // Run build at start then begins watching
    gulp.start('build');

    // Watch scripts
    watch(config.scripts.watch, {'name': 'scripts'}, function()
    {
        // Scripts run clean:scripts before building
        gulp.start('build:scripts');
    });

    // Watch styles
    watch(config.styles.watch, {'name': 'styles'}, function()
    {
        // Styles run clean:styles before building
        gulp.start('build:styles');
    });

    // Watch images
    watch(config.images.watch, {'name': 'images'}, function()
    {
        // Use sequence to ensure synchronicity
        sequence(['clean:images', 'build:images']);
    });

    // Watch fonts
    watch(config.fonts.watch, {'name': 'fonts'}, function()
    {
        // Use sequence to ensure synchronicity
        sequence(['clean:fonts', 'build:fonts']);
    });

});
<|endoftext|>


<commit_before>var fs = require('fs');
var csv = require('csv-parser');
var Q = require('q');
var _ = require('lodash');
var lut = {};
function addToLut(keyProp, data) {
  var key, value;
  key = data[keyProp];
  value = lut[key];
  if(!value) {
    value = [data];
    lut[key] = value;
  }
  else {
    value.push(data);
  }
}

var start = new Date().getTime();
var last = start;
var count = 0;
var deferred = Q.defer();
fs.createReadStream('./homologue_edge.txt')
  .pipe(csv({separator: '\t' }))
  .on('data', function(data) {
    var now = new Date().getTime();
    addToLut(':START_ID(Gene)', data);
    addToLut(':END_ID(Gene)', data);
    ++count;
    if(now - last > 10000) {
      console.log(count, _.size(lut));
      last = now;
    }
  })
  .on('end', function() {
    console.log('Done in ', (new Date().getTime() - start) / 1000, ' seconds');
    console.log(count, _.size(lut));
    deferred.resolve(lut);
  });

module.exports = deferred.promise;

<commit_msg>remove node LUT<commit_after><|endoftext|>


<commit_before>/**
Template Controllers

@module Templates
*/

/**
The chats action bar template

@class [template] views_chats_actionbar
@constructor
*/

Template['views_chats_actionbar'].events({
    /**
    Show a modal and leave the chat

    @event click button.leave-chat
    */
    'click button.leave-chat': function(e, template){

        Router.current().render('elements_modal', {to: 'modal'});
        Router.current().render('elements_modal_question', {
            to: 'modalContent',
            data: {
                text: TAPi18n.__('whisper.chat.texts.leaveChat'),

                // DELETE the chat on OK
                ok: function(){
                    // delete all messages
                    _.each(Messages.find({_id: {$in: template.data.messages}}).fetch(), function(item){
                        Messages.remove(item._id);
                    });

                    Router.go('/chat/public');

                    // delete the chat itself, after redirect
                    // (so the chat route, doesn't try to re-create the chat)
                    Tracker.afterFlush(function(){
                        Chats.remove(template.data._id);
                    });
                },
                cancel: true
            }
        });
    }
});<commit_msg>go to home when leaving chat<commit_after>/**
Template Controllers

@module Templates
*/

/**
The chats action bar template

@class [template] views_chats_actionbar
@constructor
*/

Template['views_chats_actionbar'].events({
    /**
    Show a modal and leave the chat

    @event click button.leave-chat
    */
    'click button.leave-chat': function(e, template){

        Router.current().render('elements_modal', {to: 'modal'});
        Router.current().render('elements_modal_question', {
            to: 'modalContent',
            data: {
                text: TAPi18n.__('whisper.chat.texts.leaveChat'),

                // DELETE the chat on OK
                ok: function(){
                    // delete all messages
                    _.each(Messages.find({_id: {$in: template.data.messages}}).fetch(), function(item){
                        Messages.remove(item._id);
                    });

                    Router.go('home');

                    // delete the chat itself, after redirect
                    // (so the chat route, doesn't try to re-create the chat)
                    Tracker.afterFlush(function(){
                        Chats.remove(template.data._id);
                    });
                },
                cancel: true
            }
        });
    }
});<|endoftext|>


<commit_before>var sinon = require('sinon');
var assert = require('chai').assert;
var rewire = require('rewire');

describe('Admin Module:', function() {
  var admin;
  var clients;
  var options;
  var adminModule;

  before(function() {
    clients = {
      totalUsers: 17,
      user16: {},
      user12: {}
    };
    options = {
      MATH_PROGRAM: "MyProgram"
    };
    adminModule = rewire('../lib/admin.js');
    admin = adminModule(clients, options);
  });

  describe('Admin.stats()', function() {
    it('should get the stats', function(done) {
      var spy;
      var response = {
        writeHead: function(statusCode, headers) {
          assert.equal(statusCode, 200);
          assert.deepEqual(headers, {"Content-Type": "text/html"});
        },
        write: function() {
        },
        end: function() {
          assert.equal(spy.callCount, 5);

          var firstArgument = "<link rel=\"stylesheet\" " +
              "href=\"mathProgram.css\" " +
              "type=\"text/css\" " +
              "media=\"screen\">";
          assert.equal(spy.firstCall.args[0], firstArgument);

          var secondArgument = "
MyProgram User Statistics
";
          assert.equal(spy.secondCall.args[0], secondArgument);

          var thirdArgument = "There are currently 2 users " +
              "using MyProgram.
";
          assert.equal(spy.thirdCall.args[0], thirdArgument);

          var fourthArgument = "In total, there were 17 new " +
              "users since the server started.
";
          assert.equal(spy.getCall(3).args[0], fourthArgument);

          done();
        }
      };
      spy = sinon.spy(response, "write");
      admin.stats(null, response);
    });
  });
  describe('Number of current users', function() {
    it('should be correct', function() {
      var currentUsers = adminModule.__get__("currentUsers");
      assert.equal(currentUsers(clients), 2);
    });
  });
  describe('Number of total users', function() {
    it('should be correct', function() {
      var totalUsers = adminModule.__get__("totalUsers");
      assert.equal(totalUsers(clients), 17);
    });
  });
  describe('Adding new users', function() {
    it('should change the stats', function() {
      var newUserId = 'user100';
      clients[newUserId] = {};
      var currentUsers = adminModule.__get__("currentUsers");
      assert.equal(currentUsers(clients), 3);
    });
  });
  describe('Deleting users', function() {
    it('should change the stats', function() {
      delete clients.user12;
      var currentUsers = adminModule.__get__("currentUsers");
      assert.equal(currentUsers(clients), 2);
    });
  });
  describe('Deleting users', function() {
    it('should not change total users', function() {
      delete clients.user12;
      var totalUsers = adminModule.__get__("totalUsers");
      assert.equal(totalUsers(clients), 17);
    });
  });
});
<commit_msg>Use beforeEach to set clients in test<commit_after>var sinon = require('sinon');
var assert = require('chai').assert;
var rewire = require('rewire');

describe('Admin Module:', function() {
  var admin;
  var clients;
  var options;
  var adminModule;

  before(function() {
    options = {
      MATH_PROGRAM: "MyProgram"
    };
    adminModule = rewire('../lib/admin.js');
  });

  beforeEach(function() {
    clients = {
      totalUsers: 17,
      user16: {},
      user12: {}
    };
    admin = adminModule(clients, options);
  });

  describe('Admin.stats()', function() {
    it('should get the stats', function(done) {
      var spy;
      var response = {
        writeHead: function(statusCode, headers) {
          assert.equal(statusCode, 200);
          assert.deepEqual(headers, {"Content-Type": "text/html"});
        },
        write: function() {
        },
        end: function() {
          assert.equal(spy.callCount, 5);

          var firstArgument = "<link rel=\"stylesheet\" " +
              "href=\"mathProgram.css\" " +
              "type=\"text/css\" " +
              "media=\"screen\">";
          assert.equal(spy.firstCall.args[0], firstArgument);

          var secondArgument = "
MyProgram User Statistics
";
          assert.equal(spy.secondCall.args[0], secondArgument);

          var thirdArgument = "There are currently 2 users " +
              "using MyProgram.
";
          assert.equal(spy.thirdCall.args[0], thirdArgument);

          var fourthArgument = "In total, there were 17 new " +
              "users since the server started.
";
          assert.equal(spy.getCall(3).args[0], fourthArgument);

          done();
        }
      };
      spy = sinon.spy(response, "write");
      admin.stats(null, response);
    });
  });
  describe('Number of current users', function() {
    it('should be correct', function() {
      var currentUsers = adminModule.__get__("currentUsers");
      assert.equal(currentUsers(clients), 2);
    });
  });
  describe('Number of total users', function() {
    it('should be correct', function() {
      var totalUsers = adminModule.__get__("totalUsers");
      assert.equal(totalUsers(clients), 17);
    });
  });
  describe('Adding new users', function() {
    it('should change the stats', function() {
      var newUserId = 'user100';
      clients[newUserId] = {};
      var currentUsers = adminModule.__get__("currentUsers");
      assert.equal(currentUsers(clients), 3);
    });
  });
  describe('Deleting users', function() {
    it('should change the stats', function() {
      delete clients.user12;
      var currentUsers = adminModule.__get__("currentUsers");
      assert.equal(currentUsers(clients), 1);
    });
  });
  describe('Deleting users', function() {
    it('should not change total users', function() {
      delete clients.user12;
      var totalUsers = adminModule.__get__("totalUsers");
      assert.equal(totalUsers(clients), 17);
    });
  });
});
<|endoftext|>


<commit_before>import React from 'react';

import SpellLink from 'common/SpellLink';
import SpellIcon from 'common/SpellIcon';
import Icon from 'common/Icon';

function formatThousands(number) {
  return (Math.round(number || 0) + '').replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
}
function formatNumber(number) {
  if (number > 1000000) {
    return `${(number / 1000000).toFixed(2)}m`;
  }
  if (number > 10000) {
    return `${Math.round(number / 1000)}k`;
  }
  return formatThousands(number);
}
const formatDuration = (duration) => {
  const seconds = Math.floor(duration % 60);
  return `${Math.floor(duration / 60)}:${seconds < 10 ? `0${seconds}` : seconds}`;
};
function formatPercentage(percentage) {
  return (Math.round((percentage || 0) * 10000) / 100).toFixed(2);
}

class Cooldown extends React.Component {
  static propTypes = {
    fightStart: React.PropTypes.number.isRequired,
    fightEnd: React.PropTypes.number.isRequired,
    ShowStatistics: React.PropTypes.bool,
    showResourceStatistics: React.PropTypes.bool,
    cooldown: React.PropTypes.shape({
      ability: React.PropTypes.shape({
        id: React.PropTypes.number.isRequired,
        name: React.PropTypes.string.isRequired,
        icon: React.PropTypes.string.isRequired,
      }),
      start: React.PropTypes.number.isRequired,
      end: React.PropTypes.number,
      events: React.PropTypes.arrayOf(React.PropTypes.shape({
        type: React.PropTypes.string.isRequired,
      })).isRequired,
    }).isRequired,
  };

  constructor() {
    super();
    this.state = {
      expanded: false,
      showHeals: false,
    };
    this.handleExpandClick = this.handleExpandClick.bind(this);
    this.handleShowHealsClick = this.handleShowHealsClick.bind(this);
  }

  handleExpandClick() {
    this.setState({
      expanded: !this.state.expanded,
    });
  }
  handleShowHealsClick() {
    this.setState({
      showHeals: !this.state.showHeals,
    });
  }

  groupHeals(events) {
    let lastHeal = null;
    let results = [];
    events.forEach((event) => {
      if (event.type === 'cast') {
        results.push(event);
      } else if (event.type === 'heal') {
        const spellId = event.ability.guid;
        if (lastHeal && lastHeal.ability.guid === spellId) {
          lastHeal.count += 1;
          lastHeal.amount += event.amount;
          lastHeal.absorbed += (event.absorbed || 0);
          lastHeal.overheal += (event.overheal || 0);
        } else {
          const heal = {
            ...event,
            count: 1,
            absorbed: event.absorbed || 0,
            overheal: event.overheal || 0,
          };
          results.push(heal);
          lastHeal = heal;
        }
      }
    });
    return results;
  }

  calculateHealingStatistics(cooldown) {
    let healingDone = 0, overhealingDone = 0
    cooldown.events.filter(event => event.type === 'heal' || event.type === 'absorbed').forEach((event) => {
      healingDone += event.amount + (event.absorbed || 0);
      overhealingDone += event.overheal || 0;
    });

    return {
      healingDone,
      overhealingDone
    };
  }

  calculateDamageStatistics(cooldown) {
    let damageDone = cooldown.events.reduce((acc, event) => {
      return event.type === 'damage' ? acc + event.amount : acc
    }, 0)

    return {damageDone}
  }

  render() {
    const { cooldown, fightStart, fightEnd, showOutputStatistics, showResourceStatistics } = this.props;

    let outputStatistics, resourceStatistics;

    if (showOutputStatistics) {
      outputStatistics = cooldown.ability.cooldownType === 'HEALING' ? this.calculateHealingStatistics(cooldown) : this.calculateDamageStatistics(cooldown);
    }
    if (showResourceStatistics) {
      resourceStatistics = cooldown.events.filter(event => event.type === 'cast').reduce((mana, event) => mana + (event.manaCost || 0), 0);
    }

    const start = cooldown.start;
    const end = cooldown.end || fightEnd;

    /* eslint-disable no-script-url */

    return (

          <SpellIcon id={cooldown.ability.id} />

              <SpellLink id={cooldown.ability.id} /> ({formatDuration((start - fightStart) / 1000)} -> {formatDuration((end - fightStart) / 1000)})

            {!this.state.expanded && (

                {cooldown.events.filter(event => event.type === 'cast' && event.ability.guid !== 1).map((event, i) => (
                  <SpellLink key={`${event.ability.guid}-${event.timestamp}-${i}`} id={event.ability.guid}>
                    <Icon icon={event.ability.abilityIcon} alt={event.ability.name} style={{ height: 23, marginRight: 4 }} />
                  </SpellLink>
                ))}

                More

            )}
            {this.state.expanded && !this.state.showHeals && (

                {cooldown.events.filter(event => event.type === 'cast' && event.ability.guid !== 1).map((event, i) => (

                      {((event.timestamp - cooldown.start) / 1000).toFixed(3)}

                      <SpellLink key={`${event.ability.guid}-${event.timestamp}-${i}`} id={event.ability.guid}>
                        <Icon icon={event.ability.abilityIcon} alt={event.ability.name} style={{ height: 23, marginRight: 4 }} /> {event.ability.name}
                      </SpellLink>

                ))}
                Even more{' | '}
                Show less

            )}
            {this.state.expanded && this.state.showHeals && (

                {this.groupHeals(cooldown.events.filter(event => (event.type === 'cast' || event.type === 'heal') && event.ability.guid !== 1)).map((event, i) => (

                      {((event.timestamp - cooldown.start) / 1000).toFixed(3)}

                    <div className={`col-xs-4 ${event.type === 'heal' ? 'col-xs-offset-1' : ''}`}>
                      <SpellLink key={`${event.ability.guid}-${event.timestamp}-${i}`} id={event.ability.guid}>
                        <Icon icon={event.ability.abilityIcon} alt={event.ability.name} style={{ height: 23, marginRight: 4 }} /> {event.ability.name}
                      </SpellLink>
                      {event.type === 'heal' ? ` x ${event.count}` : ''}

                      {event.type === 'heal' ? formatThousands(event.amount + event.absorbed) : ''}

                ))}
                Show less {' | '}
                Show simple

            )}

          {
            cooldown.ability.cooldownType === 'HEALING' && showOutputStatistics && (

{formatNumber(outputStatistics.healingDone)}

                  <dfn data-tip="This includes all healing the occured while the buff was up, even if it was not triggered by spells cast inside the buff duration. Any delayed healing such as HOTs, Absorbs and Atonements will stop contributing to the healing done when the cooldown buff expires, so this value is lower for any specs with such abilities.">healing ({formatNumber(outputStatistics.healingDone / (end - start) * 1000)} HPS)</dfn>

{formatPercentage(outputStatistics.overhealingDone / (outputStatistics.healingDone + outputStatistics.overhealingDone))}%

                  <dfn data-tip="This includes all healing the occured while the buff was up, even if it was not triggered by spells cast inside the buff duration. Any delayed healing such as HOTs, Absorbs and Atonements will stop contributing to the healing done when the cooldown buff expires, so this value is lower for any specs with such abilities.">overhealing</dfn>

            )
          }
          {
            cooldown.ability.cooldownType === 'DAMAGE' && showOutputStatistics && (

{formatNumber(outputStatistics.damageDone)}

                  <dfn data-tip="This number represents the total amount of damage done during the duration of this cooldown, any damage done by DOTs after the effect of this cooldown has exprired will not be included in this statistic.">Damage Done</dfn>

{formatNumber(outputStatistics.damageDone / (end - start) * 1000)}

                  <dfn data-tip="This number represents the total amount of DPS done during the duration of this cooldown, any damage done by DOTs after the effect of this cooldown has exprired will not be included in this statistic.">DPS Done</dfn>

            )
          }
          {
            showResourceStatistics && (

{formatNumber(resourceStatistics)}

                mana used

            )
          }

    );
  }
}

export default Cooldown;
<commit_msg>(fix) Cooldown damage should have the same layout like healing `#damange and damange (#DPS DPS)`.<commit_after>import React from 'react';

import SpellLink from 'common/SpellLink';
import SpellIcon from 'common/SpellIcon';
import Icon from 'common/Icon';

function formatThousands(number) {
  return (Math.round(number || 0) + '').replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
}
function formatNumber(number) {
  if (number > 1000000) {
    return `${(number / 1000000).toFixed(2)}m`;
  }
  if (number > 10000) {
    return `${Math.round(number / 1000)}k`;
  }
  return formatThousands(number);
}
const formatDuration = (duration) => {
  const seconds = Math.floor(duration % 60);
  return `${Math.floor(duration / 60)}:${seconds < 10 ? `0${seconds}` : seconds}`;
};
function formatPercentage(percentage) {
  return (Math.round((percentage || 0) * 10000) / 100).toFixed(2);
}

class Cooldown extends React.Component {
  static propTypes = {
    fightStart: React.PropTypes.number.isRequired,
    fightEnd: React.PropTypes.number.isRequired,
    ShowStatistics: React.PropTypes.bool,
    showResourceStatistics: React.PropTypes.bool,
    cooldown: React.PropTypes.shape({
      ability: React.PropTypes.shape({
        id: React.PropTypes.number.isRequired,
        name: React.PropTypes.string.isRequired,
        icon: React.PropTypes.string.isRequired,
      }),
      start: React.PropTypes.number.isRequired,
      end: React.PropTypes.number,
      events: React.PropTypes.arrayOf(React.PropTypes.shape({
        type: React.PropTypes.string.isRequired,
      })).isRequired,
    }).isRequired,
  };

  constructor() {
    super();
    this.state = {
      expanded: false,
      showHeals: false,
    };
    this.handleExpandClick = this.handleExpandClick.bind(this);
    this.handleShowHealsClick = this.handleShowHealsClick.bind(this);
  }

  handleExpandClick() {
    this.setState({
      expanded: !this.state.expanded,
    });
  }
  handleShowHealsClick() {
    this.setState({
      showHeals: !this.state.showHeals,
    });
  }

  groupHeals(events) {
    let lastHeal = null;
    let results = [];
    events.forEach((event) => {
      if (event.type === 'cast') {
        results.push(event);
      } else if (event.type === 'heal') {
        const spellId = event.ability.guid;
        if (lastHeal && lastHeal.ability.guid === spellId) {
          lastHeal.count += 1;
          lastHeal.amount += event.amount;
          lastHeal.absorbed += (event.absorbed || 0);
          lastHeal.overheal += (event.overheal || 0);
        } else {
          const heal = {
            ...event,
            count: 1,
            absorbed: event.absorbed || 0,
            overheal: event.overheal || 0,
          };
          results.push(heal);
          lastHeal = heal;
        }
      }
    });
    return results;
  }

  calculateHealingStatistics(cooldown) {
    let healingDone = 0, overhealingDone = 0
    cooldown.events.filter(event => event.type === 'heal' || event.type === 'absorbed').forEach((event) => {
      healingDone += event.amount + (event.absorbed || 0);
      overhealingDone += event.overheal || 0;
    });

    return {
      healingDone,
      overhealingDone
    };
  }

  calculateDamageStatistics(cooldown) {
    let damageDone = cooldown.events.reduce((acc, event) => {
      return event.type === 'damage' ? acc + event.amount : acc
    }, 0)

    return {damageDone}
  }

  render() {
    const { cooldown, fightStart, fightEnd, showOutputStatistics, showResourceStatistics } = this.props;

    let outputStatistics, resourceStatistics;

    if (showOutputStatistics) {
      outputStatistics = cooldown.ability.cooldownType === 'HEALING' ? this.calculateHealingStatistics(cooldown) : this.calculateDamageStatistics(cooldown);
    }
    if (showResourceStatistics) {
      resourceStatistics = cooldown.events.filter(event => event.type === 'cast').reduce((mana, event) => mana + (event.manaCost || 0), 0);
    }

    const start = cooldown.start;
    const end = cooldown.end || fightEnd;

    /* eslint-disable no-script-url */

    return (

          <SpellIcon id={cooldown.ability.id} />

              <SpellLink id={cooldown.ability.id} /> ({formatDuration((start - fightStart) / 1000)} -> {formatDuration((end - fightStart) / 1000)})

            {!this.state.expanded && (

                {cooldown.events.filter(event => event.type === 'cast' && event.ability.guid !== 1).map((event, i) => (
                  <SpellLink key={`${event.ability.guid}-${event.timestamp}-${i}`} id={event.ability.guid}>
                    <Icon icon={event.ability.abilityIcon} alt={event.ability.name} style={{ height: 23, marginRight: 4 }} />
                  </SpellLink>
                ))}

                More

            )}
            {this.state.expanded && !this.state.showHeals && (

                {cooldown.events.filter(event => event.type === 'cast' && event.ability.guid !== 1).map((event, i) => (

                      {((event.timestamp - cooldown.start) / 1000).toFixed(3)}

                      <SpellLink key={`${event.ability.guid}-${event.timestamp}-${i}`} id={event.ability.guid}>
                        <Icon icon={event.ability.abilityIcon} alt={event.ability.name} style={{ height: 23, marginRight: 4 }} /> {event.ability.name}
                      </SpellLink>

                ))}
                Even more{' | '}
                Show less

            )}
            {this.state.expanded && this.state.showHeals && (

                {this.groupHeals(cooldown.events.filter(event => (event.type === 'cast' || event.type === 'heal') && event.ability.guid !== 1)).map((event, i) => (

                      {((event.timestamp - cooldown.start) / 1000).toFixed(3)}

                    <div className={`col-xs-4 ${event.type === 'heal' ? 'col-xs-offset-1' : ''}`}>
                      <SpellLink key={`${event.ability.guid}-${event.timestamp}-${i}`} id={event.ability.guid}>
                        <Icon icon={event.ability.abilityIcon} alt={event.ability.name} style={{ height: 23, marginRight: 4 }} /> {event.ability.name}
                      </SpellLink>
                      {event.type === 'heal' ? ` x ${event.count}` : ''}

                      {event.type === 'heal' ? formatThousands(event.amount + event.absorbed) : ''}

                ))}
                Show less {' | '}
                Show simple

            )}

          {
            cooldown.ability.cooldownType === 'HEALING' && showOutputStatistics && (

{formatNumber(outputStatistics.healingDone)}

                  <dfn data-tip="This includes all healing the occured while the buff was up, even if it was not triggered by spells cast inside the buff duration. Any delayed healing such as HOTs, Absorbs and Atonements will stop contributing to the healing done when the cooldown buff expires, so this value is lower for any specs with such abilities.">healing ({formatNumber(outputStatistics.healingDone / (end - start) * 1000)} HPS)</dfn>

{formatPercentage(outputStatistics.overhealingDone / (outputStatistics.healingDone + outputStatistics.overhealingDone))}%

                  <dfn data-tip="This includes all healing the occured while the buff was up, even if it was not triggered by spells cast inside the buff duration. Any delayed healing such as HOTs, Absorbs and Atonements will stop contributing to the healing done when the cooldown buff expires, so this value is lower for any specs with such abilities.">overhealing</dfn>

            )
          }
          {
            cooldown.ability.cooldownType === 'DAMAGE' && showOutputStatistics && (

{formatNumber(outputStatistics.damageDone)}

                  <dfn data-tip="This number represents the total amount of damage done during the duration of this cooldown, any damage done by DOTs after the effect of this cooldown has exprired will not be included in this statistic.">damage ({formatNumber(outputStatistics.damageDone / (end - start) * 1000)} DPS)</dfn>

            )
          }
          {
            showResourceStatistics && (

{formatNumber(resourceStatistics)}

                mana used

            )
          }

    );
  }
}

export default Cooldown;
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { List, AutoSizer } from 'react-virtualized'
import { space as baseSpace } from 'themes/base'
import ChannelSummaryListItem from './ChannelSummaryListItem'

const StyledList = styled(List)`
  outline: none;
  overflow-y: overlay !important;
  overflow-x: hidden !important;
`

const ChannelSummaryList = ({ channels, openModal, setSelectedChannel }) => {
  const ROW_MARGIN_TOP = 3
  // current row height + margin bottom
  const ROW_HEIGHT = 107 + baseSpace[ROW_MARGIN_TOP]

  const renderRow = rowProps => {
    const { index, key, style } = rowProps
    const channel = channels[index].channel || channels[index]
    return (

        <ChannelSummaryListItem
          mx={4}
          channel={channel}
          openModal={openModal}
          setSelectedChannel={setSelectedChannel}
          mt={ROW_MARGIN_TOP}
        />

    )
  }

  return (
    <AutoSizer>
      {({ width, height }) => {
        return (


<commit_before>// Written by Joshua Paul A. Chan
(function() {
"use strict";

// `$convoListService` SERVICE
//
angular.module('wr.services')
.service('$convoListService', function($http) {

});
}());
<commit_msg>Add stubs for<commit_after>// Written by Joshua Paul A. Chan
(function() {
"use strict";

// TODO: build summary of the convoList service
// `$convoListService` SERVICE
// Serves the convoList by perfoming bulk operations on conversations.
//
// @pre     : networking libs must be initialized
//
// @attr    : apiUrl        : String    : base API url of the resource
// @attr    : query         : String    :
//
// @method  : loadConvos    : Promise   :
// @method  : search        : Promise   :
// @method  : loadNext      : Promise
// @method  : updateConvos  : Promise   :
//
angular.module('wr.services')
.service('$convoListService', function($http) {
    var service = this;
    service.apiURL = "";

    // defaults
    service.query = "";
    service.isSearch = false;
    service.curPage = 0;
    service.curSearchPage = 0;
    service.numItemsPerPage = 25;

    // TODO: `loadConvos(pg, numItemsPerPage)`
    // Retrieves a page of the most recent conversations from the server.
    //
    // @pre     : `pg` must be a non-negative Number or null, if null will
    // default to 0
    // @pre     : `numItemsPerPage` must be a positive Number or null, if null
    // will default to 25
    // @post    : if given, `pg` will set `curPage` so `loadNext` can be used
    // @post    : if given, `numItemsPerPage` will set `numItemsPerPage` so
    // `loadNext` can be used
    // @post    : [success] resets curPage to given pg
    // @post    : [success] returned promise will resolve to conversations[]
    // @post    : [error] returned promise will reject to an error message
    //
    // @param   : pg                : Number    : the page number to load
    // (assuming numItemsPerPage is the number of items in a page) [ default=0 ]
    // @param   : numItemsPerPage   : Number    : the number of items per page [
    // default=25 ]
    // @return  : Promise   : resolves to conversations[] or error message str
    service.loadConvos = function(pg, numItemsPerPage) {
        return new Promise(function(resolve, reject) {
            // CODE IN HERE
            resolve([]);
        });
    };

    // TODO: `search(query, numItemsPerPage)`
    // Retrieves a page of conversations related to the given query.
    //
    // @pre     : `query` must be a non-empty String

    // @pre     : `numItemsPerPage` must be a positive number or null, if null
    // will default to 25
    // @post    : if given, `numItemsPerPage` will set `numItemsPerPage` so
    // `loadNext` can be used
    // @post    : [success] resets `curSearchPage` to 0
    // @post    : [success] returned promise will resolve to conversations[]
    // @post    : [error] returned promise will reject to an error message
    //
    // @param   : query             : String    : search query
    // @param   : numItemsPerPage   : Number    : the number of items per page [
    // default=25 ]
    // @return  : Promise   : resolves to conversation[] or error message str
    service.search = function(query, numItemsPerPage) {
        return new Promise(function(resolve, reject) {
            // CODE IN HERE
            resolve([]);
        });
    };

    // TODO: `loadNext()`
    // Either 1) retrieves the next page following a .loadConversations(..), or
    // 2) retrieves the next page of serach results following a .search(..).
    //
    // @pre     : `numItemsPerPage` must be a positive number or null, if null
    // will default to 25
    // @pre     : if `isSearch` is truthy, will load next page of search results
    // @pre     : if `isSearch` is falsey, will load next page of conversations
    // @post    : [success] increments the current page
    // @post    : [success] returned promise will resolve to conversations[]
    // @post    : [error] returned promise will reject to an error message
    //
    // @return  : Promise   : resolves to conversation[] or error message str
    service.loadNext = function() {
        // CODE IN HERE
        if (isSearch === true) {
            // load next page of search results
        } else {
            // load next page of conversations
        }
    };

    // TODO: `updateConvos(convoIds, opts)`
    // Updates the given conversations with the given options on the server.
    //
    // @pre     : `convoIds` must be a non-empty array of valid conversation ids
    // @pre     : `opts` must be an Object with keys corresponding to valid
    // Conversation attributes and values corresponding to valid values for
    // those keys
    // @post    : [success] returned promise will resolve to conversations[]
    // with the updated information
    // @post    : [error] returned promise will reject to an error message
    //
    // @return  : Promise   : resolves to conversation[] or error message str
    service.updateConvos = function(convoIds, opts) {
        return new Promise(function(resolve, reject) {
            resolve([]);
        });
    };

    return service;
});
}());
<|endoftext|>