Commit 5227911e authored by Peter Hegman's avatar Peter Hegman Committed by Paul Slaughter

Add `FormInputGroupCopyToggleVisibility` Vue component

A component to render a value in an input and allow the user to
show/hide and copy the value.
parent 60903c44
import InputCopyToggleVisibility from './input_copy_toggle_visibility.vue';
export default {
component: InputCopyToggleVisibility,
title: 'vue_shared/components/form/input_copy_toggle_visibility',
};
const defaultProps = {
value: 'hR8x1fuJbzwu5uFKLf9e',
formInputGroupProps: { class: 'gl-form-input-xl' },
};
const Template = (args, { argTypes }) => ({
components: { InputCopyToggleVisibility },
props: Object.keys(argTypes),
template: `<input-copy-toggle-visibility
:value="value"
:initial-visibility="initialVisibility"
:show-toggle-visibility-button="showToggleVisibilityButton"
:show-copy-button="showCopyButton"
:form-input-group-props="formInputGroupProps"
:copy-button-title="copyButtonTitle"
/>`,
});
export const Default = Template.bind({});
Default.args = defaultProps;
<script>
import { GlFormInputGroup, GlFormGroup, GlButton, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default {
name: 'InputCopyToggleVisibility',
i18n: {
toggleVisibilityLabelHide: __('Click to hide'),
toggleVisibilityLabelReveal: __('Click to reveal'),
},
components: {
GlFormInputGroup,
GlFormGroup,
GlButton,
ClipboardButton,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
value: {
type: String,
required: false,
default: '',
},
initialVisibility: {
type: Boolean,
required: false,
default: false,
},
showToggleVisibilityButton: {
type: Boolean,
required: false,
default: true,
},
showCopyButton: {
type: Boolean,
required: false,
default: true,
},
copyButtonTitle: {
type: String,
required: false,
default: __('Copy'),
},
formInputGroupProps: {
type: Object,
required: false,
default() {
return {};
},
},
},
data() {
return {
valueIsVisible: this.initialVisibility,
};
},
computed: {
toggleVisibilityLabel() {
return this.valueIsVisible
? this.$options.i18n.toggleVisibilityLabelHide
: this.$options.i18n.toggleVisibilityLabelReveal;
},
toggleVisibilityIcon() {
return this.valueIsVisible ? 'eye-slash' : 'eye';
},
computedValueIsVisible() {
return !this.showToggleVisibilityButton || this.valueIsVisible;
},
displayedValue() {
return this.computedValueIsVisible ? this.value : '*'.repeat(this.value.length || 20);
},
},
methods: {
handleToggleVisibilityButtonClick() {
this.valueIsVisible = !this.valueIsVisible;
this.$emit('visibility-change', this.valueIsVisible);
},
handleCopyButtonClick() {
this.$emit('copy');
},
handleFormInputCopy(event) {
if (this.computedValueIsVisible) {
return;
}
event.clipboardData.setData('text/plain', this.value);
event.preventDefault();
},
},
};
</script>
<template>
<gl-form-group v-bind="$attrs">
<gl-form-input-group
:value="displayedValue"
input-class="gl-font-monospace! gl-cursor-default!"
select-on-click
readonly
v-bind="formInputGroupProps"
@copy="handleFormInputCopy"
>
<template v-if="showToggleVisibilityButton || showCopyButton" #append>
<gl-button
v-if="showToggleVisibilityButton"
v-gl-tooltip.hover="toggleVisibilityLabel"
:aria-label="toggleVisibilityLabel"
:icon="toggleVisibilityIcon"
@click="handleToggleVisibilityButtonClick"
/>
<clipboard-button
v-if="showCopyButton"
:text="value"
:title="copyButtonTitle"
@click="handleCopyButtonClick"
/>
</template>
</gl-form-input-group>
</gl-form-group>
</template>
import { merge } from 'lodash';
import { GlFormInputGroup } from '@gitlab/ui';
import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper';
describe('InputCopyToggleVisibility', () => {
let wrapper;
afterEach(() => {
wrapper.destroy();
});
const valueProp = 'hR8x1fuJbzwu5uFKLf9e';
const createComponent = (options = {}) => {
wrapper = mountExtended(
InputCopyToggleVisibility,
merge({}, options, {
directives: {
GlTooltip: createMockDirective(),
},
}),
);
};
const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup);
const findFormInput = () => findFormInputGroup().find('input');
const findRevealButton = () =>
wrapper.findByRole('button', {
name: InputCopyToggleVisibility.i18n.toggleVisibilityLabelReveal,
});
const findHideButton = () =>
wrapper.findByRole('button', {
name: InputCopyToggleVisibility.i18n.toggleVisibilityLabelHide,
});
const findCopyButton = () => wrapper.findComponent(ClipboardButton);
const createCopyEvent = () => {
const event = new Event('copy', { cancelable: true });
Object.assign(event, { preventDefault: jest.fn(), clipboardData: { setData: jest.fn() } });
return event;
};
const itDoesNotModifyCopyEvent = () => {
it('does not modify copy event', () => {
const event = createCopyEvent();
findFormInput().element.dispatchEvent(event);
expect(event.clipboardData.setData).not.toHaveBeenCalled();
expect(event.preventDefault).not.toHaveBeenCalled();
});
};
describe('when `value` prop is passed', () => {
beforeEach(() => {
createComponent({
propsData: {
value: valueProp,
},
});
});
it('displays value as hidden', () => {
expect(findFormInputGroup().props('value')).toBe('********************');
});
it('saves actual value to clipboard when manually copied', () => {
const event = createCopyEvent();
findFormInput().element.dispatchEvent(event);
expect(event.clipboardData.setData).toHaveBeenCalledWith('text/plain', valueProp);
expect(event.preventDefault).toHaveBeenCalled();
});
describe('visibility toggle button', () => {
it('renders a reveal button', () => {
const revealButton = findRevealButton();
expect(revealButton.exists()).toBe(true);
const tooltip = getBinding(revealButton.element, 'gl-tooltip');
expect(tooltip.value).toBe(InputCopyToggleVisibility.i18n.toggleVisibilityLabelReveal);
});
describe('when clicked', () => {
beforeEach(async () => {
await findRevealButton().trigger('click');
});
it('displays value', () => {
expect(findFormInputGroup().props('value')).toBe(valueProp);
});
it('renders a hide button', () => {
const hideButton = findHideButton();
expect(hideButton.exists()).toBe(true);
const tooltip = getBinding(hideButton.element, 'gl-tooltip');
expect(tooltip.value).toBe(InputCopyToggleVisibility.i18n.toggleVisibilityLabelHide);
});
it('emits `visibility-change` event', () => {
expect(wrapper.emitted('visibility-change')[0]).toEqual([true]);
});
});
});
describe('copy button', () => {
it('renders button with correct props passed', () => {
expect(findCopyButton().props()).toMatchObject({
text: valueProp,
title: 'Copy',
});
});
describe('when clicked', () => {
beforeEach(async () => {
await findCopyButton().trigger('click');
});
it('emits `copy` event', () => {
expect(wrapper.emitted('copy')[0]).toEqual([]);
});
});
});
});
describe('when `value` prop is not passed', () => {
beforeEach(() => {
createComponent();
});
it('displays value as hidden with 20 asterisks', () => {
expect(findFormInputGroup().props('value')).toBe('********************');
});
});
describe('when `initialVisibility` prop is `true`', () => {
beforeEach(() => {
createComponent({
propsData: {
value: valueProp,
initialVisibility: true,
},
});
});
it('displays value', () => {
expect(findFormInputGroup().props('value')).toBe(valueProp);
});
itDoesNotModifyCopyEvent();
});
describe('when `showToggleVisibilityButton` is `false`', () => {
beforeEach(() => {
createComponent({
propsData: {
value: valueProp,
showToggleVisibilityButton: false,
},
});
});
it('does not render visibility toggle button', () => {
expect(findRevealButton().exists()).toBe(false);
expect(findHideButton().exists()).toBe(false);
});
it('displays value', () => {
expect(findFormInputGroup().props('value')).toBe(valueProp);
});
itDoesNotModifyCopyEvent();
});
describe('when `showCopyButton` is `false`', () => {
beforeEach(() => {
createComponent({
propsData: {
showCopyButton: false,
},
});
});
it('does not render copy button', () => {
expect(findCopyButton().exists()).toBe(false);
});
});
it('passes `formInputGroupProps` prop to `GlFormInputGroup`', () => {
createComponent({
propsData: {
formInputGroupProps: {
label: 'Foo bar',
},
},
});
expect(findFormInputGroup().props('label')).toBe('Foo bar');
});
it('passes `copyButtonTitle` prop to `ClipboardButton`', () => {
createComponent({
propsData: {
copyButtonTitle: 'Copy token',
},
});
expect(findCopyButton().props('title')).toBe('Copy token');
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment