draper 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/CHANGELOG.md +3 -0
  2. data/CONTRIBUTING.md +5 -1
  3. data/Gemfile +23 -9
  4. data/README.md +144 -52
  5. data/Rakefile +1 -0
  6. data/draper.gemspec +1 -1
  7. data/lib/draper.rb +9 -6
  8. data/lib/draper/decoratable.rb +3 -7
  9. data/lib/draper/decoratable/equality.rb +14 -0
  10. data/lib/draper/decorator.rb +4 -7
  11. data/lib/draper/helper_proxy.rb +22 -3
  12. data/lib/draper/test/devise_helper.rb +18 -22
  13. data/lib/draper/test/rspec_integration.rb +4 -0
  14. data/lib/draper/test_case.rb +20 -0
  15. data/lib/draper/version.rb +1 -1
  16. data/lib/draper/view_context.rb +75 -13
  17. data/lib/draper/view_context/build_strategy.rb +48 -0
  18. data/lib/draper/view_helpers.rb +2 -2
  19. data/spec/draper/collection_decorator_spec.rb +169 -196
  20. data/spec/draper/decoratable/equality_spec.rb +10 -0
  21. data/spec/draper/decoratable_spec.rb +107 -132
  22. data/spec/draper/decorated_association_spec.rb +99 -96
  23. data/spec/draper/decorator_spec.rb +408 -434
  24. data/spec/draper/finders_spec.rb +160 -126
  25. data/spec/draper/helper_proxy_spec.rb +38 -8
  26. data/spec/draper/view_context/build_strategy_spec.rb +116 -0
  27. data/spec/draper/view_context_spec.rb +154 -0
  28. data/spec/draper/view_helpers_spec.rb +4 -37
  29. data/spec/dummy/app/controllers/posts_controller.rb +7 -0
  30. data/spec/dummy/app/decorators/post_decorator.rb +26 -2
  31. data/spec/dummy/app/helpers/application_helper.rb +3 -0
  32. data/spec/dummy/app/mailers/post_mailer.rb +10 -0
  33. data/spec/dummy/app/models/admin.rb +5 -0
  34. data/spec/dummy/app/models/mongoid_post.rb +5 -0
  35. data/spec/dummy/app/models/user.rb +5 -0
  36. data/spec/dummy/app/views/posts/_post.html.erb +15 -0
  37. data/spec/dummy/bin/rails +4 -0
  38. data/spec/dummy/config/application.rb +9 -3
  39. data/spec/dummy/config/boot.rb +2 -7
  40. data/spec/dummy/config/environments/development.rb +2 -3
  41. data/spec/dummy/config/environments/production.rb +2 -0
  42. data/spec/dummy/config/environments/test.rb +3 -4
  43. data/spec/dummy/config/initializers/secret_token.rb +1 -0
  44. data/spec/dummy/config/mongoid.yml +80 -0
  45. data/spec/dummy/config/routes.rb +2 -0
  46. data/spec/dummy/fast_spec/post_decorator_spec.rb +38 -0
  47. data/spec/dummy/lib/tasks/test.rake +11 -5
  48. data/spec/dummy/spec/decorators/devise_spec.rb +64 -0
  49. data/spec/dummy/spec/decorators/helpers_spec.rb +21 -0
  50. data/spec/dummy/spec/decorators/post_decorator_spec.rb +26 -6
  51. data/spec/dummy/spec/decorators/spec_type_spec.rb +7 -0
  52. data/spec/dummy/spec/decorators/view_context_spec.rb +22 -0
  53. data/spec/dummy/spec/mailers/post_mailer_spec.rb +10 -6
  54. data/spec/dummy/spec/models/mongoid_post_spec.rb +10 -0
  55. data/spec/dummy/spec/models/post_spec.rb +5 -5
  56. data/spec/dummy/spec/spec_helper.rb +1 -0
  57. data/spec/dummy/test/decorators/minitest/devise_test.rb +64 -0
  58. data/spec/dummy/test/decorators/minitest/helpers_test.rb +21 -0
  59. data/spec/dummy/{mini_test/mini_test_integration_test.rb → test/decorators/minitest/spec_type_test.rb} +9 -3
  60. data/spec/dummy/test/decorators/minitest/view_context_test.rb +24 -0
  61. data/spec/dummy/test/decorators/test_unit/devise_test.rb +64 -0
  62. data/spec/dummy/test/decorators/test_unit/helpers_test.rb +21 -0
  63. data/spec/dummy/test/decorators/test_unit/view_context_test.rb +24 -0
  64. data/spec/dummy/test/minitest_helper.rb +4 -0
  65. data/spec/dummy/test/test_helper.rb +3 -0
  66. data/spec/generators/decorator/decorator_generator_spec.rb +1 -0
  67. data/spec/integration/integration_spec.rb +31 -6
  68. data/spec/spec_helper.rb +32 -25
  69. data/spec/support/shared_examples/decoratable_equality.rb +40 -0
  70. data/spec/support/shared_examples/view_helpers.rb +39 -0
  71. metadata +56 -44
  72. data/spec/dummy/README.rdoc +0 -261
  73. data/spec/dummy/spec/decorators/rspec_integration_spec.rb +0 -19
  74. data/spec/support/action_controller.rb +0 -12
  75. data/spec/support/active_model.rb +0 -7
  76. data/spec/support/active_record.rb +0 -9
  77. data/spec/support/decorators/decorator_with_application_helper.rb +0 -25
  78. data/spec/support/decorators/namespaced_product_decorator.rb +0 -5
  79. data/spec/support/decorators/non_active_model_product_decorator.rb +0 -2
  80. data/spec/support/decorators/product_decorator.rb +0 -23
  81. data/spec/support/decorators/products_decorator.rb +0 -10
  82. data/spec/support/decorators/some_thing_decorator.rb +0 -2
  83. data/spec/support/decorators/specific_product_decorator.rb +0 -2
  84. data/spec/support/decorators/widget_decorator.rb +0 -2
  85. data/spec/support/models/namespaced_product.rb +0 -49
  86. data/spec/support/models/non_active_model_product.rb +0 -3
  87. data/spec/support/models/product.rb +0 -95
  88. data/spec/support/models/some_thing.rb +0 -5
  89. data/spec/support/models/uninferrable_decorator_model.rb +0 -3
  90. data/spec/support/models/widget.rb +0 -2
