UNPKG

tgi-store-mongodb

Version:
1,787 lines (1,398 loc) 120 kB
*217 model tests applied* ## [&#9664;](#-model)&nbsp;[&#8984;](#constructors)&nbsp;[&#9654;](#-request) &nbsp;Procedure #### Procedure Class The `Procedure` class manages a set of `Command` objects. It provides a pattern for handling asynchronous and synchronous command execution. `Command` objects create and manage the `Procedure` object. #### CONSTRUCTOR &nbsp;<b><i>objects created should be an instance of Procedure:</i></b> ```javascript return new Procedure() instanceof Procedure; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> &nbsp;<b><i>should make sure new operator used:</i></b> ```javascript Procedure(); // jshint ignore:line ``` <blockquote><strong>Error: new operator required</strong> thrown as expected </blockquote> &nbsp;<b><i>should make sure argument properties are valid:</i></b> ```javascript new Procedure({yo: 'whatup'}); ``` <blockquote><strong>Error: error creating Procedure: invalid property: yo</strong> thrown as expected </blockquote> #### PROPERTIES #### tasks Tasks is an array of objects that represent each step of the procedure. See TASKS section below for each property of this unnamed object (task array element). &nbsp;<b><i>tasks can be falsy if no tasks defined otherwise it has to be an array:</i></b> ```javascript new Procedure({tasks: true}); ``` <blockquote><strong>Error: error creating Procedure: tasks is not an array</strong> thrown as expected </blockquote> &nbsp;<b><i>the parameters must be valid for the object in each element of the array:</i></b> ```javascript new Procedure({ tasks: [ {clean: 'room'} ] }); ``` <blockquote><strong>Error: error creating Procedure: invalid task[0] property: clean</strong> thrown as expected </blockquote> #### tasksNeeded Total tasks that will execute (does not include skipped tasks). _See Integration Tests for usage_ #### tasksCompleted Number of tasks completed and started (does not include skipped tasks) _See Integration Tests for usage_ #### TASKS Each element of the array tasks is an object with the following properties: #### label optional label for this task element &nbsp;<b><i>if used it must be a string:</i></b> ```javascript new Procedure({ tasks: [ {label: true} ] }); ``` <blockquote><strong>Error: error creating Procedure: task[0].label must be string</strong> thrown as expected </blockquote> &nbsp;<b><i>shorthand version:</i></b> ```javascript new Procedure([function () { }]); ``` #### command Command to execute for this task &nbsp;<b><i>if used it must be a `Command`:</i></b> ```javascript new Procedure({ tasks: [ {command: true} ] }); ``` <blockquote><strong>Error: error creating Procedure: task[0].command must be a Command object</strong> thrown as expected </blockquote> #### requires Establish other tasks that must be complete before this task is executed. Pass as array of or single element. Can be string(for label label) or number(for array index). Use -1 for previous task, null for no dependencies &nbsp;<b><i>test it:</i></b> ```javascript this.shouldThrowError(Error('invalid type for requires in task[0]'), function () { new Procedure({ tasks: [ {requires: new Date()} ] }); }); // if number supplied it is index in array this.shouldThrowError(Error('missing task #1 for requires in task[0]'), function () { new Procedure({ tasks: [ {command: new Procedure({}), requires: 1} ] }); }); this.shouldThrowError(Error('task #-2 invalid requires in task[0]'), function () { new Procedure({ tasks: [ {command: new Procedure({}), requires: -2} ] }); }); // requires defaults to -1 which means the previous element in the array so essentially the default // is sequential processing. Set to null for no dependencies which makes it asynchronous -1 means // previous element is ignored for first index and is the default var proc = new Procedure({ tasks: [ {command: new Command({})} ] }); this.shouldBeTrue(proc.tasks[0].requires == -1); ``` #### METHODS #### getObjectStateErrors &nbsp;<b><i>should return array of validation errors:</i></b> ```javascript if (!new Procedure().getObjectStateErrors()) return 'falsy'; ``` <blockquote>returns <strong>falsy</strong> as expected </blockquote> #### INTEGRATION &nbsp;<b><i>synchronous sequential tasks are the default when tasks has no requires property:</i></b> ```javascript var cmd = new Command({ name: 'cmdProcedure', type: 'Procedure', contents: new Procedure({ tasks: [ { command: new Command({ type: 'Function', contents: function () { var self = this; setTimeout(function () { self._parentProcedure.bucket += '1'; self.complete(); }, 250); // delayed to test that order is maintained } }) }, { command: new Command({ type: 'Function', contents: function () { this._parentProcedure.bucket += '2'; this.complete(); } }) }, function () { // shorthand version of command function ... this._parentProcedure.bucket += '3'; this.complete(); } ] }) }); cmd.onEvent('*', function (event) { if (event == 'Completed') callback(cmd.bucket); }); cmd.bucket = 'abc'; cmd.execute(); ``` <blockquote>returns <strong>abc123</strong> as expected </blockquote> &nbsp;<b><i>async tasks are designated when requires is set to null:</i></b> ```javascript var execCount = 0; // Call twice to test reset state var cmd = new Command({ name: 'cmdProcedure', type: 'Procedure', contents: new Procedure({ tasks: [ { command: new Command({ type: 'Function', contents: function () { var self = this; setTimeout(function () { self._parentProcedure.bucket += ' mo'; self.complete(); }, 50); // This will be done last } }) }, { requires: null, // no wait to run this command: new Command({ type: 'Function', contents: function () { this._parentProcedure.bucket += ' miney'; this.complete(); } }) } ] }) }); cmd.onEvent('*', function (event) { if (event == 'Completed') { if (execCount++ < 2) { cmd.execute(); } else { callback(cmd.bucket); } } }); cmd.bucket = 'eenie meenie'; execCount++; cmd.execute(); ``` <blockquote>returns <strong>eenie meenie miney mo miney mo</strong> as expected </blockquote> &nbsp;<b><i>this example shows multiple dependencies:</i></b> ```javascript var cmd = new Command({ name: 'cmdProcedure', type: 'Procedure', contents: new Procedure({ tasks: [ { command: new Command({ type: 'Function', contents: function () { var self = this; setTimeout(function () { self._parentProcedure.bucket += ' rock'; self.complete(); }, 300); } }) }, { requires: null, // no wait to run this label: 'sex', command: new Command({ type: 'Function', contents: function () { var self = this; setTimeout(function () { self._parentProcedure.bucket += ' sex'; self.complete(); }, 200); } }) }, { requires: null, // no wait to run this label: 'drugs', command: new Command({ type: 'Function', contents: function () { var self = this; setTimeout(function () { self._parentProcedure.bucket += ' drugs'; self.complete(); }, 100); } }) }, { requires: ['sex', 'drugs', 0], // need these labels and array index 0 command: new Command({ type: 'Function', contents: function () { this._parentProcedure.bucket += ' & roll'; this.complete(); } }) } ] }) }); cmd.onEvent('*', function (event) { if (event == 'Completed') callback(cmd.bucket); }); cmd.bucket = 'todo:'; cmd.execute(); ``` <blockquote>returns <strong>todo: drugs sex rock & roll</strong> as expected </blockquote> ## [&#9664;](#-procedure)&nbsp;[&#8984;](#constructors)&nbsp;[&#9654;](#-store) &nbsp;Request Requests handle the Request / Response design pattern. They are used by the Interface class to communicate with the Application Model #### CONSTRUCTOR &nbsp;<b><i>objects created should be an instance of Request:</i></b> ```javascript return new Request('Null') instanceof Request; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> &nbsp;<b><i>should make sure new operator used:</i></b> ```javascript Request('Null'); // jshint ignore:line ``` <blockquote><strong>Error: new operator required</strong> thrown as expected </blockquote> &nbsp;<b><i>request type must be specified:</i></b> ```javascript new Request(); ``` <blockquote><strong>Error: Request type required</strong> thrown as expected </blockquote> &nbsp;<b><i>simple string parameter creates request of named type:</i></b> ```javascript return new Request('example').type; ``` <blockquote>returns <strong>example</strong> as expected </blockquote> &nbsp;<b><i>type can be specified when object passed:</i></b> ```javascript return new Request({type: 'example'}).type; ``` <blockquote>returns <strong>example</strong> as expected </blockquote> &nbsp;<b><i>Command type requests expect contents to contain a command object:</i></b> ```javascript return new Request({type: 'Command'}); ``` <blockquote><strong>Error: command object required</strong> thrown as expected </blockquote> &nbsp;<b><i>correct version:</i></b> ```javascript return new Request({type: 'Command', command: new Command()}); ``` <blockquote>returns <strong>Command Request: Stub Command: a command</strong> as expected </blockquote> #### METHODS #### toString() &nbsp;<b><i>should return a description of the Request:</i></b> ```javascript return new Request('Null').toString(); ``` <blockquote>returns <strong>Null Request</strong> as expected </blockquote> ## [&#9664;](#-request)&nbsp;[&#8984;](#constructors)&nbsp;[&#9654;](#-text) &nbsp;Store The store class is used for object persistence. #### CONSTRUCTOR &nbsp;<b><i>objects created should be an instance of Store:</i></b> ```javascript return new SurrogateStore() instanceof Store; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> &nbsp;<b><i>should make sure new operator used:</i></b> ```javascript SurrogateStore(); // jshint ignore:line ``` <blockquote><strong>Error: new operator required</strong> thrown as expected </blockquote> &nbsp;<b><i>should make sure properties are valid:</i></b> ```javascript new SurrogateStore({food: 'twinkies'}); ``` <blockquote><strong>Error: error creating Store: invalid property: food</strong> thrown as expected </blockquote> #### PROPERTIES #### name &nbsp;<b><i>name of store can be set in constructor:</i></b> ```javascript return new SurrogateStore({name: 'punchedCards'}).name; ``` <blockquote>returns <strong>punchedCards</strong> as expected </blockquote> #### storeType storeType defaults to Store Class Name but can be set to suite the app architecture. &nbsp;<b><i>storeType can be set in constructor:</i></b> ```javascript return new SurrogateStore({storeType: 'legacyStorage'}).storeType; ``` <blockquote>returns <strong>legacyStorage</strong> as expected </blockquote> #### METHODS &nbsp;<b><i>getServices() returns an object with interface for the Store.:</i></b> ```javascript this.log(JSON.stringify(services)); this.shouldBeTrue(services instanceof Object); this.shouldBeTrue(typeof services['isReady'] == 'boolean'); // don't use until this.shouldBeTrue(typeof services['canGetModel'] == 'boolean'); // define all allowed methods... this.shouldBeTrue(typeof services['canPutModel'] == 'boolean'); this.shouldBeTrue(typeof services['canDeleteModel'] == 'boolean'); this.shouldBeTrue(typeof services['canGetList'] == 'boolean'); ``` <blockquote><strong>log: </strong>{"isReady":false,"canGetModel":false,"canPutModel":false,"canDeleteModel":false,"canGetList":false}<br></blockquote> #### toString() &nbsp;<b><i>should return a description of the Store:</i></b> ```javascript var cStore = new SurrogateStore(); this.log(cStore.toString()); cStore.name = '7-Eleven'; cStore.storeType = 'ConvenienceStore'; this.log(cStore.toString()); return cStore.toString(); ``` <blockquote><strong>log: </strong>a Store<br><strong>log: </strong>ConvenienceStore: 7-Eleven<br>returns <strong>ConvenienceStore: 7-Eleven</strong> as expected </blockquote> #### onConnect() &nbsp;<b><i>must pass url string:</i></b> ```javascript new SurrogateStore().onConnect(); ``` <blockquote><strong>Error: argument must a url string</strong> thrown as expected </blockquote> &nbsp;<b><i>must pass callback function:</i></b> ```javascript new SurrogateStore().onConnect(""); ``` <blockquote><strong>Error: argument must a callback</strong> thrown as expected </blockquote> see integration test for Store #### getModel() &nbsp;<b><i>getModel() is not implemented for virtual class:</i></b> ```javascript new SurrogateStore().getModel(); ``` <blockquote><strong>Error: Store does not provide getModel</strong> thrown as expected </blockquote> #### putModel(model) &nbsp;<b><i>putModel() is not implemented for virtual class:</i></b> ```javascript new SurrogateStore().putModel(); ``` <blockquote><strong>Error: Store does not provide putModel</strong> thrown as expected </blockquote> #### deleteModel(model) &nbsp;<b><i>deleteModel() is not implemented for virtual class:</i></b> ```javascript new SurrogateStore().deleteModel(); ``` <blockquote><strong>Error: Store does not provide deleteModel</strong> thrown as expected </blockquote> #### getList(list, filter, [optional order], callback) This method will clear and populate the list with collection from store. The **filter** property can be used to query the store. The **order** property can specify the sort order of the list. _See integration test for more info._ #### getViewList(list, filter, [optional order], callback) This method provides getList() for View type Lists. _See integration test for more info._ #### Store Integration &nbsp;<b><i>Check each type:</i></b> ```javascript var self = this; spec.integrationStore = new SurrogateStore(); // If store is not ready then get out... if (!spec.integrationStore.getServices().isReady) { self.log('Store is not ready.'); callback(true); return; } self.Types = function () { Model.call(this, { modelType: '_tempTypes', attributes: [ new Attribute({name: 'String', type: 'String', value: 'cheese'}), new Attribute({name: 'Date', type: 'Date', value: new Date()}), new Attribute({name: 'Boolean', type: 'Boolean', value: true}), new Attribute({name: 'Number', type: 'Number', value: 42}) ] }); }; self.Types.prototype = Object.create(Model.prototype); self.types = new self.Types(); self.types2 = new self.Types(); self.types2.copy(self.types); spec.integrationStore.putModel(self.types, function (model, error) { if (typeof error != 'undefined') { callback(error); return; } self.shouldBeTrue(model.get('String') == self.types2.get('String')); self.shouldBeTrue(model.get('Date') == self.types2.get('Date')); self.shouldBeTrue(model.get('Date') instanceof Date); self.shouldBeTrue(model.get('Boolean') == self.types2.get('Boolean')); self.shouldBeTrue(model.get('Number') == self.types2.get('Number')); callback(true); }); ``` <blockquote><strong>log: </strong>Store is not ready.<br>returns <strong>true</strong> as expected </blockquote> ## [&#9664;](#-store)&nbsp;[&#8984;](#constructors)&nbsp;[&#9654;](#-transport) &nbsp;Text #### Text Class Text is used to allow display and setting of application / user text. #### CONSTRUCTOR &nbsp;<b><i>objects created should be an instance of Text:</i></b> ```javascript return new Text('Null') instanceof Text; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> &nbsp;<b><i>should make sure new operator used:</i></b> ```javascript Text('Null'); // jshint ignore:line ``` <blockquote><strong>Error: new operator required</strong> thrown as expected </blockquote> #### METHODS #### toString() &nbsp;<b><i>should return a description of the Text:</i></b> ```javascript return new Text('me').toString(); ``` <blockquote>returns <strong>Text: 'me'</strong> as expected </blockquote> #### get() &nbsp;<b><i>return value:</i></b> ```javascript return new Text('yo').get(); ``` <blockquote>returns <strong>yo</strong> as expected </blockquote> #### set() &nbsp;<b><i>set value:</i></b> ```javascript var who = new Text('Me'); who.set('You'); return who.get(); ``` <blockquote>returns <strong>You</strong> as expected </blockquote> #### onEvent Use onEvent(events,callback) &nbsp;<b><i>first parameter is a string or array of event subscriptions:</i></b> ```javascript new Text('').onEvent(); ``` <blockquote><strong>Error: subscription string or array required</strong> thrown as expected </blockquote> &nbsp;<b><i>callback is required:</i></b> ```javascript new Text('').onEvent([]); ``` <blockquote><strong>Error: callback is required</strong> thrown as expected </blockquote> &nbsp;<b><i>events are checked against known types:</i></b> ```javascript new Text('').onEvent(['onDrunk'], function () { }); ``` <blockquote><strong>Error: Unknown command event: onDrunk</strong> thrown as expected </blockquote> &nbsp;<b><i>here is a working version:</i></b> ```javascript new Text('').onEvent(['StateChange'], function () { }); ``` #### offEvents Free all onEvent listeners &nbsp;<b><i>example:</i></b> ```javascript new Text('').offEvent(); ``` ## [&#9664;](#-text)&nbsp;[&#8984;](#constructors)&nbsp;[&#9654;](#-view) &nbsp;Transport Handle message passing between host and UI. TODO: run these tests in node-make-spec-md with io defined Read the source until then... https://github.com/tgi-io/tgi-core/blob/master/lib/core/tgi-core-transport.spec.js ## [&#9664;](#-transport)&nbsp;[&#8984;](#constructors)&nbsp;[&#9654;](#-replinterface) &nbsp;View #### View Does stuff #### CONSTRUCTOR &nbsp;<b><i>objects created should be an instance of View:</i></b> ```javascript return new View(new Model(), {}, []) instanceof View; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> &nbsp;<b><i>should make sure new operator used:</i></b> ```javascript View(); // jshint ignore:line ``` <blockquote><strong>Error: new operator required</strong> thrown as expected </blockquote> &nbsp;<b><i>first parameter is primary model:</i></b> ```javascript new View(); ``` <blockquote><strong>Error: argument must be a Model</strong> thrown as expected </blockquote> &nbsp;<b><i>second parameter is object with related models:</i></b> ```javascript new View(new Model()); ``` <blockquote><strong>Error: object expected</strong> thrown as expected </blockquote> &nbsp;<b><i>third parameter is array of attributes making up view:</i></b> ```javascript new View(new Model(), {}); ``` <blockquote><strong>Error: array of attributes expected</strong> thrown as expected </blockquote> &nbsp;<b><i>related models are named objects with id & model:</i></b> ```javascript this.shouldThrowError('Error: relatedModel key values expect object', function () { new View(new Model(), {eat: 'me'}, []); }); this.shouldThrowError('Error: relatedModel key values expect object with id key', function () { new View(new Model(), {eat: {}}, []); }); this.shouldThrowError('Error: relatedModel key values expect object with model key', function () { new View(new Model(), {eat: {id: 1}}, []); }); this.shouldThrowError('Error: relatedModel id must be a Attribute', function () { new View(new Model(), {eat: {id: 1, model: new Model()}}, []); }); this.shouldThrowError('Error: relatedModel model must be a Model', function () { new View(new Model(), {eat: {id: new Attribute({name: 'eatID'}), model: 2}}, []); }); ``` &nbsp;<b><i>attributes must be valid attribute:</i></b> ```javascript new View(new Model(), {}, ['this is so wrong']); ``` <blockquote><strong>Error: attribute array must contain Attributes</strong> thrown as expected </blockquote> &nbsp;<b><i>attributes must be valid attribute:</i></b> ```javascript new View(new Model(), {}, [new Attribute({name: 'x'})]); ``` <blockquote><strong>Error: attribute array must contain Attributes with model references</strong> thrown as expected </blockquote> #### METHODS #### toString() &nbsp;<b><i>should return a description of the view:</i></b> ```javascript return new View(new Model(), {}, []).toString(); ``` <blockquote>returns <strong>a Model View</strong> as expected </blockquote> ## [&#9664;](#-view)&nbsp;[&#8984;](#constructors)&nbsp;[&#9654;](#-application) &nbsp;REPLInterface #### REPLInterface The REPLInterface is a Read Evaluate Print Loop Interface. #### CONSTRUCTOR TODO: //spec.runnerInterfaceConstructor(REPLInterface); TODO: //spec.runnerInterfaceMethods(REPLInterface); #### METHODS The REPLInterface defines adds the following methods. evaluateInput(line) &nbsp;<b><i>called when line of input available:</i></b> ```javascript return typeof REPLInterface.prototype.evaluateInput; ``` <blockquote>returns <strong>function</strong> as expected </blockquote> &nbsp;<b><i>if no input state error generated:</i></b> ```javascript ``` captureOutput(callback) &nbsp;<b><i>called when line of input available:</i></b> ```javascript return typeof REPLInterface.prototype.captureOutput; ``` <blockquote>returns <strong>function</strong> as expected </blockquote> capturePrompt(callback) &nbsp;<b><i>called when line of input available:</i></b> ```javascript return typeof REPLInterface.prototype.capturePrompt; ``` <blockquote>returns <strong>function</strong> as expected </blockquote> #### INTEGRATION &nbsp;<b><i>user queries:</i></b> ```javascript var repl = new REPLInterface(); var app = new Application({interface: repl}); var ex = this; repl.captureOutput(function (text) { ex.log('out> ' + text); //console.log('out> ' + text); }); repl.evaluateInput('input ignored if no context for it'); var input = function (text) { ex.log('in> ' + text); //console.log('in> ' + text); repl.evaluateInput(text); }; /** * test per function */ var ok1 = function () { app.ok('This is a test.', function () { yesno1(); }); input('whatever'); }; var yesno1 = function () { app.yesno('Are we having fun?', function (answer) { if (answer) { callback(answer); } else { yesno2(); } }); input('nope'); // this will be ignored input('n'); // this will be ignored }; var yesno2 = function () { app.yesno('Should I continue?', function (answer) { if (answer) { ask1(); } else { callback(answer); } }); input('yeppers'); // this will be ignored input('y'); }; var ask1 = function () { app.ask('What is your name?', function (answer) { repl.info('Nice to meet you ' + answer + '.'); if (answer == 'Sean') { choose1(); } else { callback(answer); } }); input('Sean'); }; var choose1 = function () { app.choose('Pick one...', ['Eenie', 'Meenie', 'Miney', 'Moe'], function (choice) { if (choice == 1) callback('done'); else callback(choice); }); input('m'); // first partial match }; /** * Start the first test */ ok1(); ``` <blockquote><strong>log: </strong>out> input ignored: input ignored if no context for it<br><strong>log: </strong>out> This is a test.<br><strong>log: </strong>in> whatever<br><strong>log: </strong>in> nope<br><strong>log: </strong>out> yes or no response required<br><strong>log: </strong>in> n<br><strong>log: </strong>in> yeppers<br><strong>log: </strong>out> yes or no response required<br><strong>log: </strong>in> y<br><strong>log: </strong>in> Sean<br><strong>log: </strong>out> Nice to meet you Sean.<br><strong>log: </strong>out> Pick one...<br><strong>log: </strong>out> Eenie<br><strong>log: </strong>out> Meenie<br><strong>log: </strong>out> Miney<br><strong>log: </strong>out> Moe<br><strong>log: </strong>in> m<br>returns <strong>done</strong> as expected </blockquote> &nbsp;<b><i>app navigation:</i></b> ```javascript var repl = new REPLInterface(); var app = new Application({interface: repl}); var ex = this; repl.captureOutput(function (text) { ex.log('out> ' + text); //console.log('out> ' + text); }); var input = function (text) { ex.log('in> ' + text); //console.log('in> ' + text); repl.evaluateInput(text); }; var answer = ''; var rockCommand = new Command({ name: 'Rock', type: 'Function', contents: function () { answer += 'Rock'; } }); var paperCommand = new Command({ name: 'Paper', type: 'Function', contents: function () { answer += 'Paper'; } }); var scissorsCommand = new Command({ name: 'Scissors', type: 'Function', contents: function () { answer += 'Scissors'; } }); var seeYouCommand = new Command({ name: 'SeeYou', type: 'Function', contents: function () { callback(answer); } }); var menu = new Presentation(); menu.set('name', 'Public Menu'); menu.set('contents', [ 'Strings are ignored', new Attribute({name: 'ignoredAlso'}), rockCommand, paperCommand, scissorsCommand, seeYouCommand ]); app.setPresentation(menu); app.start(function () { ex.log('app got stuff: ' + JSON.stringify(stuff)); //console.log('app got stuff: ' + JSON.stringify(stuff)); }); input('Rockaby'); input('r'); input('p'); input('s'); input('se'); ``` <blockquote><strong>log: </strong>in> Rockaby<br><strong>log: </strong>out> "Rockaby" not valid<br><strong>log: </strong>in> r<br><strong>log: </strong>in> p<br><strong>log: </strong>in> s<br><strong>log: </strong>in> se<br><strong>log: </strong>out> Rock, Paper, Scissors, SeeYou<br><strong>log: </strong>out> Rock, Paper, Scissors, SeeYou<br><strong>log: </strong>out> Rock, Paper, Scissors, SeeYou<br><strong>log: </strong>out> Rock, Paper, Scissors, SeeYou<br><strong>log: </strong>out> Rock, Paper, Scissors, SeeYou<br><strong>log: </strong>out> Rock, Paper, Scissors, SeeYou<br>returns <strong>RockPaperScissors</strong> as expected </blockquote> ## [&#9664;](#-replinterface)&nbsp;[&#8984;](#constructors)&nbsp;[&#9654;](#-log) &nbsp;Application #### CONSTRUCTOR &nbsp;<b><i>objects created should be an instance of Application:</i></b> ```javascript return new Application() instanceof Application; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> *29 model tests applied* &nbsp;<b><i>argument property interface will invoke setInterface method:</i></b> ```javascript var myInterface = new Interface(); var myApplication = new Application({interface: myInterface}); return (myApplication.getInterface() === myInterface); ``` <blockquote>returns <strong>true</strong> as expected </blockquote> #### ATTRIBUTES Application extends model and inherits the attributes property. All Application objects have the following attributes: &nbsp;<b><i>following attributes are defined::</i></b> ```javascript var presentation = new Application(); // default attributes and values this.shouldBeTrue(presentation.get('name') === 'newApp'); this.shouldBeTrue(presentation.get('brand') === 'NEW APP'); ``` #### METHODS #### setInterface(interface) Setting the interface for the application determines the primary method of user interaction. &nbsp;<b><i>must supply Interface object:</i></b> ```javascript new Application().setInterface(); ``` <blockquote><strong>Error: instance of Interface a required parameter</strong> thrown as expected </blockquote> #### getInterface() returns primary user interface for application &nbsp;<b><i>default is undefined:</i></b> ```javascript return new Application().getInterface() === undefined; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> &nbsp;<b><i>returns value set by set Interface:</i></b> ```javascript var myInterface = new Interface(); var myApplication = new Application(); myApplication.setInterface(myInterface); return (myApplication.getInterface() === myInterface); ``` <blockquote>returns <strong>true</strong> as expected </blockquote> #### setPresentation(presentation) Setting the presentation for the application determines the primary commands available to the user. &nbsp;<b><i>must supply Presentation object:</i></b> ```javascript new Application().setPresentation(); ``` <blockquote><strong>Error: instance of Presentation a required parameter</strong> thrown as expected </blockquote> #### getPresentation() returns primary user presentation for application &nbsp;<b><i>default is undefined:</i></b> ```javascript return new Application().getPresentation() === undefined; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> &nbsp;<b><i>returns value set by set Presentation:</i></b> ```javascript var myPresentation = new Presentation(); var myApplication = new Application(); myApplication.setPresentation(myPresentation); return (myApplication.getPresentation() === myPresentation); ``` <blockquote>returns <strong>true</strong> as expected </blockquote> #### start() The start method executes the application. &nbsp;<b><i>must set interface before starting:</i></b> ```javascript new Application().start(); ``` <blockquote><strong>Error: error starting application: interface not set</strong> thrown as expected </blockquote> &nbsp;<b><i>callback parameter required:</i></b> ```javascript new Application({interface: new Interface()}).start(); ``` <blockquote><strong>Error: callback required</strong> thrown as expected </blockquote> #### dispatch() The dispatch method will accept a request and act on it or pass it to the app. &nbsp;<b><i>must pass a Request object:</i></b> ```javascript new Application().dispatch(); ``` <blockquote><strong>Error: Request required</strong> thrown as expected </blockquote> &nbsp;<b><i>send command without callback when no response needed:</i></b> ```javascript var ex = this; new Application().dispatch(new Request({ type: 'Command', command: new Command(function () { ex.log('PEACE'); }) })); ``` <blockquote><strong>log: </strong>PEACE<br></blockquote> &nbsp;<b><i>optional second parameter is the response callback:</i></b> ```javascript new Application().dispatch(new Request({type: 'Command', command: new Command()}), true); ``` <blockquote><strong>Error: response callback is not a function</strong> thrown as expected </blockquote> #### info(text) Display info to user in background of primary presentation. &nbsp;<b><i>must set interface before invoking:</i></b> ```javascript new Application().info(); // see Interface for more info ``` <blockquote><strong>Error: interface not set</strong> thrown as expected </blockquote> #### done(text) Display done to user in background of primary presentation. &nbsp;<b><i>must set interface before invoking:</i></b> ```javascript new Application().done(); // see Interface for more info ``` <blockquote><strong>Error: interface not set</strong> thrown as expected </blockquote> #### warn(text) Display info to user in background of primary presentation. &nbsp;<b><i>must set interface before invoking:</i></b> ```javascript new Application().warn(); // see Interface for more info ``` <blockquote><strong>Error: interface not set</strong> thrown as expected </blockquote> #### err(text) Display info to user in background of primary presentation. &nbsp;<b><i>must set interface before invoking:</i></b> ```javascript new Application().err(); // see Interface for more info ``` <blockquote><strong>Error: interface not set</strong> thrown as expected </blockquote> #### ok(prompt, callback) Pause before proceeding &nbsp;<b><i>must set interface before invoking:</i></b> ```javascript new Application().ok(); ``` <blockquote><strong>Error: interface not set</strong> thrown as expected </blockquote> &nbsp;<b><i>must provide the text prompt param:</i></b> ```javascript new Application({interface: new Interface()}).ok(); ``` <blockquote><strong>Error: prompt required</strong> thrown as expected </blockquote> &nbsp;<b><i>must provide callback param:</i></b> ```javascript new Application({interface: new Interface()}).ok('You are about to enter the twilight zone.'); ``` <blockquote><strong>Error: callback required</strong> thrown as expected </blockquote> #### yesno(prompt, callback) Query user with a yes no question. &nbsp;<b><i>must set interface before invoking:</i></b> ```javascript new Application().yesno(); ``` <blockquote><strong>Error: interface not set</strong> thrown as expected </blockquote> &nbsp;<b><i>must provide the text question param:</i></b> ```javascript new Application({interface: new Interface()}).yesno(); ``` <blockquote><strong>Error: prompt required</strong> thrown as expected </blockquote> &nbsp;<b><i>must provide callback param:</i></b> ```javascript new Application({interface: new Interface()}).yesno('ok?'); ``` <blockquote><strong>Error: callback required</strong> thrown as expected </blockquote> #### ask(prompt, attribute, callback) Simple single item prompt. &nbsp;<b><i>must set interface before invoking:</i></b> ```javascript new Application().ask(); ``` <blockquote><strong>Error: interface not set</strong> thrown as expected </blockquote> &nbsp;<b><i>must provide the text question param:</i></b> ```javascript new Application({interface: new Interface()}).ask(); ``` <blockquote><strong>Error: prompt required</strong> thrown as expected </blockquote> &nbsp;<b><i>next param is attribute or callback:</i></b> ```javascript new Application({interface: new Interface()}).ask('sup'); ``` <blockquote><strong>Error: attribute or callback expected</strong> thrown as expected </blockquote> &nbsp;<b><i>must provide callback param:</i></b> ```javascript new Application({interface: new Interface()}). ask('Please enter your name', new Attribute({name: 'Name'})); ``` <blockquote><strong>Error: callback required</strong> thrown as expected </blockquote> #### choose prompt to choose an item &nbsp;<b><i>must set interface before invoking:</i></b> ```javascript new Application().choose(); ``` <blockquote><strong>Error: interface not set</strong> thrown as expected </blockquote> &nbsp;<b><i>must provide text prompt first:</i></b> ```javascript new Application({interface: new Interface()}).choose(); ``` <blockquote><strong>Error: prompt required</strong> thrown as expected </blockquote> &nbsp;<b><i>must supply array of choices:</i></b> ```javascript var myApplication = new Application({interface: new Interface()}); this.shouldThrowError(Error('choices array required'), function () { myApplication.choose('What it do'); }); this.shouldThrowError(Error('choices array required'), function () { myApplication.choose('this will not', 'work'); }); this.shouldThrowError(Error('choices array empty'), function () { myApplication.choose('empty array?', []); }); ``` &nbsp;<b><i>must provide callback param:</i></b> ```javascript var myApplication = new Application(); myApplication.setInterface(new Interface()); myApplication.choose('choose wisely', ['rock', 'paper', 'scissors']); ``` <blockquote><strong>Error: callback required</strong> thrown as expected </blockquote> #### Application Integration &nbsp;<b><i>minimal app:</i></b> ```javascript // Here is our app var ui = new Interface(); var app = new Application(); app.setInterface(ui); app.start(console.log); // define command to satisfy test var helloWorldCommand = new Command(function () { callback('hello world'); }); // mock ui command request - this will get executed by app directly ui.mockRequest(new Request({type: 'Command', command: helloWorldCommand})); ``` <blockquote>returns <strong>hello world</strong> as expected </blockquote> &nbsp;<b><i>little app with command execution mocking:</i></b> ```javascript // todo delamify this // Send 4 mocks and make sure we get 4 callback calls var self = this; self.callbackCount = 0; var app = new Application(); var testInterface = new Interface(); var testPresentation = new Presentation(); app.setInterface(testInterface); app.setPresentation(testPresentation); app.start(function (request) { if (request.type == 'mock count') self.callbackCount++; if (self.callbackCount > 3) callback(true); }); var cmds = []; var i; for (i = 0; i < 4; i++) { cmds.push(new Request('mock count')); } testInterface.mockRequest(cmds); ``` <blockquote>returns <strong>true</strong> as expected </blockquote> ## [&#9664;](#-application)&nbsp;[&#8984;](#constructors)&nbsp;[&#9654;](#-presentation) &nbsp;Log #### Log Model Multi purpose log model. #### CONSTRUCTOR &nbsp;<b><i>objects created should be an instance of Workspace:</i></b> ```javascript return new Log() instanceof Log; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> *29 model tests applied* #### ATTRIBUTES &nbsp;<b><i>following attributes are defined::</i></b> ```javascript var log = new Log('what up'); // default attributes and values this.shouldBeTrue(log.get('id') !== undefined); this.shouldBeTrue(log.get('dateLogged') instanceof Date); this.log(log.get('dateLogged')); this.shouldBeTrue(log.get('logType') == 'Text'); this.shouldBeTrue(log.get('importance') == 'Info'); this.shouldBeTrue(log.get('contents') == 'what up'); ``` <blockquote><strong>log: </strong>Wed Aug 30 2017 18:24:59 GMT-0400 (EDT)<br></blockquote> #### LOG TYPES &nbsp;<b><i>must be valid:</i></b> ```javascript this.log('T.getLogTypes()'); new Log({logType: 'wood'}); // default attributes and values ``` <blockquote><strong>log: </strong>T.getLogTypes()<br><strong>Error: Unknown log type: wood</strong> thrown as expected </blockquote> &nbsp;<b><i>Text simple text message:</i></b> ```javascript return new Log('sup'); ``` <blockquote>returns <strong>Info: sup</strong> as expected </blockquote> &nbsp;<b><i>Delta logged Delta (see in Core):</i></b> ```javascript var delta = new Delta(new Attribute.ModelID(new Model())); return new Log({logType: 'Delta', contents: delta}).toString(); ``` <blockquote>returns <strong>Info: (delta)</strong> as expected </blockquote> ## [&#9664;](#-log)&nbsp;[&#8984;](#constructors)&nbsp;[&#9654;](#-session) &nbsp;Presentation #### Presentation Model The Presentation Model represents the way in which a model is to be presented to the user. The specific Interface object will represent the model data according to the Presentation object. #### CONSTRUCTOR &nbsp;<b><i>objects created should be an instance of Presentation:</i></b> ```javascript return new Presentation() instanceof Presentation; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> *29 model tests applied* #### PROPERTIES #### model This is a model instance for the presentation instance. #### validationErrors &nbsp;<b><i>Array of errors:</i></b> ```javascript this.shouldBeTrue(new Presentation().validationErrors instanceof Array); this.shouldBeTrue(new Presentation().validationErrors.length === 0); ``` #### validationMessage &nbsp;<b><i>string description of error(s):</i></b> ```javascript return new Presentation().validationMessage; ``` #### preRenderCallback preRenderCallback can be set to prepare presentation prior to Interface render #### ATTRIBUTES Presentation extends model and inherits the attributes property. All Presentation objects have the following attributes: &nbsp;<b><i>following attributes are defined::</i></b> ```javascript var presentation = new Presentation(); // default attributes and values this.shouldBeTrue(presentation.get('id') === null); this.shouldBeTrue(presentation.get('name') === null); this.shouldBeTrue(presentation.get('modelName') === null); this.shouldBeTrue(presentation.get('contents') instanceof Array); ``` #### METHODS #### modelConstructor This is a reference to the constructor function to create a new model #### validate check valid object state then extend to presentation contents &nbsp;<b><i>callback is required -- see integration:</i></b> ```javascript new Presentation().validate(); ``` <blockquote><strong>Error: callback is required</strong> thrown as expected </blockquote> #### CONTENTS The contents attributes provides the structure for the presentation. &nbsp;<b><i>content must be an array:</i></b> ```javascript var pres = new Presentation(); pres.set('contents', true); return pres.getObjectStateErrors(); ``` <blockquote>returns <strong>contents must be Array</strong> as expected </blockquote> &nbsp;<b><i>contents elements must be Text, Command, Attribute, List or string:</i></b> ```javascript var pres = new Presentation(); // strings with prefix # are heading, a dash - by itself is for a visual separator pres.set('contents', ['#heading', new Text('sup'), new Command(), new Attribute({name: 'meh'}), new List(new Model())]); this.shouldBeTrue(pres.getObjectStateErrors().length === 0); pres.set('contents', [new Command(), new Attribute({name: 'meh'}), true]); return pres.getObjectStateErrors(); ``` <blockquote>returns <strong>contents elements must be Text, Command, Attribute, List or string</strong> as expected </blockquote> #### INTEGRATION &nbsp;<b><i>validation usage demonstrated:</i></b> ```javascript var attribute = new Attribute({name: 'test'}); var presentation = new Presentation(); // default attributes and values presentation.set('contents', [attribute]); attribute.setError('test', 'test error'); presentation.validate(function () { callback(presentation.validationMessage); }); ``` <blockquote>returns <strong>contents has validation errors</strong> as expected </blockquote> &nbsp;<b><i>use REPLInterface to view and edit:</i></b> ```javascript var repl = new REPLInterface(); var ex = this; repl.captureOutput(function (text) { ex.log('out> ' + text); //console.log('out> ' + text); }); repl.capturePrompt(function (text) { ex.log('prompt> ' + text); //console.log('prompt> ' + text); }); var input = function (text) { ex.log('in> ' + text); //console.log('in> ' + text); repl.evaluateInput(text); }; /** * Here is the presentation */ var firstName = new Attribute({name: 'firstName'}); var lastName = new Attribute({name: 'lastName'}); var presentation = new Presentation(); presentation.set('contents', [ '##TITLE', 'Here is **text**. _Note it uses markdown_. Eventually this will be **stripped** out!', 'Here are some attributes:', firstName, lastName ]); firstName.value = 'Elmer'; lastName.value = 'Fud'; /** * Create a command to view it (default mode) */ var presentationCommand = new Command({name: 'Presentation', type: 'Presentation', contents: presentation}); presentationCommand.onEvent('*', function (event, err) { var eventDesc = 'event> ' + event + (err || ' ok'); ex.log(eventDesc); //console.log(eventDesc); }); presentationCommand.execute(repl); /** * Now edit it */ presentationCommand.presentationMode = 'Edit'; presentationCommand.execute(repl); input('John'); input('Doe'); /** * View again */ presentationCommand.presentationMode = 'View'; presentationCommand.execute(repl); ``` <blockquote><strong>log: </strong>event> BeforeExecute ok<br><strong>log: </strong>event> ErrorError: Presentation object required<br><strong>log: </strong>event> Completed ok<br><strong>log: </strong>event> AfterExecute ok<br><strong>log: </strong>event> BeforeExecute ok<br><strong>log: </strong>event> ErrorError: Presentation object required<br><strong>log: </strong>event> Completed ok<br><strong>log: </strong>event> AfterExecute ok<br><strong>log: </strong>in> John<br><strong>log: </strong>out> input ignored: John<br><strong>log: </strong>in> Doe<br><strong>log: </strong>out> input ignored: Doe<br><strong>log: </strong>event> BeforeExecute ok<br><strong>log: </strong>event> ErrorError: Presentation object required<br><strong>log: </strong>event> Completed ok<br><strong>log: </strong>event> AfterExecute ok<br><strong>log: </strong>prompt> ?<br><strong>log: </strong>prompt> ?<br></blockquote> ## [&#9664;](#-presentation)&nbsp;[&#8984;](#constructors)&nbsp;[&#9654;](#-user) &nbsp;Session #### Session Model The Session Model represents the Session logged into the system. The library uses this for system access, logging and other functions. #### CONSTRUCTOR &nbsp;<b><i>objects created should be an instance of Session:</i></b> ```javascript return new Session() instanceof Session; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> #### CONSTRUCTOR Creation of all Models must adhere to following examples: &nbsp;<b><i>objects created should be an instance of Model:</i></b> ```javascript return new SurrogateModel() instanceof Model; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> &nbsp;<b><i>should make sure new operator used:</i></b> ```javascript SurrogateModel(); // jshint ignore:line ``` <blockquote><strong>Error: new operator required</strong> thrown as expected </blockquote> &nbsp;<b><i>should make sure properties are valid:</i></b> ```javascript new SurrogateModel({sup: 'yo'}); ``` <blockquote><strong>Error: error creating Model: invalid property: sup</strong> thrown as expected </blockquote> &nbsp;<b><i>can supply attributes in constructor in addition to ID default:</i></b> ```javascript var play = new SurrogateModel({attributes: [new Attribute('game')]}); play.set('game', 'scrabble'); // this would throw error if attribute did not exist return play.get('game'); ``` <blockquote>returns <strong>scrabble</strong> as expected </blockquote> #### PROPERTIES #### tags Tags are an array of strings that can be used in searching. &nbsp;<b><i>should be an array or undefined:</i></b> ```javascript var m = new SurrogateModel(); // default is undefined this.shouldBeTrue(m.tag === undefined && m.getObjectStateErrors().length === 0); m.tags = []; this.shouldBeTrue(m.getObjectStateErrors().length === 0); m.tags = 'your it'; this.shouldBeTrue(m.getObjectStateErrors().length == 1); ``` #### attributes The attributes property is an array of Attributes. &nbsp;<b><i>should be an array:</i></b> ```javascript var goodModel = new SurrogateModel(), badModel = new SurrogateModel(); badModel.attributes = 'wtf'; return (goodModel.getObjectStateErrors().length === 0 && badModel.getObjectStateErrors().length == 1); ``` <blockquote>returns <strong>true</strong> as expected </blockquote> &nbsp;<b><i>elements of array must be instance of Attribute:</i></b> ```javascript // passing true to getObjectStateErrors() means only check model and not subclass validations // todo make unit test for above var model = new SurrogateModel(); model.attributes = [new Attribute("ID", "ID")]; this.shouldBeTrue(model.getObjectStateErrors(true).length === 0); model.attributes = [new Attribute("ID", "ID"), new SurrogateModel(), 0, 'a', {}, [], null]; this.shouldBeTrue(model.getObjectStateErrors(true).length == 6); ``` #### METHODS #### toString() &nbsp;<b><i>should return a description of the model:</i></b> ```javascript return new SurrogateModel().toString().length > 0; ``` <blockquote>returns <strong>true</strong> as expected </blockquote> #### copy(sourceModel) &nbsp;<b><i>copy all attribute values of a model:</i></b> ```javascript var Foo = function (args) { Model.call(this, args); this.modelType = "Foo"; this.attributes.push(new Attribute('name')); }; Foo.prototype = inheritPrototype(Model.prototype); var m1 = new Foo(); var m2 = new Foo(); var m3 = m1; m1.set('name', 'Bar'); m2.set('name', 'Bar'); // First demonstrate instance ref versus another model with equal attributes this.shouldBeTrue(m1 === m3); // assigning one model to variable references same instance this.shouldBeTrue(m3.get('name') === 'Bar'); // m3 changed when m1 changed this.shouldBeTrue(m1 !== m2); // 2 models are not the same instance // clone m1 into m4 and demonstrate that contents equal but not same ref to object var m4 = new Foo(); m4.copy(m1); this.shouldBeTrue(m1 !== m4); // 2 models are not the same instance ``` #### getObjectStateErrors() &nbsp;<b><i>should return array of validation errors:</i></b> ```javascript this.shouldBeTrue(new SurrogateModel().getObjectStateErrors() instanceof Array); ``` &nbsp;<b><i>first attribute must be an ID field:</i></b> ```javascript var m = new SurrogateModel(); m.attributes = [new Attribute('spoon')]; return m.getObjectStateErrors(); ``` <blockquote>returns <strong>first attribute must be ID</strong> as expected </blockquote> #### onEvent Use onEvent(events,callback) &nbsp;<b><i>first parameter is a string or array of event subscriptions:</i></b> ```javascript new SurrogateModel().onEvent(); ``` <blockquote><strong>Error: subscription string or array required</strong> thrown as expected </blockquote> &nbsp;<b><i>callback is required:</i></b> ```javascript new SurrogateModel().onEvent([]); ``` <blockquote><strong>Error: callback is required</strong> thrown as expected </blockquote> &nbsp;<b><i>events are checked against known types:</i></b> ```javascript new SurrogateModel().onEvent(['onDrunk'], function () { }); ``` <blockquote><strong>Error: Unknown command event: onDrunk</strong> thrown as expected </blockquote> &nbsp;<b><i>here is a working version:</i></b> ```javascript this.log('T.getAttributeEvents()'); // Validate - callback when attribute needs to be validated // StateChange -- callback w