449 lines
11 KiB
JavaScript
449 lines
11 KiB
JavaScript
|
var passCount = 0;
|
||
|
var failCount = 0;
|
||
|
var testCount = 0;
|
||
|
|
||
|
$(function()
|
||
|
{
|
||
|
// First, wireup!
|
||
|
$('.results').on('click', 'td.rvl', function()
|
||
|
{
|
||
|
var tr = $(this).closest('tr');
|
||
|
if (tr.hasClass('collapsed'))
|
||
|
{
|
||
|
$('.revealer', tr).hide();
|
||
|
$('.hidden', tr).show();
|
||
|
$(this).html("-");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$('.revealer', tr).show();
|
||
|
$('.hidden', tr).hide();
|
||
|
$(this).html("+");
|
||
|
}
|
||
|
tr.toggleClass('collapsed expanded');
|
||
|
});
|
||
|
|
||
|
$('.expand-all').click(function()
|
||
|
{
|
||
|
var $testGroup = $(this).closest('.test-group');
|
||
|
$('.collapsed .rvl', $testGroup).click();
|
||
|
});
|
||
|
|
||
|
$('.collapse-all').click(function()
|
||
|
{
|
||
|
var $testGroup = $(this).closest('.test-group');
|
||
|
$('.expanded .rvl', $testGroup).click();
|
||
|
});
|
||
|
|
||
|
function asyncDone()
|
||
|
{
|
||
|
// Finally, show the overall status.
|
||
|
if (failCount == 0)
|
||
|
$('#status').addClass('status-pass').html("All <b>" + passCount + "</b> test" + (passCount == 1 ? "" : "s") + " passed");
|
||
|
else
|
||
|
$('#status').addClass('status-fail').html("<b>" + failCount + "</b> test" + (failCount == 1 ? "" : "s") + " failed; <b>" + passCount + "</b> passed");
|
||
|
}
|
||
|
|
||
|
// Next, run tests and render results!
|
||
|
runCoreParserTests();
|
||
|
runParseTests(asyncDone);
|
||
|
runUnparseTests();
|
||
|
runCustomTests(asyncDone);
|
||
|
|
||
|
});
|
||
|
|
||
|
|
||
|
// Executes all tests in CORE_PARSER_TESTS from test-cases.js
|
||
|
// and renders results in the table.
|
||
|
function runCoreParserTests()
|
||
|
{
|
||
|
for (var i = 0; i < CORE_PARSER_TESTS.length; i++)
|
||
|
{
|
||
|
var test = CORE_PARSER_TESTS[i];
|
||
|
var passed = runTest(test);
|
||
|
if (passed)
|
||
|
passCount++;
|
||
|
else
|
||
|
failCount++;
|
||
|
}
|
||
|
|
||
|
function runTest(test)
|
||
|
{
|
||
|
var actual = new Papa.Parser(test.config).parse(test.input);
|
||
|
var results = compare(actual.data, actual.errors, test.expected);
|
||
|
displayResults('#tests-for-core-parser', test, actual, results);
|
||
|
return results.data.passed && results.errors.passed
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Executes all tests in PARSE_TESTS from test-cases.js
|
||
|
// and renders results in the table.
|
||
|
function runParseTests(asyncDone)
|
||
|
{
|
||
|
for (var i = 0; i < PARSE_TESTS.length; i++)
|
||
|
{
|
||
|
var test = PARSE_TESTS[i];
|
||
|
var passed = runTest(test);
|
||
|
if (passed)
|
||
|
passCount++;
|
||
|
else
|
||
|
failCount++;
|
||
|
}
|
||
|
|
||
|
var asyncRemaining = 0;
|
||
|
|
||
|
PARSE_ASYNC_TESTS.forEach(function(test)
|
||
|
{
|
||
|
if (test.disabled)
|
||
|
return;
|
||
|
asyncRemaining++;
|
||
|
var config = test.config;
|
||
|
config.complete = function(actual)
|
||
|
{
|
||
|
var results = compare(actual.data, actual.errors, test.expected);
|
||
|
|
||
|
displayResults("#tests-for-parse", test, actual, results);
|
||
|
|
||
|
if (results.data.passed && results.errors.passed) {
|
||
|
passCount++;
|
||
|
} else {
|
||
|
failCount++;
|
||
|
}
|
||
|
if (--asyncRemaining === 0) {
|
||
|
asyncDone();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
config.error = function(err)
|
||
|
{
|
||
|
failCount++;
|
||
|
displayResults("#tests-for-parse", test, {data:[],errors:err}, test.expected);
|
||
|
if (--asyncRemaining === 0) {
|
||
|
asyncDone();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Papa.parse(test.input, config);
|
||
|
});
|
||
|
|
||
|
|
||
|
function runTest(test)
|
||
|
{
|
||
|
var actual = Papa.parse(test.input, test.config);
|
||
|
var results = compare(actual.data, actual.errors, test.expected);
|
||
|
displayResults('#tests-for-parse', test, actual, results);
|
||
|
return results.data.passed && results.errors.passed
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
function displayResults(tableId, test, actual, results)
|
||
|
{
|
||
|
var testId = testCount++;
|
||
|
|
||
|
var testDescription = (test.description || "");
|
||
|
if (testDescription.length > 0)
|
||
|
testDescription += '<br>';
|
||
|
if (test.notes)
|
||
|
testDescription += '<span class="notes">' + test.notes + '</span>';
|
||
|
|
||
|
var tr = '<tr class="collapsed" id="test-'+testId+'">'
|
||
|
+ '<td class="rvl">+</td>'
|
||
|
+ '<td>' + testDescription + '</td>'
|
||
|
+ passOrFailTd(results.data)
|
||
|
+ passOrFailTd(results.errors)
|
||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">' + JSON.stringify(test.config, null, 2) + '</div></td>'
|
||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">' + revealChars(test.input) + '</div></td>'
|
||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">data: ' + JSON.stringify(test.expected.data, null, 4) + '\r\nerrors: ' + JSON.stringify(test.expected.errors, null, 4) + '</div></td>'
|
||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">data: ' + JSON.stringify(actual.data, null, 4) + '\r\nerrors: ' + JSON.stringify(actual.errors, null, 4) + '</div></td>'
|
||
|
+ '</tr>';
|
||
|
|
||
|
$(tableId+' .results').append(tr);
|
||
|
|
||
|
if (!results.data.passed || !results.errors.passed)
|
||
|
$('#test-'+testId+' td.rvl').click();
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
function compare(actualData, actualErrors, expected)
|
||
|
{
|
||
|
var data = compareData(actualData, expected.data);
|
||
|
var errors = compareErrors(actualErrors, expected.errors);
|
||
|
|
||
|
return {
|
||
|
data: data,
|
||
|
errors: errors
|
||
|
}
|
||
|
|
||
|
|
||
|
function compareData(actual, expected)
|
||
|
{
|
||
|
var passed = true;
|
||
|
|
||
|
if (actual.length != expected.length)
|
||
|
passed = false;
|
||
|
else
|
||
|
{
|
||
|
// The order is important, so we go through manually before using stringify to check everything else
|
||
|
for (var row = 0; row < expected.length; row++)
|
||
|
{
|
||
|
if (actual[row].length != expected[row].length)
|
||
|
{
|
||
|
passed = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
for (var col = 0; col < expected[row].length; col++)
|
||
|
{
|
||
|
var expectedVal = expected[row][col];
|
||
|
var actualVal = actual[row][col];
|
||
|
|
||
|
if (actualVal !== expectedVal)
|
||
|
{
|
||
|
passed = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (passed) // final check will catch any other differences
|
||
|
passed = JSON.stringify(actual) == JSON.stringify(expected);
|
||
|
|
||
|
// We pass back an object right now, even though it only contains
|
||
|
// one value, because we might add details to the test results later
|
||
|
// (same with compareErrors below)
|
||
|
return {
|
||
|
passed: passed
|
||
|
};
|
||
|
}
|
||
|
|
||
|
|
||
|
function compareErrors(actual, expected)
|
||
|
{
|
||
|
var passed = JSON.stringify(actual) == JSON.stringify(expected);
|
||
|
|
||
|
return {
|
||
|
passed: passed
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// Executes all tests in UNPARSE_TESTS from test-cases.js
|
||
|
// and renders results in the table.
|
||
|
function runUnparseTests()
|
||
|
{
|
||
|
for (var i = 0; i < UNPARSE_TESTS.length; i++)
|
||
|
{
|
||
|
var test = UNPARSE_TESTS[i];
|
||
|
var passed = runTest(test);
|
||
|
if (passed)
|
||
|
passCount++;
|
||
|
else
|
||
|
failCount++;
|
||
|
}
|
||
|
|
||
|
function runTest(test)
|
||
|
{
|
||
|
var actual;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
actual = Papa.unparse(test.input, test.config);
|
||
|
}
|
||
|
catch (e)
|
||
|
{
|
||
|
if (e instanceof Error) {
|
||
|
throw e;
|
||
|
}
|
||
|
actual = e;
|
||
|
}
|
||
|
|
||
|
var testId = testCount++;
|
||
|
var results = compare(actual, test.expected);
|
||
|
|
||
|
var testDescription = (test.description || "");
|
||
|
if (testDescription.length > 0)
|
||
|
testDescription += '<br>';
|
||
|
if (test.notes)
|
||
|
testDescription += '<span class="notes">' + test.notes + '</span>';
|
||
|
|
||
|
var tr = '<tr class="collapsed" id="test-'+testId+'">'
|
||
|
+ '<td class="rvl">+</td>'
|
||
|
+ '<td>' + testDescription + '</td>'
|
||
|
+ passOrFailTd(results)
|
||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">' + JSON.stringify(test.config, null, 2) + '</div></td>'
|
||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">' + JSON.stringify(test.input, null, 4) + '</div></td>'
|
||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">' + revealChars(test.expected) + '</div></td>'
|
||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">' + revealChars(actual) + '</div></td>'
|
||
|
+ '</tr>';
|
||
|
|
||
|
$('#tests-for-unparse .results').append(tr);
|
||
|
|
||
|
if (!results.passed)
|
||
|
$('#test-' + testId + ' td.rvl').click();
|
||
|
|
||
|
return results.passed;
|
||
|
}
|
||
|
|
||
|
|
||
|
function compare(actual, expected)
|
||
|
{
|
||
|
return {
|
||
|
passed: actual === expected
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// Executes all tests in CUSTOM_TESTS from test-cases.js
|
||
|
// and renders results in the table.
|
||
|
function runCustomTests(asyncDone)
|
||
|
{
|
||
|
var asyncRemaining = 0;
|
||
|
for (var i = 0; i < CUSTOM_TESTS.length; i++)
|
||
|
{
|
||
|
runTest(CUSTOM_TESTS[i]);
|
||
|
}
|
||
|
|
||
|
function runTest(test)
|
||
|
{
|
||
|
if (test.disabled)
|
||
|
return;
|
||
|
asyncRemaining++;
|
||
|
try
|
||
|
{
|
||
|
displayAsyncTest(test);
|
||
|
}
|
||
|
catch (e)
|
||
|
{
|
||
|
displayResults(test, e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function displayAsyncTest(test)
|
||
|
{
|
||
|
var testId = testCount++;
|
||
|
test.testId = testId;
|
||
|
|
||
|
var testDescription = (test.description || "");
|
||
|
if (testDescription.length > 0)
|
||
|
testDescription += '<br>';
|
||
|
if (test.notes)
|
||
|
testDescription += '<span class="notes">' + test.notes + '</span>';
|
||
|
|
||
|
var tr = '<tr class="collapsed" id="test-'+testId+'">'
|
||
|
+ '<td class="rvl">+</td>'
|
||
|
+ '<td>' + testDescription + '</td>'
|
||
|
+ '<td class="status pending">pending</td>'
|
||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden">' + test.expected + '</div></td>'
|
||
|
+ '<td class="revealable pre"><div class="revealer">condensed</div><div class="hidden actual"></div></td>'
|
||
|
+ '</tr>';
|
||
|
|
||
|
$('#custom-tests .results').append(tr);
|
||
|
|
||
|
test.run(function(actual)
|
||
|
{
|
||
|
displayAsyncResults(test, actual);
|
||
|
});
|
||
|
|
||
|
setTimeout(function()
|
||
|
{
|
||
|
if (test.complete) return;
|
||
|
displayAsyncResults(test, '(incomplete)');
|
||
|
}, 2000);
|
||
|
}
|
||
|
|
||
|
function displayAsyncResults(test, actual)
|
||
|
{
|
||
|
var testId = test.testId;
|
||
|
if (test.complete)
|
||
|
{
|
||
|
asyncRemaining++;
|
||
|
actual = '(multiple results from test)';
|
||
|
}
|
||
|
test.complete = true;
|
||
|
var results = compare(actual, test.expected);
|
||
|
|
||
|
var tr = $('#test-'+testId);
|
||
|
tr.find('.actual').text(actual);
|
||
|
|
||
|
var status = $(passOrFailTd(results));
|
||
|
var oldStatus = tr.find('.status');
|
||
|
oldStatus.attr('class', status.attr('class'));
|
||
|
oldStatus.text(status.text());
|
||
|
|
||
|
if (!results.passed)
|
||
|
$('#test-' + testId + ' td.rvl').click();
|
||
|
|
||
|
if (results.passed)
|
||
|
passCount++;
|
||
|
else
|
||
|
failCount++;
|
||
|
|
||
|
if (--asyncRemaining === 0)
|
||
|
asyncDone();
|
||
|
}
|
||
|
|
||
|
|
||
|
function compare(actual, expected)
|
||
|
{
|
||
|
return {
|
||
|
passed: JSON.stringify(actual) === JSON.stringify(expected)
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
// Makes a TD tag with OK or FAIL depending on test result
|
||
|
function passOrFailTd(result)
|
||
|
{
|
||
|
if (result.passed)
|
||
|
return '<td class="status ok">OK</td>';
|
||
|
else
|
||
|
return '<td class="status fail">FAIL</td>';
|
||
|
}
|
||
|
|
||
|
|
||
|
// Reveals some hidden, whitespace, or invisible characters
|
||
|
function revealChars(txt)
|
||
|
{
|
||
|
if (typeof txt != 'string')
|
||
|
return '(file)';
|
||
|
|
||
|
// Make spaces and tabs more obvious when glancing
|
||
|
txt = txt.replace(/( |\t)/ig, '<span class="whitespace-char">$1</span>');
|
||
|
txt = txt.replace(/(\r\n|\n\r|\r|\n)/ig, '<span class="whitespace-char special-char">$1</span>$1');
|
||
|
|
||
|
// Make UNIT_SEP and RECORD_SEP characters visible
|
||
|
txt = txt.replace(/(\u001e|\u001f)/ig, '<span class="special-char">$1</span>$1');
|
||
|
|
||
|
// Now make the whitespace and invisible characters
|
||
|
// within the spans actually appear on the page
|
||
|
txt = txt.replace(/">\r\n<\/span>/ig, '">\\r\\n</span>');
|
||
|
txt = txt.replace(/">\n\r<\/span>/ig, '">\\n\\r</span>');
|
||
|
txt = txt.replace(/">\r<\/span>/ig, '">\\r</span>');
|
||
|
txt = txt.replace(/">\n<\/span>/ig, '">\\n</span>');
|
||
|
txt = txt.replace(/">\u001e<\/span>/ig, '">\\u001e</span>');
|
||
|
txt = txt.replace(/">\u001f<\/span>/ig, '">\\u001f</span>');
|
||
|
|
||
|
return txt;
|
||
|
}
|