Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-12-23 06:10:27 +00:00
parent 145560a8be
commit 7b87f43b5b
18 changed files with 388 additions and 51 deletions

View File

@ -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 };

View File

@ -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>

View File

@ -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) ||

View File

@ -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
} }

View File

@ -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" \

View File

@ -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

View File

@ -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 ""

View File

@ -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",

View File

@ -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);
});
});
});

View File

@ -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"

View File

@ -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;

View File

@ -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"

View File

@ -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;

View File

@ -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();

View File

@ -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"
> >

View File

@ -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;

View File

@ -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')

View File

@ -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"