Commit 4546adf1 authored by Kushal Pandya's avatar Kushal Pandya

Add re-usable Issuable Create app

Adds `issuable_create` Vue app in FOSS to use with
Test Cases (and other Issuable types later).
parent 8507d5cf
<script>
import IssuableForm from './issuable_form.vue';
export default {
components: {
IssuableForm,
},
props: {
descriptionPreviewPath: {
type: String,
required: true,
},
descriptionHelpPath: {
type: String,
required: true,
},
labelsFetchPath: {
type: String,
required: true,
},
labelsManagePath: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="issuable-create-container">
<slot name="title"></slot>
<hr />
<issuable-form
:description-preview-path="descriptionPreviewPath"
:description-help-path="descriptionHelpPath"
:labels-fetch-path="labelsFetchPath"
:labels-manage-path="labelsManagePath"
>
<template #actions="issuableMeta">
<slot name="actions" v-bind="issuableMeta"></slot>
</template>
</issuable-form>
</div>
</template>
<script>
import { GlForm, GlFormInput } from '@gitlab/ui';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
export default {
LabelSelectVariant: DropdownVariant,
components: {
GlForm,
GlFormInput,
MarkdownField,
LabelsSelect,
},
props: {
descriptionPreviewPath: {
type: String,
required: true,
},
descriptionHelpPath: {
type: String,
required: true,
},
labelsFetchPath: {
type: String,
required: true,
},
labelsManagePath: {
type: String,
required: true,
},
},
data() {
return {
issuableTitle: '',
issuableDescription: '',
selectedLabels: [],
};
},
methods: {
handleUpdateSelectedLabels(labels) {
if (labels.length) {
this.selectedLabels = labels;
}
},
},
};
</script>
<template>
<gl-form class="common-note-form gfm-form" @submit.stop.prevent>
<div data-testid="issuable-title" class="form-group row">
<label for="issuable-title" class="col-form-label col-sm-2">{{ __('Title') }}</label>
<div class="col-sm-10">
<gl-form-input id="issuable-title" v-model="issuableTitle" :placeholder="__('Title')" />
</div>
</div>
<div data-testid="issuable-description" class="form-group row">
<label for="issuable-description" class="col-form-label col-sm-2">{{
__('Description')
}}</label>
<div class="col-sm-10">
<markdown-field
:markdown-preview-path="descriptionPreviewPath"
:markdown-docs-path="descriptionHelpPath"
:add-spacing-classes="false"
:show-suggest-popover="true"
>
<textarea
id="issuable-description"
ref="textarea"
slot="textarea"
v-model="issuableDescription"
dir="auto"
class="note-textarea qa-issuable-form-description rspec-issuable-form-description js-gfm-input js-autosize markdown-area"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
></textarea>
</markdown-field>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div data-testid="issuable-labels" class="form-group row">
<label for="issuable-labels" class="col-form-label col-md-2 col-lg-4">{{
__('Labels')
}}</label>
<div class="col-md-8 col-sm-10">
<div class="issuable-form-select-holder">
<labels-select
:allow-label-edit="true"
:allow-label-create="true"
:allow-multiselect="true"
:allow-scoped-labels="true"
:labels-fetch-path="labelsFetchPath"
:labels-manage-path="labelsManagePath"
:selected-labels="selectedLabels"
:labels-list-title="__('Select label')"
:footer-create-label-title="__('Create project label')"
:footer-manage-label-title="__('Manage project labels')"
:variant="$options.LabelSelectVariant.Embedded"
@updateSelectedLabels="handleUpdateSelectedLabels"
/>
</div>
</div>
</div>
</div>
</div>
<div
data-testid="issuable-create-actions"
class="footer-block row-content-block gl-display-flex"
>
<slot
name="actions"
:issuable-title="issuableTitle"
:issuable-description="issuableDescription"
:selected-labels="selectedLabels"
></slot>
</div>
</gl-form>
</template>
......@@ -166,7 +166,11 @@ export default {
!state.showDropdownButton &&
!state.showDropdownContents
) {
this.handleDropdownClose(state.labels.filter(label => label.touched));
let filterFn = label => label.touched;
if (this.isDropdownVariantEmbedded) {
filterFn = label => label.set;
}
this.handleDropdownClose(state.labels.filter(filterFn));
}
},
/**
......@@ -186,7 +190,7 @@ export default {
].some(
className =>
target?.classList.contains(className) ||
target?.parentElement.classList.contains(className),
target?.parentElement?.classList.contains(className),
);
const hadExceptionParent = ['.js-btn-back', '.js-labels-list'].some(
......
import { mount } from '@vue/test-utils';
import IssuableCreateRoot from '~/issuable_create/components/issuable_create_root.vue';
import IssuableForm from '~/issuable_create/components/issuable_form.vue';
const createComponent = ({
descriptionPreviewPath = '/gitlab-org/gitlab-shell/preview_markdown',
descriptionHelpPath = '/help/user/markdown',
labelsFetchPath = '/gitlab-org/gitlab-shell/-/labels.json',
labelsManagePath = '/gitlab-org/gitlab-shell/-/labels',
} = {}) => {
return mount(IssuableCreateRoot, {
propsData: {
descriptionPreviewPath,
descriptionHelpPath,
labelsFetchPath,
labelsManagePath,
},
slots: {
title: `
<h1 class="js-create-title">New Issuable</h1>
`,
actions: `
<button class="js-issuable-save">Submit issuable</button>
`,
},
});
};
describe('IssuableCreateRoot', () => {
let wrapper;
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('template', () => {
it('renders component container element with class "issuable-create-container"', () => {
expect(wrapper.classes()).toContain('issuable-create-container');
});
it('renders contents for slot "title"', () => {
const titleEl = wrapper.find('h1.js-create-title');
expect(titleEl.exists()).toBe(true);
expect(titleEl.text()).toBe('New Issuable');
});
it('renders issuable-form component', () => {
expect(wrapper.find(IssuableForm).exists()).toBe(true);
});
it('renders contents for slot "actions" within issuable-form component', () => {
const buttonEl = wrapper.find(IssuableForm).find('button.js-issuable-save');
expect(buttonEl.exists()).toBe(true);
expect(buttonEl.text()).toBe('Submit issuable');
});
});
});
import { shallowMount } from '@vue/test-utils';
import { GlFormInput } from '@gitlab/ui';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import IssuableForm from '~/issuable_create/components/issuable_form.vue';
const createComponent = ({
descriptionPreviewPath = '/gitlab-org/gitlab-shell/preview_markdown',
descriptionHelpPath = '/help/user/markdown',
labelsFetchPath = '/gitlab-org/gitlab-shell/-/labels.json',
labelsManagePath = '/gitlab-org/gitlab-shell/-/labels',
} = {}) => {
return shallowMount(IssuableForm, {
propsData: {
descriptionPreviewPath,
descriptionHelpPath,
labelsFetchPath,
labelsManagePath,
},
slots: {
actions: `
<button class="js-issuable-save">Submit issuable</button>
`,
},
});
};
describe('IssuableForm', () => {
let wrapper;
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('methods', () => {
describe('handleUpdateSelectedLabels', () => {
it('sets provided `labels` param to prop `selectedLabels`', () => {
const labels = [
{
id: 1,
color: '#BADA55',
text_color: '#ffffff',
title: 'Documentation',
},
];
wrapper.vm.handleUpdateSelectedLabels(labels);
expect(wrapper.vm.selectedLabels).toBe(labels);
});
});
});
describe('template', () => {
it('renders issuable title input field', () => {
const titleFieldEl = wrapper.find('[data-testid="issuable-title"]');
expect(titleFieldEl.exists()).toBe(true);
expect(titleFieldEl.find('label').text()).toBe('Title');
expect(titleFieldEl.find(GlFormInput).exists()).toBe(true);
expect(titleFieldEl.find(GlFormInput).attributes('placeholder')).toBe('Title');
});
it('renders issuable description input field', () => {
const descriptionFieldEl = wrapper.find('[data-testid="issuable-description"]');
expect(descriptionFieldEl.exists()).toBe(true);
expect(descriptionFieldEl.find('label').text()).toBe('Description');
expect(descriptionFieldEl.find(MarkdownField).exists()).toBe(true);
expect(descriptionFieldEl.find(MarkdownField).props()).toMatchObject({
markdownPreviewPath: wrapper.vm.descriptionPreviewPath,
markdownDocsPath: wrapper.vm.descriptionHelpPath,
addSpacingClasses: false,
showSuggestPopover: true,
});
expect(descriptionFieldEl.find('textarea').exists()).toBe(true);
expect(descriptionFieldEl.find('textarea').attributes('placeholder')).toBe(
'Write a comment or drag your files here…',
);
});
it('renders labels select field', () => {
const labelsSelectEl = wrapper.find('[data-testid="issuable-labels"]');
expect(labelsSelectEl.exists()).toBe(true);
expect(labelsSelectEl.find('label').text()).toBe('Labels');
expect(labelsSelectEl.find(LabelsSelect).exists()).toBe(true);
expect(labelsSelectEl.find(LabelsSelect).props()).toMatchObject({
allowLabelEdit: true,
allowLabelCreate: true,
allowMultiselect: true,
allowScopedLabels: true,
labelsFetchPath: wrapper.vm.labelsFetchPath,
labelsManagePath: wrapper.vm.labelsManagePath,
selectedLabels: wrapper.vm.selectedLabels,
labelsListTitle: 'Select label',
footerCreateLabelTitle: 'Create project label',
footerManageLabelTitle: 'Manage project labels',
variant: 'embedded',
});
});
it('renders contents for slot "actions"', () => {
const buttonEl = wrapper
.find('[data-testid="issuable-create-actions"]')
.find('button.js-issuable-save');
expect(buttonEl.exists()).toBe(true);
expect(buttonEl.text()).toBe('Submit issuable');
});
});
});
......@@ -65,6 +65,33 @@ describe('LabelsSelectRoot', () => {
]),
);
});
it('calls `handleDropdownClose` with state.labels filterd using `set` prop when dropdown variant is `embedded`', () => {
wrapper = createComponent({
...mockConfig,
variant: 'embedded',
});
jest.spyOn(wrapper.vm, 'handleDropdownClose').mockImplementation();
wrapper.vm.handleVuexActionDispatch(
{ type: 'toggleDropdownContents' },
{
showDropdownButton: false,
showDropdownContents: false,
labels: [{ id: 1 }, { id: 2, set: true }],
},
);
expect(wrapper.vm.handleDropdownClose).toHaveBeenCalledWith(
expect.arrayContaining([
{
id: 2,
set: true,
},
]),
);
});
});
describe('handleDropdownClose', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment