Given Ember reached 1.0.0 recently, I wanted to start using it with tests. I'm using Yeoman 1.0 with Karma. I want to unit test models but I'm finding it very difficult to accomplish isolation.
The example I have now is:
describe("Expense", function() {
return it("has a computed property called `explained`", function() {
var expense = App.Expense.create({
name: "My first expense",
value: 34
});
return expect(expense.get("explained")).to.equal("My first expense -- 34");
});
});
As of 1.0.0, I get the following error:
Error: You should not call `create` on a model. Instead, call
`store.createRecord` with the attributes you would like to set.
How should I access store in order to create a model instance? More ideally, how can I simply spawn models like this without even resorting to the store, is that viable? There's no point in spawning an entire app just to test a model, IMO.
Thank you.
Here is the minimum code that I've used so far for unit testing models.
var container, store, expense;
container = new Ember.Container();
container.register('store:main', DS.Store.extend());
container.register('model:expense', App.Expense);
store = container.lookup('store:main');
Ember.run( function() {
expense = store.createRecord('expense', {
name: "My first expense",
value: 34
});
});
Based on the code of the store and the way models are tested inside Ember Data, I don't think that you can reduce the setup of the test.
According to #sly7_7's commentary, looking for the store inside the app via App.__container__.lookup('store:main') works.
You should use this.get('store').createRecord('expense')
It looks like you're using Ember-Data 1.0.0 beta, which changed the way things work. You should take a look at the migration.md file in the ember-data project.
(Sorry I should have read your question a bit better - I'm having the same issue at the moment and the suggested container solution doesn't work for me - causing browsers to crash) :(
Related
I have a component that expects a model property that is an ember data object (originally created from the route model)
I've tried this in the integration test but the store is undefined
test('it renders', function (assert) {
this.inject.service('store')
let model = this.get('store').createRecord('post');
this.set('model', model);
this.render(hbs`{{post-item-form model = model}}`);
assert.equal(this.$().text().trim(), 'Post your thoughts');
// Template block usage:
this.render(hbs`
{{#post-item-form}}
template block text
{{/post-item-form}}
`);
// assert.equal(this.$().text().trim(), 'template block text');
});
I would prefer to create a pure json object instead of creating a record through the usage of store in a component integration testing; because the component itself knows nothing about the store and you can just pass pure json object instead of a model instance to the component and it should still work. With this mindset, I would only deal with store in acceptance tests.
If you still would like to go the way you have mentioned; I believe you need to retrieve the store as follows:
var store = Ember.getOwner(this).lookup("service:store");
Since; auto run loops are disabled in testing mode by default; it is most likely that you will get an assertion error indicating that there is no run loop available when you run the following code let model = store.createRecord('post'); this means you need to wrap it in a within a run loop like Ember.run(()=>model = store.createRecord('post'));. I did not give a try to what I wrote; but I guess this should work.
Yet again; why do you need to create the record through store in an integration test? If you really like to use store; then an acceptance test should be better; since store will be up and running and you will not need to retrieve it through lookup. I hope this helps.
Ok, so here's what I'm trying to accomplish (in ember.js):
New model/controller to manage the current user and session information
The model needs to be available everywhere so I can just do something like currentUser.firstname (for instance, in the nav)
After lots and lots of research, it seems that setting up a separate controller/model is the best way to go. I tried doing everything in the application controller, but then I need to implicitly set the user model (somehow?) on that controller, which doesn't seem like a good idea (what if I need to do other things in the application controller?).
So here's what I've tried:
controllers/session.js
init: function() {
// this never gets called unless I call the setCurrentUser
// function from another controller using 'needs' or something
},
setCurrentUser: function() {
// you can ignore the authData.uid variable - it's something used
// by firebase, but not important for this example. Just assume I'm
// requesting and getting a user back.
this.store.find('user').then(function(users) {
this.set('currentUser', users.filterBy('uid', authData.uid)[0])
}
}
models/session.js
DS.Model.extend({
currentUser: DS.belongsTo('user')
});
I don't have a route or view/template associated with this model/controller because it really wouldn't make sense (the user doesn't need to see a page all about him/herself).
So I tried calling setCurrentUser from the application controller by doing something like this.get('setCurrentUser')() (which looks very weird - there's got to be a better way, but I think the answer may be not calling this from the application controller and initializing the controller in a different way?).
I would love some advice on how I can get this working. Sorry that I'm trying to develop and explain the architecture of the app all at the same time, so it is a bit messy - please let me know if anything is unclear.
Thanks for the help!
My EmberJS application has a ProjectRoute (/project/:project_id) and a corresponding ProjectController. When viewing a particular project, users can edit its properties, and I'd like the project to automatically be saved when the user stops looking at it.
Currently, what I'm doing is something like this:
Application.ProjectRoute = Ember.Route.extend({
...
exit: function() {
this.get('controller').saveProject();
}
...
});
This works when the user simply closest the project view. However, if the user simply switches to viewing a different project (e.g. goes directly from /project/1 to /project/2), the same route is used (it just uses a different model), and exit is not called.
What I need is a way to detect this transition and call the saveProject function before it happens. Any ideas?
I "solved" it by adding the following to my controller:
Application.ProjectController = Ember.ObjectController.extend({
...
saveOnChange: function() {
var previousProject = this.get('target.projectToSave');
if (previousProject && previousProject.get('isDirty')) {
Application.store.commit();
}
this.set('target.projectToSave', this.get('content'));
}.observes('content')
...
});
This seems to work well. Note that I'm storing the projectToSave property in the route (target) as opposed to the controller, because the controller gets wiped every time the model changes. It feels a little weird/hacky to store this in the route, but the only alternative I could think of was to store it in ApplicationController, which seemed overly broad.
Day 2 learning ember.js...
I'm working on a offline app that needs to save draggable/sortable tile positions to localstorage, and if there is no existing data, load & save from a fixture.
Using: ember 1.0.0-pre4, ember-data rev11, ember-localstorage-adapter, jQ 1.9, jQ UI 1.9
https://github.com/rpflorence/ember-localstorage-adapter
It's working, but I'm a bit of a novice, feel it's not pretty and could use some community advice.
http://jsfiddle.net/Nsbcu/4/
Questions
What is the proper way to check if your DS.Store has loaded and is empty? My method of looking directly at localstorage didn't feel right.
After I createRecords from the App.Tile.DEFAULTS I feel I should commit them, but an error is thrown. I don't have to commit the known defaults, but curious what causes the error and how I should go about committing properly. Also is the App.ready() callback the right place for loading defaults? Error only happens when localstorage is empty
Uncaught Error: Attempted to handle event loadedData on <App.Tile:ember231:1> while in state rootState.loaded.created.inFlight. Called with undefined
On the TilesController I'm using sortProperties which works great until jQ UI Sortable changes the DOM and Ember wants to update my tile order, before I get a chance to set the new order. My current solution is to turn off sortProperties temporarily while updating the model. Again this feels hacky, suggestions on proper way to do this?
=== Edit Feb 3 ===
If I do an async commit the initial error in question #2 is avoided.
App.TilesRoute = Ember.Route.extend({
model: function() {
return App.Tile.find();
},
setupController: function(controller) {
if (localStorage.getItem('fusion-emberjs') == null) {
App.Tile.DEFAULTS.forEach(function(item) {
App.Tile.createRecord(item);
});
// Commit async, else generates error
var _this = this;
setTimeout(function() {
_this.store.commit();
}, 1);
}
}
});
I would put any initial code inside the application or the index Route within the setupController method
if (localStorage.getItem('fusion-emberjs') == null) {
App.Tile.DEFAULTS.forEach(function(item) {
App.Tile.createRecord(item);
});
//*** WARNING: Generates Error ***/
App.Tile.find().get('store').commit();
}
Once you move the code inside the route, replace App.Tile.find().get('store').commit(); by App.store.commit() inside your route
Create your own transaction instead of using the default one, each time you make a call to the store directly you're using the default transaction. You can create a transaction this way
var transaction = App.store.transaction()
transaction.createRecord(App.Foo);
transaction.commit()
transaction.rollback();
Any call to App.store assumes you already created a store, right now you're only extending the DS.Store. Try instead
App.Store = DS.Store.create({
revision: 11,
adapter: 'App.LSAdapter'
});
I would suggest that you do any event handling or transaction management in the router unless it's purely for styling or animation. In that case, the view is the right place for it. I like the router to orchestrate communication between all the assets (controllers, routes, models, views)
A good pattern to remember is a view talks only to a controller, a controller is a mere proxy to a model, a router orchestrates communication between controllers and manages routes.
I am working on converting a Backbone application into an Ember application using Ember Data. It works fine in the browser but the Jasmine test cases will not pass. When I try to create a record in the Jasmine test case I get this error:
TypeError: 'undefined' is not a function (evaluating 'type._create({ store: this })') in http://localhost:8888/spec/javascripts/generated/assets/application.js (line 26874)
This is the actual code that the error message points to:
createRecord: function(type, properties, transaction) {
properties = properties || {};
// Create a new instance of the model `type` and put it
// into the specified `transaction`. If no transaction is
// specified, the default transaction will be used.
//
// NOTE: A `transaction` is specified when the
// `transaction.createRecord` API is used.
var record = type._create({
store: this // line 26874
});
The actual code that the test case is executing looks like this:
nutrient = App.Nutrient.createRecord({"name_min":"nut 1","female_31_50_min":7.5,"male_31_50_min":8.0,"created_at":"2011-10-10T01:31:53Z","female_51_70_min":8.5,"updated_at":"2011-10-12T12:28:35Z","male_70_plus_min":10.0,"female_19_30_min":6.5,"child_4_8_min":4.0,"male_19_30_min":7.0,"lactating_14_18_min":5.75,"infant_0_05_min":1.0,"female_70_plus_min":9.5,"pregnant_14_18_min":5.8,"infant_6_12_min":2.0,"id":1,"male_9_13_min":5.0,"child_1_3_min":3.0,"female_9_13_min":4.5,"female_14_18_min":5.5,"male_14_18_min":6.0,"lactating_31_50_min":7.75,"pregnant_31_50_min":7.8,"pregnant_19_30_min":6.8,"male_51_70_min":9.0,"lactating_19_30_min":6.75,"female_31_50_max":8.5,"male_31_50_max":9.0,"female_51_70_max":9.5,"male_70_plus_max":11.0,"female_19_30_max":7.5,"child_4_8_max":5.0,"male_19_30_max":8.0,"lactating_14_18_max":6.75,"infant_0_05_max":2.0,"female_70_plus_max":10.5,"pregnant_14_18_max":6.8,"infant_6_12_max":3.0,"male_9_13_max":6.0,"child_1_3_max":4.0,"female_9_13_max":5.5,"female_14_18_max":6.5,"male_14_18_max":7.0,"lactating_31_50_max":8.75,"pregnant_31_50_max":9.8,"pregnant_19_30_max":7.8,"male_51_70_max":10.0,"lactating_19_30_max":7.75})
person = new App.Person.createRecord({age: 0.25})
expect(nutrient.requiredNutrientForPerson(person)).toEqual({min_amount: 1.0, max_amount: 2.0})
Any ideas would be appreciate.
In general, if you're having problems with a test that you don't experience in the browser, it's because the tests are running outside of the Ember run loop.
Try calling Ember.run.sync() before expect() to force synchronization. Alternatively, place any code that involves binding in an anonymous fn inside: Ember.run(function() { }).
Check out the ember and ember-data source for other testing examples, since coverage is pretty solid.
With that said, I'm not an ember-data expert, so I'm not sure if this is the problem you're experiencing.
Sorry, My Bad. The problem is with this line:
person = new App.Person.createRecord({age: 0.25})
I needed to remove the new keyword and it worked correctly