<commit_before>var postcss = require('postcss');
var path    = require('path');

var mixins = require('../');

function run(input, output, opts) {
    return postcss([ mixins(opts) ]).process(input).then(result => {
        expect(result.css).toEqual(output);
        expect(result.warnings().length).toBe(0);
        return result;
    });
}

it('throws error on unknown mixin', () => {
    return postcss(mixins).process('@mixin A').catch(err => {
        expect(err.reason).toEqual('Undefined mixin A');
    });
});

it('cans remove unknown mixin on request', () => {
    return run('@mixin A; a{}', 'a{}', { silent: true });
});

it('supports functions mixins', () => {
    return run('a { @mixin color black; }', 'a { color: black; }', {
        mixins: {
            color: (rule, color) => {
                rule.replaceWith({ prop: 'color', value: color });
            }
        }
    });
});

it('removes mixin at-rule', () => {
    return run('a { @mixin none; }', 'a { }', {
        mixins: {
            none: () => { }
        }
    });
});

it('converts object from function to nodes', () => {
    return run('a { @mixin color black; }', 'a { color: black; }', {
        mixins: {
            color: (rule, color) => {
                return { color: color };
            }
        }
    });
});

it('passes undefined on missed parameters', () => {
    return run('a { @mixin test; @mixin test  ; }', 'a { }', {
        mixins: {
            test: (rule, param1) => {
                expect(param1).not.toBeDefined();
                return { };
            }
        }
    });
});

it('supports object mixins', () => {
    return run('@mixin obj;',
        '@media screen {\n    b {\n        one: 1\n    }\n}', {
            mixins: {
                obj: {
                    '@media screen': {
                        b: {
                            one: '1'
                        }
                    }
                }
            }
        });
});

it('supports nested function mixins', () => {
    return run(
        'a { color: black; @mixin parent { @mixin child; } }',
        'a { color: black; .parent { color: white } }',
        {
            mixins: {
                parent: mixin => {
                    var rule = postcss.rule({ selector: '.parent' });
                    if ( mixin.nodes ) {
                        rule.append(mixin.nodes);
                    }
                    mixin.replaceWith(rule);
                },
                child: () => {
                    return {
                        color: 'white'
                    };
                }
            }
        });
});

it('throws on unknown mixin type', done => {
    const opts = {
        mixins: {
            a: 1
        }
    };
    return postcss([ mixins(opts) ]).process('@mixin a').catch(e => {
        expect(e.message).toEqual('Wrong a mixin type number');
        done();
    });
});

it('supports CSS mixins', () => {
    return run(
        '@define-mixin black { color: black; } a { @mixin black; }',
        'a { color: black; }'
    );
});

it('uses variable', () => {
    return run(
        '@define-mixin color $color { color: $color $other; } ' +
            'a { @mixin color black; }',
        'a { color: black $other; }'
    );
});

it('supports default value', () => {
    return run(
        '@define-mixin c $color: black { color: $color; } a { @mixin c; }',
        'a { color: black; }'
    );
});

it('supports mixins with content', () => {
    return run(
        '@define-mixin m { @media { @mixin-content; } } @mixin m { a {} }',
        '@media { a {} }'
    );
});

it('supports mixins with declarations content', () => {
    return run(
        '@define-mixin m { a: 1; @mixin-content; } .m { @mixin m { b: 2 } }',
        '.m { a: 1; b: 2 }'
    );
});

it('supports mixins with empty content', () => {
    return run(
        '@define-mixin m { a: 1; @mixin-content; } .m { @mixin m; }',
        '.m { a: 1; }'
    );
});

it('supports mixins with multiple content', () => {
    return run(
        '@define-mixin m { @mixin-content; @mixin-content; } ' +
            '.m { @mixin m { a: 1 } }',
        '.m { a: 1; a: 1 }'
    );
});

it('uses variables', () => {
    return run(
        '@define-mixin m $a, $b: b, $c: c { v: $a $b $c; } @mixin m 1, 2;',
        'v: 1 2 c;'
    );
});

it('loads mixins from dir', () => {
    return run(
        'a { @mixin a 1; @mixin b; @mixin c; @mixin d; @mixin e; }',
        'a { a: 1; b: 2; c: 3; d: 4; e: 5; }',
        {
            mixinsDir: path.join(__dirname, 'mixins')
        }
    ).then(result => {
        expect(result.messages).toEqual([
            {
                file: path.join(__dirname, 'mixins/a.js'),
                type: 'dependency',
                parent: ''
            },
            {
                file: path.join(__dirname, 'mixins/b.json'),
                type: 'dependency',
                parent: ''
            },
            {
                file: path.join(__dirname, 'mixins/c.CSS'),
                type: 'dependency',
                parent: ''
            },
            {
                file: path.join(__dirname, 'mixins/d.sss'),
                type: 'dependency',
                parent: ''
            },
            {
                file: path.join(__dirname, 'mixins/e.pcss'),
                type: 'dependency',
                parent: ''
            }
        ]);
    });
});

it('loads mixins from dir with parent options', () => {
    var parent =  path.join(__dirname, 'a.js');
    return run(
        'a { @mixin a 1; @mixin b; @mixin c; @mixin d; @mixin e; }',
        'a { a: 1; b: 2; c: 3; d: 4; e: 5; }',
        {
            mixinsDir: path.join(__dirname, 'mixins'),
            parent: path.join(__dirname, 'a.js')
        }
    ).then(result => {
        expect(result.messages).toEqual([
            {
                file: path.join(__dirname, 'mixins/a.js'),
                type: 'dependency',
                parent: parent
            },
            {
                file: path.join(__dirname, 'mixins/b.json'),
                type: 'dependency',
                parent: parent
            },
            {
                file: path.join(__dirname, 'mixins/c.CSS'),
                type: 'dependency',
                parent: parent
            },
            {
                file: path.join(__dirname, 'mixins/d.sss'),
                type: 'dependency',
                parent: parent
            },
            {
                file: path.join(__dirname, 'mixins/e.pcss'),
                type: 'dependency',
                parent: parent
            }
        ]);
    });
});

it('loads mixins from dirs', () => {
    return run('a { @mixin a 1; @mixin c; }', 'a { a: 1; c: 3; }', {
        mixinsDir: [
            path.join(__dirname, 'mixins'),
            path.join(__dirname, 'other')
        ]
    });
});

it('loads mixins from relative dir', () => {
    return run(
        'a { @mixin a 1; @mixin b; @mixin c; @mixin d; @mixin e; }',
        'a { a: 1; b: 2; c: 3; d: 4; e: 5; }',
        {
            mixinsDir: 'test/mixins/'
        }
    );
});

it('loads mixins from relative dirs', () => {
    return run('a { @mixin a 1; @mixin c; }', 'a { a: 1; c: 3; }', {
        mixinsDir: ['test/mixins', 'test/other']
    });
});

it('loads mixins from file glob', () => {
    return run('a { @mixin a 1; @mixin b; }', 'a { a: 1; b: 2; }', {
        mixinsFiles: path.join(__dirname, 'mixins', '*.{js,json}')
    });
});

it('loads mixins from file globs', () => {
    return run('a { @mixin a 1; @mixin c; }', 'a { a: 1; c: 3; }', {
        mixinsFiles: [
            path.join(__dirname, 'mixins', '*.!(json|css)'),
            path.join(__dirname, 'other', '*')
        ]
    });
});

it('coverts mixins values', () => {
    var proccessor = postcss(mixins({
        mixins: {
            empty: () => {
                return { width: 0 };
            }
        }
    }));
    return proccessor.process('a{ @mixin empty; }').then(result => {
        expect(typeof result.root.first.first.value).toEqual('string');
    });
});

it('supports nested mixins', () => {
    return run(
        '@define-mixin a $a { a: $a; } ' +
            '@define-mixin b $b { @mixin a $b; } ' +
            '@mixin b 1;',
        'a: 1;'
    );
});

it('supports nested mixins in mixin-content', () => {
    return run(
        '@define-mixin a { a: 1 } ' +
            '@define-mixin b { b { @mixin-content } } ' +
            '@mixin b { @mixin a }',
        'b { a: 1}'
    );
});

it('supports nested mixins on object mixins', () => {
    return run('@define-mixin a { a: a; } @mixin b;', 'a: a;', {
        mixins: {
            b: {
                '@mixin a': { }
            }
        }
    });
});

it('supports default arguments in nested mixins', () => {
    return run(
        '@define-mixin a $a: 1 { a: $a } ' +
            '@define-mixin b $b { @mixin a $b } ' +
            '@mixin b;',
        'a: 1;'
    );
});

it('works in sync mode on no option', () => {
    var input = '@define-mixin a { a: 1 }; @mixin a';
    var output = 'a: 1';
    expect(postcss(mixins()).process(input).css).toEqual(output);
});

it('cans remove unknown mixin on request', () => {
    return run('@define-mixin a { a: 1 } @add-mixin a', 'a: 1');
});
<commit_msg>Clean up test<commit_after>var postcss = require('postcss');
var path    = require('path');

var mixins = require('../');

function run(input, output, opts) {
    return postcss([ mixins(opts) ]).process(input).then(result => {
        expect(result.css).toEqual(output);
        expect(result.warnings().length).toBe(0);
        return result;
    });
}

it('throws error on unknown mixin', () => {
    return postcss(mixins).process('@mixin A').catch(err => {
        expect(err.reason).toEqual('Undefined mixin A');
    });
});

it('cans remove unknown mixin on request', () => {
    return run('@mixin A; a{}', 'a{}', { silent: true });
});

it('supports functions mixins', () => {
    return run('a { @mixin color black; }', 'a { color: black; }', {
        mixins: {
            color: (rule, color) => {
                rule.replaceWith({ prop: 'color', value: color });
            }
        }
    });
});

it('removes mixin at-rule', () => {
    return run('a { @mixin none; }', 'a { }', {
        mixins: {
            none: () => { }
        }
    });
});

it('converts object from function to nodes', () => {
    return run('a { @mixin color black; }', 'a { color: black; }', {
        mixins: {
            color: (rule, color) => {
                return { color: color };
            }
        }
    });
});

it('passes undefined on missed parameters', () => {
    return run('a { @mixin test; @mixin test  ; }', 'a { }', {
        mixins: {
            test: (rule, param1) => {
                expect(param1).not.toBeDefined();
                return { };
            }
        }
    });
});

it('supports object mixins', () => {
    return run(
        '@mixin obj;',
        '@media screen {\n    b {\n        one: 1\n    }\n}',
        {
            mixins: {
                obj: {
                    '@media screen': {
                        b: {
                            one: '1'
                        }
                    }
                }
            }
        }
    );
});

it('supports nested function mixins', () => {
    return run(
        'a { color: black; @mixin parent { @mixin child; } }',
        'a { color: black; .parent { color: white } }',
        {
            mixins: {
                parent: mixin => {
                    var rule = postcss.rule({ selector: '.parent' });
                    if ( mixin.nodes ) {
                        rule.append(mixin.nodes);
                    }
                    mixin.replaceWith(rule);
                },
                child: () => {
                    return {
                        color: 'white'
                    };
                }
            }
        });
});

it('throws on unknown mixin type', done => {
    const opts = {
        mixins: {
            a: 1
        }
    };
    return postcss([ mixins(opts) ]).process('@mixin a').catch(e => {
        expect(e.message).toEqual('Wrong a mixin type number');
        done();
    });
});

it('supports CSS mixins', () => {
    return run(
        '@define-mixin black { color: black; } a { @mixin black; }',
        'a { color: black; }'
    );
});

it('uses variable', () => {
    return run(
        '@define-mixin color $color { color: $color $other; } ' +
            'a { @mixin color black; }',
        'a { color: black $other; }'
    );
});

