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>2021-05-19 18:44:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 18:44:42 +0300
commit4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch)
tree5423a1c7516cffe36384133ade12572cf709398d /spec/frontend/pages
parente570267f2f6b326480d284e0164a6464ba4081bc (diff)
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'spec/frontend/pages')
-rw-r--r--spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap4
-rw-r--r--spec/frontend/pages/admin/users/components/delete_user_modal_spec.js21
-rw-r--r--spec/frontend/pages/projects/forks/new/components/fork_form_spec.js152
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap73
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap31
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap2
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js4
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js3
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/mock_data.js12
-rw-r--r--spec/frontend/pages/projects/new/components/app_spec.js77
-rw-r--r--spec/frontend/pages/projects/new/components/new_project_push_tip_popover_spec.js75
-rw-r--r--spec/frontend/pages/shared/wikis/components/wiki_form_spec.js269
12 files changed, 626 insertions, 97 deletions
diff --git a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
index 9f02e5b9432..4c644a0d05f 100644
--- a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
+++ b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap
@@ -8,6 +8,10 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
/>
</p>
+ <oncall-schedules-list-stub
+ schedules="schedule1,schedule2"
+ />
+
<p>
<gl-sprintf-stub
message="To confirm, type %{username}"
diff --git a/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js b/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js
index 318b6d16008..93d9ee43179 100644
--- a/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js
+++ b/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js
@@ -1,6 +1,7 @@
import { GlButton, GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import DeleteUserModal from '~/pages/admin/users/components/delete_user_modal.vue';
+import OncallSchedulesList from '~/vue_shared/components/oncall_schedules_list.vue';
import ModalStub from './stubs/modal_stub';
const TEST_DELETE_USER_URL = 'delete-url';
@@ -17,13 +18,14 @@ describe('User Operation confirmation modal', () => {
.filter((w) => w.attributes('variant') === variant && w.attributes('category') === category)
.at(0);
const findForm = () => wrapper.find('form');
- const findUsernameInput = () => wrapper.find(GlFormInput);
+ const findUsernameInput = () => wrapper.findComponent(GlFormInput);
const findPrimaryButton = () => findButton('danger', 'primary');
const findSecondaryButton = () => findButton('danger', 'secondary');
const findAuthenticityToken = () => new FormData(findForm().element).get('authenticity_token');
const getUsername = () => findUsernameInput().attributes('value');
const getMethodParam = () => new FormData(findForm().element).get('_method');
const getFormAction = () => findForm().attributes('action');
+ const findOnCallSchedulesList = () => wrapper.findComponent(OncallSchedulesList);
const setUsername = (username) => {
findUsernameInput().vm.$emit('input', username);
@@ -31,6 +33,7 @@ describe('User Operation confirmation modal', () => {
const username = 'username';
const badUsername = 'bad_username';
+ const oncallSchedules = '["schedule1", "schedule2"]';
const createComponent = (props = {}) => {
wrapper = shallowMount(DeleteUserModal, {
@@ -43,6 +46,7 @@ describe('User Operation confirmation modal', () => {
deleteUserUrl: TEST_DELETE_USER_URL,
blockUserUrl: TEST_BLOCK_USER_URL,
csrfToken: TEST_CSRF,
+ oncallSchedules,
...props,
},
stubs: {
@@ -145,4 +149,19 @@ describe('User Operation confirmation modal', () => {
});
});
});
+
+ describe('Related oncall-schedules list', () => {
+ it('does NOT render the list when user has no related schedules', () => {
+ createComponent({ oncallSchedules: '[]' });
+ expect(findOnCallSchedulesList().exists()).toBe(false);
+ });
+
+ it('renders the list when user has related schedules', () => {
+ createComponent();
+
+ const schedules = findOnCallSchedulesList();
+ expect(schedules.exists()).toBe(true);
+ expect(schedules.props('schedules')).toEqual(JSON.parse(oncallSchedules));
+ });
+ });
});
diff --git a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
index 2992c7f0624..6d853120232 100644
--- a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
+++ b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
@@ -1,5 +1,5 @@
-import { GlForm, GlFormInputGroup, GlFormInput } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlFormInputGroup, GlFormInput, GlForm } from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
import { kebabCase } from 'lodash';
@@ -43,8 +43,8 @@ describe('ForkForm component', () => {
axiosMock.onGet(DEFAULT_PROPS.endpoint).replyOnce(statusCode, data);
};
- const createComponent = (props = {}, data = {}) => {
- wrapper = shallowMount(ForkForm, {
+ const createComponentFactory = (mountFn) => (props = {}, data = {}) => {
+ wrapper = mountFn(ForkForm, {
provide: {
newGroupPath: 'some/groups/path',
visibilityHelpPath: 'some/visibility/help/path',
@@ -65,6 +65,9 @@ describe('ForkForm component', () => {
});
};
+ const createComponent = createComponentFactory(shallowMount);
+ const createFullComponent = createComponentFactory(mount);
+
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
window.gon = {
@@ -99,44 +102,6 @@ describe('ForkForm component', () => {
expect(cancelButton.attributes('href')).toBe(projectFullPath);
});
- it('make POST request with project param', async () => {
- jest.spyOn(axios, 'post');
-
- const namespaceId = 20;
-
- mockGetRequest();
- createComponent(
- {},
- {
- selectedNamespace: {
- id: namespaceId,
- },
- },
- );
-
- wrapper.find(GlForm).vm.$emit('submit', { preventDefault: () => {} });
-
- const {
- projectId,
- projectDescription,
- projectName,
- projectPath,
- projectVisibility,
- } = DEFAULT_PROPS;
-
- const url = `/api/${GON_API_VERSION}/projects/${projectId}/fork`;
- const project = {
- description: projectDescription,
- id: projectId,
- name: projectName,
- namespace_id: namespaceId,
- path: projectPath,
- visibility: projectVisibility,
- };
-
- expect(axios.post).toHaveBeenCalledWith(url, project);
- });
-
it('has input with csrf token', () => {
mockGetRequest();
createComponent();
@@ -258,9 +223,7 @@ describe('ForkForm component', () => {
projectVisibility: project,
},
{
- selectedNamespace: {
- visibility: namespace,
- },
+ form: { fields: { namespace: { value: { visibility: namespace } } } },
},
);
@@ -274,34 +237,101 @@ describe('ForkForm component', () => {
describe('onSubmit', () => {
beforeEach(() => {
jest.spyOn(urlUtility, 'redirectTo').mockImplementation();
+
+ mockGetRequest();
+ createFullComponent(
+ {},
+ {
+ namespaces: MOCK_NAMESPACES_RESPONSE,
+ form: {
+ state: true,
+ },
+ },
+ );
});
- it('redirect to POST web_url response', async () => {
- const webUrl = `new/fork-project`;
+ const selectedMockNamespaceIndex = 1;
+ const namespaceId = MOCK_NAMESPACES_RESPONSE[selectedMockNamespaceIndex].id;
- jest.spyOn(axios, 'post').mockResolvedValue({ data: { web_url: webUrl } });
+ const fillForm = async () => {
+ const namespaceOptions = findForkUrlInput().findAll('option');
- mockGetRequest();
- createComponent();
+ await namespaceOptions.at(selectedMockNamespaceIndex + 1).setSelected();
+ };
- await wrapper.vm.onSubmit();
+ const submitForm = async () => {
+ await fillForm();
+ const form = wrapper.find(GlForm);
- expect(urlUtility.redirectTo).toHaveBeenCalledWith(webUrl);
+ await form.trigger('submit');
+ await wrapper.vm.$nextTick();
+ };
+
+ describe('with invalid form', () => {
+ it('does not make POST request', async () => {
+ jest.spyOn(axios, 'post');
+
+ expect(axios.post).not.toHaveBeenCalled();
+ });
+
+ it('does not redirect the current page', async () => {
+ await submitForm();
+
+ expect(urlUtility.redirectTo).not.toHaveBeenCalled();
+ });
});
- it('display flash when POST is unsuccessful', async () => {
- const dummyError = 'Fork project failed';
+ describe('with valid form', () => {
+ beforeEach(() => {
+ fillForm();
+ });
- jest.spyOn(axios, 'post').mockRejectedValue(dummyError);
+ it('make POST request with project param', async () => {
+ jest.spyOn(axios, 'post');
+
+ await submitForm();
+
+ const {
+ projectId,
+ projectDescription,
+ projectName,
+ projectPath,
+ projectVisibility,
+ } = DEFAULT_PROPS;
+
+ const url = `/api/${GON_API_VERSION}/projects/${projectId}/fork`;
+ const project = {
+ description: projectDescription,
+ id: projectId,
+ name: projectName,
+ namespace_id: namespaceId,
+ path: projectPath,
+ visibility: projectVisibility,
+ };
- mockGetRequest();
- createComponent();
+ expect(axios.post).toHaveBeenCalledWith(url, project);
+ });
+
+ it('redirect to POST web_url response', async () => {
+ const webUrl = `new/fork-project`;
+ jest.spyOn(axios, 'post').mockResolvedValue({ data: { web_url: webUrl } });
+
+ await submitForm();
+
+ expect(urlUtility.redirectTo).toHaveBeenCalledWith(webUrl);
+ });
+
+ it('display flash when POST is unsuccessful', async () => {
+ const dummyError = 'Fork project failed';
+
+ jest.spyOn(axios, 'post').mockRejectedValue(dummyError);
- await wrapper.vm.onSubmit();
+ await submitForm();
- expect(urlUtility.redirectTo).not.toHaveBeenCalled();
- expect(createFlash).toHaveBeenCalledWith({
- message: dummyError,
+ expect(urlUtility.redirectTo).not.toHaveBeenCalled();
+ expect(createFlash).toHaveBeenCalledWith({
+ message: dummyError,
+ });
});
});
});
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap
index 8b54a06ac7c..350669433f0 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap
+++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap
@@ -44,9 +44,7 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
class="progress-bar"
role="progressbar"
style="width: 22.22222222222222%;"
- >
- <!---->
- </div>
+ />
</div>
</div>
@@ -68,7 +66,7 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
class="learn-gitlab-section-card-header"
>
<img
- src="/assets/learn_gitlab/section_workspace.svg"
+ src="workspace.svg"
/>
<h2
@@ -134,9 +132,16 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
<span>
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Set up CI/CD"
+ data-track-property="Growth::Conversion::Experiment::LearnGitLabA"
href="http://example.com/"
+ rel="noopener noreferrer"
+ target="_blank"
>
- Set up CI/CD
+
+ Set up CI/CD
+
</a>
</span>
@@ -148,9 +153,16 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
<span>
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Start a free Ultimate trial"
+ data-track-property="Growth::Conversion::Experiment::LearnGitLabA"
href="http://example.com/"
+ rel="noopener noreferrer"
+ target="_blank"
>
- Start a free Ultimate trial
+
+ Start a free Ultimate trial
+
</a>
</span>
@@ -162,9 +174,16 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
<span>
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Add code owners"
+ data-track-property="Growth::Conversion::Experiment::LearnGitLabA"
href="http://example.com/"
+ rel="noopener noreferrer"
+ target="_blank"
>
- Add code owners
+
+ Add code owners
+
</a>
</span>
@@ -183,9 +202,16 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
<span>
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Add merge request approval"
+ data-track-property="Growth::Conversion::Experiment::LearnGitLabA"
href="http://example.com/"
+ rel="noopener noreferrer"
+ target="_blank"
>
- Add merge request approval
+
+ Add merge request approval
+
</a>
</span>
@@ -218,7 +244,7 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
class="learn-gitlab-section-card-header"
>
<img
- src="/assets/learn_gitlab/section_plan.svg"
+ src="plan.svg"
/>
<h2
@@ -240,9 +266,16 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
<span>
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Create an issue"
+ data-track-property="Growth::Conversion::Experiment::LearnGitLabA"
href="http://example.com/"
+ rel="noopener noreferrer"
+ target="_blank"
>
- Create an issue
+
+ Create an issue
+
</a>
</span>
@@ -254,9 +287,16 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
<span>
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Submit a merge request"
+ data-track-property="Growth::Conversion::Experiment::LearnGitLabA"
href="http://example.com/"
+ rel="noopener noreferrer"
+ target="_blank"
>
- Submit a merge request
+
+ Submit a merge request
+
</a>
</span>
@@ -282,7 +322,7 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
class="learn-gitlab-section-card-header"
>
<img
- src="/assets/learn_gitlab/section_deploy.svg"
+ src="deploy.svg"
/>
<h2
@@ -304,9 +344,16 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
<span>
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Run a Security scan using CI/CD"
+ data-track-property="Growth::Conversion::Experiment::LearnGitLabA"
href="http://example.com/"
+ rel="noopener noreferrer"
+ target="_blank"
>
- Run a Security scan using CI/CD
+
+ Run a Security scan using CI/CD
+
</a>
</span>
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap
index 07c7f2df09e..c9d8ab4566c 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap
+++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap
@@ -44,9 +44,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
class="progress-bar"
role="progressbar"
style="width: 22.22222222222222%;"
- >
- <!---->
- </div>
+ />
</div>
</div>
@@ -110,6 +108,9 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Invite your colleagues"
+ data-track-property="Growth::Activation::Experiment::LearnGitLabB"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
@@ -168,6 +169,9 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Create or import a repository"
+ data-track-property="Growth::Activation::Experiment::LearnGitLabB"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
@@ -218,6 +222,9 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Set-up CI/CD"
+ data-track-property="Growth::Activation::Experiment::LearnGitLabB"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
@@ -268,6 +275,9 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Try GitLab Ultimate for free"
+ data-track-property="Growth::Activation::Experiment::LearnGitLabB"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
@@ -323,6 +333,9 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Add code owners"
+ data-track-property="Growth::Activation::Experiment::LearnGitLabB"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
@@ -378,6 +391,9 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Enable require merge approvals"
+ data-track-property="Growth::Activation::Experiment::LearnGitLabB"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
@@ -444,6 +460,9 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Create an issue"
+ data-track-property="Growth::Activation::Experiment::LearnGitLabB"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
@@ -494,6 +513,9 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Submit a merge request (MR)"
+ data-track-property="Growth::Activation::Experiment::LearnGitLabB"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
@@ -560,6 +582,9 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
<a
class="gl-link"
+ data-track-action="click_link"
+ data-track-label="Run a Security scan using CI/CD"
+ data-track-property="Growth::Activation::Experiment::LearnGitLabB"
href="http://example.com/"
rel="noopener noreferrer"
target="_blank"
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap
index ad8db0822cc..9e00ace761c 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap
+++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap
@@ -11,7 +11,7 @@ exports[`Learn GitLab Section Card renders correctly 1`] = `
class="learn-gitlab-section-card-header"
>
<img
- src="/assets/learn_gitlab/section_workspace.svg"
+ src="workspace.svg"
/>
<h2
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js
index 64ace341038..ac997c1f237 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js
@@ -1,13 +1,13 @@
import { GlProgressBar } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import LearnGitlabA from '~/pages/projects/learn_gitlab/components/learn_gitlab_a.vue';
-import { testActions } from './mock_data';
+import { testActions, testSections } from './mock_data';
describe('Learn GitLab Design A', () => {
let wrapper;
const createWrapper = () => {
- wrapper = mount(LearnGitlabA, { propsData: { actions: testActions } });
+ wrapper = mount(LearnGitlabA, { propsData: { actions: testActions, sections: testSections } });
};
beforeEach(() => {
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js
index de6aca08235..3a511a009a9 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js
@@ -3,6 +3,7 @@ import LearnGitlabSectionCard from '~/pages/projects/learn_gitlab/components/lea
import { testActions } from './mock_data';
const defaultSection = 'workspace';
+const testImage = 'workspace.svg';
describe('Learn GitLab Section Card', () => {
let wrapper;
@@ -14,7 +15,7 @@ describe('Learn GitLab Section Card', () => {
const createWrapper = () => {
wrapper = shallowMount(LearnGitlabSectionCard, {
- propsData: { section: defaultSection, actions: testActions },
+ propsData: { section: defaultSection, actions: testActions, svg: testImage },
});
};
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js b/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
index d6ee2b00c8e..8d6ac737db8 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
@@ -45,3 +45,15 @@ export const testActions = {
svg: 'http://example.com/images/illustration.svg',
},
};
+
+export const testSections = {
+ workspace: {
+ svg: 'workspace.svg',
+ },
+ deploy: {
+ svg: 'deploy.svg',
+ },
+ plan: {
+ svg: 'plan.svg',
+ },
+};
diff --git a/spec/frontend/pages/projects/new/components/app_spec.js b/spec/frontend/pages/projects/new/components/app_spec.js
new file mode 100644
index 00000000000..b604e636243
--- /dev/null
+++ b/spec/frontend/pages/projects/new/components/app_spec.js
@@ -0,0 +1,77 @@
+import { shallowMount } from '@vue/test-utils';
+import { assignGitlabExperiment } from 'helpers/experimentation_helper';
+import App from '~/pages/projects/new/components/app.vue';
+import NewNamespacePage from '~/vue_shared/new_namespace/new_namespace_page.vue';
+
+describe('Experimental new project creation app', () => {
+ let wrapper;
+
+ const findNewNamespacePage = () => wrapper.findComponent(NewNamespacePage);
+
+ const createComponent = (propsData) => {
+ wrapper = shallowMount(App, { propsData });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('new_repo experiment', () => {
+ it('passes new_repo experiment', () => {
+ createComponent();
+
+ expect(findNewNamespacePage().props().experiment).toBe('new_repo');
+ });
+
+ describe('when in the candidate variant', () => {
+ assignGitlabExperiment('new_repo', 'candidate');
+
+ it('has "repository" in the panel title', () => {
+ createComponent();
+
+ expect(findNewNamespacePage().props().panels[0].title).toBe(
+ 'Create blank project/repository',
+ );
+ });
+ });
+
+ describe('when in the control variant', () => {
+ assignGitlabExperiment('new_repo', 'control');
+
+ it('has "project" in the panel title', () => {
+ createComponent();
+
+ expect(findNewNamespacePage().props().panels[0].title).toBe('Create blank project');
+ });
+ });
+ });
+
+ it('passes custom new project guideline text to underlying component', () => {
+ const DEMO_GUIDELINES = 'Demo guidelines';
+ const guidelineSelector = '#new-project-guideline';
+ createComponent({
+ newProjectGuidelines: DEMO_GUIDELINES,
+ });
+
+ expect(wrapper.find(guidelineSelector).text()).toBe(DEMO_GUIDELINES);
+ });
+
+ it.each`
+ isCiCdAvailable | outcome
+ ${false} | ${'do not show CI/CD panel'}
+ ${true} | ${'show CI/CD panel'}
+ `('$outcome when isCiCdAvailable is $isCiCdAvailable', ({ isCiCdAvailable }) => {
+ createComponent({
+ isCiCdAvailable,
+ });
+
+ expect(
+ Boolean(
+ wrapper
+ .findComponent(NewNamespacePage)
+ .props()
+ .panels.find((p) => p.name === 'cicd_for_external_repo'),
+ ),
+ ).toBe(isCiCdAvailable);
+ });
+});
diff --git a/spec/frontend/pages/projects/new/components/new_project_push_tip_popover_spec.js b/spec/frontend/pages/projects/new/components/new_project_push_tip_popover_spec.js
new file mode 100644
index 00000000000..d4cf8c78600
--- /dev/null
+++ b/spec/frontend/pages/projects/new/components/new_project_push_tip_popover_spec.js
@@ -0,0 +1,75 @@
+import { GlPopover, GlFormInputGroup } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import NewProjectPushTipPopover from '~/pages/projects/new/components/new_project_push_tip_popover.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+
+describe('New project push tip popover', () => {
+ let wrapper;
+ const targetId = 'target';
+ const pushToCreateProjectCommand = 'command';
+ const workingWithProjectsHelpPath = 'path';
+
+ const findPopover = () => wrapper.findComponent(GlPopover);
+ const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
+ const findFormInput = () => wrapper.findComponent(GlFormInputGroup);
+ const findHelpLink = () => wrapper.find('a');
+ const findTarget = () => document.getElementById(targetId);
+
+ const buildWrapper = () => {
+ wrapper = shallowMount(NewProjectPushTipPopover, {
+ propsData: {
+ target: findTarget(),
+ },
+ stubs: {
+ GlFormInputGroup,
+ },
+ provide: {
+ pushToCreateProjectCommand,
+ workingWithProjectsHelpPath,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ setFixtures(`<a id="${targetId}"></a>`);
+ buildWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders popover that targets the specified target', () => {
+ expect(findPopover().props()).toMatchObject({
+ target: findTarget(),
+ triggers: 'click blur',
+ placement: 'top',
+ title: 'Push to create a project',
+ });
+ });
+
+ it('renders a readonly form input with the push to create command', () => {
+ expect(findFormInput().props()).toMatchObject({
+ value: pushToCreateProjectCommand,
+ selectOnClick: true,
+ });
+ expect(findFormInput().attributes()).toMatchObject({
+ 'aria-label': 'Push project from command line',
+ readonly: 'readonly',
+ });
+ });
+
+ it('allows copying the push command using the clipboard button', () => {
+ expect(findClipboardButton().props()).toMatchObject({
+ text: pushToCreateProjectCommand,
+ tooltipPlacement: 'right',
+ title: 'Copy command',
+ });
+ });
+
+ it('displays a link to open the push command help page reference', () => {
+ expect(findHelpLink().attributes().href).toBe(
+ `${workingWithProjectsHelpPath}#push-to-create-a-new-project`,
+ );
+ });
+});
diff --git a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
index 8ab0b87d2ee..1cac8ef8ee2 100644
--- a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
+++ b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
@@ -1,9 +1,16 @@
+import { GlLoadingIcon, GlModal } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import ContentEditor from '~/content_editor/components/content_editor.vue';
import WikiForm from '~/pages/shared/wikis/components/wiki_form.vue';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
describe('WikiForm', () => {
let wrapper;
+ let mock;
const findForm = () => wrapper.find('form');
const findTitle = () => wrapper.find('#wiki_title');
@@ -11,10 +18,28 @@ describe('WikiForm', () => {
const findContent = () => wrapper.find('#wiki_content');
const findMessage = () => wrapper.find('#wiki_message');
const findSubmitButton = () => wrapper.findByTestId('wiki-submit-button');
- const findCancelButton = () => wrapper.findByTestId('wiki-cancel-button');
- const findTitleHelpLink = () => wrapper.findByTestId('wiki-title-help-link');
+ const findCancelButton = () => wrapper.findByRole('link', { name: 'Cancel' });
+ const findUseNewEditorButton = () => wrapper.findByRole('button', { name: 'Use new editor' });
+ const findSwitchToOldEditorButton = () =>
+ wrapper.findByRole('button', { name: 'Switch to old editor' });
+ const findTitleHelpLink = () => wrapper.findByRole('link', { name: 'More Information.' });
const findMarkdownHelpLink = () => wrapper.findByTestId('wiki-markdown-help-link');
+ const setFormat = (value) => {
+ const format = findFormat();
+ format.find(`option[value=${value}]`).setSelected();
+ format.element.dispatchEvent(new Event('change'));
+ };
+
+ const triggerFormSubmit = () => findForm().element.dispatchEvent(new Event('submit'));
+
+ const dispatchBeforeUnload = () => {
+ const e = new Event('beforeunload');
+ jest.spyOn(e, 'preventDefault');
+ window.dispatchEvent(e);
+ return e;
+ };
+
const pageInfoNew = {
persisted: false,
uploadsPath: '/project/path/-/wikis/attachments',
@@ -35,7 +60,10 @@ describe('WikiForm', () => {
path: '/project/path/-/wikis/home',
};
- function createWrapper(persisted = false, pageInfo = {}) {
+ function createWrapper(
+ persisted = false,
+ { pageInfo, glFeatures } = { glFeatures: { wikiContentEditor: false } },
+ ) {
wrapper = extendedWrapper(
mount(
WikiForm,
@@ -51,16 +79,20 @@ describe('WikiForm', () => {
...(persisted ? pageInfoPersisted : pageInfoNew),
...pageInfo,
},
+ glFeatures,
},
},
{ attachToDocument: true },
),
);
-
- jest.spyOn(wrapper.vm, 'onBeforeUnload');
}
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
afterEach(() => {
+ mock.restore();
wrapper.destroy();
wrapper = null;
});
@@ -101,7 +133,7 @@ describe('WikiForm', () => {
`('updates the link help message when format=$value is selected', async ({ value, text }) => {
createWrapper();
- findFormat().find(`option[value=${value}]`).setSelected();
+ setFormat(value);
await wrapper.vm.$nextTick();
@@ -113,9 +145,9 @@ describe('WikiForm', () => {
await wrapper.vm.$nextTick();
- window.dispatchEvent(new Event('beforeunload'));
-
- expect(wrapper.vm.onBeforeUnload).not.toHaveBeenCalled();
+ const e = dispatchBeforeUnload();
+ expect(typeof e.returnValue).not.toBe('string');
+ expect(e.preventDefault).not.toHaveBeenCalled();
});
it.each`
@@ -156,19 +188,18 @@ describe('WikiForm', () => {
});
it('sets before unload warning', () => {
- window.dispatchEvent(new Event('beforeunload'));
+ const e = dispatchBeforeUnload();
- expect(wrapper.vm.onBeforeUnload).toHaveBeenCalled();
+ expect(e.preventDefault).toHaveBeenCalledTimes(1);
});
it('when form submitted, unsets before unload warning', async () => {
- findForm().element.dispatchEvent(new Event('submit'));
+ triggerFormSubmit();
await wrapper.vm.$nextTick();
- window.dispatchEvent(new Event('beforeunload'));
-
- expect(wrapper.vm.onBeforeUnload).not.toHaveBeenCalled();
+ const e = dispatchBeforeUnload();
+ expect(e.preventDefault).not.toHaveBeenCalled();
});
});
@@ -219,4 +250,212 @@ describe('WikiForm', () => {
},
);
});
+
+ describe('when feature flag wikiContentEditor is enabled', () => {
+ beforeEach(() => {
+ createWrapper(true, { glFeatures: { wikiContentEditor: true } });
+ });
+
+ it.each`
+ format | buttonExists
+ ${'markdown'} | ${true}
+ ${'rdoc'} | ${false}
+ `(
+ 'switch to new editor button exists: $buttonExists if format is $format',
+ async ({ format, buttonExists }) => {
+ setFormat(format);
+
+ await wrapper.vm.$nextTick();
+
+ expect(findUseNewEditorButton().exists()).toBe(buttonExists);
+ },
+ );
+
+ const assertOldEditorIsVisible = () => {
+ expect(wrapper.findComponent(ContentEditor).exists()).toBe(false);
+ expect(wrapper.findComponent(MarkdownField).exists()).toBe(true);
+ expect(findSubmitButton().props('disabled')).toBe(false);
+
+ expect(wrapper.text()).not.toContain(
+ "Switching will discard any changes you've made in the new editor.",
+ );
+ expect(wrapper.text()).not.toContain(
+ "This editor is in beta and may not display the page's contents properly.",
+ );
+ };
+
+ it('shows old editor by default', assertOldEditorIsVisible);
+
+ describe('switch format to rdoc', () => {
+ beforeEach(async () => {
+ setFormat('rdoc');
+
+ await wrapper.vm.$nextTick();
+ });
+
+ it('continues to show the old editor', assertOldEditorIsVisible);
+
+ describe('switch format back to markdown', () => {
+ beforeEach(async () => {
+ setFormat('rdoc');
+
+ await wrapper.vm.$nextTick();
+ });
+
+ it(
+ 'still shows the old editor and does not automatically switch to the content editor ',
+ assertOldEditorIsVisible,
+ );
+ });
+ });
+
+ describe('clicking "use new editor": editor fails to load', () => {
+ beforeEach(async () => {
+ mock.onPost(/preview-markdown/).reply(400);
+
+ await findUseNewEditorButton().trigger('click');
+
+ // try waiting for content editor to load (but it will never actually load)
+ await waitForPromises();
+ });
+
+ it('editor is shown in a perpetual loading state', () => {
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(ContentEditor).exists()).toBe(false);
+ });
+
+ it('disables the submit button', () => {
+ expect(findSubmitButton().props('disabled')).toBe(true);
+ });
+
+ describe('clicking "switch to old editor"', () => {
+ beforeEach(() => {
+ return findSwitchToOldEditorButton().trigger('click');
+ });
+
+ it('switches to old editor directly without showing a modal', () => {
+ expect(wrapper.findComponent(ContentEditor).exists()).toBe(false);
+ expect(wrapper.findComponent(MarkdownField).exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('clicking "use new editor": editor loads successfully', () => {
+ beforeEach(() => {
+ mock.onPost(/preview-markdown/).reply(200, { body: '<p>hello <strong>world</strong></p>' });
+
+ findUseNewEditorButton().trigger('click');
+ });
+
+ it('shows a loading indicator for the rich text editor', () => {
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ it('shows warnings that the rich text editor is in beta and may not work properly', () => {
+ expect(wrapper.text()).toContain(
+ "Switching will discard any changes you've made in the new editor.",
+ );
+ expect(wrapper.text()).toContain(
+ "This editor is in beta and may not display the page's contents properly.",
+ );
+ });
+
+ it('shows the rich text editor when loading finishes', async () => {
+ // wait for content editor to load
+ await waitForPromises();
+
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.findComponent(ContentEditor).exists()).toBe(true);
+ });
+
+ it('disables the format dropdown', () => {
+ expect(findFormat().element.getAttribute('disabled')).toBeDefined();
+ });
+
+ describe('when wiki content is updated', () => {
+ beforeEach(async () => {
+ // wait for content editor to load
+ await waitForPromises();
+
+ wrapper.vm.contentEditor.tiptapEditor.commands.setContent(
+ '<p>hello __world__ from content editor</p>',
+ true,
+ );
+
+ return wrapper.vm.$nextTick();
+ });
+
+ it('sets before unload warning', () => {
+ const e = dispatchBeforeUnload();
+ expect(e.preventDefault).toHaveBeenCalledTimes(1);
+ });
+
+ it('unsets before unload warning on form submit', async () => {
+ triggerFormSubmit();
+
+ await wrapper.vm.$nextTick();
+
+ const e = dispatchBeforeUnload();
+ expect(e.preventDefault).not.toHaveBeenCalled();
+ });
+ });
+
+ it('updates content from content editor on form submit', async () => {
+ // old value
+ expect(findContent().element.value).toBe('My page content');
+
+ // wait for content editor to load
+ await waitForPromises();
+
+ triggerFormSubmit();
+
+ await wrapper.vm.$nextTick();
+
+ expect(findContent().element.value).toBe('hello **world**');
+ });
+
+ describe('clicking "switch to old editor"', () => {
+ let modal;
+
+ beforeEach(async () => {
+ modal = wrapper.findComponent(GlModal);
+ jest.spyOn(modal.vm, 'show');
+
+ findSwitchToOldEditorButton().trigger('click');
+ });
+
+ it('shows a modal confirming the change', () => {
+ expect(modal.vm.show).toHaveBeenCalled();
+ });
+
+ describe('confirming "switch to old editor" in the modal', () => {
+ beforeEach(async () => {
+ wrapper.vm.contentEditor.tiptapEditor.commands.setContent(
+ '<p>hello __world__ from content editor</p>',
+ true,
+ );
+
+ wrapper.findComponent(GlModal).vm.$emit('primary');
+
+ await wrapper.vm.$nextTick();
+ });
+
+ it('switches to old editor', () => {
+ expect(wrapper.findComponent(ContentEditor).exists()).toBe(false);
+ expect(wrapper.findComponent(MarkdownField).exists()).toBe(true);
+ });
+
+ it('does not show a warning about content editor', () => {
+ expect(wrapper.text()).not.toContain(
+ "This editor is in beta and may not display the page's contents properly.",
+ );
+ });
+
+ it('the old editor retains its old value and does not use the content from the content editor', () => {
+ expect(findContent().element.value).toBe('My page content');
+ });
+ });
+ });
+ });
+ });
});