// Copyright 2012 The Closure Library Authors. All Rights Reserved.
// Use of this source code is governed by the Apache License, Version 2.0.

goog.provide('goog.result.chainTest');
goog.setTestOnly('goog.result.chainTest');

goog.require('goog.Timer');
goog.require('goog.result');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');

var givenResult, dependentResult, counter, actionCallback;
var mockClock;

function setUpPage() {
  mockClock = new goog.testing.MockClock();
  mockClock.install();
}

function setUp() {
  mockClock.reset();
  givenResult = new goog.result.SimpleResult();
  dependentResult = new goog.result.SimpleResult();
  counter = new goog.testing.recordFunction();
  actionCallback = goog.testing.recordFunction(function(result) {
    return dependentResult;
  });
}

function tearDown() {
  givenResult = dependentResult = counter = null;
}

function tearDownPage() {
  mockClock.uninstall();
}

// SYNCHRONOUS TESTS:

function testChainWhenBothResultsSuccess() {
  var finalResult = goog.result.chain(givenResult, actionCallback);
  goog.result.wait(finalResult, counter);

  givenResult.setValue(1);
  dependentResult.setValue(2);

  assertSuccess(actionCallback, givenResult, 1);
  assertSuccess(counter, finalResult, 2);
}

function testChainWhenFirstResultError() {
  var finalResult = goog.result.chain(givenResult, actionCallback);
  goog.result.wait(finalResult, counter);

  givenResult.setError(4);

  assertNoCall(actionCallback);
  assertError(counter, finalResult, 4);
}

function testChainWhenSecondResultError() {
  var finalResult = goog.result.chain(givenResult, actionCallback);
  goog.result.wait(finalResult, counter);

  givenResult.setValue(1);
  dependentResult.setError(5);

  assertSuccess(actionCallback, givenResult, 1);
  assertError(counter, finalResult, 5);
}

function testChainCancelFirstResult() {
  var finalResult = goog.result.chain(givenResult, actionCallback);
  goog.result.wait(finalResult, counter);

  goog.result.cancelParentResults(finalResult);

  assertNoCall(actionCallback);
  assertTrue(givenResult.isCanceled());
  assertTrue(finalResult.isCanceled());
}

function testChainCancelSecondResult() {
  var finalResult = goog.result.chain(givenResult, actionCallback);
  goog.result.wait(finalResult, counter);

  givenResult.setValue(1);
  goog.result.cancelParentResults(finalResult);

  assertSuccess(actionCallback, givenResult, 1);
  assertTrue(dependentResult.isCanceled());
  assertTrue(finalResult.isCanceled());
}

function testDoubleChainCancel() {
  var intermediateResult = goog.result.chain(givenResult, actionCallback);
  var finalResult = goog.result.chain(intermediateResult, actionCallback);

  assertTrue(goog.result.cancelParentResults(finalResult));
  assertTrue(finalResult.isCanceled());
  assertTrue(intermediateResult.isCanceled());
  assertFalse(givenResult.isCanceled());
  assertFalse(goog.result.cancelParentResults(finalResult));
}

function testCustomScope() {
  var scope = {};
  var finalResult = goog.result.chain(givenResult, actionCallback, scope);
  goog.result.wait(finalResult, counter);

  givenResult.setValue(1);
  dependentResult.setValue(2);

  assertEquals(scope, actionCallback.popLastCall().getThis());
}


// ASYNCHRONOUS TESTS:

function testChainAsyncWhenBothResultsSuccess() {
  var finalResult = goog.result.chain(givenResult, actionCallback);
  goog.result.wait(finalResult, counter);

  goog.Timer.callOnce(function() { givenResult.setValue(1); });
  mockClock.tick();

  assertSuccess(actionCallback, givenResult, 1);

  goog.Timer.callOnce(function() { dependentResult.setValue(2); });
  mockClock.tick();

  assertSuccess(counter, finalResult, 2);
}

function testChainAsyncWhenFirstResultError() {
  var finalResult = goog.result.chain(givenResult, actionCallback);
  goog.result.wait(finalResult, counter);

  goog.Timer.callOnce(function() { givenResult.setError(6); });
  mockClock.tick();

  assertNoCall(actionCallback);
  assertError(counter, finalResult, 6);
}

function testChainAsyncWhenSecondResultError() {
  var finalResult = goog.result.chain(givenResult, actionCallback);
  goog.result.wait(finalResult, counter);

  goog.Timer.callOnce(function() { givenResult.setValue(1); });
  mockClock.tick();

  assertSuccess(actionCallback, givenResult, 1);

  goog.Timer.callOnce(function() { dependentResult.setError(7); });
  mockClock.tick();

  assertError(counter, finalResult, 7);
}

function testChainAsyncCancelFirstResult() {
  var finalResult = goog.result.chain(givenResult, actionCallback);
  goog.result.wait(finalResult, counter);

  goog.Timer.callOnce(function() {
    goog.result.cancelParentResults(finalResult);
  });
  mockClock.tick();

  assertNoCall(actionCallback);
  assertTrue(givenResult.isCanceled());
  assertTrue(finalResult.isCanceled());
}

function testChainAsyncCancelSecondResult() {
  var finalResult = goog.result.chain(givenResult, actionCallback);
  goog.result.wait(finalResult, counter);

  goog.Timer.callOnce(function() { givenResult.setValue(1); });
  mockClock.tick();

  assertSuccess(actionCallback, givenResult, 1);

  goog.Timer.callOnce(function() {
    goog.result.cancelParentResults(finalResult);
  });
  mockClock.tick();

  assertTrue(dependentResult.isCanceled());
  assertTrue(finalResult.isCanceled());
}

// HELPER FUNCTIONS:

// Assert that the recordFunction was called once with an argument of
// 'result' (the second argument) which has a state of SUCCESS and
// a value of 'value' (the third argument).
function assertSuccess(recordFunction, result, value) {
  assertEquals(1, recordFunction.getCallCount());
  var res = recordFunction.popLastCall().getArgument(0);
  assertEquals(result, res);
  assertEquals(goog.result.Result.State.SUCCESS, res.getState());
  assertEquals(value, res.getValue());
}

// Assert that the recordFunction was called once with an argument of
// 'result' (the second argument) which has a state of ERROR.
function assertError(recordFunction, result, value) {
  assertEquals(1, recordFunction.getCallCount());
  var res = recordFunction.popLastCall().getArgument(0);
  assertEquals(result, res);
  assertEquals(goog.result.Result.State.ERROR, res.getState());
  assertEquals(value, res.getError());
}

// Assert that the recordFunction wasn't called
function assertNoCall(recordFunction) {
  assertEquals(0, recordFunction.getCallCount());
}
