From 034d00b83ab5b992c59de0be0080947fcb8dd880 Mon Sep 17 00:00:00 2001
From: Wojciech Zawistowski <wojciech.zawistowski@gmail.com>
Date: Mon, 7 Oct 2013 17:45:09 +0200
Subject: [PATCH] adds unit tests for Discourse.debouncePromise

---
 .../discourse/components/debounce.js          |  3 +-
 .../components/debounce_promise_test.js       | 91 +++++++++++++++++++
 2 files changed, 93 insertions(+), 1 deletion(-)
 create mode 100644 test/javascripts/components/debounce_promise_test.js

diff --git a/app/assets/javascripts/discourse/components/debounce.js b/app/assets/javascripts/discourse/components/debounce.js
index a4ec880013f..b9be435c6f2 100644
--- a/app/assets/javascripts/discourse/components/debounce.js
+++ b/app/assets/javascripts/discourse/components/debounce.js
@@ -49,9 +49,10 @@ Discourse.debounce = function(func, wait) {
 Discourse.debouncePromise = function(func, wait) {
   var timeout = null;
   var args = null;
+  var context = null;
 
   return function() {
-    var context = this;
+    context = this;
     var promise = Ember.Deferred.create();
     args = arguments;
 
diff --git a/test/javascripts/components/debounce_promise_test.js b/test/javascripts/components/debounce_promise_test.js
new file mode 100644
index 00000000000..49822c5ceca
--- /dev/null
+++ b/test/javascripts/components/debounce_promise_test.js
@@ -0,0 +1,91 @@
+var clock, original, debounced, originalPromiseResolvesWith, callback;
+
+var nothingFired = function(additionalMessage) {
+  ok(!original.called, "original function is not called " + additionalMessage);
+  ok(!callback.called, "debounced promise is not resolved " + additionalMessage);
+};
+
+var originalAndCallbackFiredOnce = function(additionalMessage) {
+  ok(original.calledOnce, "original function is called once " + additionalMessage);
+  ok(callback.calledOnce, "debounced promise is resolved once " + additionalMessage);
+};
+
+module("Discourse.debouncePromise", {
+  setup: function() {
+    clock = sinon.useFakeTimers();
+
+    originalPromiseResolvesWith = null;
+    original = sinon.spy(function() {
+      var promise = Ember.Deferred.create();
+      promise.resolve(originalPromiseResolvesWith);
+      return promise;
+    });
+
+    debounced = Discourse.debouncePromise(original, 100);
+    callback = sinon.spy();
+  },
+
+  teardown: function() {
+    clock.restore();
+  }
+});
+
+test("delays execution till the end of the timeout", function() {
+  debounced().then(callback);
+  nothingFired("immediately after calling debounced function");
+
+  clock.tick(99);
+  nothingFired("just before the end of the timeout");
+
+  clock.tick(1);
+  originalAndCallbackFiredOnce("exactly at the end of the timeout");
+});
+
+test("executes only once, no matter how many times debounced function is called during the timeout", function() {
+  debounced().then(callback);
+  debounced().then(callback);
+
+  clock.tick(100);
+  originalAndCallbackFiredOnce("(second call was supressed)");
+});
+
+test("does not prolong the timeout when the debounced function is called for the second time during the timeout", function() {
+  debounced().then(callback);
+
+  clock.tick(50);
+  debounced().then(callback);
+
+  clock.tick(50);
+  originalAndCallbackFiredOnce("exactly at the end of the original timeout");
+});
+
+test("preserves last call's context and params when executing delayed function", function() {
+  var firstObj = {};
+  var secondObj = {};
+
+  debounced.call(firstObj, "first");
+  debounced.call(secondObj, "second");
+
+  clock.tick(100);
+  ok(original.calledOn(secondObj), "the context of the second of two subsequent calls is preserved");
+  ok(original.calledWithExactly("second"), "param passed during the second of two subsequent calls is preserved");
+});
+
+test("can be called again after timeout passes", function() {
+  debounced().then(callback);
+
+  clock.tick(100);
+  debounced().then(callback);
+
+  clock.tick(100);
+  ok(original.calledTwice, "original function is called for the second time");
+  ok(callback.calledTwice, "debounced promise is resolved for the second time");
+});
+
+test("passes resolved value from the original promise as a param to the debounced promise's callback", function() {
+  originalPromiseResolvesWith = "original promise return value";
+  debounced().then(callback);
+
+  clock.tick(100);
+  ok(callback.calledWith("original promise return value"));
+});