Skip to content

Commit

Permalink
chore(infrastructure): Set up testing
Browse files Browse the repository at this point in the history
* `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
  • Loading branch information
traviskaufman committed Jul 19, 2016
1 parent 3203fe7 commit 1aa5162
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
.DS_Store
/build
/coverage
packages/*/dist
*.log
51 changes: 51 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const path = require('path');
const webpackConfig = require('./webpack.config')[0];

// TODO: Sourcemaps
// TODO: eslint-plugin-tape
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['tap'],
files: [
'test/unit/index.js'
],
preprocessors: {
'test/unit/index.js': ['webpack', 'sourcemap']
},
reporters: ['dots', 'coverage'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
browsers: ['Chrome'],
concurrency: Infinity,

coverageReporter: {
dir: 'coverage',
reporters: [
{type: 'json', subdir: '.', file: 'coverage.json'},
{type: 'html'}
]
},

webpack: Object.assign({}, webpackConfig, {
devtool: 'inline-source-map',
node: {
fs: 'empty'
},
module: Object.assign({}, webpackConfig.module, {
// Cover source files when not debugging tests. Otherwise, omit coverage instrumenting to get
// uncluttered source maps.
loaders: webpackConfig.module.loaders.concat([config.singleRun ? {
test: /\.js$/,
include: path.resolve('./packages'),
loader: 'isparta'
} : undefined]).filter(Boolean)
})
}),

webpackMiddleware: {
noInfo: true
}
});
};
23 changes: 20 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,48 @@
"build:min": "mkdir -p build && MDL_ENV=production webpack -p",
"dist": "npm run clean && npm run build && npm run build:min",
"dev": "npm run clean && MDL_ENV=development webpack-dev-server --content-base demos --inline --hot",
"lint:js": "eslint --fix packages webpack.config.js",
"postinstall": "lerna bootstrap"
"lint:js": "eslint --fix packages test webpack.config.js karma.conf.js",
"postinstall": "lerna bootstrap",
"pretest": "npm run lint:js",
"test": "karma start --single-run",
"posttest": "istanbul report --root coverage text-summary && istanbul check-coverage --lines 85 --statements 85 --branches 85 --functions 85",
"test:watch": "karma start --auto-watch"
},
"devDependencies": {
"autoprefixer": "^6.3.6",
"babel-loader": "^6.2.4",
"babel-plugin-transform-object-assign": "^6.8.0",
"babel-preset-es2015": "^6.9.0",
"bel": "^4.4.3",
"css-loader": "^0.23.1",
"eslint": "^2.12.0",
"eslint-config-google": "^0.5.0",
"extract-text-webpack-plugin": "^1.0.1",
"isparta-loader": "^2.0.0",
"istanbul": "^0.4.4",
"karma": "^1.1.1",
"karma-chrome-launcher": "^1.0.1",
"karma-coverage": "^1.1.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-tap": "^2.0.1",
"karma-webpack": "^1.7.0",
"lerna": "2.0.0-beta.18",
"node-sass": "^3.7.0",
"postcss-loader": "^0.9.1",
"raw-loader": "^0.5.1",
"sass-loader": "^3.2.0",
"style-loader": "^0.13.1",
"tape": "^4.6.0",
"testdouble": "^1.6.0",
"webpack": "^1.13.1",
"webpack-dev-server": "^1.14.1"
},
"babel": {
"presets": [
"es2015"
],
"plugins": ["transform-object-assign"]
"plugins": [
"transform-object-assign"
]
}
}
20 changes: 15 additions & 5 deletions packages/mdl-auto-init/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const registry = Object.create(null);

const CONSOLE_WARN = console.warn.bind(console);

/**
* Auto-initializes all mdl components on a page.
*/
export default function mdlAutoInit() {
const nodes = document.querySelectorAll('[data-mdl-auto-init]');
export default function mdlAutoInit(root = document, warn = CONSOLE_WARN) {
const nodes = root.querySelectorAll('[data-mdl-auto-init]');
for (let i = 0, node; (node = nodes[i]); i++) {
const ctorName = node.dataset.mdlAutoInit;
if (!ctorName) {
Expand All @@ -18,7 +20,7 @@ export default function mdlAutoInit() {
}

if (node[ctorName]) {
console.warn(`(mdl-auto-init) Component already initialized for ${node}. Skipping...`);
warn(`(mdl-auto-init) Component already initialized for ${node}. Skipping...`);
continue;
}

Expand All @@ -33,14 +35,22 @@ export default function mdlAutoInit() {
}
}

mdlAutoInit.register = function(componentName, Ctor) {
mdlAutoInit.register = function(componentName, Ctor, warn = CONSOLE_WARN) {
if (typeof Ctor !== 'function') {
throw new Error(`(mdl-auto-init) Invalid Ctor value ${Ctor}. Expected function`);
}
if (registry[componentName]) {
console.warn(
warn(
`(mdl-auto-init) Overriding registration for ${componentName} with ${Ctor}. ` +
`Was: ${registry[componentName]}`);
}
registry[componentName] = Ctor;
};

mdlAutoInit.deregister = function(componentName) {
delete registry[componentName];
};

mdlAutoInit.deregisterAll = function() {
Object.keys(registry).forEach(this.deregister, this);
};
4 changes: 4 additions & 0 deletions test/unit/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @fileoverview Bootstraps the test bundle for karma-webpack. */

const testsContext = require.context('.', true, /\.test\.js$/);
testsContext.keys().forEach(testsContext);
92 changes: 92 additions & 0 deletions test/unit/mdl-auto-init/mdl-auto-init.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import bel from 'bel';
import td from 'testdouble';
import test from 'tape';
import mdlAutoInit from '../../../packages/mdl-auto-init';

class FakeComponent {
static attachTo(node) {
return new this(node);
}

constructor(node) {
this.node = node;
}
}

const createFixture = () => bel`
<div id="root">
<p data-mdl-auto-init="FakeComponent" class="mdl-fake">Fake Element</p>
</div>
`;

const setupTest = () => {
mdlAutoInit.deregisterAll();
mdlAutoInit.register(FakeComponent.name, FakeComponent);
return createFixture();
};

test('calls attachTo() on components registered for identifier on nodes w/ data-mdl-auto-init attr', t => {
t.plan(1);

const root = setupTest();
mdlAutoInit(root);

t.true(root.querySelector('.mdl-fake').FakeComponent instanceof FakeComponent);
});

test('passes the node where "data-mdl-auto-init" was found to attachTo()', t => {
t.plan(1);

const root = setupTest();
mdlAutoInit(root);

const fake = root.querySelector('.mdl-fake');
t.equal(fake.FakeComponent.node, fake);
});

test('throws when no constructor name is specified within "data-mdl-auto-init"', t => {
t.plan(1);

const root = setupTest();
root.querySelector('.mdl-fake').dataset.mdlAutoInit = '';

t.throws(() => mdlAutoInit(root));
});

test('throws when constructor is not registered', t => {
t.plan(1);

const root = setupTest();
root.querySelector('.mdl-fake').dataset.mdlAutoInit = 'MDLUnregisteredComponent';

t.throws(() => mdlAutoInit(root));
});

test('warns when autoInit called multiple times on a node', t => {
t.plan(1);

const root = setupTest();
const warn = td.func('warn');
const {contains} = td.matchers;

mdlAutoInit(root, warn);
mdlAutoInit(root, warn);

t.doesNotThrow(() => td.verify(warn(contains('(mdl-auto-init) Component already initialized'))));
});

test('#register throws when Ctor is not a function', t => {
t.plan(1);
t.throws(() => mdlAutoInit.register('not-a-function', 'Not a function'));
});

test('#register warns when registered key is being overridden', t => {
t.plan(1);

const warn = td.func('warn');
const {contains} = td.matchers;

mdlAutoInit.register(FakeComponent.name, () => ({overridden: true}), warn);

t.true(() => td.verify(warn(contains('(mdl-auto-init) Overriding registration'))));
});

0 comments on commit 1aa5162

Please sign in to comment.