diff options
author | Felipe Artur <fcardozo@gitlab.com> | 2018-06-21 12:22:40 +0000 |
---|---|---|
committer | Tim Zallmann <tzallmann@gitlab.com> | 2018-06-21 12:22:40 +0000 |
commit | 3e66795ef1ff1228906239763910b051d8afcc37 (patch) | |
tree | df6424d9ec008f5d1da455f8465681b371c4a11e /spec/javascripts/diffs | |
parent | 14e35ac9b19d358d84e0cfd167f74036937285b6 (diff) | |
download | gitlab-ce-3e66795ef1ff1228906239763910b051d8afcc37.tar.gz |
Changes tab VUE refactoring
Diffstat (limited to 'spec/javascripts/diffs')
23 files changed, 2600 insertions, 0 deletions
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js new file mode 100644 index 00000000000..7237274eb43 --- /dev/null +++ b/spec/javascripts/diffs/components/app_spec.js @@ -0,0 +1 @@ +// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 diff --git a/spec/javascripts/diffs/components/changed_files_dropdown_spec.js b/spec/javascripts/diffs/components/changed_files_dropdown_spec.js new file mode 100644 index 00000000000..7237274eb43 --- /dev/null +++ b/spec/javascripts/diffs/components/changed_files_dropdown_spec.js @@ -0,0 +1 @@ +// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 diff --git a/spec/javascripts/diffs/components/changed_files_spec.js b/spec/javascripts/diffs/components/changed_files_spec.js new file mode 100644 index 00000000000..2d57af6137c --- /dev/null +++ b/spec/javascripts/diffs/components/changed_files_spec.js @@ -0,0 +1,100 @@ +import Vue from 'vue'; +import $ from 'jquery'; +import { mountComponentWithStore } from 'spec/helpers'; +import store from '~/diffs/store'; +import ChangedFiles from '~/diffs/components/changed_files.vue'; + +describe('ChangedFiles', () => { + const Component = Vue.extend(ChangedFiles); + const createComponent = props => mountComponentWithStore(Component, { props, store }); + let vm; + + beforeEach(() => { + setFixtures(` + <div id="dummy-element"></div> + <div class="js-tabs-affix"></div> + `); + const props = { + diffFiles: [ + { + addedLines: 10, + removedLines: 20, + blob: { + path: 'some/code.txt', + }, + filePath: 'some/code.txt', + }, + ], + }; + vm = createComponent(props); + }); + + describe('with single file added', () => { + it('shows files changes', () => { + expect(vm.$el).toContainText('1 changed file'); + }); + + it('shows file additions and deletions', () => { + expect(vm.$el).toContainText('10 additions'); + expect(vm.$el).toContainText('20 deletions'); + }); + }); + + describe('template', () => { + describe('diff view mode buttons', () => { + let inlineButton; + let parallelButton; + + beforeEach(() => { + inlineButton = vm.$el.querySelector('.js-inline-diff-button'); + parallelButton = vm.$el.querySelector('.js-parallel-diff-button'); + }); + + it('should have Inline and Side-by-side buttons', () => { + expect(inlineButton).toBeDefined(); + expect(parallelButton).toBeDefined(); + }); + + it('should add active class to Inline button', done => { + vm.$store.state.diffs.diffViewType = 'inline'; + + vm.$nextTick(() => { + expect(inlineButton.classList.contains('active')).toEqual(true); + expect(parallelButton.classList.contains('active')).toEqual(false); + + done(); + }); + }); + + it('should toggle active state of buttons when diff view type changed', done => { + vm.$store.state.diffs.diffViewType = 'parallel'; + + vm.$nextTick(() => { + expect(inlineButton.classList.contains('active')).toEqual(false); + expect(parallelButton.classList.contains('active')).toEqual(true); + + done(); + }); + }); + + describe('clicking them', () => { + it('should toggle the diff view type', done => { + $(parallelButton).click(); + + vm.$nextTick(() => { + expect(inlineButton.classList.contains('active')).toEqual(false); + expect(parallelButton.classList.contains('active')).toEqual(true); + + $(inlineButton).click(); + + vm.$nextTick(() => { + expect(inlineButton.classList.contains('active')).toEqual(true); + expect(parallelButton.classList.contains('active')).toEqual(false); + done(); + }); + }); + }); + }); + }); + }); +}); diff --git a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js new file mode 100644 index 00000000000..7237274eb43 --- /dev/null +++ b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js @@ -0,0 +1 @@ +// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js new file mode 100644 index 00000000000..7237274eb43 --- /dev/null +++ b/spec/javascripts/diffs/components/compare_versions_spec.js @@ -0,0 +1 @@ +// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js new file mode 100644 index 00000000000..7237274eb43 --- /dev/null +++ b/spec/javascripts/diffs/components/diff_content_spec.js @@ -0,0 +1 @@ +// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 diff --git a/spec/javascripts/diffs/components/diff_discussions_spec.js b/spec/javascripts/diffs/components/diff_discussions_spec.js new file mode 100644 index 00000000000..270f363825f --- /dev/null +++ b/spec/javascripts/diffs/components/diff_discussions_spec.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import DiffDiscussions from '~/diffs/components/diff_discussions.vue'; +import store from '~/mr_notes/stores'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import discussionsMockData from '../mock_data/diff_discussions'; + +describe('DiffDiscussions', () => { + let component; + const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)]; + + beforeEach(() => { + component = createComponentWithStore(Vue.extend(DiffDiscussions), store, { + discussions: getDiscussionsMockData(), + }).$mount(); + }); + + describe('template', () => { + it('should have notes list', () => { + const { $el } = component; + + expect($el.querySelectorAll('.discussion .note.timeline-entry').length).toEqual(5); + }); + }); +}); diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js new file mode 100644 index 00000000000..d0f1700bee6 --- /dev/null +++ b/spec/javascripts/diffs/components/diff_file_header_spec.js @@ -0,0 +1,433 @@ +import Vue from 'vue'; +import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +const discussionFixture = 'merge_requests/diff_discussion.json'; + +describe('diff_file_header', () => { + let vm; + let props; + const Component = Vue.extend(DiffFileHeader); + + beforeEach(() => { + const diffDiscussionMock = getJSONFixture(discussionFixture)[0]; + const diffFile = convertObjectPropsToCamelCase(diffDiscussionMock.diff_file, { deep: true }); + props = { + diffFile, + currentUser: {}, + }; + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('icon', () => { + beforeEach(() => { + props.diffFile.blob.icon = 'dummy icon'; + }); + + it('returns the blob icon for files', () => { + props.diffFile.submodule = false; + + vm = mountComponent(Component, props); + + expect(vm.icon).toBe(props.diffFile.blob.icon); + }); + + it('returns the archive icon for submodules', () => { + props.diffFile.submodule = true; + + vm = mountComponent(Component, props); + + expect(vm.icon).toBe('archive'); + }); + }); + + describe('titleLink', () => { + beforeEach(() => { + Object.assign(props.diffFile, { + fileHash: 'badc0ffee', + submoduleLink: 'link://to/submodule', + submoduleTreeUrl: 'some://tree/url', + }); + }); + + it('returns the fileHash for files', () => { + props.diffFile.submodule = false; + + vm = mountComponent(Component, props); + + expect(vm.titleLink).toBe(`#${props.diffFile.fileHash}`); + }); + + it('returns the submoduleTreeUrl for submodules', () => { + props.diffFile.submodule = true; + + vm = mountComponent(Component, props); + + expect(vm.titleLink).toBe(props.diffFile.submoduleTreeUrl); + }); + + it('returns the submoduleLink for submodules without submoduleTreeUrl', () => { + Object.assign(props.diffFile, { + submodule: true, + submoduleTreeUrl: null, + }); + + vm = mountComponent(Component, props); + + expect(vm.titleLink).toBe(props.diffFile.submoduleLink); + }); + }); + + describe('filePath', () => { + beforeEach(() => { + Object.assign(props.diffFile, { + blob: { id: 'b10b1db10b1d' }, + filePath: 'path/to/file', + }); + }); + + it('returns the filePath for files', () => { + props.diffFile.submodule = false; + + vm = mountComponent(Component, props); + + expect(vm.filePath).toBe(props.diffFile.filePath); + }); + + it('appends the truncated blob id for submodules', () => { + props.diffFile.submodule = true; + + vm = mountComponent(Component, props); + + expect(vm.filePath).toBe( + `${props.diffFile.filePath} @ ${props.diffFile.blob.id.substr(0, 8)}`, + ); + }); + }); + + describe('titleTag', () => { + it('returns a link tag if fileHash is set', () => { + props.diffFile.fileHash = 'some hash'; + + vm = mountComponent(Component, props); + + expect(vm.titleTag).toBe('a'); + }); + + it('returns a span tag if fileHash is not set', () => { + props.diffFile.fileHash = null; + + vm = mountComponent(Component, props); + + expect(vm.titleTag).toBe('span'); + }); + }); + + describe('isUsingLfs', () => { + beforeEach(() => { + Object.assign(props.diffFile, { + storedExternally: true, + externalStorage: 'lfs', + }); + }); + + it('returns true if file is stored in LFS', () => { + vm = mountComponent(Component, props); + + expect(vm.isUsingLfs).toBe(true); + }); + + it('returns false if file is not stored externally', () => { + props.diffFile.storedExternally = false; + + vm = mountComponent(Component, props); + + expect(vm.isUsingLfs).toBe(false); + }); + + it('returns false if file is not stored in LFS', () => { + props.diffFile.externalStorage = 'not lfs'; + + vm = mountComponent(Component, props); + + expect(vm.isUsingLfs).toBe(false); + }); + }); + + describe('collapseIcon', () => { + it('returns chevron-down if the diff is expanded', () => { + props.expanded = true; + + vm = mountComponent(Component, props); + + expect(vm.collapseIcon).toBe('chevron-down'); + }); + + it('returns chevron-right if the diff is collapsed', () => { + props.expanded = false; + + vm = mountComponent(Component, props); + + expect(vm.collapseIcon).toBe('chevron-right'); + }); + }); + + describe('isDiscussionsExpanded', () => { + beforeEach(() => { + Object.assign(props, { + discussionsExpanded: true, + expanded: true, + }); + }); + + it('returns true if diff and discussion are expanded', () => { + vm = mountComponent(Component, props); + + expect(vm.isDiscussionsExpanded).toBe(true); + }); + + it('returns false if discussion is collapsed', () => { + props.discussionsExpanded = false; + + vm = mountComponent(Component, props); + + expect(vm.isDiscussionsExpanded).toBe(false); + }); + + it('returns false if diff is collapsed', () => { + props.expanded = false; + + vm = mountComponent(Component, props); + + expect(vm.isDiscussionsExpanded).toBe(false); + }); + }); + + describe('viewFileButtonText', () => { + it('contains the truncated content SHA', () => { + const dummySha = 'deebd00f is no SHA'; + props.diffFile.contentSha = dummySha; + + vm = mountComponent(Component, props); + + expect(vm.viewFileButtonText).not.toContain(dummySha); + expect(vm.viewFileButtonText).toContain(dummySha.substr(0, 8)); + }); + }); + + describe('viewReplacedFileButtonText', () => { + it('contains the truncated base SHA', () => { + const dummySha = 'deadabba sings no more'; + props.diffFile.diffRefs.baseSha = dummySha; + + vm = mountComponent(Component, props); + + expect(vm.viewReplacedFileButtonText).not.toContain(dummySha); + expect(vm.viewReplacedFileButtonText).toContain(dummySha.substr(0, 8)); + }); + }); + }); + + describe('methods', () => { + describe('handleToggle', () => { + beforeEach(() => { + spyOn(vm, '$emit').and.stub(); + }); + + it('emits toggleFile if checkTarget is false', () => { + vm.handleToggle(null, false); + + expect(vm.$emit).toHaveBeenCalledWith('toggleFile'); + }); + + it('emits toggleFile if checkTarget is true and event target is header', () => { + vm.handleToggle({ target: vm.$refs.header }, true); + + expect(vm.$emit).toHaveBeenCalledWith('toggleFile'); + }); + + it('does not emit toggleFile if checkTarget is true and event target is not header', () => { + vm.handleToggle({ target: 'not header' }, true); + + expect(vm.$emit).not.toHaveBeenCalled(); + }); + }); + }); + + describe('template', () => { + describe('collapse toggle', () => { + const collapseToggle = () => vm.$el.querySelector('.diff-toggle-caret'); + + it('is visible if collapsible is true', () => { + props.collapsible = true; + + vm = mountComponent(Component, props); + + expect(collapseToggle()).not.toBe(null); + }); + + it('is hidden if collapsible is false', () => { + props.collapsible = false; + + vm = mountComponent(Component, props); + + expect(collapseToggle()).toBe(null); + }); + }); + + it('displays an icon in the title', () => { + vm = mountComponent(Component, props); + + const icon = vm.$el.querySelector(`i[class="fa fa-fw fa-${vm.icon}"]`); + expect(icon).not.toBe(null); + }); + + describe('file paths', () => { + const filePaths = () => vm.$el.querySelectorAll('.file-title-name'); + + it('displays the path of a added file', () => { + props.diffFile.renamedFile = false; + + vm = mountComponent(Component, props); + + expect(filePaths()).toHaveLength(1); + expect(filePaths()[0]).toHaveText(props.diffFile.filePath); + }); + + it('displays path for deleted file', () => { + props.diffFile.renamedFile = false; + props.diffFile.deletedFile = true; + + vm = mountComponent(Component, props); + + expect(filePaths()).toHaveLength(1); + expect(filePaths()[0]).toHaveText(`${props.diffFile.filePath} deleted`); + }); + + it('displays old and new path if the file was renamed', () => { + props.diffFile.renamedFile = true; + + vm = mountComponent(Component, props); + + expect(filePaths()).toHaveLength(2); + expect(filePaths()[0]).toHaveText(props.diffFile.oldPath); + expect(filePaths()[1]).toHaveText(props.diffFile.newPath); + }); + }); + + it('displays a copy to clipboard button', () => { + vm = mountComponent(Component, props); + + const button = vm.$el.querySelector('.btn-clipboard'); + expect(button).not.toBe(null); + expect(button.dataset.clipboardText).toBe(props.diffFile.filePath); + }); + + describe('file mode', () => { + it('it displays old and new file mode if it changed', () => { + props.diffFile.modeChanged = true; + + vm = mountComponent(Component, props); + + const { fileMode } = vm.$refs; + expect(fileMode).not.toBe(undefined); + expect(fileMode).toContainText(props.diffFile.aMode); + expect(fileMode).toContainText(props.diffFile.bMode); + }); + + it('does not display the file mode if it has not changed', () => { + props.diffFile.modeChanged = false; + + vm = mountComponent(Component, props); + + const { fileMode } = vm.$refs; + expect(fileMode).toBe(undefined); + }); + }); + + describe('LFS label', () => { + const lfsLabel = () => vm.$el.querySelector('.label-lfs'); + + it('displays the LFS label for files stored in LFS', () => { + Object.assign(props.diffFile, { + storedExternally: true, + externalStorage: 'lfs', + }); + + vm = mountComponent(Component, props); + + expect(lfsLabel()).not.toBe(null); + expect(lfsLabel()).toHaveText('LFS'); + }); + + it('does not display the LFS label for files stored in repository', () => { + props.diffFile.storedExternally = false; + + vm = mountComponent(Component, props); + + expect(lfsLabel()).toBe(null); + }); + }); + + describe('edit button', () => { + it('should not render edit button if addMergeRequestButtons is not true', () => { + vm = mountComponent(Component, props); + + expect(vm.$el.querySelector('.js-edit-blob')).toEqual(null); + }); + + it('should show edit button when file is editable', () => { + props.addMergeRequestButtons = true; + props.diffFile.editPath = '/'; + vm = mountComponent(Component, props); + + expect(vm.$el.querySelector('.js-edit-blob')).toContainText('Edit'); + }); + + it('should not show edit button when file is deleted', () => { + props.addMergeRequestButtons = true; + props.diffFile.deletedFile = true; + props.diffFile.editPath = '/'; + vm = mountComponent(Component, props); + + expect(vm.$el.querySelector('.js-edit-blob')).toEqual(null); + }); + }); + + describe('addMergeRequestButtons', () => { + beforeEach(() => { + props.addMergeRequestButtons = true; + props.diffFile.editPath = ''; + }); + + describe('view on environment button', () => { + const url = 'some.external.url/'; + const title = 'url.title'; + + it('displays link to external url', () => { + props.diffFile.externalUrl = url; + props.diffFile.formattedExternalUrl = title; + + vm = mountComponent(Component, props); + + expect(vm.$el.querySelector(`a[href="${url}"]`)).not.toBe(null); + expect(vm.$el.querySelector(`a[data-original-title="View on ${title}"]`)).not.toBe(null); + }); + + it('hides link if no external url', () => { + props.diffFile.externalUrl = ''; + props.diffFile.formattedExternalUrl = title; + + vm = mountComponent(Component, props); + + expect(vm.$el.querySelector(`a[data-original-title="View on ${title}"]`)).toBe(null); + }); + }); + }); + }); +}); diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js new file mode 100644 index 00000000000..1c1edfac68c --- /dev/null +++ b/spec/javascripts/diffs/components/diff_file_spec.js @@ -0,0 +1,88 @@ +import Vue from 'vue'; +import DiffFileComponent from '~/diffs/components/diff_file.vue'; +import store from '~/mr_notes/stores'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import diffFileMockData from '../mock_data/diff_file'; + +describe('DiffFile', () => { + let vm; + const getDiffFileMock = () => Object.assign({}, diffFileMockData); + + beforeEach(() => { + vm = createComponentWithStore(Vue.extend(DiffFileComponent), store, { + file: getDiffFileMock(), + currentUser: {}, + }).$mount(); + }); + + describe('template', () => { + it('should render component with file header, file content components', () => { + const el = vm.$el; + const { fileHash, filePath } = diffFileMockData; + + expect(el.id).toEqual(fileHash); + expect(el.classList.contains('diff-file')).toEqual(true); + expect(el.querySelectorAll('.diff-content.hidden').length).toEqual(0); + expect(el.querySelector('.js-file-title')).toBeDefined(); + expect(el.querySelector('.file-title-name').innerText.indexOf(filePath) > -1).toEqual(true); + expect(el.querySelector('.js-syntax-highlight')).toBeDefined(); + expect(el.querySelectorAll('.line_content').length > 5).toEqual(true); + }); + + describe('collapsed', () => { + it('should not have file content', done => { + expect(vm.$el.querySelectorAll('.diff-content.hidden').length).toEqual(0); + expect(vm.file.collapsed).toEqual(false); + vm.file.collapsed = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelectorAll('.diff-content.hidden').length).toEqual(1); + + done(); + }); + }); + + it('should have collapsed text and link', done => { + vm.file.collapsed = true; + + vm.$nextTick(() => { + expect(vm.$el.innerText).toContain('This diff is collapsed'); + expect(vm.$el.querySelectorAll('.js-click-to-expand').length).toEqual(1); + + done(); + }); + }); + + it('should have loading icon while loading a collapsed diffs', done => { + vm.file.collapsed = true; + vm.isLoadingCollapsedDiff = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelectorAll('.diff-content.loading').length).toEqual(1); + + done(); + }); + }); + }); + }); + + describe('too large diff', () => { + it('should have too large warning and blob link', done => { + const BLOB_LINK = '/file/view/path'; + vm.file.tooLarge = true; + vm.file.viewPath = BLOB_LINK; + + vm.$nextTick(() => { + expect(vm.$el.innerText).toContain( + 'This source diff could not be displayed because it is too large', + ); + expect(vm.$el.querySelector('.js-too-large-diff')).toBeDefined(); + expect(vm.$el.querySelector('.js-too-large-diff a').href.indexOf(BLOB_LINK) > -1).toEqual( + true, + ); + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js b/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js new file mode 100644 index 00000000000..0085a16815a --- /dev/null +++ b/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js @@ -0,0 +1,115 @@ +import Vue from 'vue'; +import DiffGutterAvatarsComponent from '~/diffs/components/diff_gutter_avatars.vue'; +import { COUNT_OF_AVATARS_IN_GUTTER } from '~/diffs/constants'; +import store from '~/mr_notes/stores'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import discussionsMockData from '../mock_data/diff_discussions'; + +describe('DiffGutterAvatars', () => { + let component; + const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)]; + + beforeEach(() => { + component = createComponentWithStore(Vue.extend(DiffGutterAvatarsComponent), store, { + discussions: getDiscussionsMockData(), + }).$mount(); + }); + + describe('computed', () => { + describe('discussionsExpanded', () => { + it('should return true when all discussions are expanded', () => { + expect(component.discussionsExpanded).toEqual(true); + }); + + it('should return false when all discussions are not expanded', () => { + component.discussions[0].expanded = false; + expect(component.discussionsExpanded).toEqual(false); + }); + }); + + describe('allDiscussions', () => { + it('should return an array of notes', () => { + expect(component.allDiscussions).toEqual([...component.discussions[0].notes]); + }); + }); + + describe('notesInGutter', () => { + it('should return a subset of discussions to show in gutter', () => { + expect(component.notesInGutter.length).toEqual(COUNT_OF_AVATARS_IN_GUTTER); + expect(component.notesInGutter[0]).toEqual({ + note: component.discussions[0].notes[0].note, + author: component.discussions[0].notes[0].author, + }); + }); + }); + + describe('moreCount', () => { + it('should return count of remaining discussions from gutter', () => { + expect(component.moreCount).toEqual(2); + }); + }); + + describe('moreText', () => { + it('should return proper text if moreCount > 0', () => { + expect(component.moreText).toEqual('2 more comments'); + }); + + it('should return empty string if there is no discussion', () => { + component.discussions = []; + expect(component.moreText).toEqual(''); + }); + }); + }); + + describe('methods', () => { + describe('getTooltipText', () => { + it('should return original comment if it is shorter than max length', () => { + const note = component.discussions[0].notes[0]; + + expect(component.getTooltipText(note)).toEqual('Administrator: comment 1'); + }); + + it('should return truncated version of comment', () => { + const note = component.discussions[0].notes[1]; + + expect(component.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is r...'); + }); + }); + + describe('toggleDiscussions', () => { + it('should toggle all discussions', () => { + expect(component.discussions[0].expanded).toEqual(true); + + component.$store.dispatch('setInitialNotes', getDiscussionsMockData()); + component.discussions = component.$store.state.notes.discussions; + component.toggleDiscussions(); + + expect(component.discussions[0].expanded).toEqual(false); + component.$store.dispatch('setInitialNotes', []); + }); + }); + }); + + describe('template', () => { + const buttonSelector = '.js-diff-comment-button'; + const svgSelector = `${buttonSelector} svg`; + const avatarSelector = '.js-diff-comment-avatar'; + const plusCountSelector = '.js-diff-comment-plus'; + + it('should have button to collapse discussions when the discussions expanded', () => { + expect(component.$el.querySelector(buttonSelector)).toBeDefined(); + expect(component.$el.querySelector(svgSelector)).toBeDefined(); + }); + + it('should have user avatars when discussions collapsed', () => { + component.discussions[0].expanded = false; + + Vue.nextTick(() => { + expect(component.$el.querySelector(buttonSelector)).toBeNull(); + expect(component.$el.querySelectorAll(avatarSelector).length).toEqual(4); + expect(component.$el.querySelector(plusCountSelector)).toBeDefined(); + expect(component.$el.querySelector(plusCountSelector).textContent).toEqual('+2'); + }); + }); + }); +}); diff --git a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js new file mode 100644 index 00000000000..312a684f4d2 --- /dev/null +++ b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js @@ -0,0 +1,153 @@ +import Vue from 'vue'; +import DiffLineGutterContent from '~/diffs/components/diff_line_gutter_content.vue'; +import store from '~/mr_notes/stores'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import { + MATCH_LINE_TYPE, + CONTEXT_LINE_TYPE, + OLD_NO_NEW_LINE_TYPE, + NEW_NO_NEW_LINE_TYPE, +} from '~/diffs/constants'; +import discussionsMockData from '../mock_data/diff_discussions'; +import diffFileMockData from '../mock_data/diff_file'; + +describe('DiffLineGutterContent', () => { + const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)]; + const getDiffFileMock = () => Object.assign({}, diffFileMockData); + const createComponent = (options = {}) => { + const cmp = Vue.extend(DiffLineGutterContent); + const props = Object.assign({}, options); + props.fileHash = getDiffFileMock().fileHash; + props.contextLinesPath = '/context/lines/path'; + + return createComponentWithStore(cmp, store, props).$mount(); + }; + const setDiscussions = component => { + component.$store.dispatch('setInitialNotes', getDiscussionsMockData()); + }; + + const resetDiscussions = component => { + component.$store.dispatch('setInitialNotes', []); + }; + + describe('computed', () => { + describe('isMatchLine', () => { + it('should return true for match line type', () => { + const component = createComponent({ lineType: MATCH_LINE_TYPE }); + expect(component.isMatchLine).toEqual(true); + }); + + it('should return false for non-match line type', () => { + const component = createComponent({ lineType: CONTEXT_LINE_TYPE }); + expect(component.isMatchLine).toEqual(false); + }); + }); + + describe('isContextLine', () => { + it('should return true for context line type', () => { + const component = createComponent({ lineType: CONTEXT_LINE_TYPE }); + expect(component.isContextLine).toEqual(true); + }); + + it('should return false for non-context line type', () => { + const component = createComponent({ lineType: MATCH_LINE_TYPE }); + expect(component.isContextLine).toEqual(false); + }); + }); + + describe('isMetaLine', () => { + it('should return true for meta line type', () => { + const component = createComponent({ lineType: NEW_NO_NEW_LINE_TYPE }); + expect(component.isMetaLine).toEqual(true); + + const component2 = createComponent({ lineType: OLD_NO_NEW_LINE_TYPE }); + expect(component2.isMetaLine).toEqual(true); + }); + + it('should return false for non-meta line type', () => { + const component = createComponent({ lineType: MATCH_LINE_TYPE }); + expect(component.isMetaLine).toEqual(false); + }); + }); + + describe('lineHref', () => { + it('should prepend # to lineCode', () => { + const lineCode = 'LC_42'; + const component = createComponent({ lineCode }); + expect(component.lineHref).toEqual(`#${lineCode}`); + }); + + it('should return # if there is no lineCode', () => { + const component = createComponent({ lineCode: null }); + expect(component.lineHref).toEqual('#'); + }); + }); + + describe('discussions, hasDiscussions, shouldShowAvatarsOnGutter', () => { + it('should return empty array when there is no discussion', () => { + const component = createComponent({ lineCode: 'LC_42' }); + expect(component.discussions).toEqual([]); + expect(component.hasDiscussions).toEqual(false); + expect(component.shouldShowAvatarsOnGutter).toEqual(false); + }); + + it('should return discussions for the given lineCode', () => { + const lineCode = getDiffFileMock().highlightedDiffLines[1].lineCode; + const component = createComponent({ lineCode, showCommentButton: true }); + + setDiscussions(component); + + expect(component.discussions).toEqual(getDiscussionsMockData()); + expect(component.hasDiscussions).toEqual(true); + expect(component.shouldShowAvatarsOnGutter).toEqual(true); + + resetDiscussions(component); + }); + }); + }); + + describe('template', () => { + it('should render three dots for context lines', () => { + const component = createComponent({ + lineType: MATCH_LINE_TYPE, + }); + + expect(component.$el.querySelector('span').classList.contains('context-cell')).toEqual(true); + expect(component.$el.innerText).toEqual('...'); + }); + + it('should render comment button', () => { + const component = createComponent({ + showCommentButton: true, + }); + Object.defineProperty(component, 'isLoggedIn', { + get() { + return true; + }, + }); + + expect(component.$el.querySelector('.js-add-diff-note-button')).toBeDefined(); + }); + + it('should render line link', () => { + const lineNumber = 42; + const lineCode = `LC_${lineNumber}`; + const component = createComponent({ lineNumber, lineCode }); + const link = component.$el.querySelector('a'); + + expect(link.href.indexOf(`#${lineCode}`) > -1).toEqual(true); + expect(link.dataset.linenumber).toEqual(lineNumber.toString()); + }); + + it('should render user avatars', () => { + const component = createComponent({ + showCommentButton: true, + lineCode: getDiffFileMock().highlightedDiffLines[1].lineCode, + }); + + setDiscussions(component); + expect(component.$el.querySelector('.diff-comment-avatar-holders')).toBeDefined(); + resetDiscussions(component); + }); + }); +}); diff --git a/spec/javascripts/diffs/components/diff_line_note_form_spec.js b/spec/javascripts/diffs/components/diff_line_note_form_spec.js new file mode 100644 index 00000000000..724d1948214 --- /dev/null +++ b/spec/javascripts/diffs/components/diff_line_note_form_spec.js @@ -0,0 +1,68 @@ +import Vue from 'vue'; +import DiffLineNoteForm from '~/diffs/components/diff_line_note_form.vue'; +import store from '~/mr_notes/stores'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import diffFileMockData from '../mock_data/diff_file'; + +describe('DiffLineNoteForm', () => { + let component; + let diffFile; + let diffLines; + const getDiffFileMock = () => Object.assign({}, diffFileMockData); + + beforeEach(() => { + diffFile = getDiffFileMock(); + diffLines = diffFile.highlightedDiffLines; + + component = createComponentWithStore(Vue.extend(DiffLineNoteForm), store, { + diffFile, + diffLines, + line: diffLines[0], + noteTargetLine: diffLines[0], + }).$mount(); + }); + + describe('methods', () => { + describe('handleCancelCommentForm', () => { + it('should call cancelCommentForm with lineCode', () => { + spyOn(component, 'cancelCommentForm'); + component.handleCancelCommentForm(); + + expect(component.cancelCommentForm).toHaveBeenCalledWith({ + lineCode: diffLines[0].lineCode, + }); + }); + }); + + describe('saveNoteForm', () => { + it('should call saveNote action with proper params', done => { + let isPromiseCalled = false; + const formDataSpy = spyOnDependency(DiffLineNoteForm, 'getNoteFormData').and.returnValue({ + postData: 1, + }); + const saveNoteSpy = spyOn(component, 'saveNote').and.returnValue( + new Promise(() => { + isPromiseCalled = true; + done(); + }), + ); + + component.handleSaveNote('note body'); + + expect(formDataSpy).toHaveBeenCalled(); + expect(saveNoteSpy).toHaveBeenCalled(); + expect(isPromiseCalled).toEqual(true); + }); + }); + }); + + describe('template', () => { + it('should have note form', () => { + const { $el } = component; + + expect($el.querySelector('.js-vue-textarea')).toBeDefined(); + expect($el.querySelector('.js-vue-issue-save')).toBeDefined(); + expect($el.querySelector('.js-vue-markdown-field')).toBeDefined(); + }); + }); +}); diff --git a/spec/javascripts/diffs/components/edit_button_spec.js b/spec/javascripts/diffs/components/edit_button_spec.js new file mode 100644 index 00000000000..7237274eb43 --- /dev/null +++ b/spec/javascripts/diffs/components/edit_button_spec.js @@ -0,0 +1 @@ +// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 diff --git a/spec/javascripts/diffs/components/hidden_files_warning_spec.js b/spec/javascripts/diffs/components/hidden_files_warning_spec.js new file mode 100644 index 00000000000..7237274eb43 --- /dev/null +++ b/spec/javascripts/diffs/components/hidden_files_warning_spec.js @@ -0,0 +1 @@ +// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 diff --git a/spec/javascripts/diffs/components/inline_diff_view_spec.js b/spec/javascripts/diffs/components/inline_diff_view_spec.js new file mode 100644 index 00000000000..0d5a3576204 --- /dev/null +++ b/spec/javascripts/diffs/components/inline_diff_view_spec.js @@ -0,0 +1,111 @@ +import Vue from 'vue'; +import InlineDiffView from '~/diffs/components/inline_diff_view.vue'; +import store from '~/mr_notes/stores'; +import * as constants from '~/diffs/constants'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import diffFileMockData from '../mock_data/diff_file'; +import discussionsMockData from '../mock_data/diff_discussions'; + +describe('InlineDiffView', () => { + let component; + const getDiffFileMock = () => Object.assign({}, diffFileMockData); + const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)]; + + beforeEach(() => { + const diffFile = getDiffFileMock(); + + component = createComponentWithStore(Vue.extend(InlineDiffView), store, { + diffFile, + diffLines: diffFile.highlightedDiffLines, + }).$mount(); + }); + + describe('methods', () => { + describe('handleMouse', () => { + it('should set hoveredLineCode', () => { + expect(component.hoveredLineCode).toEqual(null); + + component.handleMouse('lineCode1', true); + expect(component.hoveredLineCode).toEqual('lineCode1'); + + component.handleMouse('lineCode1', false); + expect(component.hoveredLineCode).toEqual(null); + }); + }); + + describe('getLineClass', () => { + it('should return line class object', () => { + const { LINE_HOVER_CLASS_NAME, LINE_UNFOLD_CLASS_NAME } = constants; + const { MATCH_LINE_TYPE, NEW_LINE_TYPE } = constants; + + expect(component.getLineClass(component.diffLines[0])).toEqual({ + [NEW_LINE_TYPE]: NEW_LINE_TYPE, + [LINE_UNFOLD_CLASS_NAME]: false, + [LINE_HOVER_CLASS_NAME]: false, + }); + + component.handleMouse(component.diffLines[0].lineCode, true); + Object.defineProperty(component, 'isLoggedIn', { + get() { + return true; + }, + }); + + expect(component.getLineClass(component.diffLines[0])).toEqual({ + [NEW_LINE_TYPE]: NEW_LINE_TYPE, + [LINE_UNFOLD_CLASS_NAME]: false, + [LINE_HOVER_CLASS_NAME]: true, + }); + + expect(component.getLineClass(component.diffLines[5])).toEqual({ + [MATCH_LINE_TYPE]: MATCH_LINE_TYPE, + [LINE_UNFOLD_CLASS_NAME]: true, + [LINE_HOVER_CLASS_NAME]: false, + }); + }); + }); + }); + + describe('template', () => { + it('should have rendered diff lines', () => { + const el = component.$el; + + expect(el.querySelectorAll('tr.line_holder').length).toEqual(6); + expect(el.querySelectorAll('tr.line_holder.new').length).toEqual(2); + expect(el.querySelectorAll('tr.line_holder.match').length).toEqual(1); + expect(el.textContent.indexOf('Bad dates') > -1).toEqual(true); + }); + + it('should render discussions', done => { + const el = component.$el; + component.$store.dispatch('setInitialNotes', getDiscussionsMockData()); + + Vue.nextTick(() => { + expect(el.querySelectorAll('.notes_holder').length).toEqual(1); + expect(el.querySelectorAll('.notes_holder .note-discussion li').length).toEqual(5); + expect(el.innerText.indexOf('comment 5') > -1).toEqual(true); + component.$store.dispatch('setInitialNotes', []); + + done(); + }); + }); + + it('should render new discussion forms', done => { + const el = component.$el; + const lines = getDiffFileMock().highlightedDiffLines; + + component.handleShowCommentForm({ lineCode: lines[0].lineCode }); + component.handleShowCommentForm({ lineCode: lines[1].lineCode }); + + Vue.nextTick(() => { + expect(el.querySelectorAll('.js-vue-markdown-field').length).toEqual(2); + expect(el.querySelectorAll('tr')[1].classList.contains('notes_holder')).toEqual(true); + expect(el.querySelectorAll('tr')[3].classList.contains('notes_holder')).toEqual(true); + + store.state.diffs.diffLineCommentForms = {}; + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/diffs/components/no_changes_spec.js b/spec/javascripts/diffs/components/no_changes_spec.js new file mode 100644 index 00000000000..7237274eb43 --- /dev/null +++ b/spec/javascripts/diffs/components/no_changes_spec.js @@ -0,0 +1 @@ +// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 diff --git a/spec/javascripts/diffs/components/parallel_diff_view_spec.js b/spec/javascripts/diffs/components/parallel_diff_view_spec.js new file mode 100644 index 00000000000..cab533217c0 --- /dev/null +++ b/spec/javascripts/diffs/components/parallel_diff_view_spec.js @@ -0,0 +1,224 @@ +import Vue from 'vue'; +import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue'; +import store from '~/mr_notes/stores'; +import * as constants from '~/diffs/constants'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import diffFileMockData from '../mock_data/diff_file'; +import discussionsMockData from '../mock_data/diff_discussions'; + +describe('ParallelDiffView', () => { + let component; + const getDiffFileMock = () => Object.assign({}, diffFileMockData); + const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)]; + + beforeEach(() => { + const diffFile = getDiffFileMock(); + + component = createComponentWithStore(Vue.extend(ParallelDiffView), store, { + diffFile, + diffLines: diffFile.parallelDiffLines, + }).$mount(); + }); + + describe('computed', () => { + describe('parallelDiffLines', () => { + it('should normalize lines for empty cells', () => { + expect(component.parallelDiffLines[0].left.type).toEqual(constants.EMPTY_CELL_TYPE); + expect(component.parallelDiffLines[1].left.type).toEqual(constants.EMPTY_CELL_TYPE); + }); + }); + }); + + describe('methods', () => { + describe('hasDiscussion', () => { + it('it should return true if there is a discussion either for left or right section', () => { + Object.defineProperty(component, 'discussionsByLineCode', { + get() { + return { line_42: true }; + }, + }); + + expect(component.hasDiscussion({ left: {}, right: {} })).toEqual(undefined); + expect(component.hasDiscussion({ left: { lineCode: 'line_42' }, right: {} })).toEqual(true); + expect(component.hasDiscussion({ left: {}, right: { lineCode: 'line_42' } })).toEqual(true); + }); + }); + + describe('getClassName', () => { + it('should return line class object', () => { + const { LINE_HOVER_CLASS_NAME, LINE_UNFOLD_CLASS_NAME } = constants; + const { MATCH_LINE_TYPE, NEW_LINE_TYPE, LINE_POSITION_RIGHT } = constants; + + expect(component.getClassName(component.diffLines[1], LINE_POSITION_RIGHT)).toEqual({ + [NEW_LINE_TYPE]: NEW_LINE_TYPE, + [LINE_UNFOLD_CLASS_NAME]: false, + [LINE_HOVER_CLASS_NAME]: false, + }); + + const eventMock = { + target: component.$refs.rightLines[1], + }; + + component.handleMouse(eventMock, component.diffLines[1], true); + Object.defineProperty(component, 'isLoggedIn', { + get() { + return true; + }, + }); + + expect(component.getClassName(component.diffLines[1], LINE_POSITION_RIGHT)).toEqual({ + [NEW_LINE_TYPE]: NEW_LINE_TYPE, + [LINE_UNFOLD_CLASS_NAME]: false, + [LINE_HOVER_CLASS_NAME]: true, + }); + + expect(component.getClassName(component.diffLines[5], LINE_POSITION_RIGHT)).toEqual({ + [MATCH_LINE_TYPE]: MATCH_LINE_TYPE, + [LINE_UNFOLD_CLASS_NAME]: true, + [LINE_HOVER_CLASS_NAME]: false, + }); + }); + }); + + describe('handleMouse', () => { + it('should set hovered line code and line section to null when isHover is false', () => { + const rightLineEventMock = { target: component.$refs.rightLines[1] }; + expect(component.hoveredLineCode).toEqual(null); + expect(component.hoveredSection).toEqual(null); + + component.handleMouse(rightLineEventMock, null, false); + expect(component.hoveredLineCode).toEqual(null); + expect(component.hoveredSection).toEqual(null); + }); + + it('should set hovered line code and line section for right section', () => { + const rightLineEventMock = { target: component.$refs.rightLines[1] }; + component.handleMouse(rightLineEventMock, component.diffLines[1], true); + expect(component.hoveredLineCode).toEqual(component.diffLines[1].right.lineCode); + expect(component.hoveredSection).toEqual(constants.LINE_POSITION_RIGHT); + }); + + it('should set hovered line code and line section for left section', () => { + const leftLineEventMock = { target: component.$refs.leftLines[2] }; + component.handleMouse(leftLineEventMock, component.diffLines[2], true); + expect(component.hoveredLineCode).toEqual(component.diffLines[2].left.lineCode); + expect(component.hoveredSection).toEqual(constants.LINE_POSITION_LEFT); + }); + }); + + describe('shouldRenderDiscussions', () => { + it('should return true if there is a discussion on left side and it is expanded', () => { + const line = { left: { lineCode: 'lineCode1' } }; + spyOn(component, 'isDiscussionExpanded').and.returnValue(true); + Object.defineProperty(component, 'discussionsByLineCode', { + get() { + return { + [line.left.lineCode]: true, + }; + }, + }); + + expect(component.shouldRenderDiscussions(line, constants.LINE_POSITION_LEFT)).toEqual(true); + expect(component.isDiscussionExpanded).toHaveBeenCalledWith(line.left.lineCode); + }); + + it('should return false if there is a discussion on left side but it is collapsed', () => { + const line = { left: { lineCode: 'lineCode1' } }; + spyOn(component, 'isDiscussionExpanded').and.returnValue(false); + Object.defineProperty(component, 'discussionsByLineCode', { + get() { + return { + [line.left.lineCode]: true, + }; + }, + }); + + expect(component.shouldRenderDiscussions(line, constants.LINE_POSITION_LEFT)).toEqual( + false, + ); + }); + + it('should return false for discussions on the right side if there is no line type', () => { + const CUSTOM_RIGHT_LINE_TYPE = 'CUSTOM_RIGHT_LINE_TYPE'; + const line = { right: { lineCode: 'lineCode1', type: CUSTOM_RIGHT_LINE_TYPE } }; + spyOn(component, 'isDiscussionExpanded').and.returnValue(true); + Object.defineProperty(component, 'discussionsByLineCode', { + get() { + return { + [line.right.lineCode]: true, + }; + }, + }); + + expect(component.shouldRenderDiscussions(line, constants.LINE_POSITION_RIGHT)).toEqual( + CUSTOM_RIGHT_LINE_TYPE, + ); + }); + }); + + describe('hasAnyExpandedDiscussion', () => { + const LINE_CODE_LEFT = 'LINE_CODE_LEFT'; + const LINE_CODE_RIGHT = 'LINE_CODE_RIGHT'; + + it('should return true if there is a discussion either on the left or the right side', () => { + const mockLineOne = { + right: { lineCode: LINE_CODE_RIGHT }, + left: {}, + }; + const mockLineTwo = { + left: { lineCode: LINE_CODE_LEFT }, + right: {}, + }; + + spyOn(component, 'isDiscussionExpanded').and.callFake(lc => lc === LINE_CODE_RIGHT); + expect(component.hasAnyExpandedDiscussion(mockLineOne)).toEqual(true); + expect(component.hasAnyExpandedDiscussion(mockLineTwo)).toEqual(false); + }); + }); + }); + + describe('template', () => { + it('should have rendered diff lines', () => { + const el = component.$el; + + expect(el.querySelectorAll('tr.line_holder.parallel').length).toEqual(6); + expect(el.querySelectorAll('td.empty-cell').length).toEqual(4); + expect(el.querySelectorAll('td.line_content.parallel.right-side').length).toEqual(6); + expect(el.querySelectorAll('td.line_content.parallel.left-side').length).toEqual(6); + expect(el.querySelectorAll('td.match').length).toEqual(4); + expect(el.textContent.indexOf('Bad dates') > -1).toEqual(true); + }); + + it('should render discussions', done => { + const el = component.$el; + component.$store.dispatch('setInitialNotes', getDiscussionsMockData()); + + Vue.nextTick(() => { + expect(el.querySelectorAll('.notes_holder').length).toEqual(1); + expect(el.querySelectorAll('.notes_holder .note-discussion li').length).toEqual(5); + expect(el.innerText.indexOf('comment 5') > -1).toEqual(true); + component.$store.dispatch('setInitialNotes', []); + + done(); + }); + }); + + it('should render new discussion forms', done => { + const el = component.$el; + const lines = getDiffFileMock().parallelDiffLines; + + component.handleShowCommentForm({ lineCode: lines[0].lineCode }); + component.handleShowCommentForm({ lineCode: lines[1].lineCode }); + + Vue.nextTick(() => { + expect(el.querySelectorAll('.js-vue-markdown-field').length).toEqual(2); + expect(el.querySelectorAll('tr')[1].classList.contains('notes_holder')).toEqual(true); + expect(el.querySelectorAll('tr')[3].classList.contains('notes_holder')).toEqual(true); + + store.state.diffs.diffLineCommentForms = {}; + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js new file mode 100644 index 00000000000..41d0dfd8939 --- /dev/null +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -0,0 +1,496 @@ +export default { + id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + reply_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + position: { + formatter: { + old_line: null, + new_line: 2, + old_path: 'CHANGELOG', + new_path: 'CHANGELOG', + base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a', + start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962', + head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', + }, + }, + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', + expanded: true, + notes: [ + { + id: 1749, + type: 'DiffNote', + attachment: null, + author: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + created_at: '2018-04-03T21:06:21.521Z', + updated_at: '2018-04-08T08:50:41.762Z', + system: false, + noteable_id: 51, + noteable_type: 'MergeRequest', + noteable_iid: 20, + human_access: 'Owner', + note: 'comment 1', + note_html: '<p dir="auto">comment 1</p>', + last_edited_at: '2018-04-08T08:50:41.762Z', + last_edited_by: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + current_user: { + can_edit: true, + can_award_emoji: true, + }, + resolved: false, + resolvable: true, + resolved_by: null, + discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + emoji_awardable: true, + award_emoji: [], + toggle_award_path: '/gitlab-org/gitlab-test/notes/1749/toggle_award_emoji', + report_abuse_path: + '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1749&user_id=1', + path: '/gitlab-org/gitlab-test/notes/1749', + noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1749', + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + }, + { + id: 1753, + type: 'DiffNote', + attachment: null, + author: { + id: 1, + name: 'Fatih Acet', + username: 'fatihacet', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/fatihacevt', + }, + created_at: '2018-04-08T08:49:35.804Z', + updated_at: '2018-04-08T08:50:45.915Z', + system: false, + noteable_id: 51, + noteable_type: 'MergeRequest', + noteable_iid: 20, + human_access: 'Owner', + note: 'comment 2 is really long one', + note_html: '<p dir="auto">comment 2 is really long one</p>', + last_edited_at: '2018-04-08T08:50:45.915Z', + last_edited_by: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + current_user: { + can_edit: true, + can_award_emoji: true, + }, + resolved: false, + resolvable: true, + resolved_by: null, + discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + emoji_awardable: true, + award_emoji: [], + toggle_award_path: '/gitlab-org/gitlab-test/notes/1753/toggle_award_emoji', + report_abuse_path: + '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1753&user_id=1', + path: '/gitlab-org/gitlab-test/notes/1753', + noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1753', + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + }, + { + id: 1754, + type: 'DiffNote', + attachment: null, + author: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + created_at: '2018-04-08T08:50:48.294Z', + updated_at: '2018-04-08T08:50:48.294Z', + system: false, + noteable_id: 51, + noteable_type: 'MergeRequest', + noteable_iid: 20, + human_access: 'Owner', + note: 'comment 3', + note_html: '<p dir="auto">comment 3</p>', + current_user: { + can_edit: true, + can_award_emoji: true, + }, + resolved: false, + resolvable: true, + resolved_by: null, + discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + emoji_awardable: true, + award_emoji: [], + toggle_award_path: '/gitlab-org/gitlab-test/notes/1754/toggle_award_emoji', + report_abuse_path: + '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1754&user_id=1', + path: '/gitlab-org/gitlab-test/notes/1754', + noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1754', + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + }, + { + id: 1755, + type: 'DiffNote', + attachment: null, + author: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + created_at: '2018-04-08T08:50:50.911Z', + updated_at: '2018-04-08T08:50:50.911Z', + system: false, + noteable_id: 51, + noteable_type: 'MergeRequest', + noteable_iid: 20, + human_access: 'Owner', + note: 'comment 4', + note_html: '<p dir="auto">comment 4</p>', + current_user: { + can_edit: true, + can_award_emoji: true, + }, + resolved: false, + resolvable: true, + resolved_by: null, + discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + emoji_awardable: true, + award_emoji: [], + toggle_award_path: '/gitlab-org/gitlab-test/notes/1755/toggle_award_emoji', + report_abuse_path: + '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1755&user_id=1', + path: '/gitlab-org/gitlab-test/notes/1755', + noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1755', + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + }, + { + id: 1756, + type: 'DiffNote', + attachment: null, + author: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + created_at: '2018-04-08T08:50:53.895Z', + updated_at: '2018-04-08T08:50:53.895Z', + system: false, + noteable_id: 51, + noteable_type: 'MergeRequest', + noteable_iid: 20, + human_access: 'Owner', + note: 'comment 5', + note_html: '<p dir="auto">comment 5</p>', + current_user: { + can_edit: true, + can_award_emoji: true, + }, + resolved: false, + resolvable: true, + resolved_by: null, + discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + emoji_awardable: true, + award_emoji: [], + toggle_award_path: '/gitlab-org/gitlab-test/notes/1756/toggle_award_emoji', + report_abuse_path: + '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1756&user_id=1', + path: '/gitlab-org/gitlab-test/notes/1756', + noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1756', + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + }, + ], + individual_note: false, + resolvable: true, + resolved: false, + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + diff_file: { + submodule: false, + submodule_link: null, + blob: { + id: '9e10516ca50788acf18c518a231914a21e5f16f7', + path: 'CHANGELOG', + name: 'CHANGELOG', + mode: '100644', + readable_text: true, + icon: 'file-text-o', + }, + blob_path: 'CHANGELOG', + blob_name: 'CHANGELOG', + blob_icon: '<i aria-hidden="true" data-hidden="true" class="fa fa-file-text-o fa-fw"></i>', + file_hash: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a', + file_path: 'CHANGELOG', + new_file: false, + deleted_file: false, + renamed_file: false, + old_path: 'CHANGELOG', + new_path: 'CHANGELOG', + mode_changed: false, + a_mode: '100644', + b_mode: '100644', + text: true, + added_lines: 2, + removed_lines: 0, + diff_refs: { + base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a', + start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962', + head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', + }, + content_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', + stored_externally: null, + external_storage: null, + old_path_html: ['CHANGELOG', 'CHANGELOG'], + new_path_html: 'CHANGELOG', + context_lines_path: + '/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff', + highlighted_diff_lines: [ + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1', + type: 'new', + old_line: null, + new_line: 1, + 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, + }, + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', + type: 'new', + old_line: null, + new_line: 2, + text: '<span id="LC2" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC2" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', + type: null, + old_line: 1, + new_line: 3, + text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + meta_data: null, + }, + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4', + type: null, + old_line: 2, + new_line: 4, + text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5', + type: null, + old_line: 3, + new_line: 5, + text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + meta_data: null, + }, + { + line_code: null, + type: 'match', + old_line: null, + new_line: null, + text: '', + rich_text: '', + meta_data: { + old_pos: 3, + new_pos: 5, + }, + }, + { + line_code: null, + type: 'match', + old_line: null, + new_line: null, + text: '', + rich_text: '', + meta_data: { + old_pos: 3, + new_pos: 5, + }, + }, + { + line_code: null, + type: 'match', + old_line: null, + new_line: null, + text: '', + rich_text: '', + meta_data: { + old_pos: 3, + new_pos: 5, + }, + }, + ], + parallel_diff_lines: [ + { + left: null, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1', + type: 'new', + old_line: null, + new_line: 1, + 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, + }, + }, + { + left: null, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', + type: 'new', + old_line: null, + new_line: 2, + text: '<span id="LC2" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC2" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + }, + { + left: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', + type: null, + old_line: 1, + new_line: 3, + text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + meta_data: null, + }, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', + type: null, + old_line: 1, + new_line: 3, + text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + meta_data: null, + }, + }, + { + left: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4', + type: null, + old_line: 2, + new_line: 4, + text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4', + type: null, + old_line: 2, + new_line: 4, + text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + }, + { + left: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5', + type: null, + old_line: 3, + new_line: 5, + text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + meta_data: null, + }, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5', + type: null, + old_line: 3, + new_line: 5, + text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + meta_data: null, + }, + }, + { + left: { + line_code: null, + type: 'match', + old_line: null, + new_line: null, + text: '', + rich_text: '', + meta_data: { + old_pos: 3, + new_pos: 5, + }, + }, + right: { + line_code: null, + type: 'match', + old_line: null, + new_line: null, + text: '', + rich_text: '', + meta_data: { + old_pos: 3, + new_pos: 5, + }, + }, + }, + ], + }, + diff_discussion: true, + truncated_diff_lines: + '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n', + image_diff_html: + '<div class="image js-replaced-image" data="">\n<div class="two-up view">\n<div class="wrap">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<p class="image-info hide">\n<span class="meta-filesize">22.3 KB</span>\n|\n<strong>W:</strong>\n<span class="meta-width"></span>\n|\n<strong>H:</strong>\n<span class="meta-height"></span>\n</p>\n</div>\n<div class="wrap">\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{"base_sha":"e63f41fe459e62e1228fcef60d7189127aeba95a","start_sha":"d9eaefe5a676b820c57ff18cf5b68316025f7962","head_sha":"c48ee0d1bf3b30453f5b32250ce03134beaa6d13","old_path":"CHANGELOG","new_path":"CHANGELOG","position_type":"text","old_line":null,"new_line":2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n<p class="image-info hide">\n<span class="meta-filesize">22.3 KB</span>\n|\n<strong>W:</strong>\n<span class="meta-width"></span>\n|\n<strong>H:</strong>\n<span class="meta-height"></span>\n</p>\n</div>\n</div>\n<div class="swipe view hide">\n<div class="swipe-frame">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<div class="swipe-wrap">\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{"base_sha":"e63f41fe459e62e1228fcef60d7189127aeba95a","start_sha":"d9eaefe5a676b820c57ff18cf5b68316025f7962","head_sha":"c48ee0d1bf3b30453f5b32250ce03134beaa6d13","old_path":"CHANGELOG","new_path":"CHANGELOG","position_type":"text","old_line":null,"new_line":2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n</div>\n<span class="swipe-bar">\n<span class="top-handle"></span>\n<span class="bottom-handle"></span>\n</span>\n</div>\n</div>\n<div class="onion-skin view hide">\n<div class="onion-skin-frame">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{"base_sha":"e63f41fe459e62e1228fcef60d7189127aeba95a","start_sha":"d9eaefe5a676b820c57ff18cf5b68316025f7962","head_sha":"c48ee0d1bf3b30453f5b32250ce03134beaa6d13","old_path":"CHANGELOG","new_path":"CHANGELOG","position_type":"text","old_line":null,"new_line":2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n<div class="controls">\n<div class="transparent"></div>\n<div class="drag-track">\n<div class="dragger" style="left: 0px;"></div>\n</div>\n<div class="opaque"></div>\n</div>\n</div>\n</div>\n</div>\n<div class="view-modes hide">\n<ul class="view-modes-menu">\n<li class="two-up" data-mode="two-up">2-up</li>\n<li class="swipe" data-mode="swipe">Swipe</li>\n<li class="onion-skin" data-mode="onion-skin">Onion skin</li>\n</ul>\n</div>\n', +}; diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js new file mode 100644 index 00000000000..d3bf9525924 --- /dev/null +++ b/spec/javascripts/diffs/mock_data/diff_file.js @@ -0,0 +1,220 @@ +export default { + submodule: false, + submoduleLink: null, + blob: { + id: '9e10516ca50788acf18c518a231914a21e5f16f7', + path: 'CHANGELOG', + name: 'CHANGELOG', + mode: '100644', + readableText: true, + icon: 'file-text-o', + }, + blobPath: 'CHANGELOG', + blobName: 'CHANGELOG', + blobIcon: '<i aria-hidden="true" data-hidden="true" class="fa fa-file-text-o fa-fw"></i>', + fileHash: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a', + filePath: 'CHANGELOG', + newFile: false, + deletedFile: false, + renamedFile: false, + oldPath: 'CHANGELOG', + newPath: 'CHANGELOG', + modeChanged: false, + aMode: '100644', + bMode: '100644', + text: true, + addedLines: 2, + removedLines: 0, + diffRefs: { + baseSha: 'e63f41fe459e62e1228fcef60d7189127aeba95a', + startSha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962', + headSha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', + }, + contentSha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', + storedExternally: null, + externalStorage: null, + oldPathHtml: ['CHANGELOG', 'CHANGELOG'], + newPathHtml: 'CHANGELOG', + editPath: '/gitlab-org/gitlab-test/edit/spooky-stuff/CHANGELOG', + viewPath: '/gitlab-org/gitlab-test/blob/spooky-stuff/CHANGELOG', + replacedViewPath: null, + collapsed: false, + tooLarge: false, + contextLinesPath: + '/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff', + highlightedDiffLines: [ + { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1', + type: 'new', + oldLine: null, + newLine: 1, + text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + richText: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + metaData: null, + }, + { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', + type: 'new', + oldLine: null, + newLine: 2, + text: '+<span id="LC2" class="line" lang="plaintext"></span>\n', + richText: '+<span id="LC2" class="line" lang="plaintext"></span>\n', + metaData: null, + }, + { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', + type: null, + oldLine: 1, + newLine: 3, + text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + richText: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + metaData: null, + }, + { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4', + type: null, + oldLine: 2, + newLine: 4, + text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', + richText: ' <span id="LC4" class="line" lang="plaintext"></span>\n', + metaData: null, + }, + { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5', + type: null, + oldLine: 3, + newLine: 5, + text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + richText: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + metaData: null, + }, + { + lineCode: null, + type: 'match', + oldLine: null, + newLine: null, + text: '', + richText: '', + metaData: { + oldPos: 3, + newPos: 5, + }, + }, + ], + parallelDiffLines: [ + { + left: { + type: 'empty-cell', + }, + right: { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1', + type: 'new', + oldLine: null, + newLine: 1, + text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + richText: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + metaData: null, + }, + }, + { + left: { + type: 'empty-cell', + }, + right: { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', + type: 'new', + oldLine: null, + newLine: 2, + text: '+<span id="LC2" class="line" lang="plaintext"></span>\n', + richText: '<span id="LC2" class="line" lang="plaintext"></span>\n', + metaData: null, + }, + }, + { + left: { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', + type: null, + oldLine: 1, + newLine: 3, + text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + metaData: null, + }, + right: { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', + type: null, + oldLine: 1, + newLine: 3, + text: ' <span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + richText: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + metaData: null, + }, + }, + { + left: { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4', + type: null, + oldLine: 2, + newLine: 4, + text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', + richText: '<span id="LC4" class="line" lang="plaintext"></span>\n', + metaData: null, + }, + right: { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4', + type: null, + oldLine: 2, + newLine: 4, + text: ' <span id="LC4" class="line" lang="plaintext"></span>\n', + richText: '<span id="LC4" class="line" lang="plaintext"></span>\n', + metaData: null, + }, + }, + { + left: { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5', + type: null, + oldLine: 3, + newLine: 5, + text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + metaData: null, + }, + right: { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5', + type: null, + oldLine: 3, + newLine: 5, + text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + richText: '<span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + metaData: null, + }, + }, + { + left: { + lineCode: null, + type: 'match', + oldLine: null, + newLine: null, + text: '', + richText: '', + metaData: { + oldPos: 3, + newPos: 5, + }, + }, + right: { + lineCode: null, + type: 'match', + oldLine: null, + newLine: null, + text: '', + richText: '', + metaData: { + oldPos: 3, + newPos: 5, + }, + }, + }, + ], +}; diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js new file mode 100644 index 00000000000..e61780c9928 --- /dev/null +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -0,0 +1,210 @@ +import MockAdapter from 'axios-mock-adapter'; +import Cookies from 'js-cookie'; +import { + DIFF_VIEW_COOKIE_NAME, + INLINE_DIFF_VIEW_TYPE, + PARALLEL_DIFF_VIEW_TYPE, +} from '~/diffs/constants'; +import store from '~/diffs/store'; +import * as actions from '~/diffs/store/actions'; +import * as types from '~/diffs/store/mutation_types'; +import axios from '~/lib/utils/axios_utils'; +import testAction from '../../helpers/vuex_action_helper'; + +describe('DiffsStoreActions', () => { + describe('setEndpoint', () => { + it('should set given endpoint', done => { + const endpoint = '/diffs/set/endpoint'; + + testAction( + actions.setEndpoint, + endpoint, + { endpoint: '' }, + [{ type: types.SET_ENDPOINT, payload: endpoint }], + [], + done, + ); + }); + }); + + describe('setLoadingState', () => { + it('should set loading state', done => { + expect(store.state.diffs.isLoading).toEqual(true); + const loadingState = false; + + testAction( + actions.setLoadingState, + loadingState, + {}, + [{ type: types.SET_LOADING, payload: loadingState }], + [], + done, + ); + }); + }); + + describe('fetchDiffFiles', () => { + it('should fetch diff files', done => { + const endpoint = '/fetch/diff/files'; + const mock = new MockAdapter(axios); + const res = { diff_files: 1, merge_request_diffs: [] }; + mock.onGet(endpoint).reply(200, res); + + testAction( + actions.fetchDiffFiles, + {}, + { endpoint }, + [ + { type: types.SET_LOADING, payload: true }, + { type: types.SET_LOADING, payload: false }, + { type: types.SET_MERGE_REQUEST_DIFFS, payload: res.merge_request_diffs }, + { type: types.SET_DIFF_DATA, payload: res }, + ], + [], + () => { + mock.restore(); + done(); + }, + ); + }); + }); + + describe('setInlineDiffViewType', () => { + it('should set diff view type to inline and also set the cookie properly', done => { + testAction( + actions.setInlineDiffViewType, + null, + {}, + [{ type: types.SET_DIFF_VIEW_TYPE, payload: INLINE_DIFF_VIEW_TYPE }], + [], + () => { + setTimeout(() => { + expect(Cookies.get('diff_view')).toEqual(INLINE_DIFF_VIEW_TYPE); + done(); + }, 0); + }, + ); + }); + }); + + describe('setParallelDiffViewType', () => { + it('should set diff view type to parallel and also set the cookie properly', done => { + testAction( + actions.setParallelDiffViewType, + null, + {}, + [{ type: types.SET_DIFF_VIEW_TYPE, payload: PARALLEL_DIFF_VIEW_TYPE }], + [], + () => { + setTimeout(() => { + expect(Cookies.get(DIFF_VIEW_COOKIE_NAME)).toEqual(PARALLEL_DIFF_VIEW_TYPE); + done(); + }, 0); + }, + ); + }); + }); + + describe('showCommentForm', () => { + it('should call mutation to show comment form', done => { + const payload = { lineCode: 'lineCode' }; + + testAction( + actions.showCommentForm, + payload, + {}, + [{ type: types.ADD_COMMENT_FORM_LINE, payload }], + [], + done, + ); + }); + }); + + describe('cancelCommentForm', () => { + it('should call mutation to cancel comment form', done => { + const payload = { lineCode: 'lineCode' }; + + testAction( + actions.cancelCommentForm, + payload, + {}, + [{ type: types.REMOVE_COMMENT_FORM_LINE, payload }], + [], + done, + ); + }); + }); + + describe('loadMoreLines', () => { + it('should call mutation to show comment form', done => { + const endpoint = '/diffs/load/more/lines'; + const params = { since: 6, to: 26 }; + const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 }; + const fileHash = 'ff9200'; + const options = { endpoint, params, lineNumbers, fileHash }; + const mock = new MockAdapter(axios); + const contextLines = { contextLines: [{ lineCode: 6 }] }; + mock.onGet(endpoint).reply(200, contextLines); + + testAction( + actions.loadMoreLines, + options, + {}, + [ + { + type: types.ADD_CONTEXT_LINES, + payload: { lineNumbers, contextLines, params, fileHash }, + }, + ], + [], + () => { + mock.restore(); + done(); + }, + ); + }); + }); + + describe('loadCollapsedDiff', () => { + it('should fetch data and call mutation with response and the give parameter', done => { + const file = { hash: 123, loadCollapsedDiffUrl: '/load/collapsed/diff/url' }; + const data = { hash: 123, parallelDiffLines: [{ lineCode: 1 }] }; + const mock = new MockAdapter(axios); + mock.onGet(file.loadCollapsedDiffUrl).reply(200, data); + + testAction( + actions.loadCollapsedDiff, + file, + {}, + [ + { + type: types.ADD_COLLAPSED_DIFFS, + payload: { file, data }, + }, + ], + [], + () => { + mock.restore(); + done(); + }, + ); + }); + }); + + describe('expandAllFiles', () => { + it('should change the collapsed prop from the diffFiles', done => { + testAction( + actions.expandAllFiles, + null, + {}, + [ + { + type: types.EXPAND_ALL_FILES, + }, + ], + [], + done, + ); + }); + }); +}); diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js new file mode 100644 index 00000000000..7945ddea911 --- /dev/null +++ b/spec/javascripts/diffs/store/getters_spec.js @@ -0,0 +1,24 @@ +import getters from '~/diffs/store/getters'; +import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants'; + +describe('DiffsStoreGetters', () => { + describe('isParallelView', () => { + it('should return true if view set to parallel view', () => { + expect(getters.isParallelView({ diffViewType: PARALLEL_DIFF_VIEW_TYPE })).toBeTruthy(); + }); + + it('should return false if view not to parallel view', () => { + expect(getters.isParallelView({ diffViewType: 'foo' })).toBeFalsy(); + }); + }); + + describe('isInlineView', () => { + it('should return true if view set to inline view', () => { + expect(getters.isInlineView({ diffViewType: INLINE_DIFF_VIEW_TYPE })).toBeTruthy(); + }); + + it('should return false if view not to inline view', () => { + expect(getters.isInlineView({ diffViewType: PARALLEL_DIFF_VIEW_TYPE })).toBeFalsy(); + }); + }); +}); diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js new file mode 100644 index 00000000000..5f1a6e9def7 --- /dev/null +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -0,0 +1,147 @@ +import mutations from '~/diffs/store/mutations'; +import * as types from '~/diffs/store/mutation_types'; +import { INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants'; + +describe('DiffsStoreMutations', () => { + describe('SET_ENDPOINT', () => { + it('should set endpoint', () => { + const state = {}; + const endpoint = '/diffs/endpoint'; + + mutations[types.SET_ENDPOINT](state, endpoint); + expect(state.endpoint).toEqual(endpoint); + }); + }); + + describe('SET_LOADING', () => { + it('should set loading state', () => { + const state = {}; + + mutations[types.SET_LOADING](state, false); + expect(state.isLoading).toEqual(false); + }); + }); + + describe('SET_DIFF_FILES', () => { + it('should set diff files to state', () => { + const filePath = '/first-diff-file-path'; + const state = {}; + const diffFiles = { + a_mode: 1, + highlighted_diff_lines: [{ file_path: filePath }], + }; + + mutations[types.SET_DIFF_FILES](state, diffFiles); + expect(state.diffFiles.aMode).toEqual(1); + expect(state.diffFiles.highlightedDiffLines[0].filePath).toEqual(filePath); + }); + }); + + describe('SET_DIFF_VIEW_TYPE', () => { + it('should set diff view type properly', () => { + const state = {}; + + mutations[types.SET_DIFF_VIEW_TYPE](state, INLINE_DIFF_VIEW_TYPE); + expect(state.diffViewType).toEqual(INLINE_DIFF_VIEW_TYPE); + }); + }); + + describe('ADD_COMMENT_FORM_LINE', () => { + it('should set a truthy reference for the given line code in diffLineCommentForms', () => { + const state = { diffLineCommentForms: {} }; + const lineCode = 'FDE'; + + mutations[types.ADD_COMMENT_FORM_LINE](state, { lineCode }); + expect(state.diffLineCommentForms[lineCode]).toBeTruthy(); + }); + }); + + describe('REMOVE_COMMENT_FORM_LINE', () => { + it('should remove given reference from diffLineCommentForms', () => { + const state = { diffLineCommentForms: {} }; + const lineCode = 'FDE'; + + mutations[types.ADD_COMMENT_FORM_LINE](state, { lineCode }); + expect(state.diffLineCommentForms[lineCode]).toBeTruthy(); + + mutations[types.REMOVE_COMMENT_FORM_LINE](state, { lineCode }); + expect(state.diffLineCommentForms[lineCode]).toBeUndefined(); + }); + }); + + describe('EXPAND_ALL_FILES', () => { + it('should change the collapsed prop from diffFiles', () => { + const diffFile = { + collapsed: true, + }; + const state = { expandAllFiles: true, diffFiles: [diffFile] }; + + mutations[types.EXPAND_ALL_FILES](state); + expect(state.diffFiles[0].collapsed).toEqual(false); + }); + }); + + describe('ADD_CONTEXT_LINES', () => { + it('should call utils.addContextLines with proper params', () => { + const options = { + lineNumbers: { oldLineNumber: 1, newLineNumber: 2 }, + contextLines: [{ oldLine: 1 }], + fileHash: 'ff9200', + params: { + bottom: true, + }, + }; + const diffFile = { + fileHash: options.fileHash, + highlightedDiffLines: [], + parallelDiffLines: [], + }; + const state = { diffFiles: [diffFile] }; + const lines = [{ oldLine: 1 }]; + + const findDiffFileSpy = spyOnDependency(mutations, 'findDiffFile').and.returnValue(diffFile); + const removeMatchLineSpy = spyOnDependency(mutations, 'removeMatchLine'); + const lineRefSpy = spyOnDependency(mutations, 'addLineReferences').and.returnValue(lines); + const addContextLinesSpy = spyOnDependency(mutations, 'addContextLines'); + + mutations[types.ADD_CONTEXT_LINES](state, options); + + expect(findDiffFileSpy).toHaveBeenCalledWith(state.diffFiles, options.fileHash); + expect(removeMatchLineSpy).toHaveBeenCalledWith( + diffFile, + options.lineNumbers, + options.params.bottom, + ); + expect(lineRefSpy).toHaveBeenCalledWith( + options.contextLines, + options.lineNumbers, + options.params.bottom, + ); + expect(addContextLinesSpy).toHaveBeenCalledWith({ + inlineLines: diffFile.highlightedDiffLines, + parallelLines: diffFile.parallelDiffLines, + contextLines: options.contextLines, + bottom: options.params.bottom, + lineNumbers: options.lineNumbers, + }); + }); + }); + + describe('ADD_COLLAPSED_DIFFS', () => { + it('should update the state with the given data for the given file hash', () => { + const spy = spyOnDependency(mutations, 'convertObjectPropsToCamelCase').and.callThrough(); + + const fileHash = 123; + const state = { diffFiles: [{}, { fileHash, existingField: 0 }] }; + const file = { fileHash }; + const data = { diff_files: [{ file_hash: fileHash, extra_field: 1, existingField: 1 }] }; + + mutations[types.ADD_COLLAPSED_DIFFS](state, { file, data }); + expect(spy).toHaveBeenCalledWith(data, { deep: true }); + + expect(state.diffFiles[1].fileHash).toEqual(fileHash); + expect(state.diffFiles[1].existingField).toEqual(1); + expect(state.diffFiles[1].extraField).toEqual(1); + }); + }); +}); diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js new file mode 100644 index 00000000000..5a024a0f2ad --- /dev/null +++ b/spec/javascripts/diffs/store/utils_spec.js @@ -0,0 +1,179 @@ +import * as utils from '~/diffs/store/utils'; +import { + LINE_POSITION_LEFT, + LINE_POSITION_RIGHT, + TEXT_DIFF_POSITION_TYPE, + DIFF_NOTE_TYPE, + NEW_LINE_TYPE, + OLD_LINE_TYPE, + MATCH_LINE_TYPE, + PARALLEL_DIFF_VIEW_TYPE, +} from '~/diffs/constants'; +import { MERGE_REQUEST_NOTEABLE_TYPE } from '~/notes/constants'; +import diffFileMockData from '../mock_data/diff_file'; +import { noteableDataMock } from '../../notes/mock_data'; + +const getDiffFileMock = () => Object.assign({}, diffFileMockData); + +describe('DiffsStoreUtils', () => { + describe('findDiffFile', () => { + const files = [{ fileHash: 1, name: 'one' }]; + + it('should return correct file', () => { + expect(utils.findDiffFile(files, 1).name).toEqual('one'); + expect(utils.findDiffFile(files, 2)).toBeUndefined(); + }); + }); + + describe('getReversePosition', () => { + it('should return correct line position name', () => { + expect(utils.getReversePosition(LINE_POSITION_RIGHT)).toEqual(LINE_POSITION_LEFT); + expect(utils.getReversePosition(LINE_POSITION_LEFT)).toEqual(LINE_POSITION_RIGHT); + }); + }); + + describe('findIndexInInlineLines and findIndexInParallelLines', () => { + const expectSet = (method, lines, invalidLines) => { + expect(method(lines, { oldLineNumber: 3, newLineNumber: 5 })).toEqual(4); + expect(method(invalidLines || lines, { oldLineNumber: 32, newLineNumber: 53 })).toEqual(-1); + }; + + describe('findIndexInInlineLines', () => { + it('should return correct index for given line numbers', () => { + expectSet(utils.findIndexInInlineLines, getDiffFileMock().highlightedDiffLines); + }); + }); + + describe('findIndexInParallelLines', () => { + it('should return correct index for given line numbers', () => { + expectSet(utils.findIndexInParallelLines, getDiffFileMock().parallelDiffLines, {}); + }); + }); + }); + + describe('removeMatchLine', () => { + it('should remove match line properly by regarding the bottom parameter', () => { + const diffFile = getDiffFileMock(); + const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 }; + const inlineIndex = utils.findIndexInInlineLines(diffFile.highlightedDiffLines, lineNumbers); + const parallelIndex = utils.findIndexInParallelLines(diffFile.parallelDiffLines, lineNumbers); + const atInlineIndex = diffFile.highlightedDiffLines[inlineIndex]; + const atParallelIndex = diffFile.parallelDiffLines[parallelIndex]; + + utils.removeMatchLine(diffFile, lineNumbers, false); + expect(diffFile.highlightedDiffLines[inlineIndex]).not.toEqual(atInlineIndex); + expect(diffFile.parallelDiffLines[parallelIndex]).not.toEqual(atParallelIndex); + + utils.removeMatchLine(diffFile, lineNumbers, true); + expect(diffFile.highlightedDiffLines[inlineIndex + 1]).not.toEqual(atInlineIndex); + expect(diffFile.parallelDiffLines[parallelIndex + 1]).not.toEqual(atParallelIndex); + }); + }); + + describe('addContextLines', () => { + it('should add context lines properly with bottom parameter', () => { + const diffFile = getDiffFileMock(); + const inlineLines = diffFile.highlightedDiffLines; + const parallelLines = diffFile.parallelDiffLines; + const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 }; + const contextLines = [{ lineNumber: 42 }]; + const options = { inlineLines, parallelLines, contextLines, lineNumbers, bottom: true }; + const inlineIndex = utils.findIndexInInlineLines(diffFile.highlightedDiffLines, lineNumbers); + const parallelIndex = utils.findIndexInParallelLines(diffFile.parallelDiffLines, lineNumbers); + const normalizedParallelLine = { + left: options.contextLines[0], + right: options.contextLines[0], + }; + + utils.addContextLines(options); + expect(inlineLines[inlineLines.length - 1]).toEqual(contextLines[0]); + expect(parallelLines[parallelLines.length - 1]).toEqual(normalizedParallelLine); + + delete options.bottom; + utils.addContextLines(options); + expect(inlineLines[inlineIndex]).toEqual(contextLines[0]); + expect(parallelLines[parallelIndex]).toEqual(normalizedParallelLine); + }); + }); + + describe('getNoteFormData', () => { + it('should properly create note form data', () => { + const diffFile = getDiffFileMock(); + noteableDataMock.targetType = MERGE_REQUEST_NOTEABLE_TYPE; + + const options = { + note: 'Hello world!', + noteableData: noteableDataMock, + noteableType: MERGE_REQUEST_NOTEABLE_TYPE, + diffFile, + noteTargetLine: { + lineCode: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', + metaData: null, + newLine: 3, + oldLine: 1, + }, + diffViewType: PARALLEL_DIFF_VIEW_TYPE, + linePosition: LINE_POSITION_LEFT, + }; + + const position = JSON.stringify({ + base_sha: diffFile.diffRefs.baseSha, + start_sha: diffFile.diffRefs.startSha, + head_sha: diffFile.diffRefs.headSha, + old_path: diffFile.oldPath, + new_path: diffFile.newPath, + position_type: TEXT_DIFF_POSITION_TYPE, + old_line: options.noteTargetLine.oldLine, + new_line: options.noteTargetLine.newLine, + }); + + const postData = { + view: options.diffViewType, + line_type: options.linePosition === LINE_POSITION_RIGHT ? NEW_LINE_TYPE : OLD_LINE_TYPE, + merge_request_diff_head_sha: diffFile.diffRefs.headSha, + in_reply_to_discussion_id: '', + note_project_id: '', + target_type: options.noteableType, + target_id: options.noteableData.id, + note: { + noteable_type: options.noteableType, + noteable_id: options.noteableData.id, + commit_id: '', + type: DIFF_NOTE_TYPE, + line_code: options.noteTargetLine.lineCode, + note: options.note, + position, + }, + }; + + expect(utils.getNoteFormData(options)).toEqual({ + endpoint: options.noteableData.create_note_path, + data: postData, + }); + }); + }); + + describe('addLineReferences', () => { + const lineNumbers = { oldLineNumber: 3, newLineNumber: 4 }; + + it('should add correct line references when bottom set to true', () => { + const lines = [{ type: null }, { type: MATCH_LINE_TYPE }]; + const linesWithReferences = utils.addLineReferences(lines, lineNumbers, true); + + expect(linesWithReferences[0].oldLine).toEqual(lineNumbers.oldLineNumber + 1); + expect(linesWithReferences[0].newLine).toEqual(lineNumbers.newLineNumber + 1); + expect(linesWithReferences[1].metaData.oldPos).toEqual(4); + expect(linesWithReferences[1].metaData.newPos).toEqual(5); + }); + + it('should add correct line references when bottom falsy', () => { + const lines = [{ type: null }, { type: MATCH_LINE_TYPE }, { type: null }]; + const linesWithReferences = utils.addLineReferences(lines, lineNumbers); + + expect(linesWithReferences[0].oldLine).toEqual(0); + expect(linesWithReferences[0].newLine).toEqual(1); + expect(linesWithReferences[1].metaData.oldPos).toEqual(2); + expect(linesWithReferences[1].metaData.newPos).toEqual(3); + }); + }); +}); |