test-scribe
Version:
Your sidekick for analog machine development. Run machines interactively and generate automated test scripts based on the outcome.
395 lines (329 loc) • 14.4 kB
JavaScript
/**
* Homepage UI logic
*
* ng-app="homepage"
* ng-controller="afterRender"
*/
wireUp('homepage', {
// Other angular modules we depend on.
dependencies: ['negotiateKeyboardEvent', 'ui.bootstrap.tooltip'],
// The providers to pass in as `imports` in the functions below.
imports: ['$scope', '$timeout', '$http', 'getInputValsFromScope'],
// /$$
// | $$
// /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
// /$$__ $$| $$__ $$ /$$__ $$ /$$__ $$| $$__ $$ /$$__ $$ /$$__ $$ /$$__ $$
// | $$ \ $$| $$ \ $$ | $$ \__/| $$$$$$$$| $$ \ $$| $$ | $$| $$$$$$$$| $$ \__/
// | $$ | $$| $$ | $$ | $$ | $$_____/| $$ | $$| $$ | $$| $$_____/| $$
// | $$$$$$/| $$ | $$ | $$ | $$$$$$$| $$ | $$| $$$$$$$| $$$$$$$| $$
// \______/ |__/ |__/ |__/ \_______/|__/ |__/ \_______/ \_______/|__/
// Run when the page renders.
afterRender: function (imports) {
// Grab data that was bootstrapped on the page as view locals,
// then turned into global constants in an inline script tag
// in `homepage.ejs`.
imports.$scope.packIdentity = PACK_IDENTITY;
imports.$scope.pathToPack = PATH_TO_PACK;
imports.$scope.machines = _.values(MACHINE_DEFS);
// By default, reload pack every time a machine is run.
imports.$scope.currentPack.willReloadOnRun = true;
// A "control object" used for communicating with our dropdown directive.
// (http://stackoverflow.com/a/18487942)
imports.$scope.dropdownActions = {
// Directive will add a function:
// set: function (index) { ... }
};
},
// /$$
// | $$
// /$$$$$$ /$$ /$$ /$$$$$$ /$$$$$$/$$$$ /$$$$$$ | $$ /$$$$$$
// /$$__ $$| $$ /$$/ |____ $$| $$_ $$_ $$ /$$__ $$| $$ /$$__ $$
// | $$$$$$$$ \ $$$$/ /$$$$$$$| $$ \ $$ \ $$| $$ \ $$| $$| $$$$$$$$
// | $$_____/ >$$ $$ /$$__ $$| $$ | $$ | $$| $$ | $$| $$| $$_____/
// | $$$$$$$ /$$/\ $$| $$$$$$$| $$ | $$ | $$| $$$$$$$/| $$| $$$$$$$
// \_______/|__/ \__/ \_______/|__/ |__/ |__/| $$____/ |__/ \_______/
// | $$
// | $$
// |__/
// /$$ /$$ /$$
// |__/ | $$ | $$
// /$$ /$$ /$$$$$$ /$$$$$$ /$$ /$$$$$$ | $$$$$$$ | $$ /$$$$$$ /$$$$$$$
// | $$ /$$/|____ $$ /$$__ $$| $$ |____ $$| $$__ $$| $$ /$$__ $$ /$$_____/
// \ $$/$$/ /$$$$$$$| $$ \__/| $$ /$$$$$$$| $$ \ $$| $$| $$$$$$$$| $$$$$$
// \ $$$/ /$$__ $$| $$ | $$ /$$__ $$| $$ | $$| $$| $$_____/ \____ $$
// \ $/ | $$$$$$$| $$ | $$| $$$$$$$| $$$$$$$/| $$| $$$$$$$ /$$$$$$$/
// \_/ \_______/|__/ |__/ \_______/|_______/ |__/ \_______/|_______/
// Example values for variables which are being used on $scope.
variables: {
showingHelpText: false,
packIdentity: '',
pathToPack: '',
currentPack: {
isReloading: true,
hasReloaded: false,
hasReloadingFailed: false,
willReloadOnRun: true,
hasRunAnyMachineOnce: false
},
machines: [{
identity: '',
friendlyName: '',
description: '',
inputs: [{
id: 'thing',
friendlyName: 'The thing',
description: 'The thing that... you know, the thing.',
example: '*',
displayExample: 'some JSON',
displayType: 'JSON',
}],
exits: [{
id: 'success',
friendlyName: 'then',
description: 'Done.',
example: '*'
}]
}],
currentTest: {
hasSavedTest: false,
isSavingTest: false,
hasError: false,
error: '',
outcome: 'foo',
output: '*',
hasOutput: true,
outputType: 'string',
exitExample: '*',
hasExitExample: false,
duration: 23523
},
currentMachine: {
isDefined: false,
isRunning: false,
hasRun: false,
hasError: false,
error: '',
identity: '',
friendlyName: '',
description: '',
inputs: [{
id: 'thing',
friendlyName: 'The thing',
description: 'The thing that... you know, the thing.',
example: '*',
displayExample: 'some JSON',
displayType: 'JSON',
configuredVal: 'some string like this, typed by a human representing the current input val'
}],
exits: [{
id: 'success',
friendlyName: 'then',
description: 'Done.',
example: '*'
}]
},
configuredInputsForm: '==='
},
// /$$$$$$ /$$ /$$
// /$$__ $$ | $$ |__/
// | $$ \__//$$ /$$ /$$$$$$$ /$$$$$$$ /$$$$$$ /$$ /$$$$$$ /$$$$$$$ /$$$$$$$
// | $$$$ | $$ | $$| $$__ $$ /$$_____/|_ $$_/ | $$ /$$__ $$| $$__ $$ /$$_____/
// | $$_/ | $$ | $$| $$ \ $$| $$ | $$ | $$| $$ \ $$| $$ \ $$| $$$$$$
// | $$ | $$ | $$| $$ | $$| $$ | $$ /$$| $$| $$ | $$| $$ | $$ \____ $$
// | $$ | $$$$$$/| $$ | $$| $$$$$$$ | $$$$/| $$| $$$$$$/| $$ | $$ /$$$$$$$/
// |__/ \______/ |__/ |__/ \_______/ \___/ |__/ \______/ |__/ |__/|_______/
// Functions available on $scope.
functions: {
/**
* Set whether or not to reload a machinepack on run
* @param {[type]} inputs [description]
* @param {[type]} imports [description]
*/
setReloadOnRun: function (inputs, imports) {
imports.$scope.currentPack.willReloadOnRun = inputs.willReload;
},
/**
* Reload a machinepack
* @param {[type]} inputs [description]
* @param {[type]} imports [description]
* @return {[type]} [description]
*/
reloadPack: function (inputs, imports) {
// Set `currentPack` states for when it's reloading
imports.$scope.currentPack.hasReloaded = false;
imports.$scope.currentPack.isReloading = true;
imports.$scope.currentPack.hasReloadingFailed = false;
// Return promise.
return imports.$http.get('/', {
config: imports.getInputValsFromScope(imports.$scope) //<= why is this here again?
})
.then(function (res){
// Reset `currentPack` states
imports.$scope.currentPack.isReloading = false;
imports.$scope.currentPack.hasReloaded = true;
})
.catch(function (res){
// Give information about the error
// and reset `currentPack` states
console.log('an error occurred',res.data);
imports.$scope.currentPack.hasReloaded = false;
imports.$scope.currentPack.isReloading = false;
imports.$scope.currentPack.hasReloadingFailed = true;
});
},
/**
* Set the current machine to the one chosen from the dropdown.
* @param {[type]} inputs [description]
* @param {[type]} imports [description]
* @return {[type]} [description]
*/
chooseMachine: function (inputs, imports){
// Set the current machine on the $scope and mark it as defined
imports.$scope.currentMachine = inputs.choice;
imports.$scope.currentMachine.isDefined = true;
// Clear out all saving/error statess for machines
imports.$scope.currentMachine.isRunning = false;
imports.$scope.currentMachine.hasRun = false;
imports.$scope.currentMachine.hasError = false;
// Clear out all saving/error statess for tests
imports.$scope.currentTest.isSaving = false;
imports.$scope.currentTest.hasSaved = false;
imports.$scope.currentTest.hasError = false;
// Apply scope manually (because this is called from a directive)
imports.$scope.$apply();
},
/**
* Run the selected machine
* @param {[type]} inputs [description]
* @param {[type]} imports [description]
* @return {[type]} [description]
*/
runMachine: function (inputs, imports) {
// Set the state of `currentMachine` in the UI
imports.$scope.currentMachine.hasRun = false;
imports.$scope.currentMachine.isRunning = true;
imports.$scope.currentMachine.hasError = false;
imports.$scope.currentTest.hasSaved = false;
imports.$scope.currentTest.hasError = false;
// If configured to do so, force a reload of the pack
// before running the machine.
var promise;
if (imports.$scope.currentPack.willReloadOnRun) {
promise = imports.$scope.reloadPack({}, imports);
}
else {
promise = imports.$timeout(function (){});
}
promise.then(function (){
imports.$http.post('/machinepreview/'+imports.$scope.currentMachine.identity, {
config: imports.getInputValsFromScope(imports.$scope)
})
.then(function (res){
imports.$scope.currentMachine.isRunning = false;
imports.$scope.currentMachine.hasRun = true;
imports.$scope.currentTest.duration = res.data.duration;
imports.$scope.currentTest.outcome = res.data.outcome;
var exitDef = imports.$scope.currentMachine.exits[imports.$scope.currentTest.outcome];
// If exit def is missing, that's pretty weird.
if (!exitDef) {
imports.$scope.currentMachine.hasError = true;
imports.$scope.currentMachine.error = new Error('Outcome from run is invalid- machine does not have an exit named "'+imports.$scope.currentTest.outcome+'"');
return;
}
imports.$scope.currentTest.output = res.data.output;
imports.$scope.currentTest.outputType = res.data.outputType;
imports.$scope.currentTest.hasOutput = !_.isUndefined(imports.$scope.currentTest.output);
if (!_.isUndefined(exitDef.example)) {
imports.$scope.currentTest.exitExample = exitDef.example;
imports.$scope.currentTest.hasExitExample = true;
// console.log('EXIT EXAMPLE for %s:', imports.$scope.currentTest.outcome,imports.$scope.currentTest.exitExample);
// console.log('EXIT DEF for %s:', imports.$scope.currentTest.outcome,exitDef);
}
// If exit has a "getExample" function, we'll use `example:'*'` for our purposes here.
else if (!_.isUndefined(exitDef.getExample)) {
imports.$scope.currentTest.exitExample = '*';
imports.$scope.currentTest.hasExitExample = true;
// console.log('EXIT EXAMPLE:', imports.$scope.currentTest.exitExample);
// console.log('getExample:', exitDef.getExample);
}
else {
imports.$scope.currentTest.exitExample = undefined;
imports.$scope.currentTest.hasExitExample = false;
}
// After the first run, the button to reload on run should appear.
imports.$scope.currentPack.hasRunAnyMachineOnce = true;
// console.log('output:', imports.$scope.currentTest.output);
// console.log('hasOutput?:', imports.$scope.currentTest.hasOutput);
// console.log('outputType:', imports.$scope.currentTest.outputType);
// console.log('hasExitExample?', imports.$scope.currentTest.hasExitExample);
// console.log('exitExample:', imports.$scope.currentTest.exitExample);
})
.catch(function (res){
console.log('an error occurred',res.data);
imports.$scope.currentMachine.isRunning = false;
imports.$scope.currentMachine.hasRun = false;
imports.$scope.currentMachine.hasError = true;
imports.$scope.currentMachine.error = res.data;
// After the first run, the button to reload on run should appear.
imports.$scope.currentPack.hasRunAnyMachineOnce = true;
});
});
},
/**
* Save the previous run of the machine as a test
* @param {[type]} inputs [description]
* @param {[type]} imports [description]
* @return {[type]} [description]
*/
saveAsTest: function (inputs, imports) {
// Set the state of `currentTest` in the UI
imports.$scope.currentTest.hasSaved = false;
imports.$scope.currentTest.isSaving = true;
imports.$scope.currentTest.hasError = false;
// Only actually save the expected output if the exit has an example.
var expectedOutput;
if (imports.$scope.currentTest.hasExitExample) {
expectedOutput = JSON.stringify(imports.$scope.currentTest.output);
}
imports.$http.put('/preview/'+imports.$scope.currentMachine.identity+'/test', {
using: imports.getInputValsFromScope(imports.$scope),
returns: expectedOutput,
outcome: imports.$scope.currentTest.outcome
})
.then(function (res){
imports.$scope.currentTest.isSaving = false;
imports.$scope.currentTest.hasSaved = true;
})
.catch(function (res){
console.log('an error occurred',res.data);
imports.$scope.currentTest.isSaving = false;
imports.$scope.currentTest.hasSaved = false;
imports.$scope.currentTest.hasError = true;
imports.$scope.currentTest.error = res.data;
});
}
}
});
// /$$ /$$
// | $$ | $$
// | $$$$$$$ /$$$$$$ | $$ /$$$$$$ /$$$$$$ /$$$$$$
// | $$__ $$ /$$__ $$| $$ /$$__ $$ /$$__ $$ /$$__ $$
// | $$ \ $$| $$$$$$$$| $$| $$ \ $$| $$$$$$$$| $$ \__/
// | $$ | $$| $$_____/| $$| $$ | $$| $$_____/| $$
// | $$ | $$| $$$$$$$| $$| $$$$$$$/| $$$$$$$| $$
// |__/ |__/ \_______/|__/| $$____/ \_______/|__/
// | $$
// | $$
// |__/
// Helper factory that returns a stringified version of the configured input values from the scope.
angular.module('homepage').factory('getInputValsFromScope', [function (){
return function ($scope){
return JSON.stringify(_.reduce($scope.currentMachine.inputs, function (memo, input) {
memo.push({
name: input.id,
value: _.isUndefined(input.configuredVal) ? '' : input.configuredVal
});
return memo;
},[]));
};
}]);