Commit ad2e62cc authored by Natalia Tepluhina's avatar Natalia Tepluhina

Add mutation to move designs

This adds DesignManagementMove, as well as a new design collection type.
parent 10a69a10
...@@ -17,6 +17,11 @@ export default { ...@@ -17,6 +17,11 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
isDraggingDesign: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
...@@ -121,7 +126,7 @@ export default { ...@@ -121,7 +126,7 @@ export default {
</slot> </slot>
<transition name="design-dropzone-fade"> <transition name="design-dropzone-fade">
<div <div
v-show="dragging" v-show="dragging && !isDraggingDesign"
class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white" class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
> >
<div v-show="!isDragDataValid" class="mw-50 text-center"> <div v-show="!isDragDataValid" class="mw-50 text-center">
......
#import "../fragments/design_list.fragment.graphql"
mutation DesignManagementMove(
$id: DesignManagementDesignID!
$previous: DesignManagementDesignID
$next: DesignManagementDesignID
) {
designManagementMove(input: { id: $id, previous: $previous, next: $next }) {
designCollection {
designs {
nodes {
...DesignListItem
}
}
}
errors
}
}
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui'; import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import VueDraggable from 'vuedraggable';
import UploadButton from '../components/upload/button.vue'; import UploadButton from '../components/upload/button.vue';
import DeleteButton from '../components/delete_button.vue'; import DeleteButton from '../components/delete_button.vue';
import Design from '../components/list/item.vue'; import Design from '../components/list/item.vue';
...@@ -9,6 +10,7 @@ import DesignDestroyer from '../components/design_destroyer.vue'; ...@@ -9,6 +10,7 @@ import DesignDestroyer from '../components/design_destroyer.vue';
import DesignVersionDropdown from '../components/upload/design_version_dropdown.vue'; import DesignVersionDropdown from '../components/upload/design_version_dropdown.vue';
import DesignDropzone from '../components/upload/design_dropzone.vue'; import DesignDropzone from '../components/upload/design_dropzone.vue';
import uploadDesignMutation from '../graphql/mutations/upload_design.mutation.graphql'; import uploadDesignMutation from '../graphql/mutations/upload_design.mutation.graphql';
import moveDesignMutation from '../graphql/mutations/move_design.mutation.graphql';
import permissionsQuery from '../graphql/queries/design_permissions.query.graphql'; import permissionsQuery from '../graphql/queries/design_permissions.query.graphql';
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql'; import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
import allDesignsMixin from '../mixins/all_designs'; import allDesignsMixin from '../mixins/all_designs';
...@@ -16,13 +18,18 @@ import { ...@@ -16,13 +18,18 @@ import {
UPLOAD_DESIGN_ERROR, UPLOAD_DESIGN_ERROR,
EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE, EXISTING_DESIGN_DROP_MANY_FILES_MESSAGE,
EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE, EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE,
MOVE_DESIGN_ERROR,
designUploadSkippedWarning, designUploadSkippedWarning,
designDeletionError, designDeletionError,
} from '../utils/error_messages'; } from '../utils/error_messages';
import { updateStoreAfterUploadDesign } from '../utils/cache_update'; import {
updateStoreAfterUploadDesign,
updateDesignsOnStoreAfterReorder,
} from '../utils/cache_update';
import { import {
designUploadOptimisticResponse, designUploadOptimisticResponse,
isValidDesignFile, isValidDesignFile,
moveDesignOptimisticResponse,
} from '../utils/design_management_utils'; } from '../utils/design_management_utils';
import { getFilename } from '~/lib/utils/file_upload'; import { getFilename } from '~/lib/utils/file_upload';
import { DESIGNS_ROUTE_NAME } from '../router/constants'; import { DESIGNS_ROUTE_NAME } from '../router/constants';
...@@ -40,6 +47,7 @@ export default { ...@@ -40,6 +47,7 @@ export default {
DesignVersionDropdown, DesignVersionDropdown,
DeleteButton, DeleteButton,
DesignDropzone, DesignDropzone,
VueDraggable,
}, },
mixins: [allDesignsMixin], mixins: [allDesignsMixin],
apollo: { apollo: {
...@@ -61,6 +69,8 @@ export default { ...@@ -61,6 +69,8 @@ export default {
}, },
filesToBeSaved: [], filesToBeSaved: [],
selectedDesigns: [], selectedDesigns: [],
isDraggingDesign: false,
reorderedDesigns: null,
}; };
}, },
computed: { computed: {
...@@ -254,11 +264,48 @@ export default { ...@@ -254,11 +264,48 @@ export default {
toggleOffPasteListener() { toggleOffPasteListener() {
document.removeEventListener('paste', this.onDesignPaste); document.removeEventListener('paste', this.onDesignPaste);
}, },
designMoveVariables(newIndex, element) {
const variables = {
id: element.id,
};
if (newIndex > 0) {
variables.previous = this.reorderedDesigns[newIndex - 1].id;
}
if (newIndex < this.reorderedDesigns.length - 1) {
variables.next = this.reorderedDesigns[newIndex + 1].id;
}
return variables;
},
reorderDesigns({ moved: { newIndex, element } }) {
this.$apollo
.mutate({
mutation: moveDesignMutation,
variables: this.designMoveVariables(newIndex, element),
update: (store, { data: { designManagementMove } }) => {
return updateDesignsOnStoreAfterReorder(
store,
designManagementMove,
this.projectQueryBody,
);
},
optimisticResponse: moveDesignOptimisticResponse(this.reorderedDesigns),
})
.catch(() => {
createFlash(MOVE_DESIGN_ERROR);
});
},
onDesignMove(designs) {
this.reorderedDesigns = designs;
},
}, },
beforeRouteUpdate(to, from, next) { beforeRouteUpdate(to, from, next) {
this.selectedDesigns = []; this.selectedDesigns = [];
next(); next();
}, },
dragOptions: {
animation: 200,
ghostClass: 'gl-visibility-hidden',
},
}; };
</script> </script>
...@@ -312,20 +359,35 @@ export default { ...@@ -312,20 +359,35 @@ export default {
<gl-alert v-else-if="error" variant="danger" :dismissible="false"> <gl-alert v-else-if="error" variant="danger" :dismissible="false">
{{ __('An error occurred while loading designs. Please try again.') }} {{ __('An error occurred while loading designs. Please try again.') }}
</gl-alert> </gl-alert>
<ol v-else class="list-unstyled row"> <vue-draggable
<li :class="designDropzoneWrapperClass" data-testid="design-dropzone-wrapper"> v-else
<design-dropzone :value="designs"
:class="{ 'design-list-item design-list-item-new': !isDesignListEmpty }" :disabled="!isLatestVersion"
:has-designs="hasDesigns" v-bind="$options.dragOptions"
@change="onUploadDesign" tag="ol"
/> draggable=".js-design-tile"
</li> class="list-unstyled row"
<li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-3 gl-mb-3"> @start="isDraggingDesign = true"
@end="isDraggingDesign = false"
@change="reorderDesigns"
@input="onDesignMove"
>
<li
v-for="design in designs"
:key="design.id"
class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile"
>
<design-dropzone <design-dropzone
:has-designs="hasDesigns" :has-designs="hasDesigns"
:is-dragging-design="isDraggingDesign"
@change="onExistingDesignDropzoneChange($event, design.filename)" @change="onExistingDesignDropzoneChange($event, design.filename)"
><design v-bind="design" :is-uploading="isDesignToBeSaved(design.filename)" >
/></design-dropzone> <design
v-bind="design"
:is-uploading="isDesignToBeSaved(design.filename)"
class="gl-bg-white"
/>
</design-dropzone>
<input <input
v-if="canSelectDesign(design.filename)" v-if="canSelectDesign(design.filename)"
...@@ -335,7 +397,17 @@ export default { ...@@ -335,7 +397,17 @@ export default {
@change="changeSelectedDesigns(design.filename)" @change="changeSelectedDesigns(design.filename)"
/> />
</li> </li>
</ol> <template #header>
<li :class="designDropzoneWrapperClass" data-testid="design-dropzone-wrapper">
<design-dropzone
:is-dragging-design="isDraggingDesign"
:class="{ 'design-list-item design-list-item-new': !isDesignListEmpty }"
:has-designs="hasDesigns"
@change="onUploadDesign"
/>
</li>
</template>
</vue-draggable>
</div> </div>
<router-view :key="$route.fullPath" /> <router-view :key="$route.fullPath" />
</div> </div>
......
...@@ -203,6 +203,15 @@ const addNewDesignToStore = (store, designManagementUpload, query) => { ...@@ -203,6 +203,15 @@ const addNewDesignToStore = (store, designManagementUpload, query) => {
}); });
}; };
const moveDesignInStore = (store, designManagementMove, query) => {
const data = store.readQuery(query);
data.project.issue.designCollection.designs = designManagementMove.designCollection.designs;
store.writeQuery({
...query,
data,
});
};
const onError = (data, message) => { const onError = (data, message) => {
createFlash(message); createFlash(message);
throw new Error(data.errors); throw new Error(data.errors);
...@@ -264,3 +273,11 @@ export const updateStoreAfterUploadDesign = (store, data, query) => { ...@@ -264,3 +273,11 @@ export const updateStoreAfterUploadDesign = (store, data, query) => {
addNewDesignToStore(store, data, query); addNewDesignToStore(store, data, query);
} }
}; };
export const updateDesignsOnStoreAfterReorder = (store, data, query) => {
if (hasErrors(data)) {
createFlash(data.errors[0]);
} else {
moveDesignInStore(store, data, query);
}
};
...@@ -85,7 +85,8 @@ export const designUploadOptimisticResponse = files => { ...@@ -85,7 +85,8 @@ export const designUploadOptimisticResponse = files => {
/** /**
* Generates optimistic response for a design upload mutation * Generates optimistic response for a design upload mutation
* @param {Array<File>} files * @param {Object} note
* @param {Object} position
*/ */
export const updateImageDiffNoteOptimisticResponse = (note, { position }) => ({ export const updateImageDiffNoteOptimisticResponse = (note, { position }) => ({
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26 // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
...@@ -104,6 +105,27 @@ export const updateImageDiffNoteOptimisticResponse = (note, { position }) => ({ ...@@ -104,6 +105,27 @@ export const updateImageDiffNoteOptimisticResponse = (note, { position }) => ({
}, },
}); });
/**
* Generates optimistic response for a design upload mutation
* @param {Array} designs
*/
export const moveDesignOptimisticResponse = designs => ({
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'Mutation',
designManagementMove: {
__typename: 'DesignManagementMovePayload',
designCollection: {
__typename: 'DesignCollection',
designs: {
__typename: 'DesignConnection',
nodes: designs,
},
},
errors: [],
},
});
const normalizeAuthor = author => ({ const normalizeAuthor = author => ({
...author, ...author,
web_url: author.webUrl, web_url: author.webUrl,
......
...@@ -40,6 +40,10 @@ export const EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE = __( ...@@ -40,6 +40,10 @@ export const EXISTING_DESIGN_DROP_INVALID_FILENAME_MESSAGE = __(
'You must upload a file with the same file name when dropping onto an existing design.', 'You must upload a file with the same file name when dropping onto an existing design.',
); );
export const MOVE_DESIGN_ERROR = __(
'Something went wrong when reordering designs. Please try again',
);
const MAX_SKIPPED_FILES_LISTINGS = 5; const MAX_SKIPPED_FILES_LISTINGS = 5;
const oneDesignSkippedMessage = filename => const oneDesignSkippedMessage = filename =>
......
---
title: Resolve Allow the ability to re-order designs
merge_request: 37686
author:
type: added
...@@ -12019,6 +12019,16 @@ ...@@ -12019,6 +12019,16 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "SCALAR",
"name": "EEIterationID",
"description": "Identifier of EE::Iteration",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "INTERFACE", "kind": "INTERFACE",
"name": "Entry", "name": "Entry",
...@@ -16818,6 +16828,16 @@ ...@@ -16818,6 +16828,16 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "includeSubgroups",
"description": "Include issues belonging to subgroups.",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "false"
},
{ {
"name": "after", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
...@@ -35645,6 +35665,33 @@ ...@@ -35645,6 +35665,33 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "iteration",
"description": "Find an iteration",
"args": [
{
"name": "id",
"description": "Find an iteration by its ID",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "EEIterationID",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "Iteration",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "metadata", "name": "metadata",
"description": "Metadata about GitLab", "description": "Metadata about GitLab",
...@@ -202,6 +202,17 @@ Only the latest version of the designs can be deleted. ...@@ -202,6 +202,17 @@ Only the latest version of the designs can be deleted.
Deleted designs are not permanently lost; they can be Deleted designs are not permanently lost; they can be
viewed by browsing previous versions. viewed by browsing previous versions.
## Reordering designs
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34382) in GitLab 13.3.
You can change designs order with dragging design to the new position:
![Reorder designs](img/designs_reordering_v13_3.gif)
NOTE: **Note:**
You can reorder designs only on the latest version.
## Starting discussions on designs ## Starting discussions on designs
When a design is uploaded, you can start a discussion by clicking on When a design is uploaded, you can start a discussion by clicking on
......
...@@ -22639,6 +22639,9 @@ msgstr "" ...@@ -22639,6 +22639,9 @@ msgstr ""
msgid "Something went wrong trying to change the locked state of this %{issuableDisplayName}" msgid "Something went wrong trying to change the locked state of this %{issuableDisplayName}"
msgstr "" msgstr ""
msgid "Something went wrong when reordering designs. Please try again"
msgstr ""
msgid "Something went wrong when toggling the button" msgid "Something went wrong when toggling the button"
msgstr "" msgstr ""
......
export const designListQueryResponse = {
data: {
project: {
id: '1',
issue: {
designCollection: {
designs: {
nodes: [
{
id: '1',
event: 'NONE',
filename: 'fox_1.jpg',
notesCount: 3,
image: 'image-1',
imageV432x230: 'image-1',
},
{
id: '2',
event: 'NONE',
filename: 'fox_2.jpg',
notesCount: 2,
image: 'image-2',
imageV432x230: 'image-2',
},
{
id: '3',
event: 'NONE',
filename: 'fox_3.jpg',
notesCount: 1,
image: 'image-3',
imageV432x230: 'image-3',
},
],
},
versions: {
nodes: [],
},
},
},
},
},
};
export const permissionsQueryResponse = {
data: {
project: {
id: '1',
issue: {
userPermissions: { createDesign: true },
},
},
},
};
export const reorderedDesigns = [
{
id: '2',
event: 'NONE',
filename: 'fox_2.jpg',
notesCount: 2,
image: 'image-2',
imageV432x230: 'image-2',
},
{
id: '1',
event: 'NONE',
filename: 'fox_1.jpg',
notesCount: 3,
image: 'image-1',
imageV432x230: 'image-1',
},
{
id: '3',
event: 'NONE',
filename: 'fox_3.jpg',
notesCount: 1,
image: 'image-3',
imageV432x230: 'image-3',
},
];
export const moveDesignMutationResponse = {
data: {
designManagementMove: {
designCollection: {
designs: {
nodes: [...reorderedDesigns],
},
},
errors: [],
},
},
};
export const moveDesignMutationResponseWithErrors = {
data: {
designManagementMove: {
designCollection: {
designs: {
nodes: [...reorderedDesigns],
},
},
errors: ['Houston, we have a problem'],
},
},
};
...@@ -22,14 +22,14 @@ exports[`Design management index page designs does not render toolbar when there ...@@ -22,14 +22,14 @@ exports[`Design management index page designs does not render toolbar when there
hasdesigns="true" hasdesigns="true"
/> />
</li> </li>
<li <li
class="col-md-6 col-lg-3 gl-mb-3" class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile"
> >
<design-dropzone-stub <design-dropzone-stub
hasdesigns="true" hasdesigns="true"
> >
<design-stub <design-stub
class="gl-bg-white"
event="NONE" event="NONE"
filename="design-1-name" filename="design-1-name"
id="design-1" id="design-1"
...@@ -41,12 +41,13 @@ exports[`Design management index page designs does not render toolbar when there ...@@ -41,12 +41,13 @@ exports[`Design management index page designs does not render toolbar when there
<!----> <!---->
</li> </li>
<li <li
class="col-md-6 col-lg-3 gl-mb-3" class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile"
> >
<design-dropzone-stub <design-dropzone-stub
hasdesigns="true" hasdesigns="true"
> >
<design-stub <design-stub
class="gl-bg-white"
event="NONE" event="NONE"
filename="design-2-name" filename="design-2-name"
id="design-2" id="design-2"
...@@ -58,12 +59,13 @@ exports[`Design management index page designs does not render toolbar when there ...@@ -58,12 +59,13 @@ exports[`Design management index page designs does not render toolbar when there
<!----> <!---->
</li> </li>
<li <li
class="col-md-6 col-lg-3 gl-mb-3" class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile"
> >
<design-dropzone-stub <design-dropzone-stub
hasdesigns="true" hasdesigns="true"
> >
<design-stub <design-stub
class="gl-bg-white"
event="NONE" event="NONE"
filename="design-3-name" filename="design-3-name"
id="design-3" id="design-3"
...@@ -151,14 +153,14 @@ exports[`Design management index page designs renders designs list and header wi ...@@ -151,14 +153,14 @@ exports[`Design management index page designs renders designs list and header wi
hasdesigns="true" hasdesigns="true"
/> />
</li> </li>
<li <li
class="col-md-6 col-lg-3 gl-mb-3" class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile"
> >
<design-dropzone-stub <design-dropzone-stub
hasdesigns="true" hasdesigns="true"
> >
<design-stub <design-stub
class="gl-bg-white"
event="NONE" event="NONE"
filename="design-1-name" filename="design-1-name"
id="design-1" id="design-1"
...@@ -173,12 +175,13 @@ exports[`Design management index page designs renders designs list and header wi ...@@ -173,12 +175,13 @@ exports[`Design management index page designs renders designs list and header wi
/> />
</li> </li>
<li <li
class="col-md-6 col-lg-3 gl-mb-3" class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile"
> >
<design-dropzone-stub <design-dropzone-stub
hasdesigns="true" hasdesigns="true"
> >
<design-stub <design-stub
class="gl-bg-white"
event="NONE" event="NONE"
filename="design-2-name" filename="design-2-name"
id="design-2" id="design-2"
...@@ -193,12 +196,13 @@ exports[`Design management index page designs renders designs list and header wi ...@@ -193,12 +196,13 @@ exports[`Design management index page designs renders designs list and header wi
/> />
</li> </li>
<li <li
class="col-md-6 col-lg-3 gl-mb-3" class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile"
> >
<design-dropzone-stub <design-dropzone-stub
hasdesigns="true" hasdesigns="true"
> >
<design-stub <design-stub
class="gl-bg-white"
event="NONE" event="NONE"
filename="design-3-name" filename="design-3-name"
id="design-3" id="design-3"
...@@ -296,7 +300,6 @@ exports[`Design management index page when has no designs renders design dropzon ...@@ -296,7 +300,6 @@ exports[`Design management index page when has no designs renders design dropzon
class="" class=""
/> />
</li> </li>
</ol> </ol>
</div> </div>
......
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { createMockClient } from 'mock-apollo-client';
import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router';
import VueDraggable from 'vuedraggable';
import Design from '~/design_management/components/list/item.vue';
import createRouter from '~/design_management/router';
import getDesignListQuery from '~/design_management/graphql/queries/get_design_list.query.graphql';
import permissionsQuery from '~/design_management/graphql/queries/design_permissions.query.graphql';
import moveDesignMutation from '~/design_management/graphql/mutations/move_design.mutation.graphql';
import createFlash from '~/flash';
import Index from '~/design_management/pages/index.vue';
import {
designListQueryResponse,
permissionsQueryResponse,
moveDesignMutationResponse,
reorderedDesigns,
moveDesignMutationResponseWithErrors,
} from '../mock_data/apollo_mock';
import { InMemoryCache } from 'apollo-cache-inmemory';
jest.mock('~/flash.js');
const localVue = createLocalVue();
localVue.use(VueApollo);
const router = createRouter();
localVue.use(VueRouter);
const designToMove = {
__typename: 'Design',
id: '2',
event: 'NONE',
filename: 'fox_2.jpg',
notesCount: 2,
image: 'image-2',
imageV432x230: 'image-2',
};
describe('Design management index page with Apollo mock', () => {
let wrapper;
let mockClient;
let apolloProvider;
let moveDesignHandler;
async function moveDesigns(localWrapper) {
await jest.runOnlyPendingTimers();
await localWrapper.vm.$nextTick();
localWrapper.find(VueDraggable).vm.$emit('input', reorderedDesigns);
localWrapper.find(VueDraggable).vm.$emit('change', {
moved: {
newIndex: 0,
element: designToMove,
},
});
}
const fragmentMatcher = { match: () => true };
const cache = new InMemoryCache({
fragmentMatcher,
addTypename: false,
});
const findDesigns = () => wrapper.findAll(Design);
function createComponent({
moveHandler = jest.fn().mockResolvedValue(moveDesignMutationResponse),
}) {
mockClient = createMockClient({ cache });
mockClient.setRequestHandler(
getDesignListQuery,
jest.fn().mockResolvedValue(designListQueryResponse),
);
mockClient.setRequestHandler(
permissionsQuery,
jest.fn().mockResolvedValue(permissionsQueryResponse),
);
moveDesignHandler = moveHandler;
mockClient.setRequestHandler(moveDesignMutation, moveDesignHandler);
apolloProvider = new VueApollo({
defaultClient: mockClient,
});
wrapper = shallowMount(Index, {
localVue,
apolloProvider,
router,
stubs: { VueDraggable },
});
}
afterEach(() => {
wrapper.destroy();
wrapper = null;
mockClient = null;
apolloProvider = null;
});
it('has a design with id 1 as a first one', async () => {
createComponent({});
await jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
expect(findDesigns()).toHaveLength(3);
expect(
findDesigns()
.at(0)
.props('id'),
).toBe('1');
});
it('calls a mutation with correct parameters and reorders designs', async () => {
createComponent({});
await moveDesigns(wrapper);
expect(moveDesignHandler).toHaveBeenCalled();
await wrapper.vm.$nextTick();
expect(
findDesigns()
.at(0)
.props('id'),
).toBe('2');
});
it('displays flash if mutation had a recoverable error', async () => {
createComponent({
moveHandler: jest.fn().mockResolvedValue(moveDesignMutationResponseWithErrors),
});
await moveDesigns(wrapper);
await wrapper.vm.$nextTick();
expect(createFlash).toHaveBeenCalledWith('Houston, we have a problem');
});
it('displays flash if mutation had a non-recoverable error', async () => {
createComponent({
moveHandler: jest.fn().mockRejectedValue('Error'),
});
await moveDesigns(wrapper);
await jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
expect(createFlash).toHaveBeenCalledWith(
'Something went wrong when reordering designs. Please try again',
);
});
});
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { ApolloMutation } from 'vue-apollo'; import { ApolloMutation } from 'vue-apollo';
import VueDraggable from 'vuedraggable';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
import { GlEmptyState } from '@gitlab/ui'; import { GlEmptyState } from '@gitlab/ui';
import Index from '~/design_management/pages/index.vue'; import Index from '~/design_management/pages/index.vue';
...@@ -108,7 +109,7 @@ describe('Design management index page', () => { ...@@ -108,7 +109,7 @@ describe('Design management index page', () => {
mocks: { $apollo }, mocks: { $apollo },
localVue, localVue,
router, router,
stubs: { DesignDestroyer, ApolloMutation, ...stubs }, stubs: { DesignDestroyer, ApolloMutation, VueDraggable, ...stubs },
attachToDocument: true, attachToDocument: true,
provide: { provide: {
projectPath: 'project-path', projectPath: 'project-path',
......
...@@ -8362,6 +8362,11 @@ mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: ...@@ -8362,6 +8362,11 @@ mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
dependencies: dependencies:
minimist "0.0.8" minimist "0.0.8"
mock-apollo-client@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/mock-apollo-client/-/mock-apollo-client-0.4.0.tgz#556a6090b1816dbf07e51093b652aca84aee979e"
integrity sha512-cHznpkX8uUClkWWJMpgdDWzEgjacM85xt69S9gPLrssM8Vahas0QmEJkFUycrRQyBkaqxvRe58Bg3a5pOvj2zA==
moment-mini@^2.22.1: moment-mini@^2.22.1:
version "2.22.1" version "2.22.1"
resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.22.1.tgz#bc32d73e43a4505070be6b53494b17623183420d" resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.22.1.tgz#bc32d73e43a4505070be6b53494b17623183420d"
......
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