@@ -1,624 +1,598 @@
1
1
  require 'spec_helper'
2
+ require 'support/shared_examples/view_helpers'
2
3
 
3
- describe Draper::Decorator do
4
- before { ApplicationController.new.view_context }
5
- subject { decorator_class.new(source) }
6
- let(:decorator_class) { Draper::Decorator }
7
- let(:source) { Product.new }
4
+ module Draper
5
+ describe Decorator do
6
+ it_behaves_like "view helpers", Decorator.new(Model.new)
8
7
 
9
- describe "#initialize" do
10
- describe "options validation" do
11
- let(:valid_options) { {context: {}} }
8
+ describe "#initialize" do
9
+ describe "options validation" do
10
+ it "does not raise error on valid options" do
11
+ valid_options = {context: {}}
12
+ expect{Decorator.new(Model.new, valid_options)}.not_to raise_error
13
+ end
12
14
 
13
- it "does not raise error on valid options" do
14
- expect { decorator_class.new(source, valid_options) }.to_not raise_error
15
+ it "raises error on invalid options" do
16
+ expect{Decorator.new(Model.new, foo: "bar")}.to raise_error ArgumentError, /Unknown key/
17
+ end
15
18
  end
16
19
 
17
- it "raises error on invalid options" do
18
- expect { decorator_class.new(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, /Unknown key/)
19
- end
20
- end
20
+ it "sets the source" do
21
+ source = Model.new
22
+ decorator = Decorator.new(source)
21
23
 
22
- it "sets the source" do
23
- subject.source.should be source
24
- end
24
+ expect(decorator.source).to be source
25
+ end
25
26
 
26
- it "stores context" do
27
- decorator = decorator_class.new(source, context: {some: 'context'})
28
- decorator.context.should == {some: 'context'}
29
- end
27
+ it "stores context" do
28
+ context = {some: "context"}
29
+ decorator = Decorator.new(Model.new, context: context)
30
30
 
31
- context "when decorating an instance of itself" do
32
- it "does not redecorate" do
33
- decorator = ProductDecorator.new(source)
34
- ProductDecorator.new(decorator).source.should be source
31
+ expect(decorator.context).to be context
35
32
  end
36
33
 
37
- context "when options are supplied" do
38
- it "overwrites existing context" do
39
- decorator = ProductDecorator.new(source, context: {role: :admin})
40
- ProductDecorator.new(decorator, context: {role: :user}).context.should == {role: :user}
34
+ context "when decorating an instance of itself" do
35
+ it "applies to the source instead" do
36
+ source = Model.new
37
+ decorated = Decorator.new(source)
38
+ redecorated = Decorator.new(decorated)
39
+
40
+ expect(redecorated.source).to be source
41
41
  end
42
- end
43
42
 
44
- context "when no options are supplied" do
45
- it "preserves existing context" do
46
- decorator = ProductDecorator.new(source, context: {role: :admin})
47
- ProductDecorator.new(decorator).context.should == {role: :admin}
43
+ context "with context" do
44
+ it "overwrites existing context" do
45
+ decorated = Decorator.new(Model.new, context: {some: "context"})
46
+ new_context = {other: "context"}
47
+ redecorated = Decorator.new(decorated, context: new_context)
48
+
49
+ expect(redecorated.context).to be new_context
50
+ end
48
51
  end
49
- end
50
- end
51
52
 
52
- context "when decorating other decorators" do
53
- it "redecorates" do
54
- decorator = ProductDecorator.new(source)
55
- SpecificProductDecorator.new(decorator).source.should be decorator
53
+ context "without context" do
54
+ it "preserves existing context" do
55
+ old_context = {some: "context"}
56
+ decorated = Decorator.new(Model.new, context: old_context)
57
+ redecorated = Decorator.new(decorated)
58
+
59
+ expect(redecorated.context).to be old_context
60
+ end
61
+ end
56
62
  end
57
63
 
58
- context "when the same decorator has been applied earlier in the chain" do
59
- let(:decorator) { SpecificProductDecorator.new(ProductDecorator.new(Product.new)) }
64
+ it "decorates other decorators" do
65
+ decorated = OtherDecorator.new(Model.new)
66
+ redecorated = Decorator.new(decorated)
67
+
68
+ expect(redecorated.source).to be decorated
69
+ end
60
70
 
71
+ context "when it has been applied previously" do
61
72
  it "warns" do
73
+ decorated = OtherDecorator.new(Decorator.new(Model.new))
74
+
62
75
  warning_message = nil
63
76
  Object.any_instance.stub(:warn) {|message| warning_message = message }
64
77
 
65
- expect{ProductDecorator.new(decorator)}.to change{warning_message}
66
- warning_message.should =~ /ProductDecorator/
67
- warning_message.should include caller(1).first
78
+ expect{Decorator.new(decorated)}.to change{warning_message}
79
+ expect(warning_message).to start_with "Reapplying Draper::Decorator"
80
+ expect(warning_message).to include caller(1).first
68
81
  end
69
82
 
70
- it "redecorates" do
83
+ it "decorates anyway" do
84
+ decorated = OtherDecorator.new(Decorator.new(Model.new))
71
85
  Object.any_instance.stub(:warn)
72
- ProductDecorator.new(decorator).source.should be decorator
86
+ redecorated = Decorator.decorate(decorated)
87
+
88
+ expect(redecorated.source).to be decorated
73
89
  end
74
90
  end
75
91
  end
76
- end
77
-
78
- describe "#context=" do
79
- it "modifies the context" do
80
- decorator = decorator_class.new(source, context: {some: 'context'})
81
- decorator.context = {some: 'other_context'}
82
- decorator.context.should == {some: 'other_context'}
83
- end
84
- end
85
-
86
- describe ".decorate_collection" do
87
- let(:source) { [Product.new, Widget.new] }
88
-
89
- describe "options validation" do
90
- let(:valid_options) { {with: :infer, context: {}} }
91
- before(:each) { Draper::CollectionDecorator.stub(:new) }
92
92
 
93
- it "does not raise error on valid options" do
94
- expect { ProductDecorator.decorate_collection(source, valid_options) }.to_not raise_error
95
- end
93
+ describe "#context=" do
94
+ it "modifies the context" do
95
+ decorator = Decorator.new(Model.new, context: {some: "context"})
96
+ new_context = {other: "context"}
96
97
 
97
- it "raises error on invalid options" do
98
- expect { ProductDecorator.decorate_collection(source, valid_options.merge(foo: 'bar')) }.to raise_error(ArgumentError, /Unknown key/)
98
+ decorator.context = new_context
99
+ expect(decorator.context).to be new_context
99
100
  end
100
101
  end
101
102
 
102
- context "when a custom collection decorator does not exist" do
103
- subject { WidgetDecorator.decorate_collection(source) }
103
+ describe ".decorate_collection" do
104
+ describe "options validation" do
105
+ before { CollectionDecorator.stub(:new) }
104
106
 
105
- it "returns a regular collection decorator" do
106
- subject.should be_a Draper::CollectionDecorator
107
- subject.should == source
108
- end
107
+ it "does not raise error on valid options" do
108
+ valid_options = {with: OtherDecorator, context: {}}
109
+ expect{Decorator.decorate_collection([], valid_options)}.not_to raise_error
110
+ end
109
111
 
110
- it "uses itself as the item decorator by default" do
111
- subject.each {|item| item.should be_a WidgetDecorator}
112
+ it "raises error on invalid options" do
113
+ expect{Decorator.decorate_collection([], foo: "bar")}.to raise_error ArgumentError, /Unknown key/
114
+ end
112
115
  end
113
- end
114
-
115
- context "when a custom collection decorator exists" do
116
- subject { ProductDecorator.decorate_collection(source) }
117
116
 
118
- it "returns the custom collection decorator" do
119
- subject.should be_a ProductsDecorator
120
- subject.should == source
121
- end
117
+ context "without a custom collection decorator" do
118
+ it "creates a CollectionDecorator using itself for each item" do
119
+ source = [Model.new]
122
120
 
123
- it "uses itself as the item decorator by default" do
124
- subject.each {|item| item.should be_a ProductDecorator}
125
- end
126
- end
121
+ CollectionDecorator.should_receive(:new).with(source, with: Decorator)
122
+ Decorator.decorate_collection(source)
123
+ end
127
124
 
128
- context "with context" do
129
- subject { ProductDecorator.decorate_collection(source, with: :infer, context: {some: 'context'}) }
125
+ it "passes options to the collection decorator" do
126
+ options = {with: OtherDecorator, context: {some: "context"}}
130
127
 
131
- it "passes the context to the collection decorator" do
132
- subject.context.should == {some: 'context'}
128
+ CollectionDecorator.should_receive(:new).with([], options)
129
+ Decorator.decorate_collection([], options)
130
+ end
133
131
  end
134
- end
135
- end
136
-
137
- describe "#helpers" do
138
- it "returns a HelperProxy" do
139
- subject.helpers.should be_a Draper::HelperProxy
140
- end
141
132
 
142
- it "is aliased to #h" do
143
- subject.h.should be subject.helpers
144
- end
133
+ context "with a custom collection decorator" do
134
+ it "creates a custom collection decorator using itself for each item" do
135
+ source = [Model.new]
145
136
 
146
- it "initializes the wrapper only once" do
147
- helper_proxy = subject.helpers
148
- helper_proxy.stub(:test_method) { "test_method" }
149
- subject.helpers.test_method.should == "test_method"
150
- subject.helpers.test_method.should == "test_method"
151
- end
152
- end
137
+ ProductsDecorator.should_receive(:new).with(source, with: ProductDecorator)
138
+ ProductDecorator.decorate_collection(source)
139
+ end
153
140
 
154
- describe "#localize" do
155
- before { subject.helpers.should_receive(:localize).with(:an_object, {some: 'parameter'}) }
141
+ it "passes options to the collection decorator" do
142
+ options = {with: OtherDecorator, context: {some: "context"}}
156
143
 
157
- it "delegates to #helpers" do
158
- subject.localize(:an_object, some: 'parameter')
144
+ ProductsDecorator.should_receive(:new).with([], options)
145
+ ProductDecorator.decorate_collection([], options)
146
+ end
147
+ end
159
148
  end
160
149
 
161
- it "is aliased to #l" do
162
- subject.l(:an_object, some: 'parameter')
163
- end
164
- end
150
+ describe ".decorates" do
151
+ protect_class Decorator
165
152
 
166
- describe ".helpers" do
167
- it "returns a HelperProxy" do
168
- subject.class.helpers.should be_a Draper::HelperProxy
169
- end
153
+ it "sets .source_class with a symbol" do
154
+ Decorator.decorates :product
170
155
 
171
- it "is aliased to .h" do
172
- subject.class.h.should be subject.class.helpers
173
- end
174
- end
156
+ expect(Decorator.source_class).to be Product
157
+ end
175
158
 
176
- describe ".decorates" do
177
- subject { Class.new(Draper::Decorator) }
159
+ it "sets .source_class with a string" do
160
+ Decorator.decorates "product"
178
161
 
179
- context "with a symbol" do
180
- it "sets .source_class" do
181
- subject.decorates :product
182
- subject.source_class.should be Product
162
+ expect(Decorator.source_class).to be Product
183
163
  end
184
- end
185
164
 
186
- context "with a string" do
187
- it "sets .source_class" do
188
- subject.decorates "product"
189
- subject.source_class.should be Product
190
- end
191
- end
165
+ it "sets .source_class with a class" do
166
+ Decorator.decorates Product
192
167
 
193
- context "with a class" do
194
- it "sets .source_class" do
195
- subject.decorates Product
196
- subject.source_class.should be Product
168
+ expect(Decorator.source_class).to be Product
197
169
  end
198
170
  end
199
- end
200
-
201
- describe ".source_class" do
202
- context "when not set by .decorates" do
203
- context "for an anonymous decorator" do
204
- subject { Class.new(Draper::Decorator) }
205
171
 
206
- it "raises an UninferrableSourceError" do
207
- expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
172
+ describe ".source_class" do
173
+ context "when not set by .decorates" do
174
+ it "raises an error for a so-named 'Decorator'" do
175
+ expect{Decorator.source_class}.to raise_error UninferrableSourceError
208
176
  end
209
- end
210
177
 
211
- context "for a decorator without a corresponding source" do
212
- subject { SpecificProductDecorator }
213
-
214
- it "raises an UninferrableSourceError" do
215
- expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
178
+ it "raises an error for anonymous decorators" do
179
+ expect{Class.new(Decorator).source_class}.to raise_error UninferrableSourceError
216
180
  end
217
- end
218
-
219
- context "for a decorator called Decorator" do
220
- subject { Draper::Decorator }
221
181
 
222
- it "raises an UninferrableSourceError" do
223
- expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
182
+ it "raises an error for a decorator without a model" do
183
+ expect{OtherDecorator.source_class}.to raise_error UninferrableSourceError
224
184
  end
225
- end
226
-
227
- context "for a decorator with a name not ending in Decorator" do
228
- subject { DecoratorWithApplicationHelper }
229
185
 
230
- it "raises an UninferrableSourceError" do
231
- expect{subject.source_class}.to raise_error Draper::UninferrableSourceError
186
+ it "raises an error for other naming conventions" do
187
+ expect{ProductPresenter.source_class}.to raise_error UninferrableSourceError
232
188
  end
233
- end
234
189
 
235
- context "for an inferrable source" do
236
- subject { ProductDecorator }
237
-
238
- it "infers the source" do
239
- subject.source_class.should be Product
190
+ it "infers the source for '<Model>Decorator'" do
191
+ expect(ProductDecorator.source_class).to be Product
240
192
  end
241
- end
242
-
243
- context "for a namespaced inferrable source" do
244
- subject { Namespace::ProductDecorator }
245
193
 
246
- it "infers the namespaced source" do
247
- subject.source_class.should be Namespace::Product
194
+ it "infers namespaced sources" do
195
+ expect(Namespaced::ProductDecorator.source_class).to be Namespaced::Product
248
196
  end
249
197
  end
250
198
  end
251
- end
252
199
 
253
- describe ".source_class?" do
254
- subject { Class.new(Draper::Decorator) }
200
+ describe ".source_class?" do
201
+ it "returns truthy when .source_class is set" do
202
+ Decorator.stub(:source_class).and_return(Model)
255
203
 
256
- it "returns truthy when .source_class is set" do
257
- subject.stub(:source_class).and_return(Product)
258
- subject.source_class?.should be_true
259
- end
204
+ expect(Decorator.source_class?).to be_true
205
+ end
260
206
 
261
- it "returns false when .source_class is not inferrable" do
262
- subject.stub(:source_class).and_raise(Draper::UninferrableSourceError.new(subject))
263
- subject.source_class?.should be_false
264
- end
265
- end
207
+ it "returns false when .source_class is not inferrable" do
208
+ Decorator.stub(:source_class).and_raise(UninferrableSourceError.new(Decorator))
266
209
 
267
- describe ".decorates_association" do
268
- let(:decorator_class) { Class.new(ProductDecorator) }
269
- before { decorator_class.decorates_association :similar_products, with: ProductDecorator }
210
+ expect(Decorator.source_class?).to be_false
211
+ end
212
+ end
270
213
 
271
- describe "overridden association method" do
272
- let(:decorated_association) { ->{} }
214
+ describe ".decorates_association" do
215
+ protect_class Decorator
273
216
 
274
217
  describe "options validation" do
275
- let(:valid_options) { {with: ProductDecorator, scope: :foo, context: {}} }
276
- before(:each) { Draper::DecoratedAssociation.stub(:new).and_return(decorated_association) }
218
+ before { DecoratedAssociation.stub(:new).and_return(->{}) }
277
219
 
278
220
  it "does not raise error on valid options" do
279
- expect { decorator_class.decorates_association :similar_products, valid_options }.to_not raise_error
221
+ valid_options = {with: Class, scope: :sorted, context: {}}
222
+ expect{Decorator.decorates_association(:children, valid_options)}.not_to raise_error
280
223
  end
281
224
 
282
225
  it "raises error on invalid options" do
283
- expect { decorator_class.decorates_association :similar_products, valid_options.merge(foo: 'bar') }.to raise_error(ArgumentError, /Unknown key/)
226
+ expect{Decorator.decorates_association(:children, foo: "bar")}.to raise_error ArgumentError, /Unknown key/
284
227
  end
285
228
  end
286
229
 
287
- it "creates a DecoratedAssociation" do
288
- Draper::DecoratedAssociation.should_receive(:new).with(subject, :similar_products, {with: ProductDecorator}).and_return(decorated_association)
289
- subject.similar_products
290
- end
291
-
292
- it "receives the Decorator" do
293
- Draper::DecoratedAssociation.should_receive(:new).with(kind_of(decorator_class), :similar_products, {with: ProductDecorator}).and_return(decorated_association)
294
- subject.similar_products
295
- end
296
-
297
- it "memoizes the DecoratedAssociation" do
298
- Draper::DecoratedAssociation.should_receive(:new).once.and_return(decorated_association)
299
- subject.similar_products
300
- subject.similar_products
301
- end
230
+ describe "defines an association method" do
231
+ it "creates a DecoratedAssociation" do
232
+ options = {with: Class.new, scope: :foo, context: {}}
233
+ Decorator.decorates_association :children, options
234
+ decorator = Decorator.new(Model.new)
302
235
 
303
- it "calls the DecoratedAssociation" do
304
- Draper::DecoratedAssociation.stub(:new).and_return(decorated_association)
305
- decorated_association.should_receive(:call).and_return(:decorated)
306
- subject.similar_products.should be :decorated
307
- end
308
- end
309
- end
310
-
311
- describe ".decorates_associations" do
312
- subject { decorator_class }
236
+ DecoratedAssociation.should_receive(:new).with(decorator, :children, options).and_return(->{})
237
+ decorator.children
238
+ end
313
239
 
314
- it "decorates each of the associations" do
315
- subject.should_receive(:decorates_association).with(:similar_products, {})
316
- subject.should_receive(:decorates_association).with(:previous_version, {})
240
+ it "memoizes the DecoratedAssociation" do
241
+ Decorator.decorates_association :children
242
+ decorator = Decorator.new(Model.new)
317
243
 
318
- subject.decorates_associations :similar_products, :previous_version
319
- end
244
+ DecoratedAssociation.should_receive(:new).once.and_return(->{})
245
+ decorator.children
246
+ decorator.children
247
+ end
320
248
 
321
- it "dispatches options" do
322
- subject.should_receive(:decorates_association).with(:similar_products, {with: ProductDecorator})
323
- subject.should_receive(:decorates_association).with(:previous_version, {with: ProductDecorator})
249
+ it "calls the DecoratedAssociation" do
250
+ Decorator.decorates_association :children
251
+ decorator = Decorator.new(Model.new)
252
+ decorated_association = ->{}
253
+ DecoratedAssociation.stub(:new).and_return(decorated_association)
324
254
 
325
- subject.decorates_associations :similar_products, :previous_version, with: ProductDecorator
326
- end
327
- end
328
-
329
- describe "#applied_decorators" do
330
- it "returns a list of decorators applied to a model" do
331
- decorator = ProductDecorator.new(SpecificProductDecorator.new(Product.new))
332
- decorator.applied_decorators.should == [SpecificProductDecorator, ProductDecorator]
255
+ decorated_association.should_receive(:call).and_return(:decorated)
256
+ expect(decorator.children).to be :decorated
257
+ end
258
+ end
333
259
  end
334
- end
335
260
 
336
- describe "#decorated_with?" do
337
- it "checks if a decorator has been applied to a model" do
338
- decorator = ProductDecorator.new(SpecificProductDecorator.new(Product.new))
339
- decorator.should be_decorated_with ProductDecorator
340
- decorator.should be_decorated_with SpecificProductDecorator
341
- decorator.should_not be_decorated_with WidgetDecorator
342
- end
343
- end
261
+ describe ".decorates_associations" do
262
+ protect_class Decorator
344
263
 
345
- describe "#decorated?" do
346
- it "returns true" do
347
- subject.should be_decorated
348
- end
349
- end
264
+ it "decorates each of the associations" do
265
+ Decorator.should_receive(:decorates_association).with(:friends, {})
266
+ Decorator.should_receive(:decorates_association).with(:enemies, {})
267
+ Decorator.decorates_associations :friends, :enemies
268
+ end
350
269
 
351
- describe "#source" do
352
- it "returns the wrapped object" do
353
- subject.source.should be source
354
- end
270
+ it "dispatches options" do
271
+ options = {with: Class.new, scope: :foo, context: {}}
355
272
 
356
- it "is aliased to #to_source" do
357
- subject.to_source.should be source
273
+ Decorator.should_receive(:decorates_association).with(:friends, options)
274
+ Decorator.should_receive(:decorates_association).with(:enemies, options)
275
+ Decorator.decorates_associations :friends, :enemies, options
276
+ end
358
277
  end
359
278
 
360
- it "is aliased to #model" do
361
- subject.model.should be source
362
- end
363
- end
279
+ describe "#applied_decorators" do
280
+ it "returns a list of decorators applied to a model" do
281
+ decorator = ProductDecorator.new(OtherDecorator.new(Decorator.new(Model.new)))
364
282
 
365
- describe "#to_model" do
366
- it "returns the decorator" do
367
- subject.to_model.should be subject
283
+ expect(decorator.applied_decorators).to eq [Decorator, OtherDecorator, ProductDecorator]
284
+ end
368
285
  end
369
- end
370
286
 
371
- describe "#to_param" do
372
- it "proxies to the source" do
373
- source.stub(:to_param).and_return(42)
374
- subject.to_param.should == 42
375
- end
376
- end
287
+ describe "#decorated_with?" do
288
+ it "checks if a decorator has been applied to a model" do
289
+ decorator = ProductDecorator.new(Decorator.new(Model.new))
377
290
 
378
- describe "#==" do
379
- context "with itself" do
380
- it "returns true" do
381
- (subject == subject).should be_true
291
+ expect(decorator).to be_decorated_with Decorator
292
+ expect(decorator).to be_decorated_with ProductDecorator
293
+ expect(decorator).not_to be_decorated_with OtherDecorator
382
294
  end
383
295
  end
384
296
 
385
- context "with another decorator having the same source" do
297
+ describe "#decorated?" do
386
298
  it "returns true" do
387
- (subject == ProductDecorator.new(source)).should be_true
299
+ decorator = Decorator.new(Model.new)
300
+
301
+ expect(decorator).to be_decorated
388
302
  end
389
303
  end
390
304
 
391
- context "with another decorator having a different source" do
392
- it "returns false" do
393
- (subject == ProductDecorator.new(Object.new)).should be_false
305
+ describe "#source" do
306
+ it "returns the wrapped object" do
307
+ source = Model.new
308
+ decorator = Decorator.new(source)
309
+
310
+ expect(decorator.source).to be source
311
+ expect(decorator.model).to be source
312
+ expect(decorator.to_source).to be source
394
313
  end
395
314
  end
396
315
 
397
- context "with the source object" do
398
- it "returns true" do
399
- (subject == source).should be_true
316
+ describe "#to_model" do
317
+ it "returns the decorator" do
318
+ decorator = Decorator.new(Model.new)
319
+
320
+ expect(decorator.to_model).to be decorator
400
321
  end
401
322
  end
402
323
 
403
- context "with another object" do
404
- it "returns false" do
405
- (subject == Object.new).should be_false
324
+ describe "#to_param" do
325
+ it "delegates to the source" do
326
+ decorator = Decorator.new(double(to_param: :delegated))
327
+
328
+ expect(decorator.to_param).to be :delegated
406
329
  end
407
330
  end
408
- end
409
331
 
410
- describe "#===" do
411
- context "with itself" do
412
- it "returns true" do
413
- (subject === subject).should be_true
332
+ describe "#present?" do
333
+ it "delegates to the source" do
334
+ decorator = Decorator.new(double(present?: :delegated))
335
+
336
+ expect(decorator.present?).to be :delegated
414
337
  end
415
338
  end
416
339
 
417
- context "with another decorator having the same source" do
418
- it "returns true" do
419
- (subject === ProductDecorator.new(source)).should be_true
340
+ describe "#to_partial_path" do
341
+ it "delegates to the source" do
342
+ decorator = Decorator.new(double(to_partial_path: :delegated))
343
+
344
+ expect(decorator.to_partial_path).to be :delegated
420
345
  end
421
346
  end
422
347
 
423
- context "with another decorator having a different source" do
424
- it "returns false" do
425
- (subject === ProductDecorator.new(Object.new)).should be_false
348
+ describe ".model_name" do
349
+ it "delegates to the source class" do
350
+ Decorator.stub source_class: double(model_name: :delegated)
351
+
352
+ expect(Decorator.model_name).to be :delegated
426
353
  end
427
354
  end
428
355
 
429
- context "with the source object" do
430
- it "returns true" do
431
- (subject === source).should be_true
356
+ describe "#==" do
357
+ it "ensures the source has a decoration-aware #==" do
358
+ source = Object.new
359
+ decorator = Decorator.new(source)
360
+
361
+ expect(source).not_to be_a_kind_of Draper::Decoratable::Equality
362
+ decorator == :something
363
+ expect(source).to be_a_kind_of Draper::Decoratable::Equality
432
364
  end
433
- end
434
365
 
435
- context "with another object" do
436
- it "returns false" do
437
- (subject === Object.new).should be_false
366
+ it "is true when source #== is true" do
367
+ source = Model.new
368
+ decorator = Decorator.new(source)
369
+ other = double(source: Model.new)
370
+
371
+ source.should_receive(:==).with(other).and_return(true)
372
+ expect(decorator == other).to be_true
438
373
  end
439
- end
440
- end
441
374
 
442
- describe ".delegate" do
443
- subject { Class.new(Draper::Decorator) }
375
+ it "is false when source #== is false" do
376
+ source = Model.new
377
+ decorator = Decorator.new(source)
378
+ other = double(source: Model.new)
444
379
 
445
- it "defaults the :to option to :source" do
446
- Draper::Decorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :source)
447
- subject.delegate :foo, :bar
448
- end
380
+ source.should_receive(:==).with(other).and_return(false)
381
+ expect(decorator == other).to be_false
382
+ end
449
383
 
