brainstem-js 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/.travis.yml +3 -6
  2. data/Gemfile.lock +1 -1
  3. data/README.md +3 -1
  4. data/Rakefile +2 -0
  5. data/lib/brainstem/js/version.rb +1 -1
  6. data/spec/brainstem-collection-spec.coffee +25 -0
  7. data/spec/{brianstem-expectation-spec.coffee → brainstem-expectation-spec.coffee} +132 -17
  8. data/spec/brainstem-model-spec.coffee +67 -27
  9. data/spec/brainstem-sync-spec.coffee +29 -6
  10. data/spec/brainstem-utils-spec.coffee +11 -1
  11. data/spec/helpers/builders.coffee +2 -2
  12. data/spec/helpers/models/post.coffee +1 -1
  13. data/spec/helpers/models/project.coffee +1 -1
  14. data/spec/helpers/models/task.coffee +2 -2
  15. data/spec/helpers/models/time-entry.coffee +1 -1
  16. data/spec/helpers/models/user.coffee +1 -1
  17. data/spec/helpers/spec-helper.coffee +21 -0
  18. data/spec/loaders/abstract-loader-shared-behavior.coffee +604 -0
  19. data/spec/loaders/abstract-loader-spec.coffee +3 -0
  20. data/spec/loaders/collection-loader-spec.coffee +146 -0
  21. data/spec/loaders/model-loader-spec.coffee +99 -0
  22. data/spec/storage-manager-spec.coffee +242 -56
  23. data/vendor/assets/javascripts/brainstem/brainstem-collection.coffee +16 -6
  24. data/vendor/assets/javascripts/brainstem/brainstem-expectation.coffee +70 -20
  25. data/vendor/assets/javascripts/brainstem/brainstem-model.coffee +13 -13
  26. data/vendor/assets/javascripts/brainstem/brainstem-sync.coffee +8 -3
  27. data/vendor/assets/javascripts/brainstem/loaders/abstract-loader.coffee +289 -0
  28. data/vendor/assets/javascripts/brainstem/loaders/collection-loader.coffee +68 -0
  29. data/vendor/assets/javascripts/brainstem/loaders/model-loader.coffee +35 -0
  30. data/vendor/assets/javascripts/brainstem/loading-mixin.coffee +1 -7
  31. data/vendor/assets/javascripts/brainstem/storage-manager.coffee +79 -196
  32. data/vendor/assets/javascripts/brainstem/utils.coffee +18 -4
  33. metadata +17 -6
@@ -1,12 +1,9 @@
1
1
  language: ruby
2
- rvm: 1.9.3
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
3
5
 
4
6
  before_install: gem install bundler
5
7
 
6
- notifications:
7
- hipchat: 09a2719b3284ea0d0d247355fa5542@Mavenlink Dev
8
-
9
8
  git:
10
9
  depth: 1
11
-
12
- source_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBOHBpRjdldDZKM2FmWCt0WDlWRG5HZEt4dDdZZVJVVUh3K29JQVJDekorejBvS3MxCnNwaEUxZlBSQksyUDFMUVV5cngyUThzalgxdnA1NUt6U3lnVWExMFRlUG90VnVWN3hZbVFqTG5FM2FueTJBNXIKMFhXZG4wS2k2cUt5TkVwd2ZJa3JYam8xSmY4SHJ0ajZ5Yi9VTFcySnZxbTBGTTZRNmU1TCtmUGdQN0JqeS9WSApUWHF4UWVRaTVaSVppa2JoeGxoK3RQNVZaM1JMRTF3bEhRdXpESUFZemRFdnFXeEFxYkhvNktyMjJSZzRhaGNPCnNEZ1FGK0drR0h2bjBKTkpmc05Cd3d2YUQ2Y3M5N2pnY045b1VrK1phOFNlbFZhazRjWVhRUk5WaTdPMVdkQnkKSE1xaVNPVjFZdEhSWWdwSFpPaHdYSUdxK3ZhUXVFR21SRzdBY1FJREFRQUJBb0lCQVFDOXBiQ2xjdjFXbG13MwpEd0wrK3RUL0llL2VmeGVnN1RzSjFBMlh6NWRPc2ZYM0dJRHM4ZzUxOTVuQi8zQytSbDB1dEMvOEJYVE1ta3o3CnhIbzNXY2pFdWNsOFBJOXZMQTBiT3RSdXZ0Y0F0bGZxd1ROV1ZvejNNSit0cjZ5Q0ptTlRaK1FvVUhhMkVtM08KS1QrOHNpTEx0S01IRXlGOVZwS0EzZEkxUDRwaUhodlFBdkgzQkdRR3piNW9UczV6RXJnRmZNbEZKWFc0S2NxcApYc1NnWDBjd3d3Nit3WDFLblRGY3lNak9QbTB2MWZSSTdHMGdtOG02VVJrakdVeXhpRXhkTVNEdm5yVEt4ajlvCitIK1NucU14RW5uczJRR1lnSEtnV1ZaSFFBUU9UenBNSVdvNndHODNMMDlyOUo3S1dzZHRwNStKeDNZWWE2eTMKU0tod3NQdGxBb0dCQVAvZTJ2SitCVmpkRXVOSjJibWo4YnlGK2lJd292Vm9FYW4xUExkZ3JJbTNQWkFrLytyMgpiSFV0d0xDVjBSR1l5Z1ZqaW42YmEzUitNM2ptanErM3Z1N0FsVlJNazdvcFVXcjUwanRzbDhhT1k4WkIxTlhuClRlRW5keEswTnBWaWs1VlFhNzBqcmh3NGtmekxhUk9xdUV1SGt6Qk95MXhJeStyUkNMNDMzT1ZmQW9HQkFQSzMKOHNXYm5YRzR0WjdHMU5QVjZ0YXJ5bEt4bFluRnhseG4rUmZueTFHbG1NejU4YW1Jb1F2SnJuSHlSR29xQ2dNOQo4eWJFMmVoVjJFM20wKzUwampDeFNrMjhxbkNybDdnbTdNcjhsVzdGQnZLSUw2NDFsM1pDT3pLSnBKWFdGN1BaCmdYVGplTmduRzliQkljMldBNHpzaGVDK05CRWxidmFkekRrT0hkd3ZBb0dBWkRlZ1lCdzE4ZkZkQllNV2NSeWkKZ1Nta3FDR09vam9wdVB6aDFCMWNWdkJiZjRyT1pmUXcxTkNmeVVwVXdlU3JNK01pQ3FiTE5xeDdjcDR6UXVYZwpOZGxlWTg4K2lVckhwZlBGZ1JydWM0bXYwS1pXTzVYR0xpcnIrM3AwYXB4YW04QU5BdDdud2d2eU9pWmR1S05FClhlanpJSmVzRlRBNkZuWGJTODNMaWxjQ2dZRUExai9kd3VUOGM3ZnlTZmVGUW9DZnpXTFRNMitpYW56ei9mbWgKZmFLVWJMdmFSNFdSOW02dWlmTTdVMFhoY2owdG5YTC93WWNlT3VJY0Q1ZmtGNmMzSkhBN0FLZTdZNzEwTFkvZQprY2VvT0tFZTR0T29Fd1VuYjdKREF2ZFJHeHBpemRUL1d5aTRNVVZFWTZzVHBaLzMvbHVDU2NKYnY0N2xoamdBClg1VEFjdTBDZ1lCRFhnNUF2VHVXTE5pRk91RW5RekhrRmp0N0lwRVVUN2lpZDByMHBJempMVmc1c08zSXRqTkQKNjlvWitIYUlCSVpMWkNQc2lpS2dmQkFIS0xXY3NEQTRnelY2ckpUWDdmdXYvcHZUTk9tdmJlcG5OK0JGd05xdApVU2c5UmxFZ0lFWEE3Q041aVFvb2l2SmFiT3VEREl0NUhiTS9Gd1M0V3QvQVdnQUZkYmpab3c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
@@ -20,7 +20,7 @@ GIT
20
20
  PATH
21
21
  remote: .
22
22
  specs:
23
- brainstem-js (0.2.1)
23
+ brainstem-js (0.3.0)
24
24
 
25
25
  GEM
26
26
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Brainstem.js
2
2
 
