@@ -73,7 +73,7 @@ function $AnchorScrollProvider() {
7373 <example module="anchorScrollExample">
7474 <file name="index.html">
7575 <div id="scrollArea" ng-controller="ScrollController">
76- <a ng-click="gotoBottom()">Go to bottom</a>
76+ <a id="top" ng-click="gotoBottom()">Go to bottom</a>
7777 <a id="bottom"></a> You're at the bottom!
7878 </div>
7979 </file>
@@ -102,6 +102,37 @@ function $AnchorScrollProvider() {
102102 margin-top: 2000px;
103103 }
104104 </file>
105+ <file name="protractor.js" type="protractor">
106+ function _isElemVisible() {
107+ var elem = document.getElementById(arguments[0]);
108+ var rect = elem.getBoundingClientRect();
109+ var docElem = document.documentElement;
110+ return (rect.top < docElem.clientHeight) &&
111+ (rect.bottom > 0) &&
112+ (rect.left < docElem.clientWidth) &&
113+ (rect.right > 0);
114+ }
115+
116+ function expectVisible(id, expected) {
117+ browser.driver.executeScript(_isElemVisible, id).then(function(isVisible) {
118+ expect(isVisible).toBe(expected);
119+ });
120+ }
121+
122+ function scrollToTop() {
123+ browser.driver.executeScript('window.scrollTo(0, 0);');
124+ }
125+
126+ it('should scroll to #bottom upon clicking #top', function() {
127+ scrollToTop();
128+ expectVisible('top', true);
129+ expectVisible('bottom', false);
130+
131+ element(by.id('top')).click();
132+ expectVisible('top', false);
133+ expectVisible('bottom', true);
134+ });
135+ </file>
105136 </example>
106137 *
107138 * <hr />
@@ -112,18 +143,19 @@ function $AnchorScrollProvider() {
112143 <example module="anchorScrollOffsetExample">
113144 <file name="index.html">
114145 <div class="fixed-header" ng-controller="headerCtrl">
115- <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5 ]">
116- Go to anchor {{x }}
146+ <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [0, 1,2,3,4]">
147+ Go to anchor {{$index + 1 }}
117148 </a>
118149 </div>
119- <div id="anchor{{x }}" class="anchor" ng-repeat="x in [1,2,3,4,5 ]">
120- Anchor {{x }} of 5
150+ <div id="anchor{{y }}" class="anchor" ng-repeat="y in [0, 1,2,3,4]">
151+ Anchor {{$index + 1 }} of 5
121152 </div>
122153 </file>
123154 <file name="script.js">
124155 angular.module('anchorScrollOffsetExample', [])
125156 .run(['$anchorScroll', function($anchorScroll) {
126- $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels
157+ // scroll with a 50px offset from the top of the viewport
158+ $anchorScroll.yOffset = 50;
127159 }])
128160 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
129161 function ($anchorScroll, $location, $scope) {
@@ -144,6 +176,8 @@ function $AnchorScrollProvider() {
144176 </file>
145177 <file name="style.css">
146178 body {
179+ height: 100%;
180+ margin: 0;
147181 padding-top: 50px;
148182 }
149183
@@ -164,6 +198,153 @@ function $AnchorScrollProvider() {
164198 margin: 5px 15px;
165199 }
166200 </file>
201+ <file name="protractor.js" type="protractor">
202+ function _isElemVisible() {
203+ var elem = document.getElementById(arguments[0]);
204+ var rect = elem.getBoundingClientRect();
205+ var docElem = document.documentElement;
206+ return (rect.top < docElem.clientHeight) &&
207+ (rect.bottom > 0) &&
208+ (rect.left < docElem.clientWidth) &&
209+ (rect.right > 0);
210+ }
211+
212+ function _getElemTop() {
213+ var elem = document.getElementById(arguments[0]);
214+ var rect = elem.getBoundingClientRect();
215+ return rect.top;
216+ }
217+
218+ function _getViewportHeight() {
219+ return window.document.documentElement.clientHeight;
220+ }
221+
222+ function _scrollElemIntoView() {
223+ var elem = document.getElementById(arguments[0]);
224+ elem.scrollIntoView();
225+ }
226+
227+ function execWithTempViewportHeight(tempHeight, fn) {
228+ setViewportHeight(tempHeight).then(function(oldHeight) {
229+ fn();
230+ setViewportHeight(oldHeight);
231+ });
232+ }
233+
234+ function execWithTempHash(tempHash, fn) {
235+ browser.driver.getCurrentUrl().then(function(oldUrl) {
236+ var newUrl = oldUrl + '#/#' + tempHash;
237+ browser.get(newUrl);
238+ fn();
239+ browser.get(oldUrl);
240+ });
241+ }
242+
243+ function expectVisible(id, expected) {
244+ browser.driver.executeScript(_isElemVisible, id).then(function(isVisible) {
245+ expect(isVisible).toBe(expected);
246+ });
247+ }
248+
249+ function expectTop(id, expected) {
250+ browser.driver.executeScript(_getElemTop, id).then(function(top) {
251+ expect(top).toBe(expected);
252+ });
253+ }
254+
255+
256+ function scrollIntoView(id) {
257+ browser.driver.executeScript(_scrollElemIntoView, id);
258+ }
259+
260+ function scrollTo(y) {
261+ browser.driver.executeScript('window.scrollTo(0, ' + y + ');');
262+ }
263+
264+ function scrollToTop() {
265+ scrollTo(0);
266+ }
267+
268+ function setViewportHeight(newHeight) {
269+ return browser.driver.executeScript(_getViewportHeight).then(function(oldHeight) {
270+ var heightDiff = newHeight - oldHeight;
271+ var win = browser.driver.manage().window();
272+
273+ return win.getSize().then(function(size) {
274+ var newWinHeight = size.height + heightDiff;
275+
276+ return win.setSize(size.width, newWinHeight).then(function() {
277+ return oldHeight;
278+ });
279+ });
280+ });
281+ }
282+
283+ describe('scrolling with 50px offset', function() {
284+ var yOffset = 50;
285+
286+ beforeEach(function() {
287+ scrollToTop();
288+ expectVisible('anchor0', true);
289+ expectTop('anchor0', yOffset);
290+ });
291+
292+ it('should scroll to the correct anchor when clicking each link', function() {
293+ var links = element.all(by.repeater('x in [0,1,2,3,4]'));
294+ var lastAnchor = element.all(by.repeater('y in [0,1,2,3,4]')).last();
295+
296+ // Make sure there is enough room to scroll the last anchor to the top
297+ lastAnchor.getSize().then(function(size) {
298+ var tempHeight = size.height - 10;
299+ execWithTempViewportHeight(tempHeight, function() {
300+ var idx = 0;
301+ links.each(function(link) {
302+ var targetAnchorId = 'anchor' + idx;
303+ link.click();
304+ expectVisible(targetAnchorId, true);
305+ expectTop(targetAnchorId, yOffset);
306+ idx++;
307+ });
308+ });
309+ });
310+ });
311+
312+ it('should automatically scroll when navigating to a URL with a hash', function() {
313+ var links = element.all(by.repeater('x in [0,1,2,3,4]'));
314+ var lastAnchor = element.all(by.repeater('y in [0,1,2,3,4]')).last();
315+ var targetAnchorId = 'anchor2';
316+
317+ // Make sure there is enough room to scroll the last anchor to the top
318+ lastAnchor.getSize().then(function(size) {
319+ var tempHeight = size.height - 10;
320+ execWithTempViewportHeight(tempHeight, function() {
321+ execWithTempHash(targetAnchorId, function() {
322+ expectVisible(targetAnchorId, true);
323+ expectTop(targetAnchorId, yOffset);
324+ });
325+ });
326+ });
327+ });
328+
329+ it('should not scroll "overzealously"', function () {
330+ var lastLink = element.all(by.repeater('x in [0,1,2,3,4]')).last();
331+ var lastAnchor = element.all(by.repeater('y in [0,1,2,3,4]')).last();
332+ var targetAnchorId = 'anchor4';
333+
334+ // Make sure there is not enough room to scroll the last anchor to the top
335+ lastAnchor.getSize().then(function(size) {
336+ var tempHeight = size.height + (yOffset / 2);
337+ execWithTempViewportHeight(tempHeight, function() {
338+ scrollIntoView(targetAnchorId);
339+ expectTop(targetAnchorId, yOffset / 2);
340+ lastLink.click();
341+ expectVisible(targetAnchorId, true);
342+ expectTop(targetAnchorId, yOffset);
343+ });
344+ });
345+ });
346+ });
347+ </file>
167348 </example>
168349 */
169350 this . $get = [ '$window' , '$location' , '$rootScope' , function ( $window , $location , $rootScope ) {
0 commit comments