brainstem-js 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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()