it('supports default value', () => {
    return run(
        '@define-mixin c $color: black { color: $color; } a { @mixin c; }',
        'a { color: black; }'
    );
});

it('supports mixins with content', () => {
    return run(
        '@define-mixin m { @media { @mixin-content; } } @mixin m { a {} }',
        '@media { a {} }'
    );
});

it('supports mixins with declarations content', () => {
    return run(
        '@define-mixin m { a: 1; @mixin-content; } .m { @mixin m { b: 2 } }',
        '.m { a: 1; b: 2 }'
    );
});

it('supports mixins with empty content', () => {
    return run(
        '@define-mixin m { a: 1; @mixin-content; } .m { @mixin m; }',
        '.m { a: 1; }'
    );
});

it('supports mixins with multiple content', () => {
    return run(
        '@define-mixin m { @mixin-content; @mixin-content; } ' +
            '.m { @mixin m { a: 1 } }',
        '.m { a: 1; a: 1 }'
    );
});

it('uses variables', () => {
    return run(
        '@define-mixin m $a, $b: b, $c: c { v: $a $b $c; } @mixin m 1, 2;',
        'v: 1 2 c;'
    );
});

it('loads mixins from dir', () => {
    return run(
        'a { @mixin a 1; @mixin b; @mixin c; @mixin d; @mixin e; }',
        'a { a: 1; b: 2; c: 3; d: 4; e: 5; }',
        {
            mixinsDir: path.join(__dirname, 'mixins')
        }
    ).then(result => {
        expect(result.messages).toEqual([
            {
                file: path.join(__dirname, 'mixins/a.js'),
                type: 'dependency',
                parent: ''
            },
            {
                file: path.join(__dirname, 'mixins/b.json'),
                type: 'dependency',
                parent: ''
            },
            {
                file: path.join(__dirname, 'mixins/c.CSS'),
                type: 'dependency',
                parent: ''
            },
            {
                file: path.join(__dirname, 'mixins/d.sss'),
                type: 'dependency',
                parent: ''
            },
            {
                file: path.join(__dirname, 'mixins/e.pcss'),
                type: 'dependency',
                parent: ''
            }
        ]);
    });
});

it('loads mixins from dir with parent options', () => {
    var parent =  path.join(__dirname, 'a.js');
    return run(
        'a { @mixin a 1; @mixin b; @mixin c; @mixin d; @mixin e; }',
        'a { a: 1; b: 2; c: 3; d: 4; e: 5; }',
        {
            mixinsDir: path.join(__dirname, 'mixins'),
            parent: path.join(__dirname, 'a.js')
        }
    ).then(result => {
        expect(result.messages).toEqual([
            {
                file: path.join(__dirname, 'mixins/a.js'),
                type: 'dependency',
                parent: parent
            },
            {
                file: path.join(__dirname, 'mixins/b.json'),
                type: 'dependency',
                parent: parent
            },
            {
                file: path.join(__dirname, 'mixins/c.CSS'),
                type: 'dependency',
                parent: parent
            },
            {
                file: path.join(__dirname, 'mixins/d.sss'),
                type: 'dependency',
                parent: parent
            },
            {
                file: path.join(__dirname, 'mixins/e.pcss'),
                type: 'dependency',
                parent: parent
            }
        ]);
    });
});

it('loads mixins from dirs', () => {
    return run('a { @mixin a 1; @mixin c; }', 'a { a: 1; c: 3; }', {
        mixinsDir: [
            path.join(__dirname, 'mixins'),
            path.join(__dirname, 'other')
        ]
    });
});

it('loads mixins from relative dir', () => {
    return run(
        'a { @mixin a 1; @mixin b; @mixin c; @mixin d; @mixin e; }',
        'a { a: 1; b: 2; c: 3; d: 4; e: 5; }',
        {
            mixinsDir: 'test/mixins/'
        }
    );
});

it('loads mixins from relative dirs', () => {
    return run('a { @mixin a 1; @mixin c; }', 'a { a: 1; c: 3; }', {
        mixinsDir: ['test/mixins', 'test/other']
    });
});

it('loads mixins from file glob', () => {
    return run('a { @mixin a 1; @mixin b; }', 'a { a: 1; b: 2; }', {
        mixinsFiles: path.join(__dirname, 'mixins', '*.{js,json}')
    });
});

it('loads mixins from file globs', () => {
    return run('a { @mixin a 1; @mixin c; }', 'a { a: 1; c: 3; }', {
        mixinsFiles: [
            path.join(__dirname, 'mixins', '*.!(json|css)'),
            path.join(__dirname, 'other', '*')
        ]
    });
});

it('coverts mixins values', () => {
    var proccessor = postcss(mixins({
        mixins: {
            empty: () => {
                return { width: 0 };
            }
        }
    }));
    return proccessor.process('a{ @mixin empty; }').then(result => {
        expect(typeof result.root.first.first.value).toEqual('string');
    });
});

it('supports nested mixins', () => {
    return run(
        '@define-mixin a $a { a: $a; } ' +
            '@define-mixin b $b { @mixin a $b; } ' +
            '@mixin b 1;',
        'a: 1;'
    );
});

it('supports nested mixins in mixin-content', () => {
    return run(
        '@define-mixin a { a: 1 } ' +
            '@define-mixin b { b { @mixin-content } } ' +
            '@mixin b { @mixin a }',
        'b { a: 1}'
    );
});

it('supports nested mixins on object mixins', () => {
    return run('@define-mixin a { a: a; } @mixin b;', 'a: a;', {
        mixins: {
            b: {
                '@mixin a': { }
            }
        }
    });
});

it('supports default arguments in nested mixins', () => {
    return run(
        '@define-mixin a $a: 1 { a: $a } ' +
            '@define-mixin b $b { @mixin a $b } ' +
            '@mixin b;',
        'a: 1;'
    );
});

it('works in sync mode on no option', () => {
    var input = '@define-mixin a { a: 1 }; @mixin a';
    var output = 'a: 1';
    expect(postcss(mixins()).process(input).css).toEqual(output);
});

it('cans remove unknown mixin on request', () => {
    return run('@define-mixin a { a: 1 } @add-mixin a', 'a: 1');
});
<|endoftext|>


<commit_before>var _     = require('underscore')
, Emitter = require('events').EventEmitter
, Job     = require('da_river/lib/Job').Job
,  h      = eval(require('da_river/lib/helpers').init())
;

// ****************************************************************
// ****************** River ***************************************
// ****************************************************************

function River() {}
exports.River = River;
River.uniq_id = 0;

River.new = function () {

  var me = new River;
  me.job_list     = [];
  me.waits        = [];
  me.running_job  = null;
  me.replys       = [];
  me.data         = {};
  me.uniq_job_id  = 0;
  me.is_river     = true;
  me.emitter      = new Emitter;
  me.parent_job   = find_job(arguments);
  read_able(me);

  return me;
};

River.prototype.has_error = function (type, err, job) {
  var me = this;

  if (!arguments.length)
    return !!me.error;

  me.is_fin = true;

  me.error = err;
  if (!err.job)
    err.job = job;
  if (!err.type)
    err.type = type;

  return me;
};

River.prototype.set_finish = function (f) {
  this.set('finish', f);
  return me;
};

River.prototype.before_each = function (func) {
  var me = this;
  me.emitter.on('before job', func);
  return me;
};

River.prototype.next = function (type, func) {
  if (!this.for_next_job)
    this.for_next_job = [];
  this.for_next_job.push([type,func]);
  return this;
};

River.prototype.next_empty = function (raw_f) {
  if (!this.rel_jobs)
    this.rel_jobs = []

  this.rel_jobs.push([ null, null, function (j, last_reply) {
    if (!last_reply ||
        (_.isString(last_reply) && last_reply.trim() === '') ||
          (_.isObject(last_reply)  && _.isEmpty(last_reply))
       )
      raw_f.apply(null, arguments);
    else
      j.finish(last_reply);
  }]);
  return this;
};

River.prototype.job = function () {

  var me     = this;
  var args   = _.toArray(arguments);
  var args_l = args.length;
  var job    = null;
  switch (args_l) {
    case 2:
      var func  = args.pop();
      var group = args.pop();
      var id    = args.pop();
      break;
    default:
      var func  = args.pop();
      var id    = args.pop();
      var group = args.pop();
  };

  if (group === undefined)
    group = 'no group';
  if (id === undefined)
    id = ++this.uniq_job_id;

  job = Job.new({
    group     : group,
    id        : id,
    func      : func,
    river     : me,
  });

  _.each((me.for_next_job || []), function (pair) {
    job.set.apply(job, pair);
  });

  me.for_next_job = null;

  me.job_list.push(job);

  var rel_jobs = me.rel_jobs || [];
  me.rel_jobs = null;

  _.each(rel_jobs, function (triple) {
    var args = triple.slice();
    if (!args[0])
      args[0] = job.group;
    if (!args[1])
      args[1] = job.id + '-empty';
    me.job.apply(me, triple);
  });

  return me;
};

River.prototype.reply_counter = -1;

River.prototype.reply_for = function (group, id) {
  var me = this;
  var reply = me.replys_for(group, id);
  return reply[0];
};

River.prototype.reply_for = function (group, id) {
  var target = _.find(this.replys, function (v) {
    return v.group === group && v.id === id;
  });
  return (target || {}).val;
};

River.prototype.replys_for = function (group, id) {
  var me     = this;
  var use_both = arguments.length === 2;
  return _.pluck(_.filter(me.replys, function (r) {
    if (use_both)
      return r.group === group && r.id === id;
    else
      return r.group === group;
    return false;
  }), 'val');
};

River.prototype.first_reply = function () {
  return ( _.first(this.replys) || {} ).val;
};

River.prototype.last_reply = function () {
  return ( _.last(this.replys) || {} ).val;
};

River.prototype.job_must_find = function () {
  var args = _.toArray(arguments);
  var f = args.pop();

  var f_new = function (j) {
    j.reply(function (j, last) {
      if (last && ((_.isArray(last)) ? last.length : true))
        j.finish(last);
      else {
        if (j.group && j.group.toUpperCase() !== 'NO GROUP' && j.id)
          var err = new Error(j.group + ', ' + j.id + ', not found.');
        else
          var err = new Error('At least one reply required. Value: ' + JSON.stringify(last));
        err.job = j;

        j.finish('not_found', err, j);
      }
    });

    f(j);
  };

  args.push(f_new);
  this.job.apply(this, args);
  return this;
};

River.prototype.is_finished = function () {
  var me = this;
  return !!this.is_fin || (parent(me) && parent(me).is_finished());
};

River.prototype.finish = function (rep, err) {
  var me   = this;
  var args = arguments;

  if (me.is_fin)
    return null;

  var job = (me.running_job || {}).job;
  me.running_job = null;

  if (job) {
    if (args.length < 2) {
      me.replys.push({group: job.group, id: job.id, val: job.result});
      if (me.waits.length)
        return me.run_job();
    }
  }

  if (args.length > 1 && !me.is_fin) {
    if (!err || !err.message)
      err = new Error('' + err);
    if (!err.type)
      err.type = rep;
    me.error = err;
  }

  me.is_fin = true;

  var fin = {
    river: me,
    finish: function () {

      if (parent(me)) {
        if (me.has_error())
          return parent(me).finish(me.error.type, me.error);
        else
          return parent(me).finish(me.last_reply());
      }

      if (me.has_error())
        throw me.error;

      return me;
    }
  };

  if (me.error) {
    var err_func = me.erase(me.error.type);
    if (err_func)
      return err_func(fin);
  }

  var fin_func = me.erase('finish');
  if (fin_func)
    return fin_func(fin, me.last_reply());

  return fin.finish();
};

