phenomenal 0.11.11.24.4 → 0.99.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/LICENSE +1 -1
  2. data/README +3 -4
  3. data/Rakefile +3 -0
  4. data/lib/phenomenal.rb +15 -2
  5. data/lib/phenomenal/adaptation.rb +22 -12
  6. data/lib/phenomenal/context.rb +127 -134
  7. data/lib/phenomenal/dsl.rb +41 -14
  8. data/lib/phenomenal/feature.rb +8 -0
  9. data/lib/phenomenal/logger.rb +0 -1
  10. data/lib/phenomenal/manager.rb +117 -35
  11. data/lib/phenomenal/relationships/context_relationships.rb +22 -0
  12. data/lib/phenomenal/relationships/dsl.rb +18 -0
  13. data/lib/phenomenal/relationships/feature_relationships.rb +42 -0
  14. data/lib/phenomenal/relationships/implication.rb +35 -0
  15. data/lib/phenomenal/relationships/relationship.rb +42 -0
  16. data/lib/phenomenal/relationships/relationships_manager.rb +63 -0
  17. data/lib/phenomenal/relationships/relationships_store.rb +73 -0
  18. data/lib/phenomenal/relationships/requirement.rb +26 -0
  19. data/lib/phenomenal/relationships/suggestion.rb +41 -0
  20. data/lib/phenomenal/version.rb +3 -0
  21. data/spec/adaptation_spec.rb +64 -0
  22. data/spec/behavior/adaptation_spec.rb +5 -0
  23. data/spec/behavior/combined_contexts_spec.rb +5 -0
  24. data/spec/behavior/composition_spec.rb +5 -0
  25. data/spec/behavior/conflict_policy_spec.rb +5 -0
  26. data/spec/behavior/open_context.rb +5 -0
  27. data/spec/behavior/relationships_spec.rb +249 -0
  28. data/spec/context_spec.rb +268 -0
  29. data/spec/dsl_spec.rb +181 -0
  30. data/spec/feature_spec.rb +5 -0
  31. data/spec/manager_spec.rb +84 -0
  32. data/spec/proc_spec.rb +20 -0
  33. data/spec/relationships/context_relationships_spec.rb +13 -0
  34. data/spec/relationships/dsl_spec.rb +13 -0
  35. data/spec/relationships/feature_relationships_spec.rb +13 -0
  36. data/spec/relationships/relationship_spec.rb +31 -0
  37. data/spec/relationships/relationships_manager_spec.rb +15 -0
  38. data/spec/relationships/relationships_store_spec.rb +19 -0
  39. data/spec/spec_helper.rb +18 -0
  40. data/{test → spec}/test_classes.rb +3 -0
  41. metadata +69 -24
  42. data/demo.rb +0 -24
  43. data/demo_age.rb +0 -89
  44. data/demo_dsl.rb +0 -28
  45. data/phenomenal-0.11.11.24.3.gem +0 -0
  46. data/phenomenal.gemspec +0 -15
  47. data/test/test_cop_adaptation.rb +0 -168
  48. data/test/test_cop_composition.rb +0 -84
  49. data/test/test_cop_conflictpolicy.rb +0 -177
  50. data/test/test_cop_infrastructure.rb +0 -129
  51. data/test_declaration.rb +0 -18