3
+ [![Build Status](https://travis-ci.org/mavenlink/brainstem-js.png)](https://travis-ci.org/mavenlink/brainstem-js)
4
+
3
5
  [Brainstem](https://github.com/mavenlink/brainstem) is designed to power rich APIs in Rails. The Brainstem gem provides a presenter library that handles converting ActiveRecord objects into structured JSON and a set of API abstractions that allow users to request sorts, filters, and association loads, allowing for simpler implementations, fewer requests, and smaller responses.
4
6
 
5
7
  The Brainstem.js library is a companion library for Backbone.js that makes integration with Brainstem APIs a breeze. Brainstem.js adds an identity map and relational models to Backbone.
@@ -23,7 +25,7 @@ What follows is an overview.
23
25
 
24
26
  ### StorageManager
25
27
 
26
- The `Brianstem.StorageManager` is in charge of loading data over the API, as well as returning already cached data. We recommend setting one up in a singleton App class.
28
+ The `Brainstem.StorageManager` is in charge of loading data over the API, as well as returning already cached data. We recommend setting one up in a singleton App class.
27
29
 
28
30
  class Application
29
31
  constructor: ->
data/Rakefile CHANGED
@@ -9,6 +9,7 @@ task :build do
9
9
  sh "rakep build"
10
10
  end
11
11
 
12
+ desc 'Run jasmine specs with phantomjs'
12
13
  task :spec => :build do
13
14
  begin
14
15
  exec *%w(phantomjs build/headless.js build/headless.html)
@@ -18,6 +19,7 @@ task :spec => :build do
18
19
  end
19
20
  end
20
21
 
22
+ desc 'Start WEBrick server to run jasmine specs in browser'
21
23
  task :server => :clean do
22
24
  exec "rakep server"
23
25
  end
@@ -1,5 +1,5 @@
1
1
  module Brainstem
2
2
  module Js
3
- VERSION = "0.2.1"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
@@ -53,6 +53,31 @@ describe 'Brainstem.Collection', ->
53
53
  expect(collection.lastFetchOptions.page).toEqual 3
54
54
  expect(collection.length).toEqual 5
55
55
 
56
+ it "fetches based on the last limit and offset if they were the pagination options used", ->
57
+ respondWith server, "/api/time_entries?limit=2&offset=0", resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(), buildTimeEntry()] }
58
+ respondWith server, "/api/time_entries?limit=2&offset=2", resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(), buildTimeEntry()] }
59
+ respondWith server, "/api/time_entries?limit=2&offset=4", resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry()] }
60
+ collection = base.data.loadCollection "time_entries", limit: 2, offset: 0
61
+ expect(collection.length).toEqual 0
62
+ server.respond()
63
+ expect(collection.length).toEqual 2
64
+ expect(collection.lastFetchOptions.offset).toEqual 0
65
+
66
+ spy = jasmine.createSpy()
67
+ collection.loadNextPage success: spy
68
+ server.respond()
69
+ expect(spy).toHaveBeenCalledWith(collection, true)
70
+ expect(collection.lastFetchOptions.offset).toEqual 2
71
+ expect(collection.length).toEqual 4
72
+
73
+ spy = jasmine.createSpy()
74
+ collection.loadNextPage success: spy
75
+ expect(collection.length).toEqual 4
76
+ server.respond()
77
+ expect(spy).toHaveBeenCalledWith(collection, false)
78
+ expect(collection.lastFetchOptions.offset).toEqual 4
79
+ expect(collection.length).toEqual 5
80
+
56
81
  describe "reload", ->
57
82
  it "reloads the collection with the original params", ->
58
83
  respondWith server, "/api/posts?include=replies&parents_only=true&per_page=5&page=1", resultsFrom: "posts", data: { posts: [buildPost(message: "old post", reply_ids: [])] }
@@ -63,6 +63,32 @@ describe 'Brainstem Expectations', ->
63
63
  expect(collection.get(1).get("tasks").models).toEqual [task1]
64
64
  expect(collection.get(2).get("tasks").models).toEqual []
65
65
 
