<commit_before>var yawl = require('../');
var url = require('url');
var http = require('http');
var assert = require('assert');
var BufferList = require('bl');
var describe = global.describe;
var it = global.it;

describe("yawl", function() {
  it("fragmented messages with maxFrameSize Infinity", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      allowTextMessages: true,
      allowFragmentedMessages: true,
      maxFrameSize: Infinity,
      origin: null,
    });
    wss.on('connection', function(ws, request) {
      assert.strictEqual(request.url, "/huzzah");
      ws.on('streamMessage', function(msg, isUtf8, len) {
        assert.strictEqual(isUtf8, true);
        assert.strictEqual(len, 5);
        var bl = new BufferList();
        msg.pipe(bl);
        bl.on('finish', function() {
          assert.strictEqual(bl.toString('utf8'), "hello")
          ws.sendBinary(new Buffer([0x1, 0x3, 0x3, 0x7]));
        });
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/huzzah',
        allowBinaryMessages: true,
        maxFrameSize: Infinity,
      };
      var client = yawl.createClient(options);
      client.on('open', function(response) {
        assert.ok(response);
        assert.ok(client.socket);
        client.sendText("hello");
      });
      client.on('closeMessage', function(statusCode, reason) {
        throw new Error("closed: " + statusCode + ": " + reason);
      });
      client.on('streamMessage', function(msg, isUtf8, len) {
        assert.strictEqual(isUtf8, false);
        assert.strictEqual(len, 4);
        var bl = new BufferList();
        msg.pipe(bl);
        bl.on('finish', function() {
          var buf = bl.slice();
          assert.strictEqual(buf[0], 0x1);
          assert.strictEqual(buf[1], 0x3);
          assert.strictEqual(buf[2], 0x3);
          assert.strictEqual(buf[3], 0x7);
          client.close();
          httpServer.close(cb);
        });
      });
    });
  });

  it("maxFrameSize", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      maxFrameSize: 10,
      allowTextMessages: true,
      origin: null,
    });
    var gotErr = false;
    wss.on('connection', function(ws) {
      ws.on('error', function(err) {
        assert.strictEqual(err.statusCode, 1009);
        assert.strictEqual(err.message, "exceeded max frame size");
        gotErr = true;
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
      };
      var client = yawl.createClient(options);
      client.on('open', function() {
        client.sendText("this is a little bit longer than 10 chars");
      });
      var gotCloseMessage = false;
      client.on('closeMessage', function(statusCode, reason) {
        assert.strictEqual(statusCode, 1009);
        assert.strictEqual(reason, "exceeded max frame size");
        gotCloseMessage = true;
      });
      client.on('close', function() {
        assert.strictEqual(gotCloseMessage, true);
        assert.strictEqual(gotErr, true);
        httpServer.close(cb);
      });
    });
  });

  it("buffered messages", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      allowTextMessages: true,
      allowBinaryMessages: true,
      origin: null,
    });
    var buf = new Buffer(65536);
    buf.fill('a');
    var str = buf.toString('utf8');
    wss.on('connection', function(ws) {
      ws.on('textMessage', function(message) {
        assert.strictEqual(message, str);
        ws.sendBinary(new Buffer([100, 101, 102]));
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
        allowBinaryMessages: true,
      };
      var client = yawl.createClient(options);
      client.on('open', function() {
        client.sendText(str);
      });
      var gotMessage = false;
      client.on('binaryMessage', function(message) {
        assert.strictEqual(message[0], 100);
        assert.strictEqual(message[1], 101);
        assert.strictEqual(message[2], 102);
        client.close();
        gotMessage = true;
      });
      client.on('close', function() {
        assert.strictEqual(gotMessage, true);
        httpServer.close(cb);
      });
    });
  });

  it("streaming messages", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      allowTextMessages: true,
      allowBinaryMessages: true,
      allowFragmentedMessages: true,
      origin: null,
    });
    wss.on('connection', function(ws) {
      ws.on('textMessage', function(message) {
        ws.sendText(message);
      });
      ws.on('binaryMessage', function(message) {
        ws.sendBinary(message);
      });
      ws.on('streamMessage', function(stream, isUtf8, length) {
        stream.pipe(ws.sendStream(isUtf8, length));
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
        allowBinaryMessages: true,
        allowTextMessages: true,
        allowFragmentedMessages: true,
      };
      var client = yawl.createClient(options);
      client.on('open', function() {
        var stream = client.sendStream(true);
        stream.write("this is the first fragment");
        stream.write("this is the second fragment");
        stream.end();
      });
      client.on('streamMessage', function(stream, isUtf8, length) {
        assert.strictEqual(isUtf8, true);
        assert.equal(length, null);
        var bl = new BufferList();
        stream.pipe(bl);
        bl.on('finish', function() {
          assert.strictEqual(bl.toString('utf8'),
            "this is the first fragmentthis is the second fragment");
          client.close();
        });
      });
      client.on('close', function() {
        httpServer.close(cb);
      });
    });
  });

  it("client emits error when server misbehaves", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      origin: null,
    });
    var serverGotClose = false;
    wss.on('connection', function(ws) {
      ws.on('closeMessage', function(statusCode, message) {
        assert.strictEqual(statusCode, 1002, 'invalid reserve bits');
        serverGotClose = true;
      });
      ws.socket.write("trash data");
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
      };
      var client = yawl.createClient(options);
      var errorOccurred = false;
      var gotOpen = false;
      client.on('open', function() {
        gotOpen = true;
      });
      client.on('error', function(err) {
        assert.strictEqual(err.statusCode, 1002);
        errorOccurred = true;
      });
      client.on('closeMessage', function(statusCode, message) {
        throw new Error("did not expect client close message");
      });
      client.on('close', function() {
        assert.strictEqual(errorOccurred, true);
        assert.strictEqual(serverGotClose, true);
        assert.strictEqual(gotOpen, true);
        httpServer.close(cb);
      });
    });
  });

  it("allowUnfragmentedMessages = false", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      origin: null,
      allowUnfragmentedMessages: false,
      allowBinaryMessages: true,
      allowTextMessages: true,
    });
    var gotServerError = false;
    wss.on('connection', function(ws) {
      ws.on('error', function(err) {
        assert.strictEqual(err.statusCode, 1003);
        assert.strictEqual(err.message, "unfragmented messages not allowed");
        gotServerError = true;
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
      };
      var client = yawl.createClient(options);
      var gotOpen = false;
      client.on('open', function() {
        client.sendText("hi");
        gotOpen = true;
      });
      var gotCloseMessage = false;
      client.on('closeMessage', function(statusCode, message) {
        assert.strictEqual(statusCode, 1003);
        assert.strictEqual(message, "unfragmented messages not allowed");
        gotCloseMessage = true;
      });
      client.on('close', function() {
        assert.strictEqual(gotServerError, true);
        assert.strictEqual(gotCloseMessage, true);
        assert.strictEqual(gotOpen, true);
        httpServer.close(cb);
      });
    });
  });

  it("sendStream unfragmented message", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      allowTextMessages: true,
      origin: null,
    });
    wss.on('connection', function(ws) {
      ws.on('textMessage', function(message) {
        assert.strictEqual(message, "unfragmentedmessage");
        ws.close();
        httpServer.close(cb);
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
      };
      var client = yawl.createClient(options);
      client.on('open', function() {
        var outStream = client.sendStream(true, "unfragmentedmessage".length);
        outStream.write("unfragmented");
        outStream.write("message");
        outStream.end();
      });
    });
  });

  it("parseExtensionList missing header", function() {
    assert.deepEqual(yawl.parseExtensionList({headers: {}}), null);
  });

  it("parseExtensionList complicated", function() {
    var request = {
      headers: {
        'sec-websocket-extensions': 'foo,bar; baz=2;extra, third; arg="quoted"',
      },
    };
    var expected = [
      {
        name: 'foo',
        params: [],
      },
      {
        name: 'bar',
        params: [
          {
            name: 'baz',
            value: '2',
          },
          {
            name: 'extra',
            value: null,
          },
        ],
      },
      {
        name: 'third',
        params: [
          {
            name: 'arg',
            value: 'quoted',
          },
        ],
      },
    ];
    assert.deepEqual(yawl.parseExtensionList(request), expected);
  });
});
<commit_msg>add test for sending control messages during fragmented stream<commit_after>var yawl = require('../');
var url = require('url');
var http = require('http');
var assert = require('assert');
var BufferList = require('bl');
var describe = global.describe;
var it = global.it;

