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:
Diffstat (limited to 'spec/frontend/organizations')
-rw-r--r--spec/frontend/organizations/index/components/app_spec.js87
-rw-r--r--spec/frontend/organizations/index/components/organizations_list_item_spec.js70
-rw-r--r--spec/frontend/organizations/index/components/organizations_list_spec.js28
-rw-r--r--spec/frontend/organizations/index/components/organizations_view_spec.js57
-rw-r--r--spec/frontend/organizations/index/mock_data.js3
-rw-r--r--spec/frontend/organizations/new/components/app_spec.js113
-rw-r--r--spec/frontend/organizations/shared/components/new_edit_form_spec.js112
7 files changed, 470 insertions, 0 deletions
diff --git a/spec/frontend/organizations/index/components/app_spec.js b/spec/frontend/organizations/index/components/app_spec.js
new file mode 100644
index 00000000000..175b1e1c552
--- /dev/null
+++ b/spec/frontend/organizations/index/components/app_spec.js
@@ -0,0 +1,87 @@
+import { GlButton } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/alert';
+import { organizations } from '~/organizations/mock_data';
+import resolvers from '~/organizations/shared/graphql/resolvers';
+import organizationsQuery from '~/organizations/index/graphql/organizations.query.graphql';
+import OrganizationsIndexApp from '~/organizations/index/components/app.vue';
+import OrganizationsView from '~/organizations/index/components/organizations_view.vue';
+import { MOCK_NEW_ORG_URL } from '../mock_data';
+
+jest.mock('~/alert');
+
+Vue.use(VueApollo);
+
+describe('OrganizationsIndexApp', () => {
+ let wrapper;
+ let mockApollo;
+
+ const createComponent = (mockResolvers = resolvers) => {
+ mockApollo = createMockApollo([[organizationsQuery, mockResolvers]]);
+
+ wrapper = shallowMountExtended(OrganizationsIndexApp, {
+ apolloProvider: mockApollo,
+ provide: {
+ newOrganizationUrl: MOCK_NEW_ORG_URL,
+ },
+ });
+ };
+
+ afterEach(() => {
+ mockApollo = null;
+ });
+
+ const findOrganizationHeaderText = () => wrapper.findByText('Organizations');
+ const findNewOrganizationButton = () => wrapper.findComponent(GlButton);
+ const findOrganizationsView = () => wrapper.findComponent(OrganizationsView);
+
+ const loadingResolver = jest.fn().mockReturnValue(new Promise(() => {}));
+ const successfulResolver = (nodes) =>
+ jest.fn().mockResolvedValue({
+ data: { currentUser: { id: 1, organizations: { nodes } } },
+ });
+ const errorResolver = jest.fn().mockRejectedValue('error');
+
+ describe.each`
+ description | mockResolver | headerText | newOrgLink | loading | orgsData | error
+ ${'when API call is loading'} | ${loadingResolver} | ${true} | ${MOCK_NEW_ORG_URL} | ${true} | ${[]} | ${false}
+ ${'when API returns successful with results'} | ${successfulResolver(organizations)} | ${true} | ${MOCK_NEW_ORG_URL} | ${false} | ${organizations} | ${false}
+ ${'when API returns successful without results'} | ${successfulResolver([])} | ${false} | ${false} | ${false} | ${[]} | ${false}
+ ${'when API returns error'} | ${errorResolver} | ${false} | ${false} | ${false} | ${[]} | ${true}
+ `('$description', ({ mockResolver, headerText, newOrgLink, loading, orgsData, error }) => {
+ beforeEach(async () => {
+ createComponent(mockResolver);
+ await waitForPromises();
+ });
+
+ it(`does ${headerText ? '' : 'not '}render the header text`, () => {
+ expect(findOrganizationHeaderText().exists()).toBe(headerText);
+ });
+
+ it(`does ${newOrgLink ? '' : 'not '}render new organization button with correct link`, () => {
+ expect(
+ findNewOrganizationButton().exists() && findNewOrganizationButton().attributes('href'),
+ ).toBe(newOrgLink);
+ });
+
+ it(`renders the organizations view with ${loading} loading prop`, () => {
+ expect(findOrganizationsView().props('loading')).toBe(loading);
+ });
+
+ it(`renders the organizations view with ${
+ orgsData ? 'correct' : 'empty'
+ } organizations array prop`, () => {
+ expect(findOrganizationsView().props('organizations')).toStrictEqual(orgsData);
+ });
+
+ it(`does ${error ? '' : 'not '}render an error message`, () => {
+ return error
+ ? expect(createAlert).toHaveBeenCalled()
+ : expect(createAlert).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/organizations/index/components/organizations_list_item_spec.js b/spec/frontend/organizations/index/components/organizations_list_item_spec.js
new file mode 100644
index 00000000000..b3bff5ed517
--- /dev/null
+++ b/spec/frontend/organizations/index/components/organizations_list_item_spec.js
@@ -0,0 +1,70 @@
+import { GlAvatarLabeled } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import OrganizationsListItem from '~/organizations/index/components/organizations_list_item.vue';
+import { organizations } from '~/organizations/mock_data';
+
+const MOCK_ORGANIZATION = organizations[0];
+
+describe('OrganizationsListItem', () => {
+ let wrapper;
+
+ const defaultProps = {
+ organization: MOCK_ORGANIZATION,
+ };
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMountExtended(OrganizationsListItem, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ const findGlAvatarLabeled = () => wrapper.findComponent(GlAvatarLabeled);
+ const findHTMLOrganizationDescription = () =>
+ wrapper.findByTestId('organization-description-html');
+
+ describe('template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders GlAvatarLabeled with correct data', () => {
+ expect(findGlAvatarLabeled().attributes()).toMatchObject({
+ 'entity-id': getIdFromGraphQLId(MOCK_ORGANIZATION.id).toString(),
+ 'entity-name': MOCK_ORGANIZATION.name,
+ src: MOCK_ORGANIZATION.avatarUrl,
+ label: MOCK_ORGANIZATION.name,
+ labellink: MOCK_ORGANIZATION.webUrl,
+ });
+ });
+ });
+
+ describe('organization description', () => {
+ const descriptionHtml = '<p>Foo bar</p>';
+
+ describe('is a HTML description', () => {
+ beforeEach(() => {
+ createComponent({ organization: { ...MOCK_ORGANIZATION, descriptionHtml } });
+ });
+
+ it('renders HTML description', () => {
+ expect(findHTMLOrganizationDescription().html()).toContain(descriptionHtml);
+ });
+ });
+
+ describe('is not a HTML description', () => {
+ beforeEach(() => {
+ createComponent({
+ organization: { ...MOCK_ORGANIZATION, descriptionHtml: null },
+ });
+ });
+
+ it('does not render HTML description', () => {
+ expect(findHTMLOrganizationDescription().exists()).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/organizations/index/components/organizations_list_spec.js b/spec/frontend/organizations/index/components/organizations_list_spec.js
new file mode 100644
index 00000000000..0b59c212314
--- /dev/null
+++ b/spec/frontend/organizations/index/components/organizations_list_spec.js
@@ -0,0 +1,28 @@
+import { shallowMount } from '@vue/test-utils';
+import OrganizationsList from '~/organizations/index/components/organizations_list.vue';
+import OrganizationsListItem from '~/organizations/index/components/organizations_list_item.vue';
+import { organizations } from '~/organizations/mock_data';
+
+describe('OrganizationsList', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(OrganizationsList, {
+ propsData: {
+ organizations,
+ },
+ });
+ };
+
+ const findAllOrganizationsListItem = () => wrapper.findAllComponents(OrganizationsListItem);
+
+ describe('template', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders a list item for each organization', () => {
+ expect(findAllOrganizationsListItem()).toHaveLength(organizations.length);
+ });
+ });
+});
diff --git a/spec/frontend/organizations/index/components/organizations_view_spec.js b/spec/frontend/organizations/index/components/organizations_view_spec.js
new file mode 100644
index 00000000000..85a1c11a2b1
--- /dev/null
+++ b/spec/frontend/organizations/index/components/organizations_view_spec.js
@@ -0,0 +1,57 @@
+import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { organizations } from '~/organizations/mock_data';
+import OrganizationsView from '~/organizations/index/components/organizations_view.vue';
+import OrganizationsList from '~/organizations/index/components/organizations_list.vue';
+import { MOCK_NEW_ORG_URL, MOCK_ORG_EMPTY_STATE_SVG } from '../mock_data';
+
+describe('OrganizationsView', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(OrganizationsView, {
+ propsData: {
+ ...props,
+ },
+ provide: {
+ newOrganizationUrl: MOCK_NEW_ORG_URL,
+ organizationsEmptyStateSvgPath: MOCK_ORG_EMPTY_STATE_SVG,
+ },
+ });
+ };
+
+ const findGlLoading = () => wrapper.findComponent(GlLoadingIcon);
+ const findOrganizationsList = () => wrapper.findComponent(OrganizationsList);
+ const findGlEmptyState = () => wrapper.findComponent(GlEmptyState);
+
+ describe.each`
+ description | loading | orgsData | emptyStateSvg | emptyStateUrl
+ ${'when loading'} | ${true} | ${[]} | ${false} | ${false}
+ ${'when not loading and has organizations'} | ${false} | ${organizations} | ${false} | ${false}
+ ${'when not loading and has no organizations'} | ${false} | ${[]} | ${MOCK_ORG_EMPTY_STATE_SVG} | ${MOCK_NEW_ORG_URL}
+ `('$description', ({ loading, orgsData, emptyStateSvg, emptyStateUrl }) => {
+ beforeEach(() => {
+ createComponent({ loading, organizations: orgsData });
+ });
+
+ it(`does ${loading ? '' : 'not '}render loading icon`, () => {
+ expect(findGlLoading().exists()).toBe(loading);
+ });
+
+ it(`does ${orgsData.length ? '' : 'not '}render organizations list`, () => {
+ expect(findOrganizationsList().exists()).toBe(Boolean(orgsData.length));
+ });
+
+ it(`does ${emptyStateSvg ? '' : 'not '}render empty state with SVG`, () => {
+ expect(findGlEmptyState().exists() && findGlEmptyState().attributes('svgpath')).toBe(
+ emptyStateSvg,
+ );
+ });
+
+ it(`does ${emptyStateUrl ? '' : 'not '}render empty state with URL`, () => {
+ expect(
+ findGlEmptyState().exists() && findGlEmptyState().attributes('primarybuttonlink'),
+ ).toBe(emptyStateUrl);
+ });
+ });
+});
diff --git a/spec/frontend/organizations/index/mock_data.js b/spec/frontend/organizations/index/mock_data.js
new file mode 100644
index 00000000000..50b20b4f79c
--- /dev/null
+++ b/spec/frontend/organizations/index/mock_data.js
@@ -0,0 +1,3 @@
+export const MOCK_NEW_ORG_URL = 'gitlab.com/organizations/new';
+
+export const MOCK_ORG_EMPTY_STATE_SVG = 'illustrations/empty-state/empty-organizations-md.svg';
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..06d30ad6b12
--- /dev/null
+++ b/spec/frontend/organizations/new/components/app_spec.js
@@ -0,0 +1,113 @@
+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 { visitUrlWithAlerts } 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(visitUrlWithAlerts).toHaveBeenCalledWith(
+ createOrganizationResponse.organization.path,
+ [
+ {
+ id: 'organization-successfully-created',
+ title: 'Organization successfully created.',
+ message: 'You can now start using your new organization.',
+ variant: 'success',
+ },
+ ],
+ );
+ });
+ });
+
+ 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);
+ });
+ });
+});