Browse Source

Merge pull request #1 from mholt/master

pulling the latest changes
pull/223/head^2
ommar 10 years ago
parent
commit
7e5044a7a1
  1. 3
      .travis.yml
  2. 11
      package.json
  3. 88
      papaparse.js
  4. 4
      papaparse.min.js
  5. 23
      tests/node-tests.js
  6. 149
      tests/test-cases.js
  7. 448
      tests/test-runner.js
  8. 18
      tests/test.js
  9. 173
      tests/tests.css
  10. 120
      tests/tests.html

3
.travis.yml

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
language: node_js
node_js:
- "0.12"

11
package.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "papaparse",
"version": "4.1.1",
"version": "4.1.2",
"description": "Fast and powerful CSV parser for the browser that supports web workers and streaming large files. Converts CSV to JSON and JSON to CSV.",
"keywords": [
"csv",
@ -41,13 +41,20 @@ @@ -41,13 +41,20 @@
],
"main": "papaparse.js",
"devDependencies": {
"chai": "^3.0.0",
"connect": "^3.3.3",
"grunt": "^0.4.5",
"grunt-contrib-uglify": "^0.6.0",
"mocha": "^2.2.5",
"mocha-phantomjs": "^3.5.4",
"open": "0.0.5",
"phantomjs": "1.9.1 - 1.9.7-15",
"serve-static": "^1.7.1"
},
"scripts": {
"test": "node tests/test.js"
"test-browser": "node tests/test.js",
"test-phantomjs": "node tests/test.js --phantomjs",
"test-node": "mocha tests/node-tests.js tests/test-cases.js",
"test": "npm run test-node && npm run test-phantomjs"
}
}

88
papaparse.js

@ -1,13 +1,15 @@ @@ -1,13 +1,15 @@
/*!
Papa Parse
v4.1.1
v4.1.2
https://github.com/mholt/PapaParse
*/
(function(global)
{
"use strict";
var IS_WORKER = (!global.document && !!global.postMessage), LOADED_SYNC = false, AUTO_SCRIPT_PATH;
var IS_WORKER = !global.document && !!global.postMessage,
IS_PAPA_WORKER = IS_WORKER && /(\?|&)papaworker(=|&|$)/.test(global.location.search),
LOADED_SYNC = false, AUTO_SCRIPT_PATH;
var workers = {}, workerIdCounter = 0;
var Papa = {};
@ -19,13 +21,13 @@ @@ -19,13 +21,13 @@
Papa.UNIT_SEP = String.fromCharCode(31);
Papa.BYTE_ORDER_MARK = "\ufeff";
Papa.BAD_DELIMITERS = ["\r", "\n", "\"", Papa.BYTE_ORDER_MARK];
Papa.WORKERS_SUPPORTED = !!global.Worker;
Papa.WORKERS_SUPPORTED = !IS_WORKER && !!global.Worker;
Papa.SCRIPT_PATH = null; // Must be set by your code if you use workers and this lib is loaded asynchronously
// Configurable chunk sizes for local and remote files, respectively
Papa.LocalChunkSize = 1024 * 1024 * 10; // 10 MB
Papa.RemoteChunkSize = 1024 * 1024 * 5; // 5 MB
Papa.DefaultDelimiter = ","; // Used if not specified and detection fails
Papa.DefaultDelimiter = ","; // Used if not specified and detection fails
// Exposed for testing and development only
Papa.Parser = Parser;
@ -42,7 +44,7 @@ @@ -42,7 +44,7 @@
else if (isFunction(global.define) && global.define.amd)
{
// Wireup with RequireJS
global.define(function() { return Papa; });
define(function() { return Papa; });
}
else
{
@ -145,7 +147,7 @@ @@ -145,7 +147,7 @@
}
if (IS_WORKER)
if (IS_PAPA_WORKER)
{
global.onmessage = workerThreadReceivedMessage;
}
@ -223,9 +225,15 @@ @@ -223,9 +225,15 @@
var _fields = [];
// Default configuration
var _quotes = false; // whether to surround every datum with quotes
var _delimiter = ","; // delimiting character
var _newline = "\r\n"; // newline character(s)
/** whether to surround every datum with quotes */
var _quotes = false;
/** delimiting character */
var _delimiter = ",";
/** newline character(s) */
var _newline = "\r\n";
unpackConfig();
@ -283,7 +291,7 @@ @@ -283,7 +291,7 @@
}
// Turns an object's keys into an array
/** Turns an object's keys into an array */
function objectKeys(obj)
{
if (typeof obj !== 'object')
@ -294,7 +302,7 @@ @@ -294,7 +302,7 @@
return keys;
}
// The double for loop that iterates the data and writes out a CSV string including header row
/** The double for loop that iterates the data and writes out a CSV string including header row */
function serialize(fields, data)
{
var csv = "";
@ -340,7 +348,7 @@ @@ -340,7 +348,7 @@
return csv;
}
// Encloses a value around quotes if needed (makes a value safe for CSV insertion)
/** Encloses a value around quotes if needed (makes a value safe for CSV insertion) */
function safe(str, col)
{
if (typeof str === "undefined" || str === null)
@ -367,7 +375,7 @@ @@ -367,7 +375,7 @@
}
}
// ChunkStreamer is the base prototype for various streamer implementations.
/** ChunkStreamer is the base prototype for various streamer implementations. */
function ChunkStreamer(config)
{
this._handle = null;
@ -420,7 +428,7 @@ @@ -420,7 +428,7 @@
var finishedIncludingPreview = this._finished || (this._config.preview && this._rowCount >= this._config.preview);
if (IS_WORKER)
if (IS_PAPA_WORKER)
{
global.postMessage({
results: results,
@ -456,7 +464,7 @@ @@ -456,7 +464,7 @@
{
if (isFunction(this._config.error))
this._config.error(error);
else if (IS_WORKER && this._config.error)
else if (IS_PAPA_WORKER && this._config.error)
{
global.postMessage({
workerId: Papa.WORKER_ID,
@ -721,9 +729,11 @@ @@ -721,9 +729,11 @@
};
}
// Parses input. Most users won't need, and shouldn't mess with, the baseIndex
// and ignoreLastRow parameters. They are used by streamers (wrapper functions)
// when an input comes in multiple chunks, like from a file.
/**
* Parses input. Most users won't need, and shouldn't mess with, the baseIndex
* and ignoreLastRow parameters. They are used by streamers (wrapper functions)
* when an input comes in multiple chunks, like from a file.
*/
this.parse = function(input, baseIndex, ignoreLastRow)
{
if (!_config.newline)
@ -905,7 +915,8 @@ @@ -905,7 +915,8 @@
}
}
avgFieldCount /= preview.data.length;
if (preview.data.length > 0)
avgFieldCount /= preview.data.length;
if ((typeof bestDelta === 'undefined' || delta < bestDelta)
&& avgFieldCount > 1.99)
@ -963,7 +974,7 @@ @@ -963,7 +974,7 @@
// The core parser implements speedy and correct CSV parsing
/** The core parser implements speedy and correct CSV parsing */
function Parser(config)
{
// Unpack the config object
@ -1185,13 +1196,15 @@ @@ -1185,13 +1196,15 @@
lastCursor = cursor;
}
// Appends the remaining input from cursor to the end into
// row, saves the row, calls step, and returns the results.
/**
* Appends the remaining input from cursor to the end into
* row, saves the row, calls step, and returns the results.
*/
function finish(value)
{
if (ignoreLastRow)
return returnable();
if (!value)
if (typeof value === 'undefined')
value = input.substr(cursor);
row.push(value);
cursor = inputLen; // important in case parsing is paused
@ -1201,10 +1214,12 @@ @@ -1201,10 +1214,12 @@
return returnable();
}
// Appends the current row to the results. It sets the cursor
// to newCursor and finds the nextNewline. The caller should
// take care to execute user's step function and check for
// preview and end parsing if necessary.
/**
* Appends the current row to the results. It sets the cursor
* to newCursor and finds the nextNewline. The caller should
* take care to execute user's step function and check for
* preview and end parsing if necessary.
*/
function saveRow(newCursor)
{
cursor = newCursor;
@ -1213,7 +1228,7 @@ @@ -1213,7 +1228,7 @@
nextNewline = input.indexOf(newline, cursor);
}
// Returns an object with the results, errors, and meta.
/** Returns an object with the results, errors, and meta. */
function returnable(stopped)
{
return {
@ -1229,7 +1244,7 @@ @@ -1229,7 +1244,7 @@
};
}
// Executes the user's step function and resets data & errors.
/** Executes the user's step function and resets data & errors. */
function doStep()
{
step(returnable());
@ -1237,13 +1252,13 @@ @@ -1237,13 +1252,13 @@
}
};
// Sets the abort flag
/** Sets the abort flag */
this.abort = function()
{
aborted = true;
};
// Gets the cursor position
/** Gets the cursor position */
this.getCharIndex = function()
{
return cursor;
@ -1268,14 +1283,17 @@ @@ -1268,14 +1283,17 @@
'Script path cannot be determined automatically when Papa Parse is loaded asynchronously. ' +
'You need to set Papa.SCRIPT_PATH manually.'
);
var w = new global.Worker(Papa.SCRIPT_PATH || AUTO_SCRIPT_PATH);
var workerUrl = Papa.SCRIPT_PATH || AUTO_SCRIPT_PATH;
// Append "papaworker" to the search string to tell papaparse that this is our worker.
workerUrl += (workerUrl.indexOf('?') !== -1 ? '&' : '?') + 'papaworker';
var w = new global.Worker(workerUrl);
w.onmessage = mainThreadReceivedMessage;
w.id = workerIdCounter++;
workers[w.id] = w;
return w;
}
// Callback when main thread receives a message
/** Callback when main thread receives a message */
function mainThreadReceivedMessage(e)
{
var msg = e.data;
@ -1334,7 +1352,7 @@ @@ -1334,7 +1352,7 @@
throw "Not implemented.";
}
// Callback when worker thread receives a message
/** Callback when worker thread receives a message */
function workerThreadReceivedMessage(e)
{
var msg = e.data;
@ -1362,7 +1380,7 @@ @@ -1362,7 +1380,7 @@
}
}
// Makes a deep copy of an array or object (mostly)
/** Makes a deep copy of an array or object (mostly) */
function copy(obj)
{
if (typeof obj !== 'object')

4
papaparse.min.js vendored

File diff suppressed because one or more lines are too long

23
tests/node-tests.js

@ -37,16 +37,19 @@ @@ -37,16 +37,19 @@
assert.equal(parsedCsv.errors.length, 0)
}
var synchronouslyParsedCsvShouldBeCorrectlyParsed = function() {
assertLongSampleParsedCorrectly(Papa.parse(longSampleRawCsv));
}();
describe('PapaParse', function() {
it('synchronously parsed CSV should be correctly parsed', function() {
assertLongSampleParsedCorrectly(Papa.parse(longSampleRawCsv));
});
var asynchronouslyParsedCsvShouldBeCorrectlyParsed = function() {
Papa.parse(longSampleRawCsv, {
complete: function(parsedCsv) {
assertLongSampleParsedCorrectly(parsedCsv);
},
it('asynchronously parsed CSV should be correctly parsed', function(done) {
Papa.parse(longSampleRawCsv, {
complete: function(parsedCsv) {
assertLongSampleParsedCorrectly(parsedCsv);
done();
},
});
});
}();
});
})();
})();

