313 lines
10 KiB
JavaScript
313 lines
10 KiB
JavaScript
import { getByTestId, fireEvent } from '@testing-library/dom';
|
|
import { shallowMount } from '@vue/test-utils';
|
|
import Vue from 'vue';
|
|
import Vuex from 'vuex';
|
|
import DiffRow from '~/diffs/components/diff_row.vue';
|
|
import { mapParallel } from '~/diffs/components/diff_row_utils';
|
|
import diffsModule from '~/diffs/store/modules';
|
|
import { findInteropAttributes } from '../find_interop_attributes';
|
|
import { getDiffFileMock } from '../mock_data/diff_file';
|
|
|
|
const showCommentForm = jest.fn();
|
|
const enterdragging = jest.fn();
|
|
const stopdragging = jest.fn();
|
|
const setHighlightedRow = jest.fn();
|
|
let wrapper;
|
|
|
|
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 = {}, actions, isLoggedIn = true }) => {
|
|
Vue.use(Vuex);
|
|
|
|
const diffs = diffsModule();
|
|
diffs.state = { ...diffs.state, ...state };
|
|
diffs.actions = { ...diffs.actions, ...actions };
|
|
|
|
const getters = { isLoggedIn: () => isLoggedIn };
|
|
|
|
const store = new Vuex.Store({
|
|
modules: { diffs },
|
|
getters,
|
|
});
|
|
|
|
window.gon = { current_user_id: isLoggedIn ? 1 : 0 };
|
|
const coverageFileData = state.coverageFiles?.files ? state.coverageFiles.files : {};
|
|
|
|
const propsData = {
|
|
fileHash: 'abc',
|
|
filePath: 'abc',
|
|
line: {},
|
|
index: 0,
|
|
isHighlighted: false,
|
|
fileLineCoverage: (file, line) => {
|
|
const hits = coverageFileData[file]?.[line];
|
|
if (hits) {
|
|
return { text: `Test coverage: ${hits} hits`, class: 'coverage' };
|
|
} else if (hits === 0) {
|
|
return { text: 'No test coverage', class: 'no-coverage' };
|
|
}
|
|
|
|
return {};
|
|
},
|
|
...props,
|
|
};
|
|
|
|
const provide = {
|
|
glFeatures: { dragCommentSelection: true },
|
|
};
|
|
|
|
return shallowMount(DiffRow, {
|
|
propsData,
|
|
store,
|
|
provide,
|
|
listeners: {
|
|
enterdragging,
|
|
stopdragging,
|
|
setHighlightedRow,
|
|
showCommentForm,
|
|
},
|
|
});
|
|
};
|
|
|
|
afterEach(() => {
|
|
wrapper.destroy();
|
|
wrapper = null;
|
|
|
|
window.gon = {};
|
|
showCommentForm.mockReset();
|
|
enterdragging.mockReset();
|
|
stopdragging.mockReset();
|
|
setHighlightedRow.mockReset();
|
|
|
|
Object.values(DiffRow).forEach(({ cache }) => {
|
|
if (cache) {
|
|
cache.clear();
|
|
}
|
|
});
|
|
});
|
|
|
|
const getCommentButton = (side) => wrapper.find(`[data-testid="${side}-comment-button"]`);
|
|
|
|
describe.each`
|
|
side
|
|
${'left'}
|
|
${'right'}
|
|
`('$side side', ({ side }) => {
|
|
it(`renders empty cells if ${side} is unavailable`, () => {
|
|
wrapper = createWrapper({ props: { line: testLines[2], inline: false } });
|
|
expect(wrapper.find(`[data-testid="${side}-line-number"]`).exists()).toBe(false);
|
|
expect(wrapper.find(`[data-testid="${side}-empty-cell"]`).exists()).toBe(true);
|
|
});
|
|
|
|
describe('comment button', () => {
|
|
let line;
|
|
|
|
beforeEach(() => {
|
|
// https://eslint.org/docs/rules/prefer-destructuring#when-not-to-use-it
|
|
// eslint-disable-next-line prefer-destructuring
|
|
line = testLines[3];
|
|
});
|
|
|
|
it('renders', () => {
|
|
wrapper = createWrapper({ props: { line, inline: false } });
|
|
expect(getCommentButton(side).exists()).toBe(true);
|
|
});
|
|
|
|
it('responds to click and keyboard events', async () => {
|
|
wrapper = createWrapper({
|
|
props: { line, inline: false },
|
|
});
|
|
const commentButton = getCommentButton(side);
|
|
|
|
await commentButton.trigger('click');
|
|
await commentButton.trigger('keydown.enter');
|
|
await commentButton.trigger('keydown.space');
|
|
|
|
expect(showCommentForm).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
it('ignores click and keyboard events when comments are disabled', async () => {
|
|
line[side].commentsDisabled = true;
|
|
wrapper = createWrapper({
|
|
props: { line, inline: false },
|
|
});
|
|
const commentButton = getCommentButton(side);
|
|
|
|
await commentButton.trigger('click');
|
|
await commentButton.trigger('keydown.enter');
|
|
await commentButton.trigger('keydown.space');
|
|
|
|
expect(showCommentForm).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it('renders avatars', () => {
|
|
wrapper = createWrapper({ props: { line: testLines[0], inline: false } });
|
|
|
|
expect(wrapper.find(`[data-testid="${side}-discussions"]`).exists()).toBe(true);
|
|
});
|
|
});
|
|
|
|
it('renders left line numbers', () => {
|
|
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', () => {
|
|
wrapper = createWrapper({ props: { line: testLines[0] } });
|
|
const lineNumber = testLines[0].right.new_line;
|
|
expect(wrapper.find(`[data-linenumber="${lineNumber}"]`).exists()).toBe(true);
|
|
});
|
|
|
|
describe('drag operations', () => {
|
|
let line;
|
|
|
|
beforeEach(() => {
|
|
line = { ...testLines[0] };
|
|
});
|
|
|
|
it.each`
|
|
side
|
|
${'left'}
|
|
${'right'}
|
|
`('emits `enterdragging` onDragEnter $side side', ({ side }) => {
|
|
wrapper = createWrapper({ props: { line } });
|
|
fireEvent.dragEnter(getByTestId(wrapper.element, `${side}-side`));
|
|
|
|
expect(enterdragging).toHaveBeenCalledWith({ ...line[side], index: 0 });
|
|
});
|
|
|
|
it.each`
|
|
side
|
|
${'left'}
|
|
${'right'}
|
|
`('emits `stopdragging` onDrop $side side', ({ side }) => {
|
|
wrapper = createWrapper({ props: { line } });
|
|
fireEvent.dragEnd(getByTestId(wrapper.element, `${side}-side`));
|
|
|
|
expect(stopdragging).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('sets coverage title and class', () => {
|
|
const diffFileMockData = getDiffFileMock();
|
|
const thisLine = diffFileMockData.parallel_diff_lines[2];
|
|
const rightLine = diffFileMockData.parallel_diff_lines[2].right;
|
|
|
|
const mockDiffContent = {
|
|
diffFile: diffFileMockData,
|
|
shouldRenderDraftRow: jest.fn(),
|
|
hasParallelDraftLeft: jest.fn(),
|
|
hasParallelDraftRight: jest.fn(),
|
|
draftsForLine: jest.fn().mockReturnValue([]),
|
|
};
|
|
|
|
const applyMap = mapParallel(mockDiffContent);
|
|
const props = {
|
|
line: applyMap(thisLine),
|
|
fileHash: diffFileMockData.file_hash,
|
|
filePath: diffFileMockData.file_path,
|
|
contextLinesPath: 'contextLinesPath',
|
|
isHighlighted: false,
|
|
};
|
|
const name = diffFileMockData.file_path;
|
|
const line = rightLine.new_line;
|
|
|
|
it('for lines with coverage', () => {
|
|
const coverageFiles = { files: { [name]: { [line]: 5 } } };
|
|
wrapper = createWrapper({ props, state: { coverageFiles } });
|
|
const coverage = wrapper.find('.line-coverage.right-side');
|
|
|
|
expect(coverage.attributes('title')).toContain('Test coverage: 5 hits');
|
|
expect(coverage.classes('coverage')).toBe(true);
|
|
});
|
|
|
|
it('for lines without coverage', () => {
|
|
const coverageFiles = { files: { [name]: { [line]: 0 } } };
|
|
wrapper = createWrapper({ props, state: { coverageFiles } });
|
|
const coverage = wrapper.find('.line-coverage.right-side');
|
|
|
|
expect(coverage.attributes('title')).toContain('No test coverage');
|
|
expect(coverage.classes('no-coverage')).toBe(true);
|
|
});
|
|
|
|
it('for unknown lines', () => {
|
|
const coverageFiles = {};
|
|
wrapper = createWrapper({ props, state: { coverageFiles } });
|
|
const coverage = wrapper.find('.line-coverage.right-side');
|
|
|
|
expect(coverage.attributes('title')).toBeUndefined();
|
|
expect(coverage.classes('coverage')).toBe(false);
|
|
expect(coverage.classes('no-coverage')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('interoperability', () => {
|
|
it.each`
|
|
desc | line | inline | leftSide | rightSide
|
|
${'with inline and new_line'} | ${{ left: { old_line: 3, new_line: 5, type: 'new' } }} | ${true} | ${{ type: 'new', line: '5', oldLine: '3', newLine: '5' }} | ${null}
|
|
${'with inline and no new_line'} | ${{ left: { old_line: 3, type: 'old' } }} | ${true} | ${{ type: 'old', line: '3', oldLine: '3' }} | ${null}
|
|
${'with parallel and no right side'} | ${{ left: { old_line: 3, new_line: 5 } }} | ${false} | ${{ type: 'old', line: '3', oldLine: '3' }} | ${null}
|
|
${'with parallel and no left side'} | ${{ right: { old_line: 3, new_line: 5 } }} | ${false} | ${null} | ${{ type: 'new', line: '5', newLine: '5' }}
|
|
${'with parallel and right side'} | ${{ left: { old_line: 3 }, right: { new_line: 5 } }} | ${false} | ${{ type: 'old', line: '3', oldLine: '3' }} | ${{ type: 'new', line: '5', newLine: '5' }}
|
|
`('$desc, sets interop data attributes', ({ line, inline, leftSide, rightSide }) => {
|
|
wrapper = createWrapper({ props: { line, inline } });
|
|
|
|
expect(findInteropAttributes(wrapper, '[data-testid="left-side"]')).toEqual(leftSide);
|
|
expect(findInteropAttributes(wrapper, '[data-testid="right-side"]')).toEqual(rightSide);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('coverage state memoization', () => {
|
|
it('updates when coverage is loaded', () => {
|
|
const lineWithoutCoverage = {};
|
|
const lineWithCoverage = {
|
|
text: 'Test coverage: 5 hits',
|
|
class: 'coverage',
|
|
};
|
|
|
|
const unchangedProps = {
|
|
inline: true,
|
|
filePath: 'file/path',
|
|
line: { left: { new_line: 3 } },
|
|
};
|
|
|
|
const noCoverageProps = {
|
|
fileLineCoverage: () => lineWithoutCoverage,
|
|
coverageLoaded: false,
|
|
...unchangedProps,
|
|
};
|
|
const coverageProps = {
|
|
fileLineCoverage: () => lineWithCoverage,
|
|
coverageLoaded: true,
|
|
...unchangedProps,
|
|
};
|
|
|
|
// this caches no coverage for the line
|
|
expect(DiffRow.coverageStateLeft(noCoverageProps)).toStrictEqual(lineWithoutCoverage);
|
|
|
|
// this retrieves coverage for the line because it has been recached
|
|
expect(DiffRow.coverageStateLeft(coverageProps)).toStrictEqual(lineWithCoverage);
|
|
});
|
|
});
|