@inglorious/web 3.0.0 → 3.0.1
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 +21 -15
- package/package.json +7 -4
- package/src/form/form.test.js +0 -372
- package/src/list/list.test.js +0 -228
- package/src/router/router.test.js +0 -208
- package/src/select/select.test.js +0 -415
- package/src/table/table.test.js +0 -393
package/src/list/list.test.js
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @vitest-environment jsdom
|
|
3
|
-
*/
|
|
4
|
-
import { html } from "lit-html"
|
|
5
|
-
import { beforeEach, describe, expect, it, vi } from "vitest"
|
|
6
|
-
|
|
7
|
-
import { list } from "."
|
|
8
|
-
|
|
9
|
-
describe("list", () => {
|
|
10
|
-
let entity
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
entity = {
|
|
14
|
-
id: "test-list",
|
|
15
|
-
type: "test-item-type",
|
|
16
|
-
items: Array.from({ length: 100 }, (_, i) => ({
|
|
17
|
-
id: i,
|
|
18
|
-
name: `Item ${i}`,
|
|
19
|
-
})),
|
|
20
|
-
}
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
describe("and create()", () => {
|
|
24
|
-
it("should set default list properties on init", () => {
|
|
25
|
-
list.create(entity)
|
|
26
|
-
expect(entity.scrollTop).toBe(0)
|
|
27
|
-
expect(entity.visibleRange).toEqual({ start: 0, end: 20 })
|
|
28
|
-
expect(entity.viewportHeight).toBe(600)
|
|
29
|
-
expect(entity.bufferSize).toBe(5)
|
|
30
|
-
expect(entity.itemHeight).toBeNull()
|
|
31
|
-
expect(entity.estimatedHeight).toBe(50)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it("should not overwrite existing properties on init (except scrollTop)", () => {
|
|
35
|
-
entity.viewportHeight = 800
|
|
36
|
-
entity.visibleRange = { start: 10, end: 30 }
|
|
37
|
-
|
|
38
|
-
list.create(entity)
|
|
39
|
-
|
|
40
|
-
expect(entity.viewportHeight).toBe(800)
|
|
41
|
-
expect(entity.visibleRange).toEqual({ start: 10, end: 30 })
|
|
42
|
-
expect(entity.scrollTop).toBe(0) // scrollTop is always reset
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it("should reset the list on create", () => {
|
|
46
|
-
list.create(entity)
|
|
47
|
-
expect(entity.scrollTop).toBe(0)
|
|
48
|
-
expect(entity.visibleRange).toEqual({ start: 0, end: 20 })
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
describe("scroll()", () => {
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
list.create(entity)
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it("should calculate visible range based on itemHeight", () => {
|
|
58
|
-
entity.itemHeight = 20
|
|
59
|
-
const containerEl = { scrollTop: 200 } // 10 items down
|
|
60
|
-
|
|
61
|
-
list.scroll(entity, containerEl)
|
|
62
|
-
|
|
63
|
-
// start = floor(200 / 20) - 5 = 10 - 5 = 5
|
|
64
|
-
// visibleCount = ceil(600 / 20) = 30
|
|
65
|
-
// end = min(5 + 30 + 5, 100) = 40
|
|
66
|
-
expect(entity.visibleRange).toEqual({ start: 5, end: 40 })
|
|
67
|
-
expect(entity.scrollTop).toBe(200)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it("should calculate visible range based on estimatedHeight if itemHeight is null", () => {
|
|
71
|
-
entity.estimatedHeight = 50
|
|
72
|
-
const containerEl = { scrollTop: 500 } // 10 items down
|
|
73
|
-
|
|
74
|
-
list.scroll(entity, containerEl)
|
|
75
|
-
|
|
76
|
-
// start = floor(500 / 50) - 5 = 10 - 5 = 5
|
|
77
|
-
// visibleCount = ceil(600 / 50) = 12
|
|
78
|
-
// end = min(5 + 12 + 5, 100) = 22
|
|
79
|
-
expect(entity.visibleRange).toEqual({ start: 5, end: 22 })
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it("should not update if the visible range has not changed", () => {
|
|
83
|
-
entity.itemHeight = 20
|
|
84
|
-
entity.visibleRange = { start: 5, end: 40 }
|
|
85
|
-
const originalRange = { ...entity.visibleRange }
|
|
86
|
-
|
|
87
|
-
const containerEl = { scrollTop: 201 } // Still within the same range calculation
|
|
88
|
-
list.scroll(entity, containerEl)
|
|
89
|
-
|
|
90
|
-
expect(entity.visibleRange).toEqual(originalRange) // Should not have changed
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it("should handle scrolling to the top", () => {
|
|
94
|
-
entity.itemHeight = 20
|
|
95
|
-
const containerEl = { scrollTop: 0 }
|
|
96
|
-
|
|
97
|
-
list.scroll(entity, containerEl)
|
|
98
|
-
|
|
99
|
-
// start = max(0, floor(0/20) - 5) = 0
|
|
100
|
-
// end = min(0 + 30 + 5, 100) = 35
|
|
101
|
-
expect(entity.visibleRange).toEqual({ start: 0, end: 35 })
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it("should handle scrolling near the bottom", () => {
|
|
105
|
-
entity.itemHeight = 20
|
|
106
|
-
const containerEl = { scrollTop: 1900 } // 95 items down
|
|
107
|
-
|
|
108
|
-
list.scroll(entity, containerEl)
|
|
109
|
-
|
|
110
|
-
// start = floor(1900 / 20) - 5 = 95 - 5 = 90
|
|
111
|
-
// visibleCount = 30
|
|
112
|
-
// end = min(90 + 30 + 5, 100) = 100
|
|
113
|
-
expect(entity.visibleRange).toEqual({ start: 90, end: 100 })
|
|
114
|
-
})
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
describe("mount()", () => {
|
|
118
|
-
it("should measure and set itemHeight and update visibleRange", () => {
|
|
119
|
-
list.create(entity)
|
|
120
|
-
const itemEl = document.createElement("div")
|
|
121
|
-
vi.spyOn(itemEl, "offsetHeight", "get").mockReturnValue(40)
|
|
122
|
-
|
|
123
|
-
const containerEl = {
|
|
124
|
-
querySelector: vi.fn().mockReturnValue(itemEl),
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
list.mount(entity, containerEl)
|
|
128
|
-
|
|
129
|
-
expect(containerEl.querySelector).toHaveBeenCalledWith("[data-index]")
|
|
130
|
-
expect(entity.itemHeight).toBe(40)
|
|
131
|
-
// end = ceil(600 / 40) = 15
|
|
132
|
-
expect(entity.visibleRange).toEqual({ start: 0, end: 15 })
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
it("should do nothing if no item element is found", () => {
|
|
136
|
-
list.create(entity)
|
|
137
|
-
const originalEntity = { ...entity }
|
|
138
|
-
const containerEl = {
|
|
139
|
-
querySelector: vi.fn().mockReturnValue(null),
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
list.mount(entity, containerEl)
|
|
143
|
-
|
|
144
|
-
expect(entity).toEqual(originalEntity)
|
|
145
|
-
})
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
describe("render()", () => {
|
|
149
|
-
let api
|
|
150
|
-
|
|
151
|
-
beforeEach(() => {
|
|
152
|
-
list.create(entity)
|
|
153
|
-
api = {
|
|
154
|
-
notify: vi.fn(),
|
|
155
|
-
getType: vi.fn().mockReturnValue({
|
|
156
|
-
renderItem: (item, index) => html`<div>${index}: ${item.name}</div>`,
|
|
157
|
-
}),
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it("should return a lit-html TemplateResult", () => {
|
|
162
|
-
const result = list.render(entity, api)
|
|
163
|
-
// Duck-typing for TemplateResult, as it's not a public class
|
|
164
|
-
expect(result).toHaveProperty("strings")
|
|
165
|
-
expect(result).toHaveProperty("values")
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it("should warn and return empty if items are missing", () => {
|
|
169
|
-
const spy = vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
170
|
-
delete entity.items
|
|
171
|
-
const result = list.render(entity, api)
|
|
172
|
-
expect(spy).toHaveBeenCalledWith(`list entity ${entity.id} needs 'items'`)
|
|
173
|
-
expect(result.strings.join("").trim()).toBe("")
|
|
174
|
-
spy.mockRestore()
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
it("should warn and return empty if type.renderItem is missing", () => {
|
|
178
|
-
const spy = vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
179
|
-
api.getType.mockReturnValue({}) // No renderItem
|
|
180
|
-
const result = list.render(entity, api)
|
|
181
|
-
expect(spy).toHaveBeenCalledWith(
|
|
182
|
-
`type ${entity.type} needs 'renderItem' method`,
|
|
183
|
-
)
|
|
184
|
-
expect(result.strings.join("").trim()).toBe("")
|
|
185
|
-
spy.mockRestore()
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
it("should render only the visible items", () => {
|
|
189
|
-
entity.itemHeight = 50
|
|
190
|
-
entity.visibleRange = { start: 10, end: 15 } // 5 visible items
|
|
191
|
-
|
|
192
|
-
const result = list.render(entity, api)
|
|
193
|
-
const renderedItems = result.values.find(Array.isArray)
|
|
194
|
-
|
|
195
|
-
expect(renderedItems).toHaveLength(5) // 5 visible items
|
|
196
|
-
// Check if the first rendered item is indeed item 10
|
|
197
|
-
const innerTemplate = renderedItems[0].values[2]
|
|
198
|
-
const renderedText = `${innerTemplate.values[0]}: ${innerTemplate.values[1]}`
|
|
199
|
-
expect(renderedText).toBe("10: Item 10")
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
it("should calculate total height correctly", () => {
|
|
203
|
-
entity.itemHeight = 40
|
|
204
|
-
const result = list.render(entity, api)
|
|
205
|
-
// The styleMap for the inner div is at result.values[3].values[0]
|
|
206
|
-
const styleValue = result.values[3].values[0]
|
|
207
|
-
const expectedHeight = 100 * 40 // items.length * itemHeight
|
|
208
|
-
expect(styleValue.height).toBe(`${expectedHeight}px`)
|
|
209
|
-
})
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
describe("renderItem()", () => {
|
|
213
|
-
it("should render a default item view", () => {
|
|
214
|
-
const item = { id: 1, value: "test" }
|
|
215
|
-
const result = list.renderItem(item, 5)
|
|
216
|
-
// Duck-typing for TemplateResult
|
|
217
|
-
expect(result).toHaveProperty("strings")
|
|
218
|
-
expect(result).toHaveProperty("values")
|
|
219
|
-
const fullText =
|
|
220
|
-
result.strings[0] +
|
|
221
|
-
result.values[0] +
|
|
222
|
-
result.strings[1] +
|
|
223
|
-
result.values[1] +
|
|
224
|
-
result.strings[2]
|
|
225
|
-
expect(fullText).toBe(`<div>6. ${JSON.stringify(item)}</div>`)
|
|
226
|
-
})
|
|
227
|
-
})
|
|
228
|
-
})
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @vitest-environment jsdom
|
|
3
|
-
*/
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
|
5
|
-
|
|
6
|
-
import { router } from "."
|
|
7
|
-
|
|
8
|
-
describe("router", () => {
|
|
9
|
-
let entity
|
|
10
|
-
let api
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
entity = {
|
|
14
|
-
id: "router",
|
|
15
|
-
type: "router",
|
|
16
|
-
routes: {
|
|
17
|
-
"/": "homePage",
|
|
18
|
-
"/users": "userListPage",
|
|
19
|
-
"/users/:id": "userPage",
|
|
20
|
-
"/users/:id/posts/:postId": "postPage",
|
|
21
|
-
"*": "notFoundPage",
|
|
22
|
-
},
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
api = {
|
|
26
|
-
getEntity: vi.fn().mockReturnValue(entity),
|
|
27
|
-
notify: vi.fn(),
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Mock window.location and history
|
|
31
|
-
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
32
|
-
pathname: "/",
|
|
33
|
-
search: "",
|
|
34
|
-
hash: "",
|
|
35
|
-
origin: "http://localhost:3000",
|
|
36
|
-
})
|
|
37
|
-
vi.spyOn(history, "pushState").mockImplementation(() => {})
|
|
38
|
-
vi.spyOn(history, "replaceState").mockImplementation(() => {})
|
|
39
|
-
vi.spyOn(history, "go").mockImplementation(() => {})
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
afterEach(() => {
|
|
43
|
-
vi.restoreAllMocks()
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
describe("init()", () => {
|
|
47
|
-
it("should initialize with the current window.location, set up a popstate listener, and set up a click listener for link interception", () => {
|
|
48
|
-
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
49
|
-
pathname: "/users/123",
|
|
50
|
-
search: "?sort=asc",
|
|
51
|
-
hash: "#details",
|
|
52
|
-
origin: "http://localhost:3000",
|
|
53
|
-
})
|
|
54
|
-
const windowSpy = vi.spyOn(window, "addEventListener")
|
|
55
|
-
const documentSpy = vi.spyOn(document, "addEventListener")
|
|
56
|
-
|
|
57
|
-
router.init(entity, undefined, api)
|
|
58
|
-
|
|
59
|
-
expect(api.notify).toHaveBeenCalledWith("#router:navigate", {
|
|
60
|
-
to: "/users/123?sort=asc",
|
|
61
|
-
params: { id: "123" },
|
|
62
|
-
replace: true,
|
|
63
|
-
})
|
|
64
|
-
expect(windowSpy).toHaveBeenCalledWith("popstate", expect.any(Function))
|
|
65
|
-
expect(documentSpy).toHaveBeenCalledWith("click", expect.any(Function))
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
describe("navigate()", () => {
|
|
70
|
-
it("should navigate to a new path and update the entity", () => {
|
|
71
|
-
router.navigate(entity, "/users/456?q=test", api)
|
|
72
|
-
|
|
73
|
-
expect(entity.path).toBe("/users/456")
|
|
74
|
-
expect(entity.route).toBe("userPage")
|
|
75
|
-
expect(entity.params).toEqual({ id: "456" })
|
|
76
|
-
expect(entity.query).toEqual({ q: "test" })
|
|
77
|
-
expect(history.pushState).toHaveBeenCalledWith(
|
|
78
|
-
expect.any(Object),
|
|
79
|
-
"",
|
|
80
|
-
"/users/456?q=test",
|
|
81
|
-
)
|
|
82
|
-
expect(api.notify).toHaveBeenCalledWith("routeChange", expect.any(Object))
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it("should use replaceState when replace is true", () => {
|
|
86
|
-
router.navigate(entity, { to: "/users", replace: true }, api)
|
|
87
|
-
expect(history.replaceState).toHaveBeenCalled()
|
|
88
|
-
expect(history.pushState).not.toHaveBeenCalled()
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it("should handle numeric navigation", () => {
|
|
92
|
-
router.navigate(entity, -1, api)
|
|
93
|
-
expect(history.go).toHaveBeenCalledWith(-1)
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it("should build path from params", () => {
|
|
97
|
-
router.navigate(
|
|
98
|
-
entity,
|
|
99
|
-
{ to: "/users/:id/posts/:postId", params: { id: 1, postId: 2 } },
|
|
100
|
-
api,
|
|
101
|
-
)
|
|
102
|
-
expect(history.pushState).toHaveBeenCalledWith(
|
|
103
|
-
expect.any(Object),
|
|
104
|
-
"",
|
|
105
|
-
"/users/1/posts/2",
|
|
106
|
-
)
|
|
107
|
-
expect(entity.route).toBe("postPage")
|
|
108
|
-
expect(entity.params).toEqual({ id: "1", postId: "2" })
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it("should use the fallback route for unknown paths", () => {
|
|
112
|
-
router.navigate(entity, "/some/unknown/path", api)
|
|
113
|
-
expect(entity.route).toBe("notFoundPage")
|
|
114
|
-
expect(entity.params).toEqual({})
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it("should not navigate if the path is identical", () => {
|
|
118
|
-
entity.path = "/users"
|
|
119
|
-
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
120
|
-
pathname: "/users",
|
|
121
|
-
search: "",
|
|
122
|
-
hash: "",
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
router.navigate(entity, "/users", api)
|
|
126
|
-
|
|
127
|
-
expect(history.pushState).not.toHaveBeenCalled()
|
|
128
|
-
expect(api.notify).not.toHaveBeenCalledWith(
|
|
129
|
-
"routeChange",
|
|
130
|
-
expect.any(Object),
|
|
131
|
-
)
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it("should navigate if the path is identical but force is true", () => {
|
|
135
|
-
entity.path = "/users"
|
|
136
|
-
vi.spyOn(window, "location", "get").mockReturnValue({
|
|
137
|
-
pathname: "/users",
|
|
138
|
-
search: "",
|
|
139
|
-
hash: "",
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
router.navigate(entity, { to: "/users", force: true }, api)
|
|
143
|
-
|
|
144
|
-
expect(history.pushState).toHaveBeenCalled()
|
|
145
|
-
expect(api.notify).toHaveBeenCalledWith("routeChange", expect.any(Object))
|
|
146
|
-
})
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
describe("routeSync()", () => {
|
|
150
|
-
it("should update the entity state from a payload", () => {
|
|
151
|
-
const payload = {
|
|
152
|
-
path: "/new?a=1",
|
|
153
|
-
entityType: "newPage",
|
|
154
|
-
params: {},
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
vi.spyOn(window, "location", "get").mockReturnValue({ hash: "#section" })
|
|
158
|
-
|
|
159
|
-
router.routeSync(entity, payload)
|
|
160
|
-
|
|
161
|
-
expect(entity.path).toBe("/new")
|
|
162
|
-
expect(entity.route).toBe("newPage")
|
|
163
|
-
expect(entity.query).toEqual({ a: "1" })
|
|
164
|
-
expect(entity.hash).toBe("#section")
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
describe("loadSuccess()", () => {
|
|
169
|
-
it("should handle lazy loaded modules", () => {
|
|
170
|
-
const module = { myPage: { render: () => {} } }
|
|
171
|
-
const route = { pattern: "/lazy", params: {} }
|
|
172
|
-
const payload = {
|
|
173
|
-
module,
|
|
174
|
-
route,
|
|
175
|
-
path: "/lazy",
|
|
176
|
-
replace: false,
|
|
177
|
-
state: {},
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
router.loadSuccess(entity, payload, api)
|
|
181
|
-
|
|
182
|
-
expect(api.notify).toHaveBeenCalledWith("morph", {
|
|
183
|
-
name: "myPage",
|
|
184
|
-
type: module.myPage,
|
|
185
|
-
})
|
|
186
|
-
expect(entity.routes["/lazy"]).toBe("myPage")
|
|
187
|
-
expect(entity.loading).toBe(false)
|
|
188
|
-
expect(entity.route).toBe("myPage")
|
|
189
|
-
expect(history.pushState).toHaveBeenCalled()
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
describe("loadError()", () => {
|
|
194
|
-
it("should handle load errors", () => {
|
|
195
|
-
const error = new Error("Failed")
|
|
196
|
-
const payload = { error, path: "/lazy" }
|
|
197
|
-
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
|
|
198
|
-
|
|
199
|
-
router.loadError(entity, payload)
|
|
200
|
-
|
|
201
|
-
expect(entity.path).toBe("/lazy")
|
|
202
|
-
expect(entity.loading).toBe(false)
|
|
203
|
-
expect(entity.error).toBe(error)
|
|
204
|
-
|
|
205
|
-
consoleSpy.mockRestore()
|
|
206
|
-
})
|
|
207
|
-
})
|
|
208
|
-
})
|