Commit 108a1a40 authored by Simon Knox's avatar Simon Knox

Merge branch '334812-create-a-new-feature' into 'master'

Resolve "Create a new Feature"

See merge request gitlab-org/gitlab!73275
parents 7c609212 0790029f
#import './widget.fragment.graphql'
mutation createWorkItem($input: CreateWorkItemInput) {
createWorkItem(input: $input) @client {
workItem {
id
type
widgets {
nodes {
...WidgetBase
... on TitleWidget {
contentText
}
}
}
}
}
}
......@@ -4,6 +4,7 @@ import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import createDefaultClient from '~/lib/graphql';
import workItemQuery from './work_item.query.graphql';
import introspectionQueryResultData from './fragmentTypes.json';
import { resolvers } from './resolvers';
import typeDefs from './typedefs.graphql';
const fragmentMatcher = new IntrospectionFragmentMatcher({
......@@ -13,15 +14,12 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({
export function createApolloProvider() {
Vue.use(VueApollo);
const defaultClient = createDefaultClient(
{},
{
const defaultClient = createDefaultClient(resolvers, {
cacheConfig: {
fragmentMatcher,
},
typeDefs,
},
);
});
defaultClient.cache.writeQuery({
query: workItemQuery,
......
import { uuids } from '~/lib/utils/uuids';
import workItemQuery from './work_item.query.graphql';
export const resolvers = {
Mutation: {
createWorkItem(_, { input }, { cache }) {
const id = uuids()[0];
const workItem = {
__typename: 'WorkItem',
type: 'FEATURE',
id,
widgets: {
__typename: 'WorkItemWidgetConnection',
nodes: [
{
__typename: 'TitleWidget',
type: 'TITLE',
enabled: true,
contentText: input.title,
},
],
},
};
cache.writeQuery({ query: workItemQuery, variables: { id }, data: { workItem } });
return {
__typename: 'CreateWorkItemPayload',
workItem,
};
},
},
};
......@@ -33,6 +33,18 @@ type WorkItem {
widgets: [WorkItemWidgetConnection]
}
type CreateWorkItemInput {
title: String!
}
type CreateWorkItemPayload {
workItem: WorkItem!
}
extend type Query {
workItem(id: ID!): WorkItem!
}
extend type Mutation {
createWorkItem(input: CreateWorkItemInput!): CreateWorkItemPayload!
}
<script>
import { GlButton, GlAlert } from '@gitlab/ui';
import createWorkItemMutation from '../graphql/create_work_item.mutation.graphql';
export default {
components: {
GlButton,
GlAlert,
},
data() {
return {
title: '',
error: false,
};
},
methods: {
async createWorkItem() {
try {
const response = await this.$apollo.mutate({
mutation: createWorkItemMutation,
variables: {
input: {
title: this.title,
},
},
});
const {
data: {
createWorkItem: {
workItem: { id },
},
},
} = response;
this.$router.push({ name: 'workItem', params: { id } });
} catch {
this.error = true;
}
},
},
};
</script>
<template>
<form @submit.prevent="createWorkItem">
<gl-alert v-if="error" variant="danger" @dismiss="error = false">{{
__('Something went wrong when creating a work item. Please try again')
}}</gl-alert>
<label for="title" class="gl-sr-only">{{ __('Title') }}</label>
<input
id="title"
v-model.trim="title"
type="text"
class="gl-font-size-h-display gl-font-weight-bold gl-my-5 gl-border-none gl-w-full gl-pl-2"
data-testid="title-input"
:placeholder="__('Add a title…')"
/>
<div class="gl-bg-gray-10 gl-py-5 gl-px-6">
<gl-button
variant="confirm"
:disabled="title.length === 0"
class="gl-mr-3"
data-testid="create-button"
type="submit"
@click="createWorkItem"
>
{{ __('Create') }}
</gl-button>
<gl-button data-testid="cancel-button" @click="$router.go(-1)">
{{ __('Cancel') }}
</gl-button>
</div>
</form>
</template>
export const routes = [
{
path: '/new',
name: 'createWorkItem',
component: () => import('../pages/create_work_item.vue'),
},
{
path: '/:id',
name: 'work_item',
name: 'workItem',
component: () => import('../pages/work_item_root.vue'),
props: true,
},
......
......@@ -1977,6 +1977,9 @@ msgstr ""
msgid "Add a task list"
msgstr ""
msgid "Add a title…"
msgstr ""
msgid "Add a to do"
msgstr ""
......@@ -32373,6 +32376,9 @@ msgstr ""
msgid "Something went wrong trying to change the locked state of this %{issuableDisplayName}"
msgstr ""
msgid "Something went wrong when creating a work item. Please try again"
msgstr ""
msgid "Something went wrong when reordering designs. Please try again"
msgstr ""
......
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
import { resolvers } from '~/work_items/graphql/resolvers';
Vue.use(VueApollo);
describe('Create work item component', () => {
let wrapper;
let fakeApollo;
const findAlert = () => wrapper.findComponent(GlAlert);
const findCreateButton = () => wrapper.find('[data-testid="create-button"]');
const findCancelButton = () => wrapper.find('[data-testid="cancel-button"]');
const findTitleInput = () => wrapper.find('[data-testid="title-input"]');
const createComponent = ({ data = {} } = {}) => {
fakeApollo = createMockApollo([], resolvers);
wrapper = shallowMount(CreateWorkItem, {
apolloProvider: fakeApollo,
data() {
return {
...data,
};
},
mocks: {
$router: {
go: jest.fn(),
push: jest.fn(),
},
},
});
};
afterEach(() => {
wrapper.destroy();
fakeApollo = null;
});
it('does not render error by default', () => {
createComponent();
expect(findAlert().exists()).toBe(false);
});
it('renders a disabled Create button when title input is empty', () => {
createComponent();
expect(findCreateButton().props('disabled')).toBe(true);
});
it('redirects to the previous page on Cancel button click', () => {
createComponent();
findCancelButton().vm.$emit('click');
expect(wrapper.vm.$router.go).toHaveBeenCalledWith(-1);
});
it('hides the alert on dismissing the error', async () => {
createComponent({ data: { error: true } });
expect(findAlert().exists()).toBe(true);
findAlert().vm.$emit('dismiss');
await nextTick();
expect(findAlert().exists()).toBe(false);
});
describe('when title input field has a text', () => {
beforeEach(() => {
createComponent();
findTitleInput().setValue('Test title');
});
it('renders a non-disabled Create button', () => {
expect(findCreateButton().props('disabled')).toBe(false);
});
it('redirects to the work item page on successful mutation', async () => {
wrapper.find('form').trigger('submit');
await waitForPromises();
expect(wrapper.vm.$router.push).toHaveBeenCalled();
});
// TODO: write a proper test here when we have a backend implementation
it.todo('shows an alert on mutation error');
});
});
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import WorkItemsRoot from '~/work_items/pages/work_item_root.vue';
import { workItemQueryResponse } from '../mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
Vue.use(VueApollo);
const WORK_ITEM_ID = '1';
......@@ -30,7 +30,6 @@ describe('Work items root component', () => {
propsData: {
id: WORK_ITEM_ID,
},
localVue,
apolloProvider: fakeApollo,
});
};
......
import { mount } from '@vue/test-utils';
import App from '~/work_items/components/app.vue';
import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
import WorkItemsRoot from '~/work_items/pages/work_item_root.vue';
import { createRouter } from '~/work_items/router';
......@@ -27,4 +28,10 @@ describe('Work items router', () => {
expect(wrapper.find(WorkItemsRoot).exists()).toBe(true);
});
it('renders create work item page on `/new` route', async () => {
await createComponent('/new');
expect(wrapper.findComponent(CreateWorkItem).exists()).toBe(true);
});
});
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