rask-ui 0.23.0 → 0.24.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/compiler.d.ts +3 -1
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +7 -1
- package/dist/component.d.ts +4 -18
- package/dist/component.d.ts.map +1 -1
- package/dist/component.js +13 -65
- 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/createRouter.d.ts +8 -0
- package/dist/createRouter.d.ts.map +1 -0
- package/dist/createRouter.js +27 -0
- package/dist/createState.d.ts +2 -0
- package/dist/createState.d.ts.map +1 -1
- package/dist/createState.js +40 -5
- 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 +18 -44
- package/dist/createView.d.ts.map +1 -1
- package/dist/createView.js +57 -48
- package/dist/error.d.ts +3 -14
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +14 -15
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/jsx.d.ts +10 -256
- package/dist/observation.d.ts +1 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/patchInferno.d.ts +6 -0
- package/dist/patchInferno.d.ts.map +1 -0
- package/dist/patchInferno.js +53 -0
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +14 -26
- 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.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.map +1 -0
- package/dist/{createView.test.js → tests/createView.test.js} +40 -40
- 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.map +1 -0
- package/dist/tests/observation.test.js +341 -0
- 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
- package/dist/asyncState.d.ts +0 -16
- package/dist/asyncState.d.ts.map +0 -1
- package/dist/asyncState.js +0 -24
- package/dist/context.d.ts +0 -5
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -29
- package/dist/createAsync.test.d.ts +0 -2
- package/dist/createAsync.test.d.ts.map +0 -1
- package/dist/createAsync.test.js +0 -110
- package/dist/createMutation.test.d.ts +0 -2
- package/dist/createMutation.test.d.ts.map +0 -1
- package/dist/createMutation.test.js +0 -168
- package/dist/createQuery.test.d.ts +0 -2
- package/dist/createQuery.test.d.ts.map +0 -1
- package/dist/createQuery.test.js +0 -156
- package/dist/createRef.d.ts +0 -6
- package/dist/createRef.d.ts.map +0 -1
- package/dist/createRef.js +0 -8
- package/dist/createState.test.d.ts.map +0 -1
- package/dist/createState.test.js +0 -111
- package/dist/createView.test.d.ts.map +0 -1
- package/dist/observation.test.d.ts.map +0 -1
- package/dist/observation.test.js +0 -150
- package/dist/suspense.d.ts +0 -25
- package/dist/suspense.d.ts.map +0 -1
- package/dist/suspense.js +0 -97
- package/dist/test-setup.d.ts +0 -16
- package/dist/test-setup.d.ts.map +0 -1
- package/dist/test-setup.js +0 -40
- package/dist/test.d.ts +0 -2
- package/dist/test.d.ts.map +0 -1
- package/dist/test.js +0 -24
- /package/dist/{createState.test.d.ts → tests/createState.test.d.ts} +0 -0
- /package/dist/{createView.test.d.ts → tests/createView.test.d.ts} +0 -0
- /package/dist/{observation.test.d.ts → tests/observation.test.d.ts} +0 -0
package/dist/createQuery.test.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { createQuery } from './createQuery';
|
|
3
|
-
describe('createQuery', () => {
|
|
4
|
-
it('should start in pending state and fetch immediately', async () => {
|
|
5
|
-
const fetcher = vi.fn(() => Promise.resolve('data'));
|
|
6
|
-
const query = createQuery(fetcher);
|
|
7
|
-
expect(query.isPending).toBe(true);
|
|
8
|
-
expect(query.data).toBeNull();
|
|
9
|
-
expect(query.error).toBeNull();
|
|
10
|
-
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
11
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
12
|
-
expect(query.isPending).toBe(false);
|
|
13
|
-
expect(query.data).toBe('data');
|
|
14
|
-
});
|
|
15
|
-
it('should resolve to data state on success', async () => {
|
|
16
|
-
const fetcher = () => Promise.resolve({ id: 1, name: 'Test' });
|
|
17
|
-
const query = createQuery(fetcher);
|
|
18
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
19
|
-
expect(query.isPending).toBe(false);
|
|
20
|
-
expect(query.data).toEqual({ id: 1, name: 'Test' });
|
|
21
|
-
expect(query.error).toBeNull();
|
|
22
|
-
});
|
|
23
|
-
it('should resolve to error state on failure', async () => {
|
|
24
|
-
const fetcher = () => Promise.reject(new Error('Network error'));
|
|
25
|
-
const query = createQuery(fetcher);
|
|
26
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
27
|
-
expect(query.isPending).toBe(false);
|
|
28
|
-
expect(query.data).toBeNull();
|
|
29
|
-
expect(query.error).toContain('Network error');
|
|
30
|
-
});
|
|
31
|
-
it('should allow manual refetch', async () => {
|
|
32
|
-
const fetcher = vi.fn(() => Promise.resolve('data'));
|
|
33
|
-
const query = createQuery(fetcher);
|
|
34
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
35
|
-
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
36
|
-
query.fetch();
|
|
37
|
-
expect(query.isPending).toBe(true);
|
|
38
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
39
|
-
expect(fetcher).toHaveBeenCalledTimes(2);
|
|
40
|
-
expect(query.isPending).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
it('should retain old data during refetch by default', async () => {
|
|
43
|
-
const fetcher = vi
|
|
44
|
-
.fn()
|
|
45
|
-
.mockResolvedValueOnce('data1')
|
|
46
|
-
.mockResolvedValueOnce('data2');
|
|
47
|
-
const query = createQuery(fetcher);
|
|
48
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
49
|
-
expect(query.data).toBe('data1');
|
|
50
|
-
query.fetch();
|
|
51
|
-
expect(query.isPending).toBe(true);
|
|
52
|
-
expect(query.data).toBe('data1'); // Old data retained
|
|
53
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
54
|
-
expect(query.data).toBe('data2');
|
|
55
|
-
});
|
|
56
|
-
it('should clear old data when force refetch is used', async () => {
|
|
57
|
-
const fetcher = vi
|
|
58
|
-
.fn()
|
|
59
|
-
.mockResolvedValueOnce('data1')
|
|
60
|
-
.mockResolvedValueOnce('data2');
|
|
61
|
-
const query = createQuery(fetcher);
|
|
62
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
63
|
-
expect(query.data).toBe('data1');
|
|
64
|
-
query.fetch(true); // Force refresh
|
|
65
|
-
expect(query.isPending).toBe(true);
|
|
66
|
-
expect(query.data).toBeNull(); // Old data cleared
|
|
67
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
68
|
-
expect(query.data).toBe('data2');
|
|
69
|
-
});
|
|
70
|
-
it('should cancel previous request on new fetch', async () => {
|
|
71
|
-
let resolveFirst;
|
|
72
|
-
let resolveSecond;
|
|
73
|
-
const firstPromise = new Promise((resolve) => {
|
|
74
|
-
resolveFirst = resolve;
|
|
75
|
-
});
|
|
76
|
-
const secondPromise = new Promise((resolve) => {
|
|
77
|
-
resolveSecond = resolve;
|
|
78
|
-
});
|
|
79
|
-
const fetcher = vi
|
|
80
|
-
.fn()
|
|
81
|
-
.mockReturnValueOnce(firstPromise)
|
|
82
|
-
.mockReturnValueOnce(secondPromise);
|
|
83
|
-
const query = createQuery(fetcher);
|
|
84
|
-
expect(query.isPending).toBe(true);
|
|
85
|
-
// Trigger second fetch before first completes
|
|
86
|
-
query.fetch();
|
|
87
|
-
// Resolve first (should be ignored due to cancellation)
|
|
88
|
-
resolveFirst('first');
|
|
89
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
90
|
-
expect(query.data).toBeNull(); // First result ignored
|
|
91
|
-
// Resolve second
|
|
92
|
-
resolveSecond('second');
|
|
93
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
94
|
-
expect(query.data).toBe('second');
|
|
95
|
-
});
|
|
96
|
-
it('should handle rapid successive fetches', async () => {
|
|
97
|
-
let counter = 0;
|
|
98
|
-
const fetcher = vi.fn(() => Promise.resolve(`data-${++counter}`));
|
|
99
|
-
const query = createQuery(fetcher);
|
|
100
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
101
|
-
// Rapid fetches
|
|
102
|
-
query.fetch();
|
|
103
|
-
query.fetch();
|
|
104
|
-
query.fetch();
|
|
105
|
-
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
106
|
-
// Only the last fetch should matter
|
|
107
|
-
expect(fetcher).toHaveBeenCalledTimes(4); // Initial + 3 fetches
|
|
108
|
-
expect(query.data).toBe('data-4');
|
|
109
|
-
});
|
|
110
|
-
it('should cancel on error and allow refetch', async () => {
|
|
111
|
-
const fetcher = vi
|
|
112
|
-
.fn()
|
|
113
|
-
.mockRejectedValueOnce(new Error('First error'))
|
|
114
|
-
.mockResolvedValueOnce('success');
|
|
115
|
-
const query = createQuery(fetcher);
|
|
116
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
117
|
-
expect(query.error).toContain('First error');
|
|
118
|
-
expect(query.data).toBeNull();
|
|
119
|
-
query.fetch();
|
|
120
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
121
|
-
expect(query.error).toBeNull();
|
|
122
|
-
expect(query.data).toBe('success');
|
|
123
|
-
});
|
|
124
|
-
it('should handle AbortController cancellation correctly', async () => {
|
|
125
|
-
const abortedPromise = new Promise((_, reject) => {
|
|
126
|
-
const error = new Error('Aborted');
|
|
127
|
-
error.name = 'AbortError';
|
|
128
|
-
setTimeout(() => reject(error), 5);
|
|
129
|
-
});
|
|
130
|
-
const successPromise = Promise.resolve('success');
|
|
131
|
-
const fetcher = vi
|
|
132
|
-
.fn()
|
|
133
|
-
.mockReturnValueOnce(abortedPromise)
|
|
134
|
-
.mockReturnValueOnce(successPromise);
|
|
135
|
-
const query = createQuery(fetcher);
|
|
136
|
-
// Immediately trigger second fetch to abort first
|
|
137
|
-
query.fetch();
|
|
138
|
-
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
139
|
-
expect(query.data).toBe('success');
|
|
140
|
-
expect(query.error).toBeNull();
|
|
141
|
-
});
|
|
142
|
-
it('should expose reactive getters', async () => {
|
|
143
|
-
const fetcher = () => Promise.resolve('data');
|
|
144
|
-
const query = createQuery(fetcher);
|
|
145
|
-
// Access getters
|
|
146
|
-
const pending1 = query.isPending;
|
|
147
|
-
const data1 = query.data;
|
|
148
|
-
const error1 = query.error;
|
|
149
|
-
expect(pending1).toBe(true);
|
|
150
|
-
expect(data1).toBeNull();
|
|
151
|
-
expect(error1).toBeNull();
|
|
152
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
153
|
-
expect(query.isPending).toBe(false);
|
|
154
|
-
expect(query.data).toBe('data');
|
|
155
|
-
});
|
|
156
|
-
});
|
package/dist/createRef.d.ts
DELETED
package/dist/createRef.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createRef.d.ts","sourceRoot":"","sources":["../src/createRef.ts"],"names":[],"mappings":"AAAA,wBAAgB,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI;WAChD,CAAC,GAAG,IAAI;;mBADe,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI;EASpE"}
|
package/dist/createRef.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createState.test.d.ts","sourceRoot":"","sources":["../src/createState.test.ts"],"names":[],"mappings":""}
|
package/dist/createState.test.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createState } from './createState';
|
|
3
|
-
import { Observer } from './observation';
|
|
4
|
-
describe('createState', () => {
|
|
5
|
-
it('should create a reactive proxy from an object', () => {
|
|
6
|
-
const state = createState({ count: 0 });
|
|
7
|
-
expect(state.count).toBe(0);
|
|
8
|
-
});
|
|
9
|
-
it('should allow mutations', () => {
|
|
10
|
-
const state = createState({ count: 0 });
|
|
11
|
-
state.count = 5;
|
|
12
|
-
expect(state.count).toBe(5);
|
|
13
|
-
});
|
|
14
|
-
it('should return the same proxy for the same object', () => {
|
|
15
|
-
const obj = { count: 0 };
|
|
16
|
-
const proxy1 = createState(obj);
|
|
17
|
-
const proxy2 = createState(obj);
|
|
18
|
-
expect(proxy1).toBe(proxy2);
|
|
19
|
-
});
|
|
20
|
-
it('should create nested proxies for nested objects', () => {
|
|
21
|
-
const state = createState({ user: { name: 'Alice', age: 30 } });
|
|
22
|
-
state.user.name = 'Bob';
|
|
23
|
-
expect(state.user.name).toBe('Bob');
|
|
24
|
-
});
|
|
25
|
-
it('should handle arrays reactively', () => {
|
|
26
|
-
const state = createState({ items: [1, 2, 3] });
|
|
27
|
-
state.items.push(4);
|
|
28
|
-
expect(state.items).toEqual([1, 2, 3, 4]);
|
|
29
|
-
});
|
|
30
|
-
it('should track property access in observers', () => {
|
|
31
|
-
const state = createState({ count: 0 });
|
|
32
|
-
let renderCount = 0;
|
|
33
|
-
const observer = new Observer(() => {
|
|
34
|
-
renderCount++;
|
|
35
|
-
});
|
|
36
|
-
const dispose = observer.observe();
|
|
37
|
-
state.count; // Access property to track it
|
|
38
|
-
dispose();
|
|
39
|
-
expect(renderCount).toBe(0);
|
|
40
|
-
// Mutate after observation setup
|
|
41
|
-
const dispose2 = observer.observe();
|
|
42
|
-
const value = state.count; // Track
|
|
43
|
-
dispose2(); // Stop observing, subscriptions are now active
|
|
44
|
-
state.count = 1;
|
|
45
|
-
// Wait for microtask
|
|
46
|
-
return new Promise((resolve) => {
|
|
47
|
-
queueMicrotask(() => {
|
|
48
|
-
expect(renderCount).toBeGreaterThan(0);
|
|
49
|
-
resolve(undefined);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
it('should handle property deletion', () => {
|
|
54
|
-
const state = createState({ count: 0, temp: 'value' });
|
|
55
|
-
delete state.temp;
|
|
56
|
-
expect(state.temp).toBeUndefined();
|
|
57
|
-
expect('temp' in state).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
it('should not create proxies for functions', () => {
|
|
60
|
-
const fn = () => 'hello';
|
|
61
|
-
const state = createState({ method: fn });
|
|
62
|
-
expect(state.method).toBe(fn);
|
|
63
|
-
expect(state.method()).toBe('hello');
|
|
64
|
-
});
|
|
65
|
-
it('should handle symbol properties', () => {
|
|
66
|
-
const sym = Symbol('test');
|
|
67
|
-
const state = createState({ [sym]: 'value' });
|
|
68
|
-
expect(state[sym]).toBe('value');
|
|
69
|
-
});
|
|
70
|
-
it('should notify observers only on actual changes', () => {
|
|
71
|
-
const state = createState({ count: 0 });
|
|
72
|
-
let notifyCount = 0;
|
|
73
|
-
const observer = new Observer(() => {
|
|
74
|
-
notifyCount++;
|
|
75
|
-
});
|
|
76
|
-
const dispose = observer.observe();
|
|
77
|
-
state.count; // Track
|
|
78
|
-
dispose();
|
|
79
|
-
state.count = 0; // Same value - should still notify per current implementation
|
|
80
|
-
state.count = 0;
|
|
81
|
-
return new Promise((resolve) => {
|
|
82
|
-
queueMicrotask(() => {
|
|
83
|
-
// The implementation notifies even for same value, except for optimization cases
|
|
84
|
-
observer.dispose();
|
|
85
|
-
resolve(undefined);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
it('should handle deeply nested objects', () => {
|
|
90
|
-
const state = createState({
|
|
91
|
-
level1: {
|
|
92
|
-
level2: {
|
|
93
|
-
level3: {
|
|
94
|
-
value: 'deep',
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
state.level1.level2.level3.value = 'modified';
|
|
100
|
-
expect(state.level1.level2.level3.value).toBe('modified');
|
|
101
|
-
});
|
|
102
|
-
it('should handle array mutations correctly', () => {
|
|
103
|
-
const state = createState({ items: [1, 2, 3] });
|
|
104
|
-
state.items.pop();
|
|
105
|
-
expect(state.items).toEqual([1, 2]);
|
|
106
|
-
state.items.unshift(0);
|
|
107
|
-
expect(state.items).toEqual([0, 1, 2]);
|
|
108
|
-
state.items.splice(1, 1, 99);
|
|
109
|
-
expect(state.items).toEqual([0, 99, 2]);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createView.test.d.ts","sourceRoot":"","sources":["../src/createView.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"observation.test.d.ts","sourceRoot":"","sources":["../src/observation.test.ts"],"names":[],"mappings":""}
|
package/dist/observation.test.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { Signal, Observer, getCurrentObserver } from './observation';
|
|
3
|
-
describe('Signal', () => {
|
|
4
|
-
it('should allow subscribing to notifications', () => {
|
|
5
|
-
const signal = new Signal();
|
|
6
|
-
const callback = vi.fn();
|
|
7
|
-
signal.subscribe(callback);
|
|
8
|
-
signal.notify();
|
|
9
|
-
expect(callback).toHaveBeenCalledTimes(1);
|
|
10
|
-
});
|
|
11
|
-
it('should return a disposer function', () => {
|
|
12
|
-
const signal = new Signal();
|
|
13
|
-
const callback = vi.fn();
|
|
14
|
-
const dispose = signal.subscribe(callback);
|
|
15
|
-
dispose();
|
|
16
|
-
signal.notify();
|
|
17
|
-
expect(callback).not.toHaveBeenCalled();
|
|
18
|
-
});
|
|
19
|
-
it('should handle multiple subscribers', () => {
|
|
20
|
-
const signal = new Signal();
|
|
21
|
-
const callback1 = vi.fn();
|
|
22
|
-
const callback2 = vi.fn();
|
|
23
|
-
signal.subscribe(callback1);
|
|
24
|
-
signal.subscribe(callback2);
|
|
25
|
-
signal.notify();
|
|
26
|
-
expect(callback1).toHaveBeenCalledTimes(1);
|
|
27
|
-
expect(callback2).toHaveBeenCalledTimes(1);
|
|
28
|
-
});
|
|
29
|
-
it('should allow unsubscribing individual callbacks', () => {
|
|
30
|
-
const signal = new Signal();
|
|
31
|
-
const callback1 = vi.fn();
|
|
32
|
-
const callback2 = vi.fn();
|
|
33
|
-
const dispose1 = signal.subscribe(callback1);
|
|
34
|
-
signal.subscribe(callback2);
|
|
35
|
-
dispose1();
|
|
36
|
-
signal.notify();
|
|
37
|
-
expect(callback1).not.toHaveBeenCalled();
|
|
38
|
-
expect(callback2).toHaveBeenCalledTimes(1);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
describe('Observer', () => {
|
|
42
|
-
it('should queue notifications in microtasks', async () => {
|
|
43
|
-
let callCount = 0;
|
|
44
|
-
const observer = new Observer(() => {
|
|
45
|
-
callCount++;
|
|
46
|
-
});
|
|
47
|
-
const signal = new Signal();
|
|
48
|
-
const dispose = observer.observe();
|
|
49
|
-
observer.subscribeSignal(signal);
|
|
50
|
-
dispose();
|
|
51
|
-
// Trigger multiple notifications
|
|
52
|
-
signal.notify();
|
|
53
|
-
signal.notify();
|
|
54
|
-
signal.notify();
|
|
55
|
-
// Should not be called synchronously
|
|
56
|
-
expect(callCount).toBe(0);
|
|
57
|
-
// Wait for microtask
|
|
58
|
-
await new Promise((resolve) => queueMicrotask(() => resolve()));
|
|
59
|
-
// Should be called only once due to queuing
|
|
60
|
-
expect(callCount).toBe(1);
|
|
61
|
-
});
|
|
62
|
-
it('should track signals during observation', () => {
|
|
63
|
-
const callback = vi.fn();
|
|
64
|
-
const observer = new Observer(callback);
|
|
65
|
-
const signal = new Signal();
|
|
66
|
-
const dispose = observer.observe();
|
|
67
|
-
observer.subscribeSignal(signal);
|
|
68
|
-
dispose();
|
|
69
|
-
signal.notify();
|
|
70
|
-
return new Promise((resolve) => {
|
|
71
|
-
queueMicrotask(() => {
|
|
72
|
-
expect(callback).toHaveBeenCalledTimes(1);
|
|
73
|
-
resolve(undefined);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
it('should clear signals when observing again', async () => {
|
|
78
|
-
let callCount = 0;
|
|
79
|
-
const observer = new Observer(() => {
|
|
80
|
-
callCount++;
|
|
81
|
-
});
|
|
82
|
-
const signal1 = new Signal();
|
|
83
|
-
const signal2 = new Signal();
|
|
84
|
-
// First observation
|
|
85
|
-
let dispose = observer.observe();
|
|
86
|
-
observer.subscribeSignal(signal1);
|
|
87
|
-
dispose();
|
|
88
|
-
// Second observation - should clear previous signals
|
|
89
|
-
dispose = observer.observe();
|
|
90
|
-
observer.subscribeSignal(signal2);
|
|
91
|
-
dispose();
|
|
92
|
-
// Notify first signal - should not trigger observer
|
|
93
|
-
signal1.notify();
|
|
94
|
-
await new Promise((resolve) => queueMicrotask(() => resolve()));
|
|
95
|
-
expect(callCount).toBe(0);
|
|
96
|
-
// Notify second signal - should trigger observer
|
|
97
|
-
signal2.notify();
|
|
98
|
-
await new Promise((resolve) => queueMicrotask(() => resolve()));
|
|
99
|
-
expect(callCount).toBe(1);
|
|
100
|
-
});
|
|
101
|
-
it('should dispose of all signal subscriptions', async () => {
|
|
102
|
-
const callback = vi.fn();
|
|
103
|
-
const observer = new Observer(callback);
|
|
104
|
-
const signal = new Signal();
|
|
105
|
-
const dispose = observer.observe();
|
|
106
|
-
observer.subscribeSignal(signal);
|
|
107
|
-
dispose();
|
|
108
|
-
observer.dispose();
|
|
109
|
-
signal.notify();
|
|
110
|
-
await new Promise((resolve) => queueMicrotask(() => resolve()));
|
|
111
|
-
expect(callback).not.toHaveBeenCalled();
|
|
112
|
-
});
|
|
113
|
-
it('should set current observer during observation', () => {
|
|
114
|
-
const observer = new Observer(() => { });
|
|
115
|
-
expect(getCurrentObserver()).toBeUndefined();
|
|
116
|
-
const dispose = observer.observe();
|
|
117
|
-
expect(getCurrentObserver()).toBe(observer);
|
|
118
|
-
dispose();
|
|
119
|
-
expect(getCurrentObserver()).toBeUndefined();
|
|
120
|
-
});
|
|
121
|
-
it('should handle nested observations with stack', () => {
|
|
122
|
-
const observer1 = new Observer(() => { });
|
|
123
|
-
const observer2 = new Observer(() => { });
|
|
124
|
-
const dispose1 = observer1.observe();
|
|
125
|
-
expect(getCurrentObserver()).toBe(observer1);
|
|
126
|
-
const dispose2 = observer2.observe();
|
|
127
|
-
expect(getCurrentObserver()).toBe(observer2);
|
|
128
|
-
dispose2();
|
|
129
|
-
expect(getCurrentObserver()).toBe(observer1);
|
|
130
|
-
dispose1();
|
|
131
|
-
expect(getCurrentObserver()).toBeUndefined();
|
|
132
|
-
});
|
|
133
|
-
it('should prevent duplicate notifications while queued', async () => {
|
|
134
|
-
let callCount = 0;
|
|
135
|
-
const observer = new Observer(() => {
|
|
136
|
-
callCount++;
|
|
137
|
-
});
|
|
138
|
-
const signal = new Signal();
|
|
139
|
-
const dispose = observer.observe();
|
|
140
|
-
observer.subscribeSignal(signal);
|
|
141
|
-
dispose();
|
|
142
|
-
// Rapid-fire notifications
|
|
143
|
-
for (let i = 0; i < 100; i++) {
|
|
144
|
-
signal.notify();
|
|
145
|
-
}
|
|
146
|
-
await new Promise((resolve) => queueMicrotask(() => resolve()));
|
|
147
|
-
// Should only be called once
|
|
148
|
-
expect(callCount).toBe(1);
|
|
149
|
-
});
|
|
150
|
-
});
|
package/dist/suspense.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { VNode } from "snabbdom";
|
|
2
|
-
export declare function createSuspense<T extends Record<string, Promise<any>>>(promises: T): {
|
|
3
|
-
[K in keyof T]: Awaited<T[K]>;
|
|
4
|
-
};
|
|
5
|
-
export declare function Suspense(props: {
|
|
6
|
-
fallback: VNode;
|
|
7
|
-
children: VNode | VNode[];
|
|
8
|
-
}): () => VNode | VNode[];
|
|
9
|
-
type SuspensePromiseState<T> = {
|
|
10
|
-
status: "pending";
|
|
11
|
-
value: null;
|
|
12
|
-
error: null;
|
|
13
|
-
} | {
|
|
14
|
-
status: "resolved";
|
|
15
|
-
value: T;
|
|
16
|
-
error: null;
|
|
17
|
-
} | {
|
|
18
|
-
status: "rejected";
|
|
19
|
-
value: null;
|
|
20
|
-
error: string;
|
|
21
|
-
};
|
|
22
|
-
export type SuspensePromise<T> = Promise<T> & SuspensePromiseState<T>;
|
|
23
|
-
export declare function createSuspensePromise<T>(promise: Promise<T>): SuspensePromise<T>;
|
|
24
|
-
export {};
|
|
25
|
-
//# sourceMappingURL=suspense.d.ts.map
|
package/dist/suspense.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"suspense.d.ts","sourceRoot":"","sources":["../src/suspense.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAKjC,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,EACnE,QAAQ,EAAE,CAAC,GACV;KACA,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC9B,CAuBA;AAQD,wBAAgB,QAAQ,CAAC,KAAK,EAAE;IAC9B,QAAQ,EAAE,KAAK,CAAC;IAChB,QAAQ,EAAE,KAAK,GAAG,KAAK,EAAE,CAAC;CAC3B,yBAmBA;AAED,KAAK,oBAAoB,CAAC,CAAC,IACvB;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,IAAI,CAAC;IACZ,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,IAAI,CAAC;CACb,GACD;IACE,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,IAAI,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC;AAEtE,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAClB,eAAe,CAAC,CAAC,CAAC,CAgEpB"}
|
package/dist/suspense.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { getCurrentComponent } from "./component";
|
|
2
|
-
import { createState } from "./createState";
|
|
3
|
-
import { getCurrentObserver, Signal } from "./observation";
|
|
4
|
-
export function createSuspense(promises) {
|
|
5
|
-
let currentComponent = getCurrentComponent();
|
|
6
|
-
if (!currentComponent) {
|
|
7
|
-
throw new Error("createSuspense must be used in the setup of a component");
|
|
8
|
-
}
|
|
9
|
-
const proxy = {};
|
|
10
|
-
for (const key in promises) {
|
|
11
|
-
const promise = promises[key];
|
|
12
|
-
const suspensePromise = isSuspensePromise(promise)
|
|
13
|
-
? promise
|
|
14
|
-
: createSuspensePromise(promise);
|
|
15
|
-
currentComponent.notifyAsync(suspensePromise);
|
|
16
|
-
Object.defineProperty(proxy, key, {
|
|
17
|
-
get() {
|
|
18
|
-
return suspensePromise.value;
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
return proxy;
|
|
23
|
-
}
|
|
24
|
-
function isSuspensePromise(promise) {
|
|
25
|
-
return "status" in promise;
|
|
26
|
-
}
|
|
27
|
-
export function Suspense(props) {
|
|
28
|
-
const currentComponent = getCurrentComponent();
|
|
29
|
-
const state = createState({
|
|
30
|
-
suspendingPromises: [],
|
|
31
|
-
});
|
|
32
|
-
currentComponent.onAsync((promise) => {
|
|
33
|
-
state.suspendingPromises = state.suspendingPromises.concat(promise);
|
|
34
|
-
});
|
|
35
|
-
return () => {
|
|
36
|
-
const isAllResolved = state.suspendingPromises.every((promise) => promise.status === "resolved");
|
|
37
|
-
console.log(isAllResolved);
|
|
38
|
-
return isAllResolved ? props.children : props.fallback;
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
export function createSuspensePromise(promise) {
|
|
42
|
-
const signal = new Signal();
|
|
43
|
-
const state = {
|
|
44
|
-
error: null,
|
|
45
|
-
status: "pending",
|
|
46
|
-
value: null,
|
|
47
|
-
};
|
|
48
|
-
Object.defineProperty(promise, "value", {
|
|
49
|
-
get() {
|
|
50
|
-
const observer = getCurrentObserver();
|
|
51
|
-
if (observer) {
|
|
52
|
-
observer.subscribeSignal(signal);
|
|
53
|
-
}
|
|
54
|
-
return state.value;
|
|
55
|
-
},
|
|
56
|
-
set(newValue) {
|
|
57
|
-
state.value = newValue;
|
|
58
|
-
signal.notify();
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
Object.defineProperty(promise, "error", {
|
|
62
|
-
get() {
|
|
63
|
-
const observer = getCurrentObserver();
|
|
64
|
-
if (observer) {
|
|
65
|
-
observer.subscribeSignal(signal);
|
|
66
|
-
}
|
|
67
|
-
return state.error;
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
Object.defineProperty(promise, "status", {
|
|
71
|
-
get() {
|
|
72
|
-
const observer = getCurrentObserver();
|
|
73
|
-
if (observer) {
|
|
74
|
-
observer.subscribeSignal(signal);
|
|
75
|
-
}
|
|
76
|
-
return state.status;
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
promise
|
|
80
|
-
.then((value) => {
|
|
81
|
-
Object.assign(state, {
|
|
82
|
-
value,
|
|
83
|
-
error: null,
|
|
84
|
-
status: "resolved",
|
|
85
|
-
});
|
|
86
|
-
signal.notify();
|
|
87
|
-
})
|
|
88
|
-
.catch((error) => {
|
|
89
|
-
Object.assign(state, {
|
|
90
|
-
value: null,
|
|
91
|
-
error: String(error),
|
|
92
|
-
status: "rejected",
|
|
93
|
-
});
|
|
94
|
-
signal.notify();
|
|
95
|
-
});
|
|
96
|
-
return promise;
|
|
97
|
-
}
|
package/dist/test-setup.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { VNode } from 'snabbdom';
|
|
2
|
-
/**
|
|
3
|
-
* Test helper to render a component and provide easy cleanup
|
|
4
|
-
*
|
|
5
|
-
* @example
|
|
6
|
-
* const { container, unmount } = renderComponent(<MyComponent />);
|
|
7
|
-
* expect(container.textContent).toBe('hello');
|
|
8
|
-
* unmount();
|
|
9
|
-
*/
|
|
10
|
-
export declare function renderComponent(vnode: VNode): {
|
|
11
|
-
container: HTMLElement;
|
|
12
|
-
vnode: VNode;
|
|
13
|
-
unmount: () => void;
|
|
14
|
-
rerender: (newVnode: VNode) => VNode;
|
|
15
|
-
};
|
|
16
|
-
//# sourceMappingURL=test-setup.d.ts.map
|
package/dist/test-setup.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test-setup.d.ts","sourceRoot":"","sources":["../src/test-setup.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAUtC;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK;;;;yBAmBnB,KAAK;EAK7B"}
|
package/dist/test-setup.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// Test setup file for vitest
|
|
2
|
-
import { afterEach } from 'vitest';
|
|
3
|
-
import { render, patch } from './render';
|
|
4
|
-
// Clean up after each test
|
|
5
|
-
afterEach(() => {
|
|
6
|
-
document.body.innerHTML = '';
|
|
7
|
-
// Remove any style tags added by render function
|
|
8
|
-
document.querySelectorAll('style').forEach(style => style.remove());
|
|
9
|
-
});
|
|
10
|
-
/**
|
|
11
|
-
* Test helper to render a component and provide easy cleanup
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* const { container, unmount } = renderComponent(<MyComponent />);
|
|
15
|
-
* expect(container.textContent).toBe('hello');
|
|
16
|
-
* unmount();
|
|
17
|
-
*/
|
|
18
|
-
export function renderComponent(vnode) {
|
|
19
|
-
const container = document.createElement('div');
|
|
20
|
-
document.body.appendChild(container);
|
|
21
|
-
let currentVnode = render(vnode, container);
|
|
22
|
-
const actualElement = currentVnode.elm;
|
|
23
|
-
return {
|
|
24
|
-
// The actual rendered DOM element (after patch replaces container)
|
|
25
|
-
container: actualElement,
|
|
26
|
-
// The vnode returned by render
|
|
27
|
-
vnode: currentVnode,
|
|
28
|
-
// Cleanup function
|
|
29
|
-
unmount: () => {
|
|
30
|
-
if (actualElement && actualElement.parentNode) {
|
|
31
|
-
actualElement.parentNode.removeChild(actualElement);
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
// Re-render with new vnode
|
|
35
|
-
rerender: (newVnode) => {
|
|
36
|
-
currentVnode = patch(currentVnode, newVnode);
|
|
37
|
-
return currentVnode;
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
}
|
package/dist/test.d.ts
DELETED
package/dist/test.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAiBA,wBAAgB,IAAI,CAAC,SAAS,EAAE,GAAG,QA4BlC"}
|
package/dist/test.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { init, classModule, propsModule, styleModule, eventListenersModule, h, } from "snabbdom";
|
|
2
|
-
const patch = init([
|
|
3
|
-
// Init patch function with chosen modules
|
|
4
|
-
classModule, // makes it easy to toggle classes
|
|
5
|
-
propsModule, // for setting properties on DOM elements
|
|
6
|
-
styleModule, // handles styling on elements with support for animations
|
|
7
|
-
eventListenersModule, // attaches event listeners
|
|
8
|
-
]);
|
|
9
|
-
export function test(container) {
|
|
10
|
-
const vnode = h("div#container.two.classes", { on: { click: () => console.log("div clicked") } }, [
|
|
11
|
-
h("span", { style: { fontWeight: "bold" } }, "This is bold"),
|
|
12
|
-
" and this is just normal text",
|
|
13
|
-
h("a", { props: { href: "/foo" } }, "I'll take you places!"),
|
|
14
|
-
]);
|
|
15
|
-
// Patch into empty DOM element – this modifies the DOM as a side effect
|
|
16
|
-
patch(container, vnode);
|
|
17
|
-
const newVnode = h("div#container.two.classes", { on: { click: () => console.log("updated div clicked") } }, [
|
|
18
|
-
h("span", { style: { fontWeight: "normal", fontStyle: "italic" } }, "This is now italic type"),
|
|
19
|
-
" and this is still just normal text",
|
|
20
|
-
h("a", { props: { href: "/bar" } }, "I'll take you places!"),
|
|
21
|
-
]);
|
|
22
|
-
// Second `patch` invocation
|
|
23
|
-
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state
|
|
24
|
-
}
|
|
File without changes
|
|
File without changes
|