From fac116b835551e93ec6ebb15c9a25db3e0e50412 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Mon, 29 Jul 2019 09:18:49 +0100 Subject: [PATCH] Add tests for handling overlap of activeElement If an element receives focus when underneath a stick element, the window should scroll so the focused element is revealed. Includes some changes to the WindowMock API to allow access to data from Jest spies. --- .../stick-to-window-when-scrolling.test.js | 135 ++++++++++++++++++ tests/javascripts/support/helpers.js | 9 +- 2 files changed, 141 insertions(+), 3 deletions(-) diff --git a/tests/javascripts/stick-to-window-when-scrolling.test.js b/tests/javascripts/stick-to-window-when-scrolling.test.js index 3fb8ff1bb..6df9a3817 100644 --- a/tests/javascripts/stick-to-window-when-scrolling.test.js +++ b/tests/javascripts/stick-to-window-when-scrolling.test.js @@ -6,6 +6,29 @@ function getScreenItemBottomPosition (screenItem) { return screenItem.offsetTop + screenItem.offsetHeight; }; +function getStickyGroupPosition (screenMock, opts) { + + const edgePosition = screenMock.window[opts.edge]; + const height = opts.stickyEls + .map(el => el.offsetHeight) + .reduce((accumulator, height) => accumulator + height); + + if (opts.edge === 'top') { + return { + top: screenMock.window.top, + bottom: edgePosition + height, + height: height + }; + } else { + return { + top: edgePosition - height, + bottom: screenMock.window.bottom, + height: height + } + } + +}; + beforeAll(() => { require('../../app/assets/javascripts/stick-to-window-when-scrolling.js'); }); @@ -227,6 +250,62 @@ describe("Stick to top/bottom of window when scrolling", () => { }); + describe("if element is made sticky and another element underneath it is focused", () => { + + let checkbox; + let checkboxBottom; + + beforeEach(() => { + + const inputFormBottom = getScreenItemBottomPosition(inputForm); + + inputForm.insertAdjacentHTML('afterEnd', ''); + checkbox = document.querySelector('input[type=checkbox]'); + + screenMock.mockPositionAndDimension('checkbox', checkbox, { + offsetHeight: 50, // 118px smaller than the sticky + offsetWidth: 727, + offsetTop: inputFormBottom + }); + + checkboxBottom = getScreenItemBottomPosition(checkbox); + + // move the sticky over the checkbox. It's 168px high so this position will cause it to overlap. + screenMock.scrollTo(checkbox.offsetTop - 10); + + window.GOVUK.stickAtTopWhenScrolling.init(); + + }); + + afterEach(() => { + + screenMock.window.spies.window.scrollTo.mockClear(); + + }); + + test("the window should scroll so the focused element is revealed", () => { + + // update inputForm position as DOM normally would + inputForm.offsetTop = screenMock.window.top; + + let stickyPosition = getStickyGroupPosition(screenMock, { stickyEls: [inputForm], edge: 'top' }); + + // sticky position should overlap checkbox position + expect(stickyPosition.top).toBeLessThanOrEqual(checkbox.offsetTop); + expect(stickyPosition.bottom).toBeGreaterThanOrEqual(checkboxBottom); + + // the sticky element (page footer) is 50 high so should cover the last of the radios if the bottom edge of the viewport is at its bottom + checkbox.focus(); + + stickyPosition = getStickyGroupPosition(screenMock, { stickyEls: [inputForm], edge: 'top' }); + + // the bottom of the sticky element should be at the top of the checkbox + expect(screenMock.window.spies.window.scrollTo.mock.calls[0]).toEqual([0, checkbox.offsetTop - stickyPosition.height]); + + }); + + }); + describe("if mode is set to 'dialog' and multiple sticky elements share the same scroll area", () => { let radios; @@ -664,6 +743,62 @@ describe("Stick to top/bottom of window when scrolling", () => { }); + describe("if element is made sticky and another element underneath it is focused", () => { + + let checkbox; + let checkboxBottom; + + beforeEach(() => { + + const contentBottom = getScreenItemBottomPosition(content); + + content.insertAdjacentHTML('afterEnd', ''); + checkbox = document.querySelector('input[type=checkbox]'); + + screenMock.mockPositionAndDimension('checkbox', checkbox, { + offsetHeight: 40, // 10px smaller than the sticky + offsetWidth: 727, + offsetTop: contentBottom + }); + + checkboxBottom = getScreenItemBottomPosition(checkbox); + + // move the sticky over the checkbox. It's 50px high so this position will cause it to overlap. + screenMock.scrollTo((checkboxBottom - windowHeight) + 5); + + window.GOVUK.stickAtBottomWhenScrolling.init(); + + }); + + afterEach(() => { + + screenMock.window.spies.window.scrollTo.mockClear(); + + }); + + test("the window should scroll so the focused element is revealed", () => { + + // update inputForm position as DOM normally would + pageFooter.offsetTop = screenMock.window.bottom - pageFooter.offsetHeight; + + let stickyPosition = getStickyGroupPosition(screenMock, { stickyEls: [pageFooter], edge: 'bottom' }); + + // sticky position should overlap checkbox position + expect(stickyPosition.top).toBeLessThanOrEqual(checkbox.offsetTop); + expect(stickyPosition.bottom).toBeGreaterThanOrEqual(checkboxBottom); + + // the sticky element (page footer) is 50 high so should cover the last of the radios if the bottom edge of the viewport is at its bottom + checkbox.focus(); + + stickyPosition = getStickyGroupPosition(screenMock, { stickyEls: [pageFooter], edge: 'bottom' }); + + // the top of the sticky element should be at the bottom of the checkbox + expect(screenMock.window.spies.window.scrollTo.mock.calls[0]).toEqual([0, (checkboxBottom + pageFooter.offsetHeight) - windowHeight]); + + }); + + }); + describe("if mode is set to 'dialog' and multiple sticky elements have the same scroll area", () => { let radios; diff --git a/tests/javascripts/support/helpers.js b/tests/javascripts/support/helpers.js index 9e859e44a..5c1408fff 100644 --- a/tests/javascripts/support/helpers.js +++ b/tests/javascripts/support/helpers.js @@ -224,7 +224,8 @@ class WindowMock { width: window.innerWidth }; this.spies = { - document: {} + document: {}, + window: {} }; this._jest = jest; this._setSpies(); @@ -266,7 +267,7 @@ class WindowMock { const self = this; // JSDOM doesn't support .scrollTo - window.scrollTo = function () { + this.spies.window.scrollTo = this._jest.fn(function () { let y; // data sent as props in an object @@ -278,7 +279,9 @@ class WindowMock { self.scrollTo(y); - }; + }); + + window.scrollTo = this.spies.window.scrollTo; }