// 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.waitTest');
goog.setTestOnly('goog.result.waitTest');

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

var result, waitCallback, waitOnSuccessCallback, waitOnErrorCallback;

var mockClock, propertyReplacer;

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

function setUp() {
  mockClock.reset();
  result = new goog.result.SimpleResult();
  propertyReplacer = new goog.testing.PropertyReplacer();
  waitCallback = new goog.testing.recordFunction();
  waitOnSuccessCallback = new goog.testing.recordFunction();
  waitOnErrorCallback = new goog.testing.recordFunction();
}

function tearDown() {
  result = waitCallback = waitOnSuccessCallback = waitOnErrorCallback = null;
  propertyReplacer.reset();
}

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

function testSynchronousSuccess() {
  assertEquals(goog.result.Result.State.PENDING, result.getState());
  assertUndefined(result.getValue());

  goog.result.wait(result, waitCallback);
  goog.result.waitOnSuccess(result, waitOnSuccessCallback);
  goog.result.waitOnError(result, waitOnErrorCallback);

  result.setValue(1);

  assertEquals(goog.result.Result.State.SUCCESS, result.getState());
  assertEquals(1, result.getValue());

  assertWaitCall(waitCallback, result);
  assertCall(waitOnSuccessCallback, 1, result);
  assertNoCall(waitOnErrorCallback);
}

function testAsynchronousSuccess() {
  goog.result.wait(result, waitCallback);
  goog.result.waitOnSuccess(result, waitOnSuccessCallback);
  goog.result.waitOnError(result, waitOnErrorCallback);

  goog.Timer.callOnce(function() {
    result.setValue(1);
  });

  assertUndefined(result.getValue());
  assertEquals(goog.result.Result.State.PENDING, result.getState());

  assertNoCall(waitCallback);
  assertNoCall(waitOnSuccessCallback);
  assertNoCall(waitOnErrorCallback);

  mockClock.tick();

  assertEquals(goog.result.Result.State.SUCCESS, result.getState());
  assertEquals(1, result.getValue());

  assertWaitCall(waitCallback, result);
  assertCall(waitOnSuccessCallback, 1, result);
  assertNoCall(waitOnErrorCallback);
}

function testSynchronousError() {
  assertEquals(goog.result.Result.State.PENDING, result.getState());
  assertUndefined(result.getValue());

  goog.result.wait(result, waitCallback);
  goog.result.waitOnSuccess(result, waitOnSuccessCallback);
  goog.result.waitOnError(result, waitOnErrorCallback);

  result.setError();

  assertEquals(goog.result.Result.State.ERROR, result.getState());
  assertUndefined(result.getValue());

  assertWaitCall(waitCallback, result);
  assertNoCall(waitOnSuccessCallback);
  assertCall(waitOnErrorCallback, undefined, result);
}

function testAsynchronousError() {
  goog.result.wait(result, waitCallback);
  goog.result.waitOnSuccess(result, waitOnSuccessCallback);
  goog.result.waitOnError(result, waitOnErrorCallback);

  goog.Timer.callOnce(function() {
    result.setError();
  });

  assertEquals(goog.result.Result.State.PENDING, result.getState());
  assertUndefined(result.getValue());

  assertNoCall(waitCallback);
  assertNoCall(waitOnSuccessCallback);
  assertNoCall(waitOnErrorCallback);

  mockClock.tick();

  assertEquals(goog.result.Result.State.ERROR, result.getState());
  assertUndefined(result.getValue());

  assertWaitCall(waitCallback, result);
  assertNoCall(waitOnSuccessCallback);
  assertCall(waitOnErrorCallback, undefined, result);
}

function testCustomScope() {
  var scope = {};
  goog.result.wait(result, waitCallback, scope);
  result.setValue(1);
  assertEquals(scope, waitCallback.popLastCall().getThis());
}

function testDefaultScope() {
  goog.result.wait(result, waitCallback);
  result.setValue(1);
  assertEquals(goog.global, waitCallback.popLastCall().getThis());
}

function testOnSuccessScope() {
  var scope = {};
  goog.result.waitOnSuccess(result, waitOnSuccessCallback, scope);
  result.setValue(1);
  assertCall(waitOnSuccessCallback, 1, result, scope);
}

function testOnErrorScope() {
  var scope = {};
  goog.result.waitOnError(result, waitOnErrorCallback, scope);
  result.setError();
  assertCall(waitOnErrorCallback, undefined, result, scope);
}

/**
 * Assert that a callback function stubbed out with goog.recordFunction was
 * called with the expected arguments by goog.result.waitOnSuccess/Error.
 * @param {Function} recordedFunction The callback function.
 * @param {?} value The value stored in the result.
 * @param {!goog.result.Result} result The result that was resolved to SUCCESS
 *     or ERROR.
 * @param {Object=} opt_scope Optional scope that the test function should be
 *     called in. By default, it is goog.global.
 */
function assertCall(recordedFunction, value, result, opt_scope) {
  var scope = opt_scope || goog.global;
  assertEquals(1, recordedFunction.getCallCount());
  var call = recordedFunction.popLastCall();
  assertEquals(2, call.getArguments().length);
  assertEquals(value, call.getArgument(0));
  assertEquals(result, call.getArgument(1));
  assertEquals(scope, call.getThis());
}

/**
 * Assert that a callback function stubbed out with goog.recordFunction was
 * called with the expected arguments by goog.result.wait.
 * @param {Function} recordedFunction The callback function.
 * @param {!goog.result.Result} result The result that was resolved to SUCCESS
 *     or ERROR.
 * @param {Object=} opt_scope Optional scope that the test function should be
 *     called in. By default, it is goog.global.
 */
function assertWaitCall(recordedFunction, result, opt_scope) {
  var scope = opt_scope || goog.global;
  assertEquals(1, recordedFunction.getCallCount());
  var call = recordedFunction.popLastCall();
  assertEquals(1, call.getArguments().length);
  assertEquals(result, call.getArgument(0));
  assertEquals(scope, call.getThis());
}

function assertNoCall(recordedFunction) {
  assertEquals(0, recordedFunction.getCallCount());
}
