diff --git a/.gitignore b/.gitignore index 949e21b82b..8fdaf5903f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ # version file and tarball created by 'npm pack' /git-revision.txt /matrix-react-sdk-*.tgz + +# test reports created by karma +/karma-reports diff --git a/jenkins.sh b/jenkins.sh index a2f28ffb8f..51fab5d020 100755 --- a/jenkins.sh +++ b/jenkins.sh @@ -14,6 +14,9 @@ npm install ./node_modules/matrix-js-sdk-*.tgz # install the other dependencies npm install +# run the mocha tests +npm run test + # delete the old tarball, if it exists rm -f matrix-react-sdk-*.tgz diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000000..7a8831635b --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,121 @@ +// karma.conf.js - the config file for karma, which runs our tests. + +var webpack = require('webpack'); +var path = require('path'); + +/* + * We use webpack to build our tests. It's a pain to have to wait for webpack + * to build everything; however it's the easiest way to load our dependencies + * from node_modules. + * + * TODO: + * - can we run one test at a time? + */ + +process.env.PHANTOMJS_BIN = 'node_modules/.bin/phantomjs'; + +module.exports = function (config) { + config.set({ + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha'], + + // list of files / patterns to load in the browser + files: [ + 'test/tests.js', + ], + + // list of files to exclude + // (this doesn't work, and I don't know why - we still rerun the tests + // when lockfiles are created) + exclude: [ + '**/.#*' + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: + // https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'test/tests.js': ['webpack', 'sourcemap'] + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress', 'junit'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || + // config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file + // changes + autoWatch: true, + + // start these browsers + // available browser launchers: + // https://npmjs.org/browse/keyword/karma-launcher + browsers: [ + 'Chrome', + //'PhantomJS', + ], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity, + + junitReporter: { + outputDir: 'karma-reports', + }, + + webpack: { + module: { + loaders: [ + { test: /\.json$/, loader: "json" }, + { + // disable 'require' and 'define' for sinon, per + // https://github.com/webpack/webpack/issues/304#issuecomment-170883329 + test: /sinon\/pkg\/sinon\.js/, + // TODO: use 'query'? + loader: 'imports?define=>false,require=>false', + }, + { + test: /\.js$/, loader: "babel", + include: [path.resolve('./src'), + path.resolve('./test'), + ], + query: { + presets: ['react', 'es2015'] + }, + }, + ], + noParse: [ + // don't parse the languages within highlight.js. They + // cause stack overflows + // (https://github.com/webpack/webpack/issues/1721), and + // there is no need for webpack to parse them - they can + // just be included as-is. + /highlight\.js\/lib\/languages/, + ], + }, + resolve: { + alias: { + 'matrix-react-sdk': path.resolve('src/index.js'), + 'sinon': 'sinon/pkg/sinon.js', + }, + }, + devtool: 'inline-source-map', + }, + }); +}; diff --git a/package.json b/package.json index d2e9c32d74..c26289712c 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,9 @@ "build": "babel src -d lib --source-maps", "start": "babel src -w -d lib --source-maps", "clean": "rimraf lib", - "prepublish": "npm run build; git rev-parse HEAD > git-revision.txt" + "prepublish": "npm run build; git rev-parse HEAD > git-revision.txt", + "test": "karma start --browsers PhantomJS", + "test-multi": "karma start --single-run=false" }, "dependencies": { "classnames": "^2.1.2", @@ -38,13 +40,31 @@ "velocity-animate": "^1.2.3", "velocity-ui-pack": "^1.2.2" }, - "//deps": "The loader packages are here because webpack in a project that depends on us needs them in this package's node_modules folder", - "//depsbuglink": "https://github.com/webpack/webpack/issues/1472", "devDependencies": { "babel": "^5.8.23", + "babel-core": "^6.7.4", + "babel-loader": "^6.2.4", + "babel-preset-es2015": "^6.6.0", + "babel-preset-react": "^6.5.0", + "babel-runtime": "^6.6.1", + "expect": "^1.16.0", + "imports-loader": "^0.6.5", "json-loader": "^0.5.3", + "karma": "^0.13.22", + "karma-chrome-launcher": "^0.2.3", + "karma-cli": "^0.1.2", + "karma-junit-reporter": "^0.4.1", + "karma-mocha": "^0.2.2", + "karma-phantomjs-launcher": "^1.0.0", + "karma-sourcemap-loader": "^0.3.7", + "karma-webpack": "^1.7.0", + "mocha": "^2.4.5", + "phantomjs-prebuilt": "^2.1.7", + "react-addons-test-utils": "^0.14.7", "require-json": "0.0.1", "rimraf": "^2.4.3", - "source-map-loader": "^0.1.5" + "sinon": "^1.17.3", + "source-map-loader": "^0.1.5", + "webpack": "^1.12.14" } } diff --git a/test/components/structures/MatrixChat-test.js b/test/components/structures/MatrixChat-test.js new file mode 100644 index 0000000000..7d96b3a77a --- /dev/null +++ b/test/components/structures/MatrixChat-test.js @@ -0,0 +1,29 @@ +var React = require('react'); +var TestUtils = require('react-addons-test-utils'); +var expect = require('expect'); + +var sdk = require('matrix-react-sdk'); + +var test_utils = require('../../test-utils'); +var peg = require('../../../src/MatrixClientPeg.js'); +var q = require('q'); + +describe('MatrixChat', function () { + var MatrixChat; + before(function() { + test_utils.stubClient(); + MatrixChat = sdk.getComponent('structures.MatrixChat'); + }); + + it('gives a login panel by default', function () { + peg.get().loginFlows.returns(q({flows:[]})); + + var res = TestUtils.renderIntoDocument( + + ); + + // we expect a single component + TestUtils.findRenderedComponentWithType( + res, sdk.getComponent('structures.login.Login')); + }); +}); diff --git a/test/components/stub-component.js b/test/components/stub-component.js new file mode 100644 index 0000000000..8882a0b35d --- /dev/null +++ b/test/components/stub-component.js @@ -0,0 +1,11 @@ +/* A dummy React component which we use for stubbing out app-level components + */ +'use strict'; + +var React = require('react'); + +module.exports = React.createClass({ + render: function() { + return
; + }, +}); diff --git a/test/test-component-index.js b/test/test-component-index.js new file mode 100644 index 0000000000..9b5c07af54 --- /dev/null +++ b/test/test-component-index.js @@ -0,0 +1,18 @@ +/* + * test-component-index.js + * + * Stub out a bunch of the components which we expect the application to + * provide + */ +var components = require('../src/component-index.js').components; +var stub = require('./components/stub-component.js'); + +components['structures.LeftPanel'] = stub; +components['structures.RightPanel'] = stub; +components['structures.RoomDirectory'] = stub; +components['views.globals.MatrixToolbar'] = stub; +components['views.globals.GuestWarningBar'] = stub; +components['views.globals.NewVersionBar'] = stub; +components['views.elements.Spinner'] = stub; + +module.exports.components = components; diff --git a/test/test-utils.js b/test/test-utils.js new file mode 100644 index 0000000000..7c0f38693a --- /dev/null +++ b/test/test-utils.js @@ -0,0 +1,36 @@ +"use strict"; + +var peg = require('../src/MatrixClientPeg.js'); +var jssdk = require('matrix-js-sdk'); +var sinon = require('sinon'); + +/** + * Stub out the MatrixClient, and configure the MatrixClientPeg object to + * return it when get() is called. + */ +module.exports.stubClient = function() { + var pegstub = sinon.stub(peg); + + var matrixClientStub = sinon.createStubInstance(jssdk.MatrixClient); + pegstub.get.returns(matrixClientStub); +} + + +/** + * make the test fail, with the given exception + * + *

This is useful for use with integration tests which use asyncronous + * methods: it can be added as a 'catch' handler in a promise chain. + * + * @param {Error} error exception to be reported + * + * @example + * it("should not throw", function(done) { + * asynchronousMethod().then(function() { + * // some tests + * }).catch(utils.failTest).done(done); + * }); + */ +module.exports.failTest = function(error) { + expect(error.stack).toBe(null); +}; diff --git a/test/tests.js b/test/tests.js new file mode 100644 index 0000000000..3eec9ae8ee --- /dev/null +++ b/test/tests.js @@ -0,0 +1,11 @@ +// tests.js +// +// Our master test file: uses the webpack require API to find our test files +// and run them + +// this is a handly place to make sure the sdk has been skinned +var sdk = require("matrix-react-sdk"); +sdk.loadSkin(require('./test-component-index')); + +var context = require.context('.', true, /-test\.jsx?$/); +context.keys().forEach(context);