DEP 0003 -- Added JavaScript unit tests.

Setup QUnit, added tests, and measured test coverage.

Thanks to Nick Sanford for the initial tests.
This commit is contained in:
Trey Hunner 2015-04-14 10:55:57 -04:00 committed by Tim Graham
parent 3bbaf84d65
commit 2d0dead224
19 changed files with 4716 additions and 2 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ node_modules/
tests/coverage_html/
tests/.coverage
build/
tests/report/

20
Gruntfile.js Normal file
View File

@ -0,0 +1,20 @@
var globalThreshold = 50; // Global code coverage threshold (as a percentage)
module.exports = function(grunt) {
grunt.initConfig({
// Configuration to be run (and then tested).
blanket_qunit: {
default_options: {
options: {
urls: ['js_tests/tests.html?coverage=true&gruntReport'],
globalThreshold: globalThreshold,
threshold: 10
}
}
}
});
grunt.loadNpmTasks('grunt-blanket-qunit');
grunt.registerTask('test', ['blanket_qunit']);
grunt.registerTask('default', ['test']);
};

View File

@ -58,7 +58,85 @@ independently. The Closure Compiler library requires `Java`_ 7 or higher.
Please don't forget to run ``compress.py`` and include the ``diff`` of the
minified scripts when submitting patches for Django's JavaScript.
JavaScript tests
----------------
Django's JavaScript tests can be run in a browser or from the command line.
The tests are located in a top level ``js_tests`` directory.
Writing tests
~~~~~~~~~~~~~
Django's JavaScript tests use `QUnit`_. Here is an example test module:
.. code-block:: javascript
module('magicTricks', {
beforeEach: function() {
var $ = django.jQuery;
$('#qunit-fixture').append('<button class="button"></button>');
}
});
test('removeOnClick removes button on click', function(assert) {
var $ = django.jQuery;
removeOnClick('.button');
assert.equal($('.button').length === 1);
$('.button').click();
assert.equal($('.button').length === 0);
});
test('copyOnClick adds button on click', function(assert) {
var $ = django.jQuery;
copyOnClick('.button');
assert.equal($('.button').length === 1);
$('.button').click();
assert.equal($('.button').length === 2);
});
Please consult the QUnit documentation for information on the types of
`assertions supported by QUnit <https://api.qunitjs.com/category/assert/>`_.
Running tests
~~~~~~~~~~~~~
The JavaScript tests may be run from a web browser or from the command line.
Testing from a web browser
^^^^^^^^^^^^^^^^^^^^^^^^^^
To run the tests from a web browser, open up ``js_tests/tests.html`` in your
browser.
To measure code coverage when running the tests, you need to view that file
over HTTP. To view code coverage:
* Execute ``python -m http.server`` (or ``python -m SimpleHTTPServer`` on
Python 2) from the root directory (not from inside ``js_tests``).
* Open http://localhost:8000/js_tests/tests.html in your web browser.
Testing from the command line
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To run the tests from the command line, you need to have `Node.js`_ installed.
After installing `Node.js`, install the JavaScript test dependencies by running
the following from the root of your Django checkout:
.. code-block:: console
$ npm install
Then run the tests with:
.. code-block:: console
$ npm test
.. _Closure Compiler: https://developers.google.com/closure/compiler/
.. _EditorConfig: http://editorconfig.org/
.. _Java: https://www.java.com
.. _jshint: http://jshint.com/
.. _node.js: https://nodejs.org/
.. _qunit: https://qunitjs.com/

View File

@ -621,6 +621,7 @@ Querysets
querystring
queueing
Quickstart
QUnit
quo
quoteless
Radziej

View File

@ -0,0 +1,15 @@
module('admin.DateTimeShortcuts');
test('init', function(assert) {
var $ = django.jQuery;
var dateField = $('<input type="text" class="vDateField" value="2015-03-16"><br>');
$('#qunit-fixture').append(dateField);
DateTimeShortcuts.init();
var shortcuts = $('.datetimeshortcuts');
assert.equal(shortcuts.length, 1);
assert.equal(shortcuts.find('a:first').text(), 'Today');
assert.equal(shortcuts.find('a:last .date-icon').length, 1);
});

