Commit 6d1b5850 authored by Mike Greiling's avatar Mike Greiling

Merge branch 'tz-mr-new-datastructure' into 'master'

Vue MR Page - Data structure update

See merge request gitlab-org/gitlab-ce!21062
parents d7c694b6 f32ec46e
/* eslint-disable object-shorthand, func-names, comma-dangle, no-else-return, quotes */ /* eslint-disable object-shorthand, func-names, no-else-return */
/* global CommentsStore */ /* global CommentsStore */
/* global ResolveService */ /* global ResolveService */
...@@ -25,44 +25,44 @@ const ResolveDiscussionBtn = Vue.extend({ ...@@ -25,44 +25,44 @@ const ResolveDiscussionBtn = Vue.extend({
}; };
}, },
computed: { computed: {
showButton: function () { showButton: function() {
if (this.discussion) { if (this.discussion) {
return this.discussion.isResolvable(); return this.discussion.isResolvable();
} else { } else {
return false; return false;
} }
}, },
isDiscussionResolved: function () { isDiscussionResolved: function() {
if (this.discussion) { if (this.discussion) {
return this.discussion.isResolved(); return this.discussion.isResolved();
} else { } else {
return false; return false;
} }
}, },
buttonText: function () { buttonText: function() {
if (this.isDiscussionResolved) { if (this.isDiscussionResolved) {
return "Unresolve discussion"; return 'Unresolve discussion';
} else { } else {
return "Resolve discussion"; return 'Resolve discussion';
} }
}, },
loading: function () { loading: function() {
if (this.discussion) { if (this.discussion) {
return this.discussion.loading; return this.discussion.loading;
} else { } else {
return false; return false;
} }
} },
}, },
created: function () { created: function() {
CommentsStore.createDiscussion(this.discussionId, this.canResolve); CommentsStore.createDiscussion(this.discussionId, this.canResolve);
this.discussion = CommentsStore.state[this.discussionId]; this.discussion = CommentsStore.state[this.discussionId];
}, },
methods: { methods: {
resolve: function () { resolve: function() {
ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId); ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
} },
}, },
}); });
......
...@@ -8,9 +8,7 @@ window.gl = window.gl || {}; ...@@ -8,9 +8,7 @@ window.gl = window.gl || {};
class ResolveServiceClass { class ResolveServiceClass {
constructor(root) { constructor(root) {
this.noteResource = Vue.resource( this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve?html=true`);
`${root}/notes{/noteId}/resolve?html=true`,
);
this.discussionResource = Vue.resource( this.discussionResource = Vue.resource(
`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve?html=true`, `${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve?html=true`,
); );
...@@ -51,10 +49,7 @@ class ResolveServiceClass { ...@@ -51,10 +49,7 @@ class ResolveServiceClass {
discussion.updateHeadline(data); discussion.updateHeadline(data);
}) })
.catch( .catch(
() => () => new Flash('An error occurred when trying to resolve a discussion. Please try again.'),
new Flash(
'An error occurred when trying to resolve a discussion. Please try again.',
),
); );
} }
......
...@@ -59,7 +59,7 @@ export default { ...@@ -59,7 +59,7 @@ export default {
emailPatchPath: state => state.diffs.emailPatchPath, emailPatchPath: state => state.diffs.emailPatchPath,
}), }),
...mapGetters('diffs', ['isParallelView']), ...mapGetters('diffs', ['isParallelView']),
...mapGetters(['isNotesFetched']), ...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
targetBranch() { targetBranch() {
return { return {
branchName: this.targetBranchName, branchName: this.targetBranchName,
...@@ -112,13 +112,26 @@ export default { ...@@ -112,13 +112,26 @@ export default {
}, },
created() { created() {
this.adjustView(); this.adjustView();
eventHub.$once('fetchedNotesData', this.setDiscussions);
}, },
methods: { methods: {
...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles', 'startRenderDiffsQueue']), ...mapActions('diffs', [
'setBaseConfig',
'fetchDiffFiles',
'startRenderDiffsQueue',
'assignDiscussionsToDiff',
]),
fetchData() { fetchData() {
this.fetchDiffFiles() this.fetchDiffFiles()
.then(() => { .then(() => {
requestIdleCallback(this.startRenderDiffsQueue, { timeout: 1000 }); requestIdleCallback(
() => {
this.setDiscussions();
this.startRenderDiffsQueue();
},
{ timeout: 1000 },
);
}) })
.catch(() => { .catch(() => {
createFlash(__('Something went wrong on our end. Please try again!')); createFlash(__('Something went wrong on our end. Please try again!'));
...@@ -128,6 +141,16 @@ export default { ...@@ -128,6 +141,16 @@ export default {
eventHub.$emit('fetchNotesData'); eventHub.$emit('fetchNotesData');
} }
}, },
setDiscussions() {
if (this.isNotesFetched) {
requestIdleCallback(
() => {
this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode);
},
{ timeout: 1000 },
);
}
},
adjustView() { adjustView() {
if (this.shouldShow && this.isParallelView) { if (this.shouldShow && this.isParallelView) {
window.mrTabs.expandViewContainer(); window.mrTabs.expandViewContainer();
......
<script> <script>
import { mapActions } from 'vuex';
import noteableDiscussion from '../../notes/components/noteable_discussion.vue'; import noteableDiscussion from '../../notes/components/noteable_discussion.vue';
export default { export default {
...@@ -11,6 +12,14 @@ export default { ...@@ -11,6 +12,14 @@ export default {
required: true, required: true,
}, },
}, },
methods: {
...mapActions('diffs', ['removeDiscussionsFromDiff']),
deleteNoteHandler(discussion) {
if (discussion.notes.length <= 1) {
this.removeDiscussionsFromDiff(discussion);
}
},
},
}; };
</script> </script>
...@@ -31,6 +40,7 @@ export default { ...@@ -31,6 +40,7 @@ export default {
:render-diff-file="false" :render-diff-file="false"
:always-expanded="true" :always-expanded="true"
:discussions-by-diff-order="true" :discussions-by-diff-order="true"
@noteDeleted="deleteNoteHandler"
/> />
</ul> </ul>
</div> </div>
......
<script> <script>
import { mapActions } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore'; import _ from 'underscore';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import createFlash from '~/flash'; import createFlash from '~/flash';
...@@ -30,6 +30,7 @@ export default { ...@@ -30,6 +30,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
isCollapsed() { isCollapsed() {
return this.file.collapsed || false; return this.file.collapsed || false;
}, },
...@@ -44,23 +45,23 @@ export default { ...@@ -44,23 +45,23 @@ export default {
); );
}, },
showExpandMessage() { showExpandMessage() {
return this.isCollapsed && !this.isLoadingCollapsedDiff && !this.file.tooLarge; return (
!this.isCollapsed &&
!this.file.highlightedDiffLines &&
!this.isLoadingCollapsedDiff &&
!this.file.tooLarge &&
this.file.text
);
}, },
showLoadingIcon() { showLoadingIcon() {
return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed); return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
}, },
}, },
methods: { methods: {
...mapActions('diffs', ['loadCollapsedDiff']), ...mapActions('diffs', ['loadCollapsedDiff', 'assignDiscussionsToDiff']),
handleToggle() { handleToggle() {
const { collapsed, highlightedDiffLines, parallelDiffLines } = this.file; const { highlightedDiffLines, parallelDiffLines } = this.file;
if (!highlightedDiffLines && parallelDiffLines !== undefined && !parallelDiffLines.length) {
if (
collapsed &&
!highlightedDiffLines &&
parallelDiffLines !== undefined &&
!parallelDiffLines.length
) {
this.handleLoadCollapsedDiff(); this.handleLoadCollapsedDiff();
} else { } else {
this.file.collapsed = !this.file.collapsed; this.file.collapsed = !this.file.collapsed;
...@@ -76,6 +77,14 @@ export default { ...@@ -76,6 +77,14 @@ export default {
this.file.collapsed = false; this.file.collapsed = false;
this.file.renderIt = true; this.file.renderIt = true;
}) })
.then(() => {
requestIdleCallback(
() => {
this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode);
},
{ timeout: 1000 },
);
})
.catch(() => { .catch(() => {
this.isLoadingCollapsedDiff = false; this.isLoadingCollapsedDiff = false;
createFlash(__('Something went wrong on our end. Please try again!')); createFlash(__('Something went wrong on our end. Please try again!'));
...@@ -136,11 +145,11 @@ export default { ...@@ -136,11 +145,11 @@ export default {
:diff-file="file" :diff-file="file"
/> />
<loading-icon <loading-icon
v-else-if="showLoadingIcon" v-if="showLoadingIcon"
class="diff-content loading" class="diff-content loading"
/> />
<div <div
v-if="showExpandMessage" v-else-if="showExpandMessage"
class="nothing-here-block diff-collapsed" class="nothing-here-block diff-collapsed"
> >
{{ __('This diff is collapsed.') }} {{ __('This diff is collapsed.') }}
......
...@@ -13,6 +13,10 @@ export default { ...@@ -13,6 +13,10 @@ export default {
Icon, Icon,
}, },
props: { props: {
line: {
type: Object,
required: true,
},
fileHash: { fileHash: {
type: String, type: String,
required: true, required: true,
...@@ -21,31 +25,16 @@ export default { ...@@ -21,31 +25,16 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
lineType: {
type: String,
required: false,
default: '',
},
lineNumber: { lineNumber: {
type: Number, type: Number,
required: false, required: false,
default: 0, default: 0,
}, },
lineCode: {
type: String,
required: false,
default: '',
},
linePosition: { linePosition: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
metaData: {
type: Object,
required: false,
default: () => ({}),
},
showCommentButton: { showCommentButton: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -76,11 +65,6 @@ export default { ...@@ -76,11 +65,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
discussions: {
type: Array,
required: false,
default: () => [],
},
}, },
computed: { computed: {
...mapState({ ...mapState({
...@@ -89,7 +73,7 @@ export default { ...@@ -89,7 +73,7 @@ export default {
}), }),
...mapGetters(['isLoggedIn']), ...mapGetters(['isLoggedIn']),
lineHref() { lineHref() {
return this.lineCode ? `#${this.lineCode}` : '#'; return `#${this.line.lineCode || ''}`;
}, },
shouldShowCommentButton() { shouldShowCommentButton() {
return ( return (
...@@ -103,20 +87,19 @@ export default { ...@@ -103,20 +87,19 @@ export default {
); );
}, },
hasDiscussions() { hasDiscussions() {
return this.discussions.length > 0; return this.line.discussions && this.line.discussions.length > 0;
}, },
shouldShowAvatarsOnGutter() { shouldShowAvatarsOnGutter() {
if (!this.lineType && this.linePosition === LINE_POSITION_RIGHT) { if (!this.line.type && this.linePosition === LINE_POSITION_RIGHT) {
return false; return false;
} }
return this.showCommentButton && this.hasDiscussions; return this.showCommentButton && this.hasDiscussions;
}, },
}, },
methods: { methods: {
...mapActions('diffs', ['loadMoreLines', 'showCommentForm']), ...mapActions('diffs', ['loadMoreLines', 'showCommentForm']),
handleCommentButton() { handleCommentButton() {
this.showCommentForm({ lineCode: this.lineCode }); this.showCommentForm({ lineCode: this.line.lineCode });
}, },
handleLoadMoreLines() { handleLoadMoreLines() {
if (this.isRequesting) { if (this.isRequesting) {
...@@ -125,8 +108,8 @@ export default { ...@@ -125,8 +108,8 @@ export default {
this.isRequesting = true; this.isRequesting = true;
const endpoint = this.contextLinesPath; const endpoint = this.contextLinesPath;
const oldLineNumber = this.metaData.oldPos || 0; const oldLineNumber = this.line.metaData.oldPos || 0;
const newLineNumber = this.metaData.newPos || 0; const newLineNumber = this.line.metaData.newPos || 0;
const offset = newLineNumber - oldLineNumber; const offset = newLineNumber - oldLineNumber;
const bottom = this.isBottom; const bottom = this.isBottom;
const { fileHash } = this; const { fileHash } = this;
...@@ -201,7 +184,7 @@ export default { ...@@ -201,7 +184,7 @@ export default {
</a> </a>
<diff-gutter-avatars <diff-gutter-avatars
v-if="shouldShowAvatarsOnGutter" v-if="shouldShowAvatarsOnGutter"
:discussions="discussions" :discussions="line.discussions"
/> />
</template> </template>
</div> </div>
......
...@@ -6,6 +6,7 @@ import noteForm from '../../notes/components/note_form.vue'; ...@@ -6,6 +6,7 @@ import noteForm from '../../notes/components/note_form.vue';
import { getNoteFormData } from '../store/utils'; import { getNoteFormData } from '../store/utils';
import autosave from '../../notes/mixins/autosave'; import autosave from '../../notes/mixins/autosave';
import { DIFF_NOTE_TYPE } from '../constants'; import { DIFF_NOTE_TYPE } from '../constants';
import { reduceDiscussionsToLineCodes } from '../../notes/stores/utils';
export default { export default {
components: { components: {
...@@ -52,7 +53,7 @@ export default { ...@@ -52,7 +53,7 @@ export default {
} }
}, },
methods: { methods: {
...mapActions('diffs', ['cancelCommentForm']), ...mapActions('diffs', ['cancelCommentForm', 'assignDiscussionsToDiff']),
...mapActions(['saveNote', 'refetchDiscussionById']), ...mapActions(['saveNote', 'refetchDiscussionById']),
handleCancelCommentForm(shouldConfirm, isDirty) { handleCancelCommentForm(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) { if (shouldConfirm && isDirty) {
...@@ -88,7 +89,10 @@ export default { ...@@ -88,7 +89,10 @@ export default {
const endpoint = this.getNotesDataByProp('discussionsPath'); const endpoint = this.getNotesDataByProp('discussionsPath');
this.refetchDiscussionById({ path: endpoint, discussionId: result.discussion_id }) this.refetchDiscussionById({ path: endpoint, discussionId: result.discussion_id })
.then(() => { .then(selectedDiscussion => {
const lineCodeDiscussions = reduceDiscussionsToLineCodes([selectedDiscussion]);
this.assignDiscussionsToDiff(lineCodeDiscussions);
this.handleCancelCommentForm(); this.handleCancelCommentForm();
}) })
.catch(() => { .catch(() => {
......
...@@ -11,8 +11,6 @@ import { ...@@ -11,8 +11,6 @@ import {
LINE_HOVER_CLASS_NAME, LINE_HOVER_CLASS_NAME,
LINE_UNFOLD_CLASS_NAME, LINE_UNFOLD_CLASS_NAME,
INLINE_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE,
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
} from '../constants'; } from '../constants';
export default { export default {
...@@ -67,42 +65,24 @@ export default { ...@@ -67,42 +65,24 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
discussions: {
type: Array,
required: false,
default: () => [],
},
}, },
computed: { computed: {
...mapGetters(['isLoggedIn']), ...mapGetters(['isLoggedIn']),
normalizedLine() {
let normalizedLine;
if (this.diffViewType === INLINE_DIFF_VIEW_TYPE) {
normalizedLine = this.line;
} else if (this.linePosition === LINE_POSITION_LEFT) {
normalizedLine = this.line.left;
} else if (this.linePosition === LINE_POSITION_RIGHT) {
normalizedLine = this.line.right;
}
return normalizedLine;
},
isMatchLine() { isMatchLine() {
return this.normalizedLine.type === MATCH_LINE_TYPE; return this.line.type === MATCH_LINE_TYPE;
}, },
isContextLine() { isContextLine() {
return this.normalizedLine.type === CONTEXT_LINE_TYPE; return this.line.type === CONTEXT_LINE_TYPE;
}, },
isMetaLine() { isMetaLine() {
const { type } = this.normalizedLine; const { type } = this.line;
return ( return (
type === OLD_NO_NEW_LINE_TYPE || type === NEW_NO_NEW_LINE_TYPE || type === EMPTY_CELL_TYPE type === OLD_NO_NEW_LINE_TYPE || type === NEW_NO_NEW_LINE_TYPE || type === EMPTY_CELL_TYPE
); );
}, },
classNameMap() { classNameMap() {
const { type } = this.normalizedLine; const { type } = this.line;
return { return {
[type]: type, [type]: type,
...@@ -116,9 +96,9 @@ export default { ...@@ -116,9 +96,9 @@ export default {
}; };
}, },
lineNumber() { lineNumber() {
const { lineType, normalizedLine } = this; const { lineType } = this;
return lineType === OLD_LINE_TYPE ? normalizedLine.oldLine : normalizedLine.newLine; return lineType === OLD_LINE_TYPE ? this.line.oldLine : this.line.newLine;
}, },
}, },
}; };
...@@ -129,20 +109,17 @@ export default { ...@@ -129,20 +109,17 @@ export default {
:class="classNameMap" :class="classNameMap"
> >
<diff-line-gutter-content <diff-line-gutter-content
:line="line"
:file-hash="fileHash" :file-hash="fileHash"
:context-lines-path="contextLinesPath" :context-lines-path="contextLinesPath"
:line-type="normalizedLine.type"
:line-code="normalizedLine.lineCode"
:line-position="linePosition" :line-position="linePosition"
:line-number="lineNumber" :line-number="lineNumber"
:meta-data="normalizedLine.metaData"
:show-comment-button="showCommentButton" :show-comment-button="showCommentButton"
:is-hover="isHover" :is-hover="isHover"
:is-bottom="isBottom" :is-bottom="isBottom"
:is-match-line="isMatchLine" :is-match-line="isMatchLine"
:is-context-line="isContentLine" :is-context-line="isContentLine"
:is-meta-line="isMetaLine" :is-meta-line="isMetaLine"
:discussions="discussions"
/> />
</td> </td>
</template> </template>
...@@ -21,18 +21,13 @@ export default { ...@@ -21,18 +21,13 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
discussions: {
type: Array,
required: false,
default: () => [],
},
}, },
computed: { computed: {
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
className() { className() {
return this.discussions.length ? '' : 'js-temp-notes-holder'; return this.line.discussions.length ? '' : 'js-temp-notes-holder';
}, },
}, },
}; };
...@@ -49,8 +44,8 @@ export default { ...@@ -49,8 +44,8 @@ export default {
> >
<div class="content"> <div class="content">
<diff-discussions <diff-discussions
v-if="discussions.length" v-if="line.discussions.length"
:discussions="discussions" :discussions="line.discussions"
/> />
<diff-line-note-form <diff-line-note-form
v-if="diffLineCommentForms[line.lineCode]" v-if="diffLineCommentForms[line.lineCode]"
......
...@@ -33,11 +33,6 @@ export default { ...@@ -33,11 +33,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
discussions: {
type: Array,
required: false,
default: () => [],
},
}, },
data() { data() {
return { return {
...@@ -94,7 +89,6 @@ export default { ...@@ -94,7 +89,6 @@ export default {
:is-bottom="isBottom" :is-bottom="isBottom"
:is-hover="isHover" :is-hover="isHover"
:show-comment-button="true" :show-comment-button="true"
:discussions="discussions"
class="diff-line-num old_line" class="diff-line-num old_line"
/> />
<diff-table-cell <diff-table-cell
...@@ -104,7 +98,6 @@ export default { ...@@ -104,7 +98,6 @@ export default {
:line-type="newLineType" :line-type="newLineType"
:is-bottom="isBottom" :is-bottom="isBottom"
:is-hover="isHover" :is-hover="isHover"
:discussions="discussions"
class="diff-line-num new_line" class="diff-line-num new_line"
/> />
<td <td
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
import { mapGetters, mapState } from 'vuex'; import { mapGetters, mapState } from 'vuex';
import inlineDiffTableRow from './inline_diff_table_row.vue'; import inlineDiffTableRow from './inline_diff_table_row.vue';
import inlineDiffCommentRow from './inline_diff_comment_row.vue'; import inlineDiffCommentRow from './inline_diff_comment_row.vue';
import { trimFirstCharOfLineContent } from '../store/utils';
export default { export default {
components: { components: {
...@@ -20,29 +19,17 @@ export default { ...@@ -20,29 +19,17 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters('diffs', [ ...mapGetters('diffs', ['commitId', 'shouldRenderInlineCommentRow']),
'commitId',
'shouldRenderInlineCommentRow',
'singleDiscussionByLineCode',
]),
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
normalizedDiffLines() {
return this.diffLines.map(line => (line.richText ? trimFirstCharOfLineContent(line) : line));
},
diffLinesLength() { diffLinesLength() {
return this.normalizedDiffLines.length; return this.diffLines.length;
}, },
userColorScheme() { userColorScheme() {
return window.gon.user_color_scheme; return window.gon.user_color_scheme;
}, },
}, },
methods: {
discussionsList(line) {
return line.lineCode !== undefined ? this.singleDiscussionByLineCode(line.lineCode) : [];
},
},
}; };
</script> </script>
...@@ -53,7 +40,7 @@ export default { ...@@ -53,7 +40,7 @@ export default {
class="code diff-wrap-lines js-syntax-highlight text-file js-diff-inline-view"> class="code diff-wrap-lines js-syntax-highlight text-file js-diff-inline-view">
<tbody> <tbody>
<template <template
v-for="(line, index) in normalizedDiffLines" v-for="(line, index) in diffLines"
> >
<inline-diff-table-row <inline-diff-table-row
:file-hash="diffFile.fileHash" :file-hash="diffFile.fileHash"
...@@ -61,7 +48,6 @@ export default { ...@@ -61,7 +48,6 @@ export default {
:line="line" :line="line"
:is-bottom="index + 1 === diffLinesLength" :is-bottom="index + 1 === diffLinesLength"
:key="line.lineCode" :key="line.lineCode"
:discussions="discussionsList(line)"
/> />
<inline-diff-comment-row <inline-diff-comment-row
v-if="shouldRenderInlineCommentRow(line)" v-if="shouldRenderInlineCommentRow(line)"
...@@ -69,7 +55,6 @@ export default { ...@@ -69,7 +55,6 @@ export default {
:line="line" :line="line"
:line-index="index" :line-index="index"
:key="index" :key="index"
:discussions="discussionsList(line)"
/> />
</template> </template>
</tbody> </tbody>
......
...@@ -21,51 +21,49 @@ export default { ...@@ -21,51 +21,49 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
leftDiscussions: {
type: Array,
required: false,
default: () => [],
},
rightDiscussions: {
type: Array,
required: false,
default: () => [],
},
}, },
computed: { computed: {
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
leftLineCode() { leftLineCode() {
return this.line.left.lineCode; return this.line.left && this.line.left.lineCode;
}, },
rightLineCode() { rightLineCode() {
return this.line.right.lineCode; return this.line.right && this.line.right.lineCode;
}, },
hasExpandedDiscussionOnLeft() { hasExpandedDiscussionOnLeft() {
const discussions = this.leftDiscussions; return this.line.left && this.line.left.discussions
? this.line.left.discussions.every(discussion => discussion.expanded)
return discussions ? discussions.every(discussion => discussion.expanded) : false; : false;
}, },
hasExpandedDiscussionOnRight() { hasExpandedDiscussionOnRight() {
const discussions = this.rightDiscussions; return this.line.right && this.line.right.discussions
? this.line.right.discussions.every(discussion => discussion.expanded)
return discussions ? discussions.every(discussion => discussion.expanded) : false; : false;
}, },
hasAnyExpandedDiscussion() { hasAnyExpandedDiscussion() {
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight; return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
}, },
shouldRenderDiscussionsOnLeft() { shouldRenderDiscussionsOnLeft() {
return this.leftDiscussions && this.hasExpandedDiscussionOnLeft; return this.line.left && this.line.left.discussions && this.hasExpandedDiscussionOnLeft;
}, },
shouldRenderDiscussionsOnRight() { shouldRenderDiscussionsOnRight() {
return this.rightDiscussions && this.hasExpandedDiscussionOnRight && this.line.right.type; return (
this.line.right &&
this.line.right.discussions &&
this.hasExpandedDiscussionOnRight &&
this.line.right.type
);
}, },
showRightSideCommentForm() { showRightSideCommentForm() {
return this.line.right.type && this.diffLineCommentForms[this.rightLineCode]; return (
this.line.right && this.line.right.type && this.diffLineCommentForms[this.rightLineCode]
);
}, },
className() { className() {
return this.leftDiscussions.length > 0 || this.rightDiscussions.length > 0 return (this.left && this.line.left.discussions.length > 0) ||
(this.right && this.line.right.discussions.length > 0)
? '' ? ''
: 'js-temp-notes-holder'; : 'js-temp-notes-holder';
}, },
...@@ -85,8 +83,8 @@ export default { ...@@ -85,8 +83,8 @@ export default {
class="content" class="content"
> >
<diff-discussions <diff-discussions
v-if="leftDiscussions.length" v-if="line.left.discussions.length"
:discussions="leftDiscussions" :discussions="line.left.discussions"
/> />
</div> </div>
<diff-line-note-form <diff-line-note-form
...@@ -104,8 +102,8 @@ export default { ...@@ -104,8 +102,8 @@ export default {
class="content" class="content"
> >
<diff-discussions <diff-discussions
v-if="rightDiscussions.length" v-if="line.right.discussions.length"
:discussions="rightDiscussions" :discussions="line.right.discussions"
/> />
</div> </div>
<diff-line-note-form <diff-line-note-form
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import { mapGetters } from 'vuex';
import DiffTableCell from './diff_table_cell.vue'; import DiffTableCell from './diff_table_cell.vue';
import { import {
NEW_LINE_TYPE, NEW_LINE_TYPE,
...@@ -10,8 +9,7 @@ import { ...@@ -10,8 +9,7 @@ import {
OLD_NO_NEW_LINE_TYPE, OLD_NO_NEW_LINE_TYPE,
PARALLEL_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE,
NEW_NO_NEW_LINE_TYPE, NEW_NO_NEW_LINE_TYPE,
LINE_POSITION_LEFT, EMPTY_CELL_TYPE,
LINE_POSITION_RIGHT,
} from '../constants'; } from '../constants';
export default { export default {
...@@ -36,16 +34,6 @@ export default { ...@@ -36,16 +34,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
leftDiscussions: {
type: Array,
required: false,
default: () => [],
},
rightDiscussions: {
type: Array,
required: false,
default: () => [],
},
}, },
data() { data() {
return { return {
...@@ -54,29 +42,26 @@ export default { ...@@ -54,29 +42,26 @@ export default {
}; };
}, },
computed: { computed: {
...mapGetters('diffs', ['isParallelView']),
isContextLine() { isContextLine() {
return this.line.left.type === CONTEXT_LINE_TYPE; return this.line.left && this.line.left.type === CONTEXT_LINE_TYPE;
}, },
classNameMap() { classNameMap() {
return { return {
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine, [CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
[PARALLEL_DIFF_VIEW_TYPE]: this.isParallelView, [PARALLEL_DIFF_VIEW_TYPE]: true,
}; };
}, },
parallelViewLeftLineType() { parallelViewLeftLineType() {
if (this.line.right.type === NEW_NO_NEW_LINE_TYPE) { if (this.line.right && this.line.right.type === NEW_NO_NEW_LINE_TYPE) {
return OLD_NO_NEW_LINE_TYPE; return OLD_NO_NEW_LINE_TYPE;
} }
return this.line.left.type; return this.line.left ? this.line.left.type : EMPTY_CELL_TYPE;
}, },
}, },
created() { created() {
this.newLineType = NEW_LINE_TYPE; this.newLineType = NEW_LINE_TYPE;
this.oldLineType = OLD_LINE_TYPE; this.oldLineType = OLD_LINE_TYPE;
this.linePositionLeft = LINE_POSITION_LEFT;
this.linePositionRight = LINE_POSITION_RIGHT;
this.parallelDiffViewType = PARALLEL_DIFF_VIEW_TYPE; this.parallelDiffViewType = PARALLEL_DIFF_VIEW_TYPE;
}, },
methods: { methods: {
...@@ -116,47 +101,57 @@ export default { ...@@ -116,47 +101,57 @@ export default {
@mouseover="handleMouseMove" @mouseover="handleMouseMove"
@mouseout="handleMouseMove" @mouseout="handleMouseMove"
> >
<diff-table-cell <template v-if="line.left">
:file-hash="fileHash" <diff-table-cell
:context-lines-path="contextLinesPath" :file-hash="fileHash"
:line="line" :context-lines-path="contextLinesPath"
:line-type="oldLineType" :line="line.left"
:line-position="linePositionLeft" :line-type="oldLineType"
:is-bottom="isBottom" :is-bottom="isBottom"
:is-hover="isLeftHover" :is-hover="isLeftHover"
:show-comment-button="true" :show-comment-button="true"
:diff-view-type="parallelDiffViewType" :diff-view-type="parallelDiffViewType"
:discussions="leftDiscussions" line-position="left"
class="diff-line-num old_line" class="diff-line-num old_line"
/> />
<td <td
:id="line.left.lineCode" :id="line.left.lineCode"
:class="parallelViewLeftLineType" :class="parallelViewLeftLineType"
class="line_content parallel left-side" class="line_content parallel left-side"
@mousedown.native="handleParallelLineMouseDown" @mousedown.native="handleParallelLineMouseDown"
v-html="line.left.richText" v-html="line.left.richText"
> >
</td> </td>
<diff-table-cell </template>
:file-hash="fileHash" <template v-else>
:context-lines-path="contextLinesPath" <td class="diff-line-num old_line empty-cell"></td>
:line="line" <td class="line_content parallel left-side empty-cell"></td>
:line-type="newLineType" </template>
:line-position="linePositionRight" <template v-if="line.right">
:is-bottom="isBottom" <diff-table-cell
:is-hover="isRightHover" :file-hash="fileHash"
:show-comment-button="true" :context-lines-path="contextLinesPath"
:diff-view-type="parallelDiffViewType" :line="line.right"
:discussions="rightDiscussions" :line-type="newLineType"
class="diff-line-num new_line" :is-bottom="isBottom"
/> :is-hover="isRightHover"
<td :show-comment-button="true"
:id="line.right.lineCode" :diff-view-type="parallelDiffViewType"
:class="line.right.type" line-position="right"
class="line_content parallel right-side" class="diff-line-num new_line"
@mousedown.native="handleParallelLineMouseDown" />
v-html="line.right.richText" <td
> :id="line.right.lineCode"
</td> :class="line.right.type"
class="line_content parallel right-side"
@mousedown.native="handleParallelLineMouseDown"
v-html="line.right.richText"
>
</td>
</template>
<template v-else>
<td class="diff-line-num old_line empty-cell"></td>
<td class="line_content parallel right-side empty-cell"></td>
</template>
</tr> </tr>
</template> </template>
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
import { mapState, mapGetters } from 'vuex'; import { mapState, mapGetters } from 'vuex';
import parallelDiffTableRow from './parallel_diff_table_row.vue'; import parallelDiffTableRow from './parallel_diff_table_row.vue';
import parallelDiffCommentRow from './parallel_diff_comment_row.vue'; import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
import { EMPTY_CELL_TYPE } from '../constants';
import { trimFirstCharOfLineContent } from '../store/utils';
export default { export default {
components: { components: {
...@@ -21,46 +19,17 @@ export default { ...@@ -21,46 +19,17 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters('diffs', [ ...mapGetters('diffs', ['commitId', 'shouldRenderParallelCommentRow']),
'commitId',
'singleDiscussionByLineCode',
'shouldRenderParallelCommentRow',
]),
...mapState({ ...mapState({
diffLineCommentForms: state => state.diffs.diffLineCommentForms, diffLineCommentForms: state => state.diffs.diffLineCommentForms,
}), }),
parallelDiffLines() {
return this.diffLines.map(line => {
const parallelLine = Object.assign({}, line);
if (line.left) {
parallelLine.left = trimFirstCharOfLineContent(line.left);
} else {
parallelLine.left = { type: EMPTY_CELL_TYPE };
}
if (line.right) {
parallelLine.right = trimFirstCharOfLineContent(line.right);
} else {
parallelLine.right = { type: EMPTY_CELL_TYPE };
}
return parallelLine;
});
},
diffLinesLength() { diffLinesLength() {
return this.parallelDiffLines.length; return this.diffLines.length;
}, },
userColorScheme() { userColorScheme() {
return window.gon.user_color_scheme; return window.gon.user_color_scheme;
}, },
}, },
methods: {
discussionsByLine(line, leftOrRight) {
return line[leftOrRight] && line[leftOrRight].lineCode !== undefined ?
this.singleDiscussionByLineCode(line[leftOrRight].lineCode) : [];
},
},
}; };
</script> </script>
...@@ -73,7 +42,7 @@ export default { ...@@ -73,7 +42,7 @@ export default {
<table> <table>
<tbody> <tbody>
<template <template
v-for="(line, index) in parallelDiffLines" v-for="(line, index) in diffLines"
> >
<parallel-diff-table-row <parallel-diff-table-row
:file-hash="diffFile.fileHash" :file-hash="diffFile.fileHash"
...@@ -81,8 +50,6 @@ export default { ...@@ -81,8 +50,6 @@ export default {
:line="line" :line="line"
:is-bottom="index + 1 === diffLinesLength" :is-bottom="index + 1 === diffLinesLength"
:key="index" :key="index"
:left-discussions="discussionsByLine(line, 'left')"
:right-discussions="discussionsByLine(line, 'right')"
/> />
<parallel-diff-comment-row <parallel-diff-comment-row
v-if="shouldRenderParallelCommentRow(line)" v-if="shouldRenderParallelCommentRow(line)"
...@@ -90,8 +57,6 @@ export default { ...@@ -90,8 +57,6 @@ export default {
:line="line" :line="line"
:diff-file-hash="diffFile.fileHash" :diff-file-hash="diffFile.fileHash"
:line-index="index" :line-index="index"
:left-discussions="discussionsByLine(line, 'left')"
:right-discussions="discussionsByLine(line, 'right')"
/> />
</template> </template>
</tbody> </tbody>
......
...@@ -29,25 +29,47 @@ export const fetchDiffFiles = ({ state, commit }) => { ...@@ -29,25 +29,47 @@ export const fetchDiffFiles = ({ state, commit }) => {
.then(handleLocationHash); .then(handleLocationHash);
}; };
// This is adding line discussions to the actual lines in the diff tree
// once for parallel and once for inline mode
export const assignDiscussionsToDiff = ({ commit }, allLineDiscussions) => {
Object.values(allLineDiscussions).forEach(discussions => {
if (discussions.length > 0) {
const { fileHash } = discussions[0];
commit(types.SET_LINE_DISCUSSIONS_FOR_FILE, { fileHash, discussions });
}
});
};
export const removeDiscussionsFromDiff = ({ commit }, removeDiscussion) => {
const { fileHash, line_code } = removeDiscussion;
commit(types.REMOVE_LINE_DISCUSSIONS_FOR_FILE, { fileHash, lineCode: line_code });
};
export const startRenderDiffsQueue = ({ state, commit }) => { export const startRenderDiffsQueue = ({ state, commit }) => {
const checkItem = () => { const checkItem = () =>
const nextFile = state.diffFiles.find( new Promise(resolve => {
file => !file.renderIt && (!file.collapsed || !file.text), const nextFile = state.diffFiles.find(
); file => !file.renderIt && (!file.collapsed || !file.text),
if (nextFile) {
requestAnimationFrame(() => {
commit(types.RENDER_FILE, nextFile);
});
requestIdleCallback(
() => {
checkItem();
},
{ timeout: 1000 },
); );
}
};
checkItem(); if (nextFile) {
requestAnimationFrame(() => {
commit(types.RENDER_FILE, nextFile);
});
requestIdleCallback(
() => {
checkItem()
.then(resolve)
.catch(() => {});
},
{ timeout: 1000 },
);
} else {
resolve();
}
});
return checkItem();
}; };
export const setInlineDiffViewType = ({ commit }) => { export const setInlineDiffViewType = ({ commit }) => {
......
...@@ -17,7 +17,10 @@ export const commitId = state => (state.commit && state.commit.id ? state.commit ...@@ -17,7 +17,10 @@ export const commitId = state => (state.commit && state.commit.id ? state.commit
export const diffHasAllExpandedDiscussions = (state, getters) => diff => { export const diffHasAllExpandedDiscussions = (state, getters) => diff => {
const discussions = getters.getDiffFileDiscussions(diff); const discussions = getters.getDiffFileDiscussions(diff);
return (discussions.length && discussions.every(discussion => discussion.expanded)) || false; return (
(discussions && discussions.length && discussions.every(discussion => discussion.expanded)) ||
false
);
}; };
/** /**
...@@ -28,7 +31,10 @@ export const diffHasAllExpandedDiscussions = (state, getters) => diff => { ...@@ -28,7 +31,10 @@ export const diffHasAllExpandedDiscussions = (state, getters) => diff => {
export const diffHasAllCollpasedDiscussions = (state, getters) => diff => { export const diffHasAllCollpasedDiscussions = (state, getters) => diff => {
const discussions = getters.getDiffFileDiscussions(diff); const discussions = getters.getDiffFileDiscussions(diff);
return (discussions.length && discussions.every(discussion => !discussion.expanded)) || false; return (
(discussions && discussions.length && discussions.every(discussion => !discussion.expanded)) ||
false
);
}; };
/** /**
...@@ -40,7 +46,9 @@ export const diffHasExpandedDiscussions = (state, getters) => diff => { ...@@ -40,7 +46,9 @@ export const diffHasExpandedDiscussions = (state, getters) => diff => {
const discussions = getters.getDiffFileDiscussions(diff); const discussions = getters.getDiffFileDiscussions(diff);
return ( return (
(discussions.length && discussions.find(discussion => discussion.expanded) !== undefined) || (discussions &&
discussions.length &&
discussions.find(discussion => discussion.expanded) !== undefined) ||
false false
); );
}; };
...@@ -64,45 +72,38 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) = ...@@ -64,45 +72,38 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash), discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash),
) || []; ) || [];
export const singleDiscussionByLineCode = (state, getters, rootState, rootGetters) => lineCode => { export const shouldRenderParallelCommentRow = state => line => {
if (!lineCode || lineCode === undefined) return []; const hasDiscussion =
const discussions = rootGetters.discussionsByLineCode; (line.left && line.left.discussions && line.left.discussions.length) ||
return discussions[lineCode] || []; (line.right && line.right.discussions && line.right.discussions.length);
};
export const shouldRenderParallelCommentRow = (state, getters) => line => {
const leftLineCode = line.left.lineCode;
const rightLineCode = line.right.lineCode;
const leftDiscussions = getters.singleDiscussionByLineCode(leftLineCode);
const rightDiscussions = getters.singleDiscussionByLineCode(rightLineCode);
const hasDiscussion = leftDiscussions.length || rightDiscussions.length;
const hasExpandedDiscussionOnLeft = leftDiscussions.length const hasExpandedDiscussionOnLeft =
? leftDiscussions.every(discussion => discussion.expanded) line.left && line.left.discussions && line.left.discussions.length
: false; ? line.left.discussions.every(discussion => discussion.expanded)
const hasExpandedDiscussionOnRight = rightDiscussions.length : false;
? rightDiscussions.every(discussion => discussion.expanded) const hasExpandedDiscussionOnRight =
: false; line.right && line.right.discussions && line.right.discussions.length
? line.right.discussions.every(discussion => discussion.expanded)
: false;
if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) { if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) {
return true; return true;
} }
const hasCommentFormOnLeft = state.diffLineCommentForms[leftLineCode]; const hasCommentFormOnLeft = line.left && state.diffLineCommentForms[line.left.lineCode];
const hasCommentFormOnRight = state.diffLineCommentForms[rightLineCode]; const hasCommentFormOnRight = line.right && state.diffLineCommentForms[line.right.lineCode];
return hasCommentFormOnLeft || hasCommentFormOnRight; return hasCommentFormOnLeft || hasCommentFormOnRight;
}; };
export const shouldRenderInlineCommentRow = (state, getters) => line => { export const shouldRenderInlineCommentRow = state => line => {
if (state.diffLineCommentForms[line.lineCode]) return true; if (state.diffLineCommentForms[line.lineCode]) return true;
const lineDiscussions = getters.singleDiscussionByLineCode(line.lineCode); if (!line.discussions || line.discussions.length === 0) {
if (lineDiscussions.length === 0) {
return false; return false;
} }
return lineDiscussions.every(discussion => discussion.expanded); return line.discussions.every(discussion => discussion.expanded);
}; };
// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests // prevent babel-plugin-rewire from generating an invalid default during karma∂ tests
......
...@@ -9,3 +9,5 @@ export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES'; ...@@ -9,3 +9,5 @@ export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES';
export const ADD_COLLAPSED_DIFFS = 'ADD_COLLAPSED_DIFFS'; export const ADD_COLLAPSED_DIFFS = 'ADD_COLLAPSED_DIFFS';
export const EXPAND_ALL_FILES = 'EXPAND_ALL_FILES'; export const EXPAND_ALL_FILES = 'EXPAND_ALL_FILES';
export const RENDER_FILE = 'RENDER_FILE'; export const RENDER_FILE = 'RENDER_FILE';
export const SET_LINE_DISCUSSIONS_FOR_FILE = 'SET_LINE_DISCUSSIONS_FOR_FILE';
export const REMOVE_LINE_DISCUSSIONS_FOR_FILE = 'REMOVE_LINE_DISCUSSIONS_FOR_FILE';
import Vue from 'vue'; import Vue from 'vue';
import _ from 'underscore';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { findDiffFile, addLineReferences, removeMatchLine, addContextLines } from './utils'; import {
import { LINES_TO_BE_RENDERED_DIRECTLY, MAX_LINES_TO_BE_RENDERED } from '../constants'; findDiffFile,
addLineReferences,
removeMatchLine,
addContextLines,
prepareDiffData,
} from './utils';
import * as types from './mutation_types'; import * as types from './mutation_types';
export default { export default {
...@@ -17,38 +21,7 @@ export default { ...@@ -17,38 +21,7 @@ export default {
[types.SET_DIFF_DATA](state, data) { [types.SET_DIFF_DATA](state, data) {
const diffData = convertObjectPropsToCamelCase(data, { deep: true }); const diffData = convertObjectPropsToCamelCase(data, { deep: true });
let showingLines = 0; prepareDiffData(diffData);
const filesLength = diffData.diffFiles.length;
let i;
for (i = 0; i < filesLength; i += 1) {
const file = diffData.diffFiles[i];
if (file.parallelDiffLines) {
const linesLength = file.parallelDiffLines.length;
let u = 0;
for (u = 0; u < linesLength; u += 1) {
const line = file.parallelDiffLines[u];
if (line.left) delete line.left.text;
if (line.right) delete line.right.text;
}
}
if (file.highlightedDiffLines) {
const linesLength = file.highlightedDiffLines.length;
let u;
for (u = 0; u < linesLength; u += 1) {
const line = file.highlightedDiffLines[u];
delete line.text;
}
}
if (file.highlightedDiffLines) {
showingLines += file.parallelDiffLines.length;
}
Object.assign(file, {
renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED,
});
}
Object.assign(state, { Object.assign(state, {
...diffData, ...diffData,
...@@ -98,12 +71,10 @@ export default { ...@@ -98,12 +71,10 @@ export default {
[types.ADD_COLLAPSED_DIFFS](state, { file, data }) { [types.ADD_COLLAPSED_DIFFS](state, { file, data }) {
const normalizedData = convertObjectPropsToCamelCase(data, { deep: true }); const normalizedData = convertObjectPropsToCamelCase(data, { deep: true });
prepareDiffData(normalizedData);
const [newFileData] = normalizedData.diffFiles.filter(f => f.fileHash === file.fileHash); const [newFileData] = normalizedData.diffFiles.filter(f => f.fileHash === file.fileHash);
const selectedFile = state.diffFiles.find(f => f.fileHash === file.fileHash);
if (newFileData) { Object.assign(selectedFile, { ...newFileData });
const index = _.findIndex(state.diffFiles, f => f.fileHash === file.fileHash);
state.diffFiles.splice(index, 1, newFileData);
}
}, },
[types.EXPAND_ALL_FILES](state) { [types.EXPAND_ALL_FILES](state) {
...@@ -112,4 +83,69 @@ export default { ...@@ -112,4 +83,69 @@ export default {
collapsed: false, collapsed: false,
})); }));
}, },
[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, discussions }) {
const selectedFile = state.diffFiles.find(f => f.fileHash === fileHash);
if (selectedFile) {
const firstDiscussion = discussions[0];
const targetLine = selectedFile.parallelDiffLines.find(
line =>
(line.left && line.left.lineCode === firstDiscussion.line_code) ||
(line.right && line.right.lineCode === firstDiscussion.line_code),
);
if (targetLine) {
if (targetLine.left && targetLine.left.lineCode === firstDiscussion.line_code) {
Object.assign(targetLine.left, {
discussions,
});
} else {
Object.assign(targetLine.right, {
discussions,
});
}
}
if (selectedFile.highlightedDiffLines) {
const targetInlineLine = selectedFile.highlightedDiffLines.find(
line => line.lineCode === firstDiscussion.line_code,
);
if (targetInlineLine) {
Object.assign(targetInlineLine, {
discussions,
});
}
}
}
},
[types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) {
const selectedFile = state.diffFiles.find(f => f.fileHash === fileHash);
if (selectedFile) {
const targetLine = selectedFile.parallelDiffLines.find(
line =>
(line.left && line.left.lineCode === lineCode) ||
(line.right && line.right.lineCode === lineCode),
);
if (targetLine) {
const side = targetLine.left && targetLine.left.lineCode === lineCode ? 'left' : 'right';
Object.assign(targetLine[side], {
discussions: [],
});
}
if (selectedFile.highlightedDiffLines) {
const targetInlineLine = selectedFile.highlightedDiffLines.find(
line => line.lineCode === lineCode,
);
if (targetInlineLine) {
Object.assign(targetInlineLine, {
discussions: [],
});
}
}
}
},
}; };
...@@ -8,6 +8,8 @@ import { ...@@ -8,6 +8,8 @@ import {
NEW_LINE_TYPE, NEW_LINE_TYPE,
OLD_LINE_TYPE, OLD_LINE_TYPE,
MATCH_LINE_TYPE, MATCH_LINE_TYPE,
LINES_TO_BE_RENDERED_DIRECTLY,
MAX_LINES_TO_BE_RENDERED,
} from '../constants'; } from '../constants';
export function findDiffFile(files, hash) { export function findDiffFile(files, hash) {
...@@ -161,6 +163,11 @@ export function addContextLines(options) { ...@@ -161,6 +163,11 @@ export function addContextLines(options) {
* @returns {Object} * @returns {Object}
*/ */
export function trimFirstCharOfLineContent(line = {}) { export function trimFirstCharOfLineContent(line = {}) {
// eslint-disable-next-line no-param-reassign
delete line.text;
// eslint-disable-next-line no-param-reassign
line.discussions = [];
const parsedLine = Object.assign({}, line); const parsedLine = Object.assign({}, line);
if (line.richText) { if (line.richText) {
...@@ -174,6 +181,42 @@ export function trimFirstCharOfLineContent(line = {}) { ...@@ -174,6 +181,42 @@ export function trimFirstCharOfLineContent(line = {}) {
return parsedLine; return parsedLine;
} }
// This prepares and optimizes the incoming diff data from the server
// by setting up incremental rendering and removing unneeded data
export function prepareDiffData(diffData) {
const filesLength = diffData.diffFiles.length;
let showingLines = 0;
for (let i = 0; i < filesLength; i += 1) {
const file = diffData.diffFiles[i];
if (file.parallelDiffLines) {
const linesLength = file.parallelDiffLines.length;
for (let u = 0; u < linesLength; u += 1) {
const line = file.parallelDiffLines[u];
if (line.left) {
line.left = trimFirstCharOfLineContent(line.left);
}
if (line.right) {
line.right = trimFirstCharOfLineContent(line.right);
}
}
}
if (file.highlightedDiffLines) {
const linesLength = file.highlightedDiffLines.length;
for (let u = 0; u < linesLength; u += 1) {
trimFirstCharOfLineContent(file.highlightedDiffLines[u]);
}
showingLines += file.parallelDiffLines.length;
}
Object.assign(file, {
renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED,
});
}
}
export function getDiffRefsByLineCode(diffFiles) { export function getDiffRefsByLineCode(diffFiles) {
return diffFiles.reduce((acc, diffFile) => { return diffFiles.reduce((acc, diffFile) => {
const { baseSha, headSha, startSha } = diffFile.diffRefs; const { baseSha, headSha, startSha } = diffFile.diffRefs;
......
...@@ -154,7 +154,11 @@ export default class Notes { ...@@ -154,7 +154,11 @@ export default class Notes {
this.$wrapperEl.on('click', '.system-note-commit-list-toggler', this.toggleCommitList); this.$wrapperEl.on('click', '.system-note-commit-list-toggler', this.toggleCommitList);
this.$wrapperEl.on('click', '.js-toggle-lazy-diff', this.loadLazyDiff); this.$wrapperEl.on('click', '.js-toggle-lazy-diff', this.loadLazyDiff);
this.$wrapperEl.on('click', '.js-toggle-lazy-diff-retry-button', this.onClickRetryLazyLoad.bind(this)); this.$wrapperEl.on(
'click',
'.js-toggle-lazy-diff-retry-button',
this.onClickRetryLazyLoad.bind(this),
);
// fetch notes when tab becomes visible // fetch notes when tab becomes visible
this.$wrapperEl.on('visibilitychange', this.visibilityChange); this.$wrapperEl.on('visibilitychange', this.visibilityChange);
...@@ -252,9 +256,7 @@ export default class Notes { ...@@ -252,9 +256,7 @@ export default class Notes {
discussionNoteForm = $textarea.closest('.js-discussion-note-form'); discussionNoteForm = $textarea.closest('.js-discussion-note-form');
if (discussionNoteForm.length) { if (discussionNoteForm.length) {
if ($textarea.val() !== '') { if ($textarea.val() !== '') {
if ( if (!window.confirm('Are you sure you want to cancel creating this comment?')) {
!window.confirm('Are you sure you want to cancel creating this comment?')
) {
return; return;
} }
} }
...@@ -266,9 +268,7 @@ export default class Notes { ...@@ -266,9 +268,7 @@ export default class Notes {
originalText = $textarea.closest('form').data('originalNote'); originalText = $textarea.closest('form').data('originalNote');
newText = $textarea.val(); newText = $textarea.val();
if (originalText !== newText) { if (originalText !== newText) {
if ( if (!window.confirm('Are you sure you want to cancel editing this comment?')) {
!window.confirm('Are you sure you want to cancel editing this comment?')
) {
return; return;
} }
} }
...@@ -1316,8 +1316,7 @@ export default class Notes { ...@@ -1316,8 +1316,7 @@ export default class Notes {
$retryButton.prop('disabled', true); $retryButton.prop('disabled', true);
return this.loadLazyDiff(e) return this.loadLazyDiff(e).then(() => {
.then(() => {
$retryButton.prop('disabled', false); $retryButton.prop('disabled', false);
}); });
} }
...@@ -1343,18 +1342,18 @@ export default class Notes { ...@@ -1343,18 +1342,18 @@ export default class Notes {
*/ */
if (url) { if (url) {
return axios return axios
.get(url) .get(url)
.then(({ data }) => { .then(({ data }) => {
// Reset state in case last request returned error // Reset state in case last request returned error
$successContainer.removeClass('hidden'); $successContainer.removeClass('hidden');
$errorContainer.addClass('hidden'); $errorContainer.addClass('hidden');
Notes.renderDiffContent($container, data); Notes.renderDiffContent($container, data);
}) })
.catch(() => { .catch(() => {
$successContainer.addClass('hidden'); $successContainer.addClass('hidden');
$errorContainer.removeClass('hidden'); $errorContainer.removeClass('hidden');
}); });
} }
return Promise.resolve(); return Promise.resolve();
} }
...@@ -1545,12 +1544,8 @@ export default class Notes { ...@@ -1545,12 +1544,8 @@ export default class Notes {
<div class="note-header"> <div class="note-header">
<div class="note-header-info"> <div class="note-header-info">
<a href="/${_.escape(currentUsername)}"> <a href="/${_.escape(currentUsername)}">
<span class="d-none d-sm-inline-block">${_.escape( <span class="d-none d-sm-inline-block">${_.escape(currentUsername)}</span>
currentUsername, <span class="note-headline-light">${_.escape(currentUsername)}</span>
)}</span>
<span class="note-headline-light">${_.escape(
currentUsername,
)}</span>
</a> </a>
</div> </div>
</div> </div>
...@@ -1565,9 +1560,7 @@ export default class Notes { ...@@ -1565,9 +1560,7 @@ export default class Notes {
); );
$tempNote.find('.d-none.d-sm-inline-block').text(_.escape(currentUserFullname)); $tempNote.find('.d-none.d-sm-inline-block').text(_.escape(currentUserFullname));
$tempNote $tempNote.find('.note-headline-light').text(`@${_.escape(currentUsername)}`);
.find('.note-headline-light')
.text(`@${_.escape(currentUsername)}`);
return $tempNote; return $tempNote;
} }
......
...@@ -137,8 +137,10 @@ export default { ...@@ -137,8 +137,10 @@ export default {
return this.unresolvedDiscussions.length > 1; return this.unresolvedDiscussions.length > 1;
}, },
showJumpToNextDiscussion() { showJumpToNextDiscussion() {
return this.hasMultipleUnresolvedDiscussions && return (
!this.isLastUnresolvedDiscussion(this.discussion.id, this.discussionsByDiffOrder); this.hasMultipleUnresolvedDiscussions &&
!this.isLastUnresolvedDiscussion(this.discussion.id, this.discussionsByDiffOrder)
);
}, },
shouldRenderDiffs() { shouldRenderDiffs() {
const { diffDiscussion, diffFile } = this.transformedDiscussion; const { diffDiscussion, diffFile } = this.transformedDiscussion;
...@@ -256,11 +258,16 @@ Please check your network connection and try again.`; ...@@ -256,11 +258,16 @@ Please check your network connection and try again.`;
}); });
}, },
jumpToNextDiscussion() { jumpToNextDiscussion() {
const nextId = const nextId = this.nextUnresolvedDiscussionId(
this.nextUnresolvedDiscussionId(this.discussion.id, this.discussionsByDiffOrder); this.discussion.id,
this.discussionsByDiffOrder,
);
this.jumpToDiscussion(nextId); this.jumpToDiscussion(nextId);
}, },
deleteNoteHandler(note) {
this.$emit('noteDeleted', this.discussion, note);
},
}, },
}; };
</script> </script>
...@@ -270,6 +277,7 @@ Please check your network connection and try again.`; ...@@ -270,6 +277,7 @@ Please check your network connection and try again.`;
<div class="timeline-entry-inner"> <div class="timeline-entry-inner">
<div class="timeline-icon"> <div class="timeline-icon">
<user-avatar-link <user-avatar-link
v-if="author"
:link-href="author.path" :link-href="author.path"
:img-src="author.avatar_url" :img-src="author.avatar_url"
:img-alt="author.name" :img-alt="author.name"
...@@ -344,6 +352,7 @@ Please check your network connection and try again.`; ...@@ -344,6 +352,7 @@ Please check your network connection and try again.`;
:is="componentName(note)" :is="componentName(note)"
:note="componentData(note)" :note="componentData(note)"
:key="note.id" :key="note.id"
@handleDeleteNote="deleteNoteHandler"
/> />
</ul> </ul>
<div <div
......
...@@ -86,6 +86,7 @@ export default { ...@@ -86,6 +86,7 @@ export default {
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
if (window.confirm('Are you sure you want to delete this comment?')) { if (window.confirm('Are you sure you want to delete this comment?')) {
this.isDeleting = true; this.isDeleting = true;
this.$emit('handleDeleteNote', this.note);
this.deleteNote(this.note) this.deleteNote(this.note)
.then(() => { .then(() => {
......
...@@ -138,6 +138,7 @@ export default { ...@@ -138,6 +138,7 @@ export default {
.then(() => { .then(() => {
this.isLoading = false; this.isLoading = false;
this.setNotesFetchedState(true); this.setNotesFetchedState(true);
eventHub.$emit('fetchedNotesData');
}) })
.then(() => this.$nextTick()) .then(() => this.$nextTick())
.then(() => this.checkLocationHash()) .then(() => this.checkLocationHash())
......
...@@ -43,14 +43,23 @@ export const fetchDiscussions = ({ commit }, path) => ...@@ -43,14 +43,23 @@ export const fetchDiscussions = ({ commit }, path) =>
commit(types.SET_INITIAL_DISCUSSIONS, discussions); commit(types.SET_INITIAL_DISCUSSIONS, discussions);
}); });
export const refetchDiscussionById = ({ commit }, { path, discussionId }) => export const refetchDiscussionById = ({ commit, state }, { path, discussionId }) =>
service new Promise(resolve => {
.fetchDiscussions(path) service
.then(res => res.json()) .fetchDiscussions(path)
.then(discussions => { .then(res => res.json())
const selectedDiscussion = discussions.find(discussion => discussion.id === discussionId); .then(discussions => {
if (selectedDiscussion) commit(types.UPDATE_DISCUSSION, selectedDiscussion); const selectedDiscussion = discussions.find(discussion => discussion.id === discussionId);
}); if (selectedDiscussion) {
commit(types.UPDATE_DISCUSSION, selectedDiscussion);
// We need to refetch as it is now the transformed one in state
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
resolve(discussion);
}
})
.catch(() => {});
});
export const deleteNote = ({ commit }, note) => export const deleteNote = ({ commit }, note) =>
service.deleteNote(note.path).then(() => { service.deleteNote(note.path).then(() => {
...@@ -152,26 +161,28 @@ export const saveNote = ({ commit, dispatch }, noteData) => { ...@@ -152,26 +161,28 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
const replyId = noteData.data.in_reply_to_discussion_id; const replyId = noteData.data.in_reply_to_discussion_id;
const methodToDispatch = replyId ? 'replyToDiscussion' : 'createNewNote'; const methodToDispatch = replyId ? 'replyToDiscussion' : 'createNewNote';
commit(types.REMOVE_PLACEHOLDER_NOTES); // remove previous placeholders
$('.notes-form .flash-container').hide(); // hide previous flash notification $('.notes-form .flash-container').hide(); // hide previous flash notification
commit(types.REMOVE_PLACEHOLDER_NOTES); // remove previous placeholders
if (hasQuickActions) { if (replyId) {
placeholderText = utils.stripQuickActions(placeholderText); if (hasQuickActions) {
} placeholderText = utils.stripQuickActions(placeholderText);
}
if (placeholderText.length) { if (placeholderText.length) {
commit(types.SHOW_PLACEHOLDER_NOTE, { commit(types.SHOW_PLACEHOLDER_NOTE, {
noteBody: placeholderText, noteBody: placeholderText,
replyId, replyId,
}); });
} }
if (hasQuickActions) { if (hasQuickActions) {
commit(types.SHOW_PLACEHOLDER_NOTE, { commit(types.SHOW_PLACEHOLDER_NOTE, {
isSystemNote: true, isSystemNote: true,
noteBody: utils.getQuickActionText(note), noteBody: utils.getQuickActionText(note),
replyId, replyId,
}); });
}
} }
return dispatch(methodToDispatch, noteData).then(res => { return dispatch(methodToDispatch, noteData).then(res => {
...@@ -211,7 +222,9 @@ export const saveNote = ({ commit, dispatch }, noteData) => { ...@@ -211,7 +222,9 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
if (errors && errors.commands_only) { if (errors && errors.commands_only) {
Flash(errors.commands_only, 'notice', noteData.flashContainer); Flash(errors.commands_only, 'notice', noteData.flashContainer);
} }
commit(types.REMOVE_PLACEHOLDER_NOTES); if (replyId) {
commit(types.REMOVE_PLACEHOLDER_NOTES);
}
return res; return res;
}); });
......
import _ from 'underscore'; import _ from 'underscore';
import * as constants from '../constants'; import * as constants from '../constants';
import { reduceDiscussionsToLineCodes } from './utils';
import { collapseSystemNotes } from './collapse_utils'; import { collapseSystemNotes } from './collapse_utils';
export const discussions = state => collapseSystemNotes(state.discussions); export const discussions = state => collapseSystemNotes(state.discussions);
...@@ -28,17 +29,8 @@ export const notesById = state => ...@@ -28,17 +29,8 @@ export const notesById = state =>
return acc; return acc;
}, {}); }, {});
export const discussionsByLineCode = state => export const discussionsStructuredByLineCode = state =>
state.discussions.reduce((acc, note) => { reduceDiscussionsToLineCodes(state.discussions);
if (note.diff_discussion && note.line_code && note.resolvable) {
// For context about line notes: there might be multiple notes with the same line code
const items = acc[note.line_code] || [];
items.push(note);
Object.assign(acc, { [note.line_code]: items });
}
return acc;
}, {});
export const noteableType = state => { export const noteableType = state => {
const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants; const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants;
......
...@@ -54,13 +54,12 @@ export default { ...@@ -54,13 +54,12 @@ export default {
[types.EXPAND_DISCUSSION](state, { discussionId }) { [types.EXPAND_DISCUSSION](state, { discussionId }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId); const discussion = utils.findNoteObjectById(state.discussions, discussionId);
Object.assign(discussion, { expanded: true });
discussion.expanded = true;
}, },
[types.COLLAPSE_DISCUSSION](state, { discussionId }) { [types.COLLAPSE_DISCUSSION](state, { discussionId }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId); const discussion = utils.findNoteObjectById(state.discussions, discussionId);
discussion.expanded = false; Object.assign(discussion, { expanded: false });
}, },
[types.REMOVE_PLACEHOLDER_NOTES](state) { [types.REMOVE_PLACEHOLDER_NOTES](state) {
...@@ -95,10 +94,15 @@ export default { ...@@ -95,10 +94,15 @@ export default {
[types.SET_USER_DATA](state, data) { [types.SET_USER_DATA](state, data) {
Object.assign(state, { userData: data }); Object.assign(state, { userData: data });
}, },
[types.SET_INITIAL_DISCUSSIONS](state, discussionsData) { [types.SET_INITIAL_DISCUSSIONS](state, discussionsData) {
const discussions = []; const discussions = [];
discussionsData.forEach(discussion => { discussionsData.forEach(discussion => {
if (discussion.diff_file) {
Object.assign(discussion, { fileHash: discussion.diff_file.file_hash });
}
// To support legacy notes, should be very rare case. // To support legacy notes, should be very rare case.
if (discussion.individual_note && discussion.notes.length > 1) { if (discussion.individual_note && discussion.notes.length > 1) {
discussion.notes.forEach(n => { discussion.notes.forEach(n => {
...@@ -168,8 +172,7 @@ export default { ...@@ -168,8 +172,7 @@ export default {
[types.TOGGLE_DISCUSSION](state, { discussionId }) { [types.TOGGLE_DISCUSSION](state, { discussionId }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId); const discussion = utils.findNoteObjectById(state.discussions, discussionId);
Object.assign(discussion, { expanded: !discussion.expanded });
discussion.expanded = !discussion.expanded;
}, },
[types.UPDATE_NOTE](state, note) { [types.UPDATE_NOTE](state, note) {
...@@ -185,16 +188,12 @@ export default { ...@@ -185,16 +188,12 @@ export default {
[types.UPDATE_DISCUSSION](state, noteData) { [types.UPDATE_DISCUSSION](state, noteData) {
const note = noteData; const note = noteData;
let index = 0; const selectedDiscussion = state.discussions.find(disc => disc.id === note.id);
state.discussions.forEach((n, i) => {
if (n.id === note.id) {
index = i;
}
});
note.expanded = true; // override expand flag to prevent collapse note.expanded = true; // override expand flag to prevent collapse
state.discussions.splice(index, 1, note); if (note.diff_file) {
Object.assign(note, { fileHash: note.diff_file.file_hash });
}
Object.assign(selectedDiscussion, { ...note });
}, },
[types.CLOSE_ISSUE](state) { [types.CLOSE_ISSUE](state) {
......
...@@ -2,13 +2,11 @@ import AjaxCache from '~/lib/utils/ajax_cache'; ...@@ -2,13 +2,11 @@ import AjaxCache from '~/lib/utils/ajax_cache';
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm; const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
export const findNoteObjectById = (notes, id) => export const findNoteObjectById = (notes, id) => notes.filter(n => n.id === id)[0];
notes.filter(n => n.id === id)[0];
export const getQuickActionText = note => { export const getQuickActionText = note => {
let text = 'Applying command'; let text = 'Applying command';
const quickActions = const quickActions = AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
AjaxCache.get(gl.GfmAutoComplete.dataSources.commands) || [];
const executedCommands = quickActions.filter(command => { const executedCommands = quickActions.filter(command => {
const commandRegex = new RegExp(`/${command.name}`); const commandRegex = new RegExp(`/${command.name}`);
...@@ -27,7 +25,18 @@ export const getQuickActionText = note => { ...@@ -27,7 +25,18 @@ export const getQuickActionText = note => {
return text; return text;
}; };
export const reduceDiscussionsToLineCodes = selectedDiscussions =>
selectedDiscussions.reduce((acc, note) => {
if (note.diff_discussion && note.line_code && note.resolvable) {
// For context about line notes: there might be multiple notes with the same line code
const items = acc[note.line_code] || [];
items.push(note);
Object.assign(acc, { [note.line_code]: items });
}
return acc;
}, {});
export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note); export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note);
export const stripQuickActions = note => export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim();
note.replace(REGEX_QUICK_ACTIONS, '').trim();
...@@ -186,11 +186,8 @@ describe 'Merge request > User posts diff notes', :js do ...@@ -186,11 +186,8 @@ describe 'Merge request > User posts diff notes', :js do
describe 'posting a note' do describe 'posting a note' do
it 'adds as discussion' do it 'adds as discussion' do
expect(page).to have_css('.js-temp-notes-holder', count: 2)
should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'), asset_form_reset: false) should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'), asset_form_reset: false)
expect(page).to have_css('.notes_holder .note.note-discussion', count: 1) expect(page).to have_css('.notes_holder .note.note-discussion', count: 1)
expect(page).to have_css('.js-temp-notes-holder', count: 1)
expect(page).to have_button('Reply...') expect(page).to have_button('Reply...')
end end
end end
...@@ -267,7 +264,7 @@ describe 'Merge request > User posts diff notes', :js do ...@@ -267,7 +264,7 @@ describe 'Merge request > User posts diff notes', :js do
def assert_comment_persistence(line_holder, asset_form_reset:) def assert_comment_persistence(line_holder, asset_form_reset:)
notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath) notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath)
expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class) expect(notes_holder_saved[:class]).not_to include('note-edit-form')
expect(notes_holder_saved).to have_content test_note_comment expect(notes_holder_saved).to have_content test_note_comment
assert_form_is_reset if asset_form_reset assert_form_is_reset if asset_form_reset
...@@ -281,6 +278,6 @@ describe 'Merge request > User posts diff notes', :js do ...@@ -281,6 +278,6 @@ describe 'Merge request > User posts diff notes', :js do
end end
def assert_form_is_reset def assert_form_is_reset
expect(page).to have_no_css('.js-temp-notes-holder') expect(page).to have_no_css('.note-edit-form')
end end
end end
...@@ -51,7 +51,9 @@ describe('DiffFile', () => { ...@@ -51,7 +51,9 @@ describe('DiffFile', () => {
}); });
it('should have collapsed text and link', done => { it('should have collapsed text and link', done => {
vm.file.collapsed = true; vm.file.renderIt = true;
vm.file.collapsed = false;
vm.file.highlightedDiffLines = null;
vm.$nextTick(() => { vm.$nextTick(() => {
expect(vm.$el.innerText).toContain('This diff is collapsed'); expect(vm.$el.innerText).toContain('This diff is collapsed');
......
...@@ -6,61 +6,61 @@ import discussionsMockData from '../mock_data/diff_discussions'; ...@@ -6,61 +6,61 @@ import discussionsMockData from '../mock_data/diff_discussions';
import diffFileMockData from '../mock_data/diff_file'; import diffFileMockData from '../mock_data/diff_file';
describe('DiffLineGutterContent', () => { describe('DiffLineGutterContent', () => {
const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
const getDiffFileMock = () => Object.assign({}, diffFileMockData); const getDiffFileMock = () => Object.assign({}, diffFileMockData);
const createComponent = (options = {}) => { const createComponent = (options = {}) => {
const cmp = Vue.extend(DiffLineGutterContent); const cmp = Vue.extend(DiffLineGutterContent);
const props = Object.assign({}, options); const props = Object.assign({}, options);
props.line = {
lineCode: 'LC_42',
type: 'new',
oldLine: null,
newLine: 1,
discussions: [],
text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
richText: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
metaData: null,
};
props.fileHash = getDiffFileMock().fileHash; props.fileHash = getDiffFileMock().fileHash;
props.contextLinesPath = '/context/lines/path'; props.contextLinesPath = '/context/lines/path';
return createComponentWithStore(cmp, store, props).$mount(); return createComponentWithStore(cmp, store, props).$mount();
}; };
const setDiscussions = component => {
component.$store.dispatch('setInitialNotes', getDiscussionsMockData());
};
const resetDiscussions = component => {
component.$store.dispatch('setInitialNotes', []);
};
describe('computed', () => { describe('computed', () => {
describe('lineHref', () => { describe('lineHref', () => {
it('should prepend # to lineCode', () => { it('should prepend # to lineCode', () => {
const lineCode = 'LC_42'; const lineCode = 'LC_42';
const component = createComponent({ lineCode }); const component = createComponent();
expect(component.lineHref).toEqual(`#${lineCode}`); expect(component.lineHref).toEqual(`#${lineCode}`);
}); });
it('should return # if there is no lineCode', () => { it('should return # if there is no lineCode', () => {
const component = createComponent({ lineCode: null }); const component = createComponent();
component.line.lineCode = '';
expect(component.lineHref).toEqual('#'); expect(component.lineHref).toEqual('#');
}); });
}); });
describe('discussions, hasDiscussions, shouldShowAvatarsOnGutter', () => { describe('discussions, hasDiscussions, shouldShowAvatarsOnGutter', () => {
it('should return empty array when there is no discussion', () => { it('should return empty array when there is no discussion', () => {
const component = createComponent({ lineCode: 'LC_42' }); const component = createComponent();
expect(component.discussions).toEqual([]);
expect(component.hasDiscussions).toEqual(false); expect(component.hasDiscussions).toEqual(false);
expect(component.shouldShowAvatarsOnGutter).toEqual(false); expect(component.shouldShowAvatarsOnGutter).toEqual(false);
}); });
it('should return discussions for the given lineCode', () => { it('should return discussions for the given lineCode', () => {
const { lineCode } = getDiffFileMock().highlightedDiffLines[1]; const cmp = Vue.extend(DiffLineGutterContent);
const component = createComponent({ const props = {
lineCode, line: getDiffFileMock().highlightedDiffLines[1],
fileHash: getDiffFileMock().fileHash,
showCommentButton: true, showCommentButton: true,
discussions: getDiscussionsMockData(), contextLinesPath: '/context/lines/path',
}); };
props.line.discussions = [Object.assign({}, discussionsMockData)];
const component = createComponentWithStore(cmp, store, props).$mount();
setDiscussions(component);
expect(component.discussions).toEqual(getDiscussionsMockData());
expect(component.hasDiscussions).toEqual(true); expect(component.hasDiscussions).toEqual(true);
expect(component.shouldShowAvatarsOnGutter).toEqual(true); expect(component.shouldShowAvatarsOnGutter).toEqual(true);
resetDiscussions(component);
}); });
}); });
}); });
...@@ -104,9 +104,7 @@ describe('DiffLineGutterContent', () => { ...@@ -104,9 +104,7 @@ describe('DiffLineGutterContent', () => {
lineCode: getDiffFileMock().highlightedDiffLines[1].lineCode, lineCode: getDiffFileMock().highlightedDiffLines[1].lineCode,
}); });
setDiscussions(component);
expect(component.$el.querySelector('.diff-comment-avatar-holders')).toBeDefined(); expect(component.$el.querySelector('.diff-comment-avatar-holders')).toBeDefined();
resetDiscussions(component);
}); });
}); });
}); });
...@@ -18,11 +18,11 @@ describe('ParallelDiffView', () => { ...@@ -18,11 +18,11 @@ describe('ParallelDiffView', () => {
}).$mount(); }).$mount();
}); });
describe('computed', () => { describe('assigned', () => {
describe('parallelDiffLines', () => { describe('diffLines', () => {
it('should normalize lines for empty cells', () => { it('should normalize lines for empty cells', () => {
expect(component.parallelDiffLines[0].left.type).toEqual(constants.EMPTY_CELL_TYPE); expect(component.diffLines[0].left.type).toEqual(constants.EMPTY_CELL_TYPE);
expect(component.parallelDiffLines[1].left.type).toEqual(constants.EMPTY_CELL_TYPE); expect(component.diffLines[1].left.type).toEqual(constants.EMPTY_CELL_TYPE);
}); });
}); });
}); });
......
...@@ -49,6 +49,7 @@ export default { ...@@ -49,6 +49,7 @@ export default {
type: 'new', type: 'new',
oldLine: null, oldLine: null,
newLine: 1, newLine: 1,
discussions: [],
text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
richText: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', richText: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
metaData: null, metaData: null,
...@@ -58,6 +59,7 @@ export default { ...@@ -58,6 +59,7 @@ export default {
type: 'new', type: 'new',
oldLine: null, oldLine: null,
newLine: 2, newLine: 2,
discussions: [],
text: '+<span id="LC2" class="line" lang="plaintext"></span>\n', text: '+<span id="LC2" class="line" lang="plaintext"></span>\n',
richText: '+<span id="LC2" class="line" lang="plaintext"></span>\n', richText: '+<span id="LC2" class="line" lang="plaintext"></span>\n',
metaData: null, metaData: null,
...@@ -67,6 +69,7 @@ export default { ...@@ -67,6 +69,7 @@ export default {
type: null, type: null,
oldLine: 1, oldLine: 1,
newLine: 3, newLine: 3,
discussions: [],
text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
richText: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', richText: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
metaData: null, metaData: null,
...@@ -76,6 +79,7 @@ export default { ...@@ -76,6 +79,7 @@ export default {
type: null, type: null,
oldLine: 2, oldLine: 2,
newLine: 4, newLine: 4,
discussions: [],
text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', text: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
richText: ' <span id="LC4" class="line" lang="plaintext"></span>\n', richText: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
metaData: null, metaData: null,
...@@ -85,6 +89,7 @@ export default { ...@@ -85,6 +89,7 @@ export default {
type: null, type: null,
oldLine: 3, oldLine: 3,
newLine: 5, newLine: 5,
discussions: [],
text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
richText: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', richText: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
metaData: null, metaData: null,
...@@ -94,6 +99,7 @@ export default { ...@@ -94,6 +99,7 @@ export default {
type: 'match', type: 'match',
oldLine: null, oldLine: null,
newLine: null, newLine: null,
discussions: [],
text: '', text: '',
richText: '', richText: '',
metaData: { metaData: {
...@@ -112,6 +118,7 @@ export default { ...@@ -112,6 +118,7 @@ export default {
type: 'new', type: 'new',
oldLine: null, oldLine: null,
newLine: 1, newLine: 1,
discussions: [],
text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
richText: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', richText: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
metaData: null, metaData: null,
...@@ -126,6 +133,7 @@ export default { ...@@ -126,6 +133,7 @@ export default {
type: 'new', type: 'new',
oldLine: null, oldLine: null,
newLine: 2, newLine: 2,
discussions: [],
text: '+<span id="LC2" class="line" lang="plaintext"></span>\n', text: '+<span id="LC2" class="line" lang="plaintext"></span>\n',
richText: '<span id="LC2" class="line" lang="plaintext"></span>\n', richText: '<span id="LC2" class="line" lang="plaintext"></span>\n',
metaData: null, metaData: null,
...@@ -137,6 +145,7 @@ export default { ...@@ -137,6 +145,7 @@ export default {
type: null, type: null,
oldLine: 1, oldLine: 1,
newLine: 3, newLine: 3,
discussions: [],
text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
metaData: null, metaData: null,
...@@ -146,6 +155,7 @@ export default { ...@@ -146,6 +155,7 @@ export default {
type: null, type: null,
oldLine: 1, oldLine: 1,
newLine: 3, newLine: 3,
discussions: [],
text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n',
metaData: null, metaData: null,
...@@ -157,6 +167,7 @@ export default { ...@@ -157,6 +167,7 @@ export default {
type: null, type: null,
oldLine: 2, oldLine: 2,
newLine: 4, newLine: 4,
discussions: [],
text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', text: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
richText: '<span id="LC4" class="line" lang="plaintext"></span>\n', richText: '<span id="LC4" class="line" lang="plaintext"></span>\n',
metaData: null, metaData: null,
...@@ -166,6 +177,7 @@ export default { ...@@ -166,6 +177,7 @@ export default {
type: null, type: null,
oldLine: 2, oldLine: 2,
newLine: 4, newLine: 4,
discussions: [],
text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', text: ' <span id="LC4" class="line" lang="plaintext"></span>\n',
richText: '<span id="LC4" class="line" lang="plaintext"></span>\n', richText: '<span id="LC4" class="line" lang="plaintext"></span>\n',
metaData: null, metaData: null,
...@@ -177,6 +189,7 @@ export default { ...@@ -177,6 +189,7 @@ export default {
type: null, type: null,
oldLine: 3, oldLine: 3,
newLine: 5, newLine: 5,
discussions: [],
text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
metaData: null, metaData: null,
...@@ -186,6 +199,7 @@ export default { ...@@ -186,6 +199,7 @@ export default {
type: null, type: null,
oldLine: 3, oldLine: 3,
newLine: 5, newLine: 5,
discussions: [],
text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n',
metaData: null, metaData: null,
...@@ -197,6 +211,7 @@ export default { ...@@ -197,6 +211,7 @@ export default {
type: 'match', type: 'match',
oldLine: null, oldLine: null,
newLine: null, newLine: null,
discussions: [],
text: '', text: '',
richText: '', richText: '',
metaData: { metaData: {
...@@ -209,6 +224,7 @@ export default { ...@@ -209,6 +224,7 @@ export default {
type: 'match', type: 'match',
oldLine: null, oldLine: null,
newLine: null, newLine: null,
discussions: [],
text: '', text: '',
richText: '', richText: '',
metaData: { metaData: {
......
...@@ -7,10 +7,30 @@ import { ...@@ -7,10 +7,30 @@ import {
} from '~/diffs/constants'; } from '~/diffs/constants';
import * as actions from '~/diffs/store/actions'; import * as actions from '~/diffs/store/actions';
import * as types from '~/diffs/store/mutation_types'; import * as types from '~/diffs/store/mutation_types';
import { reduceDiscussionsToLineCodes } from '~/notes/stores/utils';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import testAction from '../../helpers/vuex_action_helper'; import testAction from '../../helpers/vuex_action_helper';
describe('DiffsStoreActions', () => { describe('DiffsStoreActions', () => {
const originalMethods = {
requestAnimationFrame: global.requestAnimationFrame,
requestIdleCallback: global.requestIdleCallback,
};
beforeEach(() => {
['requestAnimationFrame', 'requestIdleCallback'].forEach(method => {
global[method] = cb => {
cb();
};
});
});
afterEach(() => {
['requestAnimationFrame', 'requestIdleCallback'].forEach(method => {
global[method] = originalMethods[method];
});
});
describe('setBaseConfig', () => { describe('setBaseConfig', () => {
it('should set given endpoint and project path', done => { it('should set given endpoint and project path', done => {
const endpoint = '/diffs/set/endpoint'; const endpoint = '/diffs/set/endpoint';
...@@ -53,6 +73,162 @@ describe('DiffsStoreActions', () => { ...@@ -53,6 +73,162 @@ describe('DiffsStoreActions', () => {
}); });
}); });
describe('assignDiscussionsToDiff', () => {
it('should merge discussions into diffs', done => {
const state = {
diffFiles: [
{
fileHash: 'ABC',
parallelDiffLines: [
{
left: {
lineCode: 'ABC_1_1',
discussions: [],
},
right: {
lineCode: 'ABC_1_1',
discussions: [],
},
},
],
highlightedDiffLines: [
{
lineCode: 'ABC_1_1',
discussions: [],
},
],
},
],
};
const singleDiscussion = {
line_code: 'ABC_1_1',
diff_discussion: {},
diff_file: {
file_hash: 'ABC',
},
resolvable: true,
fileHash: 'ABC',
};
const discussions = reduceDiscussionsToLineCodes([singleDiscussion]);
testAction(
actions.assignDiscussionsToDiff,
discussions,
state,
[
{
type: types.SET_LINE_DISCUSSIONS_FOR_FILE,
payload: {
fileHash: 'ABC',
discussions: [singleDiscussion],
},
},
],
[],
() => {
done();
},
);
});
});
describe('removeDiscussionsFromDiff', () => {
it('should remove discussions from diffs', done => {
const state = {
diffFiles: [
{
fileHash: 'ABC',
parallelDiffLines: [
{
left: {
lineCode: 'ABC_1_1',
discussions: [
{
id: 1,
},
],
},
right: {
lineCode: 'ABC_1_1',
discussions: [],
},
},
],
highlightedDiffLines: [
{
lineCode: 'ABC_1_1',
discussions: [],
},
],
},
],
};
const singleDiscussion = {
fileHash: 'ABC',
line_code: 'ABC_1_1',
};
testAction(
actions.removeDiscussionsFromDiff,
singleDiscussion,
state,
[
{
type: types.REMOVE_LINE_DISCUSSIONS_FOR_FILE,
payload: {
fileHash: 'ABC',
lineCode: 'ABC_1_1',
},
},
],
[],
() => {
done();
},
);
});
});
describe('startRenderDiffsQueue', () => {
it('should set all files to RENDER_FILE', done => {
const state = {
diffFiles: [
{
id: 1,
renderIt: false,
collapsed: false,
},
{
id: 2,
renderIt: false,
collapsed: false,
},
],
};
const pseudoCommit = (commitType, file) => {
expect(commitType).toBe(types.RENDER_FILE);
Object.assign(file, {
renderIt: true,
});
};
actions
.startRenderDiffsQueue({ state, commit: pseudoCommit })
.then(() => {
expect(state.diffFiles[0].renderIt).toBeTruthy();
expect(state.diffFiles[1].renderIt).toBeTruthy();
done();
})
.catch(() => {
done.fail();
});
});
});
describe('setInlineDiffViewType', () => { describe('setInlineDiffViewType', () => {
it('should set diff view type to inline and also set the cookie properly', done => { it('should set diff view type to inline and also set the cookie properly', done => {
testAction( testAction(
...@@ -204,7 +380,11 @@ describe('DiffsStoreActions', () => { ...@@ -204,7 +380,11 @@ describe('DiffsStoreActions', () => {
actions.toggleFileDiscussions({ getters, dispatch }); actions.toggleFileDiscussions({ getters, dispatch });
expect(dispatch).toHaveBeenCalledWith('collapseDiscussion', { discussionId: 1 }, { root: true }); expect(dispatch).toHaveBeenCalledWith(
'collapseDiscussion',
{ discussionId: 1 },
{ root: true },
);
}); });
it('should dispatch expandDiscussion when all discussions are collapsed', () => { it('should dispatch expandDiscussion when all discussions are collapsed', () => {
...@@ -218,7 +398,11 @@ describe('DiffsStoreActions', () => { ...@@ -218,7 +398,11 @@ describe('DiffsStoreActions', () => {
actions.toggleFileDiscussions({ getters, dispatch }); actions.toggleFileDiscussions({ getters, dispatch });
expect(dispatch).toHaveBeenCalledWith('expandDiscussion', { discussionId: 1 }, { root: true }); expect(dispatch).toHaveBeenCalledWith(
'expandDiscussion',
{ discussionId: 1 },
{ root: true },
);
}); });
it('should dispatch expandDiscussion when some discussions are collapsed and others are expanded for the collapsed discussion', () => { it('should dispatch expandDiscussion when some discussions are collapsed and others are expanded for the collapsed discussion', () => {
...@@ -232,7 +416,11 @@ describe('DiffsStoreActions', () => { ...@@ -232,7 +416,11 @@ describe('DiffsStoreActions', () => {
actions.toggleFileDiscussions({ getters, dispatch }); actions.toggleFileDiscussions({ getters, dispatch });
expect(dispatch).toHaveBeenCalledWith('expandDiscussion', { discussionId: 1 }, { root: true }); expect(dispatch).toHaveBeenCalledWith(
'expandDiscussion',
{ discussionId: 1 },
{ root: true },
);
}); });
}); });
}); });
...@@ -184,101 +184,73 @@ describe('Diffs Module Getters', () => { ...@@ -184,101 +184,73 @@ describe('Diffs Module Getters', () => {
}); });
}); });
describe('singleDiscussionByLineCode', () => {
it('returns found discussion per line Code', () => {
const discussionsMock = {};
discussionsMock.ABC = discussionMock;
expect(
getters.singleDiscussionByLineCode(localState, {}, null, {
discussionsByLineCode: () => discussionsMock,
})('DEF'),
).toEqual([]);
});
it('returns empty array when no discussions match', () => {
expect(
getters.singleDiscussionByLineCode(localState, {}, null, {
discussionsByLineCode: () => {},
})('DEF'),
).toEqual([]);
});
});
describe('shouldRenderParallelCommentRow', () => { describe('shouldRenderParallelCommentRow', () => {
let line; let line;
beforeEach(() => { beforeEach(() => {
line = {}; line = {};
discussionMock.expanded = true;
line.left = { line.left = {
lineCode: 'ABC', lineCode: 'ABC',
discussions: [discussionMock],
}; };
line.right = { line.right = {
lineCode: 'DEF', lineCode: 'DEF',
discussions: [discussionMock1],
}; };
}); });
it('returns true when discussion is expanded', () => { it('returns true when discussion is expanded', () => {
discussionMock.expanded = true; expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(true);
expect(
getters.shouldRenderParallelCommentRow(localState, {
singleDiscussionByLineCode: () => [discussionMock],
})(line),
).toEqual(true);
}); });
it('returns false when no discussion was found', () => { it('returns false when no discussion was found', () => {
line.left.discussions = [];
line.right.discussions = [];
localState.diffLineCommentForms.ABC = false; localState.diffLineCommentForms.ABC = false;
localState.diffLineCommentForms.DEF = false; localState.diffLineCommentForms.DEF = false;
expect( expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(false);
getters.shouldRenderParallelCommentRow(localState, {
singleDiscussionByLineCode: () => [],
})(line),
).toEqual(false);
}); });
it('returns true when discussionForm was found', () => { it('returns true when discussionForm was found', () => {
localState.diffLineCommentForms.ABC = {}; localState.diffLineCommentForms.ABC = {};
expect( expect(getters.shouldRenderParallelCommentRow(localState)(line)).toEqual(true);
getters.shouldRenderParallelCommentRow(localState, {
singleDiscussionByLineCode: () => [discussionMock],
})(line),
).toEqual(true);
}); });
}); });
describe('shouldRenderInlineCommentRow', () => { describe('shouldRenderInlineCommentRow', () => {
let line;
beforeEach(() => {
discussionMock.expanded = true;
line = {
lineCode: 'ABC',
discussions: [discussionMock],
};
});
it('returns true when diffLineCommentForms has form', () => { it('returns true when diffLineCommentForms has form', () => {
localState.diffLineCommentForms.ABC = {}; localState.diffLineCommentForms.ABC = {};
expect( expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(true);
getters.shouldRenderInlineCommentRow(localState)({
lineCode: 'ABC',
}),
).toEqual(true);
}); });
it('returns false when no line discussions were found', () => { it('returns false when no line discussions were found', () => {
expect( line.discussions = [];
getters.shouldRenderInlineCommentRow(localState, { expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(false);
singleDiscussionByLineCode: () => [],
})('DEF'),
).toEqual(false);
}); });
it('returns true if all found discussions are expanded', () => { it('returns true if all found discussions are expanded', () => {
discussionMock.expanded = true; discussionMock.expanded = true;
expect( expect(getters.shouldRenderInlineCommentRow(localState)(line)).toEqual(true);
getters.shouldRenderInlineCommentRow(localState, {
singleDiscussionByLineCode: () => [discussionMock],
})('ABC'),
).toEqual(true);
}); });
}); });
......
...@@ -138,10 +138,9 @@ describe('DiffsStoreMutations', () => { ...@@ -138,10 +138,9 @@ describe('DiffsStoreMutations', () => {
const fileHash = 123; const fileHash = 123;
const state = { diffFiles: [{}, { fileHash, existingField: 0 }] }; const state = { diffFiles: [{}, { fileHash, existingField: 0 }] };
const file = { fileHash };
const data = { diff_files: [{ file_hash: fileHash, extra_field: 1, existingField: 1 }] }; const data = { diff_files: [{ file_hash: fileHash, extra_field: 1, existingField: 1 }] };
mutations[types.ADD_COLLAPSED_DIFFS](state, { file, data }); mutations[types.ADD_COLLAPSED_DIFFS](state, { file: state.diffFiles[1], data });
expect(spy).toHaveBeenCalledWith(data, { deep: true }); expect(spy).toHaveBeenCalledWith(data, { deep: true });
expect(state.diffFiles[1].fileHash).toEqual(fileHash); expect(state.diffFiles[1].fileHash).toEqual(fileHash);
...@@ -149,4 +148,107 @@ describe('DiffsStoreMutations', () => { ...@@ -149,4 +148,107 @@ describe('DiffsStoreMutations', () => {
expect(state.diffFiles[1].extraField).toEqual(1); expect(state.diffFiles[1].extraField).toEqual(1);
}); });
}); });
describe('SET_LINE_DISCUSSIONS_FOR_FILE', () => {
it('should add discussions to the given line', () => {
const state = {
diffFiles: [
{
fileHash: 'ABC',
parallelDiffLines: [
{
left: {
lineCode: 'ABC_1',
discussions: [],
},
right: {
lineCode: 'ABC_1',
discussions: [],
},
},
],
highlightedDiffLines: [
{
lineCode: 'ABC_1',
discussions: [],
},
],
},
],
};
const discussions = [
{
id: 1,
line_code: 'ABC_1',
},
{
id: 2,
line_code: 'ABC_1',
},
];
mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash: 'ABC', discussions });
expect(state.diffFiles[0].parallelDiffLines[0].left.discussions.length).toEqual(2);
expect(state.diffFiles[0].parallelDiffLines[0].left.discussions[1].id).toEqual(2);
expect(state.diffFiles[0].highlightedDiffLines[0].discussions.length).toEqual(2);
expect(state.diffFiles[0].highlightedDiffLines[0].discussions[1].id).toEqual(2);
});
});
describe('REMOVE_LINE_DISCUSSIONS', () => {
it('should remove the existing discussions on the given line', () => {
const state = {
diffFiles: [
{
fileHash: 'ABC',
parallelDiffLines: [
{
left: {
lineCode: 'ABC_1',
discussions: [
{
id: 1,
line_code: 'ABC_1',
},
{
id: 2,
line_code: 'ABC_1',
},
],
},
right: {
lineCode: 'ABC_1',
discussions: [],
},
},
],
highlightedDiffLines: [
{
lineCode: 'ABC_1',
discussions: [
{
id: 1,
line_code: 'ABC_1',
},
{
id: 2,
line_code: 'ABC_1',
},
],
},
],
},
],
};
mutations[types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, {
fileHash: 'ABC',
lineCode: 'ABC_1',
});
expect(state.diffFiles[0].parallelDiffLines[0].left.discussions.length).toEqual(0);
expect(state.diffFiles[0].highlightedDiffLines[0].discussions.length).toEqual(0);
});
});
}); });
...@@ -179,32 +179,64 @@ describe('DiffsStoreUtils', () => { ...@@ -179,32 +179,64 @@ describe('DiffsStoreUtils', () => {
describe('trimFirstCharOfLineContent', () => { describe('trimFirstCharOfLineContent', () => {
it('trims the line when it starts with a space', () => { it('trims the line when it starts with a space', () => {
expect(utils.trimFirstCharOfLineContent({ richText: ' diff' })).toEqual({ richText: 'diff' }); expect(utils.trimFirstCharOfLineContent({ richText: ' diff' })).toEqual({
discussions: [],
richText: 'diff',
});
}); });
it('trims the line when it starts with a +', () => { it('trims the line when it starts with a +', () => {
expect(utils.trimFirstCharOfLineContent({ richText: '+diff' })).toEqual({ richText: 'diff' }); expect(utils.trimFirstCharOfLineContent({ richText: '+diff' })).toEqual({
discussions: [],
richText: 'diff',
});
}); });
it('trims the line when it starts with a -', () => { it('trims the line when it starts with a -', () => {
expect(utils.trimFirstCharOfLineContent({ richText: '-diff' })).toEqual({ richText: 'diff' }); expect(utils.trimFirstCharOfLineContent({ richText: '-diff' })).toEqual({
discussions: [],
richText: 'diff',
});
}); });
it('does not trims the line when it starts with a letter', () => { it('does not trims the line when it starts with a letter', () => {
expect(utils.trimFirstCharOfLineContent({ richText: 'diff' })).toEqual({ richText: 'diff' }); expect(utils.trimFirstCharOfLineContent({ richText: 'diff' })).toEqual({
discussions: [],
richText: 'diff',
});
}); });
it('does not modify the provided object', () => { it('does not modify the provided object', () => {
const lineObj = { const lineObj = {
discussions: [],
richText: ' diff', richText: ' diff',
}; };
utils.trimFirstCharOfLineContent(lineObj); utils.trimFirstCharOfLineContent(lineObj);
expect(lineObj).toEqual({ richText: ' diff' }); expect(lineObj).toEqual({ discussions: [], richText: ' diff' });
}); });
it('handles a undefined or null parameter', () => { it('handles a undefined or null parameter', () => {
expect(utils.trimFirstCharOfLineContent()).toEqual({}); expect(utils.trimFirstCharOfLineContent()).toEqual({ discussions: [] });
});
});
describe('prepareDiffData', () => {
it('sets the renderIt and collapsed attribute on files', () => {
const preparedDiff = { diffFiles: [getDiffFileMock()] };
utils.prepareDiffData(preparedDiff);
const firstParallelDiffLine = preparedDiff.diffFiles[0].parallelDiffLines[2];
expect(firstParallelDiffLine.left.discussions.length).toBe(0);
expect(firstParallelDiffLine.left).not.toHaveAttr('text');
expect(firstParallelDiffLine.right.discussions.length).toBe(0);
expect(firstParallelDiffLine.right).not.toHaveAttr('text');
expect(preparedDiff.diffFiles[0].highlightedDiffLines[0].discussions.length).toBe(0);
expect(preparedDiff.diffFiles[0].highlightedDiffLines[0]).not.toHaveAttr('text');
expect(preparedDiff.diffFiles[0].renderIt).toBeTruthy();
expect(preparedDiff.diffFiles[0].collapsed).toBeFalsy();
}); });
}); });
}); });
...@@ -2,10 +2,10 @@ import LazyLoader from '~/lazy_loader'; ...@@ -2,10 +2,10 @@ import LazyLoader from '~/lazy_loader';
let lazyLoader = null; let lazyLoader = null;
describe('LazyLoader', function () { describe('LazyLoader', function() {
preloadFixtures('issues/issue_with_comment.html.raw'); preloadFixtures('issues/issue_with_comment.html.raw');
beforeEach(function () { beforeEach(function() {
loadFixtures('issues/issue_with_comment.html.raw'); loadFixtures('issues/issue_with_comment.html.raw');
lazyLoader = new LazyLoader({ lazyLoader = new LazyLoader({
observerNode: 'body', observerNode: 'body',
...@@ -13,8 +13,8 @@ describe('LazyLoader', function () { ...@@ -13,8 +13,8 @@ describe('LazyLoader', function () {
// Doing everything that happens normally in onload // Doing everything that happens normally in onload
lazyLoader.loadCheck(); lazyLoader.loadCheck();
}); });
describe('behavior', function () { describe('behavior', function() {
it('should copy value from data-src to src for img 1', function (done) { it('should copy value from data-src to src for img 1', function(done) {
const img = document.querySelectorAll('img[data-src]')[0]; const img = document.querySelectorAll('img[data-src]')[0];
const originalDataSrc = img.getAttribute('data-src'); const originalDataSrc = img.getAttribute('data-src');
img.scrollIntoView(); img.scrollIntoView();
...@@ -26,7 +26,7 @@ describe('LazyLoader', function () { ...@@ -26,7 +26,7 @@ describe('LazyLoader', function () {
}, 100); }, 100);
}); });
it('should lazy load dynamically added data-src images', function (done) { it('should lazy load dynamically added data-src images', function(done) {
const newImg = document.createElement('img'); const newImg = document.createElement('img');
const testPath = '/img/testimg.png'; const testPath = '/img/testimg.png';
newImg.className = 'lazy'; newImg.className = 'lazy';
...@@ -41,7 +41,7 @@ describe('LazyLoader', function () { ...@@ -41,7 +41,7 @@ describe('LazyLoader', function () {
}, 100); }, 100);
}); });
it('should not alter normal images', function (done) { it('should not alter normal images', function(done) {
const newImg = document.createElement('img'); const newImg = document.createElement('img');
const testPath = '/img/testimg.png'; const testPath = '/img/testimg.png';
newImg.setAttribute('src', testPath); newImg.setAttribute('src', testPath);
......
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