describe("yawl", function() {
  it("fragmented messages with maxFrameSize Infinity", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      allowTextMessages: true,
      allowFragmentedMessages: true,
      maxFrameSize: Infinity,
      origin: null,
    });
    wss.on('connection', function(ws, request) {
      assert.strictEqual(request.url, "/huzzah");
      ws.on('streamMessage', function(msg, isUtf8, len) {
        assert.strictEqual(isUtf8, true);
        assert.strictEqual(len, 5);
        var bl = new BufferList();
        msg.pipe(bl);
        bl.on('finish', function() {
          assert.strictEqual(bl.toString('utf8'), "hello")
          ws.sendBinary(new Buffer([0x1, 0x3, 0x3, 0x7]));
        });
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/huzzah',
        allowBinaryMessages: true,
        maxFrameSize: Infinity,
      };
      var client = yawl.createClient(options);
      client.on('open', function(response) {
        assert.ok(response);
        assert.ok(client.socket);
        client.sendText("hello");
      });
      client.on('closeMessage', function(statusCode, reason) {
        throw new Error("closed: " + statusCode + ": " + reason);
      });
      client.on('streamMessage', function(msg, isUtf8, len) {
        assert.strictEqual(isUtf8, false);
        assert.strictEqual(len, 4);
        var bl = new BufferList();
        msg.pipe(bl);
        bl.on('finish', function() {
          var buf = bl.slice();
          assert.strictEqual(buf[0], 0x1);
          assert.strictEqual(buf[1], 0x3);
          assert.strictEqual(buf[2], 0x3);
          assert.strictEqual(buf[3], 0x7);
          client.close();
          httpServer.close(cb);
        });
      });
    });
  });

  it("maxFrameSize", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      maxFrameSize: 10,
      allowTextMessages: true,
      origin: null,
    });
    var gotErr = false;
    wss.on('connection', function(ws) {
      ws.on('error', function(err) {
        assert.strictEqual(err.statusCode, 1009);
        assert.strictEqual(err.message, "exceeded max frame size");
        gotErr = true;
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
      };
      var client = yawl.createClient(options);
      client.on('open', function() {
        client.sendText("this is a little bit longer than 10 chars");
      });
      var gotCloseMessage = false;
      client.on('closeMessage', function(statusCode, reason) {
        assert.strictEqual(statusCode, 1009);
        assert.strictEqual(reason, "exceeded max frame size");
        gotCloseMessage = true;
      });
      client.on('close', function() {
        assert.strictEqual(gotCloseMessage, true);
        assert.strictEqual(gotErr, true);
        httpServer.close(cb);
      });
    });
  });

  it("buffered messages", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      allowTextMessages: true,
      allowBinaryMessages: true,
      origin: null,
    });
    var buf = new Buffer(65536);
    buf.fill('a');
    var str = buf.toString('utf8');
    wss.on('connection', function(ws) {
      ws.on('textMessage', function(message) {
        assert.strictEqual(message, str);
        ws.sendBinary(new Buffer([100, 101, 102]));
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
        allowBinaryMessages: true,
      };
      var client = yawl.createClient(options);
      client.on('open', function() {
        client.sendText(str);
      });
      var gotMessage = false;
      client.on('binaryMessage', function(message) {
        assert.strictEqual(message[0], 100);
        assert.strictEqual(message[1], 101);
        assert.strictEqual(message[2], 102);
        client.close();
        gotMessage = true;
      });
      client.on('close', function() {
        assert.strictEqual(gotMessage, true);
        httpServer.close(cb);
      });
    });
  });

  it("streaming messages", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      allowTextMessages: true,
      allowBinaryMessages: true,
      allowFragmentedMessages: true,
      origin: null,
    });
    wss.on('connection', function(ws) {
      ws.on('textMessage', function(message) {
        ws.sendText(message);
      });
      ws.on('binaryMessage', function(message) {
        ws.sendBinary(message);
      });
      ws.on('streamMessage', function(stream, isUtf8, length) {
        stream.pipe(ws.sendStream(isUtf8, length));
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
        allowBinaryMessages: true,
        allowTextMessages: true,
        allowFragmentedMessages: true,
      };
      var client = yawl.createClient(options);
      client.on('open', function() {
        var stream = client.sendStream(true);
        stream.write("this is the first fragment");
        stream.write("this is the second fragment");
        stream.end();
      });
      client.on('streamMessage', function(stream, isUtf8, length) {
        assert.strictEqual(isUtf8, true);
        assert.equal(length, null);
        var bl = new BufferList();
        stream.pipe(bl);
        bl.on('finish', function() {
          assert.strictEqual(bl.toString('utf8'),
            "this is the first fragmentthis is the second fragment");
          client.close();
        });
      });
      client.on('close', function() {
        httpServer.close(cb);
      });
    });
  });

  it("client emits error when server misbehaves", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      origin: null,
    });
    var serverGotClose = false;
    wss.on('connection', function(ws) {
      ws.on('closeMessage', function(statusCode, message) {
        assert.strictEqual(statusCode, 1002, 'invalid reserve bits');
        serverGotClose = true;
      });
      ws.socket.write("trash data");
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
      };
      var client = yawl.createClient(options);
      var errorOccurred = false;
      var gotOpen = false;
      client.on('open', function() {
        gotOpen = true;
      });
      client.on('error', function(err) {
        assert.strictEqual(err.statusCode, 1002);
        errorOccurred = true;
      });
      client.on('closeMessage', function(statusCode, message) {
        throw new Error("did not expect client close message");
      });
      client.on('close', function() {
        assert.strictEqual(errorOccurred, true);
        assert.strictEqual(serverGotClose, true);
        assert.strictEqual(gotOpen, true);
        httpServer.close(cb);
      });
    });
  });

  it("allowUnfragmentedMessages = false", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      origin: null,
      allowUnfragmentedMessages: false,
      allowBinaryMessages: true,
      allowTextMessages: true,
    });
    var gotServerError = false;
    wss.on('connection', function(ws) {
      ws.on('error', function(err) {
        assert.strictEqual(err.statusCode, 1003);
        assert.strictEqual(err.message, "unfragmented messages not allowed");
        gotServerError = true;
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
      };
      var client = yawl.createClient(options);
      var gotOpen = false;
      client.on('open', function() {
        client.sendText("hi");
        gotOpen = true;
      });
      var gotCloseMessage = false;
      client.on('closeMessage', function(statusCode, message) {
        assert.strictEqual(statusCode, 1003);
        assert.strictEqual(message, "unfragmented messages not allowed");
        gotCloseMessage = true;
      });
      client.on('close', function() {
        assert.strictEqual(gotServerError, true);
        assert.strictEqual(gotCloseMessage, true);
        assert.strictEqual(gotOpen, true);
        httpServer.close(cb);
      });
    });
  });

  it("sendStream unfragmented message", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      allowTextMessages: true,
      origin: null,
    });
    wss.on('connection', function(ws) {
      ws.on('textMessage', function(message) {
        assert.strictEqual(message, "unfragmentedmessage");
        ws.close();
        httpServer.close(cb);
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
      };
      var client = yawl.createClient(options);
      client.on('open', function() {
        var outStream = client.sendStream(true, "unfragmentedmessage".length);
        outStream.write("unfragmented");
        outStream.write("message");
        outStream.end();
      });
    });
  });

  it("send a ping and get a pong during a fragmented stream", function(cb) {
    var httpServer = http.createServer();
    var wss = yawl.createServer({
      server: httpServer,
      allowBinaryMessages: true,
      allowFragmentedMessages: true,
      origin: null,
    });
    wss.on('connection', function(ws) {
      ws.on('streamMessage', function(msg, isUtf8, length) {
        var bl = new BufferList();
        msg.pipe(bl);
        bl.on('finish', function() {
          assert.strictEqual(bl.toString('utf8'), 'msg1msg2');
          ws.close();
        });
      });
    });
    httpServer.listen(function() {
      var options = {
        host: 'localhost',
        protocol: 'ws',
        port: httpServer.address().port,
        path: '/',
      };
      var client = yawl.createClient(options);
      var outStream;
      client.on('open', function() {
        outStream = client.sendStream();
        outStream.write("msg1");
        client.sendPingText("msg2");
      });
      client.on('pongMessage', function(buffer) {
        outStream.write(buffer);
        outStream.end();
      });
      client.on('close', function() {
        httpServer.close(cb);
      });
    });
  });

  it("parseExtensionList missing header", function() {
    assert.deepEqual(yawl.parseExtensionList({headers: {}}), null);
  });

  it("parseExtensionList complicated", function() {
    var request = {
      headers: {
        'sec-websocket-extensions': 'foo,bar; baz=2;extra, third; arg="quoted"',
      },
    };
    var expected = [
      {
        name: 'foo',
        params: [],
      },
      {
        name: 'bar',
        params: [
          {
            name: 'baz',
            value: '2',
          },
          {
            name: 'extra',
            value: null,
          },
        ],
      },
      {
        name: 'third',
        params: [
          {
            name: 'arg',
            value: 'quoted',
          },
        ],
      },
    ];
    assert.deepEqual(yawl.parseExtensionList(request), expected);
  });
});
<|endoftext|>


<commit_before>const Game = require('./game');
const Duck = require('./duck');

var canvas = document.getElementById('game');
var elemLeft = canvas.offsetLeft;
var elemTop = canvas.offsetTop;

const duck = new Duck(50, 50, canvas);
const game = new Game();

canvas.addEventListener("mousedown", function (){
  context.fillStyle="white";
  context.fillRect(0, 0, canvas.width, canvas.height);
  this.x = event.pageX - elemLeft;
  this.y = event.pageY - elemTop;
  game.scoreKeeper(this, duck );
});

// load scenery image
var grassReady = false;
var grassImage = new Image();
grassImage.onload = function () {
	grassReady = true;
};
grassImage.src = "./lib/images/stage.png";

// load duck image
var duckReady = false;
var duckImage = new Image();
duckImage.onload = function () {
	duckReady = true;
};
duckImage.src = "./lib/images/duck.png";

var context = canvas.getContext('2d');
const render = function () {

  if (duckReady) {
    context.drawImage(duckImage, duck.x, duck.y );
  }
  if (grassReady) {
		context.drawImage(grassImage, 0, 85 );
	}

  scoreboard.call(this);
};

var scoreboard = function (){
  context.fillStyle ="rgb(0, 0, 0)";
  context.font="30px Arial";
  context.fillRect(175, 550, 350, 50);
  context.fillStyle="red";
  context.fillText("Ducks hit: " + game.successfulHits + " Shots Left: " + game.shot_count,  180, 585);
  context.lineWidth= "6";
  context.strokeStyle="green";
  context.rect(175, 550, 350, 50);
  context.stroke();
}

var restart = function() {
  game.resetGame(duck);
  duck.canvas = canvas;
  context.clearRect(0, 0, canvas.width, canvas.height);
  duck.draw(context).move();
};

