chione 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,6 +3,7 @@
3
3
  require_relative '../spec_helper'
4
4
 
5
5
  require 'chione/archetype'
6
+ require 'chione/aspect'
6
7
  require 'chione/component'
7
8
  require 'chione/entity'
8
9
  require 'chione/world'
@@ -57,6 +58,19 @@ describe Chione::Archetype do
57
58
  end
58
59
 
59
60
 
61
+ it "can be constructed from the Components specified in an Aspect" do
62
+ aspect = Chione::Aspect.with_all_of( location_component, tags_component )
63
+ archetype = described_class.from_aspect( aspect )
64
+
65
+ expect( archetype ).to be_a( Module )
66
+ expect( archetype.from_aspect ).to eq( aspect )
67
+
68
+ entity = archetype.construct_for( world )
69
+
70
+ expect( aspect ).to match( entity )
71
+ end
72
+
73
+
60
74
  it "is still loadable as an `Assemblage`" do
61
75
  expect {
62
76
  expect( Chione::Assemblage ).to equal( described_class )
@@ -4,20 +4,28 @@ require_relative '../spec_helper'
4
4
 
5
5
  require 'chione/aspect'
6
6
  require 'chione/component'
7
+ require 'chione/fixtures'
7
8
 
8
9
 
9
10
  describe Chione::Aspect do
10
11
 
12
+ before( :all ) do
13
+ Chione::Fixtures.load( :entities )
14
+ end
15
+
16
+
11
17
  let( :location ) do
12
18
  Class.new( Chione::Component ) do
13
19
  field :x, default: 0
14
20
  field :y, default: 0
21
+ def self::name; "Location"; end
15
22
  end
16
23
  end
17
24
 
18
25
  let( :tags ) do
19
26
  Class.new( Chione::Component ) do
20
27
  field :tags, default: []
28
+ def self::name; "Tags"; end
21
29
  end
22
30
  end
23
31
 
@@ -27,9 +35,29 @@ describe Chione::Aspect do
27
35
  field :shade, default: 0
28
36
  field :value, default: 0
29
37
  field :opacity, default: 0
38
+ def self::name; "Color"; end
30
39
  end
31
40
  end
32
41
 
42
+ let( :world ) { Chione::World.new }
43
+
44
+ let( :entity ) { Chione::Fixtures.entity(world) }
45
+ let( :entity1 ) do
46
+ entity.with_components( location ).instance
47
+ end
48
+ let( :entity2 ) do
49
+ entity.with_components( location, color ).instance
50
+ end
51
+ let( :entity3 ) do
52
+ entity.with_components( location, color, tags ).instance
53
+ end
54
+ let( :entity4 ) do
55
+ entity.with_components( color, tags ).instance
56
+ end
57
+ let( :entity5 ) do
58
+ entity.with_components( tags ).instance
59
+ end
60
+
33
61
 
34
62
  it "doesn't have any default criteria" do
35
63
  aspect = described_class.new
@@ -139,5 +167,67 @@ describe Chione::Aspect do
139
167
  expect( aspect.none_of ).to include( tags, location )
140
168
  end
141
169
 
170
+
171
+ it "can be inverted"
172
+
173
+
174
+ describe "entity-matching" do
175
+
176
+ let( :all_entities ) {[ entity1, entity2, entity3, entity4, entity5 ]}
177
+
178
+
179
+ it "can find the matching subset of values given a Hash keyed by Components" do
180
+ entities = all_entities()
181
+
182
+ aspect = described_class.with_all_of( color, location ).and_none_of( tags )
183
+ result = aspect.matching_entities( world.entities_by_component )
184
+
185
+ expect( result ).to contain_exactly( entity2.id )
186
+ end
187
+
188
+
189
+ it "always matches an individual entity if it's empty" do
190
+ aspect = described_class.new
191
+
192
+ expect( aspect ).to match( entity1 )
193
+ expect( aspect ).to match( entity2 )
194
+ expect( aspect ).to match( entity3 )
195
+ expect( aspect ).to match( entity4 )
196
+ expect( aspect ).to match( entity5 )
197
+ end
198
+
199
+
200
+ it "matches an individual entity if its components meet the criteria" do
201
+ aspect = described_class.with_all_of( color, location ).and_none_of( tags )
202
+
203
+ expect( aspect ).to_not match( entity1 )
204
+ expect( aspect ).to match( entity2 )
205
+ expect( aspect ).to_not match( entity3 )
206
+ expect( aspect ).to_not match( entity4 )
207
+ expect( aspect ).to_not match( entity5 )
208
+
209
+ aspect = described_class.with_one_of( tags, color )
210
+
211
+ expect( aspect ).to_not match( entity1 )
212
+ expect( aspect ).to match( entity2 )
213
+ expect( aspect ).to match( entity3 )
214
+ expect( aspect ).to match( entity4 )
215
+ expect( aspect ).to match( entity5 )
216
+ end
217
+
218
+
219
+ it "matches a component hash if it meets the criteria" do
220
+ aspect = described_class.with_all_of( color, location ).and_none_of( tags )
221
+
222
+ expect( aspect ).to_not match( entity1.components )
223
+ expect( aspect ).to match( entity2.components )
224
+ expect( aspect ).to_not match( entity3.components )
225
+ expect( aspect ).to_not match( entity4.components )
226
+ expect( aspect ).to_not match( entity5.components )
227
+ end
228
+
229
+ end
230
+
231
+
142
232
  end
143
233
 
@@ -12,6 +12,7 @@ describe Chione::Component do
12
12
  let( :component_subclass ) do
13
13
  Class.new( described_class )
14
14
  end
15
+ let( :entity_id ) { '73BA6CB5-50CB-4EA4-9941-5EBF17D5D379' }
15
16
 
16
17
 
17
18
  it "can declare fields" do
@@ -27,6 +28,19 @@ describe Chione::Component do
27
28
  end
28
29
 
29
30
 
31
+ it "includes a description of its fields in its inspection output" do
32
+ component_subclass.field( :name )
33
+ component_subclass.field( :x )
34
+ component_subclass.field( :y )
35
+
36
+ instance = component_subclass.new
37
+
38
+ expect( instance.inspect ).to match( /\bname:/ )
39
+ expect( instance.inspect ).to match( /\bx:/ )
40
+ expect( instance.inspect ).to match( /\by:/ )
41
+ end
42
+
43
+
30
44
  it "can declare fields with default values" do
31
45
  component_subclass.field( :x, default: 0 )
32
46
  component_subclass.field( :y, default: 18 )
@@ -37,6 +51,17 @@ describe Chione::Component do
37
51
  end
38
52
 
39
53
 
54
+ it "can declare fields with a block for validation or pre-processing" do
55
+ component_subclass.field( :coordinates, default: [0, 0] ) do |vals|
56
+ { x: Integer(vals[0]), y: Integer(vals[1]) }
57
+ end
58
+
59
+ instance = component_subclass.new( coordinates: [88, 19] )
60
+ expect( instance.coordinates[:x] ).to eq( 88 )
61
+ expect( instance.coordinates[:y] ).to eq( 19 )
62
+ end
63
+
64
+
40
65
  it "uses a dup of the default if it's not an immediate object" do
41
66
  component_subclass.field( :things, default: [] )
42
67
 
@@ -59,6 +84,18 @@ describe Chione::Component do
59
84
  expect( instance2.oid ).to eq( 121212 )
60
85
  end
61
86
 
87
+
88
+ it "can be created with an entity ID" do
89
+ component_subclass.field( :x )
90
+ component_subclass.field( :y )
91
+
92
+ instance = component_subclass.new( entity_id, x: 1, y: 18 )
93
+
94
+ expect( instance.entity_id ).to eq( entity_id )
95
+ expect( instance.x ).to eq( 1 )
96
+ expect( instance.y ).to eq( 18 )
97
+ end
98
+
62
99
  end
63
100
 
64
101
  end
@@ -98,51 +98,44 @@ describe Chione::Entity do
98
98
  end
99
99
 
100
100
 
101
- it "lets components be fetched from it" do
102
- entity.add_component( location_component )
103
- entity.add_component( tags_component )
101
+ it "can have components with init arguments added to it" do
102
+ entity.add_component( location_component, x: 18, y: 51 )
103
+ entity.add_component( tags_component, tags: ['trace', 'volatile'] )
104
104
 
105
- expect(
106
- entity.find_component( location_component )
107
- ).to eq( entity.components[location_component] )
105
+ expect( entity.get_component(location_component).x ).to eq( 18 )
106
+ expect( entity.get_component(location_component).y ).to eq( 51 )
107
+ expect( entity.get_component(tags_component).tags ).to contain_exactly('trace', 'volatile')
108
108
  end
109
109
 
110
110
 
111
- it "supports backward-compatible component-fetcher method" do
111
+ it "lets components be fetched for it" do
112
112
  entity.add_component( location_component )
113
113
  entity.add_component( tags_component )
114
114
 
115
115
  expect(
116
116
  entity.get_component( location_component )
117
- ).to eq( entity.components[location_component] )
117
+ ).to be_an_instance_of( location_component )
118
118
  end