66
+ describe 'recursive loading', ->
67
+ context 'recursive option is false', ->
68
+ it "should not try to recursively load includes in an expectation", ->
69
+ expectation = manager.stub "projects", include: '*', response: (stub) ->
70
+ stub.results = [project1, project2]
71
+ stub.associated.projects = [project1, project2]
72
+ stub.associated.tasks = [task1]
73
+
74
+ spy = spyOn(Brainstem.AbstractLoader.prototype, '_loadAdditionalIncludes')
75
+ collection = manager.loadCollection "projects", include: ["tasks" : ["time_entries"]]
76
+ expectation.respond()
77
+ expect(spy).not.toHaveBeenCalled()
78
+
79
+ context 'recursive option is true', ->
80
+ it "should recursively load includes in an expectation", ->
81
+ expectation = manager.stub "projects", include: '*', response: (stub) ->
82
+ stub.results = [project1, project2]
83
+ stub.associated.projects = [project1, project2]
84
+ stub.associated.tasks = [task1]
85
+ stub.recursive = true
86
+
87
+ spy = spyOn(Brainstem.AbstractLoader.prototype, '_loadAdditionalIncludes')
88
+ collection = manager.loadCollection "projects", include: ["tasks" : ["time_entries"]]
89
+ expectation.respond()
90
+ expect(spy).toHaveBeenCalled()
91
+
66
92
  describe "triggering errors", ->
67
93
  it "triggers errors when asked to do so", ->
68
94
  errorSpy = jasmine.createSpy()
@@ -80,8 +106,7 @@ describe 'Brainstem Expectations', ->
80
106
 
81
107
  expectation.respond()
82
108
  expect(errorSpy).toHaveBeenCalled()
83
- expect(errorSpy.mostRecentCall.args[0] instanceof Brainstem.Collection).toBe true
84
- expect(errorSpy.mostRecentCall.args[1]).toEqual resp
109
+ expect(errorSpy.mostRecentCall.args[0]).toEqual resp
85
110
 
86
111
  it "does not trigger errors when asked not to", ->
87
112
  errorSpy = jasmine.createSpy()
@@ -92,6 +117,10 @@ describe 'Brainstem Expectations', ->
92
117
  expectation.respond()
93
118
  expect(errorSpy).not.toHaveBeenCalled()
94
119
 
120
+ it "should work without specifying results", ->
121
+ manager.stubImmediate "projects"
122
+ expect(-> manager.loadCollection("projects")).not.toThrow()
123
+
95
124
  describe "responding immediately", ->
96
125
  it "uses stubImmediate", ->
97
126
  expectation = manager.stubImmediate "projects", include: ["tasks"], response: (stub) ->
@@ -133,6 +162,7 @@ describe 'Brainstem Expectations', ->
133
162
  it "should allow wildcard params", ->
134
163
  manager.stubImmediate "projects", include: '*', response: (stub) ->
135
164
  stub.results = [project1, project2]
165
+ stub.associated.tasks = [task1]
136
166
  expect(manager.loadCollection("projects", include: ["tasks"]).models).toEqual [project1, project2]
137
167
  expect(manager.loadCollection("projects", include: ["users"]).models).toEqual [project1, project2]
138
168
  expect(manager.loadCollection("projects").models).toEqual [project1, project2]
@@ -181,29 +211,114 @@ describe 'Brainstem Expectations', ->
181
211
  describe "optionsMatch", ->
182
212
  it "should ignore wrapping arrays", ->
183
213
  expectation = new Brainstem.Expectation("projects", { include: "workspaces" }, manager)
184
- expect(expectation.optionsMatch("projects", { include: "workspaces" })).toBe true
185
- expect(expectation.optionsMatch("projects", { include: ["workspaces"] })).toBe true
214
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
215
+
216
+ loader.setup(name: "projects", include: "workspaces")
217
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
218
+
219
+ loader.setup(name: "projects", include: ["workspaces"])
220
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
186
221
 
187
222
  it "should treat * as an any match", ->
188
223
  expectation = new Brainstem.Expectation("projects", { include: "*" }, manager)
189
- expect(expectation.optionsMatch("projects", { include: "workspaces" })).toBe true
190
- expect(expectation.optionsMatch("projects", { include: ["anything"] })).toBe true
191
- expect(expectation.optionsMatch("projects", {})).toBe true
224
+
225
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
226
+ loader.setup(name: "projects", include: "workspaces")
227
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
228
+
229
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
230
+ loader.setup(name: "projects", include: ["anything"])
231
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
232
+
233
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
234
+ loader.setup(name: "projects", {})
235
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
192
236
 
