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-10-17 21:10:11 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-10-17 21:10:11 +0300
commit5cbf24858edb03505b16474e3b7b41a49b677ff6 (patch)
tree21999fbb911fe2410cf498b3ed668d202dc90098 /spec/frontend
parent673a1a02e97181a5f2d94cd0b116ebb4dba3d875 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/crm/crm_form_spec.js2
-rw-r--r--spec/frontend/crm/organization_form_wrapper_spec.js2
-rw-r--r--spec/frontend/environments/graphql/resolvers/kubernetes_spec.js2
-rw-r--r--spec/frontend/import/details/mock_data.js6
-rw-r--r--spec/frontend/import_entities/components/group_dropdown_spec.js94
-rw-r--r--spec/frontend/import_entities/components/import_target_dropdown_spec.js55
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_table_spec.js2
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js52
-rw-r--r--spec/frontend/ml/model_registry/routes/models/index/components/ml_models_index_spec.js13
-rw-r--r--spec/frontend/ml/model_registry/routes/models/index/components/mock_data.js10
-rw-r--r--spec/frontend/ml/model_registry/routes/models/index/components/model_row_spec.js42
-rw-r--r--spec/frontend/notes/components/email_participants_warning_spec.js6
-rw-r--r--spec/frontend/organizations/new/components/app_spec.js103
-rw-r--r--spec/frontend/organizations/shared/components/new_edit_form_spec.js112
14 files changed, 340 insertions, 161 deletions
diff --git a/spec/frontend/crm/crm_form_spec.js b/spec/frontend/crm/crm_form_spec.js
index fabf43ceb9d..083b49b7c30 100644
--- a/spec/frontend/crm/crm_form_spec.js
+++ b/spec/frontend/crm/crm_form_spec.js
@@ -10,7 +10,7 @@ import routes from '~/crm/contacts/routes';
import createContactMutation from '~/crm/contacts/components/graphql/create_contact.mutation.graphql';
import updateContactMutation from '~/crm/contacts/components/graphql/update_contact.mutation.graphql';
import getGroupContactsQuery from '~/crm/contacts/components/graphql/get_group_contacts.query.graphql';
-import createOrganizationMutation from '~/crm/organizations/components/graphql/create_organization.mutation.graphql';
+import createOrganizationMutation from '~/crm/organizations/components/graphql/create_customer_relations_organization.mutation.graphql';
import getGroupOrganizationsQuery from '~/crm/organizations/components/graphql/get_group_organizations.query.graphql';
import {
createContactMutationErrorResponse,
diff --git a/spec/frontend/crm/organization_form_wrapper_spec.js b/spec/frontend/crm/organization_form_wrapper_spec.js
index 8408c1920a9..f15fcac71d5 100644
--- a/spec/frontend/crm/organization_form_wrapper_spec.js
+++ b/spec/frontend/crm/organization_form_wrapper_spec.js
@@ -2,7 +2,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import OrganizationFormWrapper from '~/crm/organizations/components/organization_form_wrapper.vue';
import CrmForm from '~/crm/components/crm_form.vue';
import getGroupOrganizationsQuery from '~/crm/organizations/components/graphql/get_group_organizations.query.graphql';
-import createOrganizationMutation from '~/crm/organizations/components/graphql/create_organization.mutation.graphql';
+import createOrganizationMutation from '~/crm/organizations/components/graphql/create_customer_relations_organization.mutation.graphql';
import updateOrganizationMutation from '~/crm/organizations/components/graphql/update_organization.mutation.graphql';
describe('Customer relations organization form wrapper', () => {
diff --git a/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js b/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js
index 9788b9062d6..ed15c66f4c6 100644
--- a/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js
+++ b/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js
@@ -97,7 +97,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
it('should request namespaced services from the cluster_client library if namespace is specified', async () => {
const services = await mockResolvers.Query.k8sServices(null, { configuration, namespace });
- expect(mockNamespacedServicesListFn).toHaveBeenCalledWith(namespace);
+ expect(mockNamespacedServicesListFn).toHaveBeenCalledWith({ namespace });
expect(mockAllServicesListFn).not.toHaveBeenCalled();
expect(services).toEqual(k8sServicesMock);
diff --git a/spec/frontend/import/details/mock_data.js b/spec/frontend/import/details/mock_data.js
index 67148173404..b61a7f36f85 100644
--- a/spec/frontend/import/details/mock_data.js
+++ b/spec/frontend/import/details/mock_data.js
@@ -7,7 +7,7 @@ export const mockImportFailures = [
exception_class: 'ActiveRecord::RecordInvalid',
exception_message: 'Record invalid',
source: 'Gitlab::GithubImport::Importer::PullRequestImporter',
- github_identifiers: {
+ external_identifiers: {
iid: 2,
issuable_type: 'MergeRequest',
object_type: 'pull_request',
@@ -22,7 +22,7 @@ export const mockImportFailures = [
exception_class: 'ActiveRecord::RecordInvalid',
exception_message: 'Record invalid',
source: 'Gitlab::GithubImport::Importer::PullRequestImporter',
- github_identifiers: {
+ external_identifiers: {
iid: 3,
issuable_type: 'MergeRequest',
object_type: 'pull_request',
@@ -37,7 +37,7 @@ export const mockImportFailures = [
exception_class: 'NameError',
exception_message: 'some message',
source: 'Gitlab::GithubImport::Importer::LfsObjectImporter',
- github_identifiers: {
+ external_identifiers: {
oid: '3a9257fae9e86faee27d7208cb55e086f18e6f29f48c430bfbc26d42eb',
size: 2473979,
},
diff --git a/spec/frontend/import_entities/components/group_dropdown_spec.js b/spec/frontend/import_entities/components/group_dropdown_spec.js
deleted file mode 100644
index 14f39a35387..00000000000
--- a/spec/frontend/import_entities/components/group_dropdown_spec.js
+++ /dev/null
@@ -1,94 +0,0 @@
-import { GlSearchBoxByType, GlDropdown } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
-import VueApollo from 'vue-apollo';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import GroupDropdown from '~/import_entities/components/group_dropdown.vue';
-import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
-import searchNamespacesWhereUserCanImportProjectsQuery from '~/import_entities/import_projects/graphql/queries/search_namespaces_where_user_can_import_projects.query.graphql';
-
-Vue.use(VueApollo);
-
-const makeGroupMock = (fullPath) => ({
- id: `gid://gitlab/Group/${fullPath}`,
- fullPath,
- name: fullPath,
- visibility: 'public',
- webUrl: `http://gdk.test:3000/groups/${fullPath}`,
- __typename: 'Group',
-});
-
-const AVAILABLE_NAMESPACES = [
- makeGroupMock('match1'),
- makeGroupMock('unrelated'),
- makeGroupMock('match2'),
-];
-
-const SEARCH_NAMESPACES_MOCK = Promise.resolve({
- data: {
- currentUser: {
- id: 'gid://gitlab/User/1',
- groups: {
- nodes: AVAILABLE_NAMESPACES,
- __typename: 'GroupConnection',
- },
- namespace: {
- id: 'gid://gitlab/Namespaces::UserNamespace/1',
- fullPath: 'root',
- __typename: 'Namespace',
- },
- __typename: 'UserCore',
- },
- },
-});
-
-describe('Import entities group dropdown component', () => {
- let wrapper;
- let namespacesTracker;
-
- const createComponent = (propsData) => {
- const apolloProvider = createMockApollo([
- [searchNamespacesWhereUserCanImportProjectsQuery, () => SEARCH_NAMESPACES_MOCK],
- ]);
-
- namespacesTracker = jest.fn();
-
- wrapper = shallowMount(GroupDropdown, {
- apolloProvider,
- scopedSlots: {
- default: namespacesTracker,
- },
- stubs: { GlDropdown },
- propsData,
- });
- };
-
- it('passes namespaces from graphql query to default slot', async () => {
- createComponent();
- jest.advanceTimersByTime(DEBOUNCE_DELAY);
- await nextTick();
- await waitForPromises();
- await nextTick();
-
- expect(namespacesTracker).toHaveBeenCalledWith({ namespaces: AVAILABLE_NAMESPACES });
- });
-
- it('filters namespaces based on user input', async () => {
- createComponent();
-
- namespacesTracker.mockReset();
- wrapper.findComponent(GlSearchBoxByType).vm.$emit('input', 'match');
- jest.advanceTimersByTime(DEBOUNCE_DELAY);
- await nextTick();
- await waitForPromises();
- await nextTick();
-
- expect(namespacesTracker).toHaveBeenCalledWith({
- namespaces: [
- expect.objectContaining({ fullPath: 'match1' }),
- expect.objectContaining({ fullPath: 'match2' }),
- ],
- });
- });
-});
diff --git a/spec/frontend/import_entities/components/import_target_dropdown_spec.js b/spec/frontend/import_entities/components/import_target_dropdown_spec.js
index c12baed2374..ba0bb0b0f74 100644
--- a/spec/frontend/import_entities/components/import_target_dropdown_spec.js
+++ b/spec/frontend/import_entities/components/import_target_dropdown_spec.js
@@ -18,7 +18,6 @@ describe('ImportTargetDropdown', () => {
const defaultProps = {
selected: mockUserNamespace,
- userNamespace: mockUserNamespace,
};
const createComponent = ({ props = {} } = {}) => {
@@ -39,7 +38,7 @@ describe('ImportTargetDropdown', () => {
};
const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
- const findListboxUsersItems = () => findListbox().props('items')[0].options;
+ const findListboxFirstGroupItems = () => findListbox().props('items')[0].options;
const findListboxGroupsItems = () => findListbox().props('items')[1].options;
const waitForQuery = async () => {
@@ -63,12 +62,54 @@ describe('ImportTargetDropdown', () => {
expect(findListbox().props('toggleText')).toBe('a-group-path-that-is-lo…');
});
- it('passes userNamespace as "Users" group item', () => {
- createComponent();
+ describe('when used on group import', () => {
+ beforeEach(() => {
+ createComponent();
+ });
- expect(findListboxUsersItems()).toEqual([
- { text: mockUserNamespace, value: mockUserNamespace },
- ]);
+ it('adds "No parent" in "Parent" group', () => {
+ expect(findListboxFirstGroupItems()).toEqual([{ text: 'No parent', value: '' }]);
+ });
+
+ it('emits "select" event with { fullPath: "", id: null } when "No parent" is selected', () => {
+ findListbox().vm.$emit('select', '');
+
+ expect(wrapper.emitted('select')[0]).toEqual([{ fullPath: '', id: null }]);
+ });
+
+ it('emits "select" event with { fullPath, id } when a group is selected', async () => {
+ await waitForQuery();
+
+ const mockGroupPath = 'match1';
+
+ findListbox().vm.$emit('select', mockGroupPath);
+
+ expect(wrapper.emitted('select')[0]).toEqual([
+ { fullPath: mockGroupPath, id: `gid://gitlab/Group/${mockGroupPath}` },
+ ]);
+ });
+ });
+
+ describe('when used on project import', () => {
+ beforeEach(() => {
+ createComponent({
+ props: { userNamespace: mockUserNamespace },
+ });
+ });
+
+ it('passes userNamespace as "Users" group item', () => {
+ expect(findListboxFirstGroupItems()).toEqual([
+ { text: mockUserNamespace, value: mockUserNamespace },
+ ]);
+ });
+
+ it('emits "select" event with path as value', () => {
+ const mockProjectPath = 'mock-project';
+
+ findListbox().vm.$emit('select', mockProjectPath);
+
+ expect(wrapper.emitted('select')[0]).toEqual([mockProjectPath]);
+ });
});
it('passes namespaces from GraphQL as "Groups" group item', async () => {
diff --git a/spec/frontend/import_entities/import_groups/components/import_table_spec.js b/spec/frontend/import_entities/import_groups/components/import_table_spec.js
index 68fbd8dae18..4fab22e316a 100644
--- a/spec/frontend/import_entities/import_groups/components/import_table_spec.js
+++ b/spec/frontend/import_entities/import_groups/components/import_table_spec.js
@@ -57,7 +57,7 @@ describe('import table', () => {
];
const findPaginationDropdown = () => wrapper.findByTestId('page-size');
const findTargetNamespaceDropdown = (rowWrapper) =>
- extendedWrapper(rowWrapper).findByTestId('target-namespace-selector');
+ extendedWrapper(rowWrapper).findByTestId('target-namespace-dropdown');
const findTargetNamespaceInput = (rowWrapper) =>
extendedWrapper(rowWrapper).findByTestId('target-namespace-input');
const findPaginationDropdownText = () => findPaginationDropdown().find('button').text();
diff --git a/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js
index 46884a42707..ac95026a9a4 100644
--- a/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js
+++ b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js
@@ -1,10 +1,9 @@
-import { GlDropdownItem, GlFormInput } from '@gitlab/ui';
+import { GlFormInput } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import ImportGroupDropdown from '~/import_entities/components/group_dropdown.vue';
+import ImportTargetDropdown from '~/import_entities/components/import_target_dropdown.vue';
import { STATUSES } from '~/import_entities/constants';
import ImportTargetCell from '~/import_entities/import_groups/components/import_target_cell.vue';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
@@ -37,7 +36,7 @@ describe('import target cell', () => {
let group;
const findNameInput = () => wrapper.findComponent(GlFormInput);
- const findNamespaceDropdown = () => wrapper.findComponent(ImportGroupDropdown);
+ const findNamespaceDropdown = () => wrapper.findComponent(ImportTargetDropdown);
const createComponent = (props) => {
apolloProvider = createMockApollo([
@@ -49,7 +48,7 @@ describe('import target cell', () => {
wrapper = shallowMount(ImportTargetCell, {
apolloProvider,
- stubs: { ImportGroupDropdown },
+ stubs: { ImportTargetDropdown },
propsData: {
groupPathRegex: /.*/,
...props,
@@ -73,14 +72,14 @@ describe('import target cell', () => {
});
it('emits update-target-namespace when dropdown option is clicked', () => {
- const dropdownItem = findNamespaceDropdown().findAllComponents(GlDropdownItem).at(2);
+ const targetNamespace = {
+ fullPath: AVAILABLE_NAMESPACES[1].fullPath,
+ id: AVAILABLE_NAMESPACES[1].id,
+ };
- dropdownItem.vm.$emit('click');
+ findNamespaceDropdown().vm.$emit('select', targetNamespace);
- expect(wrapper.emitted('update-target-namespace')).toBeDefined();
- expect(wrapper.emitted('update-target-namespace')[0][0]).toStrictEqual(
- AVAILABLE_NAMESPACES[1],
- );
+ expect(wrapper.emitted('update-target-namespace')[0]).toStrictEqual([targetNamespace]);
});
});
@@ -101,36 +100,6 @@ describe('import target cell', () => {
});
});
- it('renders only no parent option if available namespaces list is empty', () => {
- createComponent({
- group: generateFakeTableEntry({ id: 1, status: STATUSES.NONE }),
- availableNamespaces: [],
- });
-
- const items = findNamespaceDropdown()
- .findAllComponents(GlDropdownItem)
- .wrappers.map((w) => w.text());
-
- expect(items[0]).toBe('No parent');
- expect(items).toHaveLength(1);
- });
-
- it('renders both no parent option and available namespaces list when available namespaces list is not empty', async () => {
- createComponent({
- group: generateFakeTableEntry({ id: 1, status: STATUSES.NONE }),
- });
- jest.advanceTimersByTime(DEBOUNCE_DELAY);
- await waitForPromises();
- await nextTick();
-
- const [firstItem, ...rest] = findNamespaceDropdown()
- .findAllComponents(GlDropdownItem)
- .wrappers.map((w) => w.text());
-
- expect(firstItem).toBe('No parent');
- expect(rest).toHaveLength(AVAILABLE_NAMESPACES.length);
- });
-
describe('when entity is not available for import', () => {
beforeEach(() => {
group = generateFakeTableEntry({
@@ -147,6 +116,7 @@ describe('import target cell', () => {
describe('when entity is available for import', () => {
const FAKE_PROGRESS_MESSAGE = 'progress message';
+
beforeEach(() => {
group = generateFakeTableEntry({
id: 1,
diff --git a/spec/frontend/ml/model_registry/routes/models/index/components/ml_models_index_spec.js b/spec/frontend/ml/model_registry/routes/models/index/components/ml_models_index_spec.js
index b4aad5ca292..c1b9aef9634 100644
--- a/spec/frontend/ml/model_registry/routes/models/index/components/ml_models_index_spec.js
+++ b/spec/frontend/ml/model_registry/routes/models/index/components/ml_models_index_spec.js
@@ -1,6 +1,6 @@
-import { GlLink } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import MlModelsIndexApp from '~/ml/model_registry/routes/models/index';
+import ModelRow from '~/ml/model_registry/routes/models/index/components/model_row.vue';
import { TITLE_LABEL, NO_MODELS_LABEL } from '~/ml/model_registry/routes/models/index/translations';
import Pagination from '~/vue_shared/components/incubation/pagination.vue';
import { mockModels, startCursor, defaultPageInfo } from './mock_data';
@@ -10,10 +10,8 @@ const createWrapper = (propsData = { models: mockModels, pageInfo: defaultPageIn
wrapper = shallowMountExtended(MlModelsIndexApp, { propsData });
};
-const findModelLink = (index) => wrapper.findAllComponents(GlLink).at(index);
+const findModelRow = (index) => wrapper.findAllComponents(ModelRow).at(index);
const findPagination = () => wrapper.findComponent(Pagination);
-const modelLinkText = (index) => findModelLink(index).text();
-const modelLinkHref = (index) => findModelLink(index).attributes('href');
const findTitle = () => wrapper.findByText(TITLE_LABEL);
const findEmptyLabel = () => wrapper.findByText(NO_MODELS_LABEL);
@@ -47,11 +45,8 @@ describe('MlModelsIndex', () => {
describe('model list', () => {
it('displays the models', () => {
- expect(modelLinkHref(0)).toBe(mockModels[0].path);
- expect(modelLinkText(0)).toBe(`${mockModels[0].name} / ${mockModels[0].version}`);
-
- expect(modelLinkHref(1)).toBe(mockModels[1].path);
- expect(modelLinkText(1)).toBe(`${mockModels[1].name} / ${mockModels[1].version}`);
+ expect(findModelRow(0).props('model')).toMatchObject(mockModels[0]);
+ expect(findModelRow(1).props('model')).toMatchObject(mockModels[1]);
});
});
diff --git a/spec/frontend/ml/model_registry/routes/models/index/components/mock_data.js b/spec/frontend/ml/model_registry/routes/models/index/components/mock_data.js
index e65552353c3..841a543606f 100644
--- a/spec/frontend/ml/model_registry/routes/models/index/components/mock_data.js
+++ b/spec/frontend/ml/model_registry/routes/models/index/components/mock_data.js
@@ -3,14 +3,22 @@ export const mockModels = [
name: 'model_1',
version: '1.0',
path: 'path/to/model_1',
+ versionCount: 3,
},
{
name: 'model_2',
- version: '1.0',
+ version: '1.1',
path: 'path/to/model_2',
+ versionCount: 1,
},
];
+export const modelWithoutVersion = {
+ name: 'model_without_version',
+ path: 'path/to/model_without_version',
+ versionCount: 0,
+};
+
export const startCursor = 'eyJpZCI6IjE2In0';
export const defaultPageInfo = Object.freeze({
diff --git a/spec/frontend/ml/model_registry/routes/models/index/components/model_row_spec.js b/spec/frontend/ml/model_registry/routes/models/index/components/model_row_spec.js
new file mode 100644
index 00000000000..7600288f560
--- /dev/null
+++ b/spec/frontend/ml/model_registry/routes/models/index/components/model_row_spec.js
@@ -0,0 +1,42 @@
+import { GlLink } from '@gitlab/ui';
+import {
+ mockModels,
+ modelWithoutVersion,
+} from 'jest/ml/model_registry/routes/models/index/components/mock_data';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ModelRow from '~/ml/model_registry/routes/models/index/components/model_row.vue';
+
+let wrapper;
+const createWrapper = (model = mockModels[0]) => {
+ wrapper = shallowMountExtended(ModelRow, { propsData: { model } });
+};
+
+const findLink = () => wrapper.findComponent(GlLink);
+const findMessage = (message) => wrapper.findByText(message);
+
+describe('ModelRow', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('Has a link to the model', () => {
+ expect(findLink().text()).toBe(mockModels[0].name);
+ expect(findLink().attributes('href')).toBe(mockModels[0].path);
+ });
+
+ it('Shows the latest version and the version count', () => {
+ expect(findMessage('1.0 · 3 versions').exists()).toBe(true);
+ });
+
+ it('Shows the latest version and no version count if it has only 1 version', () => {
+ createWrapper(mockModels[1]);
+
+ expect(findMessage('1.1 · No other versions').exists()).toBe(true);
+ });
+
+ it('Shows no version message if model has no versions', () => {
+ createWrapper(modelWithoutVersion);
+
+ expect(findMessage('No registered versions').exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/notes/components/email_participants_warning_spec.js b/spec/frontend/notes/components/email_participants_warning_spec.js
index 34b7524d8fb..620c753e3c5 100644
--- a/spec/frontend/notes/components/email_participants_warning_spec.js
+++ b/spec/frontend/notes/components/email_participants_warning_spec.js
@@ -1,10 +1,12 @@
import { mount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+
import EmailParticipantsWarning from '~/notes/components/email_participants_warning.vue';
describe('Email Participants Warning Component', () => {
let wrapper;
- const findMoreButton = () => wrapper.find('button');
+ const findMoreButton = () => wrapper.findComponent(GlButton);
const createWrapper = (emails) => {
wrapper = mount(EmailParticipantsWarning, {
@@ -48,7 +50,7 @@ describe('Email Participants Warning Component', () => {
describe('when more button clicked', () => {
beforeEach(() => {
- findMoreButton().trigger('click');
+ findMoreButton().vm.$emit('click');
});
it('more button no longer exists', () => {
diff --git a/spec/frontend/organizations/new/components/app_spec.js b/spec/frontend/organizations/new/components/app_spec.js
new file mode 100644
index 00000000000..2206a6f0384
--- /dev/null
+++ b/spec/frontend/organizations/new/components/app_spec.js
@@ -0,0 +1,103 @@
+import VueApollo from 'vue-apollo';
+import Vue, { nextTick } from 'vue';
+
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import App from '~/organizations/new/components/app.vue';
+import resolvers from '~/organizations/shared/graphql/resolvers';
+import NewEditForm from '~/organizations/shared/components/new_edit_form.vue';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { createOrganizationResponse } from '~/organizations/mock_data';
+import { createAlert } from '~/alert';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+
+Vue.use(VueApollo);
+jest.useFakeTimers();
+
+jest.mock('~/lib/utils/url_utility');
+jest.mock('~/alert');
+
+describe('OrganizationNewApp', () => {
+ let wrapper;
+ let mockApollo;
+
+ const createComponent = ({ mockResolvers = resolvers } = {}) => {
+ mockApollo = createMockApollo([], mockResolvers);
+
+ wrapper = shallowMountExtended(App, { apolloProvider: mockApollo });
+ };
+
+ const findForm = () => wrapper.findComponent(NewEditForm);
+ const submitForm = async () => {
+ findForm().vm.$emit('submit', { name: 'Foo bar', path: 'foo-bar' });
+ await nextTick();
+ };
+
+ afterEach(() => {
+ mockApollo = null;
+ });
+
+ it('renders form', () => {
+ createComponent();
+
+ expect(findForm().exists()).toBe(true);
+ });
+
+ describe('when form is submitted', () => {
+ describe('when API is loading', () => {
+ beforeEach(async () => {
+ const mockResolvers = {
+ Mutation: {
+ createOrganization: jest.fn().mockReturnValueOnce(new Promise(() => {})),
+ },
+ };
+
+ createComponent({ mockResolvers });
+
+ await submitForm();
+ });
+
+ it('sets `NewEditForm` `loading` prop to `true`', () => {
+ expect(findForm().props('loading')).toBe(true);
+ });
+ });
+
+ describe('when API request is successful', () => {
+ beforeEach(async () => {
+ createComponent();
+ await submitForm();
+ jest.runAllTimers();
+ await waitForPromises();
+ });
+
+ it('redirects user to organization path', () => {
+ expect(visitUrl).toHaveBeenCalledWith(createOrganizationResponse.organization.path);
+ });
+ });
+
+ describe('when API request is not successful', () => {
+ const error = new Error();
+
+ beforeEach(async () => {
+ const mockResolvers = {
+ Mutation: {
+ createOrganization: jest.fn().mockRejectedValueOnce(error),
+ },
+ };
+
+ createComponent({ mockResolvers });
+ await submitForm();
+ jest.runAllTimers();
+ await waitForPromises();
+ });
+
+ it('displays error alert', () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: 'An error occurred creating an organization. Please try again.',
+ error,
+ captureError: true,
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/organizations/shared/components/new_edit_form_spec.js b/spec/frontend/organizations/shared/components/new_edit_form_spec.js
new file mode 100644
index 00000000000..43c099fbb1c
--- /dev/null
+++ b/spec/frontend/organizations/shared/components/new_edit_form_spec.js
@@ -0,0 +1,112 @@
+import { GlButton, GlInputGroupText, GlTruncate } from '@gitlab/ui';
+
+import NewEditForm from '~/organizations/shared/components/new_edit_form.vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+
+describe('NewEditForm', () => {
+ let wrapper;
+
+ const defaultProvide = {
+ organizationsPath: '/-/organizations',
+ rootUrl: 'http://127.0.0.1:3000/',
+ };
+
+ const defaultPropsData = {
+ loading: false,
+ };
+
+ const createComponent = ({ propsData = {} } = {}) => {
+ wrapper = mountExtended(NewEditForm, {
+ attachTo: document.body,
+ provide: defaultProvide,
+ propsData: {
+ ...defaultPropsData,
+ ...propsData,
+ },
+ });
+ };
+
+ const findNameField = () => wrapper.findByLabelText('Organization name');
+ const findUrlField = () => wrapper.findByLabelText('Organization URL');
+ const submitForm = async () => {
+ await wrapper.findByRole('button', { name: 'Create organization' }).trigger('click');
+ };
+
+ it('renders `Organization name` field', () => {
+ createComponent();
+
+ expect(findNameField().exists()).toBe(true);
+ });
+
+ it('renders `Organization URL` field', () => {
+ createComponent();
+
+ expect(wrapper.findComponent(GlInputGroupText).findComponent(GlTruncate).props('text')).toBe(
+ 'http://127.0.0.1:3000/-/organizations/',
+ );
+ expect(findUrlField().exists()).toBe(true);
+ });
+
+ describe('when form is submitted without filling in required fields', () => {
+ beforeEach(async () => {
+ createComponent();
+ await submitForm();
+ });
+
+ it('shows error messages', () => {
+ expect(wrapper.findByText('Organization name is required.').exists()).toBe(true);
+ expect(wrapper.findByText('Organization URL is required.').exists()).toBe(true);
+ });
+ });
+
+ describe('when form is submitted successfully', () => {
+ beforeEach(async () => {
+ createComponent();
+
+ await findNameField().setValue('Foo bar');
+ await findUrlField().setValue('foo-bar');
+ await submitForm();
+ });
+
+ it('emits `submit` event with form values', () => {
+ expect(wrapper.emitted('submit')).toEqual([[{ name: 'Foo bar', path: 'foo-bar' }]]);
+ });
+ });
+
+ describe('when `Organization URL` has not been manually set', () => {
+ beforeEach(async () => {
+ createComponent();
+
+ await findNameField().setValue('Foo bar');
+ await submitForm();
+ });
+
+ it('sets `Organization URL` when typing in `Organization name`', () => {
+ expect(findUrlField().element.value).toBe('foo-bar');
+ });
+ });
+
+ describe('when `Organization URL` has been manually set', () => {
+ beforeEach(async () => {
+ createComponent();
+
+ await findUrlField().setValue('foo-bar-baz');
+ await findNameField().setValue('Foo bar');
+ await submitForm();
+ });
+
+ it('does not modify `Organization URL` when typing in `Organization name`', () => {
+ expect(findUrlField().element.value).toBe('foo-bar-baz');
+ });
+ });
+
+ describe('when `loading` prop is `true`', () => {
+ beforeEach(() => {
+ createComponent({ propsData: { loading: true } });
+ });
+
+ it('shows button with loading icon', () => {
+ expect(wrapper.findComponent(GlButton).props('loading')).toBe(true);
+ });
+ });
+});