<commit_before>// Copyright 2013-2015, University of Colorado Boulder

/**
 * QR decomposition, based on Jama (http://math.nist.gov/javanumerics/jama/)
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

define( function( require ) {
  'use strict';

  var dot = require( 'DOT/dot' );

  var Float32Array = window.Float32Array || Array;

  // require( 'DOT/Matrix' ); // commented out so Require.js doesn't complain about the circular dependency

  dot.QRDecomposition = function QRDecomposition( matrix ) {
    this.matrix = matrix;

    // TODO: size!
    this.QR = matrix.getArrayCopy();
    var QR = this.QR;
    this.m = matrix.getRowDimension();
    var m = this.m;
    this.n = matrix.getColumnDimension();
    var n = this.n;

    this.Rdiag = [];

    var i;
    var j;
    var k;

    // Main loop.
    for ( k = 0; k < n; k++ ) {
      // Compute 2-norm of k-th column without under/overflow.
      var nrm = 0;
      for ( i = k; i < m; i++ ) {
        nrm = dot.Matrix.hypot( nrm, QR[ this.matrix.index( i, k ) ] );
        console.log( 'nrm[' + i + ']=', nrm );
      }

      if ( nrm !== 0.0 ) {
        // Form k-th Householder vector.
        if ( QR[ this.matrix.index( k, k ) ] < 0 ) {
          nrm = -nrm;
        }
        for ( i = k; i < m; i++ ) {
          QR[ this.matrix.index( i, k ) ] /= nrm;
        }
        QR[ this.matrix.index( k, k ) ] += 1.0;

        // Apply transformation to remaining columns.
        for ( j = k + 1; j < n; j++ ) {
          var s = 0.0;
          for ( i = k; i < m; i++ ) {
            s += QR[ this.matrix.index( i, k ) ] * QR[ this.matrix.index( i, j ) ];
          }
          s = -s / QR[ this.matrix.index( k, k ) ];
          for ( i = k; i < m; i++ ) {
            QR[ this.matrix.index( i, j ) ] += s * QR[ this.matrix.index( i, k ) ];
          }
        }
      }
      this.Rdiag[ k ] = -nrm;
    }

    console.log( 'rdiag' );
    console.log( this.Rdiag );
    // this.Rdiag = [-1.4142135623730951, 1.4142135623730951, -7068.729282415327, -0.7071067776486961];
  };
  var QRDecomposition = dot.QRDecomposition;

  QRDecomposition.prototype = {
    constructor: QRDecomposition,

    isFullRank: function() {
      for ( var j = 0; j < this.n; j++ ) {
        if ( this.Rdiag[ j ] === 0 ) {
          return false;
        }
      }
      return true;
    },

    getH: function() {
      var result = new dot.Matrix( this.m, this.n );
      for ( var i = 0; i < this.m; i++ ) {
        for ( var j = 0; j < this.n; j++ ) {
          if ( i >= j ) {
            result.entries[ result.index( i, j ) ] = this.QR[ this.matrix.index( i, j ) ];
          }
          else {
            result.entries[ result.index( i, j ) ] = 0.0;
          }
        }
      }
      return result;
    },

    getR: function() {
      var result = new dot.Matrix( this.n, this.n );
      for ( var i = 0; i < this.n; i++ ) {
        for ( var j = 0; j < this.n; j++ ) {
          if ( i < j ) {
            result.entries[ result.index( i, j ) ] = this.QR[ this.matrix.index( i, j ) ];
          }
          else if ( i === j ) {
            result.entries[ result.index( i, j ) ] = this.Rdiag[ i ];
          }
          else {
            result.entries[ result.index( i, j ) ] = 0.0;
          }
        }
      }
      return result;
    },

    getQ: function() {
      var i;
      var j;
      var k;
      var result = new dot.Matrix( this.m, this.n );
      for ( k = this.n - 1; k >= 0; k-- ) {
        for ( i = 0; i < this.m; i++ ) {
          result.entries[ result.index( i, k ) ] = 0.0;
        }
        result.entries[ result.index( k, k ) ] = 1.0;
        for ( j = k; j < this.n; j++ ) {
          if ( this.QR[ this.matrix.index( k, k ) ] !== 0 ) {
            var s = 0.0;
            for ( i = k; i < this.m; i++ ) {
              s += this.QR[ this.matrix.index( i, k ) ] * result.entries[ result.index( i, j ) ];
            }
            s = -s / this.QR[ this.matrix.index( k, k ) ];
            for ( i = k; i < this.m; i++ ) {
              result.entries[ result.index( i, j ) ] += s * this.QR[ this.matrix.index( i, k ) ];
            }
          }
        }
      }
      return result;
    },

    solve: function( matrix ) {
      if ( matrix.getRowDimension() !== this.m ) {
        throw new Error( 'Matrix row dimensions must agree.' );
      }
      if ( !this.isFullRank() ) {
        throw new Error( 'Matrix is rank deficient.' );
      }

      var i;
      var j;
      var k;

      // Copy right hand side
      var nx = matrix.getColumnDimension();
      var X = matrix.getArrayCopy();

      // Compute Y = transpose(Q)*matrix
      for ( k = 0; k < this.n; k++ ) {
        for ( j = 0; j < nx; j++ ) {
          var s = 0.0;
          for ( i = k; i < this.m; i++ ) {
            s += this.QR[ this.matrix.index( i, k ) ] * X[ matrix.index( i, j ) ];
          }
          s = -s / this.QR[ this.matrix.index( k, k ) ];
          for ( i = k; i < this.m; i++ ) {
            X[ matrix.index( i, j ) ] += s * this.QR[ this.matrix.index( i, k ) ];
          }
        }
      }

      // Solve R*X = Y;
      for ( k = this.n - 1; k >= 0; k-- ) {
        for ( j = 0; j < nx; j++ ) {
          X[ matrix.index( k, j ) ] /= this.Rdiag[ k ];
        }
        for ( i = 0; i < k; i++ ) {
          for ( j = 0; j < nx; j++ ) {
            X[ matrix.index( i, j ) ] -= X[ matrix.index( k, j ) ] * this.QR[ this.matrix.index( i, k ) ];
          }
        }
      }
      return new dot.Matrix( this.n, nx, X, true ).getMatrix( 0, this.n - 1, 0, nx - 1 );
    }
  };

  return QRDecomposition;
} );
<commit_msg>Removed comments, see https://github.com/phetsims/circuit-construction-kit-basics/issues/78<commit_after>// Copyright 2013-2015, University of Colorado Boulder

/**
 * QR decomposition, based on Jama (http://math.nist.gov/javanumerics/jama/)
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

define( function( require ) {
  'use strict';

  var dot = require( 'DOT/dot' );

  var Float32Array = window.Float32Array || Array;

  // require( 'DOT/Matrix' ); // commented out so Require.js doesn't complain about the circular dependency

  dot.QRDecomposition = function QRDecomposition( matrix ) {
    this.matrix = matrix;

    // TODO: size!
    this.QR = matrix.getArrayCopy();
    var QR = this.QR;
    this.m = matrix.getRowDimension();
    var m = this.m;
    this.n = matrix.getColumnDimension();
    var n = this.n;

    this.Rdiag = [];

    var i;
    var j;
    var k;

    // Main loop.
    for ( k = 0; k < n; k++ ) {
      // Compute 2-norm of k-th column without under/overflow.
      var nrm = 0;
      for ( i = k; i < m; i++ ) {
        nrm = dot.Matrix.hypot( nrm, QR[ this.matrix.index( i, k ) ] );
      }

      if ( nrm !== 0.0 ) {
        // Form k-th Householder vector.
        if ( QR[ this.matrix.index( k, k ) ] < 0 ) {
          nrm = -nrm;
        }
        for ( i = k; i < m; i++ ) {
          QR[ this.matrix.index( i, k ) ] /= nrm;
        }
        QR[ this.matrix.index( k, k ) ] += 1.0;

        // Apply transformation to remaining columns.
        for ( j = k + 1; j < n; j++ ) {
          var s = 0.0;
          for ( i = k; i < m; i++ ) {
            s += QR[ this.matrix.index( i, k ) ] * QR[ this.matrix.index( i, j ) ];
          }
          s = -s / QR[ this.matrix.index( k, k ) ];
          for ( i = k; i < m; i++ ) {
            QR[ this.matrix.index( i, j ) ] += s * QR[ this.matrix.index( i, k ) ];
          }
        }
      }
      this.Rdiag[ k ] = -nrm;
    }
  };
  var QRDecomposition = dot.QRDecomposition;

  QRDecomposition.prototype = {
    constructor: QRDecomposition,

    isFullRank: function() {
      for ( var j = 0; j < this.n; j++ ) {
        if ( this.Rdiag[ j ] === 0 ) {
          return false;
        }
      }
      return true;
    },

    getH: function() {
      var result = new dot.Matrix( this.m, this.n );
      for ( var i = 0; i < this.m; i++ ) {
        for ( var j = 0; j < this.n; j++ ) {
          if ( i >= j ) {
            result.entries[ result.index( i, j ) ] = this.QR[ this.matrix.index( i, j ) ];
          }
          else {
            result.entries[ result.index( i, j ) ] = 0.0;
          }
        }
      }
      return result;
    },

    getR: function() {
      var result = new dot.Matrix( this.n, this.n );
      for ( var i = 0; i < this.n; i++ ) {
        for ( var j = 0; j < this.n; j++ ) {
          if ( i < j ) {
            result.entries[ result.index( i, j ) ] = this.QR[ this.matrix.index( i, j ) ];
          }
          else if ( i === j ) {
            result.entries[ result.index( i, j ) ] = this.Rdiag[ i ];
          }
          else {
            result.entries[ result.index( i, j ) ] = 0.0;
          }
        }
      }
      return result;
    },

    getQ: function() {
      var i;
      var j;
      var k;
      var result = new dot.Matrix( this.m, this.n );
      for ( k = this.n - 1; k >= 0; k-- ) {
        for ( i = 0; i < this.m; i++ ) {
          result.entries[ result.index( i, k ) ] = 0.0;
        }
        result.entries[ result.index( k, k ) ] = 1.0;
        for ( j = k; j < this.n; j++ ) {
          if ( this.QR[ this.matrix.index( k, k ) ] !== 0 ) {
            var s = 0.0;
            for ( i = k; i < this.m; i++ ) {
              s += this.QR[ this.matrix.index( i, k ) ] * result.entries[ result.index( i, j ) ];
            }
            s = -s / this.QR[ this.matrix.index( k, k ) ];
            for ( i = k; i < this.m; i++ ) {
              result.entries[ result.index( i, j ) ] += s * this.QR[ this.matrix.index( i, k ) ];
            }
          }
        }
      }
      return result;
    },

    solve: function( matrix ) {
      if ( matrix.getRowDimension() !== this.m ) {
        throw new Error( 'Matrix row dimensions must agree.' );
      }
      if ( !this.isFullRank() ) {
        throw new Error( 'Matrix is rank deficient.' );
      }

      var i;
      var j;
      var k;

      // Copy right hand side
      var nx = matrix.getColumnDimension();
      var X = matrix.getArrayCopy();

      // Compute Y = transpose(Q)*matrix
      for ( k = 0; k < this.n; k++ ) {
        for ( j = 0; j < nx; j++ ) {
          var s = 0.0;
          for ( i = k; i < this.m; i++ ) {
            s += this.QR[ this.matrix.index( i, k ) ] * X[ matrix.index( i, j ) ];
          }
          s = -s / this.QR[ this.matrix.index( k, k ) ];
          for ( i = k; i < this.m; i++ ) {
            X[ matrix.index( i, j ) ] += s * this.QR[ this.matrix.index( i, k ) ];
          }
        }
      }

      // Solve R*X = Y;
      for ( k = this.n - 1; k >= 0; k-- ) {
        for ( j = 0; j < nx; j++ ) {
          X[ matrix.index( k, j ) ] /= this.Rdiag[ k ];
        }
        for ( i = 0; i < k; i++ ) {
          for ( j = 0; j < nx; j++ ) {
            X[ matrix.index( i, j ) ] -= X[ matrix.index( k, j ) ] * this.QR[ this.matrix.index( i, k ) ];
          }
        }
      }
      return new dot.Matrix( this.n, nx, X, true ).getMatrix( 0, this.n - 1, 0, nx - 1 );
    }
  };

  return QRDecomposition;
} );
<|endoftext|>


<commit_before>var tap = require('tap')
var test = tap.test
var match = require('../')

test("shouldn't care about key order and types", function (t) {
  t.ok(match({ a: 1, b: 2 }, { b: 2, a: '1' }))
  t.end()
})

test('should notice objects with different shapes', function (t) {
  t.notOk(match(
    { a: 1, b: 'a thing' },
    { a: 1, b: undefined }
  ))
  t.ok(match(
    { a: 1 },
    { a: 1, b: undefined }
  ))
  t.end()
})

test('extra keys in object are ok', function (t) {
  t.ok(match(
    { a: 1, b: null, c: 'ok' },
    { a: 1, b: undefined }
  ))
  t.end()
})

test('should notice objects with different keys', function (t) {
  t.notOk(match(
    { a: 1, b: 2 },
    { a: 1, c: 2 }
  ))
  t.end()
})

test('should handle dates', function (t) {
  t.notOk(match(new Date('1972-08-01'), null))
  t.notOk(match(new Date('1972-08-01'), undefined))
  t.ok(match(new Date('1972-08-01'), new Date('1972-08-01')))
  t.ok(match({ x: new Date('1972-08-01') }, { x: new Date('1972-08-01') }))
  t.end()
})

test('should handle RegExps', function (t) {
  t.notOk(match(/[b]/, /[a]/))
  t.notOk(match(/[a]/i, /[a]/g))
  t.ok(match(/[a]/, /[a]/))
  t.ok(match(/ab?[a-z]{,6}/g, /ab?[a-z]{,6}/g))
  t.notOk(match([1, 2, 3], /asdf/))
  t.ok(match([1, 2, 3], /,2,/))
  t.ok(match({ x: 123 }, { x: /^\w+/ }))
  t.ok(match({ toString: function () { return 'FooBar' }}, /^FooBar$/))
  t.notOk(match({ toString: function () { return 'x' }}, /^FooBar$/))
  t.end()
})

test('should handle functions', function (t) {
  var fnA = function (a) { return a }
  var fnB = function (a) { return a }

  t.notOk(match(
    function a () {},
    function a () {} // but is it the _same_ a tho
  ))
  t.notOk(match(fnA, fnB))
  t.ok(match(fnA, fnA))
  t.ok(match(fnB, fnB))
  t.end()
})

test('should handle arguments', function (t) {
  var outer = arguments
  ;(function (tt) {
    var inner = arguments
    t.ok(match(outer, outer))
    t.ok(match(outer, inner))
    t.ok(match(outer, [t]))
  }(t))
  t.end()
})

test('same arrays match', function (t) {
  t.ok(match([1, 2, 3], [1, 2, 3]))
  t.end()
})

test("different arrays don't match", function (t) {
  t.notOk(match([1, 2, 3], [1, 2, 3, 4]))
  t.notOk(match([1, 2, 3], [1, 2, 4]))
  t.end()
})

test('empty arrays match', function (t) {
  t.ok(match([], []))
  t.ok(match({ x: [] }, { x: [] }))
  t.end()
})

test("shallower shouldn't care about key order recursively and types", function (t) {
  t.ok(match(
    { x: { a: 1, b: 2 }, y: { c: 3, d: 4 } },
    { y: { d: 4, c: 3 }, x: { b: '2', a: '1' } }
  ))
  t.end()
})

test('undefined is the same as itself', function (t) {
  t.ok(match(undefined, undefined))
  t.ok(match({ x: undefined }, { x: undefined }))
  t.ok(match({ x: [undefined] }, { x: [undefined] }))
  t.end()
})

test('undefined and null are Close Enough', function (t) {
  t.ok(match(undefined, null))
  t.ok(match({ x: null }, { x: undefined }))
  t.ok(match({ x: [undefined] }, { x: [null] }))
  t.end()
})

test("null is as shallow as you'd expect", function (t) {
  t.ok(match(null, null))
  t.ok(match({ x: null }, { x: null }))
  t.ok(match({ x: [null] }, { x: [null] }))
  t.end()
})

test('the same number matches', function (t) {
  t.ok(match(0, 0))
  t.ok(match(1, 1))
  t.ok(match(3.14, 3.14))
  t.end()
})

test("different numbers don't match", function (t) {
  t.notOk(match(0, 1))
  t.notOk(match(1, -1))
  t.notOk(match(3.14, 2.72))
  t.end()
})

test("tmatch shouldn't care about key order (but still might) and types", function (t) {
  t.ok(match(
    [
      { foo: { z: 100, y: 200, x: 300 } },
      'bar',
      11,
      { baz: { d: 4, a: 1, b: 2, c: 3 } }
    ],
    [
      { foo: { z: 100, y: 200, x: 300 } },
      'bar',
      11,
      { baz: { a: '1', b: '2', c: '3', d: '4' } }
    ]
  ))
  t.end()
})

test("match shouldn't blow up on circular data structures", function (t) {
  var x1 = { z: 4 }
  var y1 = { x: x1 }
  x1.y = y1

  var x2 = { z: 4 }
  var y2 = { x: x2 }
  x2.y = y2

  t.ok(match(x1, x2))
  t.end()
})

test('regexps match strings', function (t) {
  var x = { one: 'String' }
  var y = { one: /.ring$/ }
  t.ok(match(x, y))
  t.ok(match(x.one, y.one))

  x = [ 'String', 'String' ]
  y = [ /.ring$/, /.ring$/ ]
  t.ok(match(x, y))

  x = [ 'Ring', /.ring$/ ]
  y = [ /.ring$/ ]
  t.notOk(match(x, y))
  t.end()
})

test('partial strings match on indexOf', function (t) {
  var x = { one: 'String' }
  var y = { one: 'rin' }

  t.ok(match(x, y))
  t.notOk(match(y, x))
  t.end()
})

test('ctors and other fun things', function (t) {
  function Foo () {
    this._isFoo = 'foo'
  }

  t.notOk(match(new Buffer('asdf'), new Buffer('asdff')))

  var d = new Date().toISOString()

  var obj = {
    buffer: new Buffer('x'),
    date: new Date(d),
    fn: function () {},
    foo: new Foo(),
    num: 1.2,
    nan: NaN,
    bool: true,
    array: [],
    str: 'asdf',
    inf: Infinity,
    neginf: -Infinity
  }

  t.ok(match(obj, {
    buffer: Buffer,
    date: Date,
    fn: Function,
    foo: Foo,
    num: Number,
    nan: NaN,
    bool: Boolean,
    array: Array,
    str: String
  }))

  t.ok(match(obj, {
    buffer: new Buffer('x'),
    date: d,
    foo: new Foo(),
    str: 'sd'
  }))

  var buf = new Buffer('x')
  buf.equals = null
  t.ok(match(obj, {
    buffer: buf
  }))

  var buf2 = new Buffer('y')
  buf2.equals = null
  t.notOk(match(buf, buf2))

  var buf3 = new Buffer('xy')
  buf3.equals = null
  t.notOk(match(buf, buf3))

  var buf4 = new Buffer('xy')
  buf4.equals = null
  t.ok(match(buf4, buf3))

  t.notOk(match(obj, {
    inf: Number
  }))

  t.notOk(match(obj, {
    neginf: Number
  }))

  t.notOk(match(obj, {
    nan: Number
  }))

  t.end()
})

test('js WAT! array/string stuff', function (t) {
  t.notOk(match([1], 1))
  t.notOk(match(1, [1]))
  t.ok(match([1], [1]))
  var o = {}
  t.ok(match(o, o))
  t.ok(match(1, '1'))
  t.end()
})
<commit_msg>polyfill for test coverage on 0.10<commit_after>var tap = require('tap')
var test = tap.test
var match = require('../')

test("shouldn't care about key order and types", function (t) {
  t.ok(match({ a: 1, b: 2 }, { b: 2, a: '1' }))
  t.end()
})

test('should notice objects with different shapes', function (t) {
  t.notOk(match(
    { a: 1, b: 'a thing' },
    { a: 1, b: undefined }
  ))
  t.ok(match(
    { a: 1 },
    { a: 1, b: undefined }
  ))
  t.end()
})

test('extra keys in object are ok', function (t) {
  t.ok(match(
    { a: 1, b: null, c: 'ok' },
    { a: 1, b: undefined }
  ))
  t.end()
})

test('should notice objects with different keys', function (t) {
  t.notOk(match(
    { a: 1, b: 2 },
    { a: 1, c: 2 }
  ))
  t.end()
})

test('should handle dates', function (t) {
  t.notOk(match(new Date('1972-08-01'), null))
  t.notOk(match(new Date('1972-08-01'), undefined))
  t.ok(match(new Date('1972-08-01'), new Date('1972-08-01')))
  t.ok(match({ x: new Date('1972-08-01') }, { x: new Date('1972-08-01') }))
  t.end()
})

test('should handle RegExps', function (t) {
  t.notOk(match(/[b]/, /[a]/))
  t.notOk(match(/[a]/i, /[a]/g))
  t.ok(match(/[a]/, /[a]/))
  t.ok(match(/ab?[a-z]{,6}/g, /ab?[a-z]{,6}/g))
  t.notOk(match([1, 2, 3], /asdf/))
  t.ok(match([1, 2, 3], /,2,/))
  t.ok(match({ x: 123 }, { x: /^\w+/ }))
  t.ok(match({ toString: function () { return 'FooBar' }}, /^FooBar$/))
  t.notOk(match({ toString: function () { return 'x' }}, /^FooBar$/))
  t.end()
})

test('should handle functions', function (t) {
  var fnA = function (a) { return a }
  var fnB = function (a) { return a }

  t.notOk(match(
    function a () {},
    function a () {} // but is it the _same_ a tho
  ))
  t.notOk(match(fnA, fnB))
  t.ok(match(fnA, fnA))
  t.ok(match(fnB, fnB))
  t.end()
})

test('should handle arguments', function (t) {
  var outer = arguments
  ;(function (tt) {
    var inner = arguments
    t.ok(match(outer, outer))
    t.ok(match(outer, inner))
    t.ok(match(outer, [t]))
  }(t))
  t.end()
})

test('same arrays match', function (t) {
  t.ok(match([1, 2, 3], [1, 2, 3]))
  t.end()
})

test("different arrays don't match", function (t) {
  t.notOk(match([1, 2, 3], [1, 2, 3, 4]))
  t.notOk(match([1, 2, 3], [1, 2, 4]))
  t.end()
})

test('empty arrays match', function (t) {
  t.ok(match([], []))
  t.ok(match({ x: [] }, { x: [] }))
  t.end()
})

test("shallower shouldn't care about key order recursively and types", function (t) {
  t.ok(match(
    { x: { a: 1, b: 2 }, y: { c: 3, d: 4 } },
    { y: { d: 4, c: 3 }, x: { b: '2', a: '1' } }
  ))
  t.end()
})

test('undefined is the same as itself', function (t) {
  t.ok(match(undefined, undefined))
  t.ok(match({ x: undefined }, { x: undefined }))
  t.ok(match({ x: [undefined] }, { x: [undefined] }))
  t.end()
})

test('undefined and null are Close Enough', function (t) {
  t.ok(match(undefined, null))
  t.ok(match({ x: null }, { x: undefined }))
  t.ok(match({ x: [undefined] }, { x: [null] }))
  t.end()
})

test("null is as shallow as you'd expect", function (t) {
  t.ok(match(null, null))
  t.ok(match({ x: null }, { x: null }))
  t.ok(match({ x: [null] }, { x: [null] }))
  t.end()
})

test('the same number matches', function (t) {
  t.ok(match(0, 0))
  t.ok(match(1, 1))
  t.ok(match(3.14, 3.14))
  t.end()
})

test("different numbers don't match", function (t) {
  t.notOk(match(0, 1))
  t.notOk(match(1, -1))
  t.notOk(match(3.14, 2.72))
  t.end()
})

test("tmatch shouldn't care about key order (but still might) and types", function (t) {
  t.ok(match(
    [
      { foo: { z: 100, y: 200, x: 300 } },
      'bar',
      11,
      { baz: { d: 4, a: 1, b: 2, c: 3 } }
    ],
    [
      { foo: { z: 100, y: 200, x: 300 } },
      'bar',
      11,
      { baz: { a: '1', b: '2', c: '3', d: '4' } }
    ]
  ))
  t.end()
})

test("match shouldn't blow up on circular data structures", function (t) {
  var x1 = { z: 4 }
  var y1 = { x: x1 }
  x1.y = y1

  var x2 = { z: 4 }
  var y2 = { x: x2 }
  x2.y = y2

  t.ok(match(x1, x2))
  t.end()
})

test('regexps match strings', function (t) {
  var x = { one: 'String' }
  var y = { one: /.ring$/ }
  t.ok(match(x, y))
  t.ok(match(x.one, y.one))

  x = [ 'String', 'String' ]
  y = [ /.ring$/, /.ring$/ ]
  t.ok(match(x, y))

  x = [ 'Ring', /.ring$/ ]
  y = [ /.ring$/ ]
  t.notOk(match(x, y))
  t.end()
})

test('partial strings match on indexOf', function (t) {
  var x = { one: 'String' }
  var y = { one: 'rin' }

  t.ok(match(x, y))
  t.notOk(match(y, x))
  t.end()
})

test('ctors and other fun things', function (t) {
  function Foo () {
    this._isFoo = 'foo'
  }

  if (!Buffer.prototype.equals) {
    Buffer.prototype.equals = function (pattern) {
      var obj = this
      if (obj.length !== pattern.length) return false
      for (var j = 0; j < obj.length; j++) if (obj[j] != pattern[j]) return false

      return true
    }
  }

  t.notOk(match(new Buffer('asdf'), new Buffer('asdff')))

  var d = new Date().toISOString()

  var obj = {
    buffer: new Buffer('x'),
    date: new Date(d),
    fn: function () {},
    foo: new Foo(),
    num: 1.2,
    nan: NaN,
    bool: true,
    array: [],
    str: 'asdf',
    inf: Infinity,
    neginf: -Infinity
  }

  t.ok(match(obj, {
    buffer: Buffer,
    date: Date,
    fn: Function,
    foo: Foo,
    num: Number,
    nan: NaN,
    bool: Boolean,
    array: Array,
    str: String
  }))

  t.ok(match(obj, {
    buffer: new Buffer('x'),
    date: d,
    foo: new Foo(),
    str: 'sd'
  }))

  var buf = new Buffer('x')
  buf.equals = null
  t.ok(match(obj, {
    buffer: buf
  }))

  var buf2 = new Buffer('y')
  buf2.equals = null
  t.notOk(match(buf, buf2))

  var buf3 = new Buffer('xy')
  buf3.equals = null
  t.notOk(match(buf, buf3))

  var buf4 = new Buffer('xy')
  buf4.equals = null
  t.ok(match(buf4, buf3))

  t.notOk(match(obj, {
    inf: Number
  }))

  t.notOk(match(obj, {
    neginf: Number
  }))

  t.notOk(match(obj, {
    nan: Number
  }))

  t.end()
})

test('js WAT! array/string stuff', function (t) {
  t.notOk(match([1], 1))
  t.notOk(match(1, [1]))
  t.ok(match([1], [1]))
  var o = {}
  t.ok(match(o, o))
  t.ok(match(1, '1'))
  t.end()
})
<|endoftext|>


<commit_before>/* eslint-disable no-underscore-dangle */

