@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.
Files changed (138) hide show
  1. package/package.json +5 -5
  2. package/queryClient.js +55 -0
  3. package/examples/001-counter/README.md +0 -44
  4. package/examples/001-counter/index.html +0 -33
  5. package/examples/002-counter-improved/README.md +0 -44
  6. package/examples/002-counter-improved/index.html +0 -47
  7. package/examples/003-input-reactive/README.md +0 -44
  8. package/examples/003-input-reactive/index.html +0 -33
  9. package/examples/004-computed-state/README.md +0 -45
  10. package/examples/004-computed-state/index.html +0 -65
  11. package/examples/005-conditional-rendering/README.md +0 -42
  12. package/examples/005-conditional-rendering/index.html +0 -39
  13. package/examples/006-list-rendering/README.md +0 -49
  14. package/examples/006-list-rendering/index.html +0 -63
  15. package/examples/007-form-validation/README.md +0 -52
  16. package/examples/007-form-validation/index.html +0 -102
  17. package/examples/008-undo-redo/README.md +0 -70
  18. package/examples/008-undo-redo/index.html +0 -108
  19. package/examples/009-localStorage-side-effects/README.md +0 -72
  20. package/examples/009-localStorage-side-effects/index.html +0 -57
  21. package/examples/010-decoupled-components/README.md +0 -74
  22. package/examples/010-decoupled-components/index.html +0 -93
  23. package/examples/011-async-patterns/README.md +0 -98
  24. package/examples/011-async-patterns/index.html +0 -132
  25. package/examples/028-counter-improved-eventTest/LICENSE +0 -55
  26. package/examples/028-counter-improved-eventTest/README.md +0 -131
  27. package/examples/028-counter-improved-eventTest/app/store.js +0 -9
  28. package/examples/028-counter-improved-eventTest/index.html +0 -49
  29. package/examples/028-counter-improved-eventTest/runtime/core/behaviors.runtime.js +0 -282
  30. package/examples/028-counter-improved-eventTest/runtime/core/eventState.js +0 -100
  31. package/examples/028-counter-improved-eventTest/runtime/core/eventStateNew.js +0 -149
  32. package/examples/028-counter-improved-eventTest/runtime/core/helpers.js +0 -212
  33. package/examples/028-counter-improved-eventTest/runtime/core/router.js +0 -271
  34. package/examples/028-counter-improved-eventTest/store.d.ts +0 -8
  35. package/examples/028-counter-improved-eventTest/style.css +0 -170
  36. package/examples/028-counter-improved-eventTest/tests/README.md +0 -208
  37. package/examples/028-counter-improved-eventTest/tests/counter.test.js +0 -116
  38. package/examples/028-counter-improved-eventTest/tests/eventTest.js +0 -176
  39. package/examples/028-counter-improved-eventTest/tests/generateTypes.js +0 -168
  40. package/examples/028-counter-improved-eventTest/tests/run.js +0 -20
  41. package/examples/030-todo-app-with-eventTest/LICENSE +0 -55
  42. package/examples/030-todo-app-with-eventTest/README.md +0 -121
  43. package/examples/030-todo-app-with-eventTest/app/router.js +0 -25
  44. package/examples/030-todo-app-with-eventTest/app/store.js +0 -16
  45. package/examples/030-todo-app-with-eventTest/app/views/home.js +0 -11
  46. package/examples/030-todo-app-with-eventTest/app/views/todoDemo.js +0 -88
  47. package/examples/030-todo-app-with-eventTest/index.html +0 -65
  48. package/examples/030-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
  49. package/examples/030-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
  50. package/examples/030-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
  51. package/examples/030-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
  52. package/examples/030-todo-app-with-eventTest/runtime/core/router.js +0 -271
  53. package/examples/030-todo-app-with-eventTest/store.d.ts +0 -18
  54. package/examples/030-todo-app-with-eventTest/style.css +0 -170
  55. package/examples/030-todo-app-with-eventTest/tests/README.md +0 -208
  56. package/examples/030-todo-app-with-eventTest/tests/eventTest.js +0 -176
  57. package/examples/030-todo-app-with-eventTest/tests/generateTypes.js +0 -189
  58. package/examples/030-todo-app-with-eventTest/tests/run.js +0 -20
  59. package/examples/030-todo-app-with-eventTest/tests/todos.test.js +0 -167
  60. package/examples/031-todo-app-with-eventTest/LICENSE +0 -55
  61. package/examples/031-todo-app-with-eventTest/README.md +0 -54
  62. package/examples/031-todo-app-with-eventTest/TUTORIAL.md +0 -390
  63. package/examples/031-todo-app-with-eventTest/WHY_EVENTSTATE.md +0 -777
  64. package/examples/031-todo-app-with-eventTest/app/bridges.js +0 -113
  65. package/examples/031-todo-app-with-eventTest/app/router.js +0 -26
  66. package/examples/031-todo-app-with-eventTest/app/store.js +0 -15
  67. package/examples/031-todo-app-with-eventTest/app/views/home.js +0 -46
  68. package/examples/031-todo-app-with-eventTest/app/views/todoDemo.js +0 -69
  69. package/examples/031-todo-app-with-eventTest/devtools/dock.js +0 -41
  70. package/examples/031-todo-app-with-eventTest/devtools/stateTracker.dock.js +0 -10
  71. package/examples/031-todo-app-with-eventTest/devtools/stateTracker.js +0 -246
  72. package/examples/031-todo-app-with-eventTest/devtools/telemetry.js +0 -104
  73. package/examples/031-todo-app-with-eventTest/devtools/typeGenerator.js +0 -339
  74. package/examples/031-todo-app-with-eventTest/index.html +0 -103
  75. package/examples/031-todo-app-with-eventTest/package-lock.json +0 -2184
  76. package/examples/031-todo-app-with-eventTest/package.json +0 -24
  77. package/examples/031-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
  78. package/examples/031-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
  79. package/examples/031-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
  80. package/examples/031-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
  81. package/examples/031-todo-app-with-eventTest/runtime/core/router.js +0 -271
  82. package/examples/031-todo-app-with-eventTest/runtime/extensions/boundary.js +0 -36
  83. package/examples/031-todo-app-with-eventTest/runtime/extensions/converge.js +0 -63
  84. package/examples/031-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +0 -210
  85. package/examples/031-todo-app-with-eventTest/runtime/extensions/hydrate.js +0 -157
  86. package/examples/031-todo-app-with-eventTest/runtime/extensions/queryBinding.js +0 -69
  87. package/examples/031-todo-app-with-eventTest/runtime/forms/computed.js +0 -78
  88. package/examples/031-todo-app-with-eventTest/runtime/forms/meta.js +0 -51
  89. package/examples/031-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +0 -28
  90. package/examples/031-todo-app-with-eventTest/runtime/forms/validators.js +0 -55
  91. package/examples/031-todo-app-with-eventTest/store.d.ts +0 -23
  92. package/examples/031-todo-app-with-eventTest/style.css +0 -170
  93. package/examples/031-todo-app-with-eventTest/tests/README.md +0 -208
  94. package/examples/031-todo-app-with-eventTest/tests/eventTest.js +0 -176
  95. package/examples/031-todo-app-with-eventTest/tests/generateTypes.js +0 -191
  96. package/examples/031-todo-app-with-eventTest/tests/run.js +0 -20
  97. package/examples/031-todo-app-with-eventTest/tests/todos.test.js +0 -192
  98. package/examples/032-todo-app-with-eventTest/LICENSE +0 -55
  99. package/examples/032-todo-app-with-eventTest/README.md +0 -54
  100. package/examples/032-todo-app-with-eventTest/TUTORIAL.md +0 -390
  101. package/examples/032-todo-app-with-eventTest/WHY_EVENTSTATE.md +0 -777
  102. package/examples/032-todo-app-with-eventTest/app/actions/index.js +0 -153
  103. package/examples/032-todo-app-with-eventTest/app/bridges.js +0 -113
  104. package/examples/032-todo-app-with-eventTest/app/router.js +0 -26
  105. package/examples/032-todo-app-with-eventTest/app/store.js +0 -15
  106. package/examples/032-todo-app-with-eventTest/app/views/home.js +0 -46
  107. package/examples/032-todo-app-with-eventTest/app/views/todoDemo.js +0 -69
  108. package/examples/032-todo-app-with-eventTest/devtools/dock.js +0 -41
  109. package/examples/032-todo-app-with-eventTest/devtools/stateTracker.dock.js +0 -10
  110. package/examples/032-todo-app-with-eventTest/devtools/stateTracker.js +0 -246
  111. package/examples/032-todo-app-with-eventTest/devtools/telemetry.js +0 -104
  112. package/examples/032-todo-app-with-eventTest/devtools/typeGenerator.js +0 -339
  113. package/examples/032-todo-app-with-eventTest/index.html +0 -87
  114. package/examples/032-todo-app-with-eventTest/package-lock.json +0 -2184
  115. package/examples/032-todo-app-with-eventTest/package.json +0 -24
  116. package/examples/032-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +0 -282
  117. package/examples/032-todo-app-with-eventTest/runtime/core/eventState.js +0 -100
  118. package/examples/032-todo-app-with-eventTest/runtime/core/eventStateNew.js +0 -149
  119. package/examples/032-todo-app-with-eventTest/runtime/core/helpers.js +0 -212
  120. package/examples/032-todo-app-with-eventTest/runtime/core/router.js +0 -271
  121. package/examples/032-todo-app-with-eventTest/runtime/extensions/boundary.js +0 -36
  122. package/examples/032-todo-app-with-eventTest/runtime/extensions/converge.js +0 -63
  123. package/examples/032-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +0 -210
  124. package/examples/032-todo-app-with-eventTest/runtime/extensions/hydrate.js +0 -157
  125. package/examples/032-todo-app-with-eventTest/runtime/extensions/queryBinding.js +0 -69
  126. package/examples/032-todo-app-with-eventTest/runtime/forms/computed.js +0 -78
  127. package/examples/032-todo-app-with-eventTest/runtime/forms/meta.js +0 -51
  128. package/examples/032-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +0 -28
  129. package/examples/032-todo-app-with-eventTest/runtime/forms/validators.js +0 -55
  130. package/examples/032-todo-app-with-eventTest/store.d.ts +0 -23
  131. package/examples/032-todo-app-with-eventTest/style.css +0 -170
  132. package/examples/032-todo-app-with-eventTest/tests/README.md +0 -208
  133. package/examples/032-todo-app-with-eventTest/tests/eventTest.js +0 -176
  134. package/examples/032-todo-app-with-eventTest/tests/generateTypes.js +0 -191
  135. package/examples/032-todo-app-with-eventTest/tests/run.js +0 -20
  136. package/examples/032-todo-app-with-eventTest/tests/todos.test.js +0 -192
  137. package/playground/exercise001.html +0 -38
  138. package/playground/exercise002.html +0 -49
