mtrl-addons 0.1.0 → 0.1.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/.cursorrules +117 -0
- package/AI.md +241 -0
- package/build.js +148 -0
- package/bun.lock +792 -0
- package/index.ts +7 -0
- package/package.json +10 -17
- package/scripts/analyze-orphaned-functions.ts +387 -0
- package/src/components/index.ts +45 -0
- package/src/components/list/api.ts +314 -0
- package/src/components/list/config.ts +352 -0
- package/src/components/list/constants.ts +56 -0
- package/src/components/list/features/api.ts +428 -0
- package/src/components/list/features/index.ts +31 -0
- package/src/components/list/features/list-manager.ts +502 -0
- package/src/components/list/features.ts +112 -0
- package/src/components/list/index.ts +39 -0
- package/src/components/list/list.ts +234 -0
- package/src/components/list/types.ts +513 -0
- package/src/core/collection/base-collection.ts +100 -0
- package/src/core/collection/collection-composer.ts +178 -0
- package/src/core/collection/collection.ts +745 -0
- package/src/core/collection/constants.ts +172 -0
- package/src/core/collection/events.ts +428 -0
- package/src/core/collection/features/api/loading.ts +279 -0
- package/src/core/collection/features/operations/data-operations.ts +147 -0
- package/src/core/collection/index.ts +104 -0
- package/src/core/collection/state.ts +497 -0
- package/src/core/collection/types.ts +404 -0
- package/src/core/compose/features/collection.ts +119 -0
- package/src/core/compose/features/index.ts +39 -0
- package/src/core/compose/features/performance.ts +161 -0
- package/src/core/compose/features/selection.ts +213 -0
- package/src/core/compose/features/styling.ts +108 -0
- package/src/core/compose/index.ts +31 -0
- package/src/core/index.ts +167 -0
- package/src/core/layout/config.ts +102 -0
- package/src/core/layout/index.ts +168 -0
- package/src/core/layout/jsx.ts +174 -0
- package/src/core/layout/schema.ts +963 -0
- package/src/core/layout/types.ts +92 -0
- package/src/core/list-manager/api.ts +599 -0
- package/src/core/list-manager/config.ts +593 -0
- package/src/core/list-manager/constants.ts +268 -0
- package/src/core/list-manager/features/api.ts +58 -0
- package/src/core/list-manager/features/collection/collection.ts +705 -0
- package/src/core/list-manager/features/collection/index.ts +17 -0
- package/src/core/list-manager/features/viewport/constants.ts +42 -0
- package/src/core/list-manager/features/viewport/index.ts +16 -0
- package/src/core/list-manager/features/viewport/item-size.ts +274 -0
- package/src/core/list-manager/features/viewport/loading.ts +263 -0
- package/src/core/list-manager/features/viewport/placeholders.ts +281 -0
- package/src/core/list-manager/features/viewport/rendering.ts +575 -0
- package/src/core/list-manager/features/viewport/scrollbar.ts +495 -0
- package/src/core/list-manager/features/viewport/scrolling.ts +795 -0
- package/src/core/list-manager/features/viewport/template.ts +220 -0
- package/src/core/list-manager/features/viewport/viewport.ts +654 -0
- package/src/core/list-manager/features/viewport/virtual.ts +309 -0
- package/src/core/list-manager/index.ts +279 -0
- package/src/core/list-manager/list-manager.ts +206 -0
- package/src/core/list-manager/types.ts +439 -0
- package/src/core/list-manager/utils/calculations.ts +290 -0
- package/src/core/list-manager/utils/range-calculator.ts +349 -0
- package/src/core/list-manager/utils/speed-tracker.ts +273 -0
- package/src/index.ts +17 -0
- package/src/styles/components/_list.scss +244 -0
- package/src/styles/index.scss +12 -0
- package/src/types/mtrl.d.ts +6 -0
- package/test/benchmarks/layout/advanced.test.ts +656 -0
- package/test/benchmarks/layout/comparison.test.ts +519 -0
- package/test/benchmarks/layout/performance-comparison.test.ts +274 -0
- package/test/benchmarks/layout/real-components.test.ts +733 -0
- package/test/benchmarks/layout/simple.test.ts +321 -0
- package/test/benchmarks/layout/stress.test.ts +990 -0
- package/test/collection/basic.test.ts +304 -0
- package/test/components/list.test.ts +256 -0
- package/test/core/collection/collection.test.ts +394 -0
- package/test/core/collection/failed-ranges.test.ts +270 -0
- package/test/core/compose/features.test.ts +183 -0
- package/test/core/layout/layout.test.ts +201 -0
- package/test/core/list-manager/features/collection.test.ts +704 -0
- package/test/core/list-manager/features/viewport.test.ts +698 -0
- package/test/core/list-manager/list-manager.test.ts +593 -0
- package/test/core/list-manager/utils/calculations.test.ts +433 -0
- package/test/core/list-manager/utils/range-calculator.test.ts +569 -0
- package/test/core/list-manager/utils/speed-tracker.test.ts +530 -0
- package/test/utils/dom-helpers.ts +275 -0
- package/test/utils/performance-helpers.ts +392 -0
- package/tsconfig.build.json +23 -0
- package/tsconfig.json +20 -0
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -38
- package/dist/index.mjs +0 -8
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic collection tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the core functionality of the collection system
|
|
5
|
+
* including creation, data operations, and template rendering.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect, beforeEach } from "bun:test";
|
|
9
|
+
import { JSDOM } from "jsdom";
|
|
10
|
+
|
|
11
|
+
// Mock DOM environment for testing
|
|
12
|
+
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");
|
|
13
|
+
global.document = dom.window.document;
|
|
14
|
+
global.HTMLElement = dom.window.HTMLElement;
|
|
15
|
+
global.requestAnimationFrame = (cb: FrameRequestCallback) => {
|
|
16
|
+
setTimeout(cb, 0);
|
|
17
|
+
return 0;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Import our collection system
|
|
21
|
+
import {
|
|
22
|
+
createCollection,
|
|
23
|
+
type CollectionItem,
|
|
24
|
+
} from "../../src/core/collection";
|
|
25
|
+
|
|
26
|
+
// Test data interface
|
|
27
|
+
interface TestUser extends CollectionItem {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
email: string;
|
|
31
|
+
age: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Test data
|
|
35
|
+
const testUsers: TestUser[] = [
|
|
36
|
+
{ id: "1", name: "John Doe", email: "john@example.com", age: 30 },
|
|
37
|
+
{ id: "2", name: "Jane Smith", email: "jane@example.com", age: 25 },
|
|
38
|
+
{ id: "3", name: "Bob Johnson", email: "bob@example.com", age: 35 },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
describe("Collection System", () => {
|
|
42
|
+
let container: HTMLElement;
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
// Create a fresh container for each test
|
|
46
|
+
container = document.createElement("div");
|
|
47
|
+
document.body.appendChild(container);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("creates collection with basic configuration", () => {
|
|
51
|
+
const collection = createCollection<TestUser>({
|
|
52
|
+
container,
|
|
53
|
+
items: testUsers,
|
|
54
|
+
className: "user-list",
|
|
55
|
+
ariaLabel: "User List",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(collection.element).toBe(container);
|
|
59
|
+
expect(collection.getSize()).toBe(3);
|
|
60
|
+
expect(collection.isEmpty()).toBe(false);
|
|
61
|
+
expect(container.classList.contains("mtrl-collection")).toBe(true);
|
|
62
|
+
expect(container.classList.contains("user-list")).toBe(true);
|
|
63
|
+
expect(container.getAttribute("aria-label")).toBe("User List");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("renders items with default template", async () => {
|
|
67
|
+
const collection = createCollection<TestUser>({
|
|
68
|
+
container,
|
|
69
|
+
items: testUsers,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Wait for rendering
|
|
73
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
74
|
+
|
|
75
|
+
expect(container.children.length).toBe(3);
|
|
76
|
+
expect(container.children[0].textContent).toBe("1");
|
|
77
|
+
expect(container.children[1].textContent).toBe("2");
|
|
78
|
+
expect(container.children[2].textContent).toBe("3");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("renders items with object template", async () => {
|
|
82
|
+
const collection = createCollection<TestUser>({
|
|
83
|
+
container,
|
|
84
|
+
items: testUsers,
|
|
85
|
+
template: {
|
|
86
|
+
tag: "div",
|
|
87
|
+
className: "user-item",
|
|
88
|
+
children: [
|
|
89
|
+
{ tag: "div", className: "user-name", textContent: "{{name}}" },
|
|
90
|
+
{ tag: "div", className: "user-email", textContent: "{{email}}" },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Wait for rendering
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
97
|
+
|
|
98
|
+
expect(container.children.length).toBe(3);
|
|
99
|
+
|
|
100
|
+
const firstUser = container.children[0] as HTMLElement;
|
|
101
|
+
expect(firstUser.className).toBe("user-item");
|
|
102
|
+
expect(firstUser.children.length).toBe(2);
|
|
103
|
+
expect(firstUser.children[0].textContent).toBe("John Doe");
|
|
104
|
+
expect(firstUser.children[1].textContent).toBe("john@example.com");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("renders items with string template", async () => {
|
|
108
|
+
const collection = createCollection<TestUser>({
|
|
109
|
+
container,
|
|
110
|
+
items: testUsers,
|
|
111
|
+
template: "<div class='user'>{{name}} ({{email}})</div>",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Wait for rendering
|
|
115
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
116
|
+
|
|
117
|
+
expect(container.children.length).toBe(3);
|
|
118
|
+
expect(container.children[0].innerHTML).toBe(
|
|
119
|
+
"<div class='user'>John Doe (john@example.com)</div>"
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("adds new items", async () => {
|
|
124
|
+
const collection = createCollection<TestUser>({
|
|
125
|
+
container,
|
|
126
|
+
items: [testUsers[0]],
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(collection.getSize()).toBe(1);
|
|
130
|
+
|
|
131
|
+
const newUser: TestUser = {
|
|
132
|
+
id: "4",
|
|
133
|
+
name: "Alice Brown",
|
|
134
|
+
email: "alice@example.com",
|
|
135
|
+
age: 28,
|
|
136
|
+
};
|
|
137
|
+
await collection.add(newUser);
|
|
138
|
+
|
|
139
|
+
expect(collection.getSize()).toBe(2);
|
|
140
|
+
const items = collection.getItems();
|
|
141
|
+
expect(items.length).toBe(2);
|
|
142
|
+
expect(items[1].name).toBe("Alice Brown");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("updates existing items", async () => {
|
|
146
|
+
const collection = createCollection<TestUser>({
|
|
147
|
+
container,
|
|
148
|
+
items: testUsers,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const updatedUser: TestUser = {
|
|
152
|
+
id: "1",
|
|
153
|
+
name: "John Smith",
|
|
154
|
+
email: "johnsmith@example.com",
|
|
155
|
+
age: 31,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
await collection.update(updatedUser);
|
|
159
|
+
|
|
160
|
+
const user = collection.getItem("1");
|
|
161
|
+
expect(user?.name).toBe("John Smith");
|
|
162
|
+
expect(user?.email).toBe("johnsmith@example.com");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("removes items", async () => {
|
|
166
|
+
const collection = createCollection<TestUser>({
|
|
167
|
+
container,
|
|
168
|
+
items: testUsers,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
expect(collection.getSize()).toBe(3);
|
|
172
|
+
|
|
173
|
+
await collection.remove("2");
|
|
174
|
+
|
|
175
|
+
expect(collection.getSize()).toBe(2);
|
|
176
|
+
expect(collection.getItem("2")).toBeUndefined();
|
|
177
|
+
expect(collection.getItem("1")).toBeDefined();
|
|
178
|
+
expect(collection.getItem("3")).toBeDefined();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("clears all items", () => {
|
|
182
|
+
const collection = createCollection<TestUser>({
|
|
183
|
+
container,
|
|
184
|
+
items: testUsers,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(collection.getSize()).toBe(3);
|
|
188
|
+
|
|
189
|
+
collection.clear();
|
|
190
|
+
|
|
191
|
+
expect(collection.getSize()).toBe(0);
|
|
192
|
+
expect(collection.isEmpty()).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("filters items with query", () => {
|
|
196
|
+
const collection = createCollection<TestUser>({
|
|
197
|
+
container,
|
|
198
|
+
items: testUsers,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(collection.getSize()).toBe(3);
|
|
202
|
+
|
|
203
|
+
// Filter users over 30
|
|
204
|
+
collection.query((user) => user.age > 30);
|
|
205
|
+
|
|
206
|
+
expect(collection.getSize()).toBe(1);
|
|
207
|
+
expect(collection.getItems()[0].name).toBe("Bob Johnson");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("sorts items", () => {
|
|
211
|
+
const collection = createCollection<TestUser>({
|
|
212
|
+
container,
|
|
213
|
+
items: testUsers,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Sort by age ascending
|
|
217
|
+
collection.sort((a, b) => a.age - b.age);
|
|
218
|
+
|
|
219
|
+
const items = collection.getItems();
|
|
220
|
+
expect(items[0].name).toBe("Jane Smith"); // age 25
|
|
221
|
+
expect(items[1].name).toBe("John Doe"); // age 30
|
|
222
|
+
expect(items[2].name).toBe("Bob Johnson"); // age 35
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("validates items when adding", async () => {
|
|
226
|
+
const collection = createCollection<TestUser>({
|
|
227
|
+
container,
|
|
228
|
+
items: [],
|
|
229
|
+
validate: (user) => user.age >= 18,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const validUser: TestUser = {
|
|
233
|
+
id: "1",
|
|
234
|
+
name: "John Doe",
|
|
235
|
+
email: "john@example.com",
|
|
236
|
+
age: 30,
|
|
237
|
+
};
|
|
238
|
+
const invalidUser: TestUser = {
|
|
239
|
+
id: "2",
|
|
240
|
+
name: "Minor",
|
|
241
|
+
email: "minor@example.com",
|
|
242
|
+
age: 16,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
await collection.add([validUser, invalidUser]);
|
|
246
|
+
|
|
247
|
+
expect(collection.getSize()).toBe(1);
|
|
248
|
+
expect(collection.getItems()[0].name).toBe("John Doe");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("transforms items when adding", async () => {
|
|
252
|
+
const collection = createCollection<TestUser>({
|
|
253
|
+
container,
|
|
254
|
+
items: [],
|
|
255
|
+
transform: (user: any) => ({
|
|
256
|
+
...user,
|
|
257
|
+
name: user.name.toUpperCase(),
|
|
258
|
+
}),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const user: TestUser = {
|
|
262
|
+
id: "1",
|
|
263
|
+
name: "John Doe",
|
|
264
|
+
email: "john@example.com",
|
|
265
|
+
age: 30,
|
|
266
|
+
};
|
|
267
|
+
await collection.add(user);
|
|
268
|
+
|
|
269
|
+
expect(collection.getItems()[0].name).toBe("JOHN DOE");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("handles template changes", async () => {
|
|
273
|
+
const collection = createCollection<TestUser>({
|
|
274
|
+
container,
|
|
275
|
+
items: [testUsers[0]],
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Change template
|
|
279
|
+
collection.setTemplate({
|
|
280
|
+
tag: "div",
|
|
281
|
+
className: "new-template",
|
|
282
|
+
textContent: "User: {{name}}",
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Wait for rendering
|
|
286
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
287
|
+
|
|
288
|
+
expect(container.children[0].textContent).toBe("User: John Doe");
|
|
289
|
+
expect(container.children[0].className).toBe("new-template");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("destroys collection properly", () => {
|
|
293
|
+
const collection = createCollection<TestUser>({
|
|
294
|
+
container,
|
|
295
|
+
items: testUsers,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
expect(container.children.length).toBeGreaterThan(0);
|
|
299
|
+
|
|
300
|
+
collection.destroy();
|
|
301
|
+
|
|
302
|
+
expect(container.children.length).toBe(0);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic list component tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the pipe composition list component that wraps the collection system
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, test, expect, beforeEach } from "bun:test";
|
|
8
|
+
import { JSDOM } from "jsdom";
|
|
9
|
+
|
|
10
|
+
// Mock DOM environment for testing
|
|
11
|
+
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");
|
|
12
|
+
global.document = dom.window.document;
|
|
13
|
+
global.HTMLElement = dom.window.HTMLElement;
|
|
14
|
+
global.window = dom.window as any;
|
|
15
|
+
global.navigator = dom.window.navigator;
|
|
16
|
+
global.requestAnimationFrame = (cb: FrameRequestCallback) => {
|
|
17
|
+
setTimeout(cb, 0);
|
|
18
|
+
return 0;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Mock scrollIntoView since JSDOM doesn't implement it
|
|
22
|
+
global.HTMLElement.prototype.scrollIntoView = function (
|
|
23
|
+
options?: ScrollIntoViewOptions
|
|
24
|
+
) {
|
|
25
|
+
// Mock implementation - just logs the call
|
|
26
|
+
console.log(`Mock scrollIntoView called with options:`, options);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Import our list component
|
|
30
|
+
import { createList, type ListItem } from "../../src/components/list";
|
|
31
|
+
|
|
32
|
+
// Test data interface
|
|
33
|
+
interface TestUser extends ListItem {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
email: string;
|
|
37
|
+
age: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Test data
|
|
41
|
+
const testUsers: TestUser[] = [
|
|
42
|
+
{ id: "1", name: "John Doe", email: "john@example.com", age: 30 },
|
|
43
|
+
{ id: "2", name: "Jane Smith", email: "jane@example.com", age: 25 },
|
|
44
|
+
{ id: "3", name: "Bob Johnson", email: "bob@example.com", age: 35 },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
describe("List Component (Pipe Composition)", () => {
|
|
48
|
+
let container: HTMLElement;
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
// Create a fresh container for each test
|
|
52
|
+
container = document.createElement("div");
|
|
53
|
+
document.body.appendChild(container);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("creates list with pipe composition pattern", () => {
|
|
57
|
+
const list = createList<TestUser>({
|
|
58
|
+
container,
|
|
59
|
+
items: testUsers,
|
|
60
|
+
className: "user-list",
|
|
61
|
+
ariaLabel: "User List",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// mtrl creates a new element and mounts it to the container
|
|
65
|
+
expect(list.element).not.toBe(container);
|
|
66
|
+
expect(list.element.parentElement).toBe(container);
|
|
67
|
+
expect(list.getSize()).toBe(3);
|
|
68
|
+
expect(list.isEmpty()).toBe(false);
|
|
69
|
+
expect(list.element.classList.contains("mtrl-list")).toBe(true);
|
|
70
|
+
expect(list.element.getAttribute("aria-label")).toBe("User List");
|
|
71
|
+
expect(list.element.getAttribute("role")).toBe("list");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("applies list styling through composition", async () => {
|
|
75
|
+
const list = createList<TestUser>({
|
|
76
|
+
container,
|
|
77
|
+
items: testUsers,
|
|
78
|
+
listStyle: {
|
|
79
|
+
itemSize: 60,
|
|
80
|
+
gap: 8,
|
|
81
|
+
padding: 16,
|
|
82
|
+
striped: true,
|
|
83
|
+
hoverable: true,
|
|
84
|
+
bordered: false,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Wait for rendering
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
90
|
+
|
|
91
|
+
// Check styling on the list element, not the container
|
|
92
|
+
expect(list.element.style.getPropertyValue("--mtrl-list-gap")).toBe("8px");
|
|
93
|
+
expect(list.element.style.getPropertyValue("--mtrl-list-padding")).toBe(
|
|
94
|
+
"16px"
|
|
95
|
+
);
|
|
96
|
+
expect(list.element.style.getPropertyValue("--mtrl-list-item-height")).toBe(
|
|
97
|
+
"60px"
|
|
98
|
+
);
|
|
99
|
+
expect(list.element.classList.contains("mtrl-list--striped")).toBe(true);
|
|
100
|
+
expect(list.element.classList.contains("mtrl-list--hoverable")).toBe(true);
|
|
101
|
+
expect(list.element.classList.contains("mtrl-list--bordered")).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("enables selection through composition", async () => {
|
|
105
|
+
const list = createList<TestUser>({
|
|
106
|
+
container,
|
|
107
|
+
items: testUsers,
|
|
108
|
+
selection: {
|
|
109
|
+
enabled: true,
|
|
110
|
+
multiple: true,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Wait for rendering
|
|
115
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
116
|
+
|
|
117
|
+
// Test selection methods exist
|
|
118
|
+
expect(typeof list.selectItem).toBe("function");
|
|
119
|
+
expect(typeof list.deselectItem).toBe("function");
|
|
120
|
+
expect(typeof list.selectAll).toBe("function");
|
|
121
|
+
expect(typeof list.deselectAll).toBe("function");
|
|
122
|
+
expect(typeof list.getSelectedItems).toBe("function");
|
|
123
|
+
expect(typeof list.getSelectedIds).toBe("function");
|
|
124
|
+
|
|
125
|
+
// Test selection functionality
|
|
126
|
+
list.selectItem("1");
|
|
127
|
+
expect(list.getSelectedIds()).toContain("1");
|
|
128
|
+
expect(list.getSelectedItems().length).toBe(1);
|
|
129
|
+
|
|
130
|
+
list.selectItem("2");
|
|
131
|
+
expect(list.getSelectedIds()).toHaveLength(2);
|
|
132
|
+
|
|
133
|
+
list.deselectItem("1");
|
|
134
|
+
expect(list.getSelectedIds()).toHaveLength(1);
|
|
135
|
+
expect(list.getSelectedIds()).toContain("2");
|
|
136
|
+
|
|
137
|
+
list.deselectAll();
|
|
138
|
+
expect(list.getSelectedIds()).toHaveLength(0);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("tracks performance through composition", async () => {
|
|
142
|
+
const list = createList<TestUser>({
|
|
143
|
+
container,
|
|
144
|
+
items: testUsers,
|
|
145
|
+
performance: {
|
|
146
|
+
recycleElements: true,
|
|
147
|
+
bufferSize: 50,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Wait for rendering
|
|
152
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
153
|
+
|
|
154
|
+
// Test performance methods exist
|
|
155
|
+
expect(typeof list.getMetrics).toBe("function");
|
|
156
|
+
expect(typeof list.resetMetrics).toBe("function");
|
|
157
|
+
|
|
158
|
+
const metrics = list.getMetrics();
|
|
159
|
+
expect(typeof metrics.renderCount).toBe("number");
|
|
160
|
+
expect(typeof metrics.scrollCount).toBe("number");
|
|
161
|
+
expect(typeof metrics.averageRenderTime).toBe("number");
|
|
162
|
+
expect(typeof metrics.averageScrollTime).toBe("number");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("provides list API through composition", async () => {
|
|
166
|
+
const list = createList<TestUser>({
|
|
167
|
+
container,
|
|
168
|
+
items: testUsers,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Wait for rendering
|
|
172
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
173
|
+
|
|
174
|
+
// Test API methods exist
|
|
175
|
+
expect(typeof list.scrollToItem).toBe("function");
|
|
176
|
+
expect(typeof list.scrollToIndex).toBe("function");
|
|
177
|
+
expect(typeof list.scrollToPage).toBe("function");
|
|
178
|
+
|
|
179
|
+
// Test scrolling doesn't crash
|
|
180
|
+
list.scrollToItem("2");
|
|
181
|
+
list.scrollToIndex(1);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("maintains collection functionality", async () => {
|
|
185
|
+
const list = createList<TestUser>({
|
|
186
|
+
container,
|
|
187
|
+
items: [testUsers[0]],
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Test collection methods still work
|
|
191
|
+
expect(list.getSize()).toBe(1);
|
|
192
|
+
|
|
193
|
+
const newUser: TestUser = {
|
|
194
|
+
id: "4",
|
|
195
|
+
name: "Alice Brown",
|
|
196
|
+
email: "alice@example.com",
|
|
197
|
+
age: 28,
|
|
198
|
+
};
|
|
199
|
+
await list.add(newUser);
|
|
200
|
+
|
|
201
|
+
expect(list.getSize()).toBe(2);
|
|
202
|
+
expect(list.getItem("4")?.name).toBe("Alice Brown");
|
|
203
|
+
|
|
204
|
+
await list.remove("1");
|
|
205
|
+
expect(list.getSize()).toBe(1);
|
|
206
|
+
expect(list.getItem("1")).toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("console shows pipe composition pattern", () => {
|
|
210
|
+
// Capture console logs
|
|
211
|
+
const logs: string[] = [];
|
|
212
|
+
const originalLog = console.log;
|
|
213
|
+
console.log = (message: string) => {
|
|
214
|
+
logs.push(message);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
createList<TestUser>({
|
|
219
|
+
container,
|
|
220
|
+
items: testUsers,
|
|
221
|
+
selection: { enabled: true },
|
|
222
|
+
listStyle: { striped: true },
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Restore console
|
|
226
|
+
console.log = originalLog;
|
|
227
|
+
|
|
228
|
+
// Verify composition pattern logs
|
|
229
|
+
expect(
|
|
230
|
+
logs.some((log) =>
|
|
231
|
+
log.includes("Creating list component (mtrl compose system)")
|
|
232
|
+
)
|
|
233
|
+
).toBe(true);
|
|
234
|
+
expect(
|
|
235
|
+
logs.some((log) => log.includes("Adding collection capabilities"))
|
|
236
|
+
).toBe(true);
|
|
237
|
+
expect(
|
|
238
|
+
logs.some((log) => log.includes("Adding styling capabilities"))
|
|
239
|
+
).toBe(true);
|
|
240
|
+
expect(
|
|
241
|
+
logs.some((log) => log.includes("Adding selection capabilities"))
|
|
242
|
+
).toBe(true);
|
|
243
|
+
expect(
|
|
244
|
+
logs.some((log) => log.includes("Adding performance tracking"))
|
|
245
|
+
).toBe(true);
|
|
246
|
+
expect(logs.some((log) => log.includes("Adding list API methods"))).toBe(
|
|
247
|
+
true
|
|
248
|
+
);
|
|
249
|
+
expect(
|
|
250
|
+
logs.some((log) => log.includes("created via mtrl compose system"))
|
|
251
|
+
).toBe(true);
|
|
252
|
+
} finally {
|
|
253
|
+
console.log = originalLog;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
});
|