@@ -0,0 +1,42 @@
1
+ module Phenomenal::FeatureRelationships
2
+ attr_accessor :relationships
3
+
4
+ def initialize_relationships
5
+ @relationships = Array.new
6
+ end
7
+
8
+ def requirements_for(source,targets)
9
+ add_relationship(source,targets,Phenomenal::Requirement)
10
+ end
11
+
12
+ def implications_for(source,targets)
13
+ add_relationship(source,targets,Phenomenal::Implication)
14
+ end
15
+
16
+ def suggestions_for(source,targets)
17
+ add_relationship(source,targets,Phenomenal::Suggestion)
18
+ end
19
+
20
+ private
21
+ def add_relationship(source,targets,type)
22
+ targets[:on]=Array.new.push(targets[:on]) if !targets[:on].is_a?(Array)
23
+
24
+ if targets[:on].nil?
25
+ Phenomenal::Logger.instance.error(
26
+ "Invalid relationship, missing target context"
27
+ )
28
+ end
29
+ targets[:on].each do |target|
30
+ r = type.new(source,target,self)
31
+ if relationships.find{|o| o==r}.nil?
32
+ relationships.push(r)
33
+ if self.active?
34
+ r.refresh
35
+ manager.rmanager.relationships.add(r)
36
+ r.activate_feature
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,35 @@
1
+ class Phenomenal::Implication < Phenomenal::Relationship
2
+ attr_accessor :activation_counter
3
+ def initialize(source,target,feature)
4
+ super(source,target,feature)
5
+ @activation_counter=0
6
+ end
7
+
8
+ def activate_feature
9
+ if source.active?
10
+ target.activate
11
+ activation_counter+=1
12
+ end
13
+ end
14
+
15
+ def deactivate_feature
16
+ if activation_counter>0
17
+ target.deactivate
18
+ activation_coutner-=1
19
+ end
20
+ end
21
+
22
+ def activate_context(context)
23
+ if source==context
24
+ target.activate
25
+ end
26
+ end
27
+
28
+ def deactivate_context(context)
29
+ if source==context
30
+ target.deactivate
31
+ else
32
+ source.deactivate
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ class Phenomenal::Relationship
2
+ attr_accessor :source,:target,:manager,:feature
3
+
4
+ def initialize(source,target,feature)
5
+ @source=source
6
+ @target=target
7
+ @manager=Phenomenal::Manager.instance
8
+ @feature=feature
9
+ refresh
10
+ end
11
+
12
+ def ==(other)
13
+ self.class==other.class &&
14
+ self.source==other.source &&
15
+ self.target==other.target &&
16
+ self.feature==other.feature
17
+ end
18
+
19
+ def refresh
20
+ s = manager.context_defined?(source)
21
+ self.source=s if !s.nil?
22
+
23
+ t = manager.context_defined?(target)
24
+ self.target=t if !t.nil?
25
+ end
26
+
27
+ def activate_context(context)
28
+ end
29
+
30
+ def deactivate_context(context)
31
+ end
32
+
33
+ def activate_feature
34
+ end
35
+
36
+ def deactivate_feature
37
+ end
38
+
39
+ def to_s
40
+ "#{self.class.name} between #{source.class.name}:#{source} and #{target.class.name}:#{target}"
41
+ end
42
+ end
@@ -0,0 +1,63 @@
1
+ require 'singleton'
2
+
3
+ class Phenomenal::RelationshipsManager
4
+ include Singleton
5
+
6
+ attr_accessor :relationships
7
+
8
+ def activate_relationships(context)
9
+ # Step 1: Import the new relationships for a feature
10
+ if context.is_a?(Phenomenal::Feature)
11
+ import_relationships(context)
12
+ end
13
+ # Step 2: Apply relationships
14
+ relationships.get_for(context).each do |relationship|
15
+ relationship.activate_context(context)
16
+ end
17
+ end
18
+
19
+ def deactivate_relationships(context)
20
+ # Step 1: Unapply relationships
21
+ relationships.get_for(context).each do |relationship|
22
+ relationship.deactivate_context(context)
23
+ end
24
+ # Step 2: Remove relationships
25
+ if context.is_a?(Phenomenal::Feature)
26
+ remove_relationships(context)
27
+ end
28
+ end
29
+
30
+ # Called when a context is defined in the manager
31
+ def update_relationships_references(context)
32
+ relationships.update_references(context)
33
+ end
34
+
35
+ private
36
+ def import_relationships(feature)
37
+ begin
38
+ feature.relationships.each do |relationship|
39
+ relationship.refresh # Update references
40
+ relationship.activate_feature # Activate relationship
41
+ relationships.add(relationship)
42
+ end
43
+ rescue Phenomenal::Error => m
44
+ feature.deactivate
45
+ Phenomenal::Logger.instance.debug(
46
+ "Unable to activate the feature #{feature} \n #{m}"
47
+ )
48
+ end
49
+ end
50
+
51
+ def remove_relationships(feature)
52
+ feature.relationships.each do |relationship|
53
+ if relationships.include?(relationship)
54
+ relationship.deactivate_feature
55
+ relationships.remove(relationship)
56
+ end
57
+ end
58
+ end
59
+
60
+ def initialize
61
+ @relationships = Phenomenal::RelationshipsStore.new
62
+ end
63
+ end
@@ -0,0 +1,73 @@
1
+ class Phenomenal::RelationshipsStore
2
+ attr_accessor :sources, :targets
3
+
4
+ def initialize
5
+ @sources = {}
6
+ @targets = {}
7
+ end
8
+
9
+ def add(relationship)
10
+ if @sources[relationship.source].nil?
11
+ @sources[relationship.source] = Array.new
12
+ end
13
+ @sources[relationship.source].push(relationship)
14
+
15
+ if @targets[relationship.target].nil?
16
+ @targets[relationship.target] = Array.new
17
+ end
18
+ @targets[relationship.target].push(relationship)
19
+ end
20
+
21
+ def remove(relationship)
22
+ @sources[relationship.source].delete(relationship) if @sources[relationship.source] # In case of rollback
23
+ @targets[relationship.target].delete(relationship) if @targets[relationship.target]
24
+ end
25
+
26
+ def include?(relationship)
27
+ if @sources[relationship.source]
28
+ @sources[relationship.source].include?(relationship)
29
+ else
30
+ false
31
+ end
32
+ end
33
+
34
+ def update_references(context)
35
+ # Do nothing when anonymous, references are already valid
36
+ return if context.anonymous?
37
+ # Update sources
38
+ @sources[context.name].each do |relationship|
39
+ relationship.source=context
40
+ end
41
+ @sources[context]=@source.delete(context.name)
42
+ # Update targets
43
+ @targets[context.name].each do |relationship|
44
+ relationship.target=context
45
+ end
46
+ @targets[context]=@targets.delete(context.name)
47
+ end
48
+
49
+ def get_for(context)
50
+ get_for_source(context).concat(get_for_target(context))
51
+ end
52
+
53
+ private
54
+ # Return an array of relationships
55
+ def get_for_source(source)
56
+ rel = @sources[source]
57
+ if rel.nil?
58
+ Array.new
59
+ else
60
+ rel
61
+ end
62
+ end
63
+
64
+ # Return an array of relationships
65
+ def get_for_target(target)
66
+ rel = @targets[target]
67
+ if rel.nil?
68
+ Array.new
69
+ else
70
+ rel
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,26 @@
1
+ class Phenomenal::Requirement < Phenomenal::Relationship
2
+ def activate_feature
3
+ check_requirement
4
+ end
5
+
6
+ def activate_context(context)
7
+ if(source==context)
8
+ check_requirement
9
+ end
10
+ end
11
+
12
+ def deactivate_context(context)
13
+ if(target==context)
14
+ source.deactivate
15
+ end
16
+ end
17
+
18
+ private
19
+ def check_requirement
20
+ if source.active? && !target.active?
21
+ Phenomenal::Logger.instance.error(
22
+ "Requirement of #{target} for #{source} is not satisfied"
23
+ )
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ class Phenomenal::Suggestion < Phenomenal::Relationship
2
+ attr_accessor :activation_counter
3
+
4
+ def initialize(source,target,feature)
5
+ super(source,target,feature)
6
+ @activation_counter=0
7
+ end
8
+
9
+ def activate_feature
10
+ begin
11
+ if source.active?
12
+ target.activate
13
+ self.activation_counter+=1
14
+ end
15
+ rescue
16
+ end
17
+ end
18
+
19
+ def deactivate_feature
20
+ begin
21
+ if activation_counter>0
22
+ target.deactivate
23
+ self.activation_coutner-=1
24
+ end
25
+ rescue
26
+ end
27
+ end
28
+
29
+
30
+ def activate_context(context)
31
+ if source==context
32
+ target.activate
33
+ end
34
+ end
35
+
36
+ def deactivate_context(context)
37
+ if source==context
38
+ target.deactivate
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module Phenomenal
2
+ VERSION = "0.99.0"
3
+ end
@@ -0,0 +1,64 @@
1
+ require "spec_helper"
2
+ describe Phenomenal::Adaptation do
3
+ before :each do
4
+ define_test_classes
5
+ end
6
+
7
+ describe "#deploy" do
8
+ it "should be able to deploy itself in instances to override default implementation" do
9
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:length,true,Proc.new{-1})
10
+ t = TestString.new("1234")
11
+ t.length.should == 4
12
+ expect {adaptation.deploy}.to_not raise_error
13
+ t.length.should == -1
14
+ end
15
+
16
+ it "should be able to deploy itself in classes to override default implementation" do
17
+ TestString.name.should == "TestString"
18
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:name,false,Proc.new{"TEST"})
19
+ expect {adaptation.deploy}.to_not raise_error
20
+ TestString.name.should == "TEST"
21
+ end
22
+ end
23
+
24
+ describe "#bind" do
25
+ it "should be possible to temporary bind adapation as instance methods" do
26
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:length,true,Proc.new{-10})
27
+ t = TestString.new("1234")
28
+ t.length.should == -1
29
+ adaptation.bind(t).should == -10
30
+ t.length.should == -1
31
+ end
32
+
33
+ it "should be possible to temporary bind adapatation as class methods" do
34
+ TestString.name.should == "TEST"
35
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:name,false,Proc.new{"TEST2"})
36
+ adaptation.bind(TestString).should == "TEST2"
37
+ TestString.name.should == "TEST"
38
+ end
39
+ end
40
+
41
+ describe "#instance_adaptation?" do
42
+ it "should return true if the method is a instance method" do
43
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:length,true,Proc.new{-10})
44
+ adaptation.instance_adaptation?.should be_true
45
+ end
46
+
47
+ it "should return false if the method is a class method" do
48
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:name,false,Proc.new{"TEST2"})
49
+ adaptation.instance_adaptation?.should be_false
50
+ end
51
+ end
52
+
53
+ describe "#concern" do
54
+ it "should return true if the adaptation concern the class n_klass and method n_method and is instance method if instance=true" do
55
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:length,true,Proc.new{-10})
56
+ adaptation.concern?(TestString,:length,true).should be_true
57
+ end
58
+
59
+ it "should return false if the adaptation doesn't concern the class n_klass and method n_method and is instance method if instance=true" do
60
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:size,true,Proc.new{-10})
61
+ adaptation.concern?(TestString,:length,true).should be_false
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe "Simple adaptations" do
4
+ pending "TODO"
5
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe "Combined contexts" do
4
+ pending "TODO"
5
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe "Composition of adaptations" do
4
+ pending "TODO"
5
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe "Conflict policies" do
4
+ pending "TODO"
5
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe "Open context facilities" do
4
+ pending "TODO"
5
+ end
@@ -0,0 +1,249 @@
1
+ require "spec_helper"
2
+
3
+ describe "Relationships" do
4
+ before :each do
5
+ @manager = Phenomenal::Manager.instance
6
+ @feature = Phenomenal::Feature.new(:feature)
7
+ @context_names = [:a,:b,:c,:d,:e,:f,:g]
8
+ @context_names.each do |name|
9
+ @feature.context(name)
10
+ end
11
+
12
+ end
13
+
14
+ after :each do
15
+ force_forget_context(@feature)
16
+ @context_names.each do |name|
17
+ force_forget_context(name)
18
+ end
19
+ @manager.default_context.deactivate
20
+ @manager.default_context.forget
21
+ end
22
+
23
+ describe Phenomenal::Feature do
24
+ it "should be able to add requirements for contexts" do
25
+ @feature.should respond_to :requirements_for
26
+ @feature.method(:requirements_for).arity.should be(2),
27
+ "Bad arity, should be 2"
28
+ end
29
+ it "should be able to add implications for contexts" do
30
+ @feature.should respond_to :implications_for
31
+ @feature.method(:implications_for).arity.should be(2),
32
+ "Bad arity, should be 2"
33
+ end
34
+ it "should be able to add suggestions for contexts" do
35
+ @feature.should respond_to :suggestions_for
36
+ @feature.method(:suggestions_for).arity.should be(2),
37
+ "Bad arity, should be 2"
38
+ end
39
+
40
+ describe "Requirements" do
41
+ it "should store requirements" do
42
+ @feature.requirements_for :a, :on=>[:b,:c,:d]
43
+ @feature.requirements_for :a, :on=>:e
44
+ @feature.relationships.should have(4).items
45
+ end
46
+
47
+ it "should avoid activation with missing requirements" do
48
+ @feature.requirements_for :a, :on=>[:b,:c]
49
+ phen_activate_context(:feature)
50
+ expect {phen_activate_context(:a)}.to raise_error Phenomenal::Error
51
+ phen_context_active?(:a).should be_false
52
+ end
53
+
54
+ it "should allow activation with all requirements" do
55
+ phen_activate_context(:feature)
56
+ phen_activate_context(:b)
57
+ phen_activate_context(:c)
58
+ expect {phen_activate_context(:a)}.to_not raise_error
59
+ phen_context_active?(:a).should be_true
60
+ end
61
+
62
+ it "should deactivate source when target requirement is deactivated" do
63
+ @feature.requirements_for :a, :on=>[:b,:c]
64
+ phen_activate_context(:feature)
65
+ phen_activate_context(:b)
66
+ phen_activate_context(:c)
67
+ phen_activate_context(:a)
68
+ expect {phen_deactivate_context(:b)}.to_not raise_error
69
+ phen_context_active?(:a).should be_false
70
+ end
71
+
72
+ it "should avoid feature activation when adding a not satisfied requirement" do
73
+ @feature.requirements_for :a, :on=>[:b,:c]
74
+ phen_activate_context(:a)
75
+ expect{phen_activate_context(:feature)}.to_not raise_error
76
+ phen_context_active?(:feature).should be_false
77
+ end
78
+
79
+ after do
80
+ @manager.default_context.deactivate
81
+ @manager.default_context.forget
82
+ end
83
+ it "should be possible to add requirements to the default context" do
84
+ requirements_for :a, :on=>[:b,:c,:d]
85
+ requirements_for :a, :on=>:e
86
+ @manager.default_context.relationships.should have(4).items
87
+ end
88
+
89
+ it "should be possible to put requirements in the nested contexts" do
90
+ feature :feature do
91
+ context :a do
92
+ requires :b
93
+ end
94
+ end
95
+ @feature.relationships.should have(1).items
96
+ end
97
+
98
+ it "should be possible to put requirements in combined nested contexts" do
99
+ feature :feature do
100
+ context :a,:b do
101
+ requires :c,:d,:e
102
+ end
103
+ end
104
+ @feature.relationships.should have(3).items
105
+ end
106
+ end
107
+ describe "Implications" do
108
+ it "should store implications" do
109
+ @feature.implications_for :a, :on=>[:b,:c,:d]
110
+ @feature.implications_for :a, :on=>:e
111
+ @feature.relationships.should have(4).items
112
+ end
113
+
114
+ it "should activate target when source is activated" do
115
+ @feature.implications_for :a, :on=>:b
116
+ @feature.activate
117
+ expect {phen_activate_context(:a)}.to_not raise_error
118
+ phen_context_active?(:a).should be_true
119
+ phen_context_active?(:b).should be_true
120
+ end
121
+
122
+ it "should deactivate target when source is deactivated" do
123
+ @feature.implications_for :a, :on=>:b
124
+ @feature.activate
125
+ phen_activate_context(:a)
126
+ phen_deactivate_context(:a)
127
+ phen_context_active?(:b).should be_false
128
+ end
129
+
130
+ it "should deactivate source when target is deactivated" do
131
+ @feature.implications_for :a, :on=>:b
132
+ @feature.activate
133
+ phen_activate_context(:a)
134
+ phen_deactivate_context(:b)
135
+ phen_context_active?(:a).should be_false
136
+ end
137
+
138
+ it "should be possible to put implications in the nested contexts" do
139
+ feature :feature do
140
+ context :a do
141
+ implies :b
142
+ end
143
+ end
144
+ @feature.relationships.should have(1).items
145
+ end
146
+
147
+ it "should be possible to put implications in combined nested contexts" do
148
+ feature :feature do
149
+ context :a,:b do
150
+ implies :c,:d,:e
151
+ end
152
+ end
153
+ @feature.relationships.should have(3).items
154
+ end
155
+ end
156
+
157
+ describe "Suggestions" do
158
+ it "should be working on the default feature" do
159
+ suggestions_for :a,:on=>:b
160
+ @manager.default_context.relationships.should have(1).items
161
+ context(:a).active?.should be_false
162
+ context(:b).active?.should be_false
163
+ expect {activate_context :a}.to_not raise_error
164
+ context(:a).active?.should be_true
165
+ context(:b).active?.should be_true
166
+ expect {deactivate_context :a}.to_not raise_error
167
+ context(:a).active?.should be_false
168
+ context(:b).active?.should be_false
169
+ #TODO forget default
170
+ end
171
+
172
+ it "should be possible to add relationships on active features" do
173
+ context(:a).active?.should be_false
174
+ context(:b).active?.should be_false
175
+ expect {activate_context :a}.to_not raise_error
176
+ suggestions_for :a,:on=>:b
177
+ @manager.default_context.relationships.should have(1).items
178
+ context(:a).active?.should be_true
179
+ context(:b).active?.should be_true
180
+ expect {deactivate_context :a}.to_not raise_error
181
+ context(:a).active?.should be_false
182
+ context(:b).active?.should be_false
183
+ end
184
+
185
+ it "should apply the relationship on the activation of the feature that contain it" do
186
+ context(:a).active?.should be_false
187
+ context(:b).active?.should be_false
188
+ expect {activate_context :a}.to_not raise_error
189
+ @feature.suggestions_for :a,:on=>:b
190
+ context(:a).active?.should be_true
191
+ context(:b).active?.should be_false
192
+ expect {activate_context @feature}.to_not raise_error
193
+ context(:a).active?.should be_true
194
+ context(:b).active?.should be_true
195
+ expect {deactivate_context @feature}.to_not raise_error
196
+ context(:a).active?.should be_true
197
+ context(:b).active?.should be_false
198
+ end
199
+
200
+ it "should store suggestions" do
201
+ @feature.suggestions_for :a, :on=>[:b,:c,:d]
202
+ @feature.suggestions_for :a, :on=>:e
203
+ @feature.relationships.should have(4).items
204
+ end
205
+
206
+ it "should activate target when source is activated" do
207
+ @feature.suggestions_for :a, :on=>:b
208
+ @feature.activate
209
+ expect {phen_activate_context(:a)}.to_not raise_error
210
+ phen_context_active?(:a).should be_true
211
+ phen_context_active?(:b).should be_true
212
+ end
213
+
214
+ it "should deactivate target when source is deactivated" do
215
+ @feature.suggestions_for :a, :on=>:b
216
+ @feature.activate
217
+ phen_activate_context(:a)
218
+ phen_deactivate_context(:a)
219
+ phen_context_active?(:b).should be_false
220
+ end
221
+
222
+ it "should not deactivate source when target is deactivated" do
223
+ @feature.suggestions_for :a, :on=>:b
224
+ @feature.activate
225
+ phen_activate_context(:a)
226
+ phen_deactivate_context(:b)
227
+ phen_context_active?(:a).should be_true
228
+ end
229
+
230
+ it "should be possible to put suggestions in the nested contexts" do
231
+ feature :feature do
232
+ context :a do
233
+ suggests :b
234
+ end
235
+ end
236
+ @feature.relationships.should have(1).items
237
+ end
238
+
239
+ it "should be possible to put suggestions in combined nested contexts" do
240
+ feature :feature do
241
+ context :a,:b do
242
+ suggests :c,:d,:e
243
+ end
244
+ end
245
+ @feature.relationships.should have(3).items
246
+ end
247
+ end
248
+ end
249
+ end