View File

@ -0,0 +1,22 @@
module('admin.RelatedObjectLookups');
test('html_unescape', function(assert) {
function assert_unescape(then, expected, message) {
assert.equal(html_unescape(then), expected, message);
}
assert_unescape('&lt;', '<', 'less thans are unescaped');
assert_unescape('&gt;', '>', 'greater thans are unescaped');
assert_unescape('&quot;', '"', 'double quotes are unescaped');
assert_unescape('&#39;', "'", 'single quotes are unescaped');
assert_unescape('&amp;', '&', 'ampersands are unescaped');
});
test('id_to_windowname', function(assert) {
assert.equal(id_to_windowname('.test'), '__dot__test');
assert.equal(id_to_windowname('misc-test'), 'misc__dash__test');
});
test('windowname_to_id', function(assert) {
assert.equal(windowname_to_id('__dot__test'), '.test');
assert.equal(windowname_to_id('misc__dash__test'), 'misc-test');
});

View File

@ -0,0 +1,20 @@
module('admin.SelectBox');
test('init: no options', function(assert) {
var $ = django.jQuery;
$('<select id="id"></select>').appendTo('#qunit-fixture');
SelectBox.init('id');
assert.equal(SelectBox.cache['id'].length, 0);
});
test('filter', function(assert) {
var $ = django.jQuery;
$('<select id="id"></select>').appendTo('#qunit-fixture');
$('<option value="0">A</option>').appendTo('#id');
$('<option value="1">B</option>').appendTo('#id');
SelectBox.init('id');
assert.equal($('#id option').length, 2);
SelectBox.filter('id', "A");
assert.equal($('#id option').length, 1);
assert.equal($('#id option').text(), "A");
});

View File

@ -0,0 +1,14 @@
module('admin.SelectFilter2');
test('init', function(assert) {
var $ = django.jQuery;
$('<form><select id="id"></select></form>').appendTo('#qunit-fixture');
$('<option value="0">A</option>').appendTo('#id');
SelectFilter.init('id', 'things', 0);
assert.equal($('.selector-available h2').text().trim(), "Available things");
assert.equal($('.selector-chosen h2').text().trim(), "Chosen things");
assert.equal($('.selector-chooseall').text(), "Choose all");
assert.equal($('.selector-add').text(), "Choose");
assert.equal($('.selector-remove').text(), "Remove");
assert.equal($('.selector-clearall').text(), "Remove all");
});

View File

@ -0,0 +1,18 @@
module('admin.actions', {
beforeEach: function() {
// Number of results shown on page
window._actions_icnt = '100';
var $ = django.jQuery;
$('#qunit-fixture').append($('#result-table').text());
$('tr input.action-select').actions();
}
});
test('check', function(assert) {
var $ = django.jQuery;
assert.notOk($('.action-select').is(':checked'));
$('#action-toggle').click();
assert.ok($('.action-select').is(':checked'));
});

View File

