@adaas/a-concept 0.1.47 → 0.1.50
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/README.md +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +39 -2
- package/dist/index.d.ts +39 -2
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/global/A-Dependency/A-Dependency-Parent.decorator.ts +75 -0
- package/src/global/A-Dependency/A-Dependency.class.ts +34 -0
- package/src/global/A-Dependency/A-Dependency.types.ts +7 -0
- package/src/global/A-Feature/A-Feature.class.ts +4 -3
- package/src/global/A-Inject/A-Inject.types.ts +3 -0
- package/src/global/A-Scope/A-Scope.class.ts +7 -0
- package/src/global/A-Stage/A-Stage.class.ts +27 -12
- package/src/helpers/A_Formatter.helper.ts +25 -6
- package/tests/A-Common.test.ts +59 -4
- package/tests/A-Feature.test.ts +12 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adaas/a-concept",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.50",
|
|
4
4
|
"description": "A-Concept is a framework to build new Applications within or outside the ADAAS ecosystem. This framework is designed to be modular structure regardless environment and program goal.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
import { A_Context } from "@adaas/a-concept/global/A-Context/A-Context.class";
|
|
3
|
+
import { A_Meta } from "@adaas/a-concept/global/A-Meta/A-Meta.class";
|
|
4
|
+
import { A_TYPES__ComponentMetaKey } from "@adaas/a-concept/global/A-Component/A-Component.constants";
|
|
5
|
+
import { A_TYPES__ContainerMetaKey } from "@adaas/a-concept/global/A-Container/A-Container.constants";
|
|
6
|
+
import { A_TypeGuards } from "@adaas/a-concept/helpers/A_TypeGuards.helper";
|
|
7
|
+
import { A_TYPES__A_InjectDecorator_Meta, A_TYPES__InjectableTargets } from "../A-Inject/A-Inject.types";
|
|
8
|
+
import { A_TYPES__A_Dependency_ParentDecoratorReturn } from "./A-Dependency.types";
|
|
9
|
+
import { A_DependencyError } from "./A-Dependency.error";
|
|
10
|
+
import { A_CommonHelper } from "@adaas/a-concept/helpers/A_Common.helper";
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Should indicate which dependency is required
|
|
15
|
+
*/
|
|
16
|
+
export function A_Dependency_Parent(
|
|
17
|
+
/**
|
|
18
|
+
* Indicates how many layers up the parent dependency should be resolved from current dependency
|
|
19
|
+
*
|
|
20
|
+
* Default: -1 (one layer up)
|
|
21
|
+
*/
|
|
22
|
+
layerOffset: number = -1
|
|
23
|
+
): A_TYPES__A_Dependency_ParentDecoratorReturn {
|
|
24
|
+
|
|
25
|
+
return function (
|
|
26
|
+
target: A_TYPES__InjectableTargets,
|
|
27
|
+
methodName: string | symbol | undefined,
|
|
28
|
+
parameterIndex: number
|
|
29
|
+
) {
|
|
30
|
+
// for Error handling purposes
|
|
31
|
+
const componentName = A_CommonHelper.getComponentName(target)
|
|
32
|
+
|
|
33
|
+
if (!A_TypeGuards.isTargetAvailableForInjection(target)) {
|
|
34
|
+
throw new A_DependencyError(
|
|
35
|
+
A_DependencyError.InvalidDependencyTarget,
|
|
36
|
+
`A-Dependency cannot be used on the target of type ${typeof target} (${componentName})`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// determine the method name or 'constructor' for constructor injections
|
|
41
|
+
const method = methodName ? String(methodName) : 'constructor';
|
|
42
|
+
let metaKey;
|
|
43
|
+
|
|
44
|
+
switch (true) {
|
|
45
|
+
case A_TypeGuards.isComponentConstructor(target) || A_TypeGuards.isComponentInstance(target):
|
|
46
|
+
metaKey = A_TYPES__ComponentMetaKey.INJECTIONS;
|
|
47
|
+
break;
|
|
48
|
+
|
|
49
|
+
case A_TypeGuards.isContainerInstance(target):
|
|
50
|
+
metaKey = A_TYPES__ContainerMetaKey.INJECTIONS;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// get existing meta or create a new one
|
|
55
|
+
const existedMeta = A_Context.meta(target).get(metaKey) || new A_Meta();
|
|
56
|
+
// get existing injections for the method or create a new array
|
|
57
|
+
const paramsArray: A_TYPES__A_InjectDecorator_Meta = existedMeta.get(method) || [];
|
|
58
|
+
|
|
59
|
+
// set the parameter injection info
|
|
60
|
+
paramsArray[parameterIndex] = {
|
|
61
|
+
...(paramsArray[parameterIndex] || {}),
|
|
62
|
+
parent: { layerOffset }
|
|
63
|
+
}
|
|
64
|
+
// save back the updated injections array
|
|
65
|
+
existedMeta.set(method, paramsArray);
|
|
66
|
+
|
|
67
|
+
// save back the updated meta info
|
|
68
|
+
A_Context
|
|
69
|
+
.meta(target)
|
|
70
|
+
.set(
|
|
71
|
+
metaKey,
|
|
72
|
+
existedMeta
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { A_Dependency_Default } from "./A-Dependency-Default.decorator";
|
|
2
2
|
import { A_Dependency_Load } from "./A-Dependency-Load.decorator";
|
|
3
|
+
import { A_Dependency_Parent } from "./A-Dependency-Parent.decorator";
|
|
3
4
|
import { A_Dependency_Require } from "./A-Dependency-Require.decorator";
|
|
4
5
|
|
|
5
6
|
|
|
@@ -30,4 +31,37 @@ export class A_Dependency {
|
|
|
30
31
|
static get Default(): typeof A_Dependency_Default {
|
|
31
32
|
return A_Dependency_Default;
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Allows to indicate which parent dependency should be resolved
|
|
36
|
+
* e.g. from which layer up the parent should be taken
|
|
37
|
+
*
|
|
38
|
+
* @returns
|
|
39
|
+
*/
|
|
40
|
+
static get Parent(): typeof A_Dependency_Parent {
|
|
41
|
+
return A_Dependency_Parent;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected _name: string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Class instances allows to indentify dependencies by name and use them for better type checking
|
|
48
|
+
*
|
|
49
|
+
* @param name
|
|
50
|
+
*/
|
|
51
|
+
constructor(
|
|
52
|
+
name: string
|
|
53
|
+
) {
|
|
54
|
+
this._name = name;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Gets the dependency name
|
|
59
|
+
*
|
|
60
|
+
* Can be identifier, url or any string value
|
|
61
|
+
*
|
|
62
|
+
* @returns
|
|
63
|
+
*/
|
|
64
|
+
get name(): string {
|
|
65
|
+
return this._name;
|
|
66
|
+
}
|
|
33
67
|
}
|
|
@@ -25,4 +25,11 @@ export type A_TYPES__A_Dependency_DefaultDecoratorReturn<T = any> = (
|
|
|
25
25
|
target: T,
|
|
26
26
|
propertyKey: string | symbol | undefined,
|
|
27
27
|
parameterIndex: number
|
|
28
|
+
) => void
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
export type A_TYPES__A_Dependency_ParentDecoratorReturn<T = any> = (
|
|
32
|
+
target: T,
|
|
33
|
+
propertyKey: string | symbol | undefined,
|
|
34
|
+
parameterIndex: number
|
|
28
35
|
) => void
|
|
@@ -363,9 +363,10 @@ export class A_Feature<T extends A_TYPES__FeatureAvailableComponents = A_TYPES__
|
|
|
363
363
|
scope?: A_Scope,
|
|
364
364
|
) {
|
|
365
365
|
try {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
366
|
+
// It seems like this is a bad idea to enforce scope inheritance here
|
|
367
|
+
// ---------------------------------------------------------------
|
|
368
|
+
// if (scope && !scope.isInheritedFrom(A_Context.scope(this)))
|
|
369
|
+
// scope.inherit(A_Context.scope(this));
|
|
369
370
|
|
|
370
371
|
if (this.isProcessed)
|
|
371
372
|
return;
|
|
@@ -36,6 +36,9 @@ export type A_TYPES__A_InjectDecorator_Meta = Array<{
|
|
|
36
36
|
require?: boolean
|
|
37
37
|
load?: string
|
|
38
38
|
defaultArgs?: any,
|
|
39
|
+
parent?: {
|
|
40
|
+
layerOffset?: number
|
|
41
|
+
},
|
|
39
42
|
create?: boolean
|
|
40
43
|
instructions?: Partial<A_TYPES__A_InjectDecorator_EntityInjectionInstructions>,
|
|
41
44
|
}>;
|
|
@@ -1023,6 +1023,13 @@ export class A_Scope<
|
|
|
1023
1023
|
case fragmentInstancePresented && this._fragments.has(fragment):
|
|
1024
1024
|
return fragmentInstancePresented;
|
|
1025
1025
|
|
|
1026
|
+
// 3) In case when there's a component that is inherited from the required component
|
|
1027
|
+
case !fragmentInstancePresented && Array.from(this._allowedFragments).some(el => A_CommonHelper.isInheritedFrom(el, fragment)): {
|
|
1028
|
+
const found = Array.from(this._allowedFragments).find(el => A_CommonHelper.isInheritedFrom(el, fragment))!;
|
|
1029
|
+
|
|
1030
|
+
return this.resolveFragment(found);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1026
1033
|
case !fragmentInstancePresented && !!this._parent:
|
|
1027
1034
|
return this._parent.resolveFragment(fragment);
|
|
1028
1035
|
|
|
@@ -136,24 +136,39 @@ export class A_Stage {
|
|
|
136
136
|
case A_TypeGuards.isCallerConstructor(arg.target):
|
|
137
137
|
return this._feature.caller.component;
|
|
138
138
|
|
|
139
|
-
case A_TypeGuards.isScopeConstructor(arg.target):
|
|
140
|
-
return scope;
|
|
141
|
-
|
|
142
139
|
case A_TypeGuards.isFeatureConstructor(arg.target):
|
|
143
140
|
return this._feature;
|
|
144
141
|
|
|
145
|
-
case A_TypeGuards.isEntityConstructor(arg.target) && 'instructions' in arg && !!arg.instructions:
|
|
146
|
-
return scope.resolve(arg.target, arg.instructions)
|
|
147
|
-
|
|
148
142
|
default: {
|
|
149
|
-
const { target, require, create, defaultArgs } = arg;
|
|
143
|
+
const { target, require, create, defaultArgs, parent } = arg;
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
let dependency;
|
|
147
|
+
let targetScope = scope;
|
|
148
|
+
|
|
149
|
+
if (parent && typeof parent.layerOffset === 'number') {
|
|
150
|
+
let parentScope = scope.parent;
|
|
150
151
|
|
|
151
|
-
|
|
152
|
+
let offset = parent.layerOffset;
|
|
153
|
+
|
|
154
|
+
while (offset < -1 && parentScope) {
|
|
155
|
+
parentScope = parentScope.parent;
|
|
156
|
+
offset++;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (parentScope) {
|
|
160
|
+
dependency = parentScope.resolve(target);
|
|
161
|
+
targetScope = parentScope;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
} else {
|
|
165
|
+
dependency = targetScope.resolve(target);
|
|
166
|
+
}
|
|
152
167
|
|
|
153
168
|
if (create && !dependency && A_TypeGuards.isAllowedForDependencyDefaultCreation(target)) {
|
|
154
169
|
const newDependency = new target(...defaultArgs);
|
|
155
170
|
|
|
156
|
-
|
|
171
|
+
targetScope.register(newDependency);
|
|
157
172
|
return newDependency;
|
|
158
173
|
}
|
|
159
174
|
|
|
@@ -164,7 +179,7 @@ export class A_Stage {
|
|
|
164
179
|
);
|
|
165
180
|
}
|
|
166
181
|
|
|
167
|
-
return
|
|
182
|
+
return dependency;
|
|
168
183
|
}
|
|
169
184
|
}
|
|
170
185
|
})
|
|
@@ -192,11 +207,11 @@ export class A_Stage {
|
|
|
192
207
|
break;
|
|
193
208
|
|
|
194
209
|
case A_TypeGuards.isString(component):
|
|
195
|
-
instance = scope.resolve(component);
|
|
210
|
+
instance = scope.resolve(component) || this.feature.scope.resolve(component);
|
|
196
211
|
break;
|
|
197
212
|
|
|
198
213
|
default:
|
|
199
|
-
instance = scope.resolve(component);
|
|
214
|
+
instance = scope.resolve(component) || this.feature.scope.resolve(component);
|
|
200
215
|
break;
|
|
201
216
|
}
|
|
202
217
|
|
|
@@ -13,9 +13,11 @@ export class A_FormatterHelper {
|
|
|
13
13
|
*/
|
|
14
14
|
static toUpperSnakeCase(str: string): string {
|
|
15
15
|
return str
|
|
16
|
-
.
|
|
17
|
-
.replace(/[
|
|
18
|
-
.replace(
|
|
16
|
+
.trim()
|
|
17
|
+
.replace(/([a-z])([A-Z])/g, '$1_$2') // Handle camelCase
|
|
18
|
+
.replace(/[^a-zA-Z0-9]+/g, '_') // Replace non-alphanumeric with underscores
|
|
19
|
+
.replace(/_+/g, '_') // Collapse multiple underscores
|
|
20
|
+
.replace(/^_|_$/g, '') // Remove leading/trailing underscores
|
|
19
21
|
.toUpperCase();
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
@@ -25,7 +27,18 @@ export class A_FormatterHelper {
|
|
|
25
27
|
* @returns
|
|
26
28
|
*/
|
|
27
29
|
static toCamelCase(str: string): string {
|
|
28
|
-
return str
|
|
30
|
+
return str
|
|
31
|
+
.trim()
|
|
32
|
+
.replace(/[^a-zA-Z0-9]+/g, ' ') // Replace non-alphanumeric with spaces
|
|
33
|
+
.split(' ') // Split by spaces
|
|
34
|
+
.filter(Boolean) // Remove empty items
|
|
35
|
+
.map((part, index) => {
|
|
36
|
+
if (index === 0) {
|
|
37
|
+
return part.toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
|
|
40
|
+
})
|
|
41
|
+
.join('');
|
|
29
42
|
}
|
|
30
43
|
/**
|
|
31
44
|
* Convert string to PascalCase
|
|
@@ -34,8 +47,14 @@ export class A_FormatterHelper {
|
|
|
34
47
|
* @returns
|
|
35
48
|
*/
|
|
36
49
|
static toPascalCase(str: string): string {
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
return str
|
|
51
|
+
.trim()
|
|
52
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2') // Insert space before uppercase in camelCase
|
|
53
|
+
.replace(/[^a-zA-Z0-9]+/g, ' ') // Replace non-alphanumeric with spaces
|
|
54
|
+
.split(' ') // Split by spaces
|
|
55
|
+
.filter(Boolean) // Remove empty items
|
|
56
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
57
|
+
.join('');
|
|
39
58
|
}
|
|
40
59
|
/**
|
|
41
60
|
* Convert string to kebab-case
|
package/tests/A-Common.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { A_CommonHelper } from "@adaas/a-concept/helpers/A_Common.helper";
|
|
2
2
|
import { A_IdentityHelper } from "@adaas/a-concept/helpers/A_Identity.helper";
|
|
3
3
|
import { A_TYPES__DeepPartial } from "@adaas/a-concept/types/A_Common.types";
|
|
4
|
+
import { A_FormatterHelper } from "../src";
|
|
4
5
|
jest.retryTimes(0);
|
|
5
6
|
|
|
6
7
|
describe('A-Common Tests', () => {
|
|
@@ -56,7 +57,7 @@ describe('A-Common Tests', () => {
|
|
|
56
57
|
c: {
|
|
57
58
|
d: string
|
|
58
59
|
},
|
|
59
|
-
bool:{
|
|
60
|
+
bool: {
|
|
60
61
|
a: boolean
|
|
61
62
|
},
|
|
62
63
|
f: (name: string) => string
|
|
@@ -69,7 +70,7 @@ describe('A-Common Tests', () => {
|
|
|
69
70
|
c: {
|
|
70
71
|
d: 'd'
|
|
71
72
|
},
|
|
72
|
-
bool:{
|
|
73
|
+
bool: {
|
|
73
74
|
a: true
|
|
74
75
|
},
|
|
75
76
|
f: (name: string) => { return name },
|
|
@@ -79,10 +80,10 @@ describe('A-Common Tests', () => {
|
|
|
79
80
|
const t2: any = {
|
|
80
81
|
e: 'foo',
|
|
81
82
|
b: 'bb',
|
|
82
|
-
c:{
|
|
83
|
+
c: {
|
|
83
84
|
d: 'ddd'
|
|
84
85
|
},
|
|
85
|
-
bool:{
|
|
86
|
+
bool: {
|
|
86
87
|
a: false
|
|
87
88
|
},
|
|
88
89
|
some: {
|
|
@@ -114,4 +115,58 @@ describe('A-Common Tests', () => {
|
|
|
114
115
|
expect(timestamp).toBeLessThanOrEqual(now);
|
|
115
116
|
expect(timestamp).toBeGreaterThan(now - 60000); // within the last minute
|
|
116
117
|
});
|
|
118
|
+
|
|
119
|
+
it('Should translate all strings to Pascal Case', async () => {
|
|
120
|
+
const testStrings = [
|
|
121
|
+
{ input: 'hello-world', expected: 'HelloWorld' },
|
|
122
|
+
{ input: 'FooBar', expected: 'FooBar' },
|
|
123
|
+
{ input: 'foo_bar', expected: 'FooBar' },
|
|
124
|
+
{ input: ' leading-trailing ', expected: 'LeadingTrailing' },
|
|
125
|
+
{ input: 'multiple--separators__here', expected: 'MultipleSeparatorsHere' },
|
|
126
|
+
{ input: 'alreadyPascalCase', expected: 'AlreadyPascalCase' },
|
|
127
|
+
{ input: 'single', expected: 'Single' },
|
|
128
|
+
{ input: '', expected: '' },
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
for (const { input, expected } of testStrings) {
|
|
132
|
+
const result = A_FormatterHelper.toPascalCase(input);
|
|
133
|
+
expect(result).toBe(expected);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('Should translate all strings to Upper Snake Case', async () => {
|
|
138
|
+
const testStrings = [
|
|
139
|
+
{ input: 'hello-world', expected: 'HELLO_WORLD' },
|
|
140
|
+
{ input: 'FOO_BAR', expected: 'FOO_BAR' },
|
|
141
|
+
{ input: 'fooBar', expected: 'FOO_BAR' },
|
|
142
|
+
{ input: ' leadingTrailing ', expected: 'LEADING_TRAILING' },
|
|
143
|
+
{ input: 'multiple--separators__here', expected: 'MULTIPLE_SEPARATORS_HERE' },
|
|
144
|
+
{ input: 'already_upper_snake_case', expected: 'ALREADY_UPPER_SNAKE_CASE' },
|
|
145
|
+
{ input: 'single', expected: 'SINGLE' },
|
|
146
|
+
{ input: '', expected: '' },
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
for (const { input, expected } of testStrings) {
|
|
150
|
+
const result = A_FormatterHelper.toUpperSnakeCase(input);
|
|
151
|
+
expect(result).toBe(expected);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('Should translate all strings to Kebab Case', async () => {
|
|
156
|
+
const testStrings = [
|
|
157
|
+
{ input: 'hello-world', expected: 'hello-world' },
|
|
158
|
+
{ input: 'fooBar', expected: 'foo-bar' },
|
|
159
|
+
{ input: ' leadingTrailing ', expected: 'leading-trailing' },
|
|
160
|
+
{ input: 'kebab-case', expected: 'kebab-case' },
|
|
161
|
+
{ input: 'multiple--separators__here', expected: 'multiple-separators-here' },
|
|
162
|
+
{ input: 'already_kebab_case', expected: 'already-kebab-case' },
|
|
163
|
+
{ input: 'single', expected: 'single' },
|
|
164
|
+
{ input: '', expected: '' },
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
for (const { input, expected } of testStrings) {
|
|
168
|
+
const result = A_FormatterHelper.toKebabCase(input);
|
|
169
|
+
expect(result).toBe(expected);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
117
172
|
});
|
package/tests/A-Feature.test.ts
CHANGED
|
@@ -465,19 +465,24 @@ describe('A-Feature tests', () => {
|
|
|
465
465
|
executionOrder.push('stepThree');
|
|
466
466
|
}
|
|
467
467
|
}
|
|
468
|
+
try {
|
|
468
469
|
|
|
470
|
+
// 2) create a running scope
|
|
471
|
+
const scope = new A_Scope({ name: 'TestScope', components: [ComponentA, ComponentB] });
|
|
469
472
|
|
|
470
|
-
// 2) create a running scope
|
|
471
|
-
const scope = new A_Scope({ name: 'TestScope', components: [ComponentA, ComponentB] });
|
|
472
473
|
|
|
474
|
+
// 3) create an instance of the component from the scope
|
|
475
|
+
const myComponent = scope.resolve(ComponentA)!;
|
|
476
|
+
expect(myComponent).toBeInstanceOf(ComponentA);
|
|
473
477
|
|
|
474
|
-
|
|
475
|
-
const myComponent = scope.resolve(ComponentA)!;
|
|
476
|
-
expect(myComponent).toBeInstanceOf(ComponentA);
|
|
478
|
+
await myComponent.feature1();
|
|
477
479
|
|
|
478
|
-
|
|
480
|
+
expect(executionOrder).toEqual(['stepOne', 'stepThree']);
|
|
481
|
+
|
|
482
|
+
} catch (error) {
|
|
483
|
+
console.error('Error during feature processing: ', error);
|
|
484
|
+
}
|
|
479
485
|
|
|
480
|
-
expect(executionOrder).toEqual(['stepOne', 'stepThree']);
|
|
481
486
|
});
|
|
482
487
|
it('Should allow to interrupt a new feature', async () => {
|
|
483
488
|
const feature = new A_Feature({
|