'use strict'; /* eslint-env mocha, jasmine */ describe('Input', function() { var $ = require('jquery'); require('jasmine-jquery'); require('../../src/common/dom.js').element = $; require('../../src/jquery/plugin.js'); var Input = require('../../src/autocomplete/input.js'); var _ = require('../../src/common/utils.js'); var fixtures = require('../fixtures.js'); var waitsForAndRuns = require('../helpers/waits_for.js'); var KEYS; KEYS = { enter: 13, esc: 27, tab: 9, left: 37, right: 39, up: 38, down: 40, normal: 65 // "A" key }; beforeEach(function() { var $fixture; setFixtures(fixtures.html.input + fixtures.html.hint); $fixture = $('#jasmine-fixtures'); this.$input = $fixture.find('.aa-input'); this.$hint = $fixture.find('.aa-hint'); this.view = new Input({input: this.$input, hint: this.$hint}); }); it('should throw an error if no hint and/or input is provided', function() { expect(noInput).toThrow(); function noInput() { new Input({hint: '.hint'}); } }); describe('when the blur DOM event is triggered', function() { it('should reset the input value', function() { this.view.setQuery('wine'); this.view.setInputValue('cheese', true); this.$input.blur(); expect(this.$input.val()).toBe('wine'); }); it('should trigger blurred', function() { var spy; this.view.onSync('blurred', spy = jasmine.createSpy()); this.$input.blur(); expect(spy).toHaveBeenCalled(); }); }); describe('when the focus DOM event is triggered', function() { it('should trigger focused', function() { var spy; this.view.onSync('focused', spy = jasmine.createSpy()); this.$input.focus(); expect(spy).toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by tab', function() { it('should trigger tabKeyed if no modifiers were pressed', function() { var spy; this.view.onSync('tabKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.tab); expect(spy).toHaveBeenCalled(); }); it('should not trigger tabKeyed if modifiers were pressed', function() { var spy; this.view.onSync('tabKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.tab, true); expect(spy).not.toHaveBeenCalled(); }); it('should prevent default behavior if there is a hint', function() { var $e; this.view.setHint('good'); this.view.setInputValue('goo'); $e = simulateKeyEvent(this.$input, 'keydown', KEYS.tab); expect($e.preventDefault).toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by esc', function() { it('should trigger escKeyed', function() { var spy; this.view.onSync('escKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.esc); expect(spy).toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by left', function() { it('should trigger leftKeyed', function() { var spy; this.view.onSync('leftKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.left); expect(spy).toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by right', function() { it('should trigger rightKeyed', function() { var spy; this.view.onSync('rightKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.right); expect(spy).toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by enter', function() { it('should trigger enterKeyed', function() { var spy; this.view.onSync('enterKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.enter); expect(spy).toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by up', function() { it('should trigger upKeyed', function() { var spy; this.view.onSync('upKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.up); expect(spy).toHaveBeenCalled(); }); it('should prevent default if no modifers were pressed', function() { var $e = simulateKeyEvent(this.$input, 'keydown', KEYS.up); expect($e.preventDefault).toHaveBeenCalled(); }); it('should not prevent default if modifers were pressed', function() { var $e = simulateKeyEvent(this.$input, 'keydown', KEYS.up, true); expect($e.preventDefault).not.toHaveBeenCalled(); }); }); describe('when the keydown DOM event is triggered by down', function() { it('should trigger downKeyed', function() { var spy; this.view.onSync('downKeyed', spy = jasmine.createSpy()); simulateKeyEvent(this.$input, 'keydown', KEYS.down); expect(spy).toHaveBeenCalled(); }); it('should prevent default if no modifers were pressed', function() { var $e = simulateKeyEvent(this.$input, 'keydown', KEYS.down); expect($e.preventDefault).toHaveBeenCalled(); }); it('should not prevent default if modifers were pressed', function() { var $e = simulateKeyEvent(this.$input, 'keydown', KEYS.down, true); expect($e.preventDefault).not.toHaveBeenCalled(); }); }); // NOTE: have to treat these as async because the ie polyfill acts // in a async manner describe('when the input DOM event is triggered', function() { it('should update query', function(done) { this.view.setQuery('wine'); this.view.setInputValue('cheese', true); simulateInputEvent(this.$input); var that = this; waitsForAndRuns(function() { return that.view.getQuery() === 'cheese'; }, done, 100); }); it('should trigger queryChanged if the query changed', function() { var spy; this.view.setQuery('wine'); this.view.setInputValue('cheese', true); this.view.onSync('queryChanged', spy = jasmine.createSpy()); simulateInputEvent(this.$input); expect(spy).toHaveBeenCalled(); }); it('should trigger whitespaceChagned if whitespace changed', function() { var spy; this.view.setQuery('wine bar'); this.view.setInputValue('wine bar', true); this.view.onSync('whitespaceChanged', spy = jasmine.createSpy()); simulateInputEvent(this.$input); expect(spy).toHaveBeenCalled(); }); }); describe('#focus', function() { it('should focus the input', function() { this.$input.blur(); this.view.focus(); expect(this.$input).toBeFocused(); }); }); describe('#blur', function() { it('should blur the input', function() { this.$input.focus(); this.view.blur(); expect(this.$input).not.toBeFocused(); }); }); describe('#getQuery/#setQuery', function() { it('should act as getter/setter to the query property', function() { this.view.setQuery('mouse'); expect(this.view.getQuery()).toBe('mouse'); }); }); describe('#getInputValue', function() { it('should act as getter to the input value', function() { this.$input.val('cheese'); expect(this.view.getInputValue()).toBe('cheese'); }); }); describe('#setInputValue', function() { it('should act as setter to the input value', function() { this.view.setInputValue('cheese'); expect(this.view.getInputValue()).toBe('cheese'); }); it('should not set the current query if null', function() { this.view.setQuery('cheese'); this.view.setInputValue(null); expect(this.view.getInputValue()).toBe(''); }); it('should set the current query if undefined', function() { this.view.setQuery('cheese'); this.view.setInputValue(undefined); expect(this.view.getInputValue()).toBe('cheese'); }); it('should trigger {query|whitespace}Changed when applicable', function() { var spy1; var spy2; this.view.onSync('queryChanged', spy1 = jasmine.createSpy()); this.view.onSync('whitespaceChanged', spy2 = jasmine.createSpy()); this.view.setInputValue('cheese head'); expect(spy1).toHaveBeenCalled(); expect(spy2).not.toHaveBeenCalled(); this.view.setInputValue('cheese head'); expect(spy1.calls.count()).toBe(1); expect(spy2).toHaveBeenCalled(); }); }); describe('#setActiveDescendant', function() { it('should set the aria-activedescendant attribute', function() { this.view.setActiveDescendant('abc'); expect(this.$input.attr('aria-activedescendant')).toBe('abc'); }); }); describe('#removeActiveDescendant', function() { it('should remove the aria-activedescendant attribute', function() { this.view.setActiveDescendant('foo'); expect(this.$input.attr('aria-activedescendant')).toBe('foo'); this.view.removeActiveDescendant('bar'); expect(this.$input.attr('aria-activedescendant')).toBeUndefined(); }); }); describe('#getHint/#setHint', function() { it('should act as getter/setter to value of hint', function() { this.view.setHint('mountain'); expect(this.view.getHint()).toBe('mountain'); }); }); describe('#resetInputValue', function() { it('should reset input value to last query', function() { this.view.setQuery('cheese'); this.view.setInputValue('wine', true); this.view.resetInputValue(); expect(this.view.getInputValue()).toBe('cheese'); }); }); describe('#clearHint', function() { it('should set the hint value to the empty string', function() { this.view.setHint('cheese'); this.view.clearHint(); expect(this.view.getHint()).toBe(''); }); }); describe('#clearHintIfInvalid', function() { it('should clear hint if input value is empty string', function() { this.view.setInputValue('', true); this.view.setHint('cheese'); this.view.clearHintIfInvalid(); expect(this.view.getHint()).toBe(''); }); it('should clear hint if input value is not prefix of input', function() { this.view.setInputValue('milk', true); this.view.setHint('cheese'); this.view.clearHintIfInvalid(); expect(this.view.getHint()).toBe(''); }); it('should clear hint if overflow exists', function() { spyOn(this.view, 'hasOverflow').and.returnValue(true); this.view.setInputValue('che', true); this.view.setHint('cheese'); this.view.clearHintIfInvalid(); expect(this.view.getHint()).toBe(''); }); it('should not clear hint if input value is prefix of input', function() { this.view.setInputValue('che', true); this.view.setHint('cheese'); this.view.clearHintIfInvalid(); expect(this.view.getHint()).toBe('cheese'); }); }); describe('#getLanguageDirection', function() { it('should return the language direction of the input', function() { this.$input.css('direction', 'ltr'); expect(this.view.getLanguageDirection()).toBe('ltr'); this.$input.css('direction', 'rtl'); expect(this.view.getLanguageDirection()).toBe('rtl'); }); }); describe('#hasOverflow', function() { it('should return true if the input has overflow text', function() { var longStr = new Array(1000).join('a'); this.view.setInputValue(longStr); expect(this.view.hasOverflow()).toBe(true); }); it('should return false if the input has no overflow text', function() { var shortStr = 'aah'; this.view.setInputValue(shortStr); expect(this.view.hasOverflow()).toBe(false); }); }); describe('#isCursorAtEnd', function() { it('should return true if the text cursor is at the end', function() { this.view.setInputValue('boo'); setCursorPosition(this.$input, 3); expect(this.view.isCursorAtEnd()).toBe(true); }); it('should return false if the text cursor is not at the end', function() { this.view.setInputValue('boo'); setCursorPosition(this.$input, 1); expect(this.view.isCursorAtEnd()).toBe(false); }); }); describe('#destroy', function() { it('should remove event handlers', function() { var $input; var $hint; $hint = this.view.$hint; $input = this.view.$input; spyOn($hint, 'off'); spyOn($input, 'off'); this.view.destroy(); expect($hint.off).toHaveBeenCalledWith('.aa'); expect($input.off).toHaveBeenCalledWith('.aa'); }); it('should null out its reference to DOM elements', function() { this.view.destroy(); expect(this.view.$hint).toBeNull(); expect(this.view.$input).toBeNull(); expect(this.view.$overflowHelper).toBeNull(); }); }); // helper functions // ---------------- function simulateInputEvent($node) { var $e; var type; type = _.isMsie() ? 'keypress' : 'input'; $e = $.Event(type); $node.trigger($e); } function simulateKeyEvent($node, type, key, withModifier) { var $e; $e = $.Event(type, { keyCode: key, altKey: !!withModifier, ctrlKey: !!withModifier, metaKey: !!withModifier, shiftKey: !!withModifier }); spyOn($e, 'preventDefault'); $node.trigger($e); return $e; } function setCursorPosition($input, pos) { var input = $input[0]; var range; if (input.setSelectionRange) { input.focus(); input.setSelectionRange(pos, pos); } else if (input.createTextRange) { range = input.createTextRange(); range.collapse(true); range.moveEnd('character', pos); range.moveStart('character', pos); range.select(); } } });