@uistate/core 5.2.0 ā 5.3.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/package.json +5 -5
- package/queryClient.js +55 -0
- package/examples/001-counter/README.md +0 -44
- package/examples/001-counter/index.html +0 -33
- package/examples/002-counter-improved/README.md +0 -44
- package/examples/002-counter-improved/index.html +0 -47
- package/examples/003-input-reactive/README.md +0 -44
- package/examples/003-input-reactive/index.html +0 -33
- package/examples/004-computed-state/README.md +0 -45
- package/examples/004-computed-state/index.html +0 -65
- package/examples/005-conditional-rendering/README.md +0 -42
- package/examples/005-conditional-rendering/index.html +0 -39
- package/examples/006-list-rendering/README.md +0 -49
- package/examples/006-list-rendering/index.html +0 -63
- package/examples/007-form-validation/README.md +0 -52
- package/examples/007-form-validation/index.html +0 -102
- package/examples/008-undo-redo/README.md +0 -70
- package/examples/008-undo-redo/index.html +0 -108
- package/examples/009-localStorage-side-effects/README.md +0 -72
- package/examples/009-localStorage-side-effects/index.html +0 -57
- package/examples/010-decoupled-components/README.md +0 -74
- package/examples/010-decoupled-components/index.html +0 -93
- package/examples/011-async-patterns/README.md +0 -98
- package/examples/011-async-patterns/index.html +0 -132
- package/examples/028-counter-improved-eventTest/LICENSE +0 -55
- package/examples/028-counter-improved-eventTest/README.md +0 -131
- package/examples/028-counter-improved-eventTest/app/store.js +0 -9
- package/examples/028-counter-improved-eventTest/index.html +0 -49
- package/examples/028-counter-improved-eventTest/runtime/core/behaviors.runtime.js +0 -282
- package/examples/028-counter-improved-eventTest/runtime/core/eventState.js +0 -100
- package/examples/028-counter-improved-eventTest/runtime/core/eventStateNew.js +0 -149
- package/examples/028-counter-improved-eventTest/runtime/core/helpers.js +0 -212
- package/examples/028-counter-improved-eventTest/runtime/core/router.js +0 -271
- package/examples/028-counter-improved-eventTest/store.d.ts +0 -8
- package/examples/028-counter-improved-eventTest/style.css +0 -170
- package/examples/028-counter-improved-eventTest/tests/README.md +0 -208
- package/examples/028-counter-improved-eventTest/tests/counter.test.js +0 -116
- package/examples/028-counter-improved-eventTest/tests/eventTest.js +0 -176
- package/examples/028-counter-improved-eventTest/tests/generateTypes.js +0 -168
- package/examples/028-counter-improved-eventTest/tests/run.js +0 -20
- package/examples/030-todo-app-with-eventTest/LICENSE +0 -55
- package/examples/030-todo-app-with-eventTest/README.md +0 -121
- package/examples/030-todo-app-with-eventTest/app/router.js +0 -25
- package/examples/030-todo-app-with-eventTest/app/store.js +0 -16
- package/examples/030-todo-app-with-eventTest/app/views/home.js +0 -11
- package/examples/030-todo-app-with-eventTest/app/views/todoDemo.js +0 -88
- package/examples/030-todo-app-with-eventTest/index.html +0 -65
- package/examples/030-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
- package/examples/030-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
- package/examples/030-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
- package/examples/030-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
- package/examples/030-todo-app-with-eventTest/runtime/core/router.js +0 -271
- package/examples/030-todo-app-with-eventTest/store.d.ts +0 -18
- package/examples/030-todo-app-with-eventTest/style.css +0 -170
- package/examples/030-todo-app-with-eventTest/tests/README.md +0 -208
- package/examples/030-todo-app-with-eventTest/tests/eventTest.js +0 -176
- package/examples/030-todo-app-with-eventTest/tests/generateTypes.js +0 -189
- package/examples/030-todo-app-with-eventTest/tests/run.js +0 -20
- package/examples/030-todo-app-with-eventTest/tests/todos.test.js +0 -167
- package/examples/031-todo-app-with-eventTest/LICENSE +0 -55
- package/examples/031-todo-app-with-eventTest/README.md +0 -54
- package/examples/031-todo-app-with-eventTest/TUTORIAL.md +0 -390
- package/examples/031-todo-app-with-eventTest/WHY_EVENTSTATE.md +0 -777
- package/examples/031-todo-app-with-eventTest/app/bridges.js +0 -113
- package/examples/031-todo-app-with-eventTest/app/router.js +0 -26
- package/examples/031-todo-app-with-eventTest/app/store.js +0 -15
- package/examples/031-todo-app-with-eventTest/app/views/home.js +0 -46
- package/examples/031-todo-app-with-eventTest/app/views/todoDemo.js +0 -69
- package/examples/031-todo-app-with-eventTest/devtools/dock.js +0 -41
- package/examples/031-todo-app-with-eventTest/devtools/stateTracker.dock.js +0 -10
- package/examples/031-todo-app-with-eventTest/devtools/stateTracker.js +0 -246
- package/examples/031-todo-app-with-eventTest/devtools/telemetry.js +0 -104
- package/examples/031-todo-app-with-eventTest/devtools/typeGenerator.js +0 -339
- package/examples/031-todo-app-with-eventTest/index.html +0 -103
- package/examples/031-todo-app-with-eventTest/package-lock.json +0 -2184
- package/examples/031-todo-app-with-eventTest/package.json +0 -24
- package/examples/031-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
- package/examples/031-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
- package/examples/031-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
- package/examples/031-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
- package/examples/031-todo-app-with-eventTest/runtime/core/router.js +0 -271
- package/examples/031-todo-app-with-eventTest/runtime/extensions/boundary.js +0 -36
- package/examples/031-todo-app-with-eventTest/runtime/extensions/converge.js +0 -63
- package/examples/031-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +0 -210
- package/examples/031-todo-app-with-eventTest/runtime/extensions/hydrate.js +0 -157
- package/examples/031-todo-app-with-eventTest/runtime/extensions/queryBinding.js +0 -69
- package/examples/031-todo-app-with-eventTest/runtime/forms/computed.js +0 -78
- package/examples/031-todo-app-with-eventTest/runtime/forms/meta.js +0 -51
- package/examples/031-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +0 -28
- package/examples/031-todo-app-with-eventTest/runtime/forms/validators.js +0 -55
- package/examples/031-todo-app-with-eventTest/store.d.ts +0 -23
- package/examples/031-todo-app-with-eventTest/style.css +0 -170
- package/examples/031-todo-app-with-eventTest/tests/README.md +0 -208
- package/examples/031-todo-app-with-eventTest/tests/eventTest.js +0 -176
- package/examples/031-todo-app-with-eventTest/tests/generateTypes.js +0 -191
- package/examples/031-todo-app-with-eventTest/tests/run.js +0 -20
- package/examples/031-todo-app-with-eventTest/tests/todos.test.js +0 -192
- package/examples/032-todo-app-with-eventTest/LICENSE +0 -55
- package/examples/032-todo-app-with-eventTest/README.md +0 -54
- package/examples/032-todo-app-with-eventTest/TUTORIAL.md +0 -390
- package/examples/032-todo-app-with-eventTest/WHY_EVENTSTATE.md +0 -777
- package/examples/032-todo-app-with-eventTest/app/actions/index.js +0 -153
- package/examples/032-todo-app-with-eventTest/app/bridges.js +0 -113
- package/examples/032-todo-app-with-eventTest/app/router.js +0 -26
- package/examples/032-todo-app-with-eventTest/app/store.js +0 -15
- package/examples/032-todo-app-with-eventTest/app/views/home.js +0 -46
- package/examples/032-todo-app-with-eventTest/app/views/todoDemo.js +0 -69
- package/examples/032-todo-app-with-eventTest/devtools/dock.js +0 -41
- package/examples/032-todo-app-with-eventTest/devtools/stateTracker.dock.js +0 -10
- package/examples/032-todo-app-with-eventTest/devtools/stateTracker.js +0 -246
- package/examples/032-todo-app-with-eventTest/devtools/telemetry.js +0 -104
- package/examples/032-todo-app-with-eventTest/devtools/typeGenerator.js +0 -339
- package/examples/032-todo-app-with-eventTest/index.html +0 -87
- package/examples/032-todo-app-with-eventTest/package-lock.json +0 -2184
- package/examples/032-todo-app-with-eventTest/package.json +0 -24
- package/examples/032-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
- package/examples/032-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
- package/examples/032-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
- package/examples/032-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
- package/examples/032-todo-app-with-eventTest/runtime/core/router.js +0 -271
- package/examples/032-todo-app-with-eventTest/runtime/extensions/boundary.js +0 -36
- package/examples/032-todo-app-with-eventTest/runtime/extensions/converge.js +0 -63
- package/examples/032-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +0 -210
- package/examples/032-todo-app-with-eventTest/runtime/extensions/hydrate.js +0 -157
- package/examples/032-todo-app-with-eventTest/runtime/extensions/queryBinding.js +0 -69
- package/examples/032-todo-app-with-eventTest/runtime/forms/computed.js +0 -78
- package/examples/032-todo-app-with-eventTest/runtime/forms/meta.js +0 -51
- package/examples/032-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +0 -28
- package/examples/032-todo-app-with-eventTest/runtime/forms/validators.js +0 -55
- package/examples/032-todo-app-with-eventTest/store.d.ts +0 -23
- package/examples/032-todo-app-with-eventTest/style.css +0 -170
- package/examples/032-todo-app-with-eventTest/tests/README.md +0 -208
- package/examples/032-todo-app-with-eventTest/tests/eventTest.js +0 -176
- package/examples/032-todo-app-with-eventTest/tests/generateTypes.js +0 -191
- package/examples/032-todo-app-with-eventTest/tests/run.js +0 -20
- package/examples/032-todo-app-with-eventTest/tests/todos.test.js +0 -192
- package/playground/exercise001.html +0 -38
- package/playground/exercise002.html +0 -49
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
# EventTest - Event-Sequence Testing
|
|
2
|
-
|
|
3
|
-
Event-driven TDD for EventState applications.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- ā
**Event-sequence testing** - Test state changes through event flows
|
|
8
|
-
- ā
**Type extraction** - Generate `.d.ts` files from test assertions
|
|
9
|
-
- ā
**No DOM required** - Tests run in Node.js
|
|
10
|
-
- ā
**Fluent API** - Chainable assertions
|
|
11
|
-
- ā
**Type-safe** - Tests define types, not manual definitions
|
|
12
|
-
|
|
13
|
-
## Usage
|
|
14
|
-
|
|
15
|
-
### Writing Tests
|
|
16
|
-
|
|
17
|
-
```javascript
|
|
18
|
-
import { createEventTest, test } from './eventTest.js';
|
|
19
|
-
|
|
20
|
-
test('add todo', () => {
|
|
21
|
-
const t = createEventTest({
|
|
22
|
-
domain: { todos: { items: [] } }
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// Trigger intent
|
|
26
|
-
t.trigger('intent.todo.add', { text: 'Buy milk' });
|
|
27
|
-
|
|
28
|
-
// Assert types (for .d.ts generation)
|
|
29
|
-
t.assertArrayOf('domain.todos.items', {
|
|
30
|
-
id: 'number',
|
|
31
|
-
text: 'string',
|
|
32
|
-
done: 'boolean'
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Assert values
|
|
36
|
-
t.assertArrayLength('domain.todos.items', 1);
|
|
37
|
-
});
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Running Tests
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
# Run all tests
|
|
44
|
-
node tests/run.js
|
|
45
|
-
|
|
46
|
-
# Or run specific test file
|
|
47
|
-
node tests/todos.test.js
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Generating Types
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
# Generate store.d.ts from test assertions
|
|
54
|
-
node tests/generateTypes.js
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## API Reference
|
|
58
|
-
|
|
59
|
-
### `createEventTest(initialState)`
|
|
60
|
-
|
|
61
|
-
Creates a test instance with isolated store.
|
|
62
|
-
|
|
63
|
-
**Returns:** Test API object
|
|
64
|
-
|
|
65
|
-
### Test API
|
|
66
|
-
|
|
67
|
-
#### `.trigger(path, value)`
|
|
68
|
-
|
|
69
|
-
Set a value in the store (trigger state change).
|
|
70
|
-
|
|
71
|
-
```javascript
|
|
72
|
-
t.trigger('intent.todo.add', { text: 'Buy milk' });
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
#### `.assertPath(path, expected)`
|
|
76
|
-
|
|
77
|
-
Assert exact value at path.
|
|
78
|
-
|
|
79
|
-
```javascript
|
|
80
|
-
t.assertPath('ui.theme', 'dark');
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
#### `.assertType(path, type)`
|
|
84
|
-
|
|
85
|
-
Assert primitive type. Stores type info for `.d.ts` generation.
|
|
86
|
-
|
|
87
|
-
```javascript
|
|
88
|
-
t.assertType('ui.theme', 'string');
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
#### `.assertArrayOf(path, elementShape)`
|
|
92
|
-
|
|
93
|
-
Assert array with element shape. Stores type info for `.d.ts` generation.
|
|
94
|
-
|
|
95
|
-
```javascript
|
|
96
|
-
t.assertArrayOf('domain.todos.items', {
|
|
97
|
-
id: 'number',
|
|
98
|
-
text: 'string',
|
|
99
|
-
done: 'boolean'
|
|
100
|
-
});
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
#### `.assertShape(path, objectShape)`
|
|
104
|
-
|
|
105
|
-
Assert object shape. Stores type info for `.d.ts` generation.
|
|
106
|
-
|
|
107
|
-
```javascript
|
|
108
|
-
t.assertShape('ui.route', {
|
|
109
|
-
path: 'string',
|
|
110
|
-
view: 'string'
|
|
111
|
-
});
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
#### `.assertArrayLength(path, length)`
|
|
115
|
-
|
|
116
|
-
Assert array length.
|
|
117
|
-
|
|
118
|
-
```javascript
|
|
119
|
-
t.assertArrayLength('domain.todos.items', 3);
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
#### `.assertEventFired(path, times)`
|
|
123
|
-
|
|
124
|
-
Assert event fired N times.
|
|
125
|
-
|
|
126
|
-
```javascript
|
|
127
|
-
t.assertEventFired('domain.todos.items', 1);
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
#### `.getEventLog()`
|
|
131
|
-
|
|
132
|
-
Get all captured events.
|
|
133
|
-
|
|
134
|
-
```javascript
|
|
135
|
-
const log = t.getEventLog();
|
|
136
|
-
// [{ timestamp: 123, path: 'domain.todos.items', value: [...] }]
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
#### `.getTypeAssertions()`
|
|
140
|
-
|
|
141
|
-
Get all type assertions (for type generation).
|
|
142
|
-
|
|
143
|
-
```javascript
|
|
144
|
-
const assertions = t.getTypeAssertions();
|
|
145
|
-
// [{ path: 'domain.todos.items', type: 'array', elementShape: {...} }]
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Workflow
|
|
149
|
-
|
|
150
|
-
### 1. Write Tests (TDD)
|
|
151
|
-
|
|
152
|
-
```javascript
|
|
153
|
-
// tests/todos.test.js
|
|
154
|
-
test('add todo', () => {
|
|
155
|
-
const t = createEventTest({ domain: { todos: { items: [] } } });
|
|
156
|
-
t.trigger('intent.todo.add', { text: 'Buy milk' });
|
|
157
|
-
t.assertArrayOf('domain.todos.items', {
|
|
158
|
-
id: 'number',
|
|
159
|
-
text: 'string',
|
|
160
|
-
done: 'boolean'
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### 2. Implement Bridges
|
|
166
|
-
|
|
167
|
-
```javascript
|
|
168
|
-
// app/bridges.js
|
|
169
|
-
store.subscribe('intent.todo.add', ({ text }) => {
|
|
170
|
-
const items = store.get('domain.todos.items') || [];
|
|
171
|
-
const id = items.length + 1;
|
|
172
|
-
store.set('domain.todos.items', [...items, { id, text, done: false }]);
|
|
173
|
-
});
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### 3. Generate Types
|
|
177
|
-
|
|
178
|
-
```bash
|
|
179
|
-
node tests/generateTypes.js
|
|
180
|
-
# Creates store.d.ts with perfect types!
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
### 4. Build UI
|
|
184
|
-
|
|
185
|
-
```javascript
|
|
186
|
-
// Now you have autocomplete!
|
|
187
|
-
const items = store.get('domain.todos.items');
|
|
188
|
-
// TypeScript knows: Array<{ id: number, text: string, done: boolean }>
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## Benefits
|
|
192
|
-
|
|
193
|
-
- **Tests define behavior** - TDD workflow
|
|
194
|
-
- **Types derived from tests** - No manual type definitions
|
|
195
|
-
- **Perfect coverage** - Every test adds type information
|
|
196
|
-
- **No DOM needed** - Fast feedback loop
|
|
197
|
-
- **Deterministic** - Same tests = same types
|
|
198
|
-
- **Watcher-friendly** - Auto-regenerate on test changes
|
|
199
|
-
|
|
200
|
-
## Philosophy
|
|
201
|
-
|
|
202
|
-
EventTest follows EventState's core principles:
|
|
203
|
-
|
|
204
|
-
1. **State-first** - Tests work with state paths
|
|
205
|
-
2. **Dot-paths everywhere** - No special syntax
|
|
206
|
-
3. **Types are optional** - Tests work without TypeScript
|
|
207
|
-
4. **Types are derived** - Not hand-written
|
|
208
|
-
5. **JS-first** - Clean, readable code
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* counter.test.js - Event-sequence tests for counter functionality
|
|
3
|
-
*
|
|
4
|
-
* These tests verify direct state manipulation for counter operations
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createEventTest, test, runTests } from './eventTest.js';
|
|
8
|
-
|
|
9
|
-
// Helper functions that match the view logic
|
|
10
|
-
function increment(store) {
|
|
11
|
-
const current = store.get('count');
|
|
12
|
-
store.set('count', current + 1);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function decrement(store) {
|
|
16
|
-
const current = store.get('count');
|
|
17
|
-
store.set('count', current - 1);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function double(store) {
|
|
21
|
-
const current = store.get('count');
|
|
22
|
-
store.set('count', current * 2);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Test suite
|
|
26
|
-
const tests = {
|
|
27
|
-
'increment increases count by 1': () => {
|
|
28
|
-
const t = createEventTest({ count: 0 });
|
|
29
|
-
|
|
30
|
-
increment(t.store);
|
|
31
|
-
|
|
32
|
-
// Assert type
|
|
33
|
-
t.assertType('count', 'number');
|
|
34
|
-
|
|
35
|
-
// Assert value
|
|
36
|
-
const count = t.store.get('count');
|
|
37
|
-
if (count !== 1) {
|
|
38
|
-
throw new Error(`Expected count to be 1, got ${count}`);
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
'decrement decreases count by 1': () => {
|
|
43
|
-
const t = createEventTest({ count: 5 });
|
|
44
|
-
|
|
45
|
-
decrement(t.store);
|
|
46
|
-
|
|
47
|
-
// Assert type
|
|
48
|
-
t.assertType('count', 'number');
|
|
49
|
-
|
|
50
|
-
// Assert value
|
|
51
|
-
const count = t.store.get('count');
|
|
52
|
-
if (count !== 4) {
|
|
53
|
-
throw new Error(`Expected count to be 4, got ${count}`);
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
'double multiplies count by 2': () => {
|
|
58
|
-
const t = createEventTest({ count: 3 });
|
|
59
|
-
|
|
60
|
-
double(t.store);
|
|
61
|
-
|
|
62
|
-
// Assert type
|
|
63
|
-
t.assertType('count', 'number');
|
|
64
|
-
|
|
65
|
-
// Assert value
|
|
66
|
-
const count = t.store.get('count');
|
|
67
|
-
if (count !== 6) {
|
|
68
|
-
throw new Error(`Expected count to be 6, got ${count}`);
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
'multiple increments work correctly': () => {
|
|
73
|
-
const t = createEventTest({ count: 0 });
|
|
74
|
-
|
|
75
|
-
increment(t.store);
|
|
76
|
-
increment(t.store);
|
|
77
|
-
increment(t.store);
|
|
78
|
-
|
|
79
|
-
const count = t.store.get('count');
|
|
80
|
-
if (count !== 3) {
|
|
81
|
-
throw new Error(`Expected count to be 3, got ${count}`);
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
'mixed operations work correctly': () => {
|
|
86
|
-
const t = createEventTest({ count: 10 });
|
|
87
|
-
|
|
88
|
-
increment(t.store); // 11
|
|
89
|
-
double(t.store); // 22
|
|
90
|
-
decrement(t.store); // 21
|
|
91
|
-
|
|
92
|
-
const count = t.store.get('count');
|
|
93
|
-
if (count !== 21) {
|
|
94
|
-
throw new Error(`Expected count to be 21, got ${count}`);
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
'count can go negative': () => {
|
|
99
|
-
const t = createEventTest({ count: 0 });
|
|
100
|
-
|
|
101
|
-
decrement(t.store);
|
|
102
|
-
decrement(t.store);
|
|
103
|
-
|
|
104
|
-
const count = t.store.get('count');
|
|
105
|
-
if (count !== -2) {
|
|
106
|
-
throw new Error(`Expected count to be -2, got ${count}`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
// Run tests if executed directly
|
|
112
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
113
|
-
runTests(tests);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export default tests;
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* eventTest.js - Event-Sequence Testing for EventState
|
|
3
|
-
*
|
|
4
|
-
* Provides TDD-style testing with type extraction capabilities
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createEventState } from '../runtime/core/eventStateNew.js';
|
|
8
|
-
|
|
9
|
-
export function createEventTest(initialState = {}) {
|
|
10
|
-
const store = createEventState(initialState);
|
|
11
|
-
const eventLog = [];
|
|
12
|
-
const typeAssertions = [];
|
|
13
|
-
|
|
14
|
-
// Spy on all events
|
|
15
|
-
store.subscribe('*', (detail) => {
|
|
16
|
-
const { path, value } = detail;
|
|
17
|
-
eventLog.push({ timestamp: Date.now(), path, value });
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const api = {
|
|
21
|
-
store,
|
|
22
|
-
|
|
23
|
-
// Trigger a state change
|
|
24
|
-
trigger(path, value) {
|
|
25
|
-
store.set(path, value);
|
|
26
|
-
return this;
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
// Assert exact value
|
|
30
|
-
assertPath(path, expected) {
|
|
31
|
-
const actual = store.get(path);
|
|
32
|
-
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
33
|
-
throw new Error(`Expected ${path} to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
34
|
-
}
|
|
35
|
-
return this;
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
// Assert type (for type generation)
|
|
39
|
-
assertType(path, expectedType) {
|
|
40
|
-
const actual = store.get(path);
|
|
41
|
-
const actualType = typeof actual;
|
|
42
|
-
|
|
43
|
-
if (actualType !== expectedType) {
|
|
44
|
-
throw new Error(`Expected ${path} to be type ${expectedType}, got ${actualType}`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Store for type generation
|
|
48
|
-
typeAssertions.push({ path, type: expectedType });
|
|
49
|
-
return this;
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
// Assert array with element shape (for type generation)
|
|
53
|
-
assertArrayOf(path, elementShape) {
|
|
54
|
-
const actual = store.get(path);
|
|
55
|
-
|
|
56
|
-
if (!Array.isArray(actual)) {
|
|
57
|
-
throw new Error(`Expected ${path} to be an array, got ${typeof actual}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Validate first element matches shape (if array not empty)
|
|
61
|
-
if (actual.length > 0) {
|
|
62
|
-
validateShape(actual[0], elementShape, path);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Store for type generation
|
|
66
|
-
typeAssertions.push({ path, type: 'array', elementShape });
|
|
67
|
-
return this;
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
// Assert object shape (for type generation)
|
|
71
|
-
assertShape(path, objectShape) {
|
|
72
|
-
const actual = store.get(path);
|
|
73
|
-
|
|
74
|
-
if (typeof actual !== 'object' || actual === null || Array.isArray(actual)) {
|
|
75
|
-
throw new Error(`Expected ${path} to be an object, got ${typeof actual}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
validateShape(actual, objectShape, path);
|
|
79
|
-
|
|
80
|
-
// Store for type generation
|
|
81
|
-
typeAssertions.push({ path, type: 'object', shape: objectShape });
|
|
82
|
-
return this;
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
// Assert array length
|
|
86
|
-
assertArrayLength(path, expectedLength) {
|
|
87
|
-
const actual = store.get(path);
|
|
88
|
-
|
|
89
|
-
if (!Array.isArray(actual)) {
|
|
90
|
-
throw new Error(`Expected ${path} to be an array`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (actual.length !== expectedLength) {
|
|
94
|
-
throw new Error(`Expected ${path} to have length ${expectedLength}, got ${actual.length}`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return this;
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
// Assert event fired N times
|
|
101
|
-
assertEventFired(path, times) {
|
|
102
|
-
const count = eventLog.filter(e => e.path === path).length;
|
|
103
|
-
if (times !== undefined && count !== times) {
|
|
104
|
-
throw new Error(`Expected ${path} to fire ${times} times, fired ${count}`);
|
|
105
|
-
}
|
|
106
|
-
return this;
|
|
107
|
-
},
|
|
108
|
-
|
|
109
|
-
// Get event log
|
|
110
|
-
getEventLog() {
|
|
111
|
-
return [...eventLog];
|
|
112
|
-
},
|
|
113
|
-
|
|
114
|
-
// Get type assertions (for type generation)
|
|
115
|
-
getTypeAssertions() {
|
|
116
|
-
return [...typeAssertions];
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
return api;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Helper to validate object shape
|
|
124
|
-
function validateShape(actual, shape, path) {
|
|
125
|
-
for (const [key, expectedType] of Object.entries(shape)) {
|
|
126
|
-
if (!(key in actual)) {
|
|
127
|
-
throw new Error(`Expected ${path} to have property ${key}`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const actualValue = actual[key];
|
|
131
|
-
|
|
132
|
-
// Handle nested objects
|
|
133
|
-
if (typeof expectedType === 'object' && !Array.isArray(expectedType)) {
|
|
134
|
-
validateShape(actualValue, expectedType, `${path}.${key}`);
|
|
135
|
-
} else {
|
|
136
|
-
// Primitive type check
|
|
137
|
-
const actualType = typeof actualValue;
|
|
138
|
-
if (actualType !== expectedType) {
|
|
139
|
-
throw new Error(`Expected ${path}.${key} to be type ${expectedType}, got ${actualType}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Simple test runner
|
|
146
|
-
export function test(name, fn) {
|
|
147
|
-
try {
|
|
148
|
-
fn();
|
|
149
|
-
console.log(`ā ${name}`);
|
|
150
|
-
return true;
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.error(`ā ${name}`);
|
|
153
|
-
console.error(` ${error.message}`);
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Run multiple tests
|
|
159
|
-
export function runTests(tests) {
|
|
160
|
-
console.log('\nš§Ŗ Running tests...\n');
|
|
161
|
-
|
|
162
|
-
let passed = 0;
|
|
163
|
-
let failed = 0;
|
|
164
|
-
|
|
165
|
-
for (const [name, fn] of Object.entries(tests)) {
|
|
166
|
-
if (test(name, fn)) {
|
|
167
|
-
passed++;
|
|
168
|
-
} else {
|
|
169
|
-
failed++;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
console.log(`\nš Results: ${passed} passed, ${failed} failed\n`);
|
|
174
|
-
|
|
175
|
-
return { passed, failed };
|
|
176
|
-
}
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* generateTypes.js - Generate TypeScript definitions from test assertions
|
|
4
|
-
*
|
|
5
|
-
* Usage: node tests/generateTypes.js
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createEventTest } from './eventTest.js';
|
|
9
|
-
import todosTests from './todos.test.js';
|
|
10
|
-
import { writeFileSync } from 'fs';
|
|
11
|
-
import { fileURLToPath } from 'url';
|
|
12
|
-
import { dirname, join } from 'path';
|
|
13
|
-
|
|
14
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
-
const __dirname = dirname(__filename);
|
|
16
|
-
|
|
17
|
-
// Collect all type assertions from tests
|
|
18
|
-
const allAssertions = [];
|
|
19
|
-
|
|
20
|
-
console.log('š Extracting types from tests...\n');
|
|
21
|
-
|
|
22
|
-
for (const [name, testFn] of Object.entries(todosTests)) {
|
|
23
|
-
try {
|
|
24
|
-
// Run test to collect assertions
|
|
25
|
-
testFn();
|
|
26
|
-
console.log(`ā ${name}`);
|
|
27
|
-
} catch (error) {
|
|
28
|
-
// Test might fail, but we still want type assertions
|
|
29
|
-
console.log(`ā ${name} (failed but types extracted)`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Simple type generator (minimal version for Node.js)
|
|
34
|
-
class SimpleTypeGenerator {
|
|
35
|
-
constructor() {
|
|
36
|
-
this.pathTypes = new Map();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
parseAssertions(assertions) {
|
|
40
|
-
for (const assertion of assertions) {
|
|
41
|
-
const { path, type, elementShape, shape } = assertion;
|
|
42
|
-
|
|
43
|
-
if (type === 'array' && elementShape) {
|
|
44
|
-
this.pathTypes.set(path, {
|
|
45
|
-
type: 'array',
|
|
46
|
-
element: elementShape
|
|
47
|
-
});
|
|
48
|
-
} else if (type === 'object' && shape) {
|
|
49
|
-
this.pathTypes.set(path, {
|
|
50
|
-
type: 'object',
|
|
51
|
-
shape: shape
|
|
52
|
-
});
|
|
53
|
-
} else if (type) {
|
|
54
|
-
this.pathTypes.set(path, { type });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
buildTree() {
|
|
60
|
-
const tree = {};
|
|
61
|
-
|
|
62
|
-
for (const [path, typeInfo] of this.pathTypes) {
|
|
63
|
-
const parts = path.split('.');
|
|
64
|
-
let current = tree;
|
|
65
|
-
|
|
66
|
-
for (let i = 0; i < parts.length; i++) {
|
|
67
|
-
const part = parts[i];
|
|
68
|
-
|
|
69
|
-
if (i === parts.length - 1) {
|
|
70
|
-
current[part] = { __typeInfo: typeInfo };
|
|
71
|
-
} else {
|
|
72
|
-
if (!current[part]) {
|
|
73
|
-
current[part] = {};
|
|
74
|
-
}
|
|
75
|
-
current = current[part];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return tree;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
formatType(typeInfo) {
|
|
84
|
-
if (typeof typeInfo === 'string') return typeInfo;
|
|
85
|
-
|
|
86
|
-
if (typeInfo.type === 'array' && typeInfo.element) {
|
|
87
|
-
const elementType = this.formatShape(typeInfo.element);
|
|
88
|
-
return `Array<${elementType}>`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (typeInfo.type === 'object' && typeInfo.shape) {
|
|
92
|
-
return this.formatShape(typeInfo.shape);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return typeInfo.type || 'unknown';
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
formatShape(shape) {
|
|
99
|
-
if (typeof shape === 'string') return shape;
|
|
100
|
-
|
|
101
|
-
const props = Object.entries(shape)
|
|
102
|
-
.map(([k, v]) => {
|
|
103
|
-
const type = typeof v === 'string' ? v : this.formatShape(v);
|
|
104
|
-
return `${k}: ${type}`;
|
|
105
|
-
})
|
|
106
|
-
.join('; ');
|
|
107
|
-
|
|
108
|
-
return `{ ${props} }`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
generateInterface(obj, indent = 0) {
|
|
112
|
-
const lines = [];
|
|
113
|
-
const spaces = ' '.repeat(indent);
|
|
114
|
-
|
|
115
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
116
|
-
if (value.__typeInfo) {
|
|
117
|
-
const type = this.formatType(value.__typeInfo);
|
|
118
|
-
lines.push(`${spaces}${key}: ${type};`);
|
|
119
|
-
} else {
|
|
120
|
-
lines.push(`${spaces}${key}: {`);
|
|
121
|
-
lines.push(this.generateInterface(value, indent + 1));
|
|
122
|
-
lines.push(`${spaces}};`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return lines.join('\n');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
generateDTS() {
|
|
130
|
-
const lines = [
|
|
131
|
-
'// Auto-generated from test assertions',
|
|
132
|
-
'// DO NOT EDIT - regenerate by running: node tests/generateTypes.js',
|
|
133
|
-
'',
|
|
134
|
-
'export interface StoreState {',
|
|
135
|
-
];
|
|
136
|
-
|
|
137
|
-
const tree = this.buildTree();
|
|
138
|
-
lines.push(this.generateInterface(tree, 1));
|
|
139
|
-
lines.push('}');
|
|
140
|
-
lines.push('');
|
|
141
|
-
lines.push('export default StoreState;');
|
|
142
|
-
lines.push('');
|
|
143
|
-
|
|
144
|
-
return lines.join('\n');
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Note: We need to actually collect assertions from test execution
|
|
149
|
-
// For now, manually define expected types based on test assertions
|
|
150
|
-
const manualAssertions = [
|
|
151
|
-
{
|
|
152
|
-
path: 'count',
|
|
153
|
-
type: 'number'
|
|
154
|
-
}
|
|
155
|
-
];
|
|
156
|
-
|
|
157
|
-
const generator = new SimpleTypeGenerator();
|
|
158
|
-
generator.parseAssertions(manualAssertions);
|
|
159
|
-
|
|
160
|
-
const dts = generator.generateDTS();
|
|
161
|
-
|
|
162
|
-
// Write to file
|
|
163
|
-
const outputPath = join(__dirname, '..', 'store.d.ts');
|
|
164
|
-
writeFileSync(outputPath, dts, 'utf-8');
|
|
165
|
-
|
|
166
|
-
console.log(`\nā
Generated ${outputPath}`);
|
|
167
|
-
console.log('\nš Type definitions:\n');
|
|
168
|
-
console.log(dts);
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* run.js - Test runner for Node.js
|
|
4
|
-
*
|
|
5
|
-
* Usage: node tests/run.js
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { runTests } from './eventTest.js';
|
|
9
|
-
import counterTests from './counter.test.js';
|
|
10
|
-
|
|
11
|
-
// Combine all test suites
|
|
12
|
-
const allTests = {
|
|
13
|
-
...counterTests
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
// Run tests
|
|
17
|
-
const results = runTests(allTests);
|
|
18
|
-
|
|
19
|
-
// Exit with appropriate code
|
|
20
|
-
process.exit(results.failed > 0 ? 1 : 0);
|