diff --git a/addon-test-support/@ember/test-helpers/dom/scroll-to.ts b/addon-test-support/@ember/test-helpers/dom/scroll-to.ts new file mode 100644 index 000000000..52aedc9fd --- /dev/null +++ b/addon-test-support/@ember/test-helpers/dom/scroll-to.ts @@ -0,0 +1,49 @@ +import getElement from './-get-element'; +import fireEvent from './fire-event'; +import settled from '../settled'; +import { nextTickPromise } from '../-utils'; + +/** + Scrolls DOM element or selector to the given coordinates. + @public + @param {string|HTMLElement|window} target the element or selector to trigger scroll on + @param {Number} x x-coordinate + @param {Number} y y-coordinate + @return {Promise} resolves when settled + + @example + + Scroll DOM element to specific coordinates + + + scrollTo('#my-long-div', 0, 0); // scroll to top + scrollTo('#my-long-div', 0, 100); // scroll down + scrollTo(window, 0, 0); // scroll window to top +*/ +export default function scrollTo( + target: string | HTMLElement | Window, + x: number, + y: number +): Promise { + return nextTickPromise().then(() => { + if (!target) { + throw new Error('Must pass an element or selector to `scrollTo`.'); + } + + let element = getElement(target); + if (!element) { + throw new Error(`Element not found when calling \`scrollTo('${target}')\`.`); + } + + if (element instanceof HTMLElement) { + element.scrollTop = y; + element.scrollLeft = x; + } else if (target instanceof Window) { + target.scrollTo(x, y); + } + + fireEvent(element, 'scroll'); + + return settled(); + }); +} diff --git a/addon-test-support/@ember/test-helpers/index.ts b/addon-test-support/@ember/test-helpers/index.ts index 39036eb31..54af0f657 100644 --- a/addon-test-support/@ember/test-helpers/index.ts +++ b/addon-test-support/@ember/test-helpers/index.ts @@ -39,3 +39,4 @@ export { default as getRootElement } from './dom/get-root-element'; export { default as find } from './dom/find'; export { default as findAll } from './dom/find-all'; export { default as typeIn } from './dom/type-in'; +export { default as scrollTo } from './dom/scroll-to'; diff --git a/tests/integration/dom/scroll-to-test.js b/tests/integration/dom/scroll-to-test.js new file mode 100644 index 000000000..cfa318a82 --- /dev/null +++ b/tests/integration/dom/scroll-to-test.js @@ -0,0 +1,100 @@ +import { module, test } from 'qunit'; +import hbs from 'htmlbars-inline-precompile'; +import { + render, + scrollTo, + setupContext, + setupRenderingContext, + teardownContext, +} from '@ember/test-helpers'; + +module('DOM Helper: scroll-to', function(hooks) { + hooks.beforeEach(async function() { + await setupContext(this); + await setupRenderingContext(this); + }); + + hooks.afterEach(async function() { + await teardownContext(this); + }); + + test('Scroll in vertical direction', async function(assert) { + assert.expect(1); + + let currentScrollPosition = 0; + let scrollAmount = 50; + this.callback = e => { + currentScrollPosition = e.target.scrollTop; + }; + + await render(hbs` +
+
    +
  • A
  • +
  • B
  • +
  • C
  • +
  • D
  • +
  • E
  • +
+
+ `); + + await scrollTo('.container', 0, scrollAmount); + + assert.equal( + currentScrollPosition, + scrollAmount, + 'After use of the `scrollTop` a paint cycle is triggered and the callback is called' + ); + }); + + test('Scroll in horizontal direction', async function(assert) { + let currentScrollPosition = 0; + let scrollAmount = 50; + this.callback = e => { + currentScrollPosition = e.target.scrollLeft; + }; + + await render(hbs` +
+
A
+
B
+
C
+
D
+
E
+
+ `); + + await scrollTo('.container', scrollAmount, 0); + + assert.equal( + currentScrollPosition, + scrollAmount, + 'After use of the `scrollLeft` a paint cycle is triggered and the callback is called' + ); + }); + + test('Scroll on window works', async function(assert) { + await render(hbs` + + `); + + await scrollTo(window, 0, 100); + + assert.equal(window.scrollY, 100, 'scrollTo scrolls window the appropriate amount'); + }); +});