@uistate/core 5.6.0 → 5.6.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/README.md +1 -0
- package/package.json +5 -2
- package/self-test.js +216 -0
package/README.md
CHANGED
|
@@ -131,6 +131,7 @@ qc.invalidate('users');
|
|
|
131
131
|
| [@uistate/css](https://www.npmjs.com/package/@uistate/css) | CSS-native state via custom properties and data attributes | MIT |
|
|
132
132
|
| [@uistate/event-test](https://www.npmjs.com/package/@uistate/event-test) | Event-sequence testing for UIstate stores | Proprietary |
|
|
133
133
|
| [@uistate/examples](https://www.npmjs.com/package/@uistate/examples) | Example applications and patterns | MIT |
|
|
134
|
+
| [@uistate/renderer](https://www.npmjs.com/package/@uistate/renderer) | Direct-binding reactive renderer: `bind-*`, `set`, `each` attributes. Zero build step | Proprietary |
|
|
134
135
|
| [@uistate/aliases](https://www.npmjs.com/package/@uistate/aliases) | Ergonomic single-character and short-name DOM aliases for vanilla JS | MIT |
|
|
135
136
|
|
|
136
137
|
📖 **Documentation:** [uistate.com](https://uistate.com)
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uistate/core",
|
|
3
|
-
"version": "5.6.
|
|
3
|
+
"version": "5.6.1",
|
|
4
4
|
"description": "Lightweight event-driven state management with path-based subscriptions, wildcards, and async support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"
|
|
8
|
+
"postinstall": "node self-test.js",
|
|
9
|
+
"test": "node tests/core.test.js",
|
|
10
|
+
"self-test": "node self-test.js"
|
|
9
11
|
},
|
|
10
12
|
"exports": {
|
|
11
13
|
".": "./index.js",
|
|
@@ -16,6 +18,7 @@
|
|
|
16
18
|
"index.js",
|
|
17
19
|
"eventState.js",
|
|
18
20
|
"queryClient.js",
|
|
21
|
+
"self-test.js",
|
|
19
22
|
"LICENSE"
|
|
20
23
|
],
|
|
21
24
|
"keywords": [
|
package/self-test.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @uistate/core — self-test
|
|
3
|
+
*
|
|
4
|
+
* Standalone test of core EventState functionality. No dependencies beyond eventState.js.
|
|
5
|
+
* Runs on `node self-test.js` or as a postinstall hook.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createEventState } from './eventState.js';
|
|
9
|
+
|
|
10
|
+
let passed = 0;
|
|
11
|
+
let failed = 0;
|
|
12
|
+
|
|
13
|
+
function assert(name, condition) {
|
|
14
|
+
if (condition) {
|
|
15
|
+
passed++;
|
|
16
|
+
console.log(` ✓ ${name}`);
|
|
17
|
+
} else {
|
|
18
|
+
failed++;
|
|
19
|
+
console.error(` ✗ ${name}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log('\n1. get / set');
|
|
24
|
+
const s1 = createEventState({ count: 0, user: { name: 'Alice', age: 30 } });
|
|
25
|
+
|
|
26
|
+
assert('get: primitive', s1.get('count') === 0);
|
|
27
|
+
assert('get: nested', s1.get('user.name') === 'Alice');
|
|
28
|
+
assert('get: object', s1.get('user')?.name === 'Alice');
|
|
29
|
+
assert('get: entire state', s1.get('')?.count === 0);
|
|
30
|
+
assert('get: missing path → undefined', s1.get('nonexistent') === undefined);
|
|
31
|
+
assert('get: deep missing → undefined', s1.get('user.email') === undefined);
|
|
32
|
+
|
|
33
|
+
s1.set('count', 5);
|
|
34
|
+
assert('set: primitive', s1.get('count') === 5);
|
|
35
|
+
|
|
36
|
+
s1.set('user.name', 'Bob');
|
|
37
|
+
assert('set: nested', s1.get('user.name') === 'Bob');
|
|
38
|
+
|
|
39
|
+
s1.set('user.email', 'bob@example.com');
|
|
40
|
+
assert('set: auto-creates path', s1.get('user.email') === 'bob@example.com');
|
|
41
|
+
|
|
42
|
+
s1.set('deep.nested.path', 'value');
|
|
43
|
+
assert('set: deep auto-create', s1.get('deep.nested.path') === 'value');
|
|
44
|
+
|
|
45
|
+
s1.destroy();
|
|
46
|
+
|
|
47
|
+
console.log('\n2. subscribe: exact path');
|
|
48
|
+
const s2 = createEventState({ count: 0 });
|
|
49
|
+
let lastValue = null;
|
|
50
|
+
let fireCount = 0;
|
|
51
|
+
|
|
52
|
+
const unsub = s2.subscribe('count', (value) => {
|
|
53
|
+
lastValue = value;
|
|
54
|
+
fireCount++;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
s2.set('count', 1);
|
|
58
|
+
assert('subscribe: fires on change', fireCount === 1);
|
|
59
|
+
assert('subscribe: receives value', lastValue === 1);
|
|
60
|
+
|
|
61
|
+
s2.set('count', 2);
|
|
62
|
+
assert('subscribe: fires again', fireCount === 2);
|
|
63
|
+
assert('subscribe: receives new value', lastValue === 2);
|
|
64
|
+
|
|
65
|
+
unsub();
|
|
66
|
+
s2.set('count', 3);
|
|
67
|
+
assert('unsubscribe: stops firing', fireCount === 2);
|
|
68
|
+
assert('unsubscribe: value unchanged in handler', lastValue === 2);
|
|
69
|
+
assert('set still works after unsub', s2.get('count') === 3);
|
|
70
|
+
|
|
71
|
+
s2.destroy();
|
|
72
|
+
|
|
73
|
+
console.log('\n3. subscribe: wildcard');
|
|
74
|
+
const s3 = createEventState({ user: { name: 'Alice', age: 30 } });
|
|
75
|
+
let wildcardFires = 0;
|
|
76
|
+
let wildcardDetail = null;
|
|
77
|
+
|
|
78
|
+
s3.subscribe('user.*', (detail) => {
|
|
79
|
+
wildcardFires++;
|
|
80
|
+
wildcardDetail = detail;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
s3.set('user.name', 'Bob');
|
|
84
|
+
assert('wildcard: fires on child change', wildcardFires === 1);
|
|
85
|
+
assert('wildcard: detail has path', wildcardDetail?.path === 'user.name');
|
|
86
|
+
assert('wildcard: detail has value', wildcardDetail?.value === 'Bob');
|
|
87
|
+
|
|
88
|
+
s3.set('user.age', 31);
|
|
89
|
+
assert('wildcard: fires on other child', wildcardFires === 2);
|
|
90
|
+
|
|
91
|
+
s3.destroy();
|
|
92
|
+
|
|
93
|
+
console.log('\n4. subscribe: global');
|
|
94
|
+
const s4 = createEventState({ a: 1, b: { c: 2 } });
|
|
95
|
+
let globalFires = 0;
|
|
96
|
+
|
|
97
|
+
s4.subscribe('*', () => { globalFires++; });
|
|
98
|
+
|
|
99
|
+
s4.set('a', 10);
|
|
100
|
+
assert('global: fires on root path', globalFires === 1);
|
|
101
|
+
|
|
102
|
+
s4.set('b.c', 20);
|
|
103
|
+
assert('global: fires on nested path', globalFires === 2);
|
|
104
|
+
|
|
105
|
+
s4.destroy();
|
|
106
|
+
|
|
107
|
+
console.log('\n5. batch');
|
|
108
|
+
const s5 = createEventState({ x: 0, y: 0 });
|
|
109
|
+
let batchFires = 0;
|
|
110
|
+
s5.subscribe('*', () => { batchFires++; });
|
|
111
|
+
|
|
112
|
+
s5.batch(() => {
|
|
113
|
+
s5.set('x', 1);
|
|
114
|
+
s5.set('y', 2);
|
|
115
|
+
});
|
|
116
|
+
assert('batch: fires after (not during)', batchFires === 2);
|
|
117
|
+
assert('batch: x set', s5.get('x') === 1);
|
|
118
|
+
assert('batch: y set', s5.get('y') === 2);
|
|
119
|
+
|
|
120
|
+
// Deduplication
|
|
121
|
+
batchFires = 0;
|
|
122
|
+
s5.batch(() => {
|
|
123
|
+
s5.set('x', 10);
|
|
124
|
+
s5.set('x', 20);
|
|
125
|
+
s5.set('x', 30);
|
|
126
|
+
});
|
|
127
|
+
assert('batch: deduplicates same path (1 fire)', batchFires === 1);
|
|
128
|
+
assert('batch: last write wins', s5.get('x') === 30);
|
|
129
|
+
|
|
130
|
+
// Nested batch
|
|
131
|
+
batchFires = 0;
|
|
132
|
+
s5.batch(() => {
|
|
133
|
+
s5.set('x', 100);
|
|
134
|
+
s5.batch(() => {
|
|
135
|
+
s5.set('y', 200);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
assert('nested batch: both fire after outermost', batchFires === 2);
|
|
139
|
+
assert('nested batch: x', s5.get('x') === 100);
|
|
140
|
+
assert('nested batch: y', s5.get('y') === 200);
|
|
141
|
+
|
|
142
|
+
// No notifications during batch
|
|
143
|
+
const seen = [];
|
|
144
|
+
const s5b = createEventState({ v: 0 });
|
|
145
|
+
s5b.subscribe('v', (val) => seen.push(val));
|
|
146
|
+
s5b.batch(() => {
|
|
147
|
+
s5b.set('v', 1);
|
|
148
|
+
s5b.set('v', 2);
|
|
149
|
+
});
|
|
150
|
+
assert('batch: no mid-batch notifications', seen.length === 1);
|
|
151
|
+
assert('batch: final value delivered', seen[0] === 2);
|
|
152
|
+
|
|
153
|
+
s5.destroy();
|
|
154
|
+
s5b.destroy();
|
|
155
|
+
|
|
156
|
+
console.log('\n6. setMany');
|
|
157
|
+
const s6 = createEventState({});
|
|
158
|
+
|
|
159
|
+
// Plain object
|
|
160
|
+
s6.setMany({ 'a.b': 1, 'a.c': 2 });
|
|
161
|
+
assert('setMany object: a.b', s6.get('a.b') === 1);
|
|
162
|
+
assert('setMany object: a.c', s6.get('a.c') === 2);
|
|
163
|
+
|
|
164
|
+
// Array of pairs
|
|
165
|
+
s6.setMany([['x.y', 'hello'], ['x.z', 'world']]);
|
|
166
|
+
assert('setMany array: x.y', s6.get('x.y') === 'hello');
|
|
167
|
+
assert('setMany array: x.z', s6.get('x.z') === 'world');
|
|
168
|
+
|
|
169
|
+
// Map
|
|
170
|
+
s6.setMany(new Map([['m.a', true], ['m.b', false]]));
|
|
171
|
+
assert('setMany Map: m.a', s6.get('m.a') === true);
|
|
172
|
+
assert('setMany Map: m.b', s6.get('m.b') === false);
|
|
173
|
+
|
|
174
|
+
s6.destroy();
|
|
175
|
+
|
|
176
|
+
console.log('\n7. destroy');
|
|
177
|
+
const s7 = createEventState({ z: 0 });
|
|
178
|
+
s7.destroy();
|
|
179
|
+
|
|
180
|
+
let threw = false;
|
|
181
|
+
try { s7.get('z'); } catch { threw = true; }
|
|
182
|
+
assert('destroy: get throws', threw);
|
|
183
|
+
|
|
184
|
+
threw = false;
|
|
185
|
+
try { s7.set('z', 1); } catch { threw = true; }
|
|
186
|
+
assert('destroy: set throws', threw);
|
|
187
|
+
|
|
188
|
+
threw = false;
|
|
189
|
+
try { s7.batch(() => {}); } catch { threw = true; }
|
|
190
|
+
assert('destroy: batch throws', threw);
|
|
191
|
+
|
|
192
|
+
threw = false;
|
|
193
|
+
try { s7.setMany({ a: 1 }); } catch { threw = true; }
|
|
194
|
+
assert('destroy: setMany throws', threw);
|
|
195
|
+
|
|
196
|
+
threw = false;
|
|
197
|
+
try { s7.subscribe('z', () => {}); } catch { threw = true; }
|
|
198
|
+
assert('destroy: subscribe throws', threw);
|
|
199
|
+
|
|
200
|
+
console.log('\n8. subscribe: detail object');
|
|
201
|
+
const s8 = createEventState({ count: 10 });
|
|
202
|
+
let detail = null;
|
|
203
|
+
s8.subscribe('count', (value, d) => { detail = d; });
|
|
204
|
+
s8.set('count', 20);
|
|
205
|
+
assert('detail: has path', detail?.path === 'count');
|
|
206
|
+
assert('detail: has value', detail?.value === 20);
|
|
207
|
+
assert('detail: has oldValue', detail?.oldValue === 10);
|
|
208
|
+
|
|
209
|
+
s8.destroy();
|
|
210
|
+
|
|
211
|
+
// Results
|
|
212
|
+
|
|
213
|
+
console.log(`\n@uistate/core v5.6.1 — self-test`);
|
|
214
|
+
console.log(`✓ ${passed} assertions passed${failed ? `, ✗ ${failed} failed` : ''}\n`);
|
|
215
|
+
|
|
216
|
+
if (failed > 0) process.exit(1);
|