// ========================================================================== // Project: SproutCore - JavaScript Application Framework // Copyright: ©2006-2011 Strobe Inc. and contributors. // ©2008-2011 Apple Inc. All rights reserved. // License: Licensed under MIT license (see license.js) // ========================================================================== // ======================================================================== // SC.Request Base Tests // ======================================================================== /*globals module, test, ok, isObj, equals, expects */

var url, request, contents, test_timeout=2500;

// When running Travis-CI tests through PhantomJS, wait extra long. if (window._phantom) {

test_timeout=5000;

}

module(“SC.Request”, {

setup: function() {
  url = sc_static("file_exists.json");
  request = SC.Request.getUrl(url);
  contents = null;
},

teardown: function() {
  url = request = contents = null;
}

});

test(“PRELIM: Verify basic functionality in testing environment.”, function() {

if (SC.platform.get('supportsXHR2ProgressEvent')) {
  var xhr = new XMLHttpRequest;
  xhr.addEventListener('load', function() {
    ok(true, 'Success: xhr.addEventListener for load event.');
    nextTest();
  });
  xhr.open("get", url);
  xhr.send();

  window.stop(test_timeout);

  function nextTest() {
    request.notify('load', this, function() {
      ok(true, 'Success: SC.Request#notify for load event.');
      window.start();
    });
    request.send();
  }
}

});

test(“Basic Requirements”, function() {

ok(SC.Request, "SC.Request is defined");
ok("" !== url, "url variable is not empty");
ok(request !== null, "request object is not null");
ok(contents === null, "contents is null" );

});

test(“Default properties are correct for different types of requests.”, function() {

var formBody,
  headers,
  jsonBody,
  xmlBody,
  req1, req2, req3, req4, req5;

  // use this document for creating XML
  if (document.implementation.createDocument) {
    xmlBody = document.implementation.createDocument(null, null, null);
  } else if (typeof (ActiveXObject) != "undefined") {
    // Use ActiveXObject for IE prior to version 9.
    var progIDs = [
      "Msxml2.DOMDocument.6.0",
      "Msxml2.DOMDocument.5.0",
      "Msxml2.DOMDocument.4.0",
      "Msxml2.DOMDocument.3.0",
      "MSXML2.DOMDocument",
      "MSXML.DOMDocument"
    ];

    for (var i = 0; i < progIDs.length; i++) {
      try {
        xmlBody = new ActiveXObject(progIDs[i]);
        break;
      } catch(e) {}
    }
  }

  // function that creates the XML structure
  function o() {
    var i, node = xmlBody.createElement(arguments[0]), text, child;

    for(i = 1; i < arguments.length; i++) {
        child = arguments[i];
        if(typeof child == 'string') {
            child = xmlBody.createTextNode(child);
        }
        node.appendChild(child);
    }

    return node;
  }

  // create the XML structure recursively
  o('report',
      o('submitter',
          o('name', 'John Doe')
      ),
      o('students',
          o('student',
              o('name', 'Alice'),
              o('grade', '80')
          ),
          o('student',
              o('name', 'Bob'),
              o('grade', '90')
          )
      )
  );

jsonBody = { a: 1, b: 2 };
formBody = "fname=Henry&lname=Ford";
req1 = SC.Request.getUrl(url).json()._prep();
req2 = SC.Request.postUrl(window.location.href, formBody).header('Content-Type', 'application/x-www-form-urlencoded')._prep().credentials(false);
req3 = SC.Request.putUrl('https://localhost:4020' + url, xmlBody).xml()._prep();
req4 = SC.Request.patchUrl('http://localhost' + url, jsonBody).json()._prep();
req5 = SC.Request.deleteUrl(window.location.href + '?params=true')._prep();

ok(req1.get('isJSON'), 'req1 should have isJSON true');
ok(!req1.get('isXML'), 'req1 should have isXML false');
ok(req1.get('allowCredentials'), 'req1 should have allowCredentials true');
ok(req1.get('isSameDomain'), 'req1: %@ should have isSameDomain true'.fmt(url));
equals(req1.header('Content-Type'), undefined, 'req1 should have Content-Type header as');
ok(!req2.get('isJSON'), 'req2 should have isJSON false');
ok(!req2.get('isXML'), 'req2 should have isXML false');
ok(!req2.get('allowCredentials'), 'req2 should have allowCredentials false');
ok(req2.get('isSameDomain'), 'req2: %@ should have isSameDomain true'.fmt(window.location.href));
equals(req2.header('Content-Type'), 'application/x-www-form-urlencoded', 'req2 should have Content-Type header as');
ok(!req3.get('isJSON'), 'req3 should have isJSON false');
ok(req3.get('isXML'), 'req3 should have isXML true');
ok(!req3.get('isSameDomain'), 'req3: %@ should have isSameDomain false'.fmt('https://localhost:4020' + url));
equals(req3.header('Content-Type'), 'text/xml', 'req3 should have Content-Type header as');
ok(req4.get('isJSON'), 'req4 should have isJSON true');
ok(!req4.get('isXML'), 'req4 should have isXML false');
ok(!req4.get('isSameDomain'), 'req4: %@ should have isSameDomain false'.fmt('http://localhost' + url));
equals(req4.header('Content-Type'), 'application/json', 'req4 should have Content-Type header as');
ok(!req5.get('isJSON'), 'req5 should have isJSON false');
ok(!req5.get('isXML'), 'req5 should have isXML false');
ok(req5.get('isSameDomain'), 'req5: %@ should have isSameDomain true'.fmt(window.location.href + '?params=true'));
equals(req5.header('Content-Type'), undefined, 'req5 should have Content-Type header as');

});

