Commit 5d2f8bb6 authored by Justin Boyson's avatar Justin Boyson Committed by Phil Hughes

Update tests and fix for parallel layout

Add "cell" tests to parallel diff row
Remove references to DiffTableCell
Update added table cell code to use appropriate "side"
parent a1fbd4cc
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import $ from 'jquery'; import $ from 'jquery';
import { GlTooltipDirective, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import DiffTableCell from './diff_table_cell.vue';
import { import {
MATCH_LINE_TYPE, MATCH_LINE_TYPE,
NEW_LINE_TYPE, NEW_LINE_TYPE,
...@@ -13,11 +12,16 @@ import { ...@@ -13,11 +12,16 @@ import {
PARALLEL_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE,
NEW_NO_NEW_LINE_TYPE, NEW_NO_NEW_LINE_TYPE,
EMPTY_CELL_TYPE, EMPTY_CELL_TYPE,
LINE_HOVER_CLASS_NAME,
} from '../constants'; } from '../constants';
import { __ } from '~/locale';
import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
export default { export default {
components: { components: {
DiffTableCell, GlIcon,
DiffGutterAvatars,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -51,10 +55,12 @@ export default { ...@@ -51,10 +55,12 @@ export default {
return { return {
isLeftHover: false, isLeftHover: false,
isRightHover: false, isRightHover: false,
isCommentButtonRendered: false,
}; };
}, },
computed: { computed: {
...mapGetters('diffs', ['fileLineCoverage']), ...mapGetters('diffs', ['fileLineCoverage']),
...mapGetters(['isLoggedIn']),
...mapState({ ...mapState({
isHighlighted(state) { isHighlighted(state) {
if (this.isCommented) return true; if (this.isCommented) return true;
...@@ -66,12 +72,15 @@ export default { ...@@ -66,12 +72,15 @@ export default {
return lineCode ? lineCode === state.diffs.highlightedRow : false; return lineCode ? lineCode === state.diffs.highlightedRow : false;
}, },
}), }),
isContextLine() { isContextLineLeft() {
return this.line.left && this.line.left.type === CONTEXT_LINE_TYPE; return this.line.left && this.line.left.type === CONTEXT_LINE_TYPE;
}, },
isContextLineRight() {
return this.line.right && this.line.right.type === CONTEXT_LINE_TYPE;
},
classNameMap() { classNameMap() {
return { return {
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine, [CONTEXT_LINE_CLASS_NAME]: this.isContextLineLeft,
[PARALLEL_DIFF_VIEW_TYPE]: true, [PARALLEL_DIFF_VIEW_TYPE]: true,
}; };
}, },
...@@ -98,6 +107,129 @@ export default { ...@@ -98,6 +107,129 @@ export default {
coverageState() { coverageState() {
return this.fileLineCoverage(this.filePath, this.line.right.new_line); return this.fileLineCoverage(this.filePath, this.line.right.new_line);
}, },
classNameMapCellLeft() {
const { type } = this.line.left;
return [
type,
{
hll: this.isHighlighted,
[LINE_HOVER_CLASS_NAME]:
this.isLoggedIn && this.isLeftHover && !this.isContextLineLeft && !this.isMetaLineLeft,
},
];
},
classNameMapCellRight() {
const { type } = this.line.right;
return [
type,
{
hll: this.isHighlighted,
[LINE_HOVER_CLASS_NAME]:
this.isLoggedIn &&
this.isRightHover &&
!this.isContextLineRight &&
!this.isMetaLineRight,
},
];
},
addCommentTooltipLeft() {
const brokenSymlinks = this.line.left.commentsDisabled;
let tooltip = __('Add a comment to this line');
if (brokenSymlinks) {
if (brokenSymlinks.wasSymbolic || brokenSymlinks.isSymbolic) {
tooltip = __(
'Commenting on symbolic links that replace or are replaced by files is currently not supported.',
);
} else if (brokenSymlinks.wasReal || brokenSymlinks.isReal) {
tooltip = __(
'Commenting on files that replace or are replaced by symbolic links is currently not supported.',
);
}
}
return tooltip;
},
addCommentTooltipRight() {
const brokenSymlinks = this.line.right.commentsDisabled;
let tooltip = __('Add a comment to this line');
if (brokenSymlinks) {
if (brokenSymlinks.wasSymbolic || brokenSymlinks.isSymbolic) {
tooltip = __(
'Commenting on symbolic links that replace or are replaced by files is currently not supported.',
);
} else if (brokenSymlinks.wasReal || brokenSymlinks.isReal) {
tooltip = __(
'Commenting on files that replace or are replaced by symbolic links is currently not supported.',
);
}
}
return tooltip;
},
shouldRenderCommentButton() {
if (!this.isCommentButtonRendered) {
return false;
}
if (this.isLoggedIn) {
const isDiffHead = parseBoolean(getParameterByName('diff_head'));
return !isDiffHead || gon.features?.mergeRefHeadComments;
}
return false;
},
shouldShowCommentButtonLeft() {
return (
this.isLeftHover &&
!this.isContextLineLeft &&
!this.isMetaLineLeft &&
!this.hasDiscussionsLeft
);
},
shouldShowCommentButtonRight() {
return (
this.isRightHover &&
!this.isContextLineRight &&
!this.isMetaLineRight &&
!this.hasDiscussionsRight
);
},
hasDiscussionsLeft() {
return this.line.left.discussions && this.line.left.discussions.length > 0;
},
hasDiscussionsRight() {
return this.line.right.discussions && this.line.right.discussions.length > 0;
},
lineHrefOld() {
return `#${this.line.left.line_code || ''}`;
},
lineHrefNew() {
return `#${this.line.right.line_code || ''}`;
},
lineCode() {
return (
(this.line.left && this.line.left.line_code) ||
(this.line.right && this.line.right.line_code)
);
},
isMetaLineLeft() {
const { type } = this.line.left;
return (
type === OLD_NO_NEW_LINE_TYPE || type === NEW_NO_NEW_LINE_TYPE || type === EMPTY_CELL_TYPE
);
},
isMetaLineRight() {
const { type } = this.line.right;
return (
type === OLD_NO_NEW_LINE_TYPE || type === NEW_NO_NEW_LINE_TYPE || type === EMPTY_CELL_TYPE
);
},
}, },
created() { created() {
this.newLineType = NEW_LINE_TYPE; this.newLineType = NEW_LINE_TYPE;
...@@ -106,9 +238,26 @@ export default { ...@@ -106,9 +238,26 @@ export default {
}, },
mounted() { mounted() {
this.scrollToLineIfNeededParallel(this.line); this.scrollToLineIfNeededParallel(this.line);
this.unwatchShouldShowCommentButton = this.$watch(
vm => [vm.shouldShowCommentButtonLeft, vm.shouldShowCommentButtonRight].join(),
newVal => {
if (newVal) {
this.isCommentButtonRendered = true;
this.unwatchShouldShowCommentButton();
}
},
);
},
beforeDestroy() {
this.unwatchShouldShowCommentButton();
}, },
methods: { methods: {
...mapActions('diffs', ['scrollToLineIfNeededParallel']), ...mapActions('diffs', [
'scrollToLineIfNeededParallel',
'showCommentForm',
'setHighlightedRow',
'toggleLineDiscussions',
]),
handleMouseMove(e) { handleMouseMove(e) {
const isHover = e.type === 'mouseover'; const isHover = e.type === 'mouseover';
const hoveringCell = e.target.closest('td'); const hoveringCell = e.target.closest('td');
...@@ -134,6 +283,9 @@ export default { ...@@ -134,6 +283,9 @@ export default {
table.addClass(`${lineClass}-selected`); table.addClass(`${lineClass}-selected`);
} }
}, },
handleCommentButton(line) {
this.showCommentForm({ lineCode: line.line_code, fileHash: this.fileHash });
},
}, },
}; };
</script> </script>
...@@ -146,18 +298,46 @@ export default { ...@@ -146,18 +298,46 @@ export default {
@mouseout="handleMouseMove" @mouseout="handleMouseMove"
> >
<template v-if="line.left && !isMatchLineLeft"> <template v-if="line.left && !isMatchLineLeft">
<diff-table-cell <td ref="oldTd" :class="classNameMapCellLeft" class="diff-line-num old_line">
:file-hash="fileHash" <span
:line="line.left" v-if="shouldRenderCommentButton"
:line-type="oldLineType" ref="addNoteTooltipLeft"
:is-bottom="isBottom" v-gl-tooltip
:is-hover="isLeftHover" class="add-diff-note tooltip-wrapper"
:is-highlighted="isHighlighted" :title="addCommentTooltipLeft"
:show-comment-button="true" >
:diff-view-type="parallelDiffViewType" <button
line-position="left" v-show="shouldShowCommentButtonLeft"
class="diff-line-num old_line" ref="addDiffNoteButtonLeft"
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"
ref="lineNumberRefOld"
:data-linenumber="line.left.old_line"
:href="lineHrefOld"
@click="setHighlightedRow(lineCode)"
>
</a>
<diff-gutter-avatars
v-if="hasDiscussionsLeft"
:discussions="line.left.discussions"
:discussions-expanded="line.left.discussionsExpanded"
@toggleLineDiscussions="
toggleLineDiscussions({
lineCode: line.left.line_code,
fileHash,
expanded: !line.left.discussionsExpanded,
})
"
/> />
</td>
<td :class="parallelViewLeftLineType" class="line-coverage left-side"></td> <td :class="parallelViewLeftLineType" class="line-coverage left-side"></td>
<td <td
:id="line.left.line_code" :id="line.left.line_code"
...@@ -173,18 +353,46 @@ export default { ...@@ -173,18 +353,46 @@ export default {
<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 && !isMatchLineRight">
<diff-table-cell <td ref="newTd" :class="classNameMapCellRight" class="diff-line-num new_line">
:file-hash="fileHash" <span
:line="line.right" v-if="shouldRenderCommentButton"
:line-type="newLineType" ref="addNoteTooltipRight"
:is-bottom="isBottom" v-gl-tooltip
:is-hover="isRightHover" class="add-diff-note tooltip-wrapper"
:is-highlighted="isHighlighted" :title="addCommentTooltipRight"
:show-comment-button="true" >
:diff-view-type="parallelDiffViewType" <button
line-position="right" v-show="shouldShowCommentButtonRight"
class="diff-line-num new_line" ref="addDiffNoteButtonRight"
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"
ref="lineNumberRefNew"
:data-linenumber="line.right.new_line"
:href="lineHrefNew"
@click="setHighlightedRow(lineCode)"
>
</a>
<diff-gutter-avatars
v-if="hasDiscussionsRight"
:discussions="line.right.discussions"
:discussions-expanded="line.right.discussionsExpanded"
@toggleLineDiscussions="
toggleLineDiscussions({
lineCode: line.right.line_code,
fileHash,
expanded: !line.right.discussionsExpanded,
})
"
/> />
</td>
<td <td
v-gl-tooltip.hover v-gl-tooltip.hover
:title="coverageState.text" :title="coverageState.text"
......
---
title: Jdb/refactor parallel diff table row
merge_request: 41606
author:
type: performance
...@@ -53,10 +53,6 @@ module QA ...@@ -53,10 +53,6 @@ module QA
element :diffs_tab element :diffs_tab
end end
view 'app/assets/javascripts/diffs/components/diff_table_cell.vue' do
element :diff_comment
end
view 'app/assets/javascripts/diffs/components/inline_diff_table_row.vue' do view 'app/assets/javascripts/diffs/components/inline_diff_table_row.vue' do
element :new_diff_line element :new_diff_line
end end
......
import Vue from 'vue'; import Vue from 'vue';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { TEST_HOST } from 'helpers/test_constants';
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 diffFileMockData from '../mock_data/diff_file'; 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', () => { describe('ParallelDiffTableRow', () => {
describe('when one side is empty', () => { describe('when one side is empty', () => {
...@@ -158,4 +161,259 @@ describe('ParallelDiffTableRow', () => { ...@@ -158,4 +161,259 @@ describe('ParallelDiffTableRow', () => {
}); });
}); });
}); });
describe('Table Cells', () => {
let wrapper;
let store;
let thisLine;
const TEST_USER_ID = 'abc123';
const TEST_USER = { id: TEST_USER_ID };
const createComponent = (props = {}, propsStore = store, data = {}) => {
wrapper = shallowMount(ParallelDiffTableRow, {
store: propsStore,
propsData: {
line: thisLine,
fileHash: diffFileMockData.file_hash,
filePath: diffFileMockData.file_path,
contextLinesPath: 'contextLinesPath',
isHighlighted: false,
...props,
},
data() {
return data;
},
});
};
const setWindowLocation = value => {
Object.defineProperty(window, 'location', {
writable: true,
value,
});
};
beforeEach(() => {
// eslint-disable-next-line prefer-destructuring
thisLine = diffFileMockData.parallel_diff_lines[2];
store = createStore();
store.state.notes.userData = TEST_USER;
});
afterEach(() => {
wrapper.destroy();
});
const findNewTd = () => wrapper.find({ ref: 'newTd' });
const findOldTd = () => wrapper.find({ ref: 'oldTd' });
describe('td', () => {
it('highlights when isHighlighted true', () => {
store.state.diffs.highlightedRow = thisLine.left.line_code;
createComponent({}, store);
expect(findNewTd().classes()).toContain('hll');
expect(findOldTd().classes()).toContain('hll');
});
it('does not highlight when isHighlighted false', () => {
createComponent();
expect(findNewTd().classes()).not.toContain('hll');
expect(findOldTd().classes()).not.toContain('hll');
});
});
describe('comment button', () => {
const findNoteButton = () => wrapper.find({ ref: 'addDiffNoteButtonLeft' });
it.each`
hover | userData | query | mergeRefHeadComments | expectation
${true} | ${TEST_USER} | ${'diff_head=false'} | ${false} | ${true}
${true} | ${TEST_USER} | ${'diff_head=true'} | ${true} | ${true}
${true} | ${TEST_USER} | ${'diff_head=true'} | ${false} | ${false}
${true} | ${null} | ${''} | ${true} | ${false}
${false} | ${TEST_USER} | ${'diff_head=false'} | ${false} | ${false}
`(
'exists is $expectation - with userData ($userData) query ($query)',
async ({ hover, userData, query, mergeRefHeadComments, expectation }) => {
store.state.notes.userData = userData;
gon.features = { mergeRefHeadComments };
setWindowLocation({ href: `${TEST_HOST}?${query}` });
createComponent({}, store);
if (hover) await wrapper.find('.line_holder').trigger('mouseover');
expect(findNoteButton().exists()).toBe(expectation);
},
);
it.each`
line | expectation
${{ ...thisLine, left: { discussions: [] } }} | ${true}
${{ ...thisLine, left: { type: 'context', discussions: [] } }} | ${false}
${{ ...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 });
expect(findNoteButton().isVisible()).toBe(expectation);
});
it.each`
disabled | commentsDisabled
${'disabled'} | ${true}
${undefined} | ${false}
`(
'has attribute disabled=$disabled when the outer component has prop commentsDisabled=$commentsDisabled',
({ disabled, commentsDisabled }) => {
thisLine.left.commentsDisabled = commentsDisabled;
createComponent({ line: { ...thisLine } }, store, {
isLeftHover: true,
isCommentButtonRendered: true,
});
expect(findNoteButton().attributes('disabled')).toBe(disabled);
},
);
const symlinkishFileTooltip =
'Commenting on symbolic links that replace or are replaced by files is currently not supported.';
const realishFileTooltip =
'Commenting on files that replace or are replaced by symbolic links is currently not supported.';
const otherFileTooltip = 'Add a comment to this line';
const findTooltip = () => wrapper.find({ ref: 'addNoteTooltipLeft' });
it.each`
tooltip | commentsDisabled
${symlinkishFileTooltip} | ${{ wasSymbolic: true }}
${symlinkishFileTooltip} | ${{ isSymbolic: true }}
${realishFileTooltip} | ${{ wasReal: true }}
${realishFileTooltip} | ${{ isReal: true }}
${otherFileTooltip} | ${false}
`(
'has the correct tooltip when commentsDisabled=$commentsDisabled',
({ tooltip, commentsDisabled }) => {
thisLine.left.commentsDisabled = commentsDisabled;
createComponent({ line: { ...thisLine } }, store, {
isLeftHover: true,
isCommentButtonRendered: true,
});
expect(findTooltip().attributes('title')).toBe(tooltip);
},
);
});
describe('line number', () => {
const findLineNumberOld = () => wrapper.find({ ref: 'lineNumberRefOld' });
const findLineNumberNew = () => wrapper.find({ ref: 'lineNumberRefNew' });
it('renders line numbers in correct cells', () => {
createComponent();
expect(findLineNumberOld().exists()).toBe(true);
expect(findLineNumberNew().exists()).toBe(true);
});
describe('with lineNumber prop', () => {
const TEST_LINE_CODE = 'LC_42';
const TEST_LINE_NUMBER = 1;
describe.each`
lineProps | findLineNumber | expectedHref | expectedClickArg
${{ line_code: TEST_LINE_CODE, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${`#${TEST_LINE_CODE}`} | ${TEST_LINE_CODE}
${{ line_code: undefined, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${'#'} | ${undefined}
`(
'with line ($lineProps)',
({ lineProps, findLineNumber, expectedHref, expectedClickArg }) => {
beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation();
Object.assign(thisLine.left, lineProps);
Object.assign(thisLine.right, lineProps);
createComponent({
line: { ...thisLine },
});
});
it('renders', () => {
expect(findLineNumber().exists()).toBe(true);
expect(findLineNumber().attributes()).toEqual({
href: expectedHref,
'data-linenumber': TEST_LINE_NUMBER.toString(),
});
});
it('on click, dispatches setHighlightedRow', () => {
expect(store.dispatch).toHaveBeenCalledTimes(1);
findLineNumber().trigger('click');
expect(store.dispatch).toHaveBeenCalledWith(
'diffs/setHighlightedRow',
expectedClickArg,
);
expect(store.dispatch).toHaveBeenCalledTimes(2);
});
},
);
});
});
describe('diff-gutter-avatars', () => {
const TEST_LINE_CODE = 'LC_42';
const TEST_FILE_HASH = diffFileMockData.file_hash;
const findAvatars = () => wrapper.find(DiffGutterAvatars);
let line;
beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation();
line = {
left: {
line_code: TEST_LINE_CODE,
type: 'new',
old_line: null,
new_line: 1,
discussions: [{ ...discussionsMockData }],
discussionsExpanded: true,
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,
},
};
});
describe('with showCommentButton', () => {
it('renders if line has discussions', () => {
createComponent({ line });
expect(findAvatars().props()).toEqual({
discussions: line.left.discussions,
discussionsExpanded: line.left.discussionsExpanded,
});
});
it('does notrender if line has no discussions', () => {
line.left.discussions = [];
createComponent({ line });
expect(findAvatars().exists()).toEqual(false);
});
it('toggles line discussion', () => {
createComponent({ line });
expect(store.dispatch).toHaveBeenCalledTimes(1);
findAvatars().vm.$emit('toggleLineDiscussions');
expect(store.dispatch).toHaveBeenCalledWith('diffs/toggleLineDiscussions', {
lineCode: TEST_LINE_CODE,
fileHash: TEST_FILE_HASH,
expanded: !line.left.discussionsExpanded,
});
});
});
});
});
}); });
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