@luna_ui/luna 0.11.0 → 0.17.0
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/dist/api-DAWeanTX.js +1 -0
- package/dist/api-qXll116-.d.ts +80 -0
- package/dist/cli.mjs +27 -22
- package/dist/css/index.js +1 -0
- package/dist/event-utils.d.ts +1 -1
- package/dist/event-utils.js +1 -1
- package/dist/{index-BZoM-af5.d.ts → index-VY8G32hr.d.ts} +16 -76
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/dist/raw.d.ts +2 -0
- package/dist/raw.js +1 -0
- package/dist/resource.d.ts +41 -0
- package/dist/resource.js +1 -0
- package/dist/router-lite.d.ts +44 -0
- package/dist/router-lite.js +1 -0
- package/dist/signals-shared.d.ts +12 -0
- package/dist/signals-shared.js +1 -0
- package/dist/signals.d.ts +2 -3
- package/dist/signals.js +1 -1
- package/dist/vite-plugin.d.ts +7708 -2
- package/dist/vite-plugin.js +7 -6
- package/package.json +30 -11
- package/dist/event-utils-9cHYnvun.js +0 -1
- package/dist/src-BFWjzzPo.js +0 -1
- package/src/css/extract.ts +0 -798
- package/src/css/index.ts +0 -10
- package/src/css/inject.ts +0 -205
- package/src/css/inline.ts +0 -182
- package/src/css/minify.ts +0 -70
- package/src/css/optimizer.ts +0 -6
- package/src/css/runtime.ts +0 -344
- package/src/css-optimizer/README.md +0 -353
- package/src/css-optimizer/cooccurrence.ts +0 -100
- package/src/css-optimizer/core.ts +0 -263
- package/src/css-optimizer/extractors.ts +0 -243
- package/src/css-optimizer/hash.ts +0 -54
- package/src/css-optimizer/index.ts +0 -129
- package/src/css-optimizer/merge.ts +0 -109
- package/src/css-optimizer/moonbit-analyzer.ts +0 -210
- package/src/css-optimizer/parser.ts +0 -120
- package/src/css-optimizer/pattern.ts +0 -171
- package/src/css-optimizer/transformers.ts +0 -301
- package/src/css-optimizer/types.ts +0 -128
- package/src/event-utils.ts +0 -227
- package/src/hydration/createHydrator.ts +0 -62
- package/src/hydration/delegate.ts +0 -62
- package/src/hydration/drag.ts +0 -214
- package/src/hydration/index.ts +0 -12
- package/src/hydration/keyboard.ts +0 -64
- package/src/hydration/toggle.ts +0 -101
- package/src/index.ts +0 -908
- package/src/jsx-dev-runtime.ts +0 -2
- package/src/jsx-runtime.ts +0 -398
- package/src/signals.ts +0 -113
- package/src/vite-plugin.ts +0 -718
- package/tests/__screenshots__/apg.test.ts/APG-Components---Accessibility-Tests-Button-Pattern-disabled-button-has-aria-disabled-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--createResource-error-is-undefined-when-pending-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--createResource-transitions-to-success-on-resolve-1.png +0 -0
- package/tests/apg.test.ts +0 -466
- package/tests/context.test.ts +0 -118
- package/tests/css-optimizer-extractors.test.ts +0 -264
- package/tests/css-optimizer-integration.test.ts +0 -566
- package/tests/css-optimizer-transformers.test.ts +0 -301
- package/tests/css-optimizer.test.ts +0 -646
- package/tests/css-runtime.bench.ts +0 -442
- package/tests/css-runtime.test.ts +0 -342
- package/tests/debounced.test.ts +0 -165
- package/tests/dom.test.ts +0 -873
- package/tests/integration.test.ts +0 -405
- package/tests/issue-11-show-null-to-truthy.test.ts +0 -176
- package/tests/issue-5-for-infinite-loop.test.ts +0 -516
- package/tests/jsx-runtime.test.tsx +0 -393
- package/tests/lifecycle.test.ts +0 -833
- package/tests/move-before.bench.ts +0 -304
- package/tests/preact-signals-comparison.test.ts +0 -1608
- package/tests/resource.test.ts +0 -170
- package/tests/router.test.ts +0 -117
- package/tests/show-initial-mount-leak.test.tsx +0 -182
- package/tests/solidjs-api.test.ts +0 -660
- package/tests/static-perf.bench.ts +0 -64
- package/tests/store.test.ts +0 -263
- package/tests/tsx-syntax.test.tsx +0 -404
- /package/dist/{event-utils-BkTM7rk5.d.ts → event-utils-BvAf0NwN.d.ts} +0 -0
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
initCssRuntime,
|
|
4
|
-
css,
|
|
5
|
-
styles,
|
|
6
|
-
hover,
|
|
7
|
-
focus,
|
|
8
|
-
active,
|
|
9
|
-
on,
|
|
10
|
-
hasClass,
|
|
11
|
-
getGeneratedCss,
|
|
12
|
-
getGeneratedCount,
|
|
13
|
-
resetRuntime,
|
|
14
|
-
combine,
|
|
15
|
-
} from "../src/css/runtime";
|
|
16
|
-
|
|
17
|
-
describe("CSS Runtime - Missing CSS Detection", () => {
|
|
18
|
-
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
// Reset runtime state
|
|
22
|
-
resetRuntime();
|
|
23
|
-
// Spy on console.warn
|
|
24
|
-
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
25
|
-
// Initialize runtime
|
|
26
|
-
initCssRuntime({ warnOnGenerate: true });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
afterEach(() => {
|
|
30
|
-
warnSpy.mockRestore();
|
|
31
|
-
// Clean up any injected style elements
|
|
32
|
-
const styleEl = document.getElementById("luna-dev-css");
|
|
33
|
-
if (styleEl) {
|
|
34
|
-
styleEl.remove();
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
describe("css()", () => {
|
|
39
|
-
test("generates class name from property:value", () => {
|
|
40
|
-
const className = css("display", "flex");
|
|
41
|
-
expect(className).toMatch(/^_[a-z0-9]+$/);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("same declaration produces same class name", () => {
|
|
45
|
-
const cls1 = css("display", "flex");
|
|
46
|
-
const cls2 = css("display", "flex");
|
|
47
|
-
expect(cls1).toBe(cls2);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("different declarations produce different class names", () => {
|
|
51
|
-
const cls1 = css("display", "flex");
|
|
52
|
-
const cls2 = css("display", "block");
|
|
53
|
-
expect(cls1).not.toBe(cls2);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("warns when generating CSS at runtime", () => {
|
|
57
|
-
css("color", "red");
|
|
58
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
59
|
-
expect.stringContaining("[luna-css] Generated at runtime:"),
|
|
60
|
-
expect.stringContaining("luna css extract")
|
|
61
|
-
);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("only warns once per declaration", () => {
|
|
65
|
-
css("margin", "10px");
|
|
66
|
-
css("margin", "10px");
|
|
67
|
-
css("margin", "10px");
|
|
68
|
-
// Should only warn once for the same declaration
|
|
69
|
-
const marginWarnings = warnSpy.mock.calls.filter(
|
|
70
|
-
(call) => call[0].includes("margin:10px")
|
|
71
|
-
);
|
|
72
|
-
expect(marginWarnings.length).toBe(1);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe("styles()", () => {
|
|
77
|
-
test("generates multiple class names", () => {
|
|
78
|
-
const classNames = styles([
|
|
79
|
-
["display", "flex"],
|
|
80
|
-
["padding", "10px"],
|
|
81
|
-
]);
|
|
82
|
-
expect(classNames.split(" ").length).toBe(2);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("warns for each new declaration", () => {
|
|
86
|
-
styles([
|
|
87
|
-
["width", "100%"],
|
|
88
|
-
["height", "50px"],
|
|
89
|
-
]);
|
|
90
|
-
expect(warnSpy).toHaveBeenCalledTimes(2);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe("pseudo-classes", () => {
|
|
95
|
-
test("hover() generates pseudo-class CSS", () => {
|
|
96
|
-
const className = hover("background", "blue");
|
|
97
|
-
expect(className).toMatch(/^_[a-z0-9]+$/);
|
|
98
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
99
|
-
expect.stringContaining(":hover"),
|
|
100
|
-
expect.any(String)
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("focus() generates pseudo-class CSS", () => {
|
|
105
|
-
const className = focus("outline", "none");
|
|
106
|
-
expect(className).toMatch(/^_[a-z0-9]+$/);
|
|
107
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
108
|
-
expect.stringContaining(":focus"),
|
|
109
|
-
expect.any(String)
|
|
110
|
-
);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("active() generates pseudo-class CSS", () => {
|
|
114
|
-
const className = active("transform", "scale(0.95)");
|
|
115
|
-
expect(className).toMatch(/^_[a-z0-9]+$/);
|
|
116
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
117
|
-
expect.stringContaining(":active"),
|
|
118
|
-
expect.any(String)
|
|
119
|
-
);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("on() generates custom pseudo-class", () => {
|
|
123
|
-
const className = on(":visited", "color", "purple");
|
|
124
|
-
expect(className).toMatch(/^_[a-z0-9]+$/);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe("hasClass()", () => {
|
|
129
|
-
test("returns false for non-existent class", () => {
|
|
130
|
-
expect(hasClass("_nonexistent123")).toBe(false);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test("returns true for generated class", () => {
|
|
134
|
-
const className = css("font-size", "16px");
|
|
135
|
-
expect(hasClass(className)).toBe(true);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
describe("getGeneratedCss()", () => {
|
|
140
|
-
test("returns empty string initially", () => {
|
|
141
|
-
resetRuntime();
|
|
142
|
-
initCssRuntime({ warnOnGenerate: false });
|
|
143
|
-
expect(getGeneratedCss()).toBe("");
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test("returns generated CSS rules", () => {
|
|
147
|
-
const className = css("border", "1px solid black");
|
|
148
|
-
const generatedCss = getGeneratedCss();
|
|
149
|
-
expect(generatedCss).toContain(className);
|
|
150
|
-
expect(generatedCss).toContain("border:1px solid black");
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe("getGeneratedCount()", () => {
|
|
155
|
-
test("returns 0 initially", () => {
|
|
156
|
-
resetRuntime();
|
|
157
|
-
initCssRuntime({ warnOnGenerate: false });
|
|
158
|
-
expect(getGeneratedCount()).toBe(0);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("increments with each new declaration", () => {
|
|
162
|
-
resetRuntime();
|
|
163
|
-
initCssRuntime({ warnOnGenerate: false });
|
|
164
|
-
|
|
165
|
-
css("opacity", "0.5");
|
|
166
|
-
expect(getGeneratedCount()).toBe(1);
|
|
167
|
-
|
|
168
|
-
css("visibility", "hidden");
|
|
169
|
-
expect(getGeneratedCount()).toBe(2);
|
|
170
|
-
|
|
171
|
-
// Same declaration doesn't increment
|
|
172
|
-
css("opacity", "0.5");
|
|
173
|
-
expect(getGeneratedCount()).toBe(2);
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
describe("combine()", () => {
|
|
178
|
-
test("joins class names with space", () => {
|
|
179
|
-
const result = combine(["_abc", "_def", "_ghi"]);
|
|
180
|
-
expect(result).toBe("_abc _def _ghi");
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test("filters out falsy values", () => {
|
|
184
|
-
const result = combine(["_abc", "", "_def", undefined as any, "_ghi"]);
|
|
185
|
-
expect(result).toBe("_abc _def _ghi");
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe("DOM injection", () => {
|
|
190
|
-
test("injects style element into head", () => {
|
|
191
|
-
css("z-index", "100");
|
|
192
|
-
const styleEl = document.getElementById("luna-dev-css");
|
|
193
|
-
expect(styleEl).not.toBeNull();
|
|
194
|
-
expect(styleEl?.tagName).toBe("STYLE");
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("style element contains generated CSS", () => {
|
|
198
|
-
const className = css("position", "absolute");
|
|
199
|
-
const styleEl = document.getElementById("luna-dev-css");
|
|
200
|
-
expect(styleEl?.textContent).toContain(className);
|
|
201
|
-
expect(styleEl?.textContent).toContain("position:absolute");
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("multiple rules are appended to same style element", () => {
|
|
205
|
-
css("top", "0");
|
|
206
|
-
css("left", "0");
|
|
207
|
-
css("right", "0");
|
|
208
|
-
|
|
209
|
-
const styleEl = document.getElementById("luna-dev-css");
|
|
210
|
-
expect(styleEl?.textContent).toContain("top:0");
|
|
211
|
-
expect(styleEl?.textContent).toContain("left:0");
|
|
212
|
-
expect(styleEl?.textContent).toContain("right:0");
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe("warnOnGenerate option", () => {
|
|
217
|
-
test("no warnings when warnOnGenerate is false", () => {
|
|
218
|
-
resetRuntime();
|
|
219
|
-
const quietWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
220
|
-
|
|
221
|
-
initCssRuntime({ warnOnGenerate: false });
|
|
222
|
-
css("cursor", "pointer");
|
|
223
|
-
|
|
224
|
-
expect(quietWarnSpy).not.toHaveBeenCalled();
|
|
225
|
-
quietWarnSpy.mockRestore();
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
describe("CSS Runtime - Pre-extracted CSS Integration", () => {
|
|
231
|
-
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
232
|
-
let preExtractedStyle: HTMLStyleElement;
|
|
233
|
-
|
|
234
|
-
beforeEach(() => {
|
|
235
|
-
resetRuntime();
|
|
236
|
-
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
237
|
-
|
|
238
|
-
// Simulate pre-extracted CSS (like virtual:luna.css)
|
|
239
|
-
preExtractedStyle = document.createElement("style");
|
|
240
|
-
preExtractedStyle.id = "pre-extracted-css";
|
|
241
|
-
preExtractedStyle.textContent = `
|
|
242
|
-
._swuc{display:flex}
|
|
243
|
-
._3m33u{align-items:center}
|
|
244
|
-
._5qn6e{justify-content:center}
|
|
245
|
-
`;
|
|
246
|
-
document.head.appendChild(preExtractedStyle);
|
|
247
|
-
|
|
248
|
-
initCssRuntime({ warnOnGenerate: true });
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
afterEach(() => {
|
|
252
|
-
warnSpy.mockRestore();
|
|
253
|
-
preExtractedStyle.remove();
|
|
254
|
-
const devStyle = document.getElementById("luna-dev-css");
|
|
255
|
-
if (devStyle) devStyle.remove();
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("hasClass returns true for pre-extracted classes", () => {
|
|
259
|
-
expect(hasClass("_swuc")).toBe(true);
|
|
260
|
-
expect(hasClass("_3m33u")).toBe(true);
|
|
261
|
-
expect(hasClass("_5qn6e")).toBe(true);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
test("hasClass returns false for missing classes", () => {
|
|
265
|
-
expect(hasClass("_missing123")).toBe(false);
|
|
266
|
-
expect(hasClass("_notextracted")).toBe(false);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
test("no warning when class already exists in stylesheet", () => {
|
|
270
|
-
// This simulates using a class that was pre-extracted
|
|
271
|
-
// The runtime should detect it exists and not regenerate/warn
|
|
272
|
-
// Note: css() will still generate the class name, but if it exists,
|
|
273
|
-
// it won't inject or warn
|
|
274
|
-
|
|
275
|
-
// First, verify the class exists
|
|
276
|
-
expect(hasClass("_swuc")).toBe(true);
|
|
277
|
-
|
|
278
|
-
// Calling css() with same declaration won't warn if already present
|
|
279
|
-
// But our implementation always checks after hash, so we need to test differently
|
|
280
|
-
|
|
281
|
-
// The key test: missing CSS triggers warning
|
|
282
|
-
css("background", "red"); // Not in pre-extracted
|
|
283
|
-
expect(warnSpy).toHaveBeenCalled();
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test("detects missing CSS and generates fallback", () => {
|
|
287
|
-
// Use a declaration that's definitely not pre-extracted
|
|
288
|
-
const className = css("border-radius", "999px");
|
|
289
|
-
|
|
290
|
-
// Should have warned
|
|
291
|
-
expect(warnSpy).toHaveBeenCalledWith(
|
|
292
|
-
expect.stringContaining("[luna-css]"),
|
|
293
|
-
expect.any(String)
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
// Should have injected the CSS
|
|
297
|
-
const devStyle = document.getElementById("luna-dev-css");
|
|
298
|
-
expect(devStyle?.textContent).toContain(className);
|
|
299
|
-
expect(devStyle?.textContent).toContain("border-radius:999px");
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
test("element styling works after runtime generation", () => {
|
|
303
|
-
const className = css("background-color", "#ff0000");
|
|
304
|
-
|
|
305
|
-
const div = document.createElement("div");
|
|
306
|
-
div.className = className;
|
|
307
|
-
document.body.appendChild(div);
|
|
308
|
-
|
|
309
|
-
// After runtime CSS injection, the element should have the style
|
|
310
|
-
const computed = getComputedStyle(div);
|
|
311
|
-
expect(computed.backgroundColor).toBe("rgb(255, 0, 0)");
|
|
312
|
-
|
|
313
|
-
div.remove();
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
describe("CSS Runtime - Class Name Determinism", () => {
|
|
318
|
-
beforeEach(() => {
|
|
319
|
-
resetRuntime();
|
|
320
|
-
initCssRuntime({ warnOnGenerate: false });
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test("class names are deterministic (DJB2 hash)", () => {
|
|
324
|
-
// These should produce the same class name every time
|
|
325
|
-
const cls1 = css("display", "flex");
|
|
326
|
-
resetRuntime();
|
|
327
|
-
initCssRuntime({ warnOnGenerate: false });
|
|
328
|
-
const cls2 = css("display", "flex");
|
|
329
|
-
|
|
330
|
-
expect(cls1).toBe(cls2);
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
test("known hash values for verification", () => {
|
|
334
|
-
// display:flex should produce a specific class name
|
|
335
|
-
// This verifies the hash function matches MoonBit implementation
|
|
336
|
-
const flexClass = css("display", "flex");
|
|
337
|
-
// The class should start with _ and be alphanumeric
|
|
338
|
-
expect(flexClass).toMatch(/^_[a-z0-9]+$/);
|
|
339
|
-
// Should be reasonably short (base36 encoded 24-bit hash)
|
|
340
|
-
expect(flexClass.length).toBeLessThanOrEqual(7);
|
|
341
|
-
});
|
|
342
|
-
});
|
package/tests/debounced.test.ts
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
-
import { createSignal, debounced, createRenderEffect } from "../src/index";
|
|
3
|
-
|
|
4
|
-
describe("debounced signal", () => {
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
vi.useFakeTimers();
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
vi.useRealTimers();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
test("debounced returns initial value immediately", () => {
|
|
14
|
-
const [value, setValue] = createSignal(42);
|
|
15
|
-
const [debouncedValue] = debounced([value, setValue], 100);
|
|
16
|
-
|
|
17
|
-
expect(debouncedValue()).toBe(42);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("debounced delays value updates", () => {
|
|
21
|
-
const [value, setValue] = createSignal("initial");
|
|
22
|
-
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
23
|
-
|
|
24
|
-
// Update the value
|
|
25
|
-
setDebouncedValue("updated");
|
|
26
|
-
|
|
27
|
-
// Debounced value should still be initial (before timeout)
|
|
28
|
-
expect(debouncedValue()).toBe("initial");
|
|
29
|
-
|
|
30
|
-
// Advance time by less than delay
|
|
31
|
-
vi.advanceTimersByTime(50);
|
|
32
|
-
expect(debouncedValue()).toBe("initial");
|
|
33
|
-
|
|
34
|
-
// Advance time to complete the delay
|
|
35
|
-
vi.advanceTimersByTime(50);
|
|
36
|
-
expect(debouncedValue()).toBe("updated");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("debounced cancels previous timer on rapid updates", () => {
|
|
40
|
-
const [value, setValue] = createSignal(0);
|
|
41
|
-
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
42
|
-
|
|
43
|
-
// Rapid updates
|
|
44
|
-
setDebouncedValue(1);
|
|
45
|
-
vi.advanceTimersByTime(50);
|
|
46
|
-
setDebouncedValue(2);
|
|
47
|
-
vi.advanceTimersByTime(50);
|
|
48
|
-
setDebouncedValue(3);
|
|
49
|
-
|
|
50
|
-
// Should still be initial value
|
|
51
|
-
expect(debouncedValue()).toBe(0);
|
|
52
|
-
|
|
53
|
-
// Advance to complete the delay from last update
|
|
54
|
-
vi.advanceTimersByTime(100);
|
|
55
|
-
expect(debouncedValue()).toBe(3);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("debounced with 0ms delay updates immediately after microtask", () => {
|
|
59
|
-
const [value, setValue] = createSignal("start");
|
|
60
|
-
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 0);
|
|
61
|
-
|
|
62
|
-
setDebouncedValue("end");
|
|
63
|
-
|
|
64
|
-
// Even 0ms delay needs timer to fire
|
|
65
|
-
vi.advanceTimersByTime(0);
|
|
66
|
-
expect(debouncedValue()).toBe("end");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("debounced triggers reactive effects after delay", () => {
|
|
70
|
-
const [value, setValue] = createSignal(0);
|
|
71
|
-
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
72
|
-
|
|
73
|
-
const log: number[] = [];
|
|
74
|
-
createRenderEffect(() => {
|
|
75
|
-
log.push(debouncedValue());
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Initial effect run
|
|
79
|
-
expect(log).toEqual([0]);
|
|
80
|
-
|
|
81
|
-
// Update value
|
|
82
|
-
setDebouncedValue(10);
|
|
83
|
-
expect(log).toEqual([0]); // Not yet updated
|
|
84
|
-
|
|
85
|
-
// After delay, effect should run
|
|
86
|
-
vi.advanceTimersByTime(100);
|
|
87
|
-
expect(log).toEqual([0, 10]);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("debounced handles multiple sequential updates correctly", () => {
|
|
91
|
-
const [value, setValue] = createSignal("a");
|
|
92
|
-
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 50);
|
|
93
|
-
|
|
94
|
-
// First update
|
|
95
|
-
setDebouncedValue("b");
|
|
96
|
-
vi.advanceTimersByTime(50);
|
|
97
|
-
expect(debouncedValue()).toBe("b");
|
|
98
|
-
|
|
99
|
-
// Second update (after first completed)
|
|
100
|
-
setDebouncedValue("c");
|
|
101
|
-
vi.advanceTimersByTime(50);
|
|
102
|
-
expect(debouncedValue()).toBe("c");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("debounced works with object values", () => {
|
|
106
|
-
const [value, setValue] = createSignal({ count: 0 });
|
|
107
|
-
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
108
|
-
|
|
109
|
-
const newObj = { count: 5 };
|
|
110
|
-
setDebouncedValue(newObj);
|
|
111
|
-
|
|
112
|
-
expect(debouncedValue().count).toBe(0);
|
|
113
|
-
|
|
114
|
-
vi.advanceTimersByTime(100);
|
|
115
|
-
expect(debouncedValue().count).toBe(5);
|
|
116
|
-
expect(debouncedValue()).toBe(newObj); // Same reference
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("debounced works with array values", () => {
|
|
120
|
-
const [value, setValue] = createSignal<number[]>([1, 2, 3]);
|
|
121
|
-
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
122
|
-
|
|
123
|
-
setDebouncedValue([4, 5, 6]);
|
|
124
|
-
|
|
125
|
-
expect(debouncedValue()).toEqual([1, 2, 3]);
|
|
126
|
-
|
|
127
|
-
vi.advanceTimersByTime(100);
|
|
128
|
-
expect(debouncedValue()).toEqual([4, 5, 6]);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("debounced with long delay", () => {
|
|
132
|
-
const [value, setValue] = createSignal("original");
|
|
133
|
-
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 1000);
|
|
134
|
-
|
|
135
|
-
setDebouncedValue("delayed");
|
|
136
|
-
|
|
137
|
-
// Check at various points
|
|
138
|
-
vi.advanceTimersByTime(500);
|
|
139
|
-
expect(debouncedValue()).toBe("original");
|
|
140
|
-
|
|
141
|
-
vi.advanceTimersByTime(499);
|
|
142
|
-
expect(debouncedValue()).toBe("original");
|
|
143
|
-
|
|
144
|
-
vi.advanceTimersByTime(1);
|
|
145
|
-
expect(debouncedValue()).toBe("delayed");
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("debounced handles many rapid updates efficiently", () => {
|
|
149
|
-
const [value, setValue] = createSignal(0);
|
|
150
|
-
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
151
|
-
|
|
152
|
-
// Simulate 100 rapid updates
|
|
153
|
-
for (let i = 1; i <= 100; i++) {
|
|
154
|
-
setDebouncedValue(i);
|
|
155
|
-
vi.advanceTimersByTime(10); // 10ms between each
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Should still be 0 because timer keeps getting reset
|
|
159
|
-
expect(debouncedValue()).toBe(0);
|
|
160
|
-
|
|
161
|
-
// Wait for final debounce to complete
|
|
162
|
-
vi.advanceTimersByTime(100);
|
|
163
|
-
expect(debouncedValue()).toBe(100);
|
|
164
|
-
});
|
|
165
|
-
});
|