@inglorious/web 2.2.1 → 2.2.3

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/src/mount.test.js DELETED
@@ -1,255 +0,0 @@
1
- /**
2
- * @vitest-environment jsdom
3
- */
4
-
5
- import { createStore } from "@inglorious/store"
6
- import { html } from "lit-html" // Only html is needed for templates, render is handled by mount
7
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
8
-
9
- import { mount } from "./mount.js"
10
-
11
- let rootElement
12
-
13
- beforeEach(() => {
14
- // Create a fresh DOM element for each test
15
- rootElement = document.createElement("div")
16
- document.body.appendChild(rootElement)
17
- // Spy on console.warn to check for warnings without polluting test output
18
- vi.spyOn(console, "warn").mockImplementation(() => {})
19
- })
20
-
21
- afterEach(() => {
22
- // Clean up the DOM element after each test
23
- document.body.removeChild(rootElement)
24
- // Restore console.warn to its original implementation
25
- vi.restoreAllMocks()
26
- })
27
-
28
- // Helper function to create a store with some common entities and types for testing
29
- function setupStore(entitiesOverride = {}, typesOverride = {}) {
30
- return createStore({
31
- // Pass 'entities' as a top-level property to createStore
32
- entities: {
33
- "player-1": {
34
- id: "player-1",
35
- type: "player",
36
- name: "Player One",
37
- score: 0,
38
- },
39
- "enemy-1": { id: "enemy-1", type: "enemy", health: 100 },
40
- ...entitiesOverride,
41
- },
42
- // Pass 'types' as a top-level property to createStore
43
- types: {
44
- player: {
45
- render: (entity) =>
46
- html`<span>Player: ${entity.name}, Score: ${entity.score}</span>`,
47
- incrementScore: (entity) => {
48
- entity.score++
49
- },
50
- },
51
- enemy: {
52
- render: (entity) => html`<span>Enemy: ${entity.health} HP</span>`,
53
- takeDamage: (entity, amount) => {
54
- entity.health -= amount
55
- },
56
- },
57
- simpleType: {
58
- render: () => html`<span>Simple Type Rendered</span>`,
59
- },
60
- typeWithoutRender: {
61
- /* no render function */
62
- },
63
- ...typesOverride,
64
- },
65
- })
66
- }
67
-
68
- describe("mount", () => {
69
- it("should render the initial state into the element", () => {
70
- const store = setupStore()
71
- const renderFn = (api) =>
72
- html`<div>Hello, ${api.getEntity("player-1").name}!</div>`
73
- mount(store, renderFn, rootElement)
74
-
75
- expect(rootElement.textContent).toBe("Hello, Player One!")
76
- })
77
-
78
- it("should re-render when the store state changes", async () => {
79
- const store = setupStore()
80
- const renderFn = (api) =>
81
- html`<div>Score: ${api.getEntity("player-1").score}</div>`
82
- mount(store, renderFn, rootElement)
83
-
84
- expect(rootElement.textContent).toBe("Score: 0")
85
-
86
- store.notify("incrementScore", "player-1")
87
- // lit-html renders asynchronously, so we need to wait for the next microtask
88
- await Promise.resolve()
89
-
90
- expect(rootElement.textContent).toBe("Score: 1")
91
- })
92
-
93
- it("should stop re-rendering after unsubscribe is called", async () => {
94
- const store = setupStore()
95
- const renderFn = (api) =>
96
- html`<div>Score: ${api.getEntity("player-1").score}</div>`
97
- const unsubscribe = mount(store, renderFn, rootElement)
98
-
99
- expect(rootElement.textContent).toBe("Score: 0")
100
-
101
- unsubscribe()
102
- store.notify("incrementScore", "player-1")
103
- await Promise.resolve() // Wait for potential re-render
104
-
105
- // The score should still be 0 in the DOM because unsubscribe was called
106
- expect(rootElement.textContent).toBe("Score: 0")
107
- expect(rootElement.textContent).not.toBe("Score: 1")
108
- })
109
-
110
- describe("api.select", () => {
111
- let capturedSelectResult // Used to capture the ReactiveSelectorResult instance across renders
112
-
113
- it("should return the initial selected value", () => {
114
- const store = setupStore()
115
- const renderFn = (api) => {
116
- capturedSelectResult = api.select(
117
- (api) => api.getEntity("player-1").score,
118
- )
119
- return html`<div>Selected: ${capturedSelectResult()}</div>`
120
- }
121
- mount(store, renderFn, rootElement)
122
-
123
- expect(capturedSelectResult()).toBe(0)
124
- expect(rootElement.textContent).toBe("Selected: 0")
125
- })
126
-
127
- it("should update the selected value when the relevant state changes", async () => {
128
- const store = setupStore()
129
- const renderFn = (api) => {
130
- capturedSelectResult = api.select(
131
- (api) => api.getEntity("player-1").score,
132
- )
133
- return html`<div>Selected: ${capturedSelectResult()}</div>`
134
- }
135
- mount(store, renderFn, rootElement)
136
-
137
- expect(capturedSelectResult()).toBe(0)
138
-
139
- store.notify("incrementScore", "player-1")
140
- await Promise.resolve() // Wait for store subscription to trigger and update `current` in select, and for lit-html to render
141
-
142
- expect(capturedSelectResult()).toBe(1)
143
- expect(rootElement.textContent).toBe("Selected: 1")
144
- })
145
-
146
- it("should not update the selected value when unrelated state changes", async () => {
147
- const store = setupStore()
148
- const renderFn = (api) => {
149
- capturedSelectResult = api.select(
150
- (api) => api.getEntity("player-1").score,
151
- )
152
- return html`<div>Selected: ${capturedSelectResult()}</div>`
153
- }
154
- mount(store, renderFn, rootElement)
155
-
156
- expect(capturedSelectResult()).toBe(0)
157
-
158
- store.notify("takeDamage", "enemy-1", 10) // This changes enemy health, not player score
159
- await Promise.resolve()
160
-
161
- expect(capturedSelectResult()).toBe(0) // Should remain 0
162
- expect(rootElement.textContent).toBe("Selected: 0") // DOM also remains 0
163
- })
164
-
165
- it("should stop updating after api.select unsubscribe is called", async () => {
166
- const store = setupStore()
167
- let initialSelectResult
168
- const renderFn = (api) => {
169
- // Capture the select result only on the first render to test its specific unsubscribe
170
- if (!initialSelectResult) {
171
- initialSelectResult = api.select(
172
- (api) => api.getEntity("player-1").score,
173
- )
174
- }
175
- return html`<div>Selected: ${initialSelectResult()}</div>`
176
- }
177
- mount(store, renderFn, rootElement)
178
-
179
- expect(initialSelectResult()).toBe(0)
180
-
181
- initialSelectResult.unsubscribe() // Unsubscribe the reactive selector's internal listener
182
- store.notify("incrementScore", "player-1")
183
- await Promise.resolve() // Wait for store subscription to trigger and for lit-html to render
184
-
185
- // The main mount subscription still triggers renderFn, but the value from `initialSelectResult()`
186
- // should *not* have updated because its internal listener was unsubscribed.
187
- expect(initialSelectResult()).toBe(0) // Should remain 0
188
- expect(rootElement.textContent).toBe("Selected: 0")
189
- expect(rootElement.textContent).not.toBe("Selected: 1")
190
- })
191
- })
192
-
193
- describe("api.render", () => {
194
- it("should render an entity by its ID", async () => {
195
- const store = setupStore()
196
- const renderFn = (api) => html`<div>${api.render("player-1")}</div>`
197
- mount(store, renderFn, rootElement)
198
- await Promise.resolve()
199
-
200
- expect(rootElement.textContent).toBe("Player: Player One, Score: 0")
201
- })
202
-
203
- it("should render a type by its ID when allowType is true", async () => {
204
- const store = setupStore()
205
- const renderFn = (api) =>
206
- html`<div>${api.render("simpleType", { allowType: true })}</div>`
207
- mount(store, renderFn, rootElement)
208
- await Promise.resolve()
209
-
210
- expect(rootElement.textContent).toBe("Simple Type Rendered")
211
- })
212
-
213
- it("should return an empty string for a non-existent entity/type without allowType", async () => {
214
- const store = setupStore()
215
- const renderFn = (api) =>
216
- html`<div>${api.render("non-existent-id")}</div>`
217
- mount(store, renderFn, rootElement)
218
- await Promise.resolve()
219
-
220
- expect(rootElement.textContent).toBe("") // Empty div because api.render returns ""
221
- expect(console.warn).not.toHaveBeenCalled() // No warning for non-existent entity without allowType
222
- })
223
-
224
- it("should return a fallback template and warn for a non-existent entity/type with allowType", async () => {
225
- const store = setupStore()
226
- const renderFn = (api) =>
227
- html`<div>${api.render("non-existent-id", { allowType: true })}</div>`
228
- mount(store, renderFn, rootElement)
229
- await Promise.resolve()
230
-
231
- expect(rootElement.textContent).toBe("Not found: non-existent-id")
232
- expect(console.warn).toHaveBeenCalledWith(
233
- "No entity or type found: non-existent-id",
234
- )
235
- })
236
-
237
- it("should return a fallback template and warn for an entity whose type has no render function", async () => {
238
- const store = setupStore({
239
- "no-render-entity": {
240
- id: "no-render-entity",
241
- type: "typeWithoutRender",
242
- },
243
- })
244
- const renderFn = (api) =>
245
- html`<div>${api.render("no-render-entity")}</div>`
246
- mount(store, renderFn, rootElement)
247
- await Promise.resolve()
248
-
249
- expect(rootElement.textContent).toBe("No renderer for typeWithoutRender")
250
- expect(console.warn).toHaveBeenCalledWith(
251
- "No render function for type: typeWithoutRender",
252
- )
253
- })
254
- })
255
- })