River.prototype.verbose = function () {
  this.before_each(function (j) {
    console['log'](j.group, j.id);
  });
  return this;
};

River.prototype.run_job = function () {
  if (this.has_error())
    return this;
  var me  = this;
  me.running_job = me.waits.shift();
  var job = me.job_list[me.running_job.i];

  me.emitter.emit('before job', job);
  job.func(job, me.last_reply());
};

River.prototype.run = function (f) {
  if (this.is_running)
    return this.finish('error', new Error('Already running.'));
  this.is_running = true;

  if (f)
    this.set('finish', f);

  var me     = this;
  this.waits = _.map(this.job_list, function (j, i) {
    return {group: j.group, id: j.id, job: j, i: i};
  });

  if ( !this.waits.length ) {
    return this;
  }

  me.run_job();
  return me;
};

<commit_msg>Touchup.<commit_after>var _     = require('underscore')
, Emitter = require('events').EventEmitter
, Job     = require('da_river/lib/Job').Job
,  h      = eval(require('da_river/lib/helpers').init())
;

// ****************************************************************
// ****************** River ***************************************
// ****************************************************************

function River() {}
exports.River = River;
River.uniq_id = 0;

River.new = function () {

  var me = new River;
  me.job_list     = [];
  me.waits        = [];
  me.running_job  = null;
  me.replys       = [];
  me.data         = {};
  me.uniq_job_id  = 0;
  me.is_river     = true;
  me.emitter      = new Emitter;
  me.parent_job   = find_job(arguments);
  read_able(me);

  return me;
};

River.prototype.has_error = function (type, err, job) {
  var me = this;

  if (!arguments.length)
    return !!me.error;

  me.is_fin = true;

  me.error = err;
  if (!err.job)
    err.job = job;
  if (!err.type)
    err.type = type;

  return me;
};

River.prototype.set_finish = function (f) {
  this.set('finish', f);
  return me;
};

River.prototype.before_each = function (func) {
  var me = this;
  me.emitter.on('before job', func);
  return me;
};

River.prototype.next = function (type, func) {
  if (!this.for_next_job)
    this.for_next_job = [];
  this.for_next_job.push([type,func]);
  return this;
};

River.prototype.next_empty = function (raw_f) {
  if (!this.rel_jobs)
    this.rel_jobs = []

  this.rel_jobs.push([ null, null, function (j, last_reply) {
    if (!last_reply ||
        (_.isString(last_reply) && last_reply.trim() === '') ||
          (_.isObject(last_reply)  && _.isEmpty(last_reply))
       )
      raw_f.apply(null, arguments);
    else
      j.finish(last_reply);
  }]);
  return this;
};

River.prototype.job = function () {

  var me     = this;
  var args   = _.toArray(arguments);
  var args_l = args.length;
  var job    = null;
  switch (args_l) {
    case 2:
      var func  = args.pop();
      var group = args.pop();
      var id    = args.pop();
      break;
    default:
      var func  = args.pop();
      var id    = args.pop();
      var group = args.pop();
  };

  if (group === undefined)
    group = 'no group';
  if (id === undefined)
    id = ++this.uniq_job_id;

  job = Job.new({
    group     : group,
    id        : id,
    func      : func,
    river     : me,
  });

  _.each((me.for_next_job || []), function (pair) {
    job.set.apply(job, pair);
  });

  me.for_next_job = null;

  me.job_list.push(job);

  var rel_jobs = me.rel_jobs || [];
  me.rel_jobs = null;

  _.each(rel_jobs, function (triple) {
    var args = triple.slice();
    if (!args[0])
      args[0] = job.group;
    if (!args[1])
      args[1] = job.id + '-empty';
    me.job.apply(me, triple);
  });

  return me;
};

River.prototype.reply_counter = -1;

River.prototype.reply_for = function (group, id) {
  var me = this;
  return me.replys_for(group, id)[0];
};

River.prototype.reply_for = function (group, id) {
  var target = _.find(this.replys, function (v) {
    return v.group === group && v.id === id;
  });
  return (target || {}).val;
};

River.prototype.replys_for = function (group, id) {
  var me     = this;
  var use_both = arguments.length === 2;
  return _.pluck(_.filter(me.replys, function (r) {
    if (use_both)
      return r.group === group && r.id === id;
    else
      return r.group === group;
    return false;
  }), 'val');
};

River.prototype.first_reply = function () {
  return ( _.first(this.replys) || {} ).val;
};

River.prototype.last_reply = function () {
  return ( _.last(this.replys) || {} ).val;
};

River.prototype.job_must_find = function () {
  var args = _.toArray(arguments);
  var f = args.pop();

  var f_new = function (j) {
    j.reply(function (j, last) {
      if (last && ((_.isArray(last)) ? last.length : true))
        j.finish(last);
      else {
        if (j.group && j.group.toUpperCase() !== 'NO GROUP' && j.id)
          var err = new Error(j.group + ', ' + j.id + ', not found.');
        else
          var err = new Error('At least one reply required. Value: ' + JSON.stringify(last));
        err.job = j;

        j.finish('not_found', err, j);
      }
    });

    f(j);
  };

  args.push(f_new);
  this.job.apply(this, args);
  return this;
};

River.prototype.is_finished = function () {
  var me = this;
  return !!this.is_fin || (parent(me) && parent(me).is_finished());
};

River.prototype.finish = function (rep, err) {
  var me   = this;
  var args = arguments;

  if (me.is_fin)
    return null;

  var job = (me.running_job || {}).job;
  me.running_job = null;

  if (job) {
    if (args.length < 2) {
      me.replys.push({group: job.group, id: job.id, val: job.result});
      if (me.waits.length)
        return me.run_job();
    }
  }

  if (args.length > 1 && !me.is_fin) {
    if (!err || !err.message)
      err = new Error('' + err);
    if (!err.type)
      err.type = rep;
    me.error = err;
  }

  me.is_fin = true;

  var fin = {
    river: me,
    finish: function () {

      if (parent(me)) {
        if (me.has_error())
          return parent(me).finish(me.error.type, me.error);
        else
          return parent(me).finish(me.last_reply());
      }

      if (me.has_error())
        throw me.error;

      return me;
    }
  };

  if (me.error) {
    var err_func = me.erase(me.error.type);
    if (err_func)
      return err_func(fin);
  }

  var fin_func = me.erase('finish');
  if (fin_func)
    return fin_func(fin, me.last_reply());

  return fin.finish();
};

River.prototype.verbose = function () {
  this.before_each(function (j) {
    console['log'](j.group, j.id);
  });
  return this;
};

River.prototype.run_job = function () {
  if (this.has_error())
    return this;
  var me  = this;
  me.running_job = me.waits.shift();
  var job = me.job_list[me.running_job.i];

  me.emitter.emit('before job', job);
  job.func(job, me.last_reply());
};

River.prototype.run = function (f) {
  if (this.is_running)
    return this.finish('error', new Error('Already running.'));
  this.is_running = true;

  if (f)
    this.set('finish', f);

  var me     = this;
  this.waits = _.map(this.job_list, function (j, i) {
    return {group: j.group, id: j.id, job: j, i: i};
  });

  if ( !this.waits.length ) {
    return this;
  }

  me.run_job();
  return me;
};

<|endoftext|>


<commit_before>/**
 * Spins up two web servers
 *   - http-server sends the static assets in dist and handles the /config.json endpoint
 *   - cors-proxy adds the necessary headers for xdomain access to the REST API
 */

var pkg = require('./package.json'),
    morgan = require('morgan'),

    COOPR_UI_PORT = parseInt(process.env.COOPR_UI_PORT || 8080, 10),
    COOPR_CORS_PORT = parseInt(process.env.COOPR_CORS_PORT || 8081, 10),
    COOPR_SERVER_URI = process.env.COOPR_SERVER_URI || 'http://127.0.0.1:55054',

    color = {
      hilite: function (v) { return '\x1B[7m' + v + '\x1B[27m'; },
      green: function (v) { return '\x1B[40m\x1B[32m' + v + '\x1B[39m\x1B[49m'; },
      pink: function (v) { return '\x1B[40m\x1B[35m' + v + '\x1B[39m\x1B[49m'; }
    };

morgan.token('cooprcred', function(req, res){
  return color.pink(req.headers['coopr-userid'] + '/' + req.headers['coopr-tenantid']);
});

var httpLabel = color.green('http'),
    corsLabel = color.pink('cors'),
    httpLogger = morgan(httpLabel+' :method :url :status'),
    corsLogger = morgan(corsLabel+' :method :url :cooprcred :status', {
      skip: function(req, res) { return req.method === 'OPTIONS' }
    });

console.log(color.hilite(pkg.name) + ' v' + pkg.version + ' starting up...');

/**
 * HTTP server
 */

var express = require('express'),
    app = express();

app.use(httpLogger);

// serve the config file
app.get('/config.js', function (req, res) {
  var data = JSON.stringify({
    // the following will be available in angular via the "MY_CONFIG" injectable

    COOPR_SERVER_URI: COOPR_SERVER_URI,
    COOPR_CORS_PORT: COOPR_CORS_PORT,
    authorization: req.headers.authorization

  });
  res.header({
    'Content-Type': 'text/javascript',
    'Cache-Control': 'no-store, must-revalidate'
  });
  res.send('angular.module("'+pkg.name+'.config", [])' +
            '.constant("MY_CONFIG",'+data+');');
});

// serve static assets
app.get(/\/(bundle|fonts|partials)\/.*/, express.static(__dirname + '/dist', {
  index: false
}));

// any other path, serve index.html
app.all('*', function (req, res) {
  res.sendFile(__dirname + '/dist/index.html');
});

app.listen(COOPR_UI_PORT, '0.0.0.0', function () {
  console.log(httpLabel+' listening on port %s', COOPR_UI_PORT);
});

/**
 * CORS proxy
 */
require('cors-anywhere')
  .createServer({
    requireHeader: ['x-requested-with'],
    removeHeaders: ['cookie', 'cookie2']
  })
  .on('request', function (req, res) {
    corsLogger(req, res, function noop() {} );
  })
  .listen(COOPR_CORS_PORT, '0.0.0.0', function() {
    console.log(corsLabel+' listening on port %s', COOPR_CORS_PORT);
  });
<commit_msg>hilite httpIndexLogger<commit_after>/**
 * Spins up two web servers
 *   - http-server sends the static assets in dist and handles the /config.json endpoint
 *   - cors-proxy adds the necessary headers for xdomain access to the REST API
 */

var pkg = require('./package.json'),
    morgan = require('morgan'),

    COOPR_UI_PORT = parseInt(process.env.COOPR_UI_PORT || 8080, 10),
    COOPR_CORS_PORT = parseInt(process.env.COOPR_CORS_PORT || 8081, 10),
    COOPR_SERVER_URI = process.env.COOPR_SERVER_URI || 'http://127.0.0.1:55054',

    color = {
      hilite: function (v) { return '\x1B[7m' + v + '\x1B[27m'; },
      green: function (v) { return '\x1B[40m\x1B[32m' + v + '\x1B[39m\x1B[49m'; },
      pink: function (v) { return '\x1B[40m\x1B[35m' + v + '\x1B[39m\x1B[49m'; }
    };

morgan.token('cooprcred', function(req, res){
  return color.pink(req.headers['coopr-userid'] + '/' + req.headers['coopr-tenantid']);
});

var httpLabel = color.green('http'),
    corsLabel = color.pink('cors'),
    httpStaticLogger = morgan(httpLabel+' :method :url :status'),
    httpIndexLogger = morgan(httpLabel+' :method '+color.hilite(':url')+' :status'),
    corsLogger = morgan(corsLabel+' :method :url :cooprcred :status', {
      skip: function(req, res) { return req.method === 'OPTIONS' }
    });

