diff options
Diffstat (limited to 'spec/frontend/notes')
-rw-r--r-- | spec/frontend/notes/components/comment_form_spec.js | 34 | ||||
-rw-r--r-- | spec/frontend/notes/components/comment_type_dropdown_spec.js | 32 | ||||
-rw-r--r-- | spec/frontend/notes/components/discussion_counter_spec.js | 42 | ||||
-rw-r--r-- | spec/frontend/notes/components/note_body_spec.js | 90 | ||||
-rw-r--r-- | spec/frontend/notes/components/note_form_spec.js | 21 | ||||
-rw-r--r-- | spec/frontend/notes/components/note_header_spec.js | 4 | ||||
-rw-r--r-- | spec/frontend/notes/components/notes_app_spec.js | 32 | ||||
-rw-r--r-- | spec/frontend/notes/deprecated_notes_spec.js | 11 | ||||
-rw-r--r-- | spec/frontend/notes/mixins/discussion_navigation_spec.js | 1 | ||||
-rw-r--r-- | spec/frontend/notes/mock_data.js | 14 | ||||
-rw-r--r-- | spec/frontend/notes/stores/actions_spec.js | 8 | ||||
-rw-r--r-- | spec/frontend/notes/stores/getters_spec.js | 86 |
12 files changed, 294 insertions, 81 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); diff --git a/spec/frontend/notes/deprecated_notes_spec.js b/spec/frontend/notes/deprecated_notes_spec.js index 7193475c96a..40b124b9029 100644 --- a/spec/frontend/notes/deprecated_notes_spec.js +++ b/spec/frontend/notes/deprecated_notes_spec.js @@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; import $ from 'jquery'; import '~/behaviors/markdown/render_gfm'; +import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import { createSpyObj } from 'helpers/jest_helpers'; import { TEST_HOST } from 'helpers/test_constants'; import waitForPromises from 'helpers/wait_for_promises'; @@ -33,7 +34,7 @@ gl.utils.disableButtonIfEmptyField = () => {}; // eslint-disable-next-line jest/no-disabled-tests describe.skip('Old Notes (~/deprecated_notes.js)', () => { beforeEach(() => { - loadFixtures(fixture); + loadHTMLFixture(fixture); // Re-declare this here so that test_setup.js#beforeEach() doesn't // overwrite it. @@ -50,12 +51,14 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => { setTestTimeoutOnce(4000); }); - afterEach(() => { + afterEach(async () => { // The Notes component sets a polling interval. Clear it after every run. // Make sure to use jest.runOnlyPendingTimers() instead of runAllTimers(). jest.clearAllTimers(); - return axios.waitForAll().finally(() => mockAxios.restore()); + await axios.waitForAll().finally(() => mockAxios.restore()); + + resetHTMLFixture(); }); it('loads the Notes class into the DOM', () => { @@ -629,7 +632,7 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => { let $notesContainer; beforeEach(() => { - loadFixtures('commit/show.html'); + loadHTMLFixture('commit/show.html'); mockAxios.onPost(NOTES_POST_PATH).reply(200, note); new Notes('', []); diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js index aba80789a01..35b3dec6298 100644 --- a/spec/frontend/notes/mixins/discussion_navigation_spec.js +++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js @@ -59,6 +59,7 @@ describe('Discussion navigation mixin', () => { diffs: { namespaced: true, actions: { scrollToFile }, + state: { diffFiles: [] }, }, }, }); diff --git a/spec/frontend/notes/mock_data.js b/spec/frontend/notes/mock_data.js index a4aeeda48d8..c7a6ca5eae3 100644 --- a/spec/frontend/notes/mock_data.js +++ b/spec/frontend/notes/mock_data.js @@ -1171,7 +1171,7 @@ export const discussion1 = { resolved: false, active: true, diff_file: { - file_path: 'about.md', + file_identifier_hash: 'discfile1', }, position: { new_line: 50, @@ -1189,7 +1189,7 @@ export const resolvedDiscussion1 = { resolvable: true, resolved: true, diff_file: { - file_path: 'about.md', + file_identifier_hash: 'discfile1', }, position: { new_line: 50, @@ -1208,7 +1208,7 @@ export const discussion2 = { resolved: false, active: true, diff_file: { - file_path: 'README.md', + file_identifier_hash: 'discfile2', }, position: { new_line: null, @@ -1227,7 +1227,7 @@ export const discussion3 = { active: true, resolved: false, diff_file: { - file_path: 'README.md', + file_identifier_hash: 'discfile3', }, position: { new_line: 21, @@ -1240,6 +1240,12 @@ export const discussion3 = { ], }; +export const authoritativeDiscussionFile = { + id: 'abc', + file_identifier_hash: 'discfile1', + order: 0, +}; + export const unresolvableDiscussion = { resolvable: false, }; diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js index 75e7756cd6b..ecb213590ad 100644 --- a/spec/frontend/notes/stores/actions_spec.js +++ b/spec/frontend/notes/stores/actions_spec.js @@ -1,4 +1,5 @@ import AxiosMockAdapter from 'axios-mock-adapter'; +import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import testAction from 'helpers/vuex_action_helper'; import { TEST_HOST } from 'spec/test_constants'; import Api from '~/api'; @@ -51,7 +52,7 @@ describe('Actions Notes Store', () => { axiosMock = new AxiosMockAdapter(axios); // This is necessary as we query Close issue button at the top of issue page when clicking bottom button - setFixtures( + setHTMLFixture( '<div class="detail-page-header-actions"><button class="btn-close btn-grouped"></button></div>', ); }); @@ -59,6 +60,7 @@ describe('Actions Notes Store', () => { afterEach(() => { resetStore(store); axiosMock.restore(); + resetHTMLFixture(); }); describe('setNotesData', () => { @@ -252,7 +254,9 @@ describe('Actions Notes Store', () => { jest.advanceTimersByTime(time); } - return new Promise((resolve) => requestAnimationFrame(resolve)); + return new Promise((resolve) => { + requestAnimationFrame(resolve); + }); }; const advanceXMoreIntervals = async (number) => { const timeoutLength = pollInterval * number; diff --git a/spec/frontend/notes/stores/getters_spec.js b/spec/frontend/notes/stores/getters_spec.js index 9a11fdba508..6d078dcefcf 100644 --- a/spec/frontend/notes/stores/getters_spec.js +++ b/spec/frontend/notes/stores/getters_spec.js @@ -12,6 +12,7 @@ import { discussion2, discussion3, resolvedDiscussion1, + authoritativeDiscussionFile, unresolvableDiscussion, draftComments, draftReply, @@ -26,6 +27,23 @@ const createDiscussionNeighborParams = (discussionId, diffOrder, step) => ({ }); const asDraftDiscussion = (x) => ({ ...x, individual_note: true }); +const createRootState = () => { + return { + diffs: { + diffFiles: [ + { ...authoritativeDiscussionFile }, + { + ...authoritativeDiscussionFile, + ...{ id: 'abc2', file_identifier_hash: 'discfile2', order: 1 }, + }, + { + ...authoritativeDiscussionFile, + ...{ id: 'abc3', file_identifier_hash: 'discfile3', order: 2 }, + }, + ], + }, + }; +}; describe('Getters Notes Store', () => { let state; @@ -226,20 +244,84 @@ describe('Getters Notes Store', () => { const localGetters = { allResolvableDiscussions: [discussion3, discussion1, discussion2], }; + const rootState = createRootState(); - expect(getters.unresolvedDiscussionsIdsByDiff(state, localGetters)).toEqual([ + expect(getters.unresolvedDiscussionsIdsByDiff(state, localGetters, rootState)).toEqual([ 'abc1', 'abc2', 'abc3', ]); }); + // This is the same test as above, but it exercises the sorting algorithm + // for a "strange" Diff File ordering. The intent is to ensure that even if lots + // of shuffling has to occur, everything still works + + it('should return all discussions IDs in unusual diff order', () => { + const localGetters = { + allResolvableDiscussions: [discussion3, discussion1, discussion2], + }; + const rootState = { + diffs: { + diffFiles: [ + // 2 is first, but should sort 2nd + { + ...authoritativeDiscussionFile, + ...{ id: 'abc2', file_identifier_hash: 'discfile2', order: 1 }, + }, + // 1 is second, but should sort 3rd + { ...authoritativeDiscussionFile, ...{ order: 2 } }, + // 3 is third, but should sort 1st + { + ...authoritativeDiscussionFile, + ...{ id: 'abc3', file_identifier_hash: 'discfile3', order: 0 }, + }, + ], + }, + }; + + expect(getters.unresolvedDiscussionsIdsByDiff(state, localGetters, rootState)).toEqual([ + 'abc3', + 'abc2', + 'abc1', + ]); + }); + + it("should use the discussions array order if the files don't have explicit order values", () => { + const localGetters = { + allResolvableDiscussions: [discussion3, discussion1, discussion2], // This order is used! + }; + const auth1 = { ...authoritativeDiscussionFile }; + const auth2 = { + ...authoritativeDiscussionFile, + ...{ id: 'abc2', file_identifier_hash: 'discfile2' }, + }; + const auth3 = { + ...authoritativeDiscussionFile, + ...{ id: 'abc3', file_identifier_hash: 'discfile3' }, + }; + const rootState = { + diffs: { diffFiles: [auth2, auth1, auth3] }, // This order is not used! + }; + + delete auth1.order; + delete auth2.order; + delete auth3.order; + + expect(getters.unresolvedDiscussionsIdsByDiff(state, localGetters, rootState)).toEqual([ + 'abc3', + 'abc1', + 'abc2', + ]); + }); + it('should return empty array if all discussions have been resolved', () => { const localGetters = { allResolvableDiscussions: [resolvedDiscussion1], }; + const rootState = createRootState(); - expect(getters.unresolvedDiscussionsIdsByDiff(state, localGetters)).toEqual([]); + expect(getters.unresolvedDiscussionsIdsByDiff(state, localGetters, rootState)).toEqual([]); }); }); |