@ -0,0 +1,59 @@
module('admin.core');
test('Date.getTwelveHours', function(assert) {
assert.equal(new Date(2011, 0, 1, 0, 0).getTwelveHours(), 12, '0:00');
assert.equal(new Date(2011, 0, 1, 11, 0).getTwelveHours(), 11, '11:00');
assert.equal(new Date(2011, 0, 1, 16, 0).getTwelveHours(), 4, '16:00');
});
test('Date.getTwoDigitMonth', function(assert) {
assert.equal(new Date(2011, 0, 1).getTwoDigitMonth(), '01', 'jan 1');
assert.equal(new Date(2011, 9, 1).getTwoDigitMonth(), '10', 'oct 1');
});
test('Date.getTwoDigitDate', function(assert) {
assert.equal(new Date(2011, 0, 1).getTwoDigitDate(), '01', 'jan 1');
assert.equal(new Date(2011, 0, 15).getTwoDigitDate(), '15', 'jan 15');
});
test('Date.getTwoDigitTwelveHour', function(assert) {
assert.equal(new Date(2011, 0, 1, 0, 0).getTwoDigitTwelveHour(), '12', '0:00');
assert.equal(new Date(2011, 0, 1, 4, 0).getTwoDigitTwelveHour(), '04', '4:00');
assert.equal(new Date(2011, 0, 1, 22, 0).getTwoDigitTwelveHour(), '10', '22:00');
});
test('Date.getTwoDigitHour', function(assert) {
assert.equal(new Date(2014, 6, 1, 9, 0).getTwoDigitHour(), '09', '9:00 am is 09');
assert.equal(new Date(2014, 6, 1, 11, 0).getTwoDigitHour(), '11', '11:00 am is 11');
});
test('Date.getTwoDigitMinute', function(assert) {
assert.equal(new Date(2014, 6, 1, 0, 5).getTwoDigitMinute(), '05', '12:05 am is 05');
assert.equal(new Date(2014, 6, 1, 0, 15).getTwoDigitMinute(), '15', '12:15 am is 15');
});
test('Date.getTwoDigitSecond', function(assert) {
assert.equal(new Date(2014, 6, 1, 0, 0, 2).getTwoDigitSecond(), '02', '12:00:02 am is 02');
assert.equal(new Date(2014, 6, 1, 0, 0, 20).getTwoDigitSecond(), '20', '12:00:20 am is 20');
});
test('Date.getHourMinute', function(assert) {
assert.equal(new Date(2014, 6, 1, 11, 0).getHourMinute(), '11:00', '11:00 am is 11:00');
assert.equal(new Date(2014, 6, 1, 13, 25).getHourMinute(), '13:25', '1:25 pm is 13:25');
});
test('Date.getHourMinuteSecond', function(assert) {
assert.equal(new Date(2014, 6, 1, 11, 0, 0).getHourMinuteSecond(), '11:00:00', '11:00 am is 11:00:00');
assert.equal(new Date(2014, 6, 1, 17, 45, 30).getHourMinuteSecond(), '17:45:30', '5:45:30 pm is 17:45:30');
});
test('Date.strftime', function(assert) {
var date = new Date(2014, 6, 1, 11, 0, 5);
assert.equal(date.strftime('%Y-%m-%d %H:%M:%S'), '2014-07-01 11:00:05');
});
test('String.strptime', function(assert) {
var date = new Date(1988, 1, 26);
assert.equal('1988-02-26'.strptime('%Y-%m-%d').toString(), date.toString());
assert.equal('26/02/88'.strptime('%d/%m/%y').toString(), date.toString());
});

View File

@ -0,0 +1,28 @@
module('admin.inlines: tabular formsets', {
beforeEach: function() {
var $ = django.jQuery;
var that = this;
this.addText = 'Add another';
$('#qunit-fixture').append($('#tabular-formset').text());
this.table = $('table.inline');
this.inlineRow = this.table.find('tr');
that.inlineRow.tabularFormset({
prefix: 'first',
addText: that.addText,
deleteText: 'Remove'
});
}
});
test('no forms', function(assert) {
assert.ok(this.inlineRow.hasClass('dynamic-first'));
assert.equal(this.table.find('.add-row a').text(), this.addText);
});
test('add form', function(assert) {
var addButton = this.table.find('.add-row a');
assert.equal(addButton.text(), this.addText);
addButton.click();
assert.ok(this.table.find('#first-1').hasClass('row2'));
});

View File