450
- it "does not overwrite the :to option if supplied" do
451
- Draper::Decorator.superclass.should_receive(:delegate).with(:foo, :bar, to: :baz)
452
- subject.delegate :foo, :bar, to: :baz
453
384
  end
454
- end
455
385
 
456
- describe ".delegate_all" do
457
- let(:decorator_class) { Class.new(ProductDecorator) }
458
- before { decorator_class.delegate_all }
386
+ describe "#===" do
387
+ it "is true when #== is true" do
388
+ decorator = Decorator.new(Model.new)
389
+ decorator.stub(:==).with(:anything).and_return(true)
459
390
 
460
- describe "#method_missing" do
461
- it "does not delegate methods that are defined on the decorator" do
462
- subject.overridable.should be :overridden
391
+ expect(decorator === :anything).to be_true
463
392
  end
464
393
 
465
- it "does not delegate methods inherited from Object" do
466
- subject.inspect.should_not be source.inspect
467
- end
394
+ it "is false when #== is false" do
395
+ decorator = Decorator.new(Model.new)
396
+ decorator.stub(:==).with(:anything).and_return(false)
468
397
 
469
- it "delegates missing methods that exist on the source" do
470
- source.stub(:hello_world).and_return(:delegated)
471
- subject.hello_world.should be :delegated
398
+ expect(decorator === :anything).to be_false
472
399
  end
400
+ end
473
401
 
474
- it "adds delegated methods to the decorator when they are used" do
475
- subject.methods.should_not include :hello_world
476
- subject.hello_world
477
- subject.methods.should include :hello_world
478
- end
402
+ describe ".delegate" do
403
+ protect_class Decorator
479
404
 
480
- it "passes blocks to delegated methods" do
481
- subject.block{"marker"}.should == "marker"
405
+ it "defaults the :to option to :source" do
406
+ Object.should_receive(:delegate).with(:foo, :bar, to: :source)
407
+ Decorator.delegate :foo, :bar
482
408
  end
483
409
 
484
- it "does not confuse Kernel#Array" do
485
- Array(subject).should be_a Array
410
+ it "does not overwrite the :to option if supplied" do
411
+ Object.should_receive(:delegate).with(:foo, :bar, to: :baz)
412
+ Decorator.delegate :foo, :bar, to: :baz
486
413
  end
414
+ end
487
415
 
488
- it "delegates already-delegated methods" do
489
- subject.delegated_method.should == "Yay, delegation"
490
- end
416
+ context "with .delegate_all" do
417
+ protect_class Decorator
491
418
 
492
- it "does not delegate private methods" do
493
- expect{subject.private_title}.to raise_error NoMethodError
494
- end
495
- end
419
+ before { Decorator.delegate_all }
496
420
 
