Skip to content

Commit 2c3cea9

Browse files
committed
Merge pull request #47 from motiz88/graphical-tests
Maintainable tests of actual SVG output
2 parents c49e893 + 00b14fd commit 2c3cea9

File tree

7 files changed

+263
-1
lines changed

7 files changed

+263
-1
lines changed

.babelrc

+3
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@
44
"es2015",
55
"stage-1"
66
],
7+
"plugins": [
8+
"transform-object-assign"
9+
],
710
"sourceMaps": true,
811
}

__tests__/compareSvg.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import hiff from 'hiff';
2+
3+
function normalizeStyle(style) {
4+
style = (style || '').split(';').map(s => s.trim());
5+
style.sort();
6+
return style.join(';');
7+
}
8+
9+
function normalizeAttrs(...$ns) {
10+
for (let $n of $ns) {
11+
if ($n.attr('style'))
12+
$n.attr('style', normalizeStyle($n.attr('style')));
13+
}
14+
}
15+
16+
function comparatorFn($n1, $n2, childChanges) {
17+
normalizeAttrs($n1, $n2);
18+
return hiff.defaultTagComparisonFn($n1, $n2, childChanges);
19+
}
20+
21+
export default function compareSvg(svg1, svg2, options = {}) {
22+
return hiff.compare(svg1, svg2, Object.assign({}, options, {tagComparison: comparatorFn}));
23+
}

__tests__/data.json

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"sampleData": [0.26789611283279424, -1.5618808743590797, -0.46848826820269196, -0.02429709108986638, -0.07347501430506465,
3+
0.938722048681125, -0.02488170176918398,
4+
0.014511315562131895, -1.0920317808493079, -1.6226651458214956,
5+
0.6777968350341455, -1.0989601025670448,
6+
1.402853678778828, -1.7923422052616966, -0.37025235972161835, -0.10254054014867667,
7+
0.3709902985604339,
8+
2.5285657626539253, -0.18958673659343403,
9+
0.8578243085059141,
10+
1.7395812075504404,
11+
0.9723534409914075, -0.6799757002898873,
12+
1.153081489500828,
13+
1.3851189843556257,
14+
0.19355625368483506,
15+
1.262069965103209, -0.8628137671385424, -0.6118030618030503, -0.25257403618789087
16+
],
17+
"sampleData100": [-0.2809926205121489,
18+
0.5993223086924007,
19+
0.5450586119753267, -0.4794107823559421, -0.18298272472350668,
20+
0.3219712568468161,
21+
2.0566174540438324, -1.6442809970641576,
22+
1.971025186834513,
23+
0.37237930811331116, -0.4015753275277232,
24+
1.0601229819271032, -2.0983317267366988,
25+
0.26835584955818786, -0.2899975217753408, -0.6342464890422705, -0.10975205424415876,
26+
0.39583180670735013,
27+
1.4695948548213784, -1.2295606440627673,
28+
1.0056333434310312,
29+
1.006402733277956, -1.4092655719724325,
30+
0.17595701557726026, -0.19396518917878047, -1.4314174397644206, -0.34402041741926476,
31+
0.6986827111240516, -0.6157663396302129,
32+
1.0606864486721386,
33+
1.3537300165741912, -0.9521291296713654, -1.089926042595364, -0.9723342804049446,
34+
0.2286317959508994,
35+
0.2613862542298905,
36+
0.24840731355644413,
37+
2.08064561830636,
38+
0.44534855831763426,
39+
1.5511436162779393, -1.5514313805901196, -0.7497893094776009,
40+
0.4027674242193654, -0.38986316786208264, -1.2167765233154504,
41+
0.18879490542570268, -1.5284852088503573,
42+
0.8789559275619153, -1.2451506359938267, -0.7226040247250638, -0.07157034383583998,
43+
1.9901707247581082,
44+
0.22166972734467405,
45+
0.058080839429433054, -0.6324465858010533, -0.8091687560181702, -1.293296284426419,
46+
1.8436776591711028, -0.28314101700652944,
47+
1.358988312176975, -0.1152691343859452, -2.425199332455914,
48+
0.6696100792204956,
49+
1.7308347028588733, -0.9997610678433961, -0.10106296722138419,
50+
0.3157348177184432, -0.34931234065268996,
51+
0.4662049447935582,
52+
0.8793589099345607,
53+
2.069923608446714,
54+
1.3861543531394107, -0.2705101572065443,
55+
0.5980871990258989, -0.5871146157743545, -0.9844080263005216,
56+
0.2972697252124295, -0.6119868603373193, -1.8902200290484288,
57+
0.6996282188319667, -0.24883654266800448, -0.1156025389007573,
58+
1.0370156630612894,
59+
0.9750054921585302, -0.635000984672242,
60+
0.16076716404020402,
61+
0.1379262931648021, -0.6838899322825195,
62+
0.6088591150304701, -0.3408579041001186, -0.08790701313160872, -0.38412257182424137, -1.3319278452946857,
63+
0.7154759857504911, -2.8727571205730915, -1.3501468729225303, -0.0865770144109483,
64+
0.505651174224522, -2.2111682240498753,
65+
2.035381345199811
66+
]
67+
}

