@latticexyz/recs 2.0.0-next.3 → 2.0.0-next.4

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.
@@ -0,0 +1,275 @@
1
+ import {
2
+ defineComponent,
3
+ setComponent,
4
+ removeComponent,
5
+ getComponentValue,
6
+ hasComponent,
7
+ withValue,
8
+ componentValueEquals,
9
+ getEntitiesWithValue,
10
+ overridableComponent,
11
+ } from "./Component";
12
+ import { Type } from "./constants";
13
+ import { createEntity, getEntitySymbol } from "./Entity";
14
+ import { AnyComponent, Entity, World } from "./types";
15
+ import { createWorld } from "./World";
16
+
17
+ describe("Component", () => {
18
+ let world: World;
19
+
20
+ beforeEach(() => {
21
+ world = createWorld();
22
+ });
23
+
24
+ it("emit changes to its stream", () => {
25
+ const entity = createEntity(world);
26
+ const component = defineComponent(world, { x: Type.Number, y: Type.Number });
27
+
28
+ const mock = jest.fn();
29
+ component.update$.subscribe((update) => {
30
+ mock(update);
31
+ });
32
+
33
+ setComponent(component, entity, { x: 1, y: 2 });
34
+ setComponent(component, entity, { x: 7, y: 2 });
35
+ setComponent(component, entity, { x: 7, y: 2 });
36
+ removeComponent(component, entity);
37
+
38
+ expect(mock).toHaveBeenNthCalledWith(1, { entity, value: [{ x: 1, y: 2 }, undefined], component });
39
+ expect(mock).toHaveBeenNthCalledWith(2, {
40
+ entity,
41
+ component,
42
+ value: [
43
+ { x: 7, y: 2 },
44
+ { x: 1, y: 2 },
45
+ ],
46
+ });
47
+ expect(mock).toHaveBeenNthCalledWith(3, {
48
+ entity,
49
+ component,
50
+ value: [
51
+ { x: 7, y: 2 },
52
+ { x: 7, y: 2 },
53
+ ],
54
+ });
55
+ expect(mock).toHaveBeenNthCalledWith(4, { entity, component, value: [undefined, { x: 7, y: 2 }] });
56
+ });
57
+
58
+ describe("defineComponent", () => {
59
+ it("should register the component in the world", () => {
60
+ expect(world.components.length).toBe(0);
61
+ defineComponent(world, { value: Type.Boolean });
62
+ expect(world.components.length).toBe(1);
63
+ });
64
+ });
65
+
66
+ describe("setComponent", () => {
67
+ let component: AnyComponent;
68
+ let entity: Entity;
69
+ let value: number;
70
+
71
+ beforeEach(() => {
72
+ component = defineComponent(world, { value: Type.Number });
73
+ entity = createEntity(world);
74
+ value = 1;
75
+ setComponent(component, entity, { value });
76
+ });
77
+
78
+ it("should store the component value", () => {
79
+ expect(component.values.value.get(getEntitySymbol(entity))).toBe(value);
80
+ });
81
+
82
+ it("should store the entity", () => {
83
+ expect(hasComponent(component, entity)).toBe(true);
84
+ });
85
+
86
+ it.todo("should store the value array");
87
+ });
88
+
89
+ describe("removeComponent", () => {
90
+ let component: AnyComponent;
91
+ let entity: Entity;
92
+ let value: number;
93
+
94
+ beforeEach(() => {
95
+ component = defineComponent(world, { value: Type.Number });
96
+ entity = createEntity(world);
97
+ value = 1;
98
+ setComponent(component, entity, { value });
99
+ removeComponent(component, entity);
100
+ });
101
+
102
+ it("should remove the component value", () => {
103
+ expect(component.values.value.get(getEntitySymbol(entity))).toBe(undefined);
104
+ });
105
+
106
+ it("should remove the entity", () => {
107
+ expect(hasComponent(component, entity)).toBe(false);
108
+ });
109
+
110
+ // it("shouldremove the component from the entity's component set", () => {
111
+ // expect(world.entities.get(entity)?.has(component)).toBe(false);
112
+ // });
113
+ });
114
+
115
+ describe("hasComponent", () => {
116
+ it("should return true if the entity has the component", () => {
117
+ const component = defineComponent(world, { x: Type.Number, y: Type.Number });
118
+ const entity = createEntity(world);
119
+ const value = { x: 1, y: 2 };
120
+ setComponent(component, entity, value);
121
+
122
+ expect(hasComponent(component, entity)).toEqual(true);
123
+ });
124
+ });
125
+
126
+ describe("getComponentValue", () => {
127
+ it("should return the correct component value", () => {
128
+ const component = defineComponent(world, { x: Type.Number, y: Type.Number });
129
+ const entity = createEntity(world);
130
+ const value = { x: 1, y: 2 };
131
+ setComponent(component, entity, value);
132
+
133
+ const receivedValue = getComponentValue(component, entity);
134
+ expect(receivedValue).toEqual(value);
135
+ });
136
+ });
137
+
138
+ describe("getComponentValueStrict", () => {
139
+ it.todo("should return the correct component value");
140
+ it.todo("should error if the component value does not exist");
141
+ });
142
+
143
+ describe("componentValueEquals", () => {
144
+ const value1 = { x: 1, y: 2, z: "x" };
145
+ const value2 = { x: 1, y: 2, z: "x" };
146
+ const value3 = { x: "1", y: 2, z: "x" };
147
+
148
+ expect(componentValueEquals(value1, value2)).toBe(true);
149
+ expect(componentValueEquals(value2, value3)).toBe(false);
150
+ });
151
+
152
+ describe("withValue", () => {
153
+ it("should return a ComponentWithValue", () => {
154
+ const component = defineComponent(world, { x: Type.Number, y: Type.Number });
155
+ const value = { x: 1, y: 2 };
156
+ const componentWithValue = withValue(component, value);
157
+ expect(componentWithValue).toEqual([component, value]);
158
+ });
159
+ });
160
+
161
+ describe("getEntitiesWithValue", () => {
162
+ it("Should return all and only entities with this value", () => {
163
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number });
164
+ const entity1 = createEntity(world, [withValue(Position, { x: 1, y: 2 })]);
165
+ createEntity(world, [withValue(Position, { x: 2, y: 1 })]);
166
+ createEntity(world);
167
+ const entity4 = createEntity(world, [withValue(Position, { x: 1, y: 2 })]);
168
+
169
+ expect(getEntitiesWithValue(Position, { x: 1, y: 2 })).toEqual(new Set([entity1, entity4]));
170
+ });
171
+ });
172
+
173
+ describe("overridableComponent", () => {
174
+ it("should return a overridable component", () => {
175
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number });
176
+ const OverridablePosition = overridableComponent(Position);
177
+ expect("addOverride" in OverridablePosition).toBe(true);
178
+ expect("addOverride" in OverridablePosition).toBe(true);
179
+ });
180
+
181
+ it("should mirror all values of the source component", () => {
182
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number });
183
+ const entity1 = createEntity(world);
184
+ setComponent(Position, entity1, { x: 1, y: 2 });
185
+
186
+ const OverridablePosition = overridableComponent(Position);
187
+ expect(getComponentValue(OverridablePosition, entity1)).toEqual({ x: 1, y: 2 });
188
+ });
189
+
190
+ it("the overridable component should be updated if the original component is updated", () => {
191
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number });
192
+ const entity1 = createEntity(world);
193
+ setComponent(Position, entity1, { x: 1, y: 2 });
194
+
195
+ const OverridableComponent = overridableComponent(Position);
196
+
197
+ setComponent(Position, entity1, { x: 2, y: 2 });
198
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 2 });
199
+
200
+ const entity2 = createEntity(world, [withValue(Position, { x: 3, y: 3 })]);
201
+ expect(getComponentValue(OverridableComponent, entity2)).toEqual({ x: 3, y: 3 });
202
+ });
203
+
204
+ it("should return the updated component value if there is a relevant update for the given entity", () => {
205
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number });
206
+ const entity1 = createEntity(world);
207
+ const entity2 = createEntity(world);
208
+ setComponent(Position, entity1, { x: 1, y: 2 });
209
+ setComponent(Position, entity2, { x: 5, y: 6 });
210
+
211
+ const OverridableComponent = overridableComponent(Position);
212
+ OverridableComponent.addOverride("firstOverride", { entity: entity1, value: { x: 2, y: 3 } });
213
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 3 });
214
+ expect(getComponentValue(OverridableComponent, entity2)).toEqual({ x: 5, y: 6 });
215
+
216
+ OverridableComponent.addOverride("secondOverride", { entity: entity1, value: { x: 3, y: 3 } });
217
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 3, y: 3 });
218
+
219
+ OverridableComponent.removeOverride("secondOverride");
220
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 3 });
221
+
222
+ setComponent(Position, entity1, { x: 10, y: 20 });
223
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 3 });
224
+
225
+ OverridableComponent.removeOverride("firstOverride");
226
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 10, y: 20 });
227
+ });
228
+
229
+ it("adding an override should trigger reactions depending on the getComponentValue of the overriden component", () => {
230
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number });
231
+ const entity1 = createEntity(world);
232
+ setComponent(Position, entity1, { x: 1, y: 2 });
233
+
234
+ const OverridablePosition = overridableComponent(Position);
235
+
236
+ const spy = jest.fn();
237
+ OverridablePosition.update$.subscribe(spy);
238
+
239
+ expect(spy).toHaveBeenCalledTimes(0);
240
+
241
+ OverridablePosition.addOverride("firstOverride", { entity: entity1, value: { x: 3, y: 3 } });
242
+ expect(spy).toHaveBeenCalledTimes(1);
243
+ expect(spy).toHaveBeenLastCalledWith({
244
+ entity: entity1,
245
+ component: OverridablePosition,
246
+ value: [
247
+ { x: 3, y: 3 },
248
+ { x: 1, y: 2 },
249
+ ],
250
+ });
251
+
252
+ OverridablePosition.removeOverride("firstOverride");
253
+ expect(spy).toHaveBeenCalledTimes(2);
254
+ expect(spy).toHaveBeenLastCalledWith({
255
+ entity: entity1,
256
+ component: OverridablePosition,
257
+ value: [
258
+ { x: 1, y: 2 },
259
+ { x: 3, y: 3 },
260
+ ],
261
+ });
262
+
263
+ OverridablePosition.addOverride("secondOverride", {
264
+ entity: "42" as Entity,
265
+ value: { x: 2, y: 3 },
266
+ });
267
+ expect(spy).toHaveBeenLastCalledWith({
268
+ entity: "42",
269
+ component: OverridablePosition,
270
+ value: [{ x: 2, y: 3 }, undefined],
271
+ });
272
+ expect(spy).toHaveBeenCalledTimes(3);
273
+ });
274
+ });
275
+ });
package/src/Component.ts CHANGED
@@ -37,7 +37,7 @@ function getComponentName(component: Component<any, any, any>) {
37
37
  * @param schema {@link Schema} of component values. Uses Type enum as bridge between typescript types and javascript accessible values.
38
38
  * @param options Optional: {
39
39
  * id: descriptive id for this component (otherwise an autogenerated id is used),
40
- * metadata: arbitrary metadata (eg. contractId for solecs mapped components),
40
+ * metadata: arbitrary metadata,
41
41
  * indexed: if this flag is set, an indexer is applied to this component (see {@link createIndexer})
42
42
  * }
43
43
  * @returns Component object linked to the provided World
@@ -0,0 +1,45 @@
1
+ import { defineComponent, getComponentValue, hasComponent, withValue } from "./Component";
2
+ import { Type } from "./constants";
3
+ import { createEntity } from "./Entity";
4
+ import { World } from "./types";
5
+ import { createWorld } from "./World";
6
+
7
+ describe("Entity", () => {
8
+ let world: World;
9
+
10
+ beforeEach(() => {
11
+ world = createWorld();
12
+ });
13
+
14
+ describe("createEntity", () => {
15
+ it("should return a unique id", () => {
16
+ const firstEntity = createEntity(world);
17
+ const secondEntity = createEntity(world);
18
+ expect(firstEntity).not.toEqual(secondEntity);
19
+ });
20
+
21
+ it("should register the entity in the world", () => {
22
+ expect([...world.getEntities()].length).toEqual(0);
23
+ createEntity(world);
24
+ expect([...world.getEntities()].length).toEqual(1);
25
+ });
26
+
27
+ it("should create an entity with given components and values", () => {
28
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number });
29
+ const CanMove = defineComponent(world, { value: Type.Boolean });
30
+
31
+ const value1 = { x: 1, y: 1 };
32
+ const value2 = { x: 2, y: 1 };
33
+
34
+ const movableEntity = createEntity(world, [withValue(Position, value1), withValue(CanMove, { value: true })]);
35
+
36
+ const staticEntity = createEntity(world, [withValue(Position, value2)]);
37
+
38
+ expect(getComponentValue(Position, movableEntity)).toEqual(value1);
39
+ expect(hasComponent(CanMove, movableEntity)).toBe(true);
40
+
41
+ expect(getComponentValue(Position, staticEntity)).toEqual(value2);
42
+ expect(hasComponent(CanMove, staticEntity)).toBe(false);
43
+ });
44
+ });
45
+ });
package/src/Entity.ts CHANGED
@@ -8,7 +8,7 @@ import { Component, ComponentValue, Entity, EntitySymbol, World } from "./types"
8
8
  * @param components Array of [{@link defineComponent Component}, {@link ComponentValue}] tuples to be added to this entity.
