summaryrefslogtreecommitdiff
path: root/spec/frontend_integration
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-09-20 13:18:24 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-09-20 13:18:24 +0000
commit0653e08efd039a5905f3fa4f6e9cef9f5d2f799c (patch)
tree4dcc884cf6d81db44adae4aa99f8ec1233a41f55 /spec/frontend_integration
parent744144d28e3e7fddc117924fef88de5d9674fe4c (diff)
downloadgitlab-ce-0653e08efd039a5905f3fa4f6e9cef9f5d2f799c.tar.gz
Add latest changes from gitlab-org/gitlab@14-3-stable-eev14.3.0-rc42
Diffstat (limited to 'spec/frontend_integration')
-rw-r--r--spec/frontend_integration/README.md27
-rw-r--r--spec/frontend_integration/fly_out_nav_browser_spec.js363
-rw-r--r--spec/frontend_integration/lib/utils/browser_spec.js94
3 files changed, 484 insertions, 0 deletions
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 <path-to-integration-test>
+```
+
+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 <path-to-integration-test>
+```
+
+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 = '') =>
+ `<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();
+ });
+ });
+});
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(`
+ <div class="diff-file file-title-flex-parent">
+ blah blah blah
+ </div>
+ <div class="mr-version-controls">
+ more blah blah blah
+ </div>
+ `);
+
+ expect(commonUtils.contentTop()).toBe(0);
+ });
+
+ it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => {
+ jest.spyOn(breakpointInstance, 'isDesktop').mockReturnValue(true);
+
+ setFixtures(`
+ <div class="diff-file file-title-flex-parent">
+ blah blah blah
+ </div>
+ <div class="mr-version-controls">
+ more blah blah blah
+ </div>
+ `);
+
+ 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);
+ });
+ });
+});