Skip to content
Snippets Groups Projects
Commit 004b162a authored by Jacques Erasmus's avatar Jacques Erasmus :speech_balloon:
Browse files

Merge branch '372453-convert-vanilla-js-to-vue-component' into 'master'

parents 777c74c9 1d03be0c
No related branches found
No related tags found
2 merge requests!122597doc/gitaly: Remove references to removed metrics,!116808Convert group project form to vue
Pipeline #849318087 canceled
Showing
with 728 additions and 266 deletions
import mountComponents from 'ee/registrations/groups_projects/new';
import Group from '~/group';
import { trackCombinedGroupProjectForm, trackProjectImport } from '~/google_tag_manager';
// eslint-disable-next-line no-new
new Group();
mountComponents();
trackCombinedGroupProjectForm();
trackProjectImport();
<script>
import { GlFormGroup, GlFormInput, GlTooltipDirective } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { debounce } from 'lodash';
import { mapState, mapActions } from 'vuex';
import { createAlert } from '~/alert';
import { getGroupPathAvailability } from '~/rest_api';
import { __, s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import { slugify, convertUnicodeToAscii } from '~/lib/utils/text_utility';
import { DEFAULT_GROUP_PATH, DEFAULT_PROJECT_PATH } from '../constants';
const DEBOUNCE_TIMEOUT_DURATION = 1000;
export default {
components: {
GlFormGroup,
GlFormInput,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
importGroup: {
type: Boolean,
required: true,
},
groupPersisted: {
type: Boolean,
required: true,
},
groupId: {
type: String,
required: true,
},
groupName: {
type: String,
required: true,
},
projectName: {
type: String,
required: true,
},
rootUrl: {
type: String,
required: true,
},
},
data() {
return {
projectPath: DEFAULT_PROJECT_PATH,
currentApiRequestController: null,
groupPathWithoutSuggestion: null,
};
},
computed: {
...mapState(['storeGroupName', 'storeGroupPath']),
placement() {
return bp.getBreakpointSize() === 'xs' ? 'bottom' : 'right';
},
urlGroupPath() {
// for persisted group we should not show suggestions but just slugify group name
return this.groupPersisted && !this.importGroup
? this.groupPathWithoutSuggestion
: this.storeGroupPath;
},
},
mounted() {
if (this.groupName) {
this.groupPathWithoutSuggestion = slugify(this.groupName);
this.onGroupUpdate(this.groupName);
}
if (this.projectName) {
this.onProjectUpdate(this.projectName);
}
},
methods: {
...mapActions(['setStoreGroupName', 'setStoreGroupPath']),
groupInputAttr(name) {
return `${this.importGroup ? 'import_' : ''}${name}`;
},
setSuggestedSlug(slug) {
if (this.currentApiRequestController !== null) {
this.currentApiRequestController.abort();
}
this.currentApiRequestController = new AbortController();
// parent ID always undefined because it's a sign up page and a new group
return getGroupPathAvailability(slug, undefined, {
signal: this.currentApiRequestController.signal,
})
.then(({ data }) => data)
.then(({ exists, suggests }) => {
this.currentApiRequestController = null;
if (exists && suggests.length) {
const [suggestedSlug] = suggests;
this.setStoreGroupPath(suggestedSlug);
} else if (exists && !suggests.length) {
createAlert({
message: s__('ProjectsNew|Unable to suggest a path. Please refresh and try again.'),
});
}
})
.catch((error) => {
if (axios.isCancel(error)) return;
createAlert({
message: s__(
'ProjectsNew|An error occurred while checking group path. Please refresh and try again.',
),
});
});
},
debouncedOnGroupUpdate: debounce(function debouncedUpdate(slug) {
this.setSuggestedSlug(slug);
}, DEBOUNCE_TIMEOUT_DURATION),
onGroupUpdate(value) {
const slug = slugify(value);
this.setStoreGroupName(value);
if (!slug) return this.setStoreGroupPath(DEFAULT_GROUP_PATH);
this.setStoreGroupPath(slug);
return this.debouncedOnGroupUpdate(slug);
},
onProjectUpdate(value) {
this.projectPath = slugify(convertUnicodeToAscii(value)) || DEFAULT_PROJECT_PATH;
},
},
i18n: {
groupNameLabel: s__('ProjectsNew|Group name'),
projectNameLabel: s__('ProjectsNew|Project name'),
tooltipTitle: s__('ProjectsNew|Projects are organized into groups'),
urlHeader: s__('ProjectsNew|Your project will be created at:'),
urlFooter: s__('ProjectsNew|You can always change your URL later'),
urlSlash: __('/'),
},
};
</script>
<template>
<div>
<div class="row">
<gl-form-group
class="group-name-holder col-sm-12"
:label="$options.i18n.groupNameLabel"
label-for="group_name"
>
<gl-form-input
v-if="groupPersisted && !importGroup"
id="group_name"
disabled
name="group[name]"
data-testid="persisted-group-name"
:value="groupName"
/>
<gl-form-input
v-if="groupPersisted && !importGroup"
id="group_id"
hidden
name="group[id]"
autocomplete="off"
:value="groupId"
/>
<gl-form-input
v-if="!groupPersisted || importGroup"
:id="groupInputAttr('group_name')"
v-gl-tooltip="{ placement, title: $options.i18n.tooltipTitle }"
required
autofocus
class="js-group-name-field"
name="group[name]"
data-testid="group-name"
data-placement="right"
data-show="true"
:data-qa-selector="groupInputAttr('group_name_field')"
:value="groupName || storeGroupName"
@update="onGroupUpdate"
/>
<gl-form-input
v-if="!groupPersisted || importGroup"
:id="groupInputAttr('group_path')"
hidden
name="group[path]"
autocomplete="off"
:value="storeGroupPath"
/>
</gl-form-group>
</div>
<div v-if="!importGroup" id="blank-project-name" class="row">
<gl-form-group
class="project-name col-sm-12"
:label="$options.i18n.projectNameLabel"
label-for="project_name"
>
<gl-form-input
id="blank_project_name"
required
name="project[name]"
data-testid="project-name"
data-track-label="blank_project"
data-track-action="activate_form_input"
data-track-property="project_name"
data-track-value=""
data-qa-selector="project_name_field"
:value="projectName"
@update="onProjectUpdate"
/>
</gl-form-group>
</div>
<p class="form-text gl-text-center">{{ $options.i18n.urlHeader }}</p>
<p class="form-text gl-text-center monospace gl-overflow-wrap-break">
{{ rootUrl }}<span data-testid="url-group-path">{{ urlGroupPath }}</span
><span>{{ $options.i18n.urlSlash }}</span
><span data-testid="url-project-path">{{ projectPath }}</span>
</p>
<p class="form-text text-muted gl-text-center gl-mb-5!">
{{ $options.i18n.urlFooter }}
</p>
</div>
</template>
import { __ } from '~/locale';
export const DEFAULT_GROUP_PATH = __('{group}');
export const DEFAULT_PROJECT_PATH = __('{project}');
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import $ from 'jquery';
import Vue from 'vue';
import GlFieldErrors from '~/gl_field_errors';
import { parseBoolean } from '~/lib/utils/common_utils';
import { bindHowToImport } from '~/projects/project_new';
import { displayGroupPath, displayProjectPath } from './path_display';
import showTooltip from './show_tooltip';
import GroupProjectFields from './components/group_project_fields.vue';
import createStore from './store';
const importButtonsSubmit = () => {
const buttons = document.querySelectorAll('.js-import-project-buttons a');
......@@ -31,14 +33,45 @@ const setAutofocus = () => {
$('.js-group-project-tabs').on('shown.bs.tab', setInputfocus);
};
const mobileTooltipOpts = () => (bp.getBreakpointSize() === 'xs' ? { placement: 'bottom' } : {});
const mountGroupProjectFields = (el, store) => {
if (!el) {
return null;
}
const { importGroup, groupPersisted, groupId, groupName, projectName, rootUrl } = el.dataset;
return new Vue({
el,
store,
render(createElement) {
return createElement(GroupProjectFields, {
props: {
importGroup: parseBoolean(importGroup),
groupPersisted: parseBoolean(groupPersisted),
groupId: groupId || '',
groupName: groupName || '',
projectName: projectName || '',
rootUrl,
},
});
},
});
};
const mountCreateImportGroupProjectFields = () => {
const store = createStore();
[...document.querySelectorAll('.js-create-import-group-project-fields')].map((el) =>
mountGroupProjectFields(el, store),
);
// Since we replaced form inputs, we need to re-initialize the field errors handler
return new GlFieldErrors(document.querySelectorAll('.gl-show-field-errors'));
};
export default () => {
displayGroupPath('.js-group-path-source', '.js-group-path-display');
displayGroupPath('.js-import-group-path-source', '.js-import-group-path-display');
displayProjectPath('.js-project-path-source', '.js-project-path-display');
showTooltip('.js-group-name-tooltip', mobileTooltipOpts());
importButtonsSubmit();
bindHowToImport();
setAutofocus();
mountCreateImportGroupProjectFields();
};
import { slugify, convertUnicodeToAscii } from '~/lib/utils/text_utility';
class DisplayInputValue {
constructor(sourceElementSelector, targetElementSelector, transformer) {
this.sourceElement = document.querySelector(sourceElementSelector);
this.targetElement = document.querySelector(targetElementSelector);
this.originalTargetValue = this.targetElement?.textContent;
this.transformer = transformer;
this.updateHandler = this.update.bind(this);
}
update() {
let { value } = this.sourceElement;
if (value.length === 0) {
value = this.originalTargetValue;
} else if (this.transformer) {
value = this.transformer(value);
}
this.targetElement.textContent = value;
}
listen(callback) {
if (!this.sourceElement || !this.targetElement) return null;
this.updateHandler();
return callback(this.sourceElement, this.updateHandler);
}
}
export const displayGroupPath = (sourceSelector, targetSelector) => {
const display = new DisplayInputValue(sourceSelector, targetSelector);
if (!display) return null;
const callback = (sourceElement, updateHandler) => {
const observer = new MutationObserver((mutationList) => {
mutationList.forEach((mutation) => {
if (mutation.attributeName === 'value') {
updateHandler();
}
});
});
observer.observe(sourceElement, { attributes: true });
};
return display.listen(callback);
};
export const displayProjectPath = (sourceSelector, displaySelector) => {
const transformer = (value) => slugify(convertUnicodeToAscii(value));
const display = new DisplayInputValue(sourceSelector, displaySelector, transformer);
if (!display) return null;
const callback = (sourceElement, updateHandler) => {
sourceElement.addEventListener('input', updateHandler);
};
return display.listen(callback);
};
import { initTooltips, add } from '~/tooltips';
export default function showTooltip(tooltipSelector, config = {}) {
const tooltip = document.querySelector(tooltipSelector);
if (!tooltip) return null;
initTooltips({ selector: tooltipSelector });
return add([tooltip], Object.assign(config, { show: true }));
}
import * as types from './mutation_types';
export const setStoreGroupName = ({ commit }, groupName) =>
commit(types.SET_STORE_GROUP_NAME, groupName);
export const setStoreGroupPath = ({ commit }, groupPath) =>
commit(types.SET_STORE_GROUP_PATH, groupPath);
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import createState from './state';
Vue.use(Vuex);
export default () =>
new Vuex.Store({
actions,
mutations,
state: createState(),
});
export const SET_STORE_GROUP_NAME = 'SET_STORE_GROUP_NAME';
export const SET_STORE_GROUP_PATH = 'SET_STORE_GROUP_PATH';
import * as types from './mutation_types';
export default {
[types.SET_STORE_GROUP_NAME](state, groupName) {
state.storeGroupName = groupName;
},
[types.SET_STORE_GROUP_PATH](state, groupPath) {
state.storeGroupPath = groupPath;
},
};
import { DEFAULT_GROUP_PATH } from '../constants';
export default () => ({
storeGroupName: '',
storeGroupPath: DEFAULT_GROUP_PATH,
});
......@@ -28,43 +28,13 @@
= form_tag users_sign_up_groups_projects_path(form_params), class: 'gl-show-field-errors gl-w-full gl-p-4 js-groups-projects-form' do
= form_errors(@group, type: "Group")
= form_errors(@project, type: "Project")
= render 'layouts/flash'
= fields_for :group do |gf|
.row
.form-group.group-name-holder.col-sm-12
= gf.label :name, class: 'gl-font-weight-bold' do
= _('Group name')
- if @group.persisted?
= gf.text_field :name, class: 'form-control js-group-path-source',
disabled: true
= gf.hidden_field :id
- else
= gf.text_field :name, class: 'form-control js-validate-group-path js-autofill-group-name js-group-name-tooltip js-group-name-field',
required: true,
autofocus: true,
data: { title: _('Projects are organized into groups'), placement: 'right', show: true, qa_selector: 'group_name_field'}
= gf.hidden_field :path, class: 'form-control js-autofill-group-path js-group-path-source'
= gf.hidden_field :parent_id, id: 'group_parent_id'
= fields_for :project do |pf|
#blank-project-name.row
.form-group.project-name.col-sm-12
= pf.label :name, class: 'gl-font-weight-bold' do
%span= _('Project name')
= pf.text_field :name, id: 'blank_project_name', class: 'form-control js-project-path-source',
required: true, data: { track_label: 'blank_project', track_action: 'activate_form_input', track_property: 'project_name', track_value: '', qa_selector: 'project_name_field' }
%p.form-text.gl-text-center
= _('Your project will be created at:')
%p.form-text.gl-text-center.monospace.gl-overflow-wrap-break
= root_url
%span.js-group-path-display>= _('{group}')
%span>= _('/')
%span.js-project-path-display>= _('{project}')
%p.form-text.text-muted.gl-text-center{ class: 'gl-mb-5!' }
= _('You can always change your URL later')
.js-create-import-group-project-fields{ data: { group_persisted: @group.persisted?.to_s,
group_id: @group.id,
group_name: @group.name,
project_name: @project.name,
root_url: root_url } }
.form-group
.form-check.gl-mb-3
......@@ -81,32 +51,15 @@
= form_errors(@group, type: "Group")
= render 'layouts/flash'
= fields_for :group do |gf|
.row
.form-group.group-name-holder.col-sm-12
= gf.label :name, class: 'gl-font-weight-bold' do
= _('Group name')
= gf.text_field :name, id: 'import_group_name', class: 'form-control js-validate-group-path js-autofill-group-name js-group-name-field has-tooltip',
required: true,
data: { title: _('Projects are organized into groups'), placement: 'right', qa_selector: 'import_group_name_field' }
= gf.hidden_field :path, id: 'import_group_path', class: 'form-control js-autofill-group-path js-import-group-path-source'
.js-create-import-group-project-fields{ data: { import_group: 'true',
group_persisted: @group.persisted?.to_s,
group_id: @group.id,
group_name: @group.name,
root_url: root_url } }
= hidden_field_tag :import_url, nil, class: 'js-import-url'
= submit_tag nil, class: 'gl-display-none'
%p.form-text.gl-text-center
= _('Your project will be created at:')
%p.form-text.gl-text-center.monospace.gl-overflow-wrap-break
= root_url
%span.js-import-group-path-display>= _('{group}')
%span>= _('/')
%span>= _('{project}')
%p.form-text.text-muted.gl-text-center{ class: 'gl-mb-5!' }
= _('You can always change your URL later')
.js-import-project-buttons
= render 'projects/import_project_pane', destination_namespace_id: @namespace&.id
- else
......
......@@ -22,35 +22,44 @@
end
it 'A user can create a group and project' do
page.within '.js-group-path-display' do
page.within('[data-testid="url-group-path"]') do
expect(page).to have_content('{group}')
end
page.within '.js-project-path-display' do
page.within('[data-testid="url-project-path"]') do
expect(page).to have_content('{project}')
end
fill_in 'group_name', with: 'test group'
fill_in 'group_name', with: '@_'
fill_in 'blank_project_name', with: 'test project'
page.within '.js-group-path-display' do
expect(page).to have_content('test-group')
page.within('[data-testid="url-group-path"]') do
expect(page).to have_content('_')
end
page.within '.js-project-path-display' do
page.within('[data-testid="url-project-path"]') do
expect(page).to have_content('test-project')
end
click_on 'Create project'
expect_filled_form_and_error_message
fill_in 'group_name', with: 'test group'
page.within('[data-testid="url-group-path"]') do
expect(page).to have_content('test-group')
end
click_on 'Create project'
expect(page).to have_content('Get started with GitLab Ready to get started with GitLab?')
end
it 'a user can create a group and import a project' do
click_on 'Import'
page.within '.js-import-group-path-display' do
page.within('[data-testid="url-group-path"]') do
expect(page).to have_content('{group}')
end
......@@ -62,7 +71,7 @@
fill_in 'import_group_name', with: 'test group'
page.within '.js-import-group-path-display' do
page.within('[data-testid="url-group-path"]') do
expect(page).to have_content('test-group')
end
......@@ -70,4 +79,13 @@
expect(page).to have_content('To connect GitHub repositories, you first need to authorize GitLab to')
end
def expect_filled_form_and_error_message
expect(find('[data-testid="group-name"]').value).to eq('@_')
expect(find('[data-testid="project-name"]').value).to eq('test project')
page.within('#error_explanation') do
expect(page).to have_content('The Group contains the following errors')
end
end
end
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import { GlFormInput } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import waitForPromises from 'helpers/wait_for_promises';
import GroupProjectFields from 'ee/registrations/groups_projects/new/components/group_project_fields.vue';
import createStore from 'ee/registrations/groups_projects/new/store';
import {
DEFAULT_GROUP_PATH,
DEFAULT_PROJECT_PATH,
} from 'ee/registrations/groups_projects/new/constants';
import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { s__ } from '~/locale';
jest.mock('~/alert');
describe('GroupProjectFields', () => {
const initialProps = {
importGroup: false,
groupPersisted: false,
groupId: '',
groupName: '',
projectName: '',
rootUrl: 'https://example.com/',
};
let wrapper;
let mock;
const createComponent = (props = {}) => {
wrapper = shallowMountExtended(GroupProjectFields, {
store: createStore(),
propsData: {
...initialProps,
...props,
},
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
},
stubs: {
GlFormInput,
},
});
};
const findInputByTestId = (testId) => wrapper.findByTestId(testId);
const buildUrl = (groupPath = DEFAULT_GROUP_PATH, projectPath = DEFAULT_PROJECT_PATH) =>
`${initialProps.rootUrl}${groupPath}/${projectPath}`;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('render', () => {
describe('when create group', () => {
describe('when new group', () => {
it('renders inputs', () => {
createComponent();
const groupInput = findInputByTestId('group-name');
expect(groupInput.exists()).toBe(true);
expect(groupInput.attributes('disabled')).toBe(undefined);
expect(findInputByTestId('persisted-group-name').exists()).toBe(false);
expect(findInputByTestId('project-name').exists()).toBe(true);
});
});
describe('when persisted group', () => {
it('renders inputs', () => {
createComponent({ groupPersisted: true, groupName: 'group name' });
const groupInput = findInputByTestId('persisted-group-name');
expect(groupInput.exists()).toBe(true);
expect(groupInput.attributes('disabled')).toBe('true');
expect(findInputByTestId('group-name').exists()).toBe(false);
expect(findInputByTestId('project-name').exists()).toBe(true);
});
});
});
describe('when import group', () => {
describe('when new group', () => {
it('renders inputs', () => {
createComponent({ importGroup: true });
const groupInput = findInputByTestId('group-name');
expect(groupInput.exists()).toBe(true);
expect(groupInput.attributes('disabled')).toBe(undefined);
expect(findInputByTestId('persisted-group-name').exists()).toBe(false);
expect(findInputByTestId('project-name').exists()).toBe(false);
});
});
describe('when persisted group', () => {
it('renders inputs', () => {
createComponent({
importGroup: true,
groupPersisted: true,
groupName: 'group name',
});
const groupInput = findInputByTestId('group-name');
expect(groupInput.exists()).toBe(true);
expect(groupInput.attributes('disabled')).toBe(undefined);
expect(findInputByTestId('persisted-group-name').exists()).toBe(false);
expect(findInputByTestId('project-name').exists()).toBe(false);
});
});
});
});
describe('placement', () => {
describe('when xs', () => {
it('places tooltip at the bottom', async () => {
jest.spyOn(GlBreakpointInstance, 'getBreakpointSize').mockReturnValue('xs');
window.dispatchEvent(new Event('resize'));
await nextTick();
createComponent();
const tooltip = getBinding(findInputByTestId('group-name').element, 'gl-tooltip');
expect(tooltip.value.placement).toBe('bottom');
expect(tooltip.value.title).toBe(s__('ProjectsNew|Projects are organized into groups'));
});
});
describe('when sm', () => {
it('places tooltip at the right', async () => {
jest.spyOn(GlBreakpointInstance, 'getBreakpointSize').mockReturnValue('sm');
window.dispatchEvent(new Event('resize'));
await nextTick();
createComponent();
const tooltip = getBinding(findInputByTestId('group-name').element, 'gl-tooltip');
expect(tooltip.value.placement).toBe('right');
expect(tooltip.value.title).toBe(s__('ProjectsNew|Projects are organized into groups'));
});
});
});
describe('onGroupUpdate', () => {
it('updates groupName and groupPath', async () => {
createComponent();
findInputByTestId('group-name').vm.$emit('update', 'group name');
await nextTick();
expect(wrapper.text()).toContain(buildUrl('group-name'));
findInputByTestId('group-name').vm.$emit('update', '+');
await nextTick();
expect(wrapper.text()).toContain(buildUrl());
});
describe('suggestions', () => {
const groupName = 'group name';
const apiUrl = /namespaces\/group-name\/exists/;
it('uses suggestion', async () => {
const suggestion = 'new-group-name';
mock.onGet(apiUrl).replyOnce(HTTP_STATUS_OK, {
exists: true,
suggests: [suggestion],
});
createComponent();
findInputByTestId('group-name').vm.$emit('update', groupName);
await waitForPromises();
expect(wrapper.text()).toContain(buildUrl(suggestion));
});
describe('when there are no suggestions', () => {
it('creates alert', async () => {
mock.onGet(apiUrl).replyOnce(HTTP_STATUS_OK, {
exists: true,
suggests: [],
});
createComponent();
findInputByTestId('group-name').vm.$emit('update', groupName);
await waitForPromises();
expect(createAlert).toHaveBeenCalledWith({
message: s__('ProjectsNew|Unable to suggest a path. Please refresh and try again.'),
});
});
});
describe('when suggestions request fails', () => {
it('creates alert', async () => {
mock.onGet(apiUrl).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR);
createComponent();
findInputByTestId('group-name').vm.$emit('update', groupName);
await waitForPromises();
expect(createAlert).toHaveBeenCalledWith({
message: s__(
'ProjectsNew|An error occurred while checking group path. Please refresh and try again.',
),
});
});
});
});
});
describe('onProjectUpdate', () => {
it('updates projectPath', async () => {
createComponent();
findInputByTestId('project-name').vm.$emit('update', 'project name');
await nextTick();
expect(wrapper.text()).toContain(buildUrl(DEFAULT_GROUP_PATH, 'project-name'));
findInputByTestId('project-name').vm.$emit('update', '+');
await nextTick();
expect(wrapper.text()).toContain(buildUrl());
});
});
describe('url', () => {
describe('when create group', () => {
describe('when new group', () => {
it('renders url', () => {
createComponent();
expect(wrapper.text()).toContain(buildUrl());
});
});
describe('when persisted group', () => {
it('renders url', async () => {
createComponent({ groupPersisted: true, groupName: 'group name' });
await nextTick();
expect(wrapper.text()).toContain(buildUrl('group-name'));
});
});
});
describe('when import group', () => {
it('renders url', () => {
createComponent({ importGroup: true });
expect(wrapper.text()).toContain(buildUrl());
});
});
});
describe('when form was filled, submitted and failed', () => {
it('fills inputs and renders url', async () => {
createComponent({ groupName: '@_', projectName: 'project name' });
await nextTick();
expect(findInputByTestId('group-name').attributes('value')).toBe('@_');
expect(findInputByTestId('project-name').attributes('value')).toBe('project name');
expect(wrapper.text()).toContain(buildUrl('_', 'project-name'));
});
});
});
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import mountComponents from 'ee/registrations/groups_projects/new';
import * as showTooltip from 'ee/registrations/groups_projects/new/show_tooltip';
const setup = () => {
const fixture = `
......@@ -38,25 +36,3 @@ describe('importButtonsSubmit', () => {
expect(submitSpy).toHaveBeenCalled();
});
});
describe('mobileTooltipOpts', () => {
let showTooltipSpy;
beforeEach(() => {
showTooltipSpy = jest.spyOn(showTooltip, 'default');
});
it('when xs breakpoint size, passes placement options', () => {
jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('xs');
setup();
expect(showTooltipSpy).toHaveBeenCalledWith(expect.any(String), { placement: 'bottom' });
resetHTMLFixture();
});
it('when not xs breakpoint size, passes emptyt tooltip options', () => {
jest.spyOn(bp, 'getBreakpointSize').mockReturnValue('lg');
setup();
expect(showTooltipSpy).toHaveBeenCalledWith(expect.any(String), {});
resetHTMLFixture();
});
});
import {
displayGroupPath,
displayProjectPath,
} from 'ee/registrations/groups_projects/new/path_display';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { useMockMutationObserver } from 'helpers/mock_dom_observer';
const findSource = () => document.querySelector('.source');
const displayValue = () => document.querySelector('.display').textContent;
describe('displayGroupPath', () => {
const { trigger: triggerMutate } = useMockMutationObserver();
beforeEach(() => {
setHTMLFixture("<input type='text' class='source'><div class='display'>original value<div>");
displayGroupPath('.source', '.display');
});
afterEach(() => {
resetHTMLFixture();
});
const inputSource = (value) => {
const source = findSource();
source.value = value;
triggerMutate(source, {
entry: { attributeName: 'value' },
options: { attributes: true },
});
};
it('coppies values from the source to the display', () => {
inputSource('peanut-butter-jelly-time');
expect(displayValue()).toBe('peanut-butter-jelly-time');
});
});
describe('displayProjectPath', () => {
beforeEach(() => {
setHTMLFixture("<input type='text' class='source'><div class='display'>original value<div>");
displayProjectPath('.source', '.display');
});
afterEach(() => {
resetHTMLFixture();
});
const inputSource = (value) => {
const source = findSource();
source.value = value;
source.dispatchEvent(new Event('input'));
};
it('displays the default display value when source is empty', () => {
expect(displayValue()).toBe('original value');
inputSource('its a peanut butter jelly');
expect(displayValue()).not.toBe('original value');
inputSource('');
expect(displayValue()).toBe('original value');
});
it('sluggifies values from the source to the display', () => {
inputSource('peanut butter jelly time');
expect(displayValue()).toBe('peanut-butter-jelly-time');
});
});
import { nextTick } from 'vue';
import showTooltip from 'ee/registrations/groups_projects/new/show_tooltip';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import * as tooltips from '~/tooltips';
describe('showTooltip', () => {
beforeEach(() => {
setHTMLFixture("<div class='my-tooltip' title='this is a tooltip!'></div>");
});
afterEach(() => {
resetHTMLFixture();
});
const findBodyText = () => document.body.innerText;
it('renders a tooltip immediately', async () => {
expect(findBodyText()).toBe('');
showTooltip('.my-tooltip');
await nextTick();
expect(findBodyText()).toBe('this is a tooltip!');
});
it('merges config options', () => {
const addSpy = jest.spyOn(tooltips, 'add');
showTooltip('.my-tooltip', { placement: 'bottom' });
expect(addSpy).toHaveBeenCalledWith(expect.any(Array), { placement: 'bottom', show: true });
});
});
import testAction from 'helpers/vuex_action_helper';
import * as types from 'ee/registrations/groups_projects/new/store/mutation_types';
import state from 'ee/registrations/groups_projects/new/store/state';
import {
setStoreGroupName,
setStoreGroupPath,
} from 'ee/registrations/groups_projects/new/store/actions';
describe('Actions', () => {
let mockedState;
beforeEach(() => {
mockedState = state();
});
describe('setStoreGroupName', () => {
it('should commit SET_STORE_GROUP_NAME mutation', () => {
return testAction(
setStoreGroupName,
'name',
mockedState,
[{ type: types.SET_STORE_GROUP_NAME, payload: 'name' }],
[],
);
});
});
describe('setStoreGroupPath', () => {
it('should commit SET_STORE_GROUP_PATH mutation', () => {
return testAction(
setStoreGroupPath,
'path',
mockedState,
[{ type: types.SET_STORE_GROUP_PATH, payload: 'path' }],
[],
);
});
});
});
import * as types from 'ee/registrations/groups_projects/new/store/mutation_types';
import mutations from 'ee/registrations/groups_projects/new/store/mutations';
import state from 'ee/registrations/groups_projects/new/store/state';
describe('Mutations', () => {
let stateCopy;
beforeEach(() => {
stateCopy = state();
});
describe('SET_STORE_GROUP_NAME', () => {
it('should set storeGroupName', () => {
mutations[types.SET_STORE_GROUP_NAME](stateCopy, 'name');
expect(stateCopy.storeGroupName).toEqual('name');
});
});
describe('SET_STORE_GROUP_PATH', () => {
it('should set storeGroupPath', () => {
mutations[types.SET_STORE_GROUP_PATH](stateCopy, 'path');
expect(stateCopy.storeGroupPath).toEqual('path');
});
});
});
import { DEFAULT_GROUP_PATH } from 'ee/registrations/groups_projects/new/constants';
import createState from 'ee/registrations/groups_projects/new/store/state';
describe('State', () => {
it('creates default state', () => {
expect(createState()).toEqual({
storeGroupName: '',
storeGroupPath: DEFAULT_GROUP_PATH,
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment