Commit 43ad159c authored by Phil Hughes's avatar Phil Hughes

Merge branch 'jdb/refactor-diff-html-tables-take3' into 'master'

Jdb/refactor diff html tables to use css grid

See merge request gitlab-org/gitlab!44974
parents a5f66310 ab6b1633
<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 {
required: false,
default: false,
},
},
computed: {
className() {
return this.line.discussions.length ? '' : 'js-temp-notes-holder';
},
shouldRender() {
if (this.line.hasForm) return true;
if (!this.line.discussions || !this.line.discussions.length) {
return false;
}
return this.line.discussionsExpanded;
linePosition: {
type: String,
required: false,
default: '',
},
},
methods: {
......@@ -50,33 +42,28 @@ export default {
</script>
<template>
<tr v-if="shouldRender" :class="className" class="notes_holder">
<td class="notes-content" colspan="4">
<div class="content">
<diff-discussions
v-if="line.discussions.length"
<div class="content">
<diff-discussions
v-if="line.renderDiscussion"
:line="line"
: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"
:discussions="line.discussions"
:note-target-line="line"
:help-page-path="helpPagePath"
:line-position="linePosition"
/>
<diff-discussion-reply
v-if="!hasDraft"
:has-form="line.hasForm"
: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>
</diff-discussion-reply>
</div>
</template>
......@@ -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 InlineDiffView from './inline_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 NoteForm from '../../notes/components/note_form.vue';
import ImageDiffOverlay from './image_diff_overlay.vue';
......@@ -18,12 +19,14 @@ import eventHub from '../../notes/event_hub';
import { IMAGE_DIFF_POSITION_TYPE } from '../constants';
import { getDiffMode } from '../store/utils';
import { diffViewerModes } from '~/ide/constants';
import { mapInline, mapParallel } from './diff_row_utils';
export default {
components: {
GlLoadingIcon,
InlineDiffView,
ParallelDiffView,
DiffView,
DiffViewer,
NoteForm,
DiffDiscussions,
......@@ -83,6 +86,19 @@ export default {
author() {
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() {
this.$nextTick(() => {
......@@ -113,19 +129,28 @@ export default {
<template>
<div class="diff-content">
<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
v-if="isInlineView"
:diff-file="diffFile"
:diff-lines="diffFile.highlighted_diff_lines"
:diff-lines="mappedLines"
:help-page-path="helpPagePath"
/>
<parallel-diff-view
v-else-if="isParallelView"
:diff-file="diffFile"
:diff-lines="
glFeatures.unifiedDiffLines ? diffLines(diffFile) : diffFile.parallel_diff_lines || []
"
:diff-lines="mappedLines"
:help-page-path="helpPagePath"
/>
<gl-loading-icon v-if="diffFile.renderingLines" size="md" class="mt-3" />
......
......@@ -54,11 +54,6 @@ export default {
required: false,
default: false,
},
colspan: {
type: Number,
required: false,
default: 4,
},
},
computed: {
...mapState({
......@@ -231,28 +226,26 @@ export default {
</script>
<template>
<td :colspan="colspan" class="text-center gl-font-regular">
<div class="content js-line-expansion-content">
<a
v-if="canExpandDown"
class="gl-mx-2 gl-cursor-pointer js-unfold-down gl-display-inline-block gl-py-4"
@click="handleExpandLines(EXPAND_DOWN)"
>
<gl-icon :size="12" name="expand-down" aria-hidden="true" />
<span>{{ $options.i18n.showMore }}</span>
</a>
<a class="gl-mx-2 cursor-pointer js-unfold-all" @click="handleExpandLines()">
<gl-icon :size="12" name="expand" aria-hidden="true" />
<span>{{ $options.i18n.showAll }}</span>
</a>
<a
v-if="canExpandUp"
class="gl-mx-2 gl-cursor-pointer js-unfold gl-display-inline-block gl-py-4"
@click="handleExpandLines(EXPAND_UP)"
>
<gl-icon :size="12" name="expand-up" aria-hidden="true" />
<span>{{ $options.i18n.showMore }}</span>
</a>
</div>
</td>
<div class="content js-line-expansion-content">
<a
v-if="canExpandDown"
class="gl-mx-2 gl-cursor-pointer js-unfold-down gl-display-inline-block gl-py-4"
@click="handleExpandLines(EXPAND_DOWN)"
>
<gl-icon :size="12" name="expand-down" aria-hidden="true" />
<span>{{ $options.i18n.showMore }}</span>
</a>
<a class="gl-mx-2 cursor-pointer js-unfold-all" @click="handleExpandLines()">
<gl-icon :size="12" name="expand" aria-hidden="true" />
<span>{{ $options.i18n.showAll }}</span>
</a>
<a
v-if="canExpandUp"
class="gl-mx-2 gl-cursor-pointer js-unfold gl-display-inline-block gl-py-4"
@click="handleExpandLines(EXPAND_UP)"
>
<gl-icon :size="12" name="expand-up" aria-hidden="true" />
<span>{{ $options.i18n.showMore }}</span>
</a>
</div>
</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) => {
export const shouldShowCommentButton = (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';
import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { CONTEXT_LINE_CLASS_NAME } from '../constants';
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 {
components: {
......@@ -48,60 +54,42 @@ export default {
...mapGetters('diffs', ['fileLineCoverage']),
...mapState({
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() {
return [
this.line.type,
{
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
[CONTEXT_LINE_CLASS_NAME]: this.line.isContextLine,
},
];
},
inlineRowId() {
return this.line.line_code || `${this.fileHash}_${this.line.old_line}_${this.line.new_line}`;
},
isMatchLine() {
return utils.isMatchLine(this.line.type);
},
coverageState() {
return this.fileLineCoverage(this.filePath, this.line.new_line);
},
isMetaLine() {
return utils.isMetaLine(this.line.type);
},
classNameMapCell() {
return utils.classNameMapCell(this.line, this.isHighlighted, this.isLoggedIn, this.isHover);
return classNameMapCell(this.line, this.isHighlighted, this.isLoggedIn, this.isHover);
},
addCommentTooltip() {
return utils.addCommentTooltip(this.line);
return addCommentTooltip(this.line);
},
shouldRenderCommentButton() {
return utils.shouldRenderCommentButton(this.isLoggedIn, true);
return shouldRenderCommentButton(this.isLoggedIn, true);
},
shouldShowCommentButton() {
return utils.shouldShowCommentButton(
return shouldShowCommentButton(
this.isHover,
this.isContextLine,
this.isMetaLine,
this.hasDiscussions,
this.line.isContextLine,
this.line.isMetaLine,
this.line.hasDiscussions,
);
},
hasDiscussions() {
return utils.hasDiscussions(this.line);
},
lineHref() {
return utils.lineHref(this.line);
},
lineCode() {
return utils.lineCode(this.line);
},
shouldShowAvatarsOnGutter() {
return this.hasDiscussions;
return this.line.hasDiscussions;
},
},
mounted() {
......@@ -128,7 +116,6 @@ export default {
<template>
<tr
v-if="!isMatchLine"
:id="inlineRowId"
:class="classNameMap"
class="line_holder"
......@@ -158,8 +145,8 @@ export default {
v-if="line.old_line"
ref="lineNumberRefOld"
:data-linenumber="line.old_line"
:href="lineHref"
@click="setHighlightedRow(lineCode)"
:href="line.lineHref"
@click="setHighlightedRow(line.lineCode)"
>
</a>
<diff-gutter-avatars
......@@ -167,7 +154,11 @@ export default {
:discussions="line.discussions"
:discussions-expanded="line.discussionsExpanded"
@toggleLineDiscussions="
toggleLineDiscussions({ lineCode, fileHash, expanded: !line.discussionsExpanded })
toggleLineDiscussions({
lineCode: line.lineCode,
fileHash,
expanded: !line.discussionsExpanded,
})
"
/>
</td>
......@@ -176,8 +167,8 @@ export default {
v-if="line.new_line"
ref="lineNumberRefNew"
:data-linenumber="line.new_line"
:href="lineHref"
@click="setHighlightedRow(lineCode)"
:href="line.lineHref"
@click="setHighlightedRow(line.lineCode)"
>
</a>
</td>
......
......@@ -2,18 +2,18 @@
import { mapGetters, mapState } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
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 inlineDiffCommentRow from './inline_diff_comment_row.vue';
import inlineDiffExpansionRow from './inline_diff_expansion_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: {
inlineDiffCommentRow,
DiffCommentCell,
inlineDiffTableRow,
InlineDraftCommentRow,
inlineDiffExpansionRow,
DraftNote,
DiffExpansionCell,
},
mixins: [draftCommentsMixin, glFeatureFlagsMixin()],
props: {
......@@ -65,15 +65,19 @@ export default {
</colgroup>
<tbody>
<template v-for="(line, index) in diffLines">
<inline-diff-expansion-row
:key="`expand-${index}`"
:file-hash="diffFile.file_hash"
:context-lines-path="diffFile.context_lines_path"
:line="line"
:is-top="index === 0"
:is-bottom="index + 1 === diffLinesLength"
/>
<tr v-if="line.isMatchLine" :key="`expand-${index}`" class="line_expansion match">
<td colspan="4" class="text-center gl-font-regular">
<diff-expansion-cell
:file-hash="diffFile.file_hash"
:context-lines-path="diffFile.context_lines_path"
:line="line"
:is-top="index === 0"
:is-bottom="index + 1 === diffLinesLength"
/>
</td>
</tr>
<inline-diff-table-row
v-if="!line.isMatchLine"
:key="`${line.line_code || index}`"
:file-hash="diffFile.file_hash"
:file-path="diffFile.file_path"
......@@ -81,20 +85,32 @@ export default {
:is-bottom="index + 1 === diffLinesLength"
:is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine"
/>
<inline-diff-comment-row
<tr
v-if="line.renderCommentRow"
:key="`icr-${line.line_code || index}`"
:diff-file-hash="diffFile.file_hash"
:line="line"
:help-page-path="helpPagePath"
:has-draft="shouldRenderDraftRow(diffFile.file_hash, line) || false"
/>
<inline-draft-comment-row
v-if="shouldRenderDraftRow(diffFile.file_hash, line)"
:key="`draft_${index}`"
:draft="draftForLine(diffFile.file_hash, line)"
:diff-file="diffFile"
:line="line"
/>
:class="line.commentRowClasses"
class="notes_holder"
>
<td class="notes-content" colspan="4">
<diff-comment-cell
:diff-file-hash="diffFile.file_hash"
:line="line"
:help-page-path="helpPagePath"
:has-draft="line.hasDraft"
/>
</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>
</tbody>
</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 {
return utils.isHighlighted(state, line, this.isCommented);
},
}),
isContextLineLeft() {
return utils.isContextLine(this.line.left?.type);
},
isContextLineRight() {
return utils.isContextLine(this.line.right?.type);
},
classNameMap() {
return {
[CONTEXT_LINE_CLASS_NAME]: this.isContextLineLeft,
[CONTEXT_LINE_CLASS_NAME]: this.line.isContextLineLeft,
[PARALLEL_DIFF_VIEW_TYPE]: true,
};
},
parallelViewLeftLineType() {
return utils.parallelViewLeftLineType(this.line, this.isHighlighted);
},
isMatchLineLeft() {
return utils.isMatchLine(this.line.left?.type);
},
isMatchLineRight() {
return utils.isMatchLine(this.line.right?.type);
},
coverageState() {
return this.fileLineCoverage(this.filePath, this.line.right.new_line);
},
......@@ -107,40 +95,19 @@ export default {
shouldShowCommentButtonLeft() {
return utils.shouldShowCommentButton(
this.isLeftHover,
this.isContextLineLeft,
this.isMetaLineLeft,
this.hasDiscussionsLeft,
this.line.isContextLineLeft,
this.line.isMetaLineLeft,
this.line.hasDiscussionsLeft,
);
},
shouldShowCommentButtonRight() {
return utils.shouldShowCommentButton(
this.isRightHover,
this.isContextLineRight,
this.isMetaLineRight,
this.hasDiscussionsRight,
this.line.isContextLineRight,
this.line.isMetaLineRight,
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() {
this.scrollToLineIfNeededParallel(this.line);
......@@ -203,7 +170,7 @@ export default {
@mouseover="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">
<span
v-if="shouldRenderCommentButton"
......@@ -227,12 +194,12 @@ export default {
v-if="line.left.old_line"
ref="lineNumberRefOld"
:data-linenumber="line.left.old_line"
:href="lineHrefOld"
@click="setHighlightedRow(lineCode)"
:href="line.lineHrefOld"
@click="setHighlightedRow(line.lineCode)"
>
</a>
<diff-gutter-avatars
v-if="hasDiscussionsLeft"
v-if="line.hasDiscussionsLeft"
:discussions="line.left.discussions"
:discussions-expanded="line.left.discussionsExpanded"
@toggleLineDiscussions="
......@@ -259,7 +226,7 @@ export default {
<td class="line-coverage left-side empty-cell"></td>
<td class="line_content with-coverage parallel left-side empty-cell"></td>
</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">
<span
v-if="shouldRenderCommentButton"
......@@ -283,12 +250,12 @@ export default {
v-if="line.right.new_line"
ref="lineNumberRefNew"
:data-linenumber="line.right.new_line"
:href="lineHrefNew"
@click="setHighlightedRow(lineCode)"
:href="line.lineHrefNew"
@click="setHighlightedRow(line.lineCode)"
>
</a>
<diff-gutter-avatars
v-if="hasDiscussionsRight"
v-if="line.hasDiscussionsRight"
:discussions="line.right.discussions"
:discussions-expanded="line.right.discussionsExpanded"
@toggleLineDiscussions="
......
<script>
import { mapGetters, mapState } from 'vuex';
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 parallelDiffCommentRow from './parallel_diff_comment_row.vue';
import parallelDiffExpansionRow from './parallel_diff_expansion_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: {
parallelDiffExpansionRow,
DiffExpansionCell,
parallelDiffTableRow,
parallelDiffCommentRow,
ParallelDraftCommentRow,
DiffCommentCell,
DraftNote,
},
mixins: [draftCommentsMixin],
props: {
......@@ -66,14 +66,21 @@ export default {
</colgroup>
<tbody>
<template v-for="(line, index) in diffLines">
<parallel-diff-expansion-row
<tr
v-if="line.isMatchLineLeft || line.isMatchLineRight"
:key="`expand-${index}`"
:file-hash="diffFile.file_hash"
:context-lines-path="diffFile.context_lines_path"
:line="line"
:is-top="index === 0"
:is-bottom="index + 1 === diffLinesLength"
/>
class="line_expansion match"
>
<td colspan="6" class="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"
/>
</td>
</tr>
<parallel-diff-table-row
:key="line.line_code"
:file-hash="diffFile.file_hash"
......@@ -82,21 +89,53 @@ export default {
:is-bottom="index + 1 === diffLinesLength"
:is-commented="index >= commentedLines.startLine && index <= commentedLines.endLine"
/>
<parallel-diff-comment-row
<tr
v-if="line.renderCommentRow"
:key="`dcr-${line.line_code || index}`"
:line="line"
:diff-file-hash="diffFile.file_hash"
:line-index="index"
:help-page-path="helpPagePath"
:has-draft-left="hasParallelDraftLeft(diffFile.file_hash, line) || false"
:has-draft-right="hasParallelDraftRight(diffFile.file_hash, line) || false"
/>
<parallel-draft-comment-row
:class="line.commentRowClasses"
class="notes_holder"
>
<td class="notes-content parallel old" colspan="3">
<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"
/>
</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)"
:key="`drafts-${index}`"
:line="line"
:diff-file-content-sha="diffFile.file_hash"
/>
:class="line.draftRowClasses"
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>
</tbody>
</table>
......
......@@ -165,8 +165,8 @@ export const fileLineCoverage = state => (file, line) => {
export const currentDiffIndex = state =>
Math.max(0, state.diffFiles.findIndex(diff => diff.file_hash === state.currentDiffFileId));
export const diffLines = state => file => {
if (state.diffViewType === INLINE_DIFF_VIEW_TYPE) {
export const diffLines = state => (file, unifiedDiffComponents) => {
if (!unifiedDiffComponents && state.diffViewType === INLINE_DIFF_VIEW_TYPE) {
return null;
}
......
......@@ -449,6 +449,7 @@
}
}
.diff-table.code,
table.code {
width: 100%;
font-family: $monospace-font;
......@@ -459,10 +460,12 @@ table.code {
table-layout: fixed;
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 {
border-top: 0;
}
.diff-tr:nth-last-of-type(2).line_expansion > .diff-td,
tr:nth-last-of-type(2).line_expansion,
tr:last-of-type.line_expansion {
> td {
......@@ -470,6 +473,7 @@ table.code {
}
}
.diff-tr.line_holder .diff-td,
tr.line_holder td {
line-height: $code-line-height;
font-size: $code-font-size;
......@@ -565,24 +569,95 @@ table.code {
}
.line_holder:last-of-type {
.diff-td:first-child,
td:first-child {
border-bottom-left-radius: $border-radius-default;
}
}
&.left-side-selected {
.diff-td.line_content.parallel.right-side,
td.line_content.parallel.right-side {
user-select: none;
}
}
&.right-side-selected {
.diff-td.line_content.parallel.left-side,
td.line_content.parallel.left-side {
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 {
align-items: center;
padding: 0 1rem;
......
......@@ -20,6 +20,7 @@
@mixin diff-expansion($background, $border, $link) {
background-color: $background;
.diff-td,
td {
border-top: 1px solid $border;
border-bottom: 1px solid $border;
......@@ -41,3 +42,12 @@
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;
@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.line-coverage.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
......@@ -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 {
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $dark-over-bg;
border-color: darken($dark-over-bg, 5%);
a {
color: darken($dark-over-bg, 15%);
}
@include line-number-hover($dark-over-bg);
}
}
......
......@@ -125,6 +125,9 @@ $monokai-gi: #a6e22e;
@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.line-coverage.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
......@@ -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 {
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $monokai-over-bg;
border-color: darken($monokai-over-bg, 5%);
a {
color: darken($monokai-over-bg, 15%);
}
@include line-number-hover($monokai-over-bg);
}
}
......
......@@ -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 {
&.old {
a {
......@@ -74,12 +81,7 @@
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $none-over-bg;
border-color: darken($none-over-bg, 5%);
a {
color: darken($none-over-bg, 15%);
}
@include line-number-hover($none-over-bg);
}
&.hll:not(.empty-cell) {
......
......@@ -129,6 +129,9 @@ $solarized-dark-il: #2aa198;
@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.line-coverage.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
......@@ -140,6 +143,13 @@ $solarized-dark-il: #2aa198;
@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,
.line-coverage.new,
.line_content.new {
......@@ -165,12 +175,7 @@ $solarized-dark-il: #2aa198;
.diff-line-num {
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $solarized-dark-over-bg;
border-color: darken($solarized-dark-over-bg, 5%);
a {
color: darken($solarized-dark-over-bg, 15%);
}
@include line-number-hover($solarized-dark-over-bg);
}
}
......
......@@ -136,6 +136,9 @@ $solarized-light-il: #2aa198;
@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.line-coverage.hll:not(.empty-cell),
td.line_content.hll:not(.empty-cell) {
......@@ -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,
.line-coverage.old,
.line_content.old {
......@@ -173,12 +183,7 @@ $solarized-light-il: #2aa198;
.diff-line-num {
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $solarized-light-over-bg;
border-color: darken($solarized-light-over-bg, 5%);
a {
color: darken($solarized-light-over-bg, 15%);
}
@include line-number-hover($solarized-light-over-bg);
}
}
......
......@@ -113,6 +113,13 @@ pre.code,
@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 {
&.old {
background-color: $line-number-old;
......@@ -134,12 +141,7 @@ pre.code,
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $white-over-bg;
border-color: darken($white-over-bg, 5%);
a {
color: darken($white-over-bg, 15%);
}
@include line-number-hover($white-over-bg);
}
&.hll:not(.empty-cell) {
......
......@@ -2,6 +2,7 @@
* Note Form
*/
.diff-file .diff-content {
.diff-tr.line_holder:hover > .diff-td .line_note_link,
tr.line_holder:hover > td .line_note_link {
opacity: 1;
filter: alpha(opacity = 100);
......
......@@ -453,6 +453,8 @@ $note-form-margin-left: 72px;
}
.diff-file {
.diff-grid-left:hover,
.diff-grid-right:hover,
.is-over {
.add-diff-note {
display: inline-flex;
......@@ -490,6 +492,7 @@ $note-form-margin-left: 72px;
.notes_holder {
font-family: $regular-font;
.diff-td,
td {
border: 1px solid $border-color;
border-left: 0;
......@@ -805,6 +808,8 @@ $note-form-margin-left: 72px;
* Line note button on the side of diffs
*/
.diff-grid-left:hover,
.diff-grid-right:hover,
.line_holder .is-over:not(.no-comment-btn) {
.add-diff-note {
opacity: 1;
......
......@@ -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(:merge_request_widget_graphql, @project)
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(:default_merge_ref_for_diffs, @project)
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';
import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import diffFileMockData from '../mock_data/diff_file';
import { diffViewerModes } from '~/ide/constants';
import { diffLines } from '~/diffs/store/getters';
import DiffView from '~/diffs/components/diff_view.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -33,7 +35,7 @@ describe('DiffContent', () => {
diffFile: JSON.parse(JSON.stringify(diffFileMockData)),
};
const createComponent = ({ props, state } = {}) => {
const createComponent = ({ props, state, provide } = {}) => {
const fakeStore = new Vuex.Store({
getters: {
getNoteableData() {
......@@ -55,6 +57,10 @@ describe('DiffContent', () => {
namespaced: true,
getters: {
draftsForFile: () => () => true,
draftForLine: () => () => true,
shouldRenderDraftRow: () => () => true,
hasParallelDraftLeft: () => () => true,
hasParallelDraftRight: () => () => true,
},
},
diffs: {
......@@ -68,6 +74,7 @@ describe('DiffContent', () => {
isInlineView: isInlineViewGetterMock,
isParallelView: isParallelViewGetterMock,
getCommentFormForDiffFile: getCommentFormForDiffFileGetterMock,
diffLines,
},
actions: {
saveDiffDiscussion: saveDiffDiscussionMock,
......@@ -77,6 +84,8 @@ describe('DiffContent', () => {
},
});
const glFeatures = provide ? { ...provide.glFeatures } : {};
wrapper = shallowMount(DiffContentComponent, {
propsData: {
...defaultProps,
......@@ -84,6 +93,7 @@ describe('DiffContent', () => {
},
localVue,
store: fakeStore,
provide: { glFeatures },
});
};
......@@ -112,6 +122,16 @@ describe('DiffContent', () => {
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', () => {
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', () => {
},
);
});
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';
import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
import diffFileMockData from '../mock_data/diff_file';
import discussionsMockData from '../mock_data/diff_discussions';
import { mapInline } from '~/diffs/components/diff_row_utils';
const TEST_USER_ID = 'abc123';
const TEST_USER = { id: TEST_USER_ID };
......@@ -11,7 +12,16 @@ const TEST_USER = { id: TEST_USER_ID };
describe('InlineDiffTableRow', () => {
let wrapper;
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) => {
wrapper = shallowMount(InlineDiffTableRow, {
......@@ -132,7 +142,7 @@ describe('InlineDiffTableRow', () => {
${true} | ${{ ...thisLine, type: 'old-nonewline', discussions: [] }} | ${false}
${true} | ${{ ...thisLine, discussions: [{}] }} | ${false}
`('visible is $expectation - line ($line)', ({ isHover, line, expectation }) => {
createComponent({ line });
createComponent({ line: applyMap(line) });
wrapper.setData({ isHover });
return wrapper.vm.$nextTick().then(() => {
......@@ -148,7 +158,7 @@ describe('InlineDiffTableRow', () => {
'has attribute disabled=$disabled when the outer component has prop commentsDisabled=$commentsDisabled',
({ disabled, commentsDisabled }) => {
createComponent({
line: { ...thisLine, commentsDisabled },
line: applyMap({ ...thisLine, commentsDisabled }),
});
wrapper.setData({ isHover: true });
......@@ -177,7 +187,7 @@ describe('InlineDiffTableRow', () => {
'has the correct tooltip when commentsDisabled=$commentsDisabled',
({ tooltip, commentsDisabled }) => {
createComponent({
line: { ...thisLine, commentsDisabled },
line: applyMap({ ...thisLine, commentsDisabled }),
});
wrapper.setData({ isHover: true });
......@@ -216,7 +226,7 @@ describe('InlineDiffTableRow', () => {
beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation();
createComponent({
line: { ...thisLine, ...lineProps },
line: applyMap({ ...thisLine, ...lineProps }),
});
});
......@@ -268,7 +278,7 @@ describe('InlineDiffTableRow', () => {
describe('with showCommentButton', () => {
it('renders if line has discussions', () => {
createComponent({ line });
createComponent({ line: applyMap(line) });
expect(findAvatars().props()).toEqual({
discussions: line.discussions,
......@@ -278,13 +288,13 @@ describe('InlineDiffTableRow', () => {
it('does notrender if line has no discussions', () => {
line.discussions = [];
createComponent({ line });
createComponent({ line: applyMap(line) });
expect(findAvatars().exists()).toEqual(false);
});
it('toggles line discussion', () => {
createComponent({ line });
createComponent({ line: applyMap(line) });
expect(store.dispatch).toHaveBeenCalledTimes(1);
......
import Vue from 'vue';
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 InlineDiffView from '~/diffs/components/inline_diff_view.vue';
import { mapInline } from '~/diffs/components/diff_row_utils';
import diffFileMockData from '../mock_data/diff_file';
import discussionsMockData from '../mock_data/diff_discussions';
describe('InlineDiffView', () => {
let component;
let wrapper;
const getDiffFileMock = () => ({ ...diffFileMockData });
const getDiscussionsMockData = () => [{ ...discussionsMockData }];
const notesLength = getDiscussionsMockData()[0].notes.length;
beforeEach(done => {
const diffFile = getDiffFileMock();
const setup = (diffFile, diffLines) => {
const mockDiffContent = {
diffFile,
shouldRenderDraftRow: jest.fn(),
};
const store = createStore();
store.dispatch('diffs/setInlineDiffViewType');
component = createComponentWithStore(Vue.extend(InlineDiffView), store, {
diffFile,
diffLines: diffFile.highlighted_diff_lines,
}).$mount();
Vue.nextTick(done);
});
wrapper = mount(InlineDiffView, {
store,
propsData: {
diffFile,
diffLines: diffLines.map(mapInline(mockDiffContent)),
},
});
};
describe('template', () => {
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(el.querySelectorAll('tr.line_holder.new').length).toEqual(4);
expect(el.querySelectorAll('tr.line_expansion.match').length).toEqual(1);
expect(el.textContent.indexOf('Bad dates')).toBeGreaterThan(-1);
expect(wrapper.findAll('tr.line_holder').length).toEqual(8);
expect(wrapper.findAll('tr.line_holder.new').length).toEqual(4);
expect(wrapper.findAll('tr.line_expansion.match').length).toEqual(1);
getByText(wrapper.element, /Bad dates/i);
});
it('should render discussions', done => {
const el = component.$el;
component.diffLines[1].discussions = getDiscussionsMockData();
component.diffLines[1].discussionsExpanded = true;
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', []);
it('should render discussions', () => {
const diffFile = getDiffFileMock();
diffFile.highlighted_diff_lines[1].discussions = getDiscussionsMockData();
diffFile.highlighted_diff_lines[1].discussionsExpanded = true;
setup(diffFile, diffFile.highlighted_diff_lines);
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';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from '~/mr_notes/stores';
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 DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
import discussionsMockData from '../mock_data/diff_discussions';
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', () => {
let wrapper;
let vm;
......@@ -18,7 +29,7 @@ describe('ParallelDiffTableRow', () => {
wrapper = shallowMount(ParallelDiffTableRow, {
store: createStore(),
propsData: {
line: thisLine,
line: applyMap(thisLine),
fileHash: diffFileMockData.file_hash,
filePath: diffFileMockData.file_path,
contextLinesPath: 'contextLinesPath',
......@@ -67,7 +78,7 @@ describe('ParallelDiffTableRow', () => {
beforeEach(() => {
vm = createComponentWithStore(Vue.extend(ParallelDiffTableRow), createStore(), {
line: thisLine,
line: applyMap(thisLine),
fileHash: diffFileMockData.file_hash,
filePath: diffFileMockData.file_path,
contextLinesPath: 'contextLinesPath',
......@@ -243,7 +254,10 @@ describe('ParallelDiffTableRow', () => {
${{ ...thisLine, left: { type: 'old-nonewline', discussions: [] } }} | ${false}
${{ ...thisLine, left: { discussions: [{}] } }} | ${false}
`('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);
});
......@@ -320,7 +334,7 @@ describe('ParallelDiffTableRow', () => {
Object.assign(thisLine.left, lineProps);
Object.assign(thisLine.right, lineProps);
createComponent({
line: { ...thisLine },
line: applyMap({ ...thisLine }),
});
});
......@@ -357,7 +371,7 @@ describe('ParallelDiffTableRow', () => {
beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation();
line = {
line = applyMap({
left: {
line_code: TEST_LINE_CODE,
type: 'new',
......@@ -369,7 +383,7 @@ describe('ParallelDiffTableRow', () => {
rich_text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
meta_data: null,
},
};
});
});
describe('with showCommentButton', () => {
......@@ -384,7 +398,7 @@ describe('ParallelDiffTableRow', () => {
it('does notrender if line has no discussions', () => {
line.left.discussions = [];
createComponent({ line });
createComponent({ line: applyMap(line) });
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