Commit f996f7e0 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch '49854-recover-mr-regression-fixes-safe' into 'master'

Resolve "Recover reverted fixes to Merge Request refactor regressions"

Closes #49242, #49343, #48876, #48877, #48817, #48602, and #49843

See merge request gitlab-org/gitlab-ce!20968
parents cbbcd903 f851bb80
......@@ -53,4 +53,8 @@ export default class Autosave {
return window.localStorage.removeItem(this.key);
}
dispose() {
this.field.off('input');
}
}
......@@ -30,6 +30,7 @@ export default {
:render-header="false"
:render-diff-file="false"
:always-expanded="true"
:discussions-by-diff-order="true"
/>
</ul>
</div>
......
......@@ -189,7 +189,6 @@ export default {
</button>
<a
v-if="lineNumber"
v-once
:data-linenumber="lineNumber"
:href="lineHref"
>
......
<script>
import $ from 'jquery';
import { mapState, mapGetters, mapActions } from 'vuex';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import noteForm from '../../notes/components/note_form.vue';
import { getNoteFormData } from '../store/utils';
import Autosave from '../../autosave';
import { DIFF_NOTE_TYPE, NOTE_TYPE } from '../constants';
import autosave from '../../notes/mixins/autosave';
import { DIFF_NOTE_TYPE } from '../constants';
export default {
components: {
noteForm,
},
mixins: [autosave],
props: {
diffFileHash: {
type: String,
......@@ -41,28 +41,35 @@ export default {
},
mounted() {
if (this.isLoggedIn) {
const noteableData = this.getNoteableData;
const keys = [
NOTE_TYPE,
this.noteableType,
noteableData.id,
noteableData.diff_head_sha,
this.noteableData.diff_head_sha,
DIFF_NOTE_TYPE,
noteableData.source_project_id,
this.noteableData.source_project_id,
this.line.lineCode,
];
this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), keys);
this.initAutoSave(this.noteableData, keys);
}
},
methods: {
...mapActions('diffs', ['cancelCommentForm']),
...mapActions(['saveNote', 'refetchDiscussionById']),
handleCancelCommentForm() {
this.autosave.reset();
handleCancelCommentForm(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) {
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
// eslint-disable-next-line no-alert
if (!window.confirm(msg)) {
return;
}
}
this.cancelCommentForm({
lineCode: this.line.lineCode,
});
this.$nextTick(() => {
this.resetAutoSave();
});
},
handleSaveNote(note) {
const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash);
......
......@@ -101,7 +101,6 @@ export default {
class="diff-line-num new_line"
/>
<td
v-once
:class="line.type"
class="line_content"
v-html="line.richText"
......
......@@ -119,7 +119,6 @@ export default {
class="diff-line-num old_line"
/>
<td
v-once
:id="line.left.lineCode"
:class="parallelViewLeftLineType"
class="line_content parallel left-side"
......@@ -140,7 +139,6 @@ export default {
class="diff-line-num new_line"
/>
<td
v-once
:id="line.right.lineCode"
:class="line.right.type"
class="line_content parallel right-side"
......
......@@ -5,19 +5,20 @@ import resolvedSvg from 'icons/_icon_status_success_solid.svg';
import mrIssueSvg from 'icons/_icon_mr_issue.svg';
import nextDiscussionSvg from 'icons/_next_discussion.svg';
import { pluralize } from '../../lib/utils/text_utility';
import { scrollToElement } from '../../lib/utils/common_utils';
import discussionNavigation from '../mixins/discussion_navigation';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
mixins: [discussionNavigation],
computed: {
...mapGetters([
'getUserData',
'getNoteableData',
'discussionCount',
'unresolvedDiscussions',
'firstUnresolvedDiscussionId',
'resolvedDiscussionCount',
]),
isLoggedIn() {
......@@ -35,11 +36,6 @@ export default {
resolveAllDiscussionsIssuePath() {
return this.getNoteableData.create_issue_to_resolve_discussions_path;
},
firstUnresolvedDiscussionId() {
const item = this.unresolvedDiscussions[0] || {};
return item.id;
},
},
created() {
this.resolveSvg = resolveSvg;
......@@ -50,22 +46,10 @@ export default {
methods: {
...mapActions(['expandDiscussion']),
jumpToFirstUnresolvedDiscussion() {
const discussionId = this.firstUnresolvedDiscussionId;
if (!discussionId) {
return;
}
const el = document.querySelector(`[data-discussion-id="${discussionId}"]`);
const activeTab = window.mrTabs.currentAction;
if (activeTab === 'commits' || activeTab === 'pipelines') {
window.mrTabs.activateTab('show');
}
const diffTab = window.mrTabs.currentAction === 'diffs';
const discussionId = this.firstUnresolvedDiscussionId(diffTab);
if (el) {
this.expandDiscussion({ discussionId });
scrollToElement(el);
}
this.jumpToDiscussion(discussionId);
},
},
};
......
......@@ -7,7 +7,7 @@ import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
export default {
name: 'IssueNoteForm',
name: 'NoteForm',
components: {
issueWarning,
markdownField,
......
<script>
import _ from 'underscore';
import { mapActions, mapGetters } from 'vuex';
import resolveDiscussionsSvg from 'icons/_icon_mr_issue.svg';
import nextDiscussionsSvg from 'icons/_next_discussion.svg';
import { convertObjectPropsToCamelCase, scrollToElement } from '~/lib/utils/common_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { truncateSha } from '~/lib/utils/text_utility';
import systemNote from '~/vue_shared/components/notes/system_note.vue';
import { s__ } from '~/locale';
import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
......@@ -20,6 +20,7 @@ import placeholderSystemNote from '../../vue_shared/components/notes/placeholder
import autosave from '../mixins/autosave';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
import discussionNavigation from '../mixins/discussion_navigation';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
......@@ -39,7 +40,7 @@ export default {
directives: {
tooltip,
},
mixins: [autosave, noteable, resolvable],
mixins: [autosave, noteable, resolvable, discussionNavigation],
props: {
discussion: {
type: Object,
......@@ -60,6 +61,11 @@ export default {
required: false,
default: false,
},
discussionsByDiffOrder: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
......@@ -74,7 +80,12 @@ export default {
'discussionCount',
'resolvedDiscussionCount',
'allDiscussions',
'unresolvedDiscussionsIdsByDiff',
'unresolvedDiscussionsIdsByDate',
'unresolvedDiscussions',
'unresolvedDiscussionsIdsOrdered',
'nextUnresolvedDiscussionId',
'isLastUnresolvedDiscussion',
]),
transformedDiscussion() {
return {
......@@ -125,6 +136,10 @@ export default {
hasMultipleUnresolvedDiscussions() {
return this.unresolvedDiscussions.length > 1;
},
showJumpToNextDiscussion() {
return this.hasMultipleUnresolvedDiscussions &&
!this.isLastUnresolvedDiscussion(this.discussion.id, this.discussionsByDiffOrder);
},
shouldRenderDiffs() {
const { diffDiscussion, diffFile } = this.transformedDiscussion;
......@@ -144,19 +159,17 @@ export default {
return this.isDiffDiscussion ? '' : 'card discussion-wrapper';
},
},
mounted() {
if (this.isReplying) {
this.initAutoSave(this.transformedDiscussion);
}
},
updated() {
if (this.isReplying) {
if (!this.autosave) {
this.initAutoSave(this.transformedDiscussion);
watch: {
isReplying() {
if (this.isReplying) {
this.$nextTick(() => {
// Pass an extra key to separate reply and note edit forms
this.initAutoSave(this.transformedDiscussion, ['Reply']);
});
} else {
this.setAutoSave();
this.disposeAutoSave();
}
}
},
},
created() {
this.resolveDiscussionsSvg = resolveDiscussionsSvg;
......@@ -194,16 +207,18 @@ export default {
showReplyForm() {
this.isReplying = true;
},
cancelReplyForm(shouldConfirm) {
if (shouldConfirm && this.$refs.noteForm.isDirty) {
cancelReplyForm(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) {
const msg = s__('Notes|Are you sure you want to cancel creating this comment?');
// eslint-disable-next-line no-alert
if (!window.confirm('Are you sure you want to cancel creating this comment?')) {
if (!window.confirm(msg)) {
return;
}
}
this.resetAutoSave();
this.isReplying = false;
this.resetAutoSave();
},
saveReply(noteText, form, callback) {
const postData = {
......@@ -241,21 +256,10 @@ Please check your network connection and try again.`;
});
},
jumpToNextDiscussion() {
const discussionIds = this.allDiscussions.map(d => d.id);
const unresolvedIds = this.unresolvedDiscussions.map(d => d.id);
const currentIndex = discussionIds.indexOf(this.discussion.id);
const remainingAfterCurrent = discussionIds.slice(currentIndex + 1);
const nextIndex = _.findIndex(remainingAfterCurrent, id => unresolvedIds.indexOf(id) > -1);
if (nextIndex > -1) {
const nextId = remainingAfterCurrent[nextIndex];
const el = document.querySelector(`[data-discussion-id="${nextId}"]`);
const nextId =
this.nextUnresolvedDiscussionId(this.discussion.id, this.discussionsByDiffOrder);
if (el) {
this.expandDiscussion({ discussionId: nextId });
scrollToElement(el);
}
}
this.jumpToDiscussion(nextId);
},
},
};
......@@ -397,7 +401,7 @@ Please check your network connection and try again.`;
</a>
</div>
<div
v-if="hasMultipleUnresolvedDiscussions"
v-if="showJumpToNextDiscussion"
class="btn-group"
role="group">
<button
......@@ -420,7 +424,8 @@ Please check your network connection and try again.`;
:is-editing="false"
save-button-title="Comment"
@handleFormUpdate="saveReply"
@cancelForm="cancelReplyForm" />
@cancelForm="cancelReplyForm"
/>
<note-signed-out-widget v-if="!canReply" />
</div>
</div>
......
......@@ -4,12 +4,18 @@ import { capitalizeFirstCharacter } from '../../lib/utils/text_utility';
export default {
methods: {
initAutoSave(noteable) {
this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), [
initAutoSave(noteable, extraKeys = []) {
let keys = [
'Note',
capitalizeFirstCharacter(noteable.noteable_type),
capitalizeFirstCharacter(noteable.noteable_type || noteable.noteableType),
noteable.id,
]);
];
if (extraKeys) {
keys = keys.concat(extraKeys);
}
this.autosave = new Autosave($(this.$refs.noteForm.$refs.textarea), keys);
},
resetAutoSave() {
this.autosave.reset();
......@@ -17,5 +23,8 @@ export default {
setAutoSave() {
this.autosave.save();
},
disposeAutoSave() {
this.autosave.dispose();
},
},
};
import { scrollToElement } from '~/lib/utils/common_utils';
export default {
methods: {
jumpToDiscussion(id) {
if (id) {
const activeTab = window.mrTabs.currentAction;
const selector =
activeTab === 'diffs'
? `ul.notes[data-discussion-id="${id}"]`
: `div.discussion[data-discussion-id="${id}"]`;
const el = document.querySelector(selector);
if (activeTab === 'commits' || activeTab === 'pipelines') {
window.mrTabs.activateTab('show');
}
if (el) {
this.expandDiscussion({ discussionId: id });
scrollToElement(el);
return true;
}
}
return false;
},
},
};
......@@ -82,6 +82,9 @@ export const allDiscussions = (state, getters) => {
return Object.values(resolved).concat(unresolved);
};
export const allResolvableDiscussions = (state, getters) =>
getters.allDiscussions.filter(d => !d.individual_note && d.resolvable);
export const resolvedDiscussionsById = state => {
const map = {};
......@@ -98,6 +101,51 @@ export const resolvedDiscussionsById = state => {
return map;
};
// Gets Discussions IDs ordered by the date of their initial note
export const unresolvedDiscussionsIdsByDate = (state, getters) =>
getters.allResolvableDiscussions
.filter(d => !d.resolved)
.sort((a, b) => {
const aDate = new Date(a.notes[0].created_at);
const bDate = new Date(b.notes[0].created_at);
if (aDate < bDate) {
return -1;
}
return aDate === bDate ? 0 : 1;
})
.map(d => d.id);
// Gets Discussions IDs ordered by their position in the diff
//
// Sorts the array of resolvable yet unresolved discussions by
// comparing file names first. If file names are the same, compares
// line numbers.
export const unresolvedDiscussionsIdsByDiff = (state, getters) =>
getters.allResolvableDiscussions
.filter(d => !d.resolved)
.sort((a, b) => {
if (!a.diff_file || !b.diff_file) {
return 0;
}
// Get file names comparison result
const filenameComparison = a.diff_file.file_path.localeCompare(b.diff_file.file_path);
// Get the line numbers, to compare within the same file
const aLines = [a.position.formatter.new_line, a.position.formatter.old_line];
const bLines = [b.position.formatter.new_line, b.position.formatter.old_line];
return filenameComparison < 0 ||
(filenameComparison === 0 &&
// .max() because one of them might be zero (if removed/added)
Math.max(aLines[0], aLines[1]) < Math.max(bLines[0], bLines[1]))
? -1
: 1;
})
.map(d => d.id);
export const resolvedDiscussionCount = (state, getters) => {
const resolvedMap = getters.resolvedDiscussionsById;
......@@ -114,5 +162,42 @@ export const discussionTabCounter = state => {
return all.length;
};
// Returns the list of discussion IDs ordered according to given parameter
// @param {Boolean} diffOrder - is ordered by diff?
export const unresolvedDiscussionsIdsOrdered = (state, getters) => diffOrder => {
if (diffOrder) {
return getters.unresolvedDiscussionsIdsByDiff;
}
return getters.unresolvedDiscussionsIdsByDate;
};
// Checks if a given discussion is the last in the current order (diff or date)
// @param {Boolean} discussionId - id of the discussion
// @param {Boolean} diffOrder - is ordered by diff?
export const isLastUnresolvedDiscussion = (state, getters) => (discussionId, diffOrder) => {
const idsOrdered = getters.unresolvedDiscussionsIdsOrdered(diffOrder);
const lastDiscussionId = idsOrdered[idsOrdered.length - 1];
return lastDiscussionId === discussionId;
};
// Gets the ID of the discussion following the one provided, respecting order (diff or date)
// @param {Boolean} discussionId - id of the current discussion
// @param {Boolean} diffOrder - is ordered by diff?
export const nextUnresolvedDiscussionId = (state, getters) => (discussionId, diffOrder) => {
const idsOrdered = getters.unresolvedDiscussionsIdsOrdered(diffOrder);
const currentIndex = idsOrdered.indexOf(discussionId);
return idsOrdered.slice(currentIndex + 1, currentIndex + 2)[0];
};
// @param {Boolean} diffOrder - is ordered by diff?
export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => {
if (diffOrder) {
return getters.unresolvedDiscussionsIdsByDiff[0];
}
return getters.unresolvedDiscussionsIdsByDate[0];
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
---
title: Fix rendering of the context lines in MR diffs page.
merge_request: 20968
author:
type: fixed
---
title: Fix autosave and ESC confirmation issues for MR discussions.
merge_request: 20968
author:
type: fixed
---
title: Fix navigation to First and Next discussion on MR Changes tab.
merge_request: 20968
author:
type: fixed
......@@ -3656,6 +3656,9 @@ msgstr ""
msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
msgstr ""
msgid "Notes|Are you sure you want to cancel creating this comment?"
msgstr ""
msgid "Notification events"
msgstr ""
......
......@@ -342,8 +342,9 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
end
it 'shows jump to next discussion button' do
expect(page.all('.discussion-reply-holder', count: 2)).to all(have_selector('.discussion-next-btn'))
it 'shows jump to next discussion button, apart from the last one' do
expect(page).to have_selector('.discussion-reply-holder', count: 2)
expect(page).to have_selector('.discussion-reply-holder .discussion-next-btn', count: 1)
end
it 'displays next discussion even if hidden' do
......
......@@ -59,12 +59,10 @@ describe('Autosave', () => {
Autosave.prototype.restore.call(autosave);
expect(
field.trigger,
).toHaveBeenCalled();
expect(field.trigger).toHaveBeenCalled();
});
it('triggers native event', (done) => {
it('triggers native event', done => {
autosave.field.get(0).addEventListener('change', () => {
done();
});
......@@ -81,9 +79,7 @@ describe('Autosave', () => {
it('does not trigger event', () => {
spyOn(field, 'trigger').and.callThrough();
expect(
field.trigger,
).not.toHaveBeenCalled();
expect(field.trigger).not.toHaveBeenCalled();
});
});
});
......
......@@ -3,6 +3,7 @@ import DiffLineNoteForm from '~/diffs/components/diff_line_note_form.vue';
import store from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
import { noteableDataMock } from '../../notes/mock_data';
describe('DiffLineNoteForm', () => {
let component;
......@@ -21,10 +22,9 @@ describe('DiffLineNoteForm', () => {
noteTargetLine: diffLines[0],
});
Object.defineProperty(component, 'isLoggedIn', {
get() {
return true;
},
Object.defineProperties(component, {
noteableData: { value: noteableDataMock },
isLoggedIn: { value: true },
});
component.$mount();
......@@ -32,12 +32,37 @@ describe('DiffLineNoteForm', () => {
describe('methods', () => {
describe('handleCancelCommentForm', () => {
it('should call cancelCommentForm with lineCode', () => {
it('should ask for confirmation when shouldConfirm and isDirty passed as truthy', () => {
spyOn(window, 'confirm').and.returnValue(false);
component.handleCancelCommentForm(true, true);
expect(window.confirm).toHaveBeenCalled();
});
it('should ask for confirmation when one of the params false', () => {
spyOn(window, 'confirm').and.returnValue(false);
component.handleCancelCommentForm(true, false);
expect(window.confirm).not.toHaveBeenCalled();
component.handleCancelCommentForm(false, true);
expect(window.confirm).not.toHaveBeenCalled();
});
it('should call cancelCommentForm with lineCode', done => {
spyOn(window, 'confirm');
spyOn(component, 'cancelCommentForm');
spyOn(component, 'resetAutoSave');
component.handleCancelCommentForm();
expect(component.cancelCommentForm).toHaveBeenCalledWith({
lineCode: diffLines[0].lineCode,
expect(window.confirm).not.toHaveBeenCalled();
component.$nextTick(() => {
expect(component.cancelCommentForm).toHaveBeenCalledWith({
lineCode: diffLines[0].lineCode,
});
expect(component.resetAutoSave).toHaveBeenCalled();
done();
});
});
});
......@@ -66,7 +91,7 @@ describe('DiffLineNoteForm', () => {
describe('mounted', () => {
it('should init autosave', () => {
const key = 'autosave/Note/issue///DiffNote//1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1';
const key = 'autosave/Note/Issue/98//DiffNote//1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1';
expect(component.autosave).toBeDefined();
expect(component.autosave.key).toEqual(key);
......
......@@ -46,7 +46,7 @@ describe('DiscussionCounter component', () => {
discussions,
});
setFixtures(`
<div data-discussion-id="${firstDiscussionId}"></div>
<div class="discussion" data-discussion-id="${firstDiscussionId}"></div>
`);
vm.jumpToFirstUnresolvedDiscussion();
......
......@@ -14,6 +14,7 @@ describe('noteable_discussion component', () => {
preloadFixtures(discussionWithTwoUnresolvedNotes);
beforeEach(() => {
window.mrTabs = {};
store = createStore();
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
......@@ -46,10 +47,15 @@ describe('noteable_discussion component', () => {
it('should toggle reply form', done => {
vm.$el.querySelector('.js-vue-discussion-reply').click();
Vue.nextTick(() => {
expect(vm.$refs.noteForm).not.toBeNull();
expect(vm.isReplying).toEqual(true);
done();
// There is a watcher for `isReplying` which will init autosave in the next tick
Vue.nextTick(() => {
expect(vm.$refs.noteForm).not.toBeNull();
done();
});
});
});
......@@ -101,33 +107,29 @@ describe('noteable_discussion component', () => {
describe('methods', () => {
describe('jumpToNextDiscussion', () => {
it('expands next unresolved discussion', () => {
spyOn(vm, 'expandDiscussion').and.stub();
const discussions = [
discussionMock,
{
...discussionMock,
id: discussionMock.id + 1,
notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: true }],
},
{
...discussionMock,
id: discussionMock.id + 2,
notes: [{ ...discussionMock.notes[0], resolvable: true, resolved: false }],
},
];
const nextDiscussionId = discussionMock.id + 2;
store.replaceState({
...store.state,
discussions,
});
setFixtures(`
<div data-discussion-id="${nextDiscussionId}"></div>
`);
it('expands next unresolved discussion', done => {
const discussion2 = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
discussion2.resolved = false;
discussion2.id = 'next'; // prepare this for being identified as next one (to be jumped to)
vm.$store.dispatch('setInitialNotes', [discussionMock, discussion2]);
window.mrTabs.currentAction = 'show';
Vue.nextTick()
.then(() => {
spyOn(vm, 'expandDiscussion').and.stub();
const nextDiscussionId = discussion2.id;
setFixtures(`
<div class="discussion" data-discussion-id="${nextDiscussionId}"></div>
`);
vm.jumpToNextDiscussion();
vm.jumpToNextDiscussion();
expect(vm.expandDiscussion).toHaveBeenCalledWith({ discussionId: nextDiscussionId });
expect(vm.expandDiscussion).toHaveBeenCalledWith({ discussionId: nextDiscussionId });
})
.then(done)
.catch(done.fail);
});
});
});
......
......@@ -1168,3 +1168,87 @@ export const collapsedSystemNotes = [
diff_discussion: false,
},
];
export const discussion1 = {
id: 'abc1',
resolvable: true,
resolved: false,
diff_file: {
file_path: 'about.md',
},
position: {
formatter: {
new_line: 50,
old_line: null,
},
},
notes: [
{
created_at: '2018-07-04T16:25:41.749Z',
},
],
};
export const resolvedDiscussion1 = {
id: 'abc1',
resolvable: true,
resolved: true,
diff_file: {
file_path: 'about.md',
},
position: {
formatter: {
new_line: 50,
old_line: null,
},
},
notes: [
{
created_at: '2018-07-04T16:25:41.749Z',
},
],
};
export const discussion2 = {
id: 'abc2',
resolvable: true,
resolved: false,
diff_file: {
file_path: 'README.md',
},
position: {
formatter: {
new_line: null,
old_line: 20,
},
},
notes: [
{
created_at: '2018-07-04T12:05:41.749Z',
},
],
};
export const discussion3 = {
id: 'abc3',
resolvable: true,
resolved: false,
diff_file: {
file_path: 'README.md',
},
position: {
formatter: {
new_line: 21,
old_line: null,
},
},
notes: [
{
created_at: '2018-07-05T17:25:41.749Z',
},
],
};
export const unresolvableDiscussion = {
resolvable: false,
};
......@@ -5,6 +5,11 @@ import {
noteableDataMock,
individualNote,
collapseNotesMock,
discussion1,
discussion2,
discussion3,
resolvedDiscussion1,
unresolvableDiscussion,
} from '../mock_data';
const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json';
......@@ -109,4 +114,154 @@ describe('Getters Notes Store', () => {
expect(getters.isNotesFetched(state)).toBeFalsy();
});
});
describe('allResolvableDiscussions', () => {
it('should return only resolvable discussions in same order', () => {
const localGetters = {
allDiscussions: [
discussion3,
unresolvableDiscussion,
discussion1,
unresolvableDiscussion,
discussion2,
],
};
expect(getters.allResolvableDiscussions(state, localGetters)).toEqual([
discussion3,
discussion1,
discussion2,
]);
});
it('should return empty array if there are no resolvable discussions', () => {
const localGetters = {
allDiscussions: [unresolvableDiscussion, unresolvableDiscussion],
};
expect(getters.allResolvableDiscussions(state, localGetters)).toEqual([]);
});
});
describe('unresolvedDiscussionsIdsByDiff', () => {
it('should return all discussions IDs in diff order', () => {
const localGetters = {
allResolvableDiscussions: [discussion3, discussion1, discussion2],
};
expect(getters.unresolvedDiscussionsIdsByDiff(state, localGetters)).toEqual([
'abc1',
'abc2',
'abc3',
]);
});
it('should return empty array if all discussions have been resolved', () => {
const localGetters = {
allResolvableDiscussions: [resolvedDiscussion1],
};
expect(getters.unresolvedDiscussionsIdsByDiff(state, localGetters)).toEqual([]);
});
});
describe('unresolvedDiscussionsIdsByDate', () => {
it('should return all discussions in date ascending order', () => {
const localGetters = {
allResolvableDiscussions: [discussion3, discussion1, discussion2],
};
expect(getters.unresolvedDiscussionsIdsByDate(state, localGetters)).toEqual([
'abc2',
'abc1',
'abc3',
]);
});
it('should return empty array if all discussions have been resolved', () => {
const localGetters = {
allResolvableDiscussions: [resolvedDiscussion1],
};
expect(getters.unresolvedDiscussionsIdsByDate(state, localGetters)).toEqual([]);
});
});
describe('unresolvedDiscussionsIdsOrdered', () => {
const localGetters = {
unresolvedDiscussionsIdsByDate: ['123', '456'],
unresolvedDiscussionsIdsByDiff: ['abc', 'def'],
};
it('should return IDs ordered by diff when diffOrder param is true', () => {
expect(getters.unresolvedDiscussionsIdsOrdered(state, localGetters)(true)).toEqual([
'abc',
'def',
]);
});
it('should return IDs ordered by date when diffOrder param is not true', () => {
expect(getters.unresolvedDiscussionsIdsOrdered(state, localGetters)(false)).toEqual([
'123',
'456',
]);
expect(getters.unresolvedDiscussionsIdsOrdered(state, localGetters)(undefined)).toEqual([
'123',
'456',
]);
});
});
describe('isLastUnresolvedDiscussion', () => {
const localGetters = {
unresolvedDiscussionsIdsOrdered: () => ['123', '456', '789'],
};
it('should return true if the discussion id provided is the last', () => {
expect(getters.isLastUnresolvedDiscussion(state, localGetters)('789')).toBe(true);
});
it('should return false if the discussion id provided is not the last', () => {
expect(getters.isLastUnresolvedDiscussion(state, localGetters)('123')).toBe(false);
expect(getters.isLastUnresolvedDiscussion(state, localGetters)('456')).toBe(false);
});
});
describe('nextUnresolvedDiscussionId', () => {
const localGetters = {
unresolvedDiscussionsIdsOrdered: () => ['123', '456', '789'],
};
it('should return the ID of the discussion after the ID provided', () => {
expect(getters.nextUnresolvedDiscussionId(state, localGetters)('123')).toBe('456');
expect(getters.nextUnresolvedDiscussionId(state, localGetters)('456')).toBe('789');
expect(getters.nextUnresolvedDiscussionId(state, localGetters)('789')).toBe(undefined);
});
});
describe('firstUnresolvedDiscussionId', () => {
const localGetters = {
unresolvedDiscussionsIdsByDate: ['123', '456'],
unresolvedDiscussionsIdsByDiff: ['abc', 'def'],
};
it('should return the first discussion id by diff when diffOrder param is true', () => {
expect(getters.firstUnresolvedDiscussionId(state, localGetters)(true)).toBe('abc');
});
it('should return the first discussion id by date when diffOrder param is not true', () => {
expect(getters.firstUnresolvedDiscussionId(state, localGetters)(false)).toBe('123');
expect(getters.firstUnresolvedDiscussionId(state, localGetters)(undefined)).toBe('123');
});
it('should be falsy if all discussions are resolved', () => {
const localGettersFalsy = {
unresolvedDiscussionsIdsByDiff: [],
unresolvedDiscussionsIdsByDate: [],
};
expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(true)).toBeFalsy();
expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(false)).toBeFalsy();
});
});
});
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