brainstem-js 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,613 @@
|
|
1
|
+
describe 'Brainstem Storage Manager', ->
|
2
|
+
manager = null
|
3
|
+
|
4
|
+
beforeEach ->
|
5
|
+
manager = new Brainstem.StorageManager()
|
6
|
+
|
7
|
+
describe 'addCollection and getCollectionDetails', ->
|
8
|
+
it "tracks a named collection", ->
|
9
|
+
manager.addCollection 'time_entries', App.Collections.TimeEntries
|
10
|
+
expect(manager.getCollectionDetails("time_entries").klass).toBe App.Collections.TimeEntries
|
11
|
+
|
12
|
+
it "raises an error if the named collection doesn't exist", ->
|
13
|
+
expect(-> manager.getCollectionDetails('foo')).toThrow()
|
14
|
+
|
15
|
+
describe "storage", ->
|
16
|
+
beforeEach ->
|
17
|
+
manager.addCollection 'time_entries', App.Collections.TimeEntries
|
18
|
+
|
19
|
+
it "accesses a cached collection of the appropriate type", ->
|
20
|
+
expect(manager.storage('time_entries') instanceof App.Collections.TimeEntries).toBeTruthy()
|
21
|
+
expect(manager.storage('time_entries').length).toBe 0
|
22
|
+
|
23
|
+
it "raises an error if the named collection doesn't exist", ->
|
24
|
+
expect(-> manager.storage('foo')).toThrow()
|
25
|
+
|
26
|
+
describe "reset", ->
|
27
|
+
it "should clear all storage and sort lengths", ->
|
28
|
+
buildAndCacheTask()
|
29
|
+
buildAndCacheProject()
|
30
|
+
expect(base.data.storage("projects").length).toEqual 1
|
31
|
+
expect(base.data.storage("tasks").length).toEqual 1
|
32
|
+
base.data.collections["projects"].cache = { "foo": "bar" }
|
33
|
+
base.data.reset()
|
34
|
+
expect(base.data.collections["projects"].cache).toEqual {}
|
35
|
+
expect(base.data.storage("projects").length).toEqual 0
|
36
|
+
expect(base.data.storage("tasks").length).toEqual 0
|
37
|
+
|
38
|
+
describe "loadModel", ->
|
39
|
+
beforeEach ->
|
40
|
+
tasks = [buildTask(id: 2, title: "a task", project_id: 15)]
|
41
|
+
projects = [buildProject(id: 15)]
|
42
|
+
timeEntries = [buildTimeEntry(id: 1, task_id: 2, project_id: 15, title: "a time entry")]
|
43
|
+
respondWith server, "/api/time_entries?only=1", resultsFrom: "time_entries", data: { time_entries: timeEntries }
|
44
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&only=1", resultsFrom: "time_entries", data: { time_entries: timeEntries, tasks: tasks, projects: projects }
|
45
|
+
|
46
|
+
it "loads a single model from the server, including associations", ->
|
47
|
+
model = base.data.loadModel "time_entry", 1, include: ["project", "task"]
|
48
|
+
expect(model.loaded).toBe false
|
49
|
+
server.respond()
|
50
|
+
expect(model.loaded).toBe true
|
51
|
+
expect(model.id).toEqual "1"
|
52
|
+
expect(model.get("title")).toEqual "a time entry"
|
53
|
+
expect(model.get('task').get('title')).toEqual "a task"
|
54
|
+
expect(model.get('project').id).toEqual "15"
|
55
|
+
|
56
|
+
it "works even when the server returned associations of the same type", ->
|
57
|
+
posts = [buildPost(id: 2, reply: true), buildPost(id: 3, reply: true), buildPost(id: 1, reply: false, reply_ids: [2, 3])]
|
58
|
+
respondWith server, "/api/posts?include=replies&only=1", data: { results: [{ key: "posts", id: 1 }], posts: posts }
|
59
|
+
model = base.data.loadModel "post", 1, include: ["replies"]
|
60
|
+
expect(model.loaded).toBe false
|
61
|
+
server.respond()
|
62
|
+
expect(model.loaded).toBe true
|
63
|
+
expect(model.id).toEqual "1"
|
64
|
+
expect(model.get("replies").pluck("id")).toEqual ["2", "3"]
|
65
|
+
|
66
|
+
it "updates associations before the primary model", ->
|
67
|
+
events = []
|
68
|
+
base.data.storage('time_entries').on "add", -> events.push "time_entries"
|
69
|
+
base.data.storage('tasks').on "add", -> events.push "tasks"
|
70
|
+
base.data.loadModel "time_entry", 1, include: ["project", "task"]
|
71
|
+
server.respond()
|
72
|
+
expect(events).toEqual ["tasks", "time_entries"]
|
73
|
+
|
74
|
+
it "triggers changes", ->
|
75
|
+
model = base.data.loadModel "time_entry", 1, include: ["project", "task"]
|
76
|
+
spy = jasmine.createSpy().andCallFake ->
|
77
|
+
expect(model.loaded).toBe true
|
78
|
+
expect(model.get("title")).toEqual "a time entry"
|
79
|
+
expect(model.get('task').get('title')).toEqual "a task"
|
80
|
+
expect(model.get('project').id).toEqual "15"
|
81
|
+
model.bind "change", spy
|
82
|
+
expect(spy).not.toHaveBeenCalled()
|
83
|
+
server.respond()
|
84
|
+
expect(spy).toHaveBeenCalled()
|
85
|
+
expect(spy.callCount).toEqual 1
|
86
|
+
|
87
|
+
it "accepts a success function", ->
|
88
|
+
spy = jasmine.createSpy().andCallFake (model) ->
|
89
|
+
expect(model.loaded).toBe true
|
90
|
+
model = base.data.loadModel "time_entry", 1, success: spy
|
91
|
+
server.respond()
|
92
|
+
expect(spy).toHaveBeenCalled()
|
93
|
+
|
94
|
+
it "can disbale caching", ->
|
95
|
+
spy = spyOn(base.data, 'loadCollection')
|
96
|
+
model = base.data.loadModel "time_entry", 1, cache: false
|
97
|
+
expect(spy.mostRecentCall.args[1]['cache']).toBe(false)
|
98
|
+
|
99
|
+
describe 'loadCollection', ->
|
100
|
+
it "loads a collection of models", ->
|
101
|
+
timeEntries = [buildTimeEntry(), buildTimeEntry()]
|
102
|
+
respondWith server, "/api/time_entries?per_page=20&page=1", resultsFrom: "time_entries", data: { time_entries: timeEntries }
|
103
|
+
collection = base.data.loadCollection "time_entries"
|
104
|
+
expect(collection.length).toBe 0
|
105
|
+
server.respond()
|
106
|
+
expect(collection.length).toBe 2
|
107
|
+
|
108
|
+
it "accepts a success function", ->
|
109
|
+
timeEntries = [buildTimeEntry(), buildTimeEntry()]
|
110
|
+
respondWith server, "/api/time_entries?per_page=20&page=1", resultsFrom: "time_entries", data: { time_entries: timeEntries }
|
111
|
+
spy = jasmine.createSpy().andCallFake (collection) ->
|
112
|
+
expect(collection.loaded).toBe true
|
113
|
+
collection = base.data.loadCollection "time_entries", success: spy
|
114
|
+
server.respond()
|
115
|
+
expect(spy).toHaveBeenCalledWith(collection)
|
116
|
+
|
117
|
+
it "saves it's options onto the returned collection", ->
|
118
|
+
collection = base.data.loadCollection "time_entries", order: "baz:desc", filters: { bar: 2 }
|
119
|
+
expect(collection.lastFetchOptions.order).toEqual "baz:desc"
|
120
|
+
expect(collection.lastFetchOptions.filters).toEqual { bar: 2 }
|
121
|
+
expect(collection.lastFetchOptions.collection).toBeFalsy()
|
122
|
+
|
123
|
+
describe "passing an optional collection", ->
|
124
|
+
it "accepts an optional collection instead of making a new one", ->
|
125
|
+
timeEntry = buildTimeEntry()
|
126
|
+
respondWith server, "/api/time_entries?per_page=20&page=1", data: { results: [{ key: "time_entries", id: timeEntry.id }], time_entries: [timeEntry] }
|
127
|
+
collection = new App.Collections.TimeEntries([buildTimeEntry(), buildTimeEntry()])
|
128
|
+
collection.setLoaded true
|
129
|
+
base.data.loadCollection "time_entries", collection: collection
|
130
|
+
expect(collection.lastFetchOptions.collection).toBeFalsy()
|
131
|
+
expect(collection.loaded).toBe false
|
132
|
+
expect(collection.length).toEqual 2
|
133
|
+
server.respond()
|
134
|
+
expect(collection.loaded).toBe true
|
135
|
+
expect(collection.length).toEqual 3
|
136
|
+
|
137
|
+
it "can take an optional reset command to reset the collection before using it", ->
|
138
|
+
timeEntry = buildTimeEntry()
|
139
|
+
respondWith server, "/api/time_entries?per_page=20&page=1", data: { results: [{ key: "time_entries", id: timeEntry.id }], time_entries: [timeEntry] }
|
140
|
+
collection = new App.Collections.TimeEntries([buildTimeEntry(), buildTimeEntry()])
|
141
|
+
collection.setLoaded true
|
142
|
+
spyOn(collection, 'reset').andCallThrough()
|
143
|
+
base.data.loadCollection "time_entries", collection: collection, reset: true
|
144
|
+
expect(collection.reset).toHaveBeenCalled()
|
145
|
+
expect(collection.lastFetchOptions.collection).toBeFalsy()
|
146
|
+
expect(collection.loaded).toBe false
|
147
|
+
expect(collection.length).toEqual 0
|
148
|
+
server.respond()
|
149
|
+
expect(collection.loaded).toBe true
|
150
|
+
expect(collection.length).toEqual 1
|
151
|
+
|
152
|
+
it "accepts filters", ->
|
153
|
+
posts = [buildPost(project_id: 15, id: 1), buildPost(project_id: 15, id: 2)]
|
154
|
+
respondWith server, "/api/posts?filter1=true&filter2=false&filter3=true&filter4=false&filter5=2&filter6=baz&per_page=20&page=1", data: { results: [{ key: "posts", id: 1}], posts: posts }
|
155
|
+
collection = base.data.loadCollection "posts", filters: { filter1: true, filter2: false, filter3: "true", filter4: "false", filter5: 2, filter6: "baz" }
|
156
|
+
server.respond()
|
157
|
+
|
158
|
+
it "triggers reset", ->
|
159
|
+
timeEntry = buildTimeEntry()
|
160
|
+
respondWith server, "/api/time_entries?per_page=20&page=1", data: { results: [{ key: "time_entries", id: timeEntry.id}], time_entries: [timeEntry] }
|
161
|
+
collection = base.data.loadCollection "time_entries"
|
162
|
+
expect(collection.loaded).toBe false
|
163
|
+
spy = jasmine.createSpy().andCallFake ->
|
164
|
+
expect(collection.loaded).toBe true
|
165
|
+
collection.bind "reset", spy
|
166
|
+
server.respond()
|
167
|
+
expect(spy).toHaveBeenCalled()
|
168
|
+
|
169
|
+
it "ignores count and honors results", ->
|
170
|
+
server.respondWith "GET", "/api/time_entries?per_page=20&page=1", [ 200, {"Content-Type": "application/json"}, JSON.stringify(count: 2, results: [{ key: "time_entries", id: 2 }], time_entries: [buildTimeEntry(), buildTimeEntry()]) ]
|
171
|
+
collection = base.data.loadCollection "time_entries"
|
172
|
+
server.respond()
|
173
|
+
expect(collection.length).toEqual(1)
|
174
|
+
|
175
|
+
it "works with an empty response", ->
|
176
|
+
exceptionSpy = spyOn(sinon, 'logError').andCallThrough()
|
177
|
+
respondWith server, "/api/time_entries?per_page=20&page=1", resultsFrom: "time_entries", data: { time_entries: [] }
|
178
|
+
base.data.loadCollection "time_entries"
|
179
|
+
server.respond()
|
180
|
+
expect(exceptionSpy).not.toHaveBeenCalled()
|
181
|
+
|
182
|
+
describe "fetching of associations", ->
|
183
|
+
json = null
|
184
|
+
|
185
|
+
beforeEach ->
|
186
|
+
tasks = [buildTask(id: 2, title: "a task")]
|
187
|
+
projects = [buildProject(id: 15), buildProject(id: 10)]
|
188
|
+
timeEntries = [buildTimeEntry(task_id: 2, project_id: 15, id: 1), buildTimeEntry(task_id: null, project_id: 10, id: 2)]
|
189
|
+
|
190
|
+
respondWith server, /\/api\/time_entries\?include=project%2Ctask&per_page=\d+&page=\d+/, resultsFrom: "time_entries", data: { time_entries: timeEntries, tasks: tasks, projects: projects }
|
191
|
+
respondWith server, /\/api\/time_entries\?include=project&per_page=\d+&page=\d+/, resultsFrom: "time_entries", data: { time_entries: timeEntries, projects: projects }
|
192
|
+
|
193
|
+
it "loads collections that should be included", ->
|
194
|
+
collection = base.data.loadCollection "time_entries", include: ["project", "task"]
|
195
|
+
spy = jasmine.createSpy().andCallFake ->
|
196
|
+
expect(collection.loaded).toBe true
|
197
|
+
expect(collection.get(1).get('task').get('title')).toEqual "a task"
|
198
|
+
expect(collection.get(2).get('task')).toBeFalsy()
|
199
|
+
expect(collection.get(1).get('project').id).toEqual "15"
|
200
|
+
expect(collection.get(2).get('project').id).toEqual "10"
|
201
|
+
collection.bind "reset", spy
|
202
|
+
expect(collection.loaded).toBe false
|
203
|
+
server.respond()
|
204
|
+
expect(collection.loaded).toBe true
|
205
|
+
expect(spy).toHaveBeenCalled()
|
206
|
+
|
207
|
+
it "applies uses the results array from the server (so that associations of the same type as the primary can be handled- posts with replies; tasks with subtasks, etc.)", ->
|
208
|
+
posts = [buildPost(project_id: 15, id: 1, reply_ids: [2]), buildPost(project_id: 15, id: 2, subject_id: 1, reply: true)]
|
209
|
+
respondWith server, "/api/posts?include=replies&parents_only=true&per_page=20&page=1", data: { results: [{ key: "posts", id: 1}], posts: posts }
|
210
|
+
collection = base.data.loadCollection "posts", include: ["replies"], filters: { parents_only: "true" }
|
211
|
+
server.respond()
|
212
|
+
expect(collection.pluck("id")).toEqual ["1"]
|
213
|
+
expect(collection.get(1).get('replies').pluck("id")).toEqual ["2"]
|
214
|
+
|
215
|
+
describe "fetching multiple levels of associations", ->
|
216
|
+
it "seperately requests each layer of associations", ->
|
217
|
+
projectOneTimeEntryTask = buildTask()
|
218
|
+
projectOneTimeEntry = buildTimeEntry(title: "without task"); projectOneTimeEntryWithTask = buildTimeEntry(id: projectOneTimeEntry.id, task_id: projectOneTimeEntryTask.id, title: "with task")
|
219
|
+
projectOne = buildProject(); projectOneWithTimeEntries = buildProject(id: projectOne.id, time_entry_ids: [projectOneTimeEntry.id])
|
220
|
+
projectTwo = buildProject(); projectTwoWithTimeEntries = buildProject(id: projectTwo.id, time_entry_ids: [])
|
221
|
+
taskOneAssignee = buildUser()
|
222
|
+
taskTwoAssignee = buildUser()
|
223
|
+
taskOneSubAssignee = buildUser()
|
224
|
+
taskOneSub = buildTask(project_id: projectOne.id, parent_id: 10); taskOneSubWithAssignees = buildTask(id: taskOneSub.id, assignee_ids: [taskOneSubAssignee.id], parent_id: 10)
|
225
|
+
taskTwoSub = buildTask(project_id: projectTwo.id, parent_id: 11); taskTwoSubWithAssignees = buildTask(id: taskTwoSub.id, assignee_ids: [taskTwoAssignee.id], parent_id: 11)
|
226
|
+
taskOne = buildTask(id: 10, project_id: projectOne.id, assignee_ids: [taskOneAssignee.id], sub_task_ids: [taskOneSub.id])
|
227
|
+
taskTwo = buildTask(id: 11, project_id: projectTwo.id, assignee_ids: [taskTwoAssignee.id], sub_task_ids: [taskTwoSub.id])
|
228
|
+
respondWith server, "/api/tasks.json?include=assignees%2Cproject%2Csub_tasks&parents_only=true&per_page=20&page=1", data: { results: resultsArray("tasks", [taskOne, taskTwo]), tasks: resultsObject([taskOne, taskTwo, taskOneSub, taskTwoSub]), users: resultsObject([taskOneAssignee, taskTwoAssignee]), projects: resultsObject([projectOne, projectTwo]) }
|
229
|
+
respondWith server, "/api/tasks.json?include=assignees&only=#{taskOneSub.id}%2C#{taskTwoSub.id}", data: { results: resultsArray("tasks", [taskOneSub, taskTwoSub]), tasks: resultsObject([taskOneSubWithAssignees, taskTwoSubWithAssignees]), users: resultsObject([taskOneSubAssignee, taskTwoAssignee]) }
|
230
|
+
respondWith server, "/api/projects?include=time_entries&only=#{projectOne.id}%2C#{projectTwo.id}", data: { results: resultsArray("projects", [projectOne, projectTwo]), projects: resultsObject([projectOneWithTimeEntries, projectTwoWithTimeEntries]), time_entries: resultsObject([projectOneTimeEntry]) }
|
231
|
+
respondWith server, "/api/time_entries?include=task&only=#{projectOneTimeEntry.id}", data: { results: resultsArray("time_entries", [projectOneTimeEntry]), time_entries: resultsObject([projectOneTimeEntryWithTask]), tasks: resultsObject([projectOneTimeEntryTask]) }
|
232
|
+
|
233
|
+
callCount = 0
|
234
|
+
checkStructure = (collection) ->
|
235
|
+
expect(collection.pluck("id").sort()).toEqual [taskOne.id, taskTwo.id]
|
236
|
+
expect(collection.get(taskOne.id).get("project").id).toEqual projectOne.id
|
237
|
+
expect(collection.get(taskOne.id).get("assignees").pluck("id")).toEqual [taskOneAssignee.id]
|
238
|
+
expect(collection.get(taskTwo.id).get("assignees").pluck("id")).toEqual [taskTwoAssignee.id]
|
239
|
+
expect(collection.get(taskOne.id).get("sub_tasks").pluck("id")).toEqual [taskOneSub.id]
|
240
|
+
expect(collection.get(taskTwo.id).get("sub_tasks").pluck("id")).toEqual [taskTwoSub.id]
|
241
|
+
expect(collection.get(taskOne.id).get("sub_tasks").get(taskOneSub.id).get("assignees").pluck("id")).toEqual [taskOneSubAssignee.id]
|
242
|
+
expect(collection.get(taskTwo.id).get("sub_tasks").get(taskTwoSub.id).get("assignees").pluck("id")).toEqual [taskTwoAssignee.id]
|
243
|
+
expect(collection.get(taskOne.id).get("project").get("time_entries").pluck("id")).toEqual [projectOneTimeEntry.id]
|
244
|
+
expect(collection.get(taskOne.id).get("project").get("time_entries").models[0].get("task").id).toEqual projectOneTimeEntryTask.id
|
245
|
+
callCount += 1
|
246
|
+
|
247
|
+
success = jasmine.createSpy().andCallFake checkStructure
|
248
|
+
collection = base.data.loadCollection "tasks", filters: { parents_only: "true" }, success: success, include: [
|
249
|
+
"assignees",
|
250
|
+
"project": ["time_entries": "task"],
|
251
|
+
"sub_tasks": ["assignees"]
|
252
|
+
]
|
253
|
+
collection.bind "loaded", checkStructure
|
254
|
+
collection.bind "reset", checkStructure
|
255
|
+
expect(success).not.toHaveBeenCalled()
|
256
|
+
server.respond()
|
257
|
+
expect(success).toHaveBeenCalledWith(collection)
|
258
|
+
expect(callCount).toEqual 3
|
259
|
+
|
260
|
+
describe "caching", ->
|
261
|
+
describe "without ordering", ->
|
262
|
+
it "doesn't go to the server when it already has the data", ->
|
263
|
+
collection1 = base.data.loadCollection "time_entries", include: ["project", "task"], page: 1, perPage: 2
|
264
|
+
server.respond()
|
265
|
+
expect(collection1.loaded).toBe true
|
266
|
+
expect(collection1.get(1).get('project').id).toEqual "15"
|
267
|
+
expect(collection1.get(2).get('project').id).toEqual "10"
|
268
|
+
spy = jasmine.createSpy()
|
269
|
+
collection2 = base.data.loadCollection "time_entries", include: ["project", "task"], page: 1, perPage: 2, success: spy
|
270
|
+
expect(spy).toHaveBeenCalled()
|
271
|
+
expect(collection2.loaded).toBe true
|
272
|
+
expect(collection2.get(1).get('task').get('title')).toEqual "a task"
|
273
|
+
expect(collection2.get(2).get('task')).toBeFalsy()
|
274
|
+
expect(collection2.get(1).get('project').id).toEqual "15"
|
275
|
+
expect(collection2.get(2).get('project').id).toEqual "10"
|
276
|
+
|
277
|
+
it "does go to the server when more records are requested than it has previously requested, and remembers previously requested pages", ->
|
278
|
+
collection1 = base.data.loadCollection "time_entries", include: ["project", "task"], page: 1, perPage: 2
|
279
|
+
server.respond()
|
280
|
+
expect(collection1.loaded).toBe true
|
281
|
+
collection2 = base.data.loadCollection "time_entries", include: ["project", "task"], page: 2, perPage: 2
|
282
|
+
expect(collection2.loaded).toBe false
|
283
|
+
server.respond()
|
284
|
+
expect(collection2.loaded).toBe true
|
285
|
+
collection3 = base.data.loadCollection "time_entries", include: ["project"], page: 1, perPage: 2
|
286
|
+
expect(collection3.loaded).toBe true
|
287
|
+
|
288
|
+
it "does go to the server when some associations are missing, when otherwise it would have the data", ->
|
289
|
+
collection1 = base.data.loadCollection "time_entries", include: ["project"], page: 1, perPage: 2
|
290
|
+
server.respond()
|
291
|
+
expect(collection1.loaded).toBe true
|
292
|
+
collection2 = base.data.loadCollection "time_entries", include: ["project", "task"], page: 1, perPage: 2
|
293
|
+
expect(collection2.loaded).toBe false
|
294
|
+
|
295
|
+
describe "with ordering and filtering", ->
|
296
|
+
now = ws10 = ws11 = te1Ws10 = te2Ws10 = te1Ws11 = te2Ws11 = null
|
297
|
+
|
298
|
+
beforeEach ->
|
299
|
+
now = (new Date()).getTime()
|
300
|
+
ws10 = buildProject(id: 10)
|
301
|
+
ws11 = buildProject(id: 11)
|
302
|
+
te1Ws10 = buildTimeEntry(task_id: null, project_id: 10, id: 1, created_at: now - 20 * 1000, updated_at: now - 10 * 1000)
|
303
|
+
te2Ws10 = buildTimeEntry(task_id: null, project_id: 10, id: 2, created_at: now - 10 * 1000, updated_at: now - 5 * 1000)
|
304
|
+
te1Ws11 = buildTimeEntry(task_id: null, project_id: 11, id: 3, created_at: now - 100 * 1000, updated_at: now - 4 * 1000)
|
305
|
+
te2Ws11 = buildTimeEntry(task_id: null, project_id: 11, id: 4, created_at: now - 200 * 1000, updated_at: now - 12 * 1000)
|
306
|
+
|
307
|
+
it "goes to the server for pages of data and updates the collection", ->
|
308
|
+
respondWith server, "/api/time_entries?order=created_at%3Aasc&per_page=2&page=1", data: { results: resultsArray("time_entries", [te2Ws11, te1Ws11]), time_entries: [te2Ws11, te1Ws11] }
|
309
|
+
respondWith server, "/api/time_entries?order=created_at%3Aasc&per_page=2&page=2", data: { results: resultsArray("time_entries", [te1Ws10, te2Ws10]), time_entries: [te1Ws10, te2Ws10] }
|
310
|
+
collection = base.data.loadCollection "time_entries", order: "created_at:asc", page: 1, perPage: 2
|
311
|
+
server.respond()
|
312
|
+
expect(collection.pluck("id")).toEqual [te2Ws11.id, te1Ws11.id]
|
313
|
+
base.data.loadCollection "time_entries", collection: collection, order: "created_at:asc", page: 2, perPage: 2
|
314
|
+
server.respond()
|
315
|
+
expect(collection.pluck("id")).toEqual [te2Ws11.id, te1Ws11.id, te1Ws10.id, te2Ws10.id]
|
316
|
+
|
317
|
+
it "does not re-sort the results", ->
|
318
|
+
respondWith server, "/api/time_entries?order=created_at%3Adesc&per_page=2&page=1", data: { results: resultsArray("time_entries", [te2Ws11, te1Ws11]), time_entries: [te1Ws11, te2Ws11] }
|
319
|
+
# it's really created_at:asc
|
320
|
+
collection = base.data.loadCollection "time_entries", order: "created_at:desc", page: 1, perPage: 2
|
321
|
+
server.respond()
|
322
|
+
expect(collection.pluck("id")).toEqual [te2Ws11.id, te1Ws11.id]
|
323
|
+
|
324
|
+
it "seperately caches data requested by different sort orders and filters", ->
|
325
|
+
server.responses = []
|
326
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&order=updated_at%3Adesc&project_id=10&per_page=2&page=1",
|
327
|
+
data: { results: resultsArray("time_entries", [te2Ws10, te1Ws10]), time_entries: [te2Ws10, te1Ws10], tasks: [], projects: [ws10] }
|
328
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&order=updated_at%3Adesc&project_id=11&per_page=2&page=1",
|
329
|
+
data: { results: resultsArray("time_entries", [te1Ws11, te2Ws11]), time_entries: [te1Ws11, te2Ws11], tasks: [], projects: [ws11] }
|
330
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&order=created_at%3Aasc&project_id=11&per_page=2&page=1",
|
331
|
+
data: { results: resultsArray("time_entries", [te2Ws11, te1Ws11]), time_entries: [te2Ws11, te1Ws11], tasks: [], projects: [ws11] }
|
332
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&order=created_at%3Aasc&per_page=4&page=1",
|
333
|
+
data: { results: resultsArray("time_entries", [te2Ws11, te1Ws11, te1Ws10, te2Ws10]), time_entries: [te2Ws11, te1Ws11, te1Ws10, te2Ws10], tasks: [], projects: [ws10, ws11] }
|
334
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&per_page=4&page=1",
|
335
|
+
data: { results: resultsArray("time_entries", [te1Ws11, te2Ws10, te1Ws10, te2Ws11]), time_entries: [te1Ws11, te2Ws10, te1Ws10, te2Ws11], tasks: [], projects: [ws10, ws11] }
|
336
|
+
# Make a server request
|
337
|
+
collection1 = base.data.loadCollection "time_entries", include: ["project", "task"], order: "updated_at:desc", filters: { project_id: 10 }, page: 1, perPage: 2
|
338
|
+
expect(collection1.loaded).toBe false
|
339
|
+
server.respond()
|
340
|
+
expect(collection1.loaded).toBe true
|
341
|
+
expect(collection1.pluck("id")).toEqual [te2Ws10.id, te1Ws10.id] # Show that it came back in the explicit order setup above
|
342
|
+
# Make another request, this time handled by the cache.
|
343
|
+
collection2 = base.data.loadCollection "time_entries", include: ["project", "task"], order: "updated_at:desc", filters: { project_id: 10 }, page: 1, perPage: 2
|
344
|
+
expect(collection2.loaded).toBe true
|
345
|
+
|
346
|
+
# Do it again, this time with a different filter.
|
347
|
+
collection3 = base.data.loadCollection "time_entries", include: ["project", "task"], order: "updated_at:desc", filters: { project_id: 11 }, page: 1, perPage: 2
|
348
|
+
expect(collection3.loaded).toBe false
|
349
|
+
server.respond()
|
350
|
+
expect(collection3.loaded).toBe true
|
351
|
+
expect(collection3.pluck("id")).toEqual [te1Ws11.id, te2Ws11.id]
|
352
|
+
collection4 = base.data.loadCollection "time_entries", include: ["project"], order: "updated_at:desc", filters: { project_id: 11 }, page: 1, perPage: 2
|
353
|
+
expect(collection4.loaded).toBe true
|
354
|
+
expect(collection4.pluck("id")).toEqual [te1Ws11.id, te2Ws11.id]
|
355
|
+
|
356
|
+
# Do it again, this time with a different order.
|
357
|
+
collection5 = base.data.loadCollection "time_entries", include: ["project", "task"], order: "created_at:asc", filters: { project_id: 11 } , page: 1, perPage: 2
|
358
|
+
expect(collection5.loaded).toBe false
|
359
|
+
server.respond()
|
360
|
+
expect(collection5.loaded).toBe true
|
361
|
+
expect(collection5.pluck("id")).toEqual [te2Ws11.id, te1Ws11.id]
|
362
|
+
collection6 = base.data.loadCollection "time_entries", include: ["task"], order: "created_at:asc", filters: { project_id: 11 }, page: 1, perPage: 2
|
363
|
+
expect(collection6.loaded).toBe true
|
364
|
+
expect(collection6.pluck("id")).toEqual [te2Ws11.id, te1Ws11.id]
|
365
|
+
|
366
|
+
# Do it again, this time without a filter.
|
367
|
+
collection7 = base.data.loadCollection "time_entries", include: ["project", "task"], order: "created_at:asc", page: 1, perPage: 4
|
368
|
+
expect(collection7.loaded).toBe false
|
369
|
+
server.respond()
|
370
|
+
expect(collection7.loaded).toBe true
|
371
|
+
expect(collection7.pluck("id")).toEqual [te2Ws11.id, te1Ws11.id, te1Ws10.id, te2Ws10.id]
|
372
|
+
|
373
|
+
# Do it again, this time without an order, so it should use the default (updated_at:desc).
|
374
|
+
collection9 = base.data.loadCollection "time_entries", include: ["project", "task"], page: 1, perPage: 4
|
375
|
+
expect(collection9.loaded).toBe false
|
376
|
+
server.respond()
|
377
|
+
expect(collection9.loaded).toBe true
|
378
|
+
expect(collection9.pluck("id")).toEqual [te1Ws11.id, te2Ws10.id, te1Ws10.id, te2Ws11.id]
|
379
|
+
|
380
|
+
describe "handling of only", ->
|
381
|
+
describe "when getting data from the server", ->
|
382
|
+
it "returns the requested ids with includes, triggering reset and success", ->
|
383
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&only=2",
|
384
|
+
resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(task_id: null, project_id: 10, id: 2)], tasks: [], projects: [buildProject(id: 10)] }
|
385
|
+
spy2 = jasmine.createSpy().andCallFake (collection) ->
|
386
|
+
expect(collection.loaded).toBe true
|
387
|
+
collection = base.data.loadCollection "time_entries", include: ["project", "task"], only: 2, success: spy2
|
388
|
+
spy = jasmine.createSpy().andCallFake ->
|
389
|
+
expect(collection.loaded).toBe true
|
390
|
+
expect(collection.get(2).get('task')).toBeFalsy()
|
391
|
+
expect(collection.get(2).get('project').id).toEqual "10"
|
392
|
+
expect(collection.length).toEqual 1
|
393
|
+
collection.bind "reset", spy
|
394
|
+
expect(collection.loaded).toBe false
|
395
|
+
server.respond()
|
396
|
+
expect(collection.loaded).toBe true
|
397
|
+
expect(spy).toHaveBeenCalled()
|
398
|
+
expect(spy2).toHaveBeenCalled()
|
399
|
+
|
400
|
+
it "only requests ids that we don't already have", ->
|
401
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&only=2",
|
402
|
+
resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(task_id: null, project_id: 10, id: 2)], tasks: [], projects: [buildProject(id: 10)] }
|
403
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&only=3",
|
404
|
+
resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(task_id: null, project_id: 11, id: 3)], tasks: [], projects: [buildProject(id: 11)] }
|
405
|
+
|
406
|
+
collection = base.data.loadCollection "time_entries", include: ["project", "task"], only: 2
|
407
|
+
expect(collection.loaded).toBe false
|
408
|
+
server.respond()
|
409
|
+
expect(collection.loaded).toBe true
|
410
|
+
expect(collection.get(2).get('project').id).toEqual "10"
|
411
|
+
collection2 = base.data.loadCollection "time_entries", include: ["project", "task"], only: ["2", "3"]
|
412
|
+
expect(collection2.loaded).toBe false
|
413
|
+
server.respond()
|
414
|
+
expect(collection2.loaded).toBe true
|
415
|
+
expect(collection2.length).toEqual 2
|
416
|
+
expect(collection2.get(2).get('project').id).toEqual "10"
|
417
|
+
expect(collection2.get(3).get('project').id).toEqual "11"
|
418
|
+
|
419
|
+
it "does request ids from the server again when they don't have all associations loaded yet", ->
|
420
|
+
respondWith server, "/api/time_entries?include=project&only=2",
|
421
|
+
resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(project_id: 10, id: 2, task_id: 5)], projects: [buildProject(id: 10)] }
|
422
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&only=2",
|
423
|
+
resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(project_id: 10, id: 2, task_id: 5)], tasks: [buildTask(id: 5)], projects: [buildProject(id: 10)] }
|
424
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&only=3",
|
425
|
+
resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(project_id: 11, id: 3, task_id: null)], tasks: [], projects: [buildProject(id: 11)] }
|
426
|
+
|
427
|
+
base.data.loadCollection "time_entries", include: ["project"], only: 2
|
428
|
+
server.respond()
|
429
|
+
base.data.loadCollection "time_entries", include: ["project", "task"], only: 3
|
430
|
+
server.respond()
|
431
|
+
collection2 = base.data.loadCollection "time_entries", include: ["project", "task"], only: [2, 3]
|
432
|
+
expect(collection2.loaded).toBe false
|
433
|
+
server.respond()
|
434
|
+
expect(collection2.loaded).toBe true
|
435
|
+
expect(collection2.get(2).get('task').id).toEqual "5"
|
436
|
+
expect(collection2.length).toEqual 2
|
437
|
+
|
438
|
+
it "doesn't go to the server if it doesn't need to", ->
|
439
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&only=2%2C3",
|
440
|
+
resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(project_id: 10, id: 2, task_id: null), buildTimeEntry(project_id: 11, id: 3)], tasks: [], projects: [buildProject(id: 10), buildProject(id: 11)] }
|
441
|
+
collection = base.data.loadCollection "time_entries", include: ["project", "task"], only: [2, 3]
|
442
|
+
expect(collection.loaded).toBe false
|
443
|
+
server.respond()
|
444
|
+
expect(collection.loaded).toBe true
|
445
|
+
expect(collection.get(2).get('project').id).toEqual "10"
|
446
|
+
expect(collection.get(3).get('project').id).toEqual "11"
|
447
|
+
expect(collection.length).toEqual 2
|
448
|
+
spy = jasmine.createSpy()
|
449
|
+
collection2 = base.data.loadCollection "time_entries", include: ["project"], only: [2, 3], success: spy
|
450
|
+
expect(spy).toHaveBeenCalled()
|
451
|
+
expect(collection2.loaded).toBe true
|
452
|
+
expect(collection2.get(2).get('project').id).toEqual "10"
|
453
|
+
expect(collection2.get(3).get('project').id).toEqual "11"
|
454
|
+
expect(collection2.length).toEqual 2
|
455
|
+
|
456
|
+
it "returns an empty collection when passed in an empty array", ->
|
457
|
+
timeEntries = [buildTimeEntry(task_id: 2, project_id: 15, id: 1), buildTimeEntry(project_id: 10, id: 2)]
|
458
|
+
respondWith server, "/api/time_entries?per_page=20&page=1", resultsFrom: "time_entries", data: { time_entries: timeEntries }
|
459
|
+
collection = base.data.loadCollection "time_entries", only: []
|
460
|
+
expect(collection.loaded).toBe true
|
461
|
+
expect(collection.length).toEqual 0
|
462
|
+
|
463
|
+
collection = base.data.loadCollection "time_entries", only: null
|
464
|
+
server.respond()
|
465
|
+
expect(collection.loaded).toBe true
|
466
|
+
expect(collection.length).toEqual 2
|
467
|
+
|
468
|
+
it "accepts a success function that gets triggered on cache hit", ->
|
469
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&only=2%2C3",
|
470
|
+
resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(project_id: 10, id: 2, task_id: null), buildTimeEntry(project_id: 11, id: 3, task_id: null)], tasks: [], projects: [buildProject(id: 10), buildProject(id: 11)] }
|
471
|
+
base.data.loadCollection "time_entries", include: ["project", "task"], only: [2, 3]
|
472
|
+
server.respond()
|
473
|
+
spy = jasmine.createSpy().andCallFake (collection) ->
|
474
|
+
expect(collection.loaded).toBe true
|
475
|
+
expect(collection.get(2).get('project').id).toEqual "10"
|
476
|
+
expect(collection.get(3).get('project').id).toEqual "11"
|
477
|
+
collection2 = base.data.loadCollection "time_entries", include: ["project"], only: [2, 3], success: spy
|
478
|
+
expect(spy).toHaveBeenCalled()
|
479
|
+
|
480
|
+
it "does not cache only queries", ->
|
481
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&only=2%2C3",
|
482
|
+
resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(project_id: 10, id: 2, task_id: null), buildTimeEntry(project_id: 11, id: 3, task_id: null)], tasks: [], projects: [buildProject(id: 10), buildProject(id: 11)] }
|
483
|
+
collection = base.data.loadCollection "time_entries", include: ["project", "task"], only: [2, 3]
|
484
|
+
expect(Object.keys base.data.getCollectionDetails("time_entries")["cache"]).toEqual []
|
485
|
+
server.respond()
|
486
|
+
expect(Object.keys base.data.getCollectionDetails("time_entries")["cache"]).toEqual []
|
487
|
+
|
488
|
+
it "does go to the server on a repeat request if an association is missing", ->
|
489
|
+
respondWith server, "/api/time_entries?include=project&only=2",
|
490
|
+
resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(project_id: 10, id: 2, task_id: 6)], projects: [buildProject(id: 10)] }
|
491
|
+
respondWith server, "/api/time_entries?include=project%2Ctask&only=2",
|
492
|
+
resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(project_id: 10, id: 2, task_id: 6)], tasks: [buildTask(id: 6)], projects: [buildProject(id: 10)] }
|
493
|
+
collection = base.data.loadCollection "time_entries", include: ["project"], only: 2
|
494
|
+
expect(collection.loaded).toBe false
|
495
|
+
server.respond()
|
496
|
+
expect(collection.loaded).toBe true
|
497
|
+
collection2 = base.data.loadCollection "time_entries", include: ["project", "task"], only: 2
|
498
|
+
expect(collection2.loaded).toBe false
|
499
|
+
|
500
|
+
describe "disabling caching", ->
|
501
|
+
item = null
|
502
|
+
|
503
|
+
beforeEach ->
|
504
|
+
item = buildTask()
|
505
|
+
respondWith server, "/api/tasks.json?per_page=20&page=1", resultsFrom: "tasks", data: { tasks: [item] }
|
506
|
+
|
507
|
+
it "goes to server even if we have matching items in cache", ->
|
508
|
+
syncSpy = spyOn(Backbone, 'sync')
|
509
|
+
collection = base.data.loadCollection "tasks", cache: false, only: item.id
|
510
|
+
expect(syncSpy).toHaveBeenCalled()
|
511
|
+
|
512
|
+
it "still adds results to the cache", ->
|
513
|
+
spy = spyOn(base.data.storage('tasks'), 'update')
|
514
|
+
collection = base.data.loadCollection "tasks", cache: false
|
515
|
+
server.respond()
|
516
|
+
expect(spy).toHaveBeenCalled()
|
517
|
+
|
518
|
+
describe "searching", ->
|
519
|
+
it 'turns off caching', ->
|
520
|
+
spy = spyOn(base.data, '_loadCollectionWithFirstLayer')
|
521
|
+
collection = base.data.loadCollection "tasks", search: "the meaning of life"
|
522
|
+
expect(spy.mostRecentCall.args[0]['cache']).toBe(false)
|
523
|
+
|
524
|
+
it "returns the matching items with includes, triggering reset and success", ->
|
525
|
+
task = buildTask()
|
526
|
+
respondWith server, "/api/tasks.json?per_page=20&page=1&search=go+go+gadget+search",
|
527
|
+
data: { results: [{key: "tasks", id: task.id}], tasks: [task] }
|
528
|
+
spy2 = jasmine.createSpy().andCallFake (collection) ->
|
529
|
+
expect(collection.loaded).toBe true
|
530
|
+
collection = base.data.loadCollection "tasks", search: "go go gadget search", success: spy2
|
531
|
+
spy = jasmine.createSpy().andCallFake ->
|
532
|
+
expect(collection.loaded).toBe true
|
533
|
+
collection.bind "reset", spy
|
534
|
+
expect(collection.loaded).toBe false
|
535
|
+
server.respond()
|
536
|
+
expect(collection.loaded).toBe true
|
537
|
+
expect(spy).toHaveBeenCalled()
|
538
|
+
expect(spy2).toHaveBeenCalled()
|
539
|
+
|
540
|
+
it 'does not blow up when no results are returned', ->
|
541
|
+
respondWith server, "/api/tasks.json?per_page=20&page=1&search=go+go+gadget+search", data: { results: [], tasks: [] }
|
542
|
+
collection = base.data.loadCollection "tasks", search: "go go gadget search"
|
543
|
+
server.respond()
|
544
|
+
|
545
|
+
it 'acts as if no search options were passed if the search string is blank', ->
|
546
|
+
respondWith server, "/api/tasks.json?per_page=20&page=1", data: { results: [], tasks: [] }
|
547
|
+
collection = base.data.loadCollection "tasks", search: ""
|
548
|
+
server.respond()
|
549
|
+
|
550
|
+
describe "createNewCollection", ->
|
551
|
+
it "makes a new collection of the appropriate type", ->
|
552
|
+
expect(base.data.createNewCollection("tasks", [buildTask(), buildTask()]) instanceof App.Collections.Tasks).toBe true
|
553
|
+
|
554
|
+
it "can accept a 'loaded' flag", ->
|
555
|
+
collection = base.data.createNewCollection("tasks", [buildTask(), buildTask()])
|
556
|
+
expect(collection.loaded).toBe false
|
557
|
+
collection = base.data.createNewCollection("tasks", [buildTask(), buildTask()], loaded: true)
|
558
|
+
expect(collection.loaded).toBe true
|
559
|
+
|
560
|
+
describe "_wrapObjects", ->
|
561
|
+
it "wraps elements in an array with objects unless they are already objects", ->
|
562
|
+
expect(base.data._wrapObjects([])).toEqual []
|
563
|
+
expect(base.data._wrapObjects(['a', 'b'])).toEqual [{a: []}, {b: []}]
|
564
|
+
expect(base.data._wrapObjects(['a', 'b': []])).toEqual [{a: []}, {b: []}]
|
565
|
+
expect(base.data._wrapObjects(['a', 'b': 'c'])).toEqual [{a: []}, {b: [{c: []}]}]
|
566
|
+
expect(base.data._wrapObjects([{'a':[], b: 'c', d: 'e' }])).toEqual [{a: []}, {b: [{c: []}]}, {d: [{e: []}]}]
|
567
|
+
expect(base.data._wrapObjects(['a', { b: 'c', d: 'e' }])).toEqual [{a: []}, {b: [{c: []}]}, {d: [{e: []}]}]
|
568
|
+
expect(base.data._wrapObjects([{'a': []}, {'b': ['c', d: []]}])).toEqual [{a: []}, {b: [{c: []}, {d: []}]}]
|
569
|
+
|
570
|
+
describe "_countRequiredServerRequests", ->
|
571
|
+
it "should count the number of loads needed to get the date", ->
|
572
|
+
expect(base.data._countRequiredServerRequests(['a'])).toEqual 1
|
573
|
+
expect(base.data._countRequiredServerRequests(['a', 'b', 'c': []])).toEqual 1
|
574
|
+
expect(base.data._countRequiredServerRequests([{'a': ['d']}, 'b', 'c': ['e']])).toEqual 3
|
575
|
+
expect(base.data._countRequiredServerRequests([{'a': ['d']}, 'b', 'c': ['e': []]])).toEqual 3
|
576
|
+
expect(base.data._countRequiredServerRequests([{'a': ['d']}, 'b', 'c': ['e': ['f']]])).toEqual 4
|
577
|
+
expect(base.data._countRequiredServerRequests([{'a': ['d']}, 'b', 'c': ['e': ['f', 'g': ['h']]]])).toEqual 5
|
578
|
+
expect(base.data._countRequiredServerRequests([{'a': ['d': ['h']]}, { 'b':['g'] }, 'c': ['e': ['f', 'i']]])).toEqual 6
|
579
|
+
|
580
|
+
describe "error handling", ->
|
581
|
+
describe "setting a storage manager default error handler", ->
|
582
|
+
it "allows an error interceptor to be set on construction", ->
|
583
|
+
interceptor = (handler, modelOrCollection, options, jqXHR, requestParams) -> 5
|
584
|
+
manager = new Brainstem.StorageManager(errorInterceptor: interceptor)
|
585
|
+
expect(manager.errorInterceptor).toEqual interceptor
|
586
|
+
|
587
|
+
it "allows an error interceptor to be set later", ->
|
588
|
+
spy = jasmine.createSpy()
|
589
|
+
base.data.setErrorInterceptor (handler, modelOrCollection, options, jqXHR) -> spy(modelOrCollection, jqXHR)
|
590
|
+
server.respondWith "GET", "/api/time_entries?per_page=20&page=1", [ 401, {"Content-Type": "application/json"}, JSON.stringify({ errors: ["Invalid OAuth 2 Request"]}) ]
|
591
|
+
base.data.loadCollection 'time_entries'
|
592
|
+
server.respond()
|
593
|
+
expect(spy).toHaveBeenCalled()
|
594
|
+
|
595
|
+
describe "passing in a custom error handler when loading a collection", ->
|
596
|
+
it "gets called when there is an error", ->
|
597
|
+
customHandler = jasmine.createSpy('customHandler')
|
598
|
+
server.respondWith "GET", "/api/time_entries?per_page=20&page=1", [ 401, {"Content-Type": "application/json"}, JSON.stringify({ errors: ["Invalid OAuth 2 Request"]}) ]
|
599
|
+
base.data.loadCollection('time_entries', error: customHandler)
|
600
|
+
server.respond()
|
601
|
+
expect(customHandler).toHaveBeenCalled()
|
602
|
+
|
603
|
+
describe "when no storage manager error interceptor is given", ->
|
604
|
+
it "has a default error interceptor", ->
|
605
|
+
manager = new Brainstem.StorageManager()
|
606
|
+
expect(manager.errorInterceptor).not.toBeUndefined()
|
607
|
+
|
608
|
+
it "does nothing on unhandled errors", ->
|
609
|
+
spyOn(sinon, 'logError').andCallThrough()
|
610
|
+
server.respondWith "GET", "/api/time_entries?per_page=20&page=1", [ 401, {"Content-Type": "application/json"}, JSON.stringify({ errors: ["Invalid OAuth 2 Request"]}) ]
|
611
|
+
base.data.loadCollection 'time_entries'
|
612
|
+
server.respond()
|
613
|
+
expect(sinon.logError).not.toHaveBeenCalled()
|
Binary file
|