@@ -1,170 +0,0 @@
1
- body {
2
- font: 14px/1.4 system-ui, sans-serif;
3
- margin: 0;
4
- }
5
-
6
- /* Hide focus outline on route container (set programmatically for a11y) */
7
- [data-route-root]:focus {
8
- outline: none;
9
- }
10
-
11
- nav {
12
- padding: 10px 14px;
13
- border-bottom: 1px solid #eee;
14
- position: sticky;
15
- top: 0;
16
- background: #fff;
17
- z-index: 10;
18
- }
19
-
20
- nav a {
21
- margin-right: 12px;
22
- text-decoration: none;
23
- color: #0366d6;
24
- }
25
-
26
- nav a.active {
27
- font-weight: 600;
28
- }
29
-
30
- main {
31
- padding: 16px;
32
- }
33
-
34
- .loading-badge {
35
- margin-left: 8px;
36
- font-size: 12px;
37
- color: #666;
38
- display: none;
39
- }
40
-
41
- html[data-transitioning="on"] .loading-badge {
42
- display: inline;
43
- }
44
-
45
- /* 404 view demo: tint the top bar only on notfound */
46
- html[data-view="notfound"] nav {
47
- background: #fff7f7;
48
- border-bottom-color: #f5c2c2;
49
- }
50
-
51
- html[data-view="notfound"] nav a {
52
- color: #b91c1c;
53
- }
54
-
55
- html[data-view="notfound"] .loading-badge {
56
- color: #b91c1c;
57
- }
58
-
59
- /* Tabs styling (used by views/demo.js) */
60
- .tabs {
61
- margin-top: 12px;
62
- }
63
-
64
- .tab-bar {
65
- display: flex;
66
- gap: 8px;
67
- border-bottom: 1px solid #eee;
68
- padding-bottom: 6px;
69
- }
70
-
71
- .tab {
72
- appearance: none;
73
- border: 1px solid #ddd;
74
- background: #fafafa;
75
- border-radius: 6px;
76
- padding: 6px 10px;
77
- cursor: pointer;
78
- }
79
-
80
- .tab:hover {
81
- background: #f3f4f6;
82
- }
83
-
84
- .tab.active {
85
- background: #e5efff;
86
- border-color: #bcd1ff;
87
- }
88
-
89
- .tab-panels {
90
- padding-top: 10px;
91
- }
92
-
93
- .tab-panel {
94
- display: none;
95
- }
96
-
97
- .tab-panel[data-active] {
98
- display: block;
99
- }
100
-
101
- :root {
102
- --bg: #ffffff;
103
- --fg: #111111;
104
- --muted: #555;
105
- --card: #f6f6f6;
106
- --border: #e5e5e5;
107
- --btn-bg: #f8f8f8;
108
- --btn-fg: #111;
109
- --btn-border: #ddd;
110
- --input-bg: #fff;
111
- --input-fg: #222;
112
- --input-border: #d6d6d6;
113
- }
114
-
115
- body[data-theme='dark'] {
116
- --bg: #0f1115;
117
- --fg: #cfd3dc;
118
- --muted: #9aa3b2;
119
- --card: #151923;
120
- --border: #242a36;
121
- --btn-bg: #1a2030;
122
- --btn-fg: #cfd3dc;
123
- --btn-border: #2b3242;
124
- --input-bg: #1b2130;
125
- --input-fg: #cfd3dc;
126
- --input-border: #2b3242;
127
- }
128
-
129
- body {
130
- background: var(--bg);
131
- color: var(--fg);
132
- }
133
-
134
- a {
135
- color: inherit;
136
- }
137
-
138
- nav {
139
- background: var(--card);
140
- border-bottom: 1px solid var(--border);
141
- }
142
-
143
- .btn {
144
- appearance: none;
145
- border: 1px solid var(--btn-border);
146
- background: var(--btn-bg);
147
- color: var(--btn-fg);
148
- border-radius: 6px;
149
- padding: 6px 10px;
150
- cursor: pointer;
151
- }
152
-
153
- input,
154
- select,
155
- textarea {
156
- background: var(--input-bg);
157
- color: var(--input-fg);
158
- border: 1px solid var(--input-border);
159
- border-radius: 6px;
160
- padding: 6px 8px;
161
- }
162
-
163
- input::placeholder,
164
- textarea::placeholder {
165
- color: color-mix(in srgb, var(--input-fg) 55%, transparent);
166
- }
167
-
168
- .btn:hover {
169
- filter: brightness(1.05);
170
- }
@@ -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,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
- }