Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-05 00:09:14 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-05 00:09:14 +0300
commitceb5cdd5c3293d290c1ae42a86175ac4f51b4408 (patch)
tree340743b21d708a6c06c5d402c0b697d8b89d9b06 /spec/frontend
parentbd979acf95124119d41f75d34cab231229f4dd81 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/__helpers__/assert_props.js33
-rw-r--r--spec/frontend/jobs/components/table/job_table_app_spec.js33
-rw-r--r--spec/frontend/lib/utils/css_utils_spec.js22
-rw-r--r--spec/frontend/pages/admin/jobs/components/table/admin_job_table_app_spec.js21
-rw-r--r--spec/frontend/pages/admin/jobs/components/table/cells/runner_cell_spec.js64
-rw-r--r--spec/frontend/super_sidebar/components/sidebar_peek_behavior_spec.js207
-rw-r--r--spec/frontend/super_sidebar/components/super_sidebar_spec.js135
-rw-r--r--spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js24
-rw-r--r--spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js17
9 files changed, 448 insertions, 108 deletions
diff --git a/spec/frontend/__helpers__/assert_props.js b/spec/frontend/__helpers__/assert_props.js
index 3e372454bf5..9935719580a 100644
--- a/spec/frontend/__helpers__/assert_props.js
+++ b/spec/frontend/__helpers__/assert_props.js
@@ -1,14 +1,30 @@
import { mount } from '@vue/test-utils';
import { ErrorWithStack } from 'jest-util';
-export function assertProps(Component, props, extraMountArgs = {}) {
- const originalConsoleError = global.console.error;
- global.console.error = function error(...args) {
- throw new ErrorWithStack(
- `Unexpected call of console.error() with:\n\n${args.join(', ')}`,
- this.error,
- );
+function installConsoleHandler(method) {
+ const originalHandler = global.console[method];
+
+ global.console[method] = function throwableHandler(...args) {
+ if (args[0]?.includes('Invalid prop') || args[0]?.includes('Missing required prop')) {
+ throw new ErrorWithStack(
+ `Unexpected call of console.${method}() with:\n\n${args.join(', ')}`,
+ this[method],
+ );
+ }
+
+ originalHandler.apply(this, args);
+ };
+
+ return function restore() {
+ global.console[method] = originalHandler;
};
+}
+
+export function assertProps(Component, props, extraMountArgs = {}) {
+ const [restoreError, restoreWarn] = [
+ installConsoleHandler('error'),
+ installConsoleHandler('warn'),
+ ];
const ComponentWithoutRenderFn = {
...Component,
render() {
@@ -19,6 +35,7 @@ export function assertProps(Component, props, extraMountArgs = {}) {
try {
mount(ComponentWithoutRenderFn, { propsData: props, ...extraMountArgs });
} finally {
- global.console.error = originalConsoleError;
+ restoreError();
+ restoreWarn();
}
}
diff --git a/spec/frontend/jobs/components/table/job_table_app_spec.js b/spec/frontend/jobs/components/table/job_table_app_spec.js
index 30f674f5ba7..0e59e9ab5b6 100644
--- a/spec/frontend/jobs/components/table/job_table_app_spec.js
+++ b/spec/frontend/jobs/components/table/job_table_app_spec.js
@@ -127,6 +127,25 @@ describe('Job table app', () => {
expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(0);
});
+ it('should refetch jobs count query when the amount jobs and count do not match', async () => {
+ jest.spyOn(wrapper.vm.$apollo.queries.jobsCount, 'refetch').mockImplementation(jest.fn());
+
+ expect(wrapper.vm.$apollo.queries.jobsCount.refetch).toHaveBeenCalledTimes(0);
+
+ // after applying filter a new count is fetched
+ findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]);
+
+ expect(wrapper.vm.$apollo.queries.jobsCount.refetch).toHaveBeenCalledTimes(1);
+
+ // tab is switched to `finished`, no count
+ await findTabs().vm.$emit('fetchJobsByStatus', ['FAILED', 'SUCCESS', 'CANCELED']);
+
+ // tab is switched back to `all`, the old filter count has to be overwritten with new count
+ await findTabs().vm.$emit('fetchJobsByStatus', null);
+
+ expect(wrapper.vm.$apollo.queries.jobsCount.refetch).toHaveBeenCalledTimes(2);
+ });
+
describe('when infinite scrolling is triggered', () => {
it('does not display a skeleton loader', () => {
triggerInfiniteScroll();
@@ -251,6 +270,18 @@ describe('Job table app', () => {
expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(1);
});
+ it('refetches jobs count query when filtering', async () => {
+ createComponent();
+
+ jest.spyOn(wrapper.vm.$apollo.queries.jobsCount, 'refetch').mockImplementation(jest.fn());
+
+ expect(wrapper.vm.$apollo.queries.jobsCount.refetch).toHaveBeenCalledTimes(0);
+
+ await findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]);
+
+ expect(wrapper.vm.$apollo.queries.jobsCount.refetch).toHaveBeenCalledTimes(1);
+ });
+
it('shows raw text warning when user inputs raw text', async () => {
const expectedWarning = {
message: s__(
@@ -262,11 +293,13 @@ describe('Job table app', () => {
createComponent();
jest.spyOn(wrapper.vm.$apollo.queries.jobs, 'refetch').mockImplementation(jest.fn());
+ jest.spyOn(wrapper.vm.$apollo.queries.jobsCount, 'refetch').mockImplementation(jest.fn());
await findFilteredSearch().vm.$emit('filterJobsBySearch', ['raw text']);
expect(createAlert).toHaveBeenCalledWith(expectedWarning);
expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(0);
+ expect(wrapper.vm.$apollo.queries.jobsCount.refetch).toHaveBeenCalledTimes(0);
});
it('updates URL query string when filtering jobs by status', async () => {
diff --git a/spec/frontend/lib/utils/css_utils_spec.js b/spec/frontend/lib/utils/css_utils_spec.js
new file mode 100644
index 00000000000..dcaeb075c93
--- /dev/null
+++ b/spec/frontend/lib/utils/css_utils_spec.js
@@ -0,0 +1,22 @@
+import { getCssClassDimensions } from '~/lib/utils/css_utils';
+
+describe('getCssClassDimensions', () => {
+ const mockDimensions = { width: 1, height: 2 };
+ let actual;
+
+ beforeEach(() => {
+ jest.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue(mockDimensions);
+ actual = getCssClassDimensions('foo bar');
+ });
+
+ it('returns the measured width and height', () => {
+ expect(actual).toEqual(mockDimensions);
+ });
+
+ it('measures an element with the given classes', () => {
+ expect(Element.prototype.getBoundingClientRect).toHaveBeenCalledTimes(1);
+
+ const [tempElement] = Element.prototype.getBoundingClientRect.mock.contexts;
+ expect([...tempElement.classList]).toEqual(['foo', 'bar']);
+ });
+});
diff --git a/spec/frontend/pages/admin/jobs/components/table/admin_job_table_app_spec.js b/spec/frontend/pages/admin/jobs/components/table/admin_job_table_app_spec.js
index cc6f1c27142..dad7308ac0a 100644
--- a/spec/frontend/pages/admin/jobs/components/table/admin_job_table_app_spec.js
+++ b/spec/frontend/pages/admin/jobs/components/table/admin_job_table_app_spec.js
@@ -139,6 +139,25 @@ describe('Job table app', () => {
expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(0);
});
+ it('should refetch jobs count query when the amount jobs and count do not match', async () => {
+ jest.spyOn(wrapper.vm.$apollo.queries.jobsCount, 'refetch').mockImplementation(jest.fn());
+
+ expect(wrapper.vm.$apollo.queries.jobsCount.refetch).toHaveBeenCalledTimes(0);
+
+ // after applying filter a new count is fetched
+ findFilteredSearch().vm.$emit('filterJobsBySearch', [mockFailedSearchToken]);
+
+ expect(wrapper.vm.$apollo.queries.jobsCount.refetch).toHaveBeenCalledTimes(1);
+
+ // tab is switched to `finished`, no count
+ await findTabs().vm.$emit('fetchJobsByStatus', ['FAILED', 'SUCCESS', 'CANCELED']);
+
+ // tab is switched back to `all`, the old filter count has to be overwritten with new count
+ await findTabs().vm.$emit('fetchJobsByStatus', null);
+
+ expect(wrapper.vm.$apollo.queries.jobsCount.refetch).toHaveBeenCalledTimes(2);
+ });
+
describe('when infinite scrolling is triggered', () => {
it('does not display a skeleton loader', () => {
triggerInfiniteScroll();
@@ -324,11 +343,13 @@ describe('Job table app', () => {
createComponent();
jest.spyOn(wrapper.vm.$apollo.queries.jobs, 'refetch').mockImplementation(jest.fn());
+ jest.spyOn(wrapper.vm.$apollo.queries.jobsCount, 'refetch').mockImplementation(jest.fn());
await findFilteredSearch().vm.$emit('filterJobsBySearch', ['raw text']);
expect(createAlert).toHaveBeenCalledWith(expectedWarning);
expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(0);
+ expect(wrapper.vm.$apollo.queries.jobsCount.refetch).toHaveBeenCalledTimes(0);
});
it('updates URL query string when filtering jobs by status', async () => {
diff --git a/spec/frontend/pages/admin/jobs/components/table/cells/runner_cell_spec.js b/spec/frontend/pages/admin/jobs/components/table/cells/runner_cell_spec.js
new file mode 100644
index 00000000000..2f76ad66dd5
--- /dev/null
+++ b/spec/frontend/pages/admin/jobs/components/table/cells/runner_cell_spec.js
@@ -0,0 +1,64 @@
+import { GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import RunnerCell from '~/pages/admin/jobs/components/table/cells/runner_cell.vue';
+import { RUNNER_EMPTY_TEXT } from '~/pages/admin/jobs/components/constants';
+import { allRunnersData } from '../../../../../../ci/runner/mock_data';
+
+const mockRunner = allRunnersData.data.runners.nodes[0];
+
+const mockJobWithRunner = {
+ id: 'gid://gitlab/Ci::Build/2264',
+ runner: mockRunner,
+};
+
+const mockJobWithoutRunner = {
+ id: 'gid://gitlab/Ci::Build/2265',
+};
+
+describe('Runner Cell', () => {
+ let wrapper;
+
+ const findRunnerLink = () => wrapper.findComponent(GlLink);
+ const findEmptyRunner = () => wrapper.find('[data-testid="empty-runner-text"]');
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(RunnerCell, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ describe('Runner Link', () => {
+ describe('Job with runner', () => {
+ beforeEach(() => {
+ createComponent({ job: mockJobWithRunner });
+ });
+
+ it('shows and links to the runner', () => {
+ expect(findRunnerLink().exists()).toBe(true);
+ expect(findRunnerLink().text()).toBe(mockRunner.description);
+ expect(findRunnerLink().attributes('href')).toBe(mockRunner.adminUrl);
+ });
+
+ it('hides the empty runner text', () => {
+ expect(findEmptyRunner().exists()).toBe(false);
+ });
+ });
+
+ describe('Job without runner', () => {
+ beforeEach(() => {
+ createComponent({ job: mockJobWithoutRunner });
+ });
+
+ it('shows default `empty` text', () => {
+ expect(findEmptyRunner().exists()).toBe(true);
+ expect(findEmptyRunner().text()).toBe(RUNNER_EMPTY_TEXT);
+ });
+
+ it('hides the runner link', () => {
+ expect(findRunnerLink().exists()).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/sidebar_peek_behavior_spec.js b/spec/frontend/super_sidebar/components/sidebar_peek_behavior_spec.js
new file mode 100644
index 00000000000..047dc9a6599
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/sidebar_peek_behavior_spec.js
@@ -0,0 +1,207 @@
+import { mount } from '@vue/test-utils';
+import {
+ SUPER_SIDEBAR_PEEK_OPEN_DELAY,
+ SUPER_SIDEBAR_PEEK_CLOSE_DELAY,
+} from '~/super_sidebar/constants';
+import SidebarPeek, {
+ STATE_CLOSED,
+ STATE_WILL_OPEN,
+ STATE_OPEN,
+ STATE_WILL_CLOSE,
+} from '~/super_sidebar/components/sidebar_peek_behavior.vue';
+
+// These are measured at runtime in the browser, but statically defined here
+// since Jest does not do layout/styling.
+const X_NEAR_WINDOW_EDGE = 5;
+const X_SIDEBAR_EDGE = 10;
+const X_AWAY_FROM_SIDEBAR = 20;
+
+jest.mock('~/lib/utils/css_utils', () => ({
+ getCssClassDimensions: (className) => {
+ if (className === 'gl-w-3') {
+ return { width: X_NEAR_WINDOW_EDGE };
+ }
+
+ if (className === 'super-sidebar') {
+ return { width: X_SIDEBAR_EDGE };
+ }
+
+ throw new Error(`No mock for CSS class ${className}`);
+ },
+}));
+
+describe('SidebarPeek component', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = mount(SidebarPeek);
+ };
+
+ const moveMouse = (clientX) => {
+ const event = new MouseEvent('mousemove', {
+ clientX,
+ });
+
+ document.dispatchEvent(event);
+ };
+
+ const moveMouseOutOfDocument = () => {
+ const event = new MouseEvent('mouseleave');
+ document.documentElement.dispatchEvent(event);
+ };
+
+ const lastNChangeEvents = (n = 1) => wrapper.emitted('change').slice(-n).flat();
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('begins in the closed state', () => {
+ expect(lastNChangeEvents(Infinity)).toEqual([STATE_CLOSED]);
+ });
+
+ it('does not emit duplicate events in a region', () => {
+ moveMouse(0);
+ moveMouse(1);
+ moveMouse(2);
+
+ expect(lastNChangeEvents(Infinity)).toEqual([STATE_CLOSED, STATE_WILL_OPEN]);
+ });
+
+ it('transitions to will-open when in peek region', () => {
+ moveMouse(X_NEAR_WINDOW_EDGE);
+
+ expect(lastNChangeEvents(1)).toEqual([STATE_CLOSED]);
+
+ moveMouse(X_NEAR_WINDOW_EDGE - 1);
+
+ expect(lastNChangeEvents(1)).toEqual([STATE_WILL_OPEN]);
+ });
+
+ it('transitions will-open -> open after delay', () => {
+ moveMouse(0);
+ jest.advanceTimersByTime(SUPER_SIDEBAR_PEEK_OPEN_DELAY - 1);
+
+ expect(lastNChangeEvents(1)).toEqual([STATE_WILL_OPEN]);
+
+ jest.advanceTimersByTime(1);
+
+ expect(lastNChangeEvents(2)).toEqual([STATE_WILL_OPEN, STATE_OPEN]);
+ });
+
+ it('cancels transition will-open -> open if mouse out of peek region', () => {
+ moveMouse(0);
+ jest.advanceTimersByTime(SUPER_SIDEBAR_PEEK_OPEN_DELAY - 1);
+
+ moveMouse(X_NEAR_WINDOW_EDGE);
+
+ jest.runOnlyPendingTimers();
+
+ expect(lastNChangeEvents(3)).toEqual([STATE_CLOSED, STATE_WILL_OPEN, STATE_CLOSED]);
+ });
+
+ it('transitions open -> will-close if mouse out of sidebar region', () => {
+ moveMouse(0);
+ jest.runOnlyPendingTimers();
+
+ moveMouse(X_SIDEBAR_EDGE - 1);
+
+ expect(lastNChangeEvents(1)).toEqual([STATE_OPEN]);
+
+ moveMouse(X_SIDEBAR_EDGE);
+
+ expect(lastNChangeEvents(2)).toEqual([STATE_OPEN, STATE_WILL_CLOSE]);
+ });
+
+ it('transitions will-close -> closed after delay', () => {
+ moveMouse(0);
+ jest.runOnlyPendingTimers();
+
+ moveMouse(X_SIDEBAR_EDGE);
+ jest.advanceTimersByTime(SUPER_SIDEBAR_PEEK_CLOSE_DELAY - 1);
+
+ expect(lastNChangeEvents(1)).toEqual([STATE_WILL_CLOSE]);
+
+ jest.advanceTimersByTime(1);
+
+ expect(lastNChangeEvents(2)).toEqual([STATE_WILL_CLOSE, STATE_CLOSED]);
+ });
+
+ it('cancels transition will-close -> close if mouse move in sidebar region', () => {
+ moveMouse(0);
+ jest.runOnlyPendingTimers();
+
+ moveMouse(X_SIDEBAR_EDGE);
+ jest.advanceTimersByTime(SUPER_SIDEBAR_PEEK_CLOSE_DELAY - 1);
+
+ expect(lastNChangeEvents(1)).toEqual([STATE_WILL_CLOSE]);
+
+ moveMouse(X_SIDEBAR_EDGE - 1);
+ jest.runOnlyPendingTimers();
+
+ expect(lastNChangeEvents(3)).toEqual([STATE_OPEN, STATE_WILL_CLOSE, STATE_OPEN]);
+ });
+
+ it('immediately transitions open -> closed if mouse moves far away', () => {
+ moveMouse(0);
+ jest.runOnlyPendingTimers();
+
+ moveMouse(X_AWAY_FROM_SIDEBAR);
+
+ expect(lastNChangeEvents(2)).toEqual([STATE_OPEN, STATE_CLOSED]);
+ });
+
+ it('immediately transitions will-close -> closed if mouse moves far away', () => {
+ moveMouse(0);
+ jest.runOnlyPendingTimers();
+
+ moveMouse(X_AWAY_FROM_SIDEBAR - 1);
+ moveMouse(X_AWAY_FROM_SIDEBAR);
+
+ expect(lastNChangeEvents(2)).toEqual([STATE_WILL_CLOSE, STATE_CLOSED]);
+ });
+
+ it('cleans up its mousemove listener before destroy', () => {
+ moveMouse(0);
+ jest.runOnlyPendingTimers();
+
+ wrapper.destroy();
+ moveMouse(X_AWAY_FROM_SIDEBAR);
+
+ expect(lastNChangeEvents(1)).toEqual([STATE_OPEN]);
+ });
+
+ it('cleans up its timers before destroy', () => {
+ moveMouse(0);
+
+ wrapper.destroy();
+ jest.runOnlyPendingTimers();
+
+ expect(lastNChangeEvents(1)).toEqual([STATE_WILL_OPEN]);
+ });
+
+ it('transitions will-open -> closed if cursor leaves document', () => {
+ moveMouse(0);
+ moveMouseOutOfDocument();
+
+ expect(lastNChangeEvents(2)).toEqual([STATE_WILL_OPEN, STATE_CLOSED]);
+ });
+
+ it('transitions open -> will-close if cursor leaves document', () => {
+ moveMouse(0);
+ jest.runOnlyPendingTimers();
+ moveMouseOutOfDocument();
+
+ expect(lastNChangeEvents(2)).toEqual([STATE_OPEN, STATE_WILL_CLOSE]);
+ });
+
+ it('cleans up document mouseleave listener before destroy', () => {
+ moveMouse(0);
+
+ wrapper.destroy();
+
+ moveMouseOutOfDocument();
+
+ expect(lastNChangeEvents(1)).not.toEqual([STATE_CLOSED]);
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
index c3921e0a939..b76c637caf4 100644
--- a/spec/frontend/super_sidebar/components/super_sidebar_spec.js
+++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
@@ -4,13 +4,16 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import SuperSidebar from '~/super_sidebar/components/super_sidebar.vue';
import HelpCenter from '~/super_sidebar/components/help_center.vue';
import UserBar from '~/super_sidebar/components/user_bar.vue';
+import SidebarPeekBehavior, {
+ STATE_CLOSED,
+ STATE_WILL_OPEN,
+ STATE_OPEN,
+ STATE_WILL_CLOSE,
+} from '~/super_sidebar/components/sidebar_peek_behavior.vue';
import SidebarPortalTarget from '~/super_sidebar/components/sidebar_portal_target.vue';
import ContextSwitcher from '~/super_sidebar/components/context_switcher.vue';
import SidebarMenu from '~/super_sidebar/components/sidebar_menu.vue';
-import {
- SUPER_SIDEBAR_PEEK_OPEN_DELAY,
- SUPER_SIDEBAR_PEEK_CLOSE_DELAY,
-} from '~/super_sidebar/constants';
+import { sidebarState } from '~/super_sidebar/constants';
import {
toggleSuperSidebarCollapsed,
isCollapsed,
@@ -18,6 +21,8 @@ import {
import { stubComponent } from 'helpers/stub_component';
import { sidebarData as mockSidebarData } from '../mock_data';
+const initialSidebarState = { ...sidebarState };
+
jest.mock('~/super_sidebar/super_sidebar_collapsed_state_manager');
const closeContextSwitcherMock = jest.fn();
@@ -28,16 +33,19 @@ const TrialStatusPopoverStub = {
template: `<div data-testid="${trialStatusPopoverStubTestId}" />`,
};
+const peekClass = 'super-sidebar-peek';
+const peekHintClass = 'super-sidebar-peek-hint';
+
describe('SuperSidebar component', () => {
let wrapper;
const findSidebar = () => wrapper.findByTestId('super-sidebar');
- const findHoverArea = () => wrapper.findByTestId('super-sidebar-hover-area');
const findUserBar = () => wrapper.findComponent(UserBar);
const findContextSwitcher = () => wrapper.findComponent(ContextSwitcher);
const findNavContainer = () => wrapper.findByTestId('nav-container');
const findHelpCenter = () => wrapper.findComponent(HelpCenter);
const findSidebarPortalTarget = () => wrapper.findComponent(SidebarPortalTarget);
+ const findPeekBehavior = () => wrapper.findComponent(SidebarPeekBehavior);
const findTrialStatusWidget = () => wrapper.findByTestId(trialStatusWidgetStubTestId);
const findTrialStatusPopover = () => wrapper.findByTestId(trialStatusPopoverStubTestId);
const findSidebarMenu = () => wrapper.findComponent(SidebarMenu);
@@ -45,14 +53,11 @@ describe('SuperSidebar component', () => {
const createWrapper = ({
provide = {},
sidebarData = mockSidebarData,
- sidebarState = {},
+ sidebarState: state = {},
} = {}) => {
+ Object.assign(sidebarState, state);
+
wrapper = shallowMountExtended(SuperSidebar, {
- data() {
- return {
- ...sidebarState,
- };
- },
provide: {
showTrialStatusWidget: false,
...provide,
@@ -70,6 +75,10 @@ describe('SuperSidebar component', () => {
});
};
+ beforeEach(() => {
+ Object.assign(sidebarState, initialSidebarState);
+ });
+
describe('default', () => {
it('adds inert attribute when collapsed', () => {
createWrapper({ sidebarState: { isCollapsed: true } });
@@ -154,12 +163,18 @@ describe('SuperSidebar component', () => {
expect(findTrialStatusWidget().exists()).toBe(false);
expect(findTrialStatusPopover().exists()).toBe(false);
});
+
+ it('does not have peek behavior', () => {
+ createWrapper();
+
+ expect(findPeekBehavior().exists()).toBe(false);
+ });
});
describe('on collapse', () => {
beforeEach(() => {
createWrapper();
- wrapper.vm.isCollapsed = true;
+ sidebarState.isCollapsed = true;
});
it('closes the context switcher', () => {
@@ -167,91 +182,39 @@ describe('SuperSidebar component', () => {
});
});
- describe('when peeking on hover', () => {
- const peekClass = 'super-sidebar-peek';
-
- it('updates inert attribute and peek class', async () => {
- createWrapper({
- provide: { glFeatures: { superSidebarPeek: true } },
- sidebarState: { isCollapsed: true },
- });
+ describe('peek behavior', () => {
+ it(`initially makes sidebar inert and peekable (${STATE_CLOSED})`, () => {
+ createWrapper({ sidebarState: { isCollapsed: true, isPeekable: true } });
- findHoverArea().trigger('mouseenter');
-
- jest.advanceTimersByTime(SUPER_SIDEBAR_PEEK_OPEN_DELAY - 1);
- await nextTick();
-
- // Not quite enough time has elapsed yet for sidebar to open
- expect(findSidebar().classes()).not.toContain(peekClass);
expect(findSidebar().attributes('inert')).toBe('inert');
+ expect(findSidebar().classes()).not.toContain(peekHintClass);
+ expect(findSidebar().classes()).not.toContain(peekClass);
+ });
- jest.advanceTimersByTime(1);
- await nextTick();
-
- // Exactly enough time has elapsed to open
- expect(findSidebar().classes()).toContain(peekClass);
- expect(findSidebar().attributes('inert')).toBe(undefined);
-
- // Important: assume the cursor enters the sidebar
- findSidebar().trigger('mouseenter');
-
- jest.runAllTimers();
- await nextTick();
-
- // Sidebar remains peeked open indefinitely without a mouseleave
- expect(findSidebar().classes()).toContain(peekClass);
- expect(findSidebar().attributes('inert')).toBe(undefined);
-
- findSidebar().trigger('mouseleave');
-
- jest.advanceTimersByTime(SUPER_SIDEBAR_PEEK_CLOSE_DELAY - 1);
- await nextTick();
-
- // Not quite enough time has elapsed yet for sidebar to hide
- expect(findSidebar().classes()).toContain(peekClass);
- expect(findSidebar().attributes('inert')).toBe(undefined);
+ it(`makes sidebar inert and shows peek hint when peek state is ${STATE_WILL_OPEN}`, async () => {
+ createWrapper({ sidebarState: { isCollapsed: true, isPeekable: true } });
- jest.advanceTimersByTime(1);
+ findPeekBehavior().vm.$emit('change', STATE_WILL_OPEN);
await nextTick();
- // Exactly enough time has elapsed for sidebar to hide
- expect(findSidebar().classes()).not.toContain('super-sidebar-peek');
expect(findSidebar().attributes('inert')).toBe('inert');
+ expect(findSidebar().classes()).toContain(peekHintClass);
+ expect(findSidebar().classes()).not.toContain(peekClass);
});
- it('eventually closes the sidebar if cursor never enters sidebar', async () => {
- createWrapper({
- provide: { glFeatures: { superSidebarPeek: true } },
- sidebarState: { isCollapsed: true },
- });
+ it.each([STATE_OPEN, STATE_WILL_CLOSE])(
+ 'makes sidebar interactive and visible when peek state is %s',
+ async (state) => {
+ createWrapper({ sidebarState: { isCollapsed: true, isPeekable: true } });
- findHoverArea().trigger('mouseenter');
+ findPeekBehavior().vm.$emit('change', state);
+ await nextTick();
- jest.advanceTimersByTime(SUPER_SIDEBAR_PEEK_OPEN_DELAY);
- await nextTick();
-
- // Sidebar is now open
- expect(findSidebar().classes()).toContain(peekClass);
- expect(findSidebar().attributes('inert')).toBe(undefined);
-
- // Important: do *not* fire a mouseenter event on the sidebar here. This
- // imitates what happens if the cursor moves away from the sidebar before
- // it actually appears.
-
- jest.advanceTimersByTime(SUPER_SIDEBAR_PEEK_CLOSE_DELAY - 1);
- await nextTick();
-
- // Not quite enough time has elapsed yet for sidebar to hide
- expect(findSidebar().classes()).toContain(peekClass);
- expect(findSidebar().attributes('inert')).toBe(undefined);
-
- jest.advanceTimersByTime(1);
- await nextTick();
-
- // Exactly enough time has elapsed for sidebar to hide
- expect(findSidebar().classes()).not.toContain('super-sidebar-peek');
- expect(findSidebar().attributes('inert')).toBe('inert');
- });
+ expect(findSidebar().attributes('inert')).toBe(undefined);
+ expect(findSidebar().classes()).toContain(peekClass);
+ expect(findSidebar().classes()).not.toContain(peekHintClass);
+ },
+ );
});
describe('nav container', () => {
diff --git a/spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js b/spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js
index 4028d91c82f..909f4249e28 100644
--- a/spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js
+++ b/spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js
@@ -42,24 +42,28 @@ describe('Super Sidebar Collapsed State Manager', () => {
describe('toggleSuperSidebarCollapsed', () => {
it.each`
- collapsed | saveCookie | windowWidth | hasClass
- ${true} | ${true} | ${xl} | ${true}
- ${true} | ${false} | ${xl} | ${true}
- ${true} | ${true} | ${sm} | ${true}
- ${true} | ${false} | ${sm} | ${true}
- ${false} | ${true} | ${xl} | ${false}
- ${false} | ${false} | ${xl} | ${false}
- ${false} | ${true} | ${sm} | ${false}
- ${false} | ${false} | ${sm} | ${false}
+ collapsed | saveCookie | windowWidth | hasClass | superSidebarPeek | isPeekable
+ ${true} | ${true} | ${xl} | ${true} | ${false} | ${false}
+ ${true} | ${true} | ${xl} | ${true} | ${true} | ${true}
+ ${true} | ${false} | ${xl} | ${true} | ${false} | ${false}
+ ${true} | ${true} | ${sm} | ${true} | ${false} | ${false}
+ ${true} | ${false} | ${sm} | ${true} | ${false} | ${false}
+ ${false} | ${true} | ${xl} | ${false} | ${false} | ${false}
+ ${false} | ${true} | ${xl} | ${false} | ${true} | ${false}
+ ${false} | ${false} | ${xl} | ${false} | ${false} | ${false}
+ ${false} | ${true} | ${sm} | ${false} | ${false} | ${false}
+ ${false} | ${false} | ${sm} | ${false} | ${false} | ${false}
`(
'when collapsed is $collapsed, saveCookie is $saveCookie, and windowWidth is $windowWidth then page class contains `page-with-super-sidebar-collapsed` is $hasClass',
- ({ collapsed, saveCookie, windowWidth, hasClass }) => {
+ ({ collapsed, saveCookie, windowWidth, hasClass, superSidebarPeek, isPeekable }) => {
jest.spyOn(bp, 'windowWidth').mockReturnValue(windowWidth);
+ gon.features = { superSidebarPeek };
toggleSuperSidebarCollapsed(collapsed, saveCookie);
pageHasCollapsedClass(hasClass);
expect(sidebarState.isCollapsed).toBe(collapsed);
+ expect(sidebarState.isPeekable).toBe(isPeekable);
if (saveCookie && windowWidth >= xl) {
expect(setCookie).toHaveBeenCalledWith(SIDEBAR_COLLAPSED_COOKIE, collapsed, {
diff --git a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
index dec2327db0f..d32e148ef79 100644
--- a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
@@ -62,10 +62,19 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
},
});
};
+
+ const ContentEditorStub = stubComponent(ContentEditor);
+
const findMarkdownField = () => wrapper.findComponent(MarkdownField);
const findTextarea = () => wrapper.find('textarea');
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
- const findContentEditor = () => wrapper.findComponent(ContentEditor);
+ const findContentEditor = () => {
+ const result = wrapper.findComponent(ContentEditor);
+
+ // In Vue.js 3 there are nuances stubbing component with custom stub on mount
+ // So we try to search for stub also
+ return result.exists() ? result : wrapper.findComponent(ContentEditorStub);
+ };
const enableContentEditor = async () => {
findMarkdownField().vm.$emit('enableContentEditor');
@@ -185,7 +194,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
it('autosizes the textarea when the value changes', async () => {
buildWrapper();
await findTextarea().setValue('Lots of newlines\n\n\n\n\n\n\nMore content\n\n\nand newlines');
-
+ await nextTick();
expect(Autosize.update).toHaveBeenCalled();
});
@@ -276,7 +285,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
it(`emits ${EDITING_MODE_MARKDOWN_FIELD} event when enableMarkdownEditor emitted from content editor`, async () => {
buildWrapper({
- stubs: { ContentEditor: stubComponent(ContentEditor) },
+ stubs: { ContentEditor: ContentEditorStub },
});
await enableContentEditor();
@@ -383,7 +392,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
beforeEach(() => {
buildWrapper({
propsData: { autofocus: true },
- stubs: { ContentEditor: stubComponent(ContentEditor) },
+ stubs: { ContentEditor: ContentEditorStub },
});
});