Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 5eab71b

Browse files
devversionThomasBurleson
authored andcommitted
fix(autocomplete): only handle results if it's an array or a promise
At the moment the autocomplete will handle the results wrong. So if we specify for example an empty JSON-Object, the autocomplete will handle the results as an async promise. Fixes #7074 Closes #7089 # Conflicts: # src/components/autocomplete/autocomplete.spec.js
1 parent 7a7418c commit 5eab71b

File tree

2 files changed

+244
-25
lines changed

2 files changed

+244
-25
lines changed

src/components/autocomplete/autocomplete.spec.js

Lines changed: 240 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('<md-autocomplete>', function() {
1111
return container;
1212
}
1313

14-
function createScope(items, obj) {
14+
function createScope(items, obj, matchLowercase) {
1515
var scope;
1616
items = items || ['foo', 'bar', 'baz'].map(function(item) {
1717
return {display: item};
@@ -20,7 +20,7 @@ describe('<md-autocomplete>', function() {
2020
scope = $rootScope.$new();
2121
scope.match = function(term) {
2222
return items.filter(function(item) {
23-
return item.display.indexOf(term) === 0;
23+
return item.display.indexOf(matchLowercase ? term.toLowerCase() : term) === 0;
2424
});
2525
};
2626
scope.asyncMatch = function(term) {
@@ -102,32 +102,58 @@ describe('<md-autocomplete>', function() {
102102

103103
element.remove();
104104
}));
105+
105106

106-
// @TODO - re-enable test
107-
xit('should allow receiving focus on the autocomplete', function() {
107+
it('should allow you to set an input id without floating label', inject(function() {
108108
var scope = createScope(null, {inputId: 'custom-input-id'});
109-
var template = '<md-autocomplete ' +
110-
'md-input-id="{{inputId}}" ' +
111-
'md-selected-item="selectedItem" ' +
112-
'md-search-text="searchText" ' +
113-
'md-items="item in match(searchText)" ' +
114-
'md-item-text="item.display" ' +
115-
'placeholder="placeholder">' +
116-
'<span md-highlight-text="searchText">{{item.display}}</span>' +
117-
'</md-autocomplete>';
109+
var template = '\
110+
<md-autocomplete\
111+
md-input-id="{{inputId}}"\
112+
md-selected-item="selectedItem"\
113+
md-search-text="searchText"\
114+
md-items="item in match(searchText)"\
115+
md-item-text="item.display"\
116+
placeholder="placeholder">\
117+
<span md-highlight-text="searchText">{{item.display}}</span>\
118+
</md-autocomplete>';
118119
var element = compile(template, scope);
119-
var focusSpy = jasmine.createSpy('focus');
120+
var input = element.find('input');
120121

121-
document.body.appendChild(element[0]);
122+
expect(input.attr('id')).toBe(scope.inputId);
122123

123-
element.on('focus', focusSpy);
124+
element.remove();
125+
}));
124126

125-
element.focus();
127+
it('should allow allow using ng-readonly', inject(function() {
128+
var scope = createScope(null, {inputId: 'custom-input-id'});
129+
var template = '\
130+
<md-autocomplete\
131+
md-input-id="{{inputId}}"\
132+
md-selected-item="selectedItem"\
133+
md-search-text="searchText"\
134+
md-items="item in match(searchText)"\
135+
md-item-text="item.display"\
136+
placeholder="placeholder"\
137+
ng-readonly="readonly">\
138+
<span md-highlight-text="searchText">{{item.display}}</span>\
139+
</md-autocomplete>';
140+
var element = compile(template, scope);
141+
var input = element.find('input');
126142

127-
expect(focusSpy).toHaveBeenCalled();
128-
});
143+
scope.readonly = true;
144+
scope.$digest();
129145

130-
it('should allow you to set an input id without floating label', inject(function() {
146+
expect(input.attr('readonly')).toBe('readonly');
147+
148+
scope.readonly = false;
149+
scope.$digest();
150+
151+
expect(input.attr('readonly')).toBeUndefined();
152+
153+
element.remove();
154+
}));
155+
156+
it('should allow allow using an empty readonly attribute', inject(function() {
131157
var scope = createScope(null, {inputId: 'custom-input-id'});
132158
var template = '\
133159
<md-autocomplete\
@@ -136,13 +162,14 @@ describe('<md-autocomplete>', function() {
136162
md-search-text="searchText"\
137163
md-items="item in match(searchText)"\
138164
md-item-text="item.display"\
139-
placeholder="placeholder">\
165+
placeholder="placeholder"\
166+
readonly>\
140167
<span md-highlight-text="searchText">{{item.display}}</span>\
141168
</md-autocomplete>';
142169
var element = compile(template, scope);
143170
var input = element.find('input');
144171

145-
expect(input.attr('id')).toBe(scope.inputId);
172+
expect(input.attr('readonly')).toBe('readonly');
146173

147174
element.remove();
148175
}));
@@ -168,6 +195,102 @@ describe('<md-autocomplete>', function() {
168195
element.remove();
169196
}));
170197

198+
it('should forward the `md-select-on-focus` attribute to the input', inject(function() {
199+
var scope = createScope(null, {inputId: 'custom-input-id'});
200+
var template =
201+
'<md-autocomplete ' +
202+
'md-input-id="{{inputId}}" ' +
203+
'md-selected-item="selectedItem" ' +
204+
'md-search-text="searchText" ' +
205+
'md-items="item in match(searchText)" ' +
206+
'md-item-text="item.display" ' +
207+
'md-select-on-focus="" ' +
208+
'tabindex="3"' +
209+
'placeholder="placeholder">' +
210+
'<span md-highlight-text="searchText">{{item.display}}</span>' +
211+
'</md-autocomplete>';
212+
213+
var element = compile(template, scope);
214+
var input = element.find('input');
215+
216+
expect(input.attr('md-select-on-focus')).toBe("");
217+
218+
element.remove();
219+
}));
220+
221+
it('should forward the tabindex to the input', inject(function() {
222+
var scope = createScope(null, {inputId: 'custom-input-id'});
223+
var template =
224+
'<md-autocomplete ' +
225+
'md-input-id="{{inputId}}" ' +
226+
'md-selected-item="selectedItem" ' +
227+
'md-search-text="searchText" ' +
228+
'md-items="item in match(searchText)" ' +
229+
'md-item-text="item.display" ' +
230+
'tabindex="3"' +
231+
'placeholder="placeholder">' +
232+
'<span md-highlight-text="searchText">{{item.display}}</span>' +
233+
'</md-autocomplete>';
234+
235+
var element = compile(template, scope);
236+
var input = element.find('input');
237+
238+
expect(input.attr('tabindex')).toBe('3');
239+
240+
element.remove();
241+
}));
242+
243+
it('should always set the tabindex of the autcomplete to `-1`', inject(function() {
244+
var scope = createScope(null, {inputId: 'custom-input-id'});
245+
var template =
246+
'<md-autocomplete ' +
247+
'md-input-id="{{inputId}}" ' +
248+
'md-selected-item="selectedItem" ' +
249+
'md-search-text="searchText" ' +
250+
'md-items="item in match(searchText)" ' +
251+
'md-item-text="item.display" ' +
252+
'tabindex="3"' +
253+
'placeholder="placeholder">' +
254+
'<span md-highlight-text="searchText">{{item.display}}</span>' +
255+
'</md-autocomplete>';
256+
257+
var element = compile(template, scope);
258+
259+
expect(element.attr('tabindex')).toBe('-1');
260+
261+
element.remove();
262+
}));
263+
264+
it('should not show a loading progress when the items object is invalid', inject(function() {
265+
var scope = createScope(null, {
266+
match: function() {
267+
// Return an invalid object, which is not an array, neither a promise.
268+
return {}
269+
}
270+
});
271+
272+
var template =
273+
'<md-autocomplete ' +
274+
'md-input-id="{{inputId}}" ' +
275+
'md-selected-item="selectedItem" ' +
276+
'md-search-text="searchText" ' +
277+
'md-items="item in match(searchText)" ' +
278+
'md-item-text="item.display" ' +
279+
'tabindex="3"' +
280+
'placeholder="placeholder">' +
281+
'<span md-highlight-text="searchText">{{item.display}}</span>' +
282+
'</md-autocomplete>';
283+
284+
var element = compile(template, scope);
285+
var ctrl = element.controller('mdAutocomplete');
286+
287+
scope.$apply('searchText = "test"');
288+
289+
expect(ctrl.loading).toBe(false);
290+
291+
element.remove();
292+
}));
293+
171294
it('should clear value when hitting escape', inject(function($mdConstant, $timeout) {
172295
var scope = createScope();
173296
var template = '\
@@ -860,6 +983,33 @@ describe('<md-autocomplete>', function() {
860983

861984
element.remove();
862985
}));
986+
987+
it('should select matching item using case insensitive', inject(function($timeout) {
988+
var scope = createScope(null, null, true);
989+
var template =
990+
'<md-autocomplete ' +
991+
'md-select-on-match ' +
992+
'md-selected-item="selectedItem" ' +
993+
'md-search-text="searchText" ' +
994+
'md-items="item in match(searchText)" ' +
995+
'md-item-text="item.display" ' +
996+
'placeholder="placeholder" ' +
997+
'md-match-case-insensitive="true">' +
998+
'<span md-highlight-text="searchText">{{item.display}}</span>' +
999+
'</md-autocomplete>';
1000+
var element = compile(template, scope);
1001+
1002+
expect(scope.searchText).toBe('');
1003+
expect(scope.selectedItem).toBe(null);
1004+
1005+
element.scope().searchText = 'FoO';
1006+
$timeout.flush();
1007+
1008+
expect(scope.selectedItem).not.toBe(null);
1009+
expect(scope.selectedItem.display).toBe('foo');
1010+
1011+
element.remove();
1012+
}));
8631013
});
8641014

8651015
describe('when required', function() {
@@ -892,6 +1042,74 @@ describe('<md-autocomplete>', function() {
8921042

8931043
element.remove();
8941044
});
1045+
1046+
it('should validate an empty `required` as true', function() {
1047+
var scope = createScope();
1048+
var template = '\
1049+
<md-autocomplete\
1050+
md-selected-item="selectedItem"\
1051+
md-search-text="searchText"\
1052+
md-items="item in match(searchText)"\
1053+
md-item-text="item.display"\
1054+
md-min-length="0" \
1055+
required\
1056+
placeholder="placeholder">\
1057+
<span md-highlight-text="searchText">{{item.display}}</span>\
1058+
</md-autocomplete>';
1059+
var element = compile(template, scope);
1060+
var ctrl = element.controller('mdAutocomplete');
1061+
1062+
expect(ctrl.isRequired).toBe(true);
1063+
});
1064+
1065+
it('should correctly validate an interpolated `ng-required` value', function() {
1066+
var scope = createScope();
1067+
var template = '\
1068+
<md-autocomplete\
1069+
md-selected-item="selectedItem"\
1070+
md-search-text="searchText"\
1071+
md-items="item in match(searchText)"\
1072+
md-item-text="item.display"\
1073+
md-min-length="0" \
1074+
ng-required="interpolateRequired"\
1075+
placeholder="placeholder">\
1076+
<span md-highlight-text="searchText">{{item.display}}</span>\
1077+
</md-autocomplete>';
1078+
var element = compile(template, scope);
1079+
var ctrl = element.controller('mdAutocomplete');
1080+
1081+
expect(ctrl.isRequired).toBe(false);
1082+
1083+
scope.interpolateRequired = false;
1084+
scope.$apply();
1085+
1086+
expect(ctrl.isRequired).toBe(false);
1087+
1088+
scope.interpolateRequired = true;
1089+
scope.$apply();
1090+
1091+
expect(ctrl.isRequired).toBe(true);
1092+
});
1093+
1094+
it('should forward the md-no-asterisk attribute', function() {
1095+
var scope = createScope();
1096+
var template = '\
1097+
<md-autocomplete\
1098+
md-selected-item="selectedItem"\
1099+
md-search-text="searchText"\
1100+
md-items="item in match(searchText)"\
1101+
md-item-text="item.display"\
1102+
md-min-length="0" \
1103+
required\
1104+
md-no-asterisk="true"\
1105+
md-floating-label="Asterisk Label">\
1106+
<span md-highlight-text="searchText">{{item.display}}</span>\
1107+
</md-autocomplete>';
1108+
var element = compile(template, scope);
1109+
var input = element.find('input');
1110+
1111+
expect(input.attr('md-no-asterisk')).toBe('true');
1112+
});
8951113
});
8961114

8971115
describe('md-highlight-text', function() {

src/components/autocomplete/js/autocompleteController.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -638,10 +638,11 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
638638
function fetchResults (searchText) {
639639
var items = $scope.$parent.$eval(itemExpr),
640640
term = searchText.toLowerCase(),
641-
isList = angular.isArray(items);
641+
isList = angular.isArray(items),
642+
isPromise = !!items.then; // Every promise should contain a `then` property
642643

643-
if ( isList ) handleResults(items);
644-
else handleAsyncResults(items);
644+
if (isList) handleResults(items);
645+
else if (isPromise) handleAsyncResults(items);
645646

646647
function handleAsyncResults(items) {
647648
if ( !items ) return;

0 commit comments

Comments
 (0)