I have two tests in date-dropdown-test.js:
moduleForComponent('forms/date-dropdown', 'Integration | Component | forms/date dropdown', {
integration: true
});
test('it renders in month mode', function(assert) {
assert.expect(2);
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{forms/date-dropdown dateFormat='MMMM YYYY' daySpecific=false dayToUse=26 dateUnitsLong=24 startIncrement=1}}`);
// Check 24 months appear
assert.equal(this.$('option').length, 24);
// Check next month is selected by default
var today = new Date();
today.setDate(1);
today.setMonth(today.getMonth() + 1);
today.setDate(26);
var expected = moment(today).format('DD-MM-YYYY');
assert.equal(this.$('select').val(), expected);
});
test('it renders in day mode', function(assert) {
assert.expect(1);
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{forms/date-dropdown dateFormat='MMMM YYYY' daySpecific=true dateUnitsLong=300 startIncrement=3}}`);
// Check 300 days appear
assert.equal(this.$('option').length, 300);
});
The problem I have is when the second test runs the component from the first test is still in the DOM and found by this.$('option').length
. What is the correct way to clear the DOM between or at the end of tests in Ember testing?
Or is there something more specific than this.$() to use in the context of the component rendered in the test?
EDIT
Even more confusing to me is the fact that it seems to work fine at https://github.com/yapplabs/ember-radio-button/blob/master/tests/unit/components/radio-button-test.js with multiple tests and looking at the dom, but in my second test I definitely see 324 option elements in .length
instead of the 300 added by that particular component.
EDIT 2
The components code is:
export default Ember.Component.extend({
dateItems: [],
didInitAttrs: function() {
var today = new Date();
var dateItems = this.get('dateItems');
var i = 0;
if (this.get('daySpecific')) {
for (i = 0; i < this.get('dateUnitsLong'); i++) {
var nextDay = new Date();
nextDay.setDate(today.getDate() + i);
dateItems.addObject({date: nextDay, value: moment(nextDay).format('DD-MM-YYYY')});
}
}
else {
for (i = 0; i < this.get('dateUnitsLong'); i++) {
var nextMonth = new Date();
nextMonth.setDate(1);
nextMonth.setMonth(today.getMonth() + i);
nextMonth.setDate(this.get('dayToUse'));
dateItems.addObject({date: nextMonth, value: moment(nextMonth).format('DD-MM-YYYY')});
}
}
},
didInsertElement: function() {
var startDate = new Date();
if (this.get('daySpecific')) {
startDate.setDate(startDate.getDate() + this.get('startIncrement'));
}
else {
startDate.setDate(1);
startDate.setMonth(startDate.getMonth() + this.get('startIncrement'));
startDate.setDate(this.get('dayToUse'));
}
this.$('select').val(moment(startDate).format('DD-MM-YYYY')).trigger('change');
},
actions: {
dateChange: function() {
this.set('value', this.$('select').val());
}
}
});
hbs
<select class="form-control" {{action 'dateChange' on='change'}}>
{{#each dateItems as |dateItem index|}}
<option value="{{dateItem.value}}">
{{date-formatter dateItem.date dateFormat}}
</option>
{{/each}}
</select>
The idea is to create a reusable component that creates a dropdown of months or days for a given period of time and allows a default of something other than the first day/month. So looking at the first test above {{forms/date-dropdown dateFormat='MMMM YYYY' daySpecific=false dayToUse=26 dateUnitsLong=24 startIncrement=1}}
would create a dropdown with 24 months from this month and default to next month.
In any case I wonder whether the final line: this.$('select').val(moment(startDate).format('DD-MM-YYYY')).trigger('change');
of didInsertElement
is the offender here? Perhaps the tests continue on but this stops the teardown in the test?
The two tests pass individually if I remove one or the other.
Edit 3
Removing this.$('select').val(moment(startDate).format('DD-MM-YYYY')).trigger('change');
didnt help, perhaps its my use of didInitAttrs to create my dateItems that the #each
of the template uses?
I thought this might be a bug with didInitAttrs so posted an issue on Github (http://rwjblue.jsbin.com/docepa/edit?html,js,output). It turns out that the issue was in a single line of my code:
dateItems: [],
The array is being shared on the component's prototype. As https://dockyard.com/blog/2014/04/17/ember-object-self-troll explains:
"When you don't pass any properties to create (props), all instances of the Object will share the same prototype. That's pretty much the gist of the prototypical inheritance. It means that any changes on one object will reflect on the others. That explains the behaviour in the first example."
If I had put the component on the same page twice then I would have also seen the issue outside of the test environment.
So I had two choices to resolve this, turn dataItems into a computed property or use Init
. I used init like so:
dateItems: null,
init: function() {
this._super.apply(this, arguments);
this.dateItems = [];
},
dateItems are now kept nicely distinct between component instances.