├── LICENSE
├── README.md
├── TODO.md
├── bower.json
├── demo
├── smarty-config.js
├── smarty.css
├── smarty.html
└── smarty.js
├── src
├── smarty-config.js
└── smarty.js
└── tests
└── angular-smarty-tests.js
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright © 2014 Thumbtack, Inc.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of the copyright holder nor the names of its contributors
15 | may be used to endorse or promote products derived from this software without
16 | specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angular-smarty
2 | ==============
3 |
4 | Autocomplete UI written with Angular JS.
5 |
6 | Installation
7 | ---------------------
8 |
9 | `bower install angular-smarty`
10 |
11 | Configuration
12 | ---------------------
13 |
14 | Configuration is handled in smarty-config.js which is injected into the main module in smarty.js.
15 | The main configuration variable is the function getSmartySuggestions that returns a promise
16 | containing the suggestions to be used in the autocomplete dropdown list. The default
17 | getSmartySuggestions uses a url endpoint for a backend service that returns suggestions in the
18 | following format: [{Name: suggestion1}, {Name: suggestion:2}, etc.].
19 |
20 | Usage
21 | ---------------------
22 |
23 | Include smarty-config.js and smarty.js in that order in your html. In your main Angular module,
24 | inject angular-smarty, or if you're not using Angular elsewhere on your page, include
25 | `angular.bootstrap(angular.element("body"), ["angular-smarty"]);` in a script tag in your html and
26 | don't forget that Angular is a dependency.
27 |
28 | ```js
29 | $(function() {
30 | angular.bootstrap(angular.element("body"), ["angular-smarty"]);
31 | });
32 | ```
33 |
34 | Model your html after the following:
35 |
36 | ```html
37 |
38 |
39 |
angular-smarty demo
40 |
47 |
48 |
49 | ```
50 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | To Do
2 | ======
3 |
4 | 1. Improve documentation.
5 | 2. Improve demo.
6 | 3. Create a userInteraction service for setSelected, selected, selectionMade, and suggestionPicked.
7 | 4. Create services for clickedSomewhereElse and submitClicked to make code more testable.
8 | 5. Add tests.
9 | 6. Make more contributor-friendly.
10 | 7. Add config variable for number of responses.
11 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-smarty",
3 | "version": "0.3.0",
4 | "authors": [
5 | "Katie Thomas "
6 | ],
7 | "description": "Angular add-on for creating a drop-down list of autocomplete suggestions for a form input.",
8 | "main": ["src/smarty.js", "src/smarty-config.js"],
9 | "keywords": [
10 | "autocomplete",
11 | "drop-down",
12 | "smarty",
13 | "angular"
14 | ],
15 | "license": "BSD-3-Clause",
16 | "homepage": "github.com/thumbtack/angular-smarty",
17 | "ignore": [
18 | "**/.*"
19 | ],
20 | "dependencies": {
21 | "angular": "1.*"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/demo/smarty-config.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | "use strict";
3 |
4 | var app = angular.module("angular-smarty-config", []);
5 |
6 | app.service("smartyConfig", ["$timeout", "$q", function($timeout, $q) {
7 | var possibleSuggestions = ["bakasana", "adho mukha svanasana", "adho mukha vrkshasana"];
8 |
9 | function getSmartySuggestions(prefix) {
10 | var deferred = $q.defer();
11 | $timeout(function() {
12 | var suggestions = findSuggestions(prefix);
13 | if (suggestions.length > 0) {
14 | deferred.resolve(suggestions);
15 | } else {
16 | deferred.reject([]);
17 | }
18 | }, 0);
19 | return deferred.promise;
20 | };
21 |
22 | function findSuggestions(prefix) {
23 | var prefix = prefix.toLowerCase();
24 | var suggestions = [];
25 | for (var i = 0; i < possibleSuggestions.length; i++) {
26 | if (possibleSuggestions[i].length < prefix.length) {
27 | continue;
28 | } else if (possibleSuggestions[i].slice(0, prefix.length) == prefix) {
29 | suggestions.push(possibleSuggestions[i]);
30 | }
31 | }
32 | return suggestions;
33 | };
34 |
35 | return {
36 | getSmartySuggestions: getSmartySuggestions
37 | }
38 | }]);
39 |
40 | app.service("smartySuggestor", ["smartyConfig", function(smartyConfig) {
41 | var getSmartySuggestions = smartyConfig.getSmartySuggestions;
42 |
43 | return {
44 | getSmartySuggestions: getSmartySuggestions
45 | };
46 | }]);
47 | })();
48 |
--------------------------------------------------------------------------------
/demo/smarty.css:
--------------------------------------------------------------------------------
1 | .container-main {
2 | margin: auto;
3 | width: 300px;
4 | }
5 |
6 | .container-autocomplete {
7 | margin: auto;
8 | width: 200px;
9 | position: relative;
10 | }
11 |
12 | .autocomplete-suggestions-menu {
13 | position: absolute;
14 | z-index: 1;
15 | background-color: white;
16 | border: 1px solid black;
17 | width: 100%;
18 | }
19 |
20 | .container-autocomplete input {
21 | width: 100%;
22 | }
23 |
24 | .autocomplete-suggestions-menu p:hover {
25 | cursor: default;
26 | }
27 |
28 | .autocomplete-suggestions-menu .selected {
29 | color: #CCCCCC;
30 | background: yellow;
31 | }
32 |
--------------------------------------------------------------------------------
/demo/smarty.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | angular-smarty demo
4 |
5 |
6 |
7 |
8 |
angular-smarty demo
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
--------------------------------------------------------------------------------
/demo/smarty.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | "use strict";
3 |
4 | var app = angular.module("angular-smarty", ["angular-smarty-config"]);
5 |
6 | app.controller("SmartyController", [
7 | "$scope", "$document", "smartySuggestor", "$window", "$timeout",
8 | function($scope, $document, smartySuggestor, $window, $timeout) {
9 | /** Once the user has typed something in the input, smarty should
10 | * handle the following situations accordingly:
11 | * - User clicks outside the input or suggestions dropdown:
12 | * - Suggestions dropdown disappears
13 | * - Behaves normally when user clicks back into the input
14 | * - User clicks a suggestion from the dropdown:
15 | * - Suggestions dropdown disappears
16 | * - Input is filled with value of clicked suggestion
17 | * - Focus is on zipcode input
18 | * - User presses up or down arrows, or hovers with mouse
19 | * ($scope.userInteraction = true)
20 | * - The selected suggestion changes accordingly
21 | * - User presses enter or blurs the input
22 | * - If there is a selection made, that selection should fill the input
23 | * - If there is no selection made, whatever the user has currently typed
24 | * should remain in the input
25 | * - Focus is moved to the zipcode input
26 | */
27 | // $scope.suggestions holds the smart suggestions based on the current prefix.
28 | // If there are suggestions in the array, the suggestions drowdown will show.
29 | // $scope.prefix holds the value of the request input.
30 | // $scope.selected holds the index of $scope.suggestions that is
31 | // currently selected by the user. If $scope.selected is -1, nothing is selected.
32 | $scope.suggestions = [];
33 | $scope.prefix = "";
34 | $scope.selected = -1;
35 | $scope.selectionMade = false;
36 | $scope.zip = "";
37 |
38 | $scope.$watch("prefix", function(newValue, oldValue) {
39 | if (newValue != oldValue && $scope.selectionMade == false) {
40 | if ($scope.prefix == "" || angular.isUndefined($scope.prefix)) {
41 | $scope.suggestions = [];
42 | $scope.selected = -1;
43 | } else {
44 | var promise = smartySuggestor.getSmartySuggestions($scope.prefix);
45 | promise.then(function(data) {
46 | $scope.suggestions = data;
47 | });
48 | }
49 | }
50 | });
51 |
52 | $scope.clickedSomewhereElse = function() {
53 | $scope.selected = -1;
54 | $scope.suggestions = [];
55 | };
56 |
57 | $document.bind("click", function() {
58 | $scope.$apply($scope.clickedSomewhereElse());
59 | });
60 |
61 | $scope.suggestionPicked = function() {
62 | if ($scope.selected != -1 && $scope.selected < $scope.suggestions.length) {
63 | $scope.prefix = $scope.suggestions[$scope.selected];
64 | }
65 | $scope.selectionMade = true;
66 | $scope.suggestions = [];
67 | };
68 |
69 | $scope.setSelected = function(newValue) {
70 | if (newValue > $scope.suggestions.length) {
71 | $scope.selected = 0;
72 | } else if (newValue < 0) {
73 | $scope.selected = $scope.suggestions.length;
74 | } else {
75 | $scope.selected = newValue;
76 | }
77 | };
78 |
79 | $scope.submitClicked = function() {
80 | if ($scope.requestFormStart.$valid) {
81 | $window.location = "/request?query=" + $scope.prefix + "&zipCode=" + $scope.zip +
82 | "&origin=homepage";
83 | }
84 | };
85 | }
86 | ]);
87 |
88 | app.directive("smartyInput", function() {
89 | function link(scope, element) {
90 | element.bind("keydown", function(event) {
91 | switch(event.which) {
92 | case 40: // down arrow
93 | scope.$apply(function() {
94 | scope.select({"x": parseInt(scope.index) + 1});
95 | });
96 | break;
97 | case 38: // up arrow
98 | scope.$apply(function() {
99 | scope.select({"x": parseInt(scope.index) - 1});
100 | });
101 | break;
102 | case 13: // enter
103 | event.preventDefault();
104 | if (scope.selectionMade == false) {
105 | if (scope.index == "-1") {
106 | scope.$apply(function() {
107 | scope.listItems = [];
108 | });
109 | }
110 | scope.$apply(function() {
111 | scope.close();
112 | })
113 | }
114 | break;
115 | default:
116 | scope.$apply(function() {
117 | scope.selectionMade = false;
118 | scope.index = -1;
119 | });
120 | }
121 | });
122 |
123 | element.bind("blur", function(event) {
124 | if (scope.listItems.length) {
125 | event.preventDefault();
126 | scope.$apply(function() {
127 | scope.close();
128 | })
129 | }
130 | });
131 | }
132 | return {
133 | restrict: "A",
134 | link: link,
135 | scope: {
136 | prefix: "=ngModel",
137 | select: "&",
138 | index: "=",
139 | selectionMade: "=",
140 | listItems: "=",
141 | close: "&"
142 | }
143 | };
144 | })
145 |
146 | app.directive("smartySuggestions", ["$document", function($document) {
147 | // Watches the scope variable prefix, which is bound to an input field.
148 | // Updates suggestions when there is a change in prefix, but only when
149 | // selectionMade equals false.
150 | function link(scope, element, attrs) {
151 | element.bind("click", function(e) {
152 | e.stopPropagation();
153 | });
154 | }
155 | return {
156 | restrict: "A",
157 | link: link,
158 | scope: {
159 | suggestions: "=",
160 | selected: "=",
161 | applyClass: "&",
162 | selectSuggestion: "&",
163 | prefix: "@"
164 | },
165 | template: ' '+
169 | '{{suggestion}} ' +
170 | '
' +
171 | ' ' +
174 | 'Show all for "{{prefix}}" »
'
175 | };
176 | }]);
177 |
178 | app.directive("focusMe", function() {
179 | return {
180 | restrict: "A",
181 | link: function(scope, element, attrs) {
182 | attrs.$observe("focusWhen", function() {
183 | if (attrs.focusWhen == "true") {
184 | element[0].focus();
185 | }
186 | });
187 | }
188 | };
189 | });
190 |
191 | app.directive("smartySuggestionsBox", function() {
192 | // Removes the need for duplicating the scode that makes the suggestions list.
193 | return {
194 | restrict: "A",
195 | template: ' 0" prefix="{{prefix}}"' +
199 | 'class="autocomplete-suggestions-menu ng-cloak">
'
200 | };
201 | });
202 | })();
203 |
--------------------------------------------------------------------------------
/src/smarty-config.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | "use strict";
3 |
4 | var app = angular.module("angular-smarty-config", []);
5 |
6 | app.service("smartyConfig", ["$http", "$filter", function($http, $filter) {
7 | /* The default getSmartySuggestions function makes a request to requestUrl with the given
8 | * requestParams and expects an array of JSON objects in return, e.g. [{Name: suggestion1},
9 | * {Name: suggestion2}]
10 | */
11 | var requestUrl = "",
12 | requestParams = {};
13 |
14 | /* getSmartySuggestions should return a promise. See the demo for an example of how to
15 | * construct an Angular promise.
16 | */
17 | function getSmartySuggestions(prefix) {
18 | requestParams["query"] = escape(prefix.toLowerCase());
19 | var promise = $http.get(requestUrl(),
20 | {
21 | params: requestParams,
22 | cache: true
23 | }
24 | )
25 | .then(function(response) {
26 | /* response.data is an the array of JSON objects where Name is the key used to identify
27 | * a suggestion
28 | */
29 | return $filter("limitTo")(response.data, 5).map(function(item) {
30 | return item.Name;
31 | });
32 | });
33 | return promise;
34 | }
35 |
36 | return {
37 | getSmartySuggestions: getSmartySuggestions
38 | }
39 | }]);
40 |
41 | app.service("smartySuggestor", ["smartyConfig", function(smartyConfig) {
42 | var getSmartySuggestions = smartyConfig.getSmartySuggestions;
43 |
44 | return {
45 | getSmartySuggestions: getSmartySuggestions
46 | };
47 | }]);
48 |
49 | })();
50 |
--------------------------------------------------------------------------------
/src/smarty.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | "use strict";
3 |
4 | var app = angular.module("angular-smarty", ["angular-smarty-config"]);
5 |
6 | app.controller("SmartyController", [
7 | "$scope", "$document", "smartySuggestor", "$window", "$timeout",
8 | function($scope, $document, smartySuggestor, $window, $timeout) {
9 | /** Once the user has typed something in the input, smarty should
10 | * handle the following situations accordingly:
11 | * - User clicks outside the input or suggestions dropdown:
12 | * - Suggestions dropdown disappears
13 | * - Behaves normally when user clicks back into the input
14 | * - User clicks a suggestion from the dropdown:
15 | * - Suggestions dropdown disappears
16 | * - Input is filled with value of clicked suggestion
17 | * - Focus is on zipcode input
18 | * - User presses up or down arrows, or hovers with mouse
19 | * ($scope.userInteraction = true)
20 | * - The selected suggestion changes accordingly
21 | * - User presses enter or blurs the input
22 | * - If there is a selection made, that selection should fill the input
23 | * - If there is no selection made, whatever the user has currently typed
24 | * should remain in the input
25 | * - Focus is moved to the zipcode input
26 | */
27 | // $scope.suggestions holds the smart suggestions based on the current prefix.
28 | // If there are suggestions in the array, the suggestions drowdown will show.
29 | // $scope.prefix holds the value of the request input.
30 | // $scope.selected holds the index of $scope.suggestions that is
31 | // currently selected by the user. If $scope.selected is -1, nothing is selected.
32 | $scope.suggestions = [];
33 | $scope.prefix = "";
34 | $scope.selected = -1;
35 | $scope.selectionMade = false;
36 | $scope.zip = "";
37 |
38 | $scope.$watch("prefix", function(newValue, oldValue) {
39 | if (newValue != oldValue && $scope.selectionMade == false) {
40 | if ($scope.prefix == "" || angular.isUndefined($scope.prefix)) {
41 | $scope.suggestions = [];
42 | $scope.selected = -1;
43 | } else {
44 | var promise = smartySuggestor.getSmartySuggestions($scope.prefix);
45 | promise.then(function(data) {
46 | $scope.suggestions = data;
47 | });
48 | }
49 | }
50 | });
51 |
52 | $scope.clickedSomewhereElse = function() {
53 | $scope.selected = -1;
54 | $scope.suggestions = [];
55 | };
56 |
57 | $document.bind("click", onDocumentClick);
58 | $scope.$on("$destroy", function() {
59 | $document.unbind("click", onDocumentClick);
60 | })
61 | function onDocumentClick() {
62 | $scope.$apply($scope.clickedSomewhereElse());
63 | }
64 |
65 | $scope.suggestionPicked = function() {
66 | if ($scope.selected != -1 && $scope.selected < $scope.suggestions.length) {
67 | $scope.prefix = $scope.suggestions[$scope.selected];
68 | }
69 | $scope.selectionMade = true;
70 | $scope.suggestions = [];
71 | };
72 |
73 | $scope.setSelected = function(newValue) {
74 | if (newValue > $scope.suggestions.length) {
75 | $scope.selected = 0;
76 | } else if (newValue < 0) {
77 | $scope.selected = $scope.suggestions.length;
78 | } else {
79 | $scope.selected = newValue;
80 | }
81 | };
82 |
83 | $scope.submitClicked = function() {
84 | if ($scope.requestFormStart.$valid) {
85 | $window.location = "/request?query=" + $scope.prefix + "&zipCode=" + $scope.zip +
86 | "&origin=homepage";
87 | }
88 | };
89 | }
90 | ]);
91 |
92 | app.directive("smartyInput", function() {
93 | function link(scope, element) {
94 | element.bind("keydown", function(event) {
95 | switch(event.which) {
96 | case 40: // down arrow
97 | scope.$apply(function() {
98 | scope.select({"x": parseInt(scope.index) + 1});
99 | });
100 | break;
101 | case 38: // up arrow
102 | scope.$apply(function() {
103 | scope.select({"x": parseInt(scope.index) - 1});
104 | });
105 | break;
106 | case 13: // enter
107 | event.preventDefault();
108 | if (scope.selectionMade == false) {
109 | if (scope.index == "-1") {
110 | scope.$apply(function() {
111 | scope.listItems = [];
112 | });
113 | }
114 | scope.$apply(function() {
115 | scope.close();
116 | })
117 | }
118 | break;
119 | default:
120 | scope.$apply(function() {
121 | scope.selectionMade = false;
122 | scope.index = -1;
123 | });
124 | }
125 | });
126 |
127 | element.bind("blur", function(event) {
128 | if (scope.listItems.length) {
129 | event.preventDefault();
130 | scope.$apply(function() {
131 | scope.close();
132 | })
133 | }
134 | });
135 | }
136 | return {
137 | restrict: "A",
138 | link: link,
139 | scope: {
140 | prefix: "=ngModel",
141 | select: "&",
142 | index: "=",
143 | selectionMade: "=",
144 | listItems: "=",
145 | close: "&"
146 | }
147 | };
148 | })
149 |
150 | app.directive("smartySuggestions", ["$document", function($document) {
151 | // Watches the scope variable prefix, which is bound to an input field.
152 | // Updates suggestions when there is a change in prefix, but only when
153 | // selectionMade equals false.
154 | function link(scope, element, attrs) {
155 | element.bind("click", function(e) {
156 | e.stopPropagation();
157 | });
158 | }
159 | return {
160 | restrict: "A",
161 | link: link,
162 | scope: {
163 | suggestions: "=",
164 | selected: "=",
165 | applyClass: "&",
166 | selectSuggestion: "&",
167 | prefix: "@"
168 | },
169 | template: ' '+
173 | '[[suggestion]] ' +
174 | '
' +
175 | ' ' +
178 | 'Show all for "[[prefix]]" »
'
179 | };
180 | }]);
181 |
182 | app.directive("focusMe", function() {
183 | return {
184 | restrict: "A",
185 | link: function(scope, element, attrs) {
186 | attrs.$observe("focusWhen", function() {
187 | if (attrs.focusWhen == "true") {
188 | element[0].focus();
189 | }
190 | });
191 | }
192 | };
193 | });
194 |
195 | app.directive("smartySuggestionsBox", function() {
196 | // Removes the need for duplicating the scode that makes the suggestions list.
197 | return {
198 | restrict: "A",
199 | template: ' 0" prefix="[[prefix]]"' +
203 | 'class="autocomplete-suggestions-menu ng-cloak">
'
204 | };
205 | });
206 | })();
207 |
--------------------------------------------------------------------------------
/tests/angular-smarty-tests.js:
--------------------------------------------------------------------------------
1 | describe("smarty", function() {
2 | // To configure angular-smarty-tests.js:
3 | // Variables that need to be edited:
4 | // expectedResults, line 31
5 | // expectedParsedResults, line 70
6 | // requestUrl, line 79
7 | // requestPrefix, line 80
8 |
9 | beforeEach(module("HomepageApp"));
10 |
11 | var scope;
12 |
13 | beforeEach(inject(function($rootScope) {
14 | scope = $rootScope.$new();
15 | }));
16 |
17 | // --- SMARTY CONTROLLER --- //
18 | describe("smarty controller", function() {
19 | beforeEach(inject(function($rootScope, $controller) {
20 | $controller("SmartyController", {
21 | $scope: scope
22 | });
23 | }));
24 |
25 | it("should initialize scope correctly", function() {
26 | expect(scope.selected).toEqual(-1);
27 | expect(scope.prefix).toEqual("");
28 | expect(scope.zip).toEqual("");
29 | expect(scope.suggestions).toEqual([]);
30 | expect(scope.selectionMade).toBe(false);
31 | });
32 | });
33 |
34 | // --- SMARTY SUGGESTOR SERVICE --- //
35 | describe("smarty suggestor", function() {
36 | var $httpBackend;
37 | var expectedResults = [
38 | {
39 | "ID":2,
40 | "Name":"Handyman",
41 | "Taxonym":"Handyman",
42 | "PluralTaxonym":"Handymen",
43 | "Rank":2.070353290907192
44 | },{
45 | "ID":835,
46 | "Name":"General Carpentry",
47 | "Taxonym":"Carpenter",
48 | "PluralTaxonym":"Carpenters",
49 | "Rank":1.8778105233780995
50 | },{
51 | "ID":10,
52 | "Name":"General Contracting",
53 | "Taxonym":"General Contractor",
54 | "PluralTaxonym":"General Contractors",
55 | "Rank":1.7331602733663476
56 | },{
57 | "ID":35,
58 | "Name":"Interior Design",
59 | "Taxonym":"Interior Designer",
60 | "PluralTaxonym":"Interior Designers",
61 | "Rank":1.7029912403077567
62 | },{
63 | "ID":317,
64 | "Name":"Metalwork",
65 | "Taxonym":"Metalworker",
66 | "PluralTaxonym":"Metalworkers",
67 | "Rank":1.6196185496387445
68 | }
69 | ];
70 | var expectedParsedResults = [
71 | "Handyman",
72 | "General Carpentry",
73 | "General Contracting",
74 | "Interior Design",
75 | "Metalwork"
76 | ];
77 |
78 | beforeEach(inject(function($injector) {
79 | var requestUrl = "";
80 | var requestPrefix = "";
81 | $httpBackend = $injector.get("$httpBackend");
82 | $httpBackend.when("GET", requestUrl).respond(
83 | expectedResults
84 | );
85 | }));
86 |
87 | afterEach(function() {
88 | $httpBackend.verifyNoOutstandingExpectation();
89 | $httpBackend.verifyNoOutstandingRequest();
90 | });
91 |
92 | it("returns a promise", inject(function(smartySuggestor) {
93 | $httpBackend.expectGET(requestUrl);
94 | var results;
95 | smartySuggestor.getSmartySuggestions(requestPrefix).then(function(data) {
96 | results = data;
97 | })
98 | $httpBackend.flush();
99 | expect(results).toEqual(expectedParsedResults);
100 | }));
101 | });
102 |
103 | // --- SMARTY FOCUS ME DIRECTIVE --- //
104 | describe("smarty focusMe directive", function() {
105 | var element;
106 |
107 | beforeEach(function() {
108 | var html = '';
110 |
111 | inject(function($compile, $rootScope) {
112 | scope = $rootScope.$new();
113 | scope.selectionMade = false;
114 | element = $compile(angular.element(html))(scope);
115 | scope.$digest();
116 | element.appendTo(document.body);
117 | });
118 | });
119 |
120 | afterEach(function() {
121 | element.remove();
122 | });
123 |
124 | it("is not in focus before selectionMade is true", function() {
125 | expect(element[0]).not.toEqual(document.activeElement);
126 | });
127 |
128 | it("is in focus after selectionMade is true", function() {
129 | scope.selectionMade = true;
130 | scope.$digest();
131 | expect(element[0]).toBe(document.activeElement);
132 | });
133 | });
134 |
135 | // --- SMARTY INPUT DIRECTIVE --- //
136 | describe("smarty input directive", function() {
137 | var element;
138 | var e;
139 |
140 | beforeEach(function() {
141 | var html = '';
144 |
145 | var mockSuggestions = [
146 | "florist",
147 | "fence builder",
148 | "face painter",
149 | "fence repair",
150 | "flooring"
151 | ];
152 |
153 | e = $.Event("keydown");
154 |
155 | inject(function($compile, $rootScope, $controller) {
156 | scope = $rootScope;
157 | $controller("SmartyController", {
158 | $scope: scope
159 | });
160 | scope.prefix = "f";
161 | scope.suggestions = mockSuggestions;
162 | element = $compile(angular.element(html))($rootScope);
163 | scope.$digest();
164 | });
165 | });
166 |
167 | it("should adjust index on down arrow presses", function() {
168 | expect(element.isolateScope().index).toEqual(-1);
169 | for (var i = 0; i < 6; i++) {
170 | e.which = 40;
171 | angular.element(element).triggerHandler(e);
172 | expect(element.isolateScope().index).toEqual(i % 6);
173 | };
174 | });
175 |
176 | it("should adjust index on up arrow presses", function() {
177 | var expectedIndices = [5, 4, 3, 2, 1, 0];
178 | expect(element.isolateScope().index).toEqual(-1);
179 | for (var i = 0; i < 6; i++) {
180 | e.which = 38;
181 | angular.element(element).triggerHandler(e);
182 | expect(element.isolateScope().index).toEqual(expectedIndices[i]);
183 | };
184 | });
185 |
186 | it("should, on enter, fill with the appropriate value", function() {
187 | e.which = 13;
188 | angular.element(element).triggerHandler(e);
189 | expect(element.isolateScope().prefix).toEqual("f");
190 | expect(element.isolateScope().selectionMade).toBe(true);
191 | });
192 |
193 | it("should, on enter, fill with the appropriate value (index != 0)", function() {
194 | e.which = 40;
195 | angular.element(element).triggerHandler(e);
196 | e.which = 13;
197 | angular.element(element).triggerHandler(e);
198 | expect(element.isolateScope().prefix).toEqual(mockSuggestions[0]);
199 | expect(element.isolateScope().selectionMade).toBe(true);
200 | });
201 |
202 | it("should, on tab, fill with the appropriate value", function() {
203 | e.which = 9;
204 | console.log("before:", element.isolateScope());
205 | angular.element(element).triggerHandler(e);
206 | console.log("after:", element.isolateScope());
207 | expect(element.isolateScope().prefix).toEqual("f");
208 | expect(element.isolateScope().selectionMade).toBe(true);
209 | });
210 |
211 | it("should, on tab, fill with the appropriate value (index != -1)", function() {
212 | e.which = 40;
213 | angular.element(element).triggerHandler(e);
214 | e.which = 9;
215 | angular.element(element).triggerHandler(e);
216 | expect(element.isolateScope().prefix).toEqual(mockSuggestions[0]);
217 | expect(element.isolateScope().selectionMade).toBe(true);
218 | });
219 | });
220 |
221 | // --- SMARTY SUGGESTIONS DIRECTIVE --- //
222 | describe("smarty suggestions directive", function(){
223 | var element;
224 |
225 | beforeEach(function() {
226 | var html = '';
229 | var mockSuggestions = [
230 | "florist",
231 | "fence builder",
232 | "face painter",
233 | "fence repair",
234 | "flooring"
235 | ];
236 |
237 | inject(function($compile, $rootScope, $controller) {
238 | $controller("SmartyController", {
239 | $scope: scope
240 | });
241 | element = $compile(angular.element(html))(scope);
242 | element.scope().suggestions = mockSuggestions;
243 | element.scope().$digest();
244 | scope = $rootScope.$new();
245 | });
246 | });
247 |
248 | it("should load p elements when scope.suggestions.length > 0", function() {
249 | expect(element.html()).toContain('