test(“Test Asynchronous GET Request”, function() {

var response, timer;

timer = setTimeout(function() {
  ok(false, 'response did not invoke notify() within 2sec');
  window.start();
}, 2000);

request.notify(this, function(response) {
  ok(SC.ok(response), 'response should not be an error');
  equals(response.get('body'), '{"message": "Yay!"}', 'should match retrieved message');
  clearTimeout(timer);
  window.start();
});

stop(test_timeout); // stops the test runner - wait for response

response = request.send();
ok(response !== null, 'request.send() should return a response object');
ok(response.get('status')<0, 'response should still not have a return code since this should be async');

});

test(“Test Synchronous GET Request”, function() {

request.set("isAsynchronous", NO);
var response = request.send();

ok(response !== null, 'send() should return response');
ok(SC.$ok(response), 'contents should not be an error ');
equals(response.get('body'), '{"message": "Yay!"}', 'should match retrieved message');

});

test(“Test Asynchronous GET Request, auto-deserializing JSON”, function() {

request.set("isJSON", YES);

var timer;

timer = setTimeout( function(){
  ok(false, 'response did not invoke notify()');
  window.start();
}, 1000);

request.notify(this, function(response) {
  ok(SC.ok(response), 'response should not be error');
  same(response.get('body'), {"message": "Yay!"}, 'repsonse.body');
  clearTimeout(timer);
  window.start();
});

stop(test_timeout); // stops the test runner

request.send();

});

test(“Test auto-deserializing malformed JSON”, function() {

request = SC.Request.getUrl(sc_static('malformed.json')).set('isJSON', YES);

var timer = setTimeout(function() {
  ok(false, 'response did not invoke notify()');
  window.start();
}, 1000);

request.notify(this, function(response) {
  ok(SC.ok(response), 'response should not be error');

  try {
    var body = response.get('body');
    ok(!SC.ok(body), 'body should be an error');
  } catch(e) {
    ok(false, 'getting the body should not throw an exception');
  }

  clearTimeout(timer);
  window.start();
});

stop(test_timeout);

request.send();

});

test(“Test Synchronous GET Request, auto-deserializing JSON”, function() {

request.set("isAsynchronous", false);
request.set("isJSON", true);

var response = request.send();

ok(response !== null, 'response should not be null');
ok(SC.ok(response), 'contents should not be an error');
same(response.get('body'), {"message": "Yay!"}, 'contents should have message');

});

test(“Test if Request body is being auto-serializing to JSON”, function() {

var objectToPost={"content": "garbage"};
request.set("isJSON", true).set('body', objectToPost);

var jsonEncoded = request.get('encodedBody');

equals(jsonEncoded, '{"content":"garbage"}', "The json object passed in send should be encoded and set as the body");

});

test(“Test Multiple Asynchronous GET Request - two immediate, and two in serial”, function() {

var requestCount = 3;
var responseCount = 0;
var serialCount = 0;

var observer = function(response) {
  responseCount++;
  if (serialCount<=2) {
    serialCount++;
    SC.Request.getUrl(url).notify(this, observer).send();
    requestCount++;
  }
};

SC.Request.getUrl(url).notify(this, observer).send();
SC.Request.getUrl(url).notify(this, observer).send();
SC.Request.getUrl(url).notify(this, observer).send();

stop(test_timeout); // stops the test runner
setTimeout( function(){
  equals(requestCount, 6, "requestCount should be 6");
  equals(responseCount, 6, "responseCount should be 6");
  window.start(); // starts the test runner
}, 2000);

});

// There are two ways to be notified of request changes: // - Implementing a didReceive function on the SC.Request object // - Registering a listener using notify() // The following two tests test the timeout functionality for each of these.

test(“Timeouts - SC.Request didReceive callback”, function() {

var message;

// Sanity check - Should throw an error if we try to set a timeout of 0s.
try {
  SC.Request.getUrl(url).set('timeout', 0).send();
} catch (e) {
  message = e.message;
}
ok(message && message.indexOf("The timeout value must either not be specified or must be greater than 0") !== -1, 'An error should be thrown when the timeout value is 0 ms');

// Sanity check 2 - Can't set timeouts on synchronous XHR requests
try {
  SC.Request.getUrl(url).set('isAsynchronous', NO).set('timeout', 10).send();
}
catch (e2) {
  message = e2.message;
}
ok(message && message.indexOf("Timeout values cannot be used with synchronous requests") !== -1, 'An error should be thrown when trying to use a timeout with a synchronous request');

// Make sure timeouts actually fire, and fire when expected.
// Point to the server itself so that the tests will work even when offline
var timeoutRequest = SC.Request.getUrl("/"),
    checkstop;

var now = Date.now();

// Set timeout as short as possible so that it will always timeout before
// the request returns.
// This test will fail should the response time drop to
// below 10ms.
timeoutRequest.set('timeout', 10);

timeoutRequest.set('didReceive', function(request, response) {
  // Test runner is paused after the request is sent; resume unit testing
  // once we receive a response.
  start();
  clearTimeout(checkstop);

  // If this response was caused by a timeout…
  if (response.get('timedOut')) {
    equals(response.get('status'), 0,
           'Timed out responses should have status 0');

    // We should never be called before the timeout we specified
    var elapsed = Date.now()-now;
    ok(elapsed >= 10,
      'timeout must not fire earlier than 10msec - actual %@'.fmt(elapsed));
  } else {
    // We received a response from the server, which should never happen
    ok(false, 'timeout did not fire before response was received.  should have fired after 10msec.  response time: %@msec'.fmt(Date.now() - now));
  }
});

// Stop the test runner and wait for a timeout or a response.
stop(test_timeout);

SC.RunLoop.begin();
timeoutRequest.send();
SC.RunLoop.end();

// In case we never receive a timeout, just start unit testing again after
// 500ms.
checkstop = setTimeout(function() {
  window.start();
  ok(false, 'timeout did not fire at all');
}, 500);

});

