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();
+ });
+ });
+ });
+});