@inglorious/web 2.2.1 → 2.2.2
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 +0 -24
- package/package.json +2 -2
- package/src/form.js +2 -2
- package/src/form.test.js +372 -0
- package/src/list.test.js +228 -0
- package/src/mount.js +13 -27
- package/src/router.test.js +158 -0
- package/src/table/rendering.js +7 -9
- package/src/table.test.js +372 -0
- package/types/mount.d.ts +0 -21
- package/src/mount.test.js +0 -255
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
|
-
})
|