diff --git a/.babelrc b/.babelrc
index e9d2f4d..5436124 100644
--- a/.babelrc
+++ b/.babelrc
@@ -4,5 +4,8 @@
"es2015",
"stage-1"
],
+ "plugins": [
+ "transform-object-assign"
+ ],
"sourceMaps": true,
}
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 5b1fd1c..4cc1689 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,8 @@
language: node_js
+before_install: npm install -g npm@3
script: travis_retry npm test
node_js:
- '0.12'
+- '4'
+- '5'
sudo: false
diff --git a/__tests__/Sparklines.js b/__tests__/Sparklines.js
index 6324124..1b2e173 100644
--- a/__tests__/Sparklines.js
+++ b/__tests__/Sparklines.js
@@ -3,10 +3,6 @@ import ReactDOM from 'react-dom';
import { shallow } from 'enzyme';
import { expect } from 'chai';
import { Sparklines } from '../src/Sparklines';
-import jsdom from 'jsdom'
-
-global.document = jsdom.jsdom('
');
-global.window = document.parentWindow;
describe('Sparklines', () => {
it('does not throw without any parameters', () => {
diff --git a/__tests__/compareSvg.js b/__tests__/compareSvg.js
new file mode 100644
index 0000000..8a1f7e1
--- /dev/null
+++ b/__tests__/compareSvg.js
@@ -0,0 +1,23 @@
+import hiff from 'hiff';
+
+function normalizeStyle(style) {
+ style = (style || '').split(';').map(s => s.trim());
+ style.sort();
+ return style.join(';');
+}
+
+function normalizeAttrs(...$ns) {
+ for (let $n of $ns) {
+ if ($n.attr('style'))
+ $n.attr('style', normalizeStyle($n.attr('style')));
+ }
+}
+
+function comparatorFn($n1, $n2, childChanges) {
+ normalizeAttrs($n1, $n2);
+ return hiff.defaultTagComparisonFn($n1, $n2, childChanges);
+}
+
+export default function compareSvg(svg1, svg2, options = {}) {
+ return hiff.compare(svg1, svg2, Object.assign({}, options, {tagComparison: comparatorFn}));
+}
diff --git a/__tests__/data.json b/__tests__/data.json
new file mode 100644
index 0000000..02de8e4
--- /dev/null
+++ b/__tests__/data.json
@@ -0,0 +1,67 @@
+{
+ "sampleData": [0.26789611283279424, -1.5618808743590797, -0.46848826820269196, -0.02429709108986638, -0.07347501430506465,
+ 0.938722048681125, -0.02488170176918398,
+ 0.014511315562131895, -1.0920317808493079, -1.6226651458214956,
+ 0.6777968350341455, -1.0989601025670448,
+ 1.402853678778828, -1.7923422052616966, -0.37025235972161835, -0.10254054014867667,
+ 0.3709902985604339,
+ 2.5285657626539253, -0.18958673659343403,
+ 0.8578243085059141,
+ 1.7395812075504404,
+ 0.9723534409914075, -0.6799757002898873,
+ 1.153081489500828,
+ 1.3851189843556257,
+ 0.19355625368483506,
+ 1.262069965103209, -0.8628137671385424, -0.6118030618030503, -0.25257403618789087
+ ],
+ "sampleData100": [-0.2809926205121489,
+ 0.5993223086924007,
+ 0.5450586119753267, -0.4794107823559421, -0.18298272472350668,
+ 0.3219712568468161,
+ 2.0566174540438324, -1.6442809970641576,
+ 1.971025186834513,
+ 0.37237930811331116, -0.4015753275277232,
+ 1.0601229819271032, -2.0983317267366988,
+ 0.26835584955818786, -0.2899975217753408, -0.6342464890422705, -0.10975205424415876,
+ 0.39583180670735013,
+ 1.4695948548213784, -1.2295606440627673,
+ 1.0056333434310312,
+ 1.006402733277956, -1.4092655719724325,
+ 0.17595701557726026, -0.19396518917878047, -1.4314174397644206, -0.34402041741926476,
+ 0.6986827111240516, -0.6157663396302129,
+ 1.0606864486721386,
+ 1.3537300165741912, -0.9521291296713654, -1.089926042595364, -0.9723342804049446,
+ 0.2286317959508994,
+ 0.2613862542298905,
+ 0.24840731355644413,
+ 2.08064561830636,
+ 0.44534855831763426,
+ 1.5511436162779393, -1.5514313805901196, -0.7497893094776009,
+ 0.4027674242193654, -0.38986316786208264, -1.2167765233154504,
+ 0.18879490542570268, -1.5284852088503573,
+ 0.8789559275619153, -1.2451506359938267, -0.7226040247250638, -0.07157034383583998,
+ 1.9901707247581082,
+ 0.22166972734467405,
+ 0.058080839429433054, -0.6324465858010533, -0.8091687560181702, -1.293296284426419,
+ 1.8436776591711028, -0.28314101700652944,
+ 1.358988312176975, -0.1152691343859452, -2.425199332455914,
+ 0.6696100792204956,
+ 1.7308347028588733, -0.9997610678433961, -0.10106296722138419,
+ 0.3157348177184432, -0.34931234065268996,
+ 0.4662049447935582,
+ 0.8793589099345607,
+ 2.069923608446714,
+ 1.3861543531394107, -0.2705101572065443,
+ 0.5980871990258989, -0.5871146157743545, -0.9844080263005216,
+ 0.2972697252124295, -0.6119868603373193, -1.8902200290484288,
+ 0.6996282188319667, -0.24883654266800448, -0.1156025389007573,
+ 1.0370156630612894,
+ 0.9750054921585302, -0.635000984672242,
+ 0.16076716404020402,
+ 0.1379262931648021, -0.6838899322825195,
+ 0.6088591150304701, -0.3408579041001186, -0.08790701313160872, -0.38412257182424137, -1.3319278452946857,
+ 0.7154759857504911, -2.8727571205730915, -1.3501468729225303, -0.0865770144109483,
+ 0.505651174224522, -2.2111682240498753,
+ 2.035381345199811
+ ]
+}
\ No newline at end of file
diff --git a/__tests__/fixtures.js b/__tests__/fixtures.js
new file mode 100644
index 0000000..f178336
--- /dev/null
+++ b/__tests__/fixtures.js
@@ -0,0 +1,42 @@
+// This file is auto-updated by ../bootstrap-tests.js
+
+import React from 'react';
+import { Sparklines, SparklinesBars, SparklinesLine, SparklinesCurve, SparklinesNormalBand, SparklinesReferenceLine, SparklinesSpots } from '../src/Sparklines';
+import { sampleData, sampleData100 } from './data.json';
+
+export default {
+// AUTO-GENERATED PART STARTS HERE
+ "Header": {jsx: (), svg: ""},
+ "Simple": {jsx: (), svg: ""},
+ "SimpleCurve": {jsx: (), svg: ""},
+ "Customizable1": {jsx: (), svg: ""},
+ "Customizable2": {jsx: (), svg: ""},
+ "Customizable3": {jsx: (), svg: ""},
+ "Customizable4": {jsx: (), svg: ""},
+ "Customizable5": {jsx: (), svg: ""},
+ "Customizable6": {jsx: (), svg: ""},
+ "Bounds1": {jsx: (), svg: ""},
+ "Spots1": {jsx: (), svg: ""},
+ "Spots2": {jsx: (), svg: ""},
+ "Spots3": {jsx: (), svg: ""},
+ "Bars1": {jsx: (), svg: ""},
+ "Bars2": {jsx: (), svg: ""},
+ "ReferenceLine1": {jsx: (), svg: ""},
+ "ReferenceLine2": {jsx: (), svg: ""},
+ "ReferenceLine3": {jsx: (), svg: ""},
+ "ReferenceLine4": {jsx: (), svg: ""},
+ "ReferenceLine5": {jsx: (), svg: ""},
+ "ReferenceLine6": {jsx: (), svg: ""},
+ "NormalBand1": {jsx: (), svg: ""},
+ "NormalBand2": {jsx: (), svg: ""},
+ "RealWorld1": {jsx: (), svg: ""},
+ "RealWorld2": {jsx: (), svg: ""},
+ "RealWorld3": {jsx: (), svg: ""},
+ "RealWorld4": {jsx: (), svg: ""},
+ "RealWorld5": {jsx: (), svg: ""},
+ "RealWorld6": {jsx: (), svg: ""},
+ "RealWorld7": {jsx: (), svg: ""},
+ "RealWorld8": {jsx: (), svg: ""},
+ "RealWorld9": {jsx: (), svg: ""},
+// AUTO-GENERATED PART ENDS HERE
+};
diff --git a/__tests__/graphical-tests.js b/__tests__/graphical-tests.js
new file mode 100644
index 0000000..6dd5878
--- /dev/null
+++ b/__tests__/graphical-tests.js
@@ -0,0 +1,17 @@
+import fixtures from './fixtures';
+import {render} from 'enzyme';
+import { expect } from 'chai';
+import compareSvg from './compareSvg';
+
+describe('Graphical tests from fixtures.js', function() {
+ for (let key of Object.keys(fixtures)) {
+ describe(`${key}`, function() {
+ it('should render as specified', function() {
+ const wrapper = render(fixtures[key].jsx);
+ const result = compareSvg(wrapper.html(), fixtures[key].svg);
+ const errorMessage = 'SVG output changed:\n' + result.changes.map(change => change.message).join('\n') + '\n';
+ expect(result.changes, errorMessage).to.be.empty;
+ });
+ });
+ }
+});
\ No newline at end of file
diff --git a/bootstrap-tests.js b/bootstrap-tests.js
new file mode 100644
index 0000000..10cd82a
--- /dev/null
+++ b/bootstrap-tests.js
@@ -0,0 +1,104 @@
+// bootstrap-tests.js - A tool for updating the test cases in __tests__/fixtures.js
+//
+// 1) Reads __tests__/fixtures.js and looks for a "dynamic part", which should be a list of fields
+// belonging to that file's default export, enclosed in a pair of markers (see "signal" constants
+// below).
+// 2) Imports the same fixtures file and (re-)renders each ReactElement to a static SVG string.
+// 3) On success, overwrites __tests__/fixtures.js with an updated copy.
+//
+// Run with babel-node or using "npm run test:bootstrap".
+
+import path from 'path';
+import {render} from 'enzyme';
+import LineByLineReader from 'line-by-line';
+import reactElementToJsx from 'react-element-to-jsx-string';
+import {writeFileSync} from 'fs';
+import replaceAll from 'replaceall';
+import React from 'react';
+
+const fixturesFile = path.resolve(__dirname, './__tests__/fixtures.js');
+const dynamicPartStartSignal = '// AUTO-GENERATED PART STARTS HERE';
+const dynamicPartEndSignal = '// AUTO-GENERATED PART ENDS HERE';
+
+const fixtures = require(fixturesFile).default;
+
+// Handle recurring data constants
+import {sampleData, sampleData100} from './__tests__/data.json';
+const recognizedDataConstants = {
+ sampleData, sampleData100
+};
+const recognizedDataStrings = {};
+for (let dataKey of Object.keys(recognizedDataConstants)) {
+ recognizedDataStrings[dataKey] = markupToOneLine(reactElementToJsx()
+ .replace(/[^{]*\{|\}[^}]*/g, ''));
+}
+
+// Output control
+let outData = '';
+const write = content => { outData += content + '\n'; }
+const save = () => writeFileSync(fixturesFile, outData);
+function writeFixtures() {
+ for (let key of Object.keys(fixtures)) {
+ const jsx = fixtures[key].jsx;
+ const wrapper = render(jsx);
+ let jsxCode = `(${markupToOneLine(reactElementToJsx(jsx))})`;
+ const htmlCode = JSON.stringify(wrapper.html());
+ for (let dataKey of Object.keys(recognizedDataStrings)) {
+ jsxCode = replaceAll(recognizedDataStrings[dataKey], dataKey, jsxCode);
+ }
+ write(`\t${JSON.stringify(key)}: {jsx: ${jsxCode}, svg: ${htmlCode}},`);
+ }
+}
+function markupToOneLine(code) {
+ return code.replace(/\s*[\r\n]\s*/g, ' ').replace(/\s+/g, ' ').replace(/\s*([<>])\s*/g, '$1');
+}
+
+// Input control
+const lr = new LineByLineReader(fixturesFile, {skipEmptyLines: false});
+let inDynamicPart = false, dynamicPartCount = 0, lineCount = 0;
+
+lr.on('line', line => {
+ ++lineCount;
+ if (line === dynamicPartStartSignal) {
+ if (inDynamicPart)
+ throw new LineError('Dynamic part opened again');
+ ++dynamicPartCount;
+ if (dynamicPartCount > 1)
+ throw new LineError('Multiple dynamic parts found');
+ inDynamicPart = true;
+ write(line);
+ try {
+ writeFixtures();
+ } catch(e) {
+ throw new LineError(e);
+ }
+ }
+ else if (line === dynamicPartEndSignal) {
+ if (!inDynamicPart)
+ throw new LineError('Dynamic part closed again');
+ inDynamicPart = false;
+ write(line);
+ }
+ else if (!inDynamicPart)
+ write(line);
+});
+
+lr.on('end', () => {
+ if (inDynamicPart) {
+ throw new LineError('Dynamic part not closed');
+ }
+ if (!dynamicPartCount) {
+ throw new LineError('No dynamic part found in file!');
+ }
+ save();
+});
+
+lr.on('error', function (err) {
+ throw new LineError(err);
+});
+
+class LineError extends Error {
+ constructor(message) {
+ super(`${fixturesFile}:${lineCount}: ${message}`);
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index a0680ab..39a56a5 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,8 @@
"test": "mocha --compilers js:babel-core/register __tests__",
"test:watch": "mocha --compilers js:babel-core/register --watch __tests__",
"compile": "webpack",
- "prepublish": "npm run compile"
+ "prepublish": "npm run compile",
+ "test:bootstrap": "node -r babel-core/register bootstrap-tests.js"
},
"repository": {
"type": "git",
@@ -36,15 +37,21 @@
"babel": "^6.5.2",
"babel-core": "^6.7.6",
"babel-loader": "^6.2.4",
+ "babel-plugin-transform-object-assign": "^6.5.0",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-1": "^6.5.0",
"babel-runtime": "^6.6.1",
"chai": "^3.5.0",
"enzyme": "^2.2.0",
- "jsdom": "^8.3.1",
+ "hiff": "^0.3.0",
+ "line-by-line": "^0.1.4",
"mocha": "^2.4.5",
+ "react": "^15.0.1",
"react-addons-test-utils": "^15.0.1",
+ "react-dom": "^15.0.1",
+ "react-element-to-jsx-string": "=2.5.0",
+ "replaceall": "^0.1.6",
"webpack": "^2.1.0-beta.4",
"webpack-dev-server": "^2.0.0-beta"
},