我們在上一步做了很多基礎(chǔ)性的訓(xùn)練,所以現(xiàn)在我們可以來做一些簡單的事情嘍。我們要加入全文檢索功能(沒錯(cuò),這個(gè)真的非常簡單?。?。同時(shí),我們也會寫一個(gè)端到端測試,因?yàn)橐粋€(gè)好的端到端測試可以幫上很大忙。它監(jiān)視著你的應(yīng)用,并且在發(fā)生回歸的時(shí)候迅速報(bào)告。
請重置工作目錄:
git checkout -f step-3
我們的應(yīng)用現(xiàn)在有了一個(gè)搜索框。注意到頁面上的手機(jī)列表隨著用戶在搜索框中的輸入而變化。
步驟2和步驟3之間最重要的不同在下面列出。你可以在GitHub里看到完整的差別。
我們對控制器不做任何修改。
app/index.html
<div class="container-fluid">
<div class="row-fluid">
<div class="span2">
<!--Sidebar content-->
Search: <input ng-model="query">
</div>
<div class="span10">
<!--Body content-->
<ul class="phones">
<li ng-repeat="phone in phones | filter:query">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>
我們現(xiàn)在添加了一個(gè)<input>
標(biāo)簽,并且使用AngularJS的$filter函數(shù)來處理ngRepeat指令的輸入。
這樣允許用戶輸入一個(gè)搜索條件,立刻就能看到對電話列表的搜索結(jié)果。我們來解釋一下新的代碼:
數(shù)據(jù)綁定: 這是AngularJS的一個(gè)核心特性。當(dāng)頁面加載的時(shí)候,AngularJS會根據(jù)輸入框的屬性值名字,將其與數(shù)據(jù)模型中相同名字的變量綁定在一起,以確保兩者的同步性。
在這段代碼中,用戶在輸入框中輸入的數(shù)據(jù)名字稱作query
,會立刻作為列表迭代器(phone in phones | filter:
query`)其過濾器的輸入。當(dāng)數(shù)據(jù)模型引起迭代器輸入變化的時(shí)候,迭代器可以高效得更新DOM將數(shù)據(jù)模型最新的狀態(tài)反映出來。
使用filter
過濾器:filter函數(shù)使用query
的值來創(chuàng)建一個(gè)只包含匹配query
記錄的新數(shù)組。
ngRepeat
會根據(jù)filter
過濾器生成的手機(jī)記錄數(shù)據(jù)數(shù)組來自動更新視圖。整個(gè)過程對于開發(fā)者來說都是透明的。
在步驟2,我們學(xué)習(xí)了編寫和運(yùn)行一個(gè)測試的方法。單元測試用來測試我們用js編寫的控制器和其他組件都非常方便,但是不能方便的對DOM操作和應(yīng)用集成進(jìn)行測試。對于這些來說,端到端測試是一個(gè)更好的選擇。
搜索特性是完全通過模板和數(shù)據(jù)綁定實(shí)現(xiàn)的,所以我們的第一個(gè)端到端測試就來驗(yàn)證這些特性是否符合我們的預(yù)期。
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);
});
});
});
盡管這段測試代碼的語法看起來和我們之前用Jasmine寫的單元測試非常像,但是端到端測試使用的是AngularJS端到端測試器提供的接口。
運(yùn)行一個(gè)端到端測試,在瀏覽器新標(biāo)簽頁中打開下面任意一個(gè):
http://localhost:8000/test/e2e/runner.html
http://localhost:[port-number]/[context-path]/test/e2e/runner.html
這個(gè)測試驗(yàn)證了搜素框和迭代器被正確地集成起來。你可以發(fā)現(xiàn),在AngularJS里寫一個(gè)端到端測試多么的簡單。盡管這個(gè)例子僅僅是一個(gè)簡單的測試,但是用它來構(gòu)建任何一個(gè)復(fù)雜、可讀的端到端測試都很容易。
index.html
模板中添加一個(gè){{query}}
綁定來實(shí)時(shí)顯示query
模型的當(dāng)前值,然后觀察他們是如何根據(jù)輸入框中的值而變化。現(xiàn)在我們來看一下我們怎么讓query
模型的值出現(xiàn)在HTML的頁面標(biāo)題上。
你或許認(rèn)為像下面這樣在title
標(biāo)簽上加上一個(gè)綁定就行了:
<title>Google Phone Gallery: {{query}}</title>
但是,當(dāng)你重載頁面的時(shí)候,你根本沒辦法得到期望的結(jié)果。這是因?yàn)?code>query模型僅僅在body
元素定義的作用域內(nèi)才有效。
<body ng-controller="PhoneListCtrl">
如果你想讓<title>
元素綁定上query
模型,你必須把ngController
聲明移動到HTML
元素上,因?yàn)樗?code>title和body
元素的共同祖先。
<html ng-app ng-controller="PhoneListCtrl">
一定要注意把body
元素上的ng-controller
聲明給刪了。
當(dāng)綁定兩個(gè)花括號在title
元素上可以實(shí)現(xiàn)我們的目標(biāo),但是你或許發(fā)現(xiàn)了,頁面正加載的時(shí)候它們已經(jīng)顯示給用戶看了。一個(gè)更好的解決方案是使用ngBind或者ngBindTemplate指令,它們在頁面加載時(shí)對用戶是不可見的:
<title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
test/e2e/scenarios.js
的describe
塊中加入下面這些端到端測試代碼: 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');
});
刷新瀏覽器,端到端測試器會報(bào)告測試失敗。為了讓測試通過,編輯index.html
,添加一個(gè)id為“status”
的div
或者p
元素,內(nèi)容是一個(gè)query
綁定,再加上Current filter:
前綴。例如:
<div id="status">Current filter: {{query}}</div>
pause();
語句,重新跑一遍。你將發(fā)現(xiàn)測試器暫停了!這樣允許你有機(jī)會在測試運(yùn)行過程中查看你應(yīng)用的狀態(tài)。測試應(yīng)用是實(shí)時(shí)的!你可以更換搜索內(nèi)容來證明。稍有經(jīng)驗(yàn)?zāi)憔蜁?,這對于在端到端測試中迅速找到問題是多么的關(guān)鍵。我們現(xiàn)在添加了全文搜索功能,并且完成一個(gè)測試證明了搜索是對的!現(xiàn)在讓我們繼續(xù)到雙向綁定來看看給我們的手機(jī)應(yīng)用增加排序功能。
更多建議: