mtrl-addons 0.1.2 → 0.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.
Files changed (117) hide show
  1. package/AI.md +28 -230
  2. package/CLAUDE.md +882 -0
  3. package/build.js +253 -24
  4. package/package.json +14 -4
  5. package/scripts/debug/vlist-selection.ts +121 -0
  6. package/src/components/index.ts +5 -41
  7. package/src/components/{list → vlist}/config.ts +66 -95
  8. package/src/components/vlist/constants.ts +23 -0
  9. package/src/components/vlist/features/api.ts +626 -0
  10. package/src/components/vlist/features/index.ts +10 -0
  11. package/src/components/vlist/features/selection.ts +436 -0
  12. package/src/components/vlist/features/viewport.ts +59 -0
  13. package/src/components/vlist/index.ts +17 -0
  14. package/src/components/{list → vlist}/types.ts +242 -32
  15. package/src/components/vlist/vlist.ts +92 -0
  16. package/src/core/compose/features/gestures/index.ts +227 -0
  17. package/src/core/compose/features/gestures/longpress.ts +383 -0
  18. package/src/core/compose/features/gestures/pan.ts +424 -0
  19. package/src/core/compose/features/gestures/pinch.ts +475 -0
  20. package/src/core/compose/features/gestures/rotate.ts +485 -0
  21. package/src/core/compose/features/gestures/swipe.ts +492 -0
  22. package/src/core/compose/features/gestures/tap.ts +334 -0
  23. package/src/core/compose/features/index.ts +2 -38
  24. package/src/core/compose/index.ts +13 -29
  25. package/src/core/gestures/index.ts +31 -0
  26. package/src/core/gestures/longpress.ts +68 -0
  27. package/src/core/gestures/manager.ts +418 -0
  28. package/src/core/gestures/pan.ts +48 -0
  29. package/src/core/gestures/pinch.ts +58 -0
  30. package/src/core/gestures/rotate.ts +58 -0
  31. package/src/core/gestures/swipe.ts +66 -0
  32. package/src/core/gestures/tap.ts +45 -0
  33. package/src/core/gestures/types.ts +387 -0
  34. package/src/core/gestures/utils.ts +128 -0
  35. package/src/core/index.ts +27 -151
  36. package/src/core/layout/schema.ts +153 -72
  37. package/src/core/layout/types.ts +5 -2
  38. package/src/core/viewport/constants.ts +145 -0
  39. package/src/core/viewport/features/base.ts +73 -0
  40. package/src/core/viewport/features/collection.ts +1182 -0
  41. package/src/core/viewport/features/events.ts +130 -0
  42. package/src/core/viewport/features/index.ts +20 -0
  43. package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +31 -34
  44. package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
  45. package/src/core/viewport/features/momentum.ts +269 -0
  46. package/src/core/viewport/features/placeholders.ts +335 -0
  47. package/src/core/viewport/features/rendering.ts +962 -0
  48. package/src/core/viewport/features/scrollbar.ts +434 -0
  49. package/src/core/viewport/features/scrolling.ts +634 -0
  50. package/src/core/viewport/features/utils.ts +94 -0
  51. package/src/core/viewport/features/virtual.ts +525 -0
  52. package/src/core/viewport/index.ts +31 -0
  53. package/src/core/viewport/types.ts +133 -0
  54. package/src/core/viewport/utils/speed-tracker.ts +79 -0
  55. package/src/core/viewport/viewport.ts +265 -0
  56. package/src/index.ts +0 -7
  57. package/src/styles/components/_vlist.scss +352 -0
  58. package/src/styles/index.scss +1 -1
  59. package/test/components/vlist-selection.test.ts +240 -0
  60. package/test/components/vlist.test.ts +63 -0
  61. package/test/core/collection/adapter.test.ts +161 -0
  62. package/bun.lock +0 -792
  63. package/src/components/list/api.ts +0 -314
  64. package/src/components/list/constants.ts +0 -56
  65. package/src/components/list/features/api.ts +0 -428
  66. package/src/components/list/features/index.ts +0 -31
  67. package/src/components/list/features/list-manager.ts +0 -502
  68. package/src/components/list/index.ts +0 -39
  69. package/src/components/list/list.ts +0 -234
  70. package/src/core/collection/base-collection.ts +0 -100
  71. package/src/core/collection/collection-composer.ts +0 -178
  72. package/src/core/collection/collection.ts +0 -745
  73. package/src/core/collection/constants.ts +0 -172
  74. package/src/core/collection/events.ts +0 -428
  75. package/src/core/collection/features/api/loading.ts +0 -279
  76. package/src/core/collection/features/operations/data-operations.ts +0 -147
  77. package/src/core/collection/index.ts +0 -104
  78. package/src/core/collection/state.ts +0 -497
  79. package/src/core/collection/types.ts +0 -404
  80. package/src/core/compose/features/collection.ts +0 -119
  81. package/src/core/compose/features/selection.ts +0 -213
  82. package/src/core/compose/features/styling.ts +0 -108
  83. package/src/core/list-manager/api.ts +0 -599
  84. package/src/core/list-manager/config.ts +0 -593
  85. package/src/core/list-manager/constants.ts +0 -268
  86. package/src/core/list-manager/features/api.ts +0 -58
  87. package/src/core/list-manager/features/collection/collection.ts +0 -705
  88. package/src/core/list-manager/features/collection/index.ts +0 -17
  89. package/src/core/list-manager/features/viewport/constants.ts +0 -42
  90. package/src/core/list-manager/features/viewport/index.ts +0 -16
  91. package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
  92. package/src/core/list-manager/features/viewport/rendering.ts +0 -575
  93. package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
  94. package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
  95. package/src/core/list-manager/features/viewport/template.ts +0 -220
  96. package/src/core/list-manager/features/viewport/viewport.ts +0 -654
  97. package/src/core/list-manager/features/viewport/virtual.ts +0 -309
  98. package/src/core/list-manager/index.ts +0 -279
  99. package/src/core/list-manager/list-manager.ts +0 -206
  100. package/src/core/list-manager/types.ts +0 -439
  101. package/src/core/list-manager/utils/calculations.ts +0 -290
  102. package/src/core/list-manager/utils/range-calculator.ts +0 -349
  103. package/src/core/list-manager/utils/speed-tracker.ts +0 -273
  104. package/src/styles/components/_list.scss +0 -244
  105. package/src/types/mtrl.d.ts +0 -6
  106. package/test/components/list.test.ts +0 -256
  107. package/test/core/collection/failed-ranges.test.ts +0 -270
  108. package/test/core/compose/features.test.ts +0 -183
  109. package/test/core/list-manager/features/collection.test.ts +0 -704
  110. package/test/core/list-manager/features/viewport.test.ts +0 -698
  111. package/test/core/list-manager/list-manager.test.ts +0 -593
  112. package/test/core/list-manager/utils/calculations.test.ts +0 -433
  113. package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
  114. package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
  115. package/tsconfig.build.json +0 -23
  116. /package/src/components/{list → vlist}/features.ts +0 -0
  117. /package/src/core/{compose → viewport}/features/performance.ts +0 -0
