Index: chrome/test/data/webui/settings/settings_transitions_browsertest.js |
diff --git a/chrome/test/data/webui/settings/settings_transitions_browsertest.js b/chrome/test/data/webui/settings/settings_transitions_browsertest.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..af2e65d728229ed353169c8b8121de02ac996d58 |
--- /dev/null |
+++ b/chrome/test/data/webui/settings/settings_transitions_browsertest.js |
@@ -0,0 +1,392 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+/** @fileoverview Suite of tests for Settings navigational transitions. */ |
+ |
+GEN_INCLUDE(['settings_page_browsertest.js']); |
+ |
+/** |
+ * @constructor |
+ * @extends {SettingsPageBrowserTest} |
+*/ |
+function SettingsTransitionsBrowserTest() {} |
+ |
+/** |
+ * @typedef {{ |
+ * duration: Number, |
+ * hasStarted: function(): bool, |
+ * testIntermediateState: function(), |
+ * testAfter: function() |
tommycli
2016/08/01 22:56:10
Was this supposed to be testEndState?
michaelpg
2016/08/04 00:56:48
Done.
|
+ * }} |
+ */ |
+var TransitionTest; |
+ |
+/** @const {number} Default animation timing for neon-animated-pages. */ |
+var kNeonAnimationsDuration = 500; |
+ |
+SettingsTransitionsBrowserTest.prototype = { |
+ __proto__: SettingsPageBrowserTest.prototype, |
+ |
+ /** @override */ |
+ browsePreload: 'chrome://md-settings/', |
+ |
+ /** |
+ * Tests a transition by waiting for it to start, running tests during its |
+ * execution, and running tests after its duration is reached, all using |
+ * callbacks -- no access to underlying animations. |
+ * @param {!TransitionTest} transitionTest |
+ * @return {!Promise} Resolved once the tests have finished. |
+ */ |
+ testTransition: function(transitionTest) { |
+ // We approximate the time when the transition started. |
+ var startedNoEarlierThan = window.performance.now(); |
+ var startedNoLaterThan; |
+ return new Promise(function(resolve, reject) { |
+ // Figure out when the transition has started with a rAF loop. Even if we |
+ // required access to underlying animation(s), there's no good way to |
+ // force a transition to start or know when it starts without spinning. |
+ (function checkIfStarted() { |
+ if (transitionTest.hasStarted()) { |
+ startedNoLaterThan = window.performance.now(); |
+ resolve(); |
+ } else { |
+ // Update the latest known time the animation wasn't running, then |
+ // check again. |
+ startedNoEarlierThan = window.performance.now(); |
+ requestAnimationFrame(checkIfStarted); |
+ } |
+ })(); |
tommycli
2016/08/01 22:56:10
Wow that block above is exotic.
michaelpg
2016/08/04 00:56:48
Well the recursive function to check for a conditi
|
+ }).then(function() { |
+ // Hopefully, the transition hasn't finished yet. |
+ var minDuration = window.performance.now() - startedNoLaterThan; |
+ var maxDuration = window.performance.now() - startedNoEarlierThan; |
+ assertLE(minDuration, transitionTest.duration); |
+ if (maxDuration < transitionTest.duration) { |
+ transitionTest.testIntermediateState(); |
+ } else { |
+ // We ran quite late. Don't run testIntermediateState(). |
+ console.warn('testIntermediateState() was not called because the ' + |
+ 'transition may have already ended.'); |
+ } |
+ // Wait until the transition should have completed. |
+ var maxTimeToEnd = transitionTest.duration - minDuration; |
+ var BUFFER = 10; |
tommycli
2016/08/01 22:56:10
Is this leftover?
michaelpg
2016/08/04 00:56:48
I'm now seeing occasional flakes with any kind of
|
+ return new Promise(function(resolve, reject) { |
+ setTimeout(function() { |
+ transitionTest.testEndState(); |
+ resolve(); |
+ }, maxTimeToEnd + BUFFER); |
+ }); |
+ }); |
+ }, |
+ |
+ /** |
+ * Checks whether the subpage header (back button and title) is visible. |
+ * @param {!SettingsSubpageElement} subpage |
+ * @return {boolean} |
+ */ |
+ checkSubpageHeaderVisible: function(subpage) { |
+ var backButton = subpage.$$('[icon="settings:arrow-back"]'); |
+ assertNotEquals(null, backButton); |
+ assertGT(backButton.clientHeight, 0); |
+ var title = subpage.$$('h2'); |
+ assertNotEquals(null, title); |
+ assertGT(title.clientHeight, 0); |
+ assertGT(title.innerText.trim().length, 0); |
+ }, |
+ |
+ /** |
+ * @param {!HTMLElement} newNode The node to fade to. |
+ * @param {!HTMLElement} oldNode The node to fade from. |
+ * @return {!TransitionTest} |
+ */ |
+ getFadeTransitionTest: function(newNode, oldNode) { |
+ var newNodeStyle = getComputedStyle(newNode); |
+ var oldNodeStyle = getComputedStyle(oldNode); |
+ return { |
+ duration: kNeonAnimationsDuration, |
+ |
+ hasStarted: function() { |
+ return newNodeStyle.opacity != '0' && |
+ oldNodeStyle.opacity != '1'; |
+ }, |
+ |
+ testIntermediateState: function() { |
+ assertGT(oldNode.clientHeight, 0); |
+ assertGT(newNode.clientHeight, 0); |
+ |
+ var oldNodeOpacity = parseFloat(oldNodeStyle.opacity); |
+ assertLT(oldNodeOpacity, 1); |
+ assertGT(oldNodeOpacity, 0); |
+ var newNodeOpacity = parseFloat(newNodeStyle.opacity); |
+ assertLT(newNodeOpacity, 1); |
+ assertGT(newNodeOpacity, 0); |
+ }, |
+ |
+ testEndState: function() { |
+ assertEquals('none', oldNodeStyle.display); |
+ assertEquals('1', newNodeStyle.opacity); |
+ }, |
+ }; |
+ }, |
+ |
+ /** |
+ * Tests the transition to open a subpage from the main animatable. |
+ * @param {!SettingsSectionElement} section |
+ * @param {string} subpageId |
+ * @param {!settings.Route} subpageRoute |
+ * @return {!Promise<!SettingsSubpageElement>} Resolved with the opened |
+ * subpage once the transition completes. |
+ */ |
+ testOpenSubpage: function(section, subpageId, subpageRoute) { |
+ var page = section.domHost; |
+ |
+ var sectionStyle = getComputedStyle(section); |
+ var cardStyle = getComputedStyle(section.$.card); |
+ |
+ // Some sanity checks of starting conditions. |
+ var sectionHeightStart = section.clientHeight; |
+ var cardHeightStart = section.$.card.clientHeight; |
+ assertGT(cardHeightStart, 10); |
+ assertGT(sectionHeightStart, cardHeightStart); |
+ |
+ var mainAnimatable = this.getSectionMainAnimatable(section); |
+ assertNotEquals(null, mainAnimatable); |
+ assertGT(mainAnimatable.clientHeight, 0); |
+ var mainAnimatableStyle = getComputedStyle(mainAnimatable); |
+ |
+ this.verifySubpagesHidden(section); |
+ |
+ var otherSections = this.getSections(section.domHost); |
+ otherSections.splice(otherSections.indexOf(section), 1); |
+ assertGT(otherSections.length, 0); |
+ |
+ for (var otherSection of otherSections) |
+ assertGT(otherSection.clientHeight, 0); |
+ |
+ // Play the transition. |
+ settings.navigateTo(subpageRoute); |
+ assertEquals(subpageRoute, page.currentRoute); |
+ |
+ // The section and the card should not have changed height yet. |
+ assertEquals(section.clientHeight, sectionHeightStart); |
+ assertEquals(section.$.card.clientHeight, cardHeightStart); |
+ |
+ // The main animatable is still fully visible. |
+ assertEquals('1', mainAnimatableStyle.opacity); |
+ |
+ // The subpage should be stamped but not faded in yet. |
+ var subpages = this.getSectionSubpages(section); |
+ assertTrue(subpages.has(subpageId)); |
+ var subpage = subpages.get(subpageId); |
+ this.checkSubpageHeaderVisible(subpage); |
+ var subpageStyle = getComputedStyle(subpage); |
+ assertEquals('0', subpageStyle.opacity); |
+ |
+ var container = page.offsetParent; |
+ |
+ // Test stages of the section/card expand transition. |
+ var expandTransitionTest = { |
+ duration: EXPAND_DURATION, |
+ |
+ hasStarted: function() { |
+ // Transition starts by expanding card. |
+ return section.$.card.clientHeight > cardHeightStart; |
+ }, |
+ |
+ testIntermediateState: function() { |
+ // The card must be fixed in the viewport while expanding. |
+ assertEquals('fixed', cardStyle.position); |
+ |
+ // The card is offset to be below the toolbar. |
+ assertGT(section.$.card.offsetTop, |
+ container.getBoundingClientRect().top); |
+ |
+ // All sections retain their dimensions while the expanding section's |
+ // card expands. |
+ assertEquals(sectionHeightStart, section.clientHeight); |
+ for (var otherSection of otherSections) |
+ assertGT(otherSection.clientHeight, 0); |
+ }, |
+ |
+ testEndState: function() { |
+ // Sanity check. |
+ assertGT(section.clientHeight, sectionHeightStart); |
+ |
+ // Transition ends with the expanded section spanning the |
+ // container height at a minimum. |
+ assertEquals(sectionStyle.minHeight, '100%'); |
+ assertGE(section.clientHeight, container.clientHeight); |
+ |
+ // All other sections are hidden. |
+ for (var otherSection of otherSections) |
+ assertEquals(0, otherSection.clientHeight); |
+ }, |
+ }; |
+ |
+ // Test stages of the animatable/subpage fade transition. |
+ var fadeTransitionTest = |
+ this.getFadeTransitionTest(subpage, mainAnimatable); |
+ |
+ return Promise.all([ |
+ this.testTransition(expandTransitionTest), |
+ this.testTransition(fadeTransitionTest), |
+ ]).then(function() { |
+ return subpage; |
+ }); |
+ }, |
+ |
+ /** |
+ * Tests the transition to close a subpage and return to the main animatable. |
+ * @return {!Promise} Resolved once the transition |
+ * completes. |
+ */ |
+ testCloseSubpage: function(section, subpage) { |
+ var page = section.domHost; |
+ var container = page.offsetParent; |
+ |
+ var sectionStyle = getComputedStyle(section); |
+ var cardStyle = getComputedStyle(section.$.card); |
+ var subpageStyle = getComputedStyle(subpage); |
+ |
+ var sectionHeightStart = section.clientHeight; |
+ var cardHeightStart = section.$.card.clientHeight; |
+ |
+ var mainAnimatable = this.getSectionMainAnimatable(section); |
+ assertTrue(!!mainAnimatable); |
+ assertEquals(mainAnimatable.clientHeight, 0); |
+ var mainAnimatableStyle = getComputedStyle(mainAnimatable); |
+ |
+ var otherSections = this.getSections(section.domHost); |
+ otherSections.splice(otherSections.indexOf(section), 1); |
+ assertGT(otherSections.length, 0); |
+ |
+ for (var otherSection of otherSections) |
+ assertEquals(otherSection.clientHeight, 0); |
+ |
+ // Find the 2nd-highest route (the section itself). |
+ var route = page.currentRoute; |
+ while (route.parent.parent) |
+ route = route.parent; |
+ |
+ // Play the transition. |
+ settings.navigateTo(route); |
+ assertEquals(route, page.currentRoute); |
+ |
+ // Depending on the browser height, the card may have been scrollable. |
+ if (container.clientHeight == cardHeightStart) { |
+ // The card was not scrollable, so the height should not have changed. |
+ assertEquals(cardHeightStart, section.$.card.clientHeight); |
+ } else { |
+ // The card was scrollable (larger than the container), so it should have |
+ // been resized to the container height. |
+ assertEquals(section.$.card.clientHeight, container.clientHeight); |
+ } |
+ |
+ // The subpage is still fully visible. |
+ assertEquals('1', subpageStyle.opacity); |
+ this.checkSubpageHeaderVisible(subpage); |
+ |
+ // Test stages of the section/card collapse transition. |
+ var collapseTransitionTest = { |
+ duration: EXPAND_DURATION, |
+ |
+ hasStarted: function() { |
+ // Transition starts by collapsing card. |
+ return section.$.card.clientHeight < cardHeightStart; |
+ }, |
+ |
+ testIntermediateState: function() { |
+ // The card must be fixed in the viewport while collapsing. |
+ assertEquals('fixed', cardStyle.position); |
+ |
+ // All sections are now visible. |
+ for (var otherSection of otherSections) |
+ assertGT(otherSection.clientHeight, 0); |
+ |
+ // TODO(michaelpg): The card should be offset to be below the toolbar, |
+ // but scroll position breaks on closing the subpage: crbug.com/537359. |
+ }, |
+ |
+ testEndState: function() { |
+ // Sanity check. |
+ assertLT(section.clientHeight, sectionHeightStart); |
+ assertEquals('0px', sectionStyle.minHeight); |
+ }, |
+ }; |
+ |
+ // Test stages of the animatable/subpage fade transition. |
+ var fadeTransitionTest = |
+ this.getFadeTransitionTest(mainAnimatable, subpage); |
+ |
+ return Promise.all([ |
+ this.testTransition(collapseTransitionTest), |
+ this.testTransition(fadeTransitionTest), |
+ ]).then(function() { |
+ return subpage; |
+ }); |
+ }, |
+}; |
+ |
+TEST_F('SettingsTransitionsBrowserTest', 'Subpages', function() { |
+ var self = this; |
+ |
+ suite('Subpages', function() { |
+ suiteSetup(function() { |
+ var settingsUI = document.querySelector('* /deep/ settings-ui'); |
+ MockInteractions.tap(settingsUI.$.close); |
+ }); |
+ |
+ test('open and close a sub-page', function() { |
+ var page = self.getPage('basic'); |
+ assertEquals(settings.Route.BASIC, page.currentRoute); |
+ |
+ var section = self.getSection(page, 'search'); |
+ |
+ // Make the section's main page 100px shorter than the page container to |
+ // test a normal expand animation. |
+ var container = page.offsetParent; |
+ assertGT(container.clientHeight, 200, |
+ 'Browser window too short to test WebUI!'); |
+ var mainAnimatable = self.getSectionMainAnimatable(section); |
+ mainAnimatable.style.height = container.clientHeight - 100 + 'px'; |
+ section.scrollIntoView(); |
+ |
+ return self.testOpenSubpage( |
+ section, 'search-engines', settings.Route.SEARCH_ENGINES |
+ ).then(function(subpage) { |
+ // Change the subpage's height to test scrolling. |
+ subpage.style.height = '100px'; |
+ // Everything is taller than the subpage, since the section's min-height |
+ // is 100% of the container. |
+ assertGT(section.$.card.clientHeight, subpage.clientHeight); |
+ assertEquals(section.$.card.clientHeight, section.clientHeight); |
+ // The container shouldn't scroll vertically. Check the offsetHeight in |
+ // case the container has a horizontal scrollbar (small window). |
+ // TODO(michaelpg): Why is this necessary? |
+ assertEquals(container.offsetHeight, section.clientHeight); |
+ assertEquals(container.offsetHeight, container.scrollHeight); |
+ |
+ // Make sure the section and its card stretch with the content. |
+ subpage.style.height = '10000px'; |
+ assertEquals(subpage.clientHeight, section.clientHeight); |
+ assertEquals(subpage.clientHeight, section.$.card.clientHeight); |
+ |
+ // The card is now larger than the container, so the container should |
+ // scroll. |
+ assertGT(section.$.card.clientHeight, container.clientHeight); |
+ assertGT(container.scrollHeight, container.clientHeight); |
+ assertEquals(10000, container.scrollHeight); |
+ |
+ subpage.style.height = ''; |
+ return subpage; |
+ }).then(function(subpage) { |
+ // Close the subpage. |
+ return self.testCloseSubpage(section, subpage); |
+ }); |
+ }); |
+ }); |
+ |
+ mocha.run(); |
+}); |