console.log(color.hilite(pkg.name) + ' v' + pkg.version + ' starting up...');

/**
 * HTTP server
 */

var express = require('express'),
    app = express();

// serve the config file
app.get('/config.js', function (req, res) {
  var data = JSON.stringify({
    // the following will be available in angular via the "MY_CONFIG" injectable

    COOPR_SERVER_URI: COOPR_SERVER_URI,
    COOPR_CORS_PORT: COOPR_CORS_PORT,
    authorization: req.headers.authorization

  });
  res.header({
    'Content-Type': 'text/javascript',
    'Cache-Control': 'no-store, must-revalidate'
  });
  res.send('angular.module("'+pkg.name+'.config", [])' +
            '.constant("MY_CONFIG",'+data+');');
});

// serve static assets
app.get(/\/(bundle|fonts|partials)\/.*/, [
  httpStaticLogger,
  express.static(__dirname + '/dist', {
    index: false
  })
]);

// any other path, serve index.html
app.all('*', [
  httpIndexLogger,
  function (req, res) {
    res.sendFile(__dirname + '/dist/index.html');
  }
]);

app.listen(COOPR_UI_PORT, '0.0.0.0', function () {
  console.log(httpLabel+' listening on port %s', COOPR_UI_PORT);
});

/**
 * CORS proxy
 */
require('cors-anywhere')
  .createServer({
    requireHeader: ['x-requested-with'],
    removeHeaders: ['cookie', 'cookie2']
  })
  .on('request', function (req, res) {
    corsLogger(req, res, function noop() {} );
  })
  .listen(COOPR_CORS_PORT, '0.0.0.0', function() {
    console.log(corsLabel+' listening on port %s', COOPR_CORS_PORT);
  });
<|endoftext|>


<commit_before>module.exports = function (config) {

  var configuration = {

    basePath : './src/',

    files : [

      // Third-party dependencies
      'bower_components/angular/angular.js',
      'bower_components/angular-animate/angular-animate.js',
      'bower_components/angular-aria/angular-aria.js',
      'bower_components/angular-markdown-directive/markdown.js',
      'bower_components/angular-material/angular-material.js',
      'bower_components/angular-route/angular-route.js',
      'bower_components/angular-sanitize/angular-sanitize.js',
      'bower_components/angularfire/dist/angularfire.js',
      'bower_components/angular-mocks/angular-mocks.js',
      'bower_components/mockfirebase/browser/mockfirebase.js',
      'bower_components/firebase/firebase.js',
      'bower_components/moment/moment.js',
      'bower_components/showdown/compressed/Showdown.js',

      // Test lib
      'tests/lib/*.js',

      // Assets
      'assets/code/Move.js',

      // App
      'app.js',
      'config/*.js',
      'config/**/*.js',
      'modules/*.js',
      'modules/**/*.js',
      'modules/**/**/*.js',

      // Unit Tests
      'tests/unit/*.js'
    ],

    autoWatch : true,

    frameworks: ['jasmine'],

    browsers : ['Chrome'],

    customLaunchers: {
      Chrome_travis_ci: {
        base: 'Chrome',
        flags: ['--no-sandbox']
      }
    },

    plugins : [
            'karma-chrome-launcher',
            'karma-jasmine',
            'karma-junit-reporter'
            ],

    junitReporter : {
      outputFile: 'test_out/unit.xml',
      suite: 'unit'
    },

    logLevel: 'DEBUG'
  };

  if (process.env.TRAVIS){
    configuration.browsers = ['Chrome_travis_ci'];
  }

  config.set(configuration);

};<commit_msg>Fix karma config<commit_after>module.exports = function (config) {

  var configuration = {

    basePath : './src/',

    files : [

      // Third-party dependencies
      'bower_components/angular/angular.js',
      'bower_components/angular-animate/angular-animate.js',
      'bower_components/angular-aria/angular-aria.js',
      'bower_components/angular-markdown-directive/markdown.js',
      'bower_components/angular-material/angular-material.js',
      'bower_components/angular-route/angular-route.js',
      'bower_components/angular-sanitize/angular-sanitize.js',
      'bower_components/angularfire/dist/angularfire.js',
      'bower_components/angular-mocks/angular-mocks.js',
      'bower_components/mockfirebase/browser/mockfirebase.js',
      'bower_components/firebase/firebase.js',
      'bower_components/moment/moment.js',
      'bower_components/showdown/compressed/Showdown.js',
      'bower_components/clipboard/dist/clipboard.js',

      // Test lib
      'tests/lib/*.js',

      // Assets
      'assets/code/Move.js',

      // App
      'app.js',
      'config/*.js',
      'config/**/*.js',
      'modules/*.js',
      'modules/**/*.js',
      'modules/**/**/*.js',

      // Unit Tests
      'tests/unit/*.js'
    ],

    autoWatch : true,

    frameworks: ['jasmine'],

    browsers : ['Chrome'],

    customLaunchers: {
      Chrome_travis_ci: {
        base: 'Chrome',
        flags: ['--no-sandbox']
      }
    },

    plugins : [
            'karma-chrome-launcher',
            'karma-jasmine',
            'karma-junit-reporter'
            ],

    junitReporter : {
      outputFile: 'test_out/unit.xml',
      suite: 'unit'
    },

    logLevel: 'DEBUG'
  };

  if (process.env.TRAVIS){
    configuration.browsers = ['Chrome_travis_ci'];
  }

  config.set(configuration);

};<|endoftext|>