@ -0,0 +1,82 @@
(function (globals) {
var django = globals.django || (globals.django = {});
django.pluralidx = function (count) { return (count == 1) ? 0 : 1; };
/* gettext identity library */
django.gettext = function (msgid) { return msgid; };
django.ngettext = function (singular, plural, count) { return (count == 1) ? singular : plural; };
django.gettext_noop = function (msgid) { return msgid; };
django.pgettext = function (context, msgid) { return msgid; };
django.npgettext = function (context, singular, plural, count) { return (count == 1) ? singular : plural; };
django.interpolate = function (fmt, obj, named) {
if (named) {
return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
} else {
return fmt.replace(/%s/g, function(match){return String(obj.shift())});
}
};
/* formatting library */
django.formats = {
"DATETIME_FORMAT": "N j, Y, P",
"DATETIME_INPUT_FORMATS": [
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d %H:%M:%S.%f",
"%Y-%m-%d %H:%M",
"%Y-%m-%d",
"%m/%d/%Y %H:%M:%S",
"%m/%d/%Y %H:%M:%S.%f",
"%m/%d/%Y %H:%M",
"%m/%d/%Y",
"%m/%d/%y %H:%M:%S",
"%m/%d/%y %H:%M:%S.%f",
"%m/%d/%y %H:%M",
"%m/%d/%y"
],
"DATE_FORMAT": "N j, Y",
"DATE_INPUT_FORMATS": [
"%Y-%m-%d",
"%m/%d/%Y",
"%m/%d/%y"
],
"DECIMAL_SEPARATOR": ".",
"FIRST_DAY_OF_WEEK": "0",
"MONTH_DAY_FORMAT": "F j",
"NUMBER_GROUPING": "3",
"SHORT_DATETIME_FORMAT": "m/d/Y P",
"SHORT_DATE_FORMAT": "m/d/Y",
"THOUSAND_SEPARATOR": ",",
"TIME_FORMAT": "P",
"TIME_INPUT_FORMATS": [
"%H:%M:%S",
"%H:%M:%S.%f",
"%H:%M"
],
"YEAR_MONTH_FORMAT": "F Y"
};
django.get_format = function (format_type) {
var value = django.formats[format_type];
if (typeof(value) == 'undefined') {
return format_type;
} else {
return value;
}
};
/* add to global namespace */
globals.pluralidx = django.pluralidx;
globals.gettext = django.gettext;
globals.ngettext = django.ngettext;
globals.gettext_noop = django.gettext_noop;
globals.pgettext = django.pgettext;
globals.npgettext = django.npgettext;
globals.interpolate = django.interpolate;
globals.get_format = django.get_format;
}(this));

View File

@ -0,0 +1,28 @@
module('admin.timeparse');
test('parseTimeString', function(assert) {
function time(then, expected) {
assert.equal(parseTimeString(then), expected);
}
time('9', '09:00');
time('09', '09:00');
time('13:00', '13:00');
time('13.00', '13:00');
time('9:00', '09:00');
time('9.00', '09:00');
time('3 am', '03:00');
time('3 a.m.', '03:00');
time('12 am', '00:00');
time('11 am', '11:00');
time('12 pm', '12:00');
time('3am', '03:00');
time('3.30 am', '03:30');
time('3:15 a.m.', '03:15');
time('3.00am', '03:00');
time('12.00am', '00:00');
time('11.00am', '11:00');
time('12.00pm', '12:00');
time('noon', '12:00');
time('midnight', '00:00');
time('something else', 'something else');
});

