diff --git a/app/assets/javascripts/rapid_diffs/adapters/discussions.js b/app/assets/javascripts/rapid_diffs/adapters/discussions.js index fdb28d8dbf6efc96f01618a74ee86c28c3221f4d..eda1ec7f414c9e6330491e43f43195db3a620e04 100644 --- a/app/assets/javascripts/rapid_diffs/adapters/discussions.js +++ b/app/assets/javascripts/rapid_diffs/adapters/discussions.js @@ -33,6 +33,8 @@ function mountVueApp(el, id, appData) { render(h) { if (!this.discussion) return null; + if (this.discussion.hidden) return null; + if (this.discussion.isForm) { return h(NewLineDiscussionForm, { props: { discussion: this.discussion } }); } @@ -175,6 +177,8 @@ export const parallelDiscussionsAdapter = { newDiscussion(event, button) { const [oldLine, newLine] = getParallelPosition(button); const { oldPath, newPath } = this.data; + // Show all discussions for this file when adding a new discussion + useDiffDiscussions(pinia).toggleFileDiscussions(oldPath, newPath, false); const existingDiscussionId = useDiffDiscussions(pinia).addNewLineDiscussionForm({ oldPath, newPath, @@ -199,6 +203,8 @@ export const inlineDiscussionsAdapter = { newDiscussion(event, button) { const [oldLine, newLine] = getInlinePosition(button); const { oldPath, newPath } = this.data; + // Show all discussions for this file when adding a new discussion + useDiffDiscussions(pinia).toggleFileDiscussions(oldPath, newPath, false); const existingDiscussionId = useDiffDiscussions(pinia).addNewLineDiscussionForm({ oldPath, newPath, diff --git a/app/assets/javascripts/rapid_diffs/adapters/options_menu.js b/app/assets/javascripts/rapid_diffs/adapters/options_menu.js index f8f336f94f80258dfd869cfb17ab551bbf94f989..8aa2c7f3870a0f18621078e807c591a7c2d720e2 100644 --- a/app/assets/javascripts/rapid_diffs/adapters/options_menu.js +++ b/app/assets/javascripts/rapid_diffs/adapters/options_menu.js @@ -1,24 +1,33 @@ import Vue from 'vue'; +import { pinia } from '~/pinia/instance'; import DiffFileOptionsDropdown from '~/rapid_diffs/app/options_menu/diff_file_options_dropdown.vue'; +import CommitDiffsFileOptionsDropdown from '~/rapid_diffs/app/options_menu/commit_diffs_file_options_dropdown.vue'; function getMenuItems(container) { return JSON.parse(container.querySelector('script').textContent); } -export const optionsMenuAdapter = { - clicks: { - toggleOptionsMenu(event, button) { - const menuContainer = this.diffElement.querySelector('[data-options-menu]'); - if (!menuContainer) return; - const items = getMenuItems(menuContainer); - // eslint-disable-next-line no-new - new Vue({ - el: Vue.version.startsWith('2') ? button : menuContainer, - name: 'GlDisclosureDropdown', - render(h) { - return h(DiffFileOptionsDropdown, { props: { items } }); - }, - }); +const createOptionsMenuAdaptor = (dropdownComponent) => { + return { + clicks: { + toggleOptionsMenu(event, button) { + const menuContainer = this.diffElement.querySelector('[data-options-menu]'); + if (!menuContainer) return; + const items = getMenuItems(menuContainer); + const { oldPath, newPath } = this.data; + // eslint-disable-next-line no-new + new Vue({ + el: Vue.version.startsWith('2') ? button : menuContainer, + name: 'GlDisclosureDropdown', + pinia, + render(h) { + return h(dropdownComponent, { props: { items, oldPath, newPath } }); + }, + }); + }, }, - }, + }; }; + +export const optionsMenuAdapter = createOptionsMenuAdaptor(DiffFileOptionsDropdown); +export const commitOptionsMenuAdaptor = createOptionsMenuAdaptor(CommitDiffsFileOptionsDropdown); diff --git a/app/assets/javascripts/rapid_diffs/app/adapter_configs/commit.js b/app/assets/javascripts/rapid_diffs/app/adapter_configs/commit.js index 594a3a68598cddabd59788f1cfb7267913f0b7c2..0b5e33934e5aa4ea4e5100c688296ee64bfb4f47 100644 --- a/app/assets/javascripts/rapid_diffs/app/adapter_configs/commit.js +++ b/app/assets/javascripts/rapid_diffs/app/adapter_configs/commit.js @@ -3,9 +3,17 @@ import { inlineDiscussionsAdapter, parallelDiscussionsAdapter, } from '~/rapid_diffs/adapters/discussions'; +import { optionsMenuAdapter, commitOptionsMenuAdaptor } from '~/rapid_diffs/adapters/options_menu'; export const adapters = { - ...VIEWER_ADAPTERS, - text_inline: [...VIEWER_ADAPTERS.text_inline, inlineDiscussionsAdapter], - text_parallel: [...VIEWER_ADAPTERS.text_parallel, parallelDiscussionsAdapter], + text_inline: [ + ...VIEWER_ADAPTERS.text_inline.filter((a) => a !== optionsMenuAdapter), + commitOptionsMenuAdaptor, + inlineDiscussionsAdapter, + ], + text_parallel: [ + ...VIEWER_ADAPTERS.text_parallel.filter((a) => a !== optionsMenuAdapter), + commitOptionsMenuAdaptor, + parallelDiscussionsAdapter, + ], }; diff --git a/app/assets/javascripts/rapid_diffs/app/options_menu/commit_diffs_file_options_dropdown.vue b/app/assets/javascripts/rapid_diffs/app/options_menu/commit_diffs_file_options_dropdown.vue new file mode 100644 index 0000000000000000000000000000000000000000..d479529f5c1f5338008b9cafe1b6028630d4d7d9 --- /dev/null +++ b/app/assets/javascripts/rapid_diffs/app/options_menu/commit_diffs_file_options_dropdown.vue @@ -0,0 +1,81 @@ + + + diff --git a/app/assets/javascripts/rapid_diffs/app/options_menu/diff_file_options_dropdown.vue b/app/assets/javascripts/rapid_diffs/app/options_menu/diff_file_options_dropdown.vue index 69a6b32dc470f41b52f0e838c66ff0df272d2b5f..376b07e3603b192d2ea7b11ed1848da13c399f11 100644 --- a/app/assets/javascripts/rapid_diffs/app/options_menu/diff_file_options_dropdown.vue +++ b/app/assets/javascripts/rapid_diffs/app/options_menu/diff_file_options_dropdown.vue @@ -1,16 +1,31 @@ diff --git a/app/assets/javascripts/rapid_diffs/stores/diff_discussions.js b/app/assets/javascripts/rapid_diffs/stores/diff_discussions.js index 6d2fe158607ee9f31a1b174b8e505ae5366eb5e6..58502ac995dd8bc7dea9aee2d5fe1a4341ca6319 100644 --- a/app/assets/javascripts/rapid_diffs/stores/diff_discussions.js +++ b/app/assets/javascripts/rapid_diffs/stores/diff_discussions.js @@ -9,6 +9,7 @@ function addReactiveDiscussionProps(discussion) { notes: discussion.notes.map((note) => { return Object.assign(note, { isEditing: false, editedNote: null }); }), + hidden: false, }); } @@ -128,6 +129,19 @@ export const useDiffDiscussions = defineStore('diffDiscussions', { }); } }, + toggleFileDiscussions(oldPath, newPath, newState) { + const matchingDiscussions = this.discussions.filter((discussion) => { + return ( + discussion.diff_discussion && + discussion.position?.old_path === oldPath && + discussion.position?.new_path === newPath + ); + }); + + matchingDiscussions.forEach((discussion) => { + discussion.hidden = newState; + }); + }, /* eslint-enable no-param-reassign */ }, getters: { @@ -154,5 +168,17 @@ export const useDiffDiscussions = defineStore('diffDiscussions', { }); }; }, + findDiscussionsForFile() { + return ({ oldPath, newPath }) => { + return this.discussions.filter((discussion) => { + return ( + !discussion.isForm && + discussion.diff_discussion && + discussion.position?.old_path === oldPath && + discussion.position?.new_path === newPath + ); + }); + }; + }, }, }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c410cd6e75a73e21d51f6381c258f261caa488e3..55ca1db954f582a1c665eb3b1a349196a287dc9f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -54707,6 +54707,9 @@ msgstr "" msgid "RapidDiffs|File moved from %{old} to %{new}" msgstr "" +msgid "RapidDiffs|Hide comments on this file" +msgstr "" + msgid "RapidDiffs|Line %d" msgstr "" @@ -54727,6 +54730,9 @@ msgstr[1] "" msgid "RapidDiffs|Removed line %d" msgstr "" +msgid "RapidDiffs|Show comments on this file" +msgstr "" + msgid "RapidDiffs|Show hidden lines" msgstr "" diff --git a/spec/frontend/rapid_diffs/adapters/discussions_spec.js b/spec/frontend/rapid_diffs/adapters/discussions_spec.js index 77b41e416a501110e600186263790c68bd715df6..02bda914e6c5ad14e91eec8392ecc656a1620341 100644 --- a/spec/frontend/rapid_diffs/adapters/discussions_spec.js +++ b/spec/frontend/rapid_diffs/adapters/discussions_spec.js @@ -157,6 +157,20 @@ describe('discussions adapters', () => { ).toStrictEqual('Commit'); }); + it('does not render hidden discussions', async () => { + const discussionId = 'hidden-discussion'; + useDiffDiscussions().discussions = [ + { + id: discussionId, + diff_discussion: true, + position: { old_path: oldPath, new_path: newPath, old_line: 1, new_line: null }, + hidden: true, + }, + ]; + await nextTick(); + expect(getDiffFile().querySelector('[data-discussion-id]')).toBeNull(); + }); + it('does not render discussions for different paths', async () => { useDiffDiscussions().discussions = [ { @@ -201,6 +215,31 @@ describe('discussions adapters', () => { await nextTick(); expect(document.querySelector('[data-new-discussion-form]')).not.toBeNull(); }); + + it('shows all discussions for the file when creating a new discussion', async () => { + const store = useDiffDiscussions(); + store.discussions = [ + { + id: 'hidden-discussion', + diff_discussion: true, + position: { old_path: oldPath, new_path: newPath, old_line: 1, new_line: null }, + hidden: true, + }, + ]; + await nextTick(); + + jest.spyOn(store, 'toggleFileDiscussions'); + + let event; + const button = getDiffFile().querySelector('[data-click="newDiscussion"]'); + button.addEventListener('click', (e) => { + event = e; + }); + button.click(); + getDiffFile().onClick(event); + + expect(store.toggleFileDiscussions).toHaveBeenCalledWith(oldPath, newPath, false); + }); }); describe('parallelDiscussionsAdapter', () => { @@ -333,6 +372,20 @@ describe('discussions adapters', () => { ); }); + it('does not render hidden discussions', async () => { + const discussionId = 'hidden-discussion'; + useDiffDiscussions().discussions = [ + { + id: discussionId, + diff_discussion: true, + position: { old_path: oldPath, new_path: newPath, old_line: 1, new_line: null }, + hidden: true, + }, + ]; + await nextTick(); + expect(getDiffFile().querySelector('[data-discussion-id]')).toBeNull(); + }); + it('does not render discussions for different paths', async () => { useDiffDiscussions().discussions = [ { @@ -377,5 +430,30 @@ describe('discussions adapters', () => { await nextTick(); expect(document.querySelector('[data-new-discussion-form]')).not.toBeNull(); }); + + it('shows all discussions for the file when creating a new discussion', async () => { + const store = useDiffDiscussions(); + store.discussions = [ + { + id: 'hidden-discussion', + diff_discussion: true, + position: { old_path: oldPath, new_path: newPath, old_line: null, new_line: 2 }, + hidden: true, + }, + ]; + await nextTick(); + + jest.spyOn(store, 'toggleFileDiscussions'); + + let event; + const button = getDiffFile().querySelector('[data-click="newDiscussion"]'); + button.addEventListener('click', (e) => { + event = e; + }); + button.click(); + getDiffFile().onClick(event); + + expect(store.toggleFileDiscussions).toHaveBeenCalledWith(oldPath, newPath, false); + }); }); }); diff --git a/spec/frontend/rapid_diffs/adapters/options_menu_spec.js b/spec/frontend/rapid_diffs/adapters/options_menu_spec.js index 1540e71f47e48db92a0677d29451bb3765180eae..6204248fdeae58a31c29d71fb9f9f2c6943368ca 100644 --- a/spec/frontend/rapid_diffs/adapters/options_menu_spec.js +++ b/spec/frontend/rapid_diffs/adapters/options_menu_spec.js @@ -1,8 +1,12 @@ +import { setActivePinia } from 'pinia'; import { DiffFile } from '~/rapid_diffs/web_components/diff_file'; -import { optionsMenuAdapter } from '~/rapid_diffs/adapters/options_menu'; +import { optionsMenuAdapter, commitOptionsMenuAdaptor } from '~/rapid_diffs/adapters/options_menu'; +import { pinia } from '~/pinia/instance'; describe('Diff File Options Menu', () => { - const item1 = { text: 'item 1', path: 'item/1/path' }; + const item1 = { text: 'item 1', href: 'item/1/path' }; + const oldPath = 'old/file.js'; + const newPath = 'new/file.js'; function get(element) { const elements = { @@ -30,16 +34,16 @@ describe('Diff File Options Menu', () => { get('file').onClick(event); }; - const mount = () => { + const mount = (adapter = optionsMenuAdapter) => { const viewer = 'any'; document.body.innerHTML = ` - +
@@ -49,7 +53,7 @@ describe('Diff File Options Menu', () => { `; get('file').mount({ - adapterConfig: { [viewer]: [optionsMenuAdapter] }, + adapterConfig: { [viewer]: [adapter] }, appData: {}, unobserve: jest.fn(), }); @@ -59,40 +63,88 @@ describe('Diff File Options Menu', () => { customElements.define('diff-file', DiffFile); }); - beforeEach(() => { - mount(); - }); + describe('optionsMenuAdapter', () => { + beforeEach(() => { + mount(optionsMenuAdapter); + }); - it('starts with the server-rendered button', () => { - expect(get('serverButton')).not.toBeNull(); - }); + it('starts with the server-rendered button', () => { + expect(get('serverButton')).not.toBeNull(); + }); + + it('replaces the server-rendered button with a Vue GlDisclosureDropdown when the button is clicked', () => { + const button = get('serverButton'); + + expect(get('vueButton')).toBeNull(); + expect(button).not.toBeNull(); - it('replaces the server-rendered button with a Vue GlDisclosureDropdown when the button is clicked', () => { - const button = get('serverButton'); + delegatedClick(button); - expect(get('vueButton')).toBeNull(); - expect(button).not.toBeNull(); + expect(get('vueButton')).not.toBeNull(); + /* + * This button being replaced also means this replacement can only + * happen once (desireable!), so testing that it's no longer present is good + */ + expect(get('serverButton')).toBeNull(); + expect(document.activeElement).toEqual(get('vueButton')); + }); + + it('renders the correct menu items in the GlDisclosureDropdown as provided by the back end', () => { + const button = get('serverButton'); + + delegatedClick(button); - delegatedClick(button); + const items = Array.from(get('menuItems')); - expect(get('vueButton')).not.toBeNull(); - /* - * This button being replaced also means this replacement can only - * happen once (desireable!), so testing that it's no longer present is good - */ - expect(get('serverButton')).toBeNull(); - expect(document.activeElement).toEqual(get('vueButton')); + expect(items).toHaveLength(1); + expect(items[0].textContent.trim()).toBe(item1.text); + expect(items[0].querySelector('a').getAttribute('href')).toBe(item1.href); + }); }); - it('renders the correct menu items in the GlDisclosureDropdown as provided by the back end', () => { - const button = get('serverButton'); + describe('commitOptionsMenuAdaptor', () => { + beforeEach(() => { + setActivePinia(pinia); + mount(commitOptionsMenuAdaptor); + }); + + it('starts with the server-rendered button', () => { + expect(get('serverButton')).not.toBeNull(); + }); + + it('replaces the server-rendered button with CommitDiffsFileOptionsDropdown when clicked', () => { + const button = get('serverButton'); + + expect(get('vueButton')).toBeNull(); + expect(button).not.toBeNull(); + + delegatedClick(button); + + expect(get('vueButton')).not.toBeNull(); + expect(get('serverButton')).toBeNull(); + expect(document.activeElement).toEqual(get('vueButton')); + }); - delegatedClick(button); + it('renders the correct menu items in the dropdown as provided by the back end', () => { + const button = get('serverButton'); - const items = Array.from(get('menuItems')); + delegatedClick(button); - expect(items).toHaveLength(1); - expect(items[0].textContent.trim()).toBe(item1.text); - expect(items[0].querySelector('a').getAttribute('href')).toBe(item1.path); + const items = Array.from(get('menuItems')); + + expect(items).toHaveLength(1); + expect(items[0].textContent.trim()).toBe(item1.text); + expect(items[0].querySelector('a').getAttribute('href')).toBe(item1.href); + }); + + it('passes oldPath and newPath props to the dropdown component', () => { + const button = get('serverButton'); + + delegatedClick(button); + + // The component should be mounted with oldPath and newPath props + // This is verified by the component rendering correctly with file-specific features + expect(get('vueButton')).not.toBeNull(); + }); }); }); diff --git a/spec/frontend/rapid_diffs/app/options_menu/commit_diffs_file_options_dropdown_spec.js b/spec/frontend/rapid_diffs/app/options_menu/commit_diffs_file_options_dropdown_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..fab4eac5778d1a919b7927d5254c395953b99c15 --- /dev/null +++ b/spec/frontend/rapid_diffs/app/options_menu/commit_diffs_file_options_dropdown_spec.js @@ -0,0 +1,125 @@ +import Vue, { nextTick } from 'vue'; +import { createTestingPinia } from '@pinia/testing'; +import { PiniaVuePlugin } from 'pinia'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import DiffFileOptionsDropdown from '~/rapid_diffs/app/options_menu/diff_file_options_dropdown.vue'; +import { useDiffDiscussions } from '~/rapid_diffs/stores/diff_discussions'; +import CommitDiffFileOptionsDropdown from '~/rapid_diffs/app/options_menu/commit_diffs_file_options_dropdown.vue'; + +Vue.use(PiniaVuePlugin); + +describe('CommitDiffFileOptionsDropdown', () => { + let wrapper; + let pinia; + let store; + + const createComponent = (props = {}) => { + wrapper = mountExtended(CommitDiffFileOptionsDropdown, { + propsData: { + items: [{ text: 'View file', href: '/file' }], + oldPath: 'file.js', + newPath: 'file.js', + ...props, + }, + pinia, + }); + }; + + const findToggleCommentButton = () => wrapper.findByTestId('toggle-comment-button'); + + beforeEach(() => { + pinia = createTestingPinia({ stubActions: false }); + store = useDiffDiscussions(); + }); + + describe('when file has no discussions', () => { + beforeEach(() => { + createComponent(); + }); + + it('passes groups with only base items', () => { + const dropdown = wrapper.findComponent(DiffFileOptionsDropdown); + expect(dropdown.props('items')).toHaveLength(1); + expect(dropdown.props('items')[0].items).toEqual([{ text: 'View file', href: '/file' }]); + }); + }); + + describe('when file has discussions', () => { + beforeEach(() => { + store.setInitialDiscussions([ + { + id: '1', + diff_discussion: true, + position: { old_path: 'file.js', new_path: 'file.js' }, + notes: [], + hidden: false, + }, + ]); + createComponent(); + }); + + it('includes toggle comments item in a bordered group', () => { + const dropdown = wrapper.findComponent(DiffFileOptionsDropdown); + expect(dropdown.props('items')).toHaveLength(2); + expect(findToggleCommentButton().exists()).toBe(true); + expect(findToggleCommentButton().text()).toBe('Hide comments on this file'); + }); + + it('shows "Hide comments" when discussions are visible', () => { + expect(findToggleCommentButton().text()).toBe('Hide comments on this file'); + }); + + it('shows "Show comments" when discussions are hidden', async () => { + store.discussions[0].hidden = true; + + await nextTick(); + + expect(findToggleCommentButton().text()).toBe('Show comments on this file'); + }); + }); + + describe('toggleComments', () => { + beforeEach(() => { + store.setInitialDiscussions([ + { + id: '1', + diff_discussion: true, + position: { old_path: 'file.js', new_path: 'file.js' }, + notes: [], + hidden: false, + }, + ]); + createComponent(); + }); + + it('calls store action to hide discussions when currently visible', () => { + jest.spyOn(store, 'toggleFileDiscussions'); + + findToggleCommentButton().trigger('click'); + + expect(store.toggleFileDiscussions).toHaveBeenCalledWith('file.js', 'file.js', true); + }); + + it('calls store action to show discussions when currently hidden', async () => { + store.discussions[0].hidden = true; + await nextTick(); + + jest.spyOn(store, 'toggleFileDiscussions'); + + findToggleCommentButton().trigger('click'); + + expect(store.toggleFileDiscussions).toHaveBeenCalledWith('file.js', 'file.js', false); + }); + + it('calls to close dropdown', () => { + const closeAndFocusSpy = jest.spyOn( + wrapper.findComponent(DiffFileOptionsDropdown).vm, + 'closeAndFocus', + ); + + findToggleCommentButton().trigger('click'); + + expect(closeAndFocusSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/rapid_diffs/app/options_menu/diff_file_options_dropdown_spec.js b/spec/frontend/rapid_diffs/app/options_menu/diff_file_options_dropdown_spec.js index 7beae96199a024b11b141dd7c37e533d20649da3..e9cfe13924d1bde01323adb9d659a13667a2cd41 100644 --- a/spec/frontend/rapid_diffs/app/options_menu/diff_file_options_dropdown_spec.js +++ b/spec/frontend/rapid_diffs/app/options_menu/diff_file_options_dropdown_spec.js @@ -1,4 +1,5 @@ import { mount } from '@vue/test-utils'; +import { GlDisclosureDropdownGroup, GlDisclosureDropdownItem } from '@gitlab/ui'; import DiffFileOptionsDropdown from '~/rapid_diffs/app/options_menu/diff_file_options_dropdown.vue'; describe('DiffFileOptionsDropdown', () => { @@ -9,10 +10,65 @@ describe('DiffFileOptionsDropdown', () => { wrapper = mount(DiffFileOptionsDropdown, { propsData }); }; - it('shows options', () => { - const item = { text: 'View file' }; - createComponent({ items: [item] }); - expect(wrapper.html()).toContain(item.text); + const findDropdownGroups = () => wrapper.findAllComponents(GlDisclosureDropdownGroup); + const findDropdownItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem); + + describe('with flat items', () => { + beforeEach(() => { + createComponent({ items: [{ text: 'View file' }, { text: 'Download' }] }); + }); + + it('renders items without groups', () => { + expect(findDropdownGroups()).toHaveLength(0); + expect(findDropdownItems()).toHaveLength(2); + }); + + it('shows options', () => { + expect(wrapper.html()).toContain('View file'); + expect(wrapper.html()).toContain('Download'); + }); + }); + + describe('with grouped items', () => { + beforeEach(() => { + createComponent({ + items: [ + { + name: 'File actions', + items: [{ text: 'View file' }, { text: 'Download' }], + }, + { + name: 'Comments', + bordered: true, + items: [{ text: 'Hide comments' }], + }, + ], + }); + }); + + it('renders groups', () => { + expect(findDropdownGroups()).toHaveLength(2); + expect(findDropdownItems()).toHaveLength(3); + }); + + it('shows all group items', () => { + expect(wrapper.html()).toContain('View file'); + expect(wrapper.html()).toContain('Download'); + expect(wrapper.html()).toContain('Hide comments'); + }); + + it('passes group properties to GlDisclosureDropdownGroup', () => { + const groups = findDropdownGroups(); + expect(groups.at(0).props('group')).toMatchObject({ + name: 'File actions', + items: expect.any(Array), + }); + expect(groups.at(1).props('group')).toMatchObject({ + name: 'Comments', + bordered: true, + items: expect.any(Array), + }); + }); }); it('renders code tags', () => { diff --git a/spec/frontend/rapid_diffs/stores/diff_discussions_spec.js b/spec/frontend/rapid_diffs/stores/diff_discussions_spec.js index 312ef3d510474b1090efc333dca42b306eee1d9c..aa7dfdf6bfb43fdf488479d886927b88813c7457 100644 --- a/spec/frontend/rapid_diffs/stores/diff_discussions_spec.js +++ b/spec/frontend/rapid_diffs/stores/diff_discussions_spec.js @@ -14,6 +14,7 @@ describe('diffDiscussions store', () => { expect(useDiffDiscussions().discussions[0].repliesExpanded).toBe(true); expect(useDiffDiscussions().discussions[0].notes[0].isEditing).toBe(false); expect(useDiffDiscussions().discussions[0].notes[0].editedNote).toBeNull(); + expect(useDiffDiscussions().discussions[0].hidden).toBe(false); }); }); @@ -398,6 +399,43 @@ describe('diffDiscussions store', () => { }); }); + describe('toggleFileDiscussions', () => { + beforeEach(() => { + useDiffDiscussions().discussions = [ + { + id: '1', + diff_discussion: true, + position: { old_path: 'file1.js', new_path: 'file1.js' }, + }, + { + id: '2', + diff_discussion: true, + position: { old_path: 'file1.js', new_path: 'file1.js' }, + }, + { + id: '3', + diff_discussion: true, + position: { old_path: 'file2.js', new_path: 'file2.js' }, + }, + ]; + }); + + it('hides all discussions for a file when newState is true', () => { + useDiffDiscussions().toggleFileDiscussions('file1.js', 'file1.js', true); + + expect(useDiffDiscussions().discussions[0].hidden).toBe(true); + expect(useDiffDiscussions().discussions[1].hidden).toBe(true); + }); + + it('shows all discussions for a file when newState is false', () => { + useDiffDiscussions().toggleFileDiscussions('file1.js', 'file1.js', true); + useDiffDiscussions().toggleFileDiscussions('file1.js', 'file1.js', false); + + expect(useDiffDiscussions().discussions[0].hidden).toBe(false); + expect(useDiffDiscussions().discussions[1].hidden).toBe(false); + }); + }); + describe('allNotesById', () => { it('returns all notes by id', () => { const note1 = { id: 'foo' }; @@ -467,4 +505,55 @@ describe('diffDiscussions store', () => { expect(found).toHaveLength(0); }); }); + + describe('findDiscussionsForFile', () => { + beforeEach(() => { + useDiffDiscussions().discussions = [ + { + id: '1', + diff_discussion: true, + position: { old_path: 'file1.js', new_path: 'file1.js' }, + }, + { + id: '2', + diff_discussion: true, + position: { old_path: 'file2.js', new_path: 'file2.js' }, + }, + { + id: '3', + isForm: true, + diff_discussion: true, + position: { old_path: 'file1.js', new_path: 'file1.js' }, + }, + ]; + }); + + it('returns discussions matching the file paths', () => { + const discussions = useDiffDiscussions().findDiscussionsForFile({ + oldPath: 'file1.js', + newPath: 'file1.js', + }); + + expect(discussions).toHaveLength(1); + expect(discussions[0].id).toBe('1'); + }); + + it('excludes discussion forms', () => { + const discussions = useDiffDiscussions().findDiscussionsForFile({ + oldPath: 'file1.js', + newPath: 'file1.js', + }); + + expect(discussions.every((d) => !d.isForm)).toBe(true); + }); + + it('returns empty array when no discussions match', () => { + const discussions = useDiffDiscussions().findDiscussionsForFile({ + oldPath: 'nonexistent.js', + newPath: 'nonexistent.js', + }); + + expect(discussions).toHaveLength(0); + }); + }); });