193
237
  it "should treat strings and numbers the same when appropriate", ->
194
238
  expectation = new Brainstem.Expectation("projects", { only: "1" }, manager)
195
- expect(expectation.optionsMatch("projects", {only: 1})).toBe true
196
- expect(expectation.optionsMatch("projects", {only: "1"})).toBe true
239
+
240
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
241
+ loader.setup(name: "projects", only: 1)
242
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
243
+
244
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
245
+ loader.setup(name: "projects", only: "1")
246
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
197
247
 
198
248
  it "should treat null, empty array, and empty object the same", ->
199
249
  expectation = new Brainstem.Expectation("projects", { filters: {} }, manager)
200
- expect(expectation.optionsMatch("projects", { filters: null })).toBe true
201
- expect(expectation.optionsMatch("projects", { filters: {} })).toBe true
202
- expect(expectation.optionsMatch("projects", { })).toBe true
203
- expect(expectation.optionsMatch("projects", { filters: { foo: "bar" } })).toBe false
250
+
251
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
252
+ loader.setup(name: "projects", filters: null)
253
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
254
+
255
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
256
+ loader.setup(name: "projects", filters: {})
257
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
258
+
259
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
260
+ loader.setup(name: "projects", {})
261
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
262
+
263
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
264
+ loader.setup(name: "projects", filters: { foo: "bar" })
265
+ expect(expectation.loaderOptionsMatch(loader)).toBe false
204
266
 
205
267
  expectation = new Brainstem.Expectation("projects", {}, manager)
206
- expect(expectation.optionsMatch("projects", { filters: null })).toBe true
207
- expect(expectation.optionsMatch("projects", { filters: {} })).toBe true
208
- expect(expectation.optionsMatch("projects", { })).toBe true
209
- expect(expectation.optionsMatch("projects", { filters: { foo: "bar" } })).toBe false
268
+
269
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
270
+ loader.setup(name: "projects", filters: null)
271
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
272
+
273
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
274
+ loader.setup(name: "projects", filters: {})
275
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
276
+
277
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
278
+ loader.setup(name: "projects", {})
279
+ expect(expectation.loaderOptionsMatch(loader)).toBe true
280
+
281
+ loader = new Brainstem.CollectionLoader(storageManager: manager)
282
+ loader.setup(name: "projects", filters: { foo: "bar" })
283
+ expect(expectation.loaderOptionsMatch(loader)).toBe false
284
+
285
+ describe 'stubbing models', ->
286
+ context 'a model that matches the load is already in the storage manager', ->
287
+ it 'updates that model', ->
288
+ project = buildAndCacheProject()
289
+
290
+ expectation = manager.stubModel 'project', project.id, response: (stub) ->
291
+ stub.result = buildProject(id: project.id, title: 'foobar')
292
+
293
+ loaderSpy = jasmine.createSpy('loader').andCallFake (model) ->
294
+ expect(model).toEqual project
295
+ expect(model.get('title')).toEqual 'foobar'
296
+
297
+ loader = manager.loadModel 'project', project.id
298
+ loader.done(loaderSpy)
299
+
300
+ expectation.respond()
301
+ expect(loaderSpy).toHaveBeenCalled()
302
+ expect(manager.storage('projects').length).toEqual 1
303
+
304
+ context 'a model is not already in the storage manager', ->
305
+ it 'adds the model from the loader to the storageManager', ->
306
+ project = buildProject()
307
+ stubbedProject = buildProject(id: project.id, title: 'foobar')
308
+
309
+ expectation = manager.stubModel 'project', project.id, response: (stub) ->
310
+ stub.result = stubbedProject
311
+
312
+ loader = manager.loadModel 'project', project.id
313
+
314
+ loaderSpy = jasmine.createSpy('loader').andCallFake (model) ->
315
+ expect(model).toEqual loader.getModel()
316
+ expect(model.attributes).toEqual stubbedProject.attributes
317
+ expect(manager.storage('projects').get(project.id)).toEqual loader.getModel()
318
+ expect(model.get('title')).toEqual 'foobar'
319
+
320
+ loader.done(loaderSpy)
321
+
322
+ expectation.respond()
323
+ expect(loaderSpy).toHaveBeenCalled()
324
+ expect(manager.storage('projects').length).toEqual 1
@@ -28,6 +28,51 @@ describe 'Brainstem.Model', ->
28
28
  expect(base.data.storage('users').get(5).attributes).toEqual(response.users[5])