39
js_tests/qunit/blanket.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,78 @@
// grunt-reporter.js
//
// A communication bridge between blanket.js and the grunt-blanket-qunit plugin
// Distributed as part of the grunt-blanket-qunit library
//
// Copyright (C) 2013 Model N, Inc.
// Distributed under the MIT License
//
// Documentation and full license available at:
// https://github.com/ModelN/grunt-blanket-qunit
//
(function (){
"use strict";
// this is an ugly hack, but it's the official way of communicating between
// the parent phantomjs and the inner grunt-contrib-qunit library...
var sendMessage = function sendMessage() {
var args = [].slice.call(arguments);
alert(JSON.stringify(args));
};
// helper function for computing coverage info for a particular file
var reportFile = function( data ) {
var ret = {
coverage: 0,
hits: 0,
misses: 0,
sloc: 0
};
for (var i = 0; i < data.source.length; i++) {
var line = data.source[i];
var num = i + 1;
if (data[num] === 0) {
ret.misses++;
ret.sloc++;
} else if (data[num] !== undefined) {
ret.hits++;
ret.sloc++;
}
}
ret.coverage = ret.hits / ret.sloc * 100;
return [ret.hits,ret.sloc];
};
// this function is invoked by blanket.js when the coverage data is ready. it will
// compute per-file coverage info, and send a message to the parent phantomjs process
// for each file, which the grunt task will use to report passes & failures.
var reporter = function(cov){
cov = window._$blanket;
var sortedFileNames = [];
var totals =[];
for (var filename in cov) {
if (cov.hasOwnProperty(filename)) {
sortedFileNames.push(filename);
}
}
sortedFileNames.sort();
for (var i = 0; i < sortedFileNames.length; i++) {
var thisFile = sortedFileNames[i];
var data = cov[thisFile];
var thisTotal= reportFile( data );
sendMessage("blanket:fileDone", thisTotal, thisFile);
}
sendMessage("blanket:done");
};
blanket.customReporter = reporter;
})();

291
js_tests/qunit/qunit.css Normal file
View File

@ -0,0 +1,291 @@
/*!
* QUnit 1.18.0
* http://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2015-04-03T10:23Z
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699A4;
background-color: #0D3349;
font-size: 1.5em;
line-height: 1em;
font-weight: 400;
border-radius: 5px 5px 0 0;
}
#qunit-header a {
text-decoration: none;
color: #C2CCD1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #FFF;
}
#qunit-testrunner-toolbar label {
display: inline-block;
padding: 0 0.5em 0 0.1em;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 1em 0.5em 1em;
color: #5E740B;
background-color: #EEE;
overflow: hidden;
}
#qunit-userAgent {
padding: 0.5em 1em 0.5em 1em;
background-color: #2B81AF;
color: #FFF;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
#qunit-modulefilter-container {
float: right;
padding: 0.2em;
}
.qunit-url-config {
display: inline-block;
padding: 0.1em;
}
.qunit-filter {
display: block;
float: right;
margin-left: 1em;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 1em 0.4em 1em;
border-bottom: 1px solid #FFF;
list-style-position: inside;
}
#qunit-tests > li {
display: none;
}
#qunit-tests li.running,
#qunit-tests li.pass,
#qunit-tests li.fail,
#qunit-tests li.skipped {
display: list-item;
}
#qunit-tests.hidepass li.running,
#qunit-tests.hidepass li.pass {
visibility: hidden;
position: absolute;
width: 0px;
height: 0px;
padding: 0;
border: 0;
margin: 0;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li.skipped strong {
cursor: default;
}
#qunit-tests li a {
padding: 0.5em;
color: #C2CCD1;
text-decoration: none;
}
#qunit-tests li p a {
padding: 0.25em;
color: #6B6464;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests li .runtime {
float: right;
font-size: smaller;
}
.qunit-assert-list {
margin-top: 0.5em;
padding: 0.5em;
background-color: #FFF;
border-radius: 5px;
}
.qunit-collapsed {
display: none;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: 0.2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 0.5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #E0F2BE;
color: #374E0C;
text-decoration: none;
}
#qunit-tests ins {
background-color: #FFCACA;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: #000; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
padding: 5px;
background-color: #FFF;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #3C510C;
background-color: #FFF;
border-left: 10px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #FFF;
border-left: 10px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 5px 5px;
}
#qunit-tests .fail { color: #000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: #008000; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/*** Skipped tests */
#qunit-tests .skipped {
background-color: #EBECE9;
}
#qunit-tests .qunit-skipped-label {
background-color: #F4FF77;
display: inline-block;
font-style: normal;
color: #366097;
line-height: 1.8em;
padding: 0 0.5em;
margin: -0.4em 0.4em -0.4em 0;
}
/** Result */
#qunit-testresult {
padding: 0.5em 1em 0.5em 1em;
color: #2B81AF;
background-color: #D2E0E6;
border-bottom: 1px solid #FFF;
}
#qunit-testresult .module-name {
font-weight: 700;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
width: 1000px;
height: 1000px;
}

3828
js_tests/qunit/qunit.js Normal file

File diff suppressed because it is too large Load Diff

88
js_tests/tests.html Normal file
View File

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Django JavaScript Tests</title>
<link rel="stylesheet" href="./qunit/qunit.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture">
</div>
<script type="text/html" id="result-table">
<table id="result_list">
<tr>
<th>
<input type="checkbox" id="action-toggle">
</th>
</tr>
<tr>
<td class="action-checkbox">
<input class="action-select" type="checkbox" value="618">
</td>
</tr>
</table>
</script>
<script type="text/html" id="tabular-formset">
<input id="id_first-TOTAL_FORMS" value="1">
<input id="id_first-MAX_NUM_FORMS" value="">
<table class="inline">
<tr id="first-0" class="form-row">
<td class="field-test_field">
<input id="id_first-test_field">
</td>
</tr>
<tr id="first-empty" class="empty-row">
<td class="field-test_field">
<input id="id_first-test_field">
</td>
</tr>
</table>
</script>
<script src="./qunit/qunit.js"></script>
<script src="./qunit/blanket.min.js" data-cover-flags="branchTracking"></script>
<script src='../django/contrib/admin/static/admin/js/vendor/jquery/jquery.min.js'></script>
<script src='../django/contrib/admin/static/admin/js/jquery.init.js'></script>
<script src='./admin/jsi18n-mocks.test.js'></script>
<script src='../django/contrib/admin/static/admin/js/core.js' data-cover></script>
<script src='./admin/core.test.js'></script>
<script src='../django/contrib/admin/static/admin/js/timeparse.js' data-cover></script>
<script src='./admin/timeparse.test.js'></script>
<script src='../django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js' data-cover></script>
<script src='./admin/RelatedObjectLookups.test.js'></script>
<script src='./admin/DateTimeShortcuts.test.js'></script>
<script src='../django/contrib/admin/static/admin/js/calendar.js' data-cover></script>
<script src='../django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js' data-cover></script>
<script src='../django/contrib/admin/static/admin/js/actions.js' data-cover></script>
<script src='./admin/actions.test.js'></script>
<script src='../django/contrib/admin/static/admin/js/SelectBox.js' data-cover></script>
<script src='./admin/SelectBox.test.js'></script>
<script src='../django/contrib/admin/static/admin/js/SelectFilter2.js' data-cover></script>
<script src='./admin/SelectFilter2.test.js'></script>
<script src='../django/contrib/admin/static/admin/js/inlines.js' data-cover></script>
<script src='./admin/inlines.test.js'></script>
<script src='../django/contrib/admin/static/admin/js/actions.js' data-cover></script>
<script src='../django/contrib/admin/static/admin/js/collapse.js' data-cover></script>
<script src='../django/contrib/admin/static/admin/js/prepopulate.js' data-cover></script>
<script src='../django/contrib/admin/static/admin/js/urlify.js' data-cover></script>
<script>
if (location.href.match(/(\?|&)gruntReport($|&|=)/)) {
blanket.options("reporter", "qunit/grunt-reporter.js");
}
</script>
</body>
</html>

View File

@ -2,9 +2,13 @@
"name": "Django",
"private": true,
"scripts": {
"pretest": "eslint django/"
"pretest": "eslint django/",
"test": "grunt test --verbose"
},
"devDependencies": {
"eslint": "^0.22.1"
"eslint": "^0.22.1",
"grunt": "^0.4.5",
"grunt-blanket-qunit": "^0.2.0",
"grunt-cli": "^0.1.13"
}
}