Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
145560a8be
commit
7b87f43b5b
|
@ -1,7 +1,13 @@
|
||||||
import Clipboard from 'clipboard';
|
import ClipboardJS from 'clipboard';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { sprintf, __ } from '~/locale';
|
|
||||||
import { fixTitle, add, show, once } from '~/tooltips';
|
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||||
|
import { __ } from '~/locale';
|
||||||
|
import { fixTitle, add, show, hide, once } from '~/tooltips';
|
||||||
|
|
||||||
|
const CLIPBOARD_SUCCESS_EVENT = 'clipboard-success';
|
||||||
|
const CLIPBOARD_ERROR_EVENT = 'clipboard-error';
|
||||||
|
const I18N_ERROR_MESSAGE = __('Copy failed. Please manually copy the value.');
|
||||||
|
|
||||||
function showTooltip(target, title) {
|
function showTooltip(target, title) {
|
||||||
const { title: originalTitle } = target.dataset;
|
const { title: originalTitle } = target.dataset;
|
||||||
|
@ -9,20 +15,31 @@ function showTooltip(target, title) {
|
||||||
once('hidden', (tooltip) => {
|
once('hidden', (tooltip) => {
|
||||||
if (tooltip.target === target) {
|
if (tooltip.target === target) {
|
||||||
target.setAttribute('title', originalTitle);
|
target.setAttribute('title', originalTitle);
|
||||||
|
target.setAttribute('aria-label', originalTitle);
|
||||||
fixTitle(target);
|
fixTitle(target);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
target.setAttribute('title', title);
|
target.setAttribute('title', title);
|
||||||
|
target.setAttribute('aria-label', title);
|
||||||
fixTitle(target);
|
fixTitle(target);
|
||||||
show(target);
|
show(target);
|
||||||
setTimeout(() => target.blur(), 1000);
|
setTimeout(() => {
|
||||||
|
hide(target);
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function genericSuccess(e) {
|
function genericSuccess(e) {
|
||||||
// Clear the selection and blur the trigger so it loses its border
|
// Clear the selection
|
||||||
e.clearSelection();
|
e.clearSelection();
|
||||||
showTooltip(e.trigger, __('Copied'));
|
e.trigger.focus();
|
||||||
|
e.trigger.dispatchEvent(new Event(CLIPBOARD_SUCCESS_EVENT));
|
||||||
|
|
||||||
|
const { clipboardHandleTooltip = true } = e.trigger.dataset;
|
||||||
|
if (parseBoolean(clipboardHandleTooltip)) {
|
||||||
|
// Update tooltip
|
||||||
|
showTooltip(e.trigger, __('Copied'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,17 +47,16 @@ function genericSuccess(e) {
|
||||||
* See http://clipboardjs.com/#browser-support
|
* See http://clipboardjs.com/#browser-support
|
||||||
*/
|
*/
|
||||||
function genericError(e) {
|
function genericError(e) {
|
||||||
let key;
|
e.trigger.dispatchEvent(new Event(CLIPBOARD_ERROR_EVENT));
|
||||||
if (/Mac/i.test(navigator.userAgent)) {
|
|
||||||
key = '⌘'; // Command
|
const { clipboardHandleTooltip = true } = e.trigger.dataset;
|
||||||
} else {
|
if (parseBoolean(clipboardHandleTooltip)) {
|
||||||
key = 'Ctrl';
|
showTooltip(e.trigger, I18N_ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
showTooltip(e.trigger, sprintf(__(`Press %{key}-C to copy`), { key }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function initCopyToClipboard() {
|
export default function initCopyToClipboard() {
|
||||||
const clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
|
const clipboard = new ClipboardJS('[data-clipboard-target], [data-clipboard-text]');
|
||||||
clipboard.on('success', genericSuccess);
|
clipboard.on('success', genericSuccess);
|
||||||
clipboard.on('error', genericError);
|
clipboard.on('error', genericError);
|
||||||
|
|
||||||
|
@ -74,6 +90,8 @@ export default function initCopyToClipboard() {
|
||||||
clipboardData.setData('text/plain', json.text);
|
clipboardData.setData('text/plain', json.text);
|
||||||
clipboardData.setData('text/x-gfm', json.gfm);
|
clipboardData.setData('text/x-gfm', json.gfm);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return clipboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,3 +107,5 @@ export function clickCopyToClipboardButton(btnElement) {
|
||||||
|
|
||||||
btnElement.click();
|
btnElement.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { CLIPBOARD_SUCCESS_EVENT, CLIPBOARD_ERROR_EVENT, I18N_ERROR_MESSAGE };
|
||||||
|
|
|
@ -13,9 +13,23 @@
|
||||||
* />
|
* />
|
||||||
*/
|
*/
|
||||||
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||||
|
import { uniqueId } from 'lodash';
|
||||||
|
|
||||||
|
import { __ } from '~/locale';
|
||||||
|
import {
|
||||||
|
CLIPBOARD_SUCCESS_EVENT,
|
||||||
|
CLIPBOARD_ERROR_EVENT,
|
||||||
|
I18N_ERROR_MESSAGE,
|
||||||
|
} from '~/behaviors/copy_to_clipboard';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ClipboardButton',
|
name: 'ClipboardButton',
|
||||||
|
i18n: {
|
||||||
|
copied: __('Copied'),
|
||||||
|
error: I18N_ERROR_MESSAGE,
|
||||||
|
},
|
||||||
|
CLIPBOARD_SUCCESS_EVENT,
|
||||||
|
CLIPBOARD_ERROR_EVENT,
|
||||||
directives: {
|
directives: {
|
||||||
GlTooltip: GlTooltipDirective,
|
GlTooltip: GlTooltipDirective,
|
||||||
},
|
},
|
||||||
|
@ -72,6 +86,13 @@ export default {
|
||||||
default: 'default',
|
default: 'default',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
localTitle: this.title,
|
||||||
|
titleTimeout: null,
|
||||||
|
id: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
clipboardText() {
|
clipboardText() {
|
||||||
if (this.gfm !== null) {
|
if (this.gfm !== null) {
|
||||||
|
@ -79,25 +100,50 @@ export default {
|
||||||
}
|
}
|
||||||
return this.text;
|
return this.text;
|
||||||
},
|
},
|
||||||
|
tooltipDirectiveOptions() {
|
||||||
|
return {
|
||||||
|
placement: this.tooltipPlacement,
|
||||||
|
container: this.tooltipContainer,
|
||||||
|
boundary: this.tooltipBoundary,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.id = uniqueId('clipboard-button-');
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateTooltip(title) {
|
||||||
|
this.localTitle = title;
|
||||||
|
this.$root.$emit('bv::show::tooltip', this.id);
|
||||||
|
|
||||||
|
clearTimeout(this.titleTimeout);
|
||||||
|
|
||||||
|
this.titleTimeout = setTimeout(() => {
|
||||||
|
this.localTitle = this.title;
|
||||||
|
this.$root.$emit('bv::hide::tooltip', this.id);
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<gl-button
|
<gl-button
|
||||||
v-gl-tooltip.hover.blur.viewport="{
|
:id="id"
|
||||||
placement: tooltipPlacement,
|
ref="copyButton"
|
||||||
container: tooltipContainer,
|
v-gl-tooltip.hover.focus.click.viewport="tooltipDirectiveOptions"
|
||||||
boundary: tooltipBoundary,
|
|
||||||
}"
|
|
||||||
:class="cssClass"
|
:class="cssClass"
|
||||||
:title="title"
|
:title="localTitle"
|
||||||
:data-clipboard-text="clipboardText"
|
:data-clipboard-text="clipboardText"
|
||||||
|
data-clipboard-handle-tooltip="false"
|
||||||
:category="category"
|
:category="category"
|
||||||
:size="size"
|
:size="size"
|
||||||
icon="copy-to-clipboard"
|
icon="copy-to-clipboard"
|
||||||
:aria-label="__('Copy this value')"
|
|
||||||
:variant="variant"
|
:variant="variant"
|
||||||
|
:aria-label="localTitle"
|
||||||
|
aria-live="polite"
|
||||||
|
@[$options.CLIPBOARD_SUCCESS_EVENT]="updateTooltip($options.i18n.copied)"
|
||||||
|
@[$options.CLIPBOARD_ERROR_EVENT]="updateTooltip($options.i18n.error)"
|
||||||
v-on="$listeners"
|
v-on="$listeners"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||||
import Clipboard from 'clipboard';
|
import ClipboardJS from 'clipboard';
|
||||||
import { uniqueId } from 'lodash';
|
import { uniqueId } from 'lodash';
|
||||||
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
|
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ export default {
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.clipboard = new Clipboard(this.$el, {
|
this.clipboard = new ClipboardJS(this.$el, {
|
||||||
container:
|
container:
|
||||||
document.querySelector(`${this.modalDomId} div.modal-content`) ||
|
document.querySelector(`${this.modalDomId} div.modal-content`) ||
|
||||||
document.getElementById(this.container) ||
|
document.getElementById(this.container) ||
|
||||||
|
|
|
@ -50,7 +50,7 @@ module ButtonHelper
|
||||||
data: data,
|
data: data,
|
||||||
type: :button,
|
type: :button,
|
||||||
title: title,
|
title: title,
|
||||||
aria: { label: title },
|
aria: { label: title, live: 'polite' },
|
||||||
itemprop: item_prop
|
itemprop: item_prop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
%pre
|
%pre
|
||||||
:plain
|
:plain
|
||||||
curl -X POST \
|
curl -X POST \
|
||||||
|
--fail \
|
||||||
-F token=TOKEN \
|
-F token=TOKEN \
|
||||||
-F ref=REF_NAME \
|
-F ref=REF_NAME \
|
||||||
#{builds_trigger_url(@project.id)}
|
#{builds_trigger_url(@project.id)}
|
||||||
|
@ -54,7 +55,7 @@
|
||||||
%pre
|
%pre
|
||||||
:plain
|
:plain
|
||||||
script:
|
script:
|
||||||
- "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
|
- "curl -X POST --fail -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
|
||||||
%h5.gl-mt-3
|
%h5.gl-mt-3
|
||||||
= _('Use webhook')
|
= _('Use webhook')
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@
|
||||||
%pre
|
%pre
|
||||||
:plain
|
:plain
|
||||||
curl -X POST \
|
curl -X POST \
|
||||||
|
--fail \
|
||||||
-F token=TOKEN \
|
-F token=TOKEN \
|
||||||
-F "ref=REF_NAME" \
|
-F "ref=REF_NAME" \
|
||||||
-F "variables[RUN_NIGHTLY_BUILD]=true" \
|
-F "variables[RUN_NIGHTLY_BUILD]=true" \
|
||||||
|
|
|
@ -969,6 +969,8 @@ To view running completed and scheduled on-demand DAST scans for a project, go t
|
||||||
failed, or was canceled.
|
failed, or was canceled.
|
||||||
- To view scheduled scans, select **Scheduled**. It shows on-demand scans that have a schedule
|
- To view scheduled scans, select **Scheduled**. It shows on-demand scans that have a schedule
|
||||||
set up. Those are _not_ included in the **All** tab.
|
set up. Those are _not_ included in the **All** tab.
|
||||||
|
- To view saved on-demand scan profiles, select **Scan library**.
|
||||||
|
Those are _not_ included in the **All** tab.
|
||||||
|
|
||||||
#### Cancel an on-demand scan
|
#### Cancel an on-demand scan
|
||||||
|
|
||||||
|
|
|
@ -9673,6 +9673,9 @@ msgstr ""
|
||||||
msgid "Copy evidence SHA"
|
msgid "Copy evidence SHA"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Copy failed. Please manually copy the value."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Copy file contents"
|
msgid "Copy file contents"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -9712,9 +9715,6 @@ msgstr ""
|
||||||
msgid "Copy this registration token."
|
msgid "Copy this registration token."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Copy this value"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Copy to clipboard"
|
msgid "Copy to clipboard"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -24588,6 +24588,9 @@ msgstr ""
|
||||||
msgid "OnDemandScans|Save scan"
|
msgid "OnDemandScans|Save scan"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "OnDemandScans|Scan library"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "OnDemandScans|Scan name"
|
msgid "OnDemandScans|Scan name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -24624,6 +24627,9 @@ msgstr ""
|
||||||
msgid "OnDemandScans|There are no running scans."
|
msgid "OnDemandScans|There are no running scans."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "OnDemandScans|There are no saved scans."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "OnDemandScans|There are no scheduled scans."
|
msgid "OnDemandScans|There are no scheduled scans."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -26665,9 +26671,6 @@ msgstr ""
|
||||||
msgid "Preferences|Use relative times"
|
msgid "Preferences|Use relative times"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Press %{key}-C to copy"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Prev"
|
msgid "Prev"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@
|
||||||
"bootstrap": "4.5.3",
|
"bootstrap": "4.5.3",
|
||||||
"cache-loader": "^4.1.0",
|
"cache-loader": "^4.1.0",
|
||||||
"canvas-confetti": "^1.4.0",
|
"canvas-confetti": "^1.4.0",
|
||||||
"clipboard": "^1.7.1",
|
"clipboard": "^2.0.8",
|
||||||
"codemirror": "^5.48.4",
|
"codemirror": "^5.48.4",
|
||||||
"codesandbox-api": "0.0.23",
|
"codesandbox-api": "0.0.23",
|
||||||
"compression-webpack-plugin": "^5.0.2",
|
"compression-webpack-plugin": "^5.0.2",
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
import initCopyToClipboard, {
|
||||||
|
CLIPBOARD_SUCCESS_EVENT,
|
||||||
|
CLIPBOARD_ERROR_EVENT,
|
||||||
|
I18N_ERROR_MESSAGE,
|
||||||
|
} from '~/behaviors/copy_to_clipboard';
|
||||||
|
import { show, hide, fixTitle, once } from '~/tooltips';
|
||||||
|
|
||||||
|
let onceCallback = () => {};
|
||||||
|
jest.mock('~/tooltips', () => ({
|
||||||
|
show: jest.fn(),
|
||||||
|
hide: jest.fn(),
|
||||||
|
fixTitle: jest.fn(),
|
||||||
|
once: jest.fn((event, callback) => {
|
||||||
|
onceCallback = callback;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('initCopyToClipboard', () => {
|
||||||
|
let clearSelection;
|
||||||
|
let focusSpy;
|
||||||
|
let dispatchEventSpy;
|
||||||
|
let button;
|
||||||
|
let clipboardInstance;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
clipboardInstance = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const title = 'Copy this value';
|
||||||
|
const defaultButtonAttributes = {
|
||||||
|
'data-clipboard-text': 'foo bar',
|
||||||
|
title,
|
||||||
|
'data-title': title,
|
||||||
|
};
|
||||||
|
const createButton = (attributes = {}) => {
|
||||||
|
const combinedAttributes = { ...defaultButtonAttributes, ...attributes };
|
||||||
|
button = document.createElement('button');
|
||||||
|
Object.keys(combinedAttributes).forEach((attributeName) => {
|
||||||
|
button.setAttribute(attributeName, combinedAttributes[attributeName]);
|
||||||
|
});
|
||||||
|
document.body.appendChild(button);
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
clipboardInstance = initCopyToClipboard();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupSpies = () => {
|
||||||
|
clearSelection = jest.fn();
|
||||||
|
focusSpy = jest.spyOn(button, 'focus');
|
||||||
|
dispatchEventSpy = jest.spyOn(button, 'dispatchEvent');
|
||||||
|
};
|
||||||
|
|
||||||
|
const emitSuccessEvent = () => {
|
||||||
|
clipboardInstance.emit('success', {
|
||||||
|
action: 'copy',
|
||||||
|
text: 'foo bar',
|
||||||
|
trigger: button,
|
||||||
|
clearSelection,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const emitErrorEvent = () => {
|
||||||
|
clipboardInstance.emit('error', {
|
||||||
|
action: 'copy',
|
||||||
|
text: 'foo bar',
|
||||||
|
trigger: button,
|
||||||
|
clearSelection,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const itHandlesTooltip = (expectedTooltip) => {
|
||||||
|
it('handles tooltip', () => {
|
||||||
|
expect(button.getAttribute('title')).toBe(expectedTooltip);
|
||||||
|
expect(button.getAttribute('aria-label')).toBe(expectedTooltip);
|
||||||
|
expect(fixTitle).toHaveBeenCalledWith(button);
|
||||||
|
expect(show).toHaveBeenCalledWith(button);
|
||||||
|
expect(once).toHaveBeenCalledWith('hidden', expect.any(Function));
|
||||||
|
|
||||||
|
expect(hide).not.toHaveBeenCalled();
|
||||||
|
jest.runAllTimers();
|
||||||
|
expect(hide).toHaveBeenCalled();
|
||||||
|
|
||||||
|
onceCallback({ target: button });
|
||||||
|
expect(button.getAttribute('title')).toBe(title);
|
||||||
|
expect(button.getAttribute('aria-label')).toBe(title);
|
||||||
|
expect(fixTitle).toHaveBeenCalledWith(button);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('when value is successfully copied', () => {
|
||||||
|
it(`calls clearSelection, focuses the button, and dispatches ${CLIPBOARD_SUCCESS_EVENT} event`, () => {
|
||||||
|
createButton();
|
||||||
|
init();
|
||||||
|
setupSpies();
|
||||||
|
emitSuccessEvent();
|
||||||
|
|
||||||
|
expect(clearSelection).toHaveBeenCalled();
|
||||||
|
expect(focusSpy).toHaveBeenCalled();
|
||||||
|
expect(dispatchEventSpy).toHaveBeenCalledWith(new Event(CLIPBOARD_SUCCESS_EVENT));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when `data-clipboard-handle-tooltip` is set to `false`', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createButton({
|
||||||
|
'data-clipboard-handle-tooltip': 'false',
|
||||||
|
});
|
||||||
|
init();
|
||||||
|
emitSuccessEvent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not handle success tooltip', () => {
|
||||||
|
expect(show).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when `data-clipboard-handle-tooltip` is set to `true`', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createButton({
|
||||||
|
'data-clipboard-handle-tooltip': 'true',
|
||||||
|
});
|
||||||
|
init();
|
||||||
|
emitSuccessEvent();
|
||||||
|
});
|
||||||
|
|
||||||
|
itHandlesTooltip('Copied');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when `data-clipboard-handle-tooltip` is not set', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createButton();
|
||||||
|
init();
|
||||||
|
emitSuccessEvent();
|
||||||
|
});
|
||||||
|
|
||||||
|
itHandlesTooltip('Copied');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when there is an error copying the value', () => {
|
||||||
|
it(`dispatches ${CLIPBOARD_ERROR_EVENT} event`, () => {
|
||||||
|
createButton();
|
||||||
|
init();
|
||||||
|
setupSpies();
|
||||||
|
emitErrorEvent();
|
||||||
|
|
||||||
|
expect(dispatchEventSpy).toHaveBeenCalledWith(new Event(CLIPBOARD_ERROR_EVENT));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when `data-clipboard-handle-tooltip` is set to `false`', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createButton({
|
||||||
|
'data-clipboard-handle-tooltip': 'false',
|
||||||
|
});
|
||||||
|
init();
|
||||||
|
emitErrorEvent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not handle error tooltip', () => {
|
||||||
|
expect(show).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when `data-clipboard-handle-tooltip` is set to `true`', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createButton({
|
||||||
|
'data-clipboard-handle-tooltip': 'true',
|
||||||
|
});
|
||||||
|
init();
|
||||||
|
emitErrorEvent();
|
||||||
|
});
|
||||||
|
|
||||||
|
itHandlesTooltip(I18N_ERROR_MESSAGE);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when `data-clipboard-handle-tooltip` is not set', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createButton();
|
||||||
|
init();
|
||||||
|
emitErrorEvent();
|
||||||
|
});
|
||||||
|
|
||||||
|
itHandlesTooltip(I18N_ERROR_MESSAGE);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -15,11 +15,14 @@ exports[`FileSha renders 1`] = `
|
||||||
foo
|
foo
|
||||||
|
|
||||||
<gl-button-stub
|
<gl-button-stub
|
||||||
aria-label="Copy this value"
|
aria-label="Copy SHA"
|
||||||
|
aria-live="polite"
|
||||||
buttontextclasses=""
|
buttontextclasses=""
|
||||||
category="tertiary"
|
category="tertiary"
|
||||||
|
data-clipboard-handle-tooltip="false"
|
||||||
data-clipboard-text="foo"
|
data-clipboard-text="foo"
|
||||||
icon="copy-to-clipboard"
|
icon="copy-to-clipboard"
|
||||||
|
id="clipboard-button-1"
|
||||||
size="small"
|
size="small"
|
||||||
title="Copy SHA"
|
title="Copy SHA"
|
||||||
variant="default"
|
variant="default"
|
||||||
|
|
|
@ -4,6 +4,8 @@ import FileSha from '~/packages_and_registries/infrastructure_registry/details/c
|
||||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||||
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
|
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
|
||||||
|
|
||||||
|
jest.mock('lodash/uniqueId', () => (prefix) => (prefix ? `${prefix}1` : 1));
|
||||||
|
|
||||||
describe('FileSha', () => {
|
describe('FileSha', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,14 @@ exports[`FileSha renders 1`] = `
|
||||||
foo
|
foo
|
||||||
|
|
||||||
<gl-button-stub
|
<gl-button-stub
|
||||||
aria-label="Copy this value"
|
aria-label="Copy SHA"
|
||||||
|
aria-live="polite"
|
||||||
buttontextclasses=""
|
buttontextclasses=""
|
||||||
category="tertiary"
|
category="tertiary"
|
||||||
|
data-clipboard-handle-tooltip="false"
|
||||||
data-clipboard-text="foo"
|
data-clipboard-text="foo"
|
||||||
icon="copy-to-clipboard"
|
icon="copy-to-clipboard"
|
||||||
|
id="clipboard-button-1"
|
||||||
size="small"
|
size="small"
|
||||||
title="Copy SHA"
|
title="Copy SHA"
|
||||||
variant="default"
|
variant="default"
|
||||||
|
|
|
@ -4,6 +4,8 @@ import FileSha from '~/packages_and_registries/package_registry/components/detai
|
||||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||||
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
|
import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
|
||||||
|
|
||||||
|
jest.mock('lodash/uniqueId', () => (prefix) => (prefix ? `${prefix}1` : 1));
|
||||||
|
|
||||||
describe('FileSha', () => {
|
describe('FileSha', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
import { GlButton } from '@gitlab/ui';
|
import { GlButton } from '@gitlab/ui';
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import initCopyToClipboard from '~/behaviors/copy_to_clipboard';
|
import { nextTick } from 'vue';
|
||||||
|
|
||||||
|
import initCopyToClipboard, {
|
||||||
|
CLIPBOARD_SUCCESS_EVENT,
|
||||||
|
CLIPBOARD_ERROR_EVENT,
|
||||||
|
I18N_ERROR_MESSAGE,
|
||||||
|
} from '~/behaviors/copy_to_clipboard';
|
||||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||||
|
|
||||||
|
jest.mock('lodash/uniqueId', () => (prefix) => (prefix ? `${prefix}1` : 1));
|
||||||
|
|
||||||
describe('clipboard button', () => {
|
describe('clipboard button', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
|
@ -15,6 +23,42 @@ describe('clipboard button', () => {
|
||||||
|
|
||||||
const findButton = () => wrapper.find(GlButton);
|
const findButton = () => wrapper.find(GlButton);
|
||||||
|
|
||||||
|
const expectConfirmationTooltip = async ({ event, message }) => {
|
||||||
|
const title = 'Copy this value';
|
||||||
|
|
||||||
|
createWrapper({
|
||||||
|
text: 'copy me',
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.vm.$root.$emit = jest.fn();
|
||||||
|
|
||||||
|
const button = findButton();
|
||||||
|
|
||||||
|
expect(button.attributes()).toMatchObject({
|
||||||
|
title,
|
||||||
|
'aria-label': title,
|
||||||
|
});
|
||||||
|
|
||||||
|
await button.trigger(event);
|
||||||
|
|
||||||
|
expect(wrapper.vm.$root.$emit).toHaveBeenCalledWith('bv::show::tooltip', 'clipboard-button-1');
|
||||||
|
|
||||||
|
expect(button.attributes()).toMatchObject({
|
||||||
|
title: message,
|
||||||
|
'aria-label': message,
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.runAllTimers();
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(button.attributes()).toMatchObject({
|
||||||
|
title,
|
||||||
|
'aria-label': title,
|
||||||
|
});
|
||||||
|
expect(wrapper.vm.$root.$emit).toHaveBeenCalledWith('bv::hide::tooltip', 'clipboard-button-1');
|
||||||
|
};
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
wrapper = null;
|
wrapper = null;
|
||||||
|
@ -99,6 +143,32 @@ describe('clipboard button', () => {
|
||||||
expect(findButton().props('variant')).toBe(variant);
|
expect(findButton().props('variant')).toBe(variant);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('confirmation tooltip', () => {
|
||||||
|
it('adds `id` and `data-clipboard-handle-tooltip` attributes to button', () => {
|
||||||
|
createWrapper({
|
||||||
|
text: 'copy me',
|
||||||
|
title: 'Copy this value',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(findButton().attributes()).toMatchObject({
|
||||||
|
id: 'clipboard-button-1',
|
||||||
|
'data-clipboard-handle-tooltip': 'false',
|
||||||
|
'aria-live': 'polite',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows success tooltip after successful copy', () => {
|
||||||
|
expectConfirmationTooltip({
|
||||||
|
event: CLIPBOARD_SUCCESS_EVENT,
|
||||||
|
message: ClipboardButton.i18n.copied,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows error tooltip after failed copy', () => {
|
||||||
|
expectConfirmationTooltip({ event: CLIPBOARD_ERROR_EVENT, message: I18N_ERROR_MESSAGE });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('integration', () => {
|
describe('integration', () => {
|
||||||
it('actually copies to clipboard', () => {
|
it('actually copies to clipboard', () => {
|
||||||
initCopyToClipboard();
|
initCopyToClipboard();
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
exports[`Package code instruction multiline to match the snapshot 1`] = `
|
exports[`Package code instruction multiline to match the snapshot 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="instruction-input_3"
|
for="instruction-input_1"
|
||||||
>
|
>
|
||||||
foo_label
|
foo_label
|
||||||
</label>
|
</label>
|
||||||
|
@ -23,7 +23,7 @@ multiline text
|
||||||
exports[`Package code instruction single line to match the default snapshot 1`] = `
|
exports[`Package code instruction single line to match the default snapshot 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="instruction-input_2"
|
for="instruction-input_1"
|
||||||
>
|
>
|
||||||
foo_label
|
foo_label
|
||||||
</label>
|
</label>
|
||||||
|
@ -37,7 +37,7 @@ exports[`Package code instruction single line to match the default snapshot 1`]
|
||||||
<input
|
<input
|
||||||
class="form-control gl-font-monospace"
|
class="form-control gl-font-monospace"
|
||||||
data-testid="instruction-input"
|
data-testid="instruction-input"
|
||||||
id="instruction-input_2"
|
id="instruction-input_1"
|
||||||
readonly="readonly"
|
readonly="readonly"
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
|
@ -47,9 +47,12 @@ exports[`Package code instruction single line to match the default snapshot 1`]
|
||||||
data-testid="instruction-button"
|
data-testid="instruction-button"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="Copy this value"
|
aria-label="Copy npm install command"
|
||||||
|
aria-live="polite"
|
||||||
class="btn input-group-text btn-default btn-md gl-button btn-default-secondary btn-icon"
|
class="btn input-group-text btn-default btn-md gl-button btn-default-secondary btn-icon"
|
||||||
|
data-clipboard-handle-tooltip="false"
|
||||||
data-clipboard-text="npm i @my-package"
|
data-clipboard-text="npm i @my-package"
|
||||||
|
id="clipboard-button-1"
|
||||||
title="Copy npm install command"
|
title="Copy npm install command"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,6 +3,8 @@ import Tracking from '~/tracking';
|
||||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||||
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
|
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
|
||||||
|
|
||||||
|
jest.mock('lodash/uniqueId', () => (prefix) => (prefix ? `${prefix}1` : 1));
|
||||||
|
|
||||||
describe('Package code instruction', () => {
|
describe('Package code instruction', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
|
|
|
@ -167,6 +167,7 @@ RSpec.describe ButtonHelper do
|
||||||
expect(element.attr('class')).to eq('btn btn-clipboard btn-transparent')
|
expect(element.attr('class')).to eq('btn btn-clipboard btn-transparent')
|
||||||
expect(element.attr('type')).to eq('button')
|
expect(element.attr('type')).to eq('button')
|
||||||
expect(element.attr('aria-label')).to eq('Copy')
|
expect(element.attr('aria-label')).to eq('Copy')
|
||||||
|
expect(element.attr('aria-live')).to eq('polite')
|
||||||
expect(element.attr('data-toggle')).to eq('tooltip')
|
expect(element.attr('data-toggle')).to eq('tooltip')
|
||||||
expect(element.attr('data-placement')).to eq('bottom')
|
expect(element.attr('data-placement')).to eq('bottom')
|
||||||
expect(element.attr('data-container')).to eq('body')
|
expect(element.attr('data-container')).to eq('body')
|
||||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -3435,19 +3435,10 @@ cli-boxes@^2.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d"
|
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d"
|
||||||
integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==
|
integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==
|
||||||
|
|
||||||
clipboard@^1.7.1:
|
clipboard@^2.0.0, clipboard@^2.0.8:
|
||||||
version "1.7.1"
|
version "2.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-1.7.1.tgz#360d6d6946e99a7a1fef395e42ba92b5e9b5a16b"
|
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.8.tgz#ffc6c103dd2967a83005f3f61976aa4655a4cdba"
|
||||||
integrity sha1-Ng1taUbpmnof7zleQrqStem1oWs=
|
integrity sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==
|
||||||
dependencies:
|
|
||||||
good-listener "^1.2.2"
|
|
||||||
select "^1.1.2"
|
|
||||||
tiny-emitter "^2.0.0"
|
|
||||||
|
|
||||||
clipboard@^2.0.0:
|
|
||||||
version "2.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376"
|
|
||||||
integrity sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
good-listener "^1.2.2"
|
good-listener "^1.2.2"
|
||||||
select "^1.1.2"
|
select "^1.1.2"
|
||||||
|
|
Loading…
Reference in New Issue