mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-01-12 22:45:52 +08:00
New Angular Scenario runner and DSL system with redesigned HTML UI.
Uses the Jasmine syntax for tests, ex:
describe('widgets', function() {
it('should verify that basic widgets work', function(){
navigateTo('widgets.html');
input('text.basic').enter('Carlos');
expect(binding('text.basic')).toEqual('Carlos');
input('text.basic').enter('Carlos Santana');
expect(binding('text.basic')).not().toEqual('Carlos Boozer');
input('text.password').enter('secret');
expect(binding('text.password')).toEqual('secret');
expect(binding('text.hidden')).toEqual('hiddenValue');
expect(binding('gender')).toEqual('male');
input('gender').select('female');
expect(binding('gender')).toEqual('female');
});
});
Note: To create new UI's implement the interface shown in angular.scenario.ui.Html.
This commit is contained in:
109
Rakefile
109
Rakefile
@@ -1,5 +1,46 @@
|
||||
include FileUtils
|
||||
|
||||
ANGULAR = [
|
||||
'src/Angular.js',
|
||||
'src/JSON.js',
|
||||
'src/Compiler.js',
|
||||
'src/Scope.js',
|
||||
'src/Injector.js',
|
||||
'src/Parser.js',
|
||||
'src/Resource.js',
|
||||
'src/Browser.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
'src/filters.js',
|
||||
'src/formatters.js',
|
||||
'src/validators.js',
|
||||
'src/services.js',
|
||||
'src/directives.js',
|
||||
'src/markups.js',
|
||||
'src/widgets.js',
|
||||
'src/AngularPublic.js',
|
||||
]
|
||||
|
||||
ANGULAR_SCENARIO = [
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/Application.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Future.js',
|
||||
'src/scenario/HtmlUI.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Runner.js',
|
||||
'src/scenario/SpecRunner.js',
|
||||
'src/scenario/dsl.js',
|
||||
'src/scenario/matchers.js',
|
||||
]
|
||||
|
||||
GENERATED_FILES = [
|
||||
'angular-debug.js',
|
||||
'angular-minified.js',
|
||||
'angular-minified.map',
|
||||
'angular-scenario.js',
|
||||
]
|
||||
|
||||
task :default => [:compile, :test]
|
||||
|
||||
desc 'Generate Externs'
|
||||
@@ -20,31 +61,27 @@ task :compile_externs do
|
||||
out.close
|
||||
end
|
||||
|
||||
desc 'Clean Generated Files'
|
||||
task :clean do
|
||||
GENERATED_FILES.each do |file|
|
||||
`rm #{file}`
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Compile Scenario'
|
||||
task :compile_scenario do
|
||||
concat = %x(cat \
|
||||
lib/jquery/jquery-1.4.2.js \
|
||||
src/scenario/angular.prefix \
|
||||
src/Angular.js \
|
||||
src/jqLite.js \
|
||||
src/JSON.js \
|
||||
src/Scope.js \
|
||||
src/Injector.js \
|
||||
src/Parser.js \
|
||||
src/Resource.js \
|
||||
src/Browser.js \
|
||||
src/apis.js \
|
||||
src/services.js \
|
||||
src/AngularPublic.js \
|
||||
src/scenario/DSL.js \
|
||||
src/scenario/Future.js \
|
||||
src/scenario/Matcher.js \
|
||||
src/scenario/Runner.js \
|
||||
src/scenario/angular.suffix \
|
||||
)
|
||||
|
||||
deps = [
|
||||
'lib/jquery/jquery-1.4.2.js',
|
||||
'src/scenario/angular.prefix',
|
||||
ANGULAR,
|
||||
ANGULAR_SCENARIO,
|
||||
'src/scenario/angular.suffix',
|
||||
]
|
||||
css = %x(cat css/angular-scenario.css)
|
||||
concat = 'cat ' + deps.flatten.join(' ')
|
||||
f = File.new("angular-scenario.js", 'w')
|
||||
f.write(concat)
|
||||
f.write(%x{#{concat}})
|
||||
f.write('document.write(\'<style type="text/css">\n')
|
||||
f.write(css.gsub(/'/, "\\'").gsub(/\n/, "\\n"));
|
||||
f.write('\n</style>\');')
|
||||
@@ -54,30 +91,14 @@ end
|
||||
desc 'Compile JavaScript'
|
||||
task :compile => [:compile_externs, :compile_scenario] do
|
||||
|
||||
concat = %x(cat \
|
||||
src/angular.prefix \
|
||||
src/Angular.js \
|
||||
src/JSON.js \
|
||||
src/Compiler.js \
|
||||
src/Scope.js \
|
||||
src/Injector.js \
|
||||
src/Parser.js \
|
||||
src/Resource.js \
|
||||
src/Browser.js \
|
||||
src/jqLite.js \
|
||||
src/apis.js \
|
||||
src/filters.js \
|
||||
src/formatters.js \
|
||||
src/validators.js \
|
||||
src/services.js \
|
||||
src/directives.js \
|
||||
src/markups.js \
|
||||
src/widgets.js \
|
||||
src/AngularPublic.js \
|
||||
src/angular.suffix \
|
||||
)
|
||||
deps = [
|
||||
'src/angular.prefix',
|
||||
ANGULAR,
|
||||
'src/angular.suffix',
|
||||
]
|
||||
f = File.new("angular-debug.js", 'w')
|
||||
f.write(concat)
|
||||
concat = 'cat ' + deps.flatten.join(' ')
|
||||
f.write(%x{#{concat}})
|
||||
f.close
|
||||
|
||||
%x(java -jar lib/compiler-closure/compiler.jar \
|
||||
|
||||
@@ -1,76 +1,199 @@
|
||||
@charset "UTF-8";
|
||||
/* CSS Document */
|
||||
|
||||
#runner {
|
||||
position: absolute;
|
||||
top:5px;
|
||||
left:10px;
|
||||
right:10px;
|
||||
height: 200px;
|
||||
/** Structure */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.console {
|
||||
display: block;
|
||||
overflow: scroll;
|
||||
height: 200px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
#testView {
|
||||
position: absolute;
|
||||
bottom:10px;
|
||||
top:230px;
|
||||
left:10px;
|
||||
right:10px;
|
||||
}
|
||||
|
||||
#testView iframe {
|
||||
#header {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
li.running > span {
|
||||
background-color: yellow;
|
||||
#specs {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
#runner span {
|
||||
background-color: green;
|
||||
#header .angular {
|
||||
font-family: Courier New, monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#runner .fail > span {
|
||||
background-color: red;
|
||||
#header h1 {
|
||||
font-weight: normal;
|
||||
float: left;
|
||||
font-size: 30px;
|
||||
line-height: 30px;
|
||||
margin: 0;
|
||||
padding: 10px 10px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.collapsed > ul {
|
||||
display: none;
|
||||
#frame h2,
|
||||
#specs h2 {
|
||||
margin: 0;
|
||||
padding: 0.5em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
#status-legend {
|
||||
margin-top: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
//////
|
||||
#header,
|
||||
#frame,
|
||||
.test-info,
|
||||
.test-actions li {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.run, .info, .error {
|
||||
display: block;
|
||||
padding: 0 1em;
|
||||
#frame {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#frame iframe {
|
||||
width: 100%;
|
||||
height: 758px;
|
||||
}
|
||||
|
||||
#frame .popout {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#frame iframe {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.tests li,
|
||||
.test-actions li,
|
||||
.test-it li,
|
||||
.test-it ol,
|
||||
.status-display {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.tests,
|
||||
.test-it ol,
|
||||
.status-display {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.test-info {
|
||||
margin-left: 1em;
|
||||
margin-top: 0.5em;
|
||||
border-radius: 8px 0 0 8px;
|
||||
-webkit-border-radius: 8px 0 0 8px;
|
||||
-moz-border-radius: 8px 0 0 8px;
|
||||
}
|
||||
|
||||
.test-it ol {
|
||||
margin-left: 2.5em;
|
||||
}
|
||||
|
||||
.status-display,
|
||||
.status-display li {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.status-display li {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.timer-result,
|
||||
.test-title {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.timer-result {
|
||||
width: 4em;
|
||||
padding: 0 10px;
|
||||
text-align: right;
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.run {
|
||||
background-color: lightgrey;
|
||||
padding: 0 .2em;
|
||||
.test-it pre,
|
||||
.test-actions pre {
|
||||
clear: left;
|
||||
margin-left: 6em;
|
||||
}
|
||||
|
||||
.run.pass {
|
||||
background-color: lightgreen;
|
||||
.test-describe .test-describe {
|
||||
margin: 5px 5px 10px 2em;
|
||||
}
|
||||
|
||||
.run.fail {
|
||||
background-color: lightred;
|
||||
.test-actions .status-pending .test-title:before {
|
||||
content: '» ';
|
||||
}
|
||||
|
||||
.name, .time, .state {
|
||||
padding-right: 2em;
|
||||
/** Colors */
|
||||
|
||||
#header {
|
||||
background-color: #F2C200;
|
||||
}
|
||||
|
||||
error {
|
||||
color: red;
|
||||
}
|
||||
#specs h2 {
|
||||
border-top: 2px solid #BABAD1;
|
||||
}
|
||||
|
||||
#specs h2,
|
||||
#frame h2 {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
#frame {
|
||||
border: 1px solid #BABAD1;
|
||||
}
|
||||
|
||||
.test-describe .test-describe {
|
||||
border-left: 1px solid #BABAD1;
|
||||
border-right: 1px solid #BABAD1;
|
||||
border-bottom: 1px solid #BABAD1;
|
||||
}
|
||||
|
||||
.status-display {
|
||||
border: 1px solid #777;
|
||||
}
|
||||
|
||||
.status-display .status-pending,
|
||||
.status-pending .test-info {
|
||||
background-color: #F9EEBC;
|
||||
}
|
||||
|
||||
.status-display .status-success,
|
||||
.status-success .test-info {
|
||||
background-color: #B1D7A1;
|
||||
}
|
||||
|
||||
.status-display .status-failure,
|
||||
.status-failure .test-info {
|
||||
background-color: #FF8286;
|
||||
}
|
||||
|
||||
.status-display .status-error,
|
||||
.status-error .test-info {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.test-actions .status-success .test-title {
|
||||
color: #30B30A;
|
||||
}
|
||||
|
||||
.test-actions .status-failure .test-title {
|
||||
color: #DF0000;
|
||||
}
|
||||
|
||||
.test-actions .status-error .test-title {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.test-actions .timer-result {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ load:
|
||||
- src/JSON.js
|
||||
- src/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Runner.js
|
||||
- src/scenario/Scenario.js
|
||||
- src/scenario/*.js
|
||||
- test/angular-mocks.js
|
||||
- test/scenario/*.js
|
||||
|
||||
@@ -9,7 +9,7 @@ load:
|
||||
- src/JSON.js
|
||||
- src/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Runner.js
|
||||
- src/scenario/Scenario.js
|
||||
- src/scenario/*.js
|
||||
- test/angular-mocks.js
|
||||
- test/scenario/*.js
|
||||
|
||||
@@ -5,3 +5,7 @@ th {
|
||||
tr {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.redbox {
|
||||
background-color: red;
|
||||
}
|
||||
@@ -1,25 +1,58 @@
|
||||
describe('widgets', function(){
|
||||
describe('widgets', function() {
|
||||
it('should verify that basic widgets work', function(){
|
||||
browser.navigateTo('widgets.html');
|
||||
|
||||
expect('{{text.basic}}').toEqual('');
|
||||
input('text.basic').enter('John');
|
||||
expect('{{text.basic}}').toEqual('John');
|
||||
|
||||
expect('{{text.password}}').toEqual('');
|
||||
navigateTo('widgets.html');
|
||||
input('text.basic').enter('Carlos');
|
||||
expect(binding('text.basic')).toEqual('Carlos');
|
||||
pause(2);
|
||||
input('text.basic').enter('Carlos Santana');
|
||||
pause(2);
|
||||
expect(binding('text.basic')).not().toEqual('Carlos Boozer');
|
||||
pause(2);
|
||||
input('text.password').enter('secret');
|
||||
expect('{{text.password}}').toEqual('secret');
|
||||
|
||||
expect('{{text.hidden}}').toEqual('hiddenValue');
|
||||
|
||||
expect('{{gender}}').toEqual('male');
|
||||
expect(binding('text.password')).toEqual('secret');
|
||||
expect(binding('text.hidden')).toEqual('hiddenValue');
|
||||
expect(binding('gender')).toEqual('male');
|
||||
pause(2);
|
||||
input('gender').select('female');
|
||||
input('gender').isChecked('female');
|
||||
expect('{{gender}}').toEqual('female');
|
||||
|
||||
// expect('{{tea}}').toBeChecked();
|
||||
// input('gender').select('female');
|
||||
// expect('{{gender}}').toEqual('female');
|
||||
|
||||
expect(binding('gender')).toEqual('female');
|
||||
pause(2);
|
||||
});
|
||||
describe('do it again', function() {
|
||||
it('should verify that basic widgets work', function(){
|
||||
navigateTo('widgets.html');
|
||||
input('text.basic').enter('Carlos');
|
||||
expect(binding('text.basic')).toEqual('Carlos');
|
||||
pause(2);
|
||||
input('text.basic').enter('Carlos Santana');
|
||||
pause(2);
|
||||
expect(binding('text.basic')).toEqual('Carlos Santana');
|
||||
pause(2);
|
||||
input('text.password').enter('secret');
|
||||
expect(binding('text.password')).toEqual('secret');
|
||||
expect(binding('text.hidden')).toEqual('hiddenValue');
|
||||
expect(binding('gender')).toEqual('male');
|
||||
pause(2);
|
||||
input('gender').select('female');
|
||||
expect(binding('gender')).toEqual('female');
|
||||
pause(2);
|
||||
});
|
||||
});
|
||||
it('should verify that basic widgets work', function(){
|
||||
navigateTo('widgets.html');
|
||||
input('text.basic').enter('Carlos');
|
||||
expect(binding('text.basic')).toEqual('Carlos');
|
||||
pause(2);
|
||||
input('text.basic').enter('Carlos Santana');
|
||||
pause(2);
|
||||
expect(binding('text.basic')).toEqual('Carlos Santana');
|
||||
pause(2);
|
||||
input('text.password').enter('secret');
|
||||
expect(binding('text.password')).toEqual('secret');
|
||||
expect(binding('text.hidden')).toEqual('hiddenValue');
|
||||
expect(binding('gender')).toEqual('male');
|
||||
pause(2);
|
||||
input('gender').select('female');
|
||||
expect(binding('gender')).toEqual('female');
|
||||
pause(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -270,11 +270,11 @@ function equals(o1, o2) {
|
||||
} else {
|
||||
keySet = {};
|
||||
for(key in o1) {
|
||||
if (key.charAt(0) !== '$' && !equals(o1[key], o2[key])) return false;
|
||||
if (key.charAt(0) !== '$' && !isFunction(o1[key]) && !equals(o1[key], o2[key])) return false;
|
||||
keySet[key] = true;
|
||||
}
|
||||
for(key in o2) {
|
||||
if (key.charAt(0) !== '$' && keySet[key] !== true) return false;
|
||||
if (!keySet[key] && key.charAt(0) !== '$' && !isFunction(o2[key])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
51
src/scenario/Application.js
Normal file
51
src/scenario/Application.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Represents the application currently being tested and abstracts usage
|
||||
* of iframes or separate windows.
|
||||
*/
|
||||
angular.scenario.Application = function(context) {
|
||||
this.context = context;
|
||||
context.append('<h2>Current URL: <a href="about:blank">None</a></h2>');
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the jQuery collection of frames. Don't use this directly because
|
||||
* frames may go stale.
|
||||
*
|
||||
* @return {Object} jQuery collection
|
||||
*/
|
||||
angular.scenario.Application.prototype.getFrame = function() {
|
||||
return this.context.find('> iframe');
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the window of the test runner frame. Always favor executeAction()
|
||||
* instead of this method since it prevents you from getting a stale window.
|
||||
*
|
||||
* @return {Object} the window of the frame
|
||||
*/
|
||||
angular.scenario.Application.prototype.getWindow = function() {
|
||||
var contentWindow = this.getFrame().attr('contentWindow');
|
||||
if (!contentWindow)
|
||||
throw 'No window available because frame not loaded.';
|
||||
return contentWindow;
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the location of the frame.
|
||||
*/
|
||||
angular.scenario.Application.prototype.navigateTo = function(url, onloadFn) {
|
||||
this.getFrame().remove();
|
||||
this.context.append('<iframe src=""></iframe>');
|
||||
this.context.find('> h2 a').attr('href', url).text(url);
|
||||
this.getFrame().attr('src', url).load(onloadFn);
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes a function in the context of the tested application.
|
||||
*
|
||||
* @param {Function} The callback to execute. function($window, $document)
|
||||
*/
|
||||
angular.scenario.Application.prototype.executeAction = function(action) {
|
||||
var $window = this.getWindow();
|
||||
return action.call($window, _jQuery($window.document), $window);
|
||||
};
|
||||
@@ -1,131 +1,134 @@
|
||||
angular.scenario.dsl.browser = {
|
||||
navigateTo: function(url){
|
||||
var location = this.location;
|
||||
return $scenario.addFuture('Navigate to: ' + url, function(done){
|
||||
var self = this;
|
||||
this.testFrame.load(function(){
|
||||
self.testFrame.unbind();
|
||||
self.testWindow = self.testFrame[0].contentWindow;
|
||||
self.testDocument = self.jQuery(self.testWindow.document);
|
||||
self.$browser = self.testWindow.angular.service.$browser();
|
||||
self.notifyWhenNoOutstandingRequests =
|
||||
bind(self.$browser, self.$browser.notifyWhenNoOutstandingRequests);
|
||||
self.notifyWhenNoOutstandingRequests(done);
|
||||
});
|
||||
if (this.testFrame.attr('src') == url) {
|
||||
this.testFrame[0].contentWindow.location.reload();
|
||||
} else {
|
||||
this.testFrame.attr('src', url);
|
||||
location.setLocation(url);
|
||||
}
|
||||
});
|
||||
},
|
||||
location: {
|
||||
href: "",
|
||||
hash: "",
|
||||
toEqual: function(url) {
|
||||
return (this.hash === "" ? (url == this.href) :
|
||||
(url == (this.href + "/#/" + this.hash)));
|
||||
},
|
||||
setLocation: function(url) {
|
||||
var urlParts = url.split("/#/");
|
||||
this.href = urlParts[0] || "";
|
||||
this.hash = urlParts[1] || "";
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Shared DSL statements that are useful to all scenarios.
|
||||
*/
|
||||
|
||||
angular.scenario.dsl.input = function(selector) {
|
||||
var namePrefix = "input '" + selector + "'";
|
||||
return {
|
||||
enter: function(value) {
|
||||
return $scenario.addFuture(namePrefix + " enter '" + value + "'", function(done) {
|
||||
var input = this.testDocument.find('input[name=' + selector + ']');
|
||||
input.val(value);
|
||||
this.testWindow.angular.element(input[0]).trigger('change');
|
||||
done();
|
||||
});
|
||||
},
|
||||
select: function(value) {
|
||||
return $scenario.addFuture(namePrefix + " select '" + value + "'", function(done) {
|
||||
var input = this.testDocument.
|
||||
find(':radio[name$=@' + selector + '][value=' + value + ']');
|
||||
jqLiteWrap(input[0]).trigger('click');
|
||||
input[0].checked = true;
|
||||
done();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Usage:
|
||||
* pause(seconds) pauses the test for specified number of seconds
|
||||
*/
|
||||
angular.scenario.dsl('pause', function() {
|
||||
return function(time) {
|
||||
return this.addFuture('pause for ' + time + ' seconds', function(done) {
|
||||
this.setTimeout(function() { done(null, time * 1000); }, time * 1000);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* expect(future).{matcher} where matcher is one of the matchers defined
|
||||
* with angular.scenario.matcher
|
||||
*
|
||||
* ex. expect(binding("name")).toEqual("Elliott")
|
||||
*/
|
||||
angular.scenario.dsl('expect', function() {
|
||||
var chain = angular.extend({}, angular.scenario.matcher);
|
||||
|
||||
chain.not = function() {
|
||||
this.inverse = true;
|
||||
return chain;
|
||||
};
|
||||
};
|
||||
|
||||
angular.scenario.dsl.NG_BIND_PATTERN =/\{\{[^\}]+\}\}/;
|
||||
|
||||
angular.scenario.dsl.repeater = function(selector) {
|
||||
var namePrefix = "repeater '" + selector + "'";
|
||||
return {
|
||||
count: function() {
|
||||
return $scenario.addFuture(namePrefix + ' count', function(done) {
|
||||
done(this.testDocument.find(selector).size());
|
||||
});
|
||||
},
|
||||
collect: function(collectSelector) {
|
||||
return $scenario.addFuture(
|
||||
namePrefix + " collect '" + collectSelector + "'",
|
||||
function(done) {
|
||||
var self = this;
|
||||
var doCollect = bind(this, function() {
|
||||
var repeaterArray = [], ngBindPattern;
|
||||
var startIndex = collectSelector.search(
|
||||
angular.scenario.dsl.NG_BIND_PATTERN);
|
||||
if (startIndex >= 0) {
|
||||
ngBindPattern = collectSelector.substring(
|
||||
startIndex + 2, collectSelector.length - 2);
|
||||
collectSelector = '*';
|
||||
|
||||
}
|
||||
this.testDocument.find(selector).each(function() {
|
||||
var element = self.jQuery(this);
|
||||
element.find(collectSelector).
|
||||
each(function() {
|
||||
var foundElem = self.jQuery(this);
|
||||
if (foundElem.attr('ng:bind') == ngBindPattern) {
|
||||
repeaterArray.push(foundElem.text());
|
||||
}
|
||||
});
|
||||
});
|
||||
return repeaterArray;
|
||||
});
|
||||
done(doCollect());
|
||||
});
|
||||
}
|
||||
|
||||
return function(future) {
|
||||
this.future = future;
|
||||
return chain;
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
angular.scenario.dsl.element = function(selector) {
|
||||
var namePrefix = "Element '" + selector + "'";
|
||||
var futureJquery = {};
|
||||
for (key in (jQuery || _jQuery).fn) {
|
||||
(function(){
|
||||
var jqFnName = key;
|
||||
var jqFn = (jQuery || _jQuery).fn[key];
|
||||
futureJquery[key] = function() {
|
||||
var jqArgs = arguments;
|
||||
return $scenario.addFuture(namePrefix + "." + jqFnName + "()",
|
||||
function(done) {
|
||||
var self = this, repeaterArray = [], ngBindPattern;
|
||||
var startIndex = selector.search(angular.scenario.dsl.NG_BIND_PATTERN);
|
||||
if (startIndex >= 0) {
|
||||
ngBindPattern = selector.substring(startIndex + 2, selector.length - 2);
|
||||
var element = this.testDocument.find('*').filter(function() {
|
||||
return self.jQuery(this).attr('ng:bind') == ngBindPattern;
|
||||
/**
|
||||
* Usage:
|
||||
* navigateTo(future|string) where url a string or future with a value
|
||||
* of a URL to navigate to
|
||||
*/
|
||||
angular.scenario.dsl('navigateTo', function() {
|
||||
return function(url) {
|
||||
var application = this.application;
|
||||
var name = url;
|
||||
if (url.name) {
|
||||
name = ' value of ' + url.name;
|
||||
}
|
||||
return this.addFuture('navigate to ' + name, function(done) {
|
||||
application.navigateTo(url.value || url, function() {
|
||||
application.executeAction(function() {
|
||||
if (this.angular) {
|
||||
var $browser = this.angular.service.$browser();
|
||||
$browser.poll();
|
||||
$browser.notifyWhenNoOutstandingRequests(function() {
|
||||
done(null, url.value || url);
|
||||
});
|
||||
done(jqFn.apply(element, jqArgs));
|
||||
} else {
|
||||
done(jqFn.apply(this.testDocument.find(selector), jqArgs));
|
||||
done(null, url.value || url);
|
||||
}
|
||||
});
|
||||
};
|
||||
})();
|
||||
}
|
||||
return futureJquery;
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* input(name).enter(value) enters value in input with specified name
|
||||
* input(name).check() checks checkbox
|
||||
* input(name).select(value) selects the readio button with specified name/value
|
||||
*/
|
||||
angular.scenario.dsl('input', function() {
|
||||
var chain = {};
|
||||
|
||||
chain.enter = function(value) {
|
||||
var spec = this;
|
||||
return this.addFutureAction("input '" + this.name + "' enter '" + value + "'", function(done) {
|
||||
var input = _jQuery(this.document).find('input[name=' + spec.name + ']');
|
||||
if (!input.length)
|
||||
return done("Input named '" + spec.name + "' does not exist.");
|
||||
input.val(value);
|
||||
this.angular.element(input[0]).trigger('change');
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
chain.check = function() {
|
||||
var spec = this;
|
||||
return this.addFutureAction("checkbox '" + this.name + "' toggle", function(done) {
|
||||
var input = _jQuery(this.document).
|
||||
find('input:checkbox[name=' + spec.name + ']');
|
||||
if (!input.length)
|
||||
return done("Input named '" + spec.name + "' does not exist.");
|
||||
this.angular.element(input[0]).trigger('click');
|
||||
input.attr('checked', !input.attr('checked'));
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
chain.select = function(value) {
|
||||
var spec = this;
|
||||
return this.addFutureAction("radio button '" + this.name + "' toggle '" + value + "'", function(done) {
|
||||
var input = _jQuery(this.document).
|
||||
find('input:radio[name$="@' + spec.name + '"][value="' + value + '"]');
|
||||
if (!input.length)
|
||||
return done("Input named '" + spec.name + "' does not exist.");
|
||||
this.angular.element(input[0]).trigger('click');
|
||||
input.attr('checked', !input.attr('checked'));
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
return function(name) {
|
||||
this.name = name;
|
||||
return chain;
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* binding(name) returns the value of a binding
|
||||
*/
|
||||
angular.scenario.dsl('binding', function() {
|
||||
return function(name) {
|
||||
return this.addFutureAction("select binding '" + name + "'", function(done) {
|
||||
var element = _jQuery(this.document).find('[ng\\:bind="' + name + '"]');
|
||||
if (!element.length)
|
||||
return done("Binding named '" + name + "' does not exist.");
|
||||
done(null, element.text());
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
108
src/scenario/Describe.js
Normal file
108
src/scenario/Describe.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* The representation of define blocks. Don't used directly, instead use
|
||||
* define() in your tests.
|
||||
*/
|
||||
angular.scenario.Describe = function(descName, parent) {
|
||||
this.beforeEachFns = [];
|
||||
this.afterEachFns = [];
|
||||
this.its = [];
|
||||
this.children = [];
|
||||
this.name = descName;
|
||||
this.parent = parent;
|
||||
this.id = angular.scenario.Describe.id++;
|
||||
|
||||
/**
|
||||
* Calls all before functions.
|
||||
*/
|
||||
var beforeEachFns = this.beforeEachFns;
|
||||
this.setupBefore = function() {
|
||||
if (parent) parent.setupBefore.call(this);
|
||||
angular.foreach(beforeEachFns, function(fn) { fn.call(this); }, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls all after functions.
|
||||
*/
|
||||
var afterEachFns = this.afterEachFns;
|
||||
this.setupAfter = function() {
|
||||
angular.foreach(afterEachFns, function(fn) { fn.call(this); }, this);
|
||||
if (parent) parent.setupAfter.call(this);
|
||||
};
|
||||
};
|
||||
|
||||
// Shared Unique ID generator for every describe block
|
||||
angular.scenario.Describe.id = 0;
|
||||
|
||||
/**
|
||||
* Defines a block to execute before each it or nested describe.
|
||||
*
|
||||
* @param {Function} Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.beforeEach = function(body) {
|
||||
this.beforeEachFns.push(body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a block to execute after each it or nested describe.
|
||||
*
|
||||
* @param {Function} Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.afterEach = function(body) {
|
||||
this.afterEachFns.push(body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new describe block that's a child of this one.
|
||||
*
|
||||
* @param {String} Name of the block. Appended to the parent block's name.
|
||||
* @param {Function} Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.describe = function(name, body) {
|
||||
var child = new angular.scenario.Describe(name, this);
|
||||
this.children.push(child);
|
||||
body.call(child);
|
||||
};
|
||||
|
||||
/**
|
||||
* Use to disable a describe block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.xdescribe = angular.noop;
|
||||
|
||||
/**
|
||||
* Defines a test.
|
||||
*
|
||||
* @param {String} Name of the test.
|
||||
* @param {Function} Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.it = function(name, body) {
|
||||
var self = this;
|
||||
this.its.push({
|
||||
definition: this,
|
||||
name: name,
|
||||
fn: function() {
|
||||
self.setupBefore.call(this);
|
||||
body.call(this);
|
||||
self.setupAfter.call(this);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Use to disable a test block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.xit = angular.noop;
|
||||
|
||||
/**
|
||||
* Gets an array of functions representing all the tests (recursively).
|
||||
* that can be executed with SpecRunner's.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.getSpecs = function() {
|
||||
var specs = arguments[0] || [];
|
||||
angular.foreach(this.children, function(child) {
|
||||
child.getSpecs(specs);
|
||||
});
|
||||
angular.foreach(this.its, function(it) {
|
||||
specs.push(it);
|
||||
});
|
||||
return specs;
|
||||
};
|
||||
@@ -1,13 +1,22 @@
|
||||
function Future(name, behavior) {
|
||||
/**
|
||||
* A future action in a spec.
|
||||
*/
|
||||
angular.scenario.Future = function(name, behavior) {
|
||||
this.name = name;
|
||||
this.behavior = behavior;
|
||||
this.fulfilled = false;
|
||||
this.value = _undefined;
|
||||
}
|
||||
|
||||
Future.prototype = {
|
||||
fulfill: function(value) {
|
||||
this.fulfilled = true;
|
||||
this.value = value;
|
||||
}
|
||||
this.value = undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the behavior of the closure.
|
||||
*
|
||||
* @param {Function} Callback function(error, result)
|
||||
*/
|
||||
angular.scenario.Future.prototype.execute = function(doneFn) {
|
||||
this.behavior(angular.bind(this, function(error, result) {
|
||||
this.fulfilled = true;
|
||||
this.value = error || result;
|
||||
doneFn(error, result);
|
||||
}));
|
||||
};
|
||||
|
||||
204
src/scenario/HtmlUI.js
Normal file
204
src/scenario/HtmlUI.js
Normal file
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* User Interface for the Scenario Runner.
|
||||
*
|
||||
* @param {Object} The jQuery UI object for the UI.
|
||||
*/
|
||||
angular.scenario.ui.Html = function(context) {
|
||||
this.context = context;
|
||||
context.append(
|
||||
'<div id="header">' +
|
||||
' <h1><span class="angular"><angular/></span>: Scenario Test Runner</h1>' +
|
||||
' <ul id="status-legend" class="status-display">' +
|
||||
' <li class="status-error">0 Errors</li>' +
|
||||
' <li class="status-failure">0 Failures</li>' +
|
||||
' <li class="status-success">0 Passed</li>' +
|
||||
' </ul>' +
|
||||
'</div>' +
|
||||
'<div id="specs">' +
|
||||
' <div class="test-children"></div>' +
|
||||
'</div>'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new spec to the UI.
|
||||
*
|
||||
* @param {Object} The spec object created by the Describe object.
|
||||
*/
|
||||
angular.scenario.ui.Html.prototype.addSpec = function(spec) {
|
||||
var specContext = this.findContext(spec.definition);
|
||||
specContext.find('> .tests').append(
|
||||
'<li class="status-pending test-it"></li>'
|
||||
);
|
||||
specContext = specContext.find('> .tests li:last');
|
||||
return new angular.scenario.ui.Html.Spec(specContext, spec.name,
|
||||
angular.bind(this, function(status) {
|
||||
var status = this.context.find('#status-legend .status-' + status);
|
||||
var parts = status.text().split(' ');
|
||||
var value = (parts[0] * 1) + 1;
|
||||
status.text(value + ' ' + parts[1]);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the context of a spec block defined by the passed definition.
|
||||
*
|
||||
* @param {Object} The definition created by the Describe object.
|
||||
*/
|
||||
angular.scenario.ui.Html.prototype.findContext = function(definition) {
|
||||
var path = [];
|
||||
var currentContext = this.context.find('#specs');
|
||||
var currentDefinition = definition;
|
||||
while (currentDefinition && currentDefinition.name) {
|
||||
path.unshift(currentDefinition);
|
||||
currentDefinition = currentDefinition.parent;
|
||||
}
|
||||
angular.foreach(path, angular.bind(this, function(defn) {
|
||||
var id = 'describe-' + defn.id;
|
||||
if (!this.context.find('#' + id).length) {
|
||||
currentContext.find('> .test-children').append(
|
||||
'<div class="test-describe" id="' + id + '">' +
|
||||
' <h2></h2>' +
|
||||
' <div class="test-children"></div>' +
|
||||
' <ul class="tests"></ul>' +
|
||||
'</div>'
|
||||
);
|
||||
this.context.find('#' + id).find('> h2').text('describe: ' + defn.name);
|
||||
}
|
||||
currentContext = this.context.find('#' + id);
|
||||
}));
|
||||
return this.context.find('#describe-' + definition.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* A spec block in the UI.
|
||||
*
|
||||
* @param {Object} The jQuery object for the context of the spec.
|
||||
* @param {String} The name of the spec.
|
||||
* @param {Function} Callback function(status) to call when complete.
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec = function(context, name, doneFn) {
|
||||
this.status = 'pending';
|
||||
this.context = context;
|
||||
this.startTime = new Date().getTime();
|
||||
this.doneFn = doneFn;
|
||||
context.append(
|
||||
'<div class="test-info">' +
|
||||
' <p class="test-title">' +
|
||||
' <span class="timer-result"></span>' +
|
||||
' <span class="test-name"></span>' +
|
||||
' </p>' +
|
||||
'</div>' +
|
||||
'<ol class="test-actions">' +
|
||||
'</ol>'
|
||||
);
|
||||
context.find('> .test-info .test-name').text('it ' + name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new Step to this spec and returns it.
|
||||
*
|
||||
* @param {String} The name of the step.
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec.prototype.addStep = function(name) {
|
||||
this.context.find('> .test-actions').append('<li class="status-pending"></li>');
|
||||
var stepContext = this.context.find('> .test-actions li:last');
|
||||
var self = this;
|
||||
return new angular.scenario.ui.Html.Step(stepContext, name, function(status) {
|
||||
self.status = status;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Completes the spec and sets the timer value.
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec.prototype.complete = function() {
|
||||
this.context.removeClass('status-pending');
|
||||
var endTime = new Date().getTime();
|
||||
this.context.find("> .test-info .timer-result")
|
||||
.text((endTime - this.startTime) + "ms");
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the spec, possibly with an error.
|
||||
*
|
||||
* @param {Object} An optional error
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec.prototype.finish = function(error) {
|
||||
this.complete();
|
||||
if (error) {
|
||||
if (this.status !== 'failure') {
|
||||
this.status = 'error';
|
||||
}
|
||||
this.context.append('<pre></pre>');
|
||||
this.context.find('pre:first').text(error.stack || error.toString());
|
||||
}
|
||||
this.context.addClass('status-' + this.status);
|
||||
this.doneFn(this.status);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the spec, but with a Fatal Error.
|
||||
*
|
||||
* @param {Object} Required error
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec.prototype.error = function(error) {
|
||||
this.finish(error);
|
||||
};
|
||||
|
||||
/**
|
||||
* A single step inside an it block (or a before/after function).
|
||||
*
|
||||
* @param {Object} The jQuery object for the context of the step.
|
||||
* @param {String} The name of the step.
|
||||
* @param {Function} Callback function(status) to call when complete.
|
||||
*/
|
||||
angular.scenario.ui.Html.Step = function(context, name, doneFn) {
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
this.startTime = new Date().getTime();
|
||||
this.doneFn = doneFn;
|
||||
context.append(
|
||||
'<span class="timer-result"></span>' +
|
||||
'<span class="test-title"></span>'
|
||||
);
|
||||
context.find('> .test-title').text(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Completes the step and sets the timer value.
|
||||
*/
|
||||
angular.scenario.ui.Html.Step.prototype.complete = function() {
|
||||
this.context.removeClass('status-pending');
|
||||
var endTime = new Date().getTime();
|
||||
this.context.find(".timer-result")
|
||||
.text((endTime - this.startTime) + "ms");
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the step, possibly with an error.
|
||||
*
|
||||
* @param {Object} An optional error
|
||||
*/
|
||||
angular.scenario.ui.Html.Step.prototype.finish = function(error) {
|
||||
this.complete();
|
||||
if (error) {
|
||||
this.context.addClass('status-failure');
|
||||
this.doneFn('failure');
|
||||
} else {
|
||||
this.context.addClass('status-success');
|
||||
this.doneFn('success');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the step, but with a Fatal Error.
|
||||
*
|
||||
* @param {Object} Required error
|
||||
*/
|
||||
angular.scenario.ui.Html.Step.prototype.error = function(error) {
|
||||
this.complete();
|
||||
this.context.addClass('status-error');
|
||||
this.doneFn('error');
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
function Matcher(scope, future, logger) {
|
||||
var self = scope.$scenario = this;
|
||||
this.logger = logger;
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
Matcher.addMatcher = function(name, matcher) {
|
||||
Matcher.prototype[name] = function(expected) {
|
||||
var future = this.future;
|
||||
$scenario.addFuture(
|
||||
'expect ' + future.name + ' ' + name + ' ' + expected,
|
||||
function(done){
|
||||
if (!matcher(future.value, expected))
|
||||
throw "Expected " + expected + ' but was ' + future.value;
|
||||
done();
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
Matcher.addMatcher('toEqual', angular.equals);
|
||||
@@ -1,183 +1,95 @@
|
||||
angular['scenario'] = angular['scenario'] || (angular['scenario'] = {});
|
||||
angular.scenario['dsl'] = angular.scenario['dsl'] || (angular.scenario['dsl'] = {});
|
||||
|
||||
angular.scenario.Runner = function(scope, jQuery){
|
||||
var self = scope.$scenario = this;
|
||||
this.scope = scope;
|
||||
this.jQuery = jQuery;
|
||||
this.scope.$testrun = {done: false, results: []};
|
||||
|
||||
var specs = this.specs = {};
|
||||
this.currentSpec = {name: '', futures: []};
|
||||
var path = [];
|
||||
this.scope.describe = function(name, body){
|
||||
path.push(name);
|
||||
body();
|
||||
path.pop();
|
||||
};
|
||||
var beforeEach = noop;
|
||||
var afterEach = noop;
|
||||
this.scope.beforeEach = function(body) {
|
||||
beforeEach = body;
|
||||
};
|
||||
this.scope.afterEach = function(body) {
|
||||
afterEach = body;
|
||||
};
|
||||
this.scope.expect = function(future) {
|
||||
return new Matcher(self, future, self.logger);
|
||||
};
|
||||
this.scope.it = function(name, body) {
|
||||
var specName = path.join(' ') + ': it ' + name;
|
||||
self.currentSpec = specs[specName] = {
|
||||
name: specName,
|
||||
futures: []
|
||||
};
|
||||
try {
|
||||
beforeEach();
|
||||
body();
|
||||
} catch(err) {
|
||||
self.addFuture(err.message || 'ERROR', function(){
|
||||
throw err;
|
||||
});
|
||||
} finally {
|
||||
afterEach();
|
||||
}
|
||||
self.currentSpec = _null;
|
||||
};
|
||||
this.logger = function returnNoop(){
|
||||
return extend(returnNoop, {close:noop, fail:noop});
|
||||
/**
|
||||
* Runner for scenarios.
|
||||
*/
|
||||
angular.scenario.Runner = function($window) {
|
||||
this.$window = $window;
|
||||
this.rootDescribe = new angular.scenario.Describe();
|
||||
this.currentDescribe = this.rootDescribe;
|
||||
this.api = {
|
||||
it: this.it,
|
||||
xit: angular.noop,
|
||||
describe: this.describe,
|
||||
xdescribe: angular.noop,
|
||||
beforeEach: this.beforeEach,
|
||||
afterEach: this.afterEach
|
||||
};
|
||||
angular.foreach(this.api, angular.bind(this, function(fn, key) {
|
||||
this.$window[key] = angular.bind(this, fn);
|
||||
}));
|
||||
};
|
||||
|
||||
angular.scenario.Runner.prototype = {
|
||||
run: function(body){
|
||||
var jQuery = this.jQuery;
|
||||
body.append(
|
||||
'<div id="runner">' +
|
||||
'<div class="console"></div>' +
|
||||
'</div>' +
|
||||
'<div id="testView">' +
|
||||
'<iframe></iframe>' +
|
||||
'</div>');
|
||||
var console = body.find('#runner .console');
|
||||
console.find('li').live('click', function(){
|
||||
jQuery(this).toggleClass('collapsed');
|
||||
});
|
||||
this.testFrame = body.find('#testView iframe');
|
||||
function logger(parent) {
|
||||
var container;
|
||||
return function(type, text) {
|
||||
if (!container) {
|
||||
container = jQuery('<ul></ul>');
|
||||
parent.append(container);
|
||||
}
|
||||
var element = jQuery('<li class="running '+type+'"><span></span></li>');
|
||||
element.find('span').text(text);
|
||||
container.append(element);
|
||||
return extend(logger(element), {
|
||||
close: function(){
|
||||
element.removeClass('running');
|
||||
if(!element.hasClass('fail'))
|
||||
element.addClass('collapsed');
|
||||
console.scrollTop(console[0].scrollHeight);
|
||||
},
|
||||
fail: function(){
|
||||
element.removeClass('running');
|
||||
var current = element;
|
||||
while (current[0] != console[0]) {
|
||||
if (current.is('li'))
|
||||
current.addClass('fail');
|
||||
current = current.parent();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Defines a describe block of a spec.
|
||||
*
|
||||
* @param {String} Name of the block
|
||||
* @param {Function} Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.describe = function(name, body) {
|
||||
var self = this;
|
||||
this.currentDescribe.describe(name, function() {
|
||||
var parentDescribe = self.currentDescribe;
|
||||
self.currentDescribe = this;
|
||||
try {
|
||||
body.call(this);
|
||||
} finally {
|
||||
self.currentDescribe = parentDescribe;
|
||||
}
|
||||
this.logger = logger(console);
|
||||
var specNames = [];
|
||||
foreach(this.specs, function(spec, name){
|
||||
specNames.push(name);
|
||||
}, this);
|
||||
specNames.sort();
|
||||
var self = this;
|
||||
function callback(){
|
||||
var next = specNames.shift();
|
||||
if(next) {
|
||||
self.execute(next, callback);
|
||||
} else {
|
||||
self.scope.$testrun.done = true;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a test in a describe block of a spec.
|
||||
*
|
||||
* @param {String} Name of the block
|
||||
* @param {Function} Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.it = function(name, body) {
|
||||
this.currentDescribe.it(name, body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a function to be called before each it block in the describe
|
||||
* (and before all nested describes).
|
||||
*
|
||||
* @param {Function} Callback to execute
|
||||
*/
|
||||
angular.scenario.Runner.prototype.beforeEach = function(body) {
|
||||
this.currentDescribe.beforeEach(body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a function to be called after each it block in the describe
|
||||
* (and before all nested describes).
|
||||
*
|
||||
* @param {Function} Callback to execute
|
||||
*/
|
||||
angular.scenario.Runner.prototype.afterEach = function(body) {
|
||||
this.currentDescribe.afterEach(body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a function to be called before each it block in the describe
|
||||
* (and before all nested describes).
|
||||
*
|
||||
* @param {Function} Callback to execute
|
||||
*/
|
||||
angular.scenario.Runner.prototype.run = function(ui, application, specRunnerClass, specsDone) {
|
||||
var $root = angular.scope({}, angular.service);
|
||||
var self = this;
|
||||
var specs = this.rootDescribe.getSpecs();
|
||||
$root.application = application;
|
||||
$root.ui = ui;
|
||||
$root.setTimeout = function() {
|
||||
return self.$window.setTimeout.apply(self.$window, arguments);
|
||||
};
|
||||
asyncForEach(specs, angular.bind(this, function(spec, specDone) {
|
||||
var runner = angular.scope($root);
|
||||
runner.$become(specRunnerClass);
|
||||
angular.foreach(angular.scenario.dsl, angular.bind(this, function(fn, key) {
|
||||
this.$window[key] = function() {
|
||||
return fn.call($root).apply(angular.scope(runner), arguments);
|
||||
}
|
||||
}
|
||||
callback();
|
||||
},
|
||||
|
||||
addFuture: function(name, behavior) {
|
||||
var future = new Future(name, behavior);
|
||||
this.currentSpec.futures.push(future);
|
||||
return future;
|
||||
},
|
||||
|
||||
execute: function(name, callback) {
|
||||
var spec = this.specs[name],
|
||||
self = this,
|
||||
futuresFulfilled = [],
|
||||
result = {
|
||||
passed: false,
|
||||
failed: false,
|
||||
finished: false,
|
||||
fail: function(error) {
|
||||
result.passed = false;
|
||||
result.failed = true;
|
||||
result.error = error;
|
||||
result.log('fail', isString(error) ? error : toJson(error)).fail();
|
||||
}
|
||||
},
|
||||
specThis = createScope({
|
||||
result: result,
|
||||
jQuery: this.jQuery,
|
||||
testFrame: this.testFrame,
|
||||
testWindow: this.testWindow
|
||||
}, angularService, {});
|
||||
this.self = specThis;
|
||||
var futureLogger = this.logger('spec', name);
|
||||
spec.nextFutureIndex = 0;
|
||||
function done() {
|
||||
result.finished = true;
|
||||
futureLogger.close();
|
||||
self.self = _null;
|
||||
(callback||noop).call(specThis);
|
||||
}
|
||||
function next(value){
|
||||
if (spec.nextFutureIndex > 0) {
|
||||
spec.futures[spec.nextFutureIndex - 1].fulfill(value);
|
||||
}
|
||||
var future = spec.futures[spec.nextFutureIndex];
|
||||
(result.log || {close:noop}).close();
|
||||
result.log = _null;
|
||||
if (future) {
|
||||
spec.nextFutureIndex ++;
|
||||
result.log = futureLogger('future', future.name);
|
||||
futuresFulfilled.push(future.name);
|
||||
try {
|
||||
future.behavior.call(specThis, next);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
result.fail(e);
|
||||
self.scope.$testrun.results.push(
|
||||
{name: name, passed: false, error: e, steps: futuresFulfilled});
|
||||
done();
|
||||
}
|
||||
} else {
|
||||
result.passed = !result.failed;
|
||||
self.scope.$testrun.results.push({
|
||||
name: name,
|
||||
passed: !result.failed,
|
||||
error: result.error,
|
||||
steps: futuresFulfilled});
|
||||
done();
|
||||
}
|
||||
}
|
||||
next();
|
||||
return specThis;
|
||||
}
|
||||
};
|
||||
}));
|
||||
runner.run(ui, spec, specDone);
|
||||
}), specsDone || angular.noop);
|
||||
};
|
||||
|
||||
103
src/scenario/Scenario.js
Normal file
103
src/scenario/Scenario.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Setup file for the Scenario.
|
||||
* Must be first in the compilation/bootstrap list.
|
||||
*/
|
||||
|
||||
// Public namespace
|
||||
angular.scenario = {};
|
||||
|
||||
// Namespace for the UI
|
||||
angular.scenario.ui = {};
|
||||
|
||||
/**
|
||||
* Defines a new DSL statement. If your factory function returns a Future
|
||||
* it's returned, otherwise the result is assumed to be a map of functions
|
||||
* for chaining. Chained functions are subject to the same rules.
|
||||
*
|
||||
* Note: All functions on the chain are bound to the chain scope so values
|
||||
* set on "this" in your statement function are available in the chained
|
||||
* functions.
|
||||
*
|
||||
* @param {String} The name of the statement
|
||||
* @param {Function} Factory function(application), return a function for
|
||||
* the statement.
|
||||
*/
|
||||
angular.scenario.dsl = function(name, fn) {
|
||||
angular.scenario.dsl[name] = function() {
|
||||
function executeStatement(statement, args) {
|
||||
var result = statement.apply(this, args);
|
||||
if (angular.isFunction(result) || result instanceof angular.scenario.Future)
|
||||
return result;
|
||||
var self = this;
|
||||
var chain = angular.extend({}, result);
|
||||
angular.foreach(chain, function(value, name) {
|
||||
if (angular.isFunction(value)) {
|
||||
chain[name] = angular.bind(self, function() {
|
||||
return executeStatement.call(self, value, arguments);
|
||||
});
|
||||
} else {
|
||||
chain[name] = value;
|
||||
}
|
||||
});
|
||||
return chain;
|
||||
}
|
||||
var statement = fn.apply(this, arguments);
|
||||
return function() {
|
||||
return executeStatement.call(this, statement, arguments);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a new matcher for use with the expects() statement. The value
|
||||
* this.actual (like in Jasmine) is available in your matcher to compare
|
||||
* against. Your function should return a boolean. The future is automatically
|
||||
* created for you.
|
||||
*
|
||||
* @param {String} The name of the matcher
|
||||
* @param {Function} The matching function(expected).
|
||||
*/
|
||||
angular.scenario.matcher = function(name, fn) {
|
||||
angular.scenario.matcher[name] = function(expected) {
|
||||
var prefix = 'expect ' + this.future.name + ' ';
|
||||
if (this.inverse) {
|
||||
prefix += 'not ';
|
||||
}
|
||||
this.addFuture(prefix + name + ' ' + angular.toJson(expected),
|
||||
angular.bind(this, function(done) {
|
||||
this.actual = this.future.value;
|
||||
if ((this.inverse && fn.call(this, expected)) ||
|
||||
(!this.inverse && !fn.call(this, expected))) {
|
||||
this.error = 'expected ' + angular.toJson(expected) +
|
||||
' but was ' + angular.toJson(this.actual);
|
||||
}
|
||||
done(this.error);
|
||||
})
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterates through list with iterator function that must call the
|
||||
* continueFunction to continute iterating.
|
||||
*
|
||||
* @param {Array} list to iterate over
|
||||
* @param {Function} Callback function(value, continueFunction)
|
||||
* @param {Function} Callback function(error, result) called when iteration
|
||||
* finishes or an error occurs.
|
||||
*/
|
||||
function asyncForEach(list, iterator, done) {
|
||||
var i = 0;
|
||||
function loop(error) {
|
||||
if (error || i >= list.length) {
|
||||
done(error);
|
||||
} else {
|
||||
try {
|
||||
iterator(list[i++], loop);
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
loop();
|
||||
}
|
||||
78
src/scenario/SpecRunner.js
Normal file
78
src/scenario/SpecRunner.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* This class is the "this" of the it/beforeEach/afterEach method.
|
||||
* Responsibilities:
|
||||
* - "this" for it/beforeEach/afterEach
|
||||
* - keep state for single it/beforeEach/afterEach execution
|
||||
* - keep track of all of the futures to execute
|
||||
* - run single spec (execute each future)
|
||||
*/
|
||||
angular.scenario.SpecRunner = function() {
|
||||
this.futures = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes a spec which is an it block with associated before/after functions
|
||||
* based on the describe nesting.
|
||||
*
|
||||
* @param {Object} An angular.scenario.UI implementation
|
||||
* @param {Object} A spec object
|
||||
* @param {Object} An angular.scenario.Application instance
|
||||
* @param {Function} Callback function that is called when the spec finshes.
|
||||
*/
|
||||
angular.scenario.SpecRunner.prototype.run = function(ui, spec, specDone) {
|
||||
var specUI = ui.addSpec(spec);
|
||||
|
||||
try {
|
||||
spec.fn.call(this);
|
||||
} catch (e) {
|
||||
specUI.error(e);
|
||||
specDone();
|
||||
return;
|
||||
}
|
||||
|
||||
asyncForEach(
|
||||
this.futures,
|
||||
function(future, futureDone) {
|
||||
var stepUI = specUI.addStep(future.name);
|
||||
try {
|
||||
future.execute(function(error) {
|
||||
stepUI.finish(error);
|
||||
futureDone(error);
|
||||
});
|
||||
} catch (e) {
|
||||
stepUI.error(e);
|
||||
rethrow(e);
|
||||
}
|
||||
},
|
||||
function(e) {
|
||||
specUI.finish(e);
|
||||
specDone();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new future action.
|
||||
*
|
||||
* @param {String} Name of the future
|
||||
* @param {Function} Behavior of the future
|
||||
*/
|
||||
angular.scenario.SpecRunner.prototype.addFuture = function(name, behavior) {
|
||||
var future = new angular.scenario.Future(name, angular.bind(this, behavior));
|
||||
this.futures.push(future);
|
||||
return future;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new future action to be executed on the application window.
|
||||
*
|
||||
* @param {String} Name of the future
|
||||
* @param {Function} Behavior of the future
|
||||
*/
|
||||
angular.scenario.SpecRunner.prototype.addFutureAction = function(name, behavior) {
|
||||
return this.addFuture(name, function(done) {
|
||||
this.application.executeAction(function() {
|
||||
behavior.call(this, done);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -22,9 +22,3 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
(function(window, document, previousOnLoad){
|
||||
window.angular = {
|
||||
scenario: {
|
||||
dsl: window
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
var $scenario = new angular.scenario.Runner(window);
|
||||
|
||||
var $scenarioRunner = new angular.scenario.Runner(window, jQuery);
|
||||
|
||||
window.onload = function(){
|
||||
window.onload = function() {
|
||||
try {
|
||||
if (previousOnLoad) previousOnLoad();
|
||||
} catch(e) {}
|
||||
$scenarioRunner.run(jQuery(window.document.body));
|
||||
jQuery(document.body).append(
|
||||
'<div id="runner"></div>' +
|
||||
'<div id="frame"></div>'
|
||||
);
|
||||
var frame = jQuery('#frame');
|
||||
var runner = jQuery('#runner');
|
||||
var application = new angular.scenario.Application(frame);
|
||||
var ui = new angular.scenario.ui.Html(runner);
|
||||
$scenario.run(ui, application, function(error) {
|
||||
frame.remove();
|
||||
if (error) {
|
||||
if (window.console) {
|
||||
console.log(error);
|
||||
if (error.stack) {
|
||||
console.log(error.stack);
|
||||
}
|
||||
} else {
|
||||
// Do something for IE
|
||||
alert(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
})(window, document, window.onload);
|
||||
|
||||
64
src/scenario/bootstrap.js
vendored
64
src/scenario/bootstrap.js
vendored
@@ -1,4 +1,4 @@
|
||||
(function(onLoadDelegate){
|
||||
(function(previousOnLoad){
|
||||
var prefix = (function(){
|
||||
var filename = /(.*\/)bootstrap.js(#(.*))?/;
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
@@ -10,6 +10,7 @@
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
function addScript(path) {
|
||||
document.write('<script type="text/javascript" src="' + prefix + path + '"></script>');
|
||||
}
|
||||
@@ -18,26 +19,51 @@
|
||||
document.write('<link rel="stylesheet" type="text/css" href="' + prefix + path + '"/>');
|
||||
}
|
||||
|
||||
window.angular = {
|
||||
scenario: {
|
||||
dsl: window
|
||||
}
|
||||
window.onload = function(){
|
||||
try {
|
||||
if (previousOnLoad) previousOnLoad();
|
||||
} catch(e) {}
|
||||
_jQuery(document.body).append(
|
||||
'<div id="runner"></div>' +
|
||||
'<div id="frame"></div>'
|
||||
);
|
||||
var frame = _jQuery('#frame');
|
||||
var runner = _jQuery('#runner');
|
||||
var application = new angular.scenario.Application(frame);
|
||||
var ui = new angular.scenario.ui.Html(runner);
|
||||
$scenario.run(ui, application, angular.scenario.SpecRunner, function(error) {
|
||||
frame.remove();
|
||||
if (error) {
|
||||
if (window.console) {
|
||||
console.log(error.stack || error);
|
||||
} else {
|
||||
// Do something for IE
|
||||
alert(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.onload = function(){
|
||||
setTimeout(function(){
|
||||
$scenarioRunner.run(jQuery(window.document.body));
|
||||
}, 0);
|
||||
(onLoadDelegate||function(){})();
|
||||
};
|
||||
addCSS("../../css/angular-scenario.css");
|
||||
addScript("../../lib/jquery/jquery-1.4.2.js");
|
||||
addScript("Runner.js");
|
||||
addScript("../Angular.js");
|
||||
addScript("../JSON.js");
|
||||
addScript("DSL.js");
|
||||
document.write('<script type="text/javascript">' +
|
||||
'$scenarioRunner = new angular.scenario.Runner(window, jQuery);' +
|
||||
'</script>');
|
||||
})(window.onload);
|
||||
addScript("../angular-bootstrap.js");
|
||||
|
||||
addScript("Scenario.js");
|
||||
addScript("Application.js");
|
||||
addScript("Describe.js");
|
||||
addScript("Future.js");
|
||||
addScript("HtmlUI.js");
|
||||
addScript("Runner.js");
|
||||
addScript("SpecRunner.js");
|
||||
addScript("dsl.js");
|
||||
addScript("matchers.js");
|
||||
|
||||
// Create the runner (which also sets up the global API)
|
||||
document.write(
|
||||
'<script type="text/javascript">' +
|
||||
'var _jQuery = jQuery.noConflict(true);' +
|
||||
'var $scenario = new angular.scenario.Runner(window);' +
|
||||
'</script>'
|
||||
);
|
||||
|
||||
})(window.onload);
|
||||
|
||||
39
src/scenario/matchers.js
Normal file
39
src/scenario/matchers.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Matchers for implementing specs. Follows the Jasmine spec conventions.
|
||||
*/
|
||||
|
||||
angular.scenario.matcher('toEqual', function(expected) {
|
||||
return angular.equals(this.actual, expected);
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toBeDefined', function() {
|
||||
return angular.isDefined(this.actual);
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toBeTruthy', function() {
|
||||
return this.actual;
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toBeFalsy', function() {
|
||||
return !this.actual;
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toMatch', function(expected) {
|
||||
return new RegExp(expected).test(this.actual);
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toBeNull', function() {
|
||||
return this.actual === null;
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toContain', function(expected) {
|
||||
return includes(this.actual, expected);
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toBeLessThan', function(expected) {
|
||||
return this.actual < expected;
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toBeGreaterThan', function(expected) {
|
||||
return this.actual > expected;
|
||||
});
|
||||
@@ -86,6 +86,10 @@ describe('equals', function(){
|
||||
expect(equals({name:'misko'}, {name:'misko', $id:2})).toEqual(true);
|
||||
expect(equals({name:'misko', $id:1}, {name:'misko'})).toEqual(true);
|
||||
});
|
||||
|
||||
it('should ignore functions', function(){
|
||||
expect(equals({func: function() {}}, {bar: function() {}})).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseKeyValue', function() {
|
||||
|
||||
75
test/scenario/ApplicationSpec.js
Normal file
75
test/scenario/ApplicationSpec.js
Normal file
@@ -0,0 +1,75 @@
|
||||
describe('angular.scenario.Application', function() {
|
||||
var app, frames;
|
||||
|
||||
beforeEach(function() {
|
||||
frames = _jQuery("<div></div>");
|
||||
app = new angular.scenario.Application(frames);
|
||||
});
|
||||
|
||||
it('should return new $window and $document after navigate', function() {
|
||||
var testWindow, testDocument, counter = 0;
|
||||
app.navigateTo = noop;
|
||||
app.getWindow = function() {
|
||||
return {x:counter++, document:{x:counter++}};
|
||||
};
|
||||
app.navigateTo('http://www.google.com/');
|
||||
app.executeAction(function($document, $window) {
|
||||
testWindow = $window;
|
||||
testDocument = $document;
|
||||
});
|
||||
app.navigateTo('http://www.google.com/');
|
||||
app.executeAction(function($document, $window) {
|
||||
expect($window).not.toEqual(testWindow);
|
||||
expect($document).not.toEqual(testDocument);
|
||||
});
|
||||
});
|
||||
|
||||
it('should execute callback on $window of frame', function() {
|
||||
var testWindow = {document: {}};
|
||||
app.getWindow = function() {
|
||||
return testWindow;
|
||||
};
|
||||
app.executeAction(function($document, $window) {
|
||||
expect(this).toEqual($window);
|
||||
expect(this).toEqual(testWindow);
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new iframe each time', function() {
|
||||
app.navigateTo('about:blank');
|
||||
var frame = app.getFrame();
|
||||
frame.attr('test', true);
|
||||
app.navigateTo('about:blank');
|
||||
expect(app.getFrame().attr('test')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should URL description bar', function() {
|
||||
app.navigateTo('about:blank');
|
||||
var anchor = frames.find('> h2 a');
|
||||
expect(anchor.attr('href')).toEqual('about:blank');
|
||||
expect(anchor.text()).toEqual('about:blank');
|
||||
});
|
||||
|
||||
it('should call onload handler when frame loads', function() {
|
||||
var called;
|
||||
app.getFrame = function() {
|
||||
// Mock a little jQuery
|
||||
var result = {
|
||||
remove: function() {
|
||||
return result;
|
||||
},
|
||||
attr: function(key, value) {
|
||||
return (!value) ? 'attribute value' : result;
|
||||
},
|
||||
load: function() {
|
||||
called = true;
|
||||
}
|
||||
};
|
||||
return result;
|
||||
};
|
||||
app.navigateTo('about:blank', function() {
|
||||
called = true;
|
||||
});
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,181 +1,232 @@
|
||||
describe("DSL", function() {
|
||||
/**
|
||||
* Very basic Mock of angular.
|
||||
*/
|
||||
function AngularMock() {
|
||||
this.reset();
|
||||
this.service = this;
|
||||
}
|
||||
|
||||
var lastDocument, executeFuture, Expect;
|
||||
AngularMock.prototype.reset = function() {
|
||||
this.log = [];
|
||||
};
|
||||
|
||||
AngularMock.prototype.element = function(node) {
|
||||
this.log.push('element(' + node.nodeName.toLowerCase() + ')');
|
||||
return this;
|
||||
};
|
||||
|
||||
AngularMock.prototype.trigger = function(value) {
|
||||
this.log.push('element().trigger(' + value + ')');
|
||||
};
|
||||
|
||||
AngularMock.prototype.$browser = function() {
|
||||
this.log.push('$brower()');
|
||||
return this;
|
||||
};
|
||||
|
||||
AngularMock.prototype.poll = function() {
|
||||
this.log.push('$brower.poll()');
|
||||
return this;
|
||||
};
|
||||
|
||||
AngularMock.prototype.notifyWhenNoOutstandingRequests = function(fn) {
|
||||
this.log.push('$brower.notifyWhenNoOutstandingRequests()');
|
||||
fn();
|
||||
};
|
||||
|
||||
describe("angular.scenario.dsl", function() {
|
||||
var $window;
|
||||
var $root;
|
||||
var application;
|
||||
|
||||
beforeEach(function() {
|
||||
setUpContext();
|
||||
executeFuture = function(future, html, callback) {
|
||||
lastDocument = _jQuery('<div>' + html + '</div>');
|
||||
lastFrame = _jQuery('<iframe>' + lastDocument + '</iframe>');
|
||||
_jQuery(document.body).append(lastDocument);
|
||||
var specThis = {
|
||||
testWindow: window,
|
||||
testDocument: lastDocument,
|
||||
testFrame: lastFrame,
|
||||
jQuery: _jQuery
|
||||
};
|
||||
future.behavior.call(specThis, callback || noop);
|
||||
$window = {
|
||||
document: _jQuery("<div></div>"),
|
||||
angular: new AngularMock()
|
||||
};
|
||||
Expect = _window.expect;
|
||||
});
|
||||
|
||||
describe("input", function() {
|
||||
|
||||
var input = angular.scenario.dsl.input;
|
||||
|
||||
it('should enter', function() {
|
||||
var future = input('name').enter('John');
|
||||
expect(future.name).toEqual("input 'name' enter 'John'");
|
||||
executeFuture(future, '<input type="text" name="name" />');
|
||||
expect(lastDocument.find('input').val()).toEqual('John');
|
||||
});
|
||||
|
||||
it('should select', function() {
|
||||
var future = input('gender').select('female');
|
||||
expect(future.name).toEqual("input 'gender' select 'female'");
|
||||
executeFuture(future,
|
||||
'<input type="radio" name="0@gender" value="male" checked/>' +
|
||||
'<input type="radio" name="0@gender" value="female"/>');
|
||||
expect(lastDocument.find(':radio:checked').length).toEqual(1);
|
||||
expect(lastDocument.find(':radio:checked').val()).toEqual('female');
|
||||
});
|
||||
});
|
||||
|
||||
describe('browser', function() {
|
||||
var browser = angular.scenario.dsl.browser;
|
||||
it('shoud return true if location with empty hash provided is same ' +
|
||||
'as location of the page', function() {
|
||||
browser.location.href = "http://server";
|
||||
expect(browser.location.toEqual("http://server")).toEqual(true);
|
||||
});
|
||||
it('shoud return true if location with hash provided is same ' +
|
||||
'as location of the page', function() {
|
||||
browser.location.href = "http://server";
|
||||
browser.location.hash = "hashPath";
|
||||
expect(browser.location.toEqual("http://server/#/hashPath")).toEqual(true);
|
||||
});
|
||||
it('should return true if the location provided is the same as which ' +
|
||||
'browser navigated to', function() {
|
||||
var future = browser.navigateTo("http://server/#/hashPath");
|
||||
expect(future.name).toEqual("Navigate to: http://server/#/hashPath");
|
||||
executeFuture(future, '<input type="text" name="name" />');
|
||||
expect(browser.location.toEqual("http://server/#/hashPath")).toEqual(true);
|
||||
expect(browser.location.toEqual("http://server/")).toEqual(false);
|
||||
|
||||
future = browser.navigateTo("http://server/");
|
||||
expect(future.name).toEqual("Navigate to: http://server/");
|
||||
executeFuture(future, '<input type="text" name="name" />');
|
||||
expect(browser.location.toEqual("http://server/")).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('repeater', function() {
|
||||
|
||||
var repeater = angular.scenario.dsl.repeater;
|
||||
var html;
|
||||
beforeEach(function() {
|
||||
html = "<table>" +
|
||||
"<tr class='epic'>" +
|
||||
"<td class='hero-name'>" +
|
||||
"<span ng:bind='hero'>John Marston</span>" +
|
||||
"</td>" +
|
||||
"<td class='game-name'>" +
|
||||
"<span ng:bind='game'>Red Dead Redemption</span>" +
|
||||
"</td>" +
|
||||
"</tr>" +
|
||||
"<tr class='epic'>" +
|
||||
"<td class='hero-name'>" +
|
||||
"<span ng:bind='hero'>Nathan Drake</span>" +
|
||||
"</td>" +
|
||||
"<td class='game-name'>" +
|
||||
"<span ng:bind='game'>Uncharted</span>" +
|
||||
"</td>" +
|
||||
"</tr>" +
|
||||
"</table>";
|
||||
});
|
||||
it('should count', function() {
|
||||
var future = repeater('.repeater-row').count();
|
||||
expect(future.name).toEqual("repeater '.repeater-row' count");
|
||||
executeFuture(future,
|
||||
"<div class='repeater-row'>a</div>" +
|
||||
"<div class='repeater-row'>b</div>",
|
||||
function(value) {
|
||||
future.fulfill(value);
|
||||
$root = angular.scope({}, angular.service);
|
||||
$root.futures = [];
|
||||
$root.addFuture = function(name, fn) {
|
||||
this.futures.push(name);
|
||||
fn.call(this, function(error, result) {
|
||||
$root.futureError = error;
|
||||
$root.futureResult = result;
|
||||
});
|
||||
expect(future.fulfilled).toBeTruthy();
|
||||
expect(future.value).toEqual(2);
|
||||
});
|
||||
|
||||
function assertFutureState(future, expectedName, expectedValue) {
|
||||
expect(future.name).toEqual(expectedName);
|
||||
executeFuture(future, html, function(value) {
|
||||
future.fulfill(value);
|
||||
});
|
||||
expect(future.fulfilled).toBeTruthy();
|
||||
expect(future.value).toEqual(expectedValue);
|
||||
}
|
||||
it('should collect bindings', function() {
|
||||
assertFutureState(repeater('.epic').collect('{{hero}}'),
|
||||
"repeater '.epic' collect '{{hero}}'",
|
||||
['John Marston', 'Nathan Drake']);
|
||||
assertFutureState(repeater('.epic').collect('{{game}}'),
|
||||
"repeater '.epic' collect '{{game}}'",
|
||||
['Red Dead Redemption', 'Uncharted']);
|
||||
});
|
||||
it('should collect normal selectors', function() {
|
||||
assertFutureState(repeater('.epic').collect('.hero-name'),
|
||||
"repeater '.epic' collect '.hero-name'",
|
||||
['John Marston', 'Nathan Drake']);
|
||||
assertFutureState(repeater('.epic').collect('.game-name'),
|
||||
"repeater '.epic' collect '.game-name'",
|
||||
['Red Dead Redemption', 'Uncharted']);
|
||||
});
|
||||
it('should collect normal attributes', function() {
|
||||
//TODO(shyamseshadri) : Left as an exercise to the user
|
||||
});
|
||||
};
|
||||
$root.application = new angular.scenario.Application($window.document);
|
||||
$root.application.getWindow = function() {
|
||||
return $window;
|
||||
};
|
||||
$root.application.navigateTo = function(url, callback) {
|
||||
$window.location = url;
|
||||
callback();
|
||||
};
|
||||
// Just use the real one since it delegates to this.addFuture
|
||||
$root.addFutureAction = angular.scenario.
|
||||
SpecRunner.prototype.addFutureAction;
|
||||
});
|
||||
|
||||
describe('element', function() {
|
||||
var element = angular.scenario.dsl.element;
|
||||
var html;
|
||||
|
||||
describe('Pause', function() {
|
||||
beforeEach(function() {
|
||||
html = '<div class="container">' +
|
||||
'<div class="reports-detail">' +
|
||||
'<span class="desc">Description : ' +
|
||||
'<span ng:bind="report.description">Details...</span>' +
|
||||
'</span>' +
|
||||
'<span>Date created: ' +
|
||||
'<span ng:bind="report.creationDate">01/01/01</span>' +
|
||||
'</span>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
$root.setTimeout = function(fn, value) {
|
||||
$root.timerValue = value;
|
||||
fn();
|
||||
};
|
||||
});
|
||||
function timeTravel(future) {
|
||||
executeFuture(future, html, function(value) { future.fulfill(value); });
|
||||
expect(future.fulfilled).toBeTruthy();
|
||||
}
|
||||
it('should find elements on the page and provide jquery api', function() {
|
||||
var future = element('.reports-detail').text();
|
||||
expect(future.name).toEqual("Element '.reports-detail'.text()");
|
||||
timeTravel(future);
|
||||
expect(future.value).
|
||||
toEqual('Description : Details...Date created: 01/01/01');
|
||||
// expect(future.value.find('.desc').text()).
|
||||
// toEqual('Description : Details...');
|
||||
});
|
||||
it('should find elements with angular syntax', function() {
|
||||
var future = element('{{report.description}}').text();
|
||||
expect(future.name).toEqual("Element '{{report.description}}'.text()");
|
||||
timeTravel(future);
|
||||
expect(future.value).toEqual('Details...');
|
||||
// expect(future.value.attr('ng:bind')).toEqual('report.description');
|
||||
});
|
||||
it('should be able to click elements', function(){
|
||||
var future = element('.link-class').click();
|
||||
expect(future.name).toEqual("Element '.link-class'.click()");
|
||||
executeFuture(future, html, function(value) { future.fulfill(value); });
|
||||
expect(future.fulfilled).toBeTruthy();
|
||||
// TODO(rajat): look for some side effect from click happening?
|
||||
|
||||
it('should pause for specified seconds', function() {
|
||||
angular.scenario.dsl.pause.call($root).call($root, 10);
|
||||
expect($root.timerValue).toEqual(10000);
|
||||
expect($root.futureResult).toEqual(10000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Expect', function() {
|
||||
it('should chain and execute matcher', function() {
|
||||
var future = {value: 10};
|
||||
var result = angular.scenario.dsl.expect.call($root).call($root, future);
|
||||
result.toEqual(10);
|
||||
expect($root.futureError).toBeUndefined();
|
||||
expect($root.futureResult).toBeUndefined();
|
||||
var result = angular.scenario.dsl.expect.call($root).call($root, future);
|
||||
result.toEqual(20);
|
||||
expect($root.futureError).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('NavigateTo', function() {
|
||||
it('should allow a string url', function() {
|
||||
angular.scenario.dsl.navigateTo.call($root).call($root, 'http://myurl');
|
||||
expect($window.location).toEqual('http://myurl');
|
||||
expect($root.futureResult).toEqual('http://myurl');
|
||||
});
|
||||
|
||||
it('should allow a future url', function() {
|
||||
var future = {name: 'future name', value: 'http://myurl'};
|
||||
angular.scenario.dsl.navigateTo.call($root).call($root, future);
|
||||
expect($window.location).toEqual('http://myurl');
|
||||
expect($root.futureResult).toEqual('http://myurl');
|
||||
});
|
||||
|
||||
it('should complete if angular is missing from app frame', function() {
|
||||
delete $window.angular;
|
||||
angular.scenario.dsl.navigateTo.call($root).call($root, 'http://myurl');
|
||||
expect($window.location).toEqual('http://myurl');
|
||||
expect($root.futureResult).toEqual('http://myurl');
|
||||
});
|
||||
|
||||
it('should wait for angular notify when no requests pending', function() {
|
||||
angular.scenario.dsl.navigateTo.call($root).call($root, 'url');
|
||||
expect($window.angular.log).toContain('$brower.poll()');
|
||||
expect($window.angular.log)
|
||||
.toContain('$brower.notifyWhenNoOutstandingRequests()');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Element Finding', function() {
|
||||
var doc;
|
||||
//TODO(esprehn): Work around a bug in jQuery where attribute selectors
|
||||
// only work if they are executed on a real document, not an element.
|
||||
//
|
||||
// ex. jQuery('#foo').find('[name="bar"]') // fails
|
||||
// ex. jQuery('#foo [name="bar"]') // works, wtf?
|
||||
//
|
||||
beforeEach(function() {
|
||||
doc = _jQuery('<div id="angular-scenario-binding"></div>');
|
||||
_jQuery(document.body).append(doc);
|
||||
$window.document = window.document;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
_jQuery(document.body)
|
||||
.find('#angular-scenario-binding')
|
||||
.remove();
|
||||
});
|
||||
|
||||
describe('Binding', function() {
|
||||
it('should select binding by name', function() {
|
||||
doc.append('<span ng:bind="foo.bar">some value</span>');
|
||||
angular.scenario.dsl.binding.call($root).call($root, 'foo.bar');
|
||||
expect($root.futureResult).toEqual('some value');
|
||||
});
|
||||
|
||||
it('should return error if no binding exists', function() {
|
||||
angular.scenario.dsl.binding.call($root).call($root, 'foo.bar');
|
||||
expect($root.futureError).toMatch(/does not exist/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Input', function() {
|
||||
it('should change value in text input', function() {
|
||||
doc.append('<input name="test.input" value="something">');
|
||||
var chain = angular.scenario.dsl.input
|
||||
.call($root).call($root, 'test.input');
|
||||
chain.enter('foo');
|
||||
expect($window.angular.log).toContain('element(input)');
|
||||
expect($window.angular.log).toContain('element().trigger(change)');
|
||||
expect(_jQuery('input[name="test.input"]').val()).toEqual('foo');
|
||||
});
|
||||
|
||||
it('should return error if no input exists', function() {
|
||||
var chain = angular.scenario.dsl.input
|
||||
.call($root).call($root, 'test.input');
|
||||
chain.enter('foo');
|
||||
expect($root.futureError).toMatch(/does not exist/);
|
||||
});
|
||||
|
||||
it('should toggle checkbox state', function() {
|
||||
doc.append('<input type="checkbox" name="test.input" checked>');
|
||||
expect(_jQuery('input[name="test.input"]')
|
||||
.attr('checked')).toBeTruthy();
|
||||
var chain = angular.scenario.dsl.input
|
||||
.call($root).call($root, 'test.input');
|
||||
chain.check();
|
||||
expect($window.angular.log).toContain('element(input)');
|
||||
expect($window.angular.log).toContain('element().trigger(click)');
|
||||
expect(_jQuery('input[name="test.input"]')
|
||||
.attr('checked')).toBeFalsy();
|
||||
$window.angular.reset();
|
||||
chain.check();
|
||||
expect($window.angular.log).toContain('element(input)');
|
||||
expect($window.angular.log).toContain('element().trigger(click)');
|
||||
expect(_jQuery('input[name="test.input"]')
|
||||
.attr('checked')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return error if checkbox does not exist', function() {
|
||||
var chain = angular.scenario.dsl.input
|
||||
.call($root).call($root, 'test.input');
|
||||
chain.check();
|
||||
expect($root.futureError).toMatch(/does not exist/);
|
||||
});
|
||||
|
||||
it('should select option from radio group', function() {
|
||||
doc.append(
|
||||
'<input type="radio" name="0@test.input" value="foo">' +
|
||||
'<input type="radio" name="0@test.input" value="bar" checked>'
|
||||
);
|
||||
expect(_jQuery('input[name="0@test.input"][value="bar"]')
|
||||
.attr('checked')).toBeTruthy();
|
||||
expect(_jQuery('input[name="0@test.input"][value="foo"]')
|
||||
.attr('checked')).toBeFalsy();
|
||||
var chain = angular.scenario.dsl.input
|
||||
.call($root).call($root, 'test.input');
|
||||
chain.select('foo');
|
||||
expect($window.angular.log).toContain('element(input)');
|
||||
expect($window.angular.log).toContain('element().trigger(click)');
|
||||
expect(_jQuery('input[name="0@test.input"][value="bar"]')
|
||||
.attr('checked')).toBeFalsy();
|
||||
expect(_jQuery('input[name="0@test.input"][value="foo"]')
|
||||
.attr('checked')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return error if radio button does not exist', function() {
|
||||
var chain = angular.scenario.dsl.input
|
||||
.call($root).call($root, 'test.input');
|
||||
chain.select('foo');
|
||||
expect($root.futureError).toMatch(/does not exist/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
85
test/scenario/DescribeSpec.js
Normal file
85
test/scenario/DescribeSpec.js
Normal file
@@ -0,0 +1,85 @@
|
||||
describe('angular.scenario.Describe', function() {
|
||||
var log;
|
||||
var root;
|
||||
|
||||
beforeEach(function() {
|
||||
root = new angular.scenario.Describe();
|
||||
|
||||
/**
|
||||
* Simple callback logging system. Use to assert proper order of calls.
|
||||
*/
|
||||
log = function(text) {
|
||||
log.text = log.text + text;
|
||||
};
|
||||
log.fn = function(text) {
|
||||
return function(done){
|
||||
log(text);
|
||||
(done || angular.noop)();
|
||||
};
|
||||
};
|
||||
log.reset = function() {
|
||||
log.text = '';
|
||||
};
|
||||
log.reset();
|
||||
});
|
||||
|
||||
it('should handle basic nested case', function() {
|
||||
root.describe('A', function(){
|
||||
this.beforeEach(log.fn('{'));
|
||||
this.afterEach(log.fn('}'));
|
||||
this.it('1', log.fn('1'));
|
||||
this.describe('B', function(){
|
||||
this.beforeEach(log.fn('('));
|
||||
this.afterEach(log.fn(')'));
|
||||
this.it('2', log.fn('2'));
|
||||
});
|
||||
});
|
||||
var specs = root.getSpecs();
|
||||
expect(specs.length).toEqual(2);
|
||||
|
||||
expect(specs[0].name).toEqual('2');
|
||||
specs[0].fn();
|
||||
expect(log.text).toEqual('{(2)}');
|
||||
|
||||
log.reset();
|
||||
expect(specs[1].name).toEqual('1');
|
||||
specs[1].fn();
|
||||
expect(log.text).toEqual('{1}');
|
||||
});
|
||||
|
||||
it('should link nested describe blocks with parent and children', function() {
|
||||
root.describe('A', function() {
|
||||
this.it('1', angular.noop);
|
||||
this.describe('B', function() {
|
||||
this.it('2', angular.noop);
|
||||
this.describe('C', function() {
|
||||
this.it('3', angular.noop);
|
||||
});
|
||||
});
|
||||
});
|
||||
var specs = root.getSpecs();
|
||||
expect(specs[2].definition.parent).toEqual(root);
|
||||
expect(specs[0].definition.parent).toEqual(specs[2].definition.children[0]);
|
||||
});
|
||||
|
||||
it('should not process xit and xdescribe', function() {
|
||||
root.describe('A', function() {
|
||||
this.xit('1', angular.noop);
|
||||
this.xdescribe('B', function() {
|
||||
this.it('2', angular.noop);
|
||||
this.describe('C', function() {
|
||||
this.it('3', angular.noop);
|
||||
});
|
||||
});
|
||||
});
|
||||
var specs = root.getSpecs();
|
||||
expect(specs.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should create uniqueIds in the tree', function() {
|
||||
angular.scenario.Describe.id = 0;
|
||||
var a = new angular.scenario.Describe();
|
||||
var b = new angular.scenario.Describe();
|
||||
expect(a.id).toNotEqual(b.id);
|
||||
});
|
||||
});
|
||||
38
test/scenario/FutureSpec.js
Normal file
38
test/scenario/FutureSpec.js
Normal file
@@ -0,0 +1,38 @@
|
||||
describe('angular.scenario.Future', function() {
|
||||
var future;
|
||||
|
||||
it('should set the name and behavior', function() {
|
||||
var behavior = function() {};
|
||||
var future = new angular.scenario.Future('test name', behavior);
|
||||
expect(future.name).toEqual('test name');
|
||||
expect(future.behavior).toEqual(behavior);
|
||||
expect(future.value).toBeUndefined();
|
||||
expect(future.fulfilled).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should be fulfilled after execution and done callback', function() {
|
||||
var future = new angular.scenario.Future('test name', function(done) {
|
||||
done();
|
||||
});
|
||||
future.execute(angular.noop);
|
||||
expect(future.fulfilled).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should take callback with (error, result) and forward', function() {
|
||||
var future = new angular.scenario.Future('test name', function(done) {
|
||||
done(10, 20);
|
||||
});
|
||||
future.execute(function(error, result) {
|
||||
expect(error).toEqual(10);
|
||||
expect(result).toEqual(20);
|
||||
});
|
||||
});
|
||||
|
||||
it('should use error as value if provided', function() {
|
||||
var future = new angular.scenario.Future('test name', function(done) {
|
||||
done(10, 20);
|
||||
});
|
||||
future.execute(angular.noop);
|
||||
expect(future.value).toEqual(10);
|
||||
});
|
||||
});
|
||||
87
test/scenario/HtmlUISpec.js
Normal file
87
test/scenario/HtmlUISpec.js
Normal file
@@ -0,0 +1,87 @@
|
||||
describe('angular.scenario.HtmlUI', function() {
|
||||
var ui;
|
||||
var context;
|
||||
var spec;
|
||||
|
||||
beforeEach(function() {
|
||||
spec = {
|
||||
name: 'test spec',
|
||||
definition: {
|
||||
id: 10,
|
||||
name: 'child',
|
||||
children: [],
|
||||
parent: {
|
||||
id: 20,
|
||||
name: 'parent',
|
||||
children: []
|
||||
}
|
||||
}
|
||||
};
|
||||
context = _jQuery("<div></div>");
|
||||
ui = new angular.scenario.ui.Html(context);
|
||||
});
|
||||
|
||||
it('should create nested describe context', function() {
|
||||
ui.addSpec(spec);
|
||||
expect(context.find('#describe-20 #describe-10 > h2').text())
|
||||
.toEqual('describe: child');
|
||||
expect(context.find('#describe-20 > h2').text()).toEqual('describe: parent');
|
||||
expect(context.find('#describe-10 .tests > li .test-info .test-name').text())
|
||||
.toEqual('it test spec');
|
||||
expect(context.find('#describe-10 .tests > li').hasClass('status-pending'))
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
it('should update totals when steps complete', function() {
|
||||
// Error
|
||||
ui.addSpec(spec).error('error');
|
||||
// Error
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step').finish();
|
||||
specUI.finish('error');
|
||||
// Failure
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step').finish('failure');
|
||||
specUI.finish('failure');
|
||||
// Failure
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step').finish('failure');
|
||||
specUI.finish('failure');
|
||||
// Failure
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step').finish('failure');
|
||||
specUI.finish('failure');
|
||||
// Success
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step').finish();
|
||||
specUI.finish();
|
||||
|
||||
expect(parseInt(context.find('#status-legend .status-failure').text()))
|
||||
.toEqual(3);
|
||||
expect(parseInt(context.find('#status-legend .status-error').text()))
|
||||
.toEqual(2);
|
||||
expect(parseInt(context.find('#status-legend .status-success').text()))
|
||||
.toEqual(1);
|
||||
});
|
||||
|
||||
it('should update timer when test completes', function() {
|
||||
// Success
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step').finish();
|
||||
specUI.finish();
|
||||
|
||||
// Failure
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step').finish('failure');
|
||||
specUI.finish('failure');
|
||||
|
||||
// Error
|
||||
specUI = ui.addSpec(spec).error('error');
|
||||
|
||||
context.find('#describe-10 .tests > li .test-info .timer-result')
|
||||
.each(function(index, timer) {
|
||||
expect(timer.innerHTML).toMatch(/ms$/);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
describe('Matcher', function () {
|
||||
function executeFutures() {
|
||||
for(var i in $scenario.currentSpec.futures) {
|
||||
var future = $scenario.currentSpec.futures[i];
|
||||
future.behavior.call({}, function(value) { future.fulfill(value); });
|
||||
}
|
||||
}
|
||||
var matcher;
|
||||
beforeEach(function() {
|
||||
setUpContext();
|
||||
var future = $scenario.addFuture('Calculate first future', function(done) {
|
||||
done(123);
|
||||
});
|
||||
matcher = new Matcher(this, future);
|
||||
|
||||
});
|
||||
it('should correctly match toEqual', function() {
|
||||
matcher.toEqual(123);
|
||||
executeFutures();
|
||||
});
|
||||
it('should throw an error when incorrect match toEqual', function() {
|
||||
matcher.toEqual(456);
|
||||
try {
|
||||
executeFutures();
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(e).toEqual('Expected 456 but was 123');
|
||||
}
|
||||
});
|
||||
it('should correctly match arrays', function() {
|
||||
var future = $scenario.addFuture('Calculate first future', function(done) {
|
||||
done(['a', 'b']);
|
||||
});
|
||||
matcher = new Matcher(this, future);
|
||||
matcher.toEqual(['a', 'b']);
|
||||
executeFutures();
|
||||
});
|
||||
});
|
||||
@@ -1,238 +1,96 @@
|
||||
describe('Runner', function() {
|
||||
|
||||
var Describe, It, BeforeEach, AfterEach, body;
|
||||
/**
|
||||
* Mock spec runner.
|
||||
*/
|
||||
function MockSpecRunner() {}
|
||||
MockSpecRunner.prototype.run = function(ui, spec, specDone) {
|
||||
spec.fn.call(this);
|
||||
specDone();
|
||||
};
|
||||
|
||||
describe('angular.scenario.Runner', function() {
|
||||
var $window;
|
||||
var runner;
|
||||
|
||||
beforeEach(function() {
|
||||
setUpContext();
|
||||
Describe = _window.describe;
|
||||
It = _window.it;
|
||||
BeforeEach = _window.beforeEach;
|
||||
AfterEach = _window.afterEach;
|
||||
body = _jQuery('<div></div>');
|
||||
});
|
||||
|
||||
describe('describe', function() {
|
||||
it('should consume the describe functions', function() {
|
||||
Describe('describe name', logger('body'));
|
||||
expect(log).toEqual('body');
|
||||
// Trick to get the scope out of a DSL statement
|
||||
angular.scenario.dsl('dslScope', function() {
|
||||
var scope = this;
|
||||
return function() { return scope; };
|
||||
});
|
||||
|
||||
describe('it', function() {
|
||||
it('should consume it', function() {
|
||||
Describe('describe name', function() {
|
||||
It('should text', logger('body'));
|
||||
});
|
||||
expect(log).toEqual('body');
|
||||
var spec = $scenario.specs['describe name: it should text'];
|
||||
expect(spec.futures).toEqual([]);
|
||||
expect(spec.name).toEqual('describe name: it should text');
|
||||
});
|
||||
|
||||
it('should complain on duplicate it', function() {
|
||||
// WRITE ME!!!!
|
||||
});
|
||||
|
||||
it('should create a failing future if there is a javascript error', function() {
|
||||
var spec;
|
||||
Describe('D1', function() {
|
||||
It('I1', function() {
|
||||
spec = $scenario.currentSpec;
|
||||
throw {message: 'blah'};
|
||||
});
|
||||
});
|
||||
var future = spec.futures[0];
|
||||
expect(future.name).toEqual('blah');
|
||||
try {
|
||||
future.behavior();
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(e.message).toEqual('blah');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('beforeEach', function() {
|
||||
it('should execute beforeEach before every it', function() {
|
||||
Describe('describe name', function() {
|
||||
BeforeEach(logger('before;'));
|
||||
It('should text', logger('body;'));
|
||||
It('should text2', logger('body2;'));
|
||||
});
|
||||
expect(log).toEqual('before;body;before;body2;');
|
||||
});
|
||||
});
|
||||
describe('afterEach', function() {
|
||||
it('should execute afterEach after every it', function() {
|
||||
Describe('describe name', function() {
|
||||
AfterEach(logger('after;'));
|
||||
It('should text1', logger('body1;'));
|
||||
It('should text2', logger('body2;'));
|
||||
});
|
||||
expect(log).toEqual('body1;after;body2;after;');
|
||||
});
|
||||
|
||||
it('should always execute afterEach after every it', function() {
|
||||
Describe('describe name', function() {
|
||||
AfterEach(logger('after;'));
|
||||
It('should text', function() {
|
||||
logger('body1;')();
|
||||
throw "MyError";
|
||||
});
|
||||
It('should text2', logger('body2;'));
|
||||
});
|
||||
expect(log).toEqual('body1;after;body2;after;');
|
||||
});
|
||||
|
||||
it('should report an error if afterEach fails', function() {
|
||||
var next;
|
||||
Describe('describe name', function() {
|
||||
AfterEach(function() {
|
||||
$scenario.addFuture('afterEachLog', logger('after;'));
|
||||
$scenario.addFuture('afterEachThrow', function() {
|
||||
throw "AfterError";
|
||||
});
|
||||
});
|
||||
It('should text1', function() {
|
||||
$scenario.addFuture('future1', logger('future1;'));
|
||||
});
|
||||
It('should text2', function() {
|
||||
$scenario.addFuture('future2', logger('future2;'));
|
||||
});
|
||||
});
|
||||
$scenario.run(body);
|
||||
expect(log).toEqual('future1;after;future2;after;');
|
||||
expect(_window.$testrun.results).toEqual([
|
||||
{ name : 'describe name: it should text1',
|
||||
passed : false,
|
||||
error : 'AfterError',
|
||||
steps : [ 'future1', 'afterEachLog', 'afterEachThrow' ] },
|
||||
{ name : 'describe name: it should text2',
|
||||
passed : false,
|
||||
error : 'AfterError',
|
||||
steps : [ 'future2', 'afterEachLog', 'afterEachThrow' ] }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('future building', function() {
|
||||
it('should queue futures', function() {
|
||||
function behavior(){}
|
||||
Describe('name', function() {
|
||||
It('should', function() {
|
||||
$scenario.addFuture('futureName', behavior);
|
||||
});
|
||||
});
|
||||
expect($scenario.specs['name: it should'].futures[0].name).
|
||||
toEqual('futureName');
|
||||
});
|
||||
});
|
||||
|
||||
describe('execution', function() {
|
||||
it('should execute the queued futures', function() {
|
||||
var next, firstThis, secondThis, doneThis, spec;
|
||||
$scenario.specs['spec'] = {
|
||||
futures: [
|
||||
new Future('future1', function(done) {
|
||||
next = done;
|
||||
log += 'first;';
|
||||
firstThis = this;
|
||||
}),
|
||||
new Future('future2', function(done) {
|
||||
next = done;
|
||||
log += 'second;';
|
||||
secondThis = this;
|
||||
})
|
||||
]
|
||||
// Trick to get the scope out of a DSL statement
|
||||
angular.scenario.dsl('dslChain', function() {
|
||||
return function() {
|
||||
this.chained = 0;
|
||||
this.chain = function() { this.chained++; return this; };
|
||||
return this;
|
||||
};
|
||||
|
||||
spec = $scenario.execute('spec', function(done){
|
||||
log += 'done;';
|
||||
doneThis = this;
|
||||
});
|
||||
expect(log).toEqual('first;');
|
||||
next();
|
||||
expect(log).toEqual('first;second;');
|
||||
next();
|
||||
expect(log).toEqual('first;second;done;');
|
||||
expect(spec === window).toEqual(false);
|
||||
expect(spec).toEqual(firstThis);
|
||||
expect(spec).toEqual(secondThis);
|
||||
expect(spec).toEqual(doneThis);
|
||||
|
||||
expect(spec.result.failed).toEqual(false);
|
||||
expect(spec.result.finished).toEqual(true);
|
||||
expect(spec.result.error).toBeUndefined();
|
||||
expect(spec.result.passed).toEqual(true);
|
||||
});
|
||||
|
||||
it('should handle exceptions in a future', function() {
|
||||
$scenario.specs['spec'] = {
|
||||
futures: [
|
||||
new Future('first future', function(done) {
|
||||
done();
|
||||
}),
|
||||
new Future('error', function(done) {
|
||||
throw "MyError";
|
||||
}),
|
||||
new Future('should not execute', function(done) {
|
||||
done();
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
var spec = $scenario.execute('spec');
|
||||
|
||||
expect(spec.result.passed).toEqual(false);
|
||||
expect(spec.result.failed).toEqual(true);
|
||||
expect(spec.result.finished).toEqual(true);
|
||||
expect(spec.result.error).toEqual("MyError");
|
||||
expect(_window.$testrun.results).toEqual([{
|
||||
name: 'spec',
|
||||
passed: false,
|
||||
error: 'MyError',
|
||||
steps: ['first future', 'error']}]);
|
||||
$window = {};
|
||||
runner = new angular.scenario.Runner($window);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
delete angular.scenario.dsl.dslScope;
|
||||
delete angular.scenario.dsl.dslChain;
|
||||
});
|
||||
|
||||
it('should publish the functions in the public API', function() {
|
||||
angular.foreach(runner.api, function(fn, name) {
|
||||
var func;
|
||||
if (name in $window) {
|
||||
func = $window[name];
|
||||
}
|
||||
expect(angular.isFunction(func)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('run', function() {
|
||||
var next;
|
||||
beforeEach(function() {
|
||||
Describe('d1', function() {
|
||||
It('it1', function() { $scenario.addFuture('s1', logger('s1,')); });
|
||||
It('it2', function() {
|
||||
$scenario.addFuture('s2', logger('s2,'));
|
||||
$scenario.addFuture('s2.2', function(done){ next = done; });
|
||||
|
||||
it('should construct valid describe trees with public API', function() {
|
||||
var before = [];
|
||||
var after = [];
|
||||
$window.describe('A', function() {
|
||||
$window.beforeEach(function() { before.push('A'); });
|
||||
$window.afterEach(function() { after.push('A'); });
|
||||
$window.it('1', angular.noop);
|
||||
$window.describe('B', function() {
|
||||
$window.beforeEach(function() { before.push('B'); });
|
||||
$window.afterEach(function() { after.push('B'); });
|
||||
$window.it('2', angular.noop);
|
||||
$window.describe('C', function() {
|
||||
$window.beforeEach(function() { before.push('C'); });
|
||||
$window.afterEach(function() { after.push('C'); });
|
||||
$window.it('3', angular.noop);
|
||||
});
|
||||
});
|
||||
Describe('d2', function() {
|
||||
It('it3', function() { $scenario.addFuture('s3', logger('s3,')); });
|
||||
It('it4', function() { $scenario.addFuture('s4', logger('s4,')); });
|
||||
});
|
||||
var specs = runner.rootDescribe.getSpecs();
|
||||
specs[0].fn();
|
||||
expect(before).toEqual(['A', 'B', 'C']);
|
||||
expect(after).toEqual(['C', 'B', 'A']);
|
||||
expect(specs[2].definition.parent).toEqual(runner.rootDescribe);
|
||||
expect(specs[0].definition.parent).toEqual(specs[2].definition.children[0]);
|
||||
});
|
||||
|
||||
it('should publish the DSL statements to the $window', function() {
|
||||
$window.describe('describe', function() {
|
||||
$window.it('1', function() {
|
||||
expect($window.dslScope).toBeDefined();
|
||||
});
|
||||
});
|
||||
it('should execute all specs', function() {
|
||||
$scenario.run(body);
|
||||
|
||||
expect(log).toEqual('s1,s2,');
|
||||
next();
|
||||
expect(log).toEqual('s1,s2,s3,s4,');
|
||||
});
|
||||
it('should publish done state and results as tests are run', function() {
|
||||
expect(_window.$testrun.done).toBeFalsy();
|
||||
expect(_window.$testrun.results).toEqual([]);
|
||||
$scenario.run(body);
|
||||
expect(_window.$testrun.done).toBeFalsy();
|
||||
expect(_window.$testrun.results).toEqual([
|
||||
{name: 'd1: it it1', passed: true, error: undefined, steps: ['s1']}
|
||||
]);
|
||||
next();
|
||||
expect(_window.$testrun.done).toBeTruthy();
|
||||
expect(_window.$testrun.results).toEqual([
|
||||
{name: 'd1: it it1', passed: true, error: undefined, steps: ['s1']},
|
||||
{name: 'd1: it it2', passed: true, error: undefined, steps: ['s2', 's2.2']},
|
||||
{name: 'd2: it it3', passed: true, error: undefined, steps: ['s3']},
|
||||
{name: 'd2: it it4', passed: true, error: undefined, steps: ['s4']}
|
||||
]);
|
||||
});
|
||||
runner.run(null/*ui*/, null/*application*/, MockSpecRunner, rethrow);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should create a new scope for each DSL chain', function() {
|
||||
$window.describe('describe', function() {
|
||||
$window.it('1', function() {
|
||||
var scope = $window.dslScope();
|
||||
scope.test = "foo";
|
||||
expect($window.dslScope().test).toBeUndefined();
|
||||
});
|
||||
$window.it('2', function() {
|
||||
var scope = $window.dslChain().chain().chain();
|
||||
expect(scope.chained).toEqual(2);
|
||||
});
|
||||
});
|
||||
runner.run(null/*ui*/, null/*application*/, MockSpecRunner, rethrow);
|
||||
});
|
||||
});
|
||||
|
||||
165
test/scenario/SpecRunnerSpec.js
Normal file
165
test/scenario/SpecRunnerSpec.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Mock of all required UI classes/methods. (UI, Spec, Step).
|
||||
*/
|
||||
function UIMock() {
|
||||
this.log = [];
|
||||
}
|
||||
UIMock.prototype = {
|
||||
addSpec: function(spec) {
|
||||
var log = this.log;
|
||||
log.push('addSpec:' + spec.name);
|
||||
return {
|
||||
addStep: function(name) {
|
||||
log.push('addStep:' + name);
|
||||
return {
|
||||
finish: function(e) {
|
||||
log.push('step finish:' + (e ? e : ''));
|
||||
return this;
|
||||
},
|
||||
error: function(e) {
|
||||
log.push('step error:' + (e ? e : ''));
|
||||
return this;
|
||||
}
|
||||
};
|
||||
},
|
||||
finish: function(e) {
|
||||
log.push('spec finish:' + (e ? e : ''));
|
||||
return this;
|
||||
},
|
||||
error: function(e) {
|
||||
log.push('spec error:' + (e ? e : ''));
|
||||
return this;
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Mock Application
|
||||
*/
|
||||
function ApplicationMock($window) {
|
||||
this.$window = $window;
|
||||
}
|
||||
ApplicationMock.prototype = {
|
||||
executeAction: function(callback) {
|
||||
callback.call(this.$window);
|
||||
}
|
||||
};
|
||||
|
||||
describe('angular.scenario.SpecRunner', function() {
|
||||
var $window;
|
||||
var runner;
|
||||
|
||||
beforeEach(function() {
|
||||
$window = {};
|
||||
runner = angular.scope();
|
||||
runner.application = new ApplicationMock($window);
|
||||
runner.$become(angular.scenario.SpecRunner);
|
||||
});
|
||||
|
||||
it('should bind futures to the spec', function() {
|
||||
runner.addFuture('test future', function(done) {
|
||||
this.application.value = 10;
|
||||
done();
|
||||
});
|
||||
runner.futures[0].execute(angular.noop);
|
||||
expect(runner.application.value).toEqual(10);
|
||||
});
|
||||
|
||||
it('should pass done to future action behavior', function() {
|
||||
runner.addFutureAction('test future', function(done) {
|
||||
expect(angular.isFunction(done)).toBeTruthy();
|
||||
done(10, 20);
|
||||
});
|
||||
runner.futures[0].execute(function(error, result) {
|
||||
expect(error).toEqual(10);
|
||||
expect(result).toEqual(20);
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass execute future action on the $window', function() {
|
||||
runner.addFutureAction('test future', function(done) {
|
||||
this.test = 'test value';
|
||||
done();
|
||||
});
|
||||
runner.futures[0].execute(angular.noop);
|
||||
expect($window.test).toEqual('test value');
|
||||
});
|
||||
|
||||
it('should execute spec function and notify UI', function() {
|
||||
var finished = false;
|
||||
var ui = new UIMock();
|
||||
var spec = {name: 'test spec', fn: function() {
|
||||
this.test = 'some value';
|
||||
}};
|
||||
runner.addFuture('test future', function(done) {
|
||||
done();
|
||||
});
|
||||
runner.run(ui, spec, function() {
|
||||
finished = true;
|
||||
});
|
||||
expect(runner.test).toEqual('some value');
|
||||
expect(finished).toBeTruthy();
|
||||
expect(ui.log).toEqual([
|
||||
'addSpec:test spec',
|
||||
'addStep:test future',
|
||||
'step finish:',
|
||||
'spec finish:'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should execute notify UI on spec setup error', function() {
|
||||
var finished = false;
|
||||
var ui = new UIMock();
|
||||
var spec = {name: 'test spec', fn: function() {
|
||||
throw 'message';
|
||||
}};
|
||||
runner.run(ui, spec, function() {
|
||||
finished = true;
|
||||
});
|
||||
expect(finished).toBeTruthy();
|
||||
expect(ui.log).toEqual([
|
||||
'addSpec:test spec',
|
||||
'spec error:message'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should execute notify UI on step failure', function() {
|
||||
var finished = false;
|
||||
var ui = new UIMock();
|
||||
var spec = {name: 'test spec', fn: angular.noop};
|
||||
runner.addFuture('test future', function(done) {
|
||||
done('failure message');
|
||||
});
|
||||
runner.run(ui, spec, function() {
|
||||
finished = true;
|
||||
});
|
||||
expect(finished).toBeTruthy();
|
||||
expect(ui.log).toEqual([
|
||||
'addSpec:test spec',
|
||||
'addStep:test future',
|
||||
'step finish:failure message',
|
||||
'spec finish:failure message'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should execute notify UI on step error', function() {
|
||||
var finished = false;
|
||||
var ui = new UIMock();
|
||||
var spec = {name: 'test spec', fn: angular.noop};
|
||||
runner.addFuture('test future', function(done) {
|
||||
throw 'error message';
|
||||
});
|
||||
runner.run(ui, spec, function() {
|
||||
finished = true;
|
||||
});
|
||||
expect(finished).toBeTruthy();
|
||||
expect(ui.log).toEqual([
|
||||
'addSpec:test spec',
|
||||
'addStep:test future',
|
||||
'step error:error message',
|
||||
'spec finish:error message'
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
var _window, runner, log, $scenario;
|
||||
|
||||
function logger(text) {
|
||||
return function(done){
|
||||
log += text;
|
||||
(done||noop)();
|
||||
};
|
||||
}
|
||||
|
||||
function setUpContext() {
|
||||
_window = {};
|
||||
runner = new angular.scenario.Runner(_window, _jQuery);
|
||||
$scenario = _window.$scenario;
|
||||
log = '';
|
||||
}
|
||||
43
test/scenario/matchersSpec.js
Normal file
43
test/scenario/matchersSpec.js
Normal file
@@ -0,0 +1,43 @@
|
||||
describe('angular.scenario.matchers', function () {
|
||||
var matchers;
|
||||
|
||||
function expectMatcher(value, test) {
|
||||
delete matchers.error;
|
||||
delete matchers.future.value;
|
||||
if (value !== undefined) {
|
||||
matchers.future.value = value;
|
||||
}
|
||||
test();
|
||||
expect(matchers.error).toBeUndefined();
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
/**
|
||||
* Mock up the future system wrapped around matchers.
|
||||
*
|
||||
* @see Scenario.js#angular.scenario.matcher
|
||||
*/
|
||||
matchers = {
|
||||
future: { name: 'test' }
|
||||
};
|
||||
matchers.addFuture = function(name, callback) {
|
||||
callback(function(error) {
|
||||
matchers.error = error;
|
||||
});
|
||||
};
|
||||
angular.extend(matchers, angular.scenario.matcher);
|
||||
});
|
||||
|
||||
it('should handle basic matching', function() {
|
||||
expectMatcher(10, function() { matchers.toEqual(10); });
|
||||
expectMatcher('value', function() { matchers.toBeDefined(); });
|
||||
expectMatcher([1], function() { matchers.toBeTruthy(); });
|
||||
expectMatcher("", function() { matchers.toBeFalsy(); });
|
||||
expectMatcher(0, function() { matchers.toBeFalsy(); });
|
||||
expectMatcher('foo', function() { matchers.toMatch('.o.'); });
|
||||
expectMatcher(null, function() { matchers.toBeNull(); });
|
||||
expectMatcher([1, 2, 3], function() { matchers.toContain(2); });
|
||||
expectMatcher(3, function() { matchers.toBeLessThan(10); });
|
||||
expectMatcher(3, function() { matchers.toBeGreaterThan(-5); });
|
||||
});
|
||||
});
|
||||
@@ -22,6 +22,19 @@ beforeEach(function(){
|
||||
return "Expected to not have class 'ng-validation-error' but found.";
|
||||
};
|
||||
return !hasClass;
|
||||
},
|
||||
|
||||
toEqualData: function(expected) {
|
||||
return equals(this.actual, expected);
|
||||
},
|
||||
|
||||
toHaveClass: function(clazz) {
|
||||
this.message = function(){
|
||||
return "Expected '" + sortedHtml(this.actual) + "' to have class '" + clazz + "'.";
|
||||
};
|
||||
return this.actual.hasClass ?
|
||||
this.actual.hasClass(clazz) :
|
||||
jqLite(this.actual).hasClass(clazz);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -194,3 +207,9 @@ function click(element) {
|
||||
JQLite.prototype.trigger.call(element, 'click');
|
||||
}
|
||||
}
|
||||
|
||||
function rethrow(e) {
|
||||
if(e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user