From ed3034bbb71d43b12944a9da29b5264cb3ff3312 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Thu, 13 Dec 2018 19:17:19 +0000 Subject: Allow suggesting single line changes in diffs --- .../diffs/components/diff_content_spec.js | 1 + .../diffs/mock_data/diff_discussions.js | 15 ++- .../vue_shared/components/markdown/header_spec.js | 15 +++ .../markdown/suggestion_diff_header_spec.js | 69 ++++++++++++ .../components/markdown/suggestion_diff_spec.js | 79 +++++++++++++ .../components/markdown/suggestions_spec.js | 125 +++++++++++++++++++++ 6 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js create mode 100644 spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js create mode 100644 spec/javascripts/vue_shared/components/markdown/suggestions_spec.js (limited to 'spec/javascripts') diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js index c25f6167163..d688560a260 100644 --- a/spec/javascripts/diffs/components/diff_content_spec.js +++ b/spec/javascripts/diffs/components/diff_content_spec.js @@ -17,6 +17,7 @@ describe('DiffContent', () => { current_user: { can_create_note: false, }, + preview_note_path: 'path/to/preview', }; vm = mountComponentWithStore(Component, { diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js index 44313caba29..c1e9f791925 100644 --- a/spec/javascripts/diffs/mock_data/diff_discussions.js +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -487,8 +487,19 @@ export default { ], }, diff_discussion: true, - truncated_diff_lines: - '\n\n \n\n\n1\n\n - Bad dates\n\n\n\n\n \n\n\n2\n\n\n\n\n', + truncated_diff_lines: [ + { + text: 'line', + rich_text: + '\n\n \n\n\n1\n\n - Bad dates\n\n\n\n\n \n\n\n2\n\n\n\n\n', + can_receive_suggestion: true, + line_code: '6f209374f7e565f771b95720abf46024c41d1885_1_1', + type: 'new', + old_line: null, + new_line: 1, + meta_data: null, + }, + ], }; export const imageDiffDiscussions = [ diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js index 59613faa49f..e733a95288e 100644 --- a/spec/javascripts/vue_shared/components/markdown/header_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js @@ -28,6 +28,7 @@ describe('Markdown field header component', () => { 'Add a numbered list', 'Add a task list', 'Add a table', + 'Insert suggestion', 'Go full screen', ]; const elements = vm.$el.querySelectorAll('.toolbar-btn'); @@ -93,4 +94,18 @@ describe('Markdown field header component', () => { '| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |', ); }); + + it('renders suggestion template', () => { + vm.lineContent = 'Some content'; + + expect(vm.mdSuggestion).toEqual('```suggestion\n{text}\n```'); + }); + + it('does not render suggestion button if `canSuggest` is set to false', () => { + vm.canSuggest = false; + + Vue.nextTick(() => { + expect(vm.$el.querySelector('.qa-suggestion-btn')).toBe(null); + }); + }); }); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js new file mode 100644 index 00000000000..8187b3204b1 --- /dev/null +++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js @@ -0,0 +1,69 @@ +import Vue from 'vue'; +import SuggestionDiffHeaderComponent from '~/vue_shared/components/markdown/suggestion_diff_header.vue'; + +const MOCK_DATA = { + canApply: true, + isApplied: false, + helpPagePath: 'path_to_docs', +}; + +describe('Suggestion Diff component', () => { + let vm; + + function createComponent(propsData) { + const Component = Vue.extend(SuggestionDiffHeaderComponent); + + return new Component({ + propsData, + }).$mount(); + } + + beforeEach(done => { + vm = createComponent(MOCK_DATA); + Vue.nextTick(done); + }); + + describe('init', () => { + it('renders a suggestion header', () => { + const header = vm.$el.querySelector('.qa-suggestion-diff-header'); + + expect(header).not.toBeNull(); + expect(header.innerHTML.includes('Suggested change')).toBe(true); + }); + + it('renders an apply button', () => { + const applyBtn = vm.$el.querySelector('.qa-apply-btn'); + + expect(applyBtn).not.toBeNull(); + expect(applyBtn.innerHTML.includes('Apply suggestion')).toBe(true); + }); + + it('does not render an apply button if `canApply` is set to false', () => { + const props = Object.assign(MOCK_DATA, { canApply: false }); + + vm = createComponent(props); + + expect(vm.$el.querySelector('.qa-apply-btn')).toBeNull(); + }); + }); + + describe('applySuggestion', () => { + it('emits when the apply button is clicked', () => { + const props = Object.assign(MOCK_DATA, { canApply: true }); + + vm = createComponent(props); + spyOn(vm, '$emit'); + vm.applySuggestion(); + + expect(vm.$emit).toHaveBeenCalled(); + }); + + it('does not emit when the canApply is set to false', () => { + spyOn(vm, '$emit'); + vm.canApply = false; + vm.applySuggestion(); + + expect(vm.$emit).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js new file mode 100644 index 00000000000..d4ed8f2f7a4 --- /dev/null +++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js @@ -0,0 +1,79 @@ +import Vue from 'vue'; +import SuggestionDiffComponent from '~/vue_shared/components/markdown/suggestion_diff.vue'; + +const MOCK_DATA = { + canApply: true, + newLines: [ + { content: 'Line 1\n', lineNumber: 1 }, + { content: 'Line 2\n', lineNumber: 2 }, + { content: 'Line 3\n', lineNumber: 3 }, + ], + fromLine: 1, + fromContent: 'Old content', + suggestion: { + id: 1, + }, + helpPagePath: 'path_to_docs', +}; + +describe('Suggestion Diff component', () => { + let vm; + + beforeEach(done => { + const Component = Vue.extend(SuggestionDiffComponent); + + vm = new Component({ + propsData: MOCK_DATA, + }).$mount(); + + Vue.nextTick(done); + }); + + describe('init', () => { + it('renders a suggestion header', () => { + expect(vm.$el.querySelector('.qa-suggestion-diff-header')).not.toBeNull(); + }); + + it('renders a diff table', () => { + expect(vm.$el.querySelector('table.md-suggestion-diff')).not.toBeNull(); + }); + + it('renders the oldLineNumber', () => { + const fromLine = vm.$el.querySelector('.qa-old-diff-line-number').innerHTML; + + expect(parseInt(fromLine, 10)).toBe(vm.fromLine); + }); + + it('renders the oldLineContent', () => { + const fromContent = vm.$el.querySelector('.line_content.old').innerHTML; + + expect(fromContent.includes(vm.fromContent)).toBe(true); + }); + + it('renders the contents of newLines', () => { + const newLines = vm.$el.querySelectorAll('.line_holder.new'); + + newLines.forEach((line, i) => { + expect(newLines[i].innerHTML.includes(vm.newLines[i].content)).toBe(true); + }); + }); + + it('renders a line number for each line', () => { + const newLineNumbers = vm.$el.querySelectorAll('.qa-new-diff-line-number'); + + newLineNumbers.forEach((line, i) => { + expect(newLineNumbers[i].innerHTML.includes(vm.newLines[i].lineNumber)).toBe(true); + }); + }); + }); + + describe('applySuggestion', () => { + it('emits apply event when applySuggestion is called', () => { + const callback = () => {}; + spyOn(vm, '$emit'); + vm.applySuggestion(callback); + + expect(vm.$emit).toHaveBeenCalledWith('apply', { suggestionId: vm.suggestion.id, callback }); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js new file mode 100644 index 00000000000..ab1b747c360 --- /dev/null +++ b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js @@ -0,0 +1,125 @@ +import Vue from 'vue'; +import SuggestionsComponent from '~/vue_shared/components/markdown/suggestions.vue'; + +const MOCK_DATA = { + fromLine: 1, + fromContent: 'Old content', + suggestions: [], + noteHtml: ` +
+
Suggestion 1
+
+ +
+
Suggestion 2
+
+ `, + isApplied: false, + helpPagePath: 'path_to_docs', +}; + +const generateLine = content => { + const line = document.createElement('div'); + line.className = 'line'; + line.innerHTML = content; + + return line; +}; + +const generateMockLines = () => { + const line1 = generateLine('Line 1'); + const line2 = generateLine('Line 2'); + const line3 = generateLine('Line 3'); + const container = document.createElement('div'); + + container.appendChild(line1); + container.appendChild(line2); + container.appendChild(line3); + + return container; +}; + +describe('Suggestion component', () => { + let vm; + let extractedLines; + let diffTable; + + beforeEach(done => { + const Component = Vue.extend(SuggestionsComponent); + + vm = new Component({ + propsData: MOCK_DATA, + }).$mount(); + + extractedLines = vm.extractNewLines(generateMockLines()); + diffTable = vm.generateDiff(extractedLines).$mount().$el; + + spyOn(vm, 'renderSuggestions'); + vm.renderSuggestions(); + Vue.nextTick(done); + }); + + describe('mounted', () => { + it('renders a flash container', () => { + expect(vm.$el.querySelector('.flash-container')).not.toBeNull(); + }); + + it('renders a container for suggestions', () => { + expect(vm.$refs.container).not.toBeNull(); + }); + + it('renders suggestions', () => { + expect(vm.renderSuggestions).toHaveBeenCalled(); + expect(vm.$el.innerHTML.includes('Suggestion 1')).toBe(true); + expect(vm.$el.innerHTML.includes('Suggestion 2')).toBe(true); + }); + }); + + describe('extractNewLines', () => { + it('extracts suggested lines', () => { + const expectedReturn = [ + { content: 'Line 1\n', lineNumber: 1 }, + { content: 'Line 2\n', lineNumber: 2 }, + { content: 'Line 3\n', lineNumber: 3 }, + ]; + + expect(vm.extractNewLines(generateMockLines())).toEqual(expectedReturn); + }); + + it('increments line number for each extracted line', () => { + expect(extractedLines[0].lineNumber).toEqual(1); + expect(extractedLines[1].lineNumber).toEqual(2); + expect(extractedLines[2].lineNumber).toEqual(3); + }); + + it('returns empty array if no lines are found', () => { + const el = document.createElement('div'); + + expect(vm.extractNewLines(el)).toEqual([]); + }); + }); + + describe('generateDiff', () => { + it('generates a diff table', () => { + expect(diffTable.querySelector('.md-suggestion-diff')).not.toBeNull(); + }); + + it('generates a diff table that contains contents of `oldLineContent`', () => { + expect(diffTable.innerHTML.includes(vm.fromContent)).toBe(true); + }); + + it('generates a diff table that contains contents the suggested lines', () => { + extractedLines.forEach((line, i) => { + expect(diffTable.innerHTML.includes(extractedLines[i].content)).toBe(true); + }); + }); + + it('generates a diff table with the correct line number for each suggested line', () => { + const lines = diffTable.getElementsByClassName('qa-new-diff-line-number'); + + expect([...lines][0].innerHTML).toBe('1'); + expect([...lines][1].innerHTML).toBe('2'); + expect([...lines][2].innerHTML).toBe('3'); + }); + }); +}); -- cgit v1.2.3