<commit_before>/* rektcorder-web is a frontend for rektcorder
 * Copyright (C) 2014 Eric Culp
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General
 * Public License * along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
'use strict';
var Queue = require('data-structures').Queue;

// from css-tricks.com
function getQueryVariable(variable) {
	var query = window.location.search.substring(1);
	var vars = query.split('&');
	for (var i=0;i<vars.length;i++) {
		var pair = vars[i].split('=');
		if(pair[0] === variable){return pair[1];}
	}
	return(false);
}

// return number of seconds of XXhXXmXXs format
function parseDuration(str) {
	var m = str.match(/^((\d+)h)?((\d+)m)?((\d+)s)?$/);
	if (!m) {
		return null;
	}
	var seconds = 0;
	if (m[2]) {
		seconds += m[2] * 3600;
	}
	if (m[4]) {
		seconds += m[4] * 60;
	}
	if (m[6]) {
		seconds += m[6] * 1;
	}
	return seconds;
}

var Rekt = function(options) {
	var Rekt = {};
	Rekt.chatEl = options.chatEl;
	Rekt.videoEl = options.videoEl;
	Rekt.playEl = options.playEl;
	Rekt.timeEl = options.timeEl;
	Rekt.scrolledUp = false;
	Rekt.offsetAdj = 0;
	$(Rekt.chatEl.parentElement).scroll(function() {
		var p = Rekt.chatEl.parentElement;
		var pa = p.scrollTop;
		var pb = p.scrollHeight - p.clientHeight;
		var pc = pa/pb;
		if (pc >= .99 || pb === 0) {
			Rekt.scrolledUp = false;
		} else {
			Rekt.scrolledUp = true;
		}
	});
	$(Rekt.playEl).click(function() {
		if (Rekt.paused) {
			Rekt.playEl.firstChild.className = 'glyphicon glyphicon-pause';
			Rekt.play();
		} else {
			Rekt.playEl.firstChild.className = 'glyphicon glyphicon-play';
			Rekt.pause();
		}
	});

	var logTime = function(t) {
		var p = function(n) {
			return n < 10 ? '0' + n : '' + n;
		};
		return t.getUTCFullYear() + '-' + p(t.getUTCMonth()+1) + '-' + p(t.getUTCDate()) + '-' + p(t.getUTCHours());
	};

	Rekt.start = function() {
		Rekt.scrolledUp = false;
		Rekt.chatEl.parentElement.scrollTop = Rekt.chatEl.parentElement.scrollHeight;
		Rekt.messageQueue = new Queue();
		Rekt.processing = false;
		Rekt.paused = false;
		Rekt.playEl.firstChild.className = 'glyphicon glyphicon-pause';
		Rekt.lastLogTime = Rekt.time;
		Rekt.download();
		Rekt.updateTime = setInterval(function() {
			Rekt.timeEl.innerHTML = new Date(Rekt.then()).toLocaleString();
		}, 1000);
	};
	Rekt.stop = function() {
		clearTimeout(Rekt.updateTime);
		clearTimeout(Rekt.downloadTimeout);
		clearTimeout(Rekt.nextProcess);
		Rekt.processing = false;
		if (Rekt.oboe) {
			Rekt.oboe.abort();
		}
	};
	Rekt.clear = function() {
		Rekt.chatEl.innerHTML = '';
	};
	Rekt.then = function() {
		return Date.now() - Rekt.offset + Rekt.offsetAdj * 1000;
	};
	Rekt.downloadTimeout = null;
	Rekt.download = function() {
		console.log('starting download');
		// jshint undef:false
		Rekt.oboe = oboe('http://destisenpaii.me/log/chat-'+logTime(Rekt.time)+'.log').done(function(msg) {
			// jshint undef:true
			// next object available
			Rekt.messageQueue.enqueue(msg);
			if (!Rekt.processing) {
				Rekt.process();
			}
			clearTimeout(Rekt.downloadTimeout);
			Rekt.downloadTimeout = setTimeout(function() {
				if (logTime(Rekt.lastLogTime) !== logTime(new Date(Rekt.then()))) {
					Rekt.oboe.abort();
					console.log('download finished');
					Rekt.lastLogTime = new Date(Rekt.then());
					Rekt.download();
				}
			}, 5000);
		}).fail(function(error) {
			console.log('FAIL');
			console.log(error);
			if (error.statusCode === 0 && error.thrown === undefined) {
				var d = document.createElement('div');
				d.className = 'msg';
				d.innerHTML = 'No Logs Available';
				Rekt.chatEl.appendChild(d);
			}
		});
	};
	Rekt.pause = function() {
		Rekt.paused = true;
		Rekt.pausedAt = Date.now();
	};
	Rekt.play = function() {
		Rekt.paused = false;
		Rekt.offset += Date.now() - Rekt.pausedAt;
		if (!Rekt.processing) {
			Rekt.process();
		}
	};
	Rekt.process = function() {
		if (Rekt.paused) {
			Rekt.processing = false;
			return;
		}
		if (Rekt.messageQueue.size === 0) {
			Rekt.processing = false;
			return;
		}
		Rekt.processing = true;
		var msg = Rekt.messageQueue.dequeue();
		var millis = msg.timestamp - Rekt.then();
		if (Math.abs(millis) > 1000 * 60 * 60) {
			console.log('Waiting too long: ', millis);
		}
		if (millis > 0) {
			Rekt.nextProcess = setTimeout(function() {
				Rekt.onMsg(msg);
			}, millis);
		} else {
			Rekt.onMsg(msg);
		}
	};
	var msgTmpl = doT.template(
		'{{=it.nick}}: {{=it.data}}'
	);
	Rekt.onMsg = function(msg) {
		var m = document.createElement('div');
		m.class = 'msg';
		m.innerHTML = msgTmpl(msg);
		Rekt.chatEl.appendChild(m);
		if (!Rekt.scrolledUp) {
			Rekt.chatEl.parentElement.scrollTop = Rekt.chatEl.parentElement.scrollHeight;
			while (Rekt.chatEl.children.length > 300) {
				Rekt.chatEl.firstChild.remove();
			}
		}
		Rekt.process();
	};
	Rekt.getStartedAt = function(vstr, callback) {
		var m = vstr.match(/((twitch.tv\/destiny\/)?b\/)?(\d+)/);
		var videoId = '';
		if (m) {
			videoId = 'a'+m[3];
			Rekt.archiveId = m[3];
		} else {
			m = vstr.match(/(b|c)\d+/);
			if (m) {
				videoId = m[0];
			} else {
				console.log('dont understand ', vstr);
			}
		}
		Twitch.api({method: 'videos/'+videoId}, function(error, video) {
			if (error !== null) {
				console.log(error);
				return;
			}
			// jshint camelcase: false
			Rekt.setTime(new Date(video.recorded_at));
			// jshint camelcase: true
			callback(Rekt.time);
		});
	};
	Rekt.setTime = function (t) {
		Rekt.time = t;
		Rekt.offset = Date.now() - Rekt.time.getTime();
	};
	var videoTmpl = doT.template(
	 '<object id="twitch-embed" type="application/x-shockwave-flash" data="http://www.twitch.tv/widgets/archive_embed_player.swf" width="100%" height="100%" style="display: block !important;">' +
	 '<param name="movie" value="http://www.twitch.tv/widgets/archive_embed_player.swf">' +
	 '<param name="quality" value="best">' +
	 '<param name="allowFullScreen" value="true">' +
	 '<param name="allowScriptAccess" value="always">' +
	 '<param name="pluginspage" value="http://www.macromedia.com/go/getflashplayer">' +
	 '<param name="autoplay" value="false">' +
	 '<param name="autostart" value="false">' +
	 '<param name="flashvars" value="archive_id={{= it.archiveId }}&hostname=www.twitch.tv&channel=destiny&auto_play=true&eventsCallback=r.playerEvent">' +
	 '</object>');
	Rekt.insertVideo = function() {
		Rekt.videoEl.innerHTML = videoTmpl({archiveId: Rekt.archiveId});
		Rekt.player = document.getElementById('twitch-embed');
	};
	Rekt.playerEvent = function(es) {
		for (var i = 0; i < es.length; i++) {
			var e = es[i];
			console.log('player event:', e);
			if (e.event === 'videoLoaded') {
				if (Rekt.seekTo) {
					Rekt.player.videoSeek(Rekt.seekTo);
				}
			}
			if (e.event === 'videoPlaying') {
				Rekt.videoStarted = Date.now();
				setInterval(Rekt.fixSeek, 1000);
			}
		}
	};
	Rekt.fixSeek = function() {
		var supposedVideoTime = Date.now() - Rekt.videoStarted;
		var actualVideoTime = Rekt.player.getVideoTime() * 1000;
		var millisAhead = supposedVideoTime - actualVideoTime;
		if (Math.abs(millisAhead) > 5000 && !Rekt.paused && !Rekt.player.isPaused() && actualVideoTime !== Rekt.lastVideoTime) {
			console.log('Seek of ' + millisAhead / 1000 + ' seconds detected');
			Rekt.stop();
			Rekt.clear();
			Rekt.setTime(new Date(Rekt.time.getTime() - millisAhead));
			Rekt.videoStarted = Date.now() - actualVideoTime;
			Rekt.start();
		}
		Rekt.lastVideoTime = actualVideoTime;
	};
	return Rekt;
};

var r;

// jshint undef:false
Twitch.init({clientId: '3ccszp1i7lvkkyb4npiizsy3ida8jtt'}, function(error) {
	// jshint undef:true
	if (error !== null) {
		console.log('TWITCH INIT FAILED ', error);
	}
	r = new Rekt({
		chatEl: document.getElementById('msgs'),
		videoEl: document.getElementById('video'),
		playEl: document.getElementById('play-btn'),
		timeEl: document.getElementById('time')
	});
	var o = getQueryVariable('o');
	if (o && o.match(/^-?\d+$/)) {
		$('#offset-adj').val(o);
		r.offsetAdj = o * 1;
	}
	var b = getQueryVariable('b');
	if (b && b.match(/^\d+$/)) {
		var t = getQueryVariable('t');
		var tn = 0;
		if (t) {
			tn = parseDuration(t);
			if (tn === null) {
				tn = 0;
			} else {
				console.log('seeking ' + tn + ' seconds ahead');
			}
		}
		$('#videoUrl').val('b/'+b);
		r.getStartedAt('b/'+b, function() {
			r.setTime(new Date(r.time.getTime() + tn*1000));
			r.start();
			r.insertVideo();
			r.seekTo = tn;
			$('#timepicker').data('DateTimePicker').setDate(r.time);
		});
	} else {
		$('#videoUrl').keypress(function(e) {
			e.preventDefault();
			if (e.keyCode === 13) {
				r.getStartedAt(this.value, function() {
					r.start();
					r.insertVideo();
					$('#timepicker').data('DateTimePicker').setDate(r.time);
				});
			}
			return false;
		});
	}
});
var tp = $('#timepicker');
tp.datetimepicker();
tp.on('dp.change', function(e) {
	r.stop();
	r.clear();
	r.setTime(e.date.toDate());
	r.start();
});
$('#control-menu-btn').click(function() {
	$('#control-menu').toggle();
});
$('#offset-adj').change(function() {
	r.offsetAdj = this.value * 1;
});
$('#reload-logs').click(function() {
	r.stop();
	r.clear();
	r.start();
});
<commit_msg>use leading 0<commit_after>/* rektcorder-web is a frontend for rektcorder
 * Copyright (C) 2014 Eric Culp
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General
 * Public License * along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
'use strict';
var Queue = require('data-structures').Queue;

// from css-tricks.com
function getQueryVariable(variable) {
	var query = window.location.search.substring(1);
	var vars = query.split('&');
	for (var i=0;i<vars.length;i++) {
		var pair = vars[i].split('=');
		if(pair[0] === variable){return pair[1];}
	}
	return(false);
}

// return number of seconds of XXhXXmXXs format
function parseDuration(str) {
	var m = str.match(/^((\d+)h)?((\d+)m)?((\d+)s)?$/);
	if (!m) {
		return null;
	}
	var seconds = 0;
	if (m[2]) {
		seconds += m[2] * 3600;
	}
	if (m[4]) {
		seconds += m[4] * 60;
	}
	if (m[6]) {
		seconds += m[6] * 1;
	}
	return seconds;
}

var Rekt = function(options) {
	var Rekt = {};
	Rekt.chatEl = options.chatEl;
	Rekt.videoEl = options.videoEl;
	Rekt.playEl = options.playEl;
	Rekt.timeEl = options.timeEl;
	Rekt.scrolledUp = false;
	Rekt.offsetAdj = 0;
	$(Rekt.chatEl.parentElement).scroll(function() {
		var p = Rekt.chatEl.parentElement;
		var pa = p.scrollTop;
		var pb = p.scrollHeight - p.clientHeight;
		var pc = pa/pb;
		if (pc >= 0.99 || pb === 0) {
			Rekt.scrolledUp = false;
		} else {
			Rekt.scrolledUp = true;
		}
	});
	$(Rekt.playEl).click(function() {
		if (Rekt.paused) {
			Rekt.playEl.firstChild.className = 'glyphicon glyphicon-pause';
			Rekt.play();
		} else {
			Rekt.playEl.firstChild.className = 'glyphicon glyphicon-play';
			Rekt.pause();
		}
	});

	var logTime = function(t) {
		var p = function(n) {
			return n < 10 ? '0' + n : '' + n;
		};
		return t.getUTCFullYear() + '-' + p(t.getUTCMonth()+1) + '-' + p(t.getUTCDate()) + '-' + p(t.getUTCHours());
	};

	Rekt.start = function() {
		Rekt.scrolledUp = false;
		Rekt.chatEl.parentElement.scrollTop = Rekt.chatEl.parentElement.scrollHeight;
		Rekt.messageQueue = new Queue();
		Rekt.processing = false;
		Rekt.paused = false;
		Rekt.playEl.firstChild.className = 'glyphicon glyphicon-pause';
		Rekt.lastLogTime = Rekt.time;
		Rekt.download();
		Rekt.updateTime = setInterval(function() {
			Rekt.timeEl.innerHTML = new Date(Rekt.then()).toLocaleString();
		}, 1000);
	};
	Rekt.stop = function() {
		clearTimeout(Rekt.updateTime);
		clearTimeout(Rekt.downloadTimeout);
		clearTimeout(Rekt.nextProcess);
		Rekt.processing = false;
		if (Rekt.oboe) {
			Rekt.oboe.abort();
		}
	};
	Rekt.clear = function() {
		Rekt.chatEl.innerHTML = '';
	};
	Rekt.then = function() {
		return Date.now() - Rekt.offset + Rekt.offsetAdj * 1000;
	};
	Rekt.downloadTimeout = null;
	Rekt.download = function() {
		console.log('starting download');
		// jshint undef:false
		Rekt.oboe = oboe('http://destisenpaii.me/log/chat-'+logTime(Rekt.time)+'.log').done(function(msg) {
			// jshint undef:true
			// next object available
			Rekt.messageQueue.enqueue(msg);
			if (!Rekt.processing) {
				Rekt.process();
			}
			clearTimeout(Rekt.downloadTimeout);
			Rekt.downloadTimeout = setTimeout(function() {
				if (logTime(Rekt.lastLogTime) !== logTime(new Date(Rekt.then()))) {
					Rekt.oboe.abort();
					console.log('download finished');
					Rekt.lastLogTime = new Date(Rekt.then());
					Rekt.download();
				}
			}, 5000);
		}).fail(function(error) {
			console.log('FAIL');
			console.log(error);
			if (error.statusCode === 0 && error.thrown === undefined) {
				var d = document.createElement('div');
				d.className = 'msg';
				d.innerHTML = 'No Logs Available';
				Rekt.chatEl.appendChild(d);
			}
		});
	};
	Rekt.pause = function() {
		Rekt.paused = true;
		Rekt.pausedAt = Date.now();
	};
	Rekt.play = function() {
		Rekt.paused = false;
		Rekt.offset += Date.now() - Rekt.pausedAt;
		if (!Rekt.processing) {
			Rekt.process();
		}
	};
	Rekt.process = function() {
		if (Rekt.paused) {
			Rekt.processing = false;
			return;
		}
		if (Rekt.messageQueue.size === 0) {
			Rekt.processing = false;
			return;
		}
		Rekt.processing = true;
		var msg = Rekt.messageQueue.dequeue();
		var millis = msg.timestamp - Rekt.then();
		if (Math.abs(millis) > 1000 * 60 * 60) {
			console.log('Waiting too long: ', millis);
		}
		if (millis > 0) {
			Rekt.nextProcess = setTimeout(function() {
				Rekt.onMsg(msg);
			}, millis);
		} else {
			Rekt.onMsg(msg);
		}
	};
	var msgTmpl = doT.template(
		'{{=it.nick}}: {{=it.data}}'
	);
	Rekt.onMsg = function(msg) {
		var m = document.createElement('div');
		m.class = 'msg';
		m.innerHTML = msgTmpl(msg);
		Rekt.chatEl.appendChild(m);
		if (!Rekt.scrolledUp) {
			Rekt.chatEl.parentElement.scrollTop = Rekt.chatEl.parentElement.scrollHeight;
			while (Rekt.chatEl.children.length > 300) {
				Rekt.chatEl.firstChild.remove();
			}
		}
		Rekt.process();
	};
	Rekt.getStartedAt = function(vstr, callback) {
		var m = vstr.match(/((twitch.tv\/destiny\/)?b\/)?(\d+)/);
		var videoId = '';
		if (m) {
			videoId = 'a'+m[3];
			Rekt.archiveId = m[3];
		} else {
			m = vstr.match(/(b|c)\d+/);
			if (m) {
				videoId = m[0];
			} else {
				console.log('dont understand ', vstr);
			}
		}
		Twitch.api({method: 'videos/'+videoId}, function(error, video) {
			if (error !== null) {
				console.log(error);
				return;
			}
			// jshint camelcase: false
			Rekt.setTime(new Date(video.recorded_at));
			// jshint camelcase: true
			callback(Rekt.time);
		});
	};
	Rekt.setTime = function (t) {
		Rekt.time = t;
		Rekt.offset = Date.now() - Rekt.time.getTime();
	};
	var videoTmpl = doT.template(
	 '<object id="twitch-embed" type="application/x-shockwave-flash" data="http://www.twitch.tv/widgets/archive_embed_player.swf" width="100%" height="100%" style="display: block !important;">' +
	 '<param name="movie" value="http://www.twitch.tv/widgets/archive_embed_player.swf">' +
	 '<param name="quality" value="best">' +
	 '<param name="allowFullScreen" value="true">' +
	 '<param name="allowScriptAccess" value="always">' +
	 '<param name="pluginspage" value="http://www.macromedia.com/go/getflashplayer">' +
	 '<param name="autoplay" value="false">' +
	 '<param name="autostart" value="false">' +
	 '<param name="flashvars" value="archive_id={{= it.archiveId }}&hostname=www.twitch.tv&channel=destiny&auto_play=true&eventsCallback=r.playerEvent">' +
	 '</object>');
	Rekt.insertVideo = function() {
		Rekt.videoEl.innerHTML = videoTmpl({archiveId: Rekt.archiveId});
		Rekt.player = document.getElementById('twitch-embed');
	};
	Rekt.playerEvent = function(es) {
		for (var i = 0; i < es.length; i++) {
			var e = es[i];
			console.log('player event:', e);
			if (e.event === 'videoLoaded') {
				if (Rekt.seekTo) {
					Rekt.player.videoSeek(Rekt.seekTo);
				}
			}
			if (e.event === 'videoPlaying') {
				Rekt.videoStarted = Date.now();
				setInterval(Rekt.fixSeek, 1000);
			}
		}
	};
	Rekt.fixSeek = function() {
		var supposedVideoTime = Date.now() - Rekt.videoStarted;
		var actualVideoTime = Rekt.player.getVideoTime() * 1000;
		var millisAhead = supposedVideoTime - actualVideoTime;
		if (Math.abs(millisAhead) > 5000 && !Rekt.paused && !Rekt.player.isPaused() && actualVideoTime !== Rekt.lastVideoTime) {
			console.log('Seek of ' + millisAhead / 1000 + ' seconds detected');
			Rekt.stop();
			Rekt.clear();
			Rekt.setTime(new Date(Rekt.time.getTime() - millisAhead));
			Rekt.videoStarted = Date.now() - actualVideoTime;
			Rekt.start();
		}
		Rekt.lastVideoTime = actualVideoTime;
	};
	return Rekt;
};

var r;

// jshint undef:false
Twitch.init({clientId: '3ccszp1i7lvkkyb4npiizsy3ida8jtt'}, function(error) {
	// jshint undef:true
	if (error !== null) {
		console.log('TWITCH INIT FAILED ', error);
	}
	r = new Rekt({
		chatEl: document.getElementById('msgs'),
		videoEl: document.getElementById('video'),
		playEl: document.getElementById('play-btn'),
		timeEl: document.getElementById('time')
	});
	var o = getQueryVariable('o');
	if (o && o.match(/^-?\d+$/)) {
		$('#offset-adj').val(o);
		r.offsetAdj = o * 1;
	}
	var b = getQueryVariable('b');
	if (b && b.match(/^\d+$/)) {
		var t = getQueryVariable('t');
		var tn = 0;
		if (t) {
			tn = parseDuration(t);
			if (tn === null) {
				tn = 0;
			} else {
				console.log('seeking ' + tn + ' seconds ahead');
			}
		}
		$('#videoUrl').val('b/'+b);
		r.getStartedAt('b/'+b, function() {
			r.setTime(new Date(r.time.getTime() + tn*1000));
			r.start();
			r.insertVideo();
			r.seekTo = tn;
			$('#timepicker').data('DateTimePicker').setDate(r.time);
		});
	} else {
		$('#videoUrl').keypress(function(e) {
			e.preventDefault();
			if (e.keyCode === 13) {
				r.getStartedAt(this.value, function() {
					r.start();
					r.insertVideo();
					$('#timepicker').data('DateTimePicker').setDate(r.time);
				});
			}
			return false;
		});
	}
});
var tp = $('#timepicker');
tp.datetimepicker();
tp.on('dp.change', function(e) {
	r.stop();
	r.clear();
	r.setTime(e.date.toDate());
	r.start();
});
$('#control-menu-btn').click(function() {
	$('#control-menu').toggle();
});
$('#offset-adj').change(function() {
	r.offsetAdj = this.value * 1;
});
$('#reload-logs').click(function() {
	r.stop();
	r.clear();
	r.start();
});
<|endoftext|>


<commit_before>Ext.define('Densa.mvc.bindable.ParentAssociation', {
    extend: 'Densa.mvc.bindable.Abstract',

    bindable: null,
    associationName: null,
    reloadRowOnSave: true,

    init: function()
    {
        this.callParent(arguments);
        if (!this.bindable) {
            Ext.Error.raise('bindable config is required');
        }
        if (!this.bindable.isBindableController) {
            this.bindable = this.bindable.getController();
        }
        if (!this.bindable.isBindableController) {
            Ext.Error.raise('bindable is not a bindableController');
        }
        if (!this.associationName) {
            Ext.Error.raise('associationName is required');
        }
        if (this.reloadRowOnSave) {
            //_parentRowStore is used so we can listen to 'write' event
            this._parentRowStore = new Ext4.data.Store({
                proxy: 'memory'
            });
            this._parentRowStore.on('write', this._reloadLoadedRow, this);
        }
    },

    _reloadLoadedRow: function()
    {
        var r = this.getLoadedRecord();
        if (r && !r.phantom) {
            r.self.load(r.getId(), {
                success: function(loadedRow) {
                    r.beginEdit();
                    r.set(loadedRow.getData());
                    r.endEdit();
                    r.commit();
                },
                scope: this
            });
        }
    },

    load: function(row, store)
    {
        this._loadedRecord = row;
        var getterName;
        row.associations.each(function(i) {
            if (i instanceof Ext.data.association.BelongsTo) {
                if (i.name == this.associationName) {
                    getterName = i.getterName;
                }
            }
        }, this);
        if (!getterName) Ext.Error.raise("Can't find getterName for ");
        row[getterName](function(parentRow) {
            this.bindable.load(parentRow);
            if (this.reloadRowOnSave) {
                this._parentRowStore.removeAll();
                parentRow.join(this._parentRowStore);
            }
        }, this);

    },
    reset: function()
    {
        return this.bindable.reset();
    },

    isDirty: function()
    {
        return this.bindable.isDirty();
    },

    isValid: function()
    {
        return this.bindable.isValid();
    },

    save: function(syncQueue)
    {
        return this.bindable.save(syncQueue);
    },

    getLoadedRecord: function()
    {
        return this._loadedRecord;
    },

    enable: function()
    {
        return this.bindable.enable();
    },
    disable: function()
    {
        return this.bindable.disable();
    },
    getPanel: function()
    {
        return this.bindable.getPanel();
    },
    onAdd: function()
    {
        return this.bindable.onAdd();
    },
    allowDelete: function()
    {
        return this.bindable.allowDelete();
    },
    allowSave: function()
    {
        return this.bindable.allowSave();
    }
});
<commit_msg>don't use Ext4, in densajs we have Ext in standard Ext namespace<commit_after>Ext.define('Densa.mvc.bindable.ParentAssociation', {
    extend: 'Densa.mvc.bindable.Abstract',

    bindable: null,
    associationName: null,
    reloadRowOnSave: true,

    init: function()
    {
        this.callParent(arguments);
        if (!this.bindable) {
            Ext.Error.raise('bindable config is required');
        }
        if (!this.bindable.isBindableController) {
            this.bindable = this.bindable.getController();
        }
        if (!this.bindable.isBindableController) {
            Ext.Error.raise('bindable is not a bindableController');
        }
        if (!this.associationName) {
            Ext.Error.raise('associationName is required');
        }
        if (this.reloadRowOnSave) {
            //_parentRowStore is used so we can listen to 'write' event
            this._parentRowStore = new Ext.data.Store({
                proxy: 'memory'
            });
            this._parentRowStore.on('write', this._reloadLoadedRow, this);
        }
    },

    _reloadLoadedRow: function()
    {
        var r = this.getLoadedRecord();
        if (r && !r.phantom) {
            r.self.load(r.getId(), {
                success: function(loadedRow) {
                    r.beginEdit();
                    r.set(loadedRow.getData());
                    r.endEdit();
                    r.commit();
                },
                scope: this
            });
        }
    },

    load: function(row, store)
    {
        this._loadedRecord = row;
        var getterName;
        row.associations.each(function(i) {
            if (i instanceof Ext.data.association.BelongsTo) {
                if (i.name == this.associationName) {
                    getterName = i.getterName;
                }
            }
        }, this);
        if (!getterName) Ext.Error.raise("Can't find getterName for ");
        row[getterName](function(parentRow) {
            this.bindable.load(parentRow);
            if (this.reloadRowOnSave) {
                this._parentRowStore.removeAll();
                parentRow.join(this._parentRowStore);
            }
        }, this);

    },
    reset: function()
    {
        return this.bindable.reset();
    },

    isDirty: function()
    {
        return this.bindable.isDirty();
    },

    isValid: function()
    {
        return this.bindable.isValid();
    },

    save: function(syncQueue)
    {
        return this.bindable.save(syncQueue);
    },

    getLoadedRecord: function()
    {
        return this._loadedRecord;
    },

    enable: function()
    {
        return this.bindable.enable();
    },
    disable: function()
    {
        return this.bindable.disable();
    },
    getPanel: function()
    {
        return this.bindable.getPanel();
    },
    onAdd: function()
    {
        return this.bindable.onAdd();
    },
    allowDelete: function()
    {
        return this.bindable.allowDelete();
    },
    allowSave: function()
    {
        return this.bindable.allowSave();
    }
});
<|endoftext|>


<commit_before>/*! @license
 * Shaka Player
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

goog.require('goog.asserts');
goog.require('shaka.Player');
goog.require('shaka.log');
goog.require('shaka.polyfill');
goog.require('shaka.util.Error');
goog.require('shaka.util.Platform');

// If ENABLE_DEBUG_LOADER is not set to false, goog.require() will try to load
// extra sources on-the-fly using pre-computed pathes in deps.js, which is not
// applicable for the tests.
goog['ENABLE_DEBUG_LOADER'] = false;

/**
 * Gets the value of an argument passed from karma.
 * @param {string} name
 * @return {?}
 */