497
- context ".method_missing" do
498
- subject { decorator_class }
421
+ describe "#method_missing" do
422
+ it "delegates missing methods that exist on the source" do
423
+ decorator = Decorator.new(double(hello_world: :delegated))
499
424
 
500
- context "without a source class" do
501
- it "raises a NoMethodError on missing methods" do
502
- expect{subject.hello_world}.to raise_error NoMethodError
425
+ expect(decorator.hello_world).to be :delegated
503
426
  end
504
- end
505
427
 
506
- context "with a source class" do
507
- let(:source_class) { Product }
508
- before { subject.decorates source_class }
428
+ it "adds delegated methods to the decorator when they are used" do
429
+ decorator = Decorator.new(double(hello_world: :delegated))
509
430
 
510
- it "does not delegate methods that are defined on the decorator" do
511
- subject.overridable.should be :overridden
431
+ expect(decorator.methods).not_to include :hello_world
432
+ decorator.hello_world
433
+ expect(decorator.methods).to include :hello_world
512
434
  end
513
435
 
514
- it "delegates missing methods that exist on the source" do
515
- source_class.stub(:hello_world).and_return(:delegated)
516
- subject.hello_world.should be :delegated
436
+ it "passes blocks to delegated methods" do
437
+ source = Model.new
438
+ source.stub(:hello_world).and_return{|*args, &block| block.call}
439
+ decorator = Decorator.new(source)
440
+
441
+ expect(decorator.hello_world{:yielded}).to be :yielded
517
442
  end
