Skip to content

Commit 745db82

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

File tree

5 files changed

+252
-0
lines changed

5 files changed

+252
-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-scrollSpec.js

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

0 commit comments

Comments
 (0)