test(“Timeouts - Status listener callback”, function() {

// sanity check
equals(SC.Request.manager.inflight.length,0,"there should be no inflight requests");

// Make sure timeouts actually fire, and fire when expected.
// Point to local server so test works offline
var timeoutRequest = SC.Request.getUrl("/"),
    checkstop;

// make the timeout as short as possible so that it will always happen
timeoutRequest.timeoutAfter(10).notify(this, function(response) {
  start();
  clearTimeout(checkstop);

  equals(response.get('status'), 0, "Status code should be zero");
  equals(response.get('timedOut'), YES, "Should have timedOut property set to YES");
  // timeout did fire...just resume...

  return YES;
});

stop(test_timeout); // stops the test runner

SC.RunLoop.begin();
timeoutRequest.send();
SC.RunLoop.end();

// in case nothing works
checkstop = setTimeout(function() {
  ok(false, 'timeout did not fire at all');
  window.start();
}, 500);

});

test(“Test Multiple listeners per single status response”, function() {

var numResponses = 0;
var response;

expect(8);

// sanity check
equals(SC.Request.manager.inflight.length,0,"there should be no inflight requests");

request.notify(200, this, function(response) {
  numResponses++;
  ok(true, "Received a response on first listener");
});

request.notify(200, this, function(response) {
  numResponses++;
  ok(true, "Received a response on second listener");
});

setTimeout(function() {
  equals(SC.Request.manager.inflight.length,0,"there should be no inflight requests after the timeout");
  equals(numResponses, 2, "got two notifications");
  if (numResponses === 2) { window.start(); }
}, ((test_timeout*5) - 500) );

// phantomjs (used in integration tests) needs a veeeery long timeout, just for this test
stop(test_timeout*5); // stops the test runner - wait for response

response = request.send();
ok(response !== null, 'request.send() should return a response object');
ok(response.get('status')<0, 'response should still not have a return code since this should be async');
equals(SC.Request.manager.inflight.length,1,"there should be 1 inflight request after send()");

});

/**

There was a short-lived bug where the additional Arguments passed to notify()
were being dropped because the slice on 'arguments' was happening after they
had already been adjusted.

*/ test(“Multiple arguments passed to notify()”, function() {

var response;

// sanity check
equals(SC.Request.manager.inflight.length,0,"there should be no inflight requests");

request.notify(this, function(response, a, b, c) {
  equals(a, 'a', "Listener called with argument 'a'");
  equals(b, 'b', "Listener called with argument 'b'");
  equals(c, 'c', "Listener called with argument 'c'");
}, 'a', 'b', 'c');

request.notify(200, this, function(response, a, b, c) {
  equals(a, 'a', "Listener called with argument 'a'");
  equals(b, 'b', "Listener called with argument 'b'");
  equals(c, 'c', "Listener called with argument 'c'");

  window.start();
}, 'a', 'b', 'c');

stop(test_timeout); // stops the test runner - wait for response

response = request.send();

});

test(“Test event listeners on successful request.”, function() {

var abort = false,
  error = false,
  load = false,
  loadend = false,
  loadstart = false,
  progress = false,
  response,
  status,
  timeout = false;

request.notify("loadstart", this, function(evt) {
  loadstart = true;
});

request.notify("progress", this, function(evt) {
  progress = true;
});

request.notify("load", this, function(evt) {
  load = true;
});

request.notify("loadend", this, function(evt) {
  loadend = true;
});

request.notify(200, this, function(response) {
  status = response.status;

  if (SC.platform.get('supportsXHR2ProgressEvent')) {
    ok(loadstart, "Received a loadstart event.");
    ok(progress, "Received a progress event.");
    ok(load, "Received a load event.");
    if (SC.platform.get('supportsXHR2LoadEndEvent')) ok(loadend, "Received a loadend event.");
  }

  ok(!abort, "Did not receive an abort event.");
  ok(!error, "Did not receive an error event.");
  ok(!timeout, "Did not receive a timeout event.");
  equals(status, 200, "Received a response with status 200.");

  window.start();
});

stop(test_timeout); // stops the test runner - wait for response

response = request.send();

});

