From 0e7d455351a666be619d9529706db15ae181b817 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Thu, 19 Jan 2017 11:20:33 +0000 Subject: [PATCH] de-couple gh-task-button from gh-spin-button --- ghost/admin/app/components/gh-task-button.js | 42 ++++--- .../templates/components/gh-task-button.hbs | 9 ++ .../components/gh-task-button-test.js | 106 +++++++++++++++++- 3 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 ghost/admin/app/templates/components/gh-task-button.hbs diff --git a/ghost/admin/app/components/gh-task-button.js b/ghost/admin/app/components/gh-task-button.js index e0056db8ce..43f2d8b277 100644 --- a/ghost/admin/app/components/gh-task-button.js +++ b/ghost/admin/app/components/gh-task-button.js @@ -1,9 +1,8 @@ +import Component from 'ember-component'; +import observer from 'ember-metal/observer'; import {reads} from 'ember-computed'; import {invokeAction} from 'ember-invoke-action'; -import SpinButton from './gh-spin-button'; -import layout from 'ghost-admin/templates/components/gh-spin-button'; - /** * Task Button works exactly like Spin button, but with one major difference: * @@ -15,30 +14,45 @@ import layout from 'ghost-admin/templates/components/gh-spin-button'; * component, all running promises will automatically be cancelled when this * component is removed from the DOM */ -export default SpinButton.extend({ - layout, // This is used to we don't have to re-implement the template - - classNameBindings: ['showSpinner:appear-disabled'], +export default Component.extend({ + tagName: 'button', + classNameBindings: ['isRunning:appear-disabled'], + attributeBindings: ['disabled', 'type', 'tabindex'], task: null, - - submitting: reads('task.last.isRunning'), disabled: false, + isRunning: reads('task.last.isRunning'), + click() { + // do nothing if disabled externally + if (this.get('disabled')) { + return false; + } + let task = this.get('task'); let taskName = this.get('task.name'); let lastTaskName = this.get('task.last.task.name'); - // task-buttons are never truly disabled so that clicks when a taskGroup - // is running don't get dropped however that means we need to check here - // so we don't spam actions through multiple clicks - if (this.get('showSpinner') && taskName === lastTaskName) { + // task-buttons are never disabled whilst running so that clicks when a + // taskGroup is running don't get dropped BUT that means we need to check + // here to avoid spamming actions from multiple clicks + if (this.get('isRunning') && taskName === lastTaskName) { return; } invokeAction(this, 'action'); return task.perform(); - } + }, + + setSize: observer('isRunning', function () { + if (this.get('isRunning')) { + this.$().width(this.$().width()); + this.$().height(this.$().height()); + } else { + this.$().width(''); + this.$().height(''); + } + }) }); diff --git a/ghost/admin/app/templates/components/gh-task-button.hbs b/ghost/admin/app/templates/components/gh-task-button.hbs new file mode 100644 index 0000000000..8a568759e3 --- /dev/null +++ b/ghost/admin/app/templates/components/gh-task-button.hbs @@ -0,0 +1,9 @@ +{{#if isRunning}} + +{{else}} + {{#if buttonText}} + {{buttonText}} + {{else}} + {{{yield}}} + {{/if}} +{{/if}} \ No newline at end of file diff --git a/ghost/admin/tests/integration/components/gh-task-button-test.js b/ghost/admin/tests/integration/components/gh-task-button-test.js index ac010e9517..0253052ebf 100644 --- a/ghost/admin/tests/integration/components/gh-task-button-test.js +++ b/ghost/admin/tests/integration/components/gh-task-button-test.js @@ -3,6 +3,9 @@ import {expect} from 'chai'; import {describe, it} from 'mocha'; import {setupComponentTest} from 'ember-mocha'; import hbs from 'htmlbars-inline-precompile'; +import {task, timeout} from 'ember-concurrency'; +import run from 'ember-runloop'; +import wait from 'ember-test-helpers/wait'; describe('Integration: Component: gh-task-button', function() { setupComponentTest('gh-task-button', { @@ -11,9 +14,106 @@ describe('Integration: Component: gh-task-button', function() { it('renders', function () { this.render(hbs`{{#gh-task-button}}Test{{/gh-task-button}}`); - expect(this.$()).to.have.length(1); - expect(this.$().text().trim()).to.equal('Test'); + expect(this.$('button')).to.exist; + expect(this.$('button')).to.contain('Test'); + expect(this.$('button')).to.have.prop('disabled', false); + + this.render(hbs`{{#gh-task-button class="testing"}}Test{{/gh-task-button}}`); + expect(this.$('button')).to.have.class('testing'); + + this.render(hbs`{{#gh-task-button disabled=true}}Test{{/gh-task-button}}`); + expect(this.$('button')).to.have.prop('disabled', true); + + this.render(hbs`{{#gh-task-button type="submit"}}Test{{/gh-task-button}}`); + expect(this.$('button')).to.have.attr('type', 'submit'); + + this.render(hbs`{{#gh-task-button tabindex="-1"}}Test{{/gh-task-button}}`); + expect(this.$('button')).to.have.attr('tabindex', '-1'); }); - // TODO: figure out how to test concurrency behavior + it('shows spinner whilst running', function (done) { + this.set('myTask', task(function* () { + yield timeout(50); + })); + + this.render(hbs`{{#gh-task-button task=myTask}}Test{{/gh-task-button}}`); + + this.get('myTask').perform(); + + run.later(this, function () { + expect(this.$('button')).to.have.descendants('span.spinner'); + }, 20); + + wait().then(done); + }); + + it('appears disabled whilst running', function (done) { + this.set('myTask', task(function* () { + yield timeout(50); + })); + + this.render(hbs`{{#gh-task-button task=myTask}}Test{{/gh-task-button}}`); + expect(this.$('button'), 'initial class').to.not.have.class('appear-disabled'); + + this.get('myTask').perform(); + + run.later(this, function () { + expect(this.$('button'), 'running class').to.have.class('appear-disabled'); + }, 20); + + run.later(this, function () { + expect(this.$('button'), 'ended class').to.not.have.class('appear-disabled'); + }, 70); + + wait().then(done); + }); + + it('performs task on click', function (done) { + let taskCount = 0; + + this.set('myTask', task(function* () { + yield timeout(50); + taskCount = taskCount + 1; + })); + + this.render(hbs`{{#gh-task-button task=myTask}}Test{{/gh-task-button}}`); + this.$('button').click(); + + wait().then(() => { + expect(taskCount, 'taskCount').to.equal(1); + done(); + }); + }); + + it('keeps button size when showing spinner', function (done) { + this.set('myTask', task(function* () { + yield timeout(50); + })); + + this.render(hbs`{{#gh-task-button task=myTask}}Test{{/gh-task-button}}`); + let width = this.$('button').width(); + let height = this.$('button').height(); + expect(this.$('button')).to.not.have.attr('style'); + + this.get('myTask').perform(); + + run.later(this, function () { + // we can't test exact width/height because Chrome/Firefox use different rounding methods + // expect(this.$('button')).to.have.attr('style', `width: ${width}px; height: ${height}px;`); + + let [widthInt] = width.toString().split('.'); + let [heightInt] = height.toString().split('.'); + + expect(this.$('button').attr('style')).to.have.string(`width: ${widthInt}`); + expect(this.$('button').attr('style')).to.have.string(`height: ${heightInt}`); + }, 20); + + run.later(this, function () { + // chai-jquery test doesn't work because Firefox outputs blank string + // expect(this.$('button')).to.not.have.attr('style'); + expect(this.$('button').attr('style')).to.be.blank; + }, 70); + + wait().then(done); + }); });