leonardojs
Advanced tools
Comparing version 1.0.0 to 1.0.8
@@ -10,5 +10,7 @@ { | ||
"authors": [ | ||
"sagivf <sagivf@gmail.com> (www.sagivfrankel.com)" | ||
"sagivf <sagivf@gmail.com>", | ||
"dwido <dbuchbut@outbrain.com>", | ||
"tsachis <tsachishushan@gmail.com>" | ||
], | ||
"description": "secnario runner", | ||
"description": "scenario runner", | ||
"main": [ | ||
@@ -15,0 +17,0 @@ "dist/leonardo.js", |
angular.module('leonardo', ['leonardo.templates', 'ngMockE2E']) | ||
/* wrap $httpbackend with a proxy in order to support delaying its responses | ||
* we are using the approach described in Endless Indirection: | ||
* https://endlessindirection.wordpress.com/2013/05/18/angularjs-delay-response-from-httpbackend/ | ||
*/ | ||
.config(['$provide', function($provide) { | ||
$provide.decorator('$httpBackend', ['$delegate', function($delegate) { | ||
.config(['$provide', '$httpProvider', function($provide, $httpProvider) { | ||
$httpProvider.interceptors.push('leoHttpInterceptor'); | ||
$provide.decorator('$httpBackend', ['$delegate', '$timeout', function($delegate, $timeout) { | ||
var proxy = function(method, url, data, callback, headers) { | ||
@@ -12,3 +11,3 @@ var interceptor = function() { | ||
_arguments = arguments; | ||
setTimeout(function() { | ||
$timeout(function() { | ||
callback.apply(_this, _arguments); | ||
@@ -75,10 +74,30 @@ }, proxy.delay || 0); | ||
}]); | ||
}]); | ||
}]) | ||
.run(['leoConfiguration', function(leoConfiguration) { | ||
leoConfiguration.loadSavedStates(); | ||
}]); | ||
angular.module('leonardo').provider('$leonardo', function LeonardoProvider() { | ||
var pref = ''; | ||
this.setAppPrefix = function (prefix) { | ||
pref = prefix; | ||
}; | ||
this.$get = [function leonardoProvider() { | ||
return { | ||
getAppPrefix: function () { | ||
return pref; | ||
} | ||
}; | ||
}]; | ||
}); | ||
angular.module('leonardo').factory('leoConfiguration', | ||
['leoStorage', '$httpBackend', function(leoStorage, $httpBackend) { | ||
['leoStorage', '$httpBackend', '$rootScope', function(leoStorage, $httpBackend, $rootScope) { | ||
var states = [], | ||
_scenarios = {}, | ||
responseHandlers = {}, | ||
_requestsLog = [], | ||
_savedStates = [], | ||
// Core API | ||
@@ -100,4 +119,9 @@ // ---------------- | ||
setActiveScenario: setActiveScenario, | ||
getRecordedStates: getRecordedStates, | ||
getRequestsLog: getRequestsLog, | ||
loadSavedStates: loadSavedStates, | ||
addSavedState: addSavedState, | ||
//Private api for passing through unregistered urls to $htto | ||
_requestSubmitted: requestSubmitted | ||
_requestSubmitted: requestSubmitted, | ||
_logRequest: logRequest | ||
}; | ||
@@ -157,3 +181,3 @@ return api; | ||
function sync(){ | ||
fetchStates().forEach(function (state) { | ||
fetchStates().forEach(function (state, i) { | ||
var option, responseHandler; | ||
@@ -165,2 +189,3 @@ if (state.url) { | ||
responseHandler.respond(function () { | ||
console.log(i); | ||
$httpBackend.setDelay(option.delay); | ||
@@ -177,11 +202,16 @@ return [option.status, angular.isFunction(option.data) ? option.data() : option.data]; | ||
function getResponseHandler(state) { | ||
if (!responseHandlers[state.url + '_' + state.verb]) { | ||
var url = state.url; | ||
var verb = state.verb === 'jsonp' ? state.verb : state.verb.toUpperCase(); | ||
var key = (url + '_' + verb).toUpperCase(); | ||
var escapedUrl = url.replace(/[?]/g, '\\?'); | ||
if (!responseHandlers[key]) { | ||
if (state.verb === 'jsonp'){ | ||
responseHandlers[state.url + '_' + state.verb] = $httpBackend.whenJSONP(new RegExp(state.url)); | ||
responseHandlers[key] = $httpBackend.whenJSONP(new RegExp(escapedUrl)); | ||
} | ||
else { | ||
responseHandlers[state.url + '_' + state.verb] = $httpBackend.when(state.verb || 'GET', new RegExp(state.url)); | ||
responseHandlers[key] = $httpBackend.when(verb || 'GET', new RegExp(escapedUrl)); | ||
} | ||
} | ||
return responseHandlers[state.url + '_' + state.verb]; | ||
return responseHandlers[key]; | ||
} | ||
@@ -206,8 +236,14 @@ | ||
}); | ||
$rootScope.$broadcast('leonardo:stateChanged', stateObj); | ||
} | ||
function addStates(statesArr) { | ||
statesArr.forEach(function(stateObj) { | ||
addState(stateObj); | ||
}); | ||
if (angular.isArray(statesArr)) { | ||
statesArr.forEach(function(stateObj) { | ||
addState(stateObj); | ||
}); | ||
} else { | ||
console.warn('addStates should get an array'); | ||
} | ||
} | ||
@@ -221,3 +257,3 @@ | ||
status = stateObj.status || 200, | ||
data = stateObj.data || {}, | ||
data = angular.isDefined(stateObj.data) ? stateObj.data : {}, | ||
delay = stateObj.delay || 0; | ||
@@ -318,8 +354,81 @@ var defaultState = {}; | ||
} | ||
function logRequest(method, url, data, status) { | ||
if (method && url && !(url.indexOf(".html") > 0)) { | ||
var req = { | ||
verb: method, | ||
data: data, | ||
url: url.trim(), | ||
status: status, | ||
timestamp: new Date() | ||
}; | ||
req.state = getStateByRequest(req); | ||
_requestsLog.push(req); | ||
} | ||
} | ||
function getStateByRequest(req) { | ||
return fetchStates().filter(function(state) { | ||
if (!state.url) return false; | ||
return state.url === req.url && state.verb.toLowerCase() === req.verb.toLowerCase(); | ||
})[0]; | ||
} | ||
function getRequestsLog() { | ||
return _requestsLog; | ||
} | ||
function loadSavedStates() { | ||
_savedStates = leoStorage.getSavedStates(); | ||
addStates(_savedStates); | ||
} | ||
function addSavedState(state) { | ||
_savedStates.push(state); | ||
leoStorage.setSavedStates(_savedStates); | ||
addState(state); | ||
} | ||
function getRecordedStates() { | ||
var requestsArr = _requestsLog | ||
.map(function(req){ | ||
var state = getStateByRequest(req); | ||
return { | ||
name: state ? state.name : req.verb + " " + req.url, | ||
verb: req.verb, | ||
url: req.url, | ||
options: [{ | ||
name: req.status >= 200 && req.status < 300 ? 'Success' : 'Failure', | ||
status: req.status, | ||
data: req.data | ||
}] | ||
} | ||
}); | ||
console.log(angular.toJson(requestsArr, true)); | ||
return requestsArr; | ||
} | ||
}]); | ||
angular.module('leonardo').factory('leoStorage', ['$rootScope', function storageService($rootScope) { | ||
var STATES_STORE_KEY = 'leonardo-states'; | ||
function getItem(key) { | ||
var item = localStorage.getItem(key); | ||
angular.module('leonardo').factory('leoHttpInterceptor', ['leoConfiguration', '$q', function(leoConfiguration, $q) { | ||
return { | ||
'request': function(request) { | ||
return $q.when(request); | ||
}, | ||
'response': function(response) { | ||
leoConfiguration._logRequest(response.config.method, response.config.url, response.data, response.status); | ||
return $q.when(response); | ||
}, | ||
'responseError': function(rejection) { | ||
leoConfiguration._logRequest(rejection.config.method, rejection.config.url, rejection.data, rejection.status); | ||
return $q.reject(rejection); | ||
} | ||
}; | ||
}]); | ||
angular.module('leonardo').factory('leoStorage', ['$rootScope', '$window', '$leonardo', function storageService($rootScope, $window, $leonardo) { | ||
var APP_PREFIX = $leonardo.getAppPrefix() + '_', | ||
STATES_STORE_KEY = APP_PREFIX + 'leonardo-states', | ||
SAVED_STATES_KEY = APP_PREFIX + 'leonardo-unregistered-states'; | ||
function _getItem(key) { | ||
var item = $window.localStorage.getItem(key); | ||
if (!item) { | ||
@@ -331,20 +440,28 @@ return null; | ||
function setItem(key, data) { | ||
localStorage.setItem(key, angular.toJson(data)); | ||
function _setItem(key, data) { | ||
$window.localStorage.setItem(key, angular.toJson(data)); | ||
} | ||
function getStates() { | ||
return getItem(STATES_STORE_KEY) || {}; | ||
return _getItem(STATES_STORE_KEY) || {}; | ||
} | ||
function setStates(states) { | ||
setItem(STATES_STORE_KEY, states); | ||
_setItem(STATES_STORE_KEY, states); | ||
$rootScope.$emit('leonardo:setStates'); | ||
} | ||
function getSavedStates() { | ||
return _getItem(SAVED_STATES_KEY) || []; | ||
} | ||
function setSavedStates(states) { | ||
_setItem(SAVED_STATES_KEY, states); | ||
} | ||
return { | ||
getItem: getItem, | ||
setItem: setItem, | ||
setStates: setStates, | ||
getStates: getStates | ||
getStates: getStates, | ||
getSavedStates: getSavedStates, | ||
setSavedStates: setSavedStates | ||
}; | ||
@@ -356,2 +473,9 @@ }]); | ||
restrict: 'A', | ||
controllerAs: 'leonardo', | ||
controller: function () { | ||
this.activeTab = 'scenarios'; | ||
this.selectTab = function (name) { | ||
this.activeTab = name; | ||
}; | ||
}, | ||
link: function(scope, elem) { | ||
@@ -366,3 +490,5 @@ var el = angular.element('<div ng-click="activate()" class="leonardo-activator"></div>'); | ||
'<li>LEONARDO</li>', | ||
'<li>Scenarios</li>', | ||
'<li ng-class="{ \'leo-selected-tab\': leonardo.activeTab === \'scenarios\' }" ng-click="leonardo.selectTab(\'scenarios\')">Scenarios</li>', | ||
'<li ng-class="{ \'leo-selected-tab\': leonardo.activeTab === \'recorder\' }"ng-click="leonardo.selectTab(\'recorder\')">Recorder</li>', | ||
'<li ng-class="{ \'leo-selected-tab\': leonardo.activeTab === \'export\' }"ng-click="leonardo.selectTab(\'export\')">Exported Code</li>', | ||
'</ul>', | ||
@@ -402,3 +528,3 @@ '</div>', | ||
angular.module('leonardo').directive('leoWindowBody', | ||
['$http', 'leoConfiguration', function windowBodyDirective($http, leoConfiguration) { | ||
['$http', 'leoConfiguration', '$timeout', function windowBodyDirective($http, leoConfiguration, $timeout) { | ||
return { | ||
@@ -409,14 +535,20 @@ restrict: 'E', | ||
replace: true, | ||
controller: ['$scope', function($scope){ | ||
$scope.selectedItem = 'activate'; | ||
$scope.NothasUrl = function(option){ | ||
require: '^leoActivator', | ||
controller: ['$scope', function($scope) { | ||
$scope.detail = { | ||
option: 'success', | ||
delay: 0, | ||
status: 200 | ||
}; | ||
$scope.NothasUrl = function (option) { | ||
return !option.url; | ||
}; | ||
$scope.hasUrl = function(option){ | ||
$scope.hasUrl = function (option) { | ||
return !!option.url; | ||
}; | ||
$scope.deactivate = function() { | ||
$scope.states.forEach(function(state){ | ||
state.active = false; | ||
$scope.deactivate = function () { | ||
$scope.states.forEach(function (state) { | ||
state.active = false; | ||
}); | ||
@@ -426,8 +558,8 @@ leoConfiguration.deactivateAllStates(); | ||
$scope.updateState = function(state){ | ||
$scope.updateState = function (state) { | ||
if (state.active) { | ||
console.log('activate state option:' + state.name + ': ' + state.activeOption.name); | ||
console.log('activate state option:' + state.name + ': ' + state.activeOption.name); | ||
leoConfiguration.activateStateOption(state.name, state.activeOption.name); | ||
} else { | ||
console.log('deactivating state: ' + state.name); | ||
console.log('deactivating state: ' + state.name); | ||
leoConfiguration.deactivateState(state.name); | ||
@@ -441,3 +573,3 @@ } | ||
$scope.activateScenario = function(scenario){ | ||
$scope.activateScenario = function (scenario) { | ||
$scope.activeScenario = scenario; | ||
@@ -447,4 +579,91 @@ leoConfiguration.setActiveScenario(scenario); | ||
}; | ||
$scope.requests = leoConfiguration.getRequestsLog(); | ||
$scope.$watch('detail.value', function(value){ | ||
if (!value) { | ||
return; | ||
} | ||
try { | ||
$scope.detail.stringValue = value ? JSON.stringify(value, null, 4) : ''; | ||
$scope.detail.error = ''; | ||
} | ||
catch (e) { | ||
$scope.detail.error = e.message; | ||
} | ||
}); | ||
$scope.$watch('detail.stringValue', function(value){ | ||
try { | ||
$scope.detail.value = value ? JSON.parse(value) : {}; | ||
$scope.detail.error = ''; | ||
} | ||
catch(e) { | ||
$scope.detail.error = e.message; | ||
} | ||
}); | ||
$scope.requestSelect = function (request) { | ||
$scope.requests.forEach(function (request) { | ||
request.active = false; | ||
}); | ||
request.active = true; | ||
if (request.state && request.state.name) { | ||
var optionName = request.state.name + ' option ' + request.state.options.length; | ||
} | ||
angular.extend($scope.detail, { | ||
state : (request.state && request.state.name) || '', | ||
option: optionName || '', | ||
delay: 0, | ||
status: 200, | ||
stateActive: !!request.state, | ||
value: request.data || {} | ||
}); | ||
$scope.detail._unregisteredState = request; | ||
}; | ||
$scope.$on('leonardo:stateChanged', function(event, stateObj) { | ||
$scope.states = leoConfiguration.getStates(); | ||
var state = $scope.states.filter(function(state){ | ||
return state.name === stateObj.name; | ||
})[0]; | ||
if (state) { | ||
state.highlight = true; | ||
$timeout(function(){ | ||
state.highlight = false; | ||
}, 3000); | ||
} | ||
}); | ||
$scope.getStatesForExport = function () { | ||
$scope.exportStates = leoConfiguration.getStates(); | ||
} | ||
}], | ||
link: function(scope) { | ||
link: function(scope, el, attr, leoActivator) { | ||
scope.saveUnregisteredState = function () { | ||
var stateName = scope.detail.state; | ||
leoConfiguration.addSavedState({ | ||
name: stateName, | ||
verb: scope.detail._unregisteredState.verb, | ||
url: scope.detail._unregisteredState.url, | ||
options: [ | ||
{ | ||
name: scope.detail.option, | ||
status: scope.detail.status, | ||
data: scope.detail.value | ||
} | ||
] | ||
}); | ||
leoActivator.selectTab('scenarios'); | ||
}; | ||
scope.test = { | ||
@@ -468,2 +687,18 @@ url: '', | ||
angular.module('leonardo').directive('leoRequest', function () { | ||
return { | ||
restrict: 'E', | ||
templateUrl: 'request.html', | ||
scope: { | ||
request: '=', | ||
onSelect: '&' | ||
}, | ||
controller: ['$scope', function ($scope) { | ||
$scope.select = function () { | ||
$scope.onSelect(); | ||
} | ||
}] | ||
} | ||
}); | ||
(function(module) { | ||
@@ -476,5 +711,29 @@ try { | ||
module.run(['$templateCache', function($templateCache) { | ||
$templateCache.put('request.html', | ||
'<a href="#" class="leo-list-item" ng-click="select()" ng-class="{active:request.active}"><span class="leo-request-name">{{request.url}}</span> <span ng-if="!!request.state" class="leo-request leo-request-existing">{{request.state.name}}</span> <span ng-if="!request.state" class="leo-request leo-request-new">new</span> <span ng-if="!!request.state && request.state.active" class="leo-request leo-request-mocked">mocked</span></a>'); | ||
}]); | ||
})(); | ||
(function(module) { | ||
try { | ||
module = angular.module('leonardo.templates'); | ||
} catch (e) { | ||
module = angular.module('leonardo.templates', []); | ||
} | ||
module.run(['$templateCache', function($templateCache) { | ||
$templateCache.put('window-body.html', | ||
'<div class="leonardo-window-body"><div class="leonardo-scenario-nav"><div class="leonardo-breadcrumbs">SCENARIOS > {{activeScenario}}</div><div class="leonardo-scenario-title">SCENARIOS</div></div><div ng-switch="selectedItem" class="leonardo-window-options"><div ng-switch-when="configure" class="leonardo-configure"><table><thead><tr><th>State</th><th>URL</th><th>Options</th></tr></thead><tbody><tr ng-repeat="state in states"><td>{{state.name}}</td><td>{{state.url}}</td><td><ul><li ng-repeat="option in state.options">Name: {{option.name}}<br>Status: {{option.status}}<br>Data: {{option.data}}<br></li></ul></td></tr></tbody></table></div><div ng-switch-when="activate" class="leonardo-activate"><div class="leonardo-menu"><ul><li ng-class="{ \'selected\': scenario === activeScenario }" ng-repeat="scenario in scenarios" ng-click="activateScenario(scenario)">{{scenario}}</li></ul></div><ul><li><h3>Non Ajax States</h3></li><li ng-repeat="state in states | filter:NothasUrl"><div><div class="onoffswitch"><input ng-model="state.active" ng-click="updateState(state)" class="onoffswitch-checkbox" id="{{state.name}}" type="checkbox" name="{{state.name}}" value="{{state.name}}"> <label class="onoffswitch-label" for="{{state.name}}"><span class="onoffswitch-inner"></span> <span class="onoffswitch-switch"></span></label></div></div><div><h4>{{state.name}}</h4></div><div><select ng-model="state.activeOption" ng-options="option.name for option in state.options" ng-change="updateState(state)"></select></div></li><li><h3>Ajax States</h3></li><li ng-repeat="state in states | filter:hasUrl"><div><div class="onoffswitch"><input ng-model="state.active" ng-click="updateState(state)" class="onoffswitch-checkbox" id="{{state.name}}" type="checkbox" name="{{state.name}}" value="{{state.name}}"> <label class="onoffswitch-label" for="{{state.name}}"><span class="onoffswitch-inner"></span> <span class="onoffswitch-switch"></span></label></div></div><div><h4>{{state.name}}</h4> - {{state.url}}</div><div><select ng-model="state.activeOption" ng-options="option.name for option in state.options" ng-change="updateState(state)"></select></div></li></ul></div><div ng-switch-when="test" class="leonardo-test"><div><label for="url"></label>URL: <input id="url" type="text" ng-model="test.url"> <input type="button" ng-click="submit(test.url)" value="submit"></div><textarea>{{test.value | json}}</textarea></div></div></div>'); | ||
'<div class="leonardo-window-body"><div ng-switch="leonardo.activeTab" class="leonardo-window-options"><div ng-switch-when="configure" class="leonardo-configure"><table><thead><tr><th>State</th><th>URL</th><th>Options</th></tr></thead><tbody><tr ng-repeat="state in states"><td>{{state.name}}</td><td>{{state.url}}</td><td><ul><li ng-repeat="option in state.options">Name: {{option.name}}<br>Status: {{option.status}}<br>Data: {{option.data}}<br></li></ul></td></tr></tbody></table></div><div ng-switch-when="recorder" class="leonardo-recorder"><div class="leo-list"><div class="list-group"><leo-request ng-repeat="request in requests" request="request" on-select="requestSelect(request)"></leo-request></div></div><div class="leo-detail"><div class="leo-detail-header"><div ng-if="!detail.stateActive"><span>Add new state:</span> <input class="leo-detail-state" ng-model="detail.state" placeholder="Enter state name"></div><div ng-if="detail.stateActive" class="leo-detail-state">Add mocked response for "{{detail.state}}"</div></div><div class="leo-detail-option"><div>Response name: <input ng-model="detail.option"></div><div>Status code: <input ng-model="detail.status"></div><div>Delay: <input ng-model="detail.delay"></div><div class="leo-detail-option-json">Response JSON:<div class="leo-error">{{detail.error}}</div><textarea ng-model="detail.stringValue"></textarea></div></div><div class="leo-action-row"><button ng-click="saveUnregisteredState()">{{ detail.stateActive ? \'Add Option\' : \'Add State\' }}</button></div></div></div><div ng-switch-when="export" class="leonardo-export" style="padding: 30px"><code contenteditable="" ng-init="getStatesForExport()">\n' + | ||
'\n' + | ||
' <div>angular.module(\'leonardo\').run([\'leoConfiguration\', function(leoConfiguration) {</div>\n' + | ||
'\n' + | ||
' <div ng-repeat="state in exportStates">\n' + | ||
' <div style="margin-left: 10px">leoConfiguration.addStates([</div>\n' + | ||
' <pre style="margin-left: 20px">{{state | json}}</pre>\n' + | ||
' <div style="margin-left: 10px">])</div>\n' + | ||
' </div>\n' + | ||
'\n' + | ||
' <div>])</div>\n' + | ||
'\n' + | ||
' </code></div><div ng-switch-when="scenarios" class="leonardo-activate"><div class="leonardo-menu"><div>SCENARIOS</div><ul><li ng-class="{ \'selected\': scenario === activeScenario }" ng-repeat="scenario in scenarios" ng-click="activateScenario(scenario)">{{scenario}}</li></ul></div><ul><li class="leo-non-ajax"><h3>Non Ajax States</h3></li><li ng-repeat="state in states | filter:NothasUrl"><div><div class="onoffswitch"><input ng-model="state.active" ng-click="updateState(state)" class="onoffswitch-checkbox" id="{{state.name}}" type="checkbox" name="{{state.name}}" value="{{state.name}}"> <label class="onoffswitch-label" for="{{state.name}}"><span class="onoffswitch-inner"></span> <span class="onoffswitch-switch"></span></label></div></div><div><h4>{{state.name}}</h4></div><div><select ng-disabled="!state.active" ng-model="state.activeOption" ng-options="option.name for option in state.options" ng-change="updateState(state)"></select></div></li><li><h3>Ajax States</h3></li><li ng-repeat="state in states | filter:hasUrl track by $index" ng-class="{ \'leo-highlight\': state.highlight }"><div><div class="onoffswitch"><input ng-model="state.active" ng-click="updateState(state)" class="onoffswitch-checkbox" id="{{state.name}}" type="checkbox" name="{{state.name}}" value="{{state.name}}"> <label class="onoffswitch-label" for="{{state.name}}"><span class="onoffswitch-inner"></span> <span class="onoffswitch-switch"></span></label></div></div><div><h4>{{state.name}}</h4> - {{state.url}}</div><div><select ng-disabled="!state.active" ng-model="state.activeOption" ng-options="option.name for option in state.options" ng-change="updateState(state)"></select></div></li></ul></div><div ng-switch-when="test" class="leonardo-test"><div><label for="url"></label>URL: <input id="url" type="text" ng-model="test.url"> <input type="button" ng-click="submit(test.url)" value="submit"></div><textarea>{{test.value | json}}</textarea></div></div></div>'); | ||
}]); | ||
})(); |
@@ -33,3 +33,3 @@ var gulp = require('gulp'), | ||
})) | ||
.pipe(gulp.dest('./tmp')) | ||
.pipe(gulp.dest('./tmp')); | ||
}); | ||
@@ -47,3 +47,3 @@ | ||
})) | ||
.pipe(rename('leonardo.templates.min.js')) | ||
.pipe(concat('leonardo.templates.min.js')) | ||
.pipe(gulp.dest('./tmp')); | ||
@@ -56,6 +56,9 @@ }); | ||
'./src/leonardo/module.js', | ||
'./src/leonardo/leonardo.prov.js', | ||
'./src/leonardo/configuration.srv.js', | ||
'./src/leonardo/httpInterceptor.srv.js', | ||
'./src/leonardo/storage.srv.js', | ||
'./src/leonardo/activator.drv.js', | ||
'./src/leonardo/window-body.drv.js', | ||
'./src/leonardo/request.drv.js', | ||
'./tmp/leonardo.templates.min.js' | ||
@@ -62,0 +65,0 @@ ]) |
47
index.js
"use strict"; | ||
function getRandomIntInclusive(min, max) { | ||
return Math.floor(Math.random() * (max - min + 1)) + min; | ||
} | ||
var Flicker = angular.module('flicker-example', ['leonardo','akoenig.deckgrid']) | ||
.config(function ($locationProvider) { | ||
var Flicker = angular.module('flicker-example', ['leonardo', 'akoenig.deckgrid']) | ||
.config(['$locationProvider', '$leonardoProvider', function ($locationProvider, $leonardoProvider) { | ||
$leonardoProvider.setAppPrefix('flicker-example'); | ||
$locationProvider.html5Mode(false); | ||
}) | ||
.controller('flickerCtrl', function ($scope, flickerGetter) { | ||
}]) | ||
.controller('flickerCtrl', ['$scope', 'flickerGetter', function ($scope, flickerGetter) { | ||
$scope.loadClicked = function () { | ||
@@ -12,6 +16,4 @@ $scope.loading = true; | ||
$scope.photos = data; | ||
console.log(data); | ||
}).finally(function () { | ||
$scope.loading = false; | ||
}, function () { | ||
$scope.loading = false; | ||
}); | ||
@@ -52,3 +54,3 @@ | ||
}); | ||
}]); | ||
@@ -58,13 +60,11 @@ Flicker.factory('flickerGetter', ['$q', '$http', function ($q, $http) { | ||
getData: function(){ | ||
var defer = $q.defer(); | ||
$http.jsonp(' http://api.flickr.com/services/feeds/photos_public.gne', { | ||
return $http.jsonp('http://api.flickr.com/services/feeds/photos_public.gne', { | ||
method: 'jsonp', | ||
params: { | ||
group_id: 'tmnt', | ||
tagmode: "any", | ||
format: 'json', | ||
jsoncallback: 'JSON_CALLBACK' | ||
} | ||
}).success(function (data) { | ||
data = data.items.map(function (item) { | ||
}).then(function (response) { | ||
return response.data.items.map(function (item) { | ||
var imageUrl = item.media.m; | ||
@@ -75,13 +75,6 @@ return { | ||
}); | ||
defer.resolve(data); | ||
}).error(function () { | ||
defer.resolve(); | ||
}); | ||
return defer.promise; | ||
}, | ||
getNinjaData: function () { | ||
var defer = $q.defer(); | ||
$http.jsonp('https://api.flickr.com/services/rest', { | ||
return $http.jsonp('https://api.flickr.com/services/rest', { | ||
method: 'jsonp', | ||
@@ -96,13 +89,9 @@ params: { | ||
} | ||
}).success(function (data) { | ||
defer.resolve(data.photoset.photo.map(function (photo) { | ||
}).then(function (response) { | ||
return response.data.photoset.photo.map(function (photo) { | ||
return { | ||
imageUrl: 'https://farm{farm-id}.staticflickr.com/{server-id}/{id}_{secret}_b.jpg'.replace('{farm-id}', photo.farm).replace('{server-id}', photo.server).replace('{id}', photo.id).replace('{secret}', photo.secret) | ||
}; | ||
})); | ||
}).error(function () { | ||
defer.resolve(); | ||
}); | ||
}); | ||
return defer.promise; | ||
} | ||
@@ -109,0 +98,0 @@ }; |
{ | ||
"name": "leonardojs", | ||
"version": "1.0.0", | ||
"version": "1.0.8", | ||
"description": "Leonardo ========", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
102
README.md
@@ -1,9 +0,32 @@ | ||
![Leonardo logo](extension/images/leonardo.png) Leonardo Docs | ||
============= | ||
## Leonardo | ||
Mocking and testing made simple and consistent. | ||
Developed by Outbrain. | ||
[![npm version](https://badge.fury.io/js/leonardojs.svg)](http://badge.fury.io/js/leonardojs) | ||
[![Bower version](https://badge.fury.io/bo/leonardo.svg)](http://badge.fury.io/bo/leonardo) | ||
## Requirements | ||
[![Package Quality](http://npm.packagequality.com/badge/leonardojs.png)](http://packagequality.com/#?package=leonardojs) | ||
![Mocking and testing made simple and consistent. Developed by Outbrain.](extension/images/example.png) | ||
[Demo](http://outbrain.github.io/Leonardo/) | ||
## Install | ||
__Dependencies__ | ||
* __[Angular](https://github.com/angular/bower-angular)__ | ||
* __[Angular Mocks](https://github.com/angular/bower-angular-mocks)__ | ||
Installing via `npm` or `bower` will bring in the above dependencies as well. | ||
__npm__ | ||
```sh | ||
$ npm install leonardojs | ||
``` | ||
__bower__ | ||
```sh | ||
$ bower install leonardo | ||
``` | ||
#### Load Dependency scripts | ||
@@ -24,3 +47,3 @@ | ||
//..... | ||
<script src="[bower_componenets|node_modules|other]/leonardos/leonardos.js"></script> | ||
<script src="[bower_componenets|node_modules|other]/leonardo/leonardo.js"></script> | ||
</body> | ||
@@ -37,3 +60,3 @@ </html> | ||
//..... | ||
<link rel="stylesheet" media="all" href="[bower_componenets|node_modules|other]/leonardos/leonardo.min.css" /> | ||
<link rel="stylesheet" media="all" href="[bower_componenets|node_modules|other]/leonardo/leonardo.min.css" /> | ||
</head> | ||
@@ -43,3 +66,3 @@ </html> | ||
#### Add Angular module dependancy | ||
#### Add Angular module dependency | ||
@@ -53,2 +76,3 @@ ```html | ||
var myApp = angular.module("app", ["leonardo"]); | ||
//..... | ||
</script> | ||
@@ -69,7 +93,55 @@ </body> | ||
A plunker demonstration http://plnkr.co/edit/w8oaELXwQldv6AeZjnhD?p=preview | ||
## API | ||
#### Add States | ||
```javascript | ||
//..... | ||
myApp.run(["leoConfiguration", function(leoConfiguration){ | ||
leoConfiguration.addStates([ | ||
{ | ||
name: 'Get Data', | ||
url: '/api/user/43435', | ||
options: [ | ||
{name: 'success', status: 200, data: { name: "Master Splinter" }}, | ||
{name: 'error 500', status: 500}, | ||
{name: 'error 401', status: 401} | ||
] | ||
}, | ||
{ | ||
name: 'Update Data', | ||
url: '/api/user/43435', | ||
verb: 'PUT', | ||
options: [ | ||
{name: 'success', status: 200}, | ||
{name: 'error 500', status: 500}, | ||
{name: 'error 400', status: 400} | ||
] | ||
} | ||
]); | ||
}]); | ||
``` | ||
## Screen | ||
![example image](extension/images/example.png) | ||
#### Activate State Option | ||
Activates state option, mocked response will be returned when calling the state url | ||
```javascript | ||
//..... | ||
leoConfiguration.activateStateOption('Update Data', 'success'); | ||
$http.put('/api/user/43435', { name: "Master Splinter" }).success(function(data, status) { | ||
console.log(status); // 200 | ||
}); | ||
leoConfiguration.activateStateOption('Update Data', 'error 500'); | ||
$http.put('/api/user/43435', { name: "Master Splinter" }).error(function(data, status) { | ||
console.log(status); // 500 | ||
}); | ||
//..... | ||
``` | ||
#### Deactivate State | ||
Deactivates a specific state, when calling the state url request will pass through to the server | ||
```javascript | ||
//..... | ||
leoConfiguration.deactivateState('Update Data'); | ||
//..... | ||
``` | ||
## Documentation | ||
@@ -116,1 +188,9 @@ http://outbrain.github.io/Leonardo/docs/configuration.srv.html | ||
or `cd` into the project folder | ||
```bash | ||
gulp serve | ||
``` | ||
## License | ||
Copyright © 2015 MIT License |
angular.module('leonardo').directive('leoActivator', ['$compile', function activatorDirective($compile) { | ||
return { | ||
restrict: 'A', | ||
controllerAs: 'leonardo', | ||
controller: function () { | ||
this.activeTab = 'scenarios'; | ||
this.selectTab = function (name) { | ||
this.activeTab = name; | ||
}; | ||
}, | ||
link: function(scope, elem) { | ||
@@ -13,3 +20,5 @@ var el = angular.element('<div ng-click="activate()" class="leonardo-activator"></div>'); | ||
'<li>LEONARDO</li>', | ||
'<li>Scenarios</li>', | ||
'<li ng-class="{ \'leo-selected-tab\': leonardo.activeTab === \'scenarios\' }" ng-click="leonardo.selectTab(\'scenarios\')">Scenarios</li>', | ||
'<li ng-class="{ \'leo-selected-tab\': leonardo.activeTab === \'recorder\' }"ng-click="leonardo.selectTab(\'recorder\')">Recorder</li>', | ||
'<li ng-class="{ \'leo-selected-tab\': leonardo.activeTab === \'export\' }"ng-click="leonardo.selectTab(\'export\')">Exported Code</li>', | ||
'</ul>', | ||
@@ -16,0 +25,0 @@ '</div>', |
angular.module('leonardo').factory('leoConfiguration', | ||
['leoStorage', '$httpBackend', function(leoStorage, $httpBackend) { | ||
['leoStorage', '$httpBackend', '$rootScope', function(leoStorage, $httpBackend, $rootScope) { | ||
var states = [], | ||
_scenarios = {}, | ||
responseHandlers = {}, | ||
_requestsLog = [], | ||
_savedStates = [], | ||
// Core API | ||
@@ -22,4 +24,9 @@ // ---------------- | ||
setActiveScenario: setActiveScenario, | ||
getRecordedStates: getRecordedStates, | ||
getRequestsLog: getRequestsLog, | ||
loadSavedStates: loadSavedStates, | ||
addSavedState: addSavedState, | ||
//Private api for passing through unregistered urls to $htto | ||
_requestSubmitted: requestSubmitted | ||
_requestSubmitted: requestSubmitted, | ||
_logRequest: logRequest | ||
}; | ||
@@ -79,3 +86,3 @@ return api; | ||
function sync(){ | ||
fetchStates().forEach(function (state) { | ||
fetchStates().forEach(function (state, i) { | ||
var option, responseHandler; | ||
@@ -87,2 +94,3 @@ if (state.url) { | ||
responseHandler.respond(function () { | ||
console.log(i); | ||
$httpBackend.setDelay(option.delay); | ||
@@ -99,11 +107,16 @@ return [option.status, angular.isFunction(option.data) ? option.data() : option.data]; | ||
function getResponseHandler(state) { | ||
if (!responseHandlers[state.url + '_' + state.verb]) { | ||
var url = state.url; | ||
var verb = state.verb === 'jsonp' ? state.verb : state.verb.toUpperCase(); | ||
var key = (url + '_' + verb).toUpperCase(); | ||
var escapedUrl = url.replace(/[?]/g, '\\?'); | ||
if (!responseHandlers[key]) { | ||
if (state.verb === 'jsonp'){ | ||
responseHandlers[state.url + '_' + state.verb] = $httpBackend.whenJSONP(new RegExp(state.url)); | ||
responseHandlers[key] = $httpBackend.whenJSONP(new RegExp(escapedUrl)); | ||
} | ||
else { | ||
responseHandlers[state.url + '_' + state.verb] = $httpBackend.when(state.verb || 'GET', new RegExp(state.url)); | ||
responseHandlers[key] = $httpBackend.when(verb || 'GET', new RegExp(escapedUrl)); | ||
} | ||
} | ||
return responseHandlers[state.url + '_' + state.verb]; | ||
return responseHandlers[key]; | ||
} | ||
@@ -128,8 +141,14 @@ | ||
}); | ||
$rootScope.$broadcast('leonardo:stateChanged', stateObj); | ||
} | ||
function addStates(statesArr) { | ||
statesArr.forEach(function(stateObj) { | ||
addState(stateObj); | ||
}); | ||
if (angular.isArray(statesArr)) { | ||
statesArr.forEach(function(stateObj) { | ||
addState(stateObj); | ||
}); | ||
} else { | ||
console.warn('addStates should get an array'); | ||
} | ||
} | ||
@@ -143,3 +162,3 @@ | ||
status = stateObj.status || 200, | ||
data = stateObj.data || {}, | ||
data = angular.isDefined(stateObj.data) ? stateObj.data : {}, | ||
delay = stateObj.delay || 0; | ||
@@ -240,2 +259,57 @@ var defaultState = {}; | ||
} | ||
function logRequest(method, url, data, status) { | ||
if (method && url && !(url.indexOf(".html") > 0)) { | ||
var req = { | ||
verb: method, | ||
data: data, | ||
url: url.trim(), | ||
status: status, | ||
timestamp: new Date() | ||
}; | ||
req.state = getStateByRequest(req); | ||
_requestsLog.push(req); | ||
} | ||
} | ||
function getStateByRequest(req) { | ||
return fetchStates().filter(function(state) { | ||
if (!state.url) return false; | ||
return state.url === req.url && state.verb.toLowerCase() === req.verb.toLowerCase(); | ||
})[0]; | ||
} | ||
function getRequestsLog() { | ||
return _requestsLog; | ||
} | ||
function loadSavedStates() { | ||
_savedStates = leoStorage.getSavedStates(); | ||
addStates(_savedStates); | ||
} | ||
function addSavedState(state) { | ||
_savedStates.push(state); | ||
leoStorage.setSavedStates(_savedStates); | ||
addState(state); | ||
} | ||
function getRecordedStates() { | ||
var requestsArr = _requestsLog | ||
.map(function(req){ | ||
var state = getStateByRequest(req); | ||
return { | ||
name: state ? state.name : req.verb + " " + req.url, | ||
verb: req.verb, | ||
url: req.url, | ||
options: [{ | ||
name: req.status >= 200 && req.status < 300 ? 'Success' : 'Failure', | ||
status: req.status, | ||
data: req.data | ||
}] | ||
} | ||
}); | ||
console.log(angular.toJson(requestsArr, true)); | ||
return requestsArr; | ||
} | ||
}]); |
angular.module('leonardo', ['leonardo.templates', 'ngMockE2E']) | ||
/* wrap $httpbackend with a proxy in order to support delaying its responses | ||
* we are using the approach described in Endless Indirection: | ||
* https://endlessindirection.wordpress.com/2013/05/18/angularjs-delay-response-from-httpbackend/ | ||
*/ | ||
.config(['$provide', function($provide) { | ||
$provide.decorator('$httpBackend', ['$delegate', function($delegate) { | ||
.config(['$provide', '$httpProvider', function($provide, $httpProvider) { | ||
$httpProvider.interceptors.push('leoHttpInterceptor'); | ||
$provide.decorator('$httpBackend', ['$delegate', '$timeout', function($delegate, $timeout) { | ||
var proxy = function(method, url, data, callback, headers) { | ||
@@ -12,3 +11,3 @@ var interceptor = function() { | ||
_arguments = arguments; | ||
setTimeout(function() { | ||
$timeout(function() { | ||
callback.apply(_this, _arguments); | ||
@@ -75,3 +74,5 @@ }, proxy.delay || 0); | ||
}]); | ||
}]); | ||
}]) | ||
.run(['leoConfiguration', function(leoConfiguration) { | ||
leoConfiguration.loadSavedStates(); | ||
}]); |
@@ -1,5 +0,7 @@ | ||
angular.module('leonardo').factory('leoStorage', ['$rootScope', function storageService($rootScope) { | ||
var STATES_STORE_KEY = 'leonardo-states'; | ||
function getItem(key) { | ||
var item = localStorage.getItem(key); | ||
angular.module('leonardo').factory('leoStorage', ['$rootScope', '$window', '$leonardo', function storageService($rootScope, $window, $leonardo) { | ||
var APP_PREFIX = $leonardo.getAppPrefix() + '_', | ||
STATES_STORE_KEY = APP_PREFIX + 'leonardo-states', | ||
SAVED_STATES_KEY = APP_PREFIX + 'leonardo-unregistered-states'; | ||
function _getItem(key) { | ||
var item = $window.localStorage.getItem(key); | ||
if (!item) { | ||
@@ -11,21 +13,29 @@ return null; | ||
function setItem(key, data) { | ||
localStorage.setItem(key, angular.toJson(data)); | ||
function _setItem(key, data) { | ||
$window.localStorage.setItem(key, angular.toJson(data)); | ||
} | ||
function getStates() { | ||
return getItem(STATES_STORE_KEY) || {}; | ||
return _getItem(STATES_STORE_KEY) || {}; | ||
} | ||
function setStates(states) { | ||
setItem(STATES_STORE_KEY, states); | ||
_setItem(STATES_STORE_KEY, states); | ||
$rootScope.$emit('leonardo:setStates'); | ||
} | ||
function getSavedStates() { | ||
return _getItem(SAVED_STATES_KEY) || []; | ||
} | ||
function setSavedStates(states) { | ||
_setItem(SAVED_STATES_KEY, states); | ||
} | ||
return { | ||
getItem: getItem, | ||
setItem: setItem, | ||
setStates: setStates, | ||
getStates: getStates | ||
getStates: getStates, | ||
getSavedStates: getSavedStates, | ||
setSavedStates: setSavedStates | ||
}; | ||
}]); |
angular.module('leonardo').directive('leoWindowBody', | ||
['$http', 'leoConfiguration', function windowBodyDirective($http, leoConfiguration) { | ||
['$http', 'leoConfiguration', '$timeout', function windowBodyDirective($http, leoConfiguration, $timeout) { | ||
return { | ||
@@ -8,14 +8,20 @@ restrict: 'E', | ||
replace: true, | ||
controller: ['$scope', function($scope){ | ||
$scope.selectedItem = 'activate'; | ||
$scope.NothasUrl = function(option){ | ||
require: '^leoActivator', | ||
controller: ['$scope', function($scope) { | ||
$scope.detail = { | ||
option: 'success', | ||
delay: 0, | ||
status: 200 | ||
}; | ||
$scope.NothasUrl = function (option) { | ||
return !option.url; | ||
}; | ||
$scope.hasUrl = function(option){ | ||
$scope.hasUrl = function (option) { | ||
return !!option.url; | ||
}; | ||
$scope.deactivate = function() { | ||
$scope.states.forEach(function(state){ | ||
state.active = false; | ||
$scope.deactivate = function () { | ||
$scope.states.forEach(function (state) { | ||
state.active = false; | ||
}); | ||
@@ -25,8 +31,8 @@ leoConfiguration.deactivateAllStates(); | ||
$scope.updateState = function(state){ | ||
$scope.updateState = function (state) { | ||
if (state.active) { | ||
console.log('activate state option:' + state.name + ': ' + state.activeOption.name); | ||
console.log('activate state option:' + state.name + ': ' + state.activeOption.name); | ||
leoConfiguration.activateStateOption(state.name, state.activeOption.name); | ||
} else { | ||
console.log('deactivating state: ' + state.name); | ||
console.log('deactivating state: ' + state.name); | ||
leoConfiguration.deactivateState(state.name); | ||
@@ -40,3 +46,3 @@ } | ||
$scope.activateScenario = function(scenario){ | ||
$scope.activateScenario = function (scenario) { | ||
$scope.activeScenario = scenario; | ||
@@ -46,4 +52,91 @@ leoConfiguration.setActiveScenario(scenario); | ||
}; | ||
$scope.requests = leoConfiguration.getRequestsLog(); | ||
$scope.$watch('detail.value', function(value){ | ||
if (!value) { | ||
return; | ||
} | ||
try { | ||
$scope.detail.stringValue = value ? JSON.stringify(value, null, 4) : ''; | ||
$scope.detail.error = ''; | ||
} | ||
catch (e) { | ||
$scope.detail.error = e.message; | ||
} | ||
}); | ||
$scope.$watch('detail.stringValue', function(value){ | ||
try { | ||
$scope.detail.value = value ? JSON.parse(value) : {}; | ||
$scope.detail.error = ''; | ||
} | ||
catch(e) { | ||
$scope.detail.error = e.message; | ||
} | ||
}); | ||
$scope.requestSelect = function (request) { | ||
$scope.requests.forEach(function (request) { | ||
request.active = false; | ||
}); | ||
request.active = true; | ||
if (request.state && request.state.name) { | ||
var optionName = request.state.name + ' option ' + request.state.options.length; | ||
} | ||
angular.extend($scope.detail, { | ||
state : (request.state && request.state.name) || '', | ||
option: optionName || '', | ||
delay: 0, | ||
status: 200, | ||
stateActive: !!request.state, | ||
value: request.data || {} | ||
}); | ||
$scope.detail._unregisteredState = request; | ||
}; | ||
$scope.$on('leonardo:stateChanged', function(event, stateObj) { | ||
$scope.states = leoConfiguration.getStates(); | ||
var state = $scope.states.filter(function(state){ | ||
return state.name === stateObj.name; | ||
})[0]; | ||
if (state) { | ||
state.highlight = true; | ||
$timeout(function(){ | ||
state.highlight = false; | ||
}, 3000); | ||
} | ||
}); | ||
$scope.getStatesForExport = function () { | ||
$scope.exportStates = leoConfiguration.getStates(); | ||
} | ||
}], | ||
link: function(scope) { | ||
link: function(scope, el, attr, leoActivator) { | ||
scope.saveUnregisteredState = function () { | ||
var stateName = scope.detail.state; | ||
leoConfiguration.addSavedState({ | ||
name: stateName, | ||
verb: scope.detail._unregisteredState.verb, | ||
url: scope.detail._unregisteredState.url, | ||
options: [ | ||
{ | ||
name: scope.detail.option, | ||
status: scope.detail.status, | ||
data: scope.detail.value | ||
} | ||
] | ||
}); | ||
leoActivator.selectTab('scenarios'); | ||
}; | ||
scope.test = { | ||
@@ -50,0 +143,0 @@ url: '', |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1077978
58
2698
191