Tutorial: 3 - Filtering Repeaters

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.

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 GitHub:

Controller

We made no changes to the controller.

Template

app/index.html:

...
   Fulltext Search: <input name="query"/>

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

We added a standard HTML <input> tag and use angular's $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:

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:

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);
    });
  });
});

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 angular's end-to-end test runner.

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

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

    it('should display the current filter value within an element with id "status"',
        function() {
      expect(element('#status').text()).toMatch(/Current filter: \s*$/);

      input('query').enter('nexus');

      expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/);

      //alternative version of the last assertion that tests just the value of the binding
      using('#status').expect(binding('query')).toBe('nexus');
    });
  

Refresh the browser tab with end-to-end test runner to see the test fail. Now add a div or p element with id "status" and content with the query binding into the index.html template to make the test pass.

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.