119
119
 
120
120
 
121
- it "lets one of a list of components be fetched from it" do
121
+ it "can have components removed from it" do
122
122
  entity.add_component( location_component )
123
123
  entity.add_component( tags_component )
124
124
 
125
- expect(
126
- entity.find_component( bounding_box_component, location_component )
127
- ).to eq( entity.components[location_component] )
128
- end
129
-
125
+ entity.remove_component( tags_component )
130
126
 
131
- it "raises a KeyError if it doesn't have a fetched component" do
132
- entity.add_component( tags_component )
133
-
134
- expect {
135
- entity.find_component( location_component )
136
- }.to raise_error( KeyError, /#{entity.id} doesn't have/i )
127
+ expect( entity ).to have_component( location_component )
128
+ expect( entity ).to_not have_component( tags_component )
137
129
  end
138
130
 
139
131
 
140
- it "raises a KeyError if it doesn't have any of several fetched components" do
132
+ it "returns nil when fetching a component the entity doesn't have" do
133
+ entity.add_component( location_component )
141
134
  entity.add_component( tags_component )
142
135
 
143
- expect {
144
- entity.find_component( location_component, bounding_box_component )
145
- }.to raise_error( KeyError, /#{entity.id} doesn't have any of/i )
136
+ expect(
137
+ entity.get_component( bounding_box_component )
138
+ ).to be_nil
146
139
  end
147
140
 
148
141
  end
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env rspec -cfd
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'chione/iterating_system'
6
+ require 'chione/fixtures'
7
+
8
+
9
+ describe Chione::IteratingSystem do
10
+
11
+ before( :all ) do
12
+ Chione::Fixtures.load( :entities )
13
+ @component_classes = Chione::Component.derivatives.dup
14
+ end
15
+ before( :each ) do
16
+ Chione::Component.derivatives.clear
17
+ end
18
+ after( :all ) do
19
+ Chione::Component.derivatives.replace( @component_classes )
20
+ end
21
+
22
+
23
+ let( :subclass ) { Class.new(described_class) }
24
+ let( :world ) { Chione::World.new }
25
+ let( :location_component ) do
26
+ klass = Class.new( Chione::Component ) do
27
+ def self::name; "Location"; end
28
+ field :x, default: 0
29
+ field :y, default: 0
30
+ end
31
+ Chione::Component.derivatives['location'] = klass
32
+ klass
33
+ end
34
+
35
+ let( :tags_component ) do
36
+ klass = Class.new( Chione::Component ) do
37
+ def self::name; "Tags"; end
38
+ field :tags, default: []
39
+ end
40
+ Chione::Component.derivatives['tags'] = klass
41
+ klass
42
+ end
43
+
44
+ let( :bounding_box_component ) do
45
+ klass = Class.new( Chione::Component ) do
46
+ def self::name; "BoundingBox"; end
47
+ field :width, default: 1
48
+ field :height, default: 1
49
+ field :depth, default: 1
50
+ end
51
+ Chione::Component.derivatives['bounding_box'] = klass
52
+ klass
53
+ end
54
+
55
+
56
+ describe "instances" do
57
+
58
+ let( :instance ) { subclass.new(world) }
59
+
60
+
61
+ it "requires an override for the #process method" do
62
+ expect( instance ).to respond_to( :process )
63
+ expect {
64
+ instance.process( :default, 'entity-id', {} )
65
+ }.to raise_error( NotImplementedError, /#process/i )
66
+ end
67
+
68
+
69
+ describe "with a process method" do
70
+
71
+ let( :entity ) { Chione::Fixtures.entity(world) }
72
+
73
+
74
+ let( :subclass ) do
75
+ cls = super()
76
+ cls.class_exec(location_component, bounding_box_component) do |location, bbox|
77
+ aspect :default, all_of: [location, bbox]
78
+ aspect :emphemeral, all_of: [location], none_of: bbox
79
+
80
+ def initialize( * )
81
+ super
82
+ @calls = []
83
+ end
84
+
85
+ attr_reader :calls
86
+
87
+ def process( aspect_name, entity_id, components )
88
+ @calls << [ aspect_name, entity_id, components ]
89
+ end
90
+ end
91
+
92
+ cls
93
+ end
94
+
95
+
96
+ it "gets called for each entity which matches an aspect on each tick" do
97
+ entities_with_location = entity.with_components( location_component ).
98
+ generator.take( 5 ) # :ephemeral
99
+ entities_with_bb = entity.with_components( bounding_box_component ).
100
+ generator.take( 5 ) # -
101
+ entities_with_both = entity.
102
+ with_components( location_component, bounding_box_component ).
103
+ generator.take( 5 ) # :default
104
+
105
+ sys = world.add_system( subclass )
106
+ sys.start
107
+
108
+ world.publish( 'timing', 0.5, 1 )
109
+ world.publish_deferred_events
110
+
111
+ both_components = a_hash_including( location_component, bounding_box_component )
112
+ only_location_component = a_hash_including( location_component )
113
+
114
+ expect( sys.calls.length ).to eq( 10 )
115
+ expect( sys.calls ).to contain_exactly(
116
+ [:default, entities_with_both[0].id, both_components ],
117
+ [:default, entities_with_both[1].id, both_components ],
118
+ [:default, entities_with_both[2].id, both_components ],
119
+ [:default, entities_with_both[3].id, both_components ],
120
+ [:default, entities_with_both[4].id, both_components ],
121
+ [:emphemeral, entities_with_location[0].id, only_location_component ],
122
+ [:emphemeral, entities_with_location[1].id, only_location_component ],
123
+ [:emphemeral, entities_with_location[2].id, only_location_component ],
124
+ [:emphemeral, entities_with_location[3].id, only_location_component ],
125
+ [:emphemeral, entities_with_location[4].id, only_location_component ]
126
+ )
127
+ end
128
+
129
+ end
130
+
131
+ end
132
+
133
+
134
+ end
135
+
@@ -3,10 +3,16 @@
3
3
  require_relative '../spec_helper'
4
4
 
5
5
  require 'chione/system'
6
+ require 'chione/fixtures'
6
7
 
7
8
 
8
9
  describe Chione::System do
9
10
 
11
+ before( :all ) do
12
+ Chione::Fixtures.load( :entities )
13
+ end
14
+
15
+
10
16
  let( :location_component ) do
11
17
  Class.new( Chione::Component ) do
12
18
  field :x, default: 0
@@ -30,40 +36,218 @@ describe Chione::System do
30
36
  describe "subclass" do
31
37
 
32
38
  let( :subclass ) do
33
- Class.new(described_class)
39
+ Class.new( described_class ) do
40
+ def initialize( * )
41
+ super
42
+ @calls = []
43
+ end
44
+ attr_reader :calls
45
+
46
+ def inserted( *args )
47
+ self.calls << [ __method__, args ]
48
+ super
49
+ end
50
+ def removed( *args )
51
+ self.calls << [ __method__, args ]
52
+ super
53
+ end
54
+ end
34
55
  end
35
56
  let( :world ) { Chione::World.new }
36
57
 
37
58
 
38
- it "has a default Aspect which matches all entities" do
39
- expect( subclass.aspect ).to be_empty
40
- end
59
+ describe "aspects" do
60
+
61
+ it "has a default Aspect which matches all entities" do
62
+ expect( subclass.aspects ).to be_empty
63
+ expect( subclass.aspects[:default] ).to be_empty
64
+ end
65
+
66
+
67
+ it "can declare components for its default aspect" do
68
+ subclass.aspect :default,
69
+ all_of: volition_component,
70
+ one_of: [ tags_component, location_component ]
71
+
72
+ expect( subclass.aspects.keys ).to contain_exactly( :default )
73
+ expect( subclass.aspects[:default].all_of ).to include( volition_component )
74
+ expect( subclass.aspects[:default].one_of ).
75
+ to include( tags_component, location_component )
76
+ end
77
+
78
+
79
+ it "can declare a named aspect" do
80
+ subclass.aspect( :with_location, all_of: location_component )
81
+
82
+ expect( subclass.aspects.keys ).to contain_exactly( :with_location )
83
+ expect( subclass.aspects[:with_location].all_of ).
84
+ to contain_exactly( location_component )
85
+ end
86
+
87
+
88
+ it "can declare a named aspect using simplified syntax" do
89
+ subclass.aspect( :with_location, location_component )
90
+
91
+ expect( subclass.aspects.keys ).to contain_exactly( :with_location )
92
+ expect( subclass.aspects[:with_location].all_of ).
93
+ to contain_exactly( location_component )
94
+ end
95
+
96
+
97
+ it "can declare more than one named aspect" do
98
+ subclass.aspect( :with_location, all_of: location_component )
99
+ subclass.aspect( :self_movable, all_of: [location_component, volition_component] )
100
+
101
+ expect( subclass.aspects.keys ).to contain_exactly( :with_location, :self_movable )
102
+ expect( subclass.aspects[:with_location].all_of ).to contain_exactly( location_component )
103
+ expect( subclass.aspects[:self_movable].all_of ).
104
+ to contain_exactly( location_component, volition_component )
105
+ end
106
+
107
+
108
+ it "keeps an explicitly-defined default aspect event if other named ones are added" do
109
+ subclass.aspect( :default, all_of: location_component )
110
+ subclass.aspect( :self_movable, all_of: [location_component, volition_component] )
111
+
112
+ expect( subclass.aspects.keys ).to contain_exactly( :default, :self_movable )
113
+ expect( subclass.aspects[:default].all_of ).to contain_exactly( location_component )
114
+ expect( subclass.aspects[:self_movable].all_of ).
115
+ to contain_exactly( location_component, volition_component )
116
+ end
117
+
41
118
 
119
+ it "is notified when adding a component causes an entity to start matching one of its aspects" do
120
+ subclass.aspect( :with_location, all_of: location_component )
121
+ subclass.aspect( :self_movable, all_of: [location_component, volition_component] )
122
+
123
+ entity = world.create_entity
124
+
125
+ sys = world.add_system( subclass )
126
+ sys.start
127
+
128
+ world.add_component_to( entity, location_component )
129
+ world.publish_deferred_events
130
+
131
+ expect( sys.calls.length ).to eq( 1 )
132
+ expect( sys.calls ).to include([
133
+ :inserted, [
134
+ :with_location,
135
+ entity.id,
136
+ {location_component => an_instance_of(location_component)}
137
+ ]
138
+ ])
139
+
140
+ world.add_component_to( entity, volition_component )
141
+ world.publish_deferred_events
142
+
143
+ expect( sys.calls.length ).to eq( 2 )
144
+ expect( sys.calls ).to include([
145
+ :inserted, [
146
+ :self_movable,
147
+ entity.id,
148
+ {
149
+ location_component => an_instance_of(location_component),
150
+ volition_component => an_instance_of(volition_component)
151
+ }
152
+ ]
153
+ ])
154
+ end
42
155
 
43
- it "can declare components for its aspect" do
44
- subclass.aspect all_of: volition_component,
45
- one_of: [ tags_component, location_component ]
46
156
 
47
- expect( subclass.aspect ).to_not be_empty
48
- expect( subclass.aspect.all_of ).to include( volition_component )
49
- expect( subclass.aspect.one_of ).to include( tags_component, location_component )
157
+ it "is notified when removing a component causes an entity to no longer match one of its aspects" do
158
+ subclass.aspect( :with_location, all_of: location_component )
159
+ subclass.aspect( :self_movable, all_of: [location_component, volition_component] )
160
+
161
+ entity = world.create_entity
162
+ world.add_component_to( entity, location_component )
163
+ world.add_component_to( entity, volition_component )
164
+ world.publish_deferred_events
165
+
166
+ sys = world.add_system( subclass )
167
+ sys.start
168
+
169
+ world.remove_component_from( entity, volition_component )
170
+ world.publish_deferred_events
171
+
172
+ expect( sys.calls.length ).to eq( 1 )
173
+ expect( sys.calls ).to include([
174
+ :removed, [
175
+ :self_movable,
176
+ entity.id,
177
+ {location_component => an_instance_of(location_component)}
178
+ ]
179
+ ])
180
+
181
+ world.remove_component_from( entity, location_component )
182
+ world.publish_deferred_events
183
+
184
+ expect( sys.calls.length ).to eq( 2 )
185
+ expect( sys.calls ).to include([
186
+ :removed, [
187
+ :with_location,
188
+ entity.id,
189
+ {}
190
+ ]
191
+ ])
192
+ end
193
+
50
194
  end
51
195
 
52
196
 
53
- it "can declare required components for its aspect via shorthand syntax" do
54
- subclass.for_entities_that_have( volition_component )
197
+ describe "event handlers" do
198
+
199
+ it "has no event handlers by default" do
200
+ expect( subclass.event_handlers ).to be_empty
201
+ end
202
+
203
+
204
+ it "can register a handler method for an event" do
205
+ subclass.on( 'entity/created' ) do |*|
206
+ # no-op
207
+ end
208
+
209
+ expect( subclass.event_handlers ).
210
+ to include( ['entity/created', 'on_entity_created_event'] )
211
+ expect( subclass.instance_methods ).to include( :on_entity_created_event )
212
+ end
213
+
214
+
215
+ it "provides a convenience declaration for the timing event" do
216
+ subclass.every_tick do |*|
217
+ # no-op
218
+ end
219
+
220
+ expect( subclass.event_handlers ).
221
+ to include( ['timing', 'on_timing_event'] )
222
+ expect( subclass.instance_methods ).to include( :on_timing_event )
223
+ end
224
+
225
+
226
+ it "unwraps arguments to `every_tick` callbacks" do
227
+ received_delta = received_tick = nil
228
+ subclass.every_tick do |delta, tick|
229
+ received_delta = delta
230
+ received_tick = tick
231
+ end
232
+
233
+ instance = subclass.new( world )
234
+ instance.on_timing_event( 'timing', [0.016666666666666666, 0] )
235
+
236
+ expect( received_delta ).to be_within( 0.00001 ).of( 0.016666666666666666 )
237
+ expect( received_tick ).to eq( 0 )
238
+ end
55
239
 
56
- expect( subclass.aspect ).to_not be_empty
57
- expect( subclass.aspect.all_of ).to include( volition_component )
58
240
  end
59
241
 
60
242
 
61
243
  describe "instance" do
62
244
 
63
245
  let( :subclass ) do
64
- subclass = super()
65
- subclass.aspect all_of: volition_component,
66
- one_of: [ tags_component, location_component ]
246
+ subclass = Class.new( described_class )
247
+ subclass.aspect( :default,
248
+ all_of: volition_component,
249
+ one_of: [tags_component, location_component] )
250
+ subclass.aspect( :tagged, all_of: tags_component )
67
251
  subclass
68
252
  end
69
253
 
@@ -72,20 +256,33 @@ describe Chione::System do
72
256
  end
73
257
 
74
258
 
75
- it "can enumerate the entities from the world that match its aspect" do
76
- ent1 = world.create_entity
77
- ent1.add_component( volition_component )
78
- ent1.add_component( tags_component )
259
+ before( :each ) do
260
+ @ent1 = world.create_entity
261
+ @ent1.add_component( volition_component )
262
+ @ent1.add_component( tags_component )
79
263
 
80
- ent2 = world.create_entity
81
- ent2.add_component( volition_component )
82
- ent2.add_component( location_component )
264
+ @ent2 = world.create_entity
265
+ @ent2.add_component( volition_component )
266
+ @ent2.add_component( location_component )
83
267
 
84
- ent3 = world.create_entity
85
- ent3.add_component( volition_component )
268
+ @ent3 = world.create_entity
269
+ @ent3.add_component( volition_component )
86
270
 
271
+ @ent4 = world.create_entity
272
+ @ent4.add_component( location_component )
273
+ @ent4.add_component( tags_component )
274
+ end
275
+
276
+
277
+ it "can enumerate the entities from the world that match its default aspect" do
87
278
  expect( instance.entities ).to be_a( Enumerator )
88
- expect( instance.entities ).to contain_exactly( ent1, ent2 )
279
+ expect( instance.entities ).to contain_exactly( @ent1.id, @ent2.id )
280
+ end
281
+
282
+
283
+ it "can enumerate the entities from the world that match one of its named aspects" do
284
+ expect( instance.entities(:tagged) ).to be_a( Enumerator )
285
+ expect( instance.entities(:tagged) ).to contain_exactly( @ent1.id, @ent4.id )
89
286
  end
90
287
 
91
288