var playAgain = function (message, messageX, messageY) {
  context.clearRect(0, 0, canvas.width, canvas.height);
  context.fillStyle="red";
  context.fillText(message, messageX, messageY);
  context.fillStyle="red";
  context.fillRect(280, 330, 160, 40);
  context.fillStyle="white";
  context.fillText("Play Again?", 280, 360);
};

var redrawGame = function (gameLoop) {
  context.clearRect(0, 0, canvas.width, canvas.height);
  duck.draw(context).move();
  render();
  requestAnimationFrame(gameLoop);
};

var restartListener = function (gameLoop) {
canvas.addEventListener("mousedown", function (){
   this.x = event.pageX - elemLeft;
   this.y = event.pageY - elemTop;
   if (this.x>=280 && this.x<=442 && this.y>=330 && this.y<=372) {
    restart.call(this);
    requestAnimationFrame(gameLoop);
    }
  });
};

requestAnimationFrame(function gameLoop() {
  if(game.shot_count < 1 || game.successfulHits > 0){
    if (game.shot_count < 1 && game.successfulHits < 1) {
      playAgain.call(this, "Game over! Ducks win!", 200, 300);
      restartListener(this, gameLoop);
    } else if ( duck.y + 20 < canvas.height) {
      redrawGame.call(this, gameLoop);
    } else if (duck.x <= (canvas.x + 62)) {
      playAgain.call(this, "You win", 300, 300);
      restartListener.call(this, gameLoop);
    }
  } else {
    redrawGame.call(this, gameLoop);
  }
});
<commit_msg>Fix when user doesn't click in the box to restart game<commit_after>const Game = require('./game');
const Duck = require('./duck');

var canvas = document.getElementById('game');
var elemLeft = canvas.offsetLeft;
var elemTop = canvas.offsetTop;

const duck = new Duck(50, 50, canvas);
const game = new Game();

canvas.addEventListener("mousedown", function (){
  context.fillStyle="white";
  context.fillRect(0, 0, canvas.width, canvas.height);
  this.x = event.pageX - elemLeft;
  this.y = event.pageY - elemTop;
  game.scoreKeeper(this, duck );
});

// load scenery image
var grassReady = false;
var grassImage = new Image();
grassImage.onload = function () {
	grassReady = true;
};
grassImage.src = "./lib/images/stage.png";

// load duck image
var duckReady = false;
var duckImage = new Image();
duckImage.onload = function () {
	duckReady = true;
};
duckImage.src = "./lib/images/duck.png";

var context = canvas.getContext('2d');
const render = function () {

  if (duckReady) {
    context.drawImage(duckImage, duck.x, duck.y );
  }
  if (grassReady) {
		context.drawImage(grassImage, 0, 85 );
	}

  scoreboard.call(this);
};

var scoreboard = function (){
  context.fillStyle ="rgb(0, 0, 0)";
  context.font="30px Arial";
  context.fillRect(175, 550, 350, 50);
  context.fillStyle="red";
  context.fillText("Ducks hit: " + game.successfulHits + " Shots Left: " + game.shot_count,  180, 585);
  context.lineWidth= "6";
  context.strokeStyle="green";
  context.rect(175, 550, 350, 50);
  context.stroke();
}

var restart = function() {
  game.resetGame(duck);
  duck.canvas = canvas;
  context.clearRect(0, 0, canvas.width, canvas.height);
  duck.draw(context).move();
};

var playAgain = function (message, messageX, messageY) {
  context.clearRect(0, 0, canvas.width, canvas.height);
  context.fillStyle="red";
  context.fillText(message, messageX, messageY);
  context.fillStyle="red";
  context.fillRect(280, 330, 160, 40);
  context.fillStyle="white";
  context.fillText("Play Again?", 280, 360);
};

var redrawGame = function (gameLoop) {
  context.clearRect(0, 0, canvas.width, canvas.height);
  duck.draw(context).move();
  render();
  requestAnimationFrame(gameLoop);
};

var restartListener = function (gameLoop, message, messageX, messageY ) {
  canvas.addEventListener("mousedown", function (){
     this.x = event.pageX - elemLeft;
     this.y = event.pageY - elemTop;
     if (this.x >= 280 && this.x <= 442 && this.y >= 330 && this.y <= 372) {
      restart.call(this);
      requestAnimationFrame(gameLoop);
    } else {
      playAgain.call(this, message, messageX, messageY);
    }
  });
};

requestAnimationFrame(function gameLoop() {
  if(game.shot_count < 1 || game.successfulHits > 0){
    if (game.shot_count < 1 && game.successfulHits < 1) {
      playAgain.call(this, "Game over! Ducks win!", 200, 300);
      restartListener.call(this, gameLoop, "Game over! Ducks win!", 200, 300);
    } else if ( duck.y + 20 < canvas.height) {
      redrawGame.call(this, gameLoop);
    } else if (duck.x <= (canvas.x + 62)) {
      playAgain.call(this, "You win!", 300, 300);
      restartListener.call(this, gameLoop, "You win!", 300, 300);
    }
  } else {
    redrawGame.call(this, gameLoop);
  }
});
<|endoftext|>


<commit_before>/**
 * @author Gilles Coomans <gilles.coomans@gmail.com>
 *
 */
if (typeof require !== 'undefined')
	var chai = require("./chai"),
		aright = require("../index"),
		v = aright.v;
else
	var v = aright.v;

var expect = chai.expect;

//________________ is* family

