diff --git a/.travis.yml b/.travis.yml index 4ddb3a185..5c858b87f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,13 @@ language: node_js node_js: - 'node' # Latest stable Node.js release +cache: + directories: + - src/node_modules/ env: - ON_TRAVIS=true before_install: cd src before_script: - - npm install -g grunt - - npm install -g grunt-cli - npm install script: - npm run test diff --git a/src/app/components/login/login.js b/src/app/components/login/login.js index 980bc0037..7504e9205 100644 --- a/src/app/components/login/login.js +++ b/src/app/components/login/login.js @@ -21,7 +21,7 @@ app.component('login', { this.$mdMedia = $mdMedia; this.$cookies = $cookies; - this.$scope.$watch('$ctrl.input_passphrase', this.isValid.bind(this)); + this.$scope.$watch('$ctrl.input_passphrase', this.isValidPassphrase.bind(this)); this.$timeout(this.devTestAccount.bind(this), 200); this.$scope.$watch(() => this.$mdMedia('xs') || this.$mdMedia('sm'), (wantsFullScreen) => { @@ -35,20 +35,20 @@ app.component('login', { this.seed = login.emptyBytes().map(() => '00'); } - stop() { - this.random = false; + stopNewPassphraseGeneration() { + this.generatingNewPassphrase = false; this.$document.unbind('mousemove', this.listener); } - go() { - this.passphrase = login.fix(this.input_passphrase); + doTheLogin() { + this.passphrase = login.fixCaseAndWhitespace(this.input_passphrase); this.reset(); this.$timeout(this.onLogin); } - isValid(value) { - const fixedValue = login.fix(value); + isValidPassphrase(value) { + const fixedValue = login.fixCaseAndWhitespace(value); if (fixedValue === '') { this.valid = 2; @@ -59,10 +59,10 @@ app.component('login', { } } - start() { + startGenratingNewPassphrase() { this.reset(); - this.random = true; + this.generatingNewPassphrase = true; let last = [0, 0]; let used = login.emptyBytes(); @@ -108,8 +108,8 @@ app.component('login', { } if (count >= total) { - this.stop(); - this.setNew(); + this.stopNewPassphraseGeneration(); + this.setNewPassphrase(this.seed); return; } } @@ -119,15 +119,15 @@ app.component('login', { this.$timeout(() => this.$document.mousemove(this.listener), 300); } - asd() { + simulateMousemove() { this.$document.mousemove(); } - setNew() { - const passphrase = (new mnemonic(new Buffer(this.seed.join(''), 'hex'))).toString(); + setNewPassphrase(seed) { + const passphrase = (new mnemonic(new Buffer(seed.join(''), 'hex'))).toString(); const ok = () => { this.input_passphrase = passphrase; - this.$timeout(this.go.bind(this), 100); + this.$timeout(this.doTheLogin.bind(this), 100); }; this.$mdDialog.show({ @@ -172,11 +172,11 @@ app.component('login', { const passphrase = this.$cookies.get('passphrase'); if (passphrase) { this.input_passphrase = passphrase; - this.$timeout(this.go.bind(this), 10); + this.$timeout(this.doTheLogin.bind(this), 10); } } - static fix(v) { + static fixCaseAndWhitespace(v) { return (v || '').replace(/ +/g, ' ').trim().toLowerCase(); } diff --git a/src/app/components/login/login.pug b/src/app/components/login/login.pug index d23faae4c..a630457db 100644 --- a/src/app/components/login/login.pug +++ b/src/app/components/login/login.pug @@ -6,17 +6,17 @@ md-card form(ng-submit='$ctrl.go()') md-input-container.md-block(md-is-error='$ctrl.valid === 0') label Enter your passphrase - input(type="{{ $ctrl.show_passphrase ? 'text' : 'password' }}", ng-model='$ctrl.input_passphrase', ng-disabled='$ctrl.random', autofocus) + input(type="{{ $ctrl.show_passphrase ? 'text' : 'password' }}", ng-model='$ctrl.input_passphrase', ng-disabled='$ctrl.generatingNewPassphrase', autofocus) md-input-container.md-block md-checkbox.md-primary(ng-model="$ctrl.show_passphrase", aria-label="Show passphrase") Show passphrase md-content(layout='row', layout-align='center center') - // md-button(ng-disabled='$ctrl.random', ng-click='$ctrl.devTestAccount()') Dev Test Account - md-button.md-primary(ng-disabled='$ctrl.random', ng-click='$ctrl.start()') NEW ACCOUNT + // md-button(ng-disabled='$ctrl.generatingNewPassphrase', ng-click='$ctrl.devTestAccount()') Dev Test Account + md-button.md-primary(ng-disabled='$ctrl.random', ng-click='$ctrl.startGenratingNewPassphrase()') NEW ACCOUNT md-button.md-raised.md-primary(md-autofocus, ng-disabled='$ctrl.valid !== 1', ng-click='$ctrl.go()') Login - md-content(layout-padding, layout='column', layout-align='center center', ng-show='$ctrl.random') + md-content(layout-padding, layout='column', layout-align='center center', ng-show='$ctrl.generatingNewPassphrase') h4.move(ng-show='$ctrl.mobileAndTabletcheck()') Enter text below to generate random bytes h4.move(ng-hide='$ctrl.mobileAndTabletcheck()') Move your mouse to generate random bytes - input.random-input(type="text", ng-keydown='$ctrl.asd()', ng-show='$ctrl.mobileAndTabletcheck()') + input.random-input(type="text", ng-keydown='$ctrl.simulateMousemove()', ng-show='$ctrl.mobileAndTabletcheck()') md-progress-linear(md-mode='determinate', value='{{ $ctrl.progress }}') md-content.bytes span.byte(ng-repeat='byte in $ctrl.seed track by $index', ng-bind='byte', animate-on-change='byte') diff --git a/src/karma.conf.js b/src/karma.conf.js index 66a1f776c..c5ab74762 100644 --- a/src/karma.conf.js +++ b/src/karma.conf.js @@ -13,6 +13,7 @@ preprocessors[test] = ['webpack']; var opts = { onTravis: process.env.ON_TRAVIS, + live: process.env.LIVE, }; module.exports = function(config) { @@ -57,7 +58,7 @@ module.exports = function(config) { // enable / disable watching file and executing tests whenever any file changes - autoWatch: false, + autoWatch: opts.live, ngHtml2JsPreprocessor: { stripPrefix: 'app/components/', @@ -77,7 +78,7 @@ module.exports = function(config) { // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits - singleRun: true, + singleRun: !opts.live, client: { mocha: { opts: 'test/mocha.opts' // You can set opts to equal true then plugin will load opts from default location 'test/mocha.opts' diff --git a/src/package.json b/src/package.json index 619804cac..ea3f67858 100644 --- a/src/package.json +++ b/src/package.json @@ -5,7 +5,8 @@ "scripts": { "build": "webpack --profile --progress --display-modules --display-exclude --display-chunks --display-cached --display-cached-assets", "dev": "webpack-dev-server --host 0.0.0.0 --profile --progress", - "test": "export NODE_ENV=test && karma start" + "test": "export NODE_ENV=test && karma start", + "test-live": "export NODE_ENV=test && export LIVE=true && karma start" }, "dependencies": { "angular": "=1.5.8", @@ -70,6 +71,8 @@ "pug-loader": "=2.3.0", "raw-loader": "=0.5.1", "should": "=11.2.0", + "sinon": "=2.0.0", + "sinon-chai": "=2.8.0", "style-loader": "=0.13.1", "url-loader": "=0.5.7", "webpack": "=1.13.1", diff --git a/src/test/components/login/login.spec.js b/src/test/components/login/login.spec.js index ae31f9289..bc3068dcd 100644 --- a/src/test/components/login/login.spec.js +++ b/src/test/components/login/login.spec.js @@ -1,23 +1,118 @@ +var sinon = require('sinon'); +var sinonChai = require('sinon-chai'); +var expect = chai.expect; +chai.use(sinonChai); + describe('Login component', function() { var $compile, - $rootScope; + $rootScope, + element; // Load the myApp module, which contains the directive beforeEach(angular.mock.module("app")); // Store references to $rootScope and $compile // so they are available to all tests in this describe block - beforeEach(inject(function(_$compile_, _$rootScope_){ + beforeEach(inject(function(_$compile_, _$rootScope_) { // The injector unwraps the underscores (_) from around the parameter names when matching $compile = _$compile_; $rootScope = _$rootScope_; })); - it('should contain header', function() { + beforeEach(function() { // Compile a piece of HTML containing the directive - var element = $compile("")($rootScope); + element = $compile('')($rootScope); $rootScope.$digest(); - expect(element.html()).to.contain("Sign In"); + }); + + var HEADER_TEXT = 'Sign In'; + it('should contain header saying "' + HEADER_TEXT + '"', function() { + expect(element.find('.md-title').text()).to.equal(HEADER_TEXT); + }); + + var LABEL_TEXT = 'Enter your passphrase'; + it('should contain a form with label saying "' + LABEL_TEXT + '"', function() { + expect(element.find('form label').text()).to.equal(LABEL_TEXT); + }); + + it('should contain an input field', function() { + expect(element.find('form input').html()).to.equal(''); + }); + + var LOGIN_BUTTON_TEXT = 'Login'; + it('should contain a button saying "' + LOGIN_BUTTON_TEXT + '"', function() { + expect(element.find('.md-raised').text()).to.equal(LOGIN_BUTTON_TEXT); }); }); +describe('Login controller', function() { + beforeEach(angular.mock.module('app')); + + var $controller, + $rootScope; + + beforeEach(inject(function(_$componentController_, _$rootScope_) { + $componentController = _$componentController_; + $rootScope = _$rootScope_; + })); + + describe('$scope.reset()', function() { + var $scope, + controller; + + beforeEach(function() { + $scope = $rootScope.$new(); + controller = $componentController('login', $scope, {}); + }); + + it('makes input_passphrase empty', function() { + passphrase = 'TEST'; + controller.input_passphrase = passphrase; + expect(controller.input_passphrase).to.equal(passphrase); + controller.reset(); + expect(controller.input_passphrase).to.equal(''); + }); + }); + + describe('$scope.setNewPassphrase()', function() { + var $scope, + controller; + + beforeEach(function() { + $scope = $rootScope.$new(); + controller = $componentController('login', $scope, {}); + }); + + it('opens a material design dialog', function() { + var seed = ['23', '34', '34', '34', '34', '34', '34', '34']; + var dialogSpy = sinon.spy(controller.$mdDialog, 'show'); + controller.setNewPassphrase(seed); + expect(dialogSpy).to.have.been.calledWith(); + }); + }); + + describe('$scope.isValidPassphrase(value)', function() { + var $scope, + controller; + + beforeEach(function() { + $scope = $rootScope.$new(); + controller = $componentController('login', $scope, {}); + }); + + it('sets $scope.valid = 2 if value is empty', function() { + controller.isValidPassphrase(''); + expect(controller.valid).to.equal(2); + }); + + it('sets $scope.valid = 1 if value is valid', function() { + controller.isValidPassphrase('ability theme abandon abandon abandon abandon abandon abandon abandon abandon abandon absorb'); + expect(controller.valid).to.equal(1); + }); + + it('sets $scope.valid = 0 if value is invalid', function() { + controller.isValidPassphrase('INVALID VALUE'); + expect(controller.valid).to.equal(0); + }); + }); +}); diff --git a/src/test/components/timestamp/timestamp.spec.js b/src/test/components/timestamp/timestamp.spec.js new file mode 100644 index 000000000..cee468f8c --- /dev/null +++ b/src/test/components/timestamp/timestamp.spec.js @@ -0,0 +1,28 @@ +describe('timestamp component', function() { + var $compile, + $rootScope, + element; + + // Load the myApp module, which contains the directive + beforeEach(angular.mock.module('app')); + + // Store references to $rootScope and $compile + // so they are available to all tests in this describe block + beforeEach(inject(function(_$compile_, _$rootScope_) { + // The injector unwraps the underscores (_) from around the parameter names when matching + $compile = _$compile_; + $rootScope = _$rootScope_; + })); + + beforeEach(function() { + var liskEpoch = Date.UTC(2016, 4, 24, 17, 0, 0, 0); + $rootScope.currentTimestamp = Math.floor((new Date().valueOf() - liskEpoch) / 1000); + + element = $compile('')($rootScope); + $rootScope.$digest(); + }); + + it('should contain a timeago of the date', function() { + expect(element.text()).to.equal('a few seconds'); + }); +}); diff --git a/src/test/components/top/top.spec.js b/src/test/components/top/top.spec.js index 6a0cebd6f..e41995d32 100644 --- a/src/test/components/top/top.spec.js +++ b/src/test/components/top/top.spec.js @@ -1,14 +1,13 @@ - describe('Top component', function() { var $compile, $rootScope; // Load the myApp module, which contains the directive - beforeEach(angular.mock.module("app")); + beforeEach(angular.mock.module('app')); // Store references to $rootScope and $compile // so they are available to all tests in this describe block - beforeEach(inject(function(_$compile_, _$rootScope_){ + beforeEach(inject(function(_$compile_, _$rootScope_) { // The injector unwraps the underscores (_) from around the parameter names when matching $compile = _$compile_; $rootScope = _$rootScope_; @@ -16,10 +15,10 @@ describe('Top component', function() { it('should contain address', function() { // Compile a piece of HTML containing the directive - var element = $compile("")($rootScope); + var element = $compile('')($rootScope); // fire all the watches, so the scope expression {{1 + 1}} will be evaluated $rootScope.$digest(); // Check that the compiled element contains the templated content - expect(element.html()).to.contain("address"); + expect(element.html()).to.contain('address'); }); }); diff --git a/src/test/test.js b/src/test/test.js index 39d45b3f6..8e9a4d053 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -4,4 +4,5 @@ require('chai'); require('./components/login/login.spec'); require('./components/top/top.spec'); +require('./components/timestamp/timestamp.spec');