angular-material-npfixed
Version:
The Angular Material project is an implementation of Material Design in Angular.js. This project provides a set of reusable, well-tested, and accessible Material Design UI components. Angular Material is supported internally at Google by the Angular.js, M
1,488 lines (1,160 loc) • 92.4 kB
JavaScript
describe('<md-autocomplete>', function() {
var element, scope;
beforeEach(module('material.components.autocomplete'));
afterEach(function() {
scope && scope.$destroy();
});
function compile(template, scope) {
inject(function($compile) {
element = $compile(template)(scope);
scope.$apply();
});
return element;
}
function createScope(items, scopeData, matchLowercase) {
items = items || ['foo', 'bar', 'baz'].map(function(item) {
return { display: item };
});
inject(function($rootScope, $timeout) {
scope = $rootScope.$new();
scope.match = function(term) {
return items.filter(function(item) {
return item.display.indexOf(matchLowercase ? term.toLowerCase() : term) === 0;
});
};
scope.asyncMatch = function(term) {
return $timeout(function() {
return scope.match(term);
}, 1000);
};
scope.searchText = '';
scope.selectedItem = null;
scope.items = items;
angular.forEach(scopeData, function(value, key) {
scope[key] = value;
});
});
return scope;
}
function keydownEvent(keyCode) {
return {
keyCode: keyCode,
stopPropagation: angular.noop,
preventDefault: angular.noop
};
}
function waitForVirtualRepeat() {
// Because the autocomplete does not make the suggestions menu visible
// off the bat, the virtual repeat needs a couple more iterations to
// figure out how tall it is and then how tall the repeated items are.
// Using md-item-size would reduce this to a single flush, but given that
// autocomplete allows for custom row templates, it's better to measure
// rather than assuming a given size.
inject(function($material, $timeout) {
$material.flushOutstandingAnimations();
$timeout.flush();
});
}
describe('basic functionality', function() {
it('updates selected item and search text', inject(function($timeout, $mdConstant, $material) {
var scope = createScope();
var template = '\
<md-autocomplete\
md-selected-item="selectedItem"\
md-search-text="searchText"\
md-items="item in match(searchText)"\
md-item-text="item.display"\
placeholder="placeholder">\
<span md-highlight-text="searchText">{{item.display}}</span>\
</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
var ul = element.find('ul');
$material.flushInterimElement();
expect(scope.searchText).toBe('');
expect(scope.selectedItem).toBe(null);
// Focus the input
ctrl.focus();
// Update the scope
element.scope().searchText = 'fo';
waitForVirtualRepeat(element);
// Check expectations
expect(scope.searchText).toBe('fo');
expect(scope.match(scope.searchText).length).toBe(1);
expect(ul.find('li').length).toBe(1);
// Run our key events
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.DOWN_ARROW));
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ENTER));
$timeout.flush();
// Check expectations again
expect(scope.searchText).toBe('foo');
expect(scope.selectedItem).toBe(scope.match(scope.searchText)[0]);
element.remove();
}));
it('should clear the searchText when the selectedItem manually got cleared',
inject(function($timeout, $material, $mdConstant) {
var scope = createScope();
var template =
'<md-autocomplete ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'placeholder="placeholder"> ' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
var ul = element.find('ul');
$material.flushInterimElement();
expect(scope.searchText).toBe('');
expect(scope.selectedItem).toBe(null);
ctrl.focus();
scope.$apply('searchText = "fo"');
waitForVirtualRepeat(element);
expect(scope.searchText).toBe('fo');
expect(scope.match(scope.searchText).length).toBe(1);
expect(ul.find('li').length).toBe(1);
// Run our key events to trigger a select action
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.DOWN_ARROW));
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ENTER));
$timeout.flush();
expect(scope.searchText).toBe('foo');
expect(scope.selectedItem).toBe(scope.match(scope.searchText)[0]);
// Reset / Clear the current selected item.
scope.$apply('selectedItem = null');
waitForVirtualRepeat(element);
// Run our key events to trigger a select action
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.DOWN_ARROW));
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ENTER));
$timeout.flush();
// The autocomplete automatically clears the searchText when the selectedItem was cleared.
expect(scope.searchText).toBe('');
expect(scope.selectedItem).toBeFalsy();
element.remove();
}));
it('should should not clear the searchText when clearing the selected item from the input',
inject(function($timeout, $material, $mdConstant) {
var scope = createScope();
var template =
'<md-autocomplete ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'placeholder="placeholder"> ' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
var input = element.find('input');
var ul = element.find('ul');
$material.flushInterimElement();
expect(scope.searchText).toBe('');
expect(scope.selectedItem).toBe(null);
ctrl.focus();
scope.$apply('searchText = "fo"');
waitForVirtualRepeat(element);
expect(scope.searchText).toBe('fo');
expect(scope.match(scope.searchText).length).toBe(1);
expect(ul.find('li').length).toBe(1);
// Run our key events to trigger a select action
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.DOWN_ARROW));
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ENTER));
$timeout.flush();
expect(scope.searchText).toBe('foo');
expect(scope.selectedItem).toBe(scope.match(scope.searchText)[0]);
scope.$apply('searchText = "food"');
$timeout.flush();
// The autocomplete automatically clears the searchText when the selectedItem was cleared.
expect(scope.searchText).toBe('food');
expect(scope.selectedItem).toBeFalsy();
element.remove();
}));
it('allows you to set an input id without floating label', inject(function() {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template = '\
<md-autocomplete\
md-input-id="{{inputId}}"\
md-selected-item="selectedItem"\
md-search-text="searchText"\
md-items="item in match(searchText)"\
md-item-text="item.display"\
placeholder="placeholder">\
<span md-highlight-text="searchText">{{item.display}}</span>\
</md-autocomplete>';
var element = compile(template, scope);
var input = element.find('input');
expect(input.attr('id')).toBe(scope.inputId);
element.remove();
}));
it('allows using ng-readonly', inject(function() {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template = '\
<md-autocomplete\
md-input-id="{{inputId}}"\
md-selected-item="selectedItem"\
md-search-text="searchText"\
md-items="item in match(searchText)"\
md-item-text="item.display"\
placeholder="placeholder"\
ng-readonly="readonly">\
<span md-highlight-text="searchText">{{item.display}}</span>\
</md-autocomplete>';
var element = compile(template, scope);
var input = element.find('input');
scope.readonly = true;
scope.$digest();
expect(input.attr('readonly')).toBe('readonly');
scope.readonly = false;
scope.$digest();
expect(input.attr('readonly')).toBeUndefined();
element.remove();
}));
it('should forward focus to the input element with md-autofocus', inject(function($timeout) {
var scope = createScope();
var template =
'<md-autocomplete ' +
' md-selected-item="selectedItem" ' +
' md-search-text="searchText" ' +
' md-items="item in match(searchText)" ' +
' md-item-text="item.display" ' +
' placeholder="placeholder"' +
' md-autofocus>' +
' <span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
var input = element.find('input');
document.body.appendChild(element[0]);
// Initial timeout for gathering elements
$timeout.flush();
element.triggerHandler('focus');
expect(document.activeElement).toBe(input[0]);
element.remove();
}));
it('allows using an empty readonly attribute', inject(function() {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template = '\
<md-autocomplete\
md-input-id="{{inputId}}"\
md-selected-item="selectedItem"\
md-search-text="searchText"\
md-items="item in match(searchText)"\
md-item-text="item.display"\
placeholder="placeholder"\
readonly>\
<span md-highlight-text="searchText">{{item.display}}</span>\
</md-autocomplete>';
var element = compile(template, scope);
var input = element.find('input');
expect(input.attr('readonly')).toBe('readonly');
element.remove();
}));
it('allows you to set an input id with floating label', inject(function() {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template = '\
<md-autocomplete\
md-floating-label="Some Label"\
md-input-id="{{inputId}}"\
md-selected-item="selectedItem"\
md-search-text="searchText"\
md-items="item in match(searchText)"\
md-item-text="item.display"\
placeholder="placeholder">\
<span md-highlight-text="searchText">{{item.display}}</span>\
</md-autocomplete>';
var element = compile(template, scope);
var input = element.find('input');
expect(input.attr('id')).toBe(scope.inputId);
element.remove();
}));
it('forwards the `md-select-on-focus` attribute to the input', inject(function() {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template =
'<md-autocomplete ' +
'md-input-id="{{inputId}}" ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'md-select-on-focus="" ' +
'tabindex="3"' +
'placeholder="placeholder">' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
var input = element.find('input');
expect(input.attr('md-select-on-focus')).toBe("");
element.remove();
}));
it('should support ng-trim for the search input', inject(function() {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template =
'<md-autocomplete ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'ng-trim="false" ' +
'placeholder="placeholder">' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
var input = element.find('input');
expect(input.attr('ng-trim')).toBe("false");
scope.$apply('searchText = " Text "');
expect(scope.searchText).not.toBe('Text');
element.remove();
}));
it('should support ng-pattern for the search input', inject(function() {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template =
'<form name="testForm">' +
'<md-autocomplete ' +
'md-input-name="autocomplete" ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'ng-pattern="/^[0-9]$/" ' +
'placeholder="placeholder">' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>' +
'</form>';
var element = compile(template, scope);
var input = element.find('input');
expect(input.attr('ng-pattern')).toBeTruthy();
scope.$apply('searchText = "Test"');
expect(scope.testForm.autocomplete.$error['pattern']).toBeTruthy();
scope.$apply('searchText = "3"');
expect(scope.testForm.autocomplete.$error['pattern']).toBeFalsy();
element.remove();
}));
it('forwards the tabindex to the input', inject(function() {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template =
'<md-autocomplete ' +
'md-input-id="{{inputId}}" ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'tabindex="3"' +
'placeholder="placeholder">' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
var input = element.find('input');
expect(input.attr('tabindex')).toBe('3');
element.remove();
}));
it('always sets the tabindex of the autcomplete to `-1`', inject(function() {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template =
'<md-autocomplete ' +
'md-input-id="{{inputId}}" ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'tabindex="3"' +
'placeholder="placeholder">' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
expect(element.attr('tabindex')).toBe('-1');
element.remove();
}));
it('should emit the ngBlur event from the input', inject(function() {
var scope = createScope(null, {
onBlur: jasmine.createSpy('onBlur event')
});
var template =
'<md-autocomplete ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'ng-blur="onBlur($event)" ' +
'placeholder="placeholder">' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
var input = element.find('input');
input.triggerHandler('blur');
expect(scope.onBlur).toHaveBeenCalledTimes(1);
// Confirm that the ngFocus event was called with the $event local.
var focusEvent = scope.onBlur.calls.mostRecent().args[0];
expect(focusEvent.target).toBe(input[0]);
element.remove();
}));
it('should emit the ngFocus event from the input', inject(function() {
var scope = createScope(null, {
onFocus: jasmine.createSpy('onFocus event')
});
var template =
'<md-autocomplete ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'ng-focus="onFocus($event)" ' +
'placeholder="placeholder">' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
var input = element.find('input');
input.triggerHandler('focus');
expect(scope.onFocus).toHaveBeenCalledTimes(1);
// Confirm that the ngFocus event was called with the $event object.
var focusEvent = scope.onFocus.calls.mostRecent().args[0];
expect(focusEvent.target).toBe(input[0]);
element.remove();
}));
it('should not show a loading progress when the items object is invalid', inject(function() {
var scope = createScope(null, {
match: function() {
// Return an invalid object, which is not an array, neither a promise.
return {}
}
});
var template =
'<md-autocomplete ' +
'md-input-id="{{inputId}}" ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'tabindex="3"' +
'placeholder="placeholder">' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
scope.$apply('searchText = "test"');
expect(ctrl.loading).toBe(false);
element.remove();
}));
it('clears the value when hitting escape', inject(function($mdConstant, $timeout) {
var scope = createScope();
var template = '\
<md-autocomplete\
md-search-text="searchText"\
md-items="item in match(searchText)"\
md-item-text="item.display"\
placeholder="placeholder">\
<span md-highlight-text="searchText">{{item.display}}</span>\
</md-autocomplete>';
var element = compile(template, scope);
var input = element.find('input');
var ctrl = element.controller('mdAutocomplete');
expect(scope.searchText).toBe('');
scope.$apply('searchText = "test"');
expect(scope.searchText).toBe('test');
$timeout.flush();
scope.$apply(function() {
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ESCAPE));
});
expect(scope.searchText).toBe('');
element.remove();
}));
describe('md-input-maxlength', function() {
it('should correctly set the form to invalid if floating label is present', inject(function($timeout) {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template =
'<form name="testForm">' +
'<md-autocomplete ' +
'md-input-id="{{inputId}}" ' +
'md-input-maxlength="2" ' +
'md-input-name="testAutocomplete" ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'tabindex="3"' +
'md-floating-label="Favorite state">' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>' +
'</form>';
var element = compile(template, scope);
var input = element.find('input');
expect(scope.searchText).toBe('');
expect(scope.testForm.$valid).toBe(true);
scope.$apply('searchText = "Exceeded"');
expect(scope.testForm.$valid).toBe(false);
element.remove();
}));
it('should correctly set the form to invalid when no floating label is present', inject(function($timeout) {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template =
'<form name="testForm">' +
'<md-autocomplete ' +
'md-input-id="{{inputId}}" ' +
'md-input-maxlength="5" ' +
'md-input-name="testAutocomplete" ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" >' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>' +
'</form>';
var element = compile(template, scope);
var input = element.find('input');
expect(scope.searchText).toBe('');
expect(scope.testForm.$valid).toBe(true);
scope.$apply('searchText = "Exceeded"');
expect(scope.testForm.$valid).toBe(false);
element.remove();
}));
it('should not clear the view value if the input is invalid', inject(function($timeout) {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template =
'<form name="testForm">' +
'<md-autocomplete ' +
'md-input-id="{{inputId}}" ' +
'md-input-maxlength="2" ' +
'md-input-name="testAutocomplete" ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'tabindex="3"' +
'md-floating-label="Favorite state">' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>' +
'</form>';
var element = compile(template, scope);
var input = element.find('input');
expect(scope.searchText).toBe('');
expect(scope.testForm.$valid).toBe(true);
input.val('Exceeded');
input.triggerHandler('change');
scope.$digest();
expect(scope.testForm.$valid).toBe(false);
expect(scope.searchText).toBe('Exceeded');
element.remove();
}));
});
describe('md-input-minlength', function() {
it('should correctly set the form to invalid when floating label is present', inject(function($timeout) {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template =
'<form name="testForm">' +
'<md-autocomplete ' +
'md-input-id="{{inputId}}" ' +
'md-input-minlength="4" ' +
'md-input-name="testAutocomplete" ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'tabindex="3"' +
'md-floating-label="Favorite state">' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>' +
'</form>';
var element = compile(template, scope);
var input = element.find('input');
scope.$apply('searchText = "abc"');
expect(scope.testForm.$valid).toBe(false);
scope.$apply('searchText = "abcde"');
expect(scope.testForm.$valid).toBe(true);
element.remove();
}));
it('should correctly set the form to invalid when no floating label is present', inject(function($timeout) {
var scope = createScope(null, {inputId: 'custom-input-id'});
var template =
'<form name="testForm">' +
'<md-autocomplete ' +
'md-input-id="{{inputId}}" ' +
'md-input-minlength="4" ' +
'md-input-name="testAutocomplete" ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" >' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>' +
'</form>';
var element = compile(template, scope);
var input = element.find('input');
scope.$apply('searchText = "abc"');
expect(scope.testForm.$valid).toBe(false);
scope.$apply('searchText = "abcde"');
expect(scope.testForm.$valid).toBe(true);
element.remove();
}));
});
describe('md-escape-options checks', function() {
var scope, ctrl, element;
var template = '\
<md-autocomplete\
md-escape-options="{{escapeOptions}}"\
md-search-text="searchText"\
md-items="item in match(searchText)"\
md-item-text="item.display"\
placeholder="placeholder">\
<span md-highlight-text="searchText">{{item.display}}</span>\
</md-autocomplete>';
beforeEach( inject(function($timeout, $material) {
scope = createScope();
element = compile(template, scope);
ctrl = element.controller('mdAutocomplete');
$material.flushInterimElement();
// Update the scope
element.scope().searchText = 'fo';
waitForVirtualRepeat(element);
// Focus the input
ctrl.focus();
$timeout.flush();
expect(ctrl.hidden).toBe(false);
expect(scope.searchText).toBe('fo');
waitForVirtualRepeat(element);
$timeout.flush();
expect(ctrl.hidden).toBe(false);
}));
afterEach(function() { element.remove() });
it('does not clear the value nor blur when hitting escape', inject(function($mdConstant, $document, $timeout) {
scope.$apply('escapeOptions = "none"');
scope.$apply(function() {
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ESCAPE));
$timeout.flush();
expect(ctrl.hidden).toBe(true);
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ESCAPE));
$timeout.flush();
});
expect(scope.searchText).toBe('fo');
expect($document.activeElement).toBe(ctrl[0]);
}));
it('does not clear the value but does blur when hitting escape', inject(function($mdConstant, $document, $timeout) {
scope.$apply('escapeOptions = "blur"');
scope.$apply(function() {
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ESCAPE));
$timeout.flush();
expect(ctrl.hidden).toBe(true);
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ESCAPE));
$timeout.flush();
});
expect(scope.searchText).toBe('fo');
expect($document.activeElement).toBe(undefined);
}));
it('clear the value but does not blur when hitting escape', inject(function($mdConstant, $document, $timeout) {
scope.$apply('escapeOptions = "clear"');
scope.$apply(function() {
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ESCAPE));
$timeout.flush();
expect(ctrl.hidden).toBe(true);
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ESCAPE));
$timeout.flush();
});
expect(scope.searchText).toBe('');
expect($document.activeElement).toBe(ctrl[0]);
}));
});
it('should not show the progressbar when hitting escape on an empty input', inject(function($mdConstant, $timeout) {
var scope = createScope();
var template = '\
<md-autocomplete\
md-search-text="searchText"\
md-items="item in match(searchText)">\
</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
$timeout.flush();
scope.$apply(function() {
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ESCAPE));
});
expect(element.find('md-progress-linear').length).toBe(0);
element.remove();
}));
it('should not close list on ENTER key if nothing is selected', inject(function($timeout, $mdConstant, $material) {
var scope = createScope();
var template = '\
<md-autocomplete\
md-selected-item="selectedItem"\
md-search-text="searchText"\
md-items="item in match(searchText)"\
md-item-text="item.display"\
placeholder="placeholder">\
<span md-highlight-text="searchText">{{item.display}}</span>\
</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
var ul = element.find('ul');
$material.flushInterimElement();
// Update the scope
element.scope().searchText = 'fo';
waitForVirtualRepeat(element);
// Focus the input
ctrl.focus();
$timeout.flush();
expect(ctrl.hidden).toBe(false);
// Run our key events
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ENTER));
$timeout.flush();
// Check expectations again
expect(ctrl.hidden).toBe(false);
element.remove();
}));
});
describe('basic functionality with template', function() {
it('updates selected item and search text', inject(function($timeout, $material, $mdConstant) {
var scope = createScope();
var template = '\
<md-autocomplete\
md-selected-item="selectedItem"\
md-search-text="searchText"\
md-items="item in match(searchText)"\
md-item-text="item.display"\
placeholder="placeholder">\
<md-item-template>\
<span md-highlight-text="searchText">{{item.display}}</span>\
</md-item-template>\
</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
var ul = element.find('ul');
expect(scope.searchText).toBe('');
expect(scope.selectedItem).toBe(null);
$material.flushInterimElement();
// Focus the input
ctrl.focus();
element.scope().searchText = 'fo';
waitForVirtualRepeat(element);
expect(scope.searchText).toBe('fo');
expect(scope.match(scope.searchText).length).toBe(1);
expect(ul.find('li').length).toBe(1);
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.DOWN_ARROW));
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ENTER));
$timeout.flush();
expect(scope.searchText).toBe('foo');
expect(scope.selectedItem).toBe(scope.match(scope.searchText)[0]);
element.remove();
}));
it('properly clears values when the item ends in a space character', inject(function($timeout, $material, $mdConstant) {
var myItems = ['foo ', 'bar', 'baz'].map(function(item) {
return {display: item};
});
var scope = createScope(myItems);
var template = '\
<md-autocomplete\
md-selected-item="selectedItem"\
md-search-text="searchText"\
md-items="item in match(searchText)"\
md-item-text="item.display"\
placeholder="placeholder">\
<md-item-template>\
<span md-highlight-text="searchText">{{item.display}}</span>\
</md-item-template>\
</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
var ul = element.find('ul');
expect(scope.searchText).toBe('');
expect(scope.selectedItem).toBe(null);
$material.flushInterimElement();
// Focus the input
ctrl.focus();
element.scope().searchText = 'fo';
waitForVirtualRepeat(element);
expect(scope.searchText).toBe('fo');
expect(scope.match(scope.searchText).length).toBe(1);
expect(ul.find('li').length).toBe(1);
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.DOWN_ARROW));
ctrl.keydown(keydownEvent($mdConstant.KEY_CODE.ENTER));
$timeout.flush();
expect(scope.searchText).toBe('foo ');
expect(scope.selectedItem).toBe(scope.match(scope.searchText)[0]);
ctrl.clear();
$timeout.flush();
expect(scope.searchText).toBe('');
expect(scope.selectedItem).toBe(null);
element.remove();
}));
it('compiles the template against the parent scope', inject(function($timeout, $material) {
var scope = createScope(null, {bang: 'boom'});
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <md-item-template>' +
' <span class="find-parent-scope">{{bang}}</span>' +
' <span class="find-index">{{$index}}</span>' +
' <span class="find-item">{{item.display}}</span>' +
' </md-item-template>' +
'</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
var ul = element.find('ul');
$material.flushOutstandingAnimations();
expect(scope.bang).toBe('boom');
// Focus the input
ctrl.focus();
element.scope().searchText = 'fo';
// Run our initial flush
$timeout.flush();
waitForVirtualRepeat(element);
// Wait for the next tick when the values will be updated
$timeout.flush();
var li = ul.find('li')[0];
// Expect it to be compiled against the parent scope and have our variables copied
expect(li.querySelector('.find-parent-scope').innerHTML).toBe('boom');
expect(li.querySelector('.find-index').innerHTML).toBe('0');
expect(li.querySelector('.find-item').innerHTML).toBe('foo');
// Make sure we wrap up anything and remove the element
$timeout.flush();
element.remove();
}));
it('removes the md-scroll-mask on cleanup', inject(function($mdUtil, $timeout, $material) {
spyOn($mdUtil, 'enableScrolling').and.callThrough();
var scope = createScope();
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <md-item-template>{{item.display}}</md-item-template>' +
' <md-not-found>Sorry, not found...</md-not-found>' +
'</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
$material.flushOutstandingAnimations();
// Focus our input
ctrl.focus();
// Set our search text to a value that we know doesn't exist
scope.searchText = 'somethingthatdoesnotexist';
// Run our initial flush
$timeout.flush();
waitForVirtualRepeat(element);
// Wait for the next tick when the values will be updated
$timeout.flush();
expect(ctrl.hidden).toBeFalsy();
// Make sure we wrap up anything and remove the element
$timeout.flush();
element.remove();
scope.$destroy();
// Should be hidden on once the scope is destroyed to ensure proper cleanup (like md-scroll-mask is removed from the DOM)
expect($mdUtil.enableScrolling).toHaveBeenCalled();
}));
it('removes the md-scroll-mask when md-autocomplete removed on change', inject(function($mdUtil, $timeout, $material) {
spyOn($mdUtil, 'enableScrolling').and.callThrough();
var scope = createScope();
var template =
'<div>' +
' <md-autocomplete' +
' ng-if="!removeAutocomplete"' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <md-item-template>{{item.display}}</md-item-template>' +
' <md-not-found>Sorry, not found...</md-not-found>' +
' </md-autocomplete>' +
'</div>';
var element = compile(template, scope);
var ctrl = element.children().controller('mdAutocomplete');
$material.flushOutstandingAnimations();
// Focus our input
ctrl.focus();
// Set our search text to a value to make md-scroll-mask added to DOM
scope.$apply('searchText = "searchText"');
$timeout.flush();
// Set removeAutocomplete to false to remove the md-autocomplete
scope.$apply('removeAutocomplete = true');
expect($mdUtil.enableScrolling).toHaveBeenCalled();
}));
it('should initialize the search text with an empty string', inject(function($mdUtil, $timeout, $material) {
var scope = createScope();
// Delete our searchText variable from the generated scope, because we
// want to confirm, that the autocomplete uses an empty string by default.
delete scope.searchText;
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <md-item-template>{{item.display}}</md-item-template>' +
' <md-not-found>Sorry, not found...</md-not-found>' +
'</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
$material.flushOutstandingAnimations();
// Run our initial flush
$timeout.flush();
waitForVirtualRepeat(element);
// Set our search text to a value that we know doesn't exist
expect(scope.searchText).toBe('');
// Make sure we wrap up anything and remove the element
$timeout.flush();
element.remove();
}));
it('ensures the parent scope digests along with the current scope', inject(function($timeout, $material) {
var scope = createScope(null, {bang: 'boom'});
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <md-item-template>' +
' <span class="find-parent-scope">{{bang}}</span>' +
' <span class="find-index">{{$index}}</span>' +
' <span class="find-item">{{item.display}}</span>' +
' </md-item-template>' +
'</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
var ul = element.find('ul');
$material.flushOutstandingAnimations();
// Focus the input
ctrl.focus();
element.scope().searchText = 'fo';
// Run our initial flush
$timeout.flush();
waitForVirtualRepeat(element);
// Wait for the next tick when the values will be updated
$timeout.flush();
var li = ul.find('li')[0];
var parentScope = angular.element(li.querySelector('.find-parent-scope')).scope();
// When the autocomplete item's scope digests, ensure that the parent
// scope does too.
parentScope.bang = 'big';
scope.$digest();
expect(li.querySelector('.find-parent-scope').innerHTML).toBe('big');
// Make sure we wrap up anything and remove the element
$timeout.flush();
element.remove();
}));
it('is hidden when no matches are found without an md-not-found template', inject(function($timeout, $material) {
var scope = createScope();
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <md-item-template>{{item.display}}</md-item-template>' +
'</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
$material.flushOutstandingAnimations();
// Focus our input
ctrl.focus();
// Set our search text to a value that we know doesn't exist
scope.searchText = 'somethingthatdoesnotexist';
// Run our initial flush
$timeout.flush();
waitForVirtualRepeat(element);
// Wait for the next tick when the values will be updated
$timeout.flush();
// We should be hidden since no md-not-found template was provided
expect(ctrl.hidden).toBe(true);
// Make sure we wrap up anything and remove the element
$timeout.flush();
element.remove();
}));
it('is visible when no matches are found with an md-not-found template', inject(function($timeout, $material) {
var scope = createScope();
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <md-item-template>{{item.display}}</md-item-template>' +
' <md-not-found>Sorry, not found...</md-not-found>' +
'</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
$material.flushOutstandingAnimations();
// Focus our input
ctrl.focus();
// Set our search text to a value that we know doesn't exist
scope.searchText = 'somethingthatdoesnotexist';
// Run our initial flush
$timeout.flush();
waitForVirtualRepeat(element);
// Wait for the next tick when the values will be updated
$timeout.flush();
// We should be visible since an md-not-found template was provided
expect(ctrl.hidden).toBe(false);
// Make sure we wrap up anything and remove the element
$timeout.flush();
element.remove();
}));
it('properly sets hasNotFound when element is hidden through ng-if', inject(function() {
var scope = createScope();
var template1 =
'<div>' +
'<md-autocomplete ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'placeholder="placeholder" ' +
'ng-if="showAutocomplete">' +
'<md-item-template>{{item.display}}</md-item-template>' +
'<md-not-found>Sorry, not found...</md-not-found>' +
'</md-autocomplete>' +
'</div>';
var element = compile(template1, scope);
var ctrl = element.children().controller('mdAutocomplete');
expect(ctrl).toBeUndefined();
scope.$apply('showAutocomplete = true');
ctrl = element.children().controller('mdAutocomplete');
expect(ctrl.hasNotFound).toBe(true);
}));
it('properly sets hasNotFound with multiple autocompletes', inject(function($timeout, $material) {
var scope = createScope();
var template1 =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <md-item-template>{{item.display}}</md-item-template>' +
' <md-not-found>Sorry, not found...</md-not-found>' +
'</md-autocomplete>';
var element1 = compile(template1, scope);
var ctrl1 = element1.controller('mdAutocomplete');
var template2 =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <md-item-template>{{item.display}}</md-item-template>' +
'</md-autocomplete>';
var element2 = compile(template2, scope);
var ctrl2 = element2.controller('mdAutocomplete');
// The first autocomplete has a template, the second one does not
expect(ctrl1.hasNotFound).toBe(true);
expect(ctrl2.hasNotFound).toBe(false);
}));
it('shows the md-not-found template even if we have lost focus', inject(function($timeout) {
var scope = createScope();
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <md-item-template>{{item.display}}</md-item-template>' +
' <md-not-found>Sorry, not found...</md-not-found>' +
'</md-autocomplete>';
var element = compile(template, scope);
var controller = element.controller('mdAutocomplete');
controller.focus();
scope.searchText = 'somethingthatdoesnotexist';
$timeout.flush();
controller.listEnter();
expect(controller.notFoundVisible()).toBe(true);
controller.blur();
expect(controller.notFoundVisible()).toBe(true);
controller.listLeave();
expect(controller.notFoundVisible()).toBe(false);
$timeout.flush();
element.remove();
}));
it('should not show the md-not-found template if we lost focus and left the list', inject(function($timeout) {
var scope = createScope();
var template =
'<md-autocomplete' +
' md-selected-item="selectedItem"' +
' md-search-text="searchText"' +
' md-items="item in match(searchText)"' +
' md-item-text="item.display"' +
' placeholder="placeholder">' +
' <md-item-template>{{item.display}}</md-item-template>' +
' <md-not-found>Sorry, not found...</md-not-found>' +
'</md-autocomplete>';
var element = compile(template, scope);
var controller = element.controller('mdAutocomplete');
controller.focus();
scope.searchText = 'somethingthatdoesnotexist';
$timeout.flush();
controller.listEnter();
expect(controller.notFoundVisible()).toBe(true);
controller.listLeave();
controller.blur();
expect(controller.notFoundVisible()).toBe(false);
$timeout.flush();
element.remove();
}));
it('should log a warning if the display text does not evaluate to a string',
inject(function($log) {
spyOn($log, 'warn');
var scope = createScope();
var template =
'<md-autocomplete ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText"' +
'md-items="item in match(searchText)"> ' +
'</md-autocomplete>';
var element = compile(template, scope);
scope.$apply(function() {
scope.selectedItem = { display: 'foo' };
});
expect($log.warn).toHaveBeenCalled();
expect($log.warn.calls.mostRecent().args[0]).toMatch(/md-item-text/);
element.remove();
})
);
});
describe('clear button', function() {
it('should show the clear button for inset autocomplete', function() {
var scope = createScope();
var template =
'<md-autocomplete ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'placeholder="placeholder"> ' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
var wrapEl = element.find('md-autocomplete-wrap');
expect(ctrl.scope.clearButton).toBe(true);
expect(wrapEl).toHaveClass('md-show-clear-button');
});
it('should not show the clear button for floating label autocomplete', function() {
var scope = createScope();
var template =
'<md-autocomplete ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +
'md-item-text="item.display" ' +
'md-floating-label="Label"> ' +
'<span md-highlight-text="searchText">{{item.display}}</span>' +
'</md-autocomplete>';
var element = compile(template, scope);
var ctrl = element.controller('mdAutocomplete');
var wrapEl = element.find('md-autocomplete-wrap');
expect(ctrl.scope.clearButton).toBe(false);
expect(wrapEl).not.toHaveClass('md-show-clear-button');
});
it('should allow developers to toggle the clear button', function() {
var scope = createScope();
var template =
'<md-autocomplete ' +
'md-selected-item="selectedItem" ' +
'md-search-text="searchText" ' +
'md-items="item in match(searchText)" ' +