29
29
  expect(base.data.storage('users').get(6).attributes).toEqual(response.users[6])
30
30
 
31
+ describe 'adding new models to the storage manager', ->
32
+ context 'there is an ID on the model already', ->
33
+ # usually happens when fetching an existing model and not using StorageManager#loadModel
34
+ # new App.Models.Task(id: 5).fetch()
35
+
36
+ beforeEach ->
37
+ model.set('id', 1)
38
+
39
+ context 'model ID matches response ID', ->
40
+ it 'should add the parsing model to the storage manager', ->
41
+ response.tasks[1].id = 1
42
+ expect(base.data.storage('tasks').get(1)).toBeUndefined()
43
+
44
+ model.parse(response)
45
+ expect(base.data.storage('tasks').get(1)).not.toBeUndefined()
46
+ expect(base.data.storage('tasks').get(1)).toEqual model
47
+ expect(base.data.storage('tasks').get(1).attributes).toEqual response.tasks[1]
48
+
49
+ context 'model ID does not match response ID', ->
50
+ # this only happens when an association has the same brainstemKey as the parent record
51
+ # we want to add a new model to the storage manager and not worry about ourself
52
+
53
+ it 'should not add the parsing model to the storage manager', ->
54
+ response.tasks[1].id = 2345
55
+ expect(base.data.storage('tasks').get(1)).toBeUndefined()
56
+
57
+ model.parse(response)
58
+ expect(base.data.storage('tasks').get(1)).toBeUndefined()
59
+ expect(base.data.storage('tasks').get(2345)).not.toEqual model
60
+
61
+ context 'there is not an ID on the model instance already', ->
62
+ # usually happens when creating a new model:
63
+ # new App.Models.Task(title: 'test').save()
64
+
65
+ beforeEach ->
66
+ expect(model.id).toBeUndefined()
67
+
68
+ it 'should add the parsing model to the storage manager', ->
69
+ response.tasks[1].title = 'Hello'
70
+ expect(base.data.storage('tasks').get(1)).toBeUndefined()
71
+
72
+ model.parse(response)
73
+ expect(base.data.storage('tasks').get(1)).toEqual(model)
74
+ expect(base.data.storage('tasks').get(1).get('title')).toEqual('Hello')
75
+
31
76
  it 'should work with an empty response', ->
32
77
  expect( -> model.parse(tasks: {}, results: [], count: 0)).not.toThrow()
33
78
 
@@ -74,33 +119,6 @@ describe 'Brainstem.Model', ->
74
119
  expect(base.data.storage('users').get(5).get('created_at')).toEqual(1361820357000)
75
120
  expect(base.data.storage('users').get(6).get('created_at')).toEqual(1359573957000)
76
121
 
77
- describe 'setLoaded', ->
78
- it "should set the values of @loaded", ->
79
- model.setLoaded true
80
- expect(model.loaded).toEqual(true)
81
- model.setLoaded false
82
- expect(model.loaded).toEqual(false)
83
-
84
- it "triggers 'loaded' when becoming true", ->
85
- spy = jasmine.createSpy()
86
- model.bind "loaded", spy
87
- model.setLoaded false
88
- expect(spy).not.toHaveBeenCalled()
89
- model.setLoaded true
90
- expect(spy).toHaveBeenCalled()
91
-
92
- it "doesn't trigger loaded if trigger: false is provided", ->
93
- spy = jasmine.createSpy()
94
- model.bind "loaded", spy
95
- model.setLoaded true, trigger: false
96
- expect(spy).not.toHaveBeenCalled()
97
-
98
- it "returns self", ->
99
- spy = jasmine.createSpy()
100
- model.bind "loaded", spy
101
- model.setLoaded true
102
- expect(spy).toHaveBeenCalledWith(model)
103
-
104
122
  describe 'associations', ->
