// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.setTestOnly('goog.testing.MockClassFactoryTest');
goog.require('goog.testing');
goog.require('goog.testing.MockClassFactory');
goog.require('goog.testing.jsunit');
goog.provide('fake.BaseClass');
goog.provide('fake.ChildClass');
goog.provide('goog.testing.MockClassFactoryTest');

// Classes that will be mocked.  A base class and child class are used to
// test inheritance.
fake.BaseClass = function(a) {
  fail('real object should never be called');
};

fake.BaseClass.prototype.foo = function() {
  fail('real object should never be called');
};

fake.BaseClass.prototype.toString = function() {
  return 'foo';
};

fake.BaseClass.prototype.toLocaleString = function() {
  return 'bar';
};

fake.BaseClass.prototype.overridden = function() {
  return 42;
};

fake.ChildClass = function(a) {
  fail('real object should never be called');
};
goog.inherits(fake.ChildClass, fake.BaseClass);

fake.ChildClass.staticFoo = function() {
  fail('real object should never be called');
};

fake.ChildClass.prototype.bar = function() {
  fail('real object should never be called');
};

fake.ChildClass.staticProperty = 'staticPropertyOnClass';

function TopLevelBaseClass() {}

fake.ChildClass.prototype.overridden = function() {
  var superResult = fake.ChildClass.base(this, 'overridden');
  if (superResult != 42) {
    fail('super method not invoked or returned wrong value');
  }
  return superResult + 1;
};

var mockClassFactory = new goog.testing.MockClassFactory();
var matchers = goog.testing.mockmatchers;

function tearDown() {
  mockClassFactory.reset();
}

function testGetStrictMockClass() {
  var mock1 = mockClassFactory.getStrictMockClass(fake, fake.BaseClass, 1);
  mock1.foo();
  mock1.$replay();

  var mock2 = mockClassFactory.getStrictMockClass(fake, fake.BaseClass, 2);
  mock2.foo();
  mock2.$replay();

  var mock3 = mockClassFactory.getStrictMockClass(fake, fake.ChildClass, 3);
  mock3.foo();
  mock3.bar();
  mock3.$replay();

  var instance1 = new fake.BaseClass(1);
  instance1.foo();
  mock1.$verify();

  var instance2 = new fake.BaseClass(2);
  instance2.foo();
  mock2.$verify();

  var instance3 = new fake.ChildClass(3);
  instance3.foo();
  instance3.bar();
  mock3.$verify();

  assertThrows(function() {
    new fake.BaseClass(-1);
  });
  assertTrue(instance1 instanceof fake.BaseClass);
  assertTrue(instance2 instanceof fake.BaseClass);
  assertTrue(instance3 instanceof fake.ChildClass);
}

function testGetStrictMockClassCreatesAllProxies() {
  var mock1 = mockClassFactory.getStrictMockClass(fake, fake.BaseClass, 1);
  // toString(), toLocaleString() and others are treaded specially in
  // createProxy_().
  mock1.toString();
  mock1.toLocaleString();
  mock1.$replay();

  var instance1 = new fake.BaseClass(1);
  instance1.toString();
  instance1.toLocaleString();
  mock1.$verify();
}

function testGetLooseMockClass() {
  var mock1 = mockClassFactory.getLooseMockClass(fake, fake.BaseClass, 1);
  mock1.foo().$anyTimes().$returns(3);
  mock1.$replay();

  var mock2 = mockClassFactory.getLooseMockClass(fake, fake.BaseClass, 2);
  mock2.foo().$times(3);
  mock2.$replay();

  var mock3 = mockClassFactory.getLooseMockClass(fake, fake.ChildClass, 3);
  mock3.foo().$atLeastOnce().$returns(5);
  mock3.bar().$atLeastOnce();
  mock3.$replay();

  var instance1 = new fake.BaseClass(1);
  assertEquals(3, instance1.foo());
  assertEquals(3, instance1.foo());
  assertEquals(3, instance1.foo());
  assertEquals(3, instance1.foo());
  assertEquals(3, instance1.foo());
  mock1.$verify();

  var instance2 = new fake.BaseClass(2);
  instance2.foo();
  instance2.foo();
  instance2.foo();
  mock2.$verify();

  var instance3 = new fake.ChildClass(3);
  assertEquals(5, instance3.foo());
  assertEquals(5, instance3.foo());
  instance3.bar();
  mock3.$verify();

  assertThrows(function() {
    new fake.BaseClass(-1);
  });
  assertTrue(instance1 instanceof fake.BaseClass);
  assertTrue(instance2 instanceof fake.BaseClass);
  assertTrue(instance3 instanceof fake.ChildClass);
}

