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,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
|