Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 7c6eb78

Browse files
committed
test($anchorScroll): add e2e tests
Fixes #9535 Closes #9583
1 parent c13c666 commit 7c6eb78

File tree

5 files changed

+274
-0
lines changed

5 files changed

+274
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html>
2+
<html ng-app="test">
3+
<body>
4+
<div class="fixed-header" ng-controller="TestController">
5+
<button ng-click="scrollTo('anchor-' + x)" ng-repeat="x in [1, 2, 3, 4, 5]">
6+
Scroll to anchor-{{x}}
7+
</button>
8+
</div>
9+
<div class="anchor" id="anchor-{{y}}" ng-repeat="y in [1, 2, 3, 4, 5]">
10+
Anchor {{y}} of 5
11+
</div>
12+
13+
<style type="text/css">
14+
body {
15+
height: 100%;
16+
margin: 0;
17+
padding-top: 50px;
18+
}
19+
.anchor {
20+
border: 2px dashed darkorchid;
21+
padding: 10px 10px 390px 10px;
22+
}
23+
.fixed-header {
24+
background-color: rgba(0, 0, 0, 0.2);
25+
height: 50px;
26+
position: fixed;
27+
top: 0; left: 0; right: 0;
28+
}
29+
.fixed-header > button {
30+
display: inline-block;
31+
margin: 5px 15px;
32+
}
33+
</style>
34+
35+
<script src="angular.js"></script>
36+
<script src="script.js"></script>
37+
</body>
38+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
angular.
2+
module('test', []).
3+
controller('TestController', function($anchorScroll, $location, $scope) {
4+
$anchorScroll.yOffset = 50;
5+
6+
$scope.scrollTo = function(target) {
7+
if ($location.hash() !== target) {
8+
// Set `$location.hash()` to `target` and
9+
// `$anchorScroll` will detect the change and scroll
10+
$location.hash(target);
11+
} else {
12+
// The hash is the same, but `target` might be out of view -
13+
// explicitly call `$anchorScroll`
14+
$anchorScroll();
15+
}
16+
};
17+
});
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!DOCTYPE html>
2+
<html ng-app="test">
3+
<body>
4+
<div class="scroll-area" ng-controller="TestController">
5+
<a id="top" ng-click="scrollTo('bottom')">Go to bottom</a>
6+
<a id="bottom" ng-click="scrollTo('top')">Back to top</a>
7+
</div>
8+
9+
<style type="text/css">
10+
.scroll-area {
11+
height: 200px;
12+
overflow: auto;
13+
}
14+
#bottom {
15+
display: block;
16+
margin-top: 2000px;
17+
}
18+
</style>
19+
20+
<script src="angular.js"></script>
21+
<script src="script.js"></script>
22+
</body>
23+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
angular.
2+
module('test', []).
3+
controller('TestController', function($anchorScroll, $location, $scope) {
4+
$scope.scrollTo = function(target) {
5+
// Set `$location.hash()` to `target` and
6+
// `$anchorScroll` will detect the change and scroll
7+
$location.hash(target);
8+
};
9+
});

