Skip to content

Commit

Permalink
[adopted-ember-addons#18] - defer removal of flash message (adopted-e…
Browse files Browse the repository at this point in the history
…mber-addons#209)

* defer removal of flash message

* add unit test for pausing methods

* add integration test for mouseover events
  • Loading branch information
Scott Batson authored Oct 26, 2016
1 parent a689a98 commit f5002eb
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 11 deletions.
15 changes: 15 additions & 0 deletions addon/components/flash-message.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
String: { classify, htmlSafe },
Component,
getWithDefault,
isPresent,
run,
on,
get,
Expand Down Expand Up @@ -79,6 +80,20 @@ export default Component.extend({
}
},

mouseEnter() {
const flash = get(this, 'flash');
if (isPresent(flash)) {
flash.deferTimers();
}
},

mouseLeave() {
const flash = get(this, 'flash');
if (isPresent(flash)) {
flash.resumeTimers();
}
},

willDestroy() {
this._super();
this._destroyFlashMessage();
Expand Down
51 changes: 43 additions & 8 deletions addon/flash/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import computed from 'ember-new-computed';

const {
Object: EmberObject,
run: { later, cancel },
Evented,
get,
run: { later, cancel },
set
} = Ember;
const {
Expand All @@ -17,6 +17,7 @@ export default EmberObject.extend(Evented, {
timer: null,
exitTimer: null,
exiting: false,
initializedTime: null,

queue: readOnly('flashService.queue'),
totalTimeout: customComputed.add('timeout', 'extendedTimeout').readOnly(),
Expand All @@ -29,8 +30,8 @@ export default EmberObject.extend(Evented, {
return;
}

this._setTimer('exitTimer', 'exitMessage', get(this, 'timeout'));
this._setTimer('timer', 'destroyMessage', get(this, 'totalTimeout'));
this._setupTimers();
this._setInitializedTime();
},

destroyMessage() {
Expand All @@ -52,26 +53,60 @@ export default EmberObject.extend(Evented, {
},

willDestroy() {
const timers = ['timer', 'exitTimer'];

timers.forEach((timer) => {
this._cancelTimer(timer);
});
this._cancelAllTimers();

this._super(...arguments);
},

deferTimers() {
let timeout = get(this, 'timeout');
let remainingTime = timeout - this._getElapsedTime();
set(this, 'timeout', remainingTime);

this._cancelAllTimers();
},

resumeTimers() {
this._setupTimers();
},

// private
_setTimer(name, methodName, timeout) {
return set(this, name, later(this, methodName, timeout));
},

_setupTimers() {
this._setTimer('exitTimer', 'exitMessage', get(this, 'timeout'));
this._setTimer('timer', 'destroyMessage', get(this, 'totalTimeout'));
},

_setInitializedTime() {
let currentTime = new Date().getTime();

set(this, 'initializedTime', currentTime);
},

_getElapsedTime() {
let currentTime = new Date().getTime();
let initializedTime = get(this, 'initializedTime');

return currentTime - initializedTime;
},

_cancelTimer(name) {
const timer = get(this, name);

if (timer) {
cancel(timer);
set(this, name, null);
}
},

_cancelAllTimers() {
const timers = ['timer', 'exitTimer'];

timers.forEach((timer) => {
this._cancelTimer(timer);
});
}
});
78 changes: 78 additions & 0 deletions tests/integration/components/flash-message-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import FlashMessage from 'ember-cli-flash/flash/object';
import sinon from 'sinon';
import Ember from 'ember';

const timeoutDefault = 1000;
const halfTimeout = 500;

let clock;

moduleForComponent('flash-message', 'Integration | Component | flash message', {
integration: true
Expand Down Expand Up @@ -34,3 +41,74 @@ test('it does not error when quickly removed from the DOM', function(assert) {

assert.ok(this.get('flash').isDestroyed, 'Flash Object isDestroyed');
});

if (parseFloat(Ember.VERSION) > 2.0) {
test('flash message is removed after timeout', function(assert) {
assert.expect(3);
clock = sinon.useFakeTimers();

let destroyMessage = sinon.spy();

this.set('flash', FlashMessage.create({
message: 'hi',
sticky: false,
timeout: timeoutDefault,
destroyMessage
}));

this.render(hbs`
{{#flash-message flash=flash as |component flash|}}
{{flash.message}}
{{/flash-message}}
`);

assert.equal(this.$().text().trim(), 'hi');
assert.notOk(destroyMessage.calledOnce, 'flash has not been destroyed yet');

clock.tick(timeoutDefault);
assert.ok(destroyMessage.calledOnce, 'flash is destroyed after timeout');

clock.restore();
});

test('flash message is removed after timeout', function(assert) {
assert.expect(3);
clock = sinon.useFakeTimers();

let destroyMessage = sinon.spy();

this.set('flash', FlashMessage.create({
message: 'hi',
sticky: false,
timeout: timeoutDefault,
destroyMessage
}));

this.render(hbs`
{{#flash-message flash=flash as |component flash|}}
<span id="testFlash">{{flash.message}}</span>
{{/flash-message}}
`);

assert.equal(this.$().text().trim(), 'hi');

clock.tick(halfTimeout);
this.$('#testFlash').mouseenter();

clock.tick(timeoutDefault);
assert.notOk(
destroyMessage.calledOnce,
'flash is not destroyed after enough elapsed time'
);

this.$('#testFlash').mouseleave();

clock.tick(halfTimeout);
assert.ok(
destroyMessage.calledOnce,
'flash waits remaining time from original timeout'
);

clock.restore();
});
}
2 changes: 1 addition & 1 deletion tests/unit/components/flash-message-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,4 @@ test('it does not destroy the flash object when `flash.destroyOnClick` is false'

$('.alert').click();
assert.notOk(get(component, 'flash').isDestroyed, 'it does not destroy the flash object on click');
});
});
34 changes: 32 additions & 2 deletions tests/unit/flash/object-test.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { module, test } from 'qunit';
import Ember from 'ember';
import FlashMessage from 'ember-cli-flash/flash/object';
import sinon from 'sinon';

const testTimerDuration = 50;
const {
run,
get
} = Ember;
let flash = null;
let SANDBOX = {};

module('FlashMessageObject', {
beforeEach() {
Expand All @@ -25,7 +25,6 @@ module('FlashMessageObject', {
flash.destroyMessage();
});
flash = null;
SANDBOX = {};
}
});

Expand Down Expand Up @@ -101,3 +100,34 @@ test('it sets `exiting` to true after the timer has elapsed', function(assert) {
done();
}, testTimerDuration * 2);
});

test('#deferTimers cancels timers and updates timeout', function(assert) {
assert.expect(2);

const getElapsedTimeStub = sinon.stub().returns(5);

const exitFlash = FlashMessage.create({
timeout: testTimerDuration,
extendedTimeout: testTimerDuration,
_getElapsedTime: getElapsedTimeStub
});

exitFlash.deferTimers();

assert.equal(exitFlash.get('timeout'), 45, 'elapsed time is subtracted from timeout');
assert.notOk(exitFlash.get('timer'), 'timer is canceled');
});

test('#resumeTimers resets timers', function(assert) {
assert.expect(2);

const exitFlash = FlashMessage.create({
timeout: testTimerDuration,
extendedTimeout: testTimerDuration,
});

exitFlash.resumeTimers();

assert.ok(exitFlash.get('timer'), 'timer is started');
assert.ok(exitFlash.get('exitTimer'), 'timer is started');
});

0 comments on commit f5002eb

Please sign in to comment.