function getClientArg(name) {
  if (window.__karma__ && __karma__.config.args.length) {
    return __karma__.config.args[0][name] || null;
  } else {
    return null;
  }
}

// Executed before test utilities and tests are loaded, but after Shaka Player
// is loaded in uncompiled mode.
(() => {
  // eslint-disable-next-line no-restricted-syntax
  const realAssert = console.assert.bind(console);

  /**
   * A version of assert() which hooks into jasmine and converts all failed
   * assertions into failed tests.
   * @param {*} condition
   * @param {string=} message
   */
  function jasmineAssert(condition, message) {
    realAssert(condition, message);
    if (!condition) {
      message = message || 'Assertion failed.';
      console.error(message);
      fail(message);
    }
  }
  goog.asserts.assert = jasmineAssert;
  console.assert = /** @type {?} */(jasmineAssert);

  /**
   * Patches a function on Element to fail an assertion if we use a namespaced
   * name on it.  We should use the namespace-aware versions instead.
   */
  function patchNamespaceFunction(type, name) {
    // eslint-disable-next-line no-restricted-syntax
    const real = type.prototype[name];
    /** @this {Element} */
    // eslint-disable-next-line no-restricted-syntax
    type.prototype[name] = function(arg) {
      // Ignore xml: namespaces since it's builtin.
      if (!arg.startsWith('xml:') && !arg.startsWith('xmlns:') &&
          arg.includes(':')) {
        fail('Use namespace-aware ' + name);
      }
      // eslint-disable-next-line no-restricted-syntax
      return real.apply(this, arguments);
    };
  }
  patchNamespaceFunction(Element, 'getAttribute');
  patchNamespaceFunction(Element, 'hasAttribute');
  patchNamespaceFunction(Element, 'getElementsByTagName');

  // As of Feb 2018, this is only implemented in Chrome.
  // https://developer.mozilla.org/en-US/docs/Web/Events/unhandledrejection
  window.addEventListener('unhandledrejection', (event) => {
    /** @type {?} */
    const error = event.reason;
    let message = 'Unhandled rejection in Promise: ' + error;

    // Shaka errors have the stack trace in their toString() already, so don't
    // add it again.  For native errors, we need to see where it came from.
    if (error && error.stack && !(error instanceof shaka.util.Error)) {
      message += '\n' + error.stack;
    }
    fail(message);
  });

  // Scrollbars ruin our screenshots on Safari.  In the past, we had applied
  // fixed offsets to the width to correct the scaling factor, but this was a
  // hack and inconsistent.  The best thing to do is completely disable
  // scrollbars through CSS.  This ensures that neither the inner iframe nor the
  // top-level window have scrollbars, which makes screenshots on Safari
  // consistent across versions.

  // Disable scrolling on the inner document, the execution context.
  const innerStyle = document.createElement('style');
  innerStyle.innerText = '::-webkit-scrollbar { display: none; }\n';
  innerStyle.innerText += 'body { overflow: hidden }\n';
  document.head.appendChild(innerStyle);

  try {
    // Disable scrolling on the outer document, the host context.
    const outerStyle = document.createElement('style');
    outerStyle.innerText = innerStyle.innerText;
    top.document.head.appendChild(outerStyle);

    // eslint-disable-next-line no-restricted-syntax
  } catch (error) {
    // On some platforms (Chromecast, Tizen), we are prevented from accessing
    // the host context, even though it should be in the same origin.  Ignore
    // errors here, so that the rest of the critical boot sequence can complete.
  }

  // The spec filter callback occurs before calls to beforeAll, so we need to
  // install polyfills here to ensure that browser support is correctly
  // detected.
  shaka.polyfill.installAll();

  // Jasmine's clock mocks seem to interfere with Edge's Promise implementation.
  // This is only the case if Promises are first used after installing the mock.
  // As long as a then() callback on a Promise has happened once beforehand, it
  // seems to be OK.  I suspect Edge's Promise implementation is actually not in
  // native code, but rather something like a polyfill that binds to timer calls
  // the first time it needs to schedule something.
  Promise.resolve().then(() => {});

  const timeout = getClientArg('testTimeout');
  if (timeout) {
    jasmine.DEFAULT_TIMEOUT_INTERVAL = Number(timeout);
  }

  const logLevel = getClientArg('logLevel');
  if (logLevel) {
    shaka.log.setLevel(Number(logLevel));
  } else {
    shaka.log.setLevel(shaka.log.Level.INFO);
  }

  /**
   * Returns a Jasmine callback which shims the real callback and checks for
   * a certain condition.  The test will only be run if the condition is true.
   *
   * @param {jasmine.Callback} callback  The test callback.
   * @param {function():*} cond
   * @param {?string} skipMessage  The message used when skipping a test; or
   *   null to not use pending().  This should only be null for before/after
   *   blocks.
   * @return {jasmine.Callback}
   */
  function filterShim(callback, cond, skipMessage) {
    return async () => {
      const val = await cond();
      if (!val) {
        if (skipMessage) {
          pending(skipMessage);
        }
        return;
      }

      if (callback.length) {
        // If this has a done callback, wrap in a Promise so we can await it.
        await new Promise((resolve) => callback(resolve));
      } else {
        // If this is an async test, this will wait for it to complete; if this
        // is a synchronous test, await will do nothing.
        await callback();
      }
    };
  }

  /**
   * Run a test that uses a DRM license server.
   *
   * @param {string} name
   * @param {jasmine.Callback} callback
   */
  window.drmIt = (name, callback) => {
    const shim = filterShim(
        callback, () => getClientArg('drm'),
        'Skipping tests that use a DRM license server.');
    it(name, shim);
  };

  /**
   * Run a test that has been quarantined.
   *
   * @param {string} name
   * @param {jasmine.Callback} callback
   */
  window.quarantinedIt = (name, callback) => {
    const shim = filterShim(
        callback, () => getClientArg('quarantined'),
        'Skipping tests that are quarantined.');
    it(name, shim);
  };

  /**
   * Run contained tests when the condition is true.
   *
   * @param {string} describeName  The name of the describe() block.
   * @param {function():*} cond A function for the condition; if this returns
   *   a truthy value, the tests will run, falsy will skip the tests.
   * @param {function()} describeBody The body of the describe() block.  This
   *   function will call before/after/it functions to define tests.
   */
  window.filterDescribe = (describeName, cond, describeBody) => {
    describe(describeName, () => {
      const old = {};
      for (const methodName of ['fit', 'it']) {
        old[methodName] = window[methodName];
        window[methodName] = (testName, testBody, ...rest) => {
          const shim = filterShim(
              testBody, cond, 'Skipping test due to platform support');
          return old[methodName](testName, shim, ...rest);
        };
      }
      const otherNames = ['afterAll', 'afterEach', 'beforeAll', 'beforeEach'];
      for (const methodName of otherNames) {
        old[methodName] = window[methodName];
        window[methodName] = (body, ...rest) => {
          const shim = filterShim(body, cond, null);
          return old[methodName](shim, ...rest);
        };
      }

      describeBody();

      for (const methodName in old) {
        window[methodName] = old[methodName];
      }
    });
  };

  /**
   * Unconditionally skip contained tests that would normally be run
   * conditionally.  Used to temporarily disable tests that use filterDescribe.
   * See filterDescribe above.
   *
   * @param {string} describeName
   * @param {function():*} cond
   * @param {function()} describeBody
   */
  window.xfilterDescribe = (describeName, cond, describeBody) => {
    const oldDescribe = window['describe'];
    window['describe'] = window['xdescribe'];
    filterDescribe(describeName, cond, describeBody);
    window['describe'] = oldDescribe;
  };

  beforeAll((done) => {  // eslint-disable-line no-restricted-syntax
    // Configure AMD modules and their dependencies.
    require.config({
      baseUrl: '/base/node_modules',
      packages: [
        {
          name: 'sprintf-js',
          main: 'src/sprintf',
        },
        {
          name: 'less',
          main: 'dist/less',
        },
        {
          name: 'fontfaceonload',
          main: 'dist/fontfaceonload',
        },
      ],
    });

    // Load required AMD modules, then proceed with tests.
    require(['sprintf-js', 'less', 'fontfaceonload'],
        (sprintfJs, less, FontFaceOnload) => {
          // These external interfaces are declared as "const" in the externs.
          // Avoid "const"-ness complaints from the compiler by assigning these
          // using bracket notation.
          window['sprintf'] = sprintfJs.sprintf;
          window['less'] = less;
          window['FontFaceOnload'] = FontFaceOnload;

          done();
        });
  });

  const originalSetTimeout = window.setTimeout;
  const delayTests = getClientArg('delayTests');
  if (delayTests) {
    afterEach((done) => {  // eslint-disable-line no-restricted-syntax
      console.log('Delaying test by ' + delayTests + ' seconds...');
      originalSetTimeout(done, delayTests * 1000);
    });
  }

  // Work-around: allow the Tizen media pipeline to cool down.
  // Without this, Tizen's pipeline seems to hang in subsequent tests.
  // TODO: file a bug on Tizen
  if (shaka.util.Platform.isTizen()) {
    afterEach((done) => {  // eslint-disable-line no-restricted-syntax
      originalSetTimeout(done, /* ms= */ 100);
    });
  }

  // Code in karma-jasmine's adapter will malform test failures when the
  // expectation message contains a stack trace, losing the failure message and
  // mixing up the stack trace of the failure.  To avoid this, we modify
  // shaka.util.Error not to create a stack trace.  This trace is not available
  // in production, and there is never any need for it in the tests.
  // Shimming shaka.util.Error proved too complicated because of a combination
  // of compiler restrictions and ES6 language features, so this is by far the
  // simpler answer.
  shaka.util.Error.createStack = false;
})();

