Skip to content

Maintainable tests of actual SVG output #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 5, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -4,5 +4,8 @@
"es2015",
"stage-1"
],
"plugins": [
"transform-object-assign"
],
"sourceMaps": true,
}
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 0 additions & 4 deletions __tests__/Sparklines.js
Original file line number Diff line number Diff line change
@@ -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('<!doctype html><html><body></body></html>');
global.window = document.parentWindow;

describe('Sparklines', () => {
it('does not throw without any parameters', () => {
23 changes: 23 additions & 0 deletions __tests__/compareSvg.js
Original file line number Diff line number Diff line change
@@ -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}));
}
67 changes: 67 additions & 0 deletions __tests__/data.json
Original file line number Diff line number Diff line change
@@ -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
]
}
42 changes: 42 additions & 0 deletions __tests__/fixtures.js

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions __tests__/graphical-tests.js
Original file line number Diff line number Diff line change
@@ -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;
});
});
}
});
104 changes: 104 additions & 0 deletions bootstrap-tests.js
Original file line number Diff line number Diff line change
@@ -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(<div data-value={recognizedDataConstants[dataKey]} />)
.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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@borisyankov This allows us to write data={sampleData} in fixtures.js rather than data={[0.26789611283279424, ...]} every time. Yes, I used string replacement to do this. It's slow and kind of inelegant but it works :) Which to my taste is good enough for this kind of dev script.

}
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}`);
}
}
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
},