export const MiniMongo = {}

const NAME_TO_CAUSE_MINIMONGO = null

const inSchema = (key, schemaDoc, mustBeBlackbox = false) => {
  if (key in schemaDoc) {
    return ! mustBeBlackbox || schemaDoc[key].blackbox
  }
  const parts = key.split('.')
  if (parts.length > 1) {
    return inSchema(parts.slice(0, 1).join('.'), schemaDoc, true)
  }
  return false
}

if (global.Mongo) {
  MiniMongo.Collection = class MiniMongoCollection extends global.Mongo.Collection {
    constructor(name) {
      super(NAME_TO_CAUSE_MINIMONGO)
      this._name = name   // eslint-disable-line no-underscore-dangle
      this.indexes = []
    }

    _ensureIndex(keys, options) {
      if (! this._c2 || ! this._c2._simpleSchema) {
        throw new Error("Attach a schema before adding indexes so we can check they're valid")
      }
      const schemaDoc = this._c2._simpleSchema._schema
      Object.keys(keys).forEach(k => {
        if (! inSchema(k, schemaDoc)) {
          throw new Error(`Attempt to add an index key '${k}' which is not in the schema`)
        }
      })
      this.indexes.push(options ? { keys, options } : keys)
      // MiniMongo would normally throw an exception but we ignore it
      // If this was ever used on the client it should throw still
    }
  }
}
<commit_msg>Handle array types when checking for indexes in schema<commit_after>/* eslint-disable no-underscore-dangle */