if (SC.platform.get('supportsXHR2ProgressEvent')) {

test("Test event listeners on aborted request.", function() {
  var abort = false,
    error = false,
    load = false,
    loadstart = false,
    loadend = false,
    response,
    status,
    timeout = false;

  request.notify("loadstart", this, function(evt) {
    loadstart = true;
  });

  request.notify("abort", this, function(evt) {
    abort = true;
  });

  request.notify("progress", this, function(evt) {
    progress = true;

    // Cancel it before it completes.
    response.cancel();
  });

  request.notify(SC.platform.get('supportsXHR2LoadEndEvent') ? 'loadend' : 'abort', this, function(evt) {
    loadend = true;

    ok(loadstart, "Received a loadstart event.");
    ok(progress, "Received a progress event.");
    ok(abort, "Received an abort event.");
    ok(!load, "Did not receive a load event.");
    if (SC.platform.get('supportsXHR2LoadEndEvent')) {
      ok(loadend, "Received a loadend event.");
    } else {
      ok(loadend, "loadend event not supported. Received terminal abort event.");
    }
    ok(!error, "Did not receive an error event.");
    ok(!timeout, "Did not receive a timeout event.");
    equals(status, undefined, "Did not receive a status notification.");

    window.start();
  });

  stop(test_timeout); // stops the test runner - wait for response

  response = request.send();
});

}

test(“Test upload event listeners on successful request.”, function() {

var abort = false,
  body = {},
  error = false,
  load = false,
  loadend = false,
  loadstart = false,
  progress = false,
  response,
  status,
  timeout = false;

// Use a POST request
request = SC.Request.postUrl('/');

request.notify("upload.loadstart", this, function(evt) {
  loadstart = true;
});

request.notify("upload.progress", this, function(evt) {
  progress = true;
});

request.notify("upload.load", this, function(evt) {
  load = true;
});

request.notify("upload.loadend", this, function(evt) {
  loadend = true;
});

request.notify(200, this, function(response) {
  status = response.status;

  if (SC.platform.get('supportsXHR2ProgressEvent')) {
    ok(loadstart, "Received a loadstart event.");
    ok(progress, "Received a progress event.");
    ok(load, "Received a load event.");
    if (SC.platform.get('supportsXHR2LoadEndEvent')) ok(loadend, "Received a loadend event.");
  }
  ok(!abort, "Did not receive an abort event.");
  ok(!error, "Did not receive an error event.");
  ok(!timeout, "Did not receive a timeout event.");
  equals(status, 200, "Received a response with status 200.");

  window.start();
});

// Make a significant body object.
// It looks that Firefox is not sending the progress event if the request is too small
var i;
for (i = 200000; i >= 0; i--) {
  body['k' + i] = 'v' + i;
}

stop(test_timeout); // stops the test runner - wait for response

response = request.send(JSON.stringify(body));

});

test(“Test manager.cancelAll.”, function() {

var manager = SC.Request.manager, max = manager.get('maxRequests');
// Make sure we're clear.
SC.Request.manager.cancelAll();

// Get a copy of the previous arrays, since they're overwritten on clear.
var inflight = manager.get('inflight');
var pending = manager.get('pending');

// Generate > 6 requests
for( var i = 0; i < max * 2; i++) {
  SC.Request.getUrl('/').send();
}

equals(inflight.get('length'), max, "There must be %@ inflight requests".fmt(max));
equals(pending.get('length'), max, "There must be %@ pending requests".fmt(max));

SC.Request.manager.cancelAll();

// Demonstrates memory pointer matches
equals(inflight, manager.getPath('inflight'), "Arrays must be identical");
equals(pending, manager.getPath('pending'), "Arrays must be identical");

// Demonstrates that all previous requests have been cleared.
equals(inflight.get('length'), 0, "There must be 0 inflight requests in the old array".fmt(max));
equals(pending.get('length'), 0, "There must be 0 pending requests in the old array".fmt(max));

// Demonstrates that the manager doesn't know about any requests.
equals(manager.getPath('inflight.length'), 0, "There must be 0 inflight requests".fmt(max));
equals(manager.getPath('pending.length'), 0, "There must be 0 pending requests".fmt(max));

});

