blueshell
Version:
A Behavior Tree implementation in modern Javascript
184 lines • 8.04 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = require("chai");
const lib_1 = require("../../lib");
const RobotActions_1 = require("../nodes/test/RobotActions");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const parse = require('dotparser');
class ConsumeOnce extends lib_1.Action {
onEvent(state) {
const storage = this.getNodeStorage(state);
if (storage.ateOne) {
delete storage.ateOne;
return lib_1.rc.SUCCESS;
}
else {
storage.ateOne = true;
return lib_1.rc.RUNNING;
}
}
}
describe('renderTree', function () {
context('archy tree', function () {
context('contextDepth', function () {
const testTree = new lib_1.LatchedSequence('root', [
new lib_1.LatchedSequence('0', [
new lib_1.LatchedSequence('0.0', [new ConsumeOnce('0.0.0'), new ConsumeOnce('0.0.1')]),
new lib_1.LatchedSequence('0.1', [new ConsumeOnce('0.1.0'), new ConsumeOnce('0.1.1')]),
]),
new lib_1.LatchedSequence('1', [new ConsumeOnce('1.0'), new ConsumeOnce('1.1')]),
]);
let state = {
errorReason: undefined,
__blueshell: {},
};
beforeEach(function () {
state = {
errorReason: undefined,
__blueshell: {},
};
});
function runContextDepthTest(expectedNodes, expectedEllipses, expectedArrows, contextDepth) {
const render = lib_1.renderTree.toString(testTree, state, contextDepth);
const nodesShown = render.split('\n').length - 1;
const ellipsesShown = getCount(/\.\.\./g);
const arrowsShown = getCount(/=>/g);
chai_1.assert.strictEqual(nodesShown, expectedNodes, 'nodes');
chai_1.assert.strictEqual(ellipsesShown, expectedEllipses, 'ellipses');
chai_1.assert.strictEqual(arrowsShown, expectedArrows, 'arrows');
function getCount(re) {
const matches = render.match(re);
return (matches && matches.length) || 0;
}
}
context('before running', function () {
it('should show everything at unspecified context depth', function () {
runContextDepthTest(11, 0, 0);
});
it('should show nothing at -1 context depth', function () {
runContextDepthTest(0, 0, 0, -1);
});
it('should show root ellipsis at 0 context depth', function () {
runContextDepthTest(1, 1, 0, 0);
});
});
context('after one run', function () {
beforeEach(async function () {
await testTree.handleEvent(state, {});
});
it('should arrow the first path at unspecified context depth', function () {
runContextDepthTest(11, 0, 4);
});
it('should show only the active path at -1 context depth', function () {
runContextDepthTest(4, 0, 4, -1);
});
it('should show only the active path and ellipses at 0 context depth', function () {
runContextDepthTest(7, 3, 4, 0);
});
it('should show only the active path, siblings, and ellipses at 1 context depth', function () {
runContextDepthTest(11, 4, 4, 1);
});
it('should show everything at 2 context depth', function () {
runContextDepthTest(11, 0, 4, 2);
});
});
});
it('should not crash', function (done) {
lib_1.renderTree.toConsole(RobotActions_1.waitAi);
done();
});
it('should generate a tree of nodes without a state', function (done) {
const a = lib_1.renderTree.toString(RobotActions_1.waitAi);
chai_1.assert.ok(a);
chai_1.assert.equal(a.indexOf('shutdownWithWaitAi'), 0);
const expectedWords = [
'(LatchedSelector)',
'Recharge',
'WaitForCooldown',
'EmergencyShutdown',
];
assertWordsInString(a, expectedWords);
chai_1.assert.notOk(a.includes(lib_1.rc.SUCCESS));
chai_1.assert.notOk(a.includes(lib_1.rc.FAILURE));
chai_1.assert.notOk(a.includes(lib_1.rc.RUNNING));
chai_1.assert.notOk(a.includes(lib_1.rc.ERROR));
// console.log(a);
done();
});
it('should generate a tree of nodes with state', function () {
const state = new RobotActions_1.RobotState();
const event = 'testEvent';
state.overheated = true;
RobotActions_1.waitAi.handleEvent(state, event);
const a = lib_1.renderTree.toString(RobotActions_1.waitAi, state);
chai_1.assert.ok(a);
chai_1.assert.equal(a.indexOf('shutdownWithWaitAi'), 0);
const expectedWords = [
'(LatchedSelector)',
lib_1.rc.RUNNING,
'Recharge',
lib_1.rc.FAILURE,
'WaitForCooldown',
lib_1.rc.RUNNING,
'EmergencyShutdown',
];
assertWordsInString(a, expectedWords);
// console.log(a);
});
});
context('dot notation tree', function () {
it('should not crash', function (done) {
lib_1.renderTree.toDotConsole(RobotActions_1.waitAi);
done();
});
it('should generate a dot string without state', function (done) {
const dotString = lib_1.renderTree.toDotString(RobotActions_1.waitAi);
chai_1.assert.notOk(dotString.includes('fillcolor="#4daf4a"')); // SUCCESS
chai_1.assert.notOk(dotString.includes('fillcolor="#984ea3"')); // FAILURE
chai_1.assert.notOk(dotString.includes('fillcolor="#377eb8"')); // RUNNING
chai_1.assert.notOk(dotString.includes('fillcolor="#e41a1c"')); // ERROR
// eslint-disable-next-line no-console
console.log(dotString);
chai_1.assert.doesNotThrow(function () {
parse(dotString);
});
done();
});
it('should generate a digraph string with state', function () {
const state = new RobotActions_1.RobotState();
const event = 'testEvent';
state.overheated = true;
RobotActions_1.waitAi.handleEvent(state, event);
const result = lib_1.renderTree.toDotString(RobotActions_1.waitAi, state);
chai_1.assert.ok(result);
// eslint-disable-next-line no-console
console.log(result);
chai_1.assert.doesNotThrow(function () {
parse(result);
});
});
it('should generate a digraph with custom node', function (done) {
const customLSelector = new CustomLatchedSelector();
const dotString = lib_1.renderTree.toDotString(customLSelector);
// eslint-disable-next-line no-console
console.log(dotString);
chai_1.assert.doesNotThrow(function () {
parse(dotString);
});
done();
});
});
});
function assertWordsInString(s, words) {
for (const word of words) {
const wordPos = s.indexOf(word);
chai_1.assert.isAbove(wordPos, 0, 'Expected to find ' + word);
s = s.substring(wordPos + 1);
}
}
class CustomLatchedSelector extends lib_1.LatchedSelector {
constructor() {
super('Custom', []);
}
}
//# sourceMappingURL=renderTree.test.js.map