describe("is string success", function() {
	var result = v().isString().validate('abcdef'); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("is string fail", function() {
	var result = v().isString().validate(12); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("is number success", function() {
	var result = v().isNumber().validate(12); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("is number fail", function() {
	var result = v().isNumber().validate(true); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("is bool success", function() {
	var result = v().isBool().validate(true); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("is bool fail", function() {
	var result = v().isBool().validate(1); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("is func success", function() {
	var result = v().isFunction().validate(function() {}); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("is func fail", function() {
	var result = v().isFunction().validate(1); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("isArray success", function() {
	var result = v().isArray().validate([]); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("isArray fail", function() {
	var result = v().isArray().validate({}); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("isObject success", function() {
	var result = v().isObject().validate({}); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("isObject fail", function() {
	var result = v().isObject().validate(true); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

//________________ properties family

describe("string success", function() {
	var result = v().string('test').validate({ test: 'abcdef' }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("string fail", function() {
	var result = v().string('test').validate({ test: 1 }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("number success", function() {
	var result = v().number('test').validate({ test: 1 }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("number fail", function() {
	var result = v().number('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("bool success", function() {
	var result = v().bool('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("bool fail", function() {
	var result = v().bool('test').validate({ test: 1 }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("func success", function() {
	var result = v().func('test').validate({ test: function() {} }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("func fail", function() {
	var result = v().func('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("array success", function() {
	var result = v().array('test').validate({ test: [] }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("array fail", function() {
	var result = v().array('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("object success", function() {
	var result = v().object('test').validate({ test: {} }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("object fail", function() {
	var result = v().object('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("null success", function() {
	var result = v().null('test').validate({ test: null }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("null fail", function() {
	var result = v().null('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

//_______________________________ CONSTRAINTS

describe("enumerable fail", function() {
	var result = v().enumerable(['bloupi', 'foo']).validate('bloup');
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("enumerable success", function() {
	var result = v().enumerable(['bloupi', 'foo']).validate('bloupi');
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("max fail", function() {
	var result = v().maximum(3).validate(4);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("max success", function() {
	var result = v().maximum(3).validate(2);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("min fail", function() {
	var result = v().minimum(3).validate(2);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("min success", function() {
	var result = v().minimum(3).validate(4);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("maxLength fail", function() {
	var result = v().maxLength(3).validate('abcd');
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("maxLength success", function() {
	var result = v().maxLength(3).validate('ab');
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("minLength fail", function() {
	var result = v().minLength(3).validate('ab');
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("minLength success", function() {
	var result = v().minLength(3).validate('abc');
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("equal fail", function() {
	var result = v().equal(3).validate(2);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("equal success", function() {
	var result = v().equal(3).validate(3);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("required fail", function() {
	var result = v().required().validate(undefined);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("required success", function() {
	var result = v().required().validate(3);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

//_____________________ format

describe("email fail", function() {
	var result = aright.rules.email.validate('abcdef');
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("email success", function() {
	var result = aright.rules.email.validate('john@doe.com');
	it("should", function() {
		expect(result).to.equals(true);
	});
});

//_______________________ OR

describe("or success", function() {
	var rule = v().or(v().isString(), v().isNumber()),
		result = rule.validate('john@doe.com') && rule.validate(1);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("or fail", function() {
	var rule = v().or(v().isString(), v().isNumber()),
		result = rule.validate([]);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});
//_______________________ NOT

describe("not success", function() {
	var rule = v().not(v().isString(), v().isNumber()),
		result = rule.validate([]) && rule.validate(true);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("not fail", function() {
	var rule = v().not(v().isString(), v().isNumber()),
		result = rule.validate('bloupi');
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

//_____________________ types

describe("type success", function() {
	var rule = v().type('string', 'number'),
		result = rule.validate('abc') && rule.validate(1);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("type fail", function() {
	var rule = v().type('string', 'number'),
		result = rule.validate([]) && rule.validate(false);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

// ____________________ full rules

describe("string + format + minLength", function() {
	var result = v().isString().format(/abc/).minLength(6).validate('abcdef'); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("full rule", function() {
	var rule = v()
		.isObject()
		.string('email', v().format('email').required(false))
		.number('index', v().equal(24))
		.bool('flag')
		.array('collection',
			v().item(
				v().isString()
			)
		)
		.object('child',
			v().string('title')
		)
		.bool('test');

	var result = rule.validate({
		email: 'aaa@bbb.com',
		index: 24,
		flag: true,
		collection: ['hello'],
		child: {
			title: 'hello'
		},
		test: true
	});

	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("full rule fail with report check", function() {
	var rule = v()
		.isObject()
		.string('email', v().format('email').required(false))
		.number('index', v().equal(24))
		.bool('flag')
		.array('collection',
			v().item(
				v().isString()
			)
		)
		.object('child',
			v().string('title')
		)
		.bool('test');

	var result = rule.validate({
		email: 'aaa@bbb',
		index: 1,
		flag: 'hello',
		collection: [1],
		child: null,
		test: 3
	});

	it("should", function() {
		expect(result).to.deep.equals({
			"valid": false,
			"map": {
				"email": {
					"value": "aaa@bbb",
					"errors": [
						"format failed"
					]
				},
				"index": {
					"value": 1,
					"errors": [
						"equality failed (should be : 24)"
					]
				},
				"flag": {
					"value": "hello",
					"errors": [
						"should be a boolean"
					]
				},
				"collection.0": {
					"value": 1,
					"errors": [
						"should be a string"
					]
				},
				"child.title": {
					"value": null,
					"errors": [
						"missing property"
					]
				},
				"test": {
					"value": 3,
					"errors": [
						"should be a boolean"
					]
				}
			},
			"value": {
				"email": "aaa@bbb",
				"index": 1,
				"flag": "hello",
				"collection": [
					1
				],
				"child": null,
				"test": 3
			}
		});
	});
});
<commit_msg>add tests for instanceof<commit_after>/**
 * @author Gilles Coomans <gilles.coomans@gmail.com>
 *
 */
if (typeof require !== 'undefined')
	var chai = require("./chai"),
		aright = require("../index"),
		v = aright.v;
else
	var v = aright.v;

var expect = chai.expect;

//________________ is* family

describe("is string success", function() {
	var result = v().isString().validate('abcdef'); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("is string fail", function() {
	var result = v().isString().validate(12); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("is number success", function() {
	var result = v().isNumber().validate(12); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("is number fail", function() {
	var result = v().isNumber().validate(true); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("is bool success", function() {
	var result = v().isBool().validate(true); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("is bool fail", function() {
	var result = v().isBool().validate(1); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("is func success", function() {
	var result = v().isFunction().validate(function() {}); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("is func fail", function() {
	var result = v().isFunction().validate(1); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("isArray success", function() {
	var result = v().isArray().validate([]); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("isArray fail", function() {
	var result = v().isArray().validate({}); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("isObject success", function() {
	var result = v().isObject().validate({}); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("isObject fail", function() {
	var result = v().isObject().validate(true); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

//________________ properties family

describe("string success", function() {
	var result = v().string('test').validate({ test: 'abcdef' }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("string fail", function() {
	var result = v().string('test').validate({ test: 1 }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("number success", function() {
	var result = v().number('test').validate({ test: 1 }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("number fail", function() {
	var result = v().number('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("bool success", function() {
	var result = v().bool('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("bool fail", function() {
	var result = v().bool('test').validate({ test: 1 }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("func success", function() {
	var result = v().func('test').validate({ test: function() {} }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("func fail", function() {
	var result = v().func('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("array success", function() {
	var result = v().array('test').validate({ test: [] }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("array fail", function() {
	var result = v().array('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("object success", function() {
	var result = v().object('test').validate({ test: {} }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("object fail", function() {
	var result = v().object('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("null success", function() {
	var result = v().null('test').validate({ test: null }); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});
describe("null fail", function() {
	var result = v().null('test').validate({ test: true }); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

//_______________________________ CONSTRAINTS

describe("enumerable fail", function() {
	var result = v().enumerable(['bloupi', 'foo']).validate('bloup');
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("enumerable success", function() {
	var result = v().enumerable(['bloupi', 'foo']).validate('bloupi');
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("max fail", function() {
	var result = v().maximum(3).validate(4);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("max success", function() {
	var result = v().maximum(3).validate(2);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("min fail", function() {
	var result = v().minimum(3).validate(2);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("min success", function() {
	var result = v().minimum(3).validate(4);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("maxLength fail", function() {
	var result = v().maxLength(3).validate('abcd');
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("maxLength success", function() {
	var result = v().maxLength(3).validate('ab');
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("minLength fail", function() {
	var result = v().minLength(3).validate('ab');
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("minLength success", function() {
	var result = v().minLength(3).validate('abc');
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("equal fail", function() {
	var result = v().equal(3).validate(2);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("equal success", function() {
	var result = v().equal(3).validate(3);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("required fail", function() {
	var result = v().required().validate(undefined);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("required success", function() {
	var result = v().required().validate(3);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

//_____________________ format

describe("email fail", function() {
	var result = aright.rules.email.validate('abcdef');
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("email success", function() {
	var result = aright.rules.email.validate('john@doe.com');
	it("should", function() {
		expect(result).to.equals(true);
	});
});

//_______________________ OR

describe("or success", function() {
	var rule = v().or(v().isString(), v().isNumber()),
		result = rule.validate('john@doe.com') && rule.validate(1);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("or fail", function() {
	var rule = v().or(v().isString(), v().isNumber()),
		result = rule.validate([]);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});
//_______________________ NOT

describe("not success", function() {
	var rule = v().not(v().isString(), v().isNumber()),
		result = rule.validate([]) && rule.validate(true);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("not fail", function() {
	var rule = v().not(v().isString(), v().isNumber()),
		result = rule.validate('bloupi');
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

//_____________________ types

describe("type success", function() {
	var rule = v().type('string', 'number'),
		result = rule.validate('abc') && rule.validate(1);
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("type fail", function() {
	var rule = v().type('string', 'number'),
		result = rule.validate([]) && rule.validate(false);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

//_____________________ types

describe("instanceOf success", function() {
	var rule = v().instanceOf(Date),
		result = rule.validate(new Date());
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("instanceOf fail", function() {
	var rule = v().instanceOf(Date),
		result = rule.validate(true);
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

// ____________________ full rules

describe("string + format + minLength", function() {
	var result = v().isString().format(/abc/).minLength(6).validate('abcdef'); // return true
	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("string + format + maxLength fail", function() {
	var result = v().isString().format(/abc/).maxLength(6).validate('abcdefgh'); // return true
	it("should", function() {
		expect(result).to.not.equals(true);
	});
});

describe("full rule", function() {
	var rule = v()
		.isObject()
		.string('email', v().format('email').required(false))
		.number('index', v().equal(24))
		.bool('flag')
		.array('collection',
			v().item(
				v().isString()
			)
		)
		.object('child',
			v().string('title')
		)
		.bool('test');

	var result = rule.validate({
		email: 'aaa@bbb.com',
		index: 24,
		flag: true,
		collection: ['hello'],
		child: {
			title: 'hello'
		},
		test: true
	});

	it("should", function() {
		expect(result).to.equals(true);
	});
});

describe("full rule fail with report check", function() {
	var rule = v()
		.isObject()
		.string('email', v().format('email').required(false))
		.number('index', v().equal(24))
		.bool('flag')
		.array('collection',
			v().item(
				v().isString()
			)
		)
		.object('child',
			v().string('title')
		)
		.bool('test');

	var result = rule.validate({
		email: 'aaa@bbb',
		index: 1,
		flag: 'hello',
		collection: [1],
		child: null,
		test: 3
	});

	it("should", function() {
		expect(result).to.deep.equals({
			"valid": false,
			"map": {
				"email": {
					"value": "aaa@bbb",
					"errors": [
						"format failed"
					]
				},
				"index": {
					"value": 1,
					"errors": [
						"equality failed (should be : 24)"
					]
				},
				"flag": {
					"value": "hello",
					"errors": [
						"should be a boolean"
					]
				},
				"collection.0": {
					"value": 1,
					"errors": [
						"should be a string"
					]
				},
				"child.title": {
					"value": null,
					"errors": [
						"missing property"
					]
				},
				"test": {
					"value": 3,
					"errors": [
						"should be a boolean"
					]
				}
			},
			"value": {
				"email": "aaa@bbb",
				"index": 1,
				"flag": "hello",
				"collection": [
					1
				],
				"child": null,
				"test": 3
			}
		});
	});
});
<|endoftext|>


<commit_before>/**
 * Tests that two readable streams are equal.
 *
 * @param (ReadableStream) readStream2
 * @param (ReadableStream) readStream2
 * @param (Function(!Error, boolean)) callback
 */
module.exports = function streamEqual(readStream1, readStream2, callback) {
  var stream1 = { stream: readStream1, data: null, pos: 0, ended: false };
  var stream2 = { stream: readStream2, data: null, pos: 0, ended: false };
  var ondata1 = createOnData(stream1, stream2, cleanup);
  var ondata2 = createOnData(stream2, stream1, cleanup);
  var onend1 = createOnEnd(stream1, stream2, cleanup);
  var onend2 = createOnEnd(stream2, stream1, cleanup);

  function cleanup(err, equal) {
    readStream1.removeListener('data', ondata1);
    readStream1.removeListener('error', cleanup);
    readStream1.removeListener('end', onend1);

    readStream2.removeListener('data', ondata2);
    readStream2.removeListener('error', cleanup);
    readStream2.removeListener('end', onend2);

    callback(err, equal);
  }

  readStream1.on('data', ondata1);
  readStream1.on('end', onend1);
  readStream1.on('error', cleanup);

  readStream2.on('data', ondata2);
  readStream2.on('end', onend2);
  readStream2.on('error', cleanup);
};

/**
 * Returns a function that compares emitted `data` event with that of the
 * most recent `data` event from another stream.
 *
 * @param (Object) stream
 * @param (Object) otherStream
 * @param (Function(Error, boolean)) callback
 * @return (Function(Buffer|string))
 */
function createOnData(stream, otherStream, callback) {
  return function ondata(data) {
    // make sure `data` is a  buffer
    if (!Buffer.isBuffer(data)) {
      var type = typeof data;
      if (type === 'string') {
        data = new Buffer(data);
      } else if (type === 'object') {
        data = JSON.stringify(data);
      } else {
        data = new Buffer(data.toString());
      }
    }

    var newPos = stream.pos + data.length;

    if (stream.pos < otherStream.pos) {
      var minLength = Math.min(data.length, otherStream.data.length);

      var streamData = data.slice(0, minLength);
      stream.data = data.slice(minLength);

      var otherStreamData = otherStream.data.slice(0, minLength);
      otherStream.data = otherStream.data.slice(minLength);

      // compare
      for (var i = 0, len = streamData.length; i < len; i++) {
        if (streamData[i] !== otherStreamData[i]) {
          return callback(null, false);
        }
      }

    } else if (stream.data && stream.data.length) {
      stream.data = bufferConcat(stream.data, data);
    } else {
      stream.data = data;
    }

    if (newPos > otherStream.pos) {
      if (otherStream.ended) {
        // if this stream is still emitting `data` events but the other has
        // ended, then this is longer than the other one
        return callback(null, false);
      }

      // if this stream has caught up to the other,
      // pause it and resume the other one
      stream.stream.pause();
      otherStream.stream.resume();
    }

    stream.pos = newPos;
  };
}

/**
 * Creates a function that gets called when a stream ends.
 *
 * @param (Object) stream
 * @param (Object) otherStream
 * @param (Function(Error, boolean)) callback
 */
function createOnEnd(stream, otherStream, callback) {
  return function onend() {
    stream.ended = true;
    if (otherStream.ended) {
      callback(null, stream.pos === otherStream.pos);
    } else {
      otherStream.stream.resume();
    }
  };
}

/**
 * Concatenate two buffers. Because 0.6.x...
 *
 * @param (Buffer) buf1
 * @param (Buffer) buf2
 * @return (Buffer)
 */
function bufferConcat(buf1, buf2) {
  var newBuf = new Buffer(buf1.length + buf2.length);
  buf1.copy(newBuf);
  buf2.copy(newBuf, buf1.length);
  return newBuf;
}
<commit_msg>Release v0.1.2<commit_after>/**
 * Tests that two readable streams are equal.
 *
 * @param (ReadableStream) readStream2
 * @param (ReadableStream) readStream2
 * @param (Function(!Error, boolean)) callback
 */
module.exports = function streamEqual(readStream1, readStream2, callback) {
  var stream1 = { stream: readStream1, data: null, pos: 0, ended: false };
  var stream2 = { stream: readStream2, data: null, pos: 0, ended: false };
  var ondata1 = createOnData(stream1, stream2, cleanup);
  var ondata2 = createOnData(stream2, stream1, cleanup);
  var onend1 = createOnEnd(stream1, stream2, cleanup);
  var onend2 = createOnEnd(stream2, stream1, cleanup);

  function cleanup(err, equal) {
    readStream1.removeListener('data', ondata1);
    readStream1.removeListener('error', cleanup);
    readStream1.removeListener('end', onend1);

    readStream2.removeListener('data', ondata2);
    readStream2.removeListener('error', cleanup);
    readStream2.removeListener('end', onend2);

    callback(err, equal);
  }

  readStream1.on('data', ondata1);
  readStream1.on('end', onend1);
  readStream1.on('error', cleanup);

  readStream2.on('data', ondata2);
  readStream2.on('end', onend2);
  readStream2.on('error', cleanup);

  // resume streams in order to invoke their old API
  readStream1.resume();
  readStream2.resume();
};

/**
 * Returns a function that compares emitted `data` event with that of the
 * most recent `data` event from another stream.
 *
 * @param (Object) stream
 * @param (Object) otherStream
 * @param (Function(Error, boolean)) callback
 * @return (Function(Buffer|string))
 */
function createOnData(stream, otherStream, callback) {
  return function ondata(data) {
    // make sure `data` is a  buffer
    if (!Buffer.isBuffer(data)) {
      var type = typeof data;
      if (type === 'string') {
        data = new Buffer(data);
      } else if (type === 'object') {
        data = JSON.stringify(data);
      } else {
        data = new Buffer(data.toString());
      }
    }

    var newPos = stream.pos + data.length;

    if (stream.pos < otherStream.pos) {
      var minLength = Math.min(data.length, otherStream.data.length);

      var streamData = data.slice(0, minLength);
      stream.data = data.slice(minLength);

      var otherStreamData = otherStream.data.slice(0, minLength);
      otherStream.data = otherStream.data.slice(minLength);

      // compare
      for (var i = 0, len = streamData.length; i < len; i++) {
        if (streamData[i] !== otherStreamData[i]) {
          return callback(null, false);
        }
      }

    } else if (stream.data && stream.data.length) {
      stream.data = bufferConcat(stream.data, data);
    } else {
      stream.data = data;
    }

    if (newPos > otherStream.pos) {
      if (otherStream.ended) {
        // if this stream is still emitting `data` events but the other has
        // ended, then this is longer than the other one
        return callback(null, false);
      }

      // if this stream has caught up to the other,
      // pause it and resume the other one
      stream.stream.pause();
      otherStream.stream.resume();
    }

    stream.pos = newPos;
  };
}

/**
 * Creates a function that gets called when a stream ends.
 *
 * @param (Object) stream
 * @param (Object) otherStream
 * @param (Function(Error, boolean)) callback
 */
function createOnEnd(stream, otherStream, callback) {
  return function onend() {
    stream.ended = true;
    if (otherStream.ended) {
      callback(null, stream.pos === otherStream.pos);
    } else {
      otherStream.stream.resume();
    }
  };
}

/**
 * Concatenate two buffers. Because 0.6.x...
 *
 * @param (Buffer) buf1
 * @param (Buffer) buf2
 * @return (Buffer)
 */
function bufferConcat(buf1, buf2) {
  var newBuf = new Buffer(buf1.length + buf2.length);
  buf1.copy(newBuf);
  buf2.copy(newBuf, buf1.length);
  return newBuf;
}
<|endoftext|>


<commit_before>var
	cheerio = require('cheerio'),
	request = require('./request'),

	baseHost = '.craigslist.org',
	defaultRequestOptions = {
		hostname : '',
		path : '',
	    secure : true
	},
	searchMaxAsk = '&maxAsk=',
	searchMinAsk = '&minAsk=';
	// searchPath = '/search/sss?sort=rel&query=';

    var initialize = (function (self) {
	'use strict';

	self = self || {};
	self.options = {};
	/*
		Accepts string of HTML and parses that string to find all pertinent listings.
	*/
	function getRssListings (options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

        var category = $('channel').find('title').text()

		$('item').each(function (i, element) {
			var children = $(element).children()

            var url = $(element).attr('rdf:about')
			var idx = url.lastIndexOf('/')
			var idx1 = url.indexOf('.', idx)
			var listing = { category : category, url: url, pid: url.substring(idx + 1, idx1) }

			for (var i=0; i<children.length; i++) {
				var t, child = children[i]
				var name = child.name

				if (name == 'title') {
					t = child.children[0].data.trim()
					if (t.indexOf('[CDATA[') != -1)
						t = t.substring(7, t.length - 2).trim().replace(/^\&\#x0024\;/g, '').replace(/&\#x0024\;/g, '$')

					var priceIdx = t.indexOf('$')
					if (priceIdx != -1)  {
						var sidx = t.indexOf(' ', priceIdx)
						listing.price = sidx == -1 ? t.substring(priceIdx) : t.substring(priceIdx, sidx)
					}
					var idx = t.lastIndexOf('(')
				    if (idx == -1)
				    	listing.title = t
				    else {
				    	var idx1 = t.indexOf(')', idx)
				    	listing.location = t.substring(idx + 1, idx1).trim()
				    	listing.title = t.substring(0, idx).trim()
				    }

				}
				else if (name == 'description') {
					t = child.children[0].data.trim()
					if (t.indexOf('[CDATA[') != -1)
						t = t.substring(7, t.length - 2)
					listing.description = t.replace(/&\#x0024\;/g, '$').trim()
				}
				else if (name == 'dc:date')
					listing.date = $(child).text().trim()
				else if (name == 'enc:enclosure') {
					listing.hasPic = true
					listing.img = $(child).attr('resource')
				}
			}

					// coordinates : {
					// 	lat : $(element).attr('data-latitude'),
					// 	lon : $(element).attr('data-longitude')
					// },

				// make sure lat / lon is valid
				// if (typeof listing.coordinates.lat === 'undefined' ||
				// 	typeof listing.coordinates.lon === 'undefined') {
				// 	delete listing.coordinates;
				// }
			listings.push(listing);
		});

		return listings;
	}

	/*
		Accepts string of HTML and parses that string to find all pertinent listings.
	*/
	function getListings (options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

		$('div.content').find('p.row').each(function (i, element) {
			listing = {
				category : $(element)
					.find('span.l2 a.gc')
					.text(),
				coordinates : {
					lat : $(element).attr('data-latitude'),
					lon : $(element).attr('data-longitude')
				},
				date : $(element)
					.find('span.date')
					.text(),
				hasPic : ($(element)
					.find('span.l2 span.p')
					.text()
					.trim()) !== '',
				location : $(element)
					.find('span.pnr small')
					.text()
					.replace(/[\(,\)]/g, '').trim(), // santize
				pid : $(element)
					.attr('data-pid'),
				price : $(element)
					.find('span.l2 span.price')
					.text()
					.replace(/^\&\#x0024\;/g, ''), // sanitize
				title : $(element)
					.find('span.pl a')
					.text().trim()
				// url :
				// 	(options.secure ? 'https://' : 'http://') +
				// 	options.hostname + $(element).find('span.pl a').attr('href')
			}
			var rurl = $(element).find('span.pl a').attr('href');
			if (rurl) {
			  if (rurl.indexOf('http') == -1)
			    rurl = (options.secure ? 'https://' : 'http://') + options.hostname + rurl
			  listing.url = rurl;
		    }
		    if (options.cityName)
		      listing.cityName = options.cityName

			if (listing.hasPic) {
				var dId = $(element).find('a.i').attr('data-id')
				if (dId)
				  listing.pic = 'http://images.craigslist.org/' + dId.substring(2) + '_300x300.jpg'
			}
			var d = $(element).find('span.date')
			if (!d  ||  d.text().length == 0) {
				d = $(element).find('time').attr('datetime')
				listing.date = d;
			}

			// make sure lat / lon is valid
			if (typeof listing.coordinates.lat === 'undefined' ||
				typeof listing.coordinates.lon === 'undefined') {
				delete listing.coordinates;
			}

			listings.push(listing);
		});

		return listings;
	}

	function getCities(options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

		$('div.colmask').find('a').each(function (i, element) {
           var href = $(element).attr('href')
           var idx = href.indexOf('//'), idx1 = href.indexOf('.');

           listing = { city: href.substring(idx + 2, idx1),
           			   href: href,
                       cityName: $(element).text().replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()})
                     }

           listings.push(listing)
		})
		return listings
	}

	function getDescription(options, html) {
		var
			$ = cheerio.load(html);

		var description = $('section.postingbody').text()
		return {description: description}
	}
	/*
		Accepts options, iterates through the known acceptable keys from defaultOptions
		and if found in input options, uses that. If not found in input options to method,
		falls back to the options specified when the module was initialized. If not found
		in initialization options, uses the default options setting. All keys provided in
		the input options variable are retained.
	*/
	function getRequestOptions (options, query) {
		var requestOptions = JSON.parse(JSON.stringify(defaultRequestOptions));
		if (options.fullListing  &&  options.path) {
			requestOptions.path = options.path
			requestOptions.hostname = options.hostname
			return requestOptions
		}

		// ensure default options are set, even if omitted from input options
		requestOptions.hostname = options.allCities? 'www' + baseHost : (options.city || self.options.city || '') + baseHost;

		// preserve any extraneous input option keys (may have addition instructions for underlying request object)
		Object.keys(options).forEach(function (key) {
			if (
				// key !== 'maxAsk'   &&
				// key !== 'minAsk'   &&
				// key !== 'category' &&
				// key !== 'hasPic'   &&
				// key !== 's'        &&
				typeof requestOptions[key] === 'undefined' &&
				typeof defaultRequestOptions[key] === 'undefined') {
				requestOptions[key] = options[key];
			}
		});

		// set path
		if (options.allCities)
		  requestOptions.path = '/about/sites'
		else
		  requestOptions.path = '/search/' + (options.category ? options.category : 'sss') + '?sort=rel' +
  		                                     (options.s ? '&s=' + options.s : '') +
		                                     (options.hasPic && (options.hasPic == '1' || options.hasPic == 'true') ? '&hasPic=1' : '') +
		                                     (options.rss ? '&format=rss' : '') +
		                                     (options.query ? '&query=' + encodeURIComponent(options.query) : '');

		// add min and max asking price
		if (typeof options.minAsk !== 'undefined') {
			requestOptions.path += searchMinAsk + options.minAsk;
		}

		if (typeof options.maxAsk !== 'undefined') {
			requestOptions.path += searchMaxAsk + options.maxAsk;
		}

		return requestOptions;
	}

	/*
		options = {
			city : '',
			maxAsk : '',
			minAsk : '',
		}
	*/
	self.search = function (options, query, callback) {
		if (typeof query === 'function' && typeof callback === 'undefined') {
			callback = query;
			query = options;
			options = {};
		}

		options = getRequestOptions(options, query);

		self.request.get(options, function (err, data) {
			if (err)
			  return callback(err)

            if (options.allCities)
              return callback(null, getCities(options, data))
            if (options.rss)
              return callback(null, getRssListings(options, data))
            if (options.fullListing)
              return callback(null, getDescription(options, data))

			return callback(null, getListings(options, data))
		});
	};

	/*
		the module
	*/
	return function (options) {
		options = options || {};

		self.options = options;
		self.request = request.initialize();
		return self;
	};
}({}));

exports = module.exports = initialize;
exports.initialize = initialize;
<commit_msg>get listing with full description<commit_after>var
	cheerio = require('cheerio'),
	request = require('./request'),

	baseHost = '.craigslist.org',
	defaultRequestOptions = {
		hostname : '',
		path : '',
	    secure : true
	},
	searchMaxAsk = '&maxAsk=',
	searchMinAsk = '&minAsk=';
	// searchPath = '/search/sss?sort=rel&query=';

    var initialize = (function (self) {
	'use strict';

	self = self || {};
	self.options = {};
	/*
		Accepts string of HTML and parses that string to find all pertinent listings.
	*/
	function getRssListings (options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

        var category = $('channel').find('title').text()

		$('item').each(function (i, element) {
			var children = $(element).children()

            var url = $(element).attr('rdf:about')
			var idx = url.lastIndexOf('/')
			var idx1 = url.indexOf('.', idx)
			var listing = { category : category, url: url, pid: url.substring(idx + 1, idx1) }

			for (var i=0; i<children.length; i++) {
				var t, child = children[i]
				var name = child.name

				if (name == 'title') {
					t = child.children[0].data.trim()
					if (t.indexOf('[CDATA[') != -1)
						t = t.substring(7, t.length - 2).trim().replace(/^\&\#x0024\;/g, '').replace(/&\#x0024\;/g, '$')

					var priceIdx = t.indexOf('$')
					if (priceIdx != -1)  {
						var sidx = t.indexOf(' ', priceIdx)
						listing.price = sidx == -1 ? t.substring(priceIdx) : t.substring(priceIdx, sidx)
					}
					var idx = t.lastIndexOf('(')
				    if (idx == -1)
				    	listing.title = t
				    else {
				    	var idx1 = t.indexOf(')', idx)
				    	listing.location = t.substring(idx + 1, idx1).trim()
				    	listing.title = t.substring(0, idx).trim()
				    }

				}
				else if (name == 'description') {
					t = child.children[0].data.trim()
					if (t.indexOf('[CDATA[') != -1)
						t = t.substring(7, t.length - 2)
					listing.description = t.replace(/&\#x0024\;/g, '$').trim()
				}
				else if (name == 'dc:date')
					listing.date = $(child).text().trim()
				else if (name == 'enc:enclosure') {
					listing.hasPic = true
					listing.img = $(child).attr('resource')
				}
			}

					// coordinates : {
					// 	lat : $(element).attr('data-latitude'),
					// 	lon : $(element).attr('data-longitude')
					// },

				// make sure lat / lon is valid
				// if (typeof listing.coordinates.lat === 'undefined' ||
				// 	typeof listing.coordinates.lon === 'undefined') {
				// 	delete listing.coordinates;
				// }
			listings.push(listing);
		});

		return listings;
	}

	/*
		Accepts string of HTML and parses that string to find all pertinent listings.
	*/
	function getListings (options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

		$('div.content').find('p.row').each(function (i, element) {
			listing = {
				category : $(element)
					.find('span.l2 a.gc')
					.text(),
				coordinates : {
					lat : $(element).attr('data-latitude'),
					lon : $(element).attr('data-longitude')
				},
				date : $(element)
					.find('span.date')
					.text(),
				hasPic : ($(element)
					.find('span.l2 span.p')
					.text()
					.trim()) !== '',
				location : $(element)
					.find('span.pnr small')
					.text()
					.replace(/[\(,\)]/g, '').trim(), // santize
				pid : $(element)
					.attr('data-pid'),
				price : $(element)
					.find('span.l2 span.price')
					.text()
					.replace(/^\&\#x0024\;/g, ''), // sanitize
				title : $(element)
					.find('span.pl a')
					.text().trim()
				// url :
				// 	(options.secure ? 'https://' : 'http://') +
				// 	options.hostname + $(element).find('span.pl a').attr('href')
			}
			var rurl = $(element).find('span.pl a').attr('href');
			if (rurl) {
			  if (rurl.indexOf('http') == -1)
			    rurl = (options.secure ? 'https://' : 'http://') + options.hostname + rurl
			  listing.url = rurl;
		    }
		    if (options.cityName)
		      listing.cityName = options.cityName

			if (listing.hasPic) {
				var dId = $(element).find('a.i').attr('data-id')
				if (dId)
				  listing.pic = 'http://images.craigslist.org/' + dId.substring(2) + '_300x300.jpg'
			}
			var d = $(element).find('span.date')
			if (!d  ||  d.text().length == 0) {
				d = $(element).find('time').attr('datetime')
				listing.date = d;
			}

			// make sure lat / lon is valid
			if (typeof listing.coordinates.lat === 'undefined' ||
				typeof listing.coordinates.lon === 'undefined') {
				delete listing.coordinates;
			}

			listings.push(listing);
		});

		return listings;
	}

	function getCities(options, html) {
		var
			$ = cheerio.load(html),
			listing = {},
			listings = [];

		$('div.colmask').find('a').each(function (i, element) {
           var href = $(element).attr('href')
           var idx = href.indexOf('//'), idx1 = href.indexOf('.');

           listing = { city: href.substring(idx + 2, idx1),
           			   href: href,
                       cityName: $(element).text().replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()})
                     }

           listings.push(listing)
		})
		return listings
	}

	function getDescription(options, html) {
		var
			$ = cheerio.load(html);

		var description = $('section.postingbody').text()
		return {description: description}
	}
	/*
		Accepts options, iterates through the known acceptable keys from defaultOptions
		and if found in input options, uses that. If not found in input options to method,
		falls back to the options specified when the module was initialized. If not found
		in initialization options, uses the default options setting. All keys provided in
		the input options variable are retained.
	*/
	function getRequestOptions (options, query) {
		var requestOptions = JSON.parse(JSON.stringify(defaultRequestOptions));
		if (options.fullListing  &&  options.path) {
			requestOptions.path = options.path
			requestOptions.hostname = options.hostname
			return requestOptions
		}

		// ensure default options are set, even if omitted from input options
		requestOptions.hostname = options.allCities? 'www' + baseHost : (options.city || self.options.city || '') + baseHost;

		// preserve any extraneous input option keys (may have addition instructions for underlying request object)
		Object.keys(options).forEach(function (key) {
			if (
				// key !== 'maxAsk'   &&
				// key !== 'minAsk'   &&
				// key !== 'category' &&
				// key !== 'hasPic'   &&
				// key !== 's'        &&
				typeof requestOptions[key] === 'undefined' &&
				typeof defaultRequestOptions[key] === 'undefined') {
				requestOptions[key] = options[key];
			}
		});

		// set path
		if (options.allCities)
		  requestOptions.path = '/about/sites'
		else
		  requestOptions.path = '/search/' + (options.category ? options.category : 'sss') + '?sort=rel' +
  		                                     (options.s ? '&s=' + options.s : '') +
		                                     (options.hasPic && (options.hasPic == '1' || options.hasPic == 'true') ? '&hasPic=1' : '') +
		                                     (options.rss ? '&format=rss' : '') +
		                                     (options.query ? '&query=' + encodeURIComponent(options.query) : '');

		// add min and max asking price
		if (typeof options.minAsk !== 'undefined') {
			requestOptions.path += searchMinAsk + options.minAsk;
		}

		if (typeof options.maxAsk !== 'undefined') {
			requestOptions.path += searchMaxAsk + options.maxAsk;
		}

		return requestOptions;
	}

	/*
		options = {
			city : '',
			maxAsk : '',
			minAsk : '',
		}
	*/
	self.search = function (options, query, callback) {
		if (typeof query === 'function' && typeof callback === 'undefined') {
			callback = query;
			query = options;
			options = {};
		}

        var isFullDescriptionRequest = options.fullListing  &&  options.path
		options = getRequestOptions(options, query);

		self.request.get(options, function (err, data) {
			if (err)
			  return callback(err)

            if (options.allCities)
              return callback(null, getCities(options, data))
            if (options.rss)
              return callback(null, getRssListings(options, data))
            if (isFullDescriptionRequest)
              return callback(null, getDescription(options, data))

			return callback(null, getListings(options, data))
		});
	};

	/*
		the module
	*/
	return function (options) {
		options = options || {};

		self.options = options;
		self.request = request.initialize();
		return self;
	};
}({}));

exports = module.exports = initialize;
exports.initialize = initialize;
<|endoftext|>


<commit_before>'use strict';

var assert = require('chai').assert,
    ST = require('../subtask.js');

describe('subtask', function () {
    it('should be a function', function (done) {
        assert('function', typeof ST);
        done();
    });
});
<commit_msg>more tests<commit_after>'use strict';

var assert = require('chai').assert,
    ST = require('../subtask.js');

describe('subtask', function () {
    it('should be a function', function (done) {
        assert('function', typeof ST);
        done();
    });

    it('should be an instance with execute()', function (done) {
        assert('function', typeof (new ST()).execute);
        done();
    });
});
<|endoftext|>


<commit_before>var schema = require('../').schema,
    schema_sync = require('../').schema_sync;

var assert = require('assert');

describe('mongodb-schema', function() {
    it('should import correctly', function () {
        assert.ok(schema);
        assert.ok(schema_sync);
    });
    describe ('schema_sync', function() {

        it('should work with a simple document', function () {
            var result = schema_sync([{a: 1, b: 1}]);
            var expected = {
                "$count":1,
                "a": {
                    "$count": 1,
                    "$type": "number",
                    "$prob": 1
                },
                "b": {
                    "$count": 1,
                    "$type": "number",
                    "$prob": 1
                }
            };
            assert.deepEqual(result, expected);
        });

        it('should work with an empty list of documents', function () {
            var result = schema_sync([]);
            assert.deepEqual(result, {});
        });

        it('should correctly parse nested documents', function () {
            var result = schema_sync([
                {a: {b: 1}},
                {a: {b: {c: 2}}},
            ]);

            var expected = {
                "$count": 2,
                "a": {
                    "$count": 2,
                    "b": {
                        "$count": 2,
                        "$type": {
                            "number": 1,
                            "object": 1
                        },
                        "c": {
                            "$count": 1,
                            "$type": "number",
                            "$prob": 0.5
                        },
                        "$prob": 1
                    },
                    "$prob": 1
                }
            };

            assert.deepEqual(result, expected);
        });

        it('should collapse arrays, set the $array flag and increase $count numbers', function () {
            var result = schema_sync([
                {a: [1, 2, 3, 4]},
                {a: [5, 6]}
            ]);

            var expected = {
                "$count": 2,
                "a": {
                    "$count": 6,
                    "$type": "number",
                    "$array": true,
                    "$prob": 3
                }
            };

            assert.deepEqual(result, expected);
        });

        it('should accept an existing schema and merge with new data', function () {
            var result = schema_sync([
                {a: 1}
            ], {data: true});

            result = schema_sync([
                {a: 2}
            ], {data: true, merge: result});

            assert.equal(result['$count'], 2);
            assert.equal(result.a['$count'], 2);
            assert.deepEqual(result.a['$data'], {"min": 1, "max": 2});

        });

        it('should flatten the schema with the {flat: true} option', function () {
            var result = schema_sync([
                {a: {b: 1}},
                {a: 2}
            ], {flat: true});

            assert(result['a'] !== undefined);
            assert(result['a.b'] !== undefined);
            assert(result['a']['b'] === undefined);
        });

        var docs = [
            {
                a: "foo",
                b: [1, 2, 3],
                c: true,
                d: new Date(2014, 1, 1),
                e: null,
                f: "foo"
            },
            {
                a: "bar",
                b: 2,
                c: false,
                d: new Date(2013, 1, 1),
                e: null,
                f: "foo"
            },
            {
                a: "baz",
                b: 8,
                c: false,
                d: new Date(2012, 1, 1),
                e: null,
                f: "bar"
            }
        ];

        it('should infer types correctly (not testing ObjectId currently)', function () {
            var result = schema_sync(docs);

            assert.equal(result.a['$type'], 'string');
            assert.equal(result.b['$type'], 'number');
            assert.equal(result.c['$type'], 'boolean');
            assert.equal(result.d['$type'], 'date');
            assert.equal(result.e['$type'], 'null');
            assert.equal(result.f['$type'], 'string');
        });

        it('should distinguish `text` and `category` types when using {data: true}', function () {
            var result = schema_sync(docs, {data: true});

            assert.equal(result.a['$type'], 'text');
            assert.equal(result.b['$type'], 'number');
            assert.equal(result.c['$type'], 'boolean');
            assert.equal(result.d['$type'], 'date');
            assert.equal(result.e['$type'], 'null');
            assert.equal(result.f['$type'], 'category');
        });

        it('should calculate bounds for date/number and histograms for category', function () {
            var result = schema_sync(docs, {data: true});

            assert.ok( !('$data' in result.a) );
            assert.ok( !('$data' in result.c) );
            assert.ok( !('$data' in result.e) );

            assert.deepEqual(result.b['$data'], {
                min: 1,
                max: 8
            });
            assert.deepEqual(result.d['$data'], {
                min: new Date(2012, 1, 1),
                max: new Date(2014, 1, 1)
            });
            assert.deepEqual(result.f['$data'], {
                foo: 2,
                bar: 1
            });
        });

        it('should track $data for arrays', function () {
            var result = schema_sync([
                {a: ["foo", "foo", "bar"]},
                {a: ["bar", "baz", "foo"]}
            ], {data: true});

            var expected = {
                "foo": 3,
                "bar": 2,
                "baz": 1
            }

            assert.deepEqual(result.a['$data'], expected);
        });

        it('should not infer data for mixed numbers and dates', function () {
            var result = schema_sync([
                {a: 1},
                {a: 5},
                {a: new Date(2014, 08, 20)}
            ], {data: true});

            assert.ok( !('$data' in result.a) );
        });

        it('should handle mixed types for arrays', function () {
            var result = schema_sync([
                {a: ["foo", 1, "bar"]},
                {a: ["bar", null, new Date(2014, 1, 1)]}
            ], {data: true});

            var expected = {
                "string": 3,
                "number": 1,
                "date": 1,
                "null": 1
            };

            assert.deepEqual(result.a['$type'], expected);
        });

        it('should count sub-fields correctly', function () {
            var result = schema_sync([
                {a: 1, b: 1},
                {a: 1, c: 1},
                {a: 1, b: 1}
            ]);

            assert.equal(result['$count'], 3);
            assert.equal(result.a['$count'], 3);
            assert.equal(result.b['$count'], 2);
            assert.equal(result.c['$count'], 1);
        });

        it('should return a type histogram for mixed types', function () {
            var result = schema_sync([
                {a: 1}, {a: "1"}, {a: null}, {a: "1"}, {a: {b: 1}}, {a: 1}, {a: "1"}
            ]);

            var expected = {
                "number": 2,
                "string": 3,
                "null": 1,
                "object": 1
            };
            assert.deepEqual(result.a['$type'], expected);
        });

        it('should let you change the meta-variable names', function () {
            var result = schema_sync([
                {a: 1},
                {a: [-2, -3]}
            ], {
                data: true,
                metavars: {
                    count: '#count',
                    type: '#type',
                    data: '#data',
                    array: '#array',
                    prob: '#prob'
                }
            });

            var expected = {
                "#count": 3,
                "#type": "number",
                "#data": {
                    "min": -3,
                    "max": 1
                },
                "#array": true,
                "#prob": 1.5
            };

            assert.deepEqual(result.a, expected);
        });

        it('should collect categories in $other when maxCardinality is reached', function () {
            var result = schema_sync([
                {a: "a"}, {a: "a"}, {a: "b"}, {a: "c"}, {a: "d"}, {a: "e"}, {a: "f"}
            ], {data: {maxCardinality: 3}});

            assert.ok('$other' in result.a['$data']);
        });

    });

    describe ('schema', function() {
        it('should work asynchronously and not return an error', function (done) {
            schema([{a:1, b:1}, {a:2, b:2}], {}, function (err, result) {
                if (err) throw err;
                assert.equal(err, null);
                assert.equal(result['$count'], 2);
                done();
            });
        });

        it('should return an error if `documents` is not an array', function (done) {
            schema(1, {}, function (err, result) {
                assert.ok(err);
                done();
            });
        });
    });
});
<commit_msg>added test for collapse+data.<commit_after>var schema = require('../').schema,
    schema_sync = require('../').schema_sync;

var assert = require('assert');

describe('mongodb-schema', function() {
    it('should import correctly', function () {
        assert.ok(schema);
        assert.ok(schema_sync);
    });
    describe ('schema_sync', function() {

        it('should work with a simple document', function () {
            var result = schema_sync([{a: 1, b: 1}]);
            var expected = {
                "$count":1,
                "a": {
                    "$count": 1,
                    "$type": "number",
                    "$prob": 1
                },
                "b": {
                    "$count": 1,
                    "$type": "number",
                    "$prob": 1
                }
            };
            assert.deepEqual(result, expected);
        });

        it('should work with an empty list of documents', function () {
            var result = schema_sync([]);
            assert.deepEqual(result, {});
        });

        it('should correctly parse nested documents', function () {
            var result = schema_sync([
                {a: {b: 1}},
                {a: {b: {c: 2}}},
            ]);

            var expected = {
                "$count": 2,
                "a": {
                    "$count": 2,
                    "b": {
                        "$count": 2,
                        "$type": {
                            "number": 1,
                            "object": 1
                        },
                        "c": {
                            "$count": 1,
                            "$type": "number",
                            "$prob": 0.5
                        },
                        "$prob": 1
                    },
                    "$prob": 1
                }
            };

            assert.deepEqual(result, expected);
        });

        it('should collapse arrays, set the $array flag and increase $count numbers', function () {
            var result = schema_sync([
                {a: [1, 2, 3, 4]},
                {a: [5, 6]}
            ]);

            var expected = {
                "$count": 2,
                "a": {
                    "$count": 6,
                    "$type": "number",
                    "$array": true,
                    "$prob": 3
                }
            };

            assert.deepEqual(result, expected);
        });

        it('should infer data for collapsed arrays', function () {
            var result = schema_sync([
                {a: [1, 2, 3, 4]},
                {a: [5, 6]}
            ], {data: true});

            var expected = {
                min: 1,
                max: 6
            };

            assert.deepEqual(result.a['$data'], expected);
        });

        it('should accept an existing schema and merge with new data', function () {
            var result = schema_sync([
                {a: 1}
            ], {data: true});

            result = schema_sync([
                {a: 2}
            ], {data: true, merge: result});

            assert.equal(result['$count'], 2);
            assert.equal(result.a['$count'], 2);
            assert.deepEqual(result.a['$data'], {"min": 1, "max": 2});

        });

        it('should merge existing text/category data correctly with new strings', function() {
            var result = schema_sync([
                {a: "foo"}, {a: "foo"}, {a: "bar"}, {a: "bar"}
            ], {data: true});

            result = schema_sync([
                {a: "foo"}, {a: "bar"}
            ], {data: true, merge: result});

            assert.deepEqual(result.a['$type'], "category");

        });

        it('should flatten the schema with the {flat: true} option', function () {
            var result = schema_sync([
                {a: {b: 1}},
                {a: 2}
            ], {flat: true});

            assert(result['a'] !== undefined);
            assert(result['a.b'] !== undefined);
            assert(result['a']['b'] === undefined);
        });

        var docs = [
            {
                a: "foo",
                b: [1, 2, 3],
                c: true,
                d: new Date(2014, 1, 1),
                e: null,
                f: "foo"
            },
            {
                a: "bar",
                b: 2,
                c: false,
                d: new Date(2013, 1, 1),
                e: null,
                f: "foo"
            },
            {
                a: "baz",
                b: 8,
                c: false,
                d: new Date(2012, 1, 1),
                e: null,
                f: "bar"
            }
        ];

        it('should infer types correctly (not testing ObjectId currently)', function () {
            var result = schema_sync(docs);

            assert.equal(result.a['$type'], 'string');
            assert.equal(result.b['$type'], 'number');
            assert.equal(result.c['$type'], 'boolean');
            assert.equal(result.d['$type'], 'date');
            assert.equal(result.e['$type'], 'null');
            assert.equal(result.f['$type'], 'string');
        });

        it('should distinguish `text` and `category` types when using {data: true}', function () {
            var result = schema_sync(docs, {data: true});

            assert.equal(result.a['$type'], 'text');
            assert.equal(result.b['$type'], 'number');
            assert.equal(result.c['$type'], 'boolean');
            assert.equal(result.d['$type'], 'date');
            assert.equal(result.e['$type'], 'null');
            assert.equal(result.f['$type'], 'category');
        });

        it('should calculate bounds for date/number and histograms for category', function () {
            var result = schema_sync(docs, {data: true});

            assert.ok( !('$data' in result.a) );
            assert.ok( !('$data' in result.c) );
            assert.ok( !('$data' in result.e) );

            assert.deepEqual(result.b['$data'], {
                min: 1,
                max: 8
            });
            assert.deepEqual(result.d['$data'], {
                min: new Date(2012, 1, 1),
                max: new Date(2014, 1, 1)
            });
            assert.deepEqual(result.f['$data'], {
                foo: 2,
                bar: 1
            });
        });

        it('should track $data for arrays', function () {
            var result = schema_sync([
                {a: ["foo", "foo", "bar"]},
                {a: ["bar", "baz", "foo"]}
            ], {data: true});

            var expected = {
                "foo": 3,
                "bar": 2,
                "baz": 1
            }

            assert.deepEqual(result.a['$data'], expected);
        });

        it('should not infer data for mixed numbers and dates', function () {
            var result = schema_sync([
                {a: 1},
                {a: 5},
                {a: new Date(2014, 08, 20)}
            ], {data: true});

            assert.ok( !('$data' in result.a) );
        });

        it('should handle mixed types for arrays', function () {
            var result = schema_sync([
                {a: ["foo", 1, "bar"]},
                {a: ["bar", null, new Date(2014, 1, 1)]}
            ], {data: true});

            var expected = {
                "string": 3,
                "number": 1,
                "date": 1,
                "null": 1
            };

            assert.deepEqual(result.a['$type'], expected);
        });

        it('should count sub-fields correctly', function () {
            var result = schema_sync([
                {a: 1, b: 1},
                {a: 1, c: 1},
                {a: 1, b: 1}
            ]);

            assert.equal(result['$count'], 3);
            assert.equal(result.a['$count'], 3);
            assert.equal(result.b['$count'], 2);
            assert.equal(result.c['$count'], 1);
        });

        it('should return a type histogram for mixed types', function () {
            var result = schema_sync([
                {a: 1}, {a: "1"}, {a: null}, {a: "1"}, {a: {b: 1}}, {a: 1}, {a: "1"}
            ]);

            var expected = {
                "number": 2,
                "string": 3,
                "null": 1,
                "object": 1
            };
            assert.deepEqual(result.a['$type'], expected);
        });

        it('should let you change the meta-variable names', function () {
            var result = schema_sync([
                {a: 1},
                {a: [-2, -3]}
            ], {
                data: true,
                metavars: {
                    count: '#count',
                    type: '#type',
                    data: '#data',
                    array: '#array',
                    prob: '#prob'
                }
            });

            var expected = {
                "#count": 3,
                "#type": "number",
                "#data": {
                    "min": -3,
                    "max": 1
                },
                "#array": true,
                "#prob": 1.5
            };

            assert.deepEqual(result.a, expected);
        });

        it('should collect categories in $other when maxCardinality is reached', function () {
            var result = schema_sync([
                {a: "a"}, {a: "a"}, {a: "b"}, {a: "c"}, {a: "d"}, {a: "e"}, {a: "f"}
            ], {data: {maxCardinality: 3}});

            assert.ok('$other' in result.a['$data']);
        });

    });

    describe ('schema', function() {
        it('should work asynchronously and not return an error', function (done) {
            schema([{a:1, b:1}, {a:2, b:2}], {}, function (err, result) {
                if (err) throw err;
                assert.equal(err, null);
                assert.equal(result['$count'], 2);
                done();
            });
        });

        it('should return an error if `documents` is not an array', function (done) {
            schema(1, {}, function (err, result) {
                assert.ok(err);
                done();
            });
        });
    });
});
<|endoftext|>