diff options
Diffstat (limited to 'spec/frontend/notes/components')
7 files changed, 186 insertions, 69 deletions
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js index a605edc4357..fb42e4d1d84 100644 --- a/spec/frontend/notes/components/comment_form_spec.js +++ b/spec/frontend/notes/components/comment_form_spec.js @@ -248,13 +248,21 @@ describe('issue_comment_form component', () => { describe('textarea', () => { describe('general', () => { - it('should render textarea with placeholder', () => { - mountComponent({ mountFunction: mount }); + it.each` + noteType | confidential | placeholder + ${'comment'} | ${false} | ${'Write a comment or drag your files here…'} + ${'internal note'} | ${true} | ${'Write an internal note or drag your files here…'} + `( + 'should render textarea with placeholder for $noteType', + ({ confidential, placeholder }) => { + mountComponent({ + mountFunction: mount, + initialData: { noteIsConfidential: confidential }, + }); - expect(findTextArea().attributes('placeholder')).toBe( - 'Write a comment or drag your files here…', - ); - }); + expect(findTextArea().attributes('placeholder')).toBe(placeholder); + }, + ); it('should make textarea disabled while requesting', async () => { mountComponent({ mountFunction: mount }); @@ -380,6 +388,20 @@ describe('issue_comment_form component', () => { expect(findCloseReopenButton().text()).toBe('Close issue'); }); + it.each` + confidential | buttonText + ${false} | ${'Comment'} + ${true} | ${'Add internal note'} + `('renders comment button with text "$buttonText"', ({ confidential, buttonText }) => { + mountComponent({ + mountFunction: mount, + noteableData: createNotableDataMock({ confidential }), + initialData: { noteIsConfidential: confidential }, + }); + + expect(findCommentButton().text()).toBe(buttonText); + }); + it('should render comment button as disabled', () => { mountComponent(); diff --git a/spec/frontend/notes/components/comment_type_dropdown_spec.js b/spec/frontend/notes/components/comment_type_dropdown_spec.js index 8ac6144e5c8..cabf551deba 100644 --- a/spec/frontend/notes/components/comment_type_dropdown_spec.js +++ b/spec/frontend/notes/components/comment_type_dropdown_spec.js @@ -28,18 +28,42 @@ describe('CommentTypeDropdown component', () => { wrapper.destroy(); }); - it('Should label action button "Comment" and correct dropdown item checked when selected', () => { + it.each` + isInternalNote | buttonText + ${false} | ${COMMENT_FORM.comment} + ${true} | ${COMMENT_FORM.internalComment} + `( + 'Should label action button as "$buttonText" for comment when `isInternalNote` is $isInternalNote', + ({ isInternalNote, buttonText }) => { + mountComponent({ props: { noteType: constants.COMMENT, isInternalNote } }); + + expect(findCommentGlDropdown().props()).toMatchObject({ text: buttonText }); + }, + ); + + it('Should set correct dropdown item checked when comment is selected', () => { mountComponent({ props: { noteType: constants.COMMENT } }); - expect(findCommentGlDropdown().props()).toMatchObject({ text: COMMENT_FORM.comment }); expect(findCommentDropdownOption().props()).toMatchObject({ isChecked: true }); expect(findDiscussionDropdownOption().props()).toMatchObject({ isChecked: false }); }); - it('Should label action button "Start Thread" and correct dropdown item option checked when selected', () => { + it.each` + isInternalNote | buttonText + ${false} | ${COMMENT_FORM.startThread} + ${true} | ${COMMENT_FORM.startInternalThread} + `( + 'Should label action button as "$buttonText" for discussion when `isInternalNote` is $isInternalNote', + ({ isInternalNote, buttonText }) => { + mountComponent({ props: { noteType: constants.DISCUSSION, isInternalNote } }); + + expect(findCommentGlDropdown().props()).toMatchObject({ text: buttonText }); + }, + ); + + it('Should set correct dropdown item option checked when discussion is selected', () => { mountComponent({ props: { noteType: constants.DISCUSSION } }); - expect(findCommentGlDropdown().props()).toMatchObject({ text: COMMENT_FORM.startThread }); expect(findCommentDropdownOption().props()).toMatchObject({ isChecked: false }); expect(findDiscussionDropdownOption().props()).toMatchObject({ isChecked: true }); }); diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js index a856d002d2e..f016cef18e6 100644 --- a/spec/frontend/notes/components/discussion_counter_spec.js +++ b/spec/frontend/notes/components/discussion_counter_spec.js @@ -45,7 +45,7 @@ describe('DiscussionCounter component', () => { describe('has no discussions', () => { it('does not render', () => { - wrapper = shallowMount(DiscussionCounter, { store }); + wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(false); }); @@ -55,7 +55,7 @@ describe('DiscussionCounter component', () => { it('does not render', () => { store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [{ ...discussionMock, resolvable: false }]); store.dispatch('updateResolvableDiscussionsCounts'); - wrapper = shallowMount(DiscussionCounter, { store }); + wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(false); }); @@ -75,20 +75,34 @@ describe('DiscussionCounter component', () => { it('renders', () => { updateStore(); - wrapper = shallowMount(DiscussionCounter, { store }); + wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(true); }); it.each` - title | resolved | isActive | groupLength - ${'not allResolved'} | ${false} | ${false} | ${3} - ${'allResolved'} | ${true} | ${true} | ${1} - `('renders correctly if $title', ({ resolved, isActive, groupLength }) => { + blocksMerge | color + ${true} | ${'gl-bg-orange-50'} + ${false} | ${'gl-bg-gray-50'} + `( + 'changes background color to $color if blocksMerge is $blocksMerge', + ({ blocksMerge, color }) => { + updateStore(); + store.state.unresolvedDiscussionsCount = 1; + wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge } }); + + expect(wrapper.find('[data-testid="discussions-counter-text"]').classes()).toContain(color); + }, + ); + + it.each` + title | resolved | groupLength + ${'not allResolved'} | ${false} | ${4} + ${'allResolved'} | ${true} | ${1} + `('renders correctly if $title', ({ resolved, groupLength }) => { updateStore({ resolvable: true, resolved }); - wrapper = shallowMount(DiscussionCounter, { store }); + wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); - expect(wrapper.find(`.is-active`).exists()).toBe(isActive); expect(wrapper.findAll(GlButton)).toHaveLength(groupLength); }); }); @@ -99,7 +113,7 @@ describe('DiscussionCounter component', () => { const discussion = { ...discussionMock, expanded }; store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [discussion]); store.dispatch('updateResolvableDiscussionsCounts'); - wrapper = shallowMount(DiscussionCounter, { store }); + wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); toggleAllButton = wrapper.find('.toggle-all-discussions-btn'); }; @@ -117,26 +131,26 @@ describe('DiscussionCounter component', () => { updateStoreWithExpanded(true); expect(wrapper.vm.allExpanded).toBe(true); - expect(toggleAllButton.props('icon')).toBe('angle-up'); + expect(toggleAllButton.props('icon')).toBe('collapse'); toggleAllButton.vm.$emit('click'); await nextTick(); expect(wrapper.vm.allExpanded).toBe(false); - expect(toggleAllButton.props('icon')).toBe('angle-down'); + expect(toggleAllButton.props('icon')).toBe('expand'); }); it('expands all discussions if collapsed', async () => { updateStoreWithExpanded(false); expect(wrapper.vm.allExpanded).toBe(false); - expect(toggleAllButton.props('icon')).toBe('angle-down'); + expect(toggleAllButton.props('icon')).toBe('expand'); toggleAllButton.vm.$emit('click'); await nextTick(); expect(wrapper.vm.allExpanded).toBe(true); - expect(toggleAllButton.props('icon')).toBe('angle-up'); + expect(toggleAllButton.props('icon')).toBe('collapse'); }); }); }); diff --git a/spec/frontend/notes/components/note_body_spec.js b/spec/frontend/notes/components/note_body_spec.js index 63f3cd865d5..378dcb97fab 100644 --- a/spec/frontend/notes/components/note_body_spec.js +++ b/spec/frontend/notes/components/note_body_spec.js @@ -1,9 +1,10 @@ import { shallowMount } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import { suggestionCommitMessage } from '~/diffs/store/getters'; -import noteBody from '~/notes/components/note_body.vue'; +import NoteBody from '~/notes/components/note_body.vue'; +import NoteAwardsList from '~/notes/components/note_awards_list.vue'; +import NoteForm from '~/notes/components/note_form.vue'; import createStore from '~/notes/stores'; import notes from '~/notes/stores/modules/index'; @@ -11,68 +12,89 @@ import Suggestions from '~/vue_shared/components/markdown/suggestions.vue'; import { noteableDataMock, notesDataMock, note } from '../mock_data'; +const createComponent = ({ + props = {}, + noteableData = noteableDataMock, + notesData = notesDataMock, + store = null, +} = {}) => { + let mockStore; + + if (!store) { + mockStore = createStore(); + + mockStore.dispatch('setNoteableData', noteableData); + mockStore.dispatch('setNotesData', notesData); + } + + return shallowMount(NoteBody, { + store: mockStore || store, + propsData: { + note, + canEdit: true, + canAwardEmoji: true, + isEditing: false, + ...props, + }, + }); +}; + describe('issue_note_body component', () => { - let store; - let vm; + let wrapper; beforeEach(() => { - const Component = Vue.extend(noteBody); - - store = createStore(); - store.dispatch('setNoteableData', noteableDataMock); - store.dispatch('setNotesData', notesDataMock); - - vm = new Component({ - store, - propsData: { - note, - canEdit: true, - canAwardEmoji: true, - }, - }).$mount(); + wrapper = createComponent(); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('should render the note', () => { - expect(vm.$el.querySelector('.note-text').innerHTML).toEqual(note.note_html); + expect(wrapper.find('.note-text').html()).toContain(note.note_html); }); it('should render awards list', () => { - expect(vm.$el.querySelector('.js-awards-block button [data-name="baseball"]')).not.toBeNull(); - expect(vm.$el.querySelector('.js-awards-block button [data-name="bath_tone3"]')).not.toBeNull(); + expect(wrapper.findComponent(NoteAwardsList).exists()).toBe(true); }); describe('isEditing', () => { - beforeEach(async () => { - vm.isEditing = true; - await nextTick(); + beforeEach(() => { + wrapper = createComponent({ props: { isEditing: true } }); }); it('renders edit form', () => { - expect(vm.$el.querySelector('textarea.js-task-list-field')).not.toBeNull(); + expect(wrapper.findComponent(NoteForm).exists()).toBe(true); + }); + + it.each` + confidential | buttonText + ${false} | ${'Save comment'} + ${true} | ${'Save internal note'} + `('renders save button with text "$buttonText"', ({ confidential, buttonText }) => { + wrapper = createComponent({ props: { note: { ...note, confidential }, isEditing: true } }); + + expect(wrapper.findComponent(NoteForm).props('saveButtonTitle')).toBe(buttonText); }); it('adds autosave', () => { const autosaveKey = `autosave/Note/${note.noteable_type}/${note.id}`; - expect(vm.autosave.key).toEqual(autosaveKey); + // While we discourage testing wrapper props + // here we aren't testing a component prop + // but instead an instance object property + // which is defined in `app/assets/javascripts/notes/mixins/autosave.js` + expect(wrapper.vm.autosave.key).toEqual(autosaveKey); }); }); describe('commitMessage', () => { - let wrapper; - - Vue.use(Vuex); - beforeEach(() => { const notesStore = notes(); notesStore.state.notes = {}; - store = new Vuex.Store({ + const store = new Vuex.Store({ modules: { notes: notesStore, diffs: { @@ -98,9 +120,9 @@ describe('issue_note_body component', () => { }, }); - wrapper = shallowMount(noteBody, { + wrapper = createComponent({ store, - propsData: { + props: { note: { ...note, suggestions: [12345] }, canEdit: true, file: { file_path: 'abc' }, diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js index b709141f4ac..252c24d1117 100644 --- a/spec/frontend/notes/components/note_form_spec.js +++ b/spec/frontend/notes/components/note_form_spec.js @@ -6,7 +6,7 @@ import { getDraft, updateDraft } from '~/lib/utils/autosave'; import NoteForm from '~/notes/components/note_form.vue'; import createStore from '~/notes/stores'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; -import { noteableDataMock, notesDataMock, discussionMock } from '../mock_data'; +import { noteableDataMock, notesDataMock, discussionMock, note } from '../mock_data'; jest.mock('~/lib/utils/autosave'); @@ -45,8 +45,6 @@ describe('issue_note_form component', () => { noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.', noteId: '545', }; - - gon.features = { markdownContinueLists: true }; }); afterEach(() => { @@ -116,6 +114,23 @@ describe('issue_note_form component', () => { expect(textarea.attributes('data-supports-quick-actions')).toBe('true'); }); + it.each` + confidential | placeholder + ${false} | ${'Write a comment or drag your files here…'} + ${true} | ${'Write an internal note or drag your files here…'} + `( + 'should set correct textarea placeholder text when discussion confidentiality is $confidential', + ({ confidential, placeholder }) => { + props.note = { + ...note, + confidential, + }; + wrapper = createComponentWrapper(); + + expect(wrapper.find('textarea').attributes('placeholder')).toBe(placeholder); + }, + ); + it('should link to markdown docs', () => { const { markdownDocsPath } = notesDataMock; const markdownField = wrapper.find(MarkdownField); diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js index 3513b562e0a..310a470aa18 100644 --- a/spec/frontend/notes/components/note_header_spec.js +++ b/spec/frontend/notes/components/note_header_spec.js @@ -21,7 +21,7 @@ describe('NoteHeader component', () => { const findActionText = () => wrapper.find({ ref: 'actionText' }); const findTimestampLink = () => wrapper.find({ ref: 'noteTimestampLink' }); const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' }); - const findConfidentialIndicator = () => wrapper.findByTestId('confidentialIndicator'); + const findConfidentialIndicator = () => wrapper.findByTestId('internalNoteIndicator'); const findSpinner = () => wrapper.find({ ref: 'spinner' }); const findAuthorStatus = () => wrapper.find({ ref: 'authorStatus' }); @@ -297,7 +297,7 @@ describe('NoteHeader component', () => { createComponent({ isConfidential: true, noteableType: 'issue' }); expect(findConfidentialIndicator().attributes('title')).toBe( - 'This comment is confidential and only visible to project members', + 'This internal note will always remain confidential', ); }); }); diff --git a/spec/frontend/notes/components/notes_app_spec.js b/spec/frontend/notes/components/notes_app_spec.js index e227af88d3f..413ee815906 100644 --- a/spec/frontend/notes/components/notes_app_spec.js +++ b/spec/frontend/notes/components/notes_app_spec.js @@ -2,6 +2,7 @@ import { mount, shallowMount } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; import $ from 'jquery'; import { nextTick } from 'vue'; +import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import setWindowLocation from 'helpers/set_window_location_helper'; import { setTestTimeout } from 'helpers/timeout'; import waitForPromises from 'helpers/wait_for_promises'; @@ -92,13 +93,17 @@ describe('note_app', () => { describe('set data', () => { beforeEach(() => { - setFixtures('<div class="js-discussions-count"></div>'); + setHTMLFixture('<div class="js-discussions-count"></div>'); axiosMock.onAny().reply(200, []); wrapper = mountComponent(); return waitForDiscussionsRequest(); }); + afterEach(() => { + resetHTMLFixture(); + }); + it('should set notes data', () => { expect(store.state.notesData).toEqual(mockData.notesDataMock); }); @@ -122,13 +127,17 @@ describe('note_app', () => { describe('render', () => { beforeEach(() => { - setFixtures('<div class="js-discussions-count"></div>'); + setHTMLFixture('<div class="js-discussions-count"></div>'); axiosMock.onAny().reply(mockData.getIndividualNoteResponse); wrapper = mountComponent(); return waitForDiscussionsRequest(); }); + afterEach(() => { + resetHTMLFixture(); + }); + it('should render list of notes', () => { const note = mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[ @@ -160,7 +169,7 @@ describe('note_app', () => { describe('render with comments disabled', () => { beforeEach(() => { - setFixtures('<div class="js-discussions-count"></div>'); + setHTMLFixture('<div class="js-discussions-count"></div>'); axiosMock.onAny().reply(mockData.getIndividualNoteResponse); store.state.commentsDisabled = true; @@ -168,6 +177,10 @@ describe('note_app', () => { return waitForDiscussionsRequest(); }); + afterEach(() => { + resetHTMLFixture(); + }); + it('should not render form when commenting is disabled', () => { expect(wrapper.find('.js-main-target-form').exists()).toBe(false); }); @@ -179,7 +192,7 @@ describe('note_app', () => { describe('timeline view', () => { beforeEach(() => { - setFixtures('<div class="js-discussions-count"></div>'); + setHTMLFixture('<div class="js-discussions-count"></div>'); axiosMock.onAny().reply(mockData.getIndividualNoteResponse); store.state.commentsDisabled = false; @@ -189,6 +202,10 @@ describe('note_app', () => { return waitForDiscussionsRequest(); }); + afterEach(() => { + resetHTMLFixture(); + }); + it('should not render comments form', () => { expect(wrapper.find('.js-main-target-form').exists()).toBe(false); }); @@ -196,12 +213,15 @@ describe('note_app', () => { describe('while fetching data', () => { beforeEach(() => { - setFixtures('<div class="js-discussions-count"></div>'); + setHTMLFixture('<div class="js-discussions-count"></div>'); axiosMock.onAny().reply(200, []); wrapper = mountComponent(); }); - afterEach(() => waitForDiscussionsRequest()); + afterEach(() => { + waitForDiscussionsRequest(); + resetHTMLFixture(); + }); it('renders skeleton notes', () => { expect(wrapper.find('.animation-container').exists()).toBe(true); |