518
- end
519
- end
520
443
 
521
- describe "#respond_to?" do
522
- it "returns true for its own methods" do
523
- subject.should respond_to :awesome_title
524
- end
444
+ it "does not confuse Kernel#Array" do
445
+ decorator = Decorator.new(Model.new)
525
446
 
526
- it "returns true for the source's methods" do
527
- subject.should respond_to :title
528
- end
447
+ expect(Array(decorator)).to be_an Array
448
+ end
449
+
450
+ it "delegates already-delegated methods" do
451
+ source = Class.new{ delegate :bar, to: :foo }.new
452
+ source.stub foo: double(bar: :delegated)
453
+ decorator = Decorator.new(source)
529
454
 
530
- context "with include_private" do
531
- it "returns true for its own private methods" do
532
- subject.respond_to?(:awesome_private_title, true).should be_true
455
+ expect(decorator.bar).to be :delegated
533
456
  end
534
457
 
535
- it "returns false for the source's private methods" do
536
- subject.respond_to?(:private_title, true).should be_false
458
+ it "does not delegate private methods" do
459
+ source = Class.new{ private; def hello_world; end }.new
460
+ decorator = Decorator.new(source)
461
+
462
+ expect{decorator.hello_world}.to raise_error NoMethodError
537
463
  end