export const MiniMongo = {}

const NAME_TO_CAUSE_MINIMONGO = null

const removeArrayPartsFromKey = (k) => k.split('.$').join('')

const inSchema = (indexKey, schemaDoc, mustBeBlackbox = false) => {
  const schemaKey = Object.keys(schemaDoc).find(k =>
    removeArrayPartsFromKey(k) === indexKey &&
    (! mustBeBlackbox || schemaDoc[k].blackbox)
  )
  if (schemaKey) {
    return true
  }
  const parts = indexKey.split('.')
  if (parts.length <= 1) {
    return false
  }
  return inSchema(parts.slice(0, -1).join('.'), schemaDoc, true)
}

if (global.Mongo) {
  MiniMongo.Collection = class MiniMongoCollection extends global.Mongo.Collection {
    constructor(name) {
      super(NAME_TO_CAUSE_MINIMONGO)
      this._name = name   // eslint-disable-line no-underscore-dangle
      this.indexes = []
    }

    _ensureIndex(keys, options) {
      if (! this._c2 || ! this._c2._simpleSchema) {
        throw new Error("Attach a schema before adding indexes so we can check they're valid")
      }
      const schemaDoc = this._c2._simpleSchema._schema
      Object.keys(keys).forEach(k => {
        if (! inSchema(k, schemaDoc)) {
          throw new Error(`Attempt to add an index key '${k}' which is not in the schema`)
        }
      })
      this.indexes.push(options ? { keys, options } : keys)
      // MiniMongo would normally throw an exception but we ignore it
      // If this was ever used on the client it should throw still
    }
  }
}
<|endoftext|>


<commit_before>var API_BASE = "https://bugzilla.mozilla.org/rest/";

/**
 * @returns d3.request
 */
function make_api_request(path, params, data, method) {
  var uri = API_BASE + path;
  if (params) {
    uri += "?" + params.toString();
  }
  var r = d3.json(uri);

  method = "GET";
  data = null;

  return r.send(method, data);
}

var gComponents;

function get_components() {
  $("#component-loading").progressbar({ value: false });
  return fetch("components-min.json")
    .then(function(r) { return r.json(); })
    .then(function(r) {
      gComponents = r;
      selected_from_url();
      $("#component-loading").hide();
    });
}

function selected_from_url() {
  var sp = new URLSearchParams(window.location.search);
  var components = new Set(sp.getAll("component"));
  gComponents.forEach(function(c) {
    var test = c.product_name + ":" + c.component_name;
    c.selected = components.has(test);
  });
  setup_queries();
}

// Takes an array of bugs and returns only those for which there is
// at least one flag set that's a needinfo where the requestee and
// setter are not the same
function filter_self_needinfos(bugs) {
  return bugs.filter(function(bug) {
    return bug.flags.some(function(flag) {
      return flag.name == "needinfo" && flag.requestee != flag.setter;
    });
  });
}

$(function() {
  $(".badge").hide();
  $("#tabs").tabs({ heightStyle: "fill", active: 1 });
  $("#stale-inner").accordion({ heightStyle: "content", collapsible: true, active: false });

  get_components().then(setup_components).then(() => {
    var selected = gComponents.filter(function(c) { return c.selected; });
    if (selected.length) {
      // Select the "stale" tab
      $("#tabs").tabs("option", "active", 2);
    }
  });
  d3.select("#filter").on("input", function() {
    setup_components();
  });
  window.addEventListener("popstate", function() {
    selected_from_url();
    setup_components();
  });
});

function setup_components() {
  var search = d3.select("#filter").property("value").toLowerCase().split(/\s+/).filter(function(w) { return w.length > 0; });
  var filtered;
  if (search.length == 0) {
    filtered = gComponents;
  } else {
    filtered = gComponents.filter(function(c) {
      var search_name = (c.product_name + ": " + c.component_name + " " + c.component_description).toLowerCase();
      var found = true;
      search.forEach(function(w) {
        if (search_name.indexOf(w) == -1) {
          found = false;
        }
      });
      return found;
    });
  }
  var rows = d3.select("#components tbody").selectAll("tr")
    .data(filtered, function(c) { return c.product_id + "_" + c.component_id; });
  var new_rows = rows.enter().append("tr");
  new_rows.on("click", function(d) {
    d.selected = !d.selected;
    d3.select(this).select("input").property("checked", d.selected);
    navigate_url();
    setup_queries();
  });
  new_rows.append("th").append("input")
    .attr("type", "checkbox");
  new_rows.append("th").text(function(d) {
    return d.product_name + ": " + d.component_name;
  });
  new_rows.append("td").text(function(d) {
    return d.component_description;
  });
  rows.exit().remove();
  rows.selectAll("input").property("checked", function(d) { return !!d.selected; });
  document.getElementById('filter').removeAttribute('disabled');
}

var gPendingQueries = new Set();

