Jean-Yves Didier

fixes on statemachine behaviour

This diff is collapsed. Click to expand it.
......@@ -16,11 +16,17 @@ let StateMachine = Component.create(function (obj) {
// dynamic construction: properties are initial state that have properties
// that are tokens and value that are the final state
var initial = "", final = "", transitions = {}, currentState = "", self= this;
// this part is not good with astTokens
var astTokens = {}; // it keeps AST from event logic expressions
var tokenEvents = {}; // it keeps promises for tokens.
var network = {};
let resetTokens = function() {
Object.keys(tokenEvents).forEach( (t) => tokenEvents[t] = false );
};
var addToken = function(t) {
if ( self.slots.indexOf(t) < 0 ) {
self.slot(t, function( s ) {
......@@ -35,12 +41,14 @@ let StateMachine = Component.create(function (obj) {
var setSheet = function(s) {
// we build promise trees using ast
var t;
resetTokens();
if (transitions.hasOwnProperty(s)) {
tokenEvents = {};
for (t in transitions[s]) {
if (transitions[s].hasOwnProperty(t)) {
network = TransitionNetwork.build(astTokens[t],tokenEvents).promise;
network[t] = TransitionNetwork.build(astTokens[s][t],tokenEvents);
/*network = TransitionNetwork.build(astTokens[s][t],tokenEvents).promise;
network.then(
function() {
var token;
......@@ -51,7 +59,7 @@ let StateMachine = Component.create(function (obj) {
// then activate next sheet
setSheet(transitions[s][t]);
}
);
);*/
}
}
}
......@@ -97,7 +105,9 @@ let StateMachine = Component.create(function (obj) {
}
}
astTokens[token] = tsd;
// first fix on transitions
astTokens[start] = astTokens[start] || {};
astTokens[start][token] = tsd;
if (transitions[start] === undefined) {
transitions[start] = {};
......@@ -112,8 +122,17 @@ let StateMachine = Component.create(function (obj) {
*/
this.setToken = function (token) {
if (tokenEvents.hasOwnProperty(token)) {
tokenEvents[token].accept();
//tokenEvents[token].accept();
tokenEvents[token] = true;
}
for (const [key, value] of Object.entries(network)) {
if (value.eval()) {
network = {};
setSheet(transitions[currentState][key]);
}
}
};
/**
* Sets transitions from a list of transitions
......
/* the aim of the transition network is to build a network of promises */
import TokenEvent from './tokenevent.js';
const Leaf = function(tokenEvents,nodeName) {
this.eval = function() {
return tokenEvents[nodeName];
};
};
const OrNode = function(tokenEvents, left, right) {
this.eval = function() {
return left.eval(tokenEvents) || right.eval(tokenEvents);
};
};
const AndNode = function(tokenEvents, left, right) {
this.eval = function(tokenEvents) {
return left.eval(tokenEvents) && left.eval(tokenEvents);
};
};
let TransitionNetwork = function() {
let TransitionNetwork = {};
//let TransitionNetwork = function() {
// object storing token events (that is to say references to promises)
this.promise = {};
/*this.promise = {};
this.and = function(tn) {
this.promise = Promise.all([this.promise, tn.promise]);
......@@ -14,16 +34,41 @@ let TransitionNetwork = function() {
this.or = function(tn) {
this.promise = Promise.race([this.promise, tn.promise]);
return this;
};
};*/
};
//};
TransitionNetwork.build = function(tree, tokenEvents) {
var res;
var tmpTN;
var rightTN;
if (typeof tree === "string") {
tokenEvents[tree] = tokenEvents[tree] ?? false;
return new Leaf(tokenEvents, tree);
}
res = TransitionNetwork.build(tree[0],tokenEvents);
var i;
for (i=1; i < tree.length; i++) {
if (tree[i].hasOwnProperty('and')) {
rightTN = TransitionNetwork.build(tree[i]['and'], tokenEvents);
tmpTN = new AndNode(tokenEvents, res, rightTN);
} else {
if (tree[i].hasOwnProperty('or')) {
rightTN = TransitionNetwork.build(tree[i]['or'], tokenEvents);
tmpTN = new OrNode(tokenEvents, res, rightTN);
} else {
console.warn('[ARCS] Illegal tree');
}
}
res = tmpTN;
}
/*if (typeof tree === "string") {
// here we have a terminal string i.e. a token event
var tokenEvent;
if (tokenEvents.hasOwnProperty(tree)) {
......@@ -51,7 +96,7 @@ TransitionNetwork.build = function(tree, tokenEvents) {
}
}
res = tmpTN;
}
}*/
return res;
};
......
......@@ -7,7 +7,6 @@ let signalHandler = function(chai, utils) {
const MODULE = 'signalHandler';
const FLAG_EXPECTED_SIGNALS = MODULE + "#expectedSignals";
let isComponent = function(obj) {
return Array.isArray(obj.slots) &&
typeof obj.signals === 'object' &&
......@@ -22,14 +21,16 @@ let signalHandler = function(chai, utils) {
);
});
chai.Assertion.addMethod('emit', function(signal) {
chai.Assertion.addMethod('emit', function(signal, options = {}) {
const registeredSignals = utils.flag(this, FLAG_EXPECTED_SIGNALS)
?? utils.flag(this, FLAG_EXPECTED_SIGNALS, [])
?? utils.flag(this, FLAG_EXPECTED_SIGNALS);
options.withArgs = options.withArgs ?? [];
options.count = options.count ?? 0;
registeredSignals.push(signal);
});
registeredSignals.push({ name: signal,options: options });
});
chai.Assertion.addMethod('when',function(slot) {
......@@ -45,24 +46,54 @@ let signalHandler = function(chai, utils) {
let Handler = new ARCS.Component.create(
function() {
this.triggered = false;
this.data = [];
this.callCount = 0;
this.trigger = function() {
this.triggered = true;
this.callCount++;
this.data = arguments;
};
}, ['trigger']
);
let handler = new Handler();
ARCS.Component.connect(obj, signal, handler, "trigger");
obj[slot].call(obj);
ARCS.Component.connect(obj, signal.name, handler, "trigger");
if (Array.isArray(slot)) {
slot.forEach( s => obj[s].call(obj) );
} else {
obj[slot].call(obj);
}
self.assert(
handler.triggered,
"expected #{this} to emit signal "+signal,
"expected #{this} not to emit signal "+signal
"expected #{this} to emit signal "+signal.name,
"expected #{this} not to emit signal "+signal.name
);
signal.options = signal.options ?? {};
if (signal.options.hasOwnProperty("count") && signal.options.count > 0) {
let count = signal.options.count;
self.assert(
handler.callCount === count,
"expected #{this}." + signal.name +" to be emitted " + count + " times",
"expected #{this}." + signal.name +" not to emit signal " +count + " times"
);
}
if (signal.options.hasOwnProperty("withArgs") && signal.options.withArgs.length > 0) {
let args = signal.options.withArgs;
self.assert(
args.length === handler.data.length,
"expected #{this} to have " + args.length + " arguments",
"expected #{this} not to have " + args.length + " arguments"
);
for(let i=0; i < args.length; i++) {
new chai.Assertion(handler.data[i]).to.deep.equal(
args[i],
"expected #{this} to equal \"" + args[i] +"\""
);
}
}
});
});
};
chai.use(signalHandler);
......@@ -144,19 +175,71 @@ describe('statemachine', function() {
expect(sm.slots).to.deep.equal(['setToken', 'next']);
});
// it ('should trigger a single transition', function() {
// let sm = new ARCS.StateMachine();
// sm.setTransitions({
// start: {next:"end"}
// });
// sm.start();
// expect(sm).to.emit('requestSheet').when('next');
//
// // here we should be able to capture the right signal emission and
// // its params.
//
// });
it ('should trigger a single transition', function() {
let sm = new ARCS.StateMachine({
initial: "start",
transitions: {
start: {next:"end"}
}
});
sm.start();
expect(sm).to.emit('requestSheet', { withArgs: ["end"]}).when('next');
});
it ('should trigger the right transition when faning out with the first token', function() {
let sm = new ARCS.StateMachine({
initial: "start",
transitions: {
start: { token1: "state1", token2: "state2" }
}
});
sm.start();
expect(sm).to.emit('requestSheet', { withArgs: ["state1"]}).when("token1");
});
it ('should trigger the right transition when faning out with the second token', function() {
let sm = new ARCS.StateMachine({
initial: "start",
transitions: {
start: { token1: "state1", token2: "state2" }
}
});
sm.start();
expect(sm).to.emit('requestSheet', { withArgs: ["state2"]}).when("token2");
});
it ('should trigger a transition when at least one event of two is fired (version a)', function() {
let sm = new ARCS.StateMachine({
initial: "start",
transitions: {
start: { "a|b" :"end"}
}
});
sm.start();
expect(sm).to.emit('requestSheet', { withArgs: ["end"]}).when('a');
});
it ('should trigger a transition when at least one event of two is fired (version b)', function() {
let sm = new ARCS.StateMachine({
initial: "start",
transitions: {
start: { "a|b" :"end"}
}
});
sm.start();
expect(sm).to.emit('requestSheet', { withArgs: ["end"]}).when('b');
});
it ('should trigger a transition when two events are fired', function() {
let sm = new ARCS.StateMachine({
initial: "start",
transitions: {
start: { "a&b" :"end"}
}
});
sm.start();
expect(sm).to.emit('requestSheet', { withArgs: ["end"]}).when(['a','b']);
});
});
......