538
- end
539
- end
540
464
 
541
- describe ".respond_to?" do
542
- subject { decorator_class }
465
+ it "does not delegate methods that do not exist on the source" do
466
+ decorator = Decorator.new(Model.new)
467
+
468
+ expect(decorator.methods).not_to include :hello_world
469
+ expect{decorator.hello_world}.to raise_error NoMethodError
470
+ expect(decorator.methods).not_to include :hello_world
471
+ end
472
+ end
543
473
 
544
- context "without a source class" do
545
- it "returns true for its own class methods" do
546
- subject.should respond_to :my_class_method
474
+ context ".method_missing" do
475
+ context "without a source class" do
476
+ it "raises a NoMethodError on missing methods" do
477
+ expect{Decorator.hello_world}.to raise_error NoMethodError
478
+ end
547
479
  end
548
480
 
549
- it "returns false for other class methods" do
550
- subject.should_not respond_to :sample_class_method
481
+ context "with a source class" do
482
+ it "delegates methods that exist on the source class" do
483
+ source_class = Class.new
484
+ source_class.stub hello_world: :delegated
485
+ Decorator.stub source_class: source_class
486
+
487
+ expect(Decorator.hello_world).to be :delegated
488
+ end
489
+
490
+ it "does not delegate methods that do not exist on the source class" do
491
+ Decorator.stub source_class: Class.new
492
+
493
+ expect{Decorator.hello_world}.to raise_error NoMethodError
494
+ end
551
495
  end
