@inglorious/store 6.1.2 โ 6.2.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.
- package/README.md +68 -0
- package/package.json +1 -1
- package/src/test.js +130 -0
- package/src/test.test.js +272 -0
package/README.md
CHANGED
|
@@ -480,6 +480,74 @@ Notice: you don't need pending/fulfilled/rejected actions. You stay in control o
|
|
|
480
480
|
|
|
481
481
|
All events triggered via `api.notify()` enter the queue and process together, maintaining predictability and testability.
|
|
482
482
|
|
|
483
|
+
### ๐งช Testing
|
|
484
|
+
|
|
485
|
+
Event handlers are pure functions (or can be treated as such), making them easy to test in isolation, much like Redux reducers. The `@inglorious/store/test` module provides utility functions to make this even simpler.
|
|
486
|
+
|
|
487
|
+
#### `trigger(entity, handler, payload, api?)`
|
|
488
|
+
|
|
489
|
+
The `trigger` function executes an event handler on a single entity and returns the new state and any events that were dispatched.
|
|
490
|
+
|
|
491
|
+
```javascript
|
|
492
|
+
import { trigger } from "@inglorious/store/test"
|
|
493
|
+
|
|
494
|
+
// Define your entity handler
|
|
495
|
+
function increment(entity, payload, api) {
|
|
496
|
+
entity.value += payload.amount
|
|
497
|
+
if (entity.value > 100) {
|
|
498
|
+
api.notify("overflow", { id: entity.id })
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Test it
|
|
503
|
+
const { entity, events } = trigger(
|
|
504
|
+
{ type: "counter", id: "counter1", value: 99 },
|
|
505
|
+
increment,
|
|
506
|
+
{ amount: 5 },
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
expect(entity.value).toBe(104)
|
|
510
|
+
expect(events).toEqual([{ type: "overflow", payload: { id: "counter1" } }])
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
#### `createMockApi(entities)`
|
|
514
|
+
|
|
515
|
+
If your handler needs to interact with other entities via the `api`, you can create a mock API. This is useful for testing handlers that read from other parts of the state.
|
|
516
|
+
|
|
517
|
+
```javascript
|
|
518
|
+
import { createMockApi, trigger } from "@inglorious/store/test"
|
|
519
|
+
|
|
520
|
+
// Create a mock API with some initial entities
|
|
521
|
+
const api = createMockApi({
|
|
522
|
+
counter1: { type: "counter", value: 10 },
|
|
523
|
+
counter2: { type: "counter", value: 20 },
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
// A handler that copies a value from another entity
|
|
527
|
+
function copyValue(entity, payload, api) {
|
|
528
|
+
const source = api.getEntity(payload.sourceId)
|
|
529
|
+
entity.value = source.value
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Trigger the handler with the custom mock API
|
|
533
|
+
const { entity } = trigger(
|
|
534
|
+
{ type: "counter", id: "counter2", value: 20 },
|
|
535
|
+
copyValue,
|
|
536
|
+
{ sourceId: "counter1" },
|
|
537
|
+
api,
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
expect(entity.value).toBe(10)
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
The mock API provides:
|
|
544
|
+
|
|
545
|
+
- `getEntities()`: Returns all entities (frozen).
|
|
546
|
+
- `getEntity(id)`: Returns a specific entity by ID (frozen).
|
|
547
|
+
- `dispatch(event)`: Records an event for later assertions.
|
|
548
|
+
- `notify(type, payload)`: A convenience wrapper around `dispatch`.
|
|
549
|
+
- `getEvents()`: Returns all events that were dispatched.
|
|
550
|
+
|
|
483
551
|
### ๐ Systems for Global Logic
|
|
484
552
|
|
|
485
553
|
When you need to coordinate updates across multiple entities (not just respond to individual events), use systems. Systems run after all entity handlers for the same event, ensuring global consistency, and have write access to the entire state. This concept is the 'S' in the ECS Architecture (Entity-Component-System)!
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/store",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "A state manager for real-time, collaborative apps, inspired by game development patterns and compatible with Redux.",
|
|
5
5
|
"author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
package/src/test.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { create } from "mutative"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a mock API for testing event handlers in isolation.
|
|
5
|
+
*
|
|
6
|
+
* The mock API provides read-only access to entities and tracks all events
|
|
7
|
+
* dispatched during handler execution. The entities are frozen to prevent
|
|
8
|
+
* accidental mutations - handlers should only mutate the draft state passed
|
|
9
|
+
* to them, not entities retrieved via `getEntity()` or `getEntities()`.
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} entities - The entities state (will be frozen)
|
|
12
|
+
*
|
|
13
|
+
* @returns {Object} A mock API object with methods:
|
|
14
|
+
* - `getEntities()`: Returns all entities (frozen)
|
|
15
|
+
* - `getEntity(id)`: Returns a specific entity by ID (frozen)
|
|
16
|
+
* - `dispatch(event)`: Records an event (for assertions)
|
|
17
|
+
* - `notify(type, payload)`: Convenience method that calls dispatch
|
|
18
|
+
* - `getEvents()`: Returns all events that were dispatched
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const api = createMockApi({
|
|
22
|
+
* counter1: { type: 'counter', value: 0 }
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* // In your handler
|
|
26
|
+
* const entity = api.getEntity('counter1')
|
|
27
|
+
* api.notify('increment', { id: 'counter1' })
|
|
28
|
+
*
|
|
29
|
+
* // In your test
|
|
30
|
+
* expect(api.getEvents()).toEqual([
|
|
31
|
+
* { type: 'increment', payload: { id: 'counter1' } }
|
|
32
|
+
* ])
|
|
33
|
+
*/
|
|
34
|
+
export function createMockApi(entities) {
|
|
35
|
+
const frozenEntities = Object.freeze(entities)
|
|
36
|
+
const events = []
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
getEntities() {
|
|
40
|
+
return frozenEntities
|
|
41
|
+
},
|
|
42
|
+
getEntity(id) {
|
|
43
|
+
return frozenEntities[id]
|
|
44
|
+
},
|
|
45
|
+
dispatch(event) {
|
|
46
|
+
events.push(event)
|
|
47
|
+
},
|
|
48
|
+
notify(type, payload) {
|
|
49
|
+
this.dispatch({ type, payload })
|
|
50
|
+
},
|
|
51
|
+
getEvents() {
|
|
52
|
+
return events
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Triggers an event handler on a single entity for testing purposes.
|
|
59
|
+
*
|
|
60
|
+
* This function executes an event handler on a single entity with the given
|
|
61
|
+
* payload, using Mutative to provide a mutable draft. The handler can read
|
|
62
|
+
* other entities via the API (frozen, immutable) and mutate the draft entity.
|
|
63
|
+
* All events dispatched during execution are captured and returned.
|
|
64
|
+
*
|
|
65
|
+
* Use this for unit testing individual entity handlers. For integration testing
|
|
66
|
+
* of event cascades and the full event queue, use the actual store.
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} entity - The entity to operate on
|
|
69
|
+
* @param {Function} eventHandler - The handler function to test. Should accept
|
|
70
|
+
* (draft, payload, api) where draft is the mutable entity
|
|
71
|
+
* @param {*} eventPayload - The payload to pass to the handler
|
|
72
|
+
* @param {Object} [api] - Optional custom mock API. If not provided, a default
|
|
73
|
+
* mock API will be created automatically with the entity as the only entity
|
|
74
|
+
*
|
|
75
|
+
* @returns {Object} An object containing:
|
|
76
|
+
* - `entity`: The new immutable entity after the handler executed
|
|
77
|
+
* - `events`: Array of all events dispatched during handler execution
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* // Define your entity handler
|
|
81
|
+
* const increment = (entity, payload, api) => {
|
|
82
|
+
* entity.value += payload.amount
|
|
83
|
+
* if (entity.value > 100) {
|
|
84
|
+
* api.notify('overflow', { id: entity.id })
|
|
85
|
+
* }
|
|
86
|
+
* }
|
|
87
|
+
*
|
|
88
|
+
* // Test it
|
|
89
|
+
* const { entity, events } = trigger(
|
|
90
|
+
* { type: 'counter', id: 'counter1', value: 99 },
|
|
91
|
+
* increment,
|
|
92
|
+
* { amount: 5 }
|
|
93
|
+
* )
|
|
94
|
+
*
|
|
95
|
+
* expect(entity.value).toBe(104)
|
|
96
|
+
* expect(events).toEqual([
|
|
97
|
+
* { type: 'overflow', payload: { id: 'counter1' } }
|
|
98
|
+
* ])
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* // With custom mock API to access other entities
|
|
102
|
+
* const api = createMockApi({
|
|
103
|
+
* counter1: { type: 'counter', value: 10 },
|
|
104
|
+
* counter2: { type: 'counter', value: 20 }
|
|
105
|
+
* })
|
|
106
|
+
*
|
|
107
|
+
* const copyValue = (entity, payload, api) => {
|
|
108
|
+
* const source = api.getEntity(payload.sourceId)
|
|
109
|
+
* entity.value = source.value
|
|
110
|
+
* }
|
|
111
|
+
*
|
|
112
|
+
* const { entity } = trigger(
|
|
113
|
+
* { type: 'counter', id: 'counter2', value: 20 },
|
|
114
|
+
* copyValue,
|
|
115
|
+
* { sourceId: 'counter1' },
|
|
116
|
+
* api
|
|
117
|
+
* )
|
|
118
|
+
*
|
|
119
|
+
* expect(entity.value).toBe(10)
|
|
120
|
+
*/
|
|
121
|
+
export function trigger(entity, eventHandler, eventPayload, api) {
|
|
122
|
+
api ??= createMockApi({ entities: { [entity.id]: entity } })
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
entity: create(entity, (draft) => {
|
|
126
|
+
eventHandler(draft, eventPayload, api)
|
|
127
|
+
}),
|
|
128
|
+
events: api.getEvents(),
|
|
129
|
+
}
|
|
130
|
+
}
|
package/src/test.test.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { createMockApi, trigger } from "./test"
|
|
4
|
+
|
|
5
|
+
describe("createMockApi", () => {
|
|
6
|
+
it("should create a mock API with all required methods", () => {
|
|
7
|
+
const entities = { counter1: { type: "counter", value: 0 } }
|
|
8
|
+
|
|
9
|
+
const api = createMockApi(entities)
|
|
10
|
+
|
|
11
|
+
expect(api.getEntities).toBeDefined()
|
|
12
|
+
expect(api.getEntity).toBeDefined()
|
|
13
|
+
expect(api.dispatch).toBeDefined()
|
|
14
|
+
expect(api.notify).toBeDefined()
|
|
15
|
+
expect(api.getEvents).toBeDefined()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it("should return all entities via getEntities()", () => {
|
|
19
|
+
const entities = {
|
|
20
|
+
counter1: { type: "counter", value: 5 },
|
|
21
|
+
counter2: { type: "counter", value: 10 },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const api = createMockApi(entities)
|
|
25
|
+
|
|
26
|
+
expect(api.getEntities()).toEqual(entities)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("should return a specific entity via getEntity()", () => {
|
|
30
|
+
const entities = {
|
|
31
|
+
counter1: { type: "counter", value: 5 },
|
|
32
|
+
counter2: { type: "counter", value: 10 },
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const api = createMockApi(entities)
|
|
36
|
+
|
|
37
|
+
expect(api.getEntity("counter1")).toEqual({ type: "counter", value: 5 })
|
|
38
|
+
expect(api.getEntity("counter2")).toEqual({ type: "counter", value: 10 })
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("should return undefined for non-existent entity", () => {
|
|
42
|
+
const entities = {
|
|
43
|
+
counter1: { type: "counter", value: 5 },
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const api = createMockApi(entities)
|
|
47
|
+
|
|
48
|
+
expect(api.getEntity("nonexistent")).toBeUndefined()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("should track dispatched events", () => {
|
|
52
|
+
const entities = {
|
|
53
|
+
counter1: { type: "counter", value: 0 },
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const api = createMockApi(entities)
|
|
57
|
+
|
|
58
|
+
api.dispatch({ type: "increment", payload: { id: "counter1" } })
|
|
59
|
+
api.dispatch({ type: "decrement", payload: { id: "counter1" } })
|
|
60
|
+
|
|
61
|
+
expect(api.getEvents()).toEqual([
|
|
62
|
+
{ type: "increment", payload: { id: "counter1" } },
|
|
63
|
+
{ type: "decrement", payload: { id: "counter1" } },
|
|
64
|
+
])
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("should track events dispatched via notify()", () => {
|
|
68
|
+
const entities = {
|
|
69
|
+
counter1: { type: "counter", value: 0 },
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const api = createMockApi(entities)
|
|
73
|
+
|
|
74
|
+
api.notify("increment", { id: "counter1", amount: 5 })
|
|
75
|
+
api.notify("overflow")
|
|
76
|
+
|
|
77
|
+
expect(api.getEvents()).toEqual([
|
|
78
|
+
{ type: "increment", payload: { id: "counter1", amount: 5 } },
|
|
79
|
+
{ type: "overflow", payload: undefined },
|
|
80
|
+
])
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it("should freeze entities to prevent mutations", () => {
|
|
84
|
+
const entities = {
|
|
85
|
+
counter1: { type: "counter", value: 0 },
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const api = createMockApi(entities)
|
|
89
|
+
const allEntities = api.getEntities()
|
|
90
|
+
|
|
91
|
+
expect(() => {
|
|
92
|
+
allEntities.counter1 = { type: "counter", value: 999 }
|
|
93
|
+
}).toThrow()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it("should start with empty events array", () => {
|
|
97
|
+
const entities = {
|
|
98
|
+
counter1: { type: "counter", value: 0 },
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const api = createMockApi(entities)
|
|
102
|
+
|
|
103
|
+
expect(api.getEvents()).toEqual([])
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
describe("trigger", () => {
|
|
108
|
+
it("should execute handler and return new entity", () => {
|
|
109
|
+
const entityBefore = { type: "counter", value: 0 }
|
|
110
|
+
const entityAfter = { type: "counter", value: 5 }
|
|
111
|
+
|
|
112
|
+
function increment(entity, amount) {
|
|
113
|
+
entity.value += amount
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const { entity } = trigger(entityBefore, increment, 5)
|
|
117
|
+
|
|
118
|
+
expect(entity).toStrictEqual(entityAfter)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it("should not mutate original entity", () => {
|
|
122
|
+
const entityBefore = { type: "counter", value: 0 }
|
|
123
|
+
|
|
124
|
+
function increment(entity, amount) {
|
|
125
|
+
entity.value += amount
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
trigger(entityBefore, increment, 5)
|
|
129
|
+
|
|
130
|
+
expect(entityBefore.value).toBe(0)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it("should capture events dispatched during handler execution", () => {
|
|
134
|
+
const entityBefore = { type: "counter", value: 99 }
|
|
135
|
+
const entityAfter = { type: "counter", value: 104 }
|
|
136
|
+
const eventsAfter = [{ type: "overflow", payload: 104 }]
|
|
137
|
+
|
|
138
|
+
function increment(entity, amount, api) {
|
|
139
|
+
entity.value += amount
|
|
140
|
+
|
|
141
|
+
if (entity.value > 100) {
|
|
142
|
+
api.notify("overflow", entity.value)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const { entity, events } = trigger(entityBefore, increment, 5)
|
|
147
|
+
|
|
148
|
+
expect(entity).toStrictEqual(entityAfter)
|
|
149
|
+
expect(events).toStrictEqual(eventsAfter)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it("should work with handlers that do not dispatch events", () => {
|
|
153
|
+
const entityBefore = { type: "todo", text: "Buy milk", completed: false }
|
|
154
|
+
const entityAfter = { type: "todo", text: "Buy milk", completed: true }
|
|
155
|
+
const eventsAfter = []
|
|
156
|
+
|
|
157
|
+
function toggle(entity) {
|
|
158
|
+
entity.completed = !entity.completed
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const { entity, events } = trigger(entityBefore, toggle)
|
|
162
|
+
|
|
163
|
+
expect(entity).toStrictEqual(entityAfter)
|
|
164
|
+
expect(events).toStrictEqual(eventsAfter)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it("should allow handler to read other entities via API", () => {
|
|
168
|
+
const entityBefore = { id: "counter2", type: "counter", value: 20 }
|
|
169
|
+
const entityAfter = { id: "counter2", type: "counter", value: 30 }
|
|
170
|
+
|
|
171
|
+
const mockApi = createMockApi({
|
|
172
|
+
counter1: { id: "counter1", type: "counter", value: 10 },
|
|
173
|
+
counter2: { id: "counter2", type: "counter", value: 20 },
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
function addFromSource(entity, sourceId, api) {
|
|
177
|
+
const source = api.getEntity(sourceId)
|
|
178
|
+
if (source) {
|
|
179
|
+
entity.value += source.value
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const { entity } = trigger(entityBefore, addFromSource, "counter1", mockApi)
|
|
184
|
+
|
|
185
|
+
expect(entity).toStrictEqual(entityAfter)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it("should accept custom mock API", () => {
|
|
189
|
+
const entityBefore = { type: "counter", value: 0 }
|
|
190
|
+
const entityAfter = { type: "counter", value: 5 }
|
|
191
|
+
const eventsAfter = [{ type: "incremented", payload: undefined }]
|
|
192
|
+
|
|
193
|
+
const customApi = createMockApi(entityBefore)
|
|
194
|
+
|
|
195
|
+
function increment(entity, amount, api) {
|
|
196
|
+
entity.value += amount
|
|
197
|
+
api.notify("incremented")
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const { entity } = trigger(entityBefore, increment, 5, customApi)
|
|
201
|
+
|
|
202
|
+
expect(entity).toStrictEqual(entityAfter)
|
|
203
|
+
expect(customApi.getEvents()).toStrictEqual(eventsAfter)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it("should handle multiple dispatched events", () => {
|
|
207
|
+
const entityBefore = { type: "counter", value: 50 }
|
|
208
|
+
const entityAfter = { type: "counter", value: 110 }
|
|
209
|
+
const eventsAfter = [
|
|
210
|
+
{ type: "increment:start", payload: undefined },
|
|
211
|
+
{ type: "milestone:hundred", payload: undefined },
|
|
212
|
+
{ type: "increment:complete", payload: 110 },
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
function increment(entity, amount, api) {
|
|
216
|
+
api.notify("increment:start")
|
|
217
|
+
|
|
218
|
+
entity.value += amount
|
|
219
|
+
|
|
220
|
+
if (entity.value >= 100) {
|
|
221
|
+
api.notify("milestone:hundred")
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
api.notify("increment:complete", entity.value)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const { entity, events } = trigger(entityBefore, increment, 60)
|
|
228
|
+
|
|
229
|
+
expect(entity).toStrictEqual(entityAfter)
|
|
230
|
+
expect(events).toStrictEqual(eventsAfter)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it("should work without payload", () => {
|
|
234
|
+
const entityBefore = { type: "counter", value: 5 }
|
|
235
|
+
const entityAfter = { type: "counter", value: 0 }
|
|
236
|
+
|
|
237
|
+
function reset(entity) {
|
|
238
|
+
entity.value = 0
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const { entity } = trigger(entityBefore, reset)
|
|
242
|
+
|
|
243
|
+
expect(entity).toStrictEqual(entityAfter)
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it("should work with complex entity mutations", () => {
|
|
247
|
+
const entityBefore = {
|
|
248
|
+
type: "player",
|
|
249
|
+
name: "Alice",
|
|
250
|
+
inventory: ["sword", "shield"],
|
|
251
|
+
stats: { health: 100, mana: 50 },
|
|
252
|
+
}
|
|
253
|
+
const entityAfter = {
|
|
254
|
+
type: "player",
|
|
255
|
+
name: "Alice",
|
|
256
|
+
inventory: ["sword", "shield", "potion"],
|
|
257
|
+
stats: { health: 100, mana: 50 },
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function addItem(entity, item, api) {
|
|
261
|
+
entity.inventory.push(item)
|
|
262
|
+
api.notify("item:added", { item })
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const { entity, events } = trigger(entityBefore, addItem, "potion")
|
|
266
|
+
|
|
267
|
+
expect(entity).toStrictEqual(entityAfter)
|
|
268
|
+
expect(events).toEqual([
|
|
269
|
+
{ type: "item:added", payload: { item: "potion" } },
|
|
270
|
+
])
|
|
271
|
+
})
|
|
272
|
+
})
|