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

test($anchorScroll): add e2e tests #14932

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions test/e2e/fixtures/anchor-scroll-y-offset/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html ng-app="test">
<body>
<div class="fixed-header" ng-controller="TestController">
<button ng-click="scrollTo('anchor-' + x)" ng-repeat="x in [1, 2, 3, 4, 5]">
Scroll to anchor-{{x}}
</button>
</div>
<div class="anchor" id="anchor-{{y}}" ng-repeat="y in [1, 2, 3, 4, 5]">
Anchor {{y}} of 5
</div>

<style type="text/css">
body {
height: 100%;
margin: 0;
padding-top: 50px;
}
.anchor {
border: 2px dashed darkorchid;
padding: 10px 10px 390px 10px;
}
.fixed-header {
background-color: rgba(0, 0, 0, 0.2);
height: 50px;
position: fixed;
top: 0; left: 0; right: 0;
}
.fixed-header > button {
display: inline-block;
margin: 5px 15px;
}
</style>

<script src="angular.js"></script>
<script src="script.js"></script>
</body>
</html>
17 changes: 17 additions & 0 deletions test/e2e/fixtures/anchor-scroll-y-offset/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
angular.
module('test', []).
controller('TestController', function($anchorScroll, $location, $scope) {
$anchorScroll.yOffset = 50;

$scope.scrollTo = function(target) {
if ($location.hash() !== target) {
// Set `$location.hash()` to `target` and
// `$anchorScroll` will detect the change and scroll
$location.hash(target);
} else {
// The hash is the same, but `target` might be out of view -
// explicitly call `$anchorScroll`
$anchorScroll();
}
};
});
23 changes: 23 additions & 0 deletions test/e2e/fixtures/anchor-scroll/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html ng-app="test">
<body>
<div class="scroll-area" ng-controller="TestController">
<a id="top" ng-click="scrollTo('bottom')">Go to bottom</a>
<a id="bottom" ng-click="scrollTo('top')">Back to top</a>
</div>

<style type="text/css">
.scroll-area {
height: 200px;
overflow: auto;
}
#bottom {
display: block;
margin-top: 2000px;
}
</style>

<script src="angular.js"></script>
<script src="script.js"></script>
</body>
</html>
9 changes: 9 additions & 0 deletions test/e2e/fixtures/anchor-scroll/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
angular.
module('test', []).
controller('TestController', function($anchorScroll, $location, $scope) {
$scope.scrollTo = function(target) {
// Set `$location.hash()` to `target` and
// `$anchorScroll` will detect the change and scroll
$location.hash(target);
};
});
187 changes: 187 additions & 0 deletions test/e2e/tests/anchor-scroll.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
describe('$anchorScroll', function() {
beforeEach(function() {
jasmine.addMatchers({
toBeInViewport: function() {
return {
compare: function(id) {
var result = {
pass: browser.driver.
executeScript(_script_isInViewport, id).
then(function(isInViewport) {
result.message = 'Expected #' + id + (isInViewport ? ' not' : '') +
' to be in viewport';
return isInViewport;
})
};

return result;
}
};
},
toHaveTop: function() {
return {
compare: function(id, expectedTop) {
var result = {
pass: browser.driver.
executeScript(_script_getTop, id).
then(function(actualTop) {
var passed = actualTop === expectedTop;
result.message = 'Expected #' + id + '\'s top' + (passed ? ' not' : '') +
' to be ' + expectedTop + ', but it was ' + actualTop;
return passed;
})
};

return result;
}
};
}
});
});

describe('basic functionality', function() {
beforeEach(function() {
loadFixture('anchor-scroll');
});

it('should scroll to #bottom when clicking #top and vice versa', function() {
expect('top').toBeInViewport();
expect('bottom').not.toBeInViewport();

element(by.id('top')).click();
expect('top').not.toBeInViewport();
expect('bottom').toBeInViewport();

element(by.id('bottom')).click();
expect('top').toBeInViewport();
expect('bottom').not.toBeInViewport();
});
});

describe('with `yOffset`', function() {
var yOffset = 50;
var buttons = element.all(by.repeater('x in [1, 2, 3, 4, 5]'));
var anchors = element.all(by.repeater('y in [1, 2, 3, 4, 5]'));

beforeEach(function() {
loadFixture('anchor-scroll-y-offset');
});

it('should scroll to the correct anchor when clicking each button', function() {
var lastAnchor = anchors.last();

// Make sure there is enough room to scroll the last anchor to the top
lastAnchor.getSize().then(function(size) {
var tempHeight = size.height - 10;

execWithTempViewportHeight(tempHeight, function() {
buttons.each(function(button, idx) {
// For whatever reason, we need to run the assertions inside a callback :(
button.click().then(function() {
var anchorId = 'anchor-' + (idx + 1);

expect(anchorId).toBeInViewport();
expect(anchorId).toHaveTop(yOffset);
});
});
});
});
});

it('should automatically scroll when navigating to a URL with a hash', function() {
var lastAnchor = anchors.last();
var lastAnchorId = 'anchor-5';

// Make sure there is enough room to scroll the last anchor to the top
lastAnchor.getSize().then(function(size) {
var tempHeight = size.height - 10;

execWithTempViewportHeight(tempHeight, function() {
// Test updating `$location.url()` from within the app
expect(lastAnchorId).not.toBeInViewport();

browser.setLocation('#' + lastAnchorId);
expect(lastAnchorId).toBeInViewport();
expect(lastAnchorId).toHaveTop(yOffset);

// Test navigating to the URL directly
scrollToTop();
expect(lastAnchorId).not.toBeInViewport();

browser.refresh();
expect(lastAnchorId).toBeInViewport();
expect(lastAnchorId).toHaveTop(yOffset);
});
});
});

it('should not scroll "overzealously"', function() {
var lastButton = buttons.last();
var lastAnchor = anchors.last();
var lastAnchorId = 'anchor-5';

// Make sure there is not enough room to scroll the last anchor to the top
lastAnchor.getSize().then(function(size) {
var tempHeight = size.height + (yOffset / 2);

execWithTempViewportHeight(tempHeight, function() {
scrollIntoView(lastAnchorId);
expect(lastAnchorId).toHaveTop(yOffset / 2);

lastButton.click();
expect(lastAnchorId).toBeInViewport();
expect(lastAnchorId).toHaveTop(yOffset);
});
});
});
});

// Helpers
function _script_getTop(id) {
var elem = document.getElementById(id);
var rect = elem.getBoundingClientRect();

return rect.top;
}

function _script_isInViewport(id) {
var elem = document.getElementById(id);
var rect = elem.getBoundingClientRect();
var docElem = document.documentElement;

return (rect.top < docElem.clientHeight) &&
(rect.bottom > 0) &&
(rect.left < docElem.clientWidth) &&
(rect.right > 0);
}

function execWithTempViewportHeight(tempHeight, fn) {
setViewportHeight(tempHeight).then(function(oldHeight) {
fn();
setViewportHeight(oldHeight);
});
}

function scrollIntoView(id) {
browser.driver.executeScript('document.getElementById("' + id + '").scrollIntoView()');
}

function scrollToTop() {
browser.driver.executeScript('window.scrollTo(0, 0)');
}

function setViewportHeight(newHeight) {
return browser.driver.
executeScript('return document.documentElement.clientHeight').
then(function(oldHeight) {
var heightDiff = newHeight - oldHeight;
var win = browser.driver.manage().window();

return win.getSize().then(function(size) {
return win.
setSize(size.width, size.height + heightDiff).
then(function() { return oldHeight; });
});
});
}
});