diff --git a/app/assets/javascripts/rapid_diffs/adapters.js b/app/assets/javascripts/rapid_diffs/adapters.js index fb9c404263e149b63b9e3a287b7aa9e5b84bea3f..d5c5691c0282c49898caae0e412c18a6d176ae42 100644 --- a/app/assets/javascripts/rapid_diffs/adapters.js +++ b/app/assets/javascripts/rapid_diffs/adapters.js @@ -1,4 +1,5 @@ import { ExpandLinesAdapter } from '~/rapid_diffs/expand_lines/adapter'; +import { OptionsMenuAdapter } from '~/rapid_diffs/options_menu/adapter'; import { ToggleFileAdapter } from '~/rapid_diffs/toggle_file/adapter'; const RAPID_DIFFS_VIEWERS = { @@ -6,7 +7,7 @@ const RAPID_DIFFS_VIEWERS = { text_parallel: 'text_parallel', }; -const COMMON_ADAPTERS = [ExpandLinesAdapter, ToggleFileAdapter]; +const COMMON_ADAPTERS = [ExpandLinesAdapter, OptionsMenuAdapter, ToggleFileAdapter]; export const VIEWER_ADAPTERS = { [RAPID_DIFFS_VIEWERS.text_inline]: COMMON_ADAPTERS, diff --git a/app/assets/javascripts/rapid_diffs/options_menu/adapter.js b/app/assets/javascripts/rapid_diffs/options_menu/adapter.js new file mode 100644 index 0000000000000000000000000000000000000000..e7cc7f9d2bc784139c9ec4edb6d8f47b69e38d6b --- /dev/null +++ b/app/assets/javascripts/rapid_diffs/options_menu/adapter.js @@ -0,0 +1,27 @@ +import Vue from 'vue'; +import { GlDisclosureDropdown } from '@gitlab/ui'; + +export const OptionsMenuAdapter = { + clicks: { + toggleOptionsMenu(event) { + const button = event.target.closest('.js-options-button'); + + if (!this.sink.optionsMenu) { + this.sink.optionsMenu = new Vue({ + el: Vue.version.startsWith('2') ? button : button.parentElement, + name: 'GlDisclosureDropdown', + render: (createElement = Vue.h) => + createElement(GlDisclosureDropdown, { + props: { + icon: 'ellipsis_v', + startOpened: true, + noCaret: true, + category: 'tertiary', + size: 'small', + }, + }), + }); + } + }, + }, +}; diff --git a/app/components/rapid_diffs/diff_file_header_component.html.haml b/app/components/rapid_diffs/diff_file_header_component.html.haml index 22e81aa36aa073cc8bfb58dc9f5830e3cb878a29..02b5ef07ab278448131ee29a8434a170f210d221 100644 --- a/app/components/rapid_diffs/diff_file_header_component.html.haml +++ b/app/components/rapid_diffs/diff_file_header_component.html.haml @@ -48,3 +48,6 @@ %span.rd-lines-removed %span>= "−".html_safe %span{ "data-testid" => "js-file-deletion-line" }= @diff_file.removed_lines + .rd-diff-file-options-menu.gl-ml-2 + .js-options-menu + = render Pajamas::ButtonComponent.new(category: :tertiary, size: :small, icon: 'ellipsis_v', button_options: { class: 'js-options-button', data: { click: 'toggleOptionsMenu' }, aria: { label: _('Options') } }) diff --git a/spec/frontend/rapid_diffs/options_menu/adapter_spec.js b/spec/frontend/rapid_diffs/options_menu/adapter_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..27a9c790ce12fbf49d827644dd8b7bc9d30319b4 --- /dev/null +++ b/spec/frontend/rapid_diffs/options_menu/adapter_spec.js @@ -0,0 +1,64 @@ +import { DiffFile } from '~/rapid_diffs/diff_file'; +import { OptionsMenuAdapter } from '~/rapid_diffs/options_menu/adapter'; + +describe('Diff File Options Menu', () => { + const html = ` + <diff-file data-viewer="any"> + <div class="rd-diff-file"> + <div class="rd-diff-file-header" data-testid="rd-diff-file-header"> + <div class="rd-diff-file-options-menu gl-ml-2"> + <div class="js-options-menu"> + <button class="js-options-button" data-click="toggleOptionsMenu" type="button"></button> + </div> + </div> + </div> + <div data-file-body=""><!-- body content --></div> + <diff-file-mounted></diff-file-mounted> + </diff-file> + `; + + function get(element) { + const elements = { + file: () => document.querySelector('diff-file'), + container: () => get('file').querySelector('.js-options-menu'), + serverButton: () => get('container').querySelector('.js-options-button'), + vueButton: () => get('container').querySelector('[data-testid="base-dropdown-toggle"]'), + }; + + return elements[element]?.(); + } + + function assignAdapter(customAdapter) { + get('file').adapterConfig = { any: [customAdapter] }; + } + + beforeAll(() => { + customElements.define('diff-file', DiffFile); + }); + + beforeEach(() => { + document.body.innerHTML = html; + assignAdapter(OptionsMenuAdapter); + get('file').mount(); + }); + + 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(); + + button.click(); + + 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(); + }); +});