draper 1.0.0 → 1.1.0

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