tgi-store-mongodb
Version:
1,787 lines (1,398 loc) • 120 kB
Markdown
*217 model tests applied*
## [◀](#-model) [⌘](#constructors) [▶](#-request) 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
<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>
<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>
<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).
<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>
<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
<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>
<b><i>shorthand version:</i></b>
```javascript
new Procedure([function () {
}]);
```
#### command
Command to execute for this task
<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
<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
<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
<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>
<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>
<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>
## [◀](#-procedure) [⌘](#constructors) [▶](#-store) Request
Requests handle the Request / Response design pattern. They are used by the Interface class to communicate with the Application Model
#### CONSTRUCTOR
<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>
<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>
<b><i>request type must be specified:</i></b>
```javascript
new Request();
```
<blockquote><strong>Error: Request type required</strong> thrown as expected
</blockquote>
<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>
<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>
<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>
<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()
<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>
## [◀](#-request) [⌘](#constructors) [▶](#-text) Store
The store class is used for object persistence.
#### CONSTRUCTOR
<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>
<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>
<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
<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.
<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
<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()
<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()
<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>
<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()
<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)
<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)
<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
<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>
## [◀](#-store) [⌘](#constructors) [▶](#-transport) Text
#### Text Class
Text is used to allow display and setting of application / user text.
#### CONSTRUCTOR
<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>
<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()
<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()
<b><i>return value:</i></b>
```javascript
return new Text('yo').get();
```
<blockquote>returns <strong>yo</strong> as expected
</blockquote>
#### set()
<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)
<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>
<b><i>callback is required:</i></b>
```javascript
new Text('').onEvent([]);
```
<blockquote><strong>Error: callback is required</strong> thrown as expected
</blockquote>
<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>
<b><i>here is a working version:</i></b>
```javascript
new Text('').onEvent(['StateChange'], function () {
});
```
#### offEvents
Free all onEvent listeners
<b><i>example:</i></b>
```javascript
new Text('').offEvent();
```
## [◀](#-text) [⌘](#constructors) [▶](#-view) 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
## [◀](#-transport) [⌘](#constructors) [▶](#-replinterface) View
#### View
Does stuff
#### CONSTRUCTOR
<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>
<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>
<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>
<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>
<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>
<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}}, []);
});
```
<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>
<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()
<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>
## [◀](#-view) [⌘](#constructors) [▶](#-application) 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)
<b><i>called when line of input available:</i></b>
```javascript
return typeof REPLInterface.prototype.evaluateInput;
```
<blockquote>returns <strong>function</strong> as expected
</blockquote>
<b><i>if no input state error generated:</i></b>
```javascript
```
captureOutput(callback)
<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)
<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
<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>
<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>
## [◀](#-replinterface) [⌘](#constructors) [▶](#-log) Application
#### CONSTRUCTOR
<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*
<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:
<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.
<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
<b><i>default is undefined:</i></b>
```javascript
return new Application().getInterface() === undefined;
```
<blockquote>returns <strong>true</strong> as expected
</blockquote>
<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.
<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
<b><i>default is undefined:</i></b>
```javascript
return new Application().getPresentation() === undefined;
```
<blockquote>returns <strong>true</strong> as expected
</blockquote>
<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.
<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>
<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.
<b><i>must pass a Request object:</i></b>
```javascript
new Application().dispatch();
```
<blockquote><strong>Error: Request required</strong> thrown as expected
</blockquote>
<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>
<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.
<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.
<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.
<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.
<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
<b><i>must set interface before invoking:</i></b>
```javascript
new Application().ok();
```
<blockquote><strong>Error: interface not set</strong> thrown as expected
</blockquote>
<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>
<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.
<b><i>must set interface before invoking:</i></b>
```javascript
new Application().yesno();
```
<blockquote><strong>Error: interface not set</strong> thrown as expected
</blockquote>
<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>
<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.
<b><i>must set interface before invoking:</i></b>
```javascript
new Application().ask();
```
<blockquote><strong>Error: interface not set</strong> thrown as expected
</blockquote>
<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>
<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>
<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
<b><i>must set interface before invoking:</i></b>
```javascript
new Application().choose();
```
<blockquote><strong>Error: interface not set</strong> thrown as expected
</blockquote>
<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>
<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?', []);
});
```
<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
<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>
<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>
## [◀](#-application) [⌘](#constructors) [▶](#-presentation) Log
#### Log Model
Multi purpose log model.
#### CONSTRUCTOR
<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
<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
<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>
<b><i>Text simple text message:</i></b>
```javascript
return new Log('sup');
```
<blockquote>returns <strong>Info: sup</strong> as expected
</blockquote>
<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>
## [◀](#-log) [⌘](#constructors) [▶](#-session) 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
<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
<b><i>Array of errors:</i></b>
```javascript
this.shouldBeTrue(new Presentation().validationErrors instanceof Array);
this.shouldBeTrue(new Presentation().validationErrors.length === 0);
```
#### validationMessage
<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:
<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
<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.
<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>
<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
<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>
<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>
## [◀](#-presentation) [⌘](#constructors) [▶](#-user) 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
<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:
<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>
<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>
<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>
<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.
<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.
<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>
<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()
<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)
<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()
<b><i>should return array of validation errors:</i></b>
```javascript
this.shouldBeTrue(new SurrogateModel().getObjectStateErrors() instanceof Array);
```
<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)
<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>
<b><i>callback is required:</i></b>
```javascript
new SurrogateModel().onEvent([]);
```
<blockquote><strong>Error: callback is required</strong> thrown as expected
</blockquote>
<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>
<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