aboutsummaryrefslogtreecommitdiffstats
path: root/docs/content/tutorial/step_03.ngdoc
blob: 53eaebc8e84313d986cf34bbf5c5feab6fff38a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
@ngdoc overview
@name Tutorial: Step 3
@description
<table id="tutorial_nav">
<tr>
  <td id="previous_step">{@link tutorial.step_02 Previous}</td>
  <td id="step_result">{@link  http://angular.github.com/angular-phonecat/step-3/app Live
  Demo}</td>
  <td id="tut_home">{@link tutorial Tutorial Home}</td>
<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-2...step-3 Code
Diff}</td>
  <td id="next_step">{@link tutorial.step_04 Next}</td>
</tr>
</table>

We did a lot of work in laying a foundation for the app in the last step, so now we'll do
something simple, and add full text search (yes, it will be simple!). We will also write an
end-to-end test, because a good end-to-end test is a good friend. It stays with your app, keeps an
eye on it, and quickly detects regressions. 

1. Reset your workspace to Step 3 using:

         git checkout --force step-3

  or

         ./goto_step.sh 3

2. Refresh your browser or check the app out on {@link
http://angular.github.com/angular-phonecat/step-3/app angular's server}. The app now has a search
box. The phone list on the page changes depending on what a user types into the search box.

The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-2...step-3
 GitHub}:


## Controller

We made no changes to the controller.


## Template

__`app/index.html`:__
<pre>
...
   Fulltext Search: <input name="query"/>

  <ul class="phones">
    <li ng:repeat="phone in phones.$filter(query)">
      {{phone.name}}
      <p>{{phone.snippet}}</p>
    </li>
  </ul>
...
</pre>

We added a standard HTML `<input>` tag and use angular's {@link angular.Array.filter $filter}
function to process the input for the `ng:repeater`. 

This lets a user enter search criteria and immediately see the effects of their search on the
phone list.  This new code demonstrates the following:

* Data-binding. This is one of the core features in angular. When the page loads, angular binds
the name of the input box to a variable of the same name in the data model and keeps the two in
sync.

  In this code, the data that a user types into the input box (named __`query`__) is immediately
  available as a filter input in the list repeater (`phone in phones.$filter(`__`query`__`)`).
  When changes to the data model cause the repeater's input to change, the repeater efficiently
  updates the DOM to reflect the current state of the model.

* Use of `$filter`. The {@link angular.Array.filter $filter} method, uses the `query` value, to
create a new array that contains only those records that match the `query`.

  `ng:repeat` automatically updates the view in response to the changing number of phones returned
  by the `$filter`. The process is completely transparent to the developer.

## Test

In step 2, we learned how to write and run unit tests. Unit tests are perfect for testing
controllers and other components of our application written in JavaScript, but they can't easily
test DOM manipulation or the wiring of our application. For these, an end-to-end test is a much
better choice.

The search feature was fully implemented via templates and data-binding, so we'll write our first
end-to-end test, to verify that the feature works.

__`test/e2e/scenarios.js`:__
<pre>
describe('PhoneCat App', function() {

  describe('Phone list view', function() {

    beforeEach(function() {
      browser().navigateTo('../../app/index.html');
    });

    it('should filter the phone list as user types into the search box', function() {
      expect(repeater('.phones li').count()).toBe(3);

      input('query').enter('nexus');
      expect(repeater('.phones li').count()).toBe(1);

      input('query').enter('motorola');
      expect(repeater('.phones li').count()).toBe(2);
    });
  });
});
</pre>

Even though the syntax of this test looks very much like our controller unit test written with
Jasmine, the end-to-end test uses APIs of {@link
https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en&pli=1#
angular's end-to-end test runner}.

To run the end-to-end test, open the following in a new browser tab:

* node.js users: {@link http://localhost:8000/test/e2e/runner.html}
* users with other http servers:
`http://localhost:[*port-number*]/[*context-path*]/test/e2e/runner.html`
* casual reader: {@link http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html}

This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write end-to-end tests in angular. Although this example is for a simple test, it
really is that easy to set up any functional, readable, end-to-end test.

# Experiments

* Display the current value of the `query` model by adding a `{{query}}` binding into the
`index.html` template, and see how it changes when you type in the input box.

* Change `index.html` to reflect the current search query in the html title by replacing the title
tag in the head section with: 

          <title>Google Phone Gallery: {{query}}</title>` 

  If you reload the page, you won't see the expected result. This is because the "query" model
  lives in the scope defined by: 

          <body ng:controller="PhoneListCtrl">

  In order to be able to bind to the query mode from `<title>`, we need to move the
  `ng:controller` declaration to an element that is a common parent for both the body and title
  elements.  In our case that's the html element: 

          <html ng:controller="PhoneListCtrl">

* Make the end-to-end test fail by changing the first `toBe(3)` statement to `toBe(4)`, and
refresh the end-to-end test runner tab in the browser to rerun the test.
* Add a `wait();` statement into the end-to-end test and rerun it.  You'll see the runner pausing,
giving you the opportunity to explore the state of your application displayed in the browser. The
app is live! Change the search query to prove it. This is great for troubleshooting end-to-end
tests.


# Summary

With full text search under our belt and a test to verify it, let's go to step 4 to learn how to
add sorting capability to the phone app. 

<table id="tutorial_nav">
<tr>
  <td id="previous_step">{@link tutorial.step_02 Previous}</td>
  <td id="step_result">{@link  http://angular.github.com/angular-phonecat/step-3/app Live
  Demo}</td>
  <td id="tut_home">{@link tutorial Tutorial Home}</td>
<td id="code_diff">{@link https://github.com/angular/angular-phonecat/compare/step-2...step-3 Code
Diff}</td>
  <td id="next_step">{@link tutorial.step_04 Next}</td>
</tr>
</table>