552
496
  end
553
497
 
554
- context "with a source_class" do
555
- before { subject.decorates :product }
498
+ describe "#respond_to?" do
499
+ it "returns true for its own methods" do
500
+ Decorator.class_eval{def hello_world; end}
501
+ decorator = Decorator.new(Model.new)
502
+
503
+ expect(decorator).to respond_to :hello_world
504
+ end
505
+
506
+ it "returns true for the source's methods" do
507
+ decorator = Decorator.new(double(hello_world: :delegated))
556
508
 
557
- it "returns true for its own class methods" do
558
- subject.should respond_to :my_class_method
509
+ expect(decorator).to respond_to :hello_world
559
510
  end
560
511
 
561
- it "returns true for the source's class methods" do
562
- subject.should respond_to :sample_class_method
512
+ context "with include_private" do
513
+ it "returns true for its own private methods" do
514
+ Decorator.class_eval{private; def hello_world; end}
515
+ decorator = Decorator.new(Model.new)
516
+
517
+ expect(decorator.respond_to?(:hello_world, true)).to be_true
518
+ end
519
+
520
+ it "returns false for the source's private methods" do
521
+ source = Class.new{private; def hello_world; end}.new
522
+ decorator = Decorator.new(source)
523
+
524
+ expect(decorator.respond_to?(:hello_world, true)).to be_false
525
+ end
563
526
  end
564
527
  end
565
- end
566
- end
567
528
 
568
- context "in a Rails application" do
569
- let(:decorator_class) { DecoratorWithApplicationHelper }
529
+ describe ".respond_to?" do
530
+ context "without a source class" do
531
+ it "returns true for its own class methods" do
532
+ Decorator.class_eval{def self.hello_world; end}
570
533
 
571
- it "has access to ApplicationHelper helpers" do
572
- subject.uses_hello_world.should == "Hello, World!"
573
- end
534
+ expect(Decorator).to respond_to :hello_world
535
+ end
574
536
 
575
- it "is able to use the content_tag helper" do
576
- subject.sample_content.to_s.should == "<span>Hello, World!</span>"
577
- end
537
+ it "returns false for other class methods" do
538
+ expect(Decorator).not_to respond_to :goodnight_moon
539
+ end
540
+ end
578
541
 
579
- it "is able to use the link_to helper" do
580
- subject.sample_link.should == %{<a href="/World">Hello</a>}
581
- end
542
+ context "with a source class" do
543
+ it "returns true for its own class methods" do
544
+ Decorator.class_eval{def self.hello_world; end}
545
+ Decorator.stub source_class: Class.new
582
546
 
583
- it "is able to use the truncate helper" do
584
- subject.sample_truncate.should == "Once..."
585
- end
547
+ expect(Decorator).to respond_to :hello_world
548
+ end
549
+
550
+ it "returns true for the source's class methods" do
551
+ Decorator.stub source_class: double(hello_world: :delegated)
586
552
 
587
- it "is able to access html_escape, a private method" do
588
- subject.sample_html_escaped_text.should == '&lt;script&gt;danger&lt;/script&gt;'
553
+ expect(Decorator).to respond_to :hello_world
554
+ end
555
+ end
556
+ end
589
557
  end
590
- end
591
558
 
592
- it "pretends to be the source class" do
593
- subject.kind_of?(source.class).should be_true
594
- subject.is_a?(source.class).should be_true
595
- end
559
+ describe "class spoofing" do
560
+ it "pretends to be a kind of the source class" do
561
+ decorator = Decorator.new(Model.new)
596
562
 
597
- it "is still its own class" do
598
- subject.kind_of?(subject.class).should be_true
599
- subject.is_a?(subject.class).should be_true
600
- end
563
+ expect(decorator.kind_of?(Model)).to be_true
564
+ expect(decorator.is_a?(Model)).to be_true
565
+ end
601
566
 
602
- it "pretends to be an instance of the source class" do
603
- subject.instance_of?(source.class).should be_true
604
- end
567
+ it "is still a kind of its own class" do
568
+ decorator = Decorator.new(Model.new)
605
569
 
606
- it "is still an instance of its own class" do
607
- subject.instance_of?(subject.class).should be_true
608
- end
570
+ expect(decorator.kind_of?(Decorator)).to be_true
571
+ expect(decorator.is_a?(Decorator)).to be_true
572
+ end
573
+
574
+ it "pretends to be an instance of the source class" do
575
+ decorator = Decorator.new(Model.new)
576
+
577
+ expect(decorator.instance_of?(Model)).to be_true
578
+ end
579
+
580
+ it "is still an instance of its own class" do
581
+ decorator = Decorator.new(Model.new)
609
582
 
610
- describe ".decorates_finders" do
611
- it "extends the Finders module" do
612
- ProductDecorator.should be_a_kind_of Draper::Finders
583
+ expect(decorator.instance_of?(Decorator)).to be_true
584
+ end
613
585
  end
614
- end
615
586
 
616
- describe "#serializable_hash" do
617
- let(:decorator_class) { ProductDecorator }
587
+ describe ".decorates_finders" do
588
+ protect_class Decorator
618
589
 
619
- it "serializes overridden attributes" do
620
- subject.serializable_hash[:overridable].should be :overridden
590
+ it "extends the Finders module" do
591
+ expect(Decorator).not_to be_a_kind_of Finders
592
+ Decorator.decorates_finders
593
+ expect(Decorator).to be_a_kind_of Finders
594
+ end
621
595
  end
622
- end
623
596
 
597
+ end
624
598
  end