105
123
  describe 'associationDetails', ->
106
124
 
@@ -181,6 +199,28 @@ describe 'Brainstem.Model', ->
181
199
  expect(project.associationsAreLoaded(["time_entries"])).toBeTruthy()
182
200
  expect(project.associationsAreLoaded(["tasks"])).toBeFalsy()
183
201
 
202
+ describe "when supplying associations that do not exist", ->
203
+ class TestClass extends Brainstem.Model
204
+ @associations:
205
+ user: "users"
206
+
207
+ testClass = null
208
+
209
+ beforeEach ->
210
+ testClass = new TestClass()
211
+
212
+ it "returns true when supplying only an association that does not exist", ->
213
+ expect(testClass.associationsAreLoaded(['foobar'])).toBe true
214
+ expect(testClass.associationsAreLoaded(['user'])).toBe false
215
+
216
+ it "returns false when supplying both a real association that is not loaded and an association that does not exist", ->
217
+ expect(testClass.associationsAreLoaded(['user', 'foobar'])).toBe false
218
+
219
+ it "returns true when supplying a real association that is loaded and an association that does not exist", ->
220
+ testClass.set('user_id', buildAndCacheUser().id)
221
+ expect(testClass.associationsAreLoaded(['user'])).toBe true
222
+ expect(testClass.associationsAreLoaded(['user', 'foobar'])).toBe true
223
+
184
224
  describe "get", ->
185
225
  it "should delegate to Backbone.Model#get for anything that is not an association", ->
186
226
  timeEntry = new App.Models.TimeEntry(id: 5, project_id: 10, task_id: 2, title: "foo")
@@ -1,22 +1,45 @@
1
1
  describe "Brainstem.Sync", ->
2
- describe "updating models", ->
3
- ajaxSpy = null
2
+ ajaxSpy = null
4
3
 
5
- beforeEach ->
6
- ajaxSpy = spyOn($, 'ajax')
4
+ beforeEach ->
5
+ ajaxSpy = spyOn($, 'ajax')
7
6
 
7
+ describe "updating models", ->
8
8
  it "should use toServerJSON instead of toJSON", ->
9
9
  modelSpy = spyOn(Brainstem.Model.prototype, 'toServerJSON')
10
10
  model = buildTimeEntry()
11
11
  model.save()
12
12
  expect(modelSpy).toHaveBeenCalled()
13
13
 
14
- it "should pass options.includes through the JSON", ->
14
+ it "should pass options.include through the JSON", ->
15
15
  model = buildTimeEntry()
16
16
  model.save({}, include: 'creator')
17
17
  expect(ajaxSpy.mostRecentCall.args[0].data).toMatch(/"include":"creator"/)
18
18
 
19
+ it "should accept an array for options.include", ->
20
+ model = buildTimeEntry()
21
+ model.save({}, include: ['creator', 'story'])
22
+ expect(ajaxSpy.mostRecentCall.args[0].data).toMatch(/"include":"creator,story"/)
23
+
19
24
  it "should setup param roots when models have a paramRoot set", ->
20
25
  model = buildTimeEntry()
21
26
  model.save({})
22
- expect(ajaxSpy.mostRecentCall.args[0].data).toMatch(/"time_entry":{/)
27
+ expect(ajaxSpy.mostRecentCall.args[0].data).toMatch(/"time_entry":{/)
28
+
29
+ describe 'error handler', ->
30
+ it 'wraps the error handler in an errorInterceptor', ->
31
+ model = buildTimeEntry()
32
+ base.data.errorInterceptor = jasmine.createSpy('errorInterceptor')
33
+
34
+ model.save({})
35
+ ajaxSpy.mostRecentCall.args[0].error()
36
+ expect(base.data.errorInterceptor).toHaveBeenCalled()
37
+
38
+ it 'only wraps the error handler if base.data.errorInterceptor is defined', ->
39
+ delete base.data.errorInterceptor
40
+ model = buildTimeEntry()
41
+ errorSpy = jasmine.createSpy('error spy')
42
+
43
+ model.save({}, error: errorSpy)
44
+ ajaxSpy.mostRecentCall.args[0].error()
45
+ expect(errorSpy).toHaveBeenCalled()