2

This is an Ember component that will need this at some point:

export default Component.extend({
  filteredSubs: computed.filter('model.subs', function() {
    // this will always return true in development http://localhost:4200/dummy
    // but will always return false in test because 'this' becomes undefined
    return this;
  })
});

Dummy has a one-to-many relationship to Sub:

export default Model.extend({
  subs: hasMany('sub')
});

export default Model.extend({
  dummy: belongsTo('dummy')
});

This test fails but shouldn't:

test('it renders', function(assert) {
  let dummy = server.create('dummy');
  server.create('sub', { dummy });

  this.set('dummy', dummy);
  this.render(hbs`{{show-dummy model=dummy}}`);

  assert.equal(this.$().text().trim(), 'Hi! There are 1 sub-dummies');
});

not ok 13 Chrome 63.0 - Integration | Component | show dummy: it renders

actual: Hi! There are 0 sub-dummies

expected: Hi! There are 1 sub-dummies

Stéphane Bruckert
  • 21,706
  • 14
  • 92
  • 130

1 Answers1

3

Your problem comes from an unfortune sequense of falsy assumptions.

The first assumption of you is that this inside a Ember.computed.filter should be the corresponding object. I'm not 100% sure this is documented behaviour, and personally wouldnt rely on it. If you need full access to this I would go with a simple Ember.computed.

However your primary mistake is in your test. And this also explains why you only have this problem in testing. Your directly using a mirage model as model for your component:

let dummy = server.create('dummy');
server.create('sub', {
  dummy
});

this.set('dummy', dummy);

this.render(hbs`{{show-dummy model=dummy}}`);

Here you assume that the result of server.create, a mirage model, is in some ways similar to a ember-data model. It is not! In fact, a mirage model is not even an ember object! So you can't use .get or .set on it, or anything you defined on your model, and definitly should not use it ever as an model für component testing. Instead you should use mirage as data-source for your ember-data models.

The question why this is undefined if your model is a mirage model leads to this line in ember-cli-mirage:

filter(f) {
  let filteredModels = this.models.filter(f);

  return new Collection(this.modelName, filteredModels);
}

where the this-context gets lost. Basically mirage is overriding the .filter function on their custom array-like structure, and doesnt ensure to keep the this-context.

Lux
  • 17,835
  • 5
  • 43
  • 73
  • Thanks for that @Lux. I'm now using `filteredSubs: computed('dummy.subs', function() { return this.get('dummy.subs').filter(s => s); })` instead. Whether `.filter` should keep the context for `this` or not is now being discussed on the ember-cli-mirage issue https://github.com/samselikoff/ember-cli-mirage/issues/1256 – Stéphane Bruckert Jan 27 '18 at 19:41
  • Also @Lux what's your recommendation to "fake" data as part of a component test? Most people use Mirage's `server.create` but I have to agree that it's sometimes acting weirdly, as you mentioned we don't always have access to `.get` or `.set`. Ty! – Stéphane Bruckert Jan 27 '18 at 19:47
  • 1
    I dont think thats common. `server.create` is common to produce test data, but you always should wrap them with real ember-data models! `store.push` might be what you're looking for. – Lux Feb 01 '18 at 20:24
  • You are right. I found [this answer](https://stackoverflow.com/a/45578787/1515819) to be the best way to “mock objects” inside component integration tests, as compared to Mirage’s `server.create` or Ember’s `EmberObject.create` which would not work with objects that contain relationships – Stéphane Bruckert Feb 05 '18 at 17:06