Repository: suitcss/preprocessor
Pull request #52 (2016-09-27 16:38:49)

Refactor unit tests
Breaks them out into smaller modules.

The test output is much easier to read - https://travis-ci.org/suitcss/preprocessor/jobs/163150971#L799


Patch:
diff --git a/package.json b/package.json
index 63b568b..ab7e670 100644
--- a/package.json
+++ b/package.json
@@ -42,13 +42,11 @@
     "mocha": "^2.3.4",
     "postcss-property-lookup": "^1.1.4",
     "rewire": "^2.5.1",
-    "sinon": "^1.17.2",
-    "sinon-as-promised": "^4.0.0",
-    "sinon-chai": "^2.8.0"
+    "sinon": "^1.17.2"
   },
   "scripts": {
     "test": "npm run mocha && npm run lint",
-    "mocha": "mocha --reporter spec --slow 400 --timeout 8000",
+    "mocha": "mocha test/index.js --reporter spec --slow 400 --timeout 8000",
     "lint": "eslint lib/index.js test/test.js bin/suitcss",
     "watch": "npm run mocha -- --watch"
   },


Test patch:
diff --git a/test/cli.js b/test/cli.js
new file mode 100644
index 0000000..626d474
--- /dev/null
+++ b/test/cli.js
@@ -0,0 +1,107 @@
+var chai = require('chai');
+var child = require('child_process');
+var util = require('./util');
+var exec = child.exec;
+var expect = chai.expect;
+
+describe('cli', function() {
+  var input = util.read('fixtures/cli/input');
+  var output = util.read('fixtures/cli/input.out');
+
+  afterEach(function() {
+    util.remove('fixtures/cli/output');
+  });
+
+  it('should read from a file and write to a file', function(done) {
+    exec('node bin/suitcss -c test/config/noautoprefixer.js test/fixtures/cli/input.css test/fixtures/cli/output.css', function(err) {
+      if (err) return done(err);
+      var res = util.read('fixtures/cli/output');
+      expect(res).to.equal(output);
+      done();
+    });
+  });
+
+  it('should read from a file and write to stdout', function(done) {
+    exec('node bin/suitcss -L -c test/config/noautoprefixer.js test/fixtures/cli/input.css', function(err, stdout) {
+      if (err) return done(err);
+      expect(stdout).to.equal(output);
+      done();
+    });
+  });
+
+  it('should read from stdin and write to stdout', function(done) {
+    var testChild = exec('node bin/suitcss -L -c test/config/noautoprefixer.js', function(err, stdout) {
+      if (err) return done(err);
+      expect(stdout).to.equal(output);
+      done();
+    });
+
+    testChild.stdin.write(new Buffer(input));
+    testChild.stdin.end();
+  });
+
+  it('should log on verbose', function(done) {
+    exec('node bin/suitcss -v  -c test/config/noautoprefixer.js test/fixtures/cli/input.css test/fixtures/cli/output.css', function(err, stdout) {
+      if (err) return done(err);
+      expect(stdout).to.contain('write');
+      done();
+    });
+  });
+
+  it('should allow configurable import root', function(done) {
+    exec('node bin/suitcss -i test/fixtures -c test/config/noautoprefixer.js test/fixtures/import.css test/fixtures/cli/output.css', function(err) {
+      if (err) return done(err);
+      var res = util.read('fixtures/cli/output');
+      var expected = util.read('fixtures/component.out');
+      expect(res).to.equal(expected);
+      done();
+    });
+  });
+
+  it('should output stylelint warnings', function(done) {
+    exec('node bin/suitcss -i test/fixtures -c test/config/noautoprefixer.js test/fixtures/stylelint-import.css test/fixtures/cli/output.css -l', function(err, stdout) {
+      if (err) return done(err);
+      expect(stdout).to.contain('Expected indentation of 2 spaces');
+      done();
+    });
+  });
+
+  it('should minify the output', function(done) {
+    exec('node bin/suitcss -i test/fixtures -c test/config/noautoprefixer.js test/fixtures/import.css test/fixtures/cli/output.css -m', function(err) {
+      if (err) return done(err);
+      var res = util.read('fixtures/cli/output');
+      var expected = util.read('fixtures/minify.out');
+      expect(res).to.equal(expected);
+      done();
+    });
+  });
+
+  it('should allow a config file to be passed', function(done) {
+    exec('node bin/suitcss -i test/fixtures -c test/config/test.js test/fixtures/config.css test/fixtures/cli/output.css', function(err) {
+      if (err) return done(err);
+      var res = util.read('fixtures/cli/output');
+      var expected = util.read('fixtures/config.out');
+      expect(res).to.equal(expected);
+      done();
+    });
+  });
+
+  it('should output an error to stderr on conformance failure when --throw-error is set', function(done) {
+    exec('node bin/suitcss -i test/fixtures -e test/fixtures/import-error.css test/fixtures/cli/output.css', function(err, stdout, stderr) {
+      expect(err).to.be.an('error');
+      expect(err.code).to.equal(1);
+      expect(stderr).to.contain('postcss-reporter: warnings or errors were found');
+      done();
+    });
+  });
+
+  it('should log on non-existant file', function(done) {
+    exec('node bin/suitcss -c test/config/noautoprefixer.js test/fixtures/cli/non-existant.css', function(err, stdout, stderr) {
+      expect(err).to.be.an('error');
+      expect(err.code).to.equal(1);
+      expect(stderr).to.contain('not found');
+      done();
+    });
+  });
+});
+
diff --git a/test/noautoprefixer.config.js b/test/config/noautoprefixer.js
similarity index 100%
rename from test/noautoprefixer.config.js
rename to test/config/noautoprefixer.js
diff --git a/test/test.config.js b/test/config/test.js
similarity index 100%
rename from test/test.config.js
rename to test/config/test.js
diff --git a/test/index.js b/test/index.js
new file mode 100644
index 0000000..c5a6dbc
--- /dev/null
+++ b/test/index.js
@@ -0,0 +1,4 @@
+require('./node');
+require('./options');
+require('./cli');
+require('./linting');
diff --git a/test/linting.js b/test/linting.js
new file mode 100644
index 0000000..4e8b6eb
--- /dev/null
+++ b/test/linting.js
@@ -0,0 +1,81 @@
+var chai = require('chai');
+var chaiAsPromised = require('chai-as-promised');
+var suitcss = require('../lib');
+var util = require('./util');
+var expect = chai.expect;
+
+chai.use(chaiAsPromised);
+
+describe('postcss-bem-linter', function() {
+  it('should pass if component conforms', function() {
+    return expect(
+      suitcss('/** @define Foo */\n\n.Foo { color: red; }\n', {
+        'postcss-reporter': {
+          throwError: true
+        }
+      })
+    ).to.be.fulfilled;
+  });
+
+  it('should fail if component does not conform', function() {
+    return expect(
+      suitcss('/** @define Foo */\n\n.foo { color: red; }\n', {
+        'postcss-reporter': {
+          throwError: true
+        }
+      })
+    ).to.be.rejectedWith(Error, 'postcss-reporter: warnings or errors were found');
+  });
+});
+
+describe('stylelint', function() {
+  it('should lint the component', function() {
+    return expect(
+      suitcss(util.read('fixtures/component'), {
+        root: 'test/fixtures',
+        'postcss-reporter': {
+          throwError: true
+        }
+      })
+    ).to.be.fulfilled;
+  });
+
+  it('should allow the config to be overidden', function() {
+    return expect(
+      suitcss('@import "./stylelint.css"\n\n', {
+        root: 'test/fixtures',
+        stylelint: {
+          extends: 'stylelint-config-suitcss',
+          rules: {
+            indentation: 4
+          }
+        },
+        'postcss-reporter': {
+          throwError: true
+        }
+      })
+    ).to.be.fulfilled;
+  });
+
+  it('should throw an error if stylelint fails', function() {
+    return expect(
+      suitcss('@import "./stylelint.css"\n\n', {
+        root: 'test/fixtures',
+        'postcss-reporter': {
+          throwError: true
+        }
+      })
+    ).to.be.rejectedWith(Error, 'postcss-reporter: warnings or errors were found');
+  });
+
+  it('should lint the input file', function() {
+    return expect(
+      suitcss('@import "./stylelint.css"\n\n', {
+        root: 'test/fixtures',
+        'postcss-reporter': {
+          throwError: true
+        }
+      })
+    ).to.be.rejectedWith(Error, 'postcss-reporter: warnings or errors were found');
+  });
+});
diff --git a/test/node.js b/test/node.js
new file mode 100644
index 0000000..b12877c
--- /dev/null
+++ b/test/node.js
@@ -0,0 +1,49 @@
+var chai = require('chai');
+var suitcss = require('../lib');
+var util = require('./util');
+var expect = chai.expect;
+
+describe('node API', function() {
+  it('should return a css string', function(done) {
+    suitcss('body {}', {lint: false}).then(function(result) {
+      expect(result.css).to.be.a('string');
+      done();
+    });
+  });
+
+  it('should handle invalid input', function() {
+    expect(function() {
+      suitcss(null, {lint: false});
+    }).to.throw(TypeError);
+  });
+
+  it('should preprocess CSS correctly', function(done) {
+    var input = util.read('fixtures/component');
+    var output = util.read('fixtures/component.out');
+
+    suitcss(input, {
+      root: 'test/fixtures',
+      lint: false,
+      // disable autoprefixer
+      autoprefixer: {add: false, remove: false}
+    }).then(function(result) {
+      expect(result.css.trim()).to.be.equal(output.trim());
+      done();
+    }).catch(done);
+  });
+
+  it('should add vendor prefixes', function(done) {
+    var input = '.test { filter: blur(1px) }';
+    var output = '.test { -webkit-filter: blur(1px); filter: blur(1px) }';
+
+    suitcss(input, {
+      lint: false,
+      autoprefixer: {
+        browsers: 'Chrome 50'
+      }
+    }).then(function(result) {
+      expect(result.css.trim()).to.be.equal(output.trim());
+      done();
+    }).catch(done);
+  });
+});
diff --git a/test/options.js b/test/options.js
new file mode 100644
index 0000000..5ddccce
--- /dev/null
+++ b/test/options.js
@@ -0,0 +1,240 @@
+var sinon = require('sinon');
+var rewire = require('rewire');
+var chai = require('chai');
+var suitcss = rewire('../lib');
+var expect = chai.expect;
+
+describe('basic options', function() {
+  var mergeOptions, defaults;
+
+  beforeEach(function() {
+    mergeOptions = suitcss.__get__('mergeOptions');
+    defaults = suitcss.__get__('defaults');
+  });
+
+  it('should use default options when nothing is passed', function() {
+    var keys = Object.keys(defaults);
+    expect(mergeOptions({})).to.have.keys(keys);
+    expect(mergeOptions()).to.have.keys(keys);
+    expect(mergeOptions({}).use).to.eql(defaults.use);
+    expect(mergeOptions().use).to.eql(defaults.use);
+  });
+
+  it('should allow an import root to be set', function() {
+    var opts = mergeOptions({root: 'test/root'});
+    expect(opts['postcss-easy-import'].root).to.equal('test/root');
+  });
+
+  it('should allow stylelint to be disabled', function() {
+    var opts = mergeOptions({lint: false});
+    expect(opts.lint).to.be.false;
+  });
+
+  it('should allow a minify option to be set', function() {
+    var opts = mergeOptions({minify: true});
+    expect(opts.minify).to.be.true;
+  });
+
+  it('should merge config options with existing defaults', function() {
+    var autoprefixer = {browsers: ['> 1%', 'IE 7'], cascade: false};
+    var opts = mergeOptions({
+      root: 'test/root',
+      autoprefixer: autoprefixer
+    });
+
+    expect(opts.use).to.eql([
+      'postcss-easy-import',
+      'postcss-custom-properties',
+      'postcss-calc',
+      'postcss-color-function',
+      'postcss-custom-media',
+      'postcss-apply',
+      'autoprefixer',
+      'postcss-reporter'
+    ]);
+    expect(opts.autoprefixer).to.eql(autoprefixer);
+    expect(opts['postcss-easy-import'].root).to.equal('test/root');
+  });
+});
+
+describe('re-ordering the `use` array of postcss plugins', function() {
+  var mergeOptions;
+
+  beforeEach(function() {
+    mergeOptions = suitcss.__get__('mergeOptions');
+  });
+
+  it('should allow reordering of use array and remove duplicates', function() {
+    var opts = mergeOptions({
+      use: ['autoprefixer', 'postcss-at2x', 'postcss-calc', 'postcss-reporter']
+    });
+
+    expect(opts.use).to.eql([
+      'postcss-easy-import',
+      'postcss-custom-properties',
+      'postcss-color-function',
+      'postcss-custom-media',
+      'postcss-apply',
+      'autoprefixer',
+      'postcss-at2x',
+      'postcss-calc',
+      'postcss-reporter'
+    ]);
+  });
+
+  it('should just append plugins if no duplicates are used', function() {
+    var opts = mergeOptions({
+      use: ['postcss-at2x', 'postcss-property-lookup']
+    });
+
+    expect(opts.use).to.eql([
+      'postcss-easy-import',
+      'postcss-custom-properties',
+      'postcss-calc',
+      'postcss-color-function',
+      'postcss-custom-media',
+      'postcss-apply',
+      'autoprefixer',
+      'postcss-reporter',
+      'postcss-at2x',
+      'postcss-property-lookup'
+    ]);
+  });
+});
+
+describe('using the `onImport` option in postcss-import', function() {
+  var updateWatchTaskFilesSpy, revert;
+
+  beforeEach(function() {
+    updateWatchTaskFilesSpy = sinon.spy();
+    revert = suitcss.__set__('updateWatchTaskFiles', updateWatchTaskFilesSpy);
+  });
+
+  afterEach(function() {
+    revert();
+  });
+
+  it('should call the updateWatchTaskFiles function with the file paths', function(done) {
+    suitcss('@import "./util.css";', {
+      root: 'test/fixtures',
+      lint: false
+    }).then(function() {
+      expect(updateWatchTaskFilesSpy.getCall(0).args[0][0]).to.contain('util.css');
+      done();
+    })
+      .catch(done);
+  });
+
+  it('should call a custom onImport function', function(done) {
+    var onImportSpy = sinon.spy();
+
+    suitcss('@import "./util.css";', {
+      root: 'test/fixtures',
+      lint: false,
+      'postcss-easy-import': {
+        onImport: onImportSpy
+      }
+    }).then(function() {
+      expect(onImportSpy.getCall(0).args[0][0]).to.contain('util.css');
+      expect(updateWatchTaskFilesSpy.getCall(0).args[0][0]).to.contain('util.css');
+      done();
+    })
+      .catch(done);
+  });
+});
+
+describe('using the `transform` option in postcss-import', function() {
+  it('should use a default transform function that just returns the css', function(done) {
+    suitcss('@import "./util.css";', {
+      root: 'test/fixtures',
+      lint: false
+    }).then(function(result) {
+      expect(result.css).to.equal('.u-img {\n  border-radius: 50%;\n}');
+      done();
+    })
+      .catch(done);
+  });
+
+  it('should call a custom transform function with the imported component', function(done) {
+    var transformStub = sinon.stub().returns('body { color: blue; }');
+
+    suitcss('@import "./util.css";', {
+      root: 'test/fixtures',
+      lint: false,
+      'postcss-easy-import': {
+        transform: transformStub
+      }
+    }).then(function(result) {
+      expect(transformStub.calledOnce).to.be.true;
+      expect(transformStub.getCall(0).args[0]).to.equal('.u-img {\n  border-radius: 50%;\n}\n');
+      expect(result.css).to.equal('body { color: blue; }');
+      done();
+    })
+      .catch(done);
+  });
+
+  it('should also work with a promise returned from the custom transform function', function(done) {
+    suitcss('@import "./util.css";', {
+      root: 'test/fixtures',
+      lint: false,
+      'postcss-easy-import': {
+        transform: function() {
+          return Promise.resolve('body { font: red; }');
+        }
+      }
+    }).then(function(result) {
+      expect(result.css).to.equal('body { font: red; }');
+      done();
+    })
+      .catch(done);
+  });
+});
+
+describe('using the debug option', function() {
+  it('should allow a debug function to be ran on plugins', function (done) {
+    var debug = sinon.spy(function(plugins) {
+      return plugins;
+    });
+
+    suitcss('body {}', {
+      debug: debug,
+      lint: false
+    }).then(function() {
+      expect(debug.calledOnce).to.be.true;
+      done();
+    }).catch(done);
+  });
+});
+
+describe('passing options to postcss processor instance', function() {
+  var postcssStub, processMethodStub, revert;
+
+  beforeEach(function() {
+    postcssStub = sinon.stub();
+    processMethodStub = sinon.stub().returns(Promise.resolve());
+
+    postcssStub.returns({
+      use: sinon.stub().returns({use: sinon.spy()}),
+      process: processMethodStub
+    });
+    revert = suitcss.__set__('postcss', postcssStub);
+    suitcss('body {}', {
+      root: 'something',
+      lint: false,
+      postcss: {
+        test: 'testing'
+      }
+    }, 'filename.css');
+  });
+
+  afterEach(function() {
+    revert();
+  });
+
+  it('should pass postcss options to the processor', function() {
+    expect(processMethodStub.getCall(0).args[1]).to.eql({
+      from: 'filename.css',
+      test: 'testing'
+    });
+  });
+});
diff --git a/test/test.js b/test/test.js
deleted file mode 100644
index 9a09692..0000000
--- a/test/test.js
+++ /dev/null
@@ -1,488 +0,0 @@
-var chai = require('chai');
-var sinon = require('sinon');
-var child = require('child_process');
-var exec = child.exec;
-var fs = require('fs');
-var rewire = require('rewire');
-var suitcss = rewire('../lib');
-var path = require('path');
-var sinonChai = require('sinon-chai');
-var chaiAsPromised = require('chai-as-promised');
-require('sinon-as-promised');
-
-chai.use(sinonChai);
-chai.use(chaiAsPromised);
-var expect = chai.expect;
-
-/**
- * Node API tests.
- */
-
-describe('suitcss', function() {
-  it('should return a css string', function(done) {
-    suitcss('body {}', {lint: false}).then(function(result) {
-      expect(result.css).to.be.a('string');
-      done();
-    });
-  });
-
-  it('should handle invalid input', function() {
-    expect(function() {
-      suitcss(null, {lint: false});
-    }).to.throw(TypeError);
-  });
-
-  describe('using options', function() {
-    var mergeOptions, defaults;
-
-    beforeEach(function() {
-      mergeOptions = suitcss.__get__('mergeOptions');
-      defaults = suitcss.__get__('defaults');
-    });
-
-    it('should use default options when nothing is passed', function() {
-      var keys = Object.keys(defaults);
-      expect(mergeOptions({})).to.have.keys(keys);
-      expect(mergeOptions()).to.have.keys(keys);
-      expect(mergeOptions({}).use).to.eql(defaults.use);
-      expect(mergeOptions().use).to.eql(defaults.use);
-    });
-
-    it('should allow an import root to be set', function() {
-      var opts = mergeOptions({root: 'test/root'});
-      expect(opts['postcss-easy-import'].root).to.equal('test/root');
-    });
-
-    it('should allow a debug function to be ran on plugins', function (done) {
-      var debug = sinon.spy(function (plugins) {
-        return plugins;
-      });
-
-      suitcss('body {}', {
-        debug: debug,
-        lint: false
-      }).then(function () {
-        expect(debug.calledOnce).to.be.true;
-        done();
-      }).catch(done);
-    });
-
-    it('should allow stylelint to be disabled', function() {
-      var opts = mergeOptions({lint: false});
-      expect(opts.lint).to.be.false;
-    });
-
-    it('should allow a minify option to be set', function() {
-      var opts = mergeOptions({minify: true});
-      expect(opts.minify).to.be.true;
-    });
-
-    it('should merge config options with existing defaults', function() {
-      var autoprefixer = {browsers: ['> 1%', 'IE 7'], cascade: false};
-      var opts = mergeOptions({
-        root: 'test/root',
-        autoprefixer: autoprefixer
-      });
-
-      expect(opts.use).to.eql([
-        'postcss-easy-import',
-        'postcss-custom-properties',
-        'postcss-calc',
-        'postcss-color-function',
-        'postcss-custom-media',
-        'postcss-apply',
-        'autoprefixer',
-        'postcss-reporter'
-      ]);
-      expect(opts.autoprefixer).to.eql(autoprefixer);
-      expect(opts['postcss-easy-import'].root).to.equal('test/root');
-    });
-
-    describe('passing options to postcss', function() {
-      var postcssStub, processMethodStub, revert;
-
-      beforeEach(function() {
-        postcssStub = sinon.stub();
-        processMethodStub = sinon.stub().returns(Promise.resolve());
-
-        postcssStub.returns({
-          use: sinon.stub().returns({use: sinon.spy()}),
-          process: processMethodStub
-        });
-        revert = suitcss.__set__('postcss', postcssStub);
-        suitcss('body {}', {
-          root: 'something',
-          lint: false,
-          postcss: {
-            test: 'testing'
-          }
-        }, 'filename.css');
-      });
-
-      afterEach(function() {
-        revert();
-      });
-
-      it('should pass postcss options to the processor', function() {
-        expect(processMethodStub.getCall(0).args[1]).to.eql({
-          from: 'filename.css',
-          test: 'testing'
-        });
-      });
-    });
-
-    describe('using the transform option in postcss-import', function() {
-      it('should use a default transform function that just returns the css', function(done) {
-        suitcss('@import "./util.css";', {
-          root: 'test/fixtures',
-          lint: false
-        }).then(function(result) {
-          expect(result.css).to.equal('.u-img {\n  border-radius: 50%;\n}');
-          done();
-        })
-        .catch(done);
-      });
-
-      it('should call a custom transform function with the imported component', function(done) {
-        var transformStub = sinon.stub().returns('body { color: blue; }');
-
-        suitcss('@import "./util.css";', {
-          root: 'test/fixtures',
-          lint: false,
-          'postcss-easy-import': {
-            transform: transformStub
-          }
-        }).then(function(result) {
-          expect(transformStub.calledOnce).to.be.true;
-          expect(transformStub.getCall(0).args[0]).to.equal('.u-img {\n  border-radius: 50%;\n}\n');
-          expect(result.css).to.equal('body { color: blue; }');
-          done();
-        })
-        .catch(done);
-      });
-
-      it('should also work with a promise returned from the custom transform function', function(done) {
-        suitcss('@import "./util.css";', {
-          root: 'test/fixtures',
-          lint: false,
-          'postcss-easy-import': {
-            transform: function() {
-              return Promise.resolve('body { font: red; }');
-            }
-          }
-        }).then(function(result) {
-          expect(result.css).to.equal('body { font: red; }');
-          done();
-        })
-        .catch(done);
-      });
-    });
-
-    describe('using the onImport option postcss-import', function() {
-      var updateWatchTaskFilesSpy, revert;
-
-      beforeEach(function() {
-        updateWatchTaskFilesSpy = sinon.spy();
-        revert = suitcss.__set__('updateWatchTaskFiles', updateWatchTaskFilesSpy);
-      });
-
-      afterEach(function() {
-        revert();
-      });
-
-      it('should call the updateWatchTaskFiles function with the file paths', function(done) {
-        suitcss('@import "./util.css";', {
-          root: 'test/fixtures',
-          lint: false
-        }).then(function() {
-          expect(updateWatchTaskFilesSpy.getCall(0).args[0][0]).to.contain('util.css');
-          done();
-        })
-        .catch(done);
-      });
-
-      it('should call a custom onImport function', function(done) {
-        var onImportSpy = sinon.spy();
-
-        suitcss('@import "./util.css";', {
-          root: 'test/fixtures',
-          lint: false,
-          'postcss-easy-import': {
-            onImport: onImportSpy
-          }
-        }).then(function() {
-          expect(onImportSpy.getCall(0).args[0][0]).to.contain('util.css');
-          expect(updateWatchTaskFilesSpy.getCall(0).args[0][0]).to.contain('util.css');
-          done();
-        })
-        .catch(done);
-      });
-    });
-
-    describe('re-ordering plugins', function() {
-      it('should allow reordering of use array and remove duplicates', function() {
-        var opts = mergeOptions({
-          use: ['autoprefixer', 'postcss-at2x', 'postcss-calc', 'postcss-reporter']
-        });
-
-        expect(opts.use).to.eql([
-          'postcss-easy-import',
-          'postcss-custom-properties',
-          'postcss-color-function',
-          'postcss-custom-media',
-          'postcss-apply',
-          'autoprefixer',
-          'postcss-at2x',
-          'postcss-calc',
-          'postcss-reporter'
-        ]);
-      });
-
-      it('should just append plugins if no duplicates are used', function() {
-        var opts = mergeOptions({
-          use: ['postcss-at2x', 'postcss-property-lookup']
-        });
-
-        expect(opts.use).to.eql([
-          'postcss-easy-import',
-          'postcss-custom-properties',
-          'postcss-calc',
-          'postcss-color-function',
-          'postcss-custom-media',
-          'postcss-apply',
-          'autoprefixer',
-          'postcss-reporter',
-          'postcss-at2x',
-          'postcss-property-lookup'
-        ]);
-      });
-    });
-
-    describe('stylelint', function() {
-      it('should lint the component', function() {
-        return expect(
-          suitcss(read('fixtures/component'), {
-            root: 'test/fixtures',
-            'postcss-reporter': {
-              throwError: true
-            }
-          })
-        ).to.be.fulfilled;
-      });
-
-      it('should allow the config to be overidden', function() {
-        return expect(
-          suitcss('@import "./stylelint.css"\n\n', {
-            root: 'test/fixtures',
-            stylelint: {
-              extends: 'stylelint-config-suitcss',
-              rules: {
-                indentation: 4
-              }
-            },
-            'postcss-reporter': {
-              throwError: true
-            }
-          })
-        ).to.be.fulfilled;
-      });
-
-      it('should throw an error if stylelint fails', function() {
-        return expect(
-          suitcss('@import "./stylelint.css"\n\n', {
-            root: 'test/fixtures',
-            'postcss-reporter': {
-              throwError: true
-            }
-          })
-        ).to.be.rejectedWith(Error, 'postcss-reporter: warnings or errors were found');
-      });
-
-      it('should lint the input file', function() {
-        return expect(
-          suitcss('body {}', {
-            'postcss-reporter': {
-              throwError: true
-            }
-          })
-        ).to.be.rejectedWith(Error, 'postcss-reporter: warnings or errors were found');
-      });
-    });
-  });
-});
-
-/**
- * Feature tests.
- */
-
-describe('features', function() {
-  it('should preprocess CSS correctly', function(done) {
-    var input = read('fixtures/component');
-    var output = read('fixtures/component.out');
-
-    suitcss(input, {
-      root: 'test/fixtures',
-      lint: false,
-      // disable autoprefixer
-      autoprefixer: {add: false, remove: false}
-    }).then(function(result) {
-      expect(result.css.trim()).to.be.equal(output.trim());
-      done();
-    }).catch(done);
-  });
-
-  it('should add vendor prefixes', function(done) {
-    var input = '.test { filter: blur(1px) }';
-    var output = '.test { -webkit-filter: blur(1px); filter: blur(1px) }';
-
-    suitcss(input, {
-      lint: false,
-      autoprefixer: {
-        browsers: 'Chrome 50'
-      }
-    }).then(function(result) {
-      expect(result.css.trim()).to.be.equal(output.trim());
-      done();
-    }).catch(done);
-  });
-});
-
-/**
- * CLI tests.
- */
-
-describe('cli', function() {
-  var input = read('fixtures/cli/input');
-  var output = read('fixtures/cli/input.out');
-
-  afterEach(function() {
-    remove('fixtures/cli/output');
-  });
-
-  it('should read from a file and write to a file', function(done) {
-    exec('node bin/suitcss -c test/noautoprefixer.config.js test/fixtures/cli/input.css test/fixtures/cli/output.css', function(err) {
-      if (err) return done(err);
-      var res = read('fixtures/cli/output');
-      expect(res).to.equal(output);
-      done();
-    });
-  });
-
-  it('should read from a file and write to stdout', function(done) {
-    exec('node bin/suitcss -L -c test/noautoprefixer.config.js test/fixtures/cli/input.css', function(err, stdout) {
-      if (err) return done(err);
-      expect(stdout).to.equal(output);
-      done();
-    });
-  });
-
-  it('should read from stdin and write to stdout', function(done) {
-    var testChild = exec('node bin/suitcss -L -c test/noautoprefixer.config.js', function(err, stdout) {
-      if (err) return done(err);
-      expect(stdout).to.equal(output);
-      done();
-    });
-
-    testChild.stdin.write(new Buffer(input));
-    testChild.stdin.end();
-  });
-
-  it('should log on verbose', function(done) {
-    exec('node bin/suitcss -v  -c test/noautoprefixer.config.js test/fixtures/cli/input.css test/fixtures/cli/output.css', function(err, stdout) {
-      if (err) return done(err);
-      expect(stdout).to.contain('write');
-      done();
-    });
-  });
-
-  it('should allow configurable import root', function(done) {
-    exec('node bin/suitcss -i test/fixtures -c test/noautoprefixer.config.js test/fixtures/import.css test/fixtures/cli/output.css', function(err) {
-      if (err) return done(err);
-      var res = read('fixtures/cli/output');
-      var expected = read('fixtures/component.out');
-      expect(res).to.equal(expected);
-      done();
-    });
-  });
-
-  it('should output stylelint warnings', function(done) {
-    exec('node bin/suitcss -i test/fixtures -c test/noautoprefixer.config.js test/fixtures/stylelint-import.css test/fixtures/cli/output.css -l', function(err, stdout) {
-      if (err) return done(err);
-      expect(stdout).to.contain('Expected indentation of 2 spaces');
-      done();
-    });
-  });
-
-  it('should minify the output', function(done) {
-    exec('node bin/suitcss -i test/fixtures -c test/noautoprefixer.config.js test/fixtures/import.css test/fixtures/cli/output.css -m', function(err) {
-      if (err) return done(err);
-      var res = read('fixtures/cli/output');
-      var expected = read('fixtures/minify.out');
-      expect(res).to.equal(expected);
-      done();
-    });
-  });
-
-  it('should allow a config file to be passed', function(done) {
-    exec('node bin/suitcss -i test/fixtures -c test/test.config.js test/fixtures/config.css test/fixtures/cli/output.css', function(err) {
-      if (err) return done(err);
-      var res = read('fixtures/cli/output');
-      var expected = read('fixtures/config.out');
-      expect(res).to.equal(expected);
-      done();
-    });
-  });
-
-  it('should output an error to stderr on conformance failure when --throw-error is set', function(done) {
-    exec('node bin/suitcss -i test/fixtures -e test/fixtures/import-error.css test/fixtures/cli/output.css', function(err, stdout, stderr) {
-      expect(err).to.be.an('error');
-      expect(err.code).to.equal(1);
-      expect(stderr).to.contain('postcss-reporter: warnings or errors were found');
-      done();
-    });
-  });
-
-  it('should log on non-existant file', function(done) {
-    exec('node bin/suitcss -c test/noautoprefixer.config.js test/fixtures/cli/non-existant.css', function(err, stdout, stderr) {
-      expect(err).to.be.an('error');
-      expect(err.code).to.equal(1);
-      expect(stderr).to.contain('not found');
-      done();
-    });
-  });
-});
-
-/**
- * Read a fixture by `filename`.
- *
- * @param {String} filename
- * @return {String}
- */
-
-function read(filename) {
-  var file = resolve(filename);
-  return fs.readFileSync(file, 'utf8');
-}
-
-/**
- * Remove a fixture by `filename`.
- *
- * @param {String} filename
- */
-
-function remove(filename) {
-  var file = resolve(filename);
-  if (!fs.existsSync(file)) return;
-  fs.unlinkSync(file);
-}
-
-/**
- * Resolve a fixture by `filename`.
- *
- * @param {String} filename
- * @return {String}
- */
-
-function resolve(filename) {
-  return path.resolve(__dirname, filename + '.css');
-}
diff --git a/test/util.js b/test/util.js
new file mode 100644
index 0000000..d2be763
--- /dev/null
+++ b/test/util.js
@@ -0,0 +1,23 @@
+var fs = require('fs');
+var path = require('path');
+
+function read(filename) {
+  var file = resolve(filename);
+  return fs.readFileSync(file, 'utf8');
+}
+
+function remove(filename) {
+  var file = resolve(filename);
+  if (!fs.existsSync(file)) return;
+  fs.unlinkSync(file);
+}
+
+function resolve(filename) {
+  return path.resolve(__dirname, filename + '.css');
+}
+
+module.exports = {
+  read: read,
+  remove: remove,
+  resolve: resolve
+};