UNPKG

blueshell

Version:

A Behavior Tree implementation in modern Javascript

184 lines 8.04 kB
"use strict"; 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