__tests__/fixtures.js

+42
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

__tests__/graphical-tests.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import fixtures from './fixtures';
2+
import {render} from 'enzyme';
3+
import { expect } from 'chai';
4+
import compareSvg from './compareSvg';
5+
6+
describe('Graphical tests from fixtures.js', function() {
7+
for (let key of Object.keys(fixtures)) {
8+
describe(`${key}`, function() {
9+
it('should render as specified', function() {
10+
const wrapper = render(fixtures[key].jsx);
11+
const result = compareSvg(wrapper.html(), fixtures[key].svg);
12+
const errorMessage = 'SVG output changed:\n' + result.changes.map(change => change.message).join('\n') + '\n';
13+
expect(result.changes, errorMessage).to.be.empty;
14+
});
15+
});
16+
}
17+
});

bootstrap-tests.js

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// bootstrap-tests.js - A tool for updating the test cases in __tests__/fixtures.js
2+
//
3+
// 1) Reads __tests__/fixtures.js and looks for a "dynamic part", which should be a list of fields
4+
// belonging to that file's default export, enclosed in a pair of markers (see "signal" constants
5+
// below).
6+
// 2) Imports the same fixtures file and (re-)renders each ReactElement to a static SVG string.
7+
// 3) On success, overwrites __tests__/fixtures.js with an updated copy.
8+
//
9+
// Run with babel-node or using "npm run test:bootstrap".
10+
11+
import path from 'path';
12+
import {render} from 'enzyme';
13+
import LineByLineReader from 'line-by-line';
14+
import reactElementToJsx from 'react-element-to-jsx-string';
15+
import {writeFileSync} from 'fs';
16+
import replaceAll from 'replaceall';
17+
import React from 'react';
18+
19+
const fixturesFile = path.resolve(__dirname, './__tests__/fixtures.js');
20+
const dynamicPartStartSignal = '// AUTO-GENERATED PART STARTS HERE';
21+
const dynamicPartEndSignal = '// AUTO-GENERATED PART ENDS HERE';
22+
23+
const fixtures = require(fixturesFile).default;
24+
25+
// Handle recurring data constants
26+
import {sampleData, sampleData100} from './__tests__/data.json';
27+
const recognizedDataConstants = {
28+
sampleData, sampleData100
29+
};
30+
const recognizedDataStrings = {};
31+
for (let dataKey of Object.keys(recognizedDataConstants)) {
32+
recognizedDataStrings[dataKey] = markupToOneLine(reactElementToJsx(<div data-value={recognizedDataConstants[dataKey]} />)
33+
.replace(/[^{]*\{|\}[^}]*/g, ''));
34+
}
35+
36+
// Output control
37+
let outData = '';
38+
const write = content => { outData += content + '\n'; }
39+
const save = () => writeFileSync(fixturesFile, outData);
40+
function writeFixtures() {
41+
for (let key of Object.keys(fixtures)) {
42+
const jsx = fixtures[key].jsx;
43+
const wrapper = render(jsx);
44+
let jsxCode = `(${markupToOneLine(reactElementToJsx(jsx))})`;
45+
const htmlCode = JSON.stringify(wrapper.html());
46+
for (let dataKey of Object.keys(recognizedDataStrings)) {
47+
jsxCode = replaceAll(recognizedDataStrings[dataKey], dataKey, jsxCode);
48+
}
49+
write(`\t${JSON.stringify(key)}: {jsx: ${jsxCode}, svg: ${htmlCode}},`);
50+
}
51+
}
52+
function markupToOneLine(code) {
53+
return code.replace(/\s*[\r\n]\s*/g, ' ').replace(/\s+/g, ' ').replace(/\s*([<>])\s*/g, '$1');
54+
}
55+
56+
// Input control
57+
const lr = new LineByLineReader(fixturesFile, {skipEmptyLines: false});
58+
let inDynamicPart = false, dynamicPartCount = 0, lineCount = 0;
59+
60+
lr.on('line', line => {
61+
++lineCount;
62+
if (line === dynamicPartStartSignal) {
63+
if (inDynamicPart)
64+
throw new LineError('Dynamic part opened again');
65+
++dynamicPartCount;
66+
if (dynamicPartCount > 1)
67+
throw new LineError('Multiple dynamic parts found');
68+
inDynamicPart = true;
69+
write(line);
70+
try {
71+
writeFixtures();
72+
} catch(e) {
73+
throw new LineError(e);
74+
}
75+
}
76+
else if (line === dynamicPartEndSignal) {
77+
if (!inDynamicPart)
78+
throw new LineError('Dynamic part closed again');
79+
inDynamicPart = false;
80+
write(line);
81+
}
82+
else if (!inDynamicPart)
83+
write(line);
84+
});
85+
86+
lr.on('end', () => {
87+
if (inDynamicPart) {
88+
throw new LineError('Dynamic part not closed');
89+
}
90+
if (!dynamicPartCount) {
91+
throw new LineError('No dynamic part found in file!');
92+
}
93+
save();
94+
});
95+
96+
lr.on('error', function (err) {
97+
throw new LineError(err);
98+
});
99+
100+
class LineError extends Error {
101+
constructor(message) {
102+
super(`${fixturesFile}:${lineCount}: ${message}`);
103+
}
104+
}

