jssm 5.42.0 → 5.45.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/dist/es6/jssm.d.ts +5 -1
- package/dist/es6/jssm.js +96 -8
- package/dist/es6/jssm_types.d.ts +25 -1
- package/dist/es6/jssm_util.d.ts +3 -1
- package/dist/es6/jssm_util.js +3 -1
- package/dist/es6/version.js +1 -1
- package/dist/jssm.es5.cjs.js +1 -1
- package/dist/jssm.es5.iife.js +1 -1
- package/jssm.d.ts +5 -1
- package/jssm_types.d.ts +25 -1
- package/jssm_util.d.ts +3 -1
- package/package.json +3 -3
- package/src/ts/jssm.ts +131 -12
- package/src/ts/jssm_types.ts +40 -1
- package/src/ts/jssm_util.ts +18 -1
- package/src/ts/tests/hooks.spec.ts +184 -3
- package/src/ts/version.ts +1 -1
package/jssm.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
declare type StateType = string;
|
|
2
2
|
import { JssmGenericState, JssmGenericConfig, JssmTransition, JssmTransitionList, // JssmTransitionRule,
|
|
3
|
-
JssmMachineInternalState, JssmParseTree, JssmStateDeclaration, JssmArrow, JssmArrowDirection, JssmArrowKind, JssmLayout, FslDirection, FslTheme } from './jssm_types';
|
|
3
|
+
JssmMachineInternalState, JssmParseTree, JssmStateDeclaration, JssmArrow, JssmArrowDirection, JssmArrowKind, JssmLayout, FslDirection, FslTheme, HookDescription } from './jssm_types';
|
|
4
4
|
import { seq, weighted_rand_select, weighted_sample_select, histograph, weighted_histo_key } from './jssm_util';
|
|
5
5
|
import { version } from './version';
|
|
6
6
|
declare function arrow_direction(arrow: JssmArrow): JssmArrowDirection;
|
|
@@ -37,6 +37,9 @@ declare class Machine<mDT> {
|
|
|
37
37
|
_arrange_end_declaration: Array<Array<StateType>>;
|
|
38
38
|
_theme: FslTheme;
|
|
39
39
|
_flow: FslDirection;
|
|
40
|
+
_has_hooks: boolean;
|
|
41
|
+
_hooks: Map<string, Function>;
|
|
42
|
+
_named_hooks: Map<string, Function>;
|
|
40
43
|
constructor({ start_states, complete, transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, state_declaration, fsl_version, dot_preamble, arrange_declaration, arrange_start_declaration, arrange_end_declaration, theme, flow, graph_layout }: JssmGenericConfig<mDT>);
|
|
41
44
|
_new_state(state_config: JssmGenericState): StateType;
|
|
42
45
|
state(): StateType;
|
|
@@ -86,6 +89,7 @@ declare class Machine<mDT> {
|
|
|
86
89
|
is_complete(): boolean;
|
|
87
90
|
state_is_complete(whichState: StateType): boolean;
|
|
88
91
|
has_completes(): boolean;
|
|
92
|
+
set_hook(HookDesc: HookDescription): void;
|
|
89
93
|
action(name: StateType, newData?: mDT): boolean;
|
|
90
94
|
transition(newState: StateType, newData?: mDT): boolean;
|
|
91
95
|
force_transition(newState: StateType, newData?: mDT): boolean;
|
package/jssm_types.d.ts
CHANGED
|
@@ -148,4 +148,28 @@ declare type JssmCompileSeStart<DataType> = {
|
|
|
148
148
|
};
|
|
149
149
|
declare type JssmParseTree = Array<JssmCompileSeStart<StateType>>;
|
|
150
150
|
declare type JssmParseFunctionType = (string: any) => JssmParseTree;
|
|
151
|
-
|
|
151
|
+
declare type BasicHookDescription = {
|
|
152
|
+
kind: 'hook';
|
|
153
|
+
from: string;
|
|
154
|
+
to: string;
|
|
155
|
+
handler: Function;
|
|
156
|
+
};
|
|
157
|
+
declare type HookDescriptionWithAction = {
|
|
158
|
+
kind: 'named';
|
|
159
|
+
from: string;
|
|
160
|
+
to: string;
|
|
161
|
+
action: string;
|
|
162
|
+
handler: Function;
|
|
163
|
+
};
|
|
164
|
+
declare type EntryHook = {
|
|
165
|
+
kind: 'entry';
|
|
166
|
+
to: string;
|
|
167
|
+
handler: Function;
|
|
168
|
+
};
|
|
169
|
+
declare type ExitHook = {
|
|
170
|
+
kind: 'exit';
|
|
171
|
+
from: string;
|
|
172
|
+
handler: Function;
|
|
173
|
+
};
|
|
174
|
+
declare type HookDescription = BasicHookDescription | HookDescriptionWithAction | EntryHook | ExitHook;
|
|
175
|
+
export { JssmColor, JssmTransition, JssmTransitions, JssmTransitionList, JssmTransitionRule, JssmArrow, JssmArrowKind, JssmArrowDirection, JssmGenericConfig, JssmGenericState, JssmGenericMachine, JssmParseTree, JssmCompileSe, JssmCompileSeStart, JssmCompileRule, JssmPermitted, JssmPermittedOpt, JssmResult, JssmStateDeclaration, JssmStateDeclarationRule, JssmLayout, JssmParseFunctionType, JssmMachineInternalState, FslDirection, FslTheme, HookDescription };
|
package/jssm_util.d.ts
CHANGED
|
@@ -5,4 +5,6 @@ declare const seq: Function;
|
|
|
5
5
|
declare const histograph: Function;
|
|
6
6
|
declare const weighted_sample_select: Function;
|
|
7
7
|
declare const weighted_histo_key: Function;
|
|
8
|
-
|
|
8
|
+
declare const hook_name: (from: string, to: string) => string;
|
|
9
|
+
declare const named_hook_name: (from: string, to: string, action: string) => string;
|
|
10
|
+
export { seq, arr_uniq_p, histograph, weighted_histo_key, weighted_rand_select, weighted_sample_select, array_box_if_string, hook_name, named_hook_name };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jssm",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.45.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=10.0.0"
|
|
6
6
|
},
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
}
|
|
17
17
|
]
|
|
18
18
|
},
|
|
19
|
-
"description": "A Javascript finite state machine (FSM) with a terse DSL and a simple API. Well tested, and typed with
|
|
19
|
+
"description": "A Javascript finite state machine (FSM) with a terse DSL and a simple API. Well tested, and typed with TypeScript. MIT License.",
|
|
20
20
|
"main": "dist/jssm.es5.cjs.js",
|
|
21
21
|
"module": "dist/es6/jssm.js",
|
|
22
22
|
"types": "./jssm.d.ts",
|
|
@@ -70,7 +70,6 @@
|
|
|
70
70
|
"tested",
|
|
71
71
|
"typed",
|
|
72
72
|
"typed-js",
|
|
73
|
-
"flowtype",
|
|
74
73
|
"mealy",
|
|
75
74
|
"moore",
|
|
76
75
|
"mealy machine",
|
|
@@ -81,6 +80,7 @@
|
|
|
81
80
|
"viz.js",
|
|
82
81
|
"flowchart",
|
|
83
82
|
"visualization",
|
|
83
|
+
"TypeScript",
|
|
84
84
|
"StoneCypher"
|
|
85
85
|
],
|
|
86
86
|
"author": "John Haugeland <stonecypher@gmail.com>",
|
package/src/ts/jssm.ts
CHANGED
|
@@ -19,7 +19,8 @@ import {
|
|
|
19
19
|
JssmCompileSe, JssmCompileSeStart, JssmCompileRule,
|
|
20
20
|
JssmArrow, JssmArrowDirection, JssmArrowKind,
|
|
21
21
|
JssmLayout,
|
|
22
|
-
FslDirection, FslTheme
|
|
22
|
+
FslDirection, FslTheme,
|
|
23
|
+
HookDescription
|
|
23
24
|
|
|
24
25
|
} from './jssm_types';
|
|
25
26
|
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
|
|
30
31
|
import {
|
|
31
32
|
seq, weighted_rand_select, weighted_sample_select, histograph,
|
|
32
|
-
weighted_histo_key, array_box_if_string
|
|
33
|
+
weighted_histo_key, array_box_if_string, hook_name, named_hook_name
|
|
33
34
|
} from './jssm_util';
|
|
34
35
|
|
|
35
36
|
|
|
@@ -475,11 +476,15 @@ class Machine<mDT> {
|
|
|
475
476
|
_theme : FslTheme;
|
|
476
477
|
_flow : FslDirection;
|
|
477
478
|
|
|
479
|
+
_has_hooks : boolean;
|
|
480
|
+
_hooks : Map<string, Function>;
|
|
481
|
+
_named_hooks : Map<string, Function>;
|
|
482
|
+
|
|
478
483
|
|
|
479
484
|
// whargarbl this badly needs to be broken up, monolith master
|
|
480
485
|
constructor({
|
|
481
486
|
start_states,
|
|
482
|
-
complete
|
|
487
|
+
complete = [],
|
|
483
488
|
transitions,
|
|
484
489
|
machine_author,
|
|
485
490
|
machine_comment,
|
|
@@ -530,6 +535,10 @@ class Machine<mDT> {
|
|
|
530
535
|
this._flow = flow;
|
|
531
536
|
this._graph_layout = graph_layout;
|
|
532
537
|
|
|
538
|
+
this._has_hooks = false;
|
|
539
|
+
this._hooks = new Map();
|
|
540
|
+
this._named_hooks = new Map();
|
|
541
|
+
|
|
533
542
|
|
|
534
543
|
if (state_declaration) {
|
|
535
544
|
state_declaration.map( (state_decl: JssmStateDeclaration) => {
|
|
@@ -975,14 +984,73 @@ class Machine<mDT> {
|
|
|
975
984
|
|
|
976
985
|
|
|
977
986
|
|
|
987
|
+
// basic toolable hook call. convenience wrappers will follow, like
|
|
988
|
+
// hook(from, to, handler) and exit_hook(from, handler) and etc
|
|
989
|
+
set_hook(HookDesc: HookDescription) {
|
|
990
|
+
|
|
991
|
+
switch (HookDesc.kind) {
|
|
992
|
+
|
|
993
|
+
case 'hook':
|
|
994
|
+
this._hooks.set(hook_name(HookDesc.from, HookDesc.to), HookDesc.handler);
|
|
995
|
+
this._has_hooks = true;
|
|
996
|
+
break;
|
|
997
|
+
|
|
998
|
+
case 'named':
|
|
999
|
+
this._named_hooks.set(named_hook_name(HookDesc.from, HookDesc.to, HookDesc.action), HookDesc.handler);
|
|
1000
|
+
this._has_hooks = true;
|
|
1001
|
+
break;
|
|
1002
|
+
|
|
1003
|
+
// case 'entry':
|
|
1004
|
+
// console.log('TODO: Should add entry hook here');
|
|
1005
|
+
// throw 'TODO: Should add entry hook here';
|
|
1006
|
+
|
|
1007
|
+
// case 'exit':
|
|
1008
|
+
// console.log('TODO: Should add exit hook here');
|
|
1009
|
+
// throw 'TODO: Should add exit hook here';
|
|
1010
|
+
|
|
1011
|
+
default:
|
|
1012
|
+
console.log(`Unknown hook type ${(HookDesc as any).kind}, should be impossible`);
|
|
1013
|
+
throw new RangeError(`Unknown hook type ${(HookDesc as any).kind}, should be impossible`);
|
|
1014
|
+
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// remove_hook(HookDesc: HookDescription) {
|
|
1019
|
+
// throw 'TODO: Should remove hook here';
|
|
1020
|
+
// }
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
|
|
978
1024
|
action(name: StateType, newData?: mDT): boolean {
|
|
979
1025
|
// todo whargarbl implement hooks
|
|
980
1026
|
// todo whargarbl implement data stuff
|
|
981
1027
|
// todo major incomplete whargarbl comeback
|
|
982
1028
|
if (this.valid_action(name, newData)) {
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1029
|
+
|
|
1030
|
+
const edge : JssmTransition<mDT> = this.current_action_edge_for(name);
|
|
1031
|
+
|
|
1032
|
+
if (this._has_hooks) {
|
|
1033
|
+
|
|
1034
|
+
let hook_permits : boolean | undefined = undefined;
|
|
1035
|
+
|
|
1036
|
+
const nhn : string = named_hook_name(this._state, edge.to, name),
|
|
1037
|
+
maybe_hook = this._named_hooks.get(nhn);
|
|
1038
|
+
|
|
1039
|
+
if (maybe_hook === undefined) { hook_permits = true; }
|
|
1040
|
+
else { hook_permits = maybe_hook( { from: this._state, to: edge.to, action: name } ); }
|
|
1041
|
+
|
|
1042
|
+
if (hook_permits !== false) {
|
|
1043
|
+
this._state = edge.to;
|
|
1044
|
+
return true;
|
|
1045
|
+
} else {
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
} else {
|
|
1050
|
+
this._state = edge.to;
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
986
1054
|
} else {
|
|
987
1055
|
return false;
|
|
988
1056
|
}
|
|
@@ -993,11 +1061,35 @@ class Machine<mDT> {
|
|
|
993
1061
|
// todo whargarbl implement data stuff
|
|
994
1062
|
// todo major incomplete whargarbl comeback
|
|
995
1063
|
if (this.valid_transition(newState, newData)) {
|
|
996
|
-
|
|
997
|
-
|
|
1064
|
+
|
|
1065
|
+
if (this._has_hooks) {
|
|
1066
|
+
|
|
1067
|
+
let hook_permits : boolean | undefined = undefined;
|
|
1068
|
+
|
|
1069
|
+
const hn : string = hook_name(this._state, newState),
|
|
1070
|
+
maybe_hook : Function | undefined = this._hooks.get(hn);
|
|
1071
|
+
|
|
1072
|
+
if (maybe_hook === undefined) { hook_permits = true; }
|
|
1073
|
+
else { hook_permits = maybe_hook( { from: this._state, to: newState } ); }
|
|
1074
|
+
|
|
1075
|
+
if (hook_permits !== false) {
|
|
1076
|
+
this._state = newState;
|
|
1077
|
+
return true;
|
|
1078
|
+
} else {
|
|
1079
|
+
return false;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
} else {
|
|
1083
|
+
|
|
1084
|
+
this._state = newState;
|
|
1085
|
+
return true;
|
|
1086
|
+
|
|
1087
|
+
}
|
|
1088
|
+
|
|
998
1089
|
} else {
|
|
999
1090
|
return false;
|
|
1000
1091
|
}
|
|
1092
|
+
|
|
1001
1093
|
}
|
|
1002
1094
|
|
|
1003
1095
|
// can leave machine in inconsistent state. generally do not use
|
|
@@ -1006,18 +1098,44 @@ class Machine<mDT> {
|
|
|
1006
1098
|
// todo whargarbl implement data stuff
|
|
1007
1099
|
// todo major incomplete whargarbl comeback
|
|
1008
1100
|
if (this.valid_force_transition(newState, newData)) {
|
|
1009
|
-
|
|
1010
|
-
|
|
1101
|
+
|
|
1102
|
+
if (this._has_hooks) {
|
|
1103
|
+
|
|
1104
|
+
let hook_permits : boolean | undefined = undefined;
|
|
1105
|
+
|
|
1106
|
+
const hn : string = hook_name(this._state, newState),
|
|
1107
|
+
maybe_hook : Function | undefined = this._hooks.get(hn);
|
|
1108
|
+
|
|
1109
|
+
if (maybe_hook === undefined) { hook_permits = true; }
|
|
1110
|
+
else { hook_permits = maybe_hook({ from: this._state, to: newState, forced: true }); }
|
|
1111
|
+
|
|
1112
|
+
if (hook_permits !== false) {
|
|
1113
|
+
this._state = newState;
|
|
1114
|
+
return true;
|
|
1115
|
+
} else {
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
} else {
|
|
1120
|
+
|
|
1121
|
+
this._state = newState;
|
|
1122
|
+
return true;
|
|
1123
|
+
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1011
1126
|
} else {
|
|
1012
1127
|
return false;
|
|
1013
1128
|
}
|
|
1129
|
+
|
|
1014
1130
|
}
|
|
1015
1131
|
|
|
1016
1132
|
|
|
1017
1133
|
|
|
1018
1134
|
current_action_for(action: StateType): number {
|
|
1019
1135
|
const action_base: Map<StateType, number> = this._actions.get(action);
|
|
1020
|
-
return action_base
|
|
1136
|
+
return action_base
|
|
1137
|
+
? action_base.get(this.state())
|
|
1138
|
+
: undefined;
|
|
1021
1139
|
}
|
|
1022
1140
|
|
|
1023
1141
|
current_action_edge_for(action: StateType): JssmTransition<mDT> {
|
|
@@ -1081,7 +1199,8 @@ function sm<mDT>(template_strings: TemplateStringsArray, ... remainder /* , argu
|
|
|
1081
1199
|
// string notation, as designed, it's not really worth the hassle
|
|
1082
1200
|
|
|
1083
1201
|
/* eslint-disable prefer-rest-params */
|
|
1084
|
-
(acc, val, idx): string =>
|
|
1202
|
+
(acc, val, idx): string =>
|
|
1203
|
+
`${acc}${remainder[idx-1]}${val}` // arguments[0] is never loaded, so args doesn't need to be gated
|
|
1085
1204
|
/* eslint-enable prefer-rest-params */
|
|
1086
1205
|
|
|
1087
1206
|
)));
|
package/src/ts/jssm_types.ts
CHANGED
|
@@ -305,6 +305,43 @@ type JssmParseFunctionType =
|
|
|
305
305
|
|
|
306
306
|
|
|
307
307
|
|
|
308
|
+
type BasicHookDescription = {
|
|
309
|
+
kind : 'hook'
|
|
310
|
+
from : string,
|
|
311
|
+
to : string,
|
|
312
|
+
handler : Function
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
type HookDescriptionWithAction = {
|
|
316
|
+
kind : 'named',
|
|
317
|
+
from : string,
|
|
318
|
+
to : string,
|
|
319
|
+
action : string,
|
|
320
|
+
handler : Function
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
type EntryHook = {
|
|
324
|
+
kind : 'entry',
|
|
325
|
+
to : string,
|
|
326
|
+
handler : Function
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
type ExitHook = {
|
|
330
|
+
kind : 'exit',
|
|
331
|
+
from : string,
|
|
332
|
+
handler : Function
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
type HookDescription
|
|
336
|
+
= BasicHookDescription
|
|
337
|
+
| HookDescriptionWithAction
|
|
338
|
+
| EntryHook
|
|
339
|
+
| ExitHook;
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
308
345
|
export {
|
|
309
346
|
|
|
310
347
|
JssmColor,
|
|
@@ -341,6 +378,8 @@ export {
|
|
|
341
378
|
JssmMachineInternalState,
|
|
342
379
|
|
|
343
380
|
FslDirection,
|
|
344
|
-
FslTheme
|
|
381
|
+
FslTheme,
|
|
382
|
+
|
|
383
|
+
HookDescription
|
|
345
384
|
|
|
346
385
|
};
|
package/src/ts/jssm_util.ts
CHANGED
|
@@ -90,11 +90,28 @@ const weighted_histo_key: Function = (n: number, opts: Array<any>, prob_prop: st
|
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
const hook_name = (from: string, to: string): string =>
|
|
94
|
+
|
|
95
|
+
JSON.stringify([from, to]);
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
const named_hook_name = (from: string, to: string, action: string): string =>
|
|
102
|
+
|
|
103
|
+
JSON.stringify([from, to, action]);
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
93
109
|
export {
|
|
94
110
|
seq,
|
|
95
111
|
arr_uniq_p,
|
|
96
112
|
histograph, weighted_histo_key,
|
|
97
113
|
weighted_rand_select, weighted_sample_select,
|
|
98
|
-
array_box_if_string
|
|
114
|
+
array_box_if_string,
|
|
115
|
+
hook_name, named_hook_name
|
|
99
116
|
};
|
|
100
117
|
|
|
@@ -5,9 +5,7 @@ import { sm } from '../jssm';
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
describe('Hooks', () => {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
describe('Hooks open and closed in grammar', () => {
|
|
11
9
|
|
|
12
10
|
test.todo('Hooks open doesn\'t throw' /*, () => {
|
|
13
11
|
|
|
@@ -23,6 +21,189 @@ describe('Hooks', () => {
|
|
|
23
21
|
|
|
24
22
|
} */);
|
|
25
23
|
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
describe('Basic hooks on API callpoint', () => {
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
test('Setting a regular hook doesn\'t throw', () => {
|
|
34
|
+
|
|
35
|
+
expect( () => {
|
|
36
|
+
const _foo = sm`a -> b;`;
|
|
37
|
+
_foo.set_hook({ from: 'a', to: 'b', handler: () => console.log('hi'), kind: 'hook' })
|
|
38
|
+
})
|
|
39
|
+
.not.toThrow();
|
|
40
|
+
|
|
41
|
+
} );
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
test('Setting a named hook doesn\'t throw', () => {
|
|
45
|
+
|
|
46
|
+
expect( () => {
|
|
47
|
+
const _foo = sm`a 'foo' -> b;`;
|
|
48
|
+
_foo.set_hook({ from: 'a', to: 'b', handler: () => console.log('hi'), kind: 'named', action: 'foo' })
|
|
49
|
+
})
|
|
50
|
+
.not.toThrow();
|
|
51
|
+
|
|
52
|
+
} );
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
test('Setting a kind of hook that doesn\'t exist throws', () => {
|
|
56
|
+
|
|
57
|
+
expect( () => {
|
|
58
|
+
const _foo = sm`a 'foo' -> b;`;
|
|
59
|
+
_foo.set_hook({ from: 'a', to: 'b', handler: () => console.log('hi'), kind: 'Smaug the Merciless', action: 'foo' } as any)
|
|
60
|
+
})
|
|
61
|
+
.toThrow();
|
|
62
|
+
|
|
63
|
+
} );
|
|
64
|
+
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
test('Basic hook rejection works', () => {
|
|
72
|
+
|
|
73
|
+
const foo = sm`a => b;`;
|
|
74
|
+
|
|
75
|
+
foo.set_hook({ from: 'a', to: 'b', kind: 'hook', handler: () => false });
|
|
76
|
+
expect(foo.transition('b')).toBe(false);
|
|
77
|
+
expect(foo.state()).toBe('a');
|
|
78
|
+
|
|
79
|
+
foo.set_hook({ from: 'a', to: 'b', kind: 'hook', handler: () => true });
|
|
80
|
+
expect(foo.transition('b')).toBe(true);
|
|
81
|
+
expect(foo.state()).toBe('b');
|
|
82
|
+
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
test('Basic hook rejection works on forced edges', () => {
|
|
90
|
+
|
|
91
|
+
const foo = sm`a ~> b ~> c;`;
|
|
92
|
+
|
|
93
|
+
foo.set_hook({ from: 'a', to: 'b', kind: 'hook', handler: () => false });
|
|
94
|
+
expect(foo.force_transition('b')).toBe(false);
|
|
95
|
+
expect(foo.state()).toBe('a');
|
|
96
|
+
|
|
97
|
+
foo.set_hook({ from: 'a', to: 'b', kind: 'hook', handler: () => true });
|
|
98
|
+
expect(foo.force_transition('b')).toBe(true);
|
|
99
|
+
expect(foo.state()).toBe('b');
|
|
100
|
+
|
|
101
|
+
// line completion for when a hook lookup finds nothing
|
|
102
|
+
expect(foo.force_transition('c')).toBe(true);
|
|
103
|
+
expect(foo.state()).toBe('c');
|
|
104
|
+
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
test('Named hook rejection works', () => {
|
|
112
|
+
|
|
113
|
+
const foo = sm`a 'foo' => b;`;
|
|
114
|
+
|
|
115
|
+
foo.set_hook({ from: 'a', to: 'b', action: 'foo', kind: 'named', handler: () => false });
|
|
116
|
+
expect(foo.action('foo')).toBe(false);
|
|
117
|
+
expect(foo.state()).toBe('a');
|
|
118
|
+
|
|
119
|
+
foo.set_hook({ from: 'a', to: 'b', action: 'foo', kind: 'named', handler: () => true });
|
|
120
|
+
expect(foo.action('foo')).toBe(true);
|
|
121
|
+
expect(foo.state()).toBe('b');
|
|
122
|
+
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
test('Named hook rejection doesn\'t block transitions', () => {
|
|
130
|
+
|
|
131
|
+
const foo = sm`a 'foo' => b;`;
|
|
132
|
+
|
|
133
|
+
foo.set_hook({ from: 'a', to: 'b', action: 'foo', kind: 'named', handler: () => false });
|
|
134
|
+
expect(foo.action('foo')).toBe(false);
|
|
135
|
+
expect(foo.state()).toBe('a');
|
|
136
|
+
|
|
137
|
+
expect(foo.transition('b')).toBe(true);
|
|
138
|
+
expect(foo.state()).toBe('b');
|
|
139
|
+
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
describe('Basic hooks on API callpoint', () => {
|
|
147
|
+
|
|
148
|
+
test('Basic hooks call their handler', () => {
|
|
149
|
+
|
|
150
|
+
const handler = jest.fn(x => true),
|
|
151
|
+
uncalled = jest.fn(x => true);
|
|
152
|
+
|
|
153
|
+
expect( () => {
|
|
154
|
+
const _foo = sm`a -> b -> c;`;
|
|
155
|
+
_foo.set_hook({ from: 'a', to: 'b', handler, kind: 'hook' });
|
|
156
|
+
_foo.set_hook({ from: 'b', to: 'a', handler: uncalled, kind: 'hook' });
|
|
157
|
+
_foo.set_hook({ from: 'b', to: 'c', handler: uncalled, kind: 'hook' });
|
|
158
|
+
_foo.transition('b');
|
|
159
|
+
})
|
|
160
|
+
.not.toThrow();
|
|
161
|
+
|
|
162
|
+
// should hook from first, but not from second
|
|
163
|
+
expect(handler.mock.calls.length).toBe(1);
|
|
164
|
+
expect(uncalled.mock.calls.length).toBe(0);
|
|
165
|
+
|
|
166
|
+
} );
|
|
167
|
+
|
|
168
|
+
test('Named hooks call their handler', () => {
|
|
169
|
+
|
|
170
|
+
const handler = jest.fn(x => true),
|
|
171
|
+
uncalled = jest.fn(x => true);
|
|
172
|
+
|
|
173
|
+
expect( () => {
|
|
174
|
+
const _foo = sm`a 'next' -> b 'next' -> c;`;
|
|
175
|
+
_foo.set_hook({ from: 'a', to: 'b', handler, kind: 'named', action: 'next' });
|
|
176
|
+
_foo.set_hook({ from: 'a', to: 'b', handler: uncalled, kind: 'named', action: 'borg' });
|
|
177
|
+
_foo.set_hook({ from: 'b', to: 'a', handler: uncalled, kind: 'named', action: 'next' });
|
|
178
|
+
_foo.action('next');
|
|
179
|
+
_foo.action('next');
|
|
180
|
+
})
|
|
181
|
+
.not.toThrow();
|
|
182
|
+
|
|
183
|
+
// should hook from first, but not from second
|
|
184
|
+
expect(handler.mock.calls.length).toBe(1);
|
|
185
|
+
expect(uncalled.mock.calls.length).toBe(0);
|
|
186
|
+
|
|
187
|
+
} );
|
|
188
|
+
|
|
189
|
+
test('Forced hooks call their handler', () => {
|
|
190
|
+
|
|
191
|
+
const handler = jest.fn(x => true),
|
|
192
|
+
uncalled = jest.fn(x => true);
|
|
193
|
+
|
|
194
|
+
expect( () => {
|
|
195
|
+
const _foo = sm`a ~> b ~> c;`;
|
|
196
|
+
_foo.set_hook({ from: 'a', to: 'b', handler, kind: 'hook' });
|
|
197
|
+
_foo.set_hook({ from: 'b', to: 'a', handler: uncalled, kind: 'hook' });
|
|
198
|
+
_foo.set_hook({ from: 'b', to: 'c', handler: uncalled, kind: 'hook' });
|
|
199
|
+
_foo.force_transition('b');
|
|
200
|
+
})
|
|
201
|
+
.not.toThrow();
|
|
202
|
+
|
|
203
|
+
// should hook from first, but not from second
|
|
204
|
+
expect(handler.mock.calls.length).toBe(1);
|
|
205
|
+
expect(uncalled.mock.calls.length).toBe(0);
|
|
26
206
|
|
|
207
|
+
} );
|
|
27
208
|
|
|
28
209
|
});
|
package/src/ts/version.ts
CHANGED