diff --git a/.travis.yml b/.travis.yml index c74536c4..e39db108 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,33 @@ language: node_js -node_js: - - 0.10 -before_script: - - npm install -g bower@1.2.8 - - bower install +env: + matrix: + - TEST_SUITE=unit + - TEST_SUITE=integration BROWSER='firefox' + - TEST_SUITE=integration BROWSER='firefox:3.5' + - TEST_SUITE=integration BROWSER='firefox:3.6' + - TEST_SUITE=integration BROWSER='safari:5' + - TEST_SUITE=integration BROWSER='safari:6' + - TEST_SUITE=integration BROWSER='safari:7' + - TEST_SUITE=integration BROWSER='internet explorer:7' + - TEST_SUITE=integration BROWSER='internet explorer:8' + - TEST_SUITE=integration BROWSER='internet explorer:9' + - TEST_SUITE=integration BROWSER='internet explorer:10' + - TEST_SUITE=integration BROWSER='internet explorer:11' + #- TEST_SUITE=integration BROWSER='opera:11' + #- TEST_SUITE=integration BROWSER='opera:12' + - TEST_SUITE=integration BROWSER='chrome' + global: + - secure: VY4J2ERfrMEin++f4+UDDtTMWLuE3jaYAVchRxfO2c6PQUYgR+SW4SMekz855U/BuptMtiVMR2UUoNGMgOSKIFkIXpPfHhx47G5a541v0WNjXfQ2qzivXAWaXNK3l3C58z4dKxgPWsFY9JtMVCddJd2vQieAILto8D8G09p7bpo= + - secure: kehbNCoYUG2gLnhmCH/oKhlJG6LoxgcOPMCtY7KOI4ropG8qlypb+O2b/19+BWeO3aIuMB0JajNh3p2NL0UKgLmUK7EYBA9fQz+vesFReRk0V/KqMTSxHJuseM4aLOWA2Wr9US843VGltfODVvDN5sNrfY7RcoRx2cTK/k1CXa8= +node_js: +- 0.11 +before_script: +- npm install -g grunt-cli@0.1.13 +- npm install -g node-static@0.7.3 +# until https://github.com/bower/bower/pull/1403 is merged +- npm install -g git://github.com/jharding/bower.git +- bower install +- grunt build +script: test/ci +addons: + sauce_connect: true diff --git a/Gruntfile.js b/Gruntfile.js index e4fe1235..1d9f3018 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -117,7 +117,7 @@ module.exports = function(grunt) { jshintrc: '.jshintrc' }, src: 'src/**/*.js', - test: ['test/*_spec.js'], + test: ['test/*_spec.js', 'test/integration/test.js'], gruntfile: ['Gruntfile.js'] }, @@ -163,17 +163,13 @@ module.exports = function(grunt) { connect: { server: { - options: { - port: 8888, keepalive: true - } + options: { port: 8888, keepalive: true } } }, - parallel: { - dev: [ - { grunt: true, args: ['server'] }, - { grunt: true, args: ['watch'] } - ] + concurrent: { + options: { logConcurrentOutput: true }, + dev: ['server', 'watch'] }, step: { @@ -251,7 +247,7 @@ module.exports = function(grunt) { grunt.registerTask('build', ['uglify', 'sed:version']); grunt.registerTask('server', 'connect:server'); grunt.registerTask('lint', 'jshint'); - grunt.registerTask('dev', 'parallel:dev'); + grunt.registerTask('dev', 'concurrent:dev'); // load tasks // ---------- @@ -259,7 +255,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-sed'); grunt.loadNpmTasks('grunt-exec'); grunt.loadNpmTasks('grunt-step'); - grunt.loadNpmTasks('grunt-parallel'); + grunt.loadNpmTasks('grunt-concurrent'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-uglify'); diff --git a/package.json b/package.json index 0ca0eba3..517adf9f 100644 --- a/package.json +++ b/package.json @@ -30,23 +30,28 @@ } ], "devDependencies": { + "chai": "^1.9.1", + "colors": "^0.6.2", "grunt": "~0.4", - "karma": "~0.8.6", - "semver": "~1.1.3", - "grunt-sed": "~0.1", - "grunt-exec": "~0.4.5", - "grunt-contrib-watch": "~0.2", - "grunt-contrib-jshint": "~0.8", - "grunt-contrib-uglify": "~0.2.6", + "grunt-concurrent": "^0.5.0", + "grunt-contrib-clean": "~0.4.0", "grunt-contrib-concat": "~0.1", "grunt-contrib-connect": "~0.1", - "grunt-contrib-clean": "~0.4.0", - "grunt-parallel": "0.0.2", - "grunt-step": "~0.2.0" + "grunt-contrib-jshint": "~0.8", + "grunt-contrib-uglify": "~0.2.6", + "grunt-contrib-watch": "~0.2", + "grunt-exec": "~0.4.5", + "grunt-sed": "~0.1", + "grunt-step": "~0.2.0", + "karma": "~0.8.6", + "mocha": "^1.20.1", + "semver": "~1.1.3", + "underscore": "^1.6.0", + "yiewd": "^0.5.0" }, "scripts": { "test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS" }, "version": "0.10.2", "main": "dist/typeahead.bundle.js" -} \ No newline at end of file +} diff --git a/test/ci b/test/ci new file mode 100755 index 00000000..afdb58f8 --- /dev/null +++ b/test/ci @@ -0,0 +1,12 @@ +#!/bin/bash -x + +if [ "$TEST_SUITE" == "unit" ]; then + ./node_modules/.bin/karma start --single-run --browsers PhantomJS +elif [ "$TRAVIS_SECURE_ENV_VARS" == "true" -a "$TEST_SUITE" == "integration" ]; then + static -p 8888 & + sleep 3 + # integration tests are flaky, don't let them fail the build + ./node_modules/mocha/bin/mocha --harmony -R spec ./test/integration/test.js || true +else + echo "Not running any tests" +fi diff --git a/test/integration/test.html b/test/integration/test.html new file mode 100644 index 00000000..d4a53d52 --- /dev/null +++ b/test/integration/test.html @@ -0,0 +1,112 @@ + + + + + + + + + + + +
+
+
+ + +
+
+
+ + + + diff --git a/test/integration/test.js b/test/integration/test.js new file mode 100644 index 00000000..39222759 --- /dev/null +++ b/test/integration/test.js @@ -0,0 +1,397 @@ +/* jshint esnext: true, evil: true, sub: true */ + +var wd = require('yiewd'), + colors = require('colors'), + expect = require('chai').expect, + _ = require('underscore'), + f = require('util').format, + env = process.env; + +var browser, caps; + +browser = (process.env.BROWSER || 'chrome').split(':'); + +caps = { + name: f('[%s] typeahead.js ui', browser.join(' , ')), + browserName: browser[0] +}; + +setIf(caps, 'version', browser[1]); +setIf(caps, 'platform', browser[2]); +setIf(caps, 'tunnel-identifier', env['TRAVIS_JOB_NUMBER']); +setIf(caps, 'build', env['TRAVIS_BUILD_NUMBER']); +setIf(caps, 'tags', env['CI'] ? ['CI'] : ['local']); + +function setIf(obj, key, val) { + val && (obj[key] = val); +} + +describe('jquery-typeahead.js', function() { + var driver, body, input, hint, dropdown, allPassed = true; + + this.timeout(300000); + + before(function(done) { + var host = 'ondemand.saucelabs.com', port = 80, username, password; + + if (env['CI']) { + host = 'localhost'; + port = 4445; + username = env['SAUCE_USERNAME']; + password = env['SAUCE_ACCESS_KEY']; + } + + driver = wd.remote(host, port, username, password); + driver.configureHttp({ + timeout: 30000, + retries: 5, + retryDelay: 200 + }); + + driver.on('status', function(info) { + console.log(info.cyan); + }); + + driver.on('command', function(meth, path, data) { + console.log(' > ' + meth.yellow, path.grey, data || ''); + }); + + driver.run(function*() { + yield this.init(caps); + yield this.get('http://localhost:8888/test/integration/test.html'); + + body = this.elementByTagName('body'); + input = yield this.elementById('states'); + hint = yield this.elementByClassName('tt-hint'); + dropdown = yield this.elementByClassName('tt-dropdown-menu'); + + done(); + }); + }); + + beforeEach(function(done) { + driver.run(function*() { + yield body.click(); + yield this.execute('window.jQuery("#states").typeahead("val", "")'); + done(); + }); + }); + + afterEach(function() { + allPassed = allPassed && (this.currentTest.state === 'passed'); + }); + + after(function(done) { + driver.run(function*() { + yield this.quit(); + yield driver.sauceJobStatus(allPassed); + done(); + }); + }); + + describe('on blur', function() { + it('should close dropdown', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('mi'); + expect(yield dropdown.isDisplayed()).to.equal(true); + + yield body.click(); + expect(yield dropdown.isDisplayed()).to.equal(false); + + done(); + }); + }); + + it('should clear hint', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('mi'); + expect(yield hint.getValue()).to.equal('michigan'); + + yield body.click(); + expect(yield hint.getValue()).to.equal(''); + + done(); + }); + }); + }); + + describe('on query change', function() { + it('should open dropdown if suggestions', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('mi'); + + expect(yield dropdown.isDisplayed()).to.equal(true); + + done(); + }); + }); + + it('should close dropdown if no suggestions', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('huh?'); + + expect(yield dropdown.isDisplayed()).to.equal(false); + + done(); + }); + }); + + it('should render suggestions if suggestions', function(done) { + driver.run(function*() { + var suggestions; + + yield input.click(); + yield input.type('mi'); + + suggestions = yield dropdown.elementsByClassName('tt-suggestion'); + + expect(suggestions).to.have.length('4'); + expect(yield suggestions[0].text()).to.equal('Michigan'); + expect(yield suggestions[1].text()).to.equal('Minnesota'); + expect(yield suggestions[2].text()).to.equal('Mississippi'); + expect(yield suggestions[3].text()).to.equal('Missouri'); + + done(); + }); + }); + + it('should show hint if top suggestion is a match', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('mi'); + + expect(yield hint.getValue()).to.equal('michigan'); + + done(); + }); + }); + + it('should match hint to query', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('NeW JE'); + + expect(yield hint.getValue()).to.equal('NeW JErsey'); + + done(); + }); + }); + + it('should not show hint if top suggestion is not a match', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('ham'); + + expect(yield hint.getValue()).to.equal(''); + + done(); + }); + }); + + it('should not show hint if there is query overflow', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('this is a very long value so '); + + expect(yield hint.getValue()).to.equal(''); + + done(); + }); + }); + }); + + describe('on up arrow', function() { + it('should cycle through suggestions', function(done) { + driver.run(function*() { + var suggestions; + + yield input.click(); + yield input.type('mi'); + + suggestions = yield dropdown.elementsByClassName('tt-suggestion'); + + yield input.type(wd.SPECIAL_KEYS['Up arrow']); + expect(yield input.getValue()).to.equal('Missouri'); + expect(yield suggestions[3].getAttribute('class')).to.equal('tt-suggestion tt-cursor'); + + yield input.type(wd.SPECIAL_KEYS['Up arrow']); + expect(yield input.getValue()).to.equal('Mississippi'); + expect(yield suggestions[2].getAttribute('class')).to.equal('tt-suggestion tt-cursor'); + + yield input.type(wd.SPECIAL_KEYS['Up arrow']); + expect(yield input.getValue()).to.equal('Minnesota'); + expect(yield suggestions[1].getAttribute('class')).to.equal('tt-suggestion tt-cursor'); + + yield input.type(wd.SPECIAL_KEYS['Up arrow']); + expect(yield input.getValue()).to.equal('Michigan'); + expect(yield suggestions[0].getAttribute('class')).to.equal('tt-suggestion tt-cursor'); + + yield input.type(wd.SPECIAL_KEYS['Up arrow']); + expect(yield input.getValue()).to.equal('mi'); + expect(yield suggestions[0].getAttribute('class')).to.equal('tt-suggestion'); + expect(yield suggestions[1].getAttribute('class')).to.equal('tt-suggestion'); + expect(yield suggestions[2].getAttribute('class')).to.equal('tt-suggestion'); + expect(yield suggestions[3].getAttribute('class')).to.equal('tt-suggestion'); + + done(); + }); + }); + }); + + describe('on down arrow', function() { + it('should cycle through suggestions', function(done) { + driver.run(function*() { + var suggestions; + + yield input.click(); + yield input.type('mi'); + + suggestions = yield dropdown.elementsByClassName('tt-suggestion'); + + yield input.type(wd.SPECIAL_KEYS['Down arrow']); + expect(yield input.getValue()).to.equal('Michigan'); + expect(yield suggestions[0].getAttribute('class')).to.equal('tt-suggestion tt-cursor'); + + yield input.type(wd.SPECIAL_KEYS['Down arrow']); + expect(yield input.getValue()).to.equal('Minnesota'); + expect(yield suggestions[1].getAttribute('class')).to.equal('tt-suggestion tt-cursor'); + + yield input.type(wd.SPECIAL_KEYS['Down arrow']); + expect(yield input.getValue()).to.equal('Mississippi'); + expect(yield suggestions[2].getAttribute('class')).to.equal('tt-suggestion tt-cursor'); + + yield input.type(wd.SPECIAL_KEYS['Down arrow']); + expect(yield input.getValue()).to.equal('Missouri'); + expect(yield suggestions[3].getAttribute('class')).to.equal('tt-suggestion tt-cursor'); + + yield input.type(wd.SPECIAL_KEYS['Down arrow']); + expect(yield input.getValue()).to.equal('mi'); + expect(yield suggestions[0].getAttribute('class')).to.equal('tt-suggestion'); + expect(yield suggestions[1].getAttribute('class')).to.equal('tt-suggestion'); + expect(yield suggestions[2].getAttribute('class')).to.equal('tt-suggestion'); + expect(yield suggestions[3].getAttribute('class')).to.equal('tt-suggestion'); + + done(); + }); + }); + }); + + describe('on escape', function() { + it('should close dropdown', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('mi'); + expect(yield dropdown.isDisplayed()).to.equal(true); + + yield input.type(wd.SPECIAL_KEYS['Escape']); + expect(yield dropdown.isDisplayed()).to.equal(false); + + done(); + }); + }); + + it('should clear hint', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('mi'); + expect(yield hint.getValue()).to.equal('michigan'); + + yield input.type(wd.SPECIAL_KEYS['Escape']); + expect(yield hint.getValue()).to.equal(''); + + done(); + }); + }); + }); + + describe('on tab', function() { + it('should autocomplete if hint is present', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('mi'); + + yield input.type(wd.SPECIAL_KEYS['Tab']); + expect(yield input.getValue()).to.equal('Michigan'); + + done(); + }); + }); + + it('should select if cursor is on suggestion', function(done) { + driver.run(function*() { + var suggestions; + + yield input.click(); + yield input.type('mi'); + + suggestions = yield dropdown.elementsByClassName('tt-suggestion'); + yield input.type(wd.SPECIAL_KEYS['Down arrow']); + yield input.type(wd.SPECIAL_KEYS['Down arrow']); + yield input.type(wd.SPECIAL_KEYS['Tab']); + + expect(yield dropdown.isDisplayed()).to.equal(false); + expect(yield input.getValue()).to.equal('Minnesota'); + + done(); + }); + }); + }); + + describe('on right arrow', function() { + it('should autocomplete if hint is present', function(done) { + driver.run(function*() { + yield input.click(); + yield input.type('mi'); + + yield input.type(wd.SPECIAL_KEYS['Right arrow']); + expect(yield input.getValue()).to.equal('Michigan'); + + done(); + }); + }); + }); + + describe('on suggestion click', function() { + it('should select suggestion', function(done) { + driver.run(function*() { + var suggestions; + + yield input.click(); + yield input.type('mi'); + + suggestions = yield dropdown.elementsByClassName('tt-suggestion'); + yield suggestions[1].click(); + + expect(yield dropdown.isDisplayed()).to.equal(false); + expect(yield input.getValue()).to.equal('Minnesota'); + + done(); + }); + }); + }); + + describe('on enter', function() { + it('should select if cursor is on suggestion', function(done) { + driver.run(function*() { + var suggestions; + + yield input.click(); + yield input.type('mi'); + + suggestions = yield dropdown.elementsByClassName('tt-suggestion'); + yield input.type(wd.SPECIAL_KEYS['Down arrow']); + yield input.type(wd.SPECIAL_KEYS['Down arrow']); + yield input.type(wd.SPECIAL_KEYS['Return']); + + expect(yield dropdown.isDisplayed()).to.equal(false); + expect(yield input.getValue()).to.equal('Minnesota'); + + done(); + }); + }); + }); +});