vestjs-runtime 1.7.0 → 2.0.2
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/IsolateSerializer/package.json +12 -8
- package/README.md +3 -1
- package/dist/IsolateKeys-B21aPuBk.mjs +23 -0
- package/dist/IsolateKeys-B21aPuBk.mjs.map +1 -0
- package/dist/IsolateKeys-CCvALpZC.cjs +35 -0
- package/dist/IsolateKeys-CCvALpZC.cjs.map +1 -0
- package/dist/IsolateSerializer-B1hE3gmT.mjs +1004 -0
- package/dist/IsolateSerializer-B1hE3gmT.mjs.map +1 -0
- package/dist/IsolateSerializer-pbEf5gB2.cjs +1121 -0
- package/dist/IsolateSerializer-pbEf5gB2.cjs.map +1 -0
- package/dist/chunk-CLMFDpHK.mjs +18 -0
- package/dist/exports/IsolateSerializer.cjs +4 -0
- package/dist/exports/IsolateSerializer.mjs +4 -0
- package/dist/exports/test-utils.cjs +21 -0
- package/dist/exports/test-utils.cjs.map +1 -0
- package/dist/exports/test-utils.mjs +21 -0
- package/dist/exports/test-utils.mjs.map +1 -0
- package/dist/vestjs-runtime.cjs +153 -0
- package/dist/vestjs-runtime.cjs.map +1 -0
- package/dist/vestjs-runtime.mjs +117 -0
- package/dist/vestjs-runtime.mjs.map +1 -0
- package/docs/IsolateRegistry.docs.md +146 -0
- package/docs/Isolates.md +97 -0
- package/package.json +43 -88
- package/src/Bus.ts +46 -0
- package/src/Isolate/Isolate.ts +163 -0
- package/src/Isolate/IsolateFocused.ts +93 -0
- package/src/Isolate/IsolateIndexer.ts +42 -0
- package/src/Isolate/IsolateInspector.ts +93 -0
- package/src/Isolate/IsolateKeys.ts +18 -0
- package/src/Isolate/IsolateMutator.ts +165 -0
- package/src/Isolate/IsolateRegistry.ts +176 -0
- package/src/Isolate/IsolateReorderable.ts +11 -0
- package/src/Isolate/IsolateSelectors.ts +25 -0
- package/src/Isolate/IsolateStateMachine.ts +30 -0
- package/src/Isolate/IsolateStatus.ts +8 -0
- package/src/Isolate/IsolateTransient.ts +27 -0
- package/src/Isolate/IsolateTypes.ts +33 -0
- package/src/Isolate/__tests__/Isolate.test.ts +123 -0
- package/src/Isolate/__tests__/IsolateFocused.test.ts +199 -0
- package/src/Isolate/__tests__/IsolateInspector.test.ts +136 -0
- package/src/Isolate/__tests__/IsolateMutator.test.ts +164 -0
- package/src/Isolate/__tests__/IsolatePropagation.test.ts +170 -0
- package/src/Isolate/__tests__/IsolateReorderable.test.ts +111 -0
- package/src/Isolate/__tests__/IsolateSelectors.test.ts +72 -0
- package/src/Isolate/__tests__/IsolateStatus.test.ts +44 -0
- package/src/Isolate/__tests__/IsolateTransient.test.ts +58 -0
- package/src/Isolate/__tests__/__snapshots__/asyncIsolate.test.ts.snap +71 -0
- package/src/Isolate/__tests__/asyncIsolate.test.ts +85 -0
- package/src/IsolateWalker.ts +359 -0
- package/src/Orchestrator/RuntimeStates.ts +4 -0
- package/src/Reconciler.ts +178 -0
- package/src/RuntimeEvents.ts +9 -0
- package/src/VestRuntime.ts +421 -0
- package/src/__tests__/Bus.test.ts +57 -0
- package/src/__tests__/IsolateWalker.iterative.test.ts +77 -0
- package/src/__tests__/IsolateWalker.test.ts +418 -0
- package/src/__tests__/Reconciler.test.ts +193 -0
- package/src/__tests__/Reconciler.transient.test.ts +166 -0
- package/src/__tests__/VestRuntime.test.ts +212 -0
- package/src/__tests__/VestRuntimeStateMachine.test.ts +36 -0
- package/src/__tests__/vestjs-runtime.test.ts +19 -0
- package/src/errors/ErrorStrings.ts +6 -0
- package/src/exports/IsolateSerializer.ts +131 -0
- package/src/exports/__tests__/IsolateSerializer.test.ts +334 -0
- package/src/exports/__tests__/IsolateSerializer.transient.test.ts +101 -0
- package/src/exports/__tests__/__snapshots__/IsolateSerializer.test.ts.snap +5 -0
- package/src/exports/test-utils.ts +17 -0
- package/src/vestjs-runtime.ts +28 -0
- package/test-utils/package.json +12 -8
- package/types/Isolate-DChR7h5K.d.mts +58 -0
- package/types/Isolate-DChR7h5K.d.mts.map +1 -0
- package/types/Isolate-HYIh82M8.d.cts +58 -0
- package/types/Isolate-HYIh82M8.d.cts.map +1 -0
- package/types/IsolateSerializer-BCg01Px5.d.mts +13 -0
- package/types/IsolateSerializer-BCg01Px5.d.mts.map +1 -0
- package/types/IsolateSerializer-CQpP6A4m.d.cts +13 -0
- package/types/IsolateSerializer-CQpP6A4m.d.cts.map +1 -0
- package/types/exports/IsolateSerializer.d.cts +3 -0
- package/types/exports/IsolateSerializer.d.mts +3 -0
- package/types/exports/test-utils.d.cts +7 -0
- package/types/exports/test-utils.d.cts.map +1 -0
- package/types/exports/test-utils.d.mts +7 -0
- package/types/exports/test-utils.d.mts.map +1 -0
- package/types/vestjs-runtime.d.cts +372 -0
- package/types/vestjs-runtime.d.cts.map +1 -0
- package/types/vestjs-runtime.d.mts +370 -0
- package/types/vestjs-runtime.d.mts.map +1 -0
- package/types/vestjs-runtime.d.ts +351 -257
- package/vitest.config.ts +9 -17
- package/dist/cjs/IsolateSerializer.development.js +0 -135
- package/dist/cjs/IsolateSerializer.development.js.map +0 -1
- package/dist/cjs/IsolateSerializer.js +0 -6
- package/dist/cjs/IsolateSerializer.production.js +0 -2
- package/dist/cjs/IsolateSerializer.production.js.map +0 -1
- package/dist/cjs/package.json +0 -1
- package/dist/cjs/test-utils.development.js +0 -61
- package/dist/cjs/test-utils.development.js.map +0 -1
- package/dist/cjs/test-utils.js +0 -6
- package/dist/cjs/test-utils.production.js +0 -2
- package/dist/cjs/test-utils.production.js.map +0 -1
- package/dist/cjs/vestjs-runtime.development.js +0 -686
- package/dist/cjs/vestjs-runtime.development.js.map +0 -1
- package/dist/cjs/vestjs-runtime.js +0 -6
- package/dist/cjs/vestjs-runtime.production.js +0 -2
- package/dist/cjs/vestjs-runtime.production.js.map +0 -1
- package/dist/es/IsolateSerializer.development.js +0 -133
- package/dist/es/IsolateSerializer.development.js.map +0 -1
- package/dist/es/IsolateSerializer.production.js +0 -2
- package/dist/es/IsolateSerializer.production.js.map +0 -1
- package/dist/es/package.json +0 -1
- package/dist/es/test-utils.development.js +0 -59
- package/dist/es/test-utils.development.js.map +0 -1
- package/dist/es/test-utils.production.js +0 -2
- package/dist/es/test-utils.production.js.map +0 -1
- package/dist/es/vestjs-runtime.development.js +0 -675
- package/dist/es/vestjs-runtime.development.js.map +0 -1
- package/dist/es/vestjs-runtime.production.js +0 -2
- package/dist/es/vestjs-runtime.production.js.map +0 -1
- package/dist/umd/IsolateSerializer.development.js +0 -138
- package/dist/umd/IsolateSerializer.development.js.map +0 -1
- package/dist/umd/IsolateSerializer.production.js +0 -2
- package/dist/umd/IsolateSerializer.production.js.map +0 -1
- package/dist/umd/test-utils.development.js +0 -67
- package/dist/umd/test-utils.development.js.map +0 -1
- package/dist/umd/test-utils.production.js +0 -2
- package/dist/umd/test-utils.production.js.map +0 -1
- package/dist/umd/vestjs-runtime.development.js +0 -688
- package/dist/umd/vestjs-runtime.development.js.map +0 -1
- package/dist/umd/vestjs-runtime.production.js +0 -2
- package/dist/umd/vestjs-runtime.production.js.map +0 -1
- package/types/IsolateSerializer.d.ts +0 -42
- package/types/IsolateSerializer.d.ts.map +0 -1
- package/types/test-utils.d.ts +0 -37
- package/types/test-utils.d.ts.map +0 -1
- package/types/vestjs-runtime.d.ts.map +0 -1
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { createCascade } from 'context';
|
|
2
|
+
import { RuntimeEvents } from './RuntimeEvents';
|
|
3
|
+
import {
|
|
4
|
+
invariant,
|
|
5
|
+
deferThrow,
|
|
6
|
+
isNullish,
|
|
7
|
+
assign,
|
|
8
|
+
TinyState,
|
|
9
|
+
text,
|
|
10
|
+
dynamicValue,
|
|
11
|
+
tinyState,
|
|
12
|
+
BusType,
|
|
13
|
+
bus,
|
|
14
|
+
Nullable,
|
|
15
|
+
DynamicValue,
|
|
16
|
+
asArray,
|
|
17
|
+
} from 'vest-utils';
|
|
18
|
+
|
|
19
|
+
import * as Walker from './IsolateWalker';
|
|
20
|
+
import {
|
|
21
|
+
TIsolateFocused,
|
|
22
|
+
FocusSelectors,
|
|
23
|
+
FocusModes,
|
|
24
|
+
} from './Isolate/IsolateFocused';
|
|
25
|
+
import { TIsolate } from './Isolate/Isolate';
|
|
26
|
+
import { IsolateInspector } from './Isolate/IsolateInspector';
|
|
27
|
+
import { IsolateMutator } from './Isolate/IsolateMutator';
|
|
28
|
+
import { IReconciler } from './Reconciler';
|
|
29
|
+
import { ErrorStrings } from './errors/ErrorStrings';
|
|
30
|
+
import { RuntimeState } from './Orchestrator/RuntimeStates';
|
|
31
|
+
|
|
32
|
+
type CTXType = StateRefType & {
|
|
33
|
+
historyNode: Nullable<TIsolate>;
|
|
34
|
+
runtimeNode: Nullable<TIsolate>;
|
|
35
|
+
runtimeRoot: Nullable<TIsolate>;
|
|
36
|
+
stateRef: StateRefType;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The state reference type for the Vest runtime.
|
|
41
|
+
* Holds all mutable state for the runtime instance.
|
|
42
|
+
*/
|
|
43
|
+
export type StateRefType = {
|
|
44
|
+
Bus: BusType<RuntimeEvents>;
|
|
45
|
+
appData: Record<string, any>;
|
|
46
|
+
historyRoot: TinyState<Nullable<TIsolate>>;
|
|
47
|
+
Reconciler: IReconciler;
|
|
48
|
+
implicitOnlyNodes: Set<TIsolate>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const PersistedContext = createCascade<CTXType>((stateRef, parentContext) => {
|
|
52
|
+
if (parentContext) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
invariant(stateRef.historyRoot);
|
|
57
|
+
|
|
58
|
+
const ref = stateRef as StateRefType;
|
|
59
|
+
|
|
60
|
+
const [historyRootNode] = ref.historyRoot();
|
|
61
|
+
|
|
62
|
+
// Clear the implicit-only registry from any previous run.
|
|
63
|
+
// The Set lives on stateRef (persisted across runs), so stale
|
|
64
|
+
// entries would cause hasImplicitOnly() false positives.
|
|
65
|
+
ref.implicitOnlyNodes.clear();
|
|
66
|
+
|
|
67
|
+
const ctxRef = {} as CTXType;
|
|
68
|
+
|
|
69
|
+
assign(ctxRef, {
|
|
70
|
+
historyNode: historyRootNode,
|
|
71
|
+
runtimeNode: null,
|
|
72
|
+
runtimeRoot: null,
|
|
73
|
+
stateRef,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return ctxRef;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Runs a function within the Vest runtime context.
|
|
81
|
+
* This is the main entry point for executing Vest suites.
|
|
82
|
+
*/
|
|
83
|
+
export const Run = PersistedContext.run;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Retrieves the current runtime state (e.g., STABLE, PENDING).
|
|
87
|
+
*/
|
|
88
|
+
export function useRuntimeState() {
|
|
89
|
+
return useIsStable() ? RuntimeState.STABLE : RuntimeState.PENDING;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Checks if the runtime is currently stable (no pending tests).
|
|
94
|
+
*/
|
|
95
|
+
export function useIsStable() {
|
|
96
|
+
const root = useAvailableRoot();
|
|
97
|
+
|
|
98
|
+
if (!root) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return !IsolateInspector.hasPending(root);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Retrieves the application-specific data stored in the runtime.
|
|
107
|
+
*/
|
|
108
|
+
export function useXAppData<T = object>() {
|
|
109
|
+
return useX().stateRef.appData as T;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Creates a new state reference for such as the history root, pending isolates, and the current runtime state.
|
|
114
|
+
*/
|
|
115
|
+
export function createRef(
|
|
116
|
+
Reconciler: IReconciler,
|
|
117
|
+
setter: DynamicValue<Record<string, any>>,
|
|
118
|
+
): StateRefType {
|
|
119
|
+
const ref = Object.freeze({
|
|
120
|
+
Bus: bus.createBus<RuntimeEvents>(),
|
|
121
|
+
Reconciler,
|
|
122
|
+
appData: dynamicValue(setter),
|
|
123
|
+
historyRoot: tinyState.createTinyState<Nullable<TIsolate>>(null),
|
|
124
|
+
implicitOnlyNodes: new Set<TIsolate>(),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return ref;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Dispatches a runtime event to the internal Bus.
|
|
132
|
+
* This is used to trigger state changes and notifications.
|
|
133
|
+
*/
|
|
134
|
+
export function dispatch<T extends keyof RuntimeEvents>(
|
|
135
|
+
event: RuntimeEvents[T] extends void
|
|
136
|
+
? { type: T; payload?: void }
|
|
137
|
+
: { type: T; payload: RuntimeEvents[T] },
|
|
138
|
+
) {
|
|
139
|
+
useX().stateRef.Bus.emit(event.type, event.payload as any);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Registers an isolate as pending.
|
|
144
|
+
* This is used to track async tests and other async operations.
|
|
145
|
+
*/
|
|
146
|
+
export function registerPending(isolate: TIsolate) {
|
|
147
|
+
dispatch({ type: 'ISOLATE_PENDING', payload: isolate });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Removes an isolate from the pending set.
|
|
152
|
+
* This is used when an async test or operation completes.
|
|
153
|
+
*/
|
|
154
|
+
export function removePending(isolate: TIsolate) {
|
|
155
|
+
dispatch({ type: 'ISOLATE_DONE', payload: isolate });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Retrieves the Reconciler used by the current runtime.
|
|
160
|
+
*/
|
|
161
|
+
export function useReconciler() {
|
|
162
|
+
return useX().stateRef.Reconciler;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Persists the current runtime context to a callback function.
|
|
167
|
+
* This allows the callback to be executed later (e.g. in an async operation)
|
|
168
|
+
* while still having access to the correct runtime context.
|
|
169
|
+
*/
|
|
170
|
+
export function persist<T extends (...args: any[]) => any>(cb: T): T {
|
|
171
|
+
const prev = PersistedContext.useX();
|
|
172
|
+
|
|
173
|
+
return ((...args: Parameters<T>): ReturnType<T> => {
|
|
174
|
+
const ctxToUse = PersistedContext.use() ?? prev;
|
|
175
|
+
return PersistedContext.run(ctxToUse.stateRef, () => cb(...args));
|
|
176
|
+
}) as T;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Retrieves the current runtime context.
|
|
180
|
+
* Throws if called outside of a Vest runtime.
|
|
181
|
+
*/
|
|
182
|
+
export function useX<T = object>(): CTXType & T {
|
|
183
|
+
return PersistedContext.useX() as CTXType & T;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Retrieves the history root state.
|
|
188
|
+
*/
|
|
189
|
+
export function useHistoryRoot() {
|
|
190
|
+
return useX().stateRef.historyRoot();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Retrieves the current history isolate (the one matching the current runtime isolate).
|
|
195
|
+
*/
|
|
196
|
+
export function useHistoryIsolate() {
|
|
197
|
+
return useX().historyNode;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Returns the history isolate at the current position.
|
|
202
|
+
* If there is a parent isolate, it returns the history node from the parent's children.
|
|
203
|
+
* Otherwise, it returns the history node.
|
|
204
|
+
* @returns {Nullable<TIsolate>} The history isolate at the current position.
|
|
205
|
+
*/
|
|
206
|
+
export function useHistoryIsolateAtCurrentPosition() {
|
|
207
|
+
const parent = useIsolate();
|
|
208
|
+
const historyNode = useHistoryIsolate();
|
|
209
|
+
|
|
210
|
+
if (!parent) {
|
|
211
|
+
return historyNode;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (isNullish(historyNode)) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const nonTransientIndex = countNonTransientBefore(
|
|
219
|
+
parent.children || [],
|
|
220
|
+
IsolateInspector.cursor(parent),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
return findNthNonTransient(historyNode.children || [], nonTransientIndex);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Counts non-transient children before the given cursor position.
|
|
228
|
+
*/
|
|
229
|
+
function countNonTransientBefore(siblings: TIsolate[], cursor: number): number {
|
|
230
|
+
let count = 0;
|
|
231
|
+
for (let i = 0; i < cursor; i++) {
|
|
232
|
+
if (!siblings[i]?.transient) {
|
|
233
|
+
count++;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return count;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Finds the Nth non-transient child in a children array.
|
|
241
|
+
* This is used to align the reconciler cursor, skipping over
|
|
242
|
+
* transient nodes that should not affect the index of stateful nodes.
|
|
243
|
+
*/
|
|
244
|
+
function findNthNonTransient(
|
|
245
|
+
children: TIsolate[],
|
|
246
|
+
n: number,
|
|
247
|
+
): Nullable<TIsolate> {
|
|
248
|
+
return children.filter(child => child && !child.transient)[n] ?? null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Sets the history root for the runtime.
|
|
253
|
+
* This is used to hydrate the runtime with previous results.
|
|
254
|
+
*/
|
|
255
|
+
export function useSetHistoryRoot(history: TIsolate) {
|
|
256
|
+
const [, setHistoryRoot] = useHistoryRoot();
|
|
257
|
+
setHistoryRoot(history);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Retrieves a child isolate from the history tree by its key.
|
|
262
|
+
*/
|
|
263
|
+
export function useHistoryKey(key?: Nullable<string>): Nullable<TIsolate> {
|
|
264
|
+
if (isNullish(key)) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const historyNode = useX().historyNode;
|
|
269
|
+
|
|
270
|
+
return IsolateInspector.getChildByKey(historyNode, key);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Retrieves the currently active isolate in the runtime tree.
|
|
275
|
+
*/
|
|
276
|
+
export function useIsolate() {
|
|
277
|
+
return useX().runtimeNode ?? null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Retrieves the current cursor position within the active isolate.
|
|
282
|
+
*/
|
|
283
|
+
export function useCurrentCursor() {
|
|
284
|
+
const isolate = useIsolate();
|
|
285
|
+
return isolate ? IsolateInspector.cursor(isolate) : 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Retrieves the root of the current runtime tree.
|
|
290
|
+
*/
|
|
291
|
+
export function useRuntimeRoot() {
|
|
292
|
+
return useX().runtimeRoot;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Adds a child isolate to the current isolate and sets the parent-child relationship.
|
|
297
|
+
*/
|
|
298
|
+
export function useSetNextIsolateChild(child: TIsolate): void {
|
|
299
|
+
const currentIsolate = useIsolate();
|
|
300
|
+
|
|
301
|
+
invariant(currentIsolate, ErrorStrings.NO_ACTIVE_ISOLATE);
|
|
302
|
+
|
|
303
|
+
IsolateMutator.addChild(currentIsolate, child);
|
|
304
|
+
IsolateMutator.setParent(child, currentIsolate);
|
|
305
|
+
|
|
306
|
+
if (
|
|
307
|
+
FocusSelectors.isIsolateFocused(child) &&
|
|
308
|
+
child.data?.focusMode === FocusModes.ONLY
|
|
309
|
+
) {
|
|
310
|
+
useX().stateRef.implicitOnlyNodes.add(currentIsolate);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Sets a key for a child isolate within the current isolate.
|
|
316
|
+
* Throws if the key is already taken.
|
|
317
|
+
*/
|
|
318
|
+
export function useSetIsolateKey(key: Nullable<string>, node: TIsolate): void {
|
|
319
|
+
if (!key) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const currentIsolate = useIsolate();
|
|
324
|
+
|
|
325
|
+
invariant(currentIsolate, ErrorStrings.NO_ACTIVE_ISOLATE);
|
|
326
|
+
|
|
327
|
+
if (isNullish(IsolateInspector.getChildByKey(currentIsolate, key))) {
|
|
328
|
+
IsolateMutator.addChildKey(currentIsolate, key, node);
|
|
329
|
+
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
deferThrow(text(ErrorStrings.ENCOUNTERED_THE_SAME_KEY_TWICE, { key }));
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Returns the available root isolate.
|
|
337
|
+
* If a runtime root exists (i.e. we are currently running a suite), it returns that.
|
|
338
|
+
* Otherwise, it returns the history root (i.e. the result of the last run).
|
|
339
|
+
*/
|
|
340
|
+
export function useAvailableRoot<I extends TIsolate = TIsolate>(): I {
|
|
341
|
+
const root = useRuntimeRoot();
|
|
342
|
+
|
|
343
|
+
if (root) {
|
|
344
|
+
return root as I;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const [historyRoot] = useHistoryRoot();
|
|
348
|
+
|
|
349
|
+
return historyRoot as I;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Checks whether a specific key is heavily focused out.
|
|
354
|
+
*/
|
|
355
|
+
export function useIsFocusedOut(key?: string): boolean {
|
|
356
|
+
const current = useIsolate();
|
|
357
|
+
if (!current) return false;
|
|
358
|
+
|
|
359
|
+
const focusMatch = Walker.findClosest<TIsolateFocused>(
|
|
360
|
+
current,
|
|
361
|
+
(child: TIsolate): boolean => {
|
|
362
|
+
if (!FocusSelectors.isIsolateFocused(child)) return false;
|
|
363
|
+
const data = child.data;
|
|
364
|
+
if (!data) return false;
|
|
365
|
+
if (data.matchAll) return true;
|
|
366
|
+
|
|
367
|
+
if (isNullish(key)) return false;
|
|
368
|
+
return asArray(data.match).includes(key);
|
|
369
|
+
},
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
if (focusMatch) {
|
|
373
|
+
if (FocusSelectors.isSkipFocused(focusMatch, key)) return true;
|
|
374
|
+
if (FocusSelectors.isOnlyFocused(focusMatch, key)) return false;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return hasImplicitOnly();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function hasImplicitOnly(): boolean {
|
|
381
|
+
let current = useIsolate();
|
|
382
|
+
const registry = useX().stateRef.implicitOnlyNodes;
|
|
383
|
+
|
|
384
|
+
while (current) {
|
|
385
|
+
if (registry.has(current)) {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
current = current.parent;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Resets the history root.
|
|
396
|
+
*/
|
|
397
|
+
export function reset() {
|
|
398
|
+
const [, , resetHistoryRoot] = useHistoryRoot();
|
|
399
|
+
|
|
400
|
+
resetHistoryRoot();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export const RuntimeApi = {
|
|
404
|
+
Run,
|
|
405
|
+
createRef,
|
|
406
|
+
dispatch,
|
|
407
|
+
hasImplicitOnly,
|
|
408
|
+
persist,
|
|
409
|
+
registerPending,
|
|
410
|
+
removePending,
|
|
411
|
+
reset,
|
|
412
|
+
useAvailableRoot,
|
|
413
|
+
useCurrentCursor,
|
|
414
|
+
useHistoryRoot,
|
|
415
|
+
useIsFocusedOut,
|
|
416
|
+
useIsStable,
|
|
417
|
+
useRuntimeState,
|
|
418
|
+
useSetHistoryRoot,
|
|
419
|
+
useSetNextIsolateChild,
|
|
420
|
+
useXAppData,
|
|
421
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { useBus, useEmit, usePrepareEmitter } from '../Bus';
|
|
3
|
+
import { Run, createRef } from '../VestRuntime';
|
|
4
|
+
|
|
5
|
+
describe('Bus', () => {
|
|
6
|
+
let ref: any;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
ref = createRef(vi.fn(), {} as any);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const withRun = (fn: () => void) => {
|
|
13
|
+
Run(ref, fn);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('useBus', () => {
|
|
17
|
+
it('Should return the bus instance', () => {
|
|
18
|
+
withRun(() => {
|
|
19
|
+
expect(useBus()).toBe(ref.Bus);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('useEmit', () => {
|
|
25
|
+
it('Should return the emit function checking spy', () => {
|
|
26
|
+
// We must attach spy BEFORE useEmit is called because useEmit captures the function
|
|
27
|
+
const spy = vi.spyOn(ref.Bus, 'emit');
|
|
28
|
+
|
|
29
|
+
withRun(() => {
|
|
30
|
+
const emit = useEmit();
|
|
31
|
+
emit('test' as any, 'payload');
|
|
32
|
+
expect(spy).toHaveBeenCalledWith('test', 'payload');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('Should emit immediately if event name is provided', () => {
|
|
37
|
+
const spy = vi.spyOn(ref.Bus, 'emit');
|
|
38
|
+
|
|
39
|
+
withRun(() => {
|
|
40
|
+
useEmit('immediate' as any, 123);
|
|
41
|
+
expect(spy).toHaveBeenCalledWith('immediate', 123);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('usePrepareEmitter', () => {
|
|
47
|
+
it('Should return a function that emits the specific event', () => {
|
|
48
|
+
const spy = vi.spyOn(ref.Bus, 'emit');
|
|
49
|
+
|
|
50
|
+
withRun(() => {
|
|
51
|
+
const emitter = usePrepareEmitter<any>('prepared');
|
|
52
|
+
emitter('data');
|
|
53
|
+
expect(spy).toHaveBeenCalledWith('prepared', 'data');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { makeResult } from 'vest-utils';
|
|
3
|
+
import { walk } from '../IsolateWalker';
|
|
4
|
+
import { TIsolate } from '../Isolate/Isolate';
|
|
5
|
+
|
|
6
|
+
describe('IsolateWalker Iterative', () => {
|
|
7
|
+
it('Should traverse in Pre-Order (Parent before Children)', () => {
|
|
8
|
+
// Tree Structure:
|
|
9
|
+
// root
|
|
10
|
+
// |- child_1
|
|
11
|
+
// |- child_2
|
|
12
|
+
// |- grandchild_2_1
|
|
13
|
+
const tree = {
|
|
14
|
+
data: { id: 'root' },
|
|
15
|
+
children: [
|
|
16
|
+
{ data: { id: 'child_1' }, children: [] },
|
|
17
|
+
{
|
|
18
|
+
data: { id: 'child_2' },
|
|
19
|
+
children: [{ data: { id: 'grandchild_2_1' }, children: [] }],
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const visited: string[] = [];
|
|
25
|
+
walk(tree as unknown as TIsolate, node => {
|
|
26
|
+
visited.push(node.data.id);
|
|
27
|
+
return makeResult.Ok(undefined);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Expect: root -> child_1 -> child_2 -> grandchild_2_1
|
|
31
|
+
expect(visited).toEqual(['root', 'child_1', 'child_2', 'grandchild_2_1']);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('Should handle deep nesting without stack overflow', () => {
|
|
35
|
+
// Create a linked list of depth 100,000
|
|
36
|
+
// root -> child_0 -> child_1 ... -> child_99999
|
|
37
|
+
let current: any = { data: { id: 'root' }, children: [] };
|
|
38
|
+
const root = current;
|
|
39
|
+
const depth = 100000;
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < depth; i++) {
|
|
42
|
+
const next = { data: { id: `child_${i}` }, children: [] };
|
|
43
|
+
current.children.push(next);
|
|
44
|
+
current = next;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const visitSpy = vi.fn(() => makeResult.Ok(undefined));
|
|
48
|
+
|
|
49
|
+
// This would throw RangeError in recursive implementation
|
|
50
|
+
expect(() => walk(root, visitSpy)).not.toThrow();
|
|
51
|
+
|
|
52
|
+
// Depth + 1 (root)
|
|
53
|
+
expect(visitSpy).toHaveBeenCalledTimes(depth + 1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('Should respect breakout in iterative loop', () => {
|
|
57
|
+
const tree = {
|
|
58
|
+
data: { id: 'root' },
|
|
59
|
+
children: [
|
|
60
|
+
{ data: { id: 'child_1' }, children: [] },
|
|
61
|
+
{ data: { id: 'child_2' }, children: [] }, // Break here
|
|
62
|
+
{ data: { id: 'child_3' }, children: [] }, // Should not visit
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const visited: string[] = [];
|
|
67
|
+
walk(tree as unknown as TIsolate, node => {
|
|
68
|
+
visited.push(node.data.id);
|
|
69
|
+
if (node.data.id === 'child_2') {
|
|
70
|
+
return makeResult.Err(undefined);
|
|
71
|
+
}
|
|
72
|
+
return makeResult.Ok(undefined);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(visited).toEqual(['root', 'child_1', 'child_2']);
|
|
76
|
+
});
|
|
77
|
+
});
|