@everystate/test 1.0.0 → 1.0.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/eventTest.js +178 -0
- package/index.d.ts +44 -0
- package/index.js +3 -3
- package/package.json +5 -5
package/eventTest.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @everystate/test: Event-Sequence Testing for EveryState
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2026 Ajdin Imsirovic. MIT License.
|
|
5
|
+
*
|
|
6
|
+
* Provides TDD-style testing with type extraction capabilities.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createEveryState } from '@everystate/core';
|
|
10
|
+
|
|
11
|
+
export function createEventTest(initialState = {}) {
|
|
12
|
+
const store = createEveryState(initialState);
|
|
13
|
+
const eventLog = [];
|
|
14
|
+
const typeAssertions = [];
|
|
15
|
+
|
|
16
|
+
// Spy on all events
|
|
17
|
+
store.subscribe('*', (detail) => {
|
|
18
|
+
const { path, value } = detail;
|
|
19
|
+
eventLog.push({ timestamp: Date.now(), path, value });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const api = {
|
|
23
|
+
store,
|
|
24
|
+
|
|
25
|
+
// Trigger a state change
|
|
26
|
+
trigger(path, value) {
|
|
27
|
+
store.set(path, value);
|
|
28
|
+
return this;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// Assert exact value
|
|
32
|
+
assertPath(path, expected) {
|
|
33
|
+
const actual = store.get(path);
|
|
34
|
+
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
35
|
+
throw new Error(`Expected ${path} to be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
|
36
|
+
}
|
|
37
|
+
return this;
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// Assert type (for type generation)
|
|
41
|
+
assertType(path, expectedType) {
|
|
42
|
+
const actual = store.get(path);
|
|
43
|
+
const actualType = typeof actual;
|
|
44
|
+
|
|
45
|
+
if (actualType !== expectedType) {
|
|
46
|
+
throw new Error(`Expected ${path} to be type ${expectedType}, got ${actualType}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Store for type generation
|
|
50
|
+
typeAssertions.push({ path, type: expectedType });
|
|
51
|
+
return this;
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// Assert array with element shape (for type generation)
|
|
55
|
+
assertArrayOf(path, elementShape) {
|
|
56
|
+
const actual = store.get(path);
|
|
57
|
+
|
|
58
|
+
if (!Array.isArray(actual)) {
|
|
59
|
+
throw new Error(`Expected ${path} to be an array, got ${typeof actual}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Validate first element matches shape (if array not empty)
|
|
63
|
+
if (actual.length > 0) {
|
|
64
|
+
validateShape(actual[0], elementShape, path);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Store for type generation
|
|
68
|
+
typeAssertions.push({ path, type: 'array', elementShape });
|
|
69
|
+
return this;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// Assert object shape (for type generation)
|
|
73
|
+
assertShape(path, objectShape) {
|
|
74
|
+
const actual = store.get(path);
|
|
75
|
+
|
|
76
|
+
if (typeof actual !== 'object' || actual === null || Array.isArray(actual)) {
|
|
77
|
+
throw new Error(`Expected ${path} to be an object, got ${typeof actual}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
validateShape(actual, objectShape, path);
|
|
81
|
+
|
|
82
|
+
// Store for type generation
|
|
83
|
+
typeAssertions.push({ path, type: 'object', shape: objectShape });
|
|
84
|
+
return this;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// Assert array length
|
|
88
|
+
assertArrayLength(path, expectedLength) {
|
|
89
|
+
const actual = store.get(path);
|
|
90
|
+
|
|
91
|
+
if (!Array.isArray(actual)) {
|
|
92
|
+
throw new Error(`Expected ${path} to be an array`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (actual.length !== expectedLength) {
|
|
96
|
+
throw new Error(`Expected ${path} to have length ${expectedLength}, got ${actual.length}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return this;
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// Assert event fired N times
|
|
103
|
+
assertEventFired(path, times) {
|
|
104
|
+
const count = eventLog.filter(e => e.path === path).length;
|
|
105
|
+
if (times !== undefined && count !== times) {
|
|
106
|
+
throw new Error(`Expected ${path} to fire ${times} times, fired ${count}`);
|
|
107
|
+
}
|
|
108
|
+
return this;
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
// Get event log
|
|
112
|
+
getEventLog() {
|
|
113
|
+
return [...eventLog];
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
// Get type assertions (for type generation)
|
|
117
|
+
getTypeAssertions() {
|
|
118
|
+
return [...typeAssertions];
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return api;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Helper to validate object shape
|
|
126
|
+
function validateShape(actual, shape, path) {
|
|
127
|
+
for (const [key, expectedType] of Object.entries(shape)) {
|
|
128
|
+
if (!(key in actual)) {
|
|
129
|
+
throw new Error(`Expected ${path} to have property ${key}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const actualValue = actual[key];
|
|
133
|
+
|
|
134
|
+
// Handle nested objects
|
|
135
|
+
if (typeof expectedType === 'object' && !Array.isArray(expectedType)) {
|
|
136
|
+
validateShape(actualValue, expectedType, `${path}.${key}`);
|
|
137
|
+
} else {
|
|
138
|
+
// Primitive type check
|
|
139
|
+
const actualType = typeof actualValue;
|
|
140
|
+
if (actualType !== expectedType) {
|
|
141
|
+
throw new Error(`Expected ${path}.${key} to be type ${expectedType}, got ${actualType}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Simple test runner
|
|
148
|
+
export function test(name, fn) {
|
|
149
|
+
try {
|
|
150
|
+
fn();
|
|
151
|
+
console.log(`\u2713 ${name}`);
|
|
152
|
+
return true;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(`\u2717 ${name}`);
|
|
155
|
+
console.error(` ${error.message}`);
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Run multiple tests
|
|
161
|
+
export function runTests(tests) {
|
|
162
|
+
console.log('\n\uD83E\uDDEA Running tests...\n');
|
|
163
|
+
|
|
164
|
+
let passed = 0;
|
|
165
|
+
let failed = 0;
|
|
166
|
+
|
|
167
|
+
for (const [name, fn] of Object.entries(tests)) {
|
|
168
|
+
if (test(name, fn)) {
|
|
169
|
+
passed++;
|
|
170
|
+
} else {
|
|
171
|
+
failed++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log(`\n\uD83D\uDCCA Results: ${passed} passed, ${failed} failed\n`);
|
|
176
|
+
|
|
177
|
+
return { passed, failed };
|
|
178
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @everystate/test
|
|
3
|
+
*
|
|
4
|
+
* Event-sequence testing for EveryState.
|
|
5
|
+
* TDD-style testing with type extraction capabilities.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { EveryStateStore } from '@everystate/core';
|
|
9
|
+
|
|
10
|
+
export interface TestCase {
|
|
11
|
+
name: string;
|
|
12
|
+
fn: (store: EveryStateStore) => void | Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TestResult {
|
|
16
|
+
name: string;
|
|
17
|
+
passed: boolean;
|
|
18
|
+
error?: Error;
|
|
19
|
+
duration: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TestSuite {
|
|
23
|
+
/** Add a test case */
|
|
24
|
+
add(name: string, fn: (store: EveryStateStore) => void | Promise<void>): void;
|
|
25
|
+
/** Run all registered tests */
|
|
26
|
+
run(): Promise<TestResult[]>;
|
|
27
|
+
/** Get results summary */
|
|
28
|
+
results(): TestResult[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a test suite for event-sequence testing.
|
|
33
|
+
*/
|
|
34
|
+
export function createEventTest(store: EveryStateStore): TestSuite;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Define a single test case (shorthand).
|
|
38
|
+
*/
|
|
39
|
+
export function test(name: string, fn: (store: EveryStateStore) => void | Promise<void>): TestCase;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Run an array of test cases against a store.
|
|
43
|
+
*/
|
|
44
|
+
export function runTests(store: EveryStateStore, tests: TestCase[]): Promise<TestResult[]>;
|
package/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @everystate/test
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Event-sequence testing for EveryState.
|
|
5
|
+
* TDD-style testing with type extraction capabilities.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export
|
|
8
|
+
export { createEventTest, test, runTests } from './eventTest.js';
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everystate/test",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "EveryState Test: Testing library with TDD-style assertions and event-sequence tracking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
7
8
|
"keywords": [
|
|
8
9
|
"everystate",
|
|
9
10
|
"testing",
|
|
@@ -25,10 +26,9 @@
|
|
|
25
26
|
},
|
|
26
27
|
"files": [
|
|
27
28
|
"index.js",
|
|
29
|
+
"eventTest.js",
|
|
30
|
+
"index.d.ts",
|
|
28
31
|
"README.md",
|
|
29
32
|
"LICENSE.md"
|
|
30
|
-
]
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"@uistate/event-test": "^1.0.1"
|
|
33
|
-
}
|
|
33
|
+
]
|
|
34
34
|
}
|