// Shim Jasmine's execute function.  The karma-jasmine adapter will configure
// jasmine in a way that prevents us from setting our own specFilter config.
// There is no configuration that will stop karma-jasmine from doing this.
// So we hook into Jasmine's execute function (the last step of karma-jasmine's
// startup) to set our own config first.
// See also https://github.com/karma-runner/karma-jasmine/issues/273
/** @type {!jasmine.Env} */
const jasmineEnv = jasmine.getEnv();
// eslint-disable-next-line no-restricted-syntax
const originalJasmineExecute = jasmineEnv.execute.bind(jasmineEnv);
jasmineEnv.execute = () => {
  // Use a RegExp if --filter is set, else empty string will match all.
  const specFilterRegExp = new RegExp(getClientArg('filter') || '');
  const isBrowserSupported = shaka.Player.isBrowserSupported();

  /**
   * A filter over all Jasmine specs.
   * @param {jasmine.Spec} spec
   * @return {boolean}
   */
  function specFilter(spec) {
    // If the browser is not supported, don't run the tests.
    // If the user specified a RegExp, only run the matched tests.
    // Running zero tests is considered an error so the test run will fail on
    // unsupported browsers or if the filter doesn't match any specs.
    return isBrowserSupported && specFilterRegExp.test(spec.getFullName());
  }

  // Set jasmine config.
  const jasmineConfig = {
    specFilter,
    random: !!getClientArg('random'),
    seed: getClientArg('seed'),
  };

  jasmineEnv.configure(jasmineConfig);
  originalJasmineExecute();
};
<commit_msg>test: Detect Jasmine boot sequence failures early<commit_after>/*! @license
 * Shaka Player
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

goog.require('goog.asserts');
goog.require('shaka.Player');
goog.require('shaka.log');
goog.require('shaka.polyfill');
goog.require('shaka.util.Error');
goog.require('shaka.util.Platform');

// If ENABLE_DEBUG_LOADER is not set to false, goog.require() will try to load
// extra sources on-the-fly using pre-computed pathes in deps.js, which is not
// applicable for the tests.
goog['ENABLE_DEBUG_LOADER'] = false;

/**
 * Gets the value of an argument passed from karma.
 * @param {string} name
 * @return {?}
 */
function getClientArg(name) {
  if (window.__karma__ && __karma__.config.args.length) {
    return __karma__.config.args[0][name] || null;
  } else {
    return null;
  }
}

function failTestsOnFailedAssertions() {
  // eslint-disable-next-line no-restricted-syntax
  const realAssert = console.assert.bind(console);

  /**
   * A version of assert() which hooks into jasmine and converts all failed
   * assertions into failed tests.
   * @param {*} condition
   * @param {string=} message
   */
  function jasmineAssert(condition, message) {
    realAssert(condition, message);
    if (!condition) {
      message = message || 'Assertion failed.';
      console.error(message);
      fail(message);
    }
  }
  goog.asserts.assert = jasmineAssert;
  console.assert = /** @type {?} */(jasmineAssert);
}

/**
 * Patches a function on Element to fail an assertion if we use a namespaced
 * name on it.  We should use the namespace-aware versions instead.
 */
