From 0653e08efd039a5905f3fa4f6e9cef9f5d2f799c Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 20 Sep 2021 13:18:24 +0000 Subject: Add latest changes from gitlab-org/gitlab@14-3-stable-ee --- spec/frontend_integration/README.md | 27 ++ .../fly_out_nav_browser_spec.js | 363 +++++++++++++++++++++ .../frontend_integration/lib/utils/browser_spec.js | 94 ++++++ 3 files changed, 484 insertions(+) create mode 100644 spec/frontend_integration/fly_out_nav_browser_spec.js create mode 100644 spec/frontend_integration/lib/utils/browser_spec.js (limited to 'spec/frontend_integration') diff --git a/spec/frontend_integration/README.md b/spec/frontend_integration/README.md index 573a385d81e..377294fb19f 100644 --- a/spec/frontend_integration/README.md +++ b/spec/frontend_integration/README.md @@ -11,6 +11,33 @@ Frontend integration specs: As a result, they deserve their own special place. +## Run frontend integration tests locally + +The frontend integration specs are all about testing integration frontend bundles against a +mock backend. The mock backend is built using the fixtures and GraphQL schema. + +We can generate the necessary fixtures and GraphQL schema by running: + +```shell +bundle exec rake frontend:fixtures gitlab:graphql:schema:dump +``` + +Then we can use [Jest](https://jestjs.io/) to run the frontend integration tests: + +```shell +yarn jest:integration +``` + +If you'd like to run the frontend integration specs **without** setting up the fixtures first, then you +can set `GL_IGNORE_WARNINGS=1`: + +```shell +GL_IGNORE_WARNINGS=1 yarn jest:integration +``` + +The `jest-integration` job executes the frontend integration tests in our +CI/CD pipelines. + ## References - https://docs.gitlab.com/ee/development/testing_guide/testing_levels.html#frontend-integration-tests diff --git a/spec/frontend_integration/fly_out_nav_browser_spec.js b/spec/frontend_integration/fly_out_nav_browser_spec.js new file mode 100644 index 00000000000..ef2afa20528 --- /dev/null +++ b/spec/frontend_integration/fly_out_nav_browser_spec.js @@ -0,0 +1,363 @@ +import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; +import { SIDEBAR_COLLAPSED_CLASS } from '~/contextual_sidebar'; +import { + calculateTop, + showSubLevelItems, + canShowSubItems, + canShowActiveSubItems, + mouseEnterTopItems, + mouseLeaveTopItem, + getOpenMenu, + setOpenMenu, + mousePos, + getHideSubItemsInterval, + documentMouseMove, + getHeaderHeight, + setSidebar, + subItemsMouseLeave, +} from '~/fly_out_nav'; + +describe('Fly out sidebar navigation', () => { + let el; + let breakpointSize = 'lg'; + + const OLD_SIDEBAR_WIDTH = 200; + const CONTAINER_INITIAL_BOUNDING_RECT = { + x: 8, + y: 8, + width: 769, + height: 0, + top: 8, + right: 777, + bottom: 8, + left: 8, + }; + const SUB_ITEMS_INITIAL_BOUNDING_RECT = { + x: 148, + y: 8, + width: 0, + height: 150, + top: 8, + right: 148, + bottom: 158, + left: 148, + }; + const mockBoundingClientRect = (elem, rect) => { + jest.spyOn(elem, 'getBoundingClientRect').mockReturnValue(rect); + }; + + const findSubItems = () => document.querySelector('.sidebar-sub-level-items'); + const mockBoundingRects = () => { + const subItems = findSubItems(); + mockBoundingClientRect(el, CONTAINER_INITIAL_BOUNDING_RECT); + mockBoundingClientRect(subItems, SUB_ITEMS_INITIAL_BOUNDING_RECT); + }; + const mockSidebarFragment = (styleProps = '') => + ``; + + beforeEach(() => { + el = document.createElement('div'); + el.style.position = 'relative'; + document.body.appendChild(el); + + jest.spyOn(GlBreakpointInstance, 'getBreakpointSize').mockImplementation(() => breakpointSize); + }); + + afterEach(() => { + document.body.innerHTML = ''; + breakpointSize = 'lg'; + mousePos.length = 0; + + setSidebar(null); + }); + + describe('calculateTop', () => { + it('returns boundingRect top', () => { + const boundingRect = { + top: 100, + height: 100, + }; + + expect(calculateTop(boundingRect, 100)).toBe(100); + }); + }); + + describe('getHideSubItemsInterval', () => { + beforeEach(() => { + el.innerHTML = mockSidebarFragment('position: fixed; top: 0; left: 100px; height: 150px;'); + mockBoundingRects(); + }); + + it('returns 0 if currentOpenMenu is nil', () => { + setOpenMenu(null); + expect(getHideSubItemsInterval()).toBe(0); + }); + + it('returns 0 if mousePos is empty', () => { + expect(getHideSubItemsInterval()).toBe(0); + }); + + it('returns 0 when mouse above sub-items', () => { + showSubLevelItems(el); + documentMouseMove({ + clientX: el.getBoundingClientRect().left, + clientY: el.getBoundingClientRect().top, + }); + documentMouseMove({ + clientX: el.getBoundingClientRect().left, + clientY: el.getBoundingClientRect().top - 50, + }); + + expect(getHideSubItemsInterval()).toBe(0); + }); + + it('returns 0 when mouse is below sub-items', () => { + const subItems = findSubItems(); + + showSubLevelItems(el); + documentMouseMove({ + clientX: el.getBoundingClientRect().left, + clientY: el.getBoundingClientRect().top, + }); + documentMouseMove({ + clientX: el.getBoundingClientRect().left, + clientY: el.getBoundingClientRect().top - subItems.getBoundingClientRect().height + 50, + }); + + expect(getHideSubItemsInterval()).toBe(0); + }); + + it('returns 300 when mouse is moved towards sub-items', () => { + documentMouseMove({ + clientX: el.getBoundingClientRect().left, + clientY: el.getBoundingClientRect().top, + }); + + showSubLevelItems(el); + documentMouseMove({ + clientX: el.getBoundingClientRect().left + 20, + clientY: el.getBoundingClientRect().top + 10, + }); + + expect(getHideSubItemsInterval()).toBe(300); + }); + }); + + describe('mouseLeaveTopItem', () => { + beforeEach(() => { + jest.spyOn(el.classList, 'remove'); + }); + + it('removes is-over class if currentOpenMenu is null', () => { + setOpenMenu(null); + + mouseLeaveTopItem(el); + + expect(el.classList.remove).toHaveBeenCalledWith('is-over'); + }); + + it('removes is-over class if currentOpenMenu is null & there are sub-items', () => { + setOpenMenu(null); + el.innerHTML = mockSidebarFragment('position: absolute'); + + mouseLeaveTopItem(el); + + expect(el.classList.remove).toHaveBeenCalledWith('is-over'); + }); + + it('does not remove is-over class if currentOpenMenu is the passed in sub-items', () => { + setOpenMenu(null); + el.innerHTML = mockSidebarFragment('position: absolute'); + + setOpenMenu(findSubItems()); + mouseLeaveTopItem(el); + + expect(el.classList.remove).not.toHaveBeenCalled(); + }); + }); + + describe('mouseEnterTopItems', () => { + beforeEach(() => { + el.innerHTML = mockSidebarFragment( + `position: absolute; top: 0; left: 100px; height: ${OLD_SIDEBAR_WIDTH}px;`, + ); + mockBoundingRects(); + }); + + it('shows sub-items after 0ms if no menu is open', (done) => { + const subItems = findSubItems(); + mouseEnterTopItems(el); + + expect(getHideSubItemsInterval()).toBe(0); + + setTimeout(() => { + expect(subItems.style.display).toBe('block'); + done(); + }); + }); + + it('shows sub-items after 300ms if a menu is currently open', (done) => { + const subItems = findSubItems(); + + documentMouseMove({ + clientX: el.getBoundingClientRect().left, + clientY: el.getBoundingClientRect().top, + }); + + setOpenMenu(subItems); + + documentMouseMove({ + clientX: el.getBoundingClientRect().left + 20, + clientY: el.getBoundingClientRect().top + 10, + }); + + mouseEnterTopItems(el, 0); + + setTimeout(() => { + expect(subItems.style.display).toBe('block'); + + done(); + }); + }); + }); + + describe('showSubLevelItems', () => { + beforeEach(() => { + el.innerHTML = mockSidebarFragment('position: absolute'); + }); + + it('adds is-over class to el', () => { + jest.spyOn(el.classList, 'add'); + + showSubLevelItems(el); + + expect(el.classList.add).toHaveBeenCalledWith('is-over'); + }); + + it('does not show sub-items on mobile', () => { + breakpointSize = 'xs'; + + showSubLevelItems(el); + + expect(findSubItems().style.display).not.toBe('block'); + }); + + it('shows sub-items', () => { + showSubLevelItems(el); + + expect(findSubItems().style.display).toBe('block'); + }); + + it('shows collapsed only sub-items if icon only sidebar', () => { + const subItems = findSubItems(); + const sidebar = document.createElement('div'); + sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS); + subItems.classList.add('is-fly-out-only'); + + setSidebar(sidebar); + + showSubLevelItems(el); + + expect(findSubItems().style.display).toBe('block'); + }); + + it('does not show collapsed only sub-items if icon only sidebar', () => { + const subItems = findSubItems(); + subItems.classList.add('is-fly-out-only'); + + showSubLevelItems(el); + + expect(subItems.style.display).not.toBe('block'); + }); + + it('sets transform of sub-items', () => { + const sidebar = document.createElement('div'); + const subItems = findSubItems(); + + sidebar.style.width = `${OLD_SIDEBAR_WIDTH}px`; + + document.body.appendChild(sidebar); + + setSidebar(sidebar); + showSubLevelItems(el); + + expect(subItems.style.transform).toBe( + `translate3d(${OLD_SIDEBAR_WIDTH}px, ${ + Math.floor(el.getBoundingClientRect().top) - getHeaderHeight() + }px, 0)`, + ); + }); + + it('sets is-above when element is above', () => { + const subItems = findSubItems(); + mockBoundingRects(); + + subItems.style.height = `${window.innerHeight + el.offsetHeight}px`; + el.style.top = `${window.innerHeight - el.offsetHeight}px`; + + jest.spyOn(subItems.classList, 'add'); + + showSubLevelItems(el); + + expect(subItems.classList.add).toHaveBeenCalledWith('is-above'); + }); + }); + + describe('canShowSubItems', () => { + it('returns true if on desktop size', () => { + expect(canShowSubItems()).toBeTruthy(); + }); + + it('returns false if on mobile size', () => { + breakpointSize = 'xs'; + + expect(canShowSubItems()).toBeFalsy(); + }); + }); + + describe('canShowActiveSubItems', () => { + it('returns true by default', () => { + expect(canShowActiveSubItems(el)).toBeTruthy(); + }); + + it('returns false when active & expanded sidebar', () => { + const sidebar = document.createElement('div'); + el.classList.add('active'); + + setSidebar(sidebar); + + expect(canShowActiveSubItems(el)).toBeFalsy(); + }); + + it('returns true when active & collapsed sidebar', () => { + const sidebar = document.createElement('div'); + sidebar.classList.add(SIDEBAR_COLLAPSED_CLASS); + el.classList.add('active'); + + setSidebar(sidebar); + + expect(canShowActiveSubItems(el)).toBeTruthy(); + }); + }); + + describe('subItemsMouseLeave', () => { + beforeEach(() => { + el.innerHTML = mockSidebarFragment('position: absolute'); + + setOpenMenu(findSubItems()); + }); + + it('hides subMenu if element is not hovered', () => { + subItemsMouseLeave(el); + + expect(getOpenMenu()).toBeNull(); + }); + + it('does not hide subMenu if element is hovered', () => { + el.classList.add('is-over'); + subItemsMouseLeave(el); + + expect(getOpenMenu()).not.toBeNull(); + }); + }); +}); diff --git a/spec/frontend_integration/lib/utils/browser_spec.js b/spec/frontend_integration/lib/utils/browser_spec.js new file mode 100644 index 00000000000..6c72e29076d --- /dev/null +++ b/spec/frontend_integration/lib/utils/browser_spec.js @@ -0,0 +1,94 @@ +import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/utils'; +import * as commonUtils from '~/lib/utils/common_utils'; + +describe('common_utils browser specific specs', () => { + const mockOffsetHeight = (elem, offsetHeight) => { + Object.defineProperty(elem, 'offsetHeight', { value: offsetHeight }); + }; + + const mockBoundingClientRect = (elem, rect) => { + jest.spyOn(elem, 'getBoundingClientRect').mockReturnValue(rect); + }; + + describe('contentTop', () => { + it('does not add height for fileTitle or compareVersionsHeader if screen is too small', () => { + jest.spyOn(breakpointInstance, 'isDesktop').mockReturnValue(false); + + setFixtures(` +
+ blah blah blah +
+
+ more blah blah blah +
+ `); + + expect(commonUtils.contentTop()).toBe(0); + }); + + it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => { + jest.spyOn(breakpointInstance, 'isDesktop').mockReturnValue(true); + + setFixtures(` +
+ blah blah blah +
+
+ more blah blah blah +
+ `); + + mockOffsetHeight(document.querySelector('.diff-file'), 100); + mockOffsetHeight(document.querySelector('.mr-version-controls'), 18); + expect(commonUtils.contentTop()).toBe(18); + }); + }); + + describe('isInViewport', () => { + let el; + + beforeEach(() => { + el = document.createElement('div'); + }); + + afterEach(() => { + document.body.removeChild(el); + }); + + it('returns true when provided `el` is in viewport', () => { + el.setAttribute('style', `position: absolute; right: ${window.innerWidth + 0.2};`); + mockBoundingClientRect(el, { + x: 8, + y: 8, + width: 0, + height: 0, + top: 8, + right: 8, + bottom: 8, + left: 8, + }); + + document.body.appendChild(el); + + expect(commonUtils.isInViewport(el)).toBe(true); + }); + + it('returns false when provided `el` is not in viewport', () => { + el.setAttribute('style', 'position: absolute; top: -1000px; left: -1000px;'); + mockBoundingClientRect(el, { + x: -1000, + y: -1000, + width: 0, + height: 0, + top: -1000, + right: -1000, + bottom: -1000, + left: -1000, + }); + + document.body.appendChild(el); + + expect(commonUtils.isInViewport(el)).toBe(false); + }); + }); +}); -- cgit v1.2.3