test(“Test responses moving between pending and inflight states”, function() {

var prevMaxRequests = SC.Request.manager.get('maxRequests'),
    request2 = SC.Request.getUrl(url),
    response,
    response2;

// This gives us precise control over when our requests fire.
SC.Request.manager.set('maxRequests', 0);

request.notify('loadstart', function (evt) {
  ok(!SC.Request.manager.isPending(response), 'First request is no longer pending.');
  ok(SC.Request.manager.isInFlight(response), 'First request is now inflight.');
  ok(SC.Request.manager.isPending(response2), 'Second request is still pending.');
  ok(!SC.Request.manager.isInFlight(response2), 'Second request is not inflight.');
});

request.notify('progress', this, function(evt) {
  ok(!SC.Request.manager.isPending(response), 'First request is not pending.');
  ok(SC.Request.manager.isInFlight(response), 'First request is still inflight.');
  ok(SC.Request.manager.isPending(response2), 'Second request is still pending.');
  ok(!SC.Request.manager.isInFlight(response2), 'Second request is not inflight.');
});

request.notify('loadend', this, function (evt) {
  ok(!SC.Request.manager.isPending(response), 'First request is not pending.');
  ok(SC.Request.manager.isInFlight(response), 'First request is still inflight.');
  ok(SC.Request.manager.isPending(response2), 'Second request is still pending.');
  ok(!SC.Request.manager.isInFlight(response2), 'Second request is not inflight.');
});

request.notify(this, function (evt) {
  ok(!SC.Request.manager.isPending(response), 'First request is not pending.');
  ok(SC.Request.manager.isInFlight(response), 'First request is still inflight.');
  ok(SC.Request.manager.isPending(response2), 'Second request is still pending.');
  ok(!SC.Request.manager.isInFlight(response2), 'Second request is not inflight.');
});

request2.notify('loadstart', this, function(evt) {
  ok(!SC.Request.manager.isPending(response), 'First request is not pending.');
  ok(!SC.Request.manager.isInFlight(response), 'First request is no longer inflight.');
  ok(!SC.Request.manager.isPending(response2), 'Second request is no longer pending.');
  ok(SC.Request.manager.isInFlight(response2), 'Second request is now inflight.');
});

request2.notify('progress', this, function(evt) {
  ok(!SC.Request.manager.isPending(response), 'First request is not pending.');
  ok(!SC.Request.manager.isInFlight(response), 'First request is not inflight.');
  ok(!SC.Request.manager.isPending(response2), 'Second request is not pending.');
  ok(SC.Request.manager.isInFlight(response2), 'Second request is still inflight.');
});

request2.notify('loadend', this, function(evt) {
  ok(!SC.Request.manager.isPending(response), 'First request is not pending.');
  ok(!SC.Request.manager.isInFlight(response), 'First request is not inflight.');
  ok(!SC.Request.manager.isPending(response2), 'Second request is not pending.');
  ok(SC.Request.manager.isInFlight(response2), 'Second request is still inflight.');
});

request2.notify(this, function (evt) {
  ok(!SC.Request.manager.isPending(response), 'First request is not pending.');
  ok(!SC.Request.manager.isInFlight(response), 'First request is not inflight.');
  ok(!SC.Request.manager.isPending(response2), 'Second request is not pending.');
  ok(SC.Request.manager.isInFlight(response2), 'Second request is still inflight.');

  SC.Request.manager.set('maxRequests', prevMaxRequests);
  window.start();
});

response = request.send();
response2 = request2.send();

ok(SC.Request.manager.isPending(response), 'First request is pending.');
ok(!SC.Request.manager.isInFlight(response), 'First request is not inflight.');
ok(SC.Request.manager.isPending(response2), 'Second request is pending.');
ok(!SC.Request.manager.isInFlight(response2), 'Second request is not inflight.');

SC.Request.manager.set('maxRequests', 1);
SC.Request.manager.fireRequestIfNeeded();

ok(!SC.Request.manager.isPending(response), 'First request is no longer pending.');
ok(SC.Request.manager.isInFlight(response), 'First request is inflight.');
ok(SC.Request.manager.isPending(response2), 'Second request is still pending.');
ok(!SC.Request.manager.isInFlight(response2), 'Second request is not inflight.');

stop();

});