Commit ab6b1633 authored by Justin Boyson's avatar Justin Boyson Committed by Phil Hughes

Refactor table markup out of expansion cell

To minimize duplication we are promoting as much HTML table
markup as is feasible up to the parent "row" components.

Move computed values up to diff_content
Move html tables up to row components
Remove expansion row components as no longer needed
parent 67978578
<script>
import DraftNote from './draft_note.vue';
export default {
components: {
DraftNote,
},
props: {
draft: {
type: Object,
required: true,
},
diffFile: {
type: Object,
required: true,
},
line: {
type: Object,
required: false,
default: null,
},
},
};
</script>
<template>
<tr class="notes_holder js-temp-notes-holder">
<td class="notes-content" colspan="4">
<div class="content"><draft-note :draft="draft" :diff-file="diffFile" :line="line" /></div>
</td>
</tr>
</template>
<script>
import { mapGetters } from 'vuex';
import DraftNote from './draft_note.vue';
export default {
components: {
DraftNote,
},
props: {
line: {
type: Object,
required: true,
},
diffFileContentSha: {
type: String,
required: true,
},
},
computed: {
...mapGetters('batchComments', ['draftForLine']),
className() {
return this.leftDraft > 0 || this.rightDraft > 0 ? '' : 'js-temp-notes-holder';
},
leftDraft() {
return this.draftForLine(this.diffFileContentSha, this.line, 'left');
},
rightDraft() {
return this.draftForLine(this.diffFileContentSha, this.line, 'right');
},
},
};
</script>
<template>
<tr :class="className" class="notes_holder">
<td class="notes_line old"></td>
<td class="notes-content parallel old" colspan="2">
<div v-if="leftDraft.isDraft" class="content">
<draft-note :draft="leftDraft" :line="line.left" />
</div>
</td>
<td class="notes_line new"></td>
<td class="notes-content parallel new" colspan="2">
<div v-if="rightDraft.isDraft" class="content">
<draft-note :draft="rightDraft" :line="line.right" />
</div>
</td>
</tr>
</template>
...@@ -29,18 +29,10 @@ export default { ...@@ -29,18 +29,10 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
}, linePosition: {
computed: { type: String,
className() { required: false,
return this.line.discussions.length ? '' : 'js-temp-notes-holder'; default: '',
},
shouldRender() {
if (this.line.hasForm) return true;
if (!this.line.discussions || !this.line.discussions.length) {
return false;
}
return this.line.discussionsExpanded;
}, },
}, },
methods: { methods: {
...@@ -50,33 +42,28 @@ export default { ...@@ -50,33 +42,28 @@ export default {
</script> </script>
<template> <template>
<tr v-if="shouldRender" :class="className" class="notes_holder"> <div class="content">
<td class="notes-content" colspan="4"> <diff-discussions
<div class="content"> v-if="line.renderDiscussion"
<diff-discussions :line="line"
v-if="line.discussions.length" :discussions="line.discussions"
:help-page-path="helpPagePath"
/>
<diff-discussion-reply
v-if="!hasDraft"
:has-form="line.hasCommentForm"
:render-reply-placeholder="Boolean(line.discussions.length)"
@showNewDiscussionForm="showCommentForm({ lineCode: line.line_code, fileHash: diffFileHash })"
>
<template #form>
<diff-line-note-form
:diff-file-hash="diffFileHash"
:line="line" :line="line"
:discussions="line.discussions" :note-target-line="line"
:help-page-path="helpPagePath" :help-page-path="helpPagePath"
:line-position="linePosition"
/> />
<diff-discussion-reply </template>
v-if="!hasDraft" </diff-discussion-reply>
:has-form="line.hasForm" </div>
:render-reply-placeholder="Boolean(line.discussions.length)"
@showNewDiscussionForm="
showCommentForm({ lineCode: line.line_code, fileHash: diffFileHash })
"
>
<template #form>
<diff-line-note-form
:diff-file-hash="diffFileHash"
:line="line"
:note-target-line="line"
:help-page-path="helpPagePath"
/>
</template>
</diff-discussion-reply>
</div>
</td>
</tr>
</template> </template>
...@@ -10,6 +10,7 @@ import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_prev ...@@ -10,6 +10,7 @@ import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_prev
import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue'; import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue';
import InlineDiffView from './inline_diff_view.vue'; import InlineDiffView from './inline_diff_view.vue';
import ParallelDiffView from './parallel_diff_view.vue'; import ParallelDiffView from './parallel_diff_view.vue';
import DiffView from './diff_view.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import NoteForm from '../../notes/components/note_form.vue'; import NoteForm from '../../notes/components/note_form.vue';
import ImageDiffOverlay from './image_diff_overlay.vue'; import ImageDiffOverlay from './image_diff_overlay.vue';
...@@ -18,12 +19,14 @@ import eventHub from '../../notes/event_hub'; ...@@ -18,12 +19,14 @@ import eventHub from '../../notes/event_hub';
import { IMAGE_DIFF_POSITION_TYPE } from '../constants'; import { IMAGE_DIFF_POSITION_TYPE } from '../constants';
import { getDiffMode } from '../store/utils'; import { getDiffMode } from '../store/utils';
import { diffViewerModes } from '~/ide/constants'; import { diffViewerModes } from '~/ide/constants';
import { mapInline, mapParallel } from './diff_row_utils';
export default { export default {
components: { components: {
GlLoadingIcon, GlLoadingIcon,
InlineDiffView, InlineDiffView,
ParallelDiffView, ParallelDiffView,
DiffView,
DiffViewer, DiffViewer,
NoteForm, NoteForm,
DiffDiscussions, DiffDiscussions,
...@@ -83,6 +86,19 @@ export default { ...@@ -83,6 +86,19 @@ export default {
author() { author() {
return this.getUserData; return this.getUserData;
}, },
mappedLines() {
if (this.glFeatures.unifiedDiffLines && this.glFeatures.unifiedDiffComponents) {
return this.diffLines(this.diffFile, true).map(mapParallel(this)) || [];
}
// TODO: Everything below this line can be deleted when unifiedDiffComponents FF is removed
if (this.isInlineView) {
return this.diffFile.highlighted_diff_lines.map(mapInline(this));
}
return this.glFeatures.unifiedDiffLines
? this.diffLines(this.diffFile).map(mapParallel(this))
: this.diffFile.parallel_diff_lines.map(mapParallel(this)) || [];
},
}, },
updated() { updated() {
this.$nextTick(() => { this.$nextTick(() => {
...@@ -113,19 +129,28 @@ export default { ...@@ -113,19 +129,28 @@ export default {
<template> <template>
<div class="diff-content"> <div class="diff-content">
<div class="diff-viewer"> <div class="diff-viewer">
<template v-if="isTextFile"> <template
v-if="isTextFile && glFeatures.unifiedDiffLines && glFeatures.unifiedDiffComponents"
>
<diff-view
:diff-file="diffFile"
:diff-lines="mappedLines"
:help-page-path="helpPagePath"
:inline="isInlineView"
/>
<gl-loading-icon v-if="diffFile.renderingLines" size="md" class="mt-3" />
</template>
<template v-else-if="isTextFile">
<inline-diff-view <inline-diff-view
v-if="isInlineView" v-if="isInlineView"
:diff-file="diffFile" :diff-file="diffFile"
:diff-lines="diffFile.highlighted_diff_lines" :diff-lines="mappedLines"
:help-page-path="helpPagePath" :help-page-path="helpPagePath"
/> />
<parallel-diff-view <parallel-diff-view
v-else-if="isParallelView" v-else-if="isParallelView"
:diff-file="diffFile" :diff-file="diffFile"
:diff-lines=" :diff-lines="mappedLines"
glFeatures.unifiedDiffLines ? diffLines(diffFile) : diffFile.parallel_diff_lines || []
"
:help-page-path="helpPagePath" :help-page-path="helpPagePath"
/> />
<gl-loading-icon v-if="diffFile.renderingLines" size="md" class="mt-3" /> <gl-loading-icon v-if="diffFile.renderingLines" size="md" class="mt-3" />
......
...@@ -54,11 +54,6 @@ export default { ...@@ -54,11 +54,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
colspan: {
type: Number,
required: false,
default: 4,
},
}, },
computed: { computed: {
...mapState({ ...mapState({
...@@ -231,28 +226,26 @@ export default { ...@@ -231,28 +226,26 @@ export default {
</script> </script>
<template> <template>
<td :colspan="colspan" class="text-center gl-font-regular"> <div class="content js-line-expansion-content">
<div class="content js-line-expansion-content"> <a
<a v-if="canExpandDown"
v-if="canExpandDown" class="gl-mx-2 gl-cursor-pointer js-unfold-down gl-display-inline-block gl-py-4"
class="gl-mx-2 gl-cursor-pointer js-unfold-down gl-display-inline-block gl-py-4" @click="handleExpandLines(EXPAND_DOWN)"
@click="handleExpandLines(EXPAND_DOWN)" >
> <gl-icon :size="12" name="expand-down" aria-hidden="true" />
<gl-icon :size="12" name="expand-down" aria-hidden="true" /> <span>{{ $options.i18n.showMore }}</span>
<span>{{ $options.i18n.showMore }}</span> </a>
</a> <a class="gl-mx-2 cursor-pointer js-unfold-all" @click="handleExpandLines()">
<a class="gl-mx-2 cursor-pointer js-unfold-all" @click="handleExpandLines()"> <gl-icon :size="12" name="expand" aria-hidden="true" />
<gl-icon :size="12" name="expand" aria-hidden="true" /> <span>{{ $options.i18n.showAll }}</span>
<span>{{ $options.i18n.showAll }}</span> </a>
</a> <a
<a v-if="canExpandUp"
v-if="canExpandUp" class="gl-mx-2 gl-cursor-pointer js-unfold gl-display-inline-block gl-py-4"
class="gl-mx-2 gl-cursor-pointer js-unfold gl-display-inline-block gl-py-4" @click="handleExpandLines(EXPAND_UP)"
@click="handleExpandLines(EXPAND_UP)" >
> <gl-icon :size="12" name="expand-up" aria-hidden="true" />
<gl-icon :size="12" name="expand-up" aria-hidden="true" /> <span>{{ $options.i18n.showMore }}</span>
<span>{{ $options.i18n.showMore }}</span> </a>
</a> </div>
</div>
</td>
</template> </template>
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { CONTEXT_LINE_CLASS_NAME, PARALLEL_DIFF_VIEW_TYPE } from '../constants';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
import * as utils from './diff_row_utils';
export default {
components: {
GlIcon,
DiffGutterAvatars,
},
directives: {
GlTooltip: GlTooltipDirective,
SafeHtml,
},
props: {
fileHash: {
type: String,
required: true,
},
filePath: {
type: String,
required: true,
},
line: {
type: Object,
required: true,
},
isCommented: {
type: Boolean,
required: false,
default: false,
},
inline: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapGetters('diffs', ['fileLineCoverage']),
...mapGetters(['isLoggedIn']),
...mapState({
isHighlighted(state) {
const line = this.line.left?.line_code ? this.line.left : this.line.right;
return utils.isHighlighted(state, line, this.isCommented);
},
}),
classNameMap() {
return {
[CONTEXT_LINE_CLASS_NAME]: this.line.isContextLineLeft,
[PARALLEL_DIFF_VIEW_TYPE]: true,
};
},
parallelViewLeftLineType() {
return utils.parallelViewLeftLineType(this.line, this.isHighlighted);
},
coverageState() {
return this.fileLineCoverage(this.filePath, this.line.right.new_line);
},
classNameMapCellLeft() {
return utils.classNameMapCell(this.line.left, this.isHighlighted, this.isLoggedIn);
},
classNameMapCellRight() {
return utils.classNameMapCell(this.line.right, this.isHighlighted, this.isLoggedIn);
},
addCommentTooltipLeft() {
return utils.addCommentTooltip(this.line.left);
},
addCommentTooltipRight() {
return utils.addCommentTooltip(this.line.right);
},
shouldRenderCommentButton() {
return (
this.isLoggedIn &&
!this.line.isContextLineLeft &&
!this.line.isMetaLineLeft &&
!this.line.hasDiscussionsLeft &&
!this.line.hasDiscussionsRight
);
},
},
mounted() {
this.scrollToLineIfNeededParallel(this.line);
},
methods: {
...mapActions('diffs', [
'scrollToLineIfNeededParallel',
'showCommentForm',
'setHighlightedRow',
'toggleLineDiscussions',
]),
// Prevent text selecting on both sides of parallel diff view
// Backport of the same code from legacy diff notes.
handleParallelLineMouseDown(e) {
const line = e.currentTarget;
const table = line.closest('.diff-table');
table.classList.remove('left-side-selected', 'right-side-selected');
const [lineClass] = ['left-side', 'right-side'].filter(name => line.classList.contains(name));
if (lineClass) {
table.classList.add(`${lineClass}-selected`);
}
},
handleCommentButton(line) {
this.showCommentForm({ lineCode: line.line_code, fileHash: this.fileHash });
},
},
};
</script>
<template>
<div :class="classNameMap" class="diff-grid-row diff-tr line_holder">
<div class="diff-grid-left left-side">
<template v-if="line.left">
<div
:class="classNameMapCellLeft"
data-testid="leftLineNumber"
class="diff-td diff-line-num old_line"
>
<span
v-if="shouldRenderCommentButton"
v-gl-tooltip
data-testid="leftCommentButton"
class="add-diff-note tooltip-wrapper"
:title="addCommentTooltipLeft"
>
<button
type="button"
class="add-diff-note note-button js-add-diff-note-button qa-diff-comment"
:disabled="line.left.commentsDisabled"
@click="handleCommentButton(line.left)"
>
<gl-icon :size="12" name="comment" />
</button>
</span>
<a
v-if="line.left.old_line"
:data-linenumber="line.left.old_line"
:href="line.lineHrefOld"
@click="setHighlightedRow(line.lineCode)"
>
</a>
<diff-gutter-avatars
v-if="line.hasDiscussionsLeft"
:discussions="line.left.discussions"
:discussions-expanded="line.left.discussionsExpanded"
data-testid="leftDiscussions"
@toggleLineDiscussions="
toggleLineDiscussions({
lineCode: line.left.line_code,
fileHash,
expanded: !line.left.discussionsExpanded,
})
"
/>
</div>
<div :class="classNameMapCellLeft" class="diff-td diff-line-num old_line">
<a
v-if="line.left.old_line"
:data-linenumber="line.left.old_line"
:href="line.lineHrefOld"
@click="setHighlightedRow(line.lineCode)"
>
</a>
</div>
<div :class="parallelViewLeftLineType" class="diff-td line-coverage left-side"></div>
<div
:id="line.left.line_code"
:key="line.left.line_code"
v-safe-html="line.left.rich_text"
:class="parallelViewLeftLineType"
class="diff-td line_content with-coverage parallel left-side"
data-testid="leftContent"
@mousedown="handleParallelLineMouseDown"
></div>
</template>
<template v-else>
<div data-testid="leftEmptyCell" class="diff-td diff-line-num old_line empty-cell"></div>
<div class="diff-td diff-line-num old_line empty-cell"></div>
<div class="diff-td line-coverage left-side empty-cell"></div>
<div class="diff-td line_content with-coverage parallel left-side empty-cell"></div>
</template>
</div>
<div
v-if="!inline || (line.right && Boolean(line.right.type))"
class="diff-grid-right right-side"
>
<template v-if="line.right">
<div
:class="classNameMapCellRight"
data-testid="rightLineNumber"
class="diff-td diff-line-num new_line"
>
<span
v-if="shouldRenderCommentButton"
v-gl-tooltip
data-testid="rightCommentButton"
class="add-diff-note tooltip-wrapper"
:title="addCommentTooltipRight"
>
<button
type="button"
class="add-diff-note note-button js-add-diff-note-button qa-diff-comment"
:disabled="line.right.commentsDisabled"
@click="handleCommentButton(line.right)"
>
<gl-icon :size="12" name="comment" />
</button>
</span>
<a
v-if="line.right.new_line"
:data-linenumber="line.right.new_line"
:href="line.lineHrefNew"
@click="setHighlightedRow(line.lineCode)"
>
</a>
<diff-gutter-avatars
v-if="line.hasDiscussionsRight"
:discussions="line.right.discussions"
:discussions-expanded="line.right.discussionsExpanded"
data-testid="rightDiscussions"
@toggleLineDiscussions="
toggleLineDiscussions({
lineCode: line.right.line_code,
fileHash,
expanded: !line.right.discussionsExpanded,
})
"
/>
</div>
<div :class="classNameMapCellRight" class="diff-td diff-line-num new_line">
<a
v-if="line.right.new_line"
:data-linenumber="line.right.new_line"
:href="line.lineHrefNew"
@click="setHighlightedRow(line.lineCode)"
>
</a>
</div>
<div
v-gl-tooltip.hover
:title="coverageState.text"
:class="[line.right.type, coverageState.class, { hll: isHighlighted }]"
class="diff-td line-coverage right-side"
></div>
<div
:id="line.right.line_code"
:key="line.right.rich_text"
v-safe-html="line.right.rich_text"
:class="[
line.right.type,
{
hll: isHighlighted,
},
]"
class="diff-td line_content with-coverage parallel right-side"
@mousedown="handleParallelLineMouseDown"
></div>
</template>
<template v-else>
<div data-testid="rightEmptyCell" class="diff-td diff-line-num old_line empty-cell"></div>
<div class="diff-td diff-line-num old_line empty-cell"></div>
<div class="diff-td line-coverage right-side empty-cell"></div>
<div class="diff-td line_content with-coverage parallel right-side empty-cell"></div>
</template>
</div>
</div>
</template>
...@@ -83,3 +83,76 @@ export const parallelViewLeftLineType = (line, hll) => { ...@@ -83,3 +83,76 @@ export const parallelViewLeftLineType = (line, hll) => {
export const shouldShowCommentButton = (hover, context, meta, discussions) => { export const shouldShowCommentButton = (hover, context, meta, discussions) => {
return hover && !context && !meta && !discussions; return hover && !context && !meta && !discussions;
}; };
export const mapParallel = content => line => {
let { left, right } = line;
// Dicussions/Comments
const hasExpandedDiscussionOnLeft =
left?.discussions?.length > 0 ? left?.discussionsExpanded : false;
const hasExpandedDiscussionOnRight =
right?.discussions?.length > 0 ? right?.discussionsExpanded : false;
const renderCommentRow =
hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight || left?.hasForm || right?.hasForm;
if (left) {
left = {
...left,
renderDiscussion: hasExpandedDiscussionOnLeft,
hasDraft: content.hasParallelDraftLeft(content.diffFile.file_hash, line),
lineDraft: content.draftForLine(content.diffFile.file_hash, line, 'left'),
hasCommentForm: left.hasForm,
};
}
if (right) {
right = {
...right,
renderDiscussion: Boolean(hasExpandedDiscussionOnRight && right.type),
hasDraft: content.hasParallelDraftRight(content.diffFile.file_hash, line),
lineDraft: content.draftForLine(content.diffFile.file_hash, line, 'right'),
hasCommentForm: Boolean(right.hasForm && right.type),
};
}
return {
...line,
left,
right,
isMatchLineLeft: isMatchLine(left?.type),
isMatchLineRight: isMatchLine(right?.type),
isContextLineLeft: isContextLine(left?.type),
isContextLineRight: isContextLine(right?.type),
hasDiscussionsLeft: hasDiscussions(left),
hasDiscussionsRight: hasDiscussions(right),
lineHrefOld: lineHref(left),
lineHrefNew: lineHref(right),
lineCode: lineCode(line),
isMetaLineLeft: isMetaLine(left?.type),
isMetaLineRight: isMetaLine(right?.type),
draftRowClasses: left?.lineDraft > 0 || right?.lineDraft > 0 ? '' : 'js-temp-notes-holder',
renderCommentRow,
commentRowClasses: hasDiscussions(left) || hasDiscussions(right) ? '' : 'js-temp-notes-holder',
};
};
// TODO: Delete this function when unifiedDiffComponents FF is removed
export const mapInline = content => line => {
// Discussions/Comments
const renderCommentRow = line.hasForm || (line.discussions?.length && line.discussionsExpanded);
return {
...line,
renderDiscussion: Boolean(line.discussions?.length),
isMatchLine: isMatchLine(line.type),
commentRowClasses: line.discussions?.length ? '' : 'js-temp-notes-holder',
renderCommentRow,
hasDraft: content.shouldRenderDraftRow(content.diffFile.file_hash, line),
hasCommentForm: line.hasForm,
isMetaLine: isMetaLine(line.type),
isContextLine: isContextLine(line.type),
hasDiscussions: hasDiscussions(line),
lineHref: lineHref(line),
lineCode: lineCode(line),
};
};
<script>
import { mapGetters, mapState } from 'vuex';
import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import DraftNote from '~/batch_comments/components/draft_note.vue';
import DiffRow from './diff_row.vue';
import DiffCommentCell from './diff_comment_cell.vue';
import DiffExpansionCell from './diff_expansion_cell.vue';
import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
export default {
components: {
DiffExpansionCell,
DiffRow,
DiffCommentCell,
DraftNote,
},
mixins: [draftCommentsMixin],
props: {
diffFile: {
type: Object,
required: true,
},
diffLines: {
type: Array,
required: true,
},
helpPagePath: {
type: String,
required: false,
default: '',
},
inline: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapGetters('diffs', ['commitId']),
...mapState({
selectedCommentPosition: ({ notes }) => notes.selectedCommentPosition,
selectedCommentPositionHover: ({ notes }) => notes.selectedCommentPositionHover,
}),
diffLinesLength() {
return this.diffLines.length;
},
commentedLines() {
return getCommentedLines(
this.selectedCommentPosition || this.selectedCommentPositionHover,
this.diffLines,
);
},
},
userColorScheme: window.gon.user_color_scheme,
};
</script>
<template>
<div
:class="[$options.userColorScheme, { inline }]"
:data-commit-id="commitId"
class="diff-grid diff-table code diff-wrap-lines js-syntax-highlight text-file"
>
<template v-for="(line, index) in diffLines">
<div
v-if="line.isMatchLineLeft || line.isMatchLineRight"
:key="`expand-${index}`"
class="diff-tr line_expansion match"
>
<div class="diff-td text-center gl-font-regular">
<diff-expansion-cell
:file-hash="diffFile.file_hash"
:context-lines-path="diffFile.context_lines_path"
:line="line.left"
:is-top="index === 0"
:is-bottom="index + 1 === diffLinesLength"
/>
</div>
</div>
<diff-row
v-if="!line.isMatchLineLeft && !line.isMatchLineRight"
:key="line.line_code"
:file-hash="diffFile.file_hash"
:file-path="diffFile.file_path"
:line="line"
:is-bottom="index + 1 === diffLinesLength"
:is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine"
:inline="inline"
/>
<div
v-if="line.renderCommentRow"
:key="`dcr-${line.line_code || index}`"
:class="line.commentRowClasses"
class="diff-grid-comments diff-tr notes_holder"
>
<div
v-if="!inline || (line.left && line.left.discussions.length)"
class="diff-td notes-content parallel old"
>
<diff-comment-cell
v-if="line.left"
:line="line.left"
:diff-file-hash="diffFile.file_hash"
:help-page-path="helpPagePath"
:has-draft="line.left.hasDraft"
line-position="left"
/>
</div>
<div
v-if="!inline || (line.right && line.right.discussions.length)"
class="diff-td notes-content parallel new"
>
<diff-comment-cell
v-if="line.right"
:line="line.right"
:diff-file-hash="diffFile.file_hash"
:line-index="index"
:help-page-path="helpPagePath"
:has-draft="line.right.hasDraft"
line-position="right"
/>
</div>
</div>
<div
v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)"
:key="`drafts-${index}`"
:class="line.draftRowClasses"
class="diff-grid-drafts diff-tr notes_holder"
>
<div
v-if="!inline || (line.left && line.left.lineDraft.isDraft)"
class="diff-td notes-content parallel old"
>
<div v-if="line.left && line.left.lineDraft.isDraft" class="content">
<draft-note :draft="line.left.lineDraft" :line="line.left" />
</div>
</div>
<div
v-if="!inline || (line.right && line.right.lineDraft.isDraft)"
class="diff-td notes-content parallel new"
>
<div v-if="line.right && line.right.lineDraft.isDraft" class="content">
<draft-note :draft="line.right.lineDraft" :line="line.right" />
</div>
</div>
</div>
</template>
</div>
</template>
<script>
import DiffExpansionCell from './diff_expansion_cell.vue';
import { MATCH_LINE_TYPE } from '../constants';
export default {
components: {
DiffExpansionCell,
},
props: {
fileHash: {
type: String,
required: true,
},
contextLinesPath: {
type: String,
required: true,
},
line: {
type: Object,
required: true,
},
isTop: {
type: Boolean,
required: false,
default: false,
},
isBottom: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
isMatchLine() {
return this.line.type === MATCH_LINE_TYPE;
},
},
};
</script>
<template>
<tr v-if="isMatchLine" class="line_expansion match">
<diff-expansion-cell
:file-hash="fileHash"
:context-lines-path="contextLinesPath"
:line="line"
:is-top="isTop"
:is-bottom="isBottom"
/>
</tr>
</template>
...@@ -3,7 +3,13 @@ import { mapActions, mapGetters, mapState } from 'vuex'; ...@@ -3,7 +3,13 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { CONTEXT_LINE_CLASS_NAME } from '../constants'; import { CONTEXT_LINE_CLASS_NAME } from '../constants';
import DiffGutterAvatars from './diff_gutter_avatars.vue'; import DiffGutterAvatars from './diff_gutter_avatars.vue';
import * as utils from './diff_row_utils'; import {
isHighlighted,
shouldShowCommentButton,
shouldRenderCommentButton,
classNameMapCell,
addCommentTooltip,
} from './diff_row_utils';
export default { export default {
components: { components: {
...@@ -48,60 +54,42 @@ export default { ...@@ -48,60 +54,42 @@ export default {
...mapGetters('diffs', ['fileLineCoverage']), ...mapGetters('diffs', ['fileLineCoverage']),
...mapState({ ...mapState({
isHighlighted(state) { isHighlighted(state) {
return utils.isHighlighted(state, this.line, this.isCommented); return isHighlighted(state, this.line, this.isCommented);
}, },
}), }),
isContextLine() {
return utils.isContextLine(this.line.type);
},
classNameMap() { classNameMap() {
return [ return [
this.line.type, this.line.type,
{ {
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine, [CONTEXT_LINE_CLASS_NAME]: this.line.isContextLine,
}, },
]; ];
}, },
inlineRowId() { inlineRowId() {
return this.line.line_code || `${this.fileHash}_${this.line.old_line}_${this.line.new_line}`; return this.line.line_code || `${this.fileHash}_${this.line.old_line}_${this.line.new_line}`;
}, },
isMatchLine() {
return utils.isMatchLine(this.line.type);
},
coverageState() { coverageState() {
return this.fileLineCoverage(this.filePath, this.line.new_line); return this.fileLineCoverage(this.filePath, this.line.new_line);
}, },
isMetaLine() {
return utils.isMetaLine(this.line.type);
},
classNameMapCell() { classNameMapCell() {
return utils.classNameMapCell(this.line, this.isHighlighted, this.isLoggedIn, this.isHover); return classNameMapCell(this.line, this.isHighlighted, this.isLoggedIn, this.isHover);
}, },
addCommentTooltip() { addCommentTooltip() {
return utils.addCommentTooltip(this.line); return addCommentTooltip(this.line);
}, },
shouldRenderCommentButton() { shouldRenderCommentButton() {
return utils.shouldRenderCommentButton(this.isLoggedIn, true); return shouldRenderCommentButton(this.isLoggedIn, true);
}, },
shouldShowCommentButton() { shouldShowCommentButton() {
return utils.shouldShowCommentButton( return shouldShowCommentButton(
this.isHover, this.isHover,
this.isContextLine, this.line.isContextLine,
this.isMetaLine, this.line.isMetaLine,
this.hasDiscussions, this.line.hasDiscussions,
); );
}, },
hasDiscussions() {
return utils.hasDiscussions(this.line);
},
lineHref() {
return utils.lineHref(this.line);
},
lineCode() {
return utils.lineCode(this.line);
},
shouldShowAvatarsOnGutter() { shouldShowAvatarsOnGutter() {
return this.hasDiscussions; return this.line.hasDiscussions;
}, },
}, },
mounted() { mounted() {
...@@ -128,7 +116,6 @@ export default { ...@@ -128,7 +116,6 @@ export default {
<template> <template>
<tr <tr
v-if="!isMatchLine"
:id="inlineRowId" :id="inlineRowId"
:class="classNameMap" :class="classNameMap"
class="line_holder" class="line_holder"
...@@ -158,8 +145,8 @@ export default { ...@@ -158,8 +145,8 @@ export default {
v-if="line.old_line" v-if="line.old_line"
ref="lineNumberRefOld" ref="lineNumberRefOld"
:data-linenumber="line.old_line" :data-linenumber="line.old_line"
:href="lineHref" :href="line.lineHref"
@click="setHighlightedRow(lineCode)" @click="setHighlightedRow(line.lineCode)"
> >
</a> </a>
<diff-gutter-avatars <diff-gutter-avatars
...@@ -167,7 +154,11 @@ export default { ...@@ -167,7 +154,11 @@ export default {
:discussions="line.discussions" :discussions="line.discussions"
:discussions-expanded="line.discussionsExpanded" :discussions-expanded="line.discussionsExpanded"
@toggleLineDiscussions=" @toggleLineDiscussions="
toggleLineDiscussions({ lineCode, fileHash, expanded: !line.discussionsExpanded }) toggleLineDiscussions({
lineCode: line.lineCode,
fileHash,
expanded: !line.discussionsExpanded,
})
" "
/> />
</td> </td>
...@@ -176,8 +167,8 @@ export default { ...@@ -176,8 +167,8 @@ export default {
v-if="line.new_line" v-if="line.new_line"
ref="lineNumberRefNew" ref="lineNumberRefNew"
:data-linenumber="line.new_line" :data-linenumber="line.new_line"
:href="lineHref" :href="line.lineHref"
@click="setHighlightedRow(lineCode)" @click="setHighlightedRow(line.lineCode)"
> >
</a> </a>
</td> </td>
......
...@@ -2,18 +2,18 @@ ...@@ -2,18 +2,18 @@
import { mapGetters, mapState } from 'vuex'; import { mapGetters, mapState } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import draftCommentsMixin from '~/diffs/mixins/draft_comments'; import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import InlineDraftCommentRow from '~/batch_comments/components/inline_draft_comment_row.vue'; import DraftNote from '~/batch_comments/components/draft_note.vue';
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 DiffCommentCell from './diff_comment_cell.vue';
import inlineDiffExpansionRow from './inline_diff_expansion_row.vue'; import DiffExpansionCell from './diff_expansion_cell.vue';
import { getCommentedLines } from '~/notes/components/multiline_comment_utils'; import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
export default { export default {
components: { components: {
inlineDiffCommentRow, DiffCommentCell,
inlineDiffTableRow, inlineDiffTableRow,
InlineDraftCommentRow, DraftNote,
inlineDiffExpansionRow, DiffExpansionCell,
}, },
mixins: [draftCommentsMixin, glFeatureFlagsMixin()], mixins: [draftCommentsMixin, glFeatureFlagsMixin()],
props: { props: {
...@@ -65,15 +65,19 @@ export default { ...@@ -65,15 +65,19 @@ export default {
</colgroup> </colgroup>
<tbody> <tbody>
<template v-for="(line, index) in diffLines"> <template v-for="(line, index) in diffLines">
<inline-diff-expansion-row <tr v-if="line.isMatchLine" :key="`expand-${index}`" class="line_expansion match">
:key="`expand-${index}`" <td colspan="4" class="text-center gl-font-regular">
:file-hash="diffFile.file_hash" <diff-expansion-cell
:context-lines-path="diffFile.context_lines_path" :file-hash="diffFile.file_hash"
:line="line" :context-lines-path="diffFile.context_lines_path"
:is-top="index === 0" :line="line"
:is-bottom="index + 1 === diffLinesLength" :is-top="index === 0"
/> :is-bottom="index + 1 === diffLinesLength"
/>
</td>
</tr>
<inline-diff-table-row <inline-diff-table-row
v-if="!line.isMatchLine"
:key="`${line.line_code || index}`" :key="`${line.line_code || index}`"
:file-hash="diffFile.file_hash" :file-hash="diffFile.file_hash"
:file-path="diffFile.file_path" :file-path="diffFile.file_path"
...@@ -81,20 +85,32 @@ export default { ...@@ -81,20 +85,32 @@ export default {
:is-bottom="index + 1 === diffLinesLength" :is-bottom="index + 1 === diffLinesLength"
:is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine" :is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine"
/> />
<inline-diff-comment-row <tr
v-if="line.renderCommentRow"
:key="`icr-${line.line_code || index}`" :key="`icr-${line.line_code || index}`"
:diff-file-hash="diffFile.file_hash" :class="line.commentRowClasses"
:line="line" class="notes_holder"
:help-page-path="helpPagePath" >
:has-draft="shouldRenderDraftRow(diffFile.file_hash, line) || false" <td class="notes-content" colspan="4">
/> <diff-comment-cell
<inline-draft-comment-row :diff-file-hash="diffFile.file_hash"
v-if="shouldRenderDraftRow(diffFile.file_hash, line)" :line="line"
:key="`draft_${index}`" :help-page-path="helpPagePath"
:draft="draftForLine(diffFile.file_hash, line)" :has-draft="line.hasDraft"
:diff-file="diffFile" />
:line="line" </td>
/> </tr>
<tr v-if="line.hasDraft" :key="`draft_${index}`" class="notes_holder js-temp-notes-holder">
<td class="notes-content" colspan="4">
<div class="content">
<draft-note
:draft="draftForLine(diffFile.file_hash, line)"
:diff-file="diffFile"
:line="line"
/>
</div>
</td>
</tr>
</template> </template>
</tbody> </tbody>
</table> </table>
......
<script>
import { mapActions } from 'vuex';
import DiffDiscussions from './diff_discussions.vue';
import DiffLineNoteForm from './diff_line_note_form.vue';
import DiffDiscussionReply from './diff_discussion_reply.vue';
export default {
components: {
DiffDiscussions,
DiffLineNoteForm,
DiffDiscussionReply,
},
props: {
line: {
type: Object,
required: true,
},
diffFileHash: {
type: String,
required: true,
},
lineIndex: {
type: Number,
required: true,
},
helpPagePath: {
type: String,
required: false,
default: '',
},
hasDraftLeft: {
type: Boolean,
required: false,
default: false,
},
hasDraftRight: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
hasExpandedDiscussionOnLeft() {
return this.line.left && this.line.left.discussions.length
? this.line.left.discussionsExpanded
: false;
},
hasExpandedDiscussionOnRight() {
return this.line.right && this.line.right.discussions.length
? this.line.right.discussionsExpanded
: false;
},
hasAnyExpandedDiscussion() {
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
},
shouldRenderDiscussionsOnLeft() {
return (
this.line.left &&
this.line.left.discussions &&
this.line.left.discussions.length &&
this.hasExpandedDiscussionOnLeft
);
},
shouldRenderDiscussionsOnRight() {
return (
this.line.right &&
this.line.right.discussions &&
this.line.right.discussions.length &&
this.hasExpandedDiscussionOnRight &&
this.line.right.type
);
},
showRightSideCommentForm() {
return this.line.right && this.line.right.type && this.line.right.hasForm;
},
showLeftSideCommentForm() {
return this.line.left && this.line.left.hasForm;
},
className() {
return (this.left && this.line.left.discussions.length > 0) ||
(this.right && this.line.right.discussions.length > 0)
? ''
: 'js-temp-notes-holder';
},
shouldRender() {
const { line } = this;
const hasDiscussion =
(line.left && line.left.discussions && line.left.discussions.length) ||
(line.right && line.right.discussions && line.right.discussions.length);
if (
hasDiscussion &&
(this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight)
) {
return true;
}
const hasCommentFormOnLeft = line.left && line.left.hasForm;
const hasCommentFormOnRight = line.right && line.right.hasForm;
return hasCommentFormOnLeft || hasCommentFormOnRight;
},
shouldRenderReplyPlaceholderOnLeft() {
return Boolean(
this.line.left && this.line.left.discussions && this.line.left.discussions.length,
);
},
shouldRenderReplyPlaceholderOnRight() {
return Boolean(
this.line.right && this.line.right.discussions && this.line.right.discussions.length,
);
},
},
methods: {
...mapActions('diffs', ['showCommentForm']),
showNewDiscussionForm(lineCode) {
this.showCommentForm({ lineCode, fileHash: this.diffFileHash });
},
},
};
</script>
<template>
<tr v-if="shouldRender" :class="className" class="notes_holder">
<td class="notes-content parallel old" colspan="3">
<div v-if="shouldRenderDiscussionsOnLeft" class="content">
<diff-discussions
:discussions="line.left.discussions"
:line="line.left"
:help-page-path="helpPagePath"
/>
</div>
<diff-discussion-reply
v-if="!hasDraftLeft"
:has-form="showLeftSideCommentForm"
:render-reply-placeholder="shouldRenderReplyPlaceholderOnLeft"
@showNewDiscussionForm="showNewDiscussionForm(line.left.line_code)"
>
<template #form>
<diff-line-note-form
:diff-file-hash="diffFileHash"
:line="line.left"
:note-target-line="line.left"
:help-page-path="helpPagePath"
line-position="left"
/>
</template>
</diff-discussion-reply>
</td>
<td class="notes-content parallel new" colspan="3">
<div v-if="shouldRenderDiscussionsOnRight" class="content">
<diff-discussions
:discussions="line.right.discussions"
:line="line.right"
:help-page-path="helpPagePath"
/>
</div>
<diff-discussion-reply
v-if="!hasDraftRight"
:has-form="showRightSideCommentForm"
:render-reply-placeholder="shouldRenderReplyPlaceholderOnRight"
@showNewDiscussionForm="showNewDiscussionForm(line.right.line_code)"
>
<template #form>
<diff-line-note-form
:diff-file-hash="diffFileHash"
:line="line.right"
:note-target-line="line.right"
line-position="right"
/>
</template>
</diff-discussion-reply>
</td>
</tr>
</template>
<script>
import { MATCH_LINE_TYPE } from '../constants';
import DiffExpansionCell from './diff_expansion_cell.vue';
export default {
components: {
DiffExpansionCell,
},
props: {
fileHash: {
type: String,
required: true,
},
contextLinesPath: {
type: String,
required: true,
},
line: {
type: Object,
required: true,
},
isTop: {
type: Boolean,
required: false,
default: false,
},
isBottom: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
isMatchLineLeft() {
return this.line.left && this.line.left.type === MATCH_LINE_TYPE;
},
isMatchLineRight() {
return this.line.right && this.line.right.type === MATCH_LINE_TYPE;
},
},
};
</script>
<template>
<tr class="line_expansion match">
<template v-if="isMatchLineLeft || isMatchLineRight">
<diff-expansion-cell
:file-hash="fileHash"
:context-lines-path="contextLinesPath"
:line="line.left"
:is-top="isTop"
:is-bottom="isBottom"
:colspan="6"
/>
</template>
</tr>
</template>
...@@ -55,27 +55,15 @@ export default { ...@@ -55,27 +55,15 @@ export default {
return utils.isHighlighted(state, line, this.isCommented); return utils.isHighlighted(state, line, this.isCommented);
}, },
}), }),
isContextLineLeft() {
return utils.isContextLine(this.line.left?.type);
},
isContextLineRight() {
return utils.isContextLine(this.line.right?.type);
},
classNameMap() { classNameMap() {
return { return {
[CONTEXT_LINE_CLASS_NAME]: this.isContextLineLeft, [CONTEXT_LINE_CLASS_NAME]: this.line.isContextLineLeft,
[PARALLEL_DIFF_VIEW_TYPE]: true, [PARALLEL_DIFF_VIEW_TYPE]: true,
}; };
}, },
parallelViewLeftLineType() { parallelViewLeftLineType() {
return utils.parallelViewLeftLineType(this.line, this.isHighlighted); return utils.parallelViewLeftLineType(this.line, this.isHighlighted);
}, },
isMatchLineLeft() {
return utils.isMatchLine(this.line.left?.type);
},
isMatchLineRight() {
return utils.isMatchLine(this.line.right?.type);
},
coverageState() { coverageState() {
return this.fileLineCoverage(this.filePath, this.line.right.new_line); return this.fileLineCoverage(this.filePath, this.line.right.new_line);
}, },
...@@ -107,40 +95,19 @@ export default { ...@@ -107,40 +95,19 @@ export default {
shouldShowCommentButtonLeft() { shouldShowCommentButtonLeft() {
return utils.shouldShowCommentButton( return utils.shouldShowCommentButton(
this.isLeftHover, this.isLeftHover,
this.isContextLineLeft, this.line.isContextLineLeft,
this.isMetaLineLeft, this.line.isMetaLineLeft,
this.hasDiscussionsLeft, this.line.hasDiscussionsLeft,
); );
}, },
shouldShowCommentButtonRight() { shouldShowCommentButtonRight() {
return utils.shouldShowCommentButton( return utils.shouldShowCommentButton(
this.isRightHover, this.isRightHover,
this.isContextLineRight, this.line.isContextLineRight,
this.isMetaLineRight, this.line.isMetaLineRight,
this.hasDiscussionsRight, this.line.hasDiscussionsRight,
); );
}, },
hasDiscussionsLeft() {
return utils.hasDiscussions(this.line.left);
},
hasDiscussionsRight() {
return utils.hasDiscussions(this.line.right);
},
lineHrefOld() {
return utils.lineHref(this.line.left);
},
lineHrefNew() {
return utils.lineHref(this.line.right);
},
lineCode() {
return utils.lineCode(this.line);
},
isMetaLineLeft() {
return utils.isMetaLine(this.line.left?.type);
},
isMetaLineRight() {
return utils.isMetaLine(this.line.right?.type);
},
}, },
mounted() { mounted() {
this.scrollToLineIfNeededParallel(this.line); this.scrollToLineIfNeededParallel(this.line);
...@@ -203,7 +170,7 @@ export default { ...@@ -203,7 +170,7 @@ export default {
@mouseover="handleMouseMove" @mouseover="handleMouseMove"
@mouseout="handleMouseMove" @mouseout="handleMouseMove"
> >
<template v-if="line.left && !isMatchLineLeft"> <template v-if="line.left && !line.isMatchLineLeft">
<td ref="oldTd" :class="classNameMapCellLeft" class="diff-line-num old_line"> <td ref="oldTd" :class="classNameMapCellLeft" class="diff-line-num old_line">
<span <span
v-if="shouldRenderCommentButton" v-if="shouldRenderCommentButton"
...@@ -227,12 +194,12 @@ export default { ...@@ -227,12 +194,12 @@ export default {
v-if="line.left.old_line" v-if="line.left.old_line"
ref="lineNumberRefOld" ref="lineNumberRefOld"
:data-linenumber="line.left.old_line" :data-linenumber="line.left.old_line"
:href="lineHrefOld" :href="line.lineHrefOld"
@click="setHighlightedRow(lineCode)" @click="setHighlightedRow(line.lineCode)"
> >
</a> </a>
<diff-gutter-avatars <diff-gutter-avatars
v-if="hasDiscussionsLeft" v-if="line.hasDiscussionsLeft"
:discussions="line.left.discussions" :discussions="line.left.discussions"
:discussions-expanded="line.left.discussionsExpanded" :discussions-expanded="line.left.discussionsExpanded"
@toggleLineDiscussions=" @toggleLineDiscussions="
...@@ -259,7 +226,7 @@ export default { ...@@ -259,7 +226,7 @@ export default {
<td class="line-coverage left-side empty-cell"></td> <td class="line-coverage left-side empty-cell"></td>
<td class="line_content with-coverage parallel left-side empty-cell"></td> <td class="line_content with-coverage parallel left-side empty-cell"></td>
</template> </template>
<template v-if="line.right && !isMatchLineRight"> <template v-if="line.right && !line.isMatchLineRight">
<td ref="newTd" :class="classNameMapCellRight" class="diff-line-num new_line"> <td ref="newTd" :class="classNameMapCellRight" class="diff-line-num new_line">
<span <span
v-if="shouldRenderCommentButton" v-if="shouldRenderCommentButton"
...@@ -283,12 +250,12 @@ export default { ...@@ -283,12 +250,12 @@ export default {
v-if="line.right.new_line" v-if="line.right.new_line"
ref="lineNumberRefNew" ref="lineNumberRefNew"
:data-linenumber="line.right.new_line" :data-linenumber="line.right.new_line"
:href="lineHrefNew" :href="line.lineHrefNew"
@click="setHighlightedRow(lineCode)" @click="setHighlightedRow(line.lineCode)"
> >
</a> </a>
<diff-gutter-avatars <diff-gutter-avatars
v-if="hasDiscussionsRight" v-if="line.hasDiscussionsRight"
:discussions="line.right.discussions" :discussions="line.right.discussions"
:discussions-expanded="line.right.discussionsExpanded" :discussions-expanded="line.right.discussionsExpanded"
@toggleLineDiscussions=" @toggleLineDiscussions="
......
<script> <script>
import { mapGetters, mapState } from 'vuex'; import { mapGetters, mapState } from 'vuex';
import draftCommentsMixin from '~/diffs/mixins/draft_comments'; import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import ParallelDraftCommentRow from '~/batch_comments/components/parallel_draft_comment_row.vue'; import DraftNote from '~/batch_comments/components/draft_note.vue';
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 DiffCommentCell from './diff_comment_cell.vue';
import parallelDiffExpansionRow from './parallel_diff_expansion_row.vue'; import DiffExpansionCell from './diff_expansion_cell.vue';
import { getCommentedLines } from '~/notes/components/multiline_comment_utils'; import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
export default { export default {
components: { components: {
parallelDiffExpansionRow, DiffExpansionCell,
parallelDiffTableRow, parallelDiffTableRow,
parallelDiffCommentRow, DiffCommentCell,
ParallelDraftCommentRow, DraftNote,
}, },
mixins: [draftCommentsMixin], mixins: [draftCommentsMixin],
props: { props: {
...@@ -66,14 +66,21 @@ export default { ...@@ -66,14 +66,21 @@ export default {
</colgroup> </colgroup>
<tbody> <tbody>
<template v-for="(line, index) in diffLines"> <template v-for="(line, index) in diffLines">
<parallel-diff-expansion-row <tr
v-if="line.isMatchLineLeft || line.isMatchLineRight"
:key="`expand-${index}`" :key="`expand-${index}`"
:file-hash="diffFile.file_hash" class="line_expansion match"
:context-lines-path="diffFile.context_lines_path" >
:line="line" <td colspan="6" class="text-center gl-font-regular">
:is-top="index === 0" <diff-expansion-cell
:is-bottom="index + 1 === diffLinesLength" :file-hash="diffFile.file_hash"
/> :context-lines-path="diffFile.context_lines_path"
:line="line.left"
:is-top="index === 0"
:is-bottom="index + 1 === diffLinesLength"
/>
</td>
</tr>
<parallel-diff-table-row <parallel-diff-table-row
:key="line.line_code" :key="line.line_code"
:file-hash="diffFile.file_hash" :file-hash="diffFile.file_hash"
...@@ -82,21 +89,53 @@ export default { ...@@ -82,21 +89,53 @@ export default {
:is-bottom="index + 1 === diffLinesLength" :is-bottom="index + 1 === diffLinesLength"
:is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine" :is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine"
/> />
<parallel-diff-comment-row <tr
v-if="line.renderCommentRow"
:key="`dcr-${line.line_code || index}`" :key="`dcr-${line.line_code || index}`"
:line="line" :class="line.commentRowClasses"
:diff-file-hash="diffFile.file_hash" class="notes_holder"
:line-index="index" >
:help-page-path="helpPagePath" <td class="notes-content parallel old" colspan="3">
:has-draft-left="hasParallelDraftLeft(diffFile.file_hash, line) || false" <diff-comment-cell
:has-draft-right="hasParallelDraftRight(diffFile.file_hash, line) || false" v-if="line.left"
/> :line="line.left"
<parallel-draft-comment-row :diff-file-hash="diffFile.file_hash"
:help-page-path="helpPagePath"
:has-draft="line.left.hasDraft"
line-position="left"
/>
</td>
<td class="notes-content parallel new" colspan="3">
<diff-comment-cell
v-if="line.right"
:line="line.right"
:diff-file-hash="diffFile.file_hash"
:line-index="index"
:help-page-path="helpPagePath"
:has-draft="line.right.hasDraft"
line-position="right"
/>
</td>
</tr>
<tr
v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)" v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)"
:key="`drafts-${index}`" :key="`drafts-${index}`"
:line="line" :class="line.draftRowClasses"
:diff-file-content-sha="diffFile.file_hash" class="notes_holder"
/> >
<td class="notes_line old"></td>
<td class="notes-content parallel old" colspan="2">
<div v-if="line.left && line.left.lineDraft.isDraft" class="content">
<draft-note :draft="line.left.lineDraft" :line="line.left" />
</div>
</td>
<td class="notes_line new"></td>
<td class="notes-content parallel new" colspan="2">
<div v-if="line.right && line.right.lineDraft.isDraft" class="content">
<draft-note :draft="line.right.lineDraft" :line="line.right" />
</div>
</td>
</tr>
</template> </template>
</tbody> </tbody>
</table> </table>
......
...@@ -165,8 +165,8 @@ export const fileLineCoverage = state => (file, line) => { ...@@ -165,8 +165,8 @@ export const fileLineCoverage = state => (file, line) => {
export const currentDiffIndex = state => export const currentDiffIndex = state =>
Math.max(0, state.diffFiles.findIndex(diff => diff.file_hash === state.currentDiffFileId)); Math.max(0, state.diffFiles.findIndex(diff => diff.file_hash === state.currentDiffFileId));
export const diffLines = state => file => { export const diffLines = state => (file, unifiedDiffComponents) => {
if (state.diffViewType === INLINE_DIFF_VIEW_TYPE) { if (!unifiedDiffComponents && state.diffViewType === INLINE_DIFF_VIEW_TYPE) {
return null; return null;
} }
......
...@@ -449,6 +449,7 @@ ...@@ -449,6 +449,7 @@
} }
} }
.diff-table.code,
table.code { table.code {
width: 100%; width: 100%;
font-family: $monospace-font; font-family: $monospace-font;
...@@ -459,10 +460,12 @@ table.code { ...@@ -459,10 +460,12 @@ table.code {
table-layout: fixed; table-layout: fixed;
border-radius: 0 0 $border-radius-default $border-radius-default; border-radius: 0 0 $border-radius-default $border-radius-default;
.diff-tr:first-of-type.line_expansion > .diff-td,
tr:first-of-type.line_expansion > td { tr:first-of-type.line_expansion > td {
border-top: 0; border-top: 0;
} }
.diff-tr:nth-last-of-type(2).line_expansion > .diff-td,
tr:nth-last-of-type(2).line_expansion, tr:nth-last-of-type(2).line_expansion,
tr:last-of-type.line_expansion { tr:last-of-type.line_expansion {
> td { > td {
...@@ -470,6 +473,7 @@ table.code { ...@@ -470,6 +473,7 @@ table.code {
} }
} }
.diff-tr.line_holder .diff-td,
tr.line_holder td { tr.line_holder td {
line-height: $code-line-height; line-height: $code-line-height;
font-size: $code-font-size; font-size: $code-font-size;
...@@ -565,24 +569,95 @@ table.code { ...@@ -565,24 +569,95 @@ table.code {
} }
.line_holder:last-of-type { .line_holder:last-of-type {
.diff-td:first-child,
td:first-child { td:first-child {
border-bottom-left-radius: $border-radius-default; border-bottom-left-radius: $border-radius-default;
} }
} }
&.left-side-selected { &.left-side-selected {
.diff-td.line_content.parallel.right-side,
td.line_content.parallel.right-side { td.line_content.parallel.right-side {
user-select: none; user-select: none;
} }
} }
&.right-side-selected { &.right-side-selected {
.diff-td.line_content.parallel.left-side,
td.line_content.parallel.left-side { td.line_content.parallel.left-side {
user-select: none; user-select: none;
} }
} }
} }
// Merge request diff grid layout
.diff-grid {
.diff-grid-row {
display: grid;
grid-template-columns: 1fr 1fr;
}
.diff-grid-left,
.diff-grid-right {
display: grid;
grid-template-columns: 50px 8px 1fr;
.diff-td:nth-child(2) {
display: none;
}
}
.diff-grid-comments {
display: grid;
grid-template-columns: 1fr 1fr;
}
.diff-grid-drafts {
display: grid;
grid-template-columns: 1fr 1fr;
}
&.inline {
.diff-grid-comments {
display: grid;
grid-template-columns: 1fr;
}
.diff-grid-drafts {
display: grid;
grid-template-columns: 1fr;
}
.diff-grid-row {
grid-template-columns: 1fr;
}
.diff-grid-left,
.diff-grid-right {
grid-template-columns: 50px 50px 8px 1fr;
.diff-td:nth-child(2) {
display: block;
}
}
.diff-grid-left .old:nth-child(2) [data-linenumber],
.diff-grid-right .new:nth-child(2) [data-linenumber] {
display: inline;
}
.diff-grid-left .old:nth-child(3) [data-linenumber],
.diff-grid-right .new:nth-child(1) [data-linenumber] {
display: none;
}
}
}
// Merge request diff grid layout overrides
.diff-table.code .diff-tr.line_holder .diff-td.line_content.parallel {
width: unset;
}
.diff-stats { .diff-stats {
align-items: center; align-items: center;
padding: 0 1rem; padding: 0 1rem;
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
@mixin diff-expansion($background, $border, $link) { @mixin diff-expansion($background, $border, $link) {
background-color: $background; background-color: $background;
.diff-td,
td { td {
border-top: 1px solid $border; border-top: 1px solid $border;
border-bottom: 1px solid $border; border-bottom: 1px solid $border;
...@@ -41,3 +42,12 @@ ...@@ -41,3 +42,12 @@
border-left: 3px solid $no-coverage; border-left: 3px solid $no-coverage;
} }
} }
@mixin line-number-hover($color) {
background-color: $color;
border-color: darken($color, 5%);
a {
color: darken($color, 15%);
}
}
...@@ -125,6 +125,9 @@ $dark-il: #de935f; ...@@ -125,6 +125,9 @@ $dark-il: #de935f;
@include dark-diff-match-line; @include dark-diff-match-line;
} }
.diff-td.diff-line-num.hll:not(.empty-cell),
.diff-td.line-coverage.hll:not(.empty-cell),
.diff-td.line_content.hll:not(.empty-cell),
td.diff-line-num.hll:not(.empty-cell), td.diff-line-num.hll:not(.empty-cell),
td.line-coverage.hll:not(.empty-cell), td.line-coverage.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) { td.line_content.hll:not(.empty-cell) {
...@@ -158,15 +161,17 @@ $dark-il: #de935f; ...@@ -158,15 +161,17 @@ $dark-il: #de935f;
} }
} }
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell) {
@include line-number-hover($dark-over-bg);
}
}
.diff-line-num { .diff-line-num {
&.is-over, &.is-over,
&.hll:not(.empty-cell).is-over { &.hll:not(.empty-cell).is-over {
background-color: $dark-over-bg; @include line-number-hover($dark-over-bg);
border-color: darken($dark-over-bg, 5%);
a {
color: darken($dark-over-bg, 15%);
}
} }
} }
......
...@@ -125,6 +125,9 @@ $monokai-gi: #a6e22e; ...@@ -125,6 +125,9 @@ $monokai-gi: #a6e22e;
@include dark-diff-match-line; @include dark-diff-match-line;
} }
.diff-td.diff-line-num.hll:not(.empty-cell),
.diff-td.line-coverage.hll:not(.empty-cell),
.diff-td.line_content.hll:not(.empty-cell),
td.diff-line-num.hll:not(.empty-cell), td.diff-line-num.hll:not(.empty-cell),
td.line-coverage.hll:not(.empty-cell), td.line-coverage.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) { td.line_content.hll:not(.empty-cell) {
...@@ -158,15 +161,17 @@ $monokai-gi: #a6e22e; ...@@ -158,15 +161,17 @@ $monokai-gi: #a6e22e;
} }
} }
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell) {
@include line-number-hover($monokai-over-bg);
}
}
.diff-line-num { .diff-line-num {
&.is-over, &.is-over,
&.hll:not(.empty-cell).is-over { &.hll:not(.empty-cell).is-over {
background-color: $monokai-over-bg; @include line-number-hover($monokai-over-bg);
border-color: darken($monokai-over-bg, 5%);
a {
color: darken($monokai-over-bg, 15%);
}
} }
} }
......
...@@ -59,6 +59,13 @@ ...@@ -59,6 +59,13 @@
} }
} }
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell) {
@include line-number-hover($none-over-bg);
}
}
.diff-line-num { .diff-line-num {
&.old { &.old {
a { a {
...@@ -74,12 +81,7 @@ ...@@ -74,12 +81,7 @@
&.is-over, &.is-over,
&.hll:not(.empty-cell).is-over { &.hll:not(.empty-cell).is-over {
background-color: $none-over-bg; @include line-number-hover($none-over-bg);
border-color: darken($none-over-bg, 5%);
a {
color: darken($none-over-bg, 15%);
}
} }
&.hll:not(.empty-cell) { &.hll:not(.empty-cell) {
......
...@@ -129,6 +129,9 @@ $solarized-dark-il: #2aa198; ...@@ -129,6 +129,9 @@ $solarized-dark-il: #2aa198;
@include dark-diff-match-line; @include dark-diff-match-line;
} }
.diff-td.diff-line-num.hll:not(.empty-cell),
.diff-td.line-coverage.hll:not(.empty-cell),
.diff-td.line_content.hll:not(.empty-cell),
td.diff-line-num.hll:not(.empty-cell), td.diff-line-num.hll:not(.empty-cell),
td.line-coverage.hll:not(.empty-cell), td.line-coverage.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) { td.line_content.hll:not(.empty-cell) {
...@@ -140,6 +143,13 @@ $solarized-dark-il: #2aa198; ...@@ -140,6 +143,13 @@ $solarized-dark-il: #2aa198;
@include line-coverage-border-color($solarized-dark-coverage, $solarized-dark-no-coverage); @include line-coverage-border-color($solarized-dark-coverage, $solarized-dark-no-coverage);
} }
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell) {
@include line-number-hover($solarized-dark-over-bg);
}
}
.diff-line-num.new, .diff-line-num.new,
.line-coverage.new, .line-coverage.new,
.line_content.new { .line_content.new {
...@@ -165,12 +175,7 @@ $solarized-dark-il: #2aa198; ...@@ -165,12 +175,7 @@ $solarized-dark-il: #2aa198;
.diff-line-num { .diff-line-num {
&.is-over, &.is-over,
&.hll:not(.empty-cell).is-over { &.hll:not(.empty-cell).is-over {
background-color: $solarized-dark-over-bg; @include line-number-hover($solarized-dark-over-bg);
border-color: darken($solarized-dark-over-bg, 5%);
a {
color: darken($solarized-dark-over-bg, 15%);
}
} }
} }
......
...@@ -136,6 +136,9 @@ $solarized-light-il: #2aa198; ...@@ -136,6 +136,9 @@ $solarized-light-il: #2aa198;
@include match-line; @include match-line;
} }
.diff-td.diff-line-num.hll:not(.empty-cell),
.diff-td.line-coverage.hll:not(.empty-cell),
.diff-td.line_content.hll:not(.empty-cell),
td.diff-line-num.hll:not(.empty-cell), td.diff-line-num.hll:not(.empty-cell),
td.line-coverage.hll:not(.empty-cell), td.line-coverage.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) { td.line_content.hll:not(.empty-cell) {
...@@ -159,6 +162,13 @@ $solarized-light-il: #2aa198; ...@@ -159,6 +162,13 @@ $solarized-light-il: #2aa198;
} }
} }
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell) {
@include line-number-hover($solarized-light-over-bg);
}
}
.diff-line-num.old, .diff-line-num.old,
.line-coverage.old, .line-coverage.old,
.line_content.old { .line_content.old {
...@@ -173,12 +183,7 @@ $solarized-light-il: #2aa198; ...@@ -173,12 +183,7 @@ $solarized-light-il: #2aa198;
.diff-line-num { .diff-line-num {
&.is-over, &.is-over,
&.hll:not(.empty-cell).is-over { &.hll:not(.empty-cell).is-over {
background-color: $solarized-light-over-bg; @include line-number-hover($solarized-light-over-bg);
border-color: darken($solarized-light-over-bg, 5%);
a {
color: darken($solarized-light-over-bg, 15%);
}
} }
} }
......
...@@ -113,6 +113,13 @@ pre.code, ...@@ -113,6 +113,13 @@ pre.code,
@include match-line; @include match-line;
} }
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell) {
@include line-number-hover($white-over-bg);
}
}
.diff-line-num { .diff-line-num {
&.old { &.old {
background-color: $line-number-old; background-color: $line-number-old;
...@@ -134,12 +141,7 @@ pre.code, ...@@ -134,12 +141,7 @@ pre.code,
&.is-over, &.is-over,
&.hll:not(.empty-cell).is-over { &.hll:not(.empty-cell).is-over {
background-color: $white-over-bg; @include line-number-hover($white-over-bg);
border-color: darken($white-over-bg, 5%);
a {
color: darken($white-over-bg, 15%);
}
} }
&.hll:not(.empty-cell) { &.hll:not(.empty-cell) {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
* Note Form * Note Form
*/ */
.diff-file .diff-content { .diff-file .diff-content {
.diff-tr.line_holder:hover > .diff-td .line_note_link,
tr.line_holder:hover > td .line_note_link { tr.line_holder:hover > td .line_note_link {
opacity: 1; opacity: 1;
filter: alpha(opacity = 100); filter: alpha(opacity = 100);
......
...@@ -453,6 +453,8 @@ $note-form-margin-left: 72px; ...@@ -453,6 +453,8 @@ $note-form-margin-left: 72px;
} }
.diff-file { .diff-file {
.diff-grid-left:hover,
.diff-grid-right:hover,
.is-over { .is-over {
.add-diff-note { .add-diff-note {
display: inline-flex; display: inline-flex;
...@@ -490,6 +492,7 @@ $note-form-margin-left: 72px; ...@@ -490,6 +492,7 @@ $note-form-margin-left: 72px;
.notes_holder { .notes_holder {
font-family: $regular-font; font-family: $regular-font;
.diff-td,
td { td {
border: 1px solid $border-color; border: 1px solid $border-color;
border-left: 0; border-left: 0;
...@@ -805,6 +808,8 @@ $note-form-margin-left: 72px; ...@@ -805,6 +808,8 @@ $note-form-margin-left: 72px;
* Line note button on the side of diffs * Line note button on the side of diffs
*/ */
.diff-grid-left:hover,
.diff-grid-right:hover,
.line_holder .is-over:not(.no-comment-btn) { .line_holder .is-over:not(.no-comment-btn) {
.add-diff-note { .add-diff-note {
opacity: 1; opacity: 1;
......
...@@ -37,6 +37,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -37,6 +37,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, default_enabled: true) push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, default_enabled: true)
push_frontend_feature_flag(:merge_request_widget_graphql, @project) push_frontend_feature_flag(:merge_request_widget_graphql, @project)
push_frontend_feature_flag(:unified_diff_lines, @project, default_enabled: true) push_frontend_feature_flag(:unified_diff_lines, @project, default_enabled: true)
push_frontend_feature_flag(:unified_diff_components, @project)
push_frontend_feature_flag(:highlight_current_diff_row, @project) push_frontend_feature_flag(:highlight_current_diff_row, @project)
push_frontend_feature_flag(:default_merge_ref_for_diffs, @project) push_frontend_feature_flag(:default_merge_ref_for_diffs, @project)
push_frontend_feature_flag(:core_security_mr_widget, @project, default_enabled: true) push_frontend_feature_flag(:core_security_mr_widget, @project, default_enabled: true)
......
---
name: unified_diff_components
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44974
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/268039
type: development
group: group::source code
default_enabled: false
import { shallowMount } from '@vue/test-utils';
import DiffCommentCell from '~/diffs/components/diff_comment_cell.vue';
import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
import DiffDiscussionReply from '~/diffs/components/diff_discussion_reply.vue';
describe('DiffCommentCell', () => {
const createWrapper = (props = {}) => {
const { renderDiscussion, ...otherProps } = props;
const line = {
discussions: [],
renderDiscussion,
};
const diffFileHash = 'abc';
return shallowMount(DiffCommentCell, {
propsData: { line, diffFileHash, ...otherProps },
});
};
it('renders discussions if line has discussions', () => {
const wrapper = createWrapper({ renderDiscussion: true });
expect(wrapper.find(DiffDiscussions).exists()).toBe(true);
});
it('does not render discussions if line has no discussions', () => {
const wrapper = createWrapper();
expect(wrapper.find(DiffDiscussions).exists()).toBe(false);
});
it('renders discussion reply if line has no draft', () => {
const wrapper = createWrapper();
expect(wrapper.find(DiffDiscussionReply).exists()).toBe(true);
});
it('does not render discussion reply if line has draft', () => {
const wrapper = createWrapper({ hasDraft: true });
expect(wrapper.find(DiffDiscussionReply).exists()).toBe(false);
});
});
...@@ -12,6 +12,8 @@ import DiffDiscussions from '~/diffs/components/diff_discussions.vue'; ...@@ -12,6 +12,8 @@ import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants'; import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import diffFileMockData from '../mock_data/diff_file'; import diffFileMockData from '../mock_data/diff_file';
import { diffViewerModes } from '~/ide/constants'; import { diffViewerModes } from '~/ide/constants';
import { diffLines } from '~/diffs/store/getters';
import DiffView from '~/diffs/components/diff_view.vue';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
...@@ -33,7 +35,7 @@ describe('DiffContent', () => { ...@@ -33,7 +35,7 @@ describe('DiffContent', () => {
diffFile: JSON.parse(JSON.stringify(diffFileMockData)), diffFile: JSON.parse(JSON.stringify(diffFileMockData)),
}; };
const createComponent = ({ props, state } = {}) => { const createComponent = ({ props, state, provide } = {}) => {
const fakeStore = new Vuex.Store({ const fakeStore = new Vuex.Store({
getters: { getters: {
getNoteableData() { getNoteableData() {
...@@ -55,6 +57,10 @@ describe('DiffContent', () => { ...@@ -55,6 +57,10 @@ describe('DiffContent', () => {
namespaced: true, namespaced: true,
getters: { getters: {
draftsForFile: () => () => true, draftsForFile: () => () => true,
draftForLine: () => () => true,
shouldRenderDraftRow: () => () => true,
hasParallelDraftLeft: () => () => true,
hasParallelDraftRight: () => () => true,
}, },
}, },
diffs: { diffs: {
...@@ -68,6 +74,7 @@ describe('DiffContent', () => { ...@@ -68,6 +74,7 @@ describe('DiffContent', () => {
isInlineView: isInlineViewGetterMock, isInlineView: isInlineViewGetterMock,
isParallelView: isParallelViewGetterMock, isParallelView: isParallelViewGetterMock,
getCommentFormForDiffFile: getCommentFormForDiffFileGetterMock, getCommentFormForDiffFile: getCommentFormForDiffFileGetterMock,
diffLines,
}, },
actions: { actions: {
saveDiffDiscussion: saveDiffDiscussionMock, saveDiffDiscussion: saveDiffDiscussionMock,
...@@ -77,6 +84,8 @@ describe('DiffContent', () => { ...@@ -77,6 +84,8 @@ describe('DiffContent', () => {
}, },
}); });
const glFeatures = provide ? { ...provide.glFeatures } : {};
wrapper = shallowMount(DiffContentComponent, { wrapper = shallowMount(DiffContentComponent, {
propsData: { propsData: {
...defaultProps, ...defaultProps,
...@@ -84,6 +93,7 @@ describe('DiffContent', () => { ...@@ -84,6 +93,7 @@ describe('DiffContent', () => {
}, },
localVue, localVue,
store: fakeStore, store: fakeStore,
provide: { glFeatures },
}); });
}; };
...@@ -112,6 +122,16 @@ describe('DiffContent', () => { ...@@ -112,6 +122,16 @@ describe('DiffContent', () => {
expect(wrapper.find(ParallelDiffView).exists()).toBe(true); expect(wrapper.find(ParallelDiffView).exists()).toBe(true);
}); });
it('should render diff view if `unifiedDiffLines` & `unifiedDiffComponents` are true', () => {
isParallelViewGetterMock.mockReturnValue(true);
createComponent({
props: { diffFile: textDiffFile },
provide: { glFeatures: { unifiedDiffLines: true, unifiedDiffComponents: true } },
});
expect(wrapper.find(DiffView).exists()).toBe(true);
});
it('renders rendering more lines loading icon', () => { it('renders rendering more lines loading icon', () => {
createComponent({ props: { diffFile: { ...textDiffFile, renderingLines: true } } }); createComponent({ props: { diffFile: { ...textDiffFile, renderingLines: true } } });
......
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import diffsModule from '~/diffs/store/modules';
import DiffRow from '~/diffs/components/diff_row.vue';
describe('DiffRow', () => {
const testLines = [
{
left: { old_line: 1, discussions: [] },
right: { new_line: 1, discussions: [] },
hasDiscussionsLeft: true,
hasDiscussionsRight: true,
},
{
left: {},
right: {},
isMatchLineLeft: true,
isMatchLineRight: true,
},
{},
{
left: { old_line: 1, discussions: [] },
right: { new_line: 1, discussions: [] },
},
];
const createWrapper = ({ props, state, isLoggedIn = true }) => {
const localVue = createLocalVue();
localVue.use(Vuex);
const diffs = diffsModule();
diffs.state = { ...diffs.state, ...state };
const getters = { isLoggedIn: () => isLoggedIn };
const store = new Vuex.Store({
modules: { diffs },
getters,
});
const propsData = {
fileHash: 'abc',
filePath: 'abc',
line: {},
...props,
};
return shallowMount(DiffRow, { propsData, localVue, store });
};
it('isHighlighted returns true if isCommented is true', () => {
const props = { isCommented: true };
const wrapper = createWrapper({ props });
expect(wrapper.vm.isHighlighted).toBe(true);
});
it('isHighlighted returns true given line.left', () => {
const props = {
line: {
left: {
line_code: 'abc',
},
},
};
const state = { highlightedRow: 'abc' };
const wrapper = createWrapper({ props, state });
expect(wrapper.vm.isHighlighted).toBe(true);
});
it('isHighlighted returns true given line.right', () => {
const props = {
line: {
right: {
line_code: 'abc',
},
},
};
const state = { highlightedRow: 'abc' };
const wrapper = createWrapper({ props, state });
expect(wrapper.vm.isHighlighted).toBe(true);
});
it('isHighlighted returns false given line.left', () => {
const props = {
line: {
left: {
line_code: 'abc',
},
},
};
const wrapper = createWrapper({ props });
expect(wrapper.vm.isHighlighted).toBe(false);
});
describe.each`
side
${'left'}
${'right'}
`('$side side', ({ side }) => {
it(`renders empty cells if ${side} is unavailable`, () => {
const wrapper = createWrapper({ props: { line: testLines[2] } });
expect(wrapper.find(`[data-testid="${side}LineNumber"]`).exists()).toBe(false);
expect(wrapper.find(`[data-testid="${side}EmptyCell"]`).exists()).toBe(true);
});
it('renders comment button', () => {
const wrapper = createWrapper({ props: { line: testLines[3] } });
expect(wrapper.find(`[data-testid="${side}CommentButton"]`).exists()).toBe(true);
});
it('renders avatars', () => {
const wrapper = createWrapper({ props: { line: testLines[0] } });
expect(wrapper.find(`[data-testid="${side}Discussions"]`).exists()).toBe(true);
});
});
it('renders left line numbers', () => {
const wrapper = createWrapper({ props: { line: testLines[0] } });
const lineNumber = testLines[0].left.old_line;
expect(wrapper.find(`[data-linenumber="${lineNumber}"]`).exists()).toBe(true);
});
it('renders right line numbers', () => {
const wrapper = createWrapper({ props: { line: testLines[0] } });
const lineNumber = testLines[0].right.new_line;
expect(wrapper.find(`[data-linenumber="${lineNumber}"]`).exists()).toBe(true);
});
});
...@@ -201,3 +201,76 @@ describe('shouldShowCommentButton', () => { ...@@ -201,3 +201,76 @@ describe('shouldShowCommentButton', () => {
}, },
); );
}); });
describe('mapParallel', () => {
it('should assign computed properties to the line object', () => {
const side = {
discussions: [{}],
discussionsExpanded: true,
hasForm: true,
};
const content = {
diffFile: {},
hasParallelDraftLeft: () => false,
hasParallelDraftRight: () => false,
draftForLine: () => ({}),
};
const line = { left: side, right: side };
const expectation = {
commentRowClasses: '',
draftRowClasses: 'js-temp-notes-holder',
hasDiscussionsLeft: true,
hasDiscussionsRight: true,
isContextLineLeft: false,
isContextLineRight: false,
isMatchLineLeft: false,
isMatchLineRight: false,
isMetaLineLeft: false,
isMetaLineRight: false,
};
const leftExpectation = {
renderDiscussion: true,
hasDraft: false,
lineDraft: {},
hasCommentForm: true,
};
const rightExpectation = {
renderDiscussion: false,
hasDraft: false,
lineDraft: {},
hasCommentForm: false,
};
const mapped = utils.mapParallel(content)(line);
expect(mapped).toMatchObject(expectation);
expect(mapped.left).toMatchObject(leftExpectation);
expect(mapped.right).toMatchObject(rightExpectation);
});
});
describe('mapInline', () => {
it('should assign computed properties to the line object', () => {
const content = {
diffFile: {},
shouldRenderDraftRow: () => false,
};
const line = {
discussions: [{}],
discussionsExpanded: true,
hasForm: true,
};
const expectation = {
commentRowClasses: '',
hasDiscussions: true,
isContextLine: false,
isMatchLine: false,
isMetaLine: false,
renderDiscussion: true,
hasDraft: false,
hasCommentForm: true,
};
const mapped = utils.mapInline(content)(line);
expect(mapped).toMatchObject(expectation);
});
});
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import DiffView from '~/diffs/components/diff_view.vue';
// import DraftNote from '~/batch_comments/components/draft_note.vue';
// import DiffRow from '~/diffs/components/diff_row.vue';
// import DiffCommentCell from '~/diffs/components/diff_comment_cell.vue';
// import DiffExpansionCell from '~/diffs/components/diff_expansion_cell.vue';
describe('DiffView', () => {
const DiffExpansionCell = { template: `<div/>` };
const DiffRow = { template: `<div/>` };
const DiffCommentCell = { template: `<div/>` };
const DraftNote = { template: `<div/>` };
const createWrapper = props => {
const localVue = createLocalVue();
localVue.use(Vuex);
const batchComments = {
getters: {
shouldRenderDraftRow: () => false,
shouldRenderParallelDraftRow: () => () => true,
draftForLine: () => false,
draftsForFile: () => false,
hasParallelDraftLeft: () => false,
hasParallelDraftRight: () => false,
},
namespaced: true,
};
const diffs = { getters: { commitId: () => 'abc123' }, namespaced: true };
const notes = {
state: { selectedCommentPosition: null, selectedCommentPositionHover: null },
};
const store = new Vuex.Store({
modules: { diffs, notes, batchComments },
});
const propsData = {
diffFile: {},
diffLines: [],
...props,
};
const stubs = { DiffExpansionCell, DiffRow, DiffCommentCell, DraftNote };
return shallowMount(DiffView, { propsData, store, localVue, stubs });
};
it('renders a match line', () => {
const wrapper = createWrapper({ diffLines: [{ isMatchLineLeft: true }] });
expect(wrapper.find(DiffExpansionCell).exists()).toBe(true);
});
it('renders a comment row', () => {
const wrapper = createWrapper({
diffLines: [{ renderCommentRow: true, left: { lineDraft: {} } }],
});
expect(wrapper.find(DiffCommentCell).exists()).toBe(true);
});
it('renders a draft row', () => {
const wrapper = createWrapper({
diffLines: [{ renderCommentRow: true, left: { lineDraft: { isDraft: true } } }],
});
expect(wrapper.find(DraftNote).exists()).toBe(true);
});
});
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/mr_notes/stores';
import InlineDiffExpansionRow from '~/diffs/components/inline_diff_expansion_row.vue';
import diffFileMockData from '../mock_data/diff_file';
describe('InlineDiffExpansionRow', () => {
const mockData = { ...diffFileMockData };
const matchLine = mockData.highlighted_diff_lines.pop();
const createComponent = (options = {}) => {
const cmp = Vue.extend(InlineDiffExpansionRow);
const defaults = {
fileHash: mockData.file_hash,
contextLinesPath: 'contextLinesPath',
line: matchLine,
isTop: false,
isBottom: false,
};
const props = { ...defaults, ...options };
return createComponentWithStore(cmp, createStore(), props).$mount();
};
describe('template', () => {
it('should render expansion row for match lines', () => {
const vm = createComponent();
expect(vm.$el.classList.contains('line_expansion')).toBe(true);
});
});
});
...@@ -4,6 +4,7 @@ import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue'; ...@@ -4,6 +4,7 @@ import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue'; import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
import diffFileMockData from '../mock_data/diff_file'; import diffFileMockData from '../mock_data/diff_file';
import discussionsMockData from '../mock_data/diff_discussions'; import discussionsMockData from '../mock_data/diff_discussions';
import { mapInline } from '~/diffs/components/diff_row_utils';
const TEST_USER_ID = 'abc123'; const TEST_USER_ID = 'abc123';
const TEST_USER = { id: TEST_USER_ID }; const TEST_USER = { id: TEST_USER_ID };
...@@ -11,7 +12,16 @@ const TEST_USER = { id: TEST_USER_ID }; ...@@ -11,7 +12,16 @@ const TEST_USER = { id: TEST_USER_ID };
describe('InlineDiffTableRow', () => { describe('InlineDiffTableRow', () => {
let wrapper; let wrapper;
let store; let store;
const thisLine = diffFileMockData.highlighted_diff_lines[0]; const mockDiffContent = {
diffFile: diffFileMockData,
shouldRenderDraftRow: jest.fn(),
hasParallelDraftLeft: jest.fn(),
hasParallelDraftRight: jest.fn(),
draftForLine: jest.fn(),
};
const applyMap = mapInline(mockDiffContent);
const thisLine = applyMap(diffFileMockData.highlighted_diff_lines[0]);
const createComponent = (props = {}, propsStore = store) => { const createComponent = (props = {}, propsStore = store) => {
wrapper = shallowMount(InlineDiffTableRow, { wrapper = shallowMount(InlineDiffTableRow, {
...@@ -132,7 +142,7 @@ describe('InlineDiffTableRow', () => { ...@@ -132,7 +142,7 @@ describe('InlineDiffTableRow', () => {
${true} | ${{ ...thisLine, type: 'old-nonewline', discussions: [] }} | ${false} ${true} | ${{ ...thisLine, type: 'old-nonewline', discussions: [] }} | ${false}
${true} | ${{ ...thisLine, discussions: [{}] }} | ${false} ${true} | ${{ ...thisLine, discussions: [{}] }} | ${false}
`('visible is $expectation - line ($line)', ({ isHover, line, expectation }) => { `('visible is $expectation - line ($line)', ({ isHover, line, expectation }) => {
createComponent({ line }); createComponent({ line: applyMap(line) });
wrapper.setData({ isHover }); wrapper.setData({ isHover });
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
...@@ -148,7 +158,7 @@ describe('InlineDiffTableRow', () => { ...@@ -148,7 +158,7 @@ describe('InlineDiffTableRow', () => {
'has attribute disabled=$disabled when the outer component has prop commentsDisabled=$commentsDisabled', 'has attribute disabled=$disabled when the outer component has prop commentsDisabled=$commentsDisabled',
({ disabled, commentsDisabled }) => { ({ disabled, commentsDisabled }) => {
createComponent({ createComponent({
line: { ...thisLine, commentsDisabled }, line: applyMap({ ...thisLine, commentsDisabled }),
}); });
wrapper.setData({ isHover: true }); wrapper.setData({ isHover: true });
...@@ -177,7 +187,7 @@ describe('InlineDiffTableRow', () => { ...@@ -177,7 +187,7 @@ describe('InlineDiffTableRow', () => {
'has the correct tooltip when commentsDisabled=$commentsDisabled', 'has the correct tooltip when commentsDisabled=$commentsDisabled',
({ tooltip, commentsDisabled }) => { ({ tooltip, commentsDisabled }) => {
createComponent({ createComponent({
line: { ...thisLine, commentsDisabled }, line: applyMap({ ...thisLine, commentsDisabled }),
}); });
wrapper.setData({ isHover: true }); wrapper.setData({ isHover: true });
...@@ -216,7 +226,7 @@ describe('InlineDiffTableRow', () => { ...@@ -216,7 +226,7 @@ describe('InlineDiffTableRow', () => {
beforeEach(() => { beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation(); jest.spyOn(store, 'dispatch').mockImplementation();
createComponent({ createComponent({
line: { ...thisLine, ...lineProps }, line: applyMap({ ...thisLine, ...lineProps }),
}); });
}); });
...@@ -268,7 +278,7 @@ describe('InlineDiffTableRow', () => { ...@@ -268,7 +278,7 @@ describe('InlineDiffTableRow', () => {
describe('with showCommentButton', () => { describe('with showCommentButton', () => {
it('renders if line has discussions', () => { it('renders if line has discussions', () => {
createComponent({ line }); createComponent({ line: applyMap(line) });
expect(findAvatars().props()).toEqual({ expect(findAvatars().props()).toEqual({
discussions: line.discussions, discussions: line.discussions,
...@@ -278,13 +288,13 @@ describe('InlineDiffTableRow', () => { ...@@ -278,13 +288,13 @@ describe('InlineDiffTableRow', () => {
it('does notrender if line has no discussions', () => { it('does notrender if line has no discussions', () => {
line.discussions = []; line.discussions = [];
createComponent({ line }); createComponent({ line: applyMap(line) });
expect(findAvatars().exists()).toEqual(false); expect(findAvatars().exists()).toEqual(false);
}); });
it('toggles line discussion', () => { it('toggles line discussion', () => {
createComponent({ line }); createComponent({ line: applyMap(line) });
expect(store.dispatch).toHaveBeenCalledTimes(1); expect(store.dispatch).toHaveBeenCalledTimes(1);
......
import Vue from 'vue';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { mount } from '@vue/test-utils';
import { getByText } from '@testing-library/dom';
import { createStore } from '~/mr_notes/stores'; import { createStore } from '~/mr_notes/stores';
import InlineDiffView from '~/diffs/components/inline_diff_view.vue'; import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
import { mapInline } from '~/diffs/components/diff_row_utils';
import diffFileMockData from '../mock_data/diff_file'; import diffFileMockData from '../mock_data/diff_file';
import discussionsMockData from '../mock_data/diff_discussions'; import discussionsMockData from '../mock_data/diff_discussions';
describe('InlineDiffView', () => { describe('InlineDiffView', () => {
let component; let wrapper;
const getDiffFileMock = () => ({ ...diffFileMockData }); const getDiffFileMock = () => ({ ...diffFileMockData });
const getDiscussionsMockData = () => [{ ...discussionsMockData }]; const getDiscussionsMockData = () => [{ ...discussionsMockData }];
const notesLength = getDiscussionsMockData()[0].notes.length; const notesLength = getDiscussionsMockData()[0].notes.length;
beforeEach(done => { const setup = (diffFile, diffLines) => {
const diffFile = getDiffFileMock(); const mockDiffContent = {
diffFile,
shouldRenderDraftRow: jest.fn(),
};
const store = createStore(); const store = createStore();
store.dispatch('diffs/setInlineDiffViewType'); store.dispatch('diffs/setInlineDiffViewType');
component = createComponentWithStore(Vue.extend(InlineDiffView), store, { wrapper = mount(InlineDiffView, {
diffFile, store,
diffLines: diffFile.highlighted_diff_lines, propsData: {
}).$mount(); diffFile,
diffLines: diffLines.map(mapInline(mockDiffContent)),
Vue.nextTick(done); },
}); });
};
describe('template', () => { describe('template', () => {
it('should have rendered diff lines', () => { it('should have rendered diff lines', () => {
const el = component.$el; const diffFile = getDiffFileMock();
setup(diffFile, diffFile.highlighted_diff_lines);
expect(el.querySelectorAll('tr.line_holder').length).toEqual(8); expect(wrapper.findAll('tr.line_holder').length).toEqual(8);
expect(el.querySelectorAll('tr.line_holder.new').length).toEqual(4); expect(wrapper.findAll('tr.line_holder.new').length).toEqual(4);
expect(el.querySelectorAll('tr.line_expansion.match').length).toEqual(1); expect(wrapper.findAll('tr.line_expansion.match').length).toEqual(1);
expect(el.textContent.indexOf('Bad dates')).toBeGreaterThan(-1); getByText(wrapper.element, /Bad dates/i);
}); });
it('should render discussions', done => { it('should render discussions', () => {
const el = component.$el; const diffFile = getDiffFileMock();
component.diffLines[1].discussions = getDiscussionsMockData(); diffFile.highlighted_diff_lines[1].discussions = getDiscussionsMockData();
component.diffLines[1].discussionsExpanded = true; diffFile.highlighted_diff_lines[1].discussionsExpanded = true;
setup(diffFile, diffFile.highlighted_diff_lines);
Vue.nextTick(() => {
expect(el.querySelectorAll('.notes_holder').length).toEqual(1);
expect(el.querySelectorAll('.notes_holder .note').length).toEqual(notesLength + 1);
expect(el.innerText.indexOf('comment 5')).toBeGreaterThan(-1);
component.$store.dispatch('setInitialNotes', []);
done(); expect(wrapper.findAll('.notes_holder').length).toEqual(1);
}); expect(wrapper.findAll('.notes_holder .note').length).toEqual(notesLength + 1);
getByText(wrapper.element, 'comment 5');
wrapper.vm.$store.dispatch('setInitialNotes', []);
}); });
}); });
}); });
import Vue from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/mr_notes/stores';
import ParallelDiffExpansionRow from '~/diffs/components/parallel_diff_expansion_row.vue';
import diffFileMockData from '../mock_data/diff_file';
describe('ParallelDiffExpansionRow', () => {
const matchLine = diffFileMockData.highlighted_diff_lines[5];
const createComponent = (options = {}) => {
const cmp = Vue.extend(ParallelDiffExpansionRow);
const defaults = {
fileHash: diffFileMockData.file_hash,
contextLinesPath: 'contextLinesPath',
line: matchLine,
isTop: false,
isBottom: false,
};
const props = { ...defaults, ...options };
return createComponentWithStore(cmp, createStore(), props).$mount();
};
describe('template', () => {
it('should render expansion row for match lines', () => {
const vm = createComponent();
expect(vm.$el.classList.contains('line_expansion')).toBe(true);
});
});
});
...@@ -3,11 +3,22 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -3,11 +3,22 @@ import { shallowMount } from '@vue/test-utils';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/mr_notes/stores'; import { createStore } from '~/mr_notes/stores';
import ParallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue'; import ParallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue';
import { mapParallel } from '~/diffs/components/diff_row_utils';
import diffFileMockData from '../mock_data/diff_file'; import diffFileMockData from '../mock_data/diff_file';
import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue'; import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
import discussionsMockData from '../mock_data/diff_discussions'; import discussionsMockData from '../mock_data/diff_discussions';
describe('ParallelDiffTableRow', () => { describe('ParallelDiffTableRow', () => {
const mockDiffContent = {
diffFile: diffFileMockData,
shouldRenderDraftRow: jest.fn(),
hasParallelDraftLeft: jest.fn(),
hasParallelDraftRight: jest.fn(),
draftForLine: jest.fn(),
};
const applyMap = mapParallel(mockDiffContent);
describe('when one side is empty', () => { describe('when one side is empty', () => {
let wrapper; let wrapper;
let vm; let vm;
...@@ -18,7 +29,7 @@ describe('ParallelDiffTableRow', () => { ...@@ -18,7 +29,7 @@ describe('ParallelDiffTableRow', () => {
wrapper = shallowMount(ParallelDiffTableRow, { wrapper = shallowMount(ParallelDiffTableRow, {
store: createStore(), store: createStore(),
propsData: { propsData: {
line: thisLine, line: applyMap(thisLine),
fileHash: diffFileMockData.file_hash, fileHash: diffFileMockData.file_hash,
filePath: diffFileMockData.file_path, filePath: diffFileMockData.file_path,
contextLinesPath: 'contextLinesPath', contextLinesPath: 'contextLinesPath',
...@@ -67,7 +78,7 @@ describe('ParallelDiffTableRow', () => { ...@@ -67,7 +78,7 @@ describe('ParallelDiffTableRow', () => {
beforeEach(() => { beforeEach(() => {
vm = createComponentWithStore(Vue.extend(ParallelDiffTableRow), createStore(), { vm = createComponentWithStore(Vue.extend(ParallelDiffTableRow), createStore(), {
line: thisLine, line: applyMap(thisLine),
fileHash: diffFileMockData.file_hash, fileHash: diffFileMockData.file_hash,
filePath: diffFileMockData.file_path, filePath: diffFileMockData.file_path,
contextLinesPath: 'contextLinesPath', contextLinesPath: 'contextLinesPath',
...@@ -243,7 +254,10 @@ describe('ParallelDiffTableRow', () => { ...@@ -243,7 +254,10 @@ describe('ParallelDiffTableRow', () => {
${{ ...thisLine, left: { type: 'old-nonewline', discussions: [] } }} | ${false} ${{ ...thisLine, left: { type: 'old-nonewline', discussions: [] } }} | ${false}
${{ ...thisLine, left: { discussions: [{}] } }} | ${false} ${{ ...thisLine, left: { discussions: [{}] } }} | ${false}
`('visible is $expectation - line ($line)', async ({ line, expectation }) => { `('visible is $expectation - line ($line)', async ({ line, expectation }) => {
createComponent({ line }, store, { isLeftHover: true, isCommentButtonRendered: true }); createComponent({ line: applyMap(line) }, store, {
isLeftHover: true,
isCommentButtonRendered: true,
});
expect(findNoteButton().isVisible()).toBe(expectation); expect(findNoteButton().isVisible()).toBe(expectation);
}); });
...@@ -320,7 +334,7 @@ describe('ParallelDiffTableRow', () => { ...@@ -320,7 +334,7 @@ describe('ParallelDiffTableRow', () => {
Object.assign(thisLine.left, lineProps); Object.assign(thisLine.left, lineProps);
Object.assign(thisLine.right, lineProps); Object.assign(thisLine.right, lineProps);
createComponent({ createComponent({
line: { ...thisLine }, line: applyMap({ ...thisLine }),
}); });
}); });
...@@ -357,7 +371,7 @@ describe('ParallelDiffTableRow', () => { ...@@ -357,7 +371,7 @@ describe('ParallelDiffTableRow', () => {
beforeEach(() => { beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation(); jest.spyOn(store, 'dispatch').mockImplementation();
line = { line = applyMap({
left: { left: {
line_code: TEST_LINE_CODE, line_code: TEST_LINE_CODE,
type: 'new', type: 'new',
...@@ -369,7 +383,7 @@ describe('ParallelDiffTableRow', () => { ...@@ -369,7 +383,7 @@ describe('ParallelDiffTableRow', () => {
rich_text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', rich_text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
meta_data: null, meta_data: null,
}, },
}; });
}); });
describe('with showCommentButton', () => { describe('with showCommentButton', () => {
...@@ -384,7 +398,7 @@ describe('ParallelDiffTableRow', () => { ...@@ -384,7 +398,7 @@ describe('ParallelDiffTableRow', () => {
it('does notrender if line has no discussions', () => { it('does notrender if line has no discussions', () => {
line.left.discussions = []; line.left.discussions = [];
createComponent({ line }); createComponent({ line: applyMap(line) });
expect(findAvatars().exists()).toEqual(false); expect(findAvatars().exists()).toEqual(false);
}); });
......
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