brainstem-js 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.gitignore +8 -0
  2. data/.pairs +21 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.tm_properties +1 -0
  6. data/.travis.yml +12 -0
  7. data/Assetfile +79 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +50 -0
  10. data/LICENSE.txt +22 -0
  11. data/README.md +143 -0
  12. data/Rakefile +25 -0
  13. data/brainstemjs.gemspec +24 -0
  14. data/lib/brainstem/js/engine.rb +5 -0
  15. data/lib/brainstem/js/version.rb +5 -0
  16. data/lib/brainstem/js.rb +10 -0
  17. data/spec/brainstem-collection-spec.coffee +141 -0
  18. data/spec/brainstem-model-spec.coffee +283 -0
  19. data/spec/brainstem-sync-spec.coffee +22 -0
  20. data/spec/brainstem-utils-spec.coffee +12 -0
  21. data/spec/brianstem-expectation-spec.coffee +209 -0
  22. data/spec/helpers/builders.coffee +80 -0
  23. data/spec/helpers/jquery-matchers.js +137 -0
  24. data/spec/helpers/models/post.coffee +14 -0
  25. data/spec/helpers/models/project.coffee +13 -0
  26. data/spec/helpers/models/task.coffee +14 -0
  27. data/spec/helpers/models/time-entry.coffee +13 -0
  28. data/spec/helpers/models/user.coffee +8 -0
  29. data/spec/helpers/spec-helper.coffee +79 -0
  30. data/spec/storage-manager-spec.coffee +613 -0
  31. data/spec/support/.DS_Store +0 -0
  32. data/spec/support/console-runner.js +103 -0
  33. data/spec/support/headless.coffee +47 -0
  34. data/spec/support/headless.html +60 -0
  35. data/spec/support/runner.html +85 -0
  36. data/spec/vendor/backbone-factory.js +62 -0
  37. data/spec/vendor/backbone.js +1571 -0
  38. data/spec/vendor/inflection.js +448 -0
  39. data/spec/vendor/jquery-1.7.js +9300 -0
  40. data/spec/vendor/jquery.cookie.js +47 -0
  41. data/spec/vendor/minispade.js +67 -0
  42. data/spec/vendor/sinon-1.3.4.js +3561 -0
  43. data/spec/vendor/underscore.js +1221 -0
  44. data/vendor/assets/.DS_Store +0 -0
  45. data/vendor/assets/javascripts/.DS_Store +0 -0
  46. data/vendor/assets/javascripts/brainstem/brainstem-collection.coffee +53 -0
  47. data/vendor/assets/javascripts/brainstem/brainstem-expectation.coffee +65 -0
  48. data/vendor/assets/javascripts/brainstem/brainstem-inflections.js +449 -0
  49. data/vendor/assets/javascripts/brainstem/brainstem-model.coffee +141 -0
  50. data/vendor/assets/javascripts/brainstem/brainstem-sync.coffee +76 -0
  51. data/vendor/assets/javascripts/brainstem/index.js +1 -0
  52. data/vendor/assets/javascripts/brainstem/iso8601.js +41 -0
  53. data/vendor/assets/javascripts/brainstem/loading-mixin.coffee +13 -0
  54. data/vendor/assets/javascripts/brainstem/storage-manager.coffee +275 -0
  55. data/vendor/assets/javascripts/brainstem/utils.coffee +35 -0
  56. metadata +198 -0
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /bin/
3
+ /build/
4
+ /dist/
5
+ /tmp/
6
+ /.idea/
7
+ projectFilesBackup
8
+ pkg
data/.pairs ADDED
@@ -0,0 +1,21 @@
1
+ pairs:
2
+ rn: Roger Neel
3
+ ac: Andrew Cantino; andrew
4
+ jm: Jeff Moore
5
+ cad: Chris Alvarado-Dryden
6
+ ml: Matthew LaRocca
7
+ sa: Sufyan Adam
8
+ bb: Bobby Brown
9
+ al: Andrew Langdon
10
+ kd: Katlyn Daniluk
11
+ jc: Juan-Carlos Medina
12
+ rg: Reid Gillette
13
+ aj: Alec Jacobs
14
+ mc: Michael Chen
15
+ sc: Sean Crafts
16
+ cm: Caleb Mingle
17
+ aa: André Arko
18
+ email:
19
+ prefix: pair
20
+ domain: mavenlink.com
21
+ global: true
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ brainstem-js
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-1.9.3-p392
data/.tm_properties ADDED
@@ -0,0 +1 @@
1
+ exclude = {$exclude,bin,build,dist,tmp,.tm_properties}
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm: 1.9.3
3
+
4
+ before_install: gem install bundler
5
+
6
+ notifications:
7
+ hipchat: 09a2719b3284ea0d0d247355fa5542@Mavenlink Dev
8
+
9
+ git:
10
+ depth: 1
11
+
12
+ source_key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBOHBpRjdldDZKM2FmWCt0WDlWRG5HZEt4dDdZZVJVVUh3K29JQVJDekorejBvS3MxCnNwaEUxZlBSQksyUDFMUVV5cngyUThzalgxdnA1NUt6U3lnVWExMFRlUG90VnVWN3hZbVFqTG5FM2FueTJBNXIKMFhXZG4wS2k2cUt5TkVwd2ZJa3JYam8xSmY4SHJ0ajZ5Yi9VTFcySnZxbTBGTTZRNmU1TCtmUGdQN0JqeS9WSApUWHF4UWVRaTVaSVppa2JoeGxoK3RQNVZaM1JMRTF3bEhRdXpESUFZemRFdnFXeEFxYkhvNktyMjJSZzRhaGNPCnNEZ1FGK0drR0h2bjBKTkpmc05Cd3d2YUQ2Y3M5N2pnY045b1VrK1phOFNlbFZhazRjWVhRUk5WaTdPMVdkQnkKSE1xaVNPVjFZdEhSWWdwSFpPaHdYSUdxK3ZhUXVFR21SRzdBY1FJREFRQUJBb0lCQVFDOXBiQ2xjdjFXbG13MwpEd0wrK3RUL0llL2VmeGVnN1RzSjFBMlh6NWRPc2ZYM0dJRHM4ZzUxOTVuQi8zQytSbDB1dEMvOEJYVE1ta3o3CnhIbzNXY2pFdWNsOFBJOXZMQTBiT3RSdXZ0Y0F0bGZxd1ROV1ZvejNNSit0cjZ5Q0ptTlRaK1FvVUhhMkVtM08KS1QrOHNpTEx0S01IRXlGOVZwS0EzZEkxUDRwaUhodlFBdkgzQkdRR3piNW9UczV6RXJnRmZNbEZKWFc0S2NxcApYc1NnWDBjd3d3Nit3WDFLblRGY3lNak9QbTB2MWZSSTdHMGdtOG02VVJrakdVeXhpRXhkTVNEdm5yVEt4ajlvCitIK1NucU14RW5uczJRR1lnSEtnV1ZaSFFBUU9UenBNSVdvNndHODNMMDlyOUo3S1dzZHRwNStKeDNZWWE2eTMKU0tod3NQdGxBb0dCQVAvZTJ2SitCVmpkRXVOSjJibWo4YnlGK2lJd292Vm9FYW4xUExkZ3JJbTNQWkFrLytyMgpiSFV0d0xDVjBSR1l5Z1ZqaW42YmEzUitNM2ptanErM3Z1N0FsVlJNazdvcFVXcjUwanRzbDhhT1k4WkIxTlhuClRlRW5keEswTnBWaWs1VlFhNzBqcmh3NGtmekxhUk9xdUV1SGt6Qk95MXhJeStyUkNMNDMzT1ZmQW9HQkFQSzMKOHNXYm5YRzR0WjdHMU5QVjZ0YXJ5bEt4bFluRnhseG4rUmZueTFHbG1NejU4YW1Jb1F2SnJuSHlSR29xQ2dNOQo4eWJFMmVoVjJFM20wKzUwampDeFNrMjhxbkNybDdnbTdNcjhsVzdGQnZLSUw2NDFsM1pDT3pLSnBKWFdGN1BaCmdYVGplTmduRzliQkljMldBNHpzaGVDK05CRWxidmFkekRrT0hkd3ZBb0dBWkRlZ1lCdzE4ZkZkQllNV2NSeWkKZ1Nta3FDR09vam9wdVB6aDFCMWNWdkJiZjRyT1pmUXcxTkNmeVVwVXdlU3JNK01pQ3FiTE5xeDdjcDR6UXVYZwpOZGxlWTg4K2lVckhwZlBGZ1JydWM0bXYwS1pXTzVYR0xpcnIrM3AwYXB4YW04QU5BdDdud2d2eU9pWmR1S05FClhlanpJSmVzRlRBNkZuWGJTODNMaWxjQ2dZRUExai9kd3VUOGM3ZnlTZmVGUW9DZnpXTFRNMitpYW56ei9mbWgKZmFLVWJMdmFSNFdSOW02dWlmTTdVMFhoY2owdG5YTC93WWNlT3VJY0Q1ZmtGNmMzSkhBN0FLZTdZNzEwTFkvZQprY2VvT0tFZTR0T29Fd1VuYjdKREF2ZFJHeHBpemRUL1d5aTRNVVZFWTZzVHBaLzMvbHVDU2NKYnY0N2xoamdBClg1VEFjdTBDZ1lCRFhnNUF2VHVXTE5pRk91RW5RekhrRmp0N0lwRVVUN2lpZDByMHBJempMVmc1c08zSXRqTkQKNjlvWitIYUlCSVpMWkNQc2lpS2dmQkFIS0xXY3NEQTRnelY2ckpUWDdmdXYvcHZUTk9tdmJlcG5OK0JGd05xdApVU2c5UmxFZ0lFWEE3Q041aVFvb2l2SmFiT3VEREl0NUhiTS9Gd1M0V3QvQVdnQUZkYmpab3c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
data/Assetfile ADDED
@@ -0,0 +1,79 @@
1
+ require 'rake-pipeline-web-filters'
2
+ require 'jasmine-core'
3
+
4
+ output "build"
5
+
6
+ input "vendor/assets/javascripts/brainstem" do
7
+ match "*.coffee" do
8
+ coffee_script
9
+ end
10
+
11
+ match "*.js" do
12
+ minispade :rewrite_requires => true, :module_id_generator => proc { |input|
13
+ input.path.sub(/\.js$/, '')
14
+ }, :string => true
15
+ concat "brainstem.js"
16
+ end
17
+ end
18
+
19
+ input "spec" do
20
+ match "**/*.coffee" do
21
+ coffee_script
22
+ end
23
+
24
+ match "**/*.js" do
25
+ reject "support/**/*.js"
26
+ reject "vendor/**/*.js"
27
+ reject "helpers/**/*.js"
28
+ minispade :rewrite_requires => true, :module_id_generator => proc { |input|
29
+ input.path.sub(/\.js$/, '')
30
+ }, :string => true
31
+ concat "specs.js"
32
+ end
33
+ end
34
+
35
+ input "spec/support" do
36
+ match "runner.html" do
37
+ concat "index.html"
38
+ end
39
+
40
+ match "headless.html" do
41
+ concat
42
+ end
43
+
44
+ match "**/*.coffee" do
45
+ coffee_script
46
+ end
47
+
48
+ match "**/*.js" do
49
+ concat
50
+ end
51
+ end
52
+
53
+ input "spec/vendor" do
54
+ output "build/vendor"
55
+ match "*.js" do
56
+ concat
57
+ end
58
+ end
59
+
60
+ input "spec/helpers" do
61
+
62
+ match "**/*.coffee" do
63
+ coffee_script
64
+ end
65
+
66
+ match "*.js" do
67
+ minispade :rewrite_requires => true, :module_id_generator => proc { |input|
68
+ input.path.sub(/\.js$/, '')
69
+ }, :string => true
70
+ concat "helpers.js"
71
+ end
72
+ end
73
+
74
+ input Jasmine::Core.path do
75
+ output "build/jasmine"
76
+ match /^[^\/]+\.(js|css)$/ do
77
+ concat
78
+ end
79
+ end
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake-pipeline', :github => 'livingsocial/rake-pipeline', :branch => 'master'
6
+ gem 'rake-pipeline-web-filters', :github => 'wycats/rake-pipeline-web-filters', :branch => 'master'
data/Gemfile.lock ADDED
@@ -0,0 +1,50 @@
1
+ GIT
2
+ remote: git://github.com/livingsocial/rake-pipeline.git
3
+ revision: 27718232227eddd029466b2968f0880c10a18fb4
4
+ branch: master
5
+ specs:
6
+ rake-pipeline (0.8.0)
7
+ json
8
+ rake (~> 10.0.0)
9
+ thor
10
+
11
+ GIT
12
+ remote: git://github.com/wycats/rake-pipeline-web-filters.git
13
+ revision: d83f54cc0d4e53f9833c31851964e3d8ba00f66a
14
+ branch: master
15
+ specs:
16
+ rake-pipeline-web-filters (0.6.0)
17
+ rack
18
+ rake-pipeline (~> 0.6)
19
+
20
+ PATH
21
+ remote: .
22
+ specs:
23
+ brainstem-js (0.2.1)
24
+
25
+ GEM
26
+ remote: https://rubygems.org/
27
+ specs:
28
+ coffee-script (2.2.0)
29
+ coffee-script-source
30
+ execjs
31
+ coffee-script-source (1.4.0)
32
+ execjs (1.4.0)
33
+ multi_json (~> 1.0)
34
+ jasmine-core (1.3.1)
35
+ json (1.7.6)
36
+ multi_json (1.5.0)
37
+ rack (1.5.0)
38
+ rake (10.0.3)
39
+ thor (0.16.0)
40
+
41
+ PLATFORMS
42
+ ruby
43
+
44
+ DEPENDENCIES
45
+ brainstem-js!
46
+ bundler (~> 1.2)
47
+ coffee-script (~> 2.2)
48
+ jasmine-core (~> 1.3.1)
49
+ rake-pipeline!
50
+ rake-pipeline-web-filters!
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Mavenlink
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Brainstem.js
2
+
3
+ [Brainstem](https://github.com/mavenlink/brainstem) is designed to power rich APIs in Rails. The Brainstem gem provides a presenter library that handles converting ActiveRecord objects into structured JSON and a set of API abstractions that allow users to request sorts, filters, and association loads, allowing for simpler implementations, fewer requests, and smaller responses.
4
+
5
+ The Brainstem.js library is a companion library for Backbone.js that makes integration with Brainstem APIs a breeze. Brainstem.js adds an identity map and relational models to Backbone.
6
+
7
+ ## Why Brainstem.js?
8
+
9
+ * Speaks natively to Brainstem APIs
10
+ * Adds relational models in Backbone, allowing you to setup has_one, has_many, and belongs_to relationships.
11
+ * Provides an Identity Map to avoid loading already-available records.
12
+ * Supports Brainstem side-loading of multiple objects for fast, single-request workflows.
13
+ * Interprets the Brainstem results array and hashes for you, abstracting away the JSON protocol.
14
+ * Written in CoffeeScript.
15
+
16
+ ## Usage
17
+
18
+ If you're in Rails, just add `brainstem-js` to your Gemfile and require `brainstem` in `application.js`.
19
+
20
+ We have a [comprehensive demo available](https://github.com/mavenlink/brainstem-demo-rails). We recommend that you start there.
21
+
22
+ What follows is an overview.
23
+
24
+ ### StorageManager
25
+
26
+ The `Brianstem.StorageManager` is in charge of loading data over the API, as well as returning already cached data. We recommend setting one up in a singleton App class.
27
+
28
+ class Application
29
+ constructor: ->
30
+ @data = new Brainstem.StorageManager()
31
+ @data.addCollection 'widgets', Collections.Widgets
32
+ @data.addCollection 'locations', Collections.Locations
33
+ @data.addCollection 'features', Collections.Features
34
+ @homeRouter = new Routers.WidgetsRouter()
35
+
36
+ $ ->
37
+ window.base = new Application()
38
+ Backbone.history.start(root: "/")
39
+
40
+ At the moment, the global StorageManager *must* be available at `window.base.data`. For this reason, we recommend making a singleton instance of an `Application` class for holding your `StorageManager` and any other shared resources.
41
+
42
+ ### Brainstem.Models and Brainstem.Collections
43
+
44
+ Once you have a StorageManager, you should setup some `Brainstem.Models` and `Brainstem.Collections`:
45
+
46
+ window.Models ?= {}
47
+ window.Collections ?= {}
48
+
49
+ class Models.Widget extends Brainstem.Model
50
+ paramRoot: 'widget'
51
+ brainstemKey: 'widgets'
52
+ urlRoot: '/api/v1/widgets'
53
+
54
+ @associations:
55
+ features: ["features"]
56
+ location: "locations"
57
+
58
+ class Collections.Widgets extends Brainstem.Collection
59
+ model: Models.Widget
60
+ url: '/api/v1/widgets'
61
+
62
+ class Models.Feature extends Brainstem.Model
63
+ paramRoot: 'feature'
64
+ brainstemKey: 'features'
65
+ urlRoot: '/api/v1/features'
66
+
67
+ @associations:
68
+ widgets: ["widgets"]
69
+
70
+ class Collections.Features extends Brainstem.Collection
71
+ model: Models.Feature
72
+ url: '/api/v1/features'
73
+
74
+ Use the `@associations` class method to declare the mapping between association names and `StorageManager` collections where the data can be located. Arrays indicate has_many relationships. Other than a few additions, these are just Backbone Models and Collections.
75
+
76
+ ### Backbone.Views
77
+
78
+ Now that you have models, collections, and a `StorageManager`, it's time to load some data (probably in Backbone.Views). For example:
79
+
80
+ class Views.Widgets.IndexView extends Backbone.View
81
+ template: JST["backbone/templates/widgets/index"]
82
+
83
+ initialize: ->
84
+ @collection = base.data.loadCollection "widgets", include: ["location", "features"], order: 'updated_at:desc'
85
+ @collection.bind 'reset', @addAll
86
+ @collection.bind 'remove', @addAll
87
+
88
+ render: =>
89
+ @$el.html @template()
90
+
91
+ if @collection.loaded
92
+ @addAll()
93
+ else
94
+ @$("#widgets-list").text "Just a moment..."
95
+
96
+ return this
97
+
98
+ addAll: =>
99
+ @$("#widgets-list").empty()
100
+ @collection.each(@addOne)
101
+ @addLocations()
102
+
103
+ addOne: (model) =>
104
+ view = new Views.Widgets.WidgetView(model: model)
105
+ @$("#widgets-list").append view.render().el
106
+
107
+ And finally, in your templates, you can access the relational data just like you'd normally access model data in Backbone.
108
+
109
+ @model.get('location').get('name')
110
+
111
+ for feature in @model.get('features').models:
112
+
113
+ Etc.
114
+
115
+ ## Development
116
+
117
+ We're always open to pull requests!
118
+
119
+ ### Dependencies
120
+
121
+ - PhantomJS. If you're on OS X, run `brew install phantomjs`.
122
+
123
+ ### Running Specs
124
+
125
+ To run the specs on the command line, run:
126
+
127
+ bundle exec rake
128
+
129
+ To run the specs in a server with live code reloading and compilation:
130
+
131
+ bundle exec rake server
132
+
133
+ To develop your application against a local checkout of brainstem-js, we suggest using Bundler's local gems:
134
+
135
+ bundle config local.brainstem-js ~/workspace/brainstem-js
136
+
137
+ And when you're done, run:
138
+
139
+ bundle config --delete local.brainstem-js
140
+
141
+ # License
142
+
143
+ Brainstem and Brainstem.js were created by Mavenlink, Inc. and are available under the MIT License.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ task :clean do
5
+ sh "rakep clean"
6
+ end
7
+
8
+ task :build do
9
+ sh "rakep build"
10
+ end
11
+
12
+ task :spec => :build do
13
+ begin
14
+ exec *%w(phantomjs build/headless.js build/headless.html)
15
+ rescue Errno::ENOENT => e
16
+ puts "Couldn't find phantomjs."
17
+ abort "You didn't run `brew install phantomjs`, did you?"
18
+ end
19
+ end
20
+
21
+ task :server => :clean do
22
+ exec "rakep server"
23
+ end
24
+
25
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'brainstem/js/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "brainstem-js"
8
+ gem.version = Brainstem::Js::VERSION
9
+ gem.authors = ["Mavenlink"]
10
+ gem.email = ["opensource@mavenlink.com"]
11
+ gem.description = %q{The Brainstem API adapter library for Backbone.js}
12
+ gem.summary = %q{Easily connect Backbone.js with Brainstem APIs. Get relational models in Backbone.}
13
+ gem.homepage = "http://github.com/mavenlink/brainstem-js"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency 'bundler', '~> 1.2'
21
+ gem.add_development_dependency 'coffee-script', '~> 2.2'
22
+ gem.add_development_dependency 'jasmine-core', '~> 1.3.1'
23
+ gem.add_development_dependency 'rake-pipeline-web-filters', '~> 0.6.0'
24
+ end
@@ -0,0 +1,5 @@
1
+ module Brainstemjs
2
+ class Rails < ::Rails::Engine
3
+ # So Rails will add vendor to the asset pipeline
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Brainstem
2
+ module Js
3
+ VERSION = "0.2.1"
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require "brainstem/js/version"
2
+ require "brainstem/js/engine" if defined?(::Rails::Engine)
3
+
4
+ module Brainstem
5
+ module Js
6
+ def self.path
7
+ File.expand_path("../../../vendor/assets/javascripts/brainstem", __FILE__)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,141 @@
1
+ describe 'Brainstem.Collection', ->
2
+ collection = updateArray = null
3
+
4
+ beforeEach ->
5
+ collection = new Brainstem.Collection([{id: 2, title: "1"}, {id: 3, title: "2"}, {title: "3"}])
6
+ updateArray = [{id: 2, title: "1 new"}, {id: 4, title: "this is new"}]
7
+
8
+ describe 'update', ->
9
+ it "works with an array", ->
10
+ collection.update updateArray
11
+ expect(collection.get(2).get('title')).toEqual "1 new"
12
+ expect(collection.get(3).get('title')).toEqual "2"
13
+ expect(collection.get(4).get('title')).toEqual "this is new"
14
+
15
+ it "works with a collection", ->
16
+ newCollection = new Brainstem.Collection(updateArray)
17
+ collection.update newCollection
18
+ expect(collection.get(2).get('title')).toEqual "1 new"
19
+ expect(collection.get(3).get('title')).toEqual "2"
20
+ expect(collection.get(4).get('title')).toEqual "this is new"
21
+
22
+ it "should update copies of the model that are already in the collection", ->
23
+ model = collection.get(2)
24
+ spy = jasmine.createSpy()
25
+ model.bind "change:title", spy
26
+ collection.update updateArray
27
+ expect(model.get('title')).toEqual "1 new"
28
+ expect(spy).toHaveBeenCalled()
29
+
30
+ describe "loadNextPage", ->
31
+ it "loads the next page of data for a collection that has previously been loaded in the storage manager, returns the collection and whether it thinks there is another page or not", ->
32
+ respondWith server, "/api/time_entries?per_page=2&page=1", resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(), buildTimeEntry()] }
33
+ respondWith server, "/api/time_entries?per_page=2&page=2", resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry(), buildTimeEntry()] }
34
+ respondWith server, "/api/time_entries?per_page=2&page=3", resultsFrom: "time_entries", data: { time_entries: [buildTimeEntry()] }
35
+ collection = base.data.loadCollection "time_entries", perPage: 2
36
+ expect(collection.length).toEqual 0
37
+ server.respond()
38
+ expect(collection.length).toEqual 2
39
+ expect(collection.lastFetchOptions.page).toEqual 1
40
+
41
+ spy = jasmine.createSpy()
42
+ collection.loadNextPage success: spy
43
+ server.respond()
44
+ expect(spy).toHaveBeenCalledWith(collection, true)
45
+ expect(collection.lastFetchOptions.page).toEqual 2
46
+ expect(collection.length).toEqual 4
47
+
48
+ spy = jasmine.createSpy()
49
+ collection.loadNextPage success: spy
50
+ expect(collection.length).toEqual 4
51
+ server.respond()
52
+ expect(spy).toHaveBeenCalledWith(collection, false)
53
+ expect(collection.lastFetchOptions.page).toEqual 3
54
+ expect(collection.length).toEqual 5
55
+
56
+ describe "reload", ->
57
+ it "reloads the collection with the original params", ->
58
+ respondWith server, "/api/posts?include=replies&parents_only=true&per_page=5&page=1", resultsFrom: "posts", data: { posts: [buildPost(message: "old post", reply_ids: [])] }
59
+ collection = base.data.loadCollection "posts", include: ["replies"], filters: { parents_only: "true" }, perPage: 5
60
+ server.respond()
61
+ expect(collection.lastFetchOptions.page).toEqual 1
62
+ expect(collection.lastFetchOptions.perPage).toEqual 5
63
+ expect(collection.lastFetchOptions.include).toEqual ["replies"]
64
+ server.responses = []
65
+ respondWith server, "/api/posts?include=replies&parents_only=true&per_page=5&page=1", resultsFrom: "posts", data: { posts: [buildPost(message: "new post", reply_ids: [])] }
66
+ expect(collection.models[0].get("message")).toEqual "old post"
67
+ resetCounter = jasmine.createSpy("resetCounter")
68
+ loadedCounter = jasmine.createSpy("loadedCounter")
69
+ callback = jasmine.createSpy("callback spy")
70
+ collection.bind "reset", resetCounter
71
+ collection.bind "loaded", loadedCounter
72
+
73
+ collection.reload success: callback
74
+
75
+ expect(collection.loaded).toBe false
76
+ expect(collection.length).toEqual 0
77
+ server.respond()
78
+ expect(collection.length).toEqual 1
79
+ expect(collection.models[0].get("message")).toEqual "new post"
80
+ expect(resetCounter.callCount).toEqual 1
81
+ expect(loadedCounter.callCount).toEqual 1
82
+ expect(callback).toHaveBeenCalledWith(collection)
83
+
84
+ describe "getWithAssocation", ->
85
+ it "defaults to the regular get", ->
86
+ spyOn(collection, 'get')
87
+ collection.getWithAssocation(10)
88
+ expect(collection.get).toHaveBeenCalledWith(10)
89
+
90
+ describe 'setLoaded', ->
91
+ it "should set the values of @loaded", ->
92
+ collection.setLoaded true
93
+ expect(collection.loaded).toEqual(true)
94
+ collection.setLoaded false
95
+ expect(collection.loaded).toEqual(false)
96
+
97
+ it "triggers 'loaded' when becoming true", ->
98
+ spy = jasmine.createSpy()
99
+ collection.bind "loaded", spy
100
+ collection.setLoaded false
101
+ expect(spy).not.toHaveBeenCalled()
102
+ collection.setLoaded true
103
+ expect(spy).toHaveBeenCalled()
104
+
105
+ it "doesn't trigger loaded if trigger: false is provided", ->
106
+ spy = jasmine.createSpy()
107
+ collection.bind "loaded", spy
108
+ collection.setLoaded true, trigger: false
109
+ expect(spy).not.toHaveBeenCalled()
110
+
111
+ it "returns self", ->
112
+ spy = jasmine.createSpy()
113
+ collection.bind "loaded", spy
114
+ collection.setLoaded true
115
+ expect(spy).toHaveBeenCalledWith(collection)
116
+
117
+ describe "toServerJSON", ->
118
+ it "calls through to toJSON", ->
119
+ spy = spyOn(collection, 'toJSON')
120
+ collection.toServerJSON()
121
+ expect(spy).toHaveBeenCalled()
122
+
123
+ describe "ordering and filtering", ->
124
+ beforeEach ->
125
+ collection = new Brainstem.Collection([
126
+ new Brainstem.Model(id: 2, title: "Alpha", updated_at: 2, cool: false),
127
+ new Brainstem.Model(id: 3, title: "Beta", updated_at: 10, cool: true),
128
+ new Brainstem.Model(id: 4, title: "Gamma", updated_at: 5, cool: false),
129
+ new Brainstem.Model(id: 6, title: "Gamma", updated_at: 5, cool: false),
130
+ new Brainstem.Model(id: 5, title: "Gamma", updated_at: 4, cool: true)
131
+ ])
132
+
133
+ describe "@getComparatorWithIdFailover", ->
134
+ it "returns a comparator that works for numerical ordering of unix timestamps, failing over to id when they're the same", ->
135
+ newCollection = new Brainstem.Collection collection.models, comparator: Brainstem.Collection.getComparatorWithIdFailover("updated_at:desc")
136
+ newCollection.sort()
137
+ expect(newCollection.pluck("id")).toEqual [3, 6, 4, 5, 2]
138
+
139
+ newCollection = new Brainstem.Collection collection.models, comparator: Brainstem.Collection.getComparatorWithIdFailover("updated_at:asc")
140
+ newCollection.sort()
141
+ expect(newCollection.pluck("id")).toEqual [2, 5, 4, 6, 3]