9
9
  * (Use {@link withValue} to generate these tuples with type safety.)
10
10
  * @param options Optional: {
11
- * id: {@link Entity} for this entity. Use this for entities that were created outside of recs, eg. in the corresponding solecs contracts.
11
+ * id: {@link Entity} for this entity. Use this for entities that were created outside of recs.
12
12
  * idSuffix: string to be appended to the auto-generated id. Use this for improved readability. Do not use this if the `id` option is provided.
13
13
  * }
14
14
  * @returns index of this entity in the {@link World}. This {@link Entity} is used to refer to this entity in other recs methods (eg {@link setComponent}).
@@ -0,0 +1,288 @@
1
+ import {
2
+ defineComponent,
3
+ setComponent,
4
+ removeComponent,
5
+ getComponentValue,
6
+ hasComponent,
7
+ withValue,
8
+ componentValueEquals,
9
+ getEntitiesWithValue,
10
+ overridableComponent,
11
+ } from "./Component";
12
+ import { createIndexer } from "./Indexer";
13
+ import { Type } from "./constants";
14
+ import { createEntity, getEntitySymbol } from "./Entity";
15
+ import { AnyComponent, Entity, World } from "./types";
16
+ import { createWorld } from "./World";
17
+
18
+ describe("Indexer", () => {
19
+ let world: World;
20
+
21
+ beforeEach(() => {
22
+ world = createWorld();
23
+ });
24
+
25
+ it("emit changes to its stream", () => {
26
+ const entity = createEntity(world);
27
+ const component = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
28
+
29
+ const mock = jest.fn();
30
+ component.update$.subscribe((update) => {
31
+ mock(update);
32
+ });
33
+
34
+ setComponent(component, entity, { x: 1, y: 2 });
35
+ setComponent(component, entity, { x: 7, y: 2 });
36
+ setComponent(component, entity, { x: 7, y: 2 });
37
+ removeComponent(component, entity);
38
+
39
+ expect(mock).toHaveBeenNthCalledWith(1, { entity, value: [{ x: 1, y: 2 }, undefined], component });
40
+ expect(mock).toHaveBeenNthCalledWith(2, {
41
+ entity,
42
+ component,
43
+ value: [
44
+ { x: 7, y: 2 },
45
+ { x: 1, y: 2 },
46
+ ],
47
+ });
48
+ expect(mock).toHaveBeenNthCalledWith(3, {
49
+ entity,
50
+ component,
51
+ value: [
52
+ { x: 7, y: 2 },
53
+ { x: 7, y: 2 },
54
+ ],
55
+ });
56
+ expect(mock).toHaveBeenNthCalledWith(4, { entity, component, value: [undefined, { x: 7, y: 2 }] });
57
+ });
58
+
59
+ describe("setComponent", () => {
60
+ let component: AnyComponent;
61
+ let entity: Entity;
62
+ let value: number;
63
+
64
+ beforeEach(() => {
65
+ component = defineComponent(world, { value: Type.Number }, { indexed: true });
66
+ entity = createEntity(world);
67
+ value = 1;
68
+ setComponent(component, entity, { value });
69
+ });
70
+
71
+ it("should store the component value", () => {
72
+ expect(component.values.value.get(getEntitySymbol(entity))).toBe(value);
73
+ });
74
+
75
+ it("should store the entity", () => {
76
+ expect(hasComponent(component, entity)).toBe(true);
77
+ });
78
+
79
+ it.todo("should store the value array");
80
+ });
81
+
82
+ describe("removeComponent", () => {
83
+ let component: AnyComponent;
84
+ let entity: Entity;
85
+ let value: number;
86
+
87
+ beforeEach(() => {
88
+ component = defineComponent(world, { value: Type.Number }, { indexed: true });
89
+ entity = createEntity(world);
90
+ value = 1;
91
+ setComponent(component, entity, { value });
92
+ removeComponent(component, entity);
93
+ });
94
+
95
+ it("should remove the component value", () => {
96
+ expect(component.values.value.get(getEntitySymbol(entity))).toBe(undefined);
97
+ });
98
+
99
+ it("should remove the entity", () => {
100
+ expect(hasComponent(component, entity)).toBe(false);
101
+ });
102
+
103
+ // it("shouldremove the component from the entity's component set", () => {
104
+ // expect(world.entities.get(entity)?.has(component)).toBe(false);
105
+ // });
106
+ });
107
+
108
+ describe("hasComponent", () => {
109
+ it("should return true if the entity has the component", () => {
110
+ const component = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
111
+ const entity = createEntity(world);
112
+ const value = { x: 1, y: 2 };
113
+ setComponent(component, entity, value);
114
+
115
+ expect(hasComponent(component, entity)).toEqual(true);
116
+ });
117
+ });
118
+
119
+ describe("getComponentValue", () => {
120
+ it("should return the correct component value", () => {
121
+ const component = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
122
+ const entity = createEntity(world);
123
+ const value = { x: 1, y: 2 };
124
+ setComponent(component, entity, value);
125
+
126
+ const receivedValue = getComponentValue(component, entity);
127
+ expect(receivedValue).toEqual(value);
128
+ });
129
+ });
130
+
131
+ describe("getComponentValueStrict", () => {
132
+ it.todo("should return the correct component value");
133
+ it.todo("should error if the component value does not exist");
134
+ });
135
+
136
+ describe("componentValueEquals", () => {
137
+ it("values should equal equal values", () => {
138
+ const value1 = { x: 1, y: 2, z: "x" };
139
+ const value2 = { x: 1, y: 2, z: "x" };
140
+ const value3 = { x: "1", y: 2, z: "x" };
141
+
142
+ expect(componentValueEquals(value1, value2)).toBe(true);
143
+ expect(componentValueEquals(value2, value3)).toBe(false);
144
+ });
145
+ });
146
+
147
+ describe("withValue", () => {
148
+ it("should return a ComponentWithValue", () => {
149
+ const component = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
150
+ const value = { x: 1, y: 2 };
151
+ const componentWithValue = withValue(component, value);
152
+ expect(componentWithValue).toEqual([component, value]);
153
+ });
154
+ });
155
+
156
+ describe("getEntitiesWithValue", () => {
157
+ it("Should return all and only entities with this value", () => {
158
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
159
+ const entity1 = createEntity(world, [withValue(Position, { x: 1, y: 2 })]);
160
+ createEntity(world, [withValue(Position, { x: 2, y: 1 })]);
161
+ createEntity(world);
162
+ const entity4 = createEntity(world, [withValue(Position, { x: 1, y: 2 })]);
163
+
164
+ expect(getEntitiesWithValue(Position, { x: 1, y: 2 })).toEqual(new Set([entity1, entity4]));
165
+ });
166
+
167
+ it("Should keep the entities with value up to date", () => {
168
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number });
169
+ const entity1 = createEntity(world, [withValue(Position, { x: 1, y: 2 })]);
170
+ const entity2 = createEntity(world, [withValue(Position, { x: 2, y: 1 })]);
171
+ createEntity(world);
172
+ const PositionIndexer = createIndexer(Position);
173
+ expect(getEntitiesWithValue(PositionIndexer, { x: 1, y: 2 })).toEqual(new Set([entity1]));
174
+
175
+ const entity3 = createEntity(world, [withValue(Position, { x: 1, y: 2 })]);
176
+ expect(getEntitiesWithValue(PositionIndexer, { x: 1, y: 2 })).toEqual(new Set([entity1, entity3]));
177
+
178
+ setComponent(Position, entity2, { x: 1, y: 2 });
179
+ expect(getEntitiesWithValue(PositionIndexer, { x: 1, y: 2 })).toEqual(new Set([entity1, entity2, entity3]));
180
+
181
+ setComponent(PositionIndexer, entity1, { x: 2, y: 2 });
182
+ expect(getEntitiesWithValue(PositionIndexer, { x: 1, y: 2 })).toEqual(new Set([entity2, entity3]));
183
+ });
184
+ });
185
+
186
+ describe("overridableComponent", () => {
187
+ it("should return a overridable component", () => {
188
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
189
+ const OverridablePosition = overridableComponent(Position);
190
+ expect("addOverride" in OverridablePosition).toBe(true);
191
+ expect("addOverride" in OverridablePosition).toBe(true);
192
+ });
193
+
194
+ it("should mirror all values of the source component", () => {
195
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
196
+ const entity1 = createEntity(world);
197
+ setComponent(Position, entity1, { x: 1, y: 2 });
198
+
199
+ const OverridablePosition = overridableComponent(Position);
200
+ expect(getComponentValue(OverridablePosition, entity1)).toEqual({ x: 1, y: 2 });
201
+ });
202
+
203
+ it("the overridable component should be updated if the original component is updated", () => {
204
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
205
+ const entity1 = createEntity(world);
206
+ setComponent(Position, entity1, { x: 1, y: 2 });
207
+
208
+ const OverridableComponent = overridableComponent(Position);
209
+
210
+ setComponent(Position, entity1, { x: 2, y: 2 });
211
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 2 });
212
+
213
+ const entity2 = createEntity(world, [withValue(Position, { x: 3, y: 3 })]);
214
+ expect(getComponentValue(OverridableComponent, entity2)).toEqual({ x: 3, y: 3 });
215
+ });
216
+
217
+ it("should return the updated component value if there is a relevant update for the given entity", () => {
218
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
219
+ const entity1 = createEntity(world);
220
+ const entity2 = createEntity(world);
221
+ setComponent(Position, entity1, { x: 1, y: 2 });
222
+ setComponent(Position, entity2, { x: 5, y: 6 });
223
+
224
+ const OverridableComponent = overridableComponent(Position);
225
+ OverridableComponent.addOverride("firstOverride", { entity: entity1, value: { x: 2, y: 3 } });
226
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 3 });
227
+ expect(getComponentValue(OverridableComponent, entity2)).toEqual({ x: 5, y: 6 });
228
+
229
+ OverridableComponent.addOverride("secondOverride", { entity: entity1, value: { x: 3, y: 3 } });
230
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 3, y: 3 });
231
+
232
+ OverridableComponent.removeOverride("secondOverride");
233
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 3 });
234
+
235
+ setComponent(Position, entity1, { x: 10, y: 20 });
236
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 3 });
237
+
238
+ OverridableComponent.removeOverride("firstOverride");
239
+ expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 10, y: 20 });
240
+ });
241
+
242
+ it("adding an override should trigger reactions depending on the getComponentValue of the overriden component", () => {
243
+ const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
244
+ const entity1 = createEntity(world);
245
+ setComponent(Position, entity1, { x: 1, y: 2 });
246
+
247
+ const OverridablePosition = overridableComponent(Position);
248
+
249
+ const spy = jest.fn();
250
+ OverridablePosition.update$.subscribe(spy);
251
+
252
+ expect(spy).toHaveBeenCalledTimes(0);
253
+
254
+ OverridablePosition.addOverride("firstOverride", { entity: entity1, value: { x: 3, y: 3 } });
255
+ expect(spy).toHaveBeenCalledTimes(1);
256
+ expect(spy).toHaveBeenLastCalledWith({
257
+ entity: entity1,
258
+ component: OverridablePosition,
259
+ value: [
260
+ { x: 3, y: 3 },
261
+ { x: 1, y: 2 },
262
+ ],
263
+ });
264
+
265
+ OverridablePosition.removeOverride("firstOverride");
266
+ expect(spy).toHaveBeenCalledTimes(2);
267
+ expect(spy).toHaveBeenLastCalledWith({
268
+ entity: entity1,
269
+ component: OverridablePosition,
270
+ value: [
271
+ { x: 1, y: 2 },
272
+ { x: 3, y: 3 },
273
+ ],
274
+ });
275
+
276
+ OverridablePosition.addOverride("secondOverride", {
277
+ entity: "42" as Entity,
278
+ value: { x: 2, y: 3 },
279
+ });
280
+ expect(spy).toHaveBeenLastCalledWith({
281
+ entity: "42" as Entity,
282
+ component: OverridablePosition,
283
+ value: [{ x: 2, y: 3 }, undefined],
284
+ });
285
+ expect(spy).toHaveBeenCalledTimes(3);
286
+ });
287
+ });
288
+ });