text: // Copyright 2007 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.provide('goog.testing.MockClockTest');
  goog.setTestOnly('goog.testing.MockClockTest');
  
  goog.require('goog.events');
  goog.require('goog.functions');
  goog.require('goog.testing.MockClock');
  goog.require('goog.testing.PropertyReplacer');
  goog.require('goog.testing.jsunit');
  goog.require('goog.testing.recordFunction');
  
  var stubs = new goog.testing.PropertyReplacer();
  
  function tearDown() {
    stubs.reset();
  }
  
  function testMockClockWasInstalled() {
    var clock = new goog.testing.MockClock();
    var originalTimeout = window.setTimeout;
    clock.install();
    assertNotEquals(window.setTimeout, originalTimeout);
    setTimeout(function() {}, 100);
    assertEquals(1, clock.getTimeoutsMade());
    setInterval(function() {}, 200);
    assertEquals(2, clock.getTimeoutsMade());
    clock.uninstall();
    assertEquals(window.setTimeout, originalTimeout);
    assertNull(clock.replacer_);
  }
  
  
  function testSetTimeoutAndTick() {
    var clock = new goog.testing.MockClock(true);
    var m5 = false, m10 = false, m15 = false, m20 = false;
    setTimeout(function() { m5 = true; }, 5);
    setTimeout(function() { m10 = true; }, 10);
    setTimeout(function() { m15 = true; }, 15);
    setTimeout(function() { m20 = true; }, 20);
    assertEquals(4, clock.getTimeoutsMade());
  
    assertEquals(4, clock.tick(4));
    assertEquals(4, clock.getCurrentTime());
  
    assertFalse(m5);
    assertFalse(m10);
    assertFalse(m15);
    assertFalse(m20);
  
    assertEquals(5, clock.tick(1));
    assertEquals(5, clock.getCurrentTime());
  
    assertTrue('m5 should now be true', m5);
    assertFalse(m10);
    assertFalse(m15);
    assertFalse(m20);
  
    assertEquals(10, clock.tick(5));
    assertEquals(10, clock.getCurrentTime());
  
    assertTrue('m5 should be true', m5);
    assertTrue('m10 should now be true', m10);
    assertFalse(m15);
    assertFalse(m20);
  
    assertEquals(15, clock.tick(5));
    assertEquals(15, clock.getCurrentTime());
  
    assertTrue('m5 should be true', m5);
    assertTrue('m10 should be true', m10);
    assertTrue('m15 should now be true', m15);
    assertFalse(m20);
  
    assertEquals(20, clock.tick(5));
    assertEquals(20, clock.getCurrentTime());
  
    assertTrue('m5 should be true', m5);
    assertTrue('m10 should be true', m10);
    assertTrue('m15 should be true', m15);
    assertTrue('m20 should now be true', m20);
  
    clock.uninstall();
  }
  
  
  function testSetImmediateAndTick() {
    var clock = new goog.testing.MockClock(true);
    var tick0 = false;
    var tick1 = false;
    setImmediate(function() { tick0 = true; });
    setImmediate(function() { tick1 = true; });
    assertEquals(2, clock.getTimeoutsMade());
  
    clock.tick(0);
    assertTrue(tick0);
    assertTrue(tick1);
  
    clock.uninstall();
  }
  
  
  function testSetInterval() {
    var clock = new goog.testing.MockClock(true);
    var times = 0;
    setInterval(function() { times++; }, 100);
  
    clock.tick(500);
    assertEquals(5, times);
    clock.tick(100);
    assertEquals(6, times);
    clock.tick(100);
    assertEquals(7, times);
    clock.tick(50);
    assertEquals(7, times);
    clock.tick(50);
    assertEquals(8, times);
  
    clock.uninstall();
  }
  
  
  function testRequestAnimationFrame() {
    goog.global.requestAnimationFrame = function() {
    };
    var clock = new goog.testing.MockClock(true);
    var times = [];
    var recFunc = goog.testing.recordFunction(function(now) {
      times.push(now);
    });
    goog.global.requestAnimationFrame(recFunc);
    clock.tick(50);
    assertEquals(1, recFunc.getCallCount());
    assertEquals(20, times[0]);
  
    goog.global.requestAnimationFrame(recFunc);
    clock.tick(100);
    assertEquals(2, recFunc.getCallCount());
    assertEquals(70, times[1]);
  
    clock.uninstall();
  }
  
  
  function testClearTimeout() {
    var clock = new goog.testing.MockClock(true);
    var ran = false;
    var c = setTimeout(function() { ran = true; }, 100);
    clock.tick(50);
    assertFalse(ran);
    clearTimeout(c);
    clock.tick(100);
    assertFalse(ran);
    clock.uninstall();
  }
  
  
  function testClearInterval() {
    var clock = new goog.testing.MockClock(true);
    var times = 0;
    var c = setInterval(function() { times++; }, 100);
  
    clock.tick(500);
    assertEquals(5, times);
    clock.tick(100);
    assertEquals(6, times);
    clock.tick(100);
    clearInterval(c);
    assertEquals(7, times);
    clock.tick(50);
    assertEquals(7, times);
    clock.tick(50);
    assertEquals(7, times);
  
    clock.uninstall();
  }
  
  
  function testClearInterval2() {
    // Tests that we can clear the interval from inside the function
    var clock = new goog.testing.MockClock(true);
    var times = 0;
    var c = setInterval(function() {
      times++;
      if (times == 6) {
        clearInterval(c);
      }
    }, 100);
  
    clock.tick(500);
    assertEquals(5, times);
    clock.tick(100);
    assertEquals(6, times);
    clock.tick(100);
    assertEquals(6, times);
    clock.tick(50);
    assertEquals(6, times);
    clock.tick(50);
    assertEquals(6, times);
  
    clock.uninstall();
  }
  
  
  function testCancelRequestAnimationFrame() {
    goog.global.requestAnimationFrame = function() {
    };
    goog.global.cancelRequestAnimationFrame = function() {
    };
    var clock = new goog.testing.MockClock(true);
    var ran = false;
    var c = goog.global.requestAnimationFrame(function() { ran = true; });
    clock.tick(10);
    assertFalse(ran);
    goog.global.cancelRequestAnimationFrame(c);
    clock.tick(20);
    assertFalse(ran);
    clock.uninstall();
  }
  
  
  function testMockGoogNow() {
    assertNotEquals(0, goog.now());
    var clock = new goog.testing.MockClock(true);
    assertEquals(0, goog.now());
    clock.tick(50);
    assertEquals(50, goog.now());
    clock.uninstall();
    assertNotEquals(50, goog.now());
  }
  
  
  function testTimeoutDelay() {
    var clock = new goog.testing.MockClock(true);
    var m5 = false, m10 = false, m20 = false;
    setTimeout(function() { m5 = true; }, 5);
    setTimeout(function() { m10 = true; }, 10);
    setTimeout(function() { m20 = true; }, 20);
  
    // Fire 3ms early, so m5 fires at t=2
    clock.setTimeoutDelay(-3);
    clock.tick(1);
    assertFalse(m5);
    assertFalse(m10);
    clock.tick(1);
    assertTrue(m5);
    assertFalse(m10);
  
    // Fire 3ms late, so m10 fires at t=13
    clock.setTimeoutDelay(3);
    assertEquals(12, clock.tick(10));
    assertEquals(12, clock.getCurrentTime());
    assertFalse(m10);
    clock.tick(1);
    assertTrue(m10);
    assertFalse(m20);
  
    // Fire 10ms early, so m20 fires now, since it's after t=10
    clock.setTimeoutDelay(-10);
    assertFalse(m20);
    assertEquals(14, clock.tick(1));
    assertEquals(14, clock.getCurrentTime());
    assertTrue(m20);
  
    clock.uninstall();
  }
  
  
  function testTimerCallbackCanCreateIntermediateTimer() {
    var clock = new goog.testing.MockClock(true);
    var sequence = [];
  
    // Create 3 timers: 1, 2, and 3.  Timer 1 should fire at T=1, timer 2 at
    // T=2, and timer 3 at T=3.  The catch: Timer 2 is created by the
    // callback within timer 0.
  
    // Testing method: Create a simple string sequencing each timer and at
    // what time it fired.
  
    setTimeout(function() {
      sequence.push('timer1 at T=' + goog.now());
      setTimeout(function() {
        sequence.push('timer2 at T=' + goog.now());
      }, 1);
    }, 1);
  
    setTimeout(function() {
      sequence.push('timer3 at T=' + goog.now());
    }, 3);
  
    clock.tick(4);
  
    assertEquals(
        'Each timer should fire in sequence at the correct time.',
        'timer1 at T=1, timer2 at T=2, timer3 at T=3',
        sequence.join(', '));
  
    clock.uninstall();
  }
  
  
  function testCorrectArgumentsPassedToCallback() {
    var clock = new goog.testing.MockClock(true);
    var timeoutId;
    var timeoutExecuted = false;
  
    timeoutId = setTimeout(function(arg) {
      assertEquals('"this" must be goog.global',
          goog.global, this);
      assertEquals('The timeout ID must be the first parameter',
          timeoutId, arg);
      assertEquals('Exactly one argument must be passed',
          1, arguments.length);
      timeoutExecuted = true;
    }, 1);
  
    clock.tick(4);
  
    assertTrue('The timeout was not executed', timeoutExecuted);
  
    clock.uninstall();
  }
  
  
  function testTickZero() {
    var clock = new goog.testing.MockClock(true);
    var calls = 0;
  
    setTimeout(function() {
      assertEquals('I need to be first', 0, calls);
      calls++;
    }, 0);
  
    setTimeout(function() {
      assertEquals('I need to be second', 1, calls);
      calls++;
    }, 0);
  
    clock.tick(0);
    assertEquals(2, calls);
  
    setTimeout(function() {
      assertEquals('I need to be third', 2, calls);
      calls++;
    }, 0);
  
    clock.tick(0);
    assertEquals(3, calls);
  
    assertEquals('Time should still be zero', 0, goog.now());
  
    clock.uninstall();
  }
  
  
  function testReset() {
    var clock = new goog.testing.MockClock(true);
  
    setTimeout(function() {
      fail('Timeouts should be cleared after a reset');
    }, 0);
  
    clock.reset();
    clock.tick(999999);
    clock.uninstall();
  }
  
  
  function testQueueInsertionHelper() {
    var queue = [];
  
    function queueToString() {
      var buffer = [];
      for (var i = 0; i < queue.length; i++) {
        buffer.push(queue[i].runAtMillis);
      }
      return buffer.join(',');
    }
  
    goog.testing.MockClock.insert_({runAtMillis: 2}, queue);
    assertEquals('Only item',
        '2', queueToString());
  
    goog.testing.MockClock.insert_({runAtMillis: 4}, queue);
    assertEquals('Biggest item',
        '4,2', queueToString());
  
    goog.testing.MockClock.insert_({runAtMillis: 5}, queue);
    assertEquals('An even bigger item',
        '5,4,2', queueToString());
  
    goog.testing.MockClock.insert_({runAtMillis: 1}, queue);
    assertEquals('Smallest item',
        '5,4,2,1', queueToString());
  
    goog.testing.MockClock.insert_({runAtMillis: 1, dup: true}, queue);
    assertEquals('Duplicate smallest item',
        '5,4,2,1,1', queueToString());
    assertTrue('Duplicate item comes at a smaller index', queue[3].dup);
  
    goog.testing.MockClock.insert_({runAtMillis: 3}, queue);
    goog.testing.MockClock.insert_({runAtMillis: 3, dup: true}, queue);
    assertEquals('Duplicate a middle item',
        '5,4,3,3,2,1,1', queueToString());
    assertTrue('Duplicate item comes at a smaller index', queue[2].dup);
  }
  
  function testIsTimeoutSet() {
    var clock = new goog.testing.MockClock(true);
    var timeoutKey = setTimeout(function() {}, 1);
    assertTrue('Timeout ' + timeoutKey + ' should be set',
        clock.isTimeoutSet(timeoutKey));
    var nextTimeoutKey = timeoutKey + 1;
    assertFalse('Timeout ' + nextTimeoutKey + ' should not be set',
        clock.isTimeoutSet(nextTimeoutKey));
    clearTimeout(timeoutKey);
    assertFalse('Timeout ' + timeoutKey + ' should no longer be set',
        clock.isTimeoutSet(timeoutKey));
    var newTimeoutKey = setTimeout(function() {}, 1);
    clock.tick(5);
    assertFalse('Timeout ' + timeoutKey + ' should not be set',
        clock.isTimeoutSet(timeoutKey));
    assertTrue('Timeout ' + newTimeoutKey + ' should be set',
        clock.isTimeoutSet(newTimeoutKey));
    clock.uninstall();
  }
  
  function testBalksOnTimeoutsGreaterThanMaxInt() {
    // Browsers have trouble with timeout greater than max int, so we
    // want Mock Clock to fail if this happens.
    var clock = new goog.testing.MockClock(true);
    // Functions on window don't seem to be able to throw exceptions in
    // IE6.  Explicitly reading the property makes it work.
    var setTimeout = window.setTimeout;
    assertThrows('Timeouts > MAX_INT should fail',
        function() {
          setTimeout(goog.nullFunction, 2147483648);
        });
    assertThrows('Timeouts much greater than MAX_INT should fail',
        function() {
          setTimeout(goog.nullFunction, 2147483648 * 10);
        });
    clock.uninstall();
  }
  
  function testCorrectSetTimeoutIsRestored() {
    var safe = goog.functions.error('should not have been called');
    stubs.set(window, 'setTimeout', safe);
  
    var clock = new goog.testing.MockClock(true);
    assertNotEquals('setTimeout is replaced', safe, window.setTimeout);
    clock.uninstall();
    // NOTE: If this assertion proves to be flaky in IE, the string value of
    // the two functions have to be compared as described in
    // goog.testing.TestCase#finalize.
    assertEquals('setTimeout is restored', safe, window.setTimeout);
  }
  
  function testMozRequestAnimationFrame() {
    // Setting this function will indirectly tell the mock clock to mock it out.
    stubs.set(window, 'mozRequestAnimationFrame', goog.nullFunction);
  
    var clock = new goog.testing.MockClock(true);
  
    var mozBeforePaint = goog.testing.recordFunction();
    goog.events.listen(window, 'MozBeforePaint', mozBeforePaint);
  
    window.mozRequestAnimationFrame(null);
    assertEquals(0, mozBeforePaint.getCallCount());
  
    clock.tick(goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT);
    assertEquals(1, mozBeforePaint.getCallCount());
    clock.dispose();
  }
  
  function testClearBeforeSet() {
    var clock = new goog.testing.MockClock(true);
    var expectedId = 1;
    window.clearTimeout(expectedId);
  
    var fn = goog.testing.recordFunction();
    var actualId = window.setTimeout(fn, 0);
    assertEquals(
        'In order for this test to work, we have to guess the ids in advance',
        expectedId, actualId);
    clock.tick(1);
    assertEquals(1, fn.getCallCount());
    clock.dispose();
  }
  