function setup_queries() {
  gPendingQueries.forEach(function(r) {
    r.abort();
  });
  gPendingQueries.clear();

  var selected = gComponents.filter(function(c) { return c.selected; });
  var products = new Set();
  var components = new Set();
  selected.forEach(function(c) {
    products.add(c.product_name);
    components.add(c.component_name);
  });

  var common_params = new URLSearchParams();
  Array.from(products.values()).forEach(function(p) {
    common_params.append("product", p);
  });
  Array.from(components.values()).forEach(function(c) {
    common_params.append("component", c);
  });

  var to_triage = make_search({
    priority: "--",
    n1: 1,
    f1: "flagtypes.name",
    o1: "substring",
    v1: "needinfo",
    email1: "intermittent-bug-filer@mozilla.bugs",
    emailtype1: "notequals",
    emailreporter1: 1,
    keywords: "meta",
    keywords_type: "nowords",
    resolution: "---",
    chfield: "[Bug creation]",
    chfieldto: "Now",
    query_format: "advanced",
    chfieldfrom: "2018-12-10", // Change to first nightly of current release
  }, common_params);
  document.getElementById("triage-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + to_triage.toString();
  populate_table($("#need-decision"), to_triage, $("#need-decision-marker"), !!selected.length);

  var stale_needinfo = make_search({
    f1: "flagtypes.name",
    o1: "substring",
    v1: "needinfo",
    f2: "delta_ts",
    o2: "lessthan", // means "older than"
    v2: "14d",
    resolution: "---",
    query_format: "advanced",
  }, common_params);
  document.getElementById("stuck-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_needinfo.toString();
  populate_table($("#needinfo-stale"), stale_needinfo, $("#needinfo-stale-marker"), !!selected.length, filter_self_needinfos);

  var stale_review = make_search({
    f1: "flagtypes.name",
    o1: "regexp",
    v1: "^(review|superreview|ui-review|feedback|a11y-review)\\?",
    resolution: "---",
    f2: "delta_ts",
    o2: "lessthan", // means "older than"
    v2: "5d",
    query_format: "advanced",
  }, common_params);
  document.getElementById("review-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_review.toString();
  populate_table($("#review-stale"), stale_review, $("#review-stale-marker"), !!selected.length);

  var stale_decision = make_search({
    keywords: "regression",
    keywords_type: "allwords",
    v1: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional",
    chfieldto: "Now",
    o1: "nowords",
    chfield: "[Bug creation]",
    chfieldfrom: "2019-01-28", // change to date of first nightly of next version at release
    f1: "cf_status_firefox67", // change to next version at release
    resolution: "---",
    query_format: "advanced"
  }, common_params);

  document.getElementById("decision-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_decision.toString();
  populate_table($("#decision-stale"), stale_decision, $("#decision-stale-marker"), !!selected.length);

  var stale_range = make_search({
    chfield: "[Bug creation]",
    chfieldfrom: "2018-01-28", // change to date of first nightly of next version at release
    chfieldto: "Now",
    f1: "cf_status_firefox67", // increment version numbers at release
    f2: "cf_status_firefox68",
    j_top: "OR",
    keywords: "regression",
    keywords_type: "allwords",
    o1: "nowords",
    o2: "nowords",
    query_format: "advanced",
    resolution: "---",
    v1: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional",
    v2: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional"
  }, common_params);
  document.getElementById("range-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_range.toString();
  populate_table($("#range-stale"), stale_range, $("#range-stale-marker"), !!selected.length);
}

function navigate_url() {
  var u = new URL(window.location.href);
  var sp = u.searchParams;
  sp.delete("component");
  var selected = gComponents.filter(function(c) { return c.selected; });
  selected.forEach(function(c) {
    sp.append("component", c.product_name + ":" + c.component_name);
  });
  window.history.pushState(undefined, undefined, u.href);
}

function make_search(o, base) {
  var s = new URLSearchParams(base);
  Object.keys(o).forEach(function(k) {
    var v = o[k];
    if (v instanceof Array) {
      v.forEach(function(v2) {
        s.append(k, v2);
      });
    } else {
      s.append(k, v);
    }
  });
  return s;
}

function bug_component(d) {
  return d.product + ": " + d.component;
}

function bug_description(d) {
  var s = d.summary;
  if (d.keywords.length) {
    s += " " + d.keywords.join(",");
  }
  return s;
}

function bug_users(d) {
  var s = "Owner: " + d.assigned_to;
  s += " Reporter: " + d.creator;
  return s;
}

function bug_created(d) {
  return d3.time.format("%Y-%m-%d %H:%M")(new Date(d.creation_time));
}

function bug_priority(d) {
    var priority = '';
    switch (d.priority.toLowerCase()) {
        case '--':
            priority = 'No Priority';
            break;
        case 'p1':
            priority = 'P1: This Release/Iteration';
            break;
        case 'p2':
            priority = 'P2: Next Release/Iteration';
            break;
        case 'p3':
            priority = 'P3: Backlog';
            break;
        case 'p4':
            priority = 'P4: Bot Managed';
            break;
        case 'p5':
            priority = 'P5: Won\'t fix but will accept a patch';
            break;
        default:
            priority = 'Undefined (this shouldn\'t happen)';
    }
    return priority;
}

function populate_table(s, params, marker, some_selected, filter_fn) {
  if (!some_selected) {
    $(".p", s).hide();
    d3.select(s[0]).selectAll('.bugtable > tbody > tr').remove();
    return;
  }
  $(".p", s).progressbar({ value: false }).off("click");
  var r = make_api_request("bug", params).on("load", function(data) {
    gPendingQueries.delete(r);
    $(".p", s)
      .button({ icons: { primary: 'ui-icon-refresh' }, label: 'Refresh', text: false })
      .on("click", function() { populate_table(s, params, marker, true); });
    var bugs = filter_fn ? filter_fn(data.bugs) : data.bugs;
    if (!bugs.length) {
      marker.text("(none!)").removeClass("pending");
    } else {
      marker.text("(" + bugs.length + ")").addClass("pending");
    }
    bugs.sort(function(a, b) { return d3.ascending(a.id, b.id); });
    var rows = d3.select(s[0]).select('.bugtable > tbody').selectAll('tr')
      .data(bugs, function(d) { return d.id; });
    rows.exit().remove();
    var new_rows = rows.enter().append("tr");
    new_rows.append("th").append("a")
      .attr("href", function(d) { return "https://bugzilla.mozilla.org/show_bug.cgi?id=" + d.id; })
      .attr("target", "_blank").text(function(d) { return d.id; });
    new_rows.append("td").classed("bugpriority", true);
    new_rows.append("td").classed("bugdescription", true);
    new_rows.append("td").classed("bugcomponent", true);
    new_rows.append("td").classed("bugusers", true);
    new_rows.append("td").classed("bugcreated", true);
    rows.select(".bugpriority ").text(bug_priority);
    rows.select(".bugdescription").text(bug_description);
    rows.select(".bugcomponent").text(bug_component);
    rows.select(".bugusers").text(bug_users);
    rows.select(".bugcreated").text(bug_created);
    rows.order();
  }).on("error", function(e) {
    console.log("XHR error", r, e, this);
    gPendingQueries.delete(r);
  });
  gPendingQueries.add(r);
}

<commit_msg>Update versions and dates<commit_after>var API_BASE = "https://bugzilla.mozilla.org/rest/";

/**
 * @returns d3.request
 */
function make_api_request(path, params, data, method) {
  var uri = API_BASE + path;
  if (params) {
    uri += "?" + params.toString();
  }
  var r = d3.json(uri);

  method = "GET";
  data = null;

  return r.send(method, data);
}

var gComponents;

function get_components() {
  $("#component-loading").progressbar({ value: false });
  return fetch("components-min.json")
    .then(function(r) { return r.json(); })
    .then(function(r) {
      gComponents = r;
      selected_from_url();
      $("#component-loading").hide();
    });
}

function selected_from_url() {
  var sp = new URLSearchParams(window.location.search);
  var components = new Set(sp.getAll("component"));
  gComponents.forEach(function(c) {
    var test = c.product_name + ":" + c.component_name;
    c.selected = components.has(test);
  });
  setup_queries();
}

// Takes an array of bugs and returns only those for which there is
// at least one flag set that's a needinfo where the requestee and
// setter are not the same
function filter_self_needinfos(bugs) {
  return bugs.filter(function(bug) {
    return bug.flags.some(function(flag) {
      return flag.name == "needinfo" && flag.requestee != flag.setter;
    });
  });
}

$(function() {
  $(".badge").hide();
  $("#tabs").tabs({ heightStyle: "fill", active: 1 });
  $("#stale-inner").accordion({ heightStyle: "content", collapsible: true, active: false });

  get_components().then(setup_components).then(() => {
    var selected = gComponents.filter(function(c) { return c.selected; });
    if (selected.length) {
      // Select the "stale" tab
      $("#tabs").tabs("option", "active", 2);
    }
  });
  d3.select("#filter").on("input", function() {
    setup_components();
  });
  window.addEventListener("popstate", function() {
    selected_from_url();
    setup_components();
  });
});

function setup_components() {
  var search = d3.select("#filter").property("value").toLowerCase().split(/\s+/).filter(function(w) { return w.length > 0; });
  var filtered;
  if (search.length == 0) {
    filtered = gComponents;
  } else {
    filtered = gComponents.filter(function(c) {
      var search_name = (c.product_name + ": " + c.component_name + " " + c.component_description).toLowerCase();
      var found = true;
      search.forEach(function(w) {
        if (search_name.indexOf(w) == -1) {
          found = false;
        }
      });
      return found;
    });
  }
  var rows = d3.select("#components tbody").selectAll("tr")
    .data(filtered, function(c) { return c.product_id + "_" + c.component_id; });
  var new_rows = rows.enter().append("tr");
  new_rows.on("click", function(d) {
    d.selected = !d.selected;
    d3.select(this).select("input").property("checked", d.selected);
    navigate_url();
    setup_queries();
  });
  new_rows.append("th").append("input")
    .attr("type", "checkbox");
  new_rows.append("th").text(function(d) {
    return d.product_name + ": " + d.component_name;
  });
  new_rows.append("td").text(function(d) {
    return d.component_description;
  });
  rows.exit().remove();
  rows.selectAll("input").property("checked", function(d) { return !!d.selected; });
  document.getElementById('filter').removeAttribute('disabled');
}

var gPendingQueries = new Set();

function setup_queries() {
  gPendingQueries.forEach(function(r) {
    r.abort();
  });
  gPendingQueries.clear();

  var selected = gComponents.filter(function(c) { return c.selected; });
  var products = new Set();
  var components = new Set();
  selected.forEach(function(c) {
    products.add(c.product_name);
    components.add(c.component_name);
  });

  var common_params = new URLSearchParams();
  Array.from(products.values()).forEach(function(p) {
    common_params.append("product", p);
  });
  Array.from(components.values()).forEach(function(c) {
    common_params.append("component", c);
  });

  var to_triage = make_search({
    priority: "--",
    n1: 1,
    f1: "flagtypes.name",
    o1: "substring",
    v1: "needinfo",
    email1: "intermittent-bug-filer@mozilla.bugs",
    emailtype1: "notequals",
    emailreporter1: 1,
    keywords: "meta",
    keywords_type: "nowords",
    resolution: "---",
    chfield: "[Bug creation]",
    chfieldto: "Now",
    query_format: "advanced",
    chfieldfrom: "2019-01-28", // Change to first nightly of current release
  }, common_params);
  document.getElementById("triage-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + to_triage.toString();
  populate_table($("#need-decision"), to_triage, $("#need-decision-marker"), !!selected.length);

  var stale_needinfo = make_search({
    f1: "flagtypes.name",
    o1: "substring",
    v1: "needinfo",
    f2: "delta_ts",
    o2: "lessthan", // means "older than"
    v2: "14d",
    resolution: "---",
    query_format: "advanced",
  }, common_params);
  document.getElementById("stuck-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_needinfo.toString();
  populate_table($("#needinfo-stale"), stale_needinfo, $("#needinfo-stale-marker"), !!selected.length, filter_self_needinfos);

  var stale_review = make_search({
    f1: "flagtypes.name",
    o1: "regexp",
    v1: "^(review|superreview|ui-review|feedback|a11y-review)\\?",
    resolution: "---",
    f2: "delta_ts",
    o2: "lessthan", // means "older than"
    v2: "5d",
    query_format: "advanced",
  }, common_params);
  document.getElementById("review-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_review.toString();
  populate_table($("#review-stale"), stale_review, $("#review-stale-marker"), !!selected.length);

  var stale_decision = make_search({
    keywords: "regression",
    keywords_type: "allwords",
    v1: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional",
    chfieldto: "Now",
    o1: "nowords",
    chfield: "[Bug creation]",
    chfieldfrom: "2019-03-18", // change to date of first nightly of next version at release
    f1: "cf_status_firefox68", // change to next version at release
    resolution: "---",
    query_format: "advanced"
  }, common_params);

  document.getElementById("decision-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_decision.toString();
  populate_table($("#decision-stale"), stale_decision, $("#decision-stale-marker"), !!selected.length);

  var stale_range = make_search({
    chfield: "[Bug creation]",
    chfieldfrom: "2018-03-18", // change to date of first nightly of next version at release
    chfieldto: "Now",
    f1: "cf_status_firefox68", // increment version numbers at release
    f2: "cf_status_firefox69",
    j_top: "OR",
    keywords: "regression",
    keywords_type: "allwords",
    o1: "nowords",
    o2: "nowords",
    query_format: "advanced",
    resolution: "---",
    v1: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional",
    v2: "affected,unaffected,fixed,verified,disabled,verified disabled,wontfix,fix-optional"
  }, common_params);
  document.getElementById("range-list").href = "https://bugzilla.mozilla.org/buglist.cgi?" + stale_range.toString();
  populate_table($("#range-stale"), stale_range, $("#range-stale-marker"), !!selected.length);
}

function navigate_url() {
  var u = new URL(window.location.href);
  var sp = u.searchParams;
  sp.delete("component");
  var selected = gComponents.filter(function(c) { return c.selected; });
  selected.forEach(function(c) {
    sp.append("component", c.product_name + ":" + c.component_name);
  });
  window.history.pushState(undefined, undefined, u.href);
}

function make_search(o, base) {
  var s = new URLSearchParams(base);
  Object.keys(o).forEach(function(k) {
    var v = o[k];
    if (v instanceof Array) {
      v.forEach(function(v2) {
        s.append(k, v2);
      });
    } else {
      s.append(k, v);
    }
  });
  return s;
}

function bug_component(d) {
  return d.product + ": " + d.component;
}

function bug_description(d) {
  var s = d.summary;
  if (d.keywords.length) {
    s += " " + d.keywords.join(",");
  }
  return s;
}

function bug_users(d) {
  var s = "Owner: " + d.assigned_to;
  s += " Reporter: " + d.creator;
  return s;
}

function bug_created(d) {
  return d3.time.format("%Y-%m-%d %H:%M")(new Date(d.creation_time));
}

function bug_priority(d) {
    var priority = '';
    switch (d.priority.toLowerCase()) {
        case '--':
            priority = 'No Priority';
            break;
        case 'p1':
            priority = 'P1: This Release/Iteration';
            break;
        case 'p2':
            priority = 'P2: Next Release/Iteration';
            break;
        case 'p3':
            priority = 'P3: Backlog';
            break;
        case 'p4':
            priority = 'P4: Bot Managed';
            break;
        case 'p5':
            priority = 'P5: Won\'t fix but will accept a patch';
            break;
        default:
            priority = 'Undefined (this shouldn\'t happen)';
    }
    return priority;
}

function populate_table(s, params, marker, some_selected, filter_fn) {
  if (!some_selected) {
    $(".p", s).hide();
    d3.select(s[0]).selectAll('.bugtable > tbody > tr').remove();
    return;
  }
  $(".p", s).progressbar({ value: false }).off("click");
  var r = make_api_request("bug", params).on("load", function(data) {
    gPendingQueries.delete(r);
    $(".p", s)
      .button({ icons: { primary: 'ui-icon-refresh' }, label: 'Refresh', text: false })
      .on("click", function() { populate_table(s, params, marker, true); });
    var bugs = filter_fn ? filter_fn(data.bugs) : data.bugs;
    if (!bugs.length) {
      marker.text("(none!)").removeClass("pending");
    } else {
      marker.text("(" + bugs.length + ")").addClass("pending");
    }
    bugs.sort(function(a, b) { return d3.ascending(a.id, b.id); });
    var rows = d3.select(s[0]).select('.bugtable > tbody').selectAll('tr')
      .data(bugs, function(d) { return d.id; });
    rows.exit().remove();
    var new_rows = rows.enter().append("tr");
    new_rows.append("th").append("a")
      .attr("href", function(d) { return "https://bugzilla.mozilla.org/show_bug.cgi?id=" + d.id; })
      .attr("target", "_blank").text(function(d) { return d.id; });
    new_rows.append("td").classed("bugpriority", true);
    new_rows.append("td").classed("bugdescription", true);
    new_rows.append("td").classed("bugcomponent", true);
    new_rows.append("td").classed("bugusers", true);
    new_rows.append("td").classed("bugcreated", true);
    rows.select(".bugpriority ").text(bug_priority);
    rows.select(".bugdescription").text(bug_description);
    rows.select(".bugcomponent").text(bug_component);
    rows.select(".bugusers").text(bug_users);
    rows.select(".bugcreated").text(bug_created);
    rows.order();
  }).on("error", function(e) {
    console.log("XHR error", r, e, this);
    gPendingQueries.delete(r);
  });
  gPendingQueries.add(r);
}

<|endoftext|>


<commit_before>'use strict'

var async = require('async')
  , Client = require('./client')
  , debug = require('debug')('druid-query:druid')
  , DruidError = require('./errors').DruidError
  , EventEmitter = require('events').EventEmitter
  , lodash = require('lodash')
  , request = require('request')
  , util = require('util')
  , utils = require('./utils')
  , zookeeper = require('node-zookeeper-client')
  , queries = utils.moduleMap(__dirname + '/queries')

module.exports = Druid

function getDataSources(nodeInfo, callback) {
  var url = nodeInfo.url + '/druid/v2/datasources'

  request({
    url:  url,
    json: true
  }, onresponse)

  function onresponse(err, res, data) {
    if (err) {
      callback(err)
    }
    else if (res.statusCode !== 200) {
      callback(new DruidError('Unknown error while accessing ' + url))
    }
    else {
      nodeInfo.datasources = data

      debug('Data sources for Druid node ' + nodeInfo.id + ': ' + data)

      callback(null, nodeInfo)
    }
  }
}

/**
 * Bad-ass load-balancing
 *
 * @param {Druid} client Client instance
 * @param {string} dataSource Data source name
 * @returns {DruidNode}
 */
function getLessLoaded(client, dataSource) {
  var ids = client.dataSources[dataSource]
    , nodes
    , node

  nodes = ids.map(id2client)

  function id2client(id) {
    return client.nodes[id]
  }

  node = nodes.reduce(lessLoaded)

  function lessLoaded(nodeA, nodeB) {
    if (nodeA.queries < nodeB.queries) {
      return nodeA
    }
    else {
      return nodeB
    }
  }

  return node
}

/**
 * Get Druid node data from ZooKeeper and list of data sources
 *
 * @private
 * @param {Druid} client Client instance
 * @param {string} id Druid Node ID
 * @param {function} callback Accepts arguments: (err, nodeData)
 */
function getNodeData(client, id, callback) {
  var path = client.discoveryPath + '/' + id
    , preferSSL = client.options.preferSSL

  client.zk.getData(path, handleData)

  function handleData(err, jsonBuffer) {
    if (err) {
      return callback(new DruidError('Error getting node data', err))
    }

    debug('ZooKeeper data for path ' + path + ': ' + jsonBuffer)

    var data
      , proto
      , port

    try {
      data = JSON.parse(jsonBuffer.toString('utf8'))
    }
    catch (ex) {
      return callback(ex)
    }

    if (preferSSL && data.sslPort) {
      proto = 'https'
      port = data.sslPort
    }
    else {
      proto = 'http'
      port = data.port
    }

    data.path = path
    data.url = util.format('%s://%s:%s', proto, data.address, port)

    getDataSources(data, callback)
  }
}

/**
 * Get Query object for given data source
 *
 * @param {Druid} client Client instance
 * @param {function} queryClass Query class
 * @param {string} dataSource Data source name
 * @param {object} [rawQuery] Raw query data
 * @returns {Query|null} Null if client is not ready ({@link Druid#ready})
 */
function getQuery(client, queryClass, dataSource, rawQuery) {
  if (!client.ready) {
    return null
  }
  else if (!dataSource) {
    throw new DruidError('Data source is not specified')
  }
  else if (!client.dataSources.hasOwnProperty(dataSource)) {
    return null
  }

  var node = getLessLoaded(client, dataSource)

  return new queryClass(node, rawQuery).dataSource(dataSource)
}

/**
 * Initiate ZooKeeper client
 *
 * @param {Druid} client
 * @param {string} connectionString ZooKeeper connection string
 */
function initZookeeper(client, connectionString) {
  var zk = zookeeper.createClient(connectionString, client.options)

  client.ready = false
  client.zk = zk

  zk.connect()
  zk.on('connected', init)
  zk.on('disconnected', reconnect)
  zk.on('expired', reconnect)
  zk.on('error', onerror)

  function init() {
    debug('Getting info about nodes and data sources')

    client.init()
  }

  function reconnect() {
    if (!client.closed) {
      debug('Reconnecting to ' + connectionString)

      initZookeeper(client, connectionString)
    }
  }

  function onerror(err) {
    client.emit('error', err)
  }
}

/**
 * Druid client wrapper
 *
 * @constructor
 * @extends Client
 * @param {object} data Node data from ZooKeeper
 */
function DruidNode(data) {
  Client.call(this, data.url)

  /**
   * Node data from ZooKeeper
   *
   * @public
   * @type {object}
   */
  this.data = data

  /**
   * Number of queries currently running
   *
   * @public
   * @type {number}
   */
  this.queries = 0
}
util.inherits(DruidNode, Client)

/**
 * Wrap {@link Client#exec} to count number of concurrent running queries
 *
 * @public
 * @see {@link Client#exec}
 * @param {Query} query Query wrapper
 * @param {function} callback Accepts arguments: (err, data)
 */
DruidNode.prototype.exec = function(query, callback) {
  var self = this
  this.queries++

  debug('Concurrent queries to Druid node ' + this.data.id + ': ' + this.queries)

  Client.prototype.exec.call(this, query, wrapper)

  function wrapper() {
    self.queries--

    callback.apply(null, arguments)
  }
}

/**
 * Druid client relying on ZooKeeper nodes data
 *
 * @constructor
 * @extends EventEmitter
 * @param {string} connectionString ZooKeeper connection string
 * @param {object} [options] node-zookeeper-client options
 * @param {boolean} [options.preferSSL] Prefer SSL connection to Druid nodes if available
 * @param {string} discoveryPath Nodes discovery path
 */
function Druid(connectionString, options, discoveryPath) {
  EventEmitter.call(this)
  this.setMaxListeners(0)

  if (arguments.length === 2) {
    discoveryPath = options
    options = {}
  }

  /**
   * Services discovery path
   *
   * @protected
   * @type {string}
   */
  this.discoveryPath = discoveryPath

  /**
   * Client options
   *
   * @protected
   */
  this.options = options

  /**
   * Finished getting info about nodes and datasources
   *
   * @protected
   * @type {boolean}
   */
  this.ready = false

  /**
   * Nodes (id -> node)
   *
   * @protected
   */
  this.nodes = {}

  /**
   * Nodes associated with each data source (dataSource -> [nodeA, nodeB, ..., nodeN])
   *
   * @protected
   */
  this.dataSources = {}

  /**
   * ZooKeeper client
   *
   * @protected
   */
  initZookeeper(this, connectionString)
}
util.inherits(Druid, EventEmitter)

// Expose all queries types
lodash.each(queries, function(queryClass, type) {
  Druid.prototype[type] = function(dataSource, rawQuery) {
    return getQuery(this, queryClass, dataSource, rawQuery)
  }
})

/**
 * Finish working with client
 *
 * @public
 */
Druid.prototype.end = function() {
  this.closed = true
  this.zk.close()
}

/**
 * Return list of data sources
 *
 * @public
 * @returns {string[]}
 */
Druid.prototype.getDataSources = function() {
  return Object.keys(this.dataSources)
}

/**
 * Return list of nodes
 *
 * @public
 * @returns {DruidNode[]}
 */
Druid.prototype.getNodes = function() {
  return lodash.values(this.nodes)
}

/**
 * Initialize client
 *
 * @protected
 * @see {@link Druid#reloadList}
 */
Druid.prototype.init = function() {
  var self = this

  this.reloadList(done)

  function done(err) {
    if (err) {
      self.emit('error', err)
    }
    else if (!self.ready) {
      self.ready = true
      self.emit('ready')
    }
  }
}

/**
 * Get list of nodes and data sources they serve
 *
 * @private
 * @param {function} callback Accepts arguments: (err)
 */
Druid.prototype.reloadList = function(callback) {
  var self = this

  if (!this.ready) {
    this.zk.getChildren(this.discoveryPath, update, getDataSources)
  }
  else {
    this.zk.getChildren(this.discoveryPath, getDataSources)
  }

  function update() {
    self.init()
  }

  function getDataSources(err, children) {
    if (err) {
      return callback(new DruidError('Error getting info about Druid nodes', err))
    }

    async.map(children, getData, done)
  }

  function getData(id, callback) {
    getNodeData(self, id, callback)
  }

  function done(err, data) {
    if (err) {
      return callback(err)
    }

    var dataSources = {}
      , nodes = {}
      , nodeData
      , i
      , ds

    while (nodeData = data.shift()) {
      nodes[nodeData.id] = new DruidNode(nodeData)

      i = 0
      while (ds = nodeData.datasources[i++]) {
        if (dataSources.hasOwnProperty(ds)) {
          dataSources[ds].push(nodeData.id)
        }
        else {
          dataSources[ds] = [nodeData.id]
        }
      }
    }

    self.nodes = nodes
    self.dataSources = dataSources

    callback(null)
  }
}
<commit_msg>fix(druid): fix missing async dependency<commit_after>'use strict'

var Client = require('./client')
  , debug = require('debug')('druid-query:druid')
  , DruidError = require('./errors').DruidError
  , EventEmitter = require('events').EventEmitter
  , lodash = require('lodash')
  , request = require('request')
  , util = require('util')
  , utils = require('./utils')
  , zookeeper = require('node-zookeeper-client')
  , queries = utils.moduleMap(__dirname + '/queries')

module.exports = Druid

/**
 * Works like async.map
 *
 * @param array
 * @param iterator
 * @param done
 */
function asyncMap(array, iterator, done) {
  if (!array || !array.length) {
    return done(null, [])
  }

  var error
    , out = []
    , todo = array.length

  array.forEach(exec)

  function exec(item) {
    process.nextTick(function tick() {
      iterator(item, add)
    })
  }

  function add(err, data) {
    if (error) {
      return
    }
    else if (err) {
      error = true
      return done(err)
    }

    out.push(data)

    if (--todo === 0) {
      done(null, out)
    }
  }
}

function getDataSources(nodeInfo, callback) {
  var url = nodeInfo.url + '/druid/v2/datasources'

  request({
    url:  url,
    json: true
  }, onresponse)

  function onresponse(err, res, data) {
    if (err) {
      callback(err)
    }
    else if (res.statusCode !== 200) {
      callback(new DruidError('Unknown error while accessing ' + url))
    }
    else {
      nodeInfo.datasources = data

      debug('Data sources for Druid node ' + nodeInfo.id + ': ' + data)

      callback(null, nodeInfo)
    }
  }
}

/**
 * Bad-ass load-balancing
 *
 * @param {Druid} client Client instance
 * @param {string} dataSource Data source name
 * @returns {DruidNode}
 */
function getLessLoaded(client, dataSource) {
  var ids = client.dataSources[dataSource]
    , nodes
    , node

  nodes = ids.map(id2client)

  function id2client(id) {
    return client.nodes[id]
  }

  node = nodes.reduce(lessLoaded)

  function lessLoaded(nodeA, nodeB) {
    if (nodeA.queries < nodeB.queries) {
      return nodeA
    }
    else {
      return nodeB
    }
  }

  return node
}

/**
 * Get Druid node data from ZooKeeper and list of data sources
 *
 * @private
 * @param {Druid} client Client instance
 * @param {string} id Druid Node ID
 * @param {function} callback Accepts arguments: (err, nodeData)
 */
function getNodeData(client, id, callback) {
  var path = client.discoveryPath + '/' + id
    , preferSSL = client.options.preferSSL

  client.zk.getData(path, handleData)

  function handleData(err, jsonBuffer) {
    if (err) {
      return callback(new DruidError('Error getting node data', err))
    }

    debug('ZooKeeper data for path ' + path + ': ' + jsonBuffer)

    var data
      , proto
      , port

    try {
      data = JSON.parse(jsonBuffer.toString('utf8'))
    }
    catch (ex) {
      return callback(ex)
    }

    if (preferSSL && data.sslPort) {
      proto = 'https'
      port = data.sslPort
    }
    else {
      proto = 'http'
      port = data.port
    }

    data.path = path
    data.url = util.format('%s://%s:%s', proto, data.address, port)

    getDataSources(data, callback)
  }
}

/**
 * Get Query object for given data source
 *
 * @param {Druid} client Client instance
 * @param {function} queryClass Query class
 * @param {string} dataSource Data source name
 * @param {object} [rawQuery] Raw query data
 * @returns {Query|null} Null if client is not ready ({@link Druid#ready})
 */
function getQuery(client, queryClass, dataSource, rawQuery) {
  if (!client.ready) {
    return null
  }
  else if (!dataSource) {
    throw new DruidError('Data source is not specified')
  }
  else if (!client.dataSources.hasOwnProperty(dataSource)) {
    return null
  }

  var node = getLessLoaded(client, dataSource)

  return new queryClass(node, rawQuery).dataSource(dataSource)
}

/**
 * Initiate ZooKeeper client
 *
 * @param {Druid} client
 * @param {string} connectionString ZooKeeper connection string
 */
function initZookeeper(client, connectionString) {
  var zk = zookeeper.createClient(connectionString, client.options)

  client.ready = false
  client.zk = zk

  zk.connect()
  zk.on('connected', init)
  zk.on('disconnected', reconnect)
  zk.on('expired', reconnect)
  zk.on('error', onerror)

  function init() {
    debug('Getting info about nodes and data sources')

    client.init()
  }

  function reconnect() {
    if (!client.closed) {
      debug('Reconnecting to ' + connectionString)

      initZookeeper(client, connectionString)
    }
  }

  function onerror(err) {
    client.emit('error', err)
  }
}

/**
 * Druid client wrapper
 *
 * @constructor
 * @extends Client
 * @param {object} data Node data from ZooKeeper
 */
function DruidNode(data) {
  Client.call(this, data.url)

  /**
   * Node data from ZooKeeper
   *
   * @public
   * @type {object}
   */
  this.data = data

  /**
   * Number of queries currently running
   *
   * @public
   * @type {number}
   */
  this.queries = 0
}
util.inherits(DruidNode, Client)

/**
 * Wrap {@link Client#exec} to count number of concurrent running queries
 *
 * @public
 * @see {@link Client#exec}
 * @param {Query} query Query wrapper
 * @param {function} callback Accepts arguments: (err, data)
 */
DruidNode.prototype.exec = function(query, callback) {
  var self = this
  this.queries++

  debug('Concurrent queries to Druid node ' + this.data.id + ': ' + this.queries)

  Client.prototype.exec.call(this, query, wrapper)

  function wrapper() {
    self.queries--

    callback.apply(null, arguments)
  }
}

/**
 * Druid client relying on ZooKeeper nodes data
 *
 * @constructor
 * @extends EventEmitter
 * @param {string} connectionString ZooKeeper connection string
 * @param {object} [options] node-zookeeper-client options
 * @param {boolean} [options.preferSSL] Prefer SSL connection to Druid nodes if available
 * @param {string} discoveryPath Nodes discovery path
 */
function Druid(connectionString, options, discoveryPath) {
  EventEmitter.call(this)
  this.setMaxListeners(0)

  if (arguments.length === 2) {
    discoveryPath = options
    options = {}
  }

  /**
   * Services discovery path
   *
   * @protected
   * @type {string}
   */
  this.discoveryPath = discoveryPath

  /**
   * Client options
   *
   * @protected
   */
  this.options = options

  /**
   * Finished getting info about nodes and datasources
   *
   * @protected
   * @type {boolean}
   */
  this.ready = false

  /**
   * Nodes (id -> node)
   *
   * @protected
   */
  this.nodes = {}

  /**
   * Nodes associated with each data source (dataSource -> [nodeA, nodeB, ..., nodeN])
   *
   * @protected
   */
  this.dataSources = {}

  /**
   * ZooKeeper client
   *
   * @protected
   */
  initZookeeper(this, connectionString)
}
util.inherits(Druid, EventEmitter)

// Expose all queries types
lodash.each(queries, function(queryClass, type) {
  Druid.prototype[type] = function(dataSource, rawQuery) {
    return getQuery(this, queryClass, dataSource, rawQuery)
  }
})

/**
 * Finish working with client
 *
 * @public
 */
Druid.prototype.end = function() {
  this.closed = true
  this.zk.close()
}

/**
 * Return list of data sources
 *
 * @public
 * @returns {string[]}
 */
Druid.prototype.getDataSources = function() {
  return Object.keys(this.dataSources)
}

/**
 * Return list of nodes
 *
 * @public
 * @returns {DruidNode[]}
 */
Druid.prototype.getNodes = function() {
  return lodash.values(this.nodes)
}

/**
 * Initialize client
 *
 * @protected
 * @see {@link Druid#reloadList}
 */
Druid.prototype.init = function() {
  var self = this

  this.reloadList(done)

  function done(err) {
    if (err) {
      self.emit('error', err)
    }
    else if (!self.ready) {
      self.ready = true
      self.emit('ready')
    }
  }
}

/**
 * Get list of nodes and data sources they serve
 *
 * @private
 * @param {function} callback Accepts arguments: (err)
 */
Druid.prototype.reloadList = function(callback) {
  var self = this

  if (!this.ready) {
    this.zk.getChildren(this.discoveryPath, update, getDataSources)
  }
  else {
    this.zk.getChildren(this.discoveryPath, getDataSources)
  }

  function update() {
    self.init()
  }

  function getDataSources(err, children) {
    if (err) {
      return callback(new DruidError('Error getting info about Druid nodes', err))
    }

    asyncMap(children, getData, done)
  }

  function getData(id, callback) {
    getNodeData(self, id, callback)
  }

  function done(err, data) {
    if (err) {
      return callback(err)
    }

    var dataSources = {}
      , nodes = {}
      , nodeData
      , i
      , ds

    while (nodeData = data.shift()) {
      nodes[nodeData.id] = new DruidNode(nodeData)

      i = 0
      while (ds = nodeData.datasources[i++]) {
        if (dataSources.hasOwnProperty(ds)) {
          dataSources[ds].push(nodeData.id)
        }
        else {
          dataSources[ds] = [nodeData.id]
        }
      }
    }

    self.nodes = nodes
    self.dataSources = dataSources

    callback(null)
  }
}
<|endoftext|>


<commit_before>var BattleTooltips = (function () {
	function BattleTooltips() {}
	BattleTooltips.prototype.boostBasePower = function (BattleRoom, move, pokemon, basePower, basePowerComment) {
		var myPokemon = BattleRoom.myPokemon[pokemon.slot];
		if (!BattleRoom.battle.hasPseudoWeather('Magic Room') && (!pokemon.volatiles || !pokemon.volatiles['embargo'])) {
			var item = Tools.getItem(myPokemon.item);
			var moveType = BattleRoom.getMoveType(move, pokemon);
			var splitItemName = item.name.split(' ');
			var moveName = move.name;

			// Gems
			if (splitItemName[1] == 'Gem' && moveType == splitItemName[0] && (moveName != 'Struggle' || moveName != 'Water Pledge' || moveName != 'Grass Pledge' || moveName != 'Fire Pledge' || moveName != 'Fling')) {
				basePower *= BattleRoom.battle.gen >= 6 ? 1.3 : 1.5;
				basePowerComment += ' (Boosted by ' + item.name + ')';
			}
			// Plates
			if (splitItemName[1] == 'Plate' && item.onPlate && moveType == item.onPlate) {
				basePower *= 1.2;
				basePowerComment += ' (Boosted by ' + item.name + ')';
			}
			// Type-enhancing items
			if (item.name == 'Black Belt' && moveType == 'Fighting' || item.name == 'Black Glasses' && moveType == 'Dark' || item.name == 'Charcoal' && moveType == 'Fire' || item.name == 'Dragon Fang' && moveType == 'Dragon' || item.name == 'Hard Stone' && moveType == 'Rock' || item.name == 'Magnet' && moveType == 'Electric' || item.name == 'Metal Coat' && moveType == 'Steel' || item.name == 'Miracle Seed' && moveType == 'Grass' || item.name == 'Mystic Water' && moveType == 'Water' || item.name == 'Never-Melt Ice' && moveType == 'Ice' || item.name == 'Poison Barb' && moveType == 'Poison' || item.name == 'Sharp Beak' && moveType == 'Flying' || item.name == 'Silk Scarf' && moveType == 'Normal' || item.name == 'SilverPowder' && moveType == 'Bug' || item.name == 'Soft Sand' && moveType == 'Ground' || item.name == 'Spell Tag' && moveType == 'Ghost' || item.name == 'TwistedSpoon' && moveType == 'Psychic') {
				basePower *= BattleRoom.battle.gen >= 4 ? 1.2 : 1.1;
				basePowerComment += ' (Boosted by ' + item.name + ')';
			}
			// Incenses
			if ((item.name == 'Wave Incense' || item.name == 'Sea Incense') && moveType == 'Water' || item.name == 'Rose Incense' && moveType == 'Grass' || item.name == 'Rock Incense' && moveType == 'Rock' || item.name == 'Odd Incense' && moveType == 'Psychic') {
				basePower *= 1.2;
				basePowerComment += ' (Boosted by ' + item.name + ')';
			}

			basePower = Math.floor(basePower);
			return basePower == 0 ? basePowerComment : basePower + basePowerComment;
		}
	};
	BattleTooltips.prototype.boostBasePower_Ball = function (BattleRoom, move, pokemon, min, max) {
		var myPokemon = BattleRoom.myPokemon[pokemon.slot];
		var ability = Tools.getAbility(myPokemon.baseAbility).name;
		var item = Tools.getItem(myPokemon.item);
		var moveType = BattleRoom.getMoveType(move, pokemon);
		var splitItemName = item.name.split(' ');
		var moveName = move.name;
		var itemCheck = false;
		var basePowerComment = '';
		if (!BattleRoom.battle.hasPseudoWeather('Magic Room') && (!pokemon.volatiles || !pokemon.volatiles['embargo'])) {
			if (splitItemName[1] == 'Gem' && moveType == splitItemName[0]) {
				min *= BattleRoom.battle.gen >= 6 ? 1.3 : 1.5;
				max *= BattleRoom.battle.gen >= 6 ? 1.3 : 1.5;
				itemCheck = true;
			}
			if (splitItemName[1] == 'Plate' && item.onPlate && moveType == item.onPlate) {
				min *= 1.2;
				max *= 1.2;
				itemCheck = true;
			}
			if (item.name == 'Magnet' && moveType == 'Electric' || item.name == 'Metal Coat' && moveType == 'Steel') {
				min *= BattleRoom.battle.gen >= 4 ? 1.2 : 1.1;
				max *= BattleRoom.battle.gen >= 4 ? 1.2 : 1.1;
				itemCheck = true;
			}
			if (ability === 'Technician') {
				if (min <= 60) min *= 1.5;
				if (max <= 60) max *= 1.5;
				basePowerComment += '' + ((min === max) ? Math.floor(max) : Math.floor(min) + ' to ' + Math.floor(max)) + ' (Technician boosted)';
				if (itemCheck) {
					basePowerComment += ' (Boosted by ' + item.name + ')';
				}
			} else {
				basePowerComment += (min === max) ? Math.floor(max) : Math.floor(min) + ' to ' + Math.floor(max);
				if (itemCheck) {
					basePowerComment += ' (Boosted by ' + item.name + ')';
				}
			}
		}
		return basePowerComment;
	};
	return BattleTooltips;
})();
<commit_msg>Fix bug with interaction between gems and Pledge moves<commit_after>var BattleTooltips = (function () {
	function BattleTooltips() {}
	BattleTooltips.prototype.boostBasePower = function (BattleRoom, move, pokemon, basePower, basePowerComment) {
		var myPokemon = BattleRoom.myPokemon[pokemon.slot];
		if (!BattleRoom.battle.hasPseudoWeather('Magic Room') && (!pokemon.volatiles || !pokemon.volatiles['embargo'])) {
			var item = Tools.getItem(myPokemon.item);
			var moveType = BattleRoom.getMoveType(move, pokemon);
			var splitItemName = item.name.split(' ');
			var moveName = move.name;

			// Gems
			if (splitItemName[1] == 'Gem' && moveType == splitItemName[0] && moveName != 'Struggle' && moveName != 'Water Pledge' && moveName != 'Grass Pledge' && moveName != 'Fire Pledge' && moveName != 'Fling') {
				basePower *= BattleRoom.battle.gen >= 6 ? 1.3 : 1.5;
				basePowerComment += ' (Boosted by ' + item.name + ')';
			}
			// Plates
			if (splitItemName[1] == 'Plate' && item.onPlate && moveType == item.onPlate) {
				basePower *= 1.2;
				basePowerComment += ' (Boosted by ' + item.name + ')';
			}
			// Type-enhancing items
			if (item.name == 'Black Belt' && moveType == 'Fighting' || item.name == 'Black Glasses' && moveType == 'Dark' || item.name == 'Charcoal' && moveType == 'Fire' || item.name == 'Dragon Fang' && moveType == 'Dragon' || item.name == 'Hard Stone' && moveType == 'Rock' || item.name == 'Magnet' && moveType == 'Electric' || item.name == 'Metal Coat' && moveType == 'Steel' || item.name == 'Miracle Seed' && moveType == 'Grass' || item.name == 'Mystic Water' && moveType == 'Water' || item.name == 'Never-Melt Ice' && moveType == 'Ice' || item.name == 'Poison Barb' && moveType == 'Poison' || item.name == 'Sharp Beak' && moveType == 'Flying' || item.name == 'Silk Scarf' && moveType == 'Normal' || item.name == 'SilverPowder' && moveType == 'Bug' || item.name == 'Soft Sand' && moveType == 'Ground' || item.name == 'Spell Tag' && moveType == 'Ghost' || item.name == 'TwistedSpoon' && moveType == 'Psychic') {
				basePower *= BattleRoom.battle.gen >= 4 ? 1.2 : 1.1;
				basePowerComment += ' (Boosted by ' + item.name + ')';
			}
			// Incenses
			if ((item.name == 'Wave Incense' || item.name == 'Sea Incense') && moveType == 'Water' || item.name == 'Rose Incense' && moveType == 'Grass' || item.name == 'Rock Incense' && moveType == 'Rock' || item.name == 'Odd Incense' && moveType == 'Psychic') {
				basePower *= 1.2;
				basePowerComment += ' (Boosted by ' + item.name + ')';
			}

			basePower = Math.floor(basePower);
			return basePower == 0 ? basePowerComment : basePower + basePowerComment;
		}
	};
	BattleTooltips.prototype.boostBasePower_Ball = function (BattleRoom, move, pokemon, min, max) {
		var myPokemon = BattleRoom.myPokemon[pokemon.slot];
		var ability = Tools.getAbility(myPokemon.baseAbility).name;
		var item = Tools.getItem(myPokemon.item);
		var moveType = BattleRoom.getMoveType(move, pokemon);
		var splitItemName = item.name.split(' ');
		var moveName = move.name;
		var itemCheck = false;
		var basePowerComment = '';
		if (!BattleRoom.battle.hasPseudoWeather('Magic Room') && (!pokemon.volatiles || !pokemon.volatiles['embargo'])) {
			if (splitItemName[1] == 'Gem' && moveType == splitItemName[0]) {
				min *= BattleRoom.battle.gen >= 6 ? 1.3 : 1.5;
				max *= BattleRoom.battle.gen >= 6 ? 1.3 : 1.5;
				itemCheck = true;
			}
			if (splitItemName[1] == 'Plate' && item.onPlate && moveType == item.onPlate) {
				min *= 1.2;
				max *= 1.2;
				itemCheck = true;
			}
			if (item.name == 'Magnet' && moveType == 'Electric' || item.name == 'Metal Coat' && moveType == 'Steel') {
				min *= BattleRoom.battle.gen >= 4 ? 1.2 : 1.1;
				max *= BattleRoom.battle.gen >= 4 ? 1.2 : 1.1;
				itemCheck = true;
			}
			if (ability === 'Technician') {
				if (min <= 60) min *= 1.5;
				if (max <= 60) max *= 1.5;
				basePowerComment += '' + ((min === max) ? Math.floor(max) : Math.floor(min) + ' to ' + Math.floor(max)) + ' (Technician boosted)';
				if (itemCheck) {
					basePowerComment += ' (Boosted by ' + item.name + ')';
				}
			} else {
				basePowerComment += (min === max) ? Math.floor(max) : Math.floor(min) + ' to ' + Math.floor(max);
				if (itemCheck) {
					basePowerComment += ' (Boosted by ' + item.name + ')';
				}
			}
		}
		return basePowerComment;
	};
	return BattleTooltips;
})();
<|endoftext|>


<commit_before>// Copyright 2014-2022, University of Colorado Boulder

/**
 * U-Turn arrow shape, for use with "reset" or "undo" purposes
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

import { Shape } from '../../kite/js/imports.js';
import sceneryPhet from './sceneryPhet.js';

class UTurnArrowShape extends Shape {
  /**
   * @param {number} size A size factor (it'll be a bit bigger)
   */
  constructor( size ) {
    super();

    const strokeWidth = size * 0.3;
    const strokeOffset = strokeWidth / 2;
    const mainWidth = size * 0.6;
    const mainHeight = size;
    const headWidth = size * 0.5;
    const headHeight = size * 0.75;
    const halfHeadWidth = headWidth / 2;
    const halfHeadHeight = headHeight / 2;

    // starts adjacent to the arrowhead on the top, going clockwise
    this.moveTo( halfHeadWidth, -strokeOffset );
    this.lineTo( mainWidth, -strokeOffset );
    // arc (mainWidth,-strokeOffset) => (mainWidth,mainHeight+strokeOffset)
    this.arc( mainWidth, mainHeight / 2, mainHeight / 2 + strokeOffset, -Math.PI / 2, Math.PI / 2, false );
    this.lineTo( 0, mainHeight + strokeOffset );
    this.lineTo( 0, mainHeight - strokeOffset );
    this.lineTo( mainWidth, mainHeight - strokeOffset );
    // arc (mainWidth,mainHeight-strokeOffset) => (mainWidth,strokeOffset)
    this.arc( mainWidth, mainHeight / 2, mainHeight / 2 - strokeOffset, Math.PI / 2, -Math.PI / 2, true );
    this.lineTo( halfHeadWidth, strokeOffset );
    // three lines of the arrow head
    this.lineTo( halfHeadWidth, halfHeadHeight );
    this.lineTo( -halfHeadWidth, 0 );
    this.lineTo( halfHeadWidth, -halfHeadHeight );
    this.close();
  }
}

sceneryPhet.register( 'UTurnArrowShape', UTurnArrowShape );

export default UTurnArrowShape;<commit_msg>// @ts-nocheck<commit_after>// Copyright 2014-2022, University of Colorado Boulder

// @ts-nocheck
/**
 * U-Turn arrow shape, for use with "reset" or "undo" purposes
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

import { Shape } from '../../kite/js/imports.js';
import sceneryPhet from './sceneryPhet.js';

class UTurnArrowShape extends Shape {
  /**
   * @param {number} size A size factor (it'll be a bit bigger)
   */
  constructor( size ) {
    super();

    const strokeWidth = size * 0.3;
    const strokeOffset = strokeWidth / 2;
    const mainWidth = size * 0.6;
    const mainHeight = size;
    const headWidth = size * 0.5;
    const headHeight = size * 0.75;
    const halfHeadWidth = headWidth / 2;
    const halfHeadHeight = headHeight / 2;

    // starts adjacent to the arrowhead on the top, going clockwise
    this.moveTo( halfHeadWidth, -strokeOffset );
    this.lineTo( mainWidth, -strokeOffset );
    // arc (mainWidth,-strokeOffset) => (mainWidth,mainHeight+strokeOffset)
    this.arc( mainWidth, mainHeight / 2, mainHeight / 2 + strokeOffset, -Math.PI / 2, Math.PI / 2, false );
    this.lineTo( 0, mainHeight + strokeOffset );
    this.lineTo( 0, mainHeight - strokeOffset );
    this.lineTo( mainWidth, mainHeight - strokeOffset );
    // arc (mainWidth,mainHeight-strokeOffset) => (mainWidth,strokeOffset)
    this.arc( mainWidth, mainHeight / 2, mainHeight / 2 - strokeOffset, Math.PI / 2, -Math.PI / 2, true );
    this.lineTo( halfHeadWidth, strokeOffset );
    // three lines of the arrow head
    this.lineTo( halfHeadWidth, halfHeadHeight );
    this.lineTo( -halfHeadWidth, 0 );
    this.lineTo( halfHeadWidth, -halfHeadHeight );
    this.close();
  }
}

sceneryPhet.register( 'UTurnArrowShape', UTurnArrowShape );

export default UTurnArrowShape;<|endoftext|>


<commit_before>'use babel'

import {toggleQuotes} from '../lib/toggle-quotes'
import {raw as r} from '../lib/string-helper'

describe('ToggleQuotes', () => {
  beforeEach(() => {
    atom.config.set('toggle-quotes.quoteCharacters', '\'"')
  })

  describe('toggleQuotes(editor) js', () => {
    let editor = null

    beforeEach(() => {
      waitsForPromise(() => {
        return atom.packages.activatePackage('language-javascript')
      })

      waitsForPromise(() => {
        return atom.packages.activatePackage('language-json')
      })

      waitsForPromise(() => {
        return atom.workspace.open()
      })

      runs(() => {
        editor = atom.workspace.getActiveTextEditor()
        editor.setText(
          r`console.log("Hello World")
          console.log('Hello World')
          console.log("Hello 'World'")
          console.log('Hello "World"')
          console.log('')`
        )
        editor.setGrammar(atom.grammars.selectGrammar('test.js'))
      })
    })

    describe('when the cursor is not inside a quoted string', () => {
      it('does nothing', () => {
        expect(() => toggleQuotes(editor)).not.toThrow()
      })
    })

    describe('when the cursor is inside an empty single quoted string', () => {
      it('switches the quotes to double', () => {
        editor.setCursorBufferPosition([4, 13])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(4)).toBe('console.log("")')
        expect(editor.getCursorBufferPosition()).toEqual([4, 13])
      })
    })

    describe('when the cursor is inside a double quoted string', () => {
      describe('when using default config', () => {
        it('switches the double quotes to single quotes', () => {
          editor.setCursorBufferPosition([0, 16])
          toggleQuotes(editor)
          expect(editor.lineTextForBufferRow(0)).toBe("console.log('Hello World')")
          expect(editor.getCursorBufferPosition()).toEqual([0, 16])
        })
      })
    })

    describe('when using custom config of backticks', () => {
      it('switches the double quotes to backticks', () => {
        atom.config.set('toggle-quotes.quoteCharacters', '\'"`')
        editor.setCursorBufferPosition([0, 16])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(0)).toBe('console.log(`Hello World`)')
        expect(editor.getCursorBufferPosition()).toEqual([0, 16])
      })
    })

    describe('when using a scope-specific config override', () => {
      it('prefers the scope-specific setting', () => {
        atom.config.set('toggle-quotes.quoteCharacters', '\'"~', { scopeSelector: '.source.js' })
        editor.setCursorBufferPosition([0, 16])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(0)).toBe('console.log(~Hello World~)')
        expect(editor.getCursorBufferPosition()).toEqual([0, 16])
        atom.config.unset('toggle-quotes.quoteCharacters', { scope: '.source.js' })
      })
    })

    describe('when the cursor is inside a single quoted string', () => {
      it('switches the quotes to double', () => {
        editor.setCursorBufferPosition([1, 16])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(1)).toBe('console.log("Hello World")')
        expect(editor.getCursorBufferPosition()).toEqual([1, 16])
      })
    })

    describe('when the cursor is inside a single-quoted string that is nested within a double quoted string', () => {
      it('switches the outer quotes to single and escapes the inner quotes', () => {
        editor.setCursorBufferPosition([2, 22])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(2)).toBe("console.log('Hello \\'World\\'')")
        expect(editor.getCursorBufferPosition()).toEqual([2, 22])

        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(2)).toBe('console.log("Hello \'World\'")')
      })
    })

    describe('when the cursor is inside a double-quoted string that is nested within a single quoted string', () => {
      it('switches the outer quotes to double and escapes the inner quotes', () => {
        editor.setCursorBufferPosition([3, 22])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(3)).toBe('console.log("Hello \\"World\\"")')
        expect(editor.getCursorBufferPosition()).toEqual([3, 22])

        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(3)).toBe("console.log('Hello \"World\"')")
      })
    })

    describe('when the cursor is inside multiple quoted strings', () => {
      it('switches the quotes of both quoted strings separately and leaves the cursors where they were, and does so atomically', () => {
        editor.setCursorBufferPosition([0, 16])
        editor.addCursorAtBufferPosition([1, 16])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(0)).toBe("console.log('Hello World')")
        expect(editor.lineTextForBufferRow(1)).toBe('console.log("Hello World")')
        expect(editor.getCursors()[0].getBufferPosition()).toEqual([0, 16])
        expect(editor.getCursors()[1].getBufferPosition()).toEqual([1, 16])

        editor.undo()
        expect(editor.lineTextForBufferRow(0)).toBe('console.log("Hello World")')
        expect(editor.lineTextForBufferRow(1)).toBe("console.log('Hello World')")
        expect(editor.getCursors()[0].getBufferPosition()).toEqual([0, 16])
        expect(editor.getCursors()[1].getBufferPosition()).toEqual([1, 16])
      })
    })

    describe('when the cursor is on an invalid region', () => {
      describe('when it is quoted', () => {
        it('toggles the quotes', () => {
          editor.setGrammar(atom.grammars.selectGrammar('test.json'))
          editor.setText("{'invalid': true}")
          editor.setCursorBufferPosition([0, 4])
          toggleQuotes(editor)
          expect(editor.getText()).toBe('{"invalid": true}')
        })
      })

      describe('when it is not quoted', () => {
        it('does not toggle the quotes', () => {
          editor.setGrammar(atom.grammars.selectGrammar('test.json'))
          editor.setText('{invalid: true}')
          editor.setCursorBufferPosition([0, 4])
          toggleQuotes(editor)
          expect(editor.getText()).toBe('{invalid: true}')
        })
      })
    })
  })

  describe('toggleQuotes(editor) python', () => {
    let editor = null

    beforeEach(() => {
      waitsForPromise(() => {
        return atom.packages.activatePackage('language-python')
      })

      waitsForPromise(() => {
        return atom.workspace.open()
      })

      runs(() => {
        editor = atom.workspace.getActiveTextEditor()
        editor.setText(
          r`print(u"Hello World")
          print(r'')`
        )
        editor.setGrammar(atom.grammars.selectGrammar('test.py'))
      })
    })

    describe('when cursor is inside a double quoted unicode string', () => {
      it('switches quotes to single excluding unicode character', () => {
        editor.setCursorBufferPosition([0, 16])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(0)).toBe("print(u'Hello World')")
        expect(editor.getCursorBufferPosition()).toEqual([0, 16])
      })
    })

    describe('when cursor is inside an empty single quoted raw string', () => {
      it('switches quotes to double', () => {
        editor.setCursorBufferPosition([1, 8])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(1)).toBe('print(r"")')
        expect(editor.getCursorBufferPosition()).toEqual([1, 8])
      })
    })
  })

  it('activates when a command is triggered', () => {
    let activatePromise = atom.packages.activatePackage('toggle-quotes')

    waitsForPromise(() => {
      return atom.workspace.open()
    })

    runs(() => {
      let editor = atom.workspace.getActiveTextEditor()
      atom.commands.dispatch(atom.views.getView(editor), 'toggle-quotes:toggle')
    })

    waitsForPromise(() => { return activatePromise })
  })
})
<commit_msg>Add a spec.<commit_after>'use babel'

