summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/import/details/components/import_details_app_spec.js7
-rw-r--r--spec/frontend/import/details/components/import_details_table_spec.js88
-rw-r--r--spec/frontend/import/details/mock_data.js76
-rw-r--r--spec/frontend/import_entities/components/import_status_spec.js6
-rw-r--r--spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js14
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/app_spec.js41
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/user_link_spec.js42
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js25
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js18
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js93
-rw-r--r--spec/frontend/projects/commit_box/info/init_details_button_spec.js32
-rw-r--r--spec/frontend/projects/commit_box/info/load_branches_spec.js10
-rw-r--r--spec/frontend/search/sidebar/components/scope_new_navigation_spec.js4
-rw-r--r--spec/frontend/super_sidebar/components/nav_item_link_spec.js37
-rw-r--r--spec/frontend/super_sidebar/components/nav_item_router_link_spec.js56
-rw-r--r--spec/frontend/super_sidebar/components/nav_item_spec.js89
-rw-r--r--spec/frontend/super_sidebar/utils_spec.js11
17 files changed, 490 insertions, 159 deletions
diff --git a/spec/frontend/import/details/components/import_details_app_spec.js b/spec/frontend/import/details/components/import_details_app_spec.js
index 178ce071de0..6e748a57a1d 100644
--- a/spec/frontend/import/details/components/import_details_app_spec.js
+++ b/spec/frontend/import/details/components/import_details_app_spec.js
@@ -1,16 +1,11 @@
import { shallowMount } from '@vue/test-utils';
import ImportDetailsApp from '~/import/details/components/import_details_app.vue';
-import { mockProject } from '../mock_data';
describe('Import details app', () => {
let wrapper;
const createComponent = () => {
- wrapper = shallowMount(ImportDetailsApp, {
- propsData: {
- project: mockProject,
- },
- });
+ wrapper = shallowMount(ImportDetailsApp);
};
describe('template', () => {
diff --git a/spec/frontend/import/details/components/import_details_table_spec.js b/spec/frontend/import/details/components/import_details_table_spec.js
index 43c9a66c00a..aee8573eb02 100644
--- a/spec/frontend/import/details/components/import_details_table_spec.js
+++ b/spec/frontend/import/details/components/import_details_table_spec.js
@@ -1,17 +1,30 @@
import { mount, shallowMount } from '@vue/test-utils';
-import { GlEmptyState, GlTable } from '@gitlab/ui';
+import { GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { HTTP_STATUS_OK, HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status';
+import { createAlert } from '~/alert';
+import waitForPromises from 'helpers/wait_for_promises';
import PaginationBar from '~/vue_shared/components/pagination_bar/pagination_bar.vue';
import ImportDetailsTable from '~/import/details/components/import_details_table.vue';
+import { mockImportFailures, mockHeaders } from '../mock_data';
+
+jest.mock('~/alert');
describe('Import details table', () => {
let wrapper;
+ let mock;
- const createComponent = ({ mountFn = shallowMount } = {}) => {
- wrapper = mountFn(ImportDetailsTable);
+ const createComponent = ({ mountFn = shallowMount, provide = {} } = {}) => {
+ wrapper = mountFn(ImportDetailsTable, {
+ provide,
+ });
};
+ const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findGlTable = () => wrapper.findComponent(GlTable);
+ const findGlTableRows = () => findGlTable().find('tbody').findAll('tr');
const findGlEmptyState = () => findGlTable().findComponent(GlEmptyState);
const findPaginationBar = () => wrapper.findComponent(PaginationBar);
@@ -20,7 +33,7 @@ describe('Import details table', () => {
it('renders table with empty state', () => {
createComponent({ mountFn: mount });
- expect(findGlEmptyState().exists()).toBe(true);
+ expect(findGlEmptyState().text()).toBe(ImportDetailsTable.i18n.emptyText);
});
it('does not render pagination', () => {
@@ -30,4 +43,71 @@ describe('Import details table', () => {
});
});
});
+
+ describe('fetching failures from API', () => {
+ const mockImportFailuresPath = '/failures';
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('when request is successful', () => {
+ beforeEach(() => {
+ mock.onGet(mockImportFailuresPath).reply(HTTP_STATUS_OK, mockImportFailures, mockHeaders);
+
+ createComponent({
+ mountFn: mount,
+ provide: {
+ failuresPath: mockImportFailuresPath,
+ },
+ });
+ });
+
+ it('renders loading icon', () => {
+ expect(findGlLoadingIcon().exists()).toBe(true);
+ });
+
+ it('does not render loading icon after fetch', async () => {
+ await waitForPromises();
+
+ expect(findGlLoadingIcon().exists()).toBe(false);
+ });
+
+ it('sets items and pagination info', async () => {
+ await waitForPromises();
+
+ expect(findGlTableRows().length).toBe(mockImportFailures.length);
+ expect(findPaginationBar().props('pageInfo')).toMatchObject({
+ page: mockHeaders['x-page'],
+ perPage: mockHeaders['x-per-page'],
+ total: mockHeaders['x-total'],
+ totalPages: mockHeaders['x-total-pages'],
+ });
+ });
+ });
+
+ describe('when request fails', () => {
+ beforeEach(() => {
+ mock.onGet(mockImportFailuresPath).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+
+ createComponent({
+ provide: {
+ failuresPath: mockImportFailuresPath,
+ },
+ });
+ });
+
+ it('displays an error', async () => {
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: ImportDetailsTable.i18n.fetchErrorMessage,
+ });
+ });
+ });
+ });
});
diff --git a/spec/frontend/import/details/mock_data.js b/spec/frontend/import/details/mock_data.js
index 514fb3a923d..a8e0e53ed2b 100644
--- a/spec/frontend/import/details/mock_data.js
+++ b/spec/frontend/import/details/mock_data.js
@@ -1,31 +1,53 @@
-export const mockProject = {
- id: 26,
- name: 'acl',
- fullPath: '/root/acl',
- fullName: 'Administrator / acl',
- refsUrl: '/root/acl/refs',
- importSource: 'namespace/acl',
- importStatus: 'finished',
- humanImportStatusName: 'finished',
- providerLink: 'https://github.com/namespace/acl',
- relationType: null,
- stats: {
- fetched: {
- note: 1,
- issue: 2,
- label: 5,
- collaborator: 2,
- pullRequest: 1,
- pullRequestMergedBy: 1,
+export const mockImportFailures = [
+ {
+ type: 'pull_request',
+ title: 'Add one cool feature',
+ url: 'https://github.com/USER/REPO/pull/2',
+ details: {
+ exception_class: 'ActiveRecord::RecordInvalid',
+ exception_message: 'Record invalid',
+ source: 'Gitlab::GithubImport::Importer::PullRequestImporter',
+ github_identifiers: {
+ iid: 2,
+ issuable_type: 'MergeRequest',
+ object_type: 'pull_request',
+ },
},
- imported: {
- note: 1,
- issue: 2,
- label: 6,
- collaborator: 3,
- pullRequest: 1,
- pullRequestMergedBy: 1,
- pullRequestReviewRequest: 1,
+ },
+ {
+ type: 'pull_request',
+ title: 'Add another awesome feature',
+ url: 'https://github.com/USER/REPO/pull/3',
+ details: {
+ exception_class: 'ActiveRecord::RecordInvalid',
+ exception_message: 'Record invalid',
+ source: 'Gitlab::GithubImport::Importer::PullRequestImporter',
+ github_identifiers: {
+ iid: 3,
+ issuable_type: 'MergeRequest',
+ object_type: 'pull_request',
+ },
+ },
+ },
+ {
+ type: 'lfs_object',
+ title: '3a9257fae9e86faee27d7208cb55e086f18e6f29f48c430bfbc26d42eb',
+ url: null,
+ details: {
+ exception_class: 'NameError',
+ exception_message: 'some message',
+ source: 'Gitlab::GithubImport::Importer::LfsObjectImporter',
+ github_identifiers: {
+ oid: '3a9257fae9e86faee27d7208cb55e086f18e6f29f48c430bfbc26d42eb',
+ size: 2473979,
+ },
},
},
+];
+
+export const mockHeaders = {
+ 'x-page': 1,
+ 'x-per-page': 20,
+ 'x-total': 3,
+ 'x-total-pages': 1,
};
diff --git a/spec/frontend/import_entities/components/import_status_spec.js b/spec/frontend/import_entities/components/import_status_spec.js
index 8e569e3ec85..4c6fee35389 100644
--- a/spec/frontend/import_entities/components/import_status_spec.js
+++ b/spec/frontend/import_entities/components/import_status_spec.js
@@ -155,6 +155,7 @@ describe('Import entities status component', () => {
describe('show details link', () => {
const mockDetailsPath = 'details_path';
+ const mockProjectId = 29;
const mockCompleteStats = {
fetched: { ...mockStatItems },
imported: { ...mockStatItems },
@@ -180,6 +181,7 @@ describe('Import entities status component', () => {
beforeEach(() => {
createComponent(
{
+ projectId: mockProjectId,
status: STATUSES.FINISHED,
stats: partialImport ? mockIncompleteStats : mockCompleteStats,
},
@@ -195,7 +197,9 @@ describe('Import entities status component', () => {
it(`${expectLink ? 'renders' : 'does not render'} import details link`, () => {
expect(findGlLink().exists()).toBe(expectLink);
if (expectLink) {
- expect(findGlLink().attributes('href')).toBe(mockDetailsPath);
+ expect(findGlLink().attributes('href')).toBe(
+ `${mockDetailsPath}?project_id=${mockProjectId}`,
+ );
}
});
},
diff --git a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
index 8e73f76382a..57e232a4c46 100644
--- a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
@@ -40,6 +40,7 @@ describe('ProviderRepoTableRow', () => {
const findImportButton = () => findButton('Import');
const findReimportButton = () => findButton('Re-import');
const findGroupDropdown = () => wrapper.findComponent(ImportGroupDropdown);
+ const findImportStatus = () => wrapper.findComponent(ImportStatus);
const findCancelButton = () => {
const buttons = wrapper
@@ -81,7 +82,7 @@ describe('ProviderRepoTableRow', () => {
});
it('renders empty import status', () => {
- expect(wrapper.findComponent(ImportStatus).props().status).toBe(STATUSES.NONE);
+ expect(findImportStatus().props().status).toBe(STATUSES.NONE);
});
it('renders a group namespace select', () => {
@@ -198,9 +199,7 @@ describe('ProviderRepoTableRow', () => {
});
it('renders proper import status', () => {
- expect(wrapper.findComponent(ImportStatus).props().status).toBe(
- repo.importedProject.importStatus,
- );
+ expect(findImportStatus().props().status).toBe(repo.importedProject.importStatus);
});
it('does not render a namespace select', () => {
@@ -236,8 +235,11 @@ describe('ProviderRepoTableRow', () => {
});
});
- it('passes stats to import status component', () => {
- expect(wrapper.findComponent(ImportStatus).props().stats).toBe(FAKE_STATS);
+ it('passes props to import status component', () => {
+ expect(findImportStatus().props()).toMatchObject({
+ projectId: repo.importedProject.id,
+ stats: FAKE_STATS,
+ });
});
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/app_spec.js b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
index 15a7e2e4ea0..26a9d07321c 100644
--- a/spec/frontend/jira_connect/subscriptions/components/app_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
@@ -50,18 +50,6 @@ describe('JiraConnectApp', () => {
jest.spyOn(AccessorUtilities, 'canUseCrypto').mockReturnValue(true);
});
- it('renders UserLink component', () => {
- createComponent();
-
- const userLink = findUserLink();
- expect(userLink.exists()).toBe(true);
- expect(userLink.props()).toEqual({
- hasSubscriptions: true,
- user: null,
- userSignedIn: false,
- });
- });
-
it('renders only Jira Connect app', () => {
createComponent();
@@ -79,12 +67,12 @@ describe('JiraConnectApp', () => {
});
describe.each`
- scenario | currentUser | shouldRenderSignInPage | shouldRenderSubscriptionsPage
- ${'user is not signed in'} | ${undefined} | ${true} | ${false}
- ${'user is signed in'} | ${mockCurrentUser} | ${false} | ${true}
+ scenario | currentUser | expectUserLink | expectSignInPage | expectSubscriptionsPage
+ ${'user is not signed in'} | ${undefined} | ${false} | ${true} | ${false}
+ ${'user is signed in'} | ${mockCurrentUser} | ${true} | ${false} | ${true}
`(
'when $scenario',
- ({ currentUser, shouldRenderSignInPage, shouldRenderSubscriptionsPage }) => {
+ ({ currentUser, expectUserLink, expectSignInPage, expectSubscriptionsPage }) => {
beforeEach(() => {
createComponent({
initialState: {
@@ -93,18 +81,23 @@ describe('JiraConnectApp', () => {
});
});
- it(`${shouldRenderSignInPage ? 'renders' : 'does not render'} sign in page`, () => {
- expect(findSignInPage().isVisible()).toBe(shouldRenderSignInPage);
- if (shouldRenderSignInPage) {
+ it(`${expectUserLink ? 'renders' : 'does not render'} user link`, () => {
+ expect(findUserLink().exists()).toBe(expectUserLink);
+ if (expectUserLink) {
+ expect(findUserLink().props('user')).toBe(mockCurrentUser);
+ }
+ });
+
+ it(`${expectSignInPage ? 'renders' : 'does not render'} sign in page`, () => {
+ expect(findSignInPage().isVisible()).toBe(expectSignInPage);
+ if (expectSignInPage) {
expect(findSignInPage().props('hasSubscriptions')).toBe(true);
}
});
- it(`${
- shouldRenderSubscriptionsPage ? 'renders' : 'does not render'
- } subscriptions page`, () => {
- expect(findSubscriptionsPage().exists()).toBe(shouldRenderSubscriptionsPage);
- if (shouldRenderSubscriptionsPage) {
+ it(`${expectSubscriptionsPage ? 'renders' : 'does not render'} subscriptions page`, () => {
+ expect(findSubscriptionsPage().exists()).toBe(expectSubscriptionsPage);
+ if (expectSubscriptionsPage) {
expect(findSubscriptionsPage().props('hasSubscriptions')).toBe(true);
}
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js b/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js
index a10e352ed85..77bc1d2004c 100644
--- a/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js
@@ -1,6 +1,5 @@
import { GlSprintf } from '@gitlab/ui';
import UserLink from '~/jira_connect/subscriptions/components/user_link.vue';
-import SignInOauthButton from '~/jira_connect/subscriptions/components/sign_in_oauth_button.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -13,42 +12,18 @@ describe('UserLink', () => {
provide,
stubs: {
GlSprintf,
- SignInOauthButton,
},
});
};
const findGitlabUserLink = () => wrapper.findByTestId('gitlab-user-link');
const findSprintf = () => wrapper.findComponent(GlSprintf);
- const findOauthButton = () => wrapper.findComponent(SignInOauthButton);
- describe.each`
- userSignedIn | hasSubscriptions | expectGlSprintf | expectOauthButton
- ${false} | ${false} | ${false} | ${false}
- ${false} | ${true} | ${false} | ${true}
- ${true} | ${false} | ${true} | ${false}
- ${true} | ${true} | ${true} | ${false}
- `(
- 'when `userSignedIn` is $userSignedIn, `hasSubscriptions` is $hasSubscriptions',
- ({ userSignedIn, hasSubscriptions, expectGlSprintf, expectOauthButton }) => {
- it('renders template correctly', () => {
- createComponent(
- {
- userSignedIn,
- hasSubscriptions,
- },
- {
- provide: {
- oauthMetadata: {},
- },
- },
- );
+ it('renders template correctly', () => {
+ createComponent();
- expect(findSprintf().exists()).toBe(expectGlSprintf);
- expect(findOauthButton().exists()).toBe(expectOauthButton);
- });
- },
- );
+ expect(findSprintf().exists()).toBe(true);
+ });
describe('gitlab user link', () => {
describe.each`
@@ -62,14 +37,7 @@ describe('UserLink', () => {
beforeEach(() => {
window.gon = { current_username, relative_root_url: '' };
- createComponent(
- {
- userSignedIn: true,
- hasSubscriptions: true,
- user,
- },
- { provide: { gitlabUserPath } },
- );
+ createComponent({ user }, { provide: { gitlabUserPath } });
});
it(`sets href to ${expectedUserLink}`, () => {
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
index ce6861ff460..93663319e6d 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
@@ -1,7 +1,6 @@
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import SetupInstructions from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue';
import SignInGitlabMultiversion from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue';
import SignInOauthButton from '~/jira_connect/subscriptions/components/sign_in_oauth_button.vue';
import VersionSelectForm from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue';
@@ -23,7 +22,6 @@ describe('SignInGitlabMultiversion', () => {
const mockBasePath = 'gitlab.mycompany.com';
- const findSetupInstructions = () => wrapper.findComponent(SetupInstructions);
const findSignInOauthButton = () => wrapper.findComponent(SignInOauthButton);
const findVersionSelectForm = () => wrapper.findComponent(VersionSelectForm);
const findSubtitle = () => wrapper.findByTestId('subtitle');
@@ -68,28 +66,13 @@ describe('SignInGitlabMultiversion', () => {
expect(findSubtitle().text()).toBe(SignInGitlabMultiversion.i18n.signInSubtitle);
});
- it('renders setup instructions', () => {
- expect(findSetupInstructions().exists()).toBe(true);
+ it('renders sign in button', () => {
+ expect(findSignInOauthButton().props('gitlabBasePath')).toBe(mockBasePath);
});
it('calls setApiBaseURL with correct params', () => {
expect(setApiBaseURL).toHaveBeenCalledWith(mockBasePath);
});
-
- describe('when SetupInstructions emits `next` event', () => {
- beforeEach(async () => {
- findSetupInstructions().vm.$emit('next');
- await nextTick();
- });
-
- it('renders sign in button', () => {
- expect(findSignInOauthButton().props('gitlabBasePath')).toBe(mockBasePath);
- });
-
- it('hides setup instructions', () => {
- expect(findSetupInstructions().exists()).toBe(false);
- });
- });
});
describe('when on GitLab.com', () => {
@@ -98,10 +81,6 @@ describe('SignInGitlabMultiversion', () => {
createComponent();
});
- it('does not render setup instructions', () => {
- expect(findSetupInstructions().exists()).toBe(false);
- });
-
it('renders sign in button', () => {
expect(findSignInOauthButton().props('gitlabBasePath')).toBe(GITLAB_COM_BASE_PATH);
});
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js
index 5496cf008c5..40ea6058c70 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions_spec.js
@@ -7,8 +7,9 @@ import SetupInstructions from '~/jira_connect/subscriptions/pages/sign_in/sign_i
describe('SetupInstructions', () => {
let wrapper;
- const findGlButton = () => wrapper.findComponent(GlButton);
const findGlLink = () => wrapper.findComponent(GlLink);
+ const findBackButton = () => wrapper.findAllComponents(GlButton).at(0);
+ const findNextButton = () => wrapper.findAllComponents(GlButton).at(1);
const createComponent = () => {
wrapper = shallowMount(SetupInstructions);
@@ -23,12 +24,23 @@ describe('SetupInstructions', () => {
expect(findGlLink().attributes('href')).toBe(OAUTH_SELF_MANAGED_DOC_LINK);
});
- describe('when button is clicked', () => {
+ describe('when "Next" button is clicked', () => {
it('emits "next" event', () => {
expect(wrapper.emitted('next')).toBeUndefined();
- findGlButton().vm.$emit('click');
+ findNextButton().vm.$emit('click');
expect(wrapper.emitted('next')).toHaveLength(1);
+ expect(wrapper.emitted('back')).toBeUndefined();
+ });
+ });
+
+ describe('when "Back" button is clicked', () => {
+ it('emits "back" event', () => {
+ expect(wrapper.emitted('back')).toBeUndefined();
+ findBackButton().vm.$emit('click');
+
+ expect(wrapper.emitted('back')).toHaveLength(1);
+ expect(wrapper.emitted('next')).toBeUndefined();
});
});
});
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js
index 428aa1d734b..2a08547b048 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js
@@ -1,8 +1,9 @@
import { GlFormInput, GlFormRadioGroup, GlForm } from '@gitlab/ui';
-import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import VersionSelectForm from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue';
+import SelfManagedAlert from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/self_managed_alert.vue';
+import SetupInstructions from '~/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue';
describe('VersionSelectForm', () => {
let wrapper;
@@ -10,26 +11,53 @@ describe('VersionSelectForm', () => {
const findFormRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
const findForm = () => wrapper.findComponent(GlForm);
const findInput = () => wrapper.findComponent(GlFormInput);
+ const findSelfManagedAlert = () => wrapper.findComponent(SelfManagedAlert);
+ const findSetupInstructions = () => wrapper.findComponent(SetupInstructions);
+ const findBackButton = () => wrapper.findByTestId('back-button');
+ const findSubmitButton = () => wrapper.findByTestId('submit-button');
const submitForm = () => findForm().vm.$emit('submit', new Event('submit'));
+ const expectSelfManagedFlowAtStep = (step) => {
+ // step 0 is for SaaS which doesn't have any of the self-managed elements
+ const expectSelfManagedAlert = step === 1;
+ const expectSetupInstructions = step === 2;
+ const expectSelfManagedInput = step === 3;
+
+ it(`${expectSelfManagedAlert ? 'renders' : 'does not render'} self-managed alert`, () => {
+ expect(findSelfManagedAlert().exists()).toBe(expectSelfManagedAlert);
+ });
+
+ it(`${expectSetupInstructions ? 'renders' : 'does not render'} setup instructions`, () => {
+ expect(findSetupInstructions().exists()).toBe(expectSetupInstructions);
+ });
+
+ it(`${
+ expectSelfManagedInput ? 'renders' : 'does not render'
+ } self-managed instance URL input`, () => {
+ expect(findInput().exists()).toBe(expectSelfManagedInput);
+ });
+ };
+
const createComponent = () => {
wrapper = shallowMountExtended(VersionSelectForm);
};
- describe('default state', () => {
+ describe('when "SaaS" radio option is selected (default state)', () => {
beforeEach(() => {
createComponent();
});
- it('selects saas radio option by default', () => {
+ it('selects "saas" radio option by default', () => {
expect(findFormRadioGroup().vm.$attrs.checked).toBe(VersionSelectForm.radioOptions.saas);
});
- it('does not render instance input', () => {
- expect(findInput().exists()).toBe(false);
+ it('renders submit button as "Save"', () => {
+ expect(findSubmitButton().text()).toBe(VersionSelectForm.i18n.buttonSave);
});
+ expectSelfManagedFlowAtStep(0);
+
describe('when form is submitted', () => {
it('emits "submit" event with gitlab.com as the payload', () => {
submitForm();
@@ -39,26 +67,61 @@ describe('VersionSelectForm', () => {
});
});
- describe('when "self-managed" radio option is selected', () => {
- beforeEach(async () => {
+ describe('when "self-managed" radio option is selected (step 1 of 3)', () => {
+ beforeEach(() => {
createComponent();
findFormRadioGroup().vm.$emit('input', VersionSelectForm.radioOptions.selfManaged);
- await nextTick();
});
- it('reveals the self-managed input field', () => {
- expect(findInput().exists()).toBe(true);
+ it('renders submit button as "Next"', () => {
+ expect(findSubmitButton().text()).toBe(VersionSelectForm.i18n.buttonNext);
});
- describe('when form is submitted', () => {
- it('emits "submit" event with the input field value as the payload', () => {
- const mockInstanceUrl = 'https://gitlab.example.com';
+ expectSelfManagedFlowAtStep(1);
- findInput().vm.$emit('input', mockInstanceUrl);
+ describe('when user clicks "Next" button (next to step 2 of 3)', () => {
+ beforeEach(() => {
submitForm();
+ });
+
+ expectSelfManagedFlowAtStep(2);
+
+ describe('when SetupInstructions emits `next` event (next to step 3 of 3)', () => {
+ beforeEach(() => {
+ findSetupInstructions().vm.$emit('next');
+ });
+
+ expectSelfManagedFlowAtStep(3);
+
+ describe('when form is submitted', () => {
+ it('emits "submit" event with the input field value as the payload', () => {
+ const mockInstanceUrl = 'https://gitlab.example.com';
+
+ findInput().vm.$emit('input', mockInstanceUrl);
+ submitForm();
+
+ expect(wrapper.emitted('submit')[0][0]).toBe(mockInstanceUrl);
+ });
+ });
+
+ describe('when back button is clicked', () => {
+ beforeEach(() => {
+ findBackButton().vm.$emit('click', {
+ preventDefault: jest.fn(), // preventDefault is needed to prevent form submission
+ });
+ });
+
+ expectSelfManagedFlowAtStep(1);
+ });
+ });
+
+ describe('when SetupInstructions emits `back` event (back to step 1 of 3)', () => {
+ beforeEach(() => {
+ findSetupInstructions().vm.$emit('back');
+ });
- expect(wrapper.emitted('submit')[0][0]).toBe(mockInstanceUrl);
+ expectSelfManagedFlowAtStep(1);
});
});
});
diff --git a/spec/frontend/projects/commit_box/info/init_details_button_spec.js b/spec/frontend/projects/commit_box/info/init_details_button_spec.js
new file mode 100644
index 00000000000..8aaba31e23e
--- /dev/null
+++ b/spec/frontend/projects/commit_box/info/init_details_button_spec.js
@@ -0,0 +1,32 @@
+import { setHTMLFixture } from 'helpers/fixtures';
+import { initDetailsButton } from '~/projects/commit_box/info/init_details_button';
+
+const htmlFixture = `
+ <span>
+ <a href="#" class="js-details-expand">Expand</a>
+ <span class="js-details-content hide">Some branch</span>
+ </span>`;
+
+describe('~/projects/commit_box/info/init_details_button', () => {
+ const findExpandButton = () => document.querySelector('.js-details-expand');
+ const findContent = () => document.querySelector('.js-details-content');
+
+ beforeEach(() => {
+ setHTMLFixture(htmlFixture);
+ initDetailsButton();
+ });
+
+ describe('when clicking the expand button', () => {
+ it('renders the content by removing the `hide` class', () => {
+ expect(findContent().classList).toContain('hide');
+ findExpandButton().click();
+ expect(findContent().classList).not.toContain('hide');
+ });
+
+ it('hides the expand button by adding the `gl-display-none` class', () => {
+ expect(findExpandButton().classList).not.toContain('gl-display-none');
+ findExpandButton().click();
+ expect(findExpandButton().classList).toContain('gl-display-none');
+ });
+ });
+});
diff --git a/spec/frontend/projects/commit_box/info/load_branches_spec.js b/spec/frontend/projects/commit_box/info/load_branches_spec.js
index e49d92188ed..b00a6378e07 100644
--- a/spec/frontend/projects/commit_box/info/load_branches_spec.js
+++ b/spec/frontend/projects/commit_box/info/load_branches_spec.js
@@ -4,6 +4,9 @@ import { setHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { loadBranches } from '~/projects/commit_box/info/load_branches';
+import { initDetailsButton } from '~/projects/commit_box/info/init_details_button';
+
+jest.mock('~/projects/commit_box/info/init_details_button');
const mockCommitPath = '/commit/abcd/branches';
const mockBranchesRes =
@@ -26,6 +29,13 @@ describe('~/projects/commit_box/info/load_branches', () => {
mock.onGet(mockCommitPath).reply(HTTP_STATUS_OK, mockBranchesRes);
});
+ it('initializes the details button', async () => {
+ loadBranches();
+ await waitForPromises();
+
+ expect(initDetailsButton).toHaveBeenCalled();
+ });
+
it('loads and renders branches info', async () => {
loadBranches();
await waitForPromises();
diff --git a/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js
index c7dd6e931b1..5207665f883 100644
--- a/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js
+++ b/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js
@@ -1,4 +1,4 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import ScopeNewNavigation from '~/search/sidebar/components/scope_new_navigation.vue';
@@ -30,7 +30,7 @@ describe('ScopeNewNavigation', () => {
getters: getterSpies,
});
- wrapper = shallowMount(ScopeNewNavigation, {
+ wrapper = mount(ScopeNewNavigation, {
store,
stubs: {
NavItem,
diff --git a/spec/frontend/super_sidebar/components/nav_item_link_spec.js b/spec/frontend/super_sidebar/components/nav_item_link_spec.js
new file mode 100644
index 00000000000..5cc1bd01d0f
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/nav_item_link_spec.js
@@ -0,0 +1,37 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import NavItemLink from '~/super_sidebar/components/nav_item_link.vue';
+
+describe('NavItemLink component', () => {
+ let wrapper;
+
+ const createWrapper = (item) => {
+ wrapper = shallowMountExtended(NavItemLink, {
+ propsData: {
+ item,
+ },
+ });
+ };
+
+ describe('when `item` has `is_active` set to `false`', () => {
+ it('renders an anchor tag without active CSS class and `aria-current` attribute', () => {
+ createWrapper({ title: 'foo', link: '/foo', is_active: false });
+
+ expect(wrapper.attributes()).toEqual({
+ href: '/foo',
+ class: '',
+ });
+ });
+ });
+
+ describe('when `item` has `is_active` set to `true`', () => {
+ it('renders an anchor tag with active CSS class and `aria-current="page"`', () => {
+ createWrapper({ title: 'foo', link: '/foo', is_active: true });
+
+ expect(wrapper.attributes()).toEqual({
+ href: '/foo',
+ class: 'gl-bg-t-gray-a-08',
+ 'aria-current': 'page',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/nav_item_router_link_spec.js b/spec/frontend/super_sidebar/components/nav_item_router_link_spec.js
new file mode 100644
index 00000000000..a7ca56325fe
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/nav_item_router_link_spec.js
@@ -0,0 +1,56 @@
+import { RouterLinkStub } from '@vue/test-utils';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import NavItemRouterLink from '~/super_sidebar/components/nav_item_router_link.vue';
+
+describe('NavItemRouterLink component', () => {
+ let wrapper;
+
+ const createWrapper = ({ item, routerLinkSlotProps = {} }) => {
+ wrapper = mountExtended(NavItemRouterLink, {
+ propsData: {
+ item,
+ },
+ stubs: {
+ RouterLink: {
+ ...RouterLinkStub,
+ render() {
+ const children = this.$scopedSlots.default({
+ href: '/foo',
+ isActive: false,
+ navigate: jest.fn(),
+ ...routerLinkSlotProps,
+ });
+ return children;
+ },
+ },
+ },
+ });
+ };
+
+ describe('when `RouterLink` is not active', () => {
+ it('renders an anchor tag without active CSS class and `aria-current` attribute', () => {
+ createWrapper({ item: { title: 'foo', to: { name: 'foo' } } });
+
+ expect(wrapper.attributes()).toEqual({
+ href: '/foo',
+ custom: '',
+ });
+ });
+ });
+
+ describe('when `RouterLink` is active', () => {
+ it('renders an anchor tag with active CSS class and `aria-current="page"`', () => {
+ createWrapper({
+ item: { title: 'foo', to: { name: 'foo' } },
+ routerLinkSlotProps: { isActive: true },
+ });
+
+ expect(wrapper.findComponent(RouterLinkStub).props('activeClass')).toBe('gl-bg-t-gray-a-08');
+ expect(wrapper.attributes()).toEqual({
+ href: '/foo',
+ 'aria-current': 'page',
+ custom: '',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/nav_item_spec.js b/spec/frontend/super_sidebar/components/nav_item_spec.js
index 1714a4c3a4e..43b3f14f2f5 100644
--- a/spec/frontend/super_sidebar/components/nav_item_spec.js
+++ b/spec/frontend/super_sidebar/components/nav_item_spec.js
@@ -1,6 +1,9 @@
import { GlBadge } from '@gitlab/ui';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { RouterLinkStub } from '@vue/test-utils';
+import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
import NavItem from '~/super_sidebar/components/nav_item.vue';
+import NavItemRouterLink from '~/super_sidebar/components/nav_item_router_link.vue';
+import NavItemLink from '~/super_sidebar/components/nav_item_link.vue';
import {
CLICK_MENU_ITEM_ACTION,
TRACKING_UNKNOWN_ID,
@@ -12,19 +15,36 @@ describe('NavItem component', () => {
const findLink = () => wrapper.findByTestId('nav-item-link');
const findPill = () => wrapper.findComponent(GlBadge);
- const createWrapper = (item, props = {}, provide = {}) => {
- wrapper = shallowMountExtended(NavItem, {
+ const findNavItemRouterLink = () => extendedWrapper(wrapper.findComponent(NavItemRouterLink));
+ const findNavItemLink = () => extendedWrapper(wrapper.findComponent(NavItemLink));
+
+ const createWrapper = ({ item, props = {}, provide = {}, routerLinkSlotProps = {} }) => {
+ wrapper = mountExtended(NavItem, {
propsData: {
item,
...props,
},
provide,
+ stubs: {
+ RouterLink: {
+ ...RouterLinkStub,
+ render() {
+ const children = this.$scopedSlots.default({
+ href: '/foo',
+ isActive: false,
+ navigate: jest.fn(),
+ ...routerLinkSlotProps,
+ });
+ return children;
+ },
+ },
+ },
});
};
describe('pills', () => {
it.each([0, 5, 3.4, 'foo', '10%'])('item with pill_data `%p` renders a pill', (pillCount) => {
- createWrapper({ title: 'Foo', pill_count: pillCount });
+ createWrapper({ item: { title: 'Foo', pill_count: pillCount } });
expect(findPill().text()).toEqual(pillCount.toString());
});
@@ -32,7 +52,7 @@ describe('NavItem component', () => {
it.each([null, undefined, false, true, '', NaN, Number.POSITIVE_INFINITY])(
'item with pill_data `%p` renders no pill',
(pillCount) => {
- createWrapper({ title: 'Foo', pill_count: pillCount });
+ createWrapper({ item: { title: 'Foo', pill_count: pillCount } });
expect(findPill().exists()).toEqual(false);
},
@@ -41,21 +61,21 @@ describe('NavItem component', () => {
it('applies custom link classes', () => {
const customClass = 'customClass';
- createWrapper(
- { title: 'Foo' },
- {
+ createWrapper({
+ item: { title: 'Foo' },
+ props: {
linkClasses: {
[customClass]: true,
},
},
- );
+ });
expect(findLink().attributes('class')).toContain(customClass);
});
it('applies custom classes set in the backend', () => {
const customClass = 'customBackendClass';
- createWrapper({ title: 'Foo', link_classes: customClass });
+ createWrapper({ item: { title: 'Foo', link_classes: customClass } });
expect(findLink().attributes('class')).toContain(customClass);
});
@@ -70,7 +90,7 @@ describe('NavItem component', () => {
`(
'adds appropriate data tracking labels for id=$id and panelType=$panelType',
({ id, eventLabel, panelType, eventProperty, eventExtra }) => {
- createWrapper({ title: 'Foo', id }, {}, { panelType });
+ createWrapper({ item: { title: 'Foo', id }, props: {}, provide: { panelType } });
expect(findLink().attributes('data-track-action')).toBe(CLICK_MENU_ITEM_ACTION);
expect(findLink().attributes('data-track-label')).toBe(eventLabel);
@@ -79,4 +99,51 @@ describe('NavItem component', () => {
},
);
});
+
+ describe('when `item` prop has `to` attribute', () => {
+ describe('when `RouterLink` is not active', () => {
+ it('renders `NavItemRouterLink` with active indicator hidden', () => {
+ createWrapper({ item: { title: 'Foo', to: { name: 'foo' } } });
+
+ expect(findNavItemRouterLink().findByTestId('active-indicator').classes()).toContain(
+ 'gl-bg-transparent',
+ );
+ });
+ });
+
+ describe('when `RouterLink` is active', () => {
+ it('renders `NavItemRouterLink` with active indicator shown', () => {
+ createWrapper({
+ item: { title: 'Foo', to: { name: 'foo' } },
+ routerLinkSlotProps: { isActive: true },
+ });
+
+ expect(findNavItemRouterLink().findByTestId('active-indicator').classes()).toContain(
+ 'gl-bg-blue-500',
+ );
+ });
+ });
+ });
+
+ describe('when `item` prop has `link` attribute', () => {
+ describe('when `item` has `is_active` set to `false`', () => {
+ it('renders `NavItemLink` with active indicator hidden', () => {
+ createWrapper({ item: { title: 'Foo', link: '/foo', is_active: false } });
+
+ expect(findNavItemLink().findByTestId('active-indicator').classes()).toContain(
+ 'gl-bg-transparent',
+ );
+ });
+ });
+
+ describe('when `item` has `is_active` set to `true`', () => {
+ it('renders `NavItemLink` with active indicator shown', () => {
+ createWrapper({ item: { title: 'Foo', link: '/foo', is_active: true } });
+
+ expect(findNavItemLink().findByTestId('active-indicator').classes()).toContain(
+ 'gl-bg-blue-500',
+ );
+ });
+ });
+ });
});
diff --git a/spec/frontend/super_sidebar/utils_spec.js b/spec/frontend/super_sidebar/utils_spec.js
index d2984254dee..8c8673ddbc4 100644
--- a/spec/frontend/super_sidebar/utils_spec.js
+++ b/spec/frontend/super_sidebar/utils_spec.js
@@ -2,6 +2,7 @@ import {
getTopFrequentItems,
trackContextAccess,
formatContextSwitcherItems,
+ ariaCurrent,
} from '~/super_sidebar/utils';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import AccessorUtilities from '~/lib/utils/accessor';
@@ -157,4 +158,14 @@ describe('Super sidebar utils spec', () => {
]);
});
});
+
+ describe('ariaCurrent', () => {
+ it.each`
+ isActive | expected
+ ${true} | ${'page'}
+ ${false} | ${null}
+ `('returns `$expected` when `isActive` is `$isActive`', ({ isActive, expected }) => {
+ expect(ariaCurrent(isActive)).toBe(expected);
+ });
+ });
});