import { mount } from '@vue/test-utils'; import { TEST_HOST } from 'helpers/test_constants'; import { trimText } from 'helpers/text_helper'; import { getTimeago } from '~/lib/utils/datetime_utility'; import Component from '~/diffs/components/commit_item.vue'; import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; import getDiffWithCommit from '../mock_data/diff_with_commit'; jest.mock('~/user_popovers'); const TEST_AUTHOR_NAME = 'test'; const TEST_AUTHOR_EMAIL = 'test+test@gitlab.com'; const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=40`; const TEST_SIGNATURE_HTML = 'Legit commit'; const TEST_PIPELINE_STATUS_PATH = `${TEST_HOST}/pipeline/status`; const NEXT_COMMIT_URL = `${TEST_HOST}/?commit_id=next`; const PREV_COMMIT_URL = `${TEST_HOST}/?commit_id=prev`; describe('diffs/components/commit_item', () => { let wrapper; const timeago = getTimeago(); const { commit } = getDiffWithCommit(); const getTitleElement = () => wrapper.find('.commit-row-message.item-title'); const getDescElement = () => wrapper.find('pre.commit-row-description'); const getDescExpandElement = () => wrapper.find('.commit-content .text-expander.js-toggle-button'); const getShaElement = () => wrapper.find('.commit-sha-group'); const getAvatarElement = () => wrapper.find('.user-avatar-link'); const getCommitterElement = () => wrapper.find('.committer'); const getCommitActionsElement = () => wrapper.find('.commit-actions'); const getCommitPipelineStatus = () => wrapper.find(CommitPipelineStatus); const getCommitNavButtonsElement = () => wrapper.find('.commit-nav-buttons'); const getNextCommitNavElement = () => getCommitNavButtonsElement().find('.btn-group > *:last-child'); const getPrevCommitNavElement = () => getCommitNavButtonsElement().find('.btn-group > *:first-child'); const mountComponent = (propsData, featureFlags = {}) => { wrapper = mount(Component, { propsData: { commit, ...propsData, }, provide: { glFeatures: { mrCommitNeighborNav: true, ...featureFlags, }, }, stubs: { CommitPipelineStatus: true, }, }); }; afterEach(() => { wrapper.destroy(); wrapper = null; }); describe('default state', () => { beforeEach(() => { mountComponent(); }); it('renders commit title', () => { const titleElement = getTitleElement(); expect(titleElement.attributes('href')).toBe(commit.commit_url); expect(titleElement.text()).toBe(commit.title_html); }); it('renders commit description', () => { const descElement = getDescElement(); const descExpandElement = getDescExpandElement(); const expected = commit.description_html.replace(/ /g, ''); expect(trimText(descElement.text())).toEqual(trimText(expected)); expect(descExpandElement.exists()).toBe(true); }); it('renders commit sha', () => { const shaElement = getShaElement(); const labelElement = shaElement.find('.label'); const buttonElement = shaElement.find('button'); expect(labelElement.text()).toEqual(commit.short_id); expect(buttonElement.props('text')).toBe(commit.id); }); it('renders author avatar', () => { const avatarElement = getAvatarElement(); const imgElement = avatarElement.find('img'); expect(avatarElement.attributes('href')).toBe(commit.author.web_url); expect(imgElement.classes()).toContain('s40'); expect(imgElement.attributes('alt')).toBe(commit.author.name); expect(imgElement.attributes('src')).toBe(commit.author.avatar_url); }); it('renders committer text', () => { const committerElement = getCommitterElement(); const nameElement = committerElement.find('a'); const expectTimeText = timeago.format(commit.authored_date); const expectedText = `${commit.author.name} authored ${expectTimeText}`; expect(trimText(committerElement.text())).toEqual(expectedText); expect(nameElement.attributes('href')).toBe(commit.author.web_url); expect(nameElement.text()).toBe(commit.author.name); expect(nameElement.classes()).toContain('js-user-link'); expect(nameElement.attributes('data-user-id')).toEqual(commit.author.id.toString()); }); }); describe('without commit description', () => { beforeEach(() => { mountComponent({ commit: { ...commit, description_html: '' } }); }); it('hides description', () => { const descElement = getDescElement(); const descExpandElement = getDescExpandElement(); expect(descElement.exists()).toBeFalsy(); expect(descExpandElement.exists()).toBeFalsy(); }); }); describe('with no matching user', () => { beforeEach(() => { mountComponent({ commit: { ...commit, author: null, author_email: TEST_AUTHOR_EMAIL, author_name: TEST_AUTHOR_NAME, author_gravatar_url: TEST_AUTHOR_GRAVATAR, }, }); }); it('renders author avatar', () => { const avatarElement = getAvatarElement(); const imgElement = avatarElement.find('img'); expect(avatarElement.attributes('href')).toBe(`mailto:${TEST_AUTHOR_EMAIL}`); expect(imgElement.attributes('alt')).toBe(TEST_AUTHOR_NAME); expect(imgElement.attributes('src')).toBe(TEST_AUTHOR_GRAVATAR); }); it('renders committer text', () => { const committerElement = getCommitterElement(); const nameElement = committerElement.find('a'); expect(nameElement.attributes('href')).toBe(`mailto:${TEST_AUTHOR_EMAIL}`); expect(nameElement.text()).toBe(TEST_AUTHOR_NAME); }); }); describe('with signature', () => { beforeEach(() => { mountComponent({ commit: { ...commit, signature_html: TEST_SIGNATURE_HTML }, }); }); it('renders signature html', () => { const actionsElement = getCommitActionsElement(); expect(actionsElement.html()).toContain(TEST_SIGNATURE_HTML); }); }); describe('with pipeline status', () => { beforeEach(() => { mountComponent({ commit: { ...commit, pipeline_status_path: TEST_PIPELINE_STATUS_PATH }, }); }); it('renders pipeline status', () => { expect(getCommitPipelineStatus().exists()).toBe(true); }); }); describe('without neighbor commits', () => { beforeEach(() => { mountComponent({ commit: { ...commit, prev_commit_id: null, next_commit_id: null } }); }); it('does not render any navigation buttons', () => { expect(getCommitNavButtonsElement().exists()).toEqual(false); }); }); describe('with neighbor commits', () => { let mrCommit; beforeEach(() => { mrCommit = { ...commit, next_commit_id: 'next', prev_commit_id: 'prev', }; mountComponent({ commit: mrCommit }); }); it('renders the commit navigation buttons', () => { expect(getCommitNavButtonsElement().exists()).toEqual(true); mountComponent({ commit: { ...mrCommit, next_commit_id: null }, }); expect(getCommitNavButtonsElement().exists()).toEqual(true); mountComponent({ commit: { ...mrCommit, prev_commit_id: null }, }); expect(getCommitNavButtonsElement().exists()).toEqual(true); }); it('does not render the commit navigation buttons if the `mrCommitNeighborNav` feature flag is disabled', () => { mountComponent({ commit: mrCommit }, { mrCommitNeighborNav: false }); expect(getCommitNavButtonsElement().exists()).toEqual(false); }); describe('prev commit', () => { const { location } = window; beforeAll(() => { delete window.location; window.location = { href: `${TEST_HOST}?commit_id=${mrCommit.id}` }; }); beforeEach(() => { jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {}); }); afterAll(() => { window.location = location; }); it('uses the correct href', () => { const link = getPrevCommitNavElement(); expect(link.element.getAttribute('href')).toEqual(PREV_COMMIT_URL); }); it('triggers the correct Vuex action on click', () => { const link = getPrevCommitNavElement(); link.trigger('click'); return wrapper.vm.$nextTick().then(() => { expect(wrapper.vm.moveToNeighboringCommit).toHaveBeenCalledWith({ direction: 'previous', }); }); }); it('renders a disabled button when there is no prev commit', () => { mountComponent({ commit: { ...mrCommit, prev_commit_id: null } }); const button = getPrevCommitNavElement(); expect(button.element.tagName).toEqual('BUTTON'); expect(button.element.hasAttribute('disabled')).toEqual(true); }); }); describe('next commit', () => { const { location } = window; beforeAll(() => { delete window.location; window.location = { href: `${TEST_HOST}?commit_id=${mrCommit.id}` }; }); beforeEach(() => { jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {}); }); afterAll(() => { window.location = location; }); it('uses the correct href', () => { const link = getNextCommitNavElement(); expect(link.element.getAttribute('href')).toEqual(NEXT_COMMIT_URL); }); it('triggers the correct Vuex action on click', () => { const link = getNextCommitNavElement(); link.trigger('click'); return wrapper.vm.$nextTick().then(() => { expect(wrapper.vm.moveToNeighboringCommit).toHaveBeenCalledWith({ direction: 'next' }); }); }); it('renders a disabled button when there is no next commit', () => { mountComponent({ commit: { ...mrCommit, next_commit_id: null } }); const button = getNextCommitNavElement(); expect(button.element.tagName).toEqual('BUTTON'); expect(button.element.hasAttribute('disabled')).toEqual(true); }); }); }); });