emerson 0.1.0.pre.1 → 0.1.0.pre.2
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 +17 -23
- data/.wiprc +0 -0
- data/.yardopts +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +177 -0
- data/Rakefile +18 -1
- data/emerson.gemspec +28 -15
- data/lib/emerson.rb +58 -1
- data/lib/emerson/engine.rb +5 -0
- data/lib/emerson/matchers.rb +5 -0
- data/lib/emerson/matchers/action_controller.rb +30 -0
- data/lib/emerson/matchers/action_controller/send_json_matcher.rb +106 -0
- data/lib/emerson/matchers/integrations/rspec.rb +11 -0
- data/lib/emerson/matchers/integrations/test_unit.rb +12 -0
- data/lib/emerson/responder.rb +61 -51
- data/lib/emerson/response.rb +17 -3
- data/lib/emerson/scope.rb +2 -7
- data/lib/emerson/version.rb +1 -1
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +65 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/jasmine.rb +3 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +6 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/spec/support/fixtures/responses/products/extend-array.json +1 -0
- data/spec/dummy/spec/support/fixtures/responses/products/extend-failure.json +1 -0
- data/spec/dummy/spec/support/fixtures/responses/products/extend-success.json +4 -0
- data/spec/dummy/spec/support/fixtures/responses/products/simple.json +4 -0
- data/spec/emerson/matchers/action_controller/send_json_matcher_spec.rb +126 -0
- data/spec/emerson/responder_spec.rb +222 -0
- data/spec/emerson/response_spec.rb +75 -0
- data/spec/emerson/scope_spec.rb +146 -0
- data/spec/emerson_spec.rb +105 -0
- data/spec/spec_helper.rb +67 -0
- data/spec/support/helpers/controller_helpers.rb +98 -0
- data/spec/support/helpers/feature_helpers.rb +12 -0
- data/spec/support/helpers/resource_helpers.rb +95 -0
- data/spec/support/helpers/template_helpers.rb +31 -0
- data/vendor/assets/javascripts/emerson.js +1 -0
- data/vendor/assets/javascripts/emerson/sink.js +3 -0
- metadata +309 -11
- data/lib/emerson/rails.rb +0 -6
- data/lib/emerson/rails/engine.rb +0 -7
@@ -0,0 +1,146 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Emerson::Scope, :type => :controller do
|
4
|
+
render_views
|
5
|
+
let!(:products) { resources(:product, 2) }
|
6
|
+
|
7
|
+
controller(:products)
|
8
|
+
|
9
|
+
before do
|
10
|
+
templates do
|
11
|
+
def index
|
12
|
+
<<-ERB
|
13
|
+
<ul>
|
14
|
+
<% products.each do |product| %>
|
15
|
+
<li><%= product.name %></li>
|
16
|
+
<% end %>
|
17
|
+
</ul>
|
18
|
+
ERB
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe ".scope" do
|
24
|
+
context "called without a 'resources' argument" do
|
25
|
+
it "raises an exception" do
|
26
|
+
expect {
|
27
|
+
controller_class.class_eval do
|
28
|
+
scope :by => :user
|
29
|
+
end
|
30
|
+
}.to raise_error(ArgumentError)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "called without the :by requirement" do
|
35
|
+
it "raises an exception" do
|
36
|
+
expect {
|
37
|
+
controller_class.class_eval do
|
38
|
+
scope :products
|
39
|
+
end
|
40
|
+
}.to raise_error(ArgumentError)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#current_scope" do
|
46
|
+
let(:user) { resource(:user) }
|
47
|
+
|
48
|
+
before do
|
49
|
+
user.products = [products.first]
|
50
|
+
|
51
|
+
controller do
|
52
|
+
scope :products, :by => :user
|
53
|
+
|
54
|
+
def index
|
55
|
+
render(:json => { :user => current_scope.id })
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it "returns the record representing the current scope" do
|
61
|
+
get(:index, :user_id => user.id)
|
62
|
+
expect(response).to send_json({
|
63
|
+
:user => user.id
|
64
|
+
})
|
65
|
+
end
|
66
|
+
|
67
|
+
context "detailed scope configurations and environments" do
|
68
|
+
it "is pending" do
|
69
|
+
pending "TODO"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#scoped" do
|
75
|
+
context "without a scope configuration" do
|
76
|
+
before do
|
77
|
+
controller do
|
78
|
+
def index
|
79
|
+
respond_with(scoped)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "selects the resource using the default scope, from the controller name" do
|
85
|
+
get(:index)
|
86
|
+
expect(response.body).to have_css('ul > li', :text => products[0].name)
|
87
|
+
expect(response.body).to have_css('ul > li', :text => products[1].name)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "with a scope configuration" do
|
92
|
+
let(:user) { resource(:user) }
|
93
|
+
|
94
|
+
before do
|
95
|
+
user.products = [products.first]
|
96
|
+
|
97
|
+
controller do
|
98
|
+
scope :products, :by => :user
|
99
|
+
|
100
|
+
def index
|
101
|
+
respond_with(scoped)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "given no scoping environment" do
|
107
|
+
it "selects the resource using the default scope, from configuration" do
|
108
|
+
get(:index)
|
109
|
+
expect(response.body).to have_css('ul > li', :text => products[0].name)
|
110
|
+
expect(response.body).to have_css('ul > li', :text => products[1].name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "given a scoping environment based on request params" do
|
115
|
+
it "selects the resource using the provided scope" do
|
116
|
+
get(:index, :user_id => user.id)
|
117
|
+
expect(response.body).to have_css('ul > li', :text => products[0].name)
|
118
|
+
expect(response.body).to_not have_css('ul > li', :text => products[1].name)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context "with a scope configuration and a scoping environment as a specific object" do
|
124
|
+
let(:user) { resource(:user) }
|
125
|
+
|
126
|
+
before do
|
127
|
+
user.products = [products.first]
|
128
|
+
|
129
|
+
controller.stub(:user) { user }
|
130
|
+
controller do
|
131
|
+
scope :products, :by => :user
|
132
|
+
|
133
|
+
def index
|
134
|
+
respond_with(scoped(user))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
it "selects the resource using the provided scope" do
|
140
|
+
get(:index)
|
141
|
+
expect(response.body).to have_css('ul > li', :text => products[0].name)
|
142
|
+
expect(response.body).to_not have_css('ul > li', :text => products[1].name)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Emerson do
|
4
|
+
let(:features) { Emerson.features }
|
5
|
+
|
6
|
+
around do |example|
|
7
|
+
with_features(features) { example.run }
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".responder_enabled?" do
|
11
|
+
context "with the default feature set" do
|
12
|
+
it "returns true" do
|
13
|
+
expect(Emerson).to be_responder_enabled
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when the configured feature set is :all" do
|
18
|
+
let(:features) { :all }
|
19
|
+
|
20
|
+
it "returns true" do
|
21
|
+
expect(Emerson).to be_responder_enabled
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when the configured feature set includes :responder" do
|
26
|
+
let(:features) { [:responder] }
|
27
|
+
|
28
|
+
it "returns true" do
|
29
|
+
expect(Emerson).to be_responder_enabled
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when the configured feature set is nil" do
|
34
|
+
let(:features) { nil }
|
35
|
+
|
36
|
+
it "returns false" do
|
37
|
+
expect(Emerson).to_not be_responder_enabled
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when the configured feature set is false" do
|
42
|
+
let(:features) { false }
|
43
|
+
|
44
|
+
it "returns false" do
|
45
|
+
expect(Emerson).to_not be_responder_enabled
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when the configured feature set is []" do
|
50
|
+
let(:features) { [] }
|
51
|
+
|
52
|
+
it "returns false" do
|
53
|
+
expect(Emerson).to_not be_responder_enabled
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe ".scope_enabled?" do
|
59
|
+
context "with the default feature set" do
|
60
|
+
it "returns true" do
|
61
|
+
expect(Emerson).to be_scope_enabled
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when the configured feature set is :all" do
|
66
|
+
let(:features) { :all }
|
67
|
+
|
68
|
+
it "returns true" do
|
69
|
+
expect(Emerson).to be_scope_enabled
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when the configured feature set includes :scope" do
|
74
|
+
let(:features) { [:scope] }
|
75
|
+
|
76
|
+
it "returns true" do
|
77
|
+
expect(Emerson).to be_scope_enabled
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "when the configured feature set is nil" do
|
82
|
+
let(:features) { nil }
|
83
|
+
|
84
|
+
it "returns false" do
|
85
|
+
expect(Emerson).to_not be_scope_enabled
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when the configured feature set is false" do
|
90
|
+
let(:features) { false }
|
91
|
+
|
92
|
+
it "returns false" do
|
93
|
+
expect(Emerson).to_not be_scope_enabled
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "when the configured feature set is []" do
|
98
|
+
let(:features) { [] }
|
99
|
+
|
100
|
+
it "returns false" do
|
101
|
+
expect(Emerson).to_not be_scope_enabled
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
2
|
+
ENV["RAILS_ENV"] ||= 'test'
|
3
|
+
require File.expand_path("../dummy/config/environment", __FILE__)
|
4
|
+
require 'rspec/rails'
|
5
|
+
require 'rspec/autorun'
|
6
|
+
require 'capybara/rspec'
|
7
|
+
require 'ffaker'
|
8
|
+
|
9
|
+
require 'emerson/matchers'
|
10
|
+
|
11
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
12
|
+
# in spec/support/ and its subdirectories.
|
13
|
+
Dir[Rails.root.join("../support/**/*.rb")].each {|f| require f}
|
14
|
+
|
15
|
+
Capybara.register_driver :firefox do |app|
|
16
|
+
Capybara::Selenium::Driver.new(app, :browser => :firefox)
|
17
|
+
end
|
18
|
+
|
19
|
+
RSpec.configure do |config|
|
20
|
+
config.include Capybara::RSpecMatchers
|
21
|
+
config.include Support::ControllerHelpers
|
22
|
+
config.include Support::FeatureHelpers
|
23
|
+
config.include Support::ResourceHelpers
|
24
|
+
config.include Support::TemplateHelpers
|
25
|
+
|
26
|
+
# ## Mock Framework
|
27
|
+
#
|
28
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
29
|
+
#
|
30
|
+
# config.mock_with :mocha
|
31
|
+
# config.mock_with :flexmock
|
32
|
+
# config.mock_with :rr
|
33
|
+
|
34
|
+
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
35
|
+
config.fixture_path = "#{::Rails.root}/spec/support/fixtures"
|
36
|
+
|
37
|
+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
38
|
+
# examples within a transaction, remove the following line or assign false
|
39
|
+
# instead of true.
|
40
|
+
config.use_transactional_fixtures = true
|
41
|
+
|
42
|
+
# If true, the base class of anonymous controllers will be inferred
|
43
|
+
# automatically. This will be the default behavior in future versions of
|
44
|
+
# rspec-rails.
|
45
|
+
config.infer_base_class_for_anonymous_controllers = false
|
46
|
+
|
47
|
+
# Run specs in random order to surface order dependencies. If you find an
|
48
|
+
# order dependency and want to debug it, you can fix the order by providing
|
49
|
+
# the seed, which is printed after each run.
|
50
|
+
# --seed 1234
|
51
|
+
config.order = "random"
|
52
|
+
|
53
|
+
config.before do
|
54
|
+
reset_resources
|
55
|
+
end
|
56
|
+
|
57
|
+
config.after do
|
58
|
+
reset_resources
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
Emerson.setup do |config|
|
63
|
+
# Uncomment this line to specify a custom fixture path for Emerson. It will
|
64
|
+
# default to the RSpec fixture path and is required for the response tests.
|
65
|
+
#
|
66
|
+
# config.fixture_path = "#{::Rails.root}/spec/support/fixtures"
|
67
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Support
|
2
|
+
module ControllerHelpers
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# Override `ControllerExampleGroup.controller` for some nice sugar.
|
7
|
+
# See instance method below.
|
8
|
+
def controller(key, base = nil)
|
9
|
+
klass = Class.new(base || ApplicationController) do
|
10
|
+
include Emerson::Response
|
11
|
+
end
|
12
|
+
|
13
|
+
klass.class_eval <<-CODE
|
14
|
+
def self.name
|
15
|
+
@_name ||= [key, 'controller'].join('_').camelcase
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.key
|
19
|
+
@_key ||= '#{key}'.intern
|
20
|
+
end
|
21
|
+
|
22
|
+
def #{key}
|
23
|
+
resources
|
24
|
+
end
|
25
|
+
|
26
|
+
def resources=(value)
|
27
|
+
@resources = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def resources
|
31
|
+
@resources
|
32
|
+
end
|
33
|
+
|
34
|
+
# Scope helper
|
35
|
+
def class_for(type)
|
36
|
+
"Support::ResourceHelpers::\#{type.to_s.classify}".constantize
|
37
|
+
end
|
38
|
+
CODE
|
39
|
+
|
40
|
+
metadata[:example_group][:described_class] = klass
|
41
|
+
|
42
|
+
before do
|
43
|
+
@_resource_type = key
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Add `#controller` ExampleGroup instance method which, combined with the
|
49
|
+
# overriden class method above, provides the ability to:
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# describe "GET #index" do
|
53
|
+
# controller(:products)
|
54
|
+
#
|
55
|
+
# before do
|
56
|
+
# controller do
|
57
|
+
# def index
|
58
|
+
# respond_with(products)
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# it "is successful" do
|
64
|
+
# get(:index)
|
65
|
+
# expect(response).to be_success
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
def controller(*args, &body)
|
69
|
+
if block_given?
|
70
|
+
controller_class.class_eval(&body)
|
71
|
+
|
72
|
+
resource_set = self.instance_variable_get('@_resources')
|
73
|
+
resource_set = self.instance_variable_get('@__memoized') unless resource_set.present?
|
74
|
+
resource_key = "#{controller_class.key}".singularize.intern
|
75
|
+
|
76
|
+
@controller.resources = (resource_set[resource_key] || resource_set[controller_class.key])
|
77
|
+
end
|
78
|
+
|
79
|
+
super()
|
80
|
+
end
|
81
|
+
|
82
|
+
def controller_class
|
83
|
+
described_class
|
84
|
+
end
|
85
|
+
|
86
|
+
# Wrap `ActionController::TestCase::Behavior` actions in
|
87
|
+
# a warm routing embrace.
|
88
|
+
def process(*)
|
89
|
+
with_routing do |routes|
|
90
|
+
routes.instance_eval "
|
91
|
+
draw { resources #{@_resource_type.inspect} }
|
92
|
+
"
|
93
|
+
|
94
|
+
super
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
|
3
|
+
module Support
|
4
|
+
module ResourceHelpers
|
5
|
+
def reset_resources
|
6
|
+
Product.records = []
|
7
|
+
User.records = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def resource(type)
|
11
|
+
resources(type)[0]
|
12
|
+
end
|
13
|
+
|
14
|
+
def resources(type, count = nil)
|
15
|
+
@_resources ||= {}
|
16
|
+
|
17
|
+
if count.present? || @_resources[type].blank?
|
18
|
+
model = resource_class(type)
|
19
|
+
count ||= 1
|
20
|
+
count.times { model.create }
|
21
|
+
|
22
|
+
@_resources[type] = model.records
|
23
|
+
end
|
24
|
+
|
25
|
+
@_resources[type]
|
26
|
+
end
|
27
|
+
|
28
|
+
def resource_class(type)
|
29
|
+
case type
|
30
|
+
when :product
|
31
|
+
Product
|
32
|
+
when :user
|
33
|
+
User
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Base
|
38
|
+
include Virtus
|
39
|
+
extend ActiveModel::Naming
|
40
|
+
include ActiveModel::Conversion
|
41
|
+
include ActiveModel::Validations
|
42
|
+
|
43
|
+
attribute :id, Fixnum, :default => :default_id
|
44
|
+
attribute :name, String, :default => :default_name
|
45
|
+
|
46
|
+
class_attribute :records
|
47
|
+
self.records = []
|
48
|
+
|
49
|
+
# Emulating ActiveRecord class methods
|
50
|
+
class << self
|
51
|
+
def create
|
52
|
+
record = self.new
|
53
|
+
|
54
|
+
self.records << record
|
55
|
+
record
|
56
|
+
end
|
57
|
+
|
58
|
+
def find(id)
|
59
|
+
self.records.first { |r| r.id == id.to_i }
|
60
|
+
end
|
61
|
+
|
62
|
+
def scoped
|
63
|
+
self.records
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def default_id
|
68
|
+
Faker::Lorem.word
|
69
|
+
end
|
70
|
+
|
71
|
+
def default_name
|
72
|
+
Faker::Lorem.word
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Product < Base
|
77
|
+
def default_name
|
78
|
+
Faker::Product.product_name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class User < Base
|
83
|
+
attribute :products, Array, :default => :default_products
|
84
|
+
|
85
|
+
# self.records = []
|
86
|
+
def default_name
|
87
|
+
Faker::Name.name.gsub(/[']/, '')
|
88
|
+
end
|
89
|
+
|
90
|
+
def default_products
|
91
|
+
[Product.new, Product.new]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|