test/e2e/tests/anchor-scroll.spec.js

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
describe('$anchorScroll', function() {
2+
beforeEach(function() {
3+
jasmine.addMatchers({
4+
toBeInViewport: function() {
5+
return {
6+
compare: function(id) {
7+
var result = {
8+
pass: browser.driver.
9+
executeScript(_script_isInViewport, id).
10+
then(function(isInViewport) {
11+
result.message = 'Expected #' + id + (isInViewport ? ' not' : '') +
12+
' to be in viewport';
13+
return isInViewport;
14+
})
15+
};
16+
17+
return result;
18+
}
19+
};
20+
},
21+
toHaveTop: function() {
22+
return {
23+
compare: function(id, expectedTop) {
24+
var result = {
25+
pass: browser.driver.
26+
executeScript(_script_getTop, id).
27+
then(function(actualTop) {
28+
var passed = actualTop === expectedTop;
29+
result.message = 'Expected #' + id + '\'s top' + (passed ? ' not' : '') +
30+
' to be ' + expectedTop + ', but it was ' + actualTop;
31+
return passed;
32+
})
33+
};
34+
35+
return result;
36+
}
37+
};
38+
}
39+
});
40+
});
41+
42+
describe('basic functionality', function() {
43+
beforeEach(function() {
44+
loadFixture('anchor-scroll');
45+
});
46+
47+
it('should scroll to #bottom when clicking #top and vice versa', function() {
48+
expect('top').toBeInViewport();
49+
expect('bottom').not.toBeInViewport();
50+
51+
element(by.id('top')).click();
52+
expect('top').not.toBeInViewport();
53+
expect('bottom').toBeInViewport();
54+
55+
element(by.id('bottom')).click();
56+
expect('top').toBeInViewport();
57+
expect('bottom').not.toBeInViewport();
58+
});
59+
});
60+
61+
describe('with `yOffset`', function() {
62+
var yOffset = 50;
63+
var buttons = element.all(by.repeater('x in [1, 2, 3, 4, 5]'));
64+
var anchors = element.all(by.repeater('y in [1, 2, 3, 4, 5]'));
65+
66+
beforeEach(function() {
67+
loadFixture('anchor-scroll-y-offset');
68+
});
69+
70+
it('should scroll to the correct anchor when clicking each button', function() {
71+
var lastAnchor = anchors.last();
72+
73+
// Make sure there is enough room to scroll the last anchor to the top
74+
lastAnchor.getSize().then(function(size) {
75+
var tempHeight = size.height - 10;
76+
77+
execWithTempViewportHeight(tempHeight, function() {
78+
buttons.each(function(button, idx) {
79+
// For whatever reason, we need to run the assertions inside a callback :(
80+
button.click().then(function() {
81+
var anchorId = 'anchor-' + (idx + 1);
82+
83+
expect(anchorId).toBeInViewport();
84+
expect(anchorId).toHaveTop(yOffset);
85+
});
86+
});
87+
});
88+
});
89+
});
90+
91+
it('should automatically scroll when navigating to a URL with a hash', function() {
92+
var lastAnchor = anchors.last();
93+
var lastAnchorId = 'anchor-5';
94+
95+
// Make sure there is enough room to scroll the last anchor to the top
96+
lastAnchor.getSize().then(function(size) {
97+
var tempHeight = size.height - 10;
98+
99+
execWithTempViewportHeight(tempHeight, function() {
100+
// Test updating `$location.url()` from within the app
101+
expect(lastAnchorId).not.toBeInViewport();
102+
103+
browser.setLocation('#' + lastAnchorId);
104+
expect(lastAnchorId).toBeInViewport();
105+
expect(lastAnchorId).toHaveTop(yOffset);
106+
107+
// Test navigating to the URL directly
108+
scrollToTop();
109+
expect(lastAnchorId).not.toBeInViewport();
110+
111+
browser.refresh();
112+
expect(lastAnchorId).toBeInViewport();
113+
expect(lastAnchorId).toHaveTop(yOffset);
114+
});
115+
});
116+
});
117+
118+
it('should not scroll "overzealously"', function() {
119+
var lastButton = buttons.last();
120+
var lastAnchor = anchors.last();
121+
var lastAnchorId = 'anchor-5';
122+
123+
// Make sure there is not enough room to scroll the last anchor to the top
124+
lastAnchor.getSize().then(function(size) {
125+
var tempHeight = size.height + (yOffset / 2);
126+
127+
execWithTempViewportHeight(tempHeight, function() {
128+
scrollIntoView(lastAnchorId);
129+
expect(lastAnchorId).toHaveTop(yOffset / 2);
130+
131+
lastButton.click();
132+
expect(lastAnchorId).toBeInViewport();
133+
expect(lastAnchorId).toHaveTop(yOffset);
134+
});
135+
});
136+
});
137+
});
138+
139+
// Helpers
140+
function _script_getTop(id) {
141+
var elem = document.getElementById(id);
142+
var rect = elem.getBoundingClientRect();
143+
144+
return rect.top;
145+
}
146+
147+
function _script_isInViewport(id) {
148+
var elem = document.getElementById(id);
149+
var rect = elem.getBoundingClientRect();
150+
var docElem = document.documentElement;
151+
152+
return (rect.top < docElem.clientHeight) &&
153+
(rect.bottom > 0) &&
154+
(rect.left < docElem.clientWidth) &&
155+
(rect.right > 0);
156+
}
157+
158+
function execWithTempViewportHeight(tempHeight, fn) {
159+
setViewportHeight(tempHeight).then(function(oldHeight) {
160+
fn();
161+
setViewportHeight(oldHeight);
162+
});
163+
}
164+
165+
function scrollIntoView(id) {
166+
browser.driver.executeScript('document.getElementById("' + id + '").scrollIntoView()');
167+
}
168+
169+
function scrollToTop() {
170+
browser.driver.executeScript('window.scrollTo(0, 0)');
171+
}
172+
173+
function setViewportHeight(newHeight) {
174+
return browser.driver.
175+
executeScript('return document.documentElement.clientHeight').
176+
then(function(oldHeight) {
177+
var heightDiff = newHeight - oldHeight;
178+
var win = browser.driver.manage().window();
179+
180+
return win.getSize().then(function(size) {
181+
return win.
182+
setSize(size.width, size.height + heightDiff).
183+
then(function() { return oldHeight; });
184+
});
185+
});
186+
}
187+
});

0 commit comments

Comments
 (0)