Skip to content

Commit

Permalink
Added test framework and test for studio.js
Browse files Browse the repository at this point in the history
  • Loading branch information
George Schneeloch committed Aug 28, 2015
1 parent 9894c1a commit f73d503
Show file tree
Hide file tree
Showing 4 changed files with 349 additions and 0 deletions.
30 changes: 30 additions & 0 deletions edx_sga/static/js/spec/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
(function(requirejs, define) {
requirejs.config({
paths: {
'jquery': 'js/bower/jquery/dist/jquery',
'sinon': 'js/bower/sinon/lib/sinon',
'URI': 'js/bower/URIjs/src/URI',
'underscore': 'js/bower/underscore/underscore',
'IPv6': 'js/bower/URIjs/src/IPv6',
'punycode': 'js/bower/URIjs/src/punycode',
'SecondLevelDomains': 'js/bower/URIjs/src/SecondLevelDomains'
},
shim: {
'underscore': {
exports: "_"
},
'sinon': {
exports: "sinon"
},
'URI': {
exports: 'URI',
deps: ['sinon']
}
}
});

define("main", [
'js/spec/test_studio'
]);

})(requirejs, define);
52 changes: 52 additions & 0 deletions edx_sga/static/js/spec/test_studio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
define(["js/spec_helpers/ajax_helpers", "js/src/studio"], function(AjaxHelpers) {
'use strict';
describe("studio.js", function() {
describe("StaffGradedAssignmentXBlock", function() {
it("saves the view", function() {
// Spy on Ajax requests
var requests = AjaxHelpers.requests(this);

// Mock some arguments
var fakeUrl = "/test_url/";
var element = $("<div>" +
"<input type='hidden' name='one' value='1' />" +
"<input type='hidden' name='two' value='2' />" +
"<input type='hidden' name='three' value='3' /></div>");
var server = null;

var runtime = {
handlerUrl: function() {
return fakeUrl;
},
notify: function(type, state) {
notifyStates[type] = state;
}
};

var XBlock = StaffGradedAssignmentXBlock(runtime, element, server);
// Function expects this.runtime to exist
XBlock.save = XBlock.save.bind({runtime: runtime});

var notifyStates = {};

// Execute the save
XBlock.save();

// Verify the request was made
AjaxHelpers.expectRequest(
requests, 'POST', fakeUrl, JSON.stringify({
one: "1",
two: "2",
three: "3"
})
);

// Complete ajax request
AjaxHelpers.respondWithJson(requests, {});

expect(notifyStates.save.state).toBe('end');
});

});
});
});
169 changes: 169 additions & 0 deletions edx_sga/static/js/spec_helpers/ajax_helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// NOTE: copied from https://github.com/edx/edx-platform/blob/master/common/static/common/js/spec_helpers/ajax_helpers.js
define(['sinon', 'underscore', 'URI'], function(sinon, _, URI) {
'use strict';

var fakeServer, fakeRequests, expectRequest, expectJsonRequest, expectPostRequest, expectJsonRequestURL,
respondWithJson, respondWithError, respondWithTextError, respondWithNoContent;

/* These utility methods are used by Jasmine tests to create a mock server or
* get reference to mock requests. In either case, the cleanup (restore) is done with
* an after function.
*
* This pattern is being used instead of the more common beforeEach/afterEach pattern
* because we were seeing sporadic failures in the afterEach restore call. The cause of the
* errors were that one test suite was incorrectly being linked as the parent of an unrelated
* test suite (causing both suites' afterEach methods to be called). No solution for the root
* cause has been found, but initializing sinon and cleaning it up on a method-by-method
* basis seems to work. For more details, see STUD-1264.
*/

/**
* Get a reference to the mocked server, and respond
* to all requests with the specified statusCode.
*/
fakeServer = function (that, response) {
var server = sinon.fakeServer.create();
that.after(function() {
server.restore();
});
server.respondWith(response);
return server;
};

/**
* Keep track of all requests to a fake server, and
* return a reference to the Array. This allows tests
* to respond for individual requests.
*/
fakeRequests = function (that) {
console.log(sinon);
var requests = [],
xhr = sinon.useFakeXMLHttpRequest();
xhr.onCreate = function(request) {
requests.push(request);
};

that.after(function() {
xhr.restore();
});

return requests;
};

expectRequest = function(requests, method, url, body, requestIndex) {
var request;
if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1;
}
request = requests[requestIndex];
expect(request.url).toEqual(url);
expect(request.method).toEqual(method);
expect(request.requestBody).toEqual(body);
};

expectJsonRequest = function(requests, method, url, jsonRequest, requestIndex) {
var request;
if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1;
}
request = requests[requestIndex];
expect(request.url).toEqual(url);
expect(request.method).toEqual(method);
expect(JSON.parse(request.requestBody)).toEqual(jsonRequest);
};

/**
* Expect that a JSON request be made with the given URL and parameters.
* @param requests The collected requests
* @param expectedUrl The expected URL excluding the parameters
* @param expectedParameters An object representing the URL parameters
* @param requestIndex An optional index for the request (by default, the last request is used)
*/
expectJsonRequestURL = function(requests, expectedUrl, expectedParameters, requestIndex) {
var request, parameters;
if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1;
}
request = requests[requestIndex];
expect(new URI(request.url).path()).toEqual(expectedUrl);
parameters = new URI(request.url).query(true);
delete parameters._; // Ignore the cache-busting argument
expect(parameters).toEqual(expectedParameters);
};

/**
* Intended for use with POST requests using application/x-www-form-urlencoded.
*/
expectPostRequest = function(requests, url, body, requestIndex) {
var request;
if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1;
}
request = requests[requestIndex];
expect(request.url).toEqual(url);
expect(request.method).toEqual("POST");
expect(_.difference(request.requestBody.split('&'), body.split('&'))).toEqual([]);
};

respondWithJson = function(requests, jsonResponse, requestIndex) {
if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1;
}
requests[requestIndex].respond(200,
{ 'Content-Type': 'application/json' },
JSON.stringify(jsonResponse));
};

respondWithError = function(requests, statusCode, jsonResponse, requestIndex) {
if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1;
}
if (_.isUndefined(statusCode)) {
statusCode = 500;
}
if (_.isUndefined(jsonResponse)) {
jsonResponse = {};
}
requests[requestIndex].respond(statusCode,
{ 'Content-Type': 'application/json' },
JSON.stringify(jsonResponse)
);
};

respondWithTextError = function(requests, statusCode, textResponse, requestIndex) {
if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1;
}
if (_.isUndefined(statusCode)) {
statusCode = 500;
}
if (_.isUndefined(textResponse)) {
textResponse = "";
}
requests[requestIndex].respond(statusCode,
{ 'Content-Type': 'text/plain' },
textResponse
);
};

respondWithNoContent = function(requests, requestIndex) {
if (_.isUndefined(requestIndex)) {
requestIndex = requests.length - 1;
}
requests[requestIndex].respond(204,
{ 'Content-Type': 'application/json' });
};

return {
'server': fakeServer,
'requests': fakeRequests,
'expectRequest': expectRequest,
'expectJsonRequest': expectJsonRequest,
'expectJsonRequestURL': expectJsonRequestURL,
'expectPostRequest': expectPostRequest,
'respondWithJson': respondWithJson,
'respondWithError': respondWithError,
'respondWithTextError': respondWithTextError,
'respondWithNoContent': respondWithNoContent,
};
});
98 changes: 98 additions & 0 deletions edx_sga/static/js_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
# JavaScript test suite description
#
#
# To run all the tests and print results to the console:
#
# js-test-tool run TEST_SUITE --use-firefox
#
# where `TEST_SUITE` is this file.
#
#
# To run the tests in your default browser ("dev mode"):
#
# js-test-tool dev TEST_SUITE
#

# Name of the test suite, used to construct
# the URL from which pages are served.
#
# For example, if the suite name is "test_suite",
# then:
#
# * /suite/test_suite
# serves the test suite runner page
# * /suite/test_suite/include/*
# serves dependencies (src, spec, lib, and fixtures)
#
# Test suite names must be URL-encodable and unique
# among suite descriptions passed to js-test-tool
test_suite_name: edx_sga

# Currently, the only supported test runner is Jasmine
# See http://pivotal.github.io/jasmine/
# for the Jasmine documentation.
test_runner: jasmine_requirejs

# Path prepended to source files in the coverage report (optional)
# For example, if the source path
# is "src/source.js" (relative to this YAML file)
# and the prepend path is "base/dir"
# then the coverage report will show
# "base/dir/src/source.js"
prepend_path: static

# Paths to library JavaScript files (optional)
lib_paths:
- js/bower

# Paths to source JavaScript files
src_paths:
- js/src

# Paths to spec (test) JavaScript files
spec_paths:
- js/spec
- js/spec_helpers

# Paths to fixture files (optional)
# The fixture path will be set automatically when using jasmine-jquery.
# (https://github.com/velesin/jasmine-jquery)
#
# You can then access fixtures using paths relative to
# the test suite description:
#
# loadFixtures('path/to/fixture/fixture.html');
#
#fixture_paths:
# - js/fixture

# Regular expressions used to exclude *.js files from
# appearing in the test runner page.
# Some test runners (like the jasmine runner) include files by default,
# which means that they are loaded using a <script> tag in the test
# runner page. When loading many files, this can be slow, so
# exclude any files you don't need.
# Note that not all runners behave the same way: for example, the
# jasmine_requirejs runner does not include files using <script> tags
# by default, and so will ignore this directive.
#exclude_from_page:
# - js/lib/

# Regular expression used to guarantee that a *.js file
# is included in the test runner page.
# If a file name matches both `exclude_from_page` and
# `include_in_page`, the file WILL be included.
# You can use this to exclude all files in a directory,
# but make an exception for particular files.
#include_in_page:
# - js/lib/exclude/exception_.*\.js

requirejs:
paths:
main: js/spec/main

# Because require.js is responsible for loading all dependencies, we exclude
# all files from being included in <script> tags
exclude_from_page:
- .*

0 comments on commit f73d503

Please sign in to comment.