在上一章中我们为了打牢基础做了很多的工作,所以这一章我们来学一点轻松的。我们将添加为这个网站应用添加全文搜索功能(没错,会很轻松的)。另外,结束之后我们还会写一个端到端(End to end)的测试,因为它是开发人员的好帮手。它会始终帮你监视着你的应用,并且能及时检测回归。
本次页面上的改动是增加了一个搜索框。然后下面展示的手机列表信息将会随着用户输入的搜索条件改变而改变。
代码不用自己写了,直接使用GIT命令切换到step-3
git checkout -f step-3
我们假设你已经运行了之前章节的网站,这时候,你只需要刷新页面就可以访问step-3分支对应的网站页面了。点击这里查看在线的演示。
控制器
无需做任何改动。
模版
app/index.html
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!--Sidebar content-->
Search: <input ng-model="query">
</div>
<div class="col-md-10">
<!--Body content-->
<ul class="phones">
<li ng-repeat="phone in phones | filter:query">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>
首先我们添加了一个搜索输入框<input>
标签。然后使用Angular的filter
函数来处理用户的输入,最后把结果使用ngRepeat命令输出。
用户输入了一个搜索条件后,我们可以立马在页面上看到搜索后的手机列表。这段代码像我们演示了以下几个AngularJS的特性:
- 数据绑定:这是AngularJS的关键特性之一。当模版页面被加载时,Angular将会把同名的input框和数据模型绑定在一起,并且一直保持两者数据同步(注解:只要有一个变了,另一个也会变)。
在本例中,用户在输入框(名字叫做query
)输入的字符串将会立即被当作循环器(ngRepeat指令标记)的过滤条件,这个功能是由phone in phones | filter:query
这句话决定的。用户输入引起了数据模型变化,数据模型变化引起了循环器的过滤条件变化,最终循环器快速高效的把过滤后的结果展示在页面上。
-
filter
过滤器:其实质是一个函数,这个函数将根据query
的值创建一个手机信息的数组,这个数组里只含有匹配query
的记录。然后ngRepeat
命令就会自动更新视图(这个过程对用户来说是透明的)。
测试
在上一章中,我们学会了如何编写和运行单元测试。单元测试对测试控制器代码或者应用的其他控件是非常完美的,但是用来测试DOM操作和应用完整性还是很困难的。对于这种情况,E2E测试是一个非常好的选择。
上述全文搜索的功能完全是由模版和数据绑定来实现的,所以我们将使用端到端测试来验证这个功能能运行正确。
test/e2e/scenarios.js
describe('PhoneCat App', function() {
describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html');
});
it('should filter the phone list as a user types into the search box', function() {
var phoneList = element.all(by.repeater('phone in phones'));
var query = element(by.model('query'));
expect(phoneList.count()).toBe(3);
query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
});
});
这个测试代码用来确保搜索条件输入框和循环器能够正确的关联起来。可以看到在Angular中编写端到端E2E测试有多简单!这个例子看起来很简单,但它确实和其他更加复杂的E2E测试没什么两样。
使用Protractor运行E2E测试
上述E2E测试代码使用了Protractor的API,它的语法习惯很像我们使用Jasmin写的控制器测试代码。点击这里查看关于Protractor的更多知识http://angular.github.io/protractor/#/api。
就像我们使用Karma运行单元测试一样,我们使用Protractor来运行E2E测试用例。操作很简单,我们只需要输入npm run protractor
命令来运行即可。E2E测试很慢,所以Protractor不能像Karma那样随时监控改动并且自动运行测试用例。如果要重新运行Protractor,需要手动的再次运行上述命令。
注意:整个Protractor的测试过程需要保证使用服务器来运行网站。【注解:就是说你必须要以服务器的形势运行你的网站】。运行网站的命令是:
npm start
。除此之外,你还得确保已经安装了Protractor以及它所依赖的一些控件,你可以通过npm install
和npm run update-webdriver
来完成这个步骤。然后你就可以通过npm run protractor
来运行Protractor了。
实验小能手
1. 显示当前的查询
在模版文件index.html
中添加一个{{query}}来展示query
模型数据的当前值,它可以向你展示当你输入条件的时候,它是如何变化的。
2. 在标题中展示查询条件
下面我们来看看如何获取query
模型的当前值并且显示到HTML的页面标题中。
- 首先在
test/e2e/scenarios.js
中的describe
块中添加一个E2E测试,如下所示:
describe('PhoneCat App', function() {
describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html');
});
var phoneList = element.all(by.repeater('phone in phones'));
var query = element(by.model('query'));
it('should filter the phone list as a user types into the search box', function() {
expect(phoneList.count()).toBe(3);
query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
it('should display the current filter value in the title bar', function() {
query.clear();
expect(browser.getTitle()).toMatch(/Google Phone Gallery:\s*$/);
query.sendKeys('nexus');
expect(browser.getTitle()).toMatch(/Google Phone Gallery: nexus$/);
});
});
});
重新运行Protactor(npm run protractor
),可以发现这个测试用例测试失败了。
- 也许你觉得你可以把
{{query}}
添加到<title>
标签里,像下面这样:
<title>Google Phone Gallery: {{query}}</title>
然而,当你刷新页面的时候,你将不会看到响应的结果。这是因为query
模型所在的域
是由<body>
标签中的ng-controller="PhoneListCtrl"
定义的:
<body ng-controller="PhoneListCtrl">
如果你想把query
模型绑定到<title>
中,你需要把ng-controller
的定义放到<html>
标签里,因为它是<title>
和<body>
共同的父亲元素。像下面这样:
<html ng-app="phonecatApp" ng-controller="PhoneListCtrl">
同时别忘了把原来的<body>
中的ng-controller
定义去掉。
- 重新运行
npm run protractor
,可以看到测试现在运行通过了。 - 当你使用上面这种绑定方法来显示到title里的时候,你会看到页面加载的时候,标题里显示的
{{query}}
,加载完之后才能看到query对应的值。一种更好的方案是使用ngBind
和ngBindTemplate
命令,它们可以让页面加载的时候,表达式定义不被直接输出:
<title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
总结
本章中我们往之前的网站中加了一个搜索框,并且写了一个E2E测试来测试搜索结果。下一章中我们将为这个网站应用添加排序的功能。