@@ -1,256 +0,0 @@
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
- });
@@ -1,270 +0,0 @@
1
- // test/core/collection/failed-ranges.test.ts
2
-
3
- import { describe, test, expect, beforeEach, mock, afterEach } from "bun:test";
4
- import { createListManager } from "../../../src/core/list-manager/list-manager";
5
- import { withCollection } from "../../../src/core/list-manager/features/collection/collection";
6
- import type { ListManagerConfig } from "../../../src/core/list-manager/types";
7
-
8
- describe("Collection - Failed Range Tracking", () => {
9
- let container: HTMLElement;
10
- let config: ListManagerConfig;
11
- let mockAdapter: any;
12
-
13
- beforeEach(() => {
14
- container = document.createElement("div");
15
- container.style.height = "600px";
16
- container.style.width = "400px";
17
- document.body.appendChild(container);
18
-
19
- // Mock adapter that can simulate failures
20
- mockAdapter = {
21
- read: mock(),
22
- };
23
-
24
- config = {
25
- container,
26
- items: [],
27
- template: {
28
- template: (item: any) => `<div>Item ${item.id}</div>`,
29
- },
30
- virtual: {
31
- enabled: true,
32
- itemSize: 84,
33
- estimatedItemSize: 84,
34
- overscan: 5,
35
- },
36
- };
37
- });
38
-
39
- afterEach(() => {
40
- container.remove();
41
- mock.restore();
42
- });
43
-
44
- test("should track failed ranges when data loading fails", async () => {
45
- // Configure adapter to fail
46
- mockAdapter.read.mockRejectedValueOnce(new Error("Network error"));
47
-
48
- const manager = createListManager(config);
49
- const enhancedManager = withCollection({
50
- collection: mockAdapter,
51
- rangeSize: 20,
52
- })(manager);
53
-
54
- // Try to load a range
55
- try {
56
- await enhancedManager.collection.loadRange(0, 20);
57
- } catch (error) {
58
- // Expected to fail
59
- }
60
-
61
- // Check that failed range is tracked
62
- const failedRanges = enhancedManager.collection.getFailedRanges();
63
- expect(failedRanges.size).toBe(1);
64
- expect(failedRanges.has(0)).toBe(true);
65
-
66
- const failedInfo = failedRanges.get(0);
67
- expect(failedInfo?.attempts).toBe(1);
68
- expect(failedInfo?.error.message).toBe("Network error");
69
- });
70
-
71
- test("should not reload failed ranges immediately", async () => {
72
- // Configure adapter to fail
73
- mockAdapter.read.mockRejectedValue(new Error("Network error"));
74
-
75
- const manager = createListManager(config);
76
- const enhancedManager = withCollection({
77
- collection: mockAdapter,
78
- rangeSize: 20,
79
- })(manager);
80
-
81
- // First attempt - should fail
82
- try {
83
- await enhancedManager.collection.loadRange(0, 20);
84
- } catch (error) {
85
- // Expected
86
- }
87
-
88
- // Reset mock
89
- mockAdapter.read.mockClear();
90
-
91
- // Try to load missing ranges immediately - should skip failed range
92
- await enhancedManager.collection.loadMissingRanges({ start: 0, end: 19 });
93
-
94
- // Should not have tried to load again
95
- expect(mockAdapter.read).not.toHaveBeenCalled();
96
- });
97
-
98
- test("should retry failed ranges after clearing lastAttempt", async () => {
99
- // Configure adapter to fail first, then succeed
100
- mockAdapter.read
101
- .mockRejectedValueOnce(new Error("Network error"))
102
- .mockResolvedValueOnce({
103
- items: Array.from({ length: 20 }, (_, i) => ({
104
- id: i,
105
- name: `Item ${i}`,
106
- })),
107
- meta: { total: 100 },
108
- });
109
-
110
- const manager = createListManager(config);
111
- const enhancedManager = withCollection({
112
- collection: mockAdapter,
113
- rangeSize: 20,
114
- })(manager);
115
-
116
- // First attempt - should fail
117
- try {
118
- await enhancedManager.collection.loadRange(0, 20);
119
- } catch (error) {
120
- // Expected
121
- }
122
-
123
- // Manually retry failed ranges
124
- await enhancedManager.collection.retryFailedRanges();
125
-
126
- // Check that failed range was cleared after success
127
- const failedRanges = enhancedManager.collection.getFailedRanges();
128
- expect(failedRanges.size).toBe(0);
129
-
130
- // Check that data was loaded
131
- const loadedRanges = enhancedManager.collection.getLoadedRanges();
132
- expect(loadedRanges.has(0)).toBe(true);
133
- });
134
-
135
- test("should track multiple attempts", async () => {
136
- // Configure adapter to fail multiple times
137
- mockAdapter.read.mockRejectedValue(new Error("Network error"));
138
-
139
- const manager = createListManager(config);
140
- const enhancedManager = withCollection({
141
- collection: mockAdapter,
142
- rangeSize: 20,
143
- })(manager);
144
-
145
- // Multiple attempts
146
- for (let i = 0; i < 3; i++) {
147
- try {
148
- // Clear lastAttempt to allow immediate retry
149
- const failedRanges = enhancedManager.collection.getFailedRanges();
150
- failedRanges.forEach((info) => {
151
- info.lastAttempt = 0;
152
- });
153
-
154
- await enhancedManager.collection.loadRange(0, 20);
155
- } catch (error) {
156
- // Expected
157
- }
158
- }
159
-
160
- // Check attempt count
161
- const failedRanges = enhancedManager.collection.getFailedRanges();
162
- const failedInfo = failedRanges.get(0);
163
- expect(failedInfo?.attempts).toBe(3);
164
- });
165
-
166
- test("should clear failed ranges", async () => {
167
- // Configure adapter to fail
168
- mockAdapter.read.mockRejectedValue(new Error("Network error"));
169
-
170
- const manager = createListManager(config);
171
- const enhancedManager = withCollection({
172
- collection: mockAdapter,
173
- rangeSize: 20,
174
- })(manager);
175
-
176
- // Fail to load some ranges
177
- try {
178
- await enhancedManager.collection.loadRange(0, 20);
179
- } catch (error) {
180
- // Expected
181
- }
182
- try {
183
- await enhancedManager.collection.loadRange(40, 20);
184
- } catch (error) {
185
- // Expected
186
- }
187
-
188
- // Should have 2 failed ranges
189
- expect(enhancedManager.collection.getFailedRanges().size).toBe(2);
190
-
191
- // Clear failed ranges
192
- enhancedManager.collection.clearFailedRanges();
193
-
194
- // Should have no failed ranges
195
- expect(enhancedManager.collection.getFailedRanges().size).toBe(0);
196
- });
197
-
198
- test("should handle concurrent failures correctly", async () => {
199
- // Configure adapter to fail
200
- mockAdapter.read.mockRejectedValue(new Error("Network error"));
201
-
202
- const manager = createListManager(config);
203
- const enhancedManager = withCollection({
204
- collection: mockAdapter,
205
- rangeSize: 20,
206
- })(manager);
207
-
208
- // Try to load multiple ranges concurrently
209
- const promises = [
210
- enhancedManager.collection.loadRange(0, 20),
211
- enhancedManager.collection.loadRange(20, 20),
212
- enhancedManager.collection.loadRange(40, 20),
213
- ];
214
-
215
- // All should fail
216
- const results = await Promise.allSettled(promises);
217
- expect(results.every((r) => r.status === "rejected")).toBe(true);
218
-
219
- // Should have 3 failed ranges
220
- const failedRanges = enhancedManager.collection.getFailedRanges();
221
- expect(failedRanges.size).toBe(3);
222
- expect(failedRanges.has(0)).toBe(true);
223
- expect(failedRanges.has(1)).toBe(true);
224
- expect(failedRanges.has(2)).toBe(true);
225
- });
226
-
227
- test("should respect exponential backoff", async () => {
228
- // Configure adapter to fail
229
- mockAdapter.read.mockRejectedValue(new Error("Network error"));
230
-
231
- const manager = createListManager(config);
232
- const enhancedManager = withCollection({
233
- collection: mockAdapter,
234
- rangeSize: 20,
235
- })(manager);
236
-
237
- // First failure
238
- try {
239
- await enhancedManager.collection.loadRange(0, 20);
240
- } catch (error) {
241
- // Expected
242
- }
243
-
244
- // Try again immediately - should be blocked
245
- mockAdapter.read.mockClear();
246
- try {
247
- await enhancedManager.collection.loadRange(0, 20);
248
- } catch (error) {
249
- // Should re-throw the same error without calling adapter
250
- expect(error.message).toBe("Network error");
251
- }
252
- expect(mockAdapter.read).not.toHaveBeenCalled();
253
-
254
- // Simulate time passing (clear lastAttempt)
255
- const failedRanges = enhancedManager.collection.getFailedRanges();
256
- const failedInfo = failedRanges.get(0);
257
- if (failedInfo) {
258
- failedInfo.lastAttempt = Date.now() - 2000; // 2 seconds ago
259
- }
260
-
261
- // Now it should try again
262
- mockAdapter.read.mockRejectedValueOnce(new Error("Still failing"));
263
- try {
264
- await enhancedManager.collection.loadRange(0, 20);
265
- } catch (error) {
266
- expect(error.message).toBe("Still failing");
267
- }
268
- expect(mockAdapter.read).toHaveBeenCalledTimes(1);
269
- });
270
- });
@@ -1,183 +0,0 @@
1
- /**
2
- * Modular compose features tests
3
- *
4
- * Tests that our extracted features work independently and can be reused
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
- // Import compose features
22
- import {
23
- pipe,
24
- createBase,
25
- withElement,
26
- withEvents,
27
- withCollection,
28
- withStyling,
29
- withSelection,
30
- withPerformance,
31
- } from "../../../src/core/compose";
32
-
33
- describe("Modular Compose Features", () => {
34
- let container: HTMLElement;
35
-
36
- beforeEach(() => {
37
- container = document.createElement("div");
38
- document.body.appendChild(container);
39
- });
40
-
41
- test("withStyling works independently", () => {
42
- const component = pipe(
43
- createBase,
44
- withElement({ componentName: "test" }),
45
- withStyling({
46
- gap: 16,
47
- padding: 20,
48
- striped: true,
49
- componentName: "test",
50
- })
51
- )({ prefix: "mtrl" });
52
-
53
- expect(component.element.style.getPropertyValue("--mtrl-test-gap")).toBe(
54
- "16px"
55
- );
56
- expect(
57
- component.element.style.getPropertyValue("--mtrl-test-padding")
58
- ).toBe("20px");
59
- expect(component.element.classList.contains("mtrl-test--striped")).toBe(
60
- true
61
- );
62
- expect(typeof component.setStyle).toBe("function");
63
- expect(typeof component.getStyle).toBe("function");
64
- });
65
-
66
- test.skip("withSelection works independently", async () => {
67
- // TODO: Debug why selection doesn't work in isolated test
68
- // This works fine in the full list component tests
69
- expect(true).toBe(true);
70
- });
71
-
72
- test("withPerformance works independently", () => {
73
- const component = pipe(
74
- createBase,
75
- withElement({ componentName: "test" }),
76
- withEvents(),
77
- withPerformance({
78
- trackMemory: true,
79
- maxSamples: 50,
80
- })
81
- )({ prefix: "mtrl" });
82
-
83
- // Mock getItems method that performance expects
84
- component.getItems = () => [{ id: "1" }, { id: "2" }];
85
-
86
- expect(typeof component.getMetrics).toBe("function");
87
- expect(typeof component.resetMetrics).toBe("function");
88
-
89
- const metrics = component.getMetrics();
90
- expect(typeof metrics.renderCount).toBe("number");
91
- expect(typeof metrics.scrollCount).toBe("number");
92
- });
93
-
94
- test("withCollection works independently", () => {
95
- const testData = [
96
- { id: "1", name: "Item 1" },
97
- { id: "2", name: "Item 2" },
98
- ];
99
-
100
- const component = pipe(
101
- createBase,
102
- withElement({ componentName: "test" }),
103
- withCollection({
104
- items: testData,
105
- })
106
- )({ prefix: "mtrl" });
107
-
108
- expect(typeof component.add).toBe("function");
109
- expect(typeof component.remove).toBe("function");
110
- expect(typeof component.getItems).toBe("function");
111
- expect(typeof component.getItem).toBe("function");
112
- expect(component.getSize()).toBe(2);
113
- expect(component.isEmpty()).toBe(false);
114
- });
115
-
116
- test("features can be combined flexibly", () => {
117
- const testData = [
118
- { id: "1", name: "Test Item 1" },
119
- { id: "2", name: "Test Item 2" },
120
- ];
121
-
122
- // Create a custom component with a different combination
123
- const customComponent = pipe(
124
- createBase,
125
- withElement({ componentName: "custom" }),
126
- withEvents(),
127
- withStyling({
128
- gap: 12,
129
- hoverable: true,
130
- componentName: "custom",
131
- }),
132
- withCollection({
133
- items: testData,
134
- }),
135
- withPerformance({
136
- trackMemory: false,
137
- })
138
- // Note: deliberately omitting withSelection to show flexibility
139
- )({ prefix: "mtrl" });
140
-
141
- // Should have styling
142
- expect(
143
- customComponent.element.style.getPropertyValue("--mtrl-custom-gap")
144
- ).toBe("12px");
145
- expect(
146
- customComponent.element.classList.contains("mtrl-custom--hoverable")
147
- ).toBe(true);
148
-
149
- // Should have collection
150
- expect(customComponent.getSize()).toBe(2);
151
- expect(customComponent.getItem("1").name).toBe("Test Item 1");
152
-
153
- // Should have performance
154
- expect(typeof customComponent.getMetrics).toBe("function");
155
-
156
- // Should NOT have selection (proving modularity)
157
- expect(customComponent.selectItem).toBeUndefined();
158
- });
159
-
160
- test("features maintain mtrl patterns", () => {
161
- const component = pipe(
162
- createBase,
163
- withElement({ componentName: "pattern-test" }),
164
- withStyling({
165
- striped: true,
166
- componentName: "pattern-test",
167
- })
168
- )({ prefix: "mtrl" });
169
-
170
- // Should follow mtrl class naming conventions
171
- expect(component.element.classList.contains("mtrl-pattern-test")).toBe(
172
- true
173
- );
174
- expect(
175
- component.element.classList.contains("mtrl-pattern-test--striped")
176
- ).toBe(true);
177
-
178
- // Should have mtrl prefix utilities
179
- expect(typeof component.getClass).toBe("function");
180
- expect(typeof component.getModifierClass).toBe("function");
181
- expect(component.getClass("item")).toBe("mtrl-item");
182
- });
183
- });