package.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"test": "mocha --compilers js:babel-core/register __tests__",
1212
"test:watch": "mocha --compilers js:babel-core/register --watch __tests__",
1313
"compile": "webpack",
14-
"prepublish": "npm run compile"
14+
"prepublish": "npm run compile",
15+
"test:bootstrap": "node -r babel-core/register bootstrap-tests.js"
1516
},
1617
"repository": {
1718
"type": "git",
@@ -36,16 +37,21 @@
3637
"babel": "^6.5.2",
3738
"babel-core": "^6.7.6",
3839
"babel-loader": "^6.2.4",
40+
"babel-plugin-transform-object-assign": "^6.5.0",
3941
"babel-preset-es2015": "^6.6.0",
4042
"babel-preset-react": "^6.5.0",
4143
"babel-preset-stage-1": "^6.5.0",
4244
"babel-runtime": "^6.6.1",
4345
"chai": "^3.5.0",
4446
"enzyme": "^2.2.0",
47+
"hiff": "^0.3.0",
48+
"line-by-line": "^0.1.4",
4549
"mocha": "^2.4.5",
4650
"react": "^15.0.1",
4751
"react-addons-test-utils": "^15.0.1",
4852
"react-dom": "^15.0.1",
53+
"react-element-to-jsx-string": "=2.5.0",
54+
"replaceall": "^0.1.6",
4955
"webpack": "^2.1.0-beta.4",
5056
"webpack-dev-server": "^2.0.0-beta"
5157
},

0 commit comments

Comments
 (0)