Commit 5cb62a09 authored by Scott Stern's avatar Scott Stern Committed by Natalia Tepluhina

Convert issue sidebar confidential to apollo

Behind a FF, converting the existing full page refresh
to share a store with Sidebar and Notes
parent d9a1e681
...@@ -13,11 +13,35 @@ import sidebarTimeTrackingEventHub from '../../sidebar/event_hub'; ...@@ -13,11 +13,35 @@ import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement, isInMRPage } from '../../lib/utils/common_utils'; import { isInViewport, scrollToElement, isInMRPage } from '../../lib/utils/common_utils';
import { mergeUrlParams } from '../../lib/utils/url_utility'; import { mergeUrlParams } from '../../lib/utils/url_utility';
import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub'; import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
import updateIssueConfidentialMutation from '~/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import Api from '~/api'; import Api from '~/api';
let eTagPoll; let eTagPoll;
export const updateConfidentialityOnIssue = ({ commit, getters }, { confidential, fullPath }) => {
const { iid } = getters.getNoteableData;
return utils.gqClient
.mutate({
mutation: updateIssueConfidentialMutation,
variables: {
input: {
projectPath: fullPath,
iid: String(iid),
confidential,
},
},
})
.then(({ data }) => {
const {
issueSetConfidential: { issue },
} = data;
commit(types.SET_ISSUE_CONFIDENTIAL, issue.confidential);
});
};
export const expandDiscussion = ({ commit, dispatch }, data) => { export const expandDiscussion = ({ commit, dispatch }, data) => {
if (data.discussionId) { if (data.discussionId) {
dispatch('diffs/renderFileForDiscussionId', data.discussionId, { root: true }); dispatch('diffs/renderFileForDiscussionId', data.discussionId, { root: true });
...@@ -32,6 +56,8 @@ export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, d ...@@ -32,6 +56,8 @@ export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, d
export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data); export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data);
export const setConfidentiality = ({ commit }, data) => commit(types.SET_ISSUE_CONFIDENTIAL, data);
export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data); export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data);
export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data); export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data);
......
...@@ -39,6 +39,7 @@ export const CLOSE_ISSUE = 'CLOSE_ISSUE'; ...@@ -39,6 +39,7 @@ export const CLOSE_ISSUE = 'CLOSE_ISSUE';
export const REOPEN_ISSUE = 'REOPEN_ISSUE'; export const REOPEN_ISSUE = 'REOPEN_ISSUE';
export const TOGGLE_STATE_BUTTON_LOADING = 'TOGGLE_STATE_BUTTON_LOADING'; export const TOGGLE_STATE_BUTTON_LOADING = 'TOGGLE_STATE_BUTTON_LOADING';
export const TOGGLE_BLOCKED_ISSUE_WARNING = 'TOGGLE_BLOCKED_ISSUE_WARNING'; export const TOGGLE_BLOCKED_ISSUE_WARNING = 'TOGGLE_BLOCKED_ISSUE_WARNING';
export const SET_ISSUE_CONFIDENTIAL = 'SET_ISSUE_CONFIDENTIAL';
// Description version // Description version
export const REQUEST_DESCRIPTION_VERSION = 'REQUEST_DESCRIPTION_VERSION'; export const REQUEST_DESCRIPTION_VERSION = 'REQUEST_DESCRIPTION_VERSION';
......
...@@ -95,6 +95,10 @@ export default { ...@@ -95,6 +95,10 @@ export default {
Object.assign(state, { noteableData: data }); Object.assign(state, { noteableData: data });
}, },
[types.SET_ISSUE_CONFIDENTIAL](state, data) {
state.noteableData.confidential = data;
},
[types.SET_USER_DATA](state, data) { [types.SET_USER_DATA](state, data) {
Object.assign(state, { userData: data }); Object.assign(state, { userData: data });
}, },
......
import AjaxCache from '~/lib/utils/ajax_cache'; import AjaxCache from '~/lib/utils/ajax_cache';
import { trimFirstCharOfLineContent } from '~/diffs/store/utils'; import { trimFirstCharOfLineContent } from '~/diffs/store/utils';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
// factory function because global flag makes RegExp stateful // factory function because global flag makes RegExp stateful
const createQuickActionsRegex = () => /^\/\w+.*$/gm; const createQuickActionsRegex = () => /^\/\w+.*$/gm;
...@@ -34,3 +35,10 @@ export const stripQuickActions = note => note.replace(createQuickActionsRegex(), ...@@ -34,3 +35,10 @@ export const stripQuickActions = note => note.replace(createQuickActionsRegex(),
export const prepareDiffLines = diffLines => export const prepareDiffLines = diffLines =>
diffLines.map(line => ({ ...trimFirstCharOfLineContent(line) })); diffLines.map(line => ({ ...trimFirstCharOfLineContent(line) }));
export const gqClient = createGqClient(
{},
{
fetchPolicy: fetchPolicies.NO_CACHE,
},
);
<script> <script>
import { mapState } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { __ } from '~/locale'; import { __ } from '~/locale';
import Flash from '~/flash'; import Flash from '~/flash';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
...@@ -18,6 +18,10 @@ export default { ...@@ -18,6 +18,10 @@ export default {
}, },
mixins: [recaptchaModalImplementor], mixins: [recaptchaModalImplementor],
props: { props: {
fullPath: {
required: true,
type: String,
},
isEditable: { isEditable: {
required: true, required: true,
type: Boolean, type: Boolean,
...@@ -42,16 +46,24 @@ export default { ...@@ -42,16 +46,24 @@ export default {
}, },
}, },
created() { created() {
eventHub.$on('updateConfidentialAttribute', this.updateConfidentialAttribute);
eventHub.$on('closeConfidentialityForm', this.toggleForm); eventHub.$on('closeConfidentialityForm', this.toggleForm);
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('updateConfidentialAttribute', this.updateConfidentialAttribute);
eventHub.$off('closeConfidentialityForm', this.toggleForm); eventHub.$off('closeConfidentialityForm', this.toggleForm);
}, },
methods: { methods: {
...mapActions(['setConfidentiality']),
toggleForm() { toggleForm() {
this.edit = !this.edit; this.edit = !this.edit;
}, },
updateConfidentialAttribute(confidential) { closeForm() {
this.edit = false;
},
updateConfidentialAttribute() {
// TODO: rm when FF is defaulted to on.
const confidential = !this.confidential;
this.service this.service
.update('issue', { confidential }) .update('issue', { confidential })
.then(({ data }) => this.checkForSpam(data)) .then(({ data }) => this.checkForSpam(data))
...@@ -97,12 +109,8 @@ export default { ...@@ -97,12 +109,8 @@ export default {
> >
</div> </div>
<div class="value sidebar-item-value hide-collapsed"> <div class="value sidebar-item-value hide-collapsed">
<edit-form <edit-form v-if="edit" :is-confidential="confidential" :full-path="fullPath" />
v-if="edit" <div v-if="!confidential" class="no-value sidebar-item-value" data-testid="not-confidential">
:is-confidential="confidential"
:update-confidential-attribute="updateConfidentialAttribute"
/>
<div v-if="!confidential" class="no-value sidebar-item-value">
<icon :size="16" name="eye" aria-hidden="true" class="sidebar-item-icon inline" /> <icon :size="16" name="eye" aria-hidden="true" class="sidebar-item-icon inline" />
{{ __('Not confidential') }} {{ __('Not confidential') }}
</div> </div>
......
...@@ -11,9 +11,9 @@ export default { ...@@ -11,9 +11,9 @@ export default {
required: true, required: true,
type: Boolean, type: Boolean,
}, },
updateConfidentialAttribute: { fullPath: {
required: true, required: true,
type: Function, type: String,
}, },
}, },
computed: { computed: {
...@@ -37,10 +37,7 @@ export default { ...@@ -37,10 +37,7 @@ export default {
<div> <div>
<p v-if="!isConfidential" v-html="confidentialityOnWarning"></p> <p v-if="!isConfidential" v-html="confidentialityOnWarning"></p>
<p v-else v-html="confidentialityOffWarning"></p> <p v-else v-html="confidentialityOffWarning"></p>
<edit-form-buttons <edit-form-buttons :full-path="fullPath" />
:is-confidential="isConfidential"
:update-confidential-attribute="updateConfidentialAttribute"
/>
</div> </div>
</div> </div>
</div> </div>
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import eventHub from '../../event_hub'; import { GlLoadingIcon } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale'; import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import Flash from '~/flash';
import eventHub from '../../event_hub';
export default { export default {
components: {
GlLoadingIcon,
},
mixins: [glFeatureFlagsMixin()],
props: { props: {
isConfidential: { fullPath: {
required: true,
type: Boolean,
},
updateConfidentialAttribute: {
required: true, required: true,
type: Function, type: String,
}, },
}, },
data() {
return {
isLoading: false,
};
},
computed: { computed: {
...mapState({ confidential: ({ noteableData }) => noteableData.confidential }),
toggleButtonText() { toggleButtonText() {
return this.isConfidential ? __('Turn Off') : __('Turn On'); if (this.isLoading) {
}, return __('Applying');
updateConfidentialBool() { }
return !this.isConfidential;
return this.confidential ? __('Turn Off') : __('Turn On');
}, },
}, },
methods: { methods: {
...mapActions(['updateConfidentialityOnIssue']),
closeForm() { closeForm() {
eventHub.$emit('closeConfidentialityForm'); eventHub.$emit('closeConfidentialityForm');
$(this.$el).trigger('hidden.gl.dropdown'); $(this.$el).trigger('hidden.gl.dropdown');
}, },
submitForm() { submitForm() {
this.closeForm(); this.isLoading = true;
this.updateConfidentialAttribute(this.updateConfidentialBool); const confidential = !this.confidential;
if (this.glFeatures.confidentialApolloSidebar) {
this.updateConfidentialityOnIssue({ confidential, fullPath: this.fullPath })
.catch(() => {
Flash(__('Something went wrong trying to change the confidentiality of this issue'));
})
.finally(() => {
this.closeForm();
this.isLoading = false;
});
} else {
eventHub.$emit('updateConfidentialAttribute');
}
}, },
}, },
}; };
...@@ -44,8 +69,10 @@ export default { ...@@ -44,8 +69,10 @@ export default {
type="button" type="button"
class="btn btn-close" class="btn btn-close"
data-testid="confidential-toggle" data-testid="confidential-toggle"
:disabled="isLoading"
@click.prevent="submitForm" @click.prevent="submitForm"
> >
<gl-loading-icon v-if="isLoading" inline />
{{ toggleButtonText }} {{ toggleButtonText }}
</button> </button>
</div> </div>
......
mutation updateIssueConfidential($input: IssueSetConfidentialInput!) {
issueSetConfidential(input: $input) {
issue {
confidential
}
}
}
...@@ -52,20 +52,30 @@ function mountAssigneesComponent(mediator) { ...@@ -52,20 +52,30 @@ function mountAssigneesComponent(mediator) {
function mountConfidentialComponent(mediator) { function mountConfidentialComponent(mediator) {
const el = document.getElementById('js-confidential-entry-point'); const el = document.getElementById('js-confidential-entry-point');
const { fullPath, iid } = getSidebarOptions();
if (!el) return; if (!el) return;
const dataNode = document.getElementById('js-confidential-issue-data'); const dataNode = document.getElementById('js-confidential-issue-data');
const initialData = JSON.parse(dataNode.innerHTML); const initialData = JSON.parse(dataNode.innerHTML);
const ConfidentialComp = Vue.extend(ConfidentialIssueSidebar); // eslint-disable-next-line no-new
new Vue({
new ConfidentialComp({ el,
store, store,
propsData: { components: {
isEditable: initialData.is_editable, ConfidentialIssueSidebar,
service: mediator.service,
}, },
}).$mount(el); render: createElement =>
createElement('confidential-issue-sidebar', {
props: {
iid: String(iid),
fullPath,
isEditable: initialData.is_editable,
service: mediator.service,
},
}),
});
} }
function mountLockComponent(mediator) { function mountLockComponent(mediator) {
......
...@@ -51,6 +51,8 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -51,6 +51,8 @@ class Projects::IssuesController < Projects::ApplicationController
before_action only: :show do before_action only: :show do
push_frontend_feature_flag(:real_time_issue_sidebar, @project) push_frontend_feature_flag(:real_time_issue_sidebar, @project)
push_frontend_feature_flag(:confidential_notes, @project)
push_frontend_feature_flag(:confidential_apollo_sidebar, @project)
end end
around_action :allow_gitaly_ref_name_caching, only: [:discussions] around_action :allow_gitaly_ref_name_caching, only: [:discussions]
......
...@@ -2680,6 +2680,9 @@ msgstr "" ...@@ -2680,6 +2680,9 @@ msgstr ""
msgid "Apply this approval rule to any branch or a specific protected branch." msgid "Apply this approval rule to any branch or a specific protected branch."
msgstr "" msgstr ""
msgid "Applying"
msgstr ""
msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost." msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
msgstr "" msgstr ""
......
...@@ -18,6 +18,8 @@ import { ...@@ -18,6 +18,8 @@ import {
batchSuggestionsInfoMock, batchSuggestionsInfoMock,
} from '../mock_data'; } from '../mock_data';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import * as utils from '~/notes/stores/utils';
import updateIssueConfidentialMutation from '~/sidebar/components/confidential/queries/update_issue_confidential.mutation.graphql';
const TEST_ERROR_MESSAGE = 'Test error message'; const TEST_ERROR_MESSAGE = 'Test error message';
jest.mock('~/flash'); jest.mock('~/flash');
...@@ -1142,6 +1144,14 @@ describe('Actions Notes Store', () => { ...@@ -1142,6 +1144,14 @@ describe('Actions Notes Store', () => {
}); });
}); });
describe('setConfidentiality', () => {
it('calls the correct mutation with the correct args', () => {
testAction(actions.setConfidentiality, true, { noteableData: { confidential: false } }, [
{ type: mutationTypes.SET_ISSUE_CONFIDENTIAL, payload: true },
]);
});
});
describe('updateAssignees', () => { describe('updateAssignees', () => {
it('update the assignees state', done => { it('update the assignees state', done => {
testAction( testAction(
...@@ -1154,4 +1164,49 @@ describe('Actions Notes Store', () => { ...@@ -1154,4 +1164,49 @@ describe('Actions Notes Store', () => {
); );
}); });
}); });
describe('updateConfidentialityOnIssue', () => {
state = { noteableData: { confidential: false } };
const iid = '1';
const projectPath = 'full/path';
const getters = { getNoteableData: { iid } };
const actionArgs = { fullPath: projectPath, confidential: true };
const confidential = true;
beforeEach(() => {
jest
.spyOn(utils.gqClient, 'mutate')
.mockResolvedValue({ data: { issueSetConfidential: { issue: { confidential } } } });
});
it('calls gqClient mutation one time', () => {
actions.updateConfidentialityOnIssue({ commit: () => {}, state, getters }, actionArgs);
expect(utils.gqClient.mutate).toHaveBeenCalledTimes(1);
});
it('calls gqClient mutation with the correct values', () => {
actions.updateConfidentialityOnIssue({ commit: () => {}, state, getters }, actionArgs);
expect(utils.gqClient.mutate).toHaveBeenCalledWith({
mutation: updateIssueConfidentialMutation,
variables: { input: { iid, projectPath, confidential } },
});
});
describe('on success of mutation', () => {
it('calls commit with the correct values', () => {
const commitSpy = jest.fn();
return actions
.updateConfidentialityOnIssue({ commit: commitSpy, state, getters }, actionArgs)
.then(() => {
expect(commitSpy).toHaveBeenCalledWith(
mutationTypes.SET_ISSUE_CONFIDENTIAL,
confidential,
);
});
});
});
});
}); });
...@@ -806,6 +806,20 @@ describe('Notes Store mutations', () => { ...@@ -806,6 +806,20 @@ describe('Notes Store mutations', () => {
}); });
}); });
describe('SET_ISSUE_CONFIDENTIAL', () => {
let state;
beforeEach(() => {
state = { noteableData: { confidential: false } };
});
it('sets sort order', () => {
mutations.SET_ISSUE_CONFIDENTIAL(state, true);
expect(state.noteableData.confidential).toBe(true);
});
});
describe('UPDATE_ASSIGNEES', () => { describe('UPDATE_ASSIGNEES', () => {
it('should update assignees', () => { it('should update assignees', () => {
const state = { const state = {
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
exports[`Confidential Issue Sidebar Block renders for confidential = false and isEditable = false 1`] = ` exports[`Confidential Issue Sidebar Block renders for confidential = false and isEditable = false 1`] = `
<div <div
class="block issuable-sidebar-item confidentiality" class="block issuable-sidebar-item confidentiality"
iid=""
> >
<div <div
class="sidebar-collapsed-icon" class="sidebar-collapsed-icon"
...@@ -35,6 +36,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i ...@@ -35,6 +36,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
<div <div
class="no-value sidebar-item-value" class="no-value sidebar-item-value"
data-testid="not-confidential"
> >
<icon-stub <icon-stub
aria-hidden="true" aria-hidden="true"
...@@ -55,6 +57,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i ...@@ -55,6 +57,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
exports[`Confidential Issue Sidebar Block renders for confidential = false and isEditable = true 1`] = ` exports[`Confidential Issue Sidebar Block renders for confidential = false and isEditable = true 1`] = `
<div <div
class="block issuable-sidebar-item confidentiality" class="block issuable-sidebar-item confidentiality"
iid=""
> >
<div <div
class="sidebar-collapsed-icon" class="sidebar-collapsed-icon"
...@@ -95,6 +98,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i ...@@ -95,6 +98,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
<div <div
class="no-value sidebar-item-value" class="no-value sidebar-item-value"
data-testid="not-confidential"
> >
<icon-stub <icon-stub
aria-hidden="true" aria-hidden="true"
...@@ -115,6 +119,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i ...@@ -115,6 +119,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = false and i
exports[`Confidential Issue Sidebar Block renders for confidential = true and isEditable = false 1`] = ` exports[`Confidential Issue Sidebar Block renders for confidential = true and isEditable = false 1`] = `
<div <div
class="block issuable-sidebar-item confidentiality" class="block issuable-sidebar-item confidentiality"
iid=""
> >
<div <div
class="sidebar-collapsed-icon" class="sidebar-collapsed-icon"
...@@ -167,6 +172,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = true and is ...@@ -167,6 +172,7 @@ exports[`Confidential Issue Sidebar Block renders for confidential = true and is
exports[`Confidential Issue Sidebar Block renders for confidential = true and isEditable = true 1`] = ` exports[`Confidential Issue Sidebar Block renders for confidential = true and isEditable = true 1`] = `
<div <div
class="block issuable-sidebar-item confidentiality" class="block issuable-sidebar-item confidentiality"
iid=""
> >
<div <div
class="sidebar-collapsed-icon" class="sidebar-collapsed-icon"
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import EditFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue'; import EditFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue';
import eventHub from '~/sidebar/event_hub';
import createStore from '~/notes/stores';
import waitForPromises from 'helpers/wait_for_promises';
import flash from '~/flash';
jest.mock('~/sidebar/event_hub', () => ({ $emit: jest.fn() }));
jest.mock('~/flash');
describe('Edit Form Buttons', () => { describe('Edit Form Buttons', () => {
let wrapper; let wrapper;
let store;
const findConfidentialToggle = () => wrapper.find('[data-testid="confidential-toggle"]'); const findConfidentialToggle = () => wrapper.find('[data-testid="confidential-toggle"]');
const createComponent = props => { const createComponent = ({
props = {},
data = {},
confidentialApolloSidebar = false,
resolved = true,
}) => {
store = createStore();
if (resolved) {
jest.spyOn(store, 'dispatch').mockResolvedValue();
} else {
jest.spyOn(store, 'dispatch').mockRejectedValue();
}
wrapper = shallowMount(EditFormButtons, { wrapper = shallowMount(EditFormButtons, {
propsData: { propsData: {
updateConfidentialAttribute: () => {}, fullPath: '',
...props, ...props,
}, },
data() {
return {
isLoading: true,
...data,
};
},
provide: {
glFeatures: {
confidentialApolloSidebar,
},
},
store,
}); });
}; };
...@@ -19,10 +52,32 @@ describe('Edit Form Buttons', () => { ...@@ -19,10 +52,32 @@ describe('Edit Form Buttons', () => {
wrapper = null; wrapper = null;
}); });
describe('when isLoading', () => {
beforeEach(() => {
createComponent({});
wrapper.vm.$store.state.noteableData.confidential = false;
});
it('renders "Applying" in the toggle button', () => {
expect(findConfidentialToggle().text()).toBe('Applying');
});
it('disables the toggle button', () => {
expect(findConfidentialToggle().attributes('disabled')).toBe('disabled');
});
it('finds the GlLoadingIcon', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
});
describe('when not confidential', () => { describe('when not confidential', () => {
it('renders Turn On in the ', () => { it('renders Turn On in the toggle button', () => {
createComponent({ createComponent({
isConfidential: false, data: {
isLoading: false,
},
}); });
expect(findConfidentialToggle().text()).toBe('Turn On'); expect(findConfidentialToggle().text()).toBe('Turn On');
...@@ -30,12 +85,75 @@ describe('Edit Form Buttons', () => { ...@@ -30,12 +85,75 @@ describe('Edit Form Buttons', () => {
}); });
describe('when confidential', () => { describe('when confidential', () => {
it('renders on or off text based on confidentiality', () => { beforeEach(() => {
createComponent({ createComponent({
isConfidential: true, data: {
isLoading: false,
},
}); });
wrapper.vm.$store.state.noteableData.confidential = true;
});
it('renders on or off text based on confidentiality', () => {
expect(findConfidentialToggle().text()).toBe('Turn Off'); expect(findConfidentialToggle().text()).toBe('Turn Off');
}); });
describe('when clicking on the confidential toggle', () => {
it('emits updateConfidentialAttribute', () => {
findConfidentialToggle().trigger('click');
expect(eventHub.$emit).toHaveBeenCalledWith('updateConfidentialAttribute');
});
});
});
describe('when confidentialApolloSidebar is turned on', () => {
const isConfidential = true;
describe('when succeeds', () => {
beforeEach(() => {
createComponent({ data: { isLoading: false }, confidentialApolloSidebar: true });
wrapper.vm.$store.state.noteableData.confidential = isConfidential;
findConfidentialToggle().trigger('click');
});
it('dispatches the correct action', () => {
expect(store.dispatch).toHaveBeenCalledWith('updateConfidentialityOnIssue', {
confidential: !isConfidential,
fullPath: '',
});
});
it('resets loading', () => {
return waitForPromises().then(() => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
});
});
it('emits close form', () => {
return waitForPromises().then(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('closeConfidentialityForm');
});
});
});
describe('when fails', () => {
beforeEach(() => {
createComponent({
data: { isLoading: false },
confidentialApolloSidebar: true,
resolved: false,
});
wrapper.vm.$store.state.noteableData.confidential = isConfidential;
findConfidentialToggle().trigger('click');
});
it('calls flash with the correct message', () => {
expect(flash).toHaveBeenCalledWith(
'Something went wrong trying to change the confidentiality of this issue',
);
});
});
}); });
}); });
...@@ -10,6 +10,8 @@ describe('Edit Form Dropdown', () => { ...@@ -10,6 +10,8 @@ describe('Edit Form Dropdown', () => {
wrapper = shallowMount(EditForm, { wrapper = shallowMount(EditForm, {
propsData: { propsData: {
...props, ...props,
isLoading: false,
fullPath: '',
}, },
}); });
}; };
......
...@@ -7,6 +7,7 @@ import createFlash from '~/flash'; ...@@ -7,6 +7,7 @@ import createFlash from '~/flash';
import RecaptchaModal from '~/vue_shared/components/recaptcha_modal.vue'; import RecaptchaModal from '~/vue_shared/components/recaptcha_modal.vue';
import createStore from '~/notes/stores'; import createStore from '~/notes/stores';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import eventHub from '~/sidebar/event_hub';
jest.mock('~/flash'); jest.mock('~/flash');
jest.mock('~/sidebar/services/sidebar_service'); jest.mock('~/sidebar/services/sidebar_service');
...@@ -15,6 +16,9 @@ describe('Confidential Issue Sidebar Block', () => { ...@@ -15,6 +16,9 @@ describe('Confidential Issue Sidebar Block', () => {
useMockLocationHelper(); useMockLocationHelper();
let wrapper; let wrapper;
const mutate = jest
.fn()
.mockResolvedValue({ data: { issueSetConfidential: { issue: { confidential: true } } } });
const findRecaptchaModal = () => wrapper.find(RecaptchaModal); const findRecaptchaModal = () => wrapper.find(RecaptchaModal);
...@@ -25,24 +29,32 @@ describe('Confidential Issue Sidebar Block', () => { ...@@ -25,24 +29,32 @@ describe('Confidential Issue Sidebar Block', () => {
wrapper.vm wrapper.vm
.$nextTick() .$nextTick()
.then(() => { .then(() => {
const editForm = wrapper.find(EditForm); eventHub.$emit('updateConfidentialAttribute');
const { updateConfidentialAttribute } = editForm.props();
updateConfidentialAttribute();
}) })
// wait for reCAPTCHA modal to render // wait for reCAPTCHA modal to render
.then(() => wrapper.vm.$nextTick()) .then(() => wrapper.vm.$nextTick())
); );
}; };
const createComponent = propsData => { const createComponent = ({ propsData, data = {} }) => {
const store = createStore(); const store = createStore();
const service = new SidebarService(); const service = new SidebarService();
wrapper = shallowMount(ConfidentialIssueSidebar, { wrapper = shallowMount(ConfidentialIssueSidebar, {
store, store,
data() {
return data;
},
propsData: { propsData: {
service, service,
iid: '',
fullPath: '',
...propsData, ...propsData,
}, },
mocks: {
$apollo: {
mutate,
},
},
}); });
}; };
...@@ -60,7 +72,9 @@ describe('Confidential Issue Sidebar Block', () => { ...@@ -60,7 +72,9 @@ describe('Confidential Issue Sidebar Block', () => {
'renders for confidential = $confidential and isEditable = $isEditable', 'renders for confidential = $confidential and isEditable = $isEditable',
({ confidential, isEditable }) => { ({ confidential, isEditable }) => {
createComponent({ createComponent({
isEditable, propsData: {
isEditable,
},
}); });
wrapper.vm.$store.state.noteableData.confidential = confidential; wrapper.vm.$store.state.noteableData.confidential = confidential;
...@@ -73,7 +87,9 @@ describe('Confidential Issue Sidebar Block', () => { ...@@ -73,7 +87,9 @@ describe('Confidential Issue Sidebar Block', () => {
describe('if editable', () => { describe('if editable', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
isEditable: true, propsData: {
isEditable: true,
},
}); });
wrapper.vm.$store.state.noteableData.confidential = true; wrapper.vm.$store.state.noteableData.confidential = 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