149
tests/test-cases.js

@ -1,3 +1,10 @@ @@ -1,3 +1,10 @@
if (typeof module !== 'undefined' && module.exports) {
var chai = require('chai');
var Papa = require('../papaparse.js');
}
var assert = chai.assert;
var RECORD_SEP = String.fromCharCode(30);
var UNIT_SEP = String.fromCharCode(31);
var FILES_ENABLED = false;
@ -6,6 +13,12 @@ try { @@ -6,6 +13,12 @@ try {
FILES_ENABLED = true;
} catch (e) {} // safari, ie
var XHR_ENABLED = false;
try {
new XMLHttpRequest();
XHR_ENABLED = true;
} catch (e) {} // safari, ie
// Tests for the core parser using new Papa.Parser().parse() (CSV to JSON)
var CORE_PARSER_TESTS = [
{
@ -206,6 +219,14 @@ var CORE_PARSER_TESTS = [ @@ -206,6 +219,14 @@ var CORE_PARSER_TESTS = [
errors: []
}
},
{
description: "Empty quoted field at EOF is empty",
input: 'a,b,""\na,b,""',
expected: {
data: [['a', 'b', ''], ['a', 'b', '']],
errors: []
}
},
{
description: "Multiple consecutive empty fields",
input: 'a,b,,,c,d\n,,e,,,f',
@ -428,6 +449,21 @@ var CORE_PARSER_TESTS = [ @@ -428,6 +449,21 @@ var CORE_PARSER_TESTS = [
}
];
describe('Core Parser Tests', function() {
function generateTest(test) {
(test.disabled ? it.skip : it)(test.description, function() {
var actual = new Papa.Parser(test.config).parse(test.input);
assert.deepEqual(JSON.stringify(actual.errors), JSON.stringify(test.expected.errors));
assert.deepEqual(actual.data, test.expected.data);
});
}
for (var i = 0; i < CORE_PARSER_TESTS.length; i++) {
generateTest(CORE_PARSER_TESTS[i]);
}
});
// Tests for Papa.parse() function -- high-level wrapped parser (CSV to JSON)
var PARSE_TESTS = [
@ -818,9 +854,19 @@ var PARSE_TESTS = [ @@ -818,9 +854,19 @@ var PARSE_TESTS = [
}
];
describe('Parse Tests', function() {
function generateTest(test) {
(test.disabled ? it.skip : it)(test.description, function() {
var actual = Papa.parse(test.input, test.config);
assert.deepEqual(JSON.stringify(actual.errors), JSON.stringify(test.expected.errors));
assert.deepEqual(actual.data, test.expected.data);
});
}
for (var i = 0; i < PARSE_TESTS.length; i++) {
generateTest(PARSE_TESTS[i]);
}
});
@ -843,6 +889,7 @@ var PARSE_ASYNC_TESTS = [ @@ -843,6 +889,7 @@ var PARSE_ASYNC_TESTS = [
config: {
download: true
},
disabled: !XHR_ENABLED,
expected: {
data: [['A','B','C'],['X','Y','Z']],
errors: []
@ -855,6 +902,7 @@ var PARSE_ASYNC_TESTS = [ @@ -855,6 +902,7 @@ var PARSE_ASYNC_TESTS = [
worker: true,
download: true
},
disabled: !XHR_ENABLED,
expected: {
data: [['A','B','C'],['X','Y','Z']],
errors: []
@ -885,10 +933,29 @@ var PARSE_ASYNC_TESTS = [ @@ -885,10 +933,29 @@ var PARSE_ASYNC_TESTS = [
}
];
describe('Parse Async Tests', function() {
function generateTest(test) {
(test.disabled ? it.skip : it)(test.description, function(done) {
var config = test.config;
config.complete = function(actual) {
assert.deepEqual(JSON.stringify(actual.errors), JSON.stringify(test.expected.errors));
assert.deepEqual(actual.data, test.expected.data);
done();
};
config.error = function(err) {
throw err;
};
Papa.parse(test.input, config);
});
}
for (var i = 0; i < PARSE_ASYNC_TESTS.length; i++) {
generateTest(PARSE_ASYNC_TESTS[i]);
}
});
@ -1038,12 +1105,36 @@ var UNPARSE_TESTS = [ @@ -1038,12 +1105,36 @@ var UNPARSE_TESTS = [
}
];
describe('Unparse Tests', function() {
function generateTest(test) {
(test.disabled ? it.skip : it)(test.description, function() {
var actual;
try {
actual = Papa.unparse(test.input, test.config);
} catch (e) {
if (e instanceof Error) {
throw e;
}
actual = e;
}
assert.strictEqual(actual, test.expected);
});
}
for (var i = 0; i < UNPARSE_TESTS.length; i++) {
generateTest(UNPARSE_TESTS[i]);
}
});
var CUSTOM_TESTS = [
{
description: "Complete is called with all results if neither step nor chunk is defined",
expected: [['A', 'b', 'c'], ['d', 'E', 'f'], ['G', 'h', 'i']],
disabled: !FILES_ENABLED,
run: function(callback) {
Papa.parse(new File(['A,b,c\nd,E,f\nG,h,i'], 'sample.csv'), {
chunkSize: 3,
@ -1097,6 +1188,7 @@ var CUSTOM_TESTS = [ @@ -1097,6 +1188,7 @@ var CUSTOM_TESTS = [
{
description: "Step exposes cursor for downloads",
expected: [129, 287, 452, 595, 727, 865, 1031, 1209],
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = [];
Papa.parse("/tests/long-sample.csv", {
@ -1113,6 +1205,7 @@ var CUSTOM_TESTS = [ @@ -1113,6 +1205,7 @@ var CUSTOM_TESTS = [
{
description: "Step exposes cursor for chunked downloads",
expected: [129, 287, 452, 595, 727, 865, 1031, 1209],
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = [];
Papa.parse("/tests/long-sample.csv", {
@ -1130,6 +1223,7 @@ var CUSTOM_TESTS = [ @@ -1130,6 +1223,7 @@ var CUSTOM_TESTS = [
{
description: "Step exposes cursor for workers",
expected: [452, 452, 452, 865, 865, 865, 1209, 1209],
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = [];
Papa.parse("/tests/long-sample.csv", {
@ -1148,6 +1242,7 @@ var CUSTOM_TESTS = [ @@ -1148,6 +1242,7 @@ var CUSTOM_TESTS = [
{
description: "Chunk is called for each chunk",
expected: [3, 3, 2],
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = [];
Papa.parse("/tests/long-sample.csv", {
@ -1165,6 +1260,7 @@ var CUSTOM_TESTS = [ @@ -1165,6 +1260,7 @@ var CUSTOM_TESTS = [
{
description: "Chunk is called with cursor position",
expected: [452, 865, 1209],
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = [];
Papa.parse("/tests/long-sample.csv", {
@ -1305,6 +1401,7 @@ var CUSTOM_TESTS = [ @@ -1305,6 +1401,7 @@ var CUSTOM_TESTS = [
{
description: "Step functions can abort workers",
expected: 1,
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = 0;
Papa.parse("/tests/long-sample.csv", {
@ -1324,6 +1421,7 @@ var CUSTOM_TESTS = [ @@ -1324,6 +1421,7 @@ var CUSTOM_TESTS = [
{
description: "beforeFirstChunk manipulates only first chunk",
expected: 7,
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = 0;
Papa.parse("/tests/long-sample.csv", {
@ -1344,6 +1442,7 @@ var CUSTOM_TESTS = [ @@ -1344,6 +1442,7 @@ var CUSTOM_TESTS = [
{
description: "First chunk not modified if beforeFirstChunk returns nothing",
expected: 8,
disabled: !XHR_ENABLED,
run: function(callback) {
var updates = 0;
Papa.parse("/tests/long-sample.csv", {
@ -1359,6 +1458,52 @@ var CUSTOM_TESTS = [ @@ -1359,6 +1458,52 @@ var CUSTOM_TESTS = [
}
});
}
},
{
description: "Should not assume we own the worker unless papaworker is in the search string",
disabled: typeof Worker === 'undefined',
expected: [false, true, true, true, true],
run: function(callback) {
var searchStrings = [
'',
'?papaworker',
'?x=1&papaworker',
'?x=1&papaworker&y=1',
'?x=1&papaworker=1'
];
var results = searchStrings.map(function () { return false; });
var workers = [];
// Give it .5s to do something
setTimeout(function () {
workers.forEach(function (w) { w.terminate(); });
callback(results);
}, 500);
searchStrings.forEach(function (searchString, idx) {
var w = new Worker('../papaparse.js' + searchString);
workers.push(w);
w.addEventListener('message', function () {
results[idx] = true;
});
w.postMessage({input: 'a,b,c\n1,2,3'});
});
}
}
];
describe('Custom Tests', function() {
function generateTest(test) {
(test.disabled ? it.skip : it)(test.description, function(done) {
test.run(function (actual) {
assert.deepEqual(JSON.stringify(actual), JSON.stringify(test.expected));
done();
});
});
}
for (var i = 0; i < CUSTOM_TESTS.length; i++) {
generateTest(CUSTOM_TESTS[i]);
}
});

448
tests/test-runner.js

@ -1,448 +0,0 @@ @@ -1,448 +0,0 @@
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;
}

18
tests/test.js

@ -1,11 +1,19 @@ @@ -1,11 +1,19 @@
require('./node-tests.js');
var connect = require('connect');
var serveStatic = require('serve-static');
var open = require('open');
var path = require('path');
var child_process = require('child_process');
var server = connect().use(serveStatic(path.join(__dirname, '/..'))).listen(8071, function() {
if (process.argv.indexOf('--phantomjs') !== -1) {
child_process.spawn('node_modules/.bin/mocha-phantomjs', ['http://localhost:8071/tests/tests.html'], {
stdio: 'inherit'
}).on('exit', function () {
server.close();
});
connect().use(serveStatic(path.join(__dirname, '/..'))).listen(8071, function() {
open('http://localhost:8071/tests/tests.html');
console.log('Serving tests...');
} else {
open('http://localhost:8071/tests/tests.html');
console.log('Serving tests...');
}
});

173
tests/tests.css

@ -1,173 +0,0 @@ @@ -1,173 +0,0 @@
/* Eric Meyer's Reset CSS v2.0 */
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0}
body {
background: #F0F0F0;
font: 14px 'Helvetica Neue', sans-serif;
color: #333;
padding: 30px 15px;
}
a {
color: rgb(0, 142, 236);
}
a:hover {
color: rgb(82, 186, 255);
}
b {
font-weight: bold;
}
i {
font-style: italic;
}
h1 {
text-align: center;
font-weight: bold;
font-size: 62px;
margin-bottom: 30px;
}
h2 {
text-align: center;
font-weight: bold;
font-size: 26px;
margin-bottom: 20px;
}
.status-pass,
.status-fail {
padding: 10px;
margin-bottom: 30px;
color: #FFF;
text-align: center;
text-transform: uppercase;
font-size: 18px;
letter-spacing: 1px;
font-weight: 100;
}
.status-pass {
background: rgb(3, 168, 3);
}
.status-fail {
background: #BB0000;
}
.test-group {
margin-bottom: 50px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 5px;
}
table th,
table td {
padding: 5px;
border: 1px solid #BBB;
}
table th {
color: #000;
background: #DDD;
font-weight: bold;
padding: 10px 5px;
text-transform: uppercase;
}
table td {
background: #FFF;
color: #555;
font-size: 14px;
}
td.status {
text-transform: uppercase;
font-weight: 300;
vertical-align: middle;
text-align: center;
width: 80px;
}
td.ok {
background: rgb(204, 250, 144);
}
td.fail {
background: rgb(255, 192, 192);
}
td.pending {
background: rgb(255, 255, 150);
}
td.rvl {
background: #444;
color: #999;
vertical-align: middle;
text-align: center;
cursor: pointer;
width: 20px;
}
td.rvl:hover {
color: #FFF;
}
tr.collapsed td.revealable {
background: #ECECEC;
vertical-align: middle;
text-align: center;
font-family: 'Helvetica Neue', sans-serif;
text-transform: lowercase;
color: #AAA;
}
tr.expanded .revealer {
font-family: 'Helvetica Neue', sans-serif;
text-transform: lowercase;
font-size: 10px;
background: #FFF;
position: absolute;
display: block;
padding: 3px;
top: -5px;
right: -5px;
}
td .container {
position: relative;
}
.notes {
color: #888;
font-size: 12px;
}
.pre {
font-family: Menlo, Monaco, monospace;
white-space: pre-wrap;
}
td.pre {
font-size: 12px;
}
.hidden {
display: none;
}
.special-char {
color: #78B7E7;
}
.whitespace-char {
background: #D5FCFA;
}

120
tests/tests.html

@ -1,113 +1,29 @@ @@ -1,113 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>Papa Parse Tests</title>
<meta charset="utf-8">
<link rel="stylesheet" href="tests.css">
<link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="../papaparse.js"></script>
<script src="http://chaijs.com/chai.js"></script>
<script src="../node_modules/mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="test-cases.js"></script>
<script src="test-runner.js"></script>
</head>
<body>
<h1>Papa Parse Tests</h1>
<div id="status"></div>
<div class="test-group" id="tests-for-core-parser">
<h2>Core Parser Tests</h2>
<a href="javascript:" class="expand-all">Expand all</a>
&middot;
<a href="javascript:" class="collapse-all">Collapse all</a>
<br>
<table class="results">
<tr>
<th colspan="2">Test Case</th>
<th>Data</th>
<th>Errors</th>
<th>Config</th>
<th>Input</th>
<th>Expected</th>
<th>Actual</th>
</tr>
</table>
</div>
<div class="test-group" id="tests-for-parse">
<h2>Papa.parse() Wrapper Tests</h2>
<a href="javascript:" class="expand-all">Expand all</a>
&middot;
<a href="javascript:" class="collapse-all">Collapse all</a>
<br>
<table class="results">
<tr>
<th colspan="2">Test Case</th>
<th>Data</th>
<th>Errors</th>
<th>Config</th>
<th>Input</th>
<th>Expected</th>
<th>Actual</th>
</tr>
</table>
</div>
<div class="test-group" id="tests-for-unparse">
<h2>Papa.unparse() Tests</h2>
<a href="javascript:" class="expand-all">Expand all</a>
&middot;
<a href="javascript:" class="collapse-all">Collapse all</a>
<br>
<table class="results">
<tr>
<th colspan="2">Test Case</th>
<th>Data</th>
<th>Config</th>
<th>Input</th>
<th>Expected</th>
<th>Actual</th>
</tr>
</table>
</div>
<div class="test-group" id="custom-tests">
<h2>Miscellaneous Tests</h2>
<a href="javascript:" class="expand-all">Expand all</a>
&middot;
<a href="javascript:" class="collapse-all">Collapse all</a>
<br>
<table class="results">
<tr>
<th colspan="2">Test Case</th>
<th>Data</th>
<th>Expected</th>
<th>Actual</th>
</tr>
</table>
</div>
<div id="mocha"></div>
<script>
if (window.mochaPhantomJS) {
mochaPhantomJS.run();
} else {
mocha.checkLeaks();
mocha.globals(['jQuery']);
mocha.run();
}
</script>
</body>
</html>

Loading…
Cancel
Save