function failTestsOnNamespacedElementOrAttributeNames() {
  const patchElementNamespaceFunction = (name) => {
    // eslint-disable-next-line no-restricted-syntax
    const real = Element.prototype[name];
    /** @this {Element} */
    // eslint-disable-next-line no-restricted-syntax
    Element.prototype[name] = function(arg) {
      // Ignore xml: namespaces since it's builtin.
      if (!arg.startsWith('xml:') && !arg.startsWith('xmlns:') &&
          arg.includes(':')) {
        fail('Use namespace-aware ' + name);
      }
      // eslint-disable-next-line no-restricted-syntax
      return real.apply(this, arguments);
    };
  };

  patchElementNamespaceFunction('getAttribute');
  patchElementNamespaceFunction('hasAttribute');
  patchElementNamespaceFunction('getElementsByTagName');
}

/**
 * Listen for unhandled Promise rejections (which may occur after a test) and
 * convert them into test failures.
 */
function failTestsOnUnhandledRejections() {
  // https://developer.mozilla.org/en-US/docs/Web/Events/unhandledrejection
  window.addEventListener('unhandledrejection', (event) => {
    /** @type {?} */
    const error = event.reason;
    let message = 'Unhandled rejection in Promise: ' + error;

    // Shaka errors have the stack trace in their toString() already, so don't
    // add it again.  For native errors, we need to see where it came from.
    if (error && error.stack && !(error instanceof shaka.util.Error)) {
      message += '\n' + error.stack;
    }
    fail(message);
  });
}

/**
 * Scrollbars ruin our screenshots, in particular on Safari.  Disable all
 * scrollbars in CSS to ensure consistent screenshots.
 */
function disableScrollbars() {
  // In the past, we had applied fixed offsets to the width on Safari to account
  // for scrollbars and correct the scaling factor, but this was a hack and
  // inconsistent.  The best thing to do is completely disable scrollbars
  // through CSS.  This ensures that neither the inner iframe nor the top-level
  // window have scrollbars, which makes screenshots on Safari consistent across
  // versions.

  // Disable scrolling on the inner document, the execution context.
  const innerStyle = document.createElement('style');
  innerStyle.innerText = '::-webkit-scrollbar { display: none; }\n';
  innerStyle.innerText += 'body { overflow: hidden }\n';
  document.head.appendChild(innerStyle);

  try {
    // Disable scrolling on the outer document, the host context.
    const outerStyle = document.createElement('style');
    outerStyle.innerText = innerStyle.innerText;
    top.document.head.appendChild(outerStyle);

    // eslint-disable-next-line no-restricted-syntax
  } catch (error) {
    // On some platforms (Chromecast, Tizen), we are prevented from accessing
    // the host context, even though it should be in the same origin.  Ignore
    // errors here, so that the rest of the critical boot sequence can complete.
  }
}

/**
 * Work around issues with legacy Edge's Promise implementation.
 */
function workAroundLegacyEdgePromiseIssues() {
  // Jasmine's clock mocks seem to interfere with legacy Edge's Promise
  // implementation.  This is only the case if Promises are first used after
  // installing the mock.  As long as a then() callback on a Promise has
  // happened once beforehand, it seems to be OK.  I suspect Edge's Promise
  // implementation is actually not in native code, but rather something like a
  // polyfill that binds to timer calls the first time it needs to schedule
  // something.
  Promise.resolve().then(() => {});
}

/**
 * Returns a Jasmine callback which shims the real callback and checks for
 * a certain condition.  The test will only be run if the condition is true.
 *
 * @param {jasmine.Callback} callback  The test callback.
 * @param {function():*} cond
 * @param {?string} skipMessage  The message used when skipping a test; or
 *   null to not use pending().  This should only be null for before/after
 *   blocks.
 * @return {jasmine.Callback}
 */
function filterShim(callback, cond, skipMessage) {
  return async () => {
    const val = await cond();
    if (!val) {
      if (skipMessage) {
        pending(skipMessage);
      }
      return;
    }

    if (callback.length) {
      // If this has a done callback, wrap in a Promise so we can await it.
      await new Promise((resolve) => callback(resolve));
    } else {
      // If this is an async test, this will wait for it to complete; if this
      // is a synchronous test, await will do nothing.
      await callback();
    }
  };
}

/**
 * Run a test that uses a DRM license server.
 *
 * @param {string} name
 * @param {jasmine.Callback} callback
 */
window.drmIt = (name, callback) => {
  const shim = filterShim(
      callback, () => getClientArg('drm'),
      'Skipping tests that use a DRM license server.');
  it(name, shim);
};

/**
 * Run a test that has been quarantined.
 *
 * @param {string} name
 * @param {jasmine.Callback} callback
 */
window.quarantinedIt = (name, callback) => {
  const shim = filterShim(
      callback, () => getClientArg('quarantined'),
      'Skipping tests that are quarantined.');
  it(name, shim);
};

/**
 * Run contained tests when the condition is true.
 *
 * @param {string} describeName  The name of the describe() block.
 * @param {function():*} cond A function for the condition; if this returns
 *   a truthy value, the tests will run, falsy will skip the tests.
 * @param {function()} describeBody The body of the describe() block.  This
 *   function will call before/after/it functions to define tests.
 */
window.filterDescribe = (describeName, cond, describeBody) => {
  describe(describeName, () => {
    const old = {};
    for (const methodName of ['fit', 'it']) {
      old[methodName] = window[methodName];
      window[methodName] = (testName, testBody, ...rest) => {
        const shim = filterShim(
            testBody, cond, 'Skipping test due to platform support');
        return old[methodName](testName, shim, ...rest);
      };
    }
    const otherNames = ['afterAll', 'afterEach', 'beforeAll', 'beforeEach'];
    for (const methodName of otherNames) {
      old[methodName] = window[methodName];
      window[methodName] = (body, ...rest) => {
        const shim = filterShim(body, cond, null);
        return old[methodName](shim, ...rest);
      };
    }

    describeBody();

    for (const methodName in old) {
      window[methodName] = old[methodName];
    }
  });
};

/**
 * Unconditionally skip contained tests that would normally be run
 * conditionally.  Used to temporarily disable tests that use filterDescribe.
 * See filterDescribe above.
 *
 * @param {string} describeName
 * @param {function():*} cond
 * @param {function()} describeBody
 */
window.xfilterDescribe = (describeName, cond, describeBody) => {
  const oldDescribe = window['describe'];
  window['describe'] = window['xdescribe'];
  filterDescribe(describeName, cond, describeBody);
  window['describe'] = oldDescribe;
};

/**
 * Load node modules used in testing and install them into window.
 * @return {!Promise}
 */
function loadNodeModules() {
  return new Promise((resolve) => {
    // Configure AMD modules and their dependencies.
    require.config({
      baseUrl: '/base/node_modules',
      packages: [
        {
          name: 'sprintf-js',
          main: 'src/sprintf',
        },
        {
          name: 'less',
          main: 'dist/less',
        },
        {
          name: 'fontfaceonload',
          main: 'dist/fontfaceonload',
        },
      ],
    });

    // Load required AMD modules, then proceed with tests.
    require(['sprintf-js', 'less', 'fontfaceonload'],
        (sprintfJs, less, FontFaceOnload) => {
          // These external interfaces are declared as "const" in the externs.
          // Avoid "const"-ness complaints from the compiler by assigning these
          // using bracket notation.
          window['sprintf'] = sprintfJs.sprintf;
          window['less'] = less;
          window['FontFaceOnload'] = FontFaceOnload;

          resolve();
        });
  });
}

/**
 * Apply Jasmine settings and set up anything else needed by the testing
 * environment.
 */
function configureJasmineEnvironment() {
  const timeout = getClientArg('testTimeout');
  if (timeout) {
    jasmine.DEFAULT_TIMEOUT_INTERVAL = Number(timeout);
  }

  const logLevel = getClientArg('logLevel');
  if (logLevel) {
    shaka.log.setLevel(Number(logLevel));
  } else {
    shaka.log.setLevel(shaka.log.Level.INFO);
  }

  // Ensure node modules are loaded before any tests execute.
  beforeAll(async () => {
    await loadNodeModules();
  });

  const originalSetTimeout = window.setTimeout;
  const delayTests = getClientArg('delayTests');
  if (delayTests) {
    afterEach((done) => {  // eslint-disable-line no-restricted-syntax
      console.log('Delaying test by ' + delayTests + ' seconds...');
      originalSetTimeout(done, delayTests * 1000);
    });
  }

  // Work-around: allow the Tizen media pipeline to cool down.
  // Without this, Tizen's pipeline seems to hang in subsequent tests.
  // TODO: file a bug on Tizen
  if (shaka.util.Platform.isTizen()) {
    afterEach((done) => {  // eslint-disable-line no-restricted-syntax
      originalSetTimeout(done, /* ms= */ 100);
    });
  }

  // Code in karma-jasmine's adapter will malform test failures when the
  // expectation message contains a stack trace, losing the failure message and
  // mixing up the stack trace of the failure.  To avoid this, we modify
  // shaka.util.Error not to create a stack trace.  This trace is not available
  // in production, and there is never any need for it in the tests.
  // Shimming shaka.util.Error proved too complicated because of a combination
  // of compiler restrictions and ES6 language features, so this is by far the
  // simpler answer.
  shaka.util.Error.createStack = false;

  // Shim Jasmine's execute function.  The karma-jasmine adapter will configure
  // jasmine in a way that prevents us from setting our own specFilter config.
  // There is no configuration that will stop karma-jasmine from doing this.
  // So we hook into Jasmine's execute function (the last step of
  // karma-jasmine's startup) to set our own config first.
  // See also https://github.com/karma-runner/karma-jasmine/issues/273
  /** @type {!jasmine.Env} */
  const jasmineEnv = jasmine.getEnv();
  // eslint-disable-next-line no-restricted-syntax
  const originalJasmineExecute = jasmineEnv.execute.bind(jasmineEnv);
  jasmineEnv.execute = () => {
    // Use a RegExp if --filter is set, else empty string will match all.
    const specFilterRegExp = new RegExp(getClientArg('filter') || '');
    const isBrowserSupported = shaka.Player.isBrowserSupported();

    /**
     * A filter over all Jasmine specs.
     * @param {jasmine.Spec} spec
     * @return {boolean}
     */
    function specFilter(spec) {
      // If the browser is not supported, don't run the tests.
      // If the user specified a RegExp, only run the matched tests.
      // Running zero tests is considered an error so the test run will fail on
      // unsupported browsers or if the filter doesn't match any specs.
      return isBrowserSupported && specFilterRegExp.test(spec.getFullName());
    }

    // Set jasmine config.
    const jasmineConfig = {
      specFilter,
      random: !!getClientArg('random'),
      seed: getClientArg('seed'),
    };

    jasmineEnv.configure(jasmineConfig);
    originalJasmineExecute();
  };
}

// Executed before test utilities and tests are loaded, but after Shaka Player
// is loaded in uncompiled mode.
try {
  failTestsOnFailedAssertions();
  failTestsOnNamespacedElementOrAttributeNames();
  failTestsOnUnhandledRejections();
  disableScrollbars();
  workAroundLegacyEdgePromiseIssues();

  // The spec filter callback occurs before calls to beforeAll, so we need to
  // install polyfills here to ensure that browser support is correctly
  // detected.
  shaka.polyfill.installAll();

  configureJasmineEnvironment();

  // eslint-disable-next-line no-restricted-syntax
} catch (error) {
  // Throw this boot-sequence error in place of jasmine's execute method, to
  // fail clearly and right away without running any tests.
  jasmine.getEnv().execute = () => {
    throw error;
  };
}
<|endoftext|>