ui: Implements a testable clipboard {{copy-button}} (#5967)

1. Remove ember-cli-clipboard dependency
2. Provide a new copy-button component implementing the same
interface as ^ but make the clipboard functionality injectable
3. Provide 2 implementations of a Clipboard. One using clipboard.js (as
previously) and an additional local storage 'clipboard'.
4. add a BDD step to assert whats in the clipboard (the fake one)

Main reason here is that `document.exec` must be called by a user
interaction
This commit is contained in:
John Cowen 2019-06-21 11:42:40 +01:00 committed by GitHub
parent 490242b4b8
commit e4e209b748
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 124 additions and 36 deletions

View File

@ -0,0 +1,39 @@
import Component from '@ember/component';
import { get } from '@ember/object';
import { inject as service } from '@ember/service';
import WithListeners from 'consul-ui/mixins/with-listeners';
export default Component.extend(WithListeners, {
clipboard: service('clipboard/os'),
tagName: 'button',
classNames: ['copy-btn'],
buttonType: 'button',
disabled: false,
error: function() {},
success: function() {},
attributeBindings: [
'clipboardText:data-clipboard-text',
'clipboardTarget:data-clipboard-target',
'clipboardAction:data-clipboard-action',
'buttonType:type',
'disabled',
'aria-label',
'title',
],
delegateClickEvent: true,
didInsertElement: function() {
this._super(...arguments);
const clipboard = get(this, 'clipboard').execute(
this.delegateClickEvent ? `#${this.elementId}` : this.element
);
['success', 'error'].map(event => {
return this.listen(clipboard, event, () => {
if (!this.disabled) {
this[event](...arguments);
}
});
});
},
});

View File

@ -0,0 +1,28 @@
import Service from '@ember/service';
import { get } from '@ember/object';
import Clipboard from 'npm:clipboard';
class ClipboardCallback extends Clipboard {
constructor(trigger, cb) {
super(trigger);
this._cb = cb;
}
onClick(e) {
this._cb(this.text(e.delegateTarget || e.currentTarget));
// Clipboard uses/extends `tiny-emitter`
// TODO: We should probably fill this out to match the obj passed from
// os implementation
this.emit('success', {});
}
}
export default Service.extend({
storage: window.localStorage,
key: 'clipboard',
execute: function(trigger) {
return new ClipboardCallback(trigger, val => {
get(this, 'storage').setItem(get(this, 'key'), val);
});
},
});

View File

@ -0,0 +1,9 @@
import Service from '@ember/service';
import Clipboard from 'npm:clipboard';
export default Service.extend({
execute: function(trigger) {
return new Clipboard(trigger);
},
});

View File

@ -58,7 +58,6 @@
"ember-cli-autoprefixer": "^0.8.1",
"ember-cli-babel": "^6.6.0",
"ember-cli-cjs-transform": "^1.2.0",
"ember-cli-clipboard": "^0.9.0",
"ember-cli-code-coverage": "^1.0.0-beta.4",
"ember-cli-dependency-checker": "^2.0.0",
"ember-cli-eslint": "^4.2.1",
@ -102,7 +101,8 @@
"node-sass": "^4.9.3",
"prettier": "^1.10.2",
"svgo": "^1.0.5",
"text-encoding": "^0.6.4"
"text-encoding": "^0.6.4",
"clipboard": "^2.0.4"
},
"engines": {
"node": "^4.5 || 6.* || >= 7.*"

View File

@ -70,6 +70,9 @@ export default function(assert, library, pages, utils) {
}
return obj;
};
const clipboard = function() {
return window.localStorage.getItem('clipboard');
};
models(library, utils.create);
http(library, utils.respondWith, utils.set);
visit(library, pages, setCurrentPage);
@ -79,7 +82,7 @@ export default function(assert, library, pages, utils) {
assertHttp(library, assert, utils.lastNthRequest);
assertModel(library, assert, find, getCurrentPage, pauseUntil, utils.pluralize);
assertPage(library, assert, find, getCurrentPage);
assertDom(library, assert, pauseUntil, utils.find, utils.currentURL);
assertDom(library, assert, pauseUntil, utils.find, utils.currentURL, clipboard);
assertForm(library, assert, find, getCurrentPage);
return library.given(["I'm using a legacy token"], function(number, model, data) {

View File

@ -1,4 +1,4 @@
export default function(scenario, assert, pauseUntil, find, currentURL) {
export default function(scenario, assert, pauseUntil, find, currentURL, clipboard) {
scenario
.then('pause until I see the text "$text" in "$selector"', function(text, selector) {
return pauseUntil(function(resolve) {
@ -18,6 +18,13 @@ export default function(scenario, assert, pauseUntil, find, currentURL) {
`Expected to see "${text}" in "${selector}"`
);
})
.then(['I copied "$text"'], function(text) {
const copied = clipboard();
assert.ok(
copied.indexOf(text) !== -1,
`Expected to see "${text}" in the clipboard, was "${copied}"`
);
})
.then(['I see the exact text "$text" in "$selector"'], function(text, selector) {
assert.ok(
find(selector).textContent.trim() === text,

View File

@ -6,6 +6,8 @@ import './helpers/flash-message';
import loadEmberExam from 'ember-exam/test-support/load';
loadEmberExam();
setApplication(Application.create(config.APP));
const application = Application.create(config.APP);
application.inject('component:copy-button', 'clipboard', 'service:clipboard/local-storage');
setApplication(application);
start();

View File

@ -0,0 +1,12 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('service:clipboard/local-storage', 'Unit | Service | clipboard/local storage', {
// Specify the other units that are required for this test.
// needs: ['service:foo']
});
// Replace this with your real tests.
test('it exists', function(assert) {
let service = this.subject();
assert.ok(service);
});

View File

@ -0,0 +1,12 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('service:clipboard/os', 'Unit | Service | clipboard/os', {
// Specify the other units that are required for this test.
// needs: ['service:foo']
});
// Replace this with your real tests.
test('it exists', function(assert) {
let service = this.subject();
assert.ok(service);
});

View File

@ -2600,7 +2600,7 @@ broccoli-middleware@^1.0.0:
handlebars "^4.0.4"
mime-types "^2.1.18"
broccoli-persistent-filter@^1.0.3, broccoli-persistent-filter@^1.1.6, broccoli-persistent-filter@^1.4.0, broccoli-persistent-filter@^1.4.3:
broccoli-persistent-filter@^1.1.6, broccoli-persistent-filter@^1.4.0, broccoli-persistent-filter@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-1.4.3.tgz#3511bc52fc53740cda51621f58a28152d9911bc1"
dependencies:
@ -2710,7 +2710,7 @@ broccoli-sri-hash@^2.1.0:
sri-toolbox "^0.2.0"
symlink-or-copy "^1.0.1"
broccoli-stew@^1.2.0, broccoli-stew@^1.3.3, broccoli-stew@^1.5.0:
broccoli-stew@^1.2.0, broccoli-stew@^1.3.3:
version "1.5.0"
resolved "https://registry.yarnpkg.com/broccoli-stew/-/broccoli-stew-1.5.0.tgz#d7af8c18511dce510e49d308a62e5977f461883c"
dependencies:
@ -3306,9 +3306,10 @@ cli-width@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
clipboard@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.1.tgz#a12481e1c13d8a50f5f036b0560fe5d16d74e46a"
clipboard@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d"
integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
@ -4194,7 +4195,7 @@ ember-cli-babel-plugin-helpers@^1.1.0:
resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.0.tgz#de3baedd093163b6c2461f95964888c1676325ac"
integrity sha512-Zr4my8Xn+CzO0gIuFNXji0eTRml5AxZUTDQz/wsNJ5AJAtyFWCY4QtKdoELNNbiCVGt1lq5yLiwTm4scGKu6xA==
ember-cli-babel@6.12.0, ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.10.0, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.7.2, ember-cli-babel@^6.8.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.9.0, ember-cli-babel@^6.9.2:
ember-cli-babel@6.12.0, ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.10.0, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.7.2, ember-cli-babel@^6.8.1, ember-cli-babel@^6.9.0, ember-cli-babel@^6.9.2:
version "6.12.0"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.12.0.tgz#3adcdbe1278da1fcd0b9038f1360cb4ac5d4414c"
dependencies:
@ -4343,16 +4344,6 @@ ember-cli-cjs-transform@^1.2.0:
rollup-plugin-node-resolve "^3.3.0"
username "^3.0.0"
ember-cli-clipboard@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/ember-cli-clipboard/-/ember-cli-clipboard-0.9.0.tgz#c0cfce1a8a81ba1646e54bff9d41249b8bc507f7"
dependencies:
broccoli-funnel "^1.1.0"
clipboard "^2.0.0"
ember-cli-babel "^6.8.0"
ember-cli-htmlbars "^2.0.2"
fastboot-transform "0.1.1"
ember-cli-code-coverage@^1.0.0-beta.4:
version "1.0.0-beta.6"
resolved "https://registry.yarnpkg.com/ember-cli-code-coverage/-/ember-cli-code-coverage-1.0.0-beta.6.tgz#7da5dc4e2eb7c207b827b6cbdf91a96d0cca0983"
@ -4425,15 +4416,6 @@ ember-cli-htmlbars@^2.0.1, ember-cli-htmlbars@^2.0.3:
json-stable-stringify "^1.0.0"
strip-bom "^3.0.0"
ember-cli-htmlbars@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-2.0.3.tgz#e116e1500dba12f29c94b05b9ec90f52cb8bb042"
dependencies:
broccoli-persistent-filter "^1.0.3"
hash-for-dep "^1.0.2"
json-stable-stringify "^1.0.0"
strip-bom "^3.0.0"
ember-cli-htmlbars@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-3.0.0.tgz#4977b9eddbc725f8da25090ecdbba64533b2eadc"
@ -5716,12 +5698,6 @@ fast-sourcemap-concat@^1.0.1:
source-map-url "^0.3.0"
sourcemap-validator "^1.0.5"
fastboot-transform@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/fastboot-transform/-/fastboot-transform-0.1.1.tgz#de55550d85644ec94cb11264c2ba883e3ea3b255"
dependencies:
broccoli-stew "^1.5.0"
faye-websocket@~0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"