import {toggleQuotes} from '../lib/toggle-quotes'
import {raw as r} from '../lib/string-helper'

describe('ToggleQuotes', () => {
  beforeEach(() => {
    atom.config.set('toggle-quotes.quoteCharacters', '\'"')
  })

  describe('toggleQuotes(editor) js', () => {
    let editor = null

    beforeEach(() => {
      waitsForPromise(() => {
        return Promise.all([
          atom.packages.activatePackage('language-javascript'),
          atom.packages.activatePackage('language-json'),
          atom.packages.activatePackage('language-python'),
          atom.workspace.open()
        ])
      })

      runs(() => {
        editor = atom.workspace.getActiveTextEditor()
        editor.setText(
          r`console.log("Hello World")
          console.log('Hello World')
          console.log("Hello 'World'")
          console.log('Hello "World"')
          console.log('')`
        )
        editor.setGrammar(atom.grammars.selectGrammar('test.js'))
      })
    })

    describe('when the cursor is not inside a quoted string', () => {
      it('does nothing', () => {
        expect(() => toggleQuotes(editor)).not.toThrow()
      })
    })

    describe('when the cursor is inside an empty single quoted string', () => {
      it('switches the quotes to double', () => {
        editor.setCursorBufferPosition([4, 13])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(4)).toBe('console.log("")')
        expect(editor.getCursorBufferPosition()).toEqual([4, 13])
      })
    })

    describe('when the cursor is inside a double quoted string', () => {
      describe('when using default config', () => {
        it('switches the double quotes to single quotes', () => {
          editor.setCursorBufferPosition([0, 16])
          toggleQuotes(editor)
          expect(editor.lineTextForBufferRow(0)).toBe("console.log('Hello World')")
          expect(editor.getCursorBufferPosition()).toEqual([0, 16])
        })
      })
    })

    describe('when using custom config of backticks', () => {
      it('switches the double quotes to backticks', () => {
        atom.config.set('toggle-quotes.quoteCharacters', '\'"`')
        editor.setCursorBufferPosition([0, 16])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(0)).toBe('console.log(`Hello World`)')
        expect(editor.getCursorBufferPosition()).toEqual([0, 16])
      })
    })

    describe('when using a scope-specific config override', () => {
      it('prefers the scope-specific setting', () => {
        atom.config.set('toggle-quotes.quoteCharacters', '\'"~', { scopeSelector: '.source.js' })
        editor.setCursorBufferPosition([0, 16])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(0)).toBe('console.log(~Hello World~)')
        expect(editor.getCursorBufferPosition()).toEqual([0, 16])
        atom.config.unset('toggle-quotes.quoteCharacters', { scope: '.source.js' })
      })

      it('does not bleed into the wrong scope', () => {
        atom.config.set('toggle-quotes.quoteCharacters', '\'"~', { scopeSelector: '.source.js' })
        editor.setGrammar(atom.grammars.selectGrammar('test.py'))
        editor.setCursorBufferPosition([0, 16])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(0)).toBe('console.log(\'Hello World\')')
        expect(editor.getCursorBufferPosition()).toEqual([0, 16])
        atom.config.unset('toggle-quotes.quoteCharacters', { scope: '.source.js' })
      })

    })

    describe('when the cursor is inside a single quoted string', () => {
      it('switches the quotes to double', () => {
        editor.setCursorBufferPosition([1, 16])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(1)).toBe('console.log("Hello World")')
        expect(editor.getCursorBufferPosition()).toEqual([1, 16])
      })
    })

    describe('when the cursor is inside a single-quoted string that is nested within a double quoted string', () => {
      it('switches the outer quotes to single and escapes the inner quotes', () => {
        editor.setCursorBufferPosition([2, 22])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(2)).toBe("console.log('Hello \\'World\\'')")
        expect(editor.getCursorBufferPosition()).toEqual([2, 22])

        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(2)).toBe('console.log("Hello \'World\'")')
      })
    })

    describe('when the cursor is inside a double-quoted string that is nested within a single quoted string', () => {
      it('switches the outer quotes to double and escapes the inner quotes', () => {
        editor.setCursorBufferPosition([3, 22])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(3)).toBe('console.log("Hello \\"World\\"")')
        expect(editor.getCursorBufferPosition()).toEqual([3, 22])

        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(3)).toBe("console.log('Hello \"World\"')")
      })
    })

    describe('when the cursor is inside multiple quoted strings', () => {
      it('switches the quotes of both quoted strings separately and leaves the cursors where they were, and does so atomically', () => {
        editor.setCursorBufferPosition([0, 16])
        editor.addCursorAtBufferPosition([1, 16])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(0)).toBe("console.log('Hello World')")
        expect(editor.lineTextForBufferRow(1)).toBe('console.log("Hello World")')
        expect(editor.getCursors()[0].getBufferPosition()).toEqual([0, 16])
        expect(editor.getCursors()[1].getBufferPosition()).toEqual([1, 16])

        editor.undo()
        expect(editor.lineTextForBufferRow(0)).toBe('console.log("Hello World")')
        expect(editor.lineTextForBufferRow(1)).toBe("console.log('Hello World')")
        expect(editor.getCursors()[0].getBufferPosition()).toEqual([0, 16])
        expect(editor.getCursors()[1].getBufferPosition()).toEqual([1, 16])
      })
    })

    describe('when the cursor is on an invalid region', () => {
      describe('when it is quoted', () => {
        it('toggles the quotes', () => {
          editor.setGrammar(atom.grammars.selectGrammar('test.json'))
          editor.setText("{'invalid': true}")
          editor.setCursorBufferPosition([0, 4])
          toggleQuotes(editor)
          expect(editor.getText()).toBe('{"invalid": true}')
        })
      })

      describe('when it is not quoted', () => {
        it('does not toggle the quotes', () => {
          editor.setGrammar(atom.grammars.selectGrammar('test.json'))
          editor.setText('{invalid: true}')
          editor.setCursorBufferPosition([0, 4])
          toggleQuotes(editor)
          expect(editor.getText()).toBe('{invalid: true}')
        })
      })
    })
  })

  describe('toggleQuotes(editor) python', () => {
    let editor = null

    beforeEach(() => {
      waitsForPromise(() => {
        return atom.packages.activatePackage('language-python')
      })

      waitsForPromise(() => {
        return atom.workspace.open()
      })

      runs(() => {
        editor = atom.workspace.getActiveTextEditor()
        editor.setText(
          r`print(u"Hello World")
          print(r'')`
        )
        editor.setGrammar(atom.grammars.selectGrammar('test.py'))
      })
    })

    describe('when cursor is inside a double quoted unicode string', () => {
      it('switches quotes to single excluding unicode character', () => {
        editor.setCursorBufferPosition([0, 16])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(0)).toBe("print(u'Hello World')")
        expect(editor.getCursorBufferPosition()).toEqual([0, 16])
      })
    })

    describe('when cursor is inside an empty single quoted raw string', () => {
      it('switches quotes to double', () => {
        editor.setCursorBufferPosition([1, 8])
        toggleQuotes(editor)
        expect(editor.lineTextForBufferRow(1)).toBe('print(r"")')
        expect(editor.getCursorBufferPosition()).toEqual([1, 8])
      })
    })
  })

  it('activates when a command is triggered', () => {
    let activatePromise = atom.packages.activatePackage('toggle-quotes')

    waitsForPromise(() => {
      return atom.workspace.open()
    })

    runs(() => {
      let editor = atom.workspace.getActiveTextEditor()
      atom.commands.dispatch(atom.views.getView(editor), 'toggle-quotes:toggle')
    })

    waitsForPromise(() => { return activatePromise })
  })
})
<|endoftext|>