function testGetStrictStaticMock() {
  var staticMock = mockClassFactory.getStrictStaticMock(fake, fake.ChildClass);
  var mock = mockClassFactory.getStrictMockClass(fake, fake.ChildClass, 1);

  mock.foo();
  mock.bar();
  staticMock.staticFoo();
  mock.$replay();
  staticMock.$replay();

  var instance = new fake.ChildClass(1);
  instance.foo();
  instance.bar();
  fake.ChildClass.staticFoo();
  mock.$verify();
  staticMock.$verify();

  assertTrue(instance instanceof fake.BaseClass);
  assertTrue(instance instanceof fake.ChildClass);
  assertThrows(function() {
    mockClassFactory.getLooseStaticMock(fake, fake.ChildClass);
  });
}

function testGetStrictStaticMockKeepsStaticProperties() {
  var OriginalChildClass = fake.ChildClass;
  var staticMock = mockClassFactory.getStrictStaticMock(fake, fake.ChildClass);
  assertEquals(
      OriginalChildClass.staticProperty, fake.ChildClass.staticProperty);
}

function testGetLooseStaticMockKeepsStaticProperties() {
  var OriginalChildClass = fake.ChildClass;
  var staticMock = mockClassFactory.getLooseStaticMock(fake, fake.ChildClass);
  assertEquals(
      OriginalChildClass.staticProperty, fake.ChildClass.staticProperty);
}

function testGetLooseStaticMock() {
  var staticMock = mockClassFactory.getLooseStaticMock(fake, fake.ChildClass);
  var mock = mockClassFactory.getStrictMockClass(fake, fake.ChildClass, 1);

  mock.foo();
  mock.bar();
  staticMock.staticFoo().$atLeastOnce();
  mock.$replay();
  staticMock.$replay();

  var instance = new fake.ChildClass(1);
  instance.foo();
  instance.bar();
  fake.ChildClass.staticFoo();
  fake.ChildClass.staticFoo();
  mock.$verify();
  staticMock.$verify();

  assertTrue(instance instanceof fake.BaseClass);
  assertTrue(instance instanceof fake.ChildClass);
  assertThrows(function() {
    mockClassFactory.getStrictStaticMock(fake, fake.ChildClass);
  });
}

function testFlexibleClassMockInstantiation() {
  // This mock should be returned for all instances created with a number
  // as the first argument.
  var mock = mockClassFactory.getStrictMockClass(
      fake, fake.ChildClass, matchers.isNumber);
  mock.foo();  // Will be called by the first mock instance.
  mock.foo();  // Will be called by the second mock instance.
  mock.$replay();

  var instance1 = new fake.ChildClass(1);
  var instance2 = new fake.ChildClass(2);
  instance1.foo();
  instance2.foo();
  assertThrows(function() { new fake.ChildClass('foo'); });
  mock.$verify();
}

function testMockTopLevelClass() {
  var mock = mockClassFactory.getStrictMockClass(
      goog.global, goog.global.TopLevelBaseClass);
}

function testGoogBaseCall() {
  var overriddenFn = fake.ChildClass.prototype.overridden;
  var mock = mockClassFactory.getLooseMockClass(fake, fake.ChildClass, 1);
  var instance1 = new fake.ChildClass(1);
  assertTrue(43 == overriddenFn.call(instance1));
}
