vestjs-runtime 1.6.0 → 1.7.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/IsolateSerializer/package.json +8 -12
- package/README.md +1 -3
- package/dist/cjs/IsolateSerializer.development.js +135 -0
- package/dist/cjs/IsolateSerializer.development.js.map +1 -0
- package/dist/cjs/IsolateSerializer.js +6 -0
- package/dist/cjs/IsolateSerializer.production.js +2 -0
- package/dist/cjs/IsolateSerializer.production.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test-utils.development.js +61 -0
- package/dist/cjs/test-utils.development.js.map +1 -0
- package/dist/cjs/test-utils.js +6 -0
- package/dist/cjs/test-utils.production.js +2 -0
- package/dist/cjs/test-utils.production.js.map +1 -0
- package/dist/cjs/vestjs-runtime.development.js +686 -0
- package/dist/cjs/vestjs-runtime.development.js.map +1 -0
- package/dist/cjs/vestjs-runtime.js +6 -0
- package/dist/cjs/vestjs-runtime.production.js +2 -0
- package/dist/cjs/vestjs-runtime.production.js.map +1 -0
- package/dist/es/IsolateSerializer.development.js +133 -0
- package/dist/es/IsolateSerializer.development.js.map +1 -0
- package/dist/es/IsolateSerializer.production.js +2 -0
- package/dist/es/IsolateSerializer.production.js.map +1 -0
- package/dist/es/package.json +1 -0
- package/dist/es/test-utils.development.js +59 -0
- package/dist/es/test-utils.development.js.map +1 -0
- package/dist/es/test-utils.production.js +2 -0
- package/dist/es/test-utils.production.js.map +1 -0
- package/dist/es/vestjs-runtime.development.js +675 -0
- package/dist/es/vestjs-runtime.development.js.map +1 -0
- package/dist/es/vestjs-runtime.production.js +2 -0
- package/dist/es/vestjs-runtime.production.js.map +1 -0
- package/dist/umd/IsolateSerializer.development.js +138 -0
- package/dist/umd/IsolateSerializer.development.js.map +1 -0
- package/dist/umd/IsolateSerializer.production.js +2 -0
- package/dist/umd/IsolateSerializer.production.js.map +1 -0
- package/dist/umd/test-utils.development.js +67 -0
- package/dist/umd/test-utils.development.js.map +1 -0
- package/dist/umd/test-utils.production.js +2 -0
- package/dist/umd/test-utils.production.js.map +1 -0
- package/dist/umd/vestjs-runtime.development.js +688 -0
- package/dist/umd/vestjs-runtime.development.js.map +1 -0
- package/dist/umd/vestjs-runtime.production.js +2 -0
- package/dist/umd/vestjs-runtime.production.js.map +1 -0
- package/package.json +87 -42
- package/test-utils/package.json +8 -12
- package/types/IsolateSerializer.d.ts +42 -0
- package/types/IsolateSerializer.d.ts.map +1 -0
- package/types/test-utils.d.ts +37 -0
- package/types/test-utils.d.ts.map +1 -0
- package/types/vestjs-runtime.d.ts +257 -351
- package/types/vestjs-runtime.d.ts.map +1 -0
- package/vitest.config.ts +17 -9
- package/dist/IsolateKeys-B21aPuBk.mjs +0 -23
- package/dist/IsolateKeys-B21aPuBk.mjs.map +0 -1
- package/dist/IsolateKeys-CCvALpZC.cjs +0 -35
- package/dist/IsolateKeys-CCvALpZC.cjs.map +0 -1
- package/dist/IsolateSerializer-B1hE3gmT.mjs +0 -1004
- package/dist/IsolateSerializer-B1hE3gmT.mjs.map +0 -1
- package/dist/IsolateSerializer-pbEf5gB2.cjs +0 -1121
- package/dist/IsolateSerializer-pbEf5gB2.cjs.map +0 -1
- package/dist/chunk-CLMFDpHK.mjs +0 -18
- package/dist/exports/IsolateSerializer.cjs +0 -4
- package/dist/exports/IsolateSerializer.mjs +0 -4
- package/dist/exports/test-utils.cjs +0 -21
- package/dist/exports/test-utils.cjs.map +0 -1
- package/dist/exports/test-utils.mjs +0 -21
- package/dist/exports/test-utils.mjs.map +0 -1
- package/dist/vestjs-runtime.cjs +0 -153
- package/dist/vestjs-runtime.cjs.map +0 -1
- package/dist/vestjs-runtime.mjs +0 -117
- package/dist/vestjs-runtime.mjs.map +0 -1
- package/docs/IsolateRegistry.docs.md +0 -146
- package/docs/Isolates.md +0 -97
- package/src/Bus.ts +0 -46
- package/src/Isolate/Isolate.ts +0 -163
- package/src/Isolate/IsolateFocused.ts +0 -93
- package/src/Isolate/IsolateIndexer.ts +0 -42
- package/src/Isolate/IsolateInspector.ts +0 -93
- package/src/Isolate/IsolateKeys.ts +0 -18
- package/src/Isolate/IsolateMutator.ts +0 -165
- package/src/Isolate/IsolateRegistry.ts +0 -176
- package/src/Isolate/IsolateReorderable.ts +0 -11
- package/src/Isolate/IsolateSelectors.ts +0 -25
- package/src/Isolate/IsolateStateMachine.ts +0 -30
- package/src/Isolate/IsolateStatus.ts +0 -8
- package/src/Isolate/IsolateTransient.ts +0 -27
- package/src/Isolate/IsolateTypes.ts +0 -33
- package/src/Isolate/__tests__/Isolate.test.ts +0 -123
- package/src/Isolate/__tests__/IsolateFocused.test.ts +0 -199
- package/src/Isolate/__tests__/IsolateInspector.test.ts +0 -136
- package/src/Isolate/__tests__/IsolateMutator.test.ts +0 -164
- package/src/Isolate/__tests__/IsolatePropagation.test.ts +0 -170
- package/src/Isolate/__tests__/IsolateReorderable.test.ts +0 -111
- package/src/Isolate/__tests__/IsolateSelectors.test.ts +0 -72
- package/src/Isolate/__tests__/IsolateStatus.test.ts +0 -44
- package/src/Isolate/__tests__/IsolateTransient.test.ts +0 -58
- package/src/Isolate/__tests__/__snapshots__/asyncIsolate.test.ts.snap +0 -71
- package/src/Isolate/__tests__/asyncIsolate.test.ts +0 -85
- package/src/IsolateWalker.ts +0 -359
- package/src/Orchestrator/RuntimeStates.ts +0 -4
- package/src/Reconciler.ts +0 -178
- package/src/RuntimeEvents.ts +0 -9
- package/src/VestRuntime.ts +0 -421
- package/src/__tests__/Bus.test.ts +0 -57
- package/src/__tests__/IsolateWalker.iterative.test.ts +0 -77
- package/src/__tests__/IsolateWalker.test.ts +0 -418
- package/src/__tests__/Reconciler.test.ts +0 -193
- package/src/__tests__/Reconciler.transient.test.ts +0 -166
- package/src/__tests__/VestRuntime.test.ts +0 -212
- package/src/__tests__/VestRuntimeStateMachine.test.ts +0 -36
- package/src/__tests__/vestjs-runtime.test.ts +0 -19
- package/src/errors/ErrorStrings.ts +0 -6
- package/src/exports/IsolateSerializer.ts +0 -131
- package/src/exports/__tests__/IsolateSerializer.test.ts +0 -334
- package/src/exports/__tests__/IsolateSerializer.transient.test.ts +0 -101
- package/src/exports/__tests__/__snapshots__/IsolateSerializer.test.ts.snap +0 -5
- package/src/exports/test-utils.ts +0 -17
- package/src/vestjs-runtime.ts +0 -28
- package/types/Isolate-DChR7h5K.d.mts +0 -58
- package/types/Isolate-DChR7h5K.d.mts.map +0 -1
- package/types/Isolate-HYIh82M8.d.cts +0 -58
- package/types/Isolate-HYIh82M8.d.cts.map +0 -1
- package/types/IsolateSerializer-BCg01Px5.d.mts +0 -13
- package/types/IsolateSerializer-BCg01Px5.d.mts.map +0 -1
- package/types/IsolateSerializer-CQpP6A4m.d.cts +0 -13
- package/types/IsolateSerializer-CQpP6A4m.d.cts.map +0 -1
- package/types/exports/IsolateSerializer.d.cts +0 -3
- package/types/exports/IsolateSerializer.d.mts +0 -3
- package/types/exports/test-utils.d.cts +0 -7
- package/types/exports/test-utils.d.cts.map +0 -1
- package/types/exports/test-utils.d.mts +0 -7
- package/types/exports/test-utils.d.mts.map +0 -1
- package/types/vestjs-runtime.d.cts +0 -372
- package/types/vestjs-runtime.d.cts.map +0 -1
- package/types/vestjs-runtime.d.mts +0 -370
- package/types/vestjs-runtime.d.mts.map +0 -1
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
# Isolate Registry Architecture & API
|
|
2
|
-
|
|
3
|
-
The Isolate Registry is a high-performance, O(1) indexing system that tracks isolates based on configurable predicates and keys. It eliminates the need for expensive tree traversals when calculating suite results or checking test statuses.
|
|
4
|
-
|
|
5
|
-
## 🏗️ Architecture
|
|
6
|
-
|
|
7
|
-
The Registry is divided into two layers:
|
|
8
|
-
|
|
9
|
-
1. **Generic Engine (`vestjs-runtime/IsolateRegistry`)**: A predicated indexing engine with **configurable key selectors**. It manages maps of sets on the root isolate and is completely agnostic to both the predicates and key extraction logic.
|
|
10
|
-
2. **Vest Configuration (`vest/TestRegistry`)**: Defines the categories (failed, pending, etc.) and provides **both predicates and key selectors** (e.g., extracting `fieldName` from test data).
|
|
11
|
-
|
|
12
|
-
### Key Configuration (`RegistryCategoryConfig`)
|
|
13
|
-
|
|
14
|
-
Each category is defined by:
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
type RegistryCategoryConfig = {
|
|
18
|
-
predicate: (isolate: TIsolate) => boolean; // What isolates match this category?
|
|
19
|
-
getKey: (isolate: TIsolate) => string; // How to group them (e.g., by fieldName)?
|
|
20
|
-
};
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
This decoupling allows `vestjs-runtime` to remain **domain-agnostic** while Vest defines how to extract keys from its test data.
|
|
24
|
-
|
|
25
|
-
## 🏗️ Structure & Location
|
|
26
|
-
|
|
27
|
-
The registry is stateful and is stored directly on the **Root Isolate** of a suite.
|
|
28
|
-
|
|
29
|
-
### Storage Location
|
|
30
|
-
|
|
31
|
-
Each category is stored in the `root.data` object with a `registry_` prefix:
|
|
32
|
-
|
|
33
|
-
- `root.data.registry_failed`
|
|
34
|
-
- `root.data.registry_pending`
|
|
35
|
-
- ...and so on.
|
|
36
|
-
|
|
37
|
-
### Shape
|
|
38
|
-
|
|
39
|
-
Each registry entry is a `RegistryIndex`:
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
type RegistryIndex = Map<string, Set<TIsolate>>;
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
- **Key**: Determined by the `getKey` function in the category config (e.g., `fieldName` for tests, or `_generic` for null keys).
|
|
46
|
-
- **Value**: A `Set` containing all isolates currently matching that category.
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## 🔄 Update Lifecycle
|
|
51
|
-
|
|
52
|
-
The registry is **reactive**. It re-evaluates its state whenever relevant events occur.
|
|
53
|
-
|
|
54
|
-
### Flowchart
|
|
55
|
-
|
|
56
|
-
```text
|
|
57
|
-
[ Isolate Event ]
|
|
58
|
-
|
|
|
59
|
-
( Enter / Status Change )
|
|
60
|
-
|
|
|
61
|
-
v
|
|
62
|
-
useUpdateRegistry
|
|
63
|
-
|
|
|
64
|
-
+---------v-------------------+
|
|
65
|
-
| Iterates all Predicates |
|
|
66
|
-
+---------|-------------------+
|
|
67
|
-
|
|
|
68
|
-
+---------v-------------------+
|
|
69
|
-
| For each Category: |
|
|
70
|
-
| [Match?] --Yes--> [Add] |
|
|
71
|
-
| |-------No---> [Remove] |
|
|
72
|
-
+---------|-------------------+
|
|
73
|
-
|
|
|
74
|
-
v
|
|
75
|
-
Root.data["registry_<category>"]
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### When do updates happen?
|
|
79
|
-
|
|
80
|
-
1. **Initial Registration (`ISOLATED_ENTER`)**: When a node is first encountered.
|
|
81
|
-
2. **Status Changes (`setStatus`)**: When a test moves between states (e.g. Pending -> Failed).
|
|
82
|
-
3. **Cross-Suite Reconciliation**: When a tree is reprocessed or merged.
|
|
83
|
-
4. **Field Removal**: When `REMOVE_FIELD` is emitted, the registry cleans up all indexed categories for a specific field.
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## 🛠️ APIs
|
|
88
|
-
|
|
89
|
-
### `IsolateCategory` (Vest specific)
|
|
90
|
-
|
|
91
|
-
- `failed`: Tests with error severity.
|
|
92
|
-
- `omitted`: Tests that were not run.
|
|
93
|
-
- `passing`: Tests that passed.
|
|
94
|
-
- `pending`: Tests currently in progress.
|
|
95
|
-
- `tested`: Tests that have finished running.
|
|
96
|
-
- `valid`: Tests that are either passing or warnings.
|
|
97
|
-
- `warning`: Tests with warning severity.
|
|
98
|
-
|
|
99
|
-
### Core Engine (`vestjs-runtime/IsolateRegistry`)
|
|
100
|
-
|
|
101
|
-
#### `useUpdateRegistry(isolate, predicates)`
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
useUpdateRegistry(
|
|
105
|
-
isolate: TIsolate,
|
|
106
|
-
predicates: Record<string, RegistryCategoryConfig>
|
|
107
|
-
): void
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Evaluates all predicates and updates the registry. For each category:
|
|
111
|
-
|
|
112
|
-
- Calls `config.predicate(isolate)` to check if it matches
|
|
113
|
-
- Calls `config.getKey(isolate)` to determine the index key
|
|
114
|
-
- Adds to or removes from the appropriate registry index
|
|
115
|
-
|
|
116
|
-
#### `useGetFromRegistry(category, key?)`
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
useGetFromRegistry(category: string, key?: string): Set<TIsolate>
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Retrieves isolates for a category. If `key` is provided (e.g., a fieldName), returns only isolates indexed under that key. Otherwise, returns all isolates in the category.
|
|
123
|
-
|
|
124
|
-
#### `useHasFromRegistry(category, key?)`
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
useHasFromRegistry(category: string, key?: string): boolean
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Returns `true` if any isolates exist for the given criteria.
|
|
131
|
-
|
|
132
|
-
#### `useRemoveFieldFromRegistry(key)`
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
useRemoveFieldFromRegistry(key: string): void
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Removes all entries for a specific key (e.g., fieldName) from all registry categories.
|
|
139
|
-
|
|
140
|
-
#### `useClearRegistry(root)`
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
useClearRegistry(root: TIsolate): void
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Clears all registry indices from the root isolate. Used during tree reprocessing.
|
package/docs/Isolates.md
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
# Isolates Architecture
|
|
2
|
-
|
|
3
|
-
## What is an Isolate?
|
|
4
|
-
|
|
5
|
-
In the `@vestjs-runtime`, an "Isolate" is the fundamental building block of the state and execution tree. Every piece of business logic or structural grouping—whether it's a test, a suite, a group, or a focus context—is represented as an Isolate.
|
|
6
|
-
|
|
7
|
-
The term "isolate" stems from its purpose: creating an isolated context where functionality runs safely. When traversing the tree of isolates, each context is maintained correctly, preventing sibling states from polluting one another.
|
|
8
|
-
|
|
9
|
-
Every Isolate contains at minimum:
|
|
10
|
-
|
|
11
|
-
- `Type`: A string defining the nature of the Isolate (e.g. `'Test'`, `'Suite'`, `'Group'`).
|
|
12
|
-
- `Parent` / `Children`: Linking to construct the overall state tree structure.
|
|
13
|
-
- `Data`: Any metadata the Isolate requires to execute properly.
|
|
14
|
-
- `Status`: Current state of resolution (e.g. `PENDING`, `DONE`).
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Types of Isolates
|
|
19
|
-
|
|
20
|
-
Isolates behave differently depending on their internal configurations and traits:
|
|
21
|
-
|
|
22
|
-
### 1. Stateful Isolates (Default)
|
|
23
|
-
|
|
24
|
-
Most isolates created with `Isolate.create()` are stateful. This means:
|
|
25
|
-
|
|
26
|
-
- They persist in the internal history tree.
|
|
27
|
-
- They are subject to reconciliation (comparing current vs previous states between executions).
|
|
28
|
-
- They have a predefined order in the tree matching their execution sequence, which prevents unpredictable side-effects between runs.
|
|
29
|
-
- **Example:** A `VestTest` or a `Group`. The runtime tracks how many tests successfully ran before.
|
|
30
|
-
|
|
31
|
-
### 2. Transient Isolates
|
|
32
|
-
|
|
33
|
-
Transient isolates act as "structural" or "control flow" isolates but don't hold state between runs.
|
|
34
|
-
|
|
35
|
-
Characteristics of transient isolates:
|
|
36
|
-
|
|
37
|
-
1. They **do not** persist in the history tree.
|
|
38
|
-
2. They **are not** reconciled with previous runs.
|
|
39
|
-
3. They **do not** appear in the serialized suite dump (no `.dump()` presence).
|
|
40
|
-
4. They **do not interfere** with the indexing of siblings. Since they are transient, the runtime reconciler effectively skips them when aligning sibling child indices.
|
|
41
|
-
|
|
42
|
-
These are primarily used as invisible "modifiers" that dictate logic over their children but hold no inherent measurable business-state of their own.
|
|
43
|
-
|
|
44
|
-
- **Example:** `IsolateFocused` (used for `.only` and `.skip`).
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## Focus Capabilities (`IsolateFocused`)
|
|
49
|
-
|
|
50
|
-
`IsolateFocused` is a specialized implementation of a Transient Isolate used to manage the `only` and `skip` execution boundaries.
|
|
51
|
-
|
|
52
|
-
Because they are transient, testing an `only()` block will not incorrectly disrupt the index sequence of test components evaluated below it. It is strictly used as an execution-context layer.
|
|
53
|
-
|
|
54
|
-
### How Focus Checking Works
|
|
55
|
-
|
|
56
|
-
Focus checking uses a **two-tier approach**: explicit matching and implicit exclusion.
|
|
57
|
-
|
|
58
|
-
#### 1. Explicit Focus Matching (`findClosest` — sibling-aware)
|
|
59
|
-
|
|
60
|
-
`Walker.findClosest` traverses UP through ancestor levels, and at each level searches the **children** (siblings) for a matching `IsolateFocused` node. Because `only()` and `skip()` create transient isolates that are **siblings** of the tests they affect (not ancestors), the search must start from the test object itself to correctly find focus rules at the same tree level.
|
|
61
|
-
|
|
62
|
-
**Important:** Starting from `useIsolate()` (the parent context) would be one level too high and would miss sibling focus isolates entirely. This is why the vest-level `useIsExcludedByField` passes the specific `testObject` to `findClosest`, while the runtime-level `useIsFocusedOut` starts from the current isolate context.
|
|
63
|
-
|
|
64
|
-
If an explicit focus match is found:
|
|
65
|
-
|
|
66
|
-
- **Skip-focused:** The test is excluded immediately.
|
|
67
|
-
- **Only-focused:** The test is NOT excluded (it matched the only rule).
|
|
68
|
-
|
|
69
|
-
#### 2. Implicit ONLY Identification (`hasImplicitOnly` — centralized registry)
|
|
70
|
-
|
|
71
|
-
When no explicit focus match is found for a test, the runtime checks whether any ancestor scope contains an `ONLY` rule — meaning any test _not_ explicitly matched should be excluded.
|
|
72
|
-
|
|
73
|
-
To keep this check **O(Depth)** performant — instead of O(N) traversing thousands of elements per check — the runtime maintains a centralized `implicitOnlyNodes` Set on the `StateRef` context. When an `IsolateFocused` node with `FocusModes.ONLY` is created, its **parent** is registered in this Set via `useSetNextIsolateChild`.
|
|
74
|
-
|
|
75
|
-
When checking implicit exclusion, `hasImplicitOnly()` simply walks up the current node's ancestors and queries `implicitOnlyNodes.has(current)` at each level. This approach:
|
|
76
|
-
|
|
77
|
-
- **Separates concerns:** The Isolate data model remains pure — no focus-specific flags on AST nodes.
|
|
78
|
-
- **Is O(Depth):** Only walks the ancestor chain, not the full tree.
|
|
79
|
-
- **Is centralized:** All implicit-only state lives on the runtime's `StateRef`, not scattered across nodes.
|
|
80
|
-
|
|
81
|
-
### Interaction with `include()`
|
|
82
|
-
|
|
83
|
-
At the vest application layer, `useIsExcludedByField` combines both tiers:
|
|
84
|
-
|
|
85
|
-
1. Explicit focus match via `useClosestMatchingFocus(testObject, fieldName)`.
|
|
86
|
-
2. If no match, falls back to `VestRuntime.hasImplicitOnly()`.
|
|
87
|
-
3. If implicitly excluded, checks the `inclusion` map — `include()` rules can override implicit exclusion but **not** explicit `skip()`.
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## The Reconciler Core
|
|
92
|
-
|
|
93
|
-
All isolates (except Transient ones) pass through the underlying Reconciler matching system. The reconciler evaluates:
|
|
94
|
-
|
|
95
|
-
1. Is there an active existing node from the previous history tree in this slot?
|
|
96
|
-
2. If yes: Can it be reused? (Bypassing execution to save processing).
|
|
97
|
-
3. If no: The execution runs anew and is marked as new runtime history.
|
package/src/Bus.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { BusType, isNullish } from 'vest-utils';
|
|
2
|
-
|
|
3
|
-
import { persist, useX } from './VestRuntime';
|
|
4
|
-
import { RuntimeEvents } from './RuntimeEvents';
|
|
5
|
-
|
|
6
|
-
export function useBus<
|
|
7
|
-
E extends Record<string, any> = RuntimeEvents,
|
|
8
|
-
>(): BusType<E> {
|
|
9
|
-
return useX().stateRef.Bus as unknown as BusType<E>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/*
|
|
13
|
-
Returns an emitter, but it also has a shortcut for emitting an event immediately
|
|
14
|
-
by passing an event name.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export function useEmit<
|
|
18
|
-
E extends Record<string, any> = RuntimeEvents,
|
|
19
|
-
T extends keyof E = keyof E,
|
|
20
|
-
>(
|
|
21
|
-
event: T,
|
|
22
|
-
...args: E[T] extends void ? [payload?: E[T]] : [payload: E[T]]
|
|
23
|
-
): void;
|
|
24
|
-
export function useEmit<_E extends Record<string, any> = RuntimeEvents>(): (
|
|
25
|
-
event: string,
|
|
26
|
-
data?: any,
|
|
27
|
-
) => void;
|
|
28
|
-
export function useEmit(event?: string, data?: any): any {
|
|
29
|
-
const emit = useBus().emit;
|
|
30
|
-
|
|
31
|
-
if (!isNullish(event)) {
|
|
32
|
-
// @ts-ignore - We're allowing any event name here because the bus might be extended
|
|
33
|
-
emit(event, data);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return persist(emit);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function usePrepareEmitter<
|
|
40
|
-
E extends Record<string, any> = RuntimeEvents,
|
|
41
|
-
T extends keyof E = keyof E,
|
|
42
|
-
>(event: T): (arg: E[T]) => void {
|
|
43
|
-
const emit = useEmit<E>();
|
|
44
|
-
|
|
45
|
-
return (arg: E[T]) => emit(event as string, arg);
|
|
46
|
-
}
|
package/src/Isolate/Isolate.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { CB, Maybe, Nullable, isNotNullish, isPromise } from 'vest-utils';
|
|
2
|
-
|
|
3
|
-
import { useEmit } from '../Bus';
|
|
4
|
-
import { Reconciler } from '../Reconciler';
|
|
5
|
-
import * as VestRuntime from '../VestRuntime';
|
|
6
|
-
|
|
7
|
-
import { IsolateKeys } from './IsolateKeys';
|
|
8
|
-
import { IsolateMutator } from './IsolateMutator';
|
|
9
|
-
import { IsolateStatus } from './IsolateStatus';
|
|
10
|
-
import type { IsolateKey, IsolatePayload, TIsolate } from './IsolateTypes';
|
|
11
|
-
|
|
12
|
-
export { IsolateKey, TIsolate };
|
|
13
|
-
|
|
14
|
-
export class Isolate {
|
|
15
|
-
// eslint-disable-next-line max-statements
|
|
16
|
-
static create<Payload extends IsolatePayload>(
|
|
17
|
-
type: string,
|
|
18
|
-
callback: CB,
|
|
19
|
-
payload: Maybe<Payload> = undefined,
|
|
20
|
-
key?: IsolateKey,
|
|
21
|
-
): TIsolate<Payload> {
|
|
22
|
-
const parent = VestRuntime.useIsolate();
|
|
23
|
-
|
|
24
|
-
const newCreatedNode = IsolateMutator.setParent(
|
|
25
|
-
baseIsolate(type, payload, key),
|
|
26
|
-
parent,
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const nextIsolateChild = Reconciler.reconcile(newCreatedNode);
|
|
30
|
-
|
|
31
|
-
const localHistoryNode = VestRuntime.useHistoryIsolateAtCurrentPosition();
|
|
32
|
-
|
|
33
|
-
const shouldRunNew = Object.is(nextIsolateChild, newCreatedNode);
|
|
34
|
-
|
|
35
|
-
if (parent) {
|
|
36
|
-
// We are within an isolate context. This means that
|
|
37
|
-
// we need to set the new node to be the child of this parent node.
|
|
38
|
-
VestRuntime.useSetNextIsolateChild(nextIsolateChild);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let output;
|
|
42
|
-
|
|
43
|
-
if (shouldRunNew) {
|
|
44
|
-
output = useRunAsNew(localHistoryNode, newCreatedNode, callback);
|
|
45
|
-
} else {
|
|
46
|
-
const emit = useEmit();
|
|
47
|
-
output = nextIsolateChild.output;
|
|
48
|
-
emit('ISOLATE_RECONCILED', nextIsolateChild);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
IsolateMutator.saveOutput(nextIsolateChild, output);
|
|
52
|
-
|
|
53
|
-
if (!parent) {
|
|
54
|
-
// We're exiting the node, and there is no parent. This means
|
|
55
|
-
// that we're at the top level and this node should be set
|
|
56
|
-
// as the new root of the history tree.
|
|
57
|
-
VestRuntime.useSetHistoryRoot(nextIsolateChild);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return nextIsolateChild as TIsolate<Payload>;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
static isIsolate(node: any): node is TIsolate {
|
|
64
|
-
return isNotNullish(node) && node[IsolateKeys.Type];
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Creates a new child isolate context where the local history node is the current history node, thus advancing the history cursor.
|
|
70
|
-
* Runs the callback function and returns its output.
|
|
71
|
-
* @param localHistoryNode The local history node.
|
|
72
|
-
* @param current The current isolate.
|
|
73
|
-
* @param callback The callback function to execute.
|
|
74
|
-
* @returns The output of the callback function.
|
|
75
|
-
*/
|
|
76
|
-
function useRunAsNew<Callback extends CB = CB>(
|
|
77
|
-
localHistoryNode: Nullable<TIsolate>,
|
|
78
|
-
current: TIsolate,
|
|
79
|
-
callback: CB,
|
|
80
|
-
): ReturnType<Callback> {
|
|
81
|
-
const runtimeRoot = VestRuntime.useRuntimeRoot();
|
|
82
|
-
|
|
83
|
-
// We're creating a new child isolate context where the local history node
|
|
84
|
-
// is the current history node, thus advancing the history cursor.
|
|
85
|
-
const output = VestRuntime.Run(
|
|
86
|
-
{
|
|
87
|
-
historyNode: localHistoryNode,
|
|
88
|
-
runtimeNode: current,
|
|
89
|
-
...(!runtimeRoot && { runtimeRoot: current }),
|
|
90
|
-
},
|
|
91
|
-
() => useRunAsNewCallback(current, callback),
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
current.output = output;
|
|
95
|
-
return output;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function useRunAsNewCallback(current: TIsolate, callback: CB): any {
|
|
99
|
-
const emit = useEmit();
|
|
100
|
-
emit('ISOLATE_ENTER', current);
|
|
101
|
-
const output = callback(current);
|
|
102
|
-
|
|
103
|
-
if (isPromise(output)) {
|
|
104
|
-
emit('ISOLATE_PENDING', current);
|
|
105
|
-
IsolateMutator.setPending(current);
|
|
106
|
-
output.then(
|
|
107
|
-
VestRuntime.persist(iso => {
|
|
108
|
-
if (Isolate.isIsolate(iso)) {
|
|
109
|
-
IsolateMutator.addChild(current, iso);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
IsolateMutator.setDone(current);
|
|
113
|
-
emit('ASYNC_ISOLATE_DONE', current);
|
|
114
|
-
}),
|
|
115
|
-
VestRuntime.persist(() => {
|
|
116
|
-
IsolateMutator.setDone(current);
|
|
117
|
-
emit('ASYNC_ISOLATE_DONE', current);
|
|
118
|
-
}),
|
|
119
|
-
);
|
|
120
|
-
} else {
|
|
121
|
-
IsolateMutator.setDone(current);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return output;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
class IsolateInstance implements TIsolate {
|
|
128
|
-
[IsolateKeys.Type]: string;
|
|
129
|
-
children: Nullable<TIsolate[]> = null;
|
|
130
|
-
[IsolateKeys.Keys]: Nullable<Record<string, TIsolate>> = null;
|
|
131
|
-
[IsolateKeys.Parent]: Nullable<TIsolate> = null;
|
|
132
|
-
output: any = null;
|
|
133
|
-
key: IsolateKey = null;
|
|
134
|
-
[IsolateKeys.AllowReorder]: Maybe<boolean> = undefined;
|
|
135
|
-
[IsolateKeys.Transient]: Maybe<boolean> = undefined;
|
|
136
|
-
[IsolateKeys.Status]: IsolateStatus = IsolateStatus.INITIAL;
|
|
137
|
-
[IsolateKeys.AbortController]: Nullable<AbortController> = null;
|
|
138
|
-
[IsolateKeys.Data]: Maybe<any>;
|
|
139
|
-
|
|
140
|
-
constructor(
|
|
141
|
-
type: string,
|
|
142
|
-
payload: Maybe<IsolatePayload> = undefined,
|
|
143
|
-
key: IsolateKey = null,
|
|
144
|
-
) {
|
|
145
|
-
this[IsolateKeys.Type] = type;
|
|
146
|
-
this.key = key;
|
|
147
|
-
const { allowReorder, transient, status, ...data } = payload ?? {};
|
|
148
|
-
this[IsolateKeys.AllowReorder] = allowReorder;
|
|
149
|
-
this[IsolateKeys.Transient] = transient;
|
|
150
|
-
if (status) {
|
|
151
|
-
this[IsolateKeys.Status] = status;
|
|
152
|
-
}
|
|
153
|
-
this[IsolateKeys.Data] = data;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function baseIsolate(
|
|
158
|
-
type: string,
|
|
159
|
-
payload: Maybe<IsolatePayload> = undefined,
|
|
160
|
-
key: IsolateKey = null,
|
|
161
|
-
): TIsolate {
|
|
162
|
-
return new IsolateInstance(type, payload, key);
|
|
163
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
asArray,
|
|
3
|
-
Maybe,
|
|
4
|
-
Nullish,
|
|
5
|
-
OneOrMoreOf,
|
|
6
|
-
noop,
|
|
7
|
-
isNotEmpty,
|
|
8
|
-
isStringValue,
|
|
9
|
-
} from 'vest-utils';
|
|
10
|
-
|
|
11
|
-
import { IsolateTransient } from './IsolateTransient';
|
|
12
|
-
import { TIsolate } from './Isolate';
|
|
13
|
-
import { IsolateKeys } from './IsolateKeys';
|
|
14
|
-
|
|
15
|
-
export const VestIsolateTypeFocused = 'Focused';
|
|
16
|
-
|
|
17
|
-
export enum FocusModes {
|
|
18
|
-
SKIP = 'skip',
|
|
19
|
-
ONLY = 'only',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type FocusMatchExclusion = Maybe<OneOrMoreOf<string>>;
|
|
23
|
-
|
|
24
|
-
export type TIsolateFocused = TIsolate<IsolateFocusedPayload>;
|
|
25
|
-
|
|
26
|
-
type IsolateFocusedPayload = {
|
|
27
|
-
focusMode: FocusModes;
|
|
28
|
-
match: FocusMatchExclusion;
|
|
29
|
-
matchAll: boolean;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Creates a focused isolate.
|
|
34
|
-
* Focused isolates are transient because they only affect the current run
|
|
35
|
-
* and do not need to be preserved in history or appearing in the suite result.
|
|
36
|
-
*/
|
|
37
|
-
export function IsolateFocused(
|
|
38
|
-
focusMode: FocusModes,
|
|
39
|
-
match?: true | FocusMatchExclusion,
|
|
40
|
-
): TIsolateFocused | undefined {
|
|
41
|
-
const matchedFields = asArray(match).filter(isStringValue).filter(isNotEmpty);
|
|
42
|
-
|
|
43
|
-
const matchAll = match === true;
|
|
44
|
-
|
|
45
|
-
// If there are no fields to match and matchAll is false,
|
|
46
|
-
// skip creating the isolate entirely — it would be a no-op.
|
|
47
|
-
if (!isNotEmpty(matchedFields) && !matchAll) {
|
|
48
|
-
return undefined;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return IsolateTransient<IsolateFocusedPayload>(noop, VestIsolateTypeFocused, {
|
|
52
|
-
focusMode,
|
|
53
|
-
match: matchedFields,
|
|
54
|
-
matchAll,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export class FocusSelectors {
|
|
59
|
-
static isSkipFocused(
|
|
60
|
-
focus: Nullish<TIsolateFocused>,
|
|
61
|
-
fieldName?: string,
|
|
62
|
-
): boolean {
|
|
63
|
-
if (!focus) return false;
|
|
64
|
-
const data = focus.data;
|
|
65
|
-
if (!data || data.focusMode !== FocusModes.SKIP) return false;
|
|
66
|
-
if (data.matchAll) return true;
|
|
67
|
-
return hasFocus(focus, fieldName);
|
|
68
|
-
}
|
|
69
|
-
static isOnlyFocused(
|
|
70
|
-
focus: Nullish<TIsolateFocused>,
|
|
71
|
-
fieldName?: string,
|
|
72
|
-
): boolean {
|
|
73
|
-
if (!focus) return false;
|
|
74
|
-
const data = focus.data;
|
|
75
|
-
if (!data || data.focusMode !== FocusModes.ONLY) return false;
|
|
76
|
-
if (data.matchAll) return true;
|
|
77
|
-
return hasFocus(focus, fieldName);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
static isIsolateFocused(isolate: TIsolate): isolate is TIsolateFocused {
|
|
81
|
-
return isolate[IsolateKeys.Type] === VestIsolateTypeFocused;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function hasFocus(
|
|
86
|
-
focus: Nullish<TIsolateFocused>,
|
|
87
|
-
fieldName?: string,
|
|
88
|
-
): boolean {
|
|
89
|
-
const match = asArray(focus?.data?.match);
|
|
90
|
-
if (!match.length) return false;
|
|
91
|
-
|
|
92
|
-
return !fieldName || match.includes(fieldName as string);
|
|
93
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { isNullish, Nullable } from 'vest-utils';
|
|
2
|
-
|
|
3
|
-
import { TIsolate } from './IsolateTypes';
|
|
4
|
-
|
|
5
|
-
export function createHistoryIndex(
|
|
6
|
-
children: Nullable<TIsolate[]>,
|
|
7
|
-
): Map<string, TIsolate | TIsolate[]> {
|
|
8
|
-
const index = new Map<string, TIsolate | TIsolate[]>();
|
|
9
|
-
|
|
10
|
-
if (!children) {
|
|
11
|
-
return index;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
for (const child of children) {
|
|
15
|
-
addToIndex(index, child);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return index;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function addToIndex(
|
|
22
|
-
index: Map<string, TIsolate | TIsolate[]>,
|
|
23
|
-
isolate: TIsolate,
|
|
24
|
-
): void {
|
|
25
|
-
const { key } = isolate;
|
|
26
|
-
|
|
27
|
-
if (isNullish(key)) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const existing = index.get(key);
|
|
32
|
-
|
|
33
|
-
if (existing) {
|
|
34
|
-
if (Array.isArray(existing)) {
|
|
35
|
-
existing.push(isolate);
|
|
36
|
-
} else {
|
|
37
|
-
index.set(key, [existing, isolate]);
|
|
38
|
-
}
|
|
39
|
-
} else {
|
|
40
|
-
index.set(key, isolate);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { Nullable, isNotNullish, isNullish } from 'vest-utils';
|
|
2
|
-
|
|
3
|
-
import { TIsolate } from './Isolate';
|
|
4
|
-
import { IsolateStatus } from './IsolateStatus';
|
|
5
|
-
|
|
6
|
-
export class IsolateInspector {
|
|
7
|
-
static at(isolate: Nullable<TIsolate>, at: number): Nullable<TIsolate> {
|
|
8
|
-
if (isNullish(isolate)) {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
return isolate.children?.[at] ?? null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
static cursor(isolate: Nullable<TIsolate>): number {
|
|
15
|
-
if (isNullish(isolate)) {
|
|
16
|
-
return 0;
|
|
17
|
-
}
|
|
18
|
-
return isolate.children?.length ?? 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
static canReorder<I extends TIsolate>(isolate: Nullable<I>): boolean {
|
|
22
|
-
if (isNullish(isolate)) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return IsolateInspector.allowsReorder(isolate.parent);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
static allowsReorder<I extends Record<any, any>>(
|
|
30
|
-
isolate: Nullable<I>,
|
|
31
|
-
): boolean {
|
|
32
|
-
return isolate?.allowReorder === true;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
static usesKey(isolate: Nullable<TIsolate>): boolean {
|
|
36
|
-
if (isNullish(isolate)) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
return isNotNullish(isolate.key);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
static getChildByKey(
|
|
43
|
-
isolate: Nullable<TIsolate>,
|
|
44
|
-
key: string,
|
|
45
|
-
): Nullable<TIsolate> {
|
|
46
|
-
if (isNullish(isolate)) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
return isolate.keys?.[key] ?? null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
static getStatus(isolate: Nullable<TIsolate>): IsolateStatus {
|
|
53
|
-
if (isNullish(isolate)) {
|
|
54
|
-
return IsolateStatus.INITIAL;
|
|
55
|
-
}
|
|
56
|
-
return isolate.status ?? IsolateStatus.INITIAL;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
static statusEquals(
|
|
60
|
-
isolate: Nullable<TIsolate>,
|
|
61
|
-
status: IsolateStatus,
|
|
62
|
-
): boolean {
|
|
63
|
-
return IsolateInspector.getStatus(isolate) === status;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
static isPending(isolate: Nullable<TIsolate>): boolean {
|
|
67
|
-
return IsolateInspector.statusEquals(isolate, IsolateStatus.PENDING);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
static isHasPending(isolate: Nullable<TIsolate>): boolean {
|
|
71
|
-
return IsolateInspector.statusEquals(isolate, IsolateStatus.HAS_PENDING);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
static hasPending(isolate: Nullable<TIsolate>): boolean {
|
|
75
|
-
return (
|
|
76
|
-
IsolateInspector.isPending(isolate) ||
|
|
77
|
-
IsolateInspector.isHasPending(isolate)
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
static hasActiveChildren(isolate: Nullable<TIsolate>): boolean {
|
|
82
|
-
if (isNullish(isolate) || isNullish(isolate.children)) {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Check if ANY immediate child is currently PENDING or HAS_PENDING
|
|
87
|
-
return isolate.children.some(child => IsolateInspector.hasPending(child));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
static getParent(isolate: Nullable<TIsolate>): Nullable<TIsolate> {
|
|
91
|
-
return isolate?.parent ?? null;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export enum IsolateKeys {
|
|
2
|
-
Type = '$type',
|
|
3
|
-
Keys = 'keys',
|
|
4
|
-
Key = 'key',
|
|
5
|
-
Parent = 'parent',
|
|
6
|
-
Data = 'data',
|
|
7
|
-
AllowReorder = 'allowReorder',
|
|
8
|
-
Transient = 'transient',
|
|
9
|
-
Status = 'status',
|
|
10
|
-
AbortController = 'abortController',
|
|
11
|
-
Children = 'children',
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const ExcludedFromDump = new Set([
|
|
15
|
-
IsolateKeys.AbortController,
|
|
16
|
-
IsolateKeys.Parent,
|
|
17
|
-
IsolateKeys.Keys,
|
|
18
|
-
]);
|