diff options
Diffstat (limited to 'spec/frontend_integration/fly_out_nav_browser_spec.js')
-rw-r--r-- | spec/frontend_integration/fly_out_nav_browser_spec.js | 363 |
1 files changed, 363 insertions, 0 deletions
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 = '') => + `<div class="sidebar-sub-level-items" style="${styleProps}"></div>`; + + 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(); + }); + }); +}); |