rask-ui 0.27.0 → 0.28.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/dist/compiler.d.ts +4 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +7 -0
- package/dist/component.d.ts.map +1 -1
- package/dist/component.js +9 -4
- package/dist/createAsync.d.ts +39 -0
- package/dist/createAsync.d.ts.map +1 -0
- package/dist/createAsync.js +47 -0
- package/dist/createComputed.d.ts +4 -0
- package/dist/createComputed.d.ts.map +1 -0
- package/dist/createComputed.js +69 -0
- package/dist/createEffect.d.ts +2 -0
- package/dist/createEffect.d.ts.map +1 -0
- package/dist/createEffect.js +29 -0
- package/dist/createMutation.d.ts +43 -0
- package/dist/createMutation.d.ts.map +1 -0
- package/dist/createMutation.js +76 -0
- package/dist/createQuery.d.ts +42 -0
- package/dist/createQuery.d.ts.map +1 -0
- package/dist/createQuery.js +80 -0
- package/dist/createRouter.d.ts +8 -0
- package/dist/createRouter.d.ts.map +1 -0
- package/dist/createRouter.js +27 -0
- package/dist/createState.d.ts +28 -0
- package/dist/createState.d.ts.map +1 -0
- package/dist/createState.js +129 -0
- package/dist/createTask.d.ts +31 -0
- package/dist/createTask.d.ts.map +1 -0
- package/dist/createTask.js +79 -0
- package/dist/createView.d.ts +28 -0
- package/dist/createView.d.ts.map +1 -0
- package/dist/createView.js +77 -0
- package/dist/error.d.ts +5 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +16 -0
- package/dist/jsx.d.ts +11 -0
- package/dist/patchInferno.d.ts +6 -0
- package/dist/patchInferno.d.ts.map +1 -0
- package/dist/patchInferno.js +53 -0
- package/dist/scheduler.d.ts +4 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +107 -0
- package/dist/tests/batch.test.d.ts +2 -0
- package/dist/tests/batch.test.d.ts.map +1 -0
- package/dist/tests/batch.test.js +244 -0
- package/dist/tests/createComputed.test.d.ts +2 -0
- package/dist/tests/createComputed.test.d.ts.map +1 -0
- package/dist/tests/createComputed.test.js +257 -0
- package/dist/tests/createContext.test.d.ts +2 -0
- package/dist/tests/createContext.test.d.ts.map +1 -0
- package/dist/tests/createContext.test.js +136 -0
- package/dist/tests/createEffect.test.d.ts +2 -0
- package/dist/tests/createEffect.test.d.ts.map +1 -0
- package/dist/tests/createEffect.test.js +467 -0
- package/dist/tests/createState.test.d.ts +2 -0
- package/dist/tests/createState.test.d.ts.map +1 -0
- package/dist/tests/createState.test.js +144 -0
- package/dist/tests/createTask.test.d.ts +2 -0
- package/dist/tests/createTask.test.d.ts.map +1 -0
- package/dist/tests/createTask.test.js +322 -0
- package/dist/tests/createView.test.d.ts +2 -0
- package/dist/tests/createView.test.d.ts.map +1 -0
- package/dist/tests/createView.test.js +203 -0
- package/dist/tests/error.test.d.ts +2 -0
- package/dist/tests/error.test.d.ts.map +1 -0
- package/dist/tests/error.test.js +168 -0
- package/dist/tests/observation.test.d.ts +2 -0
- package/dist/tests/observation.test.d.ts.map +1 -0
- package/dist/tests/observation.test.js +341 -0
- package/dist/transformer.d.ts.map +1 -1
- package/dist/transformer.js +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/useComputed.d.ts +5 -0
- package/dist/useComputed.d.ts.map +1 -0
- package/dist/useComputed.js +69 -0
- package/dist/useQuery.d.ts +25 -0
- package/dist/useQuery.d.ts.map +1 -0
- package/dist/useQuery.js +25 -0
- package/dist/useSuspendAsync.d.ts +18 -0
- package/dist/useSuspendAsync.d.ts.map +1 -0
- package/dist/useSuspendAsync.js +37 -0
- package/dist/useTask.d.ts +25 -0
- package/dist/useTask.d.ts.map +1 -0
- package/dist/useTask.js +70 -0
- package/package.json +1 -1
- package/swc-plugin/target/wasm32-wasip1/release/swc_plugin_rask_component.wasm +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { createTask } from "../createTask";
|
|
3
|
+
describe("createTask", () => {
|
|
4
|
+
describe("without parameters", () => {
|
|
5
|
+
it("should auto-run on creation and start in idle state", () => {
|
|
6
|
+
const promise = new Promise(() => { });
|
|
7
|
+
const task = createTask(() => promise);
|
|
8
|
+
// Initial fetch() doesn't set isRunning, only run()/rerun() do
|
|
9
|
+
expect(task.isRunning).toBe(false);
|
|
10
|
+
expect(task.result).toBeNull();
|
|
11
|
+
expect(task.error).toBeNull();
|
|
12
|
+
expect(task.params).toBeNull();
|
|
13
|
+
});
|
|
14
|
+
it("should resolve to result state on success", async () => {
|
|
15
|
+
const task = createTask(() => Promise.resolve("success"));
|
|
16
|
+
// Task doesn't auto-run anymore
|
|
17
|
+
expect(task.isRunning).toBe(false);
|
|
18
|
+
task.run();
|
|
19
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
20
|
+
expect(task.isRunning).toBe(false);
|
|
21
|
+
expect(task.result).toBe("success");
|
|
22
|
+
expect(task.error).toBeNull();
|
|
23
|
+
expect(task.params).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
it("should resolve to error state on rejection", async () => {
|
|
26
|
+
const task = createTask(() => Promise.reject(new Error("failed")));
|
|
27
|
+
task.run();
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
29
|
+
expect(task.isRunning).toBe(false);
|
|
30
|
+
expect(task.result).toBeNull();
|
|
31
|
+
expect(task.error).toContain("failed");
|
|
32
|
+
expect(task.params).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
it("should handle run() method", async () => {
|
|
35
|
+
const fetcher = vi.fn(() => Promise.resolve("data"));
|
|
36
|
+
const task = createTask(fetcher);
|
|
37
|
+
// Tasks no longer auto-run, so fetcher hasn't been called yet
|
|
38
|
+
expect(fetcher).toHaveBeenCalledTimes(0);
|
|
39
|
+
task.run();
|
|
40
|
+
expect(task.isRunning).toBe(true);
|
|
41
|
+
expect(task.result).toBeNull(); // Cleared on run
|
|
42
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
43
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
44
|
+
expect(task.result).toBe("data");
|
|
45
|
+
});
|
|
46
|
+
it("should handle rerun() method", async () => {
|
|
47
|
+
const fetcher = vi
|
|
48
|
+
.fn()
|
|
49
|
+
.mockResolvedValueOnce("data1")
|
|
50
|
+
.mockResolvedValueOnce("data2");
|
|
51
|
+
const task = createTask(fetcher);
|
|
52
|
+
// First run
|
|
53
|
+
task.run();
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
55
|
+
expect(task.result).toBe("data1");
|
|
56
|
+
task.rerun();
|
|
57
|
+
expect(task.isRunning).toBe(true);
|
|
58
|
+
expect(task.result).toBe("data1"); // Kept during rerun
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
60
|
+
expect(task.result).toBe("data2");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("with parameters", () => {
|
|
64
|
+
it("should not auto-run on creation", () => {
|
|
65
|
+
const fetcher = vi.fn((page) => Promise.resolve(`page-${page}`));
|
|
66
|
+
const task = createTask(fetcher);
|
|
67
|
+
// Tasks no longer auto-run
|
|
68
|
+
expect(task.isRunning).toBe(false);
|
|
69
|
+
expect(task.result).toBeNull();
|
|
70
|
+
expect(task.error).toBeNull();
|
|
71
|
+
expect(task.params).toBeNull();
|
|
72
|
+
expect(fetcher).toHaveBeenCalledTimes(0);
|
|
73
|
+
});
|
|
74
|
+
it("should run when run() is called with params", async () => {
|
|
75
|
+
const fetcher = vi.fn((page) => Promise.resolve(`page-${page}`));
|
|
76
|
+
const task = createTask(fetcher);
|
|
77
|
+
task.run(1);
|
|
78
|
+
expect(task.isRunning).toBe(true);
|
|
79
|
+
expect(task.params).toBe(1);
|
|
80
|
+
expect(task.result).toBeNull();
|
|
81
|
+
expect(fetcher).toHaveBeenCalledWith(1);
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
83
|
+
expect(task.isRunning).toBe(false);
|
|
84
|
+
expect(task.result).toBe("page-1");
|
|
85
|
+
expect(task.params).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
it("should handle error state with params", async () => {
|
|
88
|
+
const task = createTask((id) => Promise.reject(new Error(`failed-${id}`)));
|
|
89
|
+
task.run(42);
|
|
90
|
+
expect(task.isRunning).toBe(true);
|
|
91
|
+
expect(task.params).toBe(42);
|
|
92
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
93
|
+
expect(task.isRunning).toBe(false);
|
|
94
|
+
expect(task.result).toBeNull();
|
|
95
|
+
expect(task.error).toContain("failed-42");
|
|
96
|
+
expect(task.params).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
it("should handle rerun with params", async () => {
|
|
99
|
+
const fetcher = vi
|
|
100
|
+
.fn()
|
|
101
|
+
.mockResolvedValueOnce("data1")
|
|
102
|
+
.mockResolvedValueOnce("data2");
|
|
103
|
+
const task = createTask((page) => fetcher());
|
|
104
|
+
// First run
|
|
105
|
+
task.run(1);
|
|
106
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
107
|
+
expect(task.result).toBe("data1");
|
|
108
|
+
task.rerun(1);
|
|
109
|
+
expect(task.isRunning).toBe(true);
|
|
110
|
+
expect(task.result).toBe("data1"); // Kept during rerun
|
|
111
|
+
expect(task.params).toBe(1);
|
|
112
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
113
|
+
expect(task.result).toBe("data2");
|
|
114
|
+
});
|
|
115
|
+
it("should update params when running with different values", async () => {
|
|
116
|
+
const task = createTask((page) => Promise.resolve(`page-${page}`));
|
|
117
|
+
task.run(1);
|
|
118
|
+
expect(task.params).toBe(1);
|
|
119
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
120
|
+
task.run(2);
|
|
121
|
+
expect(task.params).toBe(2);
|
|
122
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
123
|
+
expect(task.result).toBe("page-2");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe("cancellation", () => {
|
|
127
|
+
it("should cancel previous request when run again", async () => {
|
|
128
|
+
let resolveFirst;
|
|
129
|
+
let resolveSecond;
|
|
130
|
+
const firstPromise = new Promise((resolve) => {
|
|
131
|
+
resolveFirst = resolve;
|
|
132
|
+
});
|
|
133
|
+
const secondPromise = new Promise((resolve) => {
|
|
134
|
+
resolveSecond = resolve;
|
|
135
|
+
});
|
|
136
|
+
const fetcher = vi
|
|
137
|
+
.fn()
|
|
138
|
+
.mockReturnValueOnce(firstPromise)
|
|
139
|
+
.mockReturnValueOnce(secondPromise);
|
|
140
|
+
const task = createTask(fetcher);
|
|
141
|
+
// First run
|
|
142
|
+
task.run();
|
|
143
|
+
expect(task.isRunning).toBe(true);
|
|
144
|
+
// Trigger second run before first completes - this cancels the first run
|
|
145
|
+
task.run();
|
|
146
|
+
expect(task.isRunning).toBe(true);
|
|
147
|
+
// Resolve first (should be ignored due to cancellation)
|
|
148
|
+
resolveFirst("first");
|
|
149
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
150
|
+
expect(task.result).toBeNull(); // First result ignored
|
|
151
|
+
// Resolve second
|
|
152
|
+
resolveSecond("second");
|
|
153
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
154
|
+
expect(task.result).toBe("second");
|
|
155
|
+
});
|
|
156
|
+
it("should cancel previous request with params", async () => {
|
|
157
|
+
let resolveFirst;
|
|
158
|
+
let resolveSecond;
|
|
159
|
+
const firstPromise = new Promise((resolve) => {
|
|
160
|
+
resolveFirst = resolve;
|
|
161
|
+
});
|
|
162
|
+
const secondPromise = new Promise((resolve) => {
|
|
163
|
+
resolveSecond = resolve;
|
|
164
|
+
});
|
|
165
|
+
const fetcher = vi
|
|
166
|
+
.fn()
|
|
167
|
+
.mockReturnValueOnce(firstPromise)
|
|
168
|
+
.mockReturnValueOnce(secondPromise);
|
|
169
|
+
const task = createTask((page) => fetcher());
|
|
170
|
+
task.run(1);
|
|
171
|
+
expect(task.params).toBe(1);
|
|
172
|
+
// Trigger second run before first completes
|
|
173
|
+
task.run(2);
|
|
174
|
+
expect(task.params).toBe(2);
|
|
175
|
+
// Resolve first (should be ignored due to cancellation)
|
|
176
|
+
resolveFirst("first");
|
|
177
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
178
|
+
expect(task.result).toBeNull(); // First result ignored
|
|
179
|
+
// Resolve second
|
|
180
|
+
resolveSecond("second");
|
|
181
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
182
|
+
expect(task.result).toBe("second");
|
|
183
|
+
});
|
|
184
|
+
it("should handle rapid successive runs", async () => {
|
|
185
|
+
let counter = 0;
|
|
186
|
+
const fetcher = vi.fn(() => Promise.resolve(`data-${++counter}`));
|
|
187
|
+
const task = createTask(fetcher);
|
|
188
|
+
// Rapid runs
|
|
189
|
+
task.run();
|
|
190
|
+
task.run();
|
|
191
|
+
task.run();
|
|
192
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
193
|
+
// Only the last run should complete
|
|
194
|
+
expect(fetcher).toHaveBeenCalledTimes(3); // 3 runs
|
|
195
|
+
expect(task.result).toBe("data-3");
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe("type handling", () => {
|
|
199
|
+
it("should handle numeric values", async () => {
|
|
200
|
+
const task = createTask(() => Promise.resolve(42));
|
|
201
|
+
task.run();
|
|
202
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
203
|
+
expect(task.result).toBe(42);
|
|
204
|
+
});
|
|
205
|
+
it("should handle object values", async () => {
|
|
206
|
+
const data = { id: 1, name: "Test" };
|
|
207
|
+
const task = createTask(() => Promise.resolve(data));
|
|
208
|
+
task.run();
|
|
209
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
210
|
+
expect(task.result).toEqual(data);
|
|
211
|
+
});
|
|
212
|
+
it("should handle array values", async () => {
|
|
213
|
+
const data = [1, 2, 3, 4, 5];
|
|
214
|
+
const task = createTask(() => Promise.resolve(data));
|
|
215
|
+
task.run();
|
|
216
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
217
|
+
expect(task.result).toEqual(data);
|
|
218
|
+
});
|
|
219
|
+
it("should convert error to string", async () => {
|
|
220
|
+
const task = createTask(() => Promise.reject("string error"));
|
|
221
|
+
task.run();
|
|
222
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
223
|
+
expect(typeof task.error).toBe("string");
|
|
224
|
+
expect(task.error).toBe("string error");
|
|
225
|
+
});
|
|
226
|
+
it("should handle error objects", async () => {
|
|
227
|
+
const error = new Error("Something went wrong");
|
|
228
|
+
const task = createTask(() => Promise.reject(error));
|
|
229
|
+
task.run();
|
|
230
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
231
|
+
expect(task.error).toContain("Something went wrong");
|
|
232
|
+
});
|
|
233
|
+
it("should handle different parameter types", async () => {
|
|
234
|
+
const fetcher = vi.fn((params) => Promise.resolve(params));
|
|
235
|
+
// Object params
|
|
236
|
+
const task1 = createTask(fetcher);
|
|
237
|
+
task1.run({ id: 1, name: "test" });
|
|
238
|
+
expect(task1.params).toEqual({ id: 1, name: "test" });
|
|
239
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
240
|
+
// Array params
|
|
241
|
+
const task2 = createTask(fetcher);
|
|
242
|
+
task2.run([1, 2, 3]);
|
|
243
|
+
expect(task2.params).toEqual([1, 2, 3]);
|
|
244
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
245
|
+
// String params
|
|
246
|
+
const task3 = createTask((str) => Promise.resolve(str));
|
|
247
|
+
task3.run("test");
|
|
248
|
+
expect(task3.params).toBe("test");
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
describe("edge cases", () => {
|
|
252
|
+
it("should handle immediate resolution", async () => {
|
|
253
|
+
const task = createTask(() => Promise.resolve("immediate"));
|
|
254
|
+
task.run();
|
|
255
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
256
|
+
expect(task.isRunning).toBe(false);
|
|
257
|
+
expect(task.result).toBe("immediate");
|
|
258
|
+
});
|
|
259
|
+
it("should handle immediate rejection", async () => {
|
|
260
|
+
const task = createTask(() => Promise.reject("immediate error"));
|
|
261
|
+
task.run();
|
|
262
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
263
|
+
expect(task.isRunning).toBe(false);
|
|
264
|
+
expect(task.error).toBe("immediate error");
|
|
265
|
+
});
|
|
266
|
+
it("should handle delayed resolution", async () => {
|
|
267
|
+
const task = createTask(() => new Promise((resolve) => {
|
|
268
|
+
setTimeout(() => resolve("delayed"), 10);
|
|
269
|
+
}));
|
|
270
|
+
task.run();
|
|
271
|
+
expect(task.isRunning).toBe(true);
|
|
272
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
273
|
+
expect(task.isRunning).toBe(false);
|
|
274
|
+
expect(task.result).toBe("delayed");
|
|
275
|
+
});
|
|
276
|
+
it("should clear error on successful retry", async () => {
|
|
277
|
+
const fetcher = vi
|
|
278
|
+
.fn()
|
|
279
|
+
.mockRejectedValueOnce(new Error("First error"))
|
|
280
|
+
.mockResolvedValueOnce("success");
|
|
281
|
+
const task = createTask(fetcher);
|
|
282
|
+
task.run();
|
|
283
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
284
|
+
expect(task.error).toContain("First error");
|
|
285
|
+
expect(task.result).toBeNull();
|
|
286
|
+
task.run();
|
|
287
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
288
|
+
expect(task.error).toBeNull();
|
|
289
|
+
expect(task.result).toBe("success");
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
describe("reactive getters", () => {
|
|
293
|
+
it("should expose reactive getters", async () => {
|
|
294
|
+
const task = createTask(() => Promise.resolve("data"));
|
|
295
|
+
task.run();
|
|
296
|
+
// Access getters before completion
|
|
297
|
+
const running1 = task.isRunning;
|
|
298
|
+
const result1 = task.result;
|
|
299
|
+
const error1 = task.error;
|
|
300
|
+
const params1 = task.params;
|
|
301
|
+
expect(running1).toBe(true);
|
|
302
|
+
expect(result1).toBeNull();
|
|
303
|
+
expect(error1).toBeNull();
|
|
304
|
+
expect(params1).toBeNull();
|
|
305
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
306
|
+
// Access getters after completion
|
|
307
|
+
expect(task.isRunning).toBe(false);
|
|
308
|
+
expect(task.result).toBe("data");
|
|
309
|
+
expect(task.error).toBeNull();
|
|
310
|
+
expect(task.params).toBeNull();
|
|
311
|
+
});
|
|
312
|
+
it("should track params during execution", async () => {
|
|
313
|
+
const task = createTask((id) => new Promise((resolve) => setTimeout(() => resolve(`result-${id}`), 20)));
|
|
314
|
+
task.run(123);
|
|
315
|
+
expect(task.isRunning).toBe(true);
|
|
316
|
+
expect(task.params).toBe(123);
|
|
317
|
+
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
318
|
+
expect(task.isRunning).toBe(false);
|
|
319
|
+
expect(task.params).toBeNull();
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createView.test.d.ts","sourceRoot":"","sources":["../../src/tests/createView.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createView } from "../createView";
|
|
3
|
+
import { createState } from "../createState";
|
|
4
|
+
import { Observer } from "../observation";
|
|
5
|
+
describe("createView", () => {
|
|
6
|
+
it("should merge two plain objects", () => {
|
|
7
|
+
const a = { x: 1, y: 2 };
|
|
8
|
+
const b = { z: 3 };
|
|
9
|
+
const view = createView(a, b);
|
|
10
|
+
expect(view.x).toBe(1);
|
|
11
|
+
expect(view.y).toBe(2);
|
|
12
|
+
expect(view.z).toBe(3);
|
|
13
|
+
});
|
|
14
|
+
it("should allow later arguments to override earlier ones", () => {
|
|
15
|
+
const a = { x: 1, y: 2 };
|
|
16
|
+
const b = { y: 3, z: 4 };
|
|
17
|
+
const view = createView(a, b);
|
|
18
|
+
expect(view.x).toBe(1);
|
|
19
|
+
expect(view.y).toBe(3); // b.y overrides a.y
|
|
20
|
+
expect(view.z).toBe(4);
|
|
21
|
+
});
|
|
22
|
+
it("should maintain reactivity with reactive objects", async () => {
|
|
23
|
+
const state = createState({ count: 0 });
|
|
24
|
+
const view = createView(state);
|
|
25
|
+
let renderCount = 0;
|
|
26
|
+
let lastValue = 0;
|
|
27
|
+
const observer = new Observer(() => {
|
|
28
|
+
renderCount++;
|
|
29
|
+
});
|
|
30
|
+
const dispose = observer.observe();
|
|
31
|
+
lastValue = view.count; // Track the property
|
|
32
|
+
dispose();
|
|
33
|
+
expect(renderCount).toBe(0);
|
|
34
|
+
state.count = 5;
|
|
35
|
+
// Wait for microtask to process notification
|
|
36
|
+
await new Promise((resolve) => {
|
|
37
|
+
queueMicrotask(() => {
|
|
38
|
+
expect(renderCount).toBe(1);
|
|
39
|
+
lastValue = view.count;
|
|
40
|
+
expect(lastValue).toBe(5);
|
|
41
|
+
resolve(undefined);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
it("should merge reactive and plain objects while maintaining reactivity", () => {
|
|
46
|
+
const state = createState({ count: 0 });
|
|
47
|
+
const helpers = {
|
|
48
|
+
increment() {
|
|
49
|
+
state.count++;
|
|
50
|
+
},
|
|
51
|
+
decrement() {
|
|
52
|
+
state.count--;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const view = createView(state, helpers);
|
|
56
|
+
expect(view.count).toBe(0);
|
|
57
|
+
expect(typeof view.increment).toBe("function");
|
|
58
|
+
expect(typeof view.decrement).toBe("function");
|
|
59
|
+
view.increment();
|
|
60
|
+
expect(view.count).toBe(1);
|
|
61
|
+
view.decrement();
|
|
62
|
+
expect(view.count).toBe(0);
|
|
63
|
+
});
|
|
64
|
+
it("should merge multiple reactive objects", () => {
|
|
65
|
+
const state1 = createState({ count: 0 });
|
|
66
|
+
const state2 = createState({ name: "Alice" });
|
|
67
|
+
const state3 = createState({ age: 25 });
|
|
68
|
+
const view = createView(state1, state2, state3);
|
|
69
|
+
expect(view.count).toBe(0);
|
|
70
|
+
expect(view.name).toBe("Alice");
|
|
71
|
+
expect(view.age).toBe(25);
|
|
72
|
+
state1.count = 10;
|
|
73
|
+
state2.name = "Bob";
|
|
74
|
+
state3.age = 30;
|
|
75
|
+
expect(view.count).toBe(10);
|
|
76
|
+
expect(view.name).toBe("Bob");
|
|
77
|
+
expect(view.age).toBe(30);
|
|
78
|
+
});
|
|
79
|
+
it("should reflect changes in source objects", () => {
|
|
80
|
+
const source = { x: 1 };
|
|
81
|
+
const view = createView(source);
|
|
82
|
+
expect(view.x).toBe(1);
|
|
83
|
+
source.x = 2;
|
|
84
|
+
expect(view.x).toBe(2);
|
|
85
|
+
});
|
|
86
|
+
it("should handle property override order correctly", () => {
|
|
87
|
+
const a = { x: 1, y: 2, z: 3 };
|
|
88
|
+
const b = { y: 20 };
|
|
89
|
+
const c = { z: 30 };
|
|
90
|
+
const view = createView(a, b, c);
|
|
91
|
+
expect(view.x).toBe(1); // From a
|
|
92
|
+
expect(view.y).toBe(20); // From b (overrides a)
|
|
93
|
+
expect(view.z).toBe(30); // From c (overrides a)
|
|
94
|
+
});
|
|
95
|
+
it("should only include enumerable properties", () => {
|
|
96
|
+
const obj = { x: 1 };
|
|
97
|
+
Object.defineProperty(obj, "hidden", {
|
|
98
|
+
value: 42,
|
|
99
|
+
enumerable: false,
|
|
100
|
+
});
|
|
101
|
+
const view = createView(obj);
|
|
102
|
+
expect(view.x).toBe(1);
|
|
103
|
+
expect(view.hidden).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
it("should handle symbol keys", () => {
|
|
106
|
+
const sym = Symbol("test");
|
|
107
|
+
const obj = { x: 1, [sym]: "symbol value" };
|
|
108
|
+
const view = createView(obj);
|
|
109
|
+
expect(view.x).toBe(1);
|
|
110
|
+
expect(view[sym]).toBe("symbol value");
|
|
111
|
+
});
|
|
112
|
+
it("should track dependencies for each property independently", async () => {
|
|
113
|
+
const state = createState({ count: 0, name: "Alice" });
|
|
114
|
+
const view = createView(state);
|
|
115
|
+
let countRenderCount = 0;
|
|
116
|
+
let nameRenderCount = 0;
|
|
117
|
+
// Observer that only accesses count
|
|
118
|
+
const countObserver = new Observer(() => {
|
|
119
|
+
countRenderCount++;
|
|
120
|
+
});
|
|
121
|
+
const dispose1 = countObserver.observe();
|
|
122
|
+
view.count; // Track count
|
|
123
|
+
dispose1();
|
|
124
|
+
expect(countRenderCount).toBe(0);
|
|
125
|
+
// Change count - should trigger
|
|
126
|
+
state.count = 1;
|
|
127
|
+
await new Promise((resolve) => {
|
|
128
|
+
queueMicrotask(() => {
|
|
129
|
+
expect(countRenderCount).toBe(1);
|
|
130
|
+
resolve(undefined);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
// Change name - should NOT trigger (not tracked)
|
|
134
|
+
state.name = "Bob";
|
|
135
|
+
await new Promise((resolve) => {
|
|
136
|
+
queueMicrotask(() => {
|
|
137
|
+
expect(countRenderCount).toBe(1); // Still 1
|
|
138
|
+
resolve(undefined);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
// Now track name with a different observer
|
|
142
|
+
const nameObserver = new Observer(() => {
|
|
143
|
+
nameRenderCount++;
|
|
144
|
+
});
|
|
145
|
+
const dispose2 = nameObserver.observe();
|
|
146
|
+
view.name; // Track name
|
|
147
|
+
dispose2();
|
|
148
|
+
expect(nameRenderCount).toBe(0);
|
|
149
|
+
// Change name - should trigger name observer
|
|
150
|
+
state.name = "Charlie";
|
|
151
|
+
await new Promise((resolve) => {
|
|
152
|
+
queueMicrotask(() => {
|
|
153
|
+
expect(nameRenderCount).toBe(1);
|
|
154
|
+
resolve(undefined);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
it("should return the same value type as source", () => {
|
|
159
|
+
const obj = { nums: [1, 2, 3], nested: { x: 1 } };
|
|
160
|
+
const view = createView(obj);
|
|
161
|
+
expect(Array.isArray(view.nums)).toBe(true);
|
|
162
|
+
expect(view.nums).toEqual([1, 2, 3]);
|
|
163
|
+
expect(typeof view.nested).toBe("object");
|
|
164
|
+
expect(view.nested.x).toBe(1);
|
|
165
|
+
});
|
|
166
|
+
it("should handle empty merge", () => {
|
|
167
|
+
const view = createView({});
|
|
168
|
+
expect(Object.keys(view).length).toBe(0);
|
|
169
|
+
});
|
|
170
|
+
it("should merge single object", () => {
|
|
171
|
+
const obj = { x: 1, y: 2 };
|
|
172
|
+
const view = createView(obj);
|
|
173
|
+
expect(view.x).toBe(1);
|
|
174
|
+
expect(view.y).toBe(2);
|
|
175
|
+
});
|
|
176
|
+
it("should maintain function context", () => {
|
|
177
|
+
const state = createState({ count: 0 });
|
|
178
|
+
const methods = {
|
|
179
|
+
increment() {
|
|
180
|
+
state.count++;
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
const view = createView(state, methods);
|
|
184
|
+
expect(view.count).toBe(0);
|
|
185
|
+
view.increment();
|
|
186
|
+
expect(view.count).toBe(1);
|
|
187
|
+
expect(state.count).toBe(1);
|
|
188
|
+
});
|
|
189
|
+
it("should work with reactive state and computed-like patterns", () => {
|
|
190
|
+
const state = createState({ firstName: "John", lastName: "Doe" });
|
|
191
|
+
const computed = {
|
|
192
|
+
get fullName() {
|
|
193
|
+
return `${state.firstName} ${state.lastName}`;
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
const view = createView(state, computed);
|
|
197
|
+
expect(view.firstName).toBe("John");
|
|
198
|
+
expect(view.lastName).toBe("Doe");
|
|
199
|
+
expect(view.fullName).toBe("John Doe");
|
|
200
|
+
state.firstName = "Jane";
|
|
201
|
+
expect(view.fullName).toBe("Jane Doe");
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.test.d.ts","sourceRoot":"","sources":["../../src/tests/error.test.tsx"],"names":[],"mappings":""}
|