brainstem-js 0.2.1
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.
- data/.gitignore +8 -0
- data/.pairs +21 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.tm_properties +1 -0
- data/.travis.yml +12 -0
- data/Assetfile +79 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +50 -0
- data/LICENSE.txt +22 -0
- data/README.md +143 -0
- data/Rakefile +25 -0
- data/brainstemjs.gemspec +24 -0
- data/lib/brainstem/js/engine.rb +5 -0
- data/lib/brainstem/js/version.rb +5 -0
- data/lib/brainstem/js.rb +10 -0
- data/spec/brainstem-collection-spec.coffee +141 -0
- data/spec/brainstem-model-spec.coffee +283 -0
- data/spec/brainstem-sync-spec.coffee +22 -0
- data/spec/brainstem-utils-spec.coffee +12 -0
- data/spec/brianstem-expectation-spec.coffee +209 -0
- data/spec/helpers/builders.coffee +80 -0
- data/spec/helpers/jquery-matchers.js +137 -0
- data/spec/helpers/models/post.coffee +14 -0
- data/spec/helpers/models/project.coffee +13 -0
- data/spec/helpers/models/task.coffee +14 -0
- data/spec/helpers/models/time-entry.coffee +13 -0
- data/spec/helpers/models/user.coffee +8 -0
- data/spec/helpers/spec-helper.coffee +79 -0
- data/spec/storage-manager-spec.coffee +613 -0
- data/spec/support/.DS_Store +0 -0
- data/spec/support/console-runner.js +103 -0
- data/spec/support/headless.coffee +47 -0
- data/spec/support/headless.html +60 -0
- data/spec/support/runner.html +85 -0
- data/spec/vendor/backbone-factory.js +62 -0
- data/spec/vendor/backbone.js +1571 -0
- data/spec/vendor/inflection.js +448 -0
- data/spec/vendor/jquery-1.7.js +9300 -0
- data/spec/vendor/jquery.cookie.js +47 -0
- data/spec/vendor/minispade.js +67 -0
- data/spec/vendor/sinon-1.3.4.js +3561 -0
- data/spec/vendor/underscore.js +1221 -0
- data/vendor/assets/.DS_Store +0 -0
- data/vendor/assets/javascripts/.DS_Store +0 -0
- data/vendor/assets/javascripts/brainstem/brainstem-collection.coffee +53 -0
- data/vendor/assets/javascripts/brainstem/brainstem-expectation.coffee +65 -0
- data/vendor/assets/javascripts/brainstem/brainstem-inflections.js +449 -0
- data/vendor/assets/javascripts/brainstem/brainstem-model.coffee +141 -0
- data/vendor/assets/javascripts/brainstem/brainstem-sync.coffee +76 -0
- data/vendor/assets/javascripts/brainstem/index.js +1 -0
- data/vendor/assets/javascripts/brainstem/iso8601.js +41 -0
- data/vendor/assets/javascripts/brainstem/loading-mixin.coffee +13 -0
- data/vendor/assets/javascripts/brainstem/storage-manager.coffee +275 -0
- data/vendor/assets/javascripts/brainstem/utils.coffee +35 -0
- metadata +198 -0
@@ -0,0 +1,283 @@
|
|
1
|
+
describe 'Brainstem.Model', ->
|
2
|
+
model = null
|
3
|
+
|
4
|
+
beforeEach ->
|
5
|
+
model = new App.Models.Task()
|
6
|
+
|
7
|
+
describe 'parse', ->
|
8
|
+
response = null
|
9
|
+
|
10
|
+
beforeEach ->
|
11
|
+
response = count: 1, results: [id: 1, key: 'tasks'], tasks: { 1: { id: 1, title: 'Do Work' } }
|
12
|
+
|
13
|
+
it "extracts object data from JSON with root keys", ->
|
14
|
+
parsed = model.parse(response)
|
15
|
+
expect(parsed.id).toEqual(1)
|
16
|
+
|
17
|
+
it "passes through object data from flat JSON", ->
|
18
|
+
parsed = model.parse({id: 1})
|
19
|
+
expect(parsed.id).toEqual(1)
|
20
|
+
|
21
|
+
it 'should update the storage manager with the new model and its associations', ->
|
22
|
+
response.tasks[1].assignee_ids = [5, 6]
|
23
|
+
response.users = { 5: {id: 5, name: 'Jon'}, 6: {id: 6, name: 'Betty'} }
|
24
|
+
|
25
|
+
model.parse(response)
|
26
|
+
|
27
|
+
expect(base.data.storage('tasks').get(1).attributes).toEqual(response.tasks[1])
|
28
|
+
expect(base.data.storage('users').get(5).attributes).toEqual(response.users[5])
|
29
|
+
expect(base.data.storage('users').get(6).attributes).toEqual(response.users[6])
|
30
|
+
|
31
|
+
it 'should work with an empty response', ->
|
32
|
+
expect( -> model.parse(tasks: {}, results: [], count: 0)).not.toThrow()
|
33
|
+
|
34
|
+
describe 'updateStorageManager', ->
|
35
|
+
it 'should update the associations before the new model', ->
|
36
|
+
response.tasks[1].assignee_ids = [5]
|
37
|
+
response.users = { 5: {id: 5, name: 'Jon'} }
|
38
|
+
|
39
|
+
spy = spyOn(base.data, 'storage').andCallThrough()
|
40
|
+
model.updateStorageManager(response)
|
41
|
+
expect(spy.calls[0].args[0]).toEqual('users')
|
42
|
+
expect(spy.calls[1].args[0]).toEqual('tasks')
|
43
|
+
|
44
|
+
it 'should work with an empty response', ->
|
45
|
+
expect( -> model.updateStorageManager(count: 0, results: [])).not.toThrow()
|
46
|
+
|
47
|
+
it 'should return the first object from the result set', ->
|
48
|
+
response.tasks[2] = (id: 2, title: 'foo')
|
49
|
+
response.results.unshift(id: 2, key: 'tasks')
|
50
|
+
parsed = model.parse(response)
|
51
|
+
expect(parsed.id).toEqual 2
|
52
|
+
expect(parsed.title).toEqual 'foo'
|
53
|
+
|
54
|
+
it 'should not blow up on server side validation error', ->
|
55
|
+
response = errors: ["Invalid task state. Valid states are:'notstarted','started',and'completed'."]
|
56
|
+
expect(-> model.parse(response)).not.toThrow()
|
57
|
+
|
58
|
+
describe 'date handling', ->
|
59
|
+
it "parses ISO 8601 dates into date objects / milliseconds", ->
|
60
|
+
parsed = model.parse({created_at: "2013-01-25T11:25:57-08:00"})
|
61
|
+
expect(parsed.created_at).toEqual(1359141957000)
|
62
|
+
|
63
|
+
it "passes through dates in milliseconds already", ->
|
64
|
+
parsed = model.parse({created_at: 1359142047000})
|
65
|
+
expect(parsed.created_at).toEqual(1359142047000)
|
66
|
+
|
67
|
+
it 'parses dates on associated models', ->
|
68
|
+
response.tasks[1].created_at = "2013-01-25T11:25:57-08:00"
|
69
|
+
response.tasks[1].assignee_ids = [5, 6]
|
70
|
+
response.users = { 5: {id: 5, name: 'John', created_at: "2013-02-25T11:25:57-08:00"}, 6: {id: 6, name: 'Betty', created_at: "2013-01-30T11:25:57-08:00"} }
|
71
|
+
|
72
|
+
parsed = model.parse(response)
|
73
|
+
expect(parsed.created_at).toEqual(1359141957000)
|
74
|
+
expect(base.data.storage('users').get(5).get('created_at')).toEqual(1361820357000)
|
75
|
+
expect(base.data.storage('users').get(6).get('created_at')).toEqual(1359573957000)
|
76
|
+
|
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
|
+
describe 'associations', ->
|
105
|
+
describe 'associationDetails', ->
|
106
|
+
|
107
|
+
class TestClass extends Brainstem.Model
|
108
|
+
@associations:
|
109
|
+
my_users: ["storage_system_collection_name"]
|
110
|
+
my_user: "users"
|
111
|
+
user: "users"
|
112
|
+
users: ["users"]
|
113
|
+
|
114
|
+
it "returns a hash containing the key, type and plural of the association", ->
|
115
|
+
testClass = new TestClass()
|
116
|
+
expect(TestClass.associationDetails('my_users')).toEqual key: "my_user_ids", type: "HasMany", collectionName: "storage_system_collection_name"
|
117
|
+
expect(TestClass.associationDetails('my_user')).toEqual key: "my_user_id", type: "BelongsTo", collectionName: "users"
|
118
|
+
expect(TestClass.associationDetails('user')).toEqual key: "user_id", type: "BelongsTo", collectionName: "users"
|
119
|
+
expect(TestClass.associationDetails('users')).toEqual key: "user_ids", type: "HasMany", collectionName: "users"
|
120
|
+
|
121
|
+
expect(testClass.constructor.associationDetails('users')).toEqual key: "user_ids", type: "HasMany", collectionName: "users"
|
122
|
+
|
123
|
+
it "is cached on the class for speed", ->
|
124
|
+
original = TestClass.associationDetails('my_users')
|
125
|
+
TestClass.associations.my_users = "something_else"
|
126
|
+
expect(TestClass.associationDetails('my_users')).toEqual original
|
127
|
+
|
128
|
+
it "returns falsy if the association cannot be found", ->
|
129
|
+
expect(TestClass.associationDetails("I'mNotAThing")).toBeFalsy()
|
130
|
+
|
131
|
+
describe 'associationsAreLoaded', ->
|
132
|
+
describe "with BelongsTo associations", ->
|
133
|
+
it "should return true when all provided associations are loaded for the model", ->
|
134
|
+
timeEntry = new App.Models.TimeEntry(id: 5, project_id: 10, task_id: 2)
|
135
|
+
expect(timeEntry.associationsAreLoaded(["project", "task"])).toBeFalsy()
|
136
|
+
buildAndCacheProject( id: 10, title: "a project!")
|
137
|
+
expect(timeEntry.associationsAreLoaded(["project", "task"])).toBeFalsy()
|
138
|
+
expect(timeEntry.associationsAreLoaded(["project"])).toBeTruthy()
|
139
|
+
buildAndCacheTask(id: 2, title: "a task!")
|
140
|
+
expect(timeEntry.associationsAreLoaded(["project", "task"])).toBeTruthy()
|
141
|
+
expect(timeEntry.associationsAreLoaded(["project"])).toBeTruthy()
|
142
|
+
expect(timeEntry.associationsAreLoaded(["task"])).toBeTruthy()
|
143
|
+
|
144
|
+
it "should default to all of the associations defined on the model", ->
|
145
|
+
timeEntry = new App.Models.TimeEntry(id: 5, project_id: 10, task_id: 2, user_id: 666)
|
146
|
+
expect(timeEntry.associationsAreLoaded()).toBeFalsy()
|
147
|
+
buildAndCacheProject(id: 10, title: "a project!")
|
148
|
+
expect(timeEntry.associationsAreLoaded()).toBeFalsy()
|
149
|
+
buildAndCacheTask(id: 2, title: "a task!")
|
150
|
+
expect(timeEntry.associationsAreLoaded()).toBeFalsy()
|
151
|
+
buildAndCacheUser(id:666)
|
152
|
+
expect(timeEntry.associationsAreLoaded()).toBeTruthy()
|
153
|
+
|
154
|
+
it "should appear loaded when an association is null, but not loaded when the key is missing", ->
|
155
|
+
timeEntry = buildAndCacheTimeEntry()
|
156
|
+
delete timeEntry.attributes.project_id
|
157
|
+
expect(timeEntry.associationsAreLoaded(["project"])).toBeFalsy()
|
158
|
+
timeEntry = new App.Models.TimeEntry(id: 5, project_id: null)
|
159
|
+
expect(timeEntry.associationsAreLoaded(["project"])).toBeTruthy()
|
160
|
+
timeEntry = new App.Models.TimeEntry(id: 5, project_id: 2)
|
161
|
+
expect(timeEntry.associationsAreLoaded(["project"])).toBeFalsy()
|
162
|
+
|
163
|
+
describe "with HasMany associations", ->
|
164
|
+
it "should return true when all provided associations are loaded", ->
|
165
|
+
project = new App.Models.Project(id: 5, time_entry_ids: [10, 11], task_ids: [2, 3])
|
166
|
+
expect(project.associationsAreLoaded(["time_entries", "tasks"])).toBeFalsy()
|
167
|
+
buildAndCacheTimeEntry(id: 10)
|
168
|
+
expect(project.associationsAreLoaded(["time_entries"])).toBeFalsy()
|
169
|
+
buildAndCacheTimeEntry(id: 11)
|
170
|
+
expect(project.associationsAreLoaded(["time_entries"])).toBeTruthy()
|
171
|
+
expect(project.associationsAreLoaded(["time_entries", "tasks"])).toBeFalsy()
|
172
|
+
expect(project.associationsAreLoaded(["tasks"])).toBeFalsy()
|
173
|
+
buildAndCacheTask(id: 2)
|
174
|
+
expect(project.associationsAreLoaded(["tasks"])).toBeFalsy()
|
175
|
+
buildAndCacheTask(id: 3)
|
176
|
+
expect(project.associationsAreLoaded(["tasks"])).toBeTruthy()
|
177
|
+
expect(project.associationsAreLoaded(["tasks", "time_entries"])).toBeTruthy()
|
178
|
+
|
179
|
+
it "should appear loaded when an association is an empty array, but not loaded when the key is missing", ->
|
180
|
+
project = new App.Models.Project(id: 5, time_entry_ids: [])
|
181
|
+
expect(project.associationsAreLoaded(["time_entries"])).toBeTruthy()
|
182
|
+
expect(project.associationsAreLoaded(["tasks"])).toBeFalsy()
|
183
|
+
|
184
|
+
describe "get", ->
|
185
|
+
it "should delegate to Backbone.Model#get for anything that is not an association", ->
|
186
|
+
timeEntry = new App.Models.TimeEntry(id: 5, project_id: 10, task_id: 2, title: "foo")
|
187
|
+
expect(timeEntry.get("title")).toEqual "foo"
|
188
|
+
expect(timeEntry.get("missing")).toBeUndefined()
|
189
|
+
|
190
|
+
describe "BelongsTo associations", ->
|
191
|
+
it "should return associations", ->
|
192
|
+
timeEntry = new App.Models.TimeEntry(id: 5, project_id: 10, task_id: 2)
|
193
|
+
expect(-> timeEntry.get("project")).toThrow()
|
194
|
+
base.data.storage("projects").add { id: 10, title: "a project!" }
|
195
|
+
expect(timeEntry.get("project").get("title")).toEqual "a project!"
|
196
|
+
expect(timeEntry.get("project")).toEqual base.data.storage("projects").get(10)
|
197
|
+
|
198
|
+
it "should return null when we don't have an association id", ->
|
199
|
+
timeEntry = new App.Models.TimeEntry(id: 5, task_id: 2)
|
200
|
+
expect(timeEntry.get("project")).toBeFalsy()
|
201
|
+
|
202
|
+
it "should throw when we have an association id but it cannot be found", ->
|
203
|
+
timeEntry = new App.Models.TimeEntry(id: 5, task_id: 2)
|
204
|
+
expect(-> timeEntry.get("task")).toThrow()
|
205
|
+
|
206
|
+
describe "HasMany associations", ->
|
207
|
+
it "should return HasMany associations", ->
|
208
|
+
project = new App.Models.Project(id: 5, time_entry_ids: [2, 5])
|
209
|
+
expect(-> project.get("time_entries")).toThrow()
|
210
|
+
base.data.storage("time_entries").add buildTimeEntry(id: 2, project_id: 5, title: "first time entry")
|
211
|
+
base.data.storage("time_entries").add buildTimeEntry(id: 5, project_id: 5, title: "second time entry")
|
212
|
+
expect(project.get("time_entries").get(2).get("title")).toEqual "first time entry"
|
213
|
+
expect(project.get("time_entries").get(5).get("title")).toEqual "second time entry"
|
214
|
+
|
215
|
+
it "should return null when we don't have any association ids", ->
|
216
|
+
project = new App.Models.Project(id: 5)
|
217
|
+
expect(project.get("time_entries").models).toEqual []
|
218
|
+
|
219
|
+
it "should throw when we have an association id but it cannot be found", ->
|
220
|
+
project = new App.Models.Project(id: 5, time_entry_ids: [2, 5])
|
221
|
+
expect(-> project.get("time_entries")).toThrow()
|
222
|
+
|
223
|
+
it "should apply a sort order to has many associations if it is provided at time of get", ->
|
224
|
+
task = buildAndCacheTask(id: 5, sub_task_ids: [103, 77, 99])
|
225
|
+
buildAndCacheTask(id:103 , position: 3, updated_at: 845785)
|
226
|
+
buildAndCacheTask(id:77 , position: 2, updated_at: 995785)
|
227
|
+
buildAndCacheTask(id:99 , position: 1, updated_at: 635785)
|
228
|
+
|
229
|
+
subTasks = task.get("sub_tasks")
|
230
|
+
expect(subTasks.at(0).get('position')).toEqual(3)
|
231
|
+
expect(subTasks.at(1).get('position')).toEqual(2)
|
232
|
+
expect(subTasks.at(2).get('position')).toEqual(1)
|
233
|
+
|
234
|
+
subTasks = task.get("sub_tasks", order: "position:asc")
|
235
|
+
expect(subTasks.at(0).get('position')).toEqual(1)
|
236
|
+
expect(subTasks.at(1).get('position')).toEqual(2)
|
237
|
+
expect(subTasks.at(2).get('position')).toEqual(3)
|
238
|
+
|
239
|
+
subTasks = task.get("sub_tasks", order: "updated_at:desc")
|
240
|
+
expect(subTasks.at(0).get('id')).toEqual("77")
|
241
|
+
expect(subTasks.at(1).get('id')).toEqual("103")
|
242
|
+
expect(subTasks.at(2).get('id')).toEqual("99")
|
243
|
+
|
244
|
+
describe "toServerJSON", ->
|
245
|
+
it "calls toJSON", ->
|
246
|
+
spy = spyOn(model, "toJSON").andCallThrough()
|
247
|
+
model.toServerJSON()
|
248
|
+
expect(spy).toHaveBeenCalled()
|
249
|
+
|
250
|
+
it "always removes default blacklisted keys", ->
|
251
|
+
defaultBlacklistKeys = model.defaultJSONBlacklist()
|
252
|
+
expect(defaultBlacklistKeys.length).toEqual(3)
|
253
|
+
|
254
|
+
model.set('safe', true)
|
255
|
+
for key in defaultBlacklistKeys
|
256
|
+
model.set(key, true)
|
257
|
+
|
258
|
+
json = model.toServerJSON("create")
|
259
|
+
expect(json['safe']).toEqual(true)
|
260
|
+
for key in defaultBlacklistKeys
|
261
|
+
expect(json[key]).toBeUndefined()
|
262
|
+
|
263
|
+
it "removes blacklisted keys for create actions", ->
|
264
|
+
createBlacklist = ['flies', 'ants', 'fire ants']
|
265
|
+
spyOn(model, 'createJSONBlacklist').andReturn(createBlacklist)
|
266
|
+
|
267
|
+
for key in createBlacklist
|
268
|
+
model.set(key, true)
|
269
|
+
|
270
|
+
json = model.toServerJSON("create")
|
271
|
+
for key in createBlacklist
|
272
|
+
expect(json[key]).toBeUndefined()
|
273
|
+
|
274
|
+
it "removes blacklisted keys for update actions", ->
|
275
|
+
updateBlacklist = ['possums', 'racoons', 'potatoes']
|
276
|
+
spyOn(model, 'updateJSONBlacklist').andReturn(updateBlacklist)
|
277
|
+
|
278
|
+
for key in updateBlacklist
|
279
|
+
model.set(key, true)
|
280
|
+
|
281
|
+
json = model.toServerJSON("update")
|
282
|
+
for key in updateBlacklist
|
283
|
+
expect(json[key]).toBeUndefined()
|
@@ -0,0 +1,22 @@
|
|
1
|
+
describe "Brainstem.Sync", ->
|
2
|
+
describe "updating models", ->
|
3
|
+
ajaxSpy = null
|
4
|
+
|
5
|
+
beforeEach ->
|
6
|
+
ajaxSpy = spyOn($, 'ajax')
|
7
|
+
|
8
|
+
it "should use toServerJSON instead of toJSON", ->
|
9
|
+
modelSpy = spyOn(Brainstem.Model.prototype, 'toServerJSON')
|
10
|
+
model = buildTimeEntry()
|
11
|
+
model.save()
|
12
|
+
expect(modelSpy).toHaveBeenCalled()
|
13
|
+
|
14
|
+
it "should pass options.includes through the JSON", ->
|
15
|
+
model = buildTimeEntry()
|
16
|
+
model.save({}, include: 'creator')
|
17
|
+
expect(ajaxSpy.mostRecentCall.args[0].data).toMatch(/"include":"creator"/)
|
18
|
+
|
19
|
+
it "should setup param roots when models have a paramRoot set", ->
|
20
|
+
model = buildTimeEntry()
|
21
|
+
model.save({})
|
22
|
+
expect(ajaxSpy.mostRecentCall.args[0].data).toMatch(/"time_entry":{/)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
describe 'Brainstem Utils', ->
|
2
|
+
describe "matches", ->
|
3
|
+
it "should recursively compare objects and arrays", ->
|
4
|
+
expect(Brainstem.Utils.matches(2, 2)).toBe true
|
5
|
+
expect(Brainstem.Utils.matches([2], [2])).toBe true, '[2], [2]'
|
6
|
+
expect(Brainstem.Utils.matches([2, 3], [2])).toBe false
|
7
|
+
expect(Brainstem.Utils.matches([2, 3], [2, 3])).toBe true, '[2, 3], [2, 3]'
|
8
|
+
expect(Brainstem.Utils.matches({ hi: "there" }, { hi: "there" })).toBe true, '{ hi: "there" }, { hi: "there" }'
|
9
|
+
expect(Brainstem.Utils.matches([2, { hi: "there" }], [2, { hi: 2 }])).toBe false
|
10
|
+
expect(Brainstem.Utils.matches([2, { hi: "there" }], [2, { hi: "there" }])).toBe true, '[2, { hi: "there" }], [2, { hi: "there" }]'
|
11
|
+
expect(Brainstem.Utils.matches([2, { hi: ["there", 3] }], [2, { hi: ["there", 2] }])).toBe false
|
12
|
+
expect(Brainstem.Utils.matches([2, { hi: ["there", 2] }], [2, { hi: ["there", 2] }])).toBe true, '[2, { hi: ["there", 2] }], [2, { hi: ["there", 2] }]'
|
@@ -0,0 +1,209 @@
|
|
1
|
+
describe 'Brainstem Expectations', ->
|
2
|
+
manager = project1 = project2 = task1 = null
|
3
|
+
|
4
|
+
beforeEach ->
|
5
|
+
manager = base.data
|
6
|
+
manager.enableExpectations()
|
7
|
+
|
8
|
+
project1 = buildProject(id: 1, task_ids: [1])
|
9
|
+
project2 = buildProject(id: 2)
|
10
|
+
task1 = buildTask(id: 1, project_id: project1.id)
|
11
|
+
|
12
|
+
describe "stubbing responses", ->
|
13
|
+
it "should update returned collections", ->
|
14
|
+
expectation = manager.stub "projects", response: (stub) ->
|
15
|
+
stub.results = [project1, project2]
|
16
|
+
collection = manager.loadCollection "projects"
|
17
|
+
expect(collection.length).toEqual 0
|
18
|
+
expectation.respond()
|
19
|
+
expect(collection.length).toEqual 2
|
20
|
+
expect(collection.get(1)).toEqual project1
|
21
|
+
expect(collection.get(2)).toEqual project2
|
22
|
+
|
23
|
+
it "should call callbacks", ->
|
24
|
+
expectation = manager.stub "projects", response: (stub) ->
|
25
|
+
stub.results = [project1, project2]
|
26
|
+
collection = null
|
27
|
+
manager.loadCollection "projects", success: (c) -> collection = c
|
28
|
+
expect(collection).toBeNull()
|
29
|
+
expectation.respond()
|
30
|
+
expect(collection.length).toEqual 2
|
31
|
+
expect(collection.get(1)).toEqual project1
|
32
|
+
expect(collection.get(2)).toEqual project2
|
33
|
+
|
34
|
+
it "should add to passed-in collections", ->
|
35
|
+
expectation = manager.stub "projects", response: (stub) ->
|
36
|
+
stub.results = [project1, project2]
|
37
|
+
collection = new Brainstem.Collection()
|
38
|
+
manager.loadCollection "projects", collection: collection
|
39
|
+
expect(collection.length).toEqual 0
|
40
|
+
expectation.respond()
|
41
|
+
expect(collection.length).toEqual 2
|
42
|
+
expect(collection.get(1)).toEqual project1
|
43
|
+
expect(collection.get(2)).toEqual project2
|
44
|
+
|
45
|
+
it "should work with results hashes", ->
|
46
|
+
expectation = manager.stub "projects", response: (stub) ->
|
47
|
+
stub.results = [{ key: "projects", id: 2 }, { key: "projects", id: 1 }]
|
48
|
+
stub.associated.projects = [project1, project2]
|
49
|
+
collection = manager.loadCollection "projects"
|
50
|
+
expectation.respond()
|
51
|
+
expect(collection.length).toEqual 2
|
52
|
+
expect(collection.models[0]).toEqual project2
|
53
|
+
expect(collection.models[1]).toEqual project1
|
54
|
+
|
55
|
+
it "can populate associated objects", ->
|
56
|
+
expectation = manager.stub "projects", include: ["tasks"], response: (stub) ->
|
57
|
+
stub.results = [project1, project2]
|
58
|
+
stub.associated.projects = [project1, project2]
|
59
|
+
stub.associated.tasks = [task1]
|
60
|
+
collection = new Brainstem.Collection()
|
61
|
+
manager.loadCollection "projects", collection: collection, include: ["tasks"]
|
62
|
+
expectation.respond()
|
63
|
+
expect(collection.get(1).get("tasks").models).toEqual [task1]
|
64
|
+
expect(collection.get(2).get("tasks").models).toEqual []
|
65
|
+
|
66
|
+
describe "triggering errors", ->
|
67
|
+
it "triggers errors when asked to do so", ->
|
68
|
+
errorSpy = jasmine.createSpy()
|
69
|
+
|
70
|
+
collection = new Brainstem.Collection()
|
71
|
+
|
72
|
+
resp =
|
73
|
+
readyState: 4
|
74
|
+
status: 401
|
75
|
+
responseText: ""
|
76
|
+
|
77
|
+
expectation = manager.stub "projects", collection: collection, triggerError: resp
|
78
|
+
|
79
|
+
manager.loadCollection "projects", error: errorSpy
|
80
|
+
|
81
|
+
expectation.respond()
|
82
|
+
expect(errorSpy).toHaveBeenCalled()
|
83
|
+
expect(errorSpy.mostRecentCall.args[0] instanceof Brainstem.Collection).toBe true
|
84
|
+
expect(errorSpy.mostRecentCall.args[1]).toEqual resp
|
85
|
+
|
86
|
+
it "does not trigger errors when asked not to", ->
|
87
|
+
errorSpy = jasmine.createSpy()
|
88
|
+
expectation = manager.stub "projects", response: (exp) -> exp.results = [project1, project2]
|
89
|
+
|
90
|
+
manager.loadCollection "projects", error: errorSpy
|
91
|
+
|
92
|
+
expectation.respond()
|
93
|
+
expect(errorSpy).not.toHaveBeenCalled()
|
94
|
+
|
95
|
+
describe "responding immediately", ->
|
96
|
+
it "uses stubImmediate", ->
|
97
|
+
expectation = manager.stubImmediate "projects", include: ["tasks"], response: (stub) ->
|
98
|
+
stub.results = [project1, project2]
|
99
|
+
stub.associated.tasks = [task1]
|
100
|
+
collection = manager.loadCollection "projects", include: ["tasks"]
|
101
|
+
expect(collection.get(1).get("tasks").models).toEqual [task1]
|
102
|
+
|
103
|
+
describe "multiple stubs", ->
|
104
|
+
it "should match the first valid expectation", ->
|
105
|
+
manager.stubImmediate "projects", only: [1], response: (stub) ->
|
106
|
+
stub.results = [project1]
|
107
|
+
manager.stubImmediate "projects", response: (stub) ->
|
108
|
+
stub.results = [project1, project2]
|
109
|
+
manager.stubImmediate "projects", only: [2], response: (stub) ->
|
110
|
+
stub.results = [project2]
|
111
|
+
expect(manager.loadCollection("projects", only: 1).models).toEqual [project1]
|
112
|
+
expect(manager.loadCollection("projects").models).toEqual [project1, project2]
|
113
|
+
expect(manager.loadCollection("projects", only: 2).models).toEqual [project2]
|
114
|
+
|
115
|
+
it "should fail if it cannot find a specific match", ->
|
116
|
+
manager.stubImmediate "projects", response: (stub) ->
|
117
|
+
stub.results = [project1]
|
118
|
+
manager.stubImmediate "projects", include: ["tasks"], filters: { something: "else" }, response: (stub) ->
|
119
|
+
stub.results = [project1, project2]
|
120
|
+
stub.associated.tasks = [task1]
|
121
|
+
expect(manager.loadCollection("projects", include: ["tasks"], filters: { something: "else" }).models).toEqual [project1, project2]
|
122
|
+
expect(-> manager.loadCollection("projects", include: ["tasks"], filters: { something: "wrong" })).toThrow()
|
123
|
+
expect(-> manager.loadCollection("projects", include: ["users"], filters: { something: "else" })).toThrow()
|
124
|
+
expect(-> manager.loadCollection("projects", filters: { something: "else" })).toThrow()
|
125
|
+
expect(-> manager.loadCollection("projects", include: ["users"])).toThrow()
|
126
|
+
expect(manager.loadCollection("projects").models).toEqual [project1]
|
127
|
+
|
128
|
+
it "should ignore empty arrays", ->
|
129
|
+
manager.stubImmediate "projects", response: (stub) ->
|
130
|
+
stub.results = [project1, project2]
|
131
|
+
expect(manager.loadCollection("projects", include: []).models).toEqual [project1, project2]
|
132
|
+
|
133
|
+
it "should allow wildcard params", ->
|
134
|
+
manager.stubImmediate "projects", include: '*', response: (stub) ->
|
135
|
+
stub.results = [project1, project2]
|
136
|
+
expect(manager.loadCollection("projects", include: ["tasks"]).models).toEqual [project1, project2]
|
137
|
+
expect(manager.loadCollection("projects", include: ["users"]).models).toEqual [project1, project2]
|
138
|
+
expect(manager.loadCollection("projects").models).toEqual [project1, project2]
|
139
|
+
|
140
|
+
describe "recording", ->
|
141
|
+
it "should record options", ->
|
142
|
+
expectation = manager.stubImmediate "projects", filters: { something: "else" }, response: (stub) ->
|
143
|
+
stub.results = [project1, project2]
|
144
|
+
manager.loadCollection("projects", filters: { something: "else" })
|
145
|
+
expect(expectation.matches[0].filters).toEqual { something: "else" }
|
146
|
+
|
147
|
+
describe "clearing expectations", ->
|
148
|
+
it "expectations can be removed", ->
|
149
|
+
expectation = manager.stub "projects", include: ["tasks"], response: (stub) ->
|
150
|
+
stub.results = [project1, project2]
|
151
|
+
stub.associated.tasks = [task1]
|
152
|
+
|
153
|
+
collection = manager.loadCollection "projects", include: ["tasks"]
|
154
|
+
expectation.respond()
|
155
|
+
expect(collection.get(1).get("tasks").models).toEqual [task1]
|
156
|
+
|
157
|
+
collection2 = manager.loadCollection "projects", include: ["tasks"]
|
158
|
+
expect(collection2.get(1)).toBeFalsy()
|
159
|
+
expectation.respond()
|
160
|
+
expect(collection2.get(1).get("tasks").models).toEqual [task1]
|
161
|
+
|
162
|
+
expectation.remove()
|
163
|
+
expect(-> manager.loadCollection "projects").toThrow()
|
164
|
+
|
165
|
+
describe "lastMatch", ->
|
166
|
+
it "retrives the last match object", ->
|
167
|
+
expectation = manager.stubImmediate "projects", include: "*", response: (stub) ->
|
168
|
+
stub.results = []
|
169
|
+
|
170
|
+
manager.loadCollection("projects", include: ["tasks"])
|
171
|
+
manager.loadCollection("projects", include: ["users"])
|
172
|
+
|
173
|
+
expect(expectation.matches.length).toEqual(2)
|
174
|
+
expect(expectation.lastMatch().include).toEqual(["users"])
|
175
|
+
|
176
|
+
it "returns undefined if no matches exist", ->
|
177
|
+
expectation = manager.stub "projects", response: (stub) ->
|
178
|
+
stub.results = []
|
179
|
+
expect(expectation.lastMatch()).toBeUndefined()
|
180
|
+
|
181
|
+
describe "optionsMatch", ->
|
182
|
+
it "should ignore wrapping arrays", ->
|
183
|
+
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
|
186
|
+
|
187
|
+
it "should treat * as an any match", ->
|
188
|
+
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
|
192
|
+
|
193
|
+
it "should treat strings and numbers the same when appropriate", ->
|
194
|
+
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
|
197
|
+
|
198
|
+
it "should treat null, empty array, and empty object the same", ->
|
199
|
+
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
|
204
|
+
|
205
|
+
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
|
@@ -0,0 +1,80 @@
|
|
1
|
+
window.spec ?= {}
|
2
|
+
|
3
|
+
spec.defineBuilders = ->
|
4
|
+
window.defineBuilder = (name, klass, defaultOptions) ->
|
5
|
+
class_defaults = {}
|
6
|
+
|
7
|
+
for key, value of defaultOptions
|
8
|
+
if typeof(value) == "function"
|
9
|
+
do ->
|
10
|
+
seq_name = name + "_" + key
|
11
|
+
BackboneFactory.define_sequence(seq_name, value)
|
12
|
+
class_defaults[key] = ->
|
13
|
+
next = BackboneFactory.next(seq_name)
|
14
|
+
if isIdAttr(seq_name) then arrayPreservedToString(next) else next
|
15
|
+
else
|
16
|
+
class_defaults[key] = if isIdAttr(key) then arrayPreservedToString(value) else value
|
17
|
+
|
18
|
+
factory = BackboneFactory.define(name, klass, -> return class_defaults)
|
19
|
+
builder = (opts) ->
|
20
|
+
BackboneFactory.create(name, $.extend({}, class_defaults, idsToStrings(opts)))
|
21
|
+
|
22
|
+
creator = (opts) ->
|
23
|
+
obj = builder(idsToStrings(opts))
|
24
|
+
storageName = name.underscore().pluralize()
|
25
|
+
window.base.data.storage(storageName).add obj if window.base.data.collectionExists(storageName)
|
26
|
+
obj
|
27
|
+
|
28
|
+
eval("window.#{"build_#{name.underscore()}".camelize(true)} = builder")
|
29
|
+
eval("window.#{"build_and_cache_#{name.underscore()}".camelize(true)} = creator")
|
30
|
+
|
31
|
+
isIdAttr = (attrName) ->
|
32
|
+
attrName == 'id' || attrName.match(/_id$/) || (attrName.match(/_ids$/))
|
33
|
+
|
34
|
+
arrayPreservedToString = (value) ->
|
35
|
+
if _.isArray(value)
|
36
|
+
_.map(value, (v) -> arrayPreservedToString(v))
|
37
|
+
else if value? && !$.isPlainObject(value)
|
38
|
+
String(value)
|
39
|
+
else
|
40
|
+
value
|
41
|
+
|
42
|
+
idsToStrings = (builderOpts) ->
|
43
|
+
for key, value of builderOpts
|
44
|
+
if isIdAttr(key)
|
45
|
+
builderOpts[key] = arrayPreservedToString(value)
|
46
|
+
|
47
|
+
builderOpts
|
48
|
+
|
49
|
+
window.defineBuilder "user", App.Models.User, {
|
50
|
+
id: (n) -> return n
|
51
|
+
}
|
52
|
+
|
53
|
+
window.defineBuilder "project", App.Models.Project, {
|
54
|
+
id: (n) -> return n
|
55
|
+
title: "new project"
|
56
|
+
}
|
57
|
+
|
58
|
+
getTimeEntryDefaults = ->
|
59
|
+
project = buildProject()
|
60
|
+
|
61
|
+
return {
|
62
|
+
id: (n)-> return n
|
63
|
+
project_id: project.get("id")
|
64
|
+
}
|
65
|
+
window.defineBuilder "timeEntry", App.Models.TimeEntry, getTimeEntryDefaults()
|
66
|
+
|
67
|
+
getTaskDefaults = ->
|
68
|
+
project = buildProject()
|
69
|
+
|
70
|
+
return {
|
71
|
+
id: (n) -> n
|
72
|
+
project_id: project.get("id")
|
73
|
+
description: "a very interesting task"
|
74
|
+
title: (n) -> "new Task#{n}"
|
75
|
+
archived: false
|
76
|
+
parent_id: null
|
77
|
+
}
|
78
|
+
window.defineBuilder "task", App.Models.Task, getTaskDefaults()
|
79
|
+
|
80
|
+
window.defineBuilder "post", App.Models.Post, {}
|