import { shallowMount } from '@vue/test-utils'; import { TEST_HOST } from 'helpers/test_constants'; import { createStore } from '~/mr_notes/stores'; 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'; const TEST_USER_ID = 'abc123'; const TEST_USER = { id: TEST_USER_ID }; describe('InlineDiffTableRow', () => { let wrapper; let store; const thisLine = diffFileMockData.highlighted_diff_lines[0]; const createComponent = (props = {}, propsStore = store) => { wrapper = shallowMount(InlineDiffTableRow, { store: propsStore, propsData: { line: thisLine, fileHash: diffFileMockData.file_hash, filePath: diffFileMockData.file_path, contextLinesPath: 'contextLinesPath', isHighlighted: false, ...props, }, }); }; const setWindowLocation = value => { Object.defineProperty(window, 'location', { writable: true, value, }); }; beforeEach(() => { store = createStore(); store.state.notes.userData = TEST_USER; }); afterEach(() => { wrapper.destroy(); }); it('does not add hll class to line content when line does not match highlighted row', () => { createComponent(); expect(wrapper.find('.line_content').classes('hll')).toBe(false); }); it('adds hll class to lineContent when line is the highlighted row', () => { store.state.diffs.highlightedRow = thisLine.line_code; createComponent({}, store); expect(wrapper.find('.line_content').classes('hll')).toBe(true); }); it('adds hll class to lineContent when line is part of a multiline comment', () => { createComponent({ isCommented: true }); expect(wrapper.find('.line_content').classes('hll')).toBe(true); }); describe('sets coverage title and class', () => { it('for lines with coverage', () => { const name = diffFileMockData.file_path; const line = thisLine.new_line; store.state.diffs.coverageFiles = { files: { [name]: { [line]: 5 } } }; createComponent({}, store); const coverage = wrapper.find('.line-coverage'); expect(coverage.attributes('title')).toContain('Test coverage: 5 hits'); expect(coverage.classes('coverage')).toBe(true); }); it('for lines without coverage', () => { const name = diffFileMockData.file_path; const line = thisLine.new_line; store.state.diffs.coverageFiles = { files: { [name]: { [line]: 0 } } }; createComponent({}, store); const coverage = wrapper.find('.line-coverage'); expect(coverage.attributes('title')).toContain('No test coverage'); expect(coverage.classes('no-coverage')).toBe(true); }); it('for unknown lines', () => { store.state.diffs.coverageFiles = {}; createComponent({}, store); const coverage = wrapper.find('.line-coverage'); expect(coverage.attributes('title')).toBeUndefined(); expect(coverage.classes('coverage')).toBe(false); expect(coverage.classes('no-coverage')).toBe(false); }); }); describe('Table Cells', () => { const findNewTd = () => wrapper.find({ ref: 'newTd' }); const findOldTd = () => wrapper.find({ ref: 'oldTd' }); describe('td', () => { it('highlights when isHighlighted true', () => { store.state.diffs.highlightedRow = thisLine.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: 'addDiffNoteButton' }); it.each` userData | query | mergeRefHeadComments | expectation ${TEST_USER} | ${'diff_head=false'} | ${false} | ${true} ${TEST_USER} | ${'diff_head=true'} | ${true} | ${true} ${TEST_USER} | ${'diff_head=true'} | ${false} | ${false} ${null} | ${''} | ${true} | ${false} `( 'exists is $expectation - with userData ($userData) query ($query)', ({ userData, query, mergeRefHeadComments, expectation }) => { store.state.notes.userData = userData; gon.features = { mergeRefHeadComments }; setWindowLocation({ href: `${TEST_HOST}?${query}` }); createComponent({}, store); expect(findNoteButton().exists()).toBe(expectation); }, ); it.each` isHover | line | expectation ${true} | ${{ ...thisLine, discussions: [] }} | ${true} ${false} | ${{ ...thisLine, discussions: [] }} | ${false} ${true} | ${{ ...thisLine, type: 'context', discussions: [] }} | ${false} ${true} | ${{ ...thisLine, type: 'old-nonewline', discussions: [] }} | ${false} ${true} | ${{ ...thisLine, discussions: [{}] }} | ${false} `('visible is $expectation - line ($line)', ({ isHover, line, expectation }) => { createComponent({ line }); wrapper.setData({ isHover }); return wrapper.vm.$nextTick().then(() => { 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 }) => { createComponent({ line: { ...thisLine, commentsDisabled }, }); wrapper.setData({ isHover: true }); return wrapper.vm.$nextTick().then(() => { 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: 'addNoteTooltip' }); 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 }) => { createComponent({ line: { ...thisLine, commentsDisabled }, }); wrapper.setData({ isHover: true }); return wrapper.vm.$nextTick().then(() => { 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(false); 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} ${{ line_code: undefined, left: { line_code: TEST_LINE_CODE }, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${'#'} | ${TEST_LINE_CODE} ${{ line_code: undefined, right: { line_code: TEST_LINE_CODE }, new_line: TEST_LINE_NUMBER }} | ${findLineNumberNew} | ${'#'} | ${TEST_LINE_CODE} `( 'with line ($lineProps)', ({ lineProps, findLineNumber, expectedHref, expectedClickArg }) => { beforeEach(() => { jest.spyOn(store, 'dispatch').mockImplementation(); createComponent({ line: { ...thisLine, ...lineProps }, }); }); 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 = { line_code: TEST_LINE_CODE, type: 'new', old_line: null, new_line: 1, discussions: [{ ...discussionsMockData }], discussionsExpanded: true, text: '+ - Bad dates\n', rich_text: '+ - Bad dates\n', meta_data: null, }; }); describe('with showCommentButton', () => { it('renders if line has discussions', () => { createComponent({ line }); expect(findAvatars().props()).toEqual({ discussions: line.discussions, discussionsExpanded: line.discussionsExpanded, }); }); it('does notrender if line has no discussions', () => { line.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.discussionsExpanded, }); }); }); }); }); });