Skip to content

Commit 1aa5162

Browse files
committed
chore(infrastructure): Set up testing
* `npm test` lints files, runs karma, reports coverages, checks coverage thresholds * `npm run test:watch` runs karma in auto-watch mode, with source maps for both source and test files. * [karma](https://karma-runner.github.io/1.0/index.html) is used for running tests. * [tape](https://github.com/substack/tape) is used as the actual test runner. * [bel](https://github.com/shama/bel) is used for easy DOM fixtures. * [testdouble](https://github.com/testdouble/testdouble.js) is used for mocking/doubles. * [isparta](https://github.com/douglasduteil/isparta) is used to instrument source files for coverage. * [istanbul](https://github.com/gotwarlost/istanbul) is used to check and report coverage. resolves #4465
1 parent 3203fe7 commit 1aa5162

File tree

6 files changed

+183
-8
lines changed

6 files changed

+183
-8
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
node_modules
22
.DS_Store
33
/build
4+
/coverage
45
packages/*/dist
56
*.log

Diff for: karma.conf.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
const path = require('path');
2+
const webpackConfig = require('./webpack.config')[0];
3+
4+
// TODO: Sourcemaps
5+
// TODO: eslint-plugin-tape
6+
module.exports = function(config) {
7+
config.set({
8+
basePath: '',
9+
frameworks: ['tap'],
10+
files: [
11+
'test/unit/index.js'
12+
],
13+
preprocessors: {
14+
'test/unit/index.js': ['webpack', 'sourcemap']
15+
},
16+
reporters: ['dots', 'coverage'],
17+
port: 9876,
18+
colors: true,
19+
logLevel: config.LOG_INFO,
20+
browsers: ['Chrome'],
21+
concurrency: Infinity,
22+
23+
coverageReporter: {
24+
dir: 'coverage',
25+
reporters: [
26+
{type: 'json', subdir: '.', file: 'coverage.json'},
27+
{type: 'html'}
28+
]
29+
},
30+
31+
webpack: Object.assign({}, webpackConfig, {
32+
devtool: 'inline-source-map',
33+
node: {
34+
fs: 'empty'
35+
},
36+
module: Object.assign({}, webpackConfig.module, {
37+
// Cover source files when not debugging tests. Otherwise, omit coverage instrumenting to get
38+
// uncluttered source maps.
39+
loaders: webpackConfig.module.loaders.concat([config.singleRun ? {
40+
test: /\.js$/,
41+
include: path.resolve('./packages'),
42+
loader: 'isparta'
43+
} : undefined]).filter(Boolean)
44+
})
45+
}),
46+
47+
webpackMiddleware: {
48+
noInfo: true
49+
}
50+
});
51+
};

Diff for: package.json

+20-3
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,48 @@
99
"build:min": "mkdir -p build && MDL_ENV=production webpack -p",
1010
"dist": "npm run clean && npm run build && npm run build:min",
1111
"dev": "npm run clean && MDL_ENV=development webpack-dev-server --content-base demos --inline --hot",
12-
"lint:js": "eslint --fix packages webpack.config.js",
13-
"postinstall": "lerna bootstrap"
12+
"lint:js": "eslint --fix packages test webpack.config.js karma.conf.js",
13+
"postinstall": "lerna bootstrap",
14+
"pretest": "npm run lint:js",
15+
"test": "karma start --single-run",
16+
"posttest": "istanbul report --root coverage text-summary && istanbul check-coverage --lines 85 --statements 85 --branches 85 --functions 85",
17+
"test:watch": "karma start --auto-watch"
1418
},
1519
"devDependencies": {
1620
"autoprefixer": "^6.3.6",
1721
"babel-loader": "^6.2.4",
1822
"babel-plugin-transform-object-assign": "^6.8.0",
1923
"babel-preset-es2015": "^6.9.0",
24+
"bel": "^4.4.3",
2025
"css-loader": "^0.23.1",
2126
"eslint": "^2.12.0",
2227
"eslint-config-google": "^0.5.0",
2328
"extract-text-webpack-plugin": "^1.0.1",
29+
"isparta-loader": "^2.0.0",
30+
"istanbul": "^0.4.4",
31+
"karma": "^1.1.1",
32+
"karma-chrome-launcher": "^1.0.1",
33+
"karma-coverage": "^1.1.0",
34+
"karma-sourcemap-loader": "^0.3.7",
35+
"karma-tap": "^2.0.1",
36+
"karma-webpack": "^1.7.0",
2437
"lerna": "2.0.0-beta.18",
2538
"node-sass": "^3.7.0",
2639
"postcss-loader": "^0.9.1",
2740
"raw-loader": "^0.5.1",
2841
"sass-loader": "^3.2.0",
2942
"style-loader": "^0.13.1",
43+
"tape": "^4.6.0",
44+
"testdouble": "^1.6.0",
3045
"webpack": "^1.13.1",
3146
"webpack-dev-server": "^1.14.1"
3247
},
3348
"babel": {
3449
"presets": [
3550
"es2015"
3651
],
37-
"plugins": ["transform-object-assign"]
52+
"plugins": [
53+
"transform-object-assign"
54+
]
3855
}
3956
}

Diff for: packages/mdl-auto-init/index.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
const registry = Object.create(null);
22

3+
const CONSOLE_WARN = console.warn.bind(console);
4+
35
/**
46
* Auto-initializes all mdl components on a page.
57
*/
6-
export default function mdlAutoInit() {
7-
const nodes = document.querySelectorAll('[data-mdl-auto-init]');
8+
export default function mdlAutoInit(root = document, warn = CONSOLE_WARN) {
9+
const nodes = root.querySelectorAll('[data-mdl-auto-init]');
810
for (let i = 0, node; (node = nodes[i]); i++) {
911
const ctorName = node.dataset.mdlAutoInit;
1012
if (!ctorName) {
@@ -18,7 +20,7 @@ export default function mdlAutoInit() {
1820
}
1921

2022
if (node[ctorName]) {
21-
console.warn(`(mdl-auto-init) Component already initialized for ${node}. Skipping...`);
23+
warn(`(mdl-auto-init) Component already initialized for ${node}. Skipping...`);
2224
continue;
2325
}
2426

@@ -33,14 +35,22 @@ export default function mdlAutoInit() {
3335
}
3436
}
3537

36-
mdlAutoInit.register = function(componentName, Ctor) {
38+
mdlAutoInit.register = function(componentName, Ctor, warn = CONSOLE_WARN) {
3739
if (typeof Ctor !== 'function') {
3840
throw new Error(`(mdl-auto-init) Invalid Ctor value ${Ctor}. Expected function`);
3941
}
4042
if (registry[componentName]) {
41-
console.warn(
43+
warn(
4244
`(mdl-auto-init) Overriding registration for ${componentName} with ${Ctor}. ` +
4345
`Was: ${registry[componentName]}`);
4446
}
4547
registry[componentName] = Ctor;
4648
};
49+
50+
mdlAutoInit.deregister = function(componentName) {
51+
delete registry[componentName];
52+
};
53+
54+
mdlAutoInit.deregisterAll = function() {
55+
Object.keys(registry).forEach(this.deregister, this);
56+
};

Diff for: test/unit/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/** @fileoverview Bootstraps the test bundle for karma-webpack. */
2+
3+
const testsContext = require.context('.', true, /\.test\.js$/);
4+
testsContext.keys().forEach(testsContext);

Diff for: test/unit/mdl-auto-init/mdl-auto-init.test.js

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import bel from 'bel';
2+
import td from 'testdouble';
3+
import test from 'tape';
4+
import mdlAutoInit from '../../../packages/mdl-auto-init';
5+
6+
class FakeComponent {
7+
static attachTo(node) {
8+
return new this(node);
9+
}
10+
11+
constructor(node) {
12+
this.node = node;
13+
}
14+
}
15+
16+
const createFixture = () => bel`
17+
<div id="root">
18+
<p data-mdl-auto-init="FakeComponent" class="mdl-fake">Fake Element</p>
19+
</div>
20+
`;
21+
22+
const setupTest = () => {
23+
mdlAutoInit.deregisterAll();
24+
mdlAutoInit.register(FakeComponent.name, FakeComponent);
25+
return createFixture();
26+
};
27+
28+
test('calls attachTo() on components registered for identifier on nodes w/ data-mdl-auto-init attr', t => {
29+
t.plan(1);
30+
31+
const root = setupTest();
32+
mdlAutoInit(root);
33+
34+
t.true(root.querySelector('.mdl-fake').FakeComponent instanceof FakeComponent);
35+
});
36+
37+
test('passes the node where "data-mdl-auto-init" was found to attachTo()', t => {
38+
t.plan(1);
39+
40+
const root = setupTest();
41+
mdlAutoInit(root);
42+
43+
const fake = root.querySelector('.mdl-fake');
44+
t.equal(fake.FakeComponent.node, fake);
45+
});
46+
47+
test('throws when no constructor name is specified within "data-mdl-auto-init"', t => {
48+
t.plan(1);
49+
50+
const root = setupTest();
51+
root.querySelector('.mdl-fake').dataset.mdlAutoInit = '';
52+
53+
t.throws(() => mdlAutoInit(root));
54+
});
55+
56+
test('throws when constructor is not registered', t => {
57+
t.plan(1);
58+
59+
const root = setupTest();
60+
root.querySelector('.mdl-fake').dataset.mdlAutoInit = 'MDLUnregisteredComponent';
61+
62+
t.throws(() => mdlAutoInit(root));
63+
});
64+
65+
test('warns when autoInit called multiple times on a node', t => {
66+
t.plan(1);
67+
68+
const root = setupTest();
69+
const warn = td.func('warn');
70+
const {contains} = td.matchers;
71+
72+
mdlAutoInit(root, warn);
73+
mdlAutoInit(root, warn);
74+
75+
t.doesNotThrow(() => td.verify(warn(contains('(mdl-auto-init) Component already initialized'))));
76+
});
77+
78+
test('#register throws when Ctor is not a function', t => {
79+
t.plan(1);
80+
t.throws(() => mdlAutoInit.register('not-a-function', 'Not a function'));
81+
});
82+
83+
test('#register warns when registered key is being overridden', t => {
84+
t.plan(1);
85+
86+
const warn = td.func('warn');
87+
const {contains} = td.matchers;
88+
89+
mdlAutoInit.register(FakeComponent.name, () => ({overridden: true}), warn);
90+
91+
t.true(() => td.verify(warn(contains('(mdl-auto-init) Overriding registration'))));
92+
});

0 commit comments

Comments
 (0)