var assert = require("../assert");

module.exports = {
  "start": {
    topic: function(d3) {
      var callback = this.callback,
          div = d3.select("body").html("").selectAll().data(["foo", "bar"]).enter().append("div").attr("class", String),
          transition = div.transition().delay(350),
          then = Date.now(),
          n = 0,
          calls = [],
          context = [],
          data = [],
          index = [],
          count = [],
          delay = [];

      // A callback to verify that multiple callbacks are allowed.
      transition.each("start.other", function() {
        ++n;
      });

      // A callback which captures arguments and context.
      transition.each("start", function(d, i) {
        context.push(this);
        data.push(d);
        index.push(i);
        count.push(++n);
        delay.push(Date.now() - then);
        if (n >= 4) callback(null, {
          selection: div,
          delay: delay,
          context: context,
          data: data,
          index: index,
          count: count,
          id: transition.id
        });
      });
    },

    "invokes the listener after the specified delay": function(result) {
      assert.inDelta(result.delay, [350, 350], 20);
    },
    "invokes each listener exactly once, in order": function(result) {
      assert.deepEqual(result.count, [2, 4]);
    },

    // For the same node, listeners will be called back in order, according to
    // the implementation of d3.dispatch. However, the order of callbacks across
    // nodes is not guaranteed; currently, callbacks are in reverse order if
    // they share the same delay, because of the timer queue. I suppose it'd be
    // slightly better if the callbacks were invoked in node order (consistent
    // with selections), but since these callbacks happen asynchronously I don't
    // think the API needs to guarantee the order of callbacks.

    "uses the node as the context": function(result) {
      assert.domEqual(result.context[0], result.selection[0][0]);
      assert.domEqual(result.context[1], result.selection[0][1]);
    },
    "passes the data and index to the function": function(result) {
      assert.deepEqual(result.data, ["foo", "bar"], "expected data, got {actual}");
      assert.deepEqual(result.index, [0, 1], "expected index, got {actual}");
    },

    "sets an exclusive lock on transitioning nodes": function(result) {
      var id = result.id;
      assert.isTrue(id > 0);
      assert.equal(result.selection[0][0].__transition__.count, 1);
      assert.equal(result.selection[0][1].__transition__.count, 1);
      assert.equal(result.selection[0][0].__transition__.active, id);
      assert.equal(result.selection[0][1].__transition__.active, id);
    }
  },

  "end": {
    topic: function(d3) {
      var callback = this.callback,
          div = d3.select("body").html("").selectAll().data(["foo", "bar"]).enter().append("div").attr("class", String),
          transition = div.transition().duration(350),
          then = Date.now(),
          n = 0,
          calls = [],
          context = [],
          data = [],
          index = [],
          count = [],
          delay = [];

      // A callback to verify that multiple callbacks are allowed.
      transition.each("end.other", function() {
        ++n;
      });

      // A callback which captures arguments and context.
      transition.each("end", function(d, i) {
        context.push(this);
        data.push(d);
        index.push(i);
        count.push(++n);
        delay.push(Date.now() - then);
        if (n >= 4) callback(null, {
          selection: div,
          delay: delay,
          context: context,
          data: data,
          index: index,
          count: count,
          id: transition.id
        });
      });
    },

    "invokes the listener after the specified delay": function(result) {
      assert.inDelta(result.delay, [350, 350], 20);
    },
    "invokes each listener exactly once, in order": function(result) {
      assert.deepEqual(result.count, [2, 4]);
    },

    // For the same node, listeners will be called back in order, according to
    // the implementation of d3.dispatch. However, the order of callbacks across
    // nodes is not guaranteed; currently, callbacks are in reverse order if
    // they share the same delay, because of the timer queue. I suppose it'd be
    // slightly better if the callbacks were invoked in node order (consistent
    // with selections), but since these callbacks happen asynchronously I don't
    // think the API needs to guarantee the order of callbacks.

    "uses the node as the context": function(result) {
      assert.domEqual(result.context[0], result.selection[0][0]);
      assert.domEqual(result.context[1], result.selection[0][1]);
    },
    "passes the data and index to the function": function(result) {
      assert.deepEqual(result.data, ["foo", "bar"], "expected data, got {actual}");
      assert.deepEqual(result.index, [0, 1], "expected index, got {actual}");
    },

    "after the transition ends": {
      topic: function(result) {
        var callback = this.callback;
        process.nextTick(function() {
          callback(null, result);
        });
      },
      "deletes the transition lock": function(result) {
        assert.isFalse("__transition__" in result.selection[0][0]);
        assert.isFalse("__transition__" in result.selection[0][1]);
      }
    },

    // I'd like to test d3.timer.flush here, but unfortunately there's a bug in
    // Vows where it really doesn't like to receive multiple callbacks from
    // different tests at the same time!

    "sequenced": {
      topic: function(result, d3) {
        var callback = this.callback,
            node = d3.select("body").append("div").node(),
            id = result.id;
        d3.select(node).transition().delay(150).each("start", function() {
          callback(null, {id: id, node: this});
        });
      },
      "does not inherit the transition id": function(result) {
        assert.isTrue(result.id > 0);
        assert.equal(result.node.__transition__.count, 1);
        assert.isTrue(result.node.__transition__.active > result.id);
      }
    }
  }
};
