Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ffb47bdb6 | |||
| ec885489a2 | |||
| 01c52e92b1 | |||
| 943377a091 | |||
| 72b7a1c531 | |||
| 9c0225512c | |||
| 40d7e66f40 | |||
| 1d52349440 | |||
| 3eb0c8bc67 | |||
| 42855e4363 | |||
| 4c61fc01f9 | |||
| 4fdab37659 | |||
| 841013a4c4 | |||
| 4e9a2aa10e | |||
| 4fc2141458 | |||
| 5b40e87ac6 | |||
| 1391f19fb4 | |||
| 04a4d8b061 | |||
| bbd87c9425 | |||
| 64063b5d41 | |||
| 833e0ae343 | |||
| d74ef497de | |||
| 6ddcf91861 | |||
| 8a867cee22 | |||
| 68217d427c | |||
| 1efef67b5f | |||
| a6cfa43c19 | |||
| b41bc98c54 | |||
| aaabeb8c5e | |||
| 05d4971abb | |||
| c6107fe8ac | |||
| 68f074c299 | |||
| c53a37ed91 |
@@ -1,3 +1,4 @@
|
||||
build/
|
||||
angularjs.netrc
|
||||
jstd.log
|
||||
.DS_Store
|
||||
@@ -0,0 +1,55 @@
|
||||
# <angular/> 0.9.1 repulsion-field (2010-10-26) #
|
||||
|
||||
### Security
|
||||
- added html sanitizer to fix the last few known security issues (issues #33 and #34)
|
||||
|
||||
### API
|
||||
- new ng:submit directive for creating onSubmit handlers on forms (issue #76)
|
||||
- the date filter now accepts milliseconds as well as date strings (issue #78)
|
||||
- the html filter now supports 'unsafe' option to bypass html sanitization
|
||||
|
||||
### Testability
|
||||
- lots of improvements related to the scenario runner (commit 40d7e66f)
|
||||
|
||||
### Demo
|
||||
- added a new demo application: Personal Log (src example/personalLog)
|
||||
|
||||
### Chores
|
||||
- lots of fixes to get all tests pass on IE
|
||||
- added TzDate type to allow us to create timezone idependent tests (issue #88)
|
||||
|
||||
### Breaking changes
|
||||
- $cookieStore service is not globally published any more, if you use it, you must request it via
|
||||
$inject as any other non-global service
|
||||
- html filter now sanitizes html content for XSS attacks which may result in different behavior
|
||||
|
||||
|
||||
# <angular/> 0.9.0 dragon-breath (2010-10-20) #
|
||||
|
||||
### Security
|
||||
- angular.fromJson not safer (issue #57)
|
||||
- readString consumes invalid escapes (issue #56)
|
||||
- use new Function instead of eval (issue #52)
|
||||
|
||||
### Speed
|
||||
- css cleanup + inline all css and images in the main js (issue #64)
|
||||
|
||||
### Testability
|
||||
- initial version of the built-in end-to-end scenario runner (issues #50, #67, #70)
|
||||
|
||||
### API
|
||||
- allow ng:controller nesting (issue #39)
|
||||
- new built-in date format filter (issue #45)
|
||||
- $location needs method you call on updates (issue #32)
|
||||
|
||||
|
||||
### Chores
|
||||
- release versioning + file renaming (issue #69)
|
||||
|
||||
### Breaking changes
|
||||
- $location.parse was replaced with $location.update
|
||||
- all css and img files were inlined into the main js file, to support IE7 and older app must host
|
||||
angular-ie-compat.js file
|
||||
|
||||
### Big Thanks to Our Community Contributors
|
||||
- Vojta Jina
|
||||
@@ -9,6 +9,7 @@ ANGULAR = [
|
||||
'src/Parser.js',
|
||||
'src/Resource.js',
|
||||
'src/Browser.js',
|
||||
'src/sanitizer.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
'src/filters.js',
|
||||
@@ -26,12 +27,16 @@ ANGULAR_SCENARIO = [
|
||||
'src/scenario/Application.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Future.js',
|
||||
'src/scenario/HtmlUI.js',
|
||||
'src/scenario/ObjectModel.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Runner.js',
|
||||
'src/scenario/SpecRunner.js',
|
||||
'src/scenario/dsl.js',
|
||||
'src/scenario/matchers.js',
|
||||
'src/scenario/output/Html.js',
|
||||
'src/scenario/output/Json.js',
|
||||
'src/scenario/output/Xml.js',
|
||||
'src/scenario/output/Object.js',
|
||||
]
|
||||
|
||||
BUILD_DIR = 'build'
|
||||
@@ -39,6 +44,12 @@ BUILD_DIR = 'build'
|
||||
task :default => [:compile, :test]
|
||||
|
||||
|
||||
desc 'Init the build workspace'
|
||||
task :init do
|
||||
FileUtils.mkdir(BUILD_DIR) unless File.directory?(BUILD_DIR)
|
||||
end
|
||||
|
||||
|
||||
desc 'Clean Generated Files'
|
||||
task :clean do
|
||||
FileUtils.rm_r(BUILD_DIR, :force => true)
|
||||
@@ -47,7 +58,7 @@ end
|
||||
|
||||
|
||||
desc 'Compile Scenario'
|
||||
task :compile_scenario do
|
||||
task :compile_scenario => :init do
|
||||
|
||||
deps = [
|
||||
'lib/jquery/jquery-1.4.2.js',
|
||||
@@ -67,11 +78,8 @@ task :compile_scenario do
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
desc 'Generate IE css js patch'
|
||||
task :generate_ie_compat do
|
||||
task :generate_ie_compat => :init do
|
||||
css = File.open('css/angular.css', 'r') {|f| f.read }
|
||||
|
||||
# finds all css rules that contain backround images and extracts the rule name(s), content type of
|
||||
@@ -127,7 +135,7 @@ end
|
||||
|
||||
|
||||
desc 'Compile JavaScript'
|
||||
task :compile => [:compile_scenario, :generate_ie_compat] do
|
||||
task :compile => [:init, :compile_scenario, :generate_ie_compat] do
|
||||
|
||||
deps = [
|
||||
'src/angular.prefix',
|
||||
@@ -149,7 +157,7 @@ end
|
||||
|
||||
|
||||
desc 'Create angular distribution'
|
||||
task :package => :compile do
|
||||
task :package => [:clean, :compile] do
|
||||
v = YAML::load( File.open( 'version.yaml' ) )['version']
|
||||
match = v.match(/^([^-]*)(-snapshot)?$/)
|
||||
version = match[1] + (match[2] ? ('-' + %x(git rev-parse HEAD)[0..7]) : '')
|
||||
|
||||
@@ -8,6 +8,10 @@ body {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#json, #xml {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#header {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
@@ -32,7 +36,7 @@ body {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#frame h2,
|
||||
#application h2,
|
||||
#specs h2 {
|
||||
margin: 0;
|
||||
padding: 0.5em;
|
||||
@@ -45,26 +49,26 @@ body {
|
||||
}
|
||||
|
||||
#header,
|
||||
#frame,
|
||||
#application,
|
||||
.test-info,
|
||||
.test-actions li {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#frame {
|
||||
#application {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#frame iframe {
|
||||
#application iframe {
|
||||
width: 100%;
|
||||
height: 758px;
|
||||
}
|
||||
|
||||
#frame .popout {
|
||||
#application .popout {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#frame iframe {
|
||||
#application iframe {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -154,6 +158,10 @@ body {
|
||||
margin-left: 6em;
|
||||
}
|
||||
|
||||
.test-describe {
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.test-describe .test-describe {
|
||||
margin: 5px 5px 10px 2em;
|
||||
}
|
||||
@@ -178,11 +186,11 @@ body {
|
||||
}
|
||||
|
||||
#specs h2,
|
||||
#frame h2 {
|
||||
#application h2 {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
#frame {
|
||||
#application {
|
||||
border: 1px solid #BABAD1;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<ul>
|
||||
<li ng:repeat="item in activities.data.items.$filter(filterText)">
|
||||
<h1>
|
||||
<img src="{{item.actor.thumbnailUrl}}"/>
|
||||
<img ng:src="{{item.actor.thumbnailUrl}}"/>
|
||||
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
|
||||
<a href="#" ng:click="expandReplies(item)">Replies: {{item.links.replies[0].count}}</a>
|
||||
</h1>
|
||||
@@ -31,14 +31,14 @@
|
||||
{{item.object.content | html}}
|
||||
<div>
|
||||
<a href="{{attachment.links.enclosure[0].href}}" ng:repeat="attachment in item.object.attachments">
|
||||
<img src="{{attachment.links.preview[0].href}}"/>
|
||||
<img ng:src="{{attachment.links.preview[0].href}}"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<my:expand expand="item.replies.show">
|
||||
<ul>
|
||||
<li ng:repeat="reply in item.replies.data.items">
|
||||
<img src="{{reply.actor.thumbnailUrl}}"/>
|
||||
<img ng:src="{{reply.actor.thumbnailUrl}}"/>
|
||||
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
|
||||
{{reply.content | html}}
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<title>Personal Log</title>
|
||||
<script type="text/javascript" src="../../src/angular-bootstrap.js" ng:autobind></script>
|
||||
<script type="text/javascript" src="personalLog.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
<!-- TODO: we need to expose $root so that we can delete cookies in the scenario runner, there
|
||||
must be a better way to do this -->
|
||||
<body ng:controller="example.personalLog.LogCtrl" ng:init="$window.$root = $root">
|
||||
|
||||
<form action="" ng:submit="addLog(newMsg)">
|
||||
<input type="text" name="newMsg" />
|
||||
<input type="submit" value="add" />
|
||||
<input type="button" value="remove all" ng:click="rmLogs()" />
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
<h2>Logs:</h2>
|
||||
<ul>
|
||||
<li ng:repeat="log in logs.$orderBy('-at')">
|
||||
{{log.at | date:'yy-MM-dd HH:mm'}} {{log.msg}}
|
||||
[<a href="" ng:click="rmLog(log)">x</a>]
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @fileOverview Very simple personal log demo application to demonstrate angular functionality,
|
||||
* especially:
|
||||
* - the MVC model
|
||||
* - testability of controllers
|
||||
* - dependency injection for controllers via $inject and constructor function
|
||||
* - $cookieStore for persistent cookie-backed storage
|
||||
* - simple templating constructs such as ng:repeat and {{}}
|
||||
* - date filter
|
||||
* - and binding onSubmit and onClick events to angular expressions
|
||||
* @author Igor Minar
|
||||
*/
|
||||
|
||||
|
||||
/** @namespace the 'example' namespace */
|
||||
var example = example || {};
|
||||
/** @namespace namespace of the personal log app */
|
||||
example.personalLog = {};
|
||||
|
||||
|
||||
//name space isolating closure
|
||||
(function() {
|
||||
|
||||
var LOGS = 'logs';
|
||||
|
||||
/**
|
||||
* The controller for the personal log app.
|
||||
*/
|
||||
function LogCtrl($cookieStore) {
|
||||
var self = this,
|
||||
logs = self.logs = $cookieStore.get(LOGS) || []; //main model
|
||||
|
||||
|
||||
/**
|
||||
* Adds newMsg to the logs array as a log, persists it and clears newMsg.
|
||||
* @param {string} msg Message to add (message is passed as parameter to make testing easier).
|
||||
*/
|
||||
this.addLog = function(msg) {
|
||||
var newMsg = msg || self.newMsg;
|
||||
if (!newMsg) return;
|
||||
var log = {
|
||||
at: new Date().getTime(),
|
||||
msg: newMsg
|
||||
}
|
||||
|
||||
logs.push(log);
|
||||
$cookieStore.put(LOGS, logs);
|
||||
self.newMsg = '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Persistently removes a log from logs.
|
||||
* @param {object} log The log to remove.
|
||||
*/
|
||||
this.rmLog = function(log) {
|
||||
angular.Array.remove(logs, log);
|
||||
$cookieStore.put(LOGS, logs);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Persistently removes all logs.
|
||||
*/
|
||||
this.rmLogs = function() {
|
||||
logs.splice(0, logs.length);
|
||||
$cookieStore.remove(LOGS);
|
||||
};
|
||||
}
|
||||
|
||||
//inject
|
||||
LogCtrl.$inject = ['$cookieStore'];
|
||||
|
||||
//export
|
||||
example.personalLog.LogCtrl = LogCtrl;
|
||||
})();
|
||||
@@ -0,0 +1,98 @@
|
||||
describe('personal log', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
navigateTo('../personalLog.html');
|
||||
});
|
||||
|
||||
|
||||
afterEach(function() {
|
||||
clearCookies();
|
||||
});
|
||||
|
||||
|
||||
it('should create new logs and order them in reverse chronological order', function(){
|
||||
//create first msg
|
||||
input('newMsg').enter('my first message');
|
||||
element('form input[type="submit"]').click();
|
||||
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
expect(repeater('ul li').column('log.msg')).toEqual('my first message');
|
||||
|
||||
//create second msg
|
||||
input('newMsg').enter('my second message');
|
||||
element('form input[type="submit"]').click();
|
||||
|
||||
expect(repeater('ul li').count()).toEqual(2);
|
||||
expect(repeater('ul li').column('log.msg')).toEqual(['my second message', 'my first message']);
|
||||
});
|
||||
|
||||
|
||||
it('should delete a log when user clicks on the related X link', function() {
|
||||
//create first msg
|
||||
input('newMsg').enter('my first message');
|
||||
element('form input[type="submit"]').click();
|
||||
//create second msg
|
||||
input('newMsg').enter('my second message');
|
||||
element('form input[type="submit"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(2);
|
||||
|
||||
element('ul li a:eq(1)').click();
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
expect(repeater('ul li').column('log.msg')).toEqual('my second message');
|
||||
|
||||
element('ul li a:eq(0)').click();
|
||||
expect(repeater('ul li').count()).toEqual(0);
|
||||
});
|
||||
|
||||
|
||||
it('should delete all cookies when user clicks on "remove all" button', function() {
|
||||
//create first msg
|
||||
input('newMsg').enter('my first message');
|
||||
element('form input[type="submit"]').click();
|
||||
//create second msg
|
||||
input('newMsg').enter('my second message');
|
||||
element('form input[type="submit"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(2);
|
||||
|
||||
element('input[value="remove all"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(0);
|
||||
});
|
||||
|
||||
|
||||
it('should preserve logs over page reloads', function() {
|
||||
input('newMsg').enter('my persistent message');
|
||||
element('form input[type="submit"]').click();
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
|
||||
navigateTo('about:blank');
|
||||
navigateTo('../personalLog.html');
|
||||
|
||||
expect(repeater('ul li').column('log.msg')).toEqual('my persistent message');
|
||||
expect(repeater('ul li').count()).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* DSL for deleting all cookies.
|
||||
*/
|
||||
angular.scenario.dsl('clearCookies', function() {
|
||||
/**
|
||||
* Deletes cookies by interacting with the cookie service within the application under test.
|
||||
*/
|
||||
return function() {
|
||||
this.addFutureAction('clear all cookies', function($window, $document, done) {
|
||||
//TODO: accessing angular services is pretty nasty, we need a better way to reach them
|
||||
var $cookies = $window.$root.$cookies,
|
||||
cookieName;
|
||||
|
||||
for (cookieName in $cookies) {
|
||||
console.log('deleting cookie: ' + cookieName);
|
||||
delete $cookies[cookieName];
|
||||
}
|
||||
$window.$root.$eval();
|
||||
|
||||
done();
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
<!doctype html">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<title>Personal Log Scenario Runner</title>
|
||||
<script type="text/javascript" src="../../../src/scenario/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript" src="personalLogScenario.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,122 @@
|
||||
describe('example.personalLog.LogCtrl', function() {
|
||||
var logCtrl;
|
||||
|
||||
function createNotesCtrl() {
|
||||
var scope = angular.scope();
|
||||
return scope.$new(example.personalLog.LogCtrl);
|
||||
}
|
||||
|
||||
|
||||
beforeEach(function() {
|
||||
logCtrl = createNotesCtrl();
|
||||
});
|
||||
|
||||
|
||||
it('should initialize notes with an empty array', function() {
|
||||
expect(logCtrl.logs).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
describe('addLog', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
expect(logCtrl.logs).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should add newMsg to logs as a log entry', function() {
|
||||
logCtrl.newMsg = 'first log message';
|
||||
logCtrl.addLog();
|
||||
|
||||
expect(logCtrl.logs.length).toBe(1);
|
||||
expect(logCtrl.logs[0].msg).toBe('first log message');
|
||||
|
||||
//one more msg, this time passed in as param
|
||||
logCtrl.addLog('second log message');
|
||||
|
||||
expect(logCtrl.logs.length).toBe(2);
|
||||
expect(logCtrl.logs[0].msg).toBe('first log message');
|
||||
expect(logCtrl.logs[1].msg).toBe('second log message');
|
||||
});
|
||||
|
||||
|
||||
it('should clear newMsg when log entry is persisted', function() {
|
||||
logCtrl.addLog('first log message');
|
||||
expect(logCtrl.newMsg).toBe('');
|
||||
});
|
||||
|
||||
|
||||
it('should store logs in the logs cookie', function() {
|
||||
expect(logCtrl.$cookies.logs).not.toBeDefined();
|
||||
logCtrl.addLog('first log message');
|
||||
expect(logCtrl.$cookies.logs).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
it('should do nothing if newMsg is empty', function() {
|
||||
logCtrl.addLog('');
|
||||
expect(logCtrl.logs.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('rmLog', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
logCtrl.addLog('message1');
|
||||
logCtrl.addLog('message2');
|
||||
logCtrl.addLog('message3');
|
||||
logCtrl.addLog('message4');
|
||||
expect(logCtrl.logs.length).toBe(4);
|
||||
});
|
||||
|
||||
|
||||
it('should delete a message identified by index', function() {
|
||||
logCtrl.rmLog(logCtrl.logs[1]);
|
||||
expect(logCtrl.logs.length).toBe(3);
|
||||
|
||||
logCtrl.rmLog(logCtrl.logs[2]);
|
||||
expect(logCtrl.logs.length).toBe(2);
|
||||
expect(logCtrl.logs[0].msg).toBe('message1');
|
||||
expect(logCtrl.logs[1].msg).toBe('message3');
|
||||
});
|
||||
|
||||
|
||||
it('should update cookies when a log is deleted', function() {
|
||||
expect(logCtrl.$cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){3}\]/);
|
||||
|
||||
logCtrl.rmLog(logCtrl.logs[1]);
|
||||
expect(logCtrl.$cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){2}\]/);
|
||||
|
||||
logCtrl.rmLog(logCtrl.logs[0]);
|
||||
logCtrl.rmLog(logCtrl.logs[0]);
|
||||
logCtrl.rmLog(logCtrl.logs[0]);
|
||||
expect(logCtrl.$cookies.logs).toMatch(/\[\]/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('rmLogs', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
logCtrl.addLog('message1');
|
||||
logCtrl.addLog('message2');
|
||||
logCtrl.addLog('message3');
|
||||
logCtrl.addLog('message4');
|
||||
expect(logCtrl.logs.length).toBe(4);
|
||||
});
|
||||
|
||||
|
||||
it('should remove all logs', function() {
|
||||
logCtrl.rmLogs();
|
||||
expect(logCtrl.logs).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should remove logs cookie', function() {
|
||||
expect(logCtrl.$cookies.logs).toBeTruthy();
|
||||
logCtrl.rmLogs();
|
||||
expect(logCtrl.$cookies.logs).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
server: http://localhost:9876
|
||||
|
||||
load:
|
||||
- lib/jasmine/jasmine-1.0.1.js
|
||||
- lib/jasmine-1.0.1/jasmine.js
|
||||
- lib/jasmine-jstd-adapter/JasmineAdapter.js
|
||||
- lib/jquery/jquery-1.4.2.js
|
||||
- test/jquery_alias.js
|
||||
@@ -10,9 +10,11 @@ load:
|
||||
- src/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
- src/scenario/*.js
|
||||
- test/angular-mocks.js
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
|
||||
exclude:
|
||||
@@ -20,6 +22,6 @@ exclude:
|
||||
- src/angular.suffix
|
||||
- src/angular-bootstrap.js
|
||||
- src/AngularPublic.js
|
||||
- src/scenario/bootstrap.js
|
||||
- src/scenario/angular-bootstrap.js
|
||||
- test/jquery_remove.js
|
||||
|
||||
|
||||
+6
-2
@@ -1,24 +1,28 @@
|
||||
server: http://localhost:9876
|
||||
|
||||
load:
|
||||
- lib/jasmine/jasmine-1.0.1.js
|
||||
- lib/jasmine-1.0.1/jasmine.js
|
||||
- lib/jasmine-jstd-adapter/JasmineAdapter.js
|
||||
- lib/jquery/jquery-1.4.2.js
|
||||
- test/jquery_remove.js
|
||||
- src/Angular.js
|
||||
- src/JSON.js
|
||||
- src/*.js
|
||||
- example/personalLog/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
- src/scenario/*.js
|
||||
- test/angular-mocks.js
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
- example/personalLog/test/*.js
|
||||
|
||||
exclude:
|
||||
- test/jquery_alias.js
|
||||
- src/angular.prefix
|
||||
- src/angular.suffix
|
||||
- src/angular-bootstrap.js
|
||||
- src/scenario/bootstrap.js
|
||||
- src/scenario/angular-bootstrap.js
|
||||
- src/AngularPublic.js
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* HTML Parser By John Resig (ejohn.org)
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*
|
||||
* // Use like so:
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
* // or to get an XML string:
|
||||
* HTMLtoXML(htmlString);
|
||||
*
|
||||
* // or to get an XML DOM Document
|
||||
* HTMLtoDOM(htmlString);
|
||||
*
|
||||
* // or to inject into an existing document/DOM node
|
||||
* HTMLtoDOM(htmlString, document);
|
||||
* HTMLtoDOM(htmlString, document.body);
|
||||
*
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var startTag = /^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
|
||||
endTag = /^<\/(\w+)[^>]*>/,
|
||||
attr = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
|
||||
|
||||
// Empty Elements - HTML 4.01
|
||||
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
|
||||
|
||||
// Block Elements - HTML 4.01
|
||||
var block = makeMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
|
||||
|
||||
// Inline Elements - HTML 4.01
|
||||
var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
|
||||
|
||||
// Elements that you can, intentionally, leave open
|
||||
// (and which close themselves)
|
||||
var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
|
||||
|
||||
// Attributes that have their values filled in disabled="disabled"
|
||||
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
var special = makeMap("script,style");
|
||||
|
||||
var htmlParser = this.htmlParser = function( html, handler ) {
|
||||
var index, chars, match, stack = [], last = html;
|
||||
stack.last = function(){
|
||||
return this[ this.length - 1 ];
|
||||
};
|
||||
|
||||
while ( html ) {
|
||||
chars = true;
|
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if ( !stack.last() || !special[ stack.last() ] ) {
|
||||
|
||||
// Comment
|
||||
if ( html.indexOf("<!--") == 0 ) {
|
||||
index = html.indexOf("-->");
|
||||
|
||||
if ( index >= 0 ) {
|
||||
if ( handler.comment )
|
||||
handler.comment( html.substring( 4, index ) );
|
||||
html = html.substring( index + 3 );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// end tag
|
||||
} else if ( html.indexOf("</") == 0 ) {
|
||||
match = html.match( endTag );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( endTag, parseEndTag );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// start tag
|
||||
} else if ( html.indexOf("<") == 0 ) {
|
||||
match = html.match( startTag );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( startTag, parseStartTag );
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( chars ) {
|
||||
index = html.indexOf("<");
|
||||
|
||||
var text = index < 0 ? html : html.substring( 0, index );
|
||||
html = index < 0 ? "" : html.substring( index );
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
}
|
||||
|
||||
} else {
|
||||
html = html.replace(new RegExp("(.*)<\/" + stack.last() + "[^>]*>"), function(all, text){
|
||||
text = text.replace(/<!--(.*?)-->/g, "$1")
|
||||
.replace(/<!\[CDATA\[(.*?)]]>/g, "$1");
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
|
||||
if ( html == last )
|
||||
throw "Parse Error: " + html;
|
||||
last = html;
|
||||
}
|
||||
|
||||
// Clean up any remaining tags
|
||||
parseEndTag();
|
||||
|
||||
function parseStartTag( tag, tagName, rest, unary ) {
|
||||
if ( block[ tagName ] ) {
|
||||
while ( stack.last() && inline[ stack.last() ] ) {
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( closeSelf[ tagName ] && stack.last() == tagName ) {
|
||||
parseEndTag( "", tagName );
|
||||
}
|
||||
|
||||
unary = empty[ tagName ] || !!unary;
|
||||
|
||||
if ( !unary )
|
||||
stack.push( tagName );
|
||||
|
||||
if ( handler.start ) {
|
||||
var attrs = [];
|
||||
|
||||
rest.replace(attr, function(match, name) {
|
||||
var value = arguments[2] ? arguments[2] :
|
||||
arguments[3] ? arguments[3] :
|
||||
arguments[4] ? arguments[4] :
|
||||
fillAttrs[name] ? name : "";
|
||||
|
||||
attrs.push({
|
||||
name: name,
|
||||
value: value,
|
||||
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
|
||||
});
|
||||
});
|
||||
|
||||
if ( handler.start )
|
||||
handler.start( tagName, attrs, unary );
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag( tag, tagName ) {
|
||||
// If no tag name is provided, clean shop
|
||||
if ( !tagName )
|
||||
var pos = 0;
|
||||
|
||||
// Find the closest opened tag of the same type
|
||||
else
|
||||
for ( var pos = stack.length - 1; pos >= 0; pos-- )
|
||||
if ( stack[ pos ] == tagName )
|
||||
break;
|
||||
|
||||
if ( pos >= 0 ) {
|
||||
// Close all the open elements, up the stack
|
||||
for ( var i = stack.length - 1; i >= pos; i-- )
|
||||
if ( handler.end )
|
||||
handler.end( stack[ i ] );
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.HTMLtoXML = function( html ) {
|
||||
var results = "";
|
||||
|
||||
htmlParser(html, {
|
||||
start: function( tag, attrs, unary ) {
|
||||
results += "<" + tag;
|
||||
|
||||
for ( var i = 0; i < attrs.length; i++ )
|
||||
results += " " + attrs[i].name + '="' + attrs[i].escaped + '"';
|
||||
|
||||
results += (unary ? "/" : "") + ">";
|
||||
},
|
||||
end: function( tag ) {
|
||||
results += "</" + tag + ">";
|
||||
},
|
||||
chars: function( text ) {
|
||||
results += text;
|
||||
},
|
||||
comment: function( text ) {
|
||||
results += "<!--" + text + "-->";
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
this.HTMLtoDOM = function( html, doc ) {
|
||||
// There can be only one of these elements
|
||||
var one = makeMap("html,head,body,title");
|
||||
|
||||
// Enforce a structure for the document
|
||||
var structure = {
|
||||
link: "head",
|
||||
base: "head"
|
||||
};
|
||||
|
||||
if ( !doc ) {
|
||||
if ( typeof DOMDocument != "undefined" )
|
||||
doc = new DOMDocument();
|
||||
else if ( typeof document != "undefined" && document.implementation && document.implementation.createDocument )
|
||||
doc = document.implementation.createDocument("", "", null);
|
||||
else if ( typeof ActiveX != "undefined" )
|
||||
doc = new ActiveXObject("Msxml.DOMDocument");
|
||||
|
||||
} else
|
||||
doc = doc.ownerDocument ||
|
||||
doc.getOwnerDocument && doc.getOwnerDocument() ||
|
||||
doc;
|
||||
|
||||
var elems = [],
|
||||
documentElement = doc.documentElement ||
|
||||
doc.getDocumentElement && doc.getDocumentElement();
|
||||
|
||||
// If we're dealing with an empty document then we
|
||||
// need to pre-populate it with the HTML document structure
|
||||
if ( !documentElement && doc.createElement ) (function(){
|
||||
var html = doc.createElement("html");
|
||||
var head = doc.createElement("head");
|
||||
head.appendChild( doc.createElement("title") );
|
||||
html.appendChild( head );
|
||||
html.appendChild( doc.createElement("body") );
|
||||
doc.appendChild( html );
|
||||
})();
|
||||
|
||||
// Find all the unique elements
|
||||
if ( doc.getElementsByTagName )
|
||||
for ( var i in one )
|
||||
one[ i ] = doc.getElementsByTagName( i )[0];
|
||||
|
||||
// If we're working with a document, inject contents into
|
||||
// the body element
|
||||
var curParentNode = one.body;
|
||||
|
||||
htmlParser( html, {
|
||||
start: function( tagName, attrs, unary ) {
|
||||
// If it's a pre-built element, then we can ignore
|
||||
// its construction
|
||||
if ( one[ tagName ] ) {
|
||||
curParentNode = one[ tagName ];
|
||||
return;
|
||||
}
|
||||
|
||||
var elem = doc.createElement( tagName );
|
||||
|
||||
for ( var attr in attrs )
|
||||
elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );
|
||||
|
||||
if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] != "boolean" )
|
||||
one[ structure[ tagName ] ].appendChild( elem );
|
||||
|
||||
else if ( curParentNode && curParentNode.appendChild )
|
||||
curParentNode.appendChild( elem );
|
||||
|
||||
if ( !unary ) {
|
||||
elems.push( elem );
|
||||
curParentNode = elem;
|
||||
}
|
||||
},
|
||||
end: function( tag ) {
|
||||
elems.length -= 1;
|
||||
|
||||
// Init the new parentNode
|
||||
curParentNode = elems[ elems.length - 1 ];
|
||||
},
|
||||
chars: function( text ) {
|
||||
curParentNode.appendChild( doc.createTextNode( text ) );
|
||||
},
|
||||
comment: function( text ) {
|
||||
// create comment node
|
||||
}
|
||||
});
|
||||
|
||||
return doc;
|
||||
};
|
||||
|
||||
function makeMap(str){
|
||||
var obj = {}, items = str.split(",");
|
||||
for ( var i = 0; i < items.length; i++ )
|
||||
obj[ items[i] ] = true;
|
||||
return obj;
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2008-2010 Pivotal Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,188 @@
|
||||
jasmine.TrivialReporter = function(doc) {
|
||||
this.document = doc || document;
|
||||
this.suiteDivs = {};
|
||||
this.logRunningSpecs = false;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
|
||||
var el = document.createElement(type);
|
||||
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
var child = arguments[i];
|
||||
|
||||
if (typeof child === 'string') {
|
||||
el.appendChild(document.createTextNode(child));
|
||||
} else {
|
||||
if (child) { el.appendChild(child); }
|
||||
}
|
||||
}
|
||||
|
||||
for (var attr in attrs) {
|
||||
if (attr == "className") {
|
||||
el[attr] = attrs[attr];
|
||||
} else {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
|
||||
var showPassed, showSkipped;
|
||||
|
||||
this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
|
||||
this.createDom('div', { className: 'banner' },
|
||||
this.createDom('div', { className: 'logo' },
|
||||
this.createDom('a', { href: 'http://pivotal.github.com/jasmine/', target: "_blank" }, "Jasmine"),
|
||||
this.createDom('span', { className: 'version' }, runner.env.versionString())),
|
||||
this.createDom('div', { className: 'options' },
|
||||
"Show ",
|
||||
showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
|
||||
showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
|
||||
this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
|
||||
)
|
||||
),
|
||||
|
||||
this.runnerDiv = this.createDom('div', { className: 'runner running' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
|
||||
this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
|
||||
this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
|
||||
);
|
||||
|
||||
this.document.body.appendChild(this.outerDiv);
|
||||
|
||||
var suites = runner.suites();
|
||||
for (var i = 0; i < suites.length; i++) {
|
||||
var suite = suites[i];
|
||||
var suiteDiv = this.createDom('div', { className: 'suite' },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
|
||||
this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
|
||||
this.suiteDivs[suite.id] = suiteDiv;
|
||||
var parentDiv = this.outerDiv;
|
||||
if (suite.parentSuite) {
|
||||
parentDiv = this.suiteDivs[suite.parentSuite.id];
|
||||
}
|
||||
parentDiv.appendChild(suiteDiv);
|
||||
}
|
||||
|
||||
this.startedAt = new Date();
|
||||
|
||||
var self = this;
|
||||
showPassed.onclick = function(evt) {
|
||||
if (showPassed.checked) {
|
||||
self.outerDiv.className += ' show-passed';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
|
||||
}
|
||||
};
|
||||
|
||||
showSkipped.onclick = function(evt) {
|
||||
if (showSkipped.checked) {
|
||||
self.outerDiv.className += ' show-skipped';
|
||||
} else {
|
||||
self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
|
||||
var results = runner.results();
|
||||
var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
|
||||
this.runnerDiv.setAttribute("class", className);
|
||||
//do it twice for IE
|
||||
this.runnerDiv.setAttribute("className", className);
|
||||
var specs = runner.specs();
|
||||
var specCount = 0;
|
||||
for (var i = 0; i < specs.length; i++) {
|
||||
if (this.specFilter(specs[i])) {
|
||||
specCount++;
|
||||
}
|
||||
}
|
||||
var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
|
||||
message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
|
||||
this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
|
||||
|
||||
this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
|
||||
var results = suite.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.totalCount == 0) { // todo: change this to check results.skipped
|
||||
status = 'skipped';
|
||||
}
|
||||
this.suiteDivs[suite.id].className += " " + status;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
|
||||
if (this.logRunningSpecs) {
|
||||
this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
|
||||
var results = spec.results();
|
||||
var status = results.passed() ? 'passed' : 'failed';
|
||||
if (results.skipped) {
|
||||
status = 'skipped';
|
||||
}
|
||||
var specDiv = this.createDom('div', { className: 'spec ' + status },
|
||||
this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
|
||||
this.createDom('a', {
|
||||
className: 'description',
|
||||
href: '?spec=' + encodeURIComponent(spec.getFullName()),
|
||||
title: spec.getFullName()
|
||||
}, spec.description));
|
||||
|
||||
|
||||
var resultItems = results.getItems();
|
||||
var messagesDiv = this.createDom('div', { className: 'messages' });
|
||||
for (var i = 0; i < resultItems.length; i++) {
|
||||
var result = resultItems[i];
|
||||
|
||||
if (result.type == 'log') {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
|
||||
} else if (result.type == 'expect' && result.passed && !result.passed()) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
|
||||
|
||||
if (result.trace.stack) {
|
||||
messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messagesDiv.childNodes.length > 0) {
|
||||
specDiv.appendChild(messagesDiv);
|
||||
}
|
||||
|
||||
this.suiteDivs[spec.suite.id].appendChild(specDiv);
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.log = function() {
|
||||
var console = jasmine.getGlobal().console;
|
||||
if (console && console.log) {
|
||||
if (console.log.apply) {
|
||||
console.log.apply(console, arguments);
|
||||
} else {
|
||||
console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.getLocation = function() {
|
||||
return this.document.location;
|
||||
};
|
||||
|
||||
jasmine.TrivialReporter.prototype.specFilter = function(spec) {
|
||||
var paramMap = {};
|
||||
var params = this.getLocation().search.substring(1).split('&');
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
var p = params[i].split('=');
|
||||
paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
|
||||
}
|
||||
|
||||
if (!paramMap["spec"]) return true;
|
||||
return spec.getFullName().indexOf(paramMap["spec"]) == 0;
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
body {
|
||||
font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
|
||||
}
|
||||
|
||||
|
||||
.jasmine_reporter a:visited, .jasmine_reporter a {
|
||||
color: #303;
|
||||
}
|
||||
|
||||
.jasmine_reporter a:hover, .jasmine_reporter a:active {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.run_spec {
|
||||
float:right;
|
||||
padding-right: 5px;
|
||||
font-size: .8em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.jasmine_reporter {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.banner {
|
||||
color: #303;
|
||||
background-color: #fef;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
float: left;
|
||||
font-size: 1.1em;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.logo .version {
|
||||
font-size: .6em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.runner.running {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
|
||||
.options {
|
||||
text-align: right;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.suite {
|
||||
border: 1px outset gray;
|
||||
margin: 5px 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.suite .suite {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.suite.passed {
|
||||
background-color: #dfd;
|
||||
}
|
||||
|
||||
.suite.failed {
|
||||
background-color: #fdd;
|
||||
}
|
||||
|
||||
.spec {
|
||||
margin: 5px;
|
||||
padding-left: 1em;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.spec.failed, .spec.passed, .spec.skipped {
|
||||
padding-bottom: 5px;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
.spec.failed {
|
||||
background-color: #fbb;
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.spec.passed {
|
||||
background-color: #bfb;
|
||||
border-color: green;
|
||||
}
|
||||
|
||||
.spec.skipped {
|
||||
background-color: #bbb;
|
||||
}
|
||||
|
||||
.messages {
|
||||
border-left: 1px dashed gray;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.passed {
|
||||
background-color: #cfc;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.failed {
|
||||
background-color: #fbb;
|
||||
}
|
||||
|
||||
.skipped {
|
||||
color: #777;
|
||||
background-color: #eee;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/*.resultMessage {*/
|
||||
/*white-space: pre;*/
|
||||
/*}*/
|
||||
|
||||
.resultMessage span.result {
|
||||
display: block;
|
||||
line-height: 2em;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.resultMessage .mismatch {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.stackTrace {
|
||||
white-space: pre;
|
||||
font-size: .8em;
|
||||
margin-left: 10px;
|
||||
max-height: 5em;
|
||||
overflow: auto;
|
||||
border: 1px inset red;
|
||||
padding: 1em;
|
||||
background: #eef;
|
||||
}
|
||||
|
||||
.finished-at {
|
||||
padding-left: 1em;
|
||||
font-size: .6em;
|
||||
}
|
||||
|
||||
.show-passed .passed,
|
||||
.show-skipped .skipped {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
#jasmine_content {
|
||||
position:fixed;
|
||||
right: 100%;
|
||||
}
|
||||
|
||||
.runner {
|
||||
border: 1px solid gray;
|
||||
display: block;
|
||||
margin: 5px 0;
|
||||
padding: 2px 0 2px 10px;
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
+223
-16
@@ -1,22 +1,229 @@
|
||||
var sys = require('sys'),
|
||||
http = require('http'),
|
||||
fs = require('fs');
|
||||
fs = require('fs'),
|
||||
url = require('url'),
|
||||
events = require('events');
|
||||
|
||||
http.createServer(function (req, res) {
|
||||
res.writeHead(200, {});
|
||||
sys.p('GET ' + req.url);
|
||||
var file = fs.createReadStream('.' + req.url);
|
||||
file.addListener('data', bind(res, res.write));
|
||||
file.addListener('error', function( error ){
|
||||
sys.p(error);
|
||||
var DEFAULT_PORT = 8000;
|
||||
|
||||
function main(argv) {
|
||||
new HttpServer({
|
||||
'GET': (function() {
|
||||
var servlet = new StaticServlet();
|
||||
return servlet.handleRequest.bind(servlet)
|
||||
})()
|
||||
}).start(Number(argv[2]) || DEFAULT_PORT);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return value.toString().
|
||||
replace('<', '<').
|
||||
replace('>', '>').
|
||||
replace('"', '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* An Http server implementation that uses a map of methods to decide
|
||||
* action routing.
|
||||
*
|
||||
* @param {Object} Map of method => Handler function
|
||||
*/
|
||||
function HttpServer(handlers) {
|
||||
this.handlers = handlers;
|
||||
this.server = http.createServer(this.handleRequest_.bind(this));
|
||||
}
|
||||
|
||||
HttpServer.prototype.start = function(port) {
|
||||
this.port = port;
|
||||
this.server.listen(port);
|
||||
sys.puts('Http Server running at http://127.0.0.1:' + port + '/');
|
||||
};
|
||||
|
||||
HttpServer.prototype.parseUrl_ = function(urlString) {
|
||||
var parsed = url.parse(urlString);
|
||||
parsed.pathname = url.resolve('/', parsed.pathname);
|
||||
return url.parse(url.format(parsed), true);
|
||||
};
|
||||
|
||||
HttpServer.prototype.handleRequest_ = function(req, res) {
|
||||
var logEntry = req.method + ' ' + req.url;
|
||||
if (req.headers['user-agent']) {
|
||||
logEntry += ' ' + req.headers['user-agent'];
|
||||
}
|
||||
sys.puts(logEntry);
|
||||
req.url = this.parseUrl_(req.url);
|
||||
var handler = this.handlers[req.method];
|
||||
if (!handler) {
|
||||
res.writeHead(501);
|
||||
res.end();
|
||||
} else {
|
||||
handler.call(this, req, res);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles static content.
|
||||
*/
|
||||
function StaticServlet() {}
|
||||
|
||||
StaticServlet.MimeMap = {
|
||||
'txt': 'text/plain',
|
||||
'html': 'text/html',
|
||||
'css': 'text/css',
|
||||
'xml': 'application/xml',
|
||||
'json': 'application/json',
|
||||
'js': 'application/javascript',
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'gif': 'image/gif',
|
||||
'png': 'image/png'
|
||||
};
|
||||
|
||||
StaticServlet.prototype.handleRequest = function(req, res) {
|
||||
var self = this;
|
||||
var path = ('./' + req.url.pathname).replace('//','/');
|
||||
var parts = path.split('/');
|
||||
if (parts[parts.length-1].charAt(0) === '.')
|
||||
return self.sendForbidden_(req, res, path);
|
||||
fs.stat(path, function(err, stat) {
|
||||
if (err)
|
||||
return self.sendMissing_(req, res, path);
|
||||
if (stat.isDirectory())
|
||||
return self.sendDirectory_(req, res, path);
|
||||
return self.sendFile_(req, res, path);
|
||||
});
|
||||
}
|
||||
|
||||
StaticServlet.prototype.sendError_ = function(req, res, error) {
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>Internal Server Error</title>\n');
|
||||
res.write('<h1>Internal Server Error</h1>');
|
||||
res.write('<pre>' + escapeHtml(sys.inspect(error)) + '</pre>');
|
||||
sys.puts('500 Internal Server Error');
|
||||
sys.puts(sys.inspect(error));
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendMissing_ = function(req, res, path) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(404, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>404 Not Found</title>\n');
|
||||
res.write('<h1>Not Found</h1>');
|
||||
res.write(
|
||||
'<p>The requested URL ' +
|
||||
escapeHtml(path) +
|
||||
' was not found on this server.</p>'
|
||||
);
|
||||
res.end();
|
||||
sys.puts('404 Not Found: ' + path);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(403, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>403 Forbidden</title>\n');
|
||||
res.write('<h1>Forbidden</h1>');
|
||||
res.write(
|
||||
'<p>You do not have permission to access ' +
|
||||
escapeHtml(path) + ' on this server.</p>'
|
||||
);
|
||||
res.end();
|
||||
sys.puts('403 Forbidden: ' + path);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
|
||||
res.writeHead(301, {
|
||||
'Content-Type': 'text/html',
|
||||
'Location': redirectUrl
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>301 Moved Permanently</title>\n');
|
||||
res.write('<h1>Moved Permanently</h1>');
|
||||
res.write(
|
||||
'<p>The document has moved <a href="' +
|
||||
redirectUrl +
|
||||
'">here</a>.</p>'
|
||||
);
|
||||
res.end();
|
||||
sys.puts('401 Moved Permanently: ' + redirectUrl);
|
||||
};
|
||||
|
||||
StaticServlet.prototype.sendFile_ = function(req, res, path) {
|
||||
var self = this;
|
||||
var file = fs.createReadStream(path);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': StaticServlet.
|
||||
MimeMap[path.split('.').pop()] || 'text/plain'
|
||||
});
|
||||
file.on('data', res.write.bind(res));
|
||||
file.on('close', function() {
|
||||
res.end();
|
||||
});
|
||||
file.addListener('close', bind(res, res.end));
|
||||
}).listen(8000);
|
||||
sys.puts('Server running at http://127.0.0.1:8000/');
|
||||
file.on('error', function(error) {
|
||||
self.sendError_(req, res, error);
|
||||
});
|
||||
};
|
||||
|
||||
function bind(_this, _fn) {
|
||||
return function(){
|
||||
return _fn.apply(_this, arguments);
|
||||
};
|
||||
}
|
||||
StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
|
||||
var self = this;
|
||||
if (path.match(/[^\/]$/)) {
|
||||
req.url.pathname += '/';
|
||||
var redirectUrl = url.format(url.parse(url.format(req.url)));
|
||||
return self.sendRedirect_(req, res, redirectUrl);
|
||||
}
|
||||
fs.readdir(path, function(err, files) {
|
||||
if (err)
|
||||
return self.sendError_(req, res, error);
|
||||
|
||||
if (!files.length)
|
||||
return self.writeDirectoryIndex_(req, res, path, []);
|
||||
|
||||
var remaining = files.length;
|
||||
files.forEach(function(fileName, index) {
|
||||
fs.stat(path + '/' + fileName, function(err, stat) {
|
||||
if (err)
|
||||
return self.sendError_(req, res, err);
|
||||
if (stat.isDirectory()) {
|
||||
files[index] = fileName + '/';
|
||||
}
|
||||
if (!(--remaining))
|
||||
return self.writeDirectoryIndex_(req, res, path, files);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
|
||||
path = path.substring(1);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write('<!doctype html>\n');
|
||||
res.write('<title>' + escapeHtml(path) + '</title>\n');
|
||||
res.write('<style>\n');
|
||||
res.write(' ol { list-style-type: none; font-size: 1.2em; }\n');
|
||||
res.write('</style>\n');
|
||||
res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>');
|
||||
res.write('<ol>');
|
||||
files.forEach(function(fileName) {
|
||||
if (fileName.charAt(0) !== '.') {
|
||||
res.write('<li><a href="' +
|
||||
escapeHtml(fileName) + '">' +
|
||||
escapeHtml(fileName) + '</a></li>');
|
||||
}
|
||||
});
|
||||
res.write('</ol>');
|
||||
res.end();
|
||||
};
|
||||
|
||||
// Must be last,
|
||||
main(process.argv);
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
node lib/nodeserver/server.js
|
||||
node lib/nodeserver/server.js $1
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>
|
||||
</script>
|
||||
</head>
|
||||
<body ng:init="$window.$root = this; data = [{foo: 'foo'},{bar: 'bar'}]">
|
||||
<p>This is a demo of a potential bug in angular.</p>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>
|
||||
</head>
|
||||
<body ng:init="$window.$root = this; data = [{foo: 'foo'},{bar: 'bar'}]">
|
||||
<ng:include src="'ng_include_this.partial'" scope="this"/>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
included HTML. eval count: {{c=c+1}}
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<script type="text/javascript" src="../angular-scenario.js"></script>
|
||||
<script type="text/javascript" src="../build/angular-scenario.js"></script>
|
||||
<script type="text/javascript" src="widgets-scenario.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<script type="text/javascript" src="../src/scenario/bootstrap.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript" src="widgets-scenario.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -27,15 +27,28 @@ describe('widgets', function() {
|
||||
expect(binding('multiselect').fromJson()).toEqual(['A', 'C']);
|
||||
|
||||
expect(binding('button').fromJson()).toEqual({'count': 0});
|
||||
expect(binding('form').fromJson()).toEqual({'count': 0});
|
||||
|
||||
element('form a').click();
|
||||
expect(binding('button').fromJson()).toEqual({'count': 1});
|
||||
element('input[value="submit"]').click();
|
||||
|
||||
element('input[value="submit input"]').click();
|
||||
expect(binding('button').fromJson()).toEqual({'count': 2});
|
||||
expect(binding('form').fromJson()).toEqual({'count': 1});
|
||||
|
||||
element('button:contains("submit button")').click();
|
||||
expect(binding('button').fromJson()).toEqual({'count': 2});
|
||||
expect(binding('form').fromJson()).toEqual({'count': 2});
|
||||
|
||||
element('input[value="button"]').click();
|
||||
expect(binding('button').fromJson()).toEqual({'count': 3});
|
||||
|
||||
element('input[type="image"]').click();
|
||||
expect(binding('button').fromJson()).toEqual({'count': 4});
|
||||
|
||||
element('#navigate a').click();
|
||||
expect(binding('$location.hash')).toEqual('route');
|
||||
|
||||
/**
|
||||
* Custom value parser for futures.
|
||||
*/
|
||||
|
||||
+10
-5
@@ -2,7 +2,6 @@
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="style.css"/>
|
||||
<script type="text/javascript" src="../libs/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js" ng:autobind></script>
|
||||
</head>
|
||||
<body ng:init="$window.$scope = this">
|
||||
@@ -74,15 +73,16 @@
|
||||
<tr><th colspan="3">Buttons</th></tr>
|
||||
<tr>
|
||||
<td>ng:change<br/>ng:click</td>
|
||||
<td ng:init="button.count = 0">
|
||||
<form>
|
||||
<td ng:init="button.count = 0; form.count = 0;">
|
||||
<form ng:submit="form.count = form.count + 1">
|
||||
<input type="button" value="button" ng:change="button.count = button.count + 1"/> <br/>
|
||||
<input type="submit" value="submit" ng:change="button.count = button.count + 1"/><br/>
|
||||
<input type="submit" value="submit input" ng:change="button.count = button.count + 1"/><br/>
|
||||
<button type="submit">submit button</button>
|
||||
<input type="image" src="" ng:change="button.count = button.count + 1"/><br/>
|
||||
<a href="" ng:click="button.count = button.count + 1">action</a>
|
||||
</form>
|
||||
</td>
|
||||
<td>button={{button}}</td>
|
||||
<td>button={{button}} form={{form}}</td>
|
||||
</tr>
|
||||
<tr><th colspan="3">Repeaters</th></tr>
|
||||
<tr id="repeater-row">
|
||||
@@ -94,6 +94,11 @@
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr id="navigate">
|
||||
<td>navigate</td>
|
||||
<td><a href="#route">Go to #route</td>
|
||||
<td>{{$location.hash}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1 +1 @@
|
||||
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876
|
||||
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 20000
|
||||
|
||||
+33
-20
@@ -3,6 +3,25 @@
|
||||
if (typeof document.getAttribute == $undefined)
|
||||
document.getAttribute = function() {};
|
||||
|
||||
//The below may not be true on browsers in the Turkish locale.
|
||||
var lowercase = function (value){ return isString(value) ? value.toLowerCase() : value; };
|
||||
var uppercase = function (value){ return isString(value) ? value.toUpperCase() : value; };
|
||||
var manualLowercase = function (s) {
|
||||
return isString(s) ? s.replace(/[A-Z]/g,
|
||||
function (ch) {return fromCharCode(ch.charCodeAt(0) | 32); }) : s;
|
||||
};
|
||||
var manualUppercase = function (s) {
|
||||
return isString(s) ? s.replace(/[a-z]/g,
|
||||
function (ch) {return fromCharCode(ch.charCodeAt(0) & ~32); }) : s;
|
||||
};
|
||||
if ('i' !== 'I'.toLowerCase()) {
|
||||
lowercase = manualLowercase;
|
||||
uppercase = manulaUppercase;
|
||||
}
|
||||
|
||||
function fromCharCode(code) { return String.fromCharCode(code); }
|
||||
|
||||
|
||||
var _undefined = undefined,
|
||||
_null = null,
|
||||
$$element = '$element',
|
||||
@@ -134,15 +153,26 @@ function isNumber(value){ return typeof value == $number;}
|
||||
function isArray(value) { return value instanceof Array; }
|
||||
function isFunction(value){ return typeof value == $function;}
|
||||
function isTextNode(node) { return nodeName(node) == '#text'; }
|
||||
function lowercase(value){ return isString(value) ? value.toLowerCase() : value; }
|
||||
function uppercase(value){ return isString(value) ? value.toUpperCase() : value; }
|
||||
function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }
|
||||
function isElement(node) {
|
||||
return node && (node.nodeName || node instanceof JQLite || (jQuery && node instanceof jQuery));
|
||||
}
|
||||
|
||||
function HTML(html) {
|
||||
/**
|
||||
* HTML class which is the only class which can be used in ng:bind to inline HTML for security reasons.
|
||||
* @constructor
|
||||
* @param html raw (unsafe) html
|
||||
* @param {string=} option if set to 'usafe' then get method will return raw (unsafe/unsanitized) html
|
||||
*/
|
||||
function HTML(html, option) {
|
||||
this.html = html;
|
||||
this.get = lowercase(option) == 'unsafe' ?
|
||||
valueFn(html) :
|
||||
function htmlSanitize() {
|
||||
var buf = [];
|
||||
htmlParser(html, htmlSanitizeWriter(buf));
|
||||
return buf.join('');
|
||||
};
|
||||
}
|
||||
|
||||
if (msie) {
|
||||
@@ -297,16 +327,6 @@ function setHtml(node, html) {
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(html) {
|
||||
if (!html || !html.replace)
|
||||
return html;
|
||||
return html.
|
||||
replace(/&/g, '&').
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>');
|
||||
}
|
||||
|
||||
|
||||
function isRenderableElement(element) {
|
||||
var name = element && element[0] && element[0].nodeName;
|
||||
return name && name.charAt(0) != '#' &&
|
||||
@@ -328,13 +348,6 @@ function elementError(element, type, error) {
|
||||
}
|
||||
}
|
||||
|
||||
function escapeAttr(html) {
|
||||
if (!html || !html.replace)
|
||||
return html;
|
||||
return html.replace(/</g, '<').replace(/>/g, '>').replace(/\"/g,
|
||||
'"');
|
||||
}
|
||||
|
||||
function concat(array1, array2, index) {
|
||||
return array1.concat(slice.call(array2, index, array2.length));
|
||||
}
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ function lex(text, parseStringsForObjects){
|
||||
}
|
||||
function isWhitespace(ch) {
|
||||
return ch == ' ' || ch == '\r' || ch == '\t' ||
|
||||
ch == '\n' || ch == '\v';
|
||||
ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0
|
||||
}
|
||||
function isIdent(ch) {
|
||||
return 'a' <= ch && ch <= 'z' ||
|
||||
|
||||
Vendored
+1
@@ -53,6 +53,7 @@
|
||||
addScript("/parser.js");
|
||||
addScript("/Resource.js");
|
||||
addScript("/Browser.js");
|
||||
addScript("/sanitizer.js");
|
||||
addScript("/AngularPublic.js");
|
||||
|
||||
// Extension points
|
||||
|
||||
@@ -202,6 +202,12 @@ var angularString = {
|
||||
}
|
||||
return chars.join('');
|
||||
},
|
||||
|
||||
/**
|
||||
* Tries to convert input to date and if successful returns the date, otherwise returns the input.
|
||||
* @param {string} string
|
||||
* @return {(Date|string)}
|
||||
*/
|
||||
'toDate':function(string){
|
||||
var match;
|
||||
if (typeof string == 'string' &&
|
||||
|
||||
+27
-4
@@ -26,15 +26,19 @@ angularDirective("ng:bind", function(expression){
|
||||
return function(element) {
|
||||
var lastValue = noop, lastError = noop;
|
||||
this.$onEval(function() {
|
||||
var error, value, isHtml, isDomElement,
|
||||
var error, value, html, isHtml, isDomElement,
|
||||
oldElement = this.hasOwnProperty($$element) ? this.$element : _undefined;
|
||||
this.$element = element;
|
||||
value = this.$tryEval(expression, function(e){
|
||||
error = toJson(e);
|
||||
});
|
||||
this.$element = oldElement;
|
||||
// If we are HTML than save the raw HTML data so that we don't
|
||||
// recompute sanitization since it is expensive.
|
||||
// TODO: turn this into a more generic way to compute this
|
||||
if (isHtml = (value instanceof HTML))
|
||||
value = (html = value).html;
|
||||
if (lastValue === value && lastError == error) return;
|
||||
isHtml = value instanceof HTML;
|
||||
isDomElement = isElement(value);
|
||||
if (!isHtml && !isDomElement && isObject(value)) {
|
||||
value = toJson(value);
|
||||
@@ -45,7 +49,7 @@ angularDirective("ng:bind", function(expression){
|
||||
elementError(element, NG_EXCEPTION, error);
|
||||
if (error) value = error;
|
||||
if (isHtml) {
|
||||
element.html(value.html);
|
||||
element.html(html.get());
|
||||
} else if (isDomElement) {
|
||||
element.html('');
|
||||
element.append(value);
|
||||
@@ -205,7 +209,7 @@ angularWidget("@ng:repeat", function(expression, element){
|
||||
*
|
||||
* Events that are handled via these handler are always configured not to propagate further.
|
||||
*
|
||||
* TODO: maybe we should consider allowing users to control even propagation in the future.
|
||||
* TODO: maybe we should consider allowing users to control event propagation in the future.
|
||||
*/
|
||||
angularDirective("ng:click", function(expression, element){
|
||||
return function(element){
|
||||
@@ -218,6 +222,25 @@ angularDirective("ng:click", function(expression, element){
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Enables binding angular expressions to onsubmit events.
|
||||
*
|
||||
* Additionally it prevents the default action (which for form means sending the request to the
|
||||
* server and reloading the current page).
|
||||
*/
|
||||
angularDirective("ng:submit", function(expression, element) {
|
||||
return function(element) {
|
||||
var self = this;
|
||||
element.bind('submit', function(event) {
|
||||
self.$tryEval(expression, element);
|
||||
self.$root.$eval();
|
||||
event.preventDefault();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
angularDirective("ng:watch", function(expression, element){
|
||||
return function(element){
|
||||
var self = this;
|
||||
|
||||
+28
-11
@@ -44,7 +44,7 @@ function padNumber(num, digits, trim) {
|
||||
}
|
||||
function dateGetter(name, size, offset, trim) {
|
||||
return function(date) {
|
||||
var value = date['get' + name].call(date);
|
||||
var value = date['get' + name]();
|
||||
if (offset > 0 || value > -offset)
|
||||
value += offset;
|
||||
if (value === 0 && offset == -12 ) value = 12;
|
||||
@@ -73,9 +73,19 @@ var DATE_FORMATS = {
|
||||
}
|
||||
};
|
||||
var DATE_FORMATS_SPLIT = /([^yMdHhmsaZ]*)(y+|M+|d+|H+|h+|m+|s+|a|Z)(.*)/;
|
||||
var NUMBER_STRING = /^\d+$/;
|
||||
|
||||
angularFilter.date = function(date, format) {
|
||||
if (!(date instanceof Date)) return date;
|
||||
if (isString(date) && NUMBER_STRING.test(date)) {
|
||||
date = parseInt(date, 10);
|
||||
}
|
||||
|
||||
if (isNumber(date)) {
|
||||
date = new Date(date);
|
||||
} else if (!(date instanceof Date)) {
|
||||
return date;
|
||||
}
|
||||
|
||||
var text = date.toLocaleDateString(), fn;
|
||||
if (format && isString(format)) {
|
||||
text = '';
|
||||
@@ -101,8 +111,12 @@ angularFilter.lowercase = lowercase;
|
||||
|
||||
angularFilter.uppercase = uppercase;
|
||||
|
||||
angularFilter.html = function(html){
|
||||
return new HTML(html);
|
||||
/**</>
|
||||
* @exportedAs filter:html
|
||||
* @param {string=} option if 'unsafe' then do not sanitize the HTML input
|
||||
*/
|
||||
angularFilter.html = function(html, option){
|
||||
return new HTML(html, option);
|
||||
};
|
||||
|
||||
angularFilter.linky = function(text){
|
||||
@@ -114,15 +128,18 @@ angularFilter.linky = function(text){
|
||||
var match;
|
||||
var raw = text;
|
||||
var html = [];
|
||||
var writer = htmlSanitizeWriter(html);
|
||||
var url;
|
||||
var i;
|
||||
while (match=raw.match(URL)) {
|
||||
var url = match[0].replace(/[\.\;\,\(\)\{\}\<\>]$/,'');
|
||||
var i = raw.indexOf(url);
|
||||
html.push(escapeHtml(raw.substr(0, i)));
|
||||
html.push('<a href="' + url + '">');
|
||||
html.push(url);
|
||||
html.push('</a>');
|
||||
url = match[0].replace(/[\.\;\,\(\)\{\}\<\>]$/,'');
|
||||
i = raw.indexOf(url);
|
||||
writer.chars(raw.substr(0, i));
|
||||
writer.start('a', {href:url});
|
||||
writer.chars(url);
|
||||
writer.end('a');
|
||||
raw = raw.substring(i + url.length);
|
||||
}
|
||||
html.push(escapeHtml(raw));
|
||||
writer.chars(raw);
|
||||
return new HTML(html.join(''));
|
||||
};
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* HTML Parser By Misko Hevery (misko@hevery.com)
|
||||
* based on: HTML Parser By John Resig (ejohn.org)
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*
|
||||
* // Use like so:
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
*/
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var START_TAG_REGEXP = /^<\s*([\w:]+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
|
||||
END_TAG_REGEXP = /^<\s*\/\s*([\w:]+)[^>]*>/,
|
||||
ATTR_REGEXP = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g,
|
||||
BEGIN_TAG_REGEXP = /^</,
|
||||
BEGING_END_TAGE_REGEXP = /^<\s*\//,
|
||||
COMMENT_REGEXP = /<!--(.*?)-->/g,
|
||||
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g;
|
||||
|
||||
// Empty Elements - HTML 4.01
|
||||
var emptyElements = makeMap("area,base,basefont,br,col,hr,img,input,isindex,link,param");
|
||||
|
||||
// Block Elements - HTML 4.01
|
||||
var blockElements = makeMap("address,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,"+
|
||||
"form,hr,ins,isindex,li,map,menu,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
|
||||
|
||||
// Inline Elements - HTML 4.01
|
||||
var inlineElements = makeMap("a,abbr,acronym,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,img,"+
|
||||
"input,ins,kbd,label,map,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
|
||||
|
||||
// Elements that you can, intentionally, leave open
|
||||
// (and which close themselves)
|
||||
var closeSelfElements = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
|
||||
|
||||
// Attributes that have their values filled in disabled="disabled"
|
||||
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
var specialElements = makeMap("script,style");
|
||||
|
||||
var validElements = extend({}, emptyElements, blockElements, inlineElements, closeSelfElements);
|
||||
var validAttrs = extend({}, fillAttrs, makeMap(
|
||||
'abbr,align,alink,alt,archive,axis,background,bgcolor,border,cellpadding,cellspacing,cite,class,classid,clear,code,codebase,'+
|
||||
'codetype,color,cols,colspan,content,coords,data,dir,face,for,headers,height,href,hreflang,hspace,id,label,lang,language,'+
|
||||
'link,longdesc,marginheight,marginwidth,maxlength,media,method,name,nowrap,profile,prompt,rel,rev,rows,rowspan,rules,scheme,'+
|
||||
'scope,scrolling,shape,size,span,src,standby,start,summary,tabindex,target,text,title,type,usemap,valign,value,valuetype,'+
|
||||
'vlink,vspace,width'));
|
||||
|
||||
/**
|
||||
* @example
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
* @param {string} html string
|
||||
* @param {object} handler
|
||||
*/
|
||||
var htmlParser = function( html, handler ) {
|
||||
var index, chars, match, stack = [], last = html;
|
||||
stack.last = function(){ return stack[ stack.length - 1 ]; };
|
||||
|
||||
while ( html ) {
|
||||
chars = true;
|
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if ( !stack.last() || !specialElements[ stack.last() ] ) {
|
||||
|
||||
// Comment
|
||||
if ( html.indexOf("<!--") === 0 ) {
|
||||
index = html.indexOf("-->");
|
||||
|
||||
if ( index >= 0 ) {
|
||||
if ( handler.comment )
|
||||
handler.comment( html.substring( 4, index ) );
|
||||
html = html.substring( index + 3 );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// end tag
|
||||
} else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
|
||||
match = html.match( END_TAG_REGEXP );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( END_TAG_REGEXP, parseEndTag );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// start tag
|
||||
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
|
||||
match = html.match( START_TAG_REGEXP );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( START_TAG_REGEXP, parseStartTag );
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( chars ) {
|
||||
index = html.indexOf("<");
|
||||
|
||||
var text = index < 0 ? html : html.substring( 0, index );
|
||||
html = index < 0 ? "" : html.substring( index );
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
}
|
||||
|
||||
} else {
|
||||
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){
|
||||
text = text.
|
||||
replace(COMMENT_REGEXP, "$1").
|
||||
replace(CDATA_REGEXP, "$1");
|
||||
|
||||
if ( handler.chars )
|
||||
handler.chars( text );
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
|
||||
if ( html == last ) {
|
||||
throw "Parse Error: " + html;
|
||||
}
|
||||
last = html;
|
||||
}
|
||||
|
||||
// Clean up any remaining tags
|
||||
parseEndTag();
|
||||
|
||||
function parseStartTag( tag, tagName, rest, unary ) {
|
||||
tagName = lowercase(tagName);
|
||||
if ( blockElements[ tagName ] ) {
|
||||
while ( stack.last() && inlineElements[ stack.last() ] ) {
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( closeSelfElements[ tagName ] && stack.last() == tagName ) {
|
||||
parseEndTag( "", tagName );
|
||||
}
|
||||
|
||||
unary = emptyElements[ tagName ] || !!unary;
|
||||
|
||||
if ( !unary )
|
||||
stack.push( tagName );
|
||||
|
||||
if ( handler.start ) {
|
||||
var attrs = {};
|
||||
|
||||
rest.replace(ATTR_REGEXP, function(match, name) {
|
||||
var value = arguments[2] ? arguments[2] :
|
||||
arguments[3] ? arguments[3] :
|
||||
arguments[4] ? arguments[4] :
|
||||
fillAttrs[name] ? name : "";
|
||||
|
||||
attrs[name] = value; //value.replace(/(^|[^\\])"/g, '$1\\\"') //"
|
||||
});
|
||||
|
||||
if ( handler.start )
|
||||
handler.start( tagName, attrs, unary );
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag( tag, tagName ) {
|
||||
var pos = 0, i;
|
||||
tagName = lowercase(tagName);
|
||||
if ( tagName )
|
||||
// Find the closest opened tag of the same type
|
||||
for ( pos = stack.length - 1; pos >= 0; pos-- )
|
||||
if ( stack[ pos ] == tagName )
|
||||
break;
|
||||
|
||||
if ( pos >= 0 ) {
|
||||
// Close all the open elements, up the stack
|
||||
for ( i = stack.length - 1; i >= pos; i-- )
|
||||
if ( handler.end )
|
||||
handler.end( stack[ i ] );
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param str 'key1,key2,...'
|
||||
* @returns {object} in the form of {key1:true, key2:true, ...}
|
||||
*/
|
||||
function makeMap(str){
|
||||
var obj = {}, items = str.split(","), i;
|
||||
for ( i = 0; i < items.length; i++ )
|
||||
obj[ items[i] ] = true;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/*
|
||||
* For attack vectors see: http://ha.ckers.org/xss.html
|
||||
*/
|
||||
var JAVASCRIPT_URL = /^javascript:/i,
|
||||
NBSP_REGEXP = / /gim,
|
||||
HEX_ENTITY_REGEXP = /&#x([\da-f]*);?/igm,
|
||||
DEC_ENTITY_REGEXP = /&#(\d+);?/igm,
|
||||
CHAR_REGEXP = /[\w:]/gm,
|
||||
HEX_DECODE = function(match, code){return fromCharCode(parseInt(code,16));},
|
||||
DEC_DECODE = function(match, code){return fromCharCode(code);};
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns true if url decodes to something which starts with 'javascript:' hence unsafe
|
||||
*/
|
||||
function isJavaScriptUrl(url) {
|
||||
var chars = [];
|
||||
url.replace(NBSP_REGEXP, '').
|
||||
replace(HEX_ENTITY_REGEXP, HEX_DECODE).
|
||||
replace(DEC_ENTITY_REGEXP, DEC_DECODE).
|
||||
// Remove all non \w: characters, unfurtunetly value.replace(/[\w:]/,'') can be defeated using \u0000
|
||||
replace(CHAR_REGEXP, function(ch){chars.push(ch);});
|
||||
return JAVASCRIPT_URL.test(lowercase(chars.join('')));
|
||||
}
|
||||
|
||||
/**
|
||||
* create an HTML/XML writer which writes to buffer
|
||||
* @param {Array} buf use buf.jain('') to get out sanitized html string
|
||||
* @returns {object} in the form of {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* }
|
||||
*/
|
||||
function htmlSanitizeWriter(buf){
|
||||
var ignore = false;
|
||||
var out = bind(buf, buf.push);
|
||||
return {
|
||||
start: function(tag, attrs, unary){
|
||||
tag = lowercase(tag);
|
||||
if (!ignore && specialElements[tag]) {
|
||||
ignore = tag;
|
||||
}
|
||||
if (!ignore && validElements[tag]) {
|
||||
out('<');
|
||||
out(tag);
|
||||
foreach(attrs, function(value, key){
|
||||
if (validAttrs[lowercase(key)] && !isJavaScriptUrl(value)) {
|
||||
out(' ');
|
||||
out(key);
|
||||
out('="');
|
||||
out(value.
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>').
|
||||
replace(/\"/g,'"'));
|
||||
out('"');
|
||||
}
|
||||
});
|
||||
out(unary ? '/>' : '>');
|
||||
}
|
||||
},
|
||||
end: function(tag){
|
||||
tag = lowercase(tag);
|
||||
if (!ignore && validElements[tag]) {
|
||||
out('</');
|
||||
out(tag);
|
||||
out('>');
|
||||
}
|
||||
if (tag == ignore) {
|
||||
ignore = false;
|
||||
}
|
||||
},
|
||||
chars: function(chars){
|
||||
if (!ignore) {
|
||||
out(chars.
|
||||
replace(/&(\w+[&;\W])?/g, function(match, entity){return entity?match:'&';}).
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>'));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
+47
-14
@@ -1,51 +1,84 @@
|
||||
/**
|
||||
* Represents the application currently being tested and abstracts usage
|
||||
* of iframes or separate windows.
|
||||
*
|
||||
* @param {Object} context jQuery wrapper around HTML context.
|
||||
*/
|
||||
angular.scenario.Application = function(context) {
|
||||
this.context = context;
|
||||
context.append('<h2>Current URL: <a href="about:blank">None</a></h2>');
|
||||
context.append(
|
||||
'<h2>Current URL: <a href="about:blank">None</a></h2>' +
|
||||
'<div id="test-frames"></div>'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the jQuery collection of frames. Don't use this directly because
|
||||
* frames may go stale.
|
||||
*
|
||||
* @private
|
||||
* @return {Object} jQuery collection
|
||||
*/
|
||||
angular.scenario.Application.prototype.getFrame = function() {
|
||||
return this.context.find('> iframe');
|
||||
angular.scenario.Application.prototype.getFrame_ = function() {
|
||||
return this.context.find('#test-frames iframe:last');
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the window of the test runner frame. Always favor executeAction()
|
||||
* Gets the window of the test runner frame. Always favor executeAction()
|
||||
* instead of this method since it prevents you from getting a stale window.
|
||||
*
|
||||
* @private
|
||||
* @return {Object} the window of the frame
|
||||
*/
|
||||
angular.scenario.Application.prototype.getWindow = function() {
|
||||
var contentWindow = this.getFrame().attr('contentWindow');
|
||||
angular.scenario.Application.prototype.getWindow_ = function() {
|
||||
var contentWindow = this.getFrame_().attr('contentWindow');
|
||||
if (!contentWindow)
|
||||
throw 'No window available because frame not loaded.';
|
||||
throw 'Frame window is not accessible.';
|
||||
return contentWindow;
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the location of the frame.
|
||||
*
|
||||
* @param {string} url The URL. If it begins with a # then only the
|
||||
* hash of the page is changed.
|
||||
* @param {Function} onloadFn function($window, $document)
|
||||
*/
|
||||
angular.scenario.Application.prototype.navigateTo = function(url, onloadFn) {
|
||||
this.getFrame().remove();
|
||||
this.context.append('<iframe src=""></iframe>');
|
||||
var self = this;
|
||||
var frame = this.getFrame_();
|
||||
if (url.charAt(0) === '#') {
|
||||
url = frame.attr('src').split('#')[0] + url;
|
||||
frame.attr('src', url);
|
||||
this.executeAction(onloadFn);
|
||||
} else {
|
||||
frame.css('display', 'none').attr('src', 'about:blank');
|
||||
this.context.find('#test-frames').append('<iframe>');
|
||||
frame = this.getFrame_();
|
||||
frame.load(function() {
|
||||
self.executeAction(onloadFn);
|
||||
frame.unbind();
|
||||
}).attr('src', url);
|
||||
}
|
||||
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.
|
||||
* Executes a function in the context of the tested application. Will wait
|
||||
* for all pending angular xhr requests before executing.
|
||||
*
|
||||
* @param {Function} The callback to execute. function($window, $document)
|
||||
* @param {Function} action The callback to execute. function($window, $document)
|
||||
* $document is a jQuery wrapped document.
|
||||
*/
|
||||
angular.scenario.Application.prototype.executeAction = function(action) {
|
||||
var $window = this.getWindow();
|
||||
return action.call(this, $window, _jQuery($window.document));
|
||||
var self = this;
|
||||
var $window = this.getWindow_();
|
||||
if (!$window.angular) {
|
||||
return action.call(this, $window, _jQuery($window.document));
|
||||
}
|
||||
var $browser = $window.angular.service.$browser();
|
||||
$browser.poll();
|
||||
$browser.notifyWhenNoOutstandingRequests(function() {
|
||||
action.call(self, $window, _jQuery($window.document));
|
||||
});
|
||||
};
|
||||
|
||||
+54
-11
@@ -1,8 +1,12 @@
|
||||
/**
|
||||
* The representation of define blocks. Don't used directly, instead use
|
||||
* define() in your tests.
|
||||
*
|
||||
* @param {string} descName Name of the block
|
||||
* @param {Object} parent describe or undefined if the root.
|
||||
*/
|
||||
angular.scenario.Describe = function(descName, parent) {
|
||||
this.only = parent && parent.only;
|
||||
this.beforeEachFns = [];
|
||||
this.afterEachFns = [];
|
||||
this.its = [];
|
||||
@@ -10,7 +14,7 @@ angular.scenario.Describe = function(descName, parent) {
|
||||
this.name = descName;
|
||||
this.parent = parent;
|
||||
this.id = angular.scenario.Describe.id++;
|
||||
|
||||
|
||||
/**
|
||||
* Calls all before functions.
|
||||
*/
|
||||
@@ -36,7 +40,7 @@ angular.scenario.Describe.id = 0;
|
||||
/**
|
||||
* Defines a block to execute before each it or nested describe.
|
||||
*
|
||||
* @param {Function} Body of the block.
|
||||
* @param {Function} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.beforeEach = function(body) {
|
||||
this.beforeEachFns.push(body);
|
||||
@@ -45,7 +49,7 @@ angular.scenario.Describe.prototype.beforeEach = function(body) {
|
||||
/**
|
||||
* Defines a block to execute after each it or nested describe.
|
||||
*
|
||||
* @param {Function} Body of the block.
|
||||
* @param {Function} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.afterEach = function(body) {
|
||||
this.afterEachFns.push(body);
|
||||
@@ -54,8 +58,8 @@ angular.scenario.Describe.prototype.afterEach = function(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.
|
||||
* @param {string} name Name of the block. Appended to the parent block's name.
|
||||
* @param {Function} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.describe = function(name, body) {
|
||||
var child = new angular.scenario.Describe(name, this);
|
||||
@@ -63,6 +67,19 @@ angular.scenario.Describe.prototype.describe = function(name, body) {
|
||||
body.call(child);
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as describe() but makes ddescribe blocks the only to run.
|
||||
*
|
||||
* @param {string} name Name of the test.
|
||||
* @param {Function} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.ddescribe = function(name, body) {
|
||||
var child = new angular.scenario.Describe(name, this);
|
||||
child.only = true;
|
||||
this.children.push(child);
|
||||
body.call(child);
|
||||
};
|
||||
|
||||
/**
|
||||
* Use to disable a describe block.
|
||||
*/
|
||||
@@ -71,20 +88,31 @@ angular.scenario.Describe.prototype.xdescribe = angular.noop;
|
||||
/**
|
||||
* Defines a test.
|
||||
*
|
||||
* @param {String} Name of the test.
|
||||
* @param {Function} Body of the block.
|
||||
* @param {string} name Name of the test.
|
||||
* @param {Function} vody Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.it = function(name, body) {
|
||||
var self = this;
|
||||
this.its.push({
|
||||
definition: this,
|
||||
only: this.only,
|
||||
name: name,
|
||||
before: self.setupBefore,
|
||||
before: this.setupBefore,
|
||||
body: body,
|
||||
after: self.setupAfter
|
||||
after: this.setupAfter
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as it() but makes iit tests the only test to run.
|
||||
*
|
||||
* @param {string} name Name of the test.
|
||||
* @param {Function} body Body of the block.
|
||||
*/
|
||||
angular.scenario.Describe.prototype.iit = function(name, body) {
|
||||
this.it.apply(this, arguments);
|
||||
this.its[this.its.length-1].only = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Use to disable a test block.
|
||||
*/
|
||||
@@ -93,6 +121,15 @@ angular.scenario.Describe.prototype.xit = angular.noop;
|
||||
/**
|
||||
* Gets an array of functions representing all the tests (recursively).
|
||||
* that can be executed with SpecRunner's.
|
||||
*
|
||||
* @return {Array<Object>} Array of it blocks {
|
||||
* definition : Object // parent Describe
|
||||
* only: boolean
|
||||
* name: string
|
||||
* before: Function
|
||||
* body: Function
|
||||
* after: Function
|
||||
* }
|
||||
*/
|
||||
angular.scenario.Describe.prototype.getSpecs = function() {
|
||||
var specs = arguments[0] || [];
|
||||
@@ -102,5 +139,11 @@ angular.scenario.Describe.prototype.getSpecs = function() {
|
||||
angular.foreach(this.its, function(it) {
|
||||
specs.push(it);
|
||||
});
|
||||
return specs;
|
||||
var only = [];
|
||||
angular.foreach(specs, function(it) {
|
||||
if (it.only) {
|
||||
only.push(it);
|
||||
}
|
||||
});
|
||||
return (only.length && only) || specs;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* A future action in a spec.
|
||||
*
|
||||
* @param {String} name of the future action
|
||||
* @param {string} name of the future action
|
||||
* @param {Function} future callback(error, result)
|
||||
* @param {String} Optional. function that returns the file/line number.
|
||||
* @param {Function} Optional. function that returns the file/line number.
|
||||
*/
|
||||
angular.scenario.Future = function(name, behavior, line) {
|
||||
this.name = name;
|
||||
@@ -17,7 +17,7 @@ angular.scenario.Future = function(name, behavior, line) {
|
||||
/**
|
||||
* Executes the behavior of the closure.
|
||||
*
|
||||
* @param {Function} Callback function(error, result)
|
||||
* @param {Function} doneFn Callback function(error, result)
|
||||
*/
|
||||
angular.scenario.Future.prototype.execute = function(doneFn) {
|
||||
var self = this;
|
||||
@@ -37,6 +37,8 @@ angular.scenario.Future.prototype.execute = function(doneFn) {
|
||||
|
||||
/**
|
||||
* Configures the future to convert it's final with a function fn(value)
|
||||
*
|
||||
* @param {Function} fn function(value) that returns the parsed value
|
||||
*/
|
||||
angular.scenario.Future.prototype.parsedWith = function(fn) {
|
||||
this.parser = fn;
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
/**
|
||||
* 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>'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* The severity order of an error.
|
||||
*/
|
||||
angular.scenario.ui.Html.SEVERITY = ['pending', 'success', 'failure', 'error'];
|
||||
|
||||
/**
|
||||
* 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 self = this;
|
||||
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,
|
||||
function(status) {
|
||||
status = self.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 self = this;
|
||||
var path = [];
|
||||
var currentContext = this.context.find('#specs');
|
||||
var currentDefinition = definition;
|
||||
while (currentDefinition && currentDefinition.name) {
|
||||
path.unshift(currentDefinition);
|
||||
currentDefinition = currentDefinition.parent;
|
||||
}
|
||||
angular.foreach(path, function(defn) {
|
||||
var id = 'describe-' + defn.id;
|
||||
if (!self.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>'
|
||||
);
|
||||
self.context.find('#' + id).find('> h2').text('describe: ' + defn.name);
|
||||
}
|
||||
currentContext = self.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>' +
|
||||
'<div class="scrollpane">' +
|
||||
' <ol class="test-actions">' +
|
||||
' </ol>' +
|
||||
'</div>'
|
||||
);
|
||||
context.find('> .test-info').click(function() {
|
||||
var scrollpane = context.find('> .scrollpane');
|
||||
var actions = scrollpane.find('> .test-actions');
|
||||
var name = context.find('> .test-info .test-name');
|
||||
if (actions.find(':visible').length) {
|
||||
actions.hide();
|
||||
name.removeClass('open').addClass('closed');
|
||||
} else {
|
||||
actions.show();
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
name.removeClass('closed').addClass('open');
|
||||
}
|
||||
});
|
||||
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.
|
||||
* @param {Function} function() that returns a string with the file/line number
|
||||
* where the step was added from.
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec.prototype.addStep = function(name, location) {
|
||||
this.context.find('> .scrollpane .test-actions').append('<li class="status-pending"></li>');
|
||||
var stepContext = this.context.find('> .scrollpane .test-actions li:last');
|
||||
var self = this;
|
||||
return new angular.scenario.ui.Html.Step(stepContext, name, location, function(status) {
|
||||
if (indexOf(angular.scenario.ui.Html.SEVERITY, status) >
|
||||
indexOf(angular.scenario.ui.Html.SEVERITY, self.status)) {
|
||||
self.status = status;
|
||||
}
|
||||
var scrollpane = self.context.find('> .scrollpane');
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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");
|
||||
if (this.status === 'success') {
|
||||
this.context.find('> .test-info .test-name').addClass('closed');
|
||||
this.context.find('> .scrollpane .test-actions').hide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the spec, possibly with an error.
|
||||
*
|
||||
* @param {Object} An optional error
|
||||
*/
|
||||
angular.scenario.ui.Html.Spec.prototype.finish = function() {
|
||||
this.complete();
|
||||
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.status = 'error';
|
||||
this.context.append('<pre></pre>');
|
||||
this.context.find('> pre').text(formatException(error));
|
||||
this.finish();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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} function() that returns file/line number of step.
|
||||
* @param {Function} Callback function(status) to call when complete.
|
||||
*/
|
||||
angular.scenario.ui.Html.Step = function(context, name, location, doneFn) {
|
||||
this.context = context;
|
||||
this.name = name;
|
||||
this.location = location;
|
||||
this.startTime = new Date().getTime();
|
||||
this.doneFn = doneFn;
|
||||
context.append(
|
||||
'<div class="timer-result"></div>' +
|
||||
'<div class="test-title"></div>'
|
||||
);
|
||||
context.find('> .test-title').text(name);
|
||||
var scrollpane = context.parents('.scrollpane');
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Completes the step and sets the timer value.
|
||||
*/
|
||||
angular.scenario.ui.Html.Step.prototype.complete = function(error) {
|
||||
this.context.removeClass('status-pending');
|
||||
var endTime = new Date().getTime();
|
||||
this.context.find(".timer-result").
|
||||
text((endTime - this.startTime) + "ms");
|
||||
if (error) {
|
||||
if (!this.context.find('.test-title pre').length) {
|
||||
this.context.find('.test-title').append('<pre></pre>');
|
||||
}
|
||||
var message = _jQuery.trim(this.location() + '\n\n' + formatException(error));
|
||||
this.context.find('.test-title pre').text(message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the step, possibly with an error.
|
||||
*
|
||||
* @param {Object} An optional error
|
||||
*/
|
||||
angular.scenario.ui.Html.Step.prototype.finish = function(error) {
|
||||
this.complete(error);
|
||||
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(error);
|
||||
this.context.addClass('status-error');
|
||||
this.doneFn('error');
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Maintains an object tree from the runner events.
|
||||
*
|
||||
* @param {Object} runner The scenario Runner instance to connect to.
|
||||
*
|
||||
* TODO(esprehn): Every output type creates one of these, but we probably
|
||||
* want one glonal shared instance. Need to handle events better too
|
||||
* so the HTML output doesn't need to do spec model.getSpec(spec.id)
|
||||
* silliness.
|
||||
*/
|
||||
angular.scenario.ObjectModel = function(runner) {
|
||||
var self = this;
|
||||
|
||||
this.specMap = {};
|
||||
this.value = {
|
||||
name: '',
|
||||
children: {}
|
||||
};
|
||||
|
||||
runner.on('SpecBegin', function(spec) {
|
||||
var block = self.value;
|
||||
angular.foreach(self.getDefinitionPath(spec), function(def) {
|
||||
if (!block.children[def.name]) {
|
||||
block.children[def.name] = {
|
||||
id: def.id,
|
||||
name: def.name,
|
||||
children: {},
|
||||
specs: {}
|
||||
};
|
||||
}
|
||||
block = block.children[def.name];
|
||||
});
|
||||
self.specMap[spec.id] = block.specs[spec.name] =
|
||||
new angular.scenario.ObjectModel.Spec(spec.id, spec.name);
|
||||
});
|
||||
|
||||
runner.on('SpecError', function(spec, error) {
|
||||
var it = self.getSpec(spec.id);
|
||||
it.status = 'error';
|
||||
it.error = error;
|
||||
});
|
||||
|
||||
runner.on('SpecEnd', function(spec) {
|
||||
var it = self.getSpec(spec.id);
|
||||
complete(it);
|
||||
});
|
||||
|
||||
runner.on('StepBegin', function(spec, step) {
|
||||
var it = self.getSpec(spec.id);
|
||||
it.steps.push(new angular.scenario.ObjectModel.Step(step.name));
|
||||
});
|
||||
|
||||
runner.on('StepEnd', function(spec, step) {
|
||||
var it = self.getSpec(spec.id);
|
||||
if (it.getLastStep().name !== step.name)
|
||||
throw 'Events fired in the wrong order. Step names don\' match.';
|
||||
complete(it.getLastStep());
|
||||
});
|
||||
|
||||
runner.on('StepFailure', function(spec, step, error) {
|
||||
var it = self.getSpec(spec.id);
|
||||
var item = it.getLastStep();
|
||||
item.error = error;
|
||||
if (!it.status) {
|
||||
it.status = item.status = 'failure';
|
||||
}
|
||||
});
|
||||
|
||||
runner.on('StepError', function(spec, step, error) {
|
||||
var it = self.getSpec(spec.id);
|
||||
var item = it.getLastStep();
|
||||
it.status = 'error';
|
||||
item.status = 'error';
|
||||
item.error = error;
|
||||
});
|
||||
|
||||
function complete(item) {
|
||||
item.endTime = new Date().getTime();
|
||||
item.duration = item.endTime - item.startTime;
|
||||
item.status = item.status || 'success';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the path of definition describe blocks that wrap around
|
||||
* this spec.
|
||||
*
|
||||
* @param spec Spec to compute the path for.
|
||||
* @return {Array<Describe>} The describe block path
|
||||
*/
|
||||
angular.scenario.ObjectModel.prototype.getDefinitionPath = function(spec) {
|
||||
var path = [];
|
||||
var currentDefinition = spec.definition;
|
||||
while (currentDefinition && currentDefinition.name) {
|
||||
path.unshift(currentDefinition);
|
||||
currentDefinition = currentDefinition.parent;
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a spec by id.
|
||||
*
|
||||
* @param {string} The id of the spec to get the object for.
|
||||
* @return {Object} the Spec instance
|
||||
*/
|
||||
angular.scenario.ObjectModel.prototype.getSpec = function(id) {
|
||||
return this.specMap[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* A single it block.
|
||||
*
|
||||
* @param {string} id Id of the spec
|
||||
* @param {string} name Name of the spec
|
||||
*/
|
||||
angular.scenario.ObjectModel.Spec = function(id, name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.startTime = new Date().getTime();
|
||||
this.steps = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new step to the Spec.
|
||||
*
|
||||
* @param {string} step Name of the step (really name of the future)
|
||||
* @return {Object} the added step
|
||||
*/
|
||||
angular.scenario.ObjectModel.Spec.prototype.addStep = function(name) {
|
||||
var step = new angular.scenario.ObjectModel.Step(name);
|
||||
this.steps.push(step);
|
||||
return step;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the most recent step.
|
||||
*
|
||||
* @return {Object} the step
|
||||
*/
|
||||
angular.scenario.ObjectModel.Spec.prototype.getLastStep = function() {
|
||||
return this.steps[this.steps.length-1];
|
||||
};
|
||||
|
||||
/**
|
||||
* A single step inside a Spec.
|
||||
*
|
||||
* @param {string} step Name of the step
|
||||
*/
|
||||
angular.scenario.ObjectModel.Step = function(name) {
|
||||
this.name = name;
|
||||
this.startTime = new Date().getTime();
|
||||
};
|
||||
+107
-21
@@ -2,13 +2,16 @@
|
||||
* Runner for scenarios.
|
||||
*/
|
||||
angular.scenario.Runner = function($window) {
|
||||
this.listeners = [];
|
||||
this.$window = $window;
|
||||
this.rootDescribe = new angular.scenario.Describe();
|
||||
this.currentDescribe = this.rootDescribe;
|
||||
this.api = {
|
||||
it: this.it,
|
||||
iit: this.iit,
|
||||
xit: angular.noop,
|
||||
describe: this.describe,
|
||||
ddescribe: this.ddescribe,
|
||||
xdescribe: angular.noop,
|
||||
beforeEach: this.beforeEach,
|
||||
afterEach: this.afterEach
|
||||
@@ -18,11 +21,42 @@ angular.scenario.Runner = function($window) {
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits an event which notifies listeners and passes extra
|
||||
* arguments.
|
||||
*
|
||||
* @param {string} eventName Name of the event to fire.
|
||||
*/
|
||||
angular.scenario.Runner.prototype.emit = function(eventName) {
|
||||
var self = this;
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
eventName = eventName.toLowerCase();
|
||||
if (!this.listeners[eventName])
|
||||
return;
|
||||
angular.foreach(this.listeners[eventName], function(listener) {
|
||||
listener.apply(self, args);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a listener for an event.
|
||||
*
|
||||
* @param {string} eventName The name of the event to add a handler for
|
||||
* @param {string} listener The fn(...) that takes the extra arguments from emit()
|
||||
*/
|
||||
angular.scenario.Runner.prototype.on = function(eventName, listener) {
|
||||
eventName = eventName.toLowerCase();
|
||||
this.listeners[eventName] = this.listeners[eventName] || [];
|
||||
this.listeners[eventName].push(listener);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a describe block of a spec.
|
||||
*
|
||||
* @param {String} Name of the block
|
||||
* @param {Function} Body of the block
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {Function} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.describe = function(name, body) {
|
||||
var self = this;
|
||||
@@ -37,20 +71,57 @@ angular.scenario.Runner.prototype.describe = function(name, body) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as describe, but makes ddescribe the only blocks to run.
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {Function} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.ddescribe = function(name, body) {
|
||||
var self = this;
|
||||
this.currentDescribe.ddescribe(name, function() {
|
||||
var parentDescribe = self.currentDescribe;
|
||||
self.currentDescribe = this;
|
||||
try {
|
||||
body.call(this);
|
||||
} finally {
|
||||
self.currentDescribe = parentDescribe;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a test in a describe block of a spec.
|
||||
*
|
||||
* @param {String} Name of the block
|
||||
* @param {Function} Body of the block
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {Function} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.it = function(name, body) {
|
||||
this.currentDescribe.it(name, body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as it, but makes iit tests the only tests to run.
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {string} name Name of the block
|
||||
* @param {Function} body Body of the block
|
||||
*/
|
||||
angular.scenario.Runner.prototype.iit = function(name, body) {
|
||||
this.currentDescribe.iit(name, body);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a function to be called before each it block in the describe
|
||||
* (and before all nested describes).
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {Function} Callback to execute
|
||||
*/
|
||||
angular.scenario.Runner.prototype.beforeEach = function(body) {
|
||||
@@ -61,6 +132,8 @@ angular.scenario.Runner.prototype.beforeEach = function(body) {
|
||||
* Defines a function to be called after each it block in the describe
|
||||
* (and before all nested describes).
|
||||
*
|
||||
* @see Describe.js
|
||||
*
|
||||
* @param {Function} Callback to execute
|
||||
*/
|
||||
angular.scenario.Runner.prototype.afterEach = function(body) {
|
||||
@@ -68,24 +141,29 @@ angular.scenario.Runner.prototype.afterEach = function(body) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a function to be called before each it block in the describe
|
||||
* (and before all nested describes).
|
||||
* Creates a new spec runner.
|
||||
*
|
||||
* @param {Function} Callback to execute
|
||||
* @private
|
||||
* @param {Object} scope parent scope
|
||||
*/
|
||||
angular.scenario.Runner.prototype.run = function(ui, application, specRunnerClass, specsDone) {
|
||||
var $root = angular.scope({}, angular.service);
|
||||
angular.scenario.Runner.prototype.createSpecRunner_ = function(scope) {
|
||||
return scope.$new(angular.scenario.SpecRunner);
|
||||
};
|
||||
|
||||
/**
|
||||
* Runs all the loaded tests with the specified runner class on the
|
||||
* provided application.
|
||||
*
|
||||
* @param {angular.scenario.Application} application App to remote control.
|
||||
*/
|
||||
angular.scenario.Runner.prototype.run = function(application) {
|
||||
var self = this;
|
||||
var specs = this.rootDescribe.getSpecs();
|
||||
var $root = angular.scope(this);
|
||||
$root.application = application;
|
||||
$root.ui = ui;
|
||||
$root.setTimeout = function() {
|
||||
return self.$window.setTimeout.apply(self.$window, arguments);
|
||||
};
|
||||
asyncForEach(specs, function(spec, specDone) {
|
||||
this.emit('RunnerBegin');
|
||||
asyncForEach(this.rootDescribe.getSpecs(), function(spec, specDone) {
|
||||
var dslCache = {};
|
||||
var runner = angular.scope($root);
|
||||
runner.$become(specRunnerClass);
|
||||
var runner = self.createSpecRunner_($root);
|
||||
angular.foreach(angular.scenario.dsl, function(fn, key) {
|
||||
dslCache[key] = fn.call($root);
|
||||
});
|
||||
@@ -105,16 +183,24 @@ angular.scenario.Runner.prototype.run = function(ui, application, specRunnerClas
|
||||
// Make these methods work on the current chain
|
||||
scope.addFuture = function() {
|
||||
Array.prototype.push.call(arguments, line);
|
||||
return specRunnerClass.prototype.addFuture.apply(scope, arguments);
|
||||
return angular.scenario.SpecRunner.
|
||||
prototype.addFuture.apply(scope, arguments);
|
||||
};
|
||||
scope.addFutureAction = function() {
|
||||
Array.prototype.push.call(arguments, line);
|
||||
return specRunnerClass.prototype.addFutureAction.apply(scope, arguments);
|
||||
return angular.scenario.SpecRunner.
|
||||
prototype.addFutureAction.apply(scope, arguments);
|
||||
};
|
||||
|
||||
return scope.dsl[key].apply(scope, arguments);
|
||||
};
|
||||
});
|
||||
runner.run(ui, spec, specDone);
|
||||
}, specsDone || angular.noop);
|
||||
runner.run(spec, specDone);
|
||||
},
|
||||
function(error) {
|
||||
if (error) {
|
||||
self.emit('RunnerError', error);
|
||||
}
|
||||
self.emit('RunnerEnd');
|
||||
});
|
||||
};
|
||||
|
||||
+98
-25
@@ -6,8 +6,15 @@
|
||||
// Public namespace
|
||||
angular.scenario = angular.scenario || {};
|
||||
|
||||
// Namespace for the UI
|
||||
angular.scenario.ui = angular.scenario.ui || {};
|
||||
/**
|
||||
* Defines a new output format.
|
||||
*
|
||||
* @param {string} name the name of the new output format
|
||||
* @param {Function} fn function(context, runner) that generates the output
|
||||
*/
|
||||
angular.scenario.output = angular.scenario.output || function(name, fn) {
|
||||
angular.scenario.output[name] = fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a new DSL statement. If your factory function returns a Future
|
||||
@@ -18,8 +25,8 @@ angular.scenario.ui = angular.scenario.ui || {};
|
||||
* 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
|
||||
* @param {string} name The name of the statement
|
||||
* @param {Function} fn Factory function(), return a function for
|
||||
* the statement.
|
||||
*/
|
||||
angular.scenario.dsl = angular.scenario.dsl || function(name, fn) {
|
||||
@@ -54,8 +61,8 @@ angular.scenario.dsl = angular.scenario.dsl || function(name, fn) {
|
||||
* 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).
|
||||
* @param {string} name The name of the matcher
|
||||
* @param {Function} fn The matching function(expected).
|
||||
*/
|
||||
angular.scenario.matcher = angular.scenario.matcher || function(name, fn) {
|
||||
angular.scenario.matcher[name] = function(expected) {
|
||||
@@ -64,7 +71,7 @@ angular.scenario.matcher = angular.scenario.matcher || function(name, fn) {
|
||||
prefix += 'not ';
|
||||
}
|
||||
var self = this;
|
||||
this.addFuture(prefix + name + ' ' + angular.toJson(expected),
|
||||
this.addFuture(prefix + name + ' ' + angular.toJson(expected),
|
||||
function(done) {
|
||||
var error;
|
||||
self.actual = self.future.value;
|
||||
@@ -78,14 +85,56 @@ angular.scenario.matcher = angular.scenario.matcher || function(name, fn) {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialization function for the scenario runner.
|
||||
*
|
||||
* @param {angular.scenario.Runner} $scenario The runner to setup
|
||||
* @param {Object} config Config options
|
||||
*/
|
||||
function angularScenarioInit($scenario, config) {
|
||||
var body = _jQuery(document.body);
|
||||
var output = [];
|
||||
|
||||
if (config.scenario_output) {
|
||||
output = config.scenario_output.split(',');
|
||||
}
|
||||
|
||||
angular.foreach(angular.scenario.output, function(fn, name) {
|
||||
if (!output.length || indexOf(output,name) != -1) {
|
||||
var context = body.append('<div></div>').find('div:last');
|
||||
context.attr('id', name);
|
||||
fn.call({}, context, $scenario);
|
||||
}
|
||||
});
|
||||
|
||||
var appFrame = body.append('<div id="application"></div>').find('#application');
|
||||
var application = new angular.scenario.Application(appFrame);
|
||||
|
||||
$scenario.on('RunnerEnd', function() {
|
||||
appFrame.css('display', 'none');
|
||||
appFrame.find('iframe').attr('src', 'about:blank');
|
||||
});
|
||||
|
||||
$scenario.on('RunnerError', function(error) {
|
||||
if (window.console) {
|
||||
console.log(formatException(error));
|
||||
} else {
|
||||
// Do something for IE
|
||||
alert(error);
|
||||
}
|
||||
});
|
||||
|
||||
$scenario.run(application);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param {Array} list list to iterate over
|
||||
* @param {Function} iterator Callback function(value, continueFunction)
|
||||
* @param {Function} done Callback function(error, result) called when
|
||||
* iteration finishes or an error occurs.
|
||||
*/
|
||||
function asyncForEach(list, iterator, done) {
|
||||
var i = 0;
|
||||
@@ -110,8 +159,8 @@ function asyncForEach(list, iterator, done) {
|
||||
* Formats an exception into a string with the stack trace, but limits
|
||||
* to a specific line length.
|
||||
*
|
||||
* @param {Object} the exception to format, can be anything throwable
|
||||
* @param {Number} Optional. max lines of the stack trace to include
|
||||
* @param {Object} error The exception to format, can be anything throwable
|
||||
* @param {Number} maxStackLines Optional. max lines of the stack trace to include
|
||||
* default is 5.
|
||||
*/
|
||||
function formatException(error, maxStackLines) {
|
||||
@@ -129,18 +178,20 @@ function formatException(error, maxStackLines) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that gets the file name and line number from a
|
||||
* Returns a function that gets the file name and line number from a
|
||||
* location in the stack if available based on the call site.
|
||||
*
|
||||
* Note: this returns another function because accessing .stack is very
|
||||
* expensive in Chrome.
|
||||
*
|
||||
* @param {Number} offset Number of stack lines to skip
|
||||
*/
|
||||
function callerFile(offset) {
|
||||
var error = new Error();
|
||||
|
||||
|
||||
return function() {
|
||||
var line = (error.stack || '').split('\n')[offset];
|
||||
|
||||
|
||||
// Clean up the stack trace line
|
||||
if (line) {
|
||||
if (line.indexOf('@') !== -1) {
|
||||
@@ -151,7 +202,7 @@ function callerFile(offset) {
|
||||
line = line.substring(line.indexOf('(')+1).replace(')', '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return line || '';
|
||||
};
|
||||
}
|
||||
@@ -161,7 +212,7 @@ function callerFile(offset) {
|
||||
* not specified.
|
||||
*
|
||||
* @param {Object} Either a wrapped jQuery/jqLite node or a DOMElement
|
||||
* @param {String} Optional event type.
|
||||
* @param {string} Optional event type.
|
||||
*/
|
||||
function browserTrigger(element, type) {
|
||||
if (element && !element.nodeName) element = element[0];
|
||||
@@ -188,7 +239,22 @@ function browserTrigger(element, type) {
|
||||
type = 'change';
|
||||
}
|
||||
if (msie) {
|
||||
switch(element.type) {
|
||||
case 'radio':
|
||||
case 'checkbox':
|
||||
element.checked = !element.checked;
|
||||
break;
|
||||
}
|
||||
element.fireEvent('on' + type);
|
||||
if (lowercase(element.type) == 'submit') {
|
||||
while(element) {
|
||||
if (lowercase(element.nodeName) == 'form') {
|
||||
element.fireEvent('onsubmit');
|
||||
break;
|
||||
}
|
||||
element = element.parentNode;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var evnt = document.createEvent('MouseEvents');
|
||||
evnt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
|
||||
@@ -200,13 +266,20 @@ function browserTrigger(element, type) {
|
||||
* Don't use the jQuery trigger method since it works incorrectly.
|
||||
*
|
||||
* jQuery notifies listeners and then changes the state of a checkbox and
|
||||
* does not create a real browser event. A real click changes the state of
|
||||
* does not create a real browser event. A real click changes the state of
|
||||
* the checkbox and then notifies listeners.
|
||||
*
|
||||
*
|
||||
* To work around this we instead use our own handler that fires a real event.
|
||||
*/
|
||||
_jQuery.fn.trigger = function(type) {
|
||||
return this.each(function(index, node) {
|
||||
browserTrigger(node, type);
|
||||
});
|
||||
};
|
||||
(function(fn){
|
||||
var parentTrigger = fn.trigger;
|
||||
fn.trigger = function(type) {
|
||||
if (/(click|change|keyup)/.test(type)) {
|
||||
return this.each(function(index, node) {
|
||||
browserTrigger(node, type);
|
||||
});
|
||||
}
|
||||
return parentTrigger.apply(this, arguments);
|
||||
};
|
||||
})(_jQuery.fn);
|
||||
|
||||
|
||||
+35
-22
@@ -15,14 +15,16 @@ angular.scenario.SpecRunner = function() {
|
||||
* 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 {Object} spec A spec object
|
||||
* @param {Object} specDone 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) {
|
||||
angular.scenario.SpecRunner.prototype.run = function(spec, specDone) {
|
||||
var self = this;
|
||||
var specUI = ui.addSpec(spec);
|
||||
var count = 0;
|
||||
this.spec = spec;
|
||||
|
||||
this.emit('SpecBegin', spec);
|
||||
|
||||
try {
|
||||
spec.before.call(this);
|
||||
@@ -30,7 +32,8 @@ angular.scenario.SpecRunner.prototype.run = function(ui, spec, specDone) {
|
||||
this.afterIndex = this.futures.length;
|
||||
spec.after.call(this);
|
||||
} catch (e) {
|
||||
specUI.error(e);
|
||||
this.emit('SpecError', spec, e);
|
||||
this.emit('SpecEnd', spec);
|
||||
specDone();
|
||||
return;
|
||||
}
|
||||
@@ -42,32 +45,40 @@ angular.scenario.SpecRunner.prototype.run = function(ui, spec, specDone) {
|
||||
self.error = true;
|
||||
done(null, self.afterIndex);
|
||||
};
|
||||
|
||||
var spec = this;
|
||||
|
||||
asyncForEach(
|
||||
this.futures,
|
||||
function(future, futureDone) {
|
||||
var stepUI = specUI.addStep(future.name, future.line);
|
||||
self.step = future;
|
||||
self.emit('StepBegin', spec, future);
|
||||
try {
|
||||
future.execute(function(error) {
|
||||
stepUI.finish(error);
|
||||
if (error) {
|
||||
self.emit('StepFailure', spec, future, error);
|
||||
self.emit('StepEnd', spec, future);
|
||||
return handleError(error, futureDone);
|
||||
}
|
||||
spec.$window.setTimeout( function() { futureDone(); }, 0);
|
||||
self.emit('StepEnd', spec, future);
|
||||
if ((count++) % 10 === 0) {
|
||||
self.$window.setTimeout(function() { futureDone(); }, 0);
|
||||
} else {
|
||||
futureDone();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
stepUI.error(e);
|
||||
self.emit('StepError', spec, future, e);
|
||||
self.emit('StepEnd', spec, future);
|
||||
handleError(e, futureDone);
|
||||
}
|
||||
},
|
||||
function(e) {
|
||||
if (e) {
|
||||
specUI.error(e);
|
||||
} else {
|
||||
specUI.finish();
|
||||
self.emit('SpecError', spec, e);
|
||||
}
|
||||
specDone();
|
||||
self.emit('SpecEnd', spec);
|
||||
// Call done in a timeout so exceptions don't recursively
|
||||
// call this function
|
||||
self.$window.setTimeout(function() { specDone(); }, 0);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -77,9 +88,9 @@ angular.scenario.SpecRunner.prototype.run = function(ui, spec, specDone) {
|
||||
*
|
||||
* Note: Do not pass line manually. It happens automatically.
|
||||
*
|
||||
* @param {String} Name of the future
|
||||
* @param {Function} Behavior of the future
|
||||
* @param {Function} fn() that returns file/line number
|
||||
* @param {string} name Name of the future
|
||||
* @param {Function} behavior Behavior of the future
|
||||
* @param {Function} line fn() that returns file/line number
|
||||
*/
|
||||
angular.scenario.SpecRunner.prototype.addFuture = function(name, behavior, line) {
|
||||
var future = new angular.scenario.Future(name, angular.bind(this, behavior), line);
|
||||
@@ -92,14 +103,16 @@ angular.scenario.SpecRunner.prototype.addFuture = function(name, behavior, line)
|
||||
*
|
||||
* Note: Do not pass line manually. It happens automatically.
|
||||
*
|
||||
* @param {String} Name of the future
|
||||
* @param {Function} Behavior of the future
|
||||
* @param {Function} fn() that returns file/line number
|
||||
* @param {string} name Name of the future
|
||||
* @param {Function} behavior Behavior of the future
|
||||
* @param {Function} line fn() that returns file/line number
|
||||
*/
|
||||
angular.scenario.SpecRunner.prototype.addFutureAction = function(name, behavior, line) {
|
||||
var self = this;
|
||||
return this.addFuture(name, function(done) {
|
||||
this.application.executeAction(function($window, $document) {
|
||||
|
||||
//TODO(esprehn): Refactor this so it doesn't need to be in here.
|
||||
$document.elements = function(selector) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
if (self.selector) {
|
||||
|
||||
+12
-26
@@ -1,6 +1,6 @@
|
||||
(function(previousOnLoad){
|
||||
var prefix = (function(){
|
||||
var filename = /(.*\/)bootstrap.js(#(.*))?/;
|
||||
var filename = /(.*\/)angular-bootstrap.js(#(.*))?/;
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
for(var j = 0; j < scripts.length; j++) {
|
||||
var src = scripts[j].src;
|
||||
@@ -23,50 +23,36 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
angularScenarioInit($scenario, angularJsConfig(document));
|
||||
};
|
||||
|
||||
addCSS("../../css/angular-scenario.css");
|
||||
addScript("../../lib/jquery/jquery-1.4.2.js");
|
||||
document.write(
|
||||
'<script type="text/javascript">' +
|
||||
'var _jQuery = jQuery.noConflict(true);' +
|
||||
'</script>'
|
||||
);
|
||||
'<script type="text/javascript">' +
|
||||
'var _jQuery = jQuery.noConflict(true);' +
|
||||
'</script>'
|
||||
);
|
||||
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");
|
||||
addScript("ObjectModel.js");
|
||||
addScript("output/Html.js");
|
||||
addScript("output/Json.js");
|
||||
addScript("output/Object.js");
|
||||
addScript("output/Xml.js");
|
||||
|
||||
// Create the runner (which also sets up the global API)
|
||||
document.write(
|
||||
'<script type="text/javascript">' +
|
||||
'var $scenario = new angular.scenario.Runner(window);' +
|
||||
'var $scenario = new angular.scenario.Runner(window, angular.scenario.SpecRunner);' +
|
||||
'</script>'
|
||||
);
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
(function(window, document, previousOnLoad){
|
||||
var _jQuery = window.jQuery.noConflict(true);
|
||||
var _jQuery = window.jQuery.noConflict(true);
|
||||
|
||||
@@ -4,25 +4,7 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
angularScenarioInit($scenario, angularJsConfig(document));
|
||||
};
|
||||
|
||||
})(window, document, window.onload);
|
||||
|
||||
+35
-21
@@ -1,18 +1,19 @@
|
||||
/**
|
||||
* Shared DSL statements that are useful to all scenarios.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* wait() waits until you call resume() in the console
|
||||
*/
|
||||
angular.scenario.dsl('wait', function() {
|
||||
angular.scenario.dsl('wait', function() {
|
||||
return function() {
|
||||
return this.addFuture('waiting for you to call resume() in the console', function(done) {
|
||||
return this.addFuture('waiting for you to resume', function(done) {
|
||||
this.emit('InteractiveWait', this.spec, this.step);
|
||||
this.$window.resume = function() { done(); };
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
@@ -21,7 +22,7 @@
|
||||
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);
|
||||
this.$window.setTimeout(function() { done(null, time * 1000); }, time * 1000);
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -49,8 +50,8 @@ angular.scenario.dsl('expect', function() {
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* navigateTo(future|string) where url a string or future with a value
|
||||
* of a URL to navigate to
|
||||
* navigateTo(url) Loads the url into the frame
|
||||
* navigateTo(url, fn) where fn(url) is called and returns the URL to navigate to
|
||||
*/
|
||||
angular.scenario.dsl('navigateTo', function() {
|
||||
return function(url, delegate) {
|
||||
@@ -60,17 +61,7 @@ angular.scenario.dsl('navigateTo', function() {
|
||||
url = delegate.call(this, url);
|
||||
}
|
||||
application.navigateTo(url, function() {
|
||||
application.executeAction(function($window) {
|
||||
if ($window.angular) {
|
||||
var $browser = $window.angular.service.$browser();
|
||||
$browser.poll();
|
||||
$browser.notifyWhenNoOutstandingRequests(function() {
|
||||
done(null, url);
|
||||
});
|
||||
} else {
|
||||
done(null, url);
|
||||
}
|
||||
});
|
||||
done(null, url);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -162,7 +153,11 @@ angular.scenario.dsl('repeater', function() {
|
||||
|
||||
chain.count = function() {
|
||||
return this.addFutureAction('repeater ' + this.selector + ' count', function($window, $document, done) {
|
||||
done(null, $document.elements().size());
|
||||
try {
|
||||
done(null, $document.elements().length);
|
||||
} catch (e) {
|
||||
done(null, 0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -238,6 +233,7 @@ angular.scenario.dsl('select', function() {
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* element(selector).count() get the number of elements that match selector
|
||||
* element(selector).click() clicks an element
|
||||
* element(selector).attr(name) gets the value of an attribute
|
||||
* element(selector).attr(name, value) sets the value of an attribute
|
||||
@@ -248,10 +244,28 @@ angular.scenario.dsl('select', function() {
|
||||
angular.scenario.dsl('element', function() {
|
||||
var chain = {};
|
||||
|
||||
chain.count = function() {
|
||||
return this.addFutureAction('element ' + this.selector + ' count', function($window, $document, done) {
|
||||
try {
|
||||
done(null, $document.elements().length);
|
||||
} catch (e) {
|
||||
done(null, 0);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
chain.click = function() {
|
||||
return this.addFutureAction('element ' + this.selector + ' click', function($window, $document, done) {
|
||||
$document.elements().trigger('click');
|
||||
done();
|
||||
var elements = $document.elements();
|
||||
var href = elements.attr('href');
|
||||
elements.trigger('click');
|
||||
if (href && elements[0].nodeName.toUpperCase() === 'A') {
|
||||
this.application.navigateTo(href, function() {
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ angular.scenario.matcher('toEqual', function(expected) {
|
||||
return angular.equals(this.actual, expected);
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toBe', function(expected) {
|
||||
return this.actual === expected;
|
||||
});
|
||||
|
||||
angular.scenario.matcher('toBeDefined', function() {
|
||||
return angular.isDefined(this.actual);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* User Interface for the Scenario Runner.
|
||||
*
|
||||
* TODO(esprehn): This should be refactored now that ObjectModel exists
|
||||
* to use angular bindings for the UI.
|
||||
*/
|
||||
angular.scenario.output('html', function(context, runner) {
|
||||
var model = new angular.scenario.ObjectModel(runner);
|
||||
|
||||
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>'
|
||||
);
|
||||
|
||||
runner.on('InteractiveWait', function(spec, step) {
|
||||
var ui = model.getSpec(spec.id).getLastStep().ui;
|
||||
ui.find('.test-title').
|
||||
html('waiting for you to <a href="javascript:resume()">resume</a>.');
|
||||
});
|
||||
|
||||
runner.on('SpecBegin', function(spec) {
|
||||
var ui = findContext(spec);
|
||||
ui.find('> .tests').append(
|
||||
'<li class="status-pending test-it"></li>'
|
||||
);
|
||||
ui = ui.find('> .tests li:last');
|
||||
ui.append(
|
||||
'<div class="test-info">' +
|
||||
' <p class="test-title">' +
|
||||
' <span class="timer-result"></span>' +
|
||||
' <span class="test-name"></span>' +
|
||||
' </p>' +
|
||||
'</div>' +
|
||||
'<div class="scrollpane">' +
|
||||
' <ol class="test-actions"></ol>' +
|
||||
'</div>'
|
||||
);
|
||||
ui.find('> .test-info .test-name').text(spec.name);
|
||||
ui.find('> .test-info').click(function() {
|
||||
var scrollpane = ui.find('> .scrollpane');
|
||||
var actions = scrollpane.find('> .test-actions');
|
||||
var name = context.find('> .test-info .test-name');
|
||||
if (actions.find(':visible').length) {
|
||||
actions.hide();
|
||||
name.removeClass('open').addClass('closed');
|
||||
} else {
|
||||
actions.show();
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
name.removeClass('closed').addClass('open');
|
||||
}
|
||||
});
|
||||
model.getSpec(spec.id).ui = ui;
|
||||
});
|
||||
|
||||
runner.on('SpecError', function(spec, error) {
|
||||
var ui = model.getSpec(spec.id).ui;
|
||||
ui.append('<pre></pre>');
|
||||
ui.find('> pre').text(formatException(error));
|
||||
});
|
||||
|
||||
runner.on('SpecEnd', function(spec) {
|
||||
spec = model.getSpec(spec.id);
|
||||
spec.ui.removeClass('status-pending');
|
||||
spec.ui.addClass('status-' + spec.status);
|
||||
spec.ui.find("> .test-info .timer-result").text(spec.duration + "ms");
|
||||
if (spec.status === 'success') {
|
||||
spec.ui.find('> .test-info .test-name').addClass('closed');
|
||||
spec.ui.find('> .scrollpane .test-actions').hide();
|
||||
}
|
||||
updateTotals(spec.status);
|
||||
});
|
||||
|
||||
runner.on('StepBegin', function(spec, step) {
|
||||
spec = model.getSpec(spec.id);
|
||||
step = spec.getLastStep();
|
||||
spec.ui.find('> .scrollpane .test-actions').
|
||||
append('<li class="status-pending"></li>');
|
||||
step.ui = spec.ui.find('> .scrollpane .test-actions li:last');
|
||||
step.ui.append(
|
||||
'<div class="timer-result"></div>' +
|
||||
'<div class="test-title"></div>'
|
||||
);
|
||||
step.ui.find('> .test-title').text(step.name);
|
||||
var scrollpane = step.ui.parents('.scrollpane');
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
});
|
||||
|
||||
runner.on('StepFailure', function(spec, step, error) {
|
||||
var ui = model.getSpec(spec.id).getLastStep().ui;
|
||||
addError(ui, step.line, error);
|
||||
});
|
||||
|
||||
runner.on('StepError', function(spec, step, error) {
|
||||
var ui = model.getSpec(spec.id).getLastStep().ui;
|
||||
addError(ui, step.line, error);
|
||||
});
|
||||
|
||||
runner.on('StepEnd', function(spec, step) {
|
||||
spec = model.getSpec(spec.id);
|
||||
step = spec.getLastStep();
|
||||
step.ui.find('.timer-result').text(step.duration + 'ms');
|
||||
step.ui.removeClass('status-pending');
|
||||
step.ui.addClass('status-' + step.status);
|
||||
var scrollpane = spec.ui.find('> .scrollpane');
|
||||
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds the context of a spec block defined by the passed definition.
|
||||
*
|
||||
* @param {Object} The definition created by the Describe object.
|
||||
*/
|
||||
function findContext(spec) {
|
||||
var currentContext = context.find('#specs');
|
||||
angular.foreach(model.getDefinitionPath(spec), function(defn) {
|
||||
var id = 'describe-' + defn.id;
|
||||
if (!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>'
|
||||
);
|
||||
context.find('#' + id).find('> h2').text('describe: ' + defn.name);
|
||||
}
|
||||
currentContext = context.find('#' + id);
|
||||
});
|
||||
return context.find('#describe-' + spec.definition.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the test counter for the status.
|
||||
*
|
||||
* @param {string} the status.
|
||||
*/
|
||||
function updateTotals(status) {
|
||||
var legend = context.find('#status-legend .status-' + status);
|
||||
var parts = legend.text().split(' ');
|
||||
var value = (parts[0] * 1) + 1;
|
||||
legend.text(value + ' ' + parts[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an error to a step.
|
||||
*
|
||||
* @param {Object} The JQuery wrapped context
|
||||
* @param {Function} fn() that should return the file/line number of the error
|
||||
* @param {Object} the error.
|
||||
*/
|
||||
function addError(context, line, error) {
|
||||
context.find('.test-title').append('<pre></pre>');
|
||||
var message = _jQuery.trim(line() + '\n\n' + formatException(error));
|
||||
context.find('.test-title pre:last').text(message);
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Generates JSON output into a context.
|
||||
*/
|
||||
angular.scenario.output('json', function(context, runner) {
|
||||
var model = new angular.scenario.ObjectModel(runner);
|
||||
|
||||
runner.on('RunnerEnd', function() {
|
||||
context.text(angular.toJson(model.value));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Creates a global value $result with the result of the runner.
|
||||
*/
|
||||
angular.scenario.output('object', function(context, runner) {
|
||||
runner.$window.$result = new angular.scenario.ObjectModel(runner).value;
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Generates XML output into a context.
|
||||
*/
|
||||
angular.scenario.output('xml', function(context, runner) {
|
||||
var model = new angular.scenario.ObjectModel(runner);
|
||||
var $ = function(args) {return new context.init(args);};
|
||||
runner.on('RunnerEnd', function() {
|
||||
var scenario = $('<scenario></scenario>');
|
||||
context.append(scenario);
|
||||
serializeXml(scenario, model.value);
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert the tree into XML.
|
||||
*
|
||||
* @param {Object} context jQuery context to add the XML to.
|
||||
* @param {Object} tree node to serialize
|
||||
*/
|
||||
function serializeXml(context, tree) {
|
||||
angular.foreach(tree.children, function(child) {
|
||||
var describeContext = $('<describe></describe>');
|
||||
describeContext.attr('id', child.id);
|
||||
describeContext.attr('name', child.name);
|
||||
context.append(describeContext);
|
||||
serializeXml(describeContext, child);
|
||||
});
|
||||
var its = $('<its></its>');
|
||||
context.append(its);
|
||||
angular.foreach(tree.specs, function(spec) {
|
||||
var it = $('<it></it>');
|
||||
it.attr('id', spec.id);
|
||||
it.attr('name', spec.name);
|
||||
it.attr('duration', spec.duration);
|
||||
it.attr('status', spec.status);
|
||||
its.append(it);
|
||||
angular.foreach(spec.steps, function(step) {
|
||||
var stepContext = $('<step></step>');
|
||||
stepContext.attr('name', step.name);
|
||||
stepContext.attr('duration', step.duration);
|
||||
stepContext.attr('status', step.status);
|
||||
it.append(stepContext);
|
||||
if (step.error) {
|
||||
var error = $('<error></error');
|
||||
stepContext.append(error);
|
||||
error.text(formatException(stepContext.error));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
+1
-1
@@ -641,4 +641,4 @@ angularServiceInject('$cookieStore', function($store) {
|
||||
}
|
||||
};
|
||||
|
||||
}, ['$cookies'], EAGER_PUBLISHED);
|
||||
}, ['$cookies']);
|
||||
|
||||
+9
-5
@@ -203,10 +203,6 @@ function inputWidget(events, modelAccessor, viewAccessor, initFn) {
|
||||
lastValue = model.get();
|
||||
scope.$tryEval(action, element);
|
||||
scope.$root.$eval();
|
||||
// if we have noop initFn than we are just a button,
|
||||
// therefore we want to prevent default action
|
||||
if(initFn == noop)
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
function updateView(){
|
||||
@@ -256,11 +252,19 @@ angularWidget('ng:include', function(element){
|
||||
return extend(function(xhr, element){
|
||||
var scope = this, childScope;
|
||||
var changeCounter = 0;
|
||||
var preventRecursion = false;
|
||||
function incrementChange(){ changeCounter++;}
|
||||
this.$watch(srcExp, incrementChange);
|
||||
this.$watch(scopeExp, incrementChange);
|
||||
scope.$onEval(function(){
|
||||
if (childScope) childScope.$eval();
|
||||
if (childScope && !preventRecursion) {
|
||||
preventRecursion = true;
|
||||
try {
|
||||
childScope.$eval();
|
||||
} finally {
|
||||
preventRecursion = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.$watch(function(){return changeCounter;}, function(){
|
||||
var src = this.$eval(srcExp),
|
||||
|
||||
+31
-1
@@ -13,6 +13,15 @@ describe('Angular', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('case', function(){
|
||||
it('should change case', function(){
|
||||
expect(lowercase('ABC90')).toEqual('abc90');
|
||||
expect(manualLowercase('ABC90')).toEqual('abc90');
|
||||
expect(uppercase('abc90')).toEqual('ABC90');
|
||||
expect(manualUppercase('abc90')).toEqual('ABC90');
|
||||
});
|
||||
});
|
||||
|
||||
describe("copy", function(){
|
||||
it("should return same object", function (){
|
||||
var obj = {};
|
||||
@@ -115,7 +124,7 @@ describe('toKeyValue', function() {
|
||||
toEqual('escaped%20key=escaped%20value');
|
||||
expect(toKeyValue({emptyKey: ''})).toEqual('emptyKey=');
|
||||
});
|
||||
|
||||
|
||||
it('should parse true values into flags', function() {
|
||||
expect(toKeyValue({flag1: true, key: 'value', flag2: true})).toEqual('flag1&key=value&flag2');
|
||||
});
|
||||
@@ -187,6 +196,27 @@ describe ('rngScript', function() {
|
||||
expect('my-angular-app-0.9.0-de0a8612.min.js'.match(rngScript)).toBeNull();
|
||||
expect('foo/../my-angular-app-0.9.0-de0a8612.min.js'.match(rngScript)).toBeNull();
|
||||
});
|
||||
|
||||
it('should match angular-scenario.js', function() {
|
||||
expect('angular-scenario.js'.match(rngScript)).not.toBeNull();
|
||||
expect('angular-scenario.min.js'.match(rngScript)).not.toBeNull();
|
||||
expect('../angular-scenario.js'.match(rngScript)).not.toBeNull();
|
||||
expect('foo/angular-scenario.min.js'.match(rngScript)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should match angular-scenario-0.9.0(.min).js', function() {
|
||||
expect('angular-scenario-0.9.0.js'.match(rngScript)).not.toBeNull();
|
||||
expect('angular-scenario-0.9.0.min.js'.match(rngScript)).not.toBeNull();
|
||||
expect('../angular-scenario-0.9.0.js'.match(rngScript)).not.toBeNull();
|
||||
expect('foo/angular-scenario-0.9.0.min.js'.match(rngScript)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should match angular-scenario-0.9.0-de0a8612(.min).js', function() {
|
||||
expect('angular-scenario-0.9.0-de0a8612.js'.match(rngScript)).not.toBeNull();
|
||||
expect('angular-scenario-0.9.0-de0a8612.min.js'.match(rngScript)).not.toBeNull();
|
||||
expect('../angular-scenario-0.9.0-de0a8612.js'.match(rngScript)).not.toBeNull();
|
||||
expect('foo/angular-scenario-0.9.0-de0a8612.min.js'.match(rngScript)).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -189,6 +189,10 @@ describe('api', function(){
|
||||
assertEquals(date.getTime(), angular.String.toDate(angular.Date.toString(date)).getTime());
|
||||
});
|
||||
|
||||
it('UTCtoDate', function(){
|
||||
expect(angular.String.toDate("2003-09-10T13:02:03Z")).toEqual(new Date("Sep 10 2003 13:02:03 GMT"));
|
||||
});
|
||||
|
||||
it('StringFromUTC', function(){
|
||||
var date = angular.String.toDate("2003-09-10T13:02:03Z");
|
||||
assertEquals("date", angular.Object.typeOf(date));
|
||||
|
||||
+19
-23
@@ -87,23 +87,15 @@ describe('filter', function(){
|
||||
});
|
||||
|
||||
describe('date', function(){
|
||||
var morning = angular.String.toDate('2010-09-03T23:05:08Z');
|
||||
var midnight = angular.String.toDate('2010-09-03T23:05:08Z');
|
||||
var noon = angular.String.toDate('2010-09-03T23:05:08Z');
|
||||
morning.setHours(7);
|
||||
noon.setHours(12);
|
||||
midnight.setHours(0);
|
||||
|
||||
//butt-ugly hack: force the date to be 2pm PDT for locale testing
|
||||
morning.getTimezoneOffset =
|
||||
noon.getTimezoneOffset =
|
||||
midnight.getTimezoneOffset =
|
||||
function() { return 7 * 60; };
|
||||
var morning = new TzDate(+5, '2010-09-03T12:05:08Z'); //7am
|
||||
var noon = new TzDate(+5, '2010-09-03T17:05:08Z'); //12pm
|
||||
var midnight = new TzDate(+5, '2010-09-03T05:05:08Z'); //12am
|
||||
|
||||
|
||||
it('should ignore falsy inputs', function() {
|
||||
expect(filter.date(null)).toEqual(null);
|
||||
expect(filter.date('')).toEqual('');
|
||||
expect(filter.date(123)).toEqual(123);
|
||||
});
|
||||
|
||||
it('should do basic filter', function() {
|
||||
@@ -111,19 +103,23 @@ describe('filter', function(){
|
||||
expect(filter.date(noon, '')).toEqual(noon.toLocaleDateString());
|
||||
});
|
||||
|
||||
it('should accept format', function() {
|
||||
expect(filter.date(midnight, "yyyy-M-d h=H:m:saZ")).
|
||||
toEqual('2010-9-3 12=0:5:8am0700');
|
||||
|
||||
expect(filter.date(midnight, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=00:05:08am0700');
|
||||
|
||||
expect(filter.date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=12:05:08pm0700');
|
||||
|
||||
it('should accept number or number string representing milliseconds as input', function() {
|
||||
expect(filter.date(noon.getTime())).toEqual(noon.toLocaleDateString());
|
||||
expect(filter.date(noon.getTime() + "")).toEqual(noon.toLocaleDateString());
|
||||
});
|
||||
|
||||
it('should accept format', function() {
|
||||
expect(filter.date(morning, "yy-MM-dd HH:mm:ss")).
|
||||
toEqual('10-09-03 07:05:08');
|
||||
|
||||
expect(filter.date(midnight, "yyyy-M-d h=H:m:saZ")).
|
||||
toEqual('2010-9-3 12=0:5:8am0500');
|
||||
|
||||
expect(filter.date(midnight, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=00:05:08am0500');
|
||||
|
||||
expect(filter.date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")).
|
||||
toEqual('2010-09-03 12=12:05:08pm0500');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Vendored
+102
@@ -162,3 +162,105 @@ MockBrowser.prototype = {
|
||||
angular.service('$browser', function(){
|
||||
return new MockBrowser();
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Mock of the Date type which has its timezone specified via constroctor arg.
|
||||
*
|
||||
* The main purpose is to create Date-like instances with timezone fixed to the specified timezone
|
||||
* offset, so that we can test code that depends on local timezone settings without dependency on
|
||||
* the time zone settings of the machine where the code is running.
|
||||
*
|
||||
* @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
|
||||
* @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
|
||||
*
|
||||
* @example
|
||||
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
|
||||
* newYearInBratislava.getTimezoneOffset() => -60;
|
||||
* newYearInBratislava.getFullYear() => 2010;
|
||||
* newYearInBratislava.getMonth() => 0;
|
||||
* newYearInBratislava.getDate() => 1;
|
||||
* newYearInBratislava.getHours() => 0;
|
||||
* newYearInBratislava.getMinutes() => 0;
|
||||
*
|
||||
*
|
||||
* !!!! WARNING !!!!!
|
||||
* This is not a complete Date object so only methods that were implemented can be called safely.
|
||||
* To make matters worse, TzDate instances inherit stuff from Date via a prototype.
|
||||
*
|
||||
* We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
|
||||
* incomplete we might be missing some non-standard methods. This can result in errors like:
|
||||
* "Date.prototype.foo called on incompatible Object".
|
||||
*/
|
||||
function TzDate(offset, timestamp) {
|
||||
if (angular.isString(timestamp)) {
|
||||
var tsStr = timestamp;
|
||||
timestamp = angular.String.toDate(timestamp).getTime();
|
||||
if (isNaN(timestamp))
|
||||
throw {
|
||||
name: "Illegal Argument",
|
||||
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
|
||||
};
|
||||
}
|
||||
|
||||
var localOffset = new Date(timestamp).getTimezoneOffset();
|
||||
this.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
|
||||
this.date = new Date(timestamp + this.offsetDiff);
|
||||
|
||||
this.getTime = function() {
|
||||
return this.date.getTime() - this.offsetDiff;
|
||||
};
|
||||
|
||||
this.toLocaleDateString = function() {
|
||||
return this.date.toLocaleDateString();
|
||||
};
|
||||
|
||||
this.getFullYear = function() {
|
||||
return this.date.getFullYear();
|
||||
};
|
||||
|
||||
this.getMonth = function() {
|
||||
return this.date.getMonth();
|
||||
};
|
||||
|
||||
this.getDate = function() {
|
||||
return this.date.getDate();
|
||||
};
|
||||
|
||||
this.getHours = function() {
|
||||
return this.date.getHours();
|
||||
};
|
||||
|
||||
this.getMinutes = function() {
|
||||
return this.date.getMinutes();
|
||||
};
|
||||
|
||||
this.getSeconds = function() {
|
||||
return this.date.getSeconds();
|
||||
};
|
||||
|
||||
this.getTimezoneOffset = function() {
|
||||
return offset * 60;
|
||||
};
|
||||
|
||||
//hide all methods not implemented in this mock that the Date prototype exposes
|
||||
var unimplementedMethods = ['getDay', 'getMilliseconds', 'getTime', 'getUTCDate', 'getUTCDay',
|
||||
'getUTCFullYear', 'getUTCHours', 'getUTCMilliseconds', 'getUTCMinutes', 'getUTCMonth',
|
||||
'getUTCSeconds', 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
|
||||
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
|
||||
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
|
||||
'setYear', 'toDateString', 'toJSON', 'toGMTString', 'toLocaleFormat', 'toLocaleString',
|
||||
'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
|
||||
|
||||
angular.foreach(unimplementedMethods, function(methodName) {
|
||||
this[methodName] = function() {
|
||||
throw {
|
||||
name: "MethodNotImplemented",
|
||||
message: "Method '" + methodName + "' is not implemented in the TzDate mock"
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
//make "tzDateInstance instanceof Date" return true
|
||||
TzDate.prototype = Date.prototype;
|
||||
|
||||
Vendored
+99
@@ -0,0 +1,99 @@
|
||||
describe('TzDate', function() {
|
||||
|
||||
function minutes(min) {
|
||||
return min*60*1000;
|
||||
}
|
||||
|
||||
it('should take millis as constructor argument', function() {
|
||||
expect(new TzDate(0, 0).getTime()).toBe(0);
|
||||
expect(new TzDate(0, 1283555108000).getTime()).toBe(1283555108000);
|
||||
});
|
||||
|
||||
it('should take dateString as constructor argument', function() {
|
||||
expect(new TzDate(0, '1970-01-01T00:00:00Z').getTime()).toBe(0);
|
||||
expect(new TzDate(0, '2010-09-03T23:05:08Z').getTime()).toBe(1283555108000);
|
||||
});
|
||||
|
||||
|
||||
it('should fake getLocalDateString method', function() {
|
||||
//0 in -3h
|
||||
var t0 = new TzDate(-3, 0);
|
||||
expect(t0.toLocaleDateString()).toMatch('1970');
|
||||
|
||||
//0 in +0h
|
||||
var t1 = new TzDate(0, 0);
|
||||
expect(t1.toLocaleDateString()).toMatch('1970');
|
||||
|
||||
//0 in +3h
|
||||
var t2 = new TzDate(3, 0);
|
||||
expect(t2.toLocaleDateString()).toMatch('1969');
|
||||
});
|
||||
|
||||
|
||||
it('should fake getHours method', function() {
|
||||
//0 in -3h
|
||||
var t0 = new TzDate(-3, 0);
|
||||
expect(t0.getHours()).toBe(3);
|
||||
|
||||
//0 in +0h
|
||||
var t1 = new TzDate(0, 0);
|
||||
expect(t1.getHours()).toBe(0);
|
||||
|
||||
//0 in +3h
|
||||
var t2 = new TzDate(3, 0);
|
||||
expect(t2.getHours()).toMatch(21);
|
||||
});
|
||||
|
||||
|
||||
it('should fake getMinutes method', function() {
|
||||
//0:15 in -3h
|
||||
var t0 = new TzDate(-3, minutes(15));
|
||||
expect(t0.getMinutes()).toBe(15);
|
||||
|
||||
//0:15 in -3.25h
|
||||
var t0a = new TzDate(-3.25, minutes(15));
|
||||
expect(t0a.getMinutes()).toBe(30);
|
||||
|
||||
//0 in +0h
|
||||
var t1 = new TzDate(0, minutes(0));
|
||||
expect(t1.getMinutes()).toBe(0);
|
||||
|
||||
//0:15 in +0h
|
||||
var t1a = new TzDate(0, minutes(15));
|
||||
expect(t1a.getMinutes()).toBe(15);
|
||||
|
||||
//0:15 in +3h
|
||||
var t2 = new TzDate(3, minutes(15));
|
||||
expect(t2.getMinutes()).toMatch(15);
|
||||
|
||||
//0:15 in +3.25h
|
||||
var t2a = new TzDate(3.25, minutes(15));
|
||||
expect(t2a.getMinutes()).toMatch(0);
|
||||
});
|
||||
|
||||
|
||||
it('should fake getSeconds method', function() {
|
||||
//0 in -3h
|
||||
var t0 = new TzDate(-3, 0);
|
||||
expect(t0.getSeconds()).toBe(0);
|
||||
|
||||
//0 in +0h
|
||||
var t1 = new TzDate(0, 0);
|
||||
expect(t1.getSeconds()).toBe(0);
|
||||
|
||||
//0 in +3h
|
||||
var t2 = new TzDate(3, 0);
|
||||
expect(t2.getSeconds()).toMatch(0);
|
||||
});
|
||||
|
||||
|
||||
it('should create a date representing new year in Bratislava', function() {
|
||||
var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
|
||||
expect(newYearInBratislava.getTimezoneOffset()).toBe(-60);
|
||||
expect(newYearInBratislava.getFullYear()).toBe(2010);
|
||||
expect(newYearInBratislava.getMonth()).toBe(0);
|
||||
expect(newYearInBratislava.getDate()).toBe(1);
|
||||
expect(newYearInBratislava.getHours()).toBe(0);
|
||||
expect(newYearInBratislava.getMinutes()).toBe(0);
|
||||
});
|
||||
});
|
||||
+28
-7
@@ -50,11 +50,18 @@ describe("directives", function(){
|
||||
|
||||
it('should set html', function() {
|
||||
var scope = compile('<div ng:bind="html|html"></div>');
|
||||
scope.html = '<div>hello</div>';
|
||||
scope.html = '<div unknown>hello</div>';
|
||||
scope.$eval();
|
||||
expect(lowercase(element.html())).toEqual('<div>hello</div>');
|
||||
});
|
||||
|
||||
it('should set unsafe html', function() {
|
||||
var scope = compile('<div ng:bind="html|html:\'unsafe\'"></div>');
|
||||
scope.html = '<div onclick="">hello</div>';
|
||||
scope.$eval();
|
||||
expect(lowercase(element.html())).toEqual('<div onclick="">hello</div>');
|
||||
});
|
||||
|
||||
it('should set element element', function() {
|
||||
angularFilter.myElement = function() {
|
||||
return jqLite('<a>hello</a>');
|
||||
@@ -172,7 +179,7 @@ describe("directives", function(){
|
||||
});
|
||||
|
||||
describe('ng:click', function(){
|
||||
it('should fire event', function(){
|
||||
it('should get called on a click', function(){
|
||||
var scope = compile('<div ng:click="clicked = true"></div>');
|
||||
scope.$eval();
|
||||
expect(scope.$get('clicked')).toBeFalsy();
|
||||
@@ -184,14 +191,28 @@ describe("directives", function(){
|
||||
it('should stop event propagation', function() {
|
||||
var scope = compile('<div ng:click="outer = true"><div ng:click="inner = true"></div></div>');
|
||||
scope.$eval();
|
||||
expect(scope.$get('outer')).not.toBeDefined();
|
||||
expect(scope.$get('inner')).not.toBeDefined();
|
||||
expect(scope.outer).not.toBeDefined();
|
||||
expect(scope.inner).not.toBeDefined();
|
||||
|
||||
var innerDiv = jqLite(element.children()[0]);
|
||||
var innerDiv = element.children()[0];
|
||||
|
||||
browserTrigger(innerDiv, 'click');
|
||||
expect(scope.$get('outer')).not.toBeDefined();
|
||||
expect(scope.$get('inner')).toEqual(true);
|
||||
expect(scope.outer).not.toBeDefined();
|
||||
expect(scope.inner).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('ng:submit', function() {
|
||||
it('should get called on form submit', function() {
|
||||
var scope = compile('<form action="" ng:submit="submitted = true">' +
|
||||
'<input type="submit"/>' +
|
||||
'</form>');
|
||||
scope.$eval();
|
||||
expect(scope.submitted).not.toBeDefined();
|
||||
|
||||
browserTrigger(element.children()[0]);
|
||||
expect(scope.submitted).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Jasmine Test Runner</title>
|
||||
<link rel="stylesheet" type="text/css" href="../lib/jasmine-1.0.1/jasmine.css">
|
||||
<script type="text/javascript" src="../lib/jasmine-1.0.1/jasmine.js"></script>
|
||||
<script type="text/javascript" src="../lib/jasmine-1.0.1/jasmine-html.js"></script>
|
||||
|
||||
|
||||
<!-- include source files here... -->
|
||||
<script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript"> var _jQuery = $;</script>
|
||||
|
||||
<script type="text/javascript" src="../src/Angular.js"></script>
|
||||
<script type="text/javascript" src="../src/JSON.js"></script>
|
||||
<script type="text/javascript" src="../src/Compiler.js"></script>
|
||||
<script type="text/javascript" src="../src/Scope.js"></script>
|
||||
<script type="text/javascript" src="../src/Injector.js"></script>
|
||||
<script type="text/javascript" src="../src/jqLite.js"></script>
|
||||
<script type="text/javascript" src="../src/parser.js"></script>
|
||||
<script type="text/javascript" src="../src/Resource.js"></script>
|
||||
<script type="text/javascript" src="../src/Browser.js"></script>
|
||||
<script type="text/javascript" src="../src/AngularPublic.js"></script>
|
||||
<script type="text/javascript" src="../src/services.js"></script>
|
||||
<script type="text/javascript" src="../src/apis.js"></script>
|
||||
<script type="text/javascript" src="../src/filters.js"></script>
|
||||
<script type="text/javascript" src="../src/formatters.js"></script>
|
||||
<script type="text/javascript" src="../src/validators.js"></script>
|
||||
<script type="text/javascript" src="../src/directives.js"></script>
|
||||
<script type="text/javascript" src="../src/markups.js"></script>
|
||||
<script type="text/javascript" src="../src/widgets.js"></script>
|
||||
|
||||
<script type="text/javascript" src="../src/scenario/Scenario.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/Application.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/Describe.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/Future.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/HtmlUI.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/Runner.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/SpecRunner.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/dsl.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/matchers.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/ObjectModel.js"></script>
|
||||
|
||||
<script type="text/javascript" src="../src/scenario/output/Html.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/output/Object.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/output/Json.js"></script>
|
||||
<script type="text/javascript" src="../src/scenario/output/Xml.js"></script>
|
||||
|
||||
<script type="text/javascript" src="angular-mocks.js"></script>
|
||||
<script type="text/javascript" src="../test/scenario/mocks.js"></script>
|
||||
<script type="text/javascript" src="testabilityPatch.js"></script>
|
||||
|
||||
<!-- include spec files here... -->
|
||||
<script type="text/javascript">
|
||||
describe('manual', function(){
|
||||
var compile, model, element;
|
||||
|
||||
beforeEach(function() {
|
||||
var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
|
||||
compile = function(html) {
|
||||
element = jqLite(html);
|
||||
model = compiler.compile(element)(element);
|
||||
model.$init();
|
||||
return model;
|
||||
};
|
||||
});
|
||||
|
||||
it('should get called on form submit', function() {
|
||||
var scope = compile('<form action="" ng:submit="submitted = true">' +
|
||||
'<input type="submit"/>' +
|
||||
'</form>');
|
||||
scope.$eval();
|
||||
expect(scope.submitted).not.toBeDefined();
|
||||
|
||||
browserTrigger(element.children()[0]);
|
||||
expect(scope.submitted).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('angular.scenario.output.json', function() {
|
||||
var output, context;
|
||||
var runner, $window;
|
||||
var spec, step;
|
||||
|
||||
beforeEach(function() {
|
||||
$window = {};
|
||||
context = _jQuery('<div>text</div>');
|
||||
$(document.body).append(context);
|
||||
runner = new angular.scenario.testing.MockRunner();
|
||||
output = angular.scenario.output.xml(context, runner);
|
||||
spec = {
|
||||
name: 'test spec',
|
||||
definition: {
|
||||
id: 10,
|
||||
name: 'describe'
|
||||
}
|
||||
};
|
||||
step = {
|
||||
name: 'some step',
|
||||
line: function() { return 'unknown:-1'; }
|
||||
};
|
||||
});
|
||||
|
||||
it('should create XML nodes for object model', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
runner.emit('RunnerEnd');
|
||||
expect(_jQuery(context).find('it').attr('status')).toEqual('success');
|
||||
expect(_jQuery(context).find('it step').attr('status')).toEqual('success');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
|
||||
function run(){
|
||||
jasmine.getEnv().execute();
|
||||
}
|
||||
run();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,154 @@
|
||||
describe('HTML', function(){
|
||||
|
||||
function expectHTML(html) {
|
||||
return expect(new HTML(html).get());
|
||||
}
|
||||
|
||||
it('should echo html', function(){
|
||||
expectHTML('hello<b class="1\'23" align=\'""\'>world</b>.').
|
||||
toEqual('hello<b class="1\'23" align="""">world</b>.');
|
||||
});
|
||||
|
||||
it('should remove script', function(){
|
||||
expectHTML('a<SCRIPT>evil< / scrIpt >c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove nested script', function(){
|
||||
expectHTML('a< SCRIPT >A< SCRIPT >evil< / scrIpt >B< / scrIpt >c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove attrs', function(){
|
||||
expectHTML('a<div style="abc">b</div>c').toEqual('a<div>b</div>c');
|
||||
});
|
||||
|
||||
it('should remove style', function(){
|
||||
expectHTML('a<STyle>evil</stYle>c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove script and style', function(){
|
||||
expectHTML('a<STyle>evil<script></script></stYle>c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove double nested script', function(){
|
||||
expectHTML('a<SCRIPT>ev<script>evil</sCript>il</scrIpt>c.').toEqual('ac.');
|
||||
});
|
||||
|
||||
it('should remove unknown tag names', function(){
|
||||
expectHTML('a<xxx><B>b</B></xxx>c').toEqual('a<b>b</b>c');
|
||||
});
|
||||
|
||||
it('should remove unsafe value', function(){
|
||||
expectHTML('<a href="javascript:alert()">').toEqual('<a></a>');
|
||||
});
|
||||
|
||||
it('should handle self closed elements', function(){
|
||||
expectHTML('a<hr/>c').toEqual('a<hr/>c');
|
||||
});
|
||||
|
||||
it('should handle namespace', function(){
|
||||
expectHTML('a<my:hr/><my:div>b</my:div>c').toEqual('abc');
|
||||
});
|
||||
|
||||
it('should handle improper html', function(){
|
||||
expectHTML('< div id="</div>" alt=abc href=\'"\' >text< /div>').
|
||||
toEqual('<div id="</div>" alt="abc" href=""">text</div>');
|
||||
});
|
||||
|
||||
it('should handle improper html2', function(){
|
||||
expectHTML('< div id="</div>" / >').
|
||||
toEqual('<div id="</div>"/>');
|
||||
});
|
||||
|
||||
describe('htmlSanitizerWriter', function(){
|
||||
var writer, html;
|
||||
beforeEach(function(){
|
||||
html = '';
|
||||
writer = htmlSanitizeWriter({push:function(text){html+=text;}});
|
||||
});
|
||||
|
||||
it('should write basic HTML', function(){
|
||||
writer.chars('before');
|
||||
writer.start('div', {id:'123'}, false);
|
||||
writer.chars('in');
|
||||
writer.end('div');
|
||||
writer.chars('after');
|
||||
|
||||
expect(html).toEqual('before<div id="123">in</div>after');
|
||||
});
|
||||
|
||||
it('should escape text nodes', function(){
|
||||
writer.chars('a<div>&</div>c');
|
||||
expect(html).toEqual('a<div>&</div>c');
|
||||
});
|
||||
|
||||
it('should not double escape entities', function(){
|
||||
writer.chars(' ><');
|
||||
expect(html).toEqual(' ><');
|
||||
});
|
||||
|
||||
it('should escape IE script', function(){
|
||||
writer.chars('&{}');
|
||||
expect(html).toEqual('&{}');
|
||||
});
|
||||
|
||||
it('should escape attributes', function(){
|
||||
writer.start('div', {id:'\"\'<>'});
|
||||
expect(html).toEqual('<div id=""\'<>">');
|
||||
});
|
||||
|
||||
it('should ignore missformed elements', function(){
|
||||
writer.start('d>i&v', {});
|
||||
expect(html).toEqual('');
|
||||
});
|
||||
|
||||
it('should ignore unknown attributes', function(){
|
||||
writer.start('div', {unknown:""});
|
||||
expect(html).toEqual('<div>');
|
||||
});
|
||||
|
||||
describe('javascript URL attribute', function(){
|
||||
beforeEach(function(){
|
||||
this.addMatchers({
|
||||
toBeValidUrl: function(){
|
||||
return !isJavaScriptUrl(this.actual);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore javascript:', function(){
|
||||
expect('JavaScript:abc').not.toBeValidUrl();
|
||||
expect(' \n Java\n Script:abc').not.toBeValidUrl();
|
||||
expect('JavaScript/my.js').toBeValidUrl();
|
||||
});
|
||||
|
||||
it('should ignore dec encoded javascript:', function(){
|
||||
expect('javascript:').not.toBeValidUrl();
|
||||
expect('javascript:').not.toBeValidUrl();
|
||||
expect('j avascript:').not.toBeValidUrl();
|
||||
});
|
||||
|
||||
it('should ignore decimal with leading 0 encodede javascript:', function(){
|
||||
expect('javascript:').not.toBeValidUrl();
|
||||
expect('j avascript:').not.toBeValidUrl();
|
||||
expect('j avascript:').not.toBeValidUrl();
|
||||
});
|
||||
|
||||
it('should ignore hex encoded javascript:', function(){
|
||||
expect('javascript:').not.toBeValidUrl();
|
||||
expect('javascript:').not.toBeValidUrl();
|
||||
expect('j avascript:').not.toBeValidUrl();
|
||||
});
|
||||
|
||||
it('should ignore hex encoded whitespace javascript:', function(){
|
||||
expect('jav	ascript:alert("A");').not.toBeValidUrl();
|
||||
expect('jav
ascript:alert("B");').not.toBeValidUrl();
|
||||
expect('jav
 ascript:alert("C");').not.toBeValidUrl();
|
||||
expect('jav\u0000ascript:alert("D");').not.toBeValidUrl();
|
||||
expect('java\u0000\u0000script:alert("D");').not.toBeValidUrl();
|
||||
expect('  java\u0000\u0000script:alert("D");').not.toBeValidUrl();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -7,10 +7,11 @@ describe('angular.scenario.Application', function() {
|
||||
});
|
||||
|
||||
it('should return new $window and $document after navigate', function() {
|
||||
var called;
|
||||
var testWindow, testDocument, counter = 0;
|
||||
app.navigateTo = noop;
|
||||
app.getWindow = function() {
|
||||
return {x:counter++, document:{x:counter++}};
|
||||
app.getWindow_ = function() {
|
||||
return {x:counter++, document:{x:counter++}};
|
||||
};
|
||||
app.navigateTo('http://www.google.com/');
|
||||
app.executeAction(function($document, $window) {
|
||||
@@ -21,56 +22,95 @@ describe('angular.scenario.Application', function() {
|
||||
app.executeAction(function($window, $document) {
|
||||
expect($window).not.toEqual(testWindow);
|
||||
expect($document).not.toEqual(testDocument);
|
||||
called = true;
|
||||
});
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should execute callback with correct arguments', function() {
|
||||
var called;
|
||||
var testWindow = {document: {}};
|
||||
app.getWindow = function() {
|
||||
return testWindow;
|
||||
app.getWindow_ = function() {
|
||||
return testWindow;
|
||||
};
|
||||
app.executeAction(function($window, $document) {
|
||||
expect(this).toEqual(app);
|
||||
expect($document).toEqual(_jQuery($window.document));
|
||||
expect($window).toEqual(testWindow);
|
||||
called = true;
|
||||
});
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should create a new iframe each time', function() {
|
||||
|
||||
it('should use a new iframe each time', function() {
|
||||
app.navigateTo('about:blank');
|
||||
var frame = app.getFrame();
|
||||
var frame = app.getFrame_();
|
||||
frame.attr('test', true);
|
||||
app.navigateTo('about:blank');
|
||||
expect(app.getFrame().attr('test')).toBeFalsy();
|
||||
expect(app.getFrame_().attr('test')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should URL description bar', function() {
|
||||
|
||||
it('should hide old iframes and navigate to about:blank', function() {
|
||||
app.navigateTo('about:blank#foo');
|
||||
app.navigateTo('about:blank#bar');
|
||||
var iframes = frames.find('iframe');
|
||||
expect(iframes.length).toEqual(2);
|
||||
expect(iframes[0].src).toEqual('about:blank');
|
||||
expect(iframes[1].src).toEqual('about:blank#bar');
|
||||
expect(_jQuery(iframes[0]).css('display')).toEqual('none');
|
||||
});
|
||||
|
||||
it('should URL update 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.getWindow_ = function() {
|
||||
return {};
|
||||
};
|
||||
app.navigateTo('about:blank', function() {
|
||||
app.navigateTo('about:blank', function($window, $document) {
|
||||
called = true;
|
||||
});
|
||||
var handlers = app.getFrame_().data('events').load;
|
||||
expect(handlers).toBeDefined();
|
||||
expect(handlers.length).toEqual(1);
|
||||
handlers[0].handler();
|
||||
expect(called).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should wait for pending requests in executeAction', function() {
|
||||
var called, polled;
|
||||
var handlers = [];
|
||||
var testWindow = {
|
||||
document: _jQuery('<div class="test-foo"></div>'),
|
||||
angular: {
|
||||
service: {}
|
||||
}
|
||||
};
|
||||
testWindow.angular.service.$browser = function() {
|
||||
return {
|
||||
poll: function() {
|
||||
polled = true;
|
||||
},
|
||||
notifyWhenNoOutstandingRequests: function(fn) {
|
||||
handlers.push(fn);
|
||||
}
|
||||
}
|
||||
};
|
||||
app.getWindow_ = function() {
|
||||
return testWindow;
|
||||
};
|
||||
app.executeAction(function($window, $document) {
|
||||
expect($window).toEqual(testWindow);
|
||||
expect($document).toBeDefined();
|
||||
expect($document[0].className).toEqual('test-foo');
|
||||
});
|
||||
expect(polled).toBeTruthy();
|
||||
expect(handlers.length).toEqual(1);
|
||||
handlers[0]();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,21 +4,21 @@ describe('angular.scenario.Describe', function() {
|
||||
|
||||
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 = function(text) {
|
||||
log.text = log.text + text;
|
||||
};
|
||||
log.fn = function(text) {
|
||||
return function(done){
|
||||
log(text);
|
||||
(done || angular.noop)();
|
||||
return function(done){
|
||||
log(text);
|
||||
(done || angular.noop)();
|
||||
};
|
||||
};
|
||||
log.reset = function() {
|
||||
log.text = '';
|
||||
log.reset = function() {
|
||||
log.text = '';
|
||||
};
|
||||
log.reset();
|
||||
});
|
||||
@@ -50,7 +50,7 @@ describe('angular.scenario.Describe', function() {
|
||||
specs[1].after();
|
||||
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);
|
||||
@@ -65,7 +65,7 @@ describe('angular.scenario.Describe', function() {
|
||||
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);
|
||||
@@ -79,7 +79,28 @@ describe('angular.scenario.Describe', function() {
|
||||
var specs = root.getSpecs();
|
||||
expect(specs.length).toEqual(0);
|
||||
});
|
||||
|
||||
|
||||
it('should only return iit and ddescribe if present', function() {
|
||||
root.describe('A', function() {
|
||||
this.it('1', angular.noop);
|
||||
this.iit('2', angular.noop);
|
||||
this.describe('B', function() {
|
||||
this.it('3', angular.noop);
|
||||
this.ddescribe('C', function() {
|
||||
this.it('4', angular.noop);
|
||||
this.describe('D', function() {
|
||||
this.it('5', angular.noop);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
var specs = root.getSpecs();
|
||||
expect(specs.length).toEqual(3);
|
||||
expect(specs[0].name).toEqual('5');
|
||||
expect(specs[1].name).toEqual('4');
|
||||
expect(specs[2].name).toEqual('2');
|
||||
});
|
||||
|
||||
it('should create uniqueIds in the tree', function() {
|
||||
angular.scenario.Describe.id = 0;
|
||||
var a = new angular.scenario.Describe();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
describe('angular.scenario.Future', function() {
|
||||
var future;
|
||||
|
||||
|
||||
it('should set the sane defaults', function() {
|
||||
var behavior = function() {};
|
||||
var future = new angular.scenario.Future('test name', behavior, 'foo');
|
||||
@@ -11,7 +11,7 @@ describe('angular.scenario.Future', function() {
|
||||
expect(future.fulfilled).toBeFalsy();
|
||||
expect(future.parser).toEqual(angular.identity);
|
||||
});
|
||||
|
||||
|
||||
it('should be fulfilled after execution and done callback', function() {
|
||||
var future = new angular.scenario.Future('test name', function(done) {
|
||||
done();
|
||||
@@ -19,7 +19,7 @@ describe('angular.scenario.Future', function() {
|
||||
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);
|
||||
@@ -29,7 +29,7 @@ describe('angular.scenario.Future', function() {
|
||||
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);
|
||||
@@ -37,7 +37,7 @@ describe('angular.scenario.Future', function() {
|
||||
future.execute(angular.noop);
|
||||
expect(future.value).toEqual(10);
|
||||
});
|
||||
|
||||
|
||||
it('should parse json with fromJson', function() {
|
||||
var future = new angular.scenario.Future('test name', function(done) {
|
||||
done(null, "{test: 'foo'}");
|
||||
@@ -45,7 +45,7 @@ describe('angular.scenario.Future', function() {
|
||||
future.fromJson().execute(angular.noop);
|
||||
expect(future.value).toEqual({test: 'foo'});
|
||||
});
|
||||
|
||||
|
||||
it('should convert to json with toJson', function() {
|
||||
var future = new angular.scenario.Future('test name', function(done) {
|
||||
done(null, {test: 'foo'});
|
||||
@@ -53,7 +53,7 @@ describe('angular.scenario.Future', function() {
|
||||
future.toJson().execute(angular.noop);
|
||||
expect(future.value).toEqual('{"test":"foo"}');
|
||||
});
|
||||
|
||||
|
||||
it('should convert with custom parser', function() {
|
||||
var future = new angular.scenario.Future('test name', function(done) {
|
||||
done(null, 'foo');
|
||||
@@ -63,7 +63,7 @@ describe('angular.scenario.Future', function() {
|
||||
}).execute(angular.noop);
|
||||
expect(future.value).toEqual('FOO');
|
||||
});
|
||||
|
||||
|
||||
it('should pass error if parser fails', function() {
|
||||
var future = new angular.scenario.Future('test name', function(done) {
|
||||
done(null, '{');
|
||||
@@ -71,5 +71,5 @@ describe('angular.scenario.Future', function() {
|
||||
future.fromJson().execute(function(error, result) {
|
||||
expect(error).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
describe('angular.scenario.HtmlUI', function() {
|
||||
var ui;
|
||||
var context;
|
||||
var spec;
|
||||
|
||||
function line() { return 'unknown:-1'; }
|
||||
|
||||
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');
|
||||
// Failure
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step', line).finish('failure');
|
||||
specUI.finish();
|
||||
// Failure
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step', line).finish('failure');
|
||||
specUI.finish();
|
||||
// Failure
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step', line).finish('failure');
|
||||
specUI.finish();
|
||||
// Success
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step', line).finish();
|
||||
specUI.finish();
|
||||
// Success
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('another step', line).finish();
|
||||
specUI.finish();
|
||||
|
||||
expect(parseInt(context.find('#status-legend .status-failure').text(), 10)).
|
||||
toEqual(3);
|
||||
expect(parseInt(context.find('#status-legend .status-success').text(), 10)).
|
||||
toEqual(2);
|
||||
expect(parseInt(context.find('#status-legend .status-error').text(), 10)).
|
||||
toEqual(1);
|
||||
});
|
||||
|
||||
it('should update timer when test completes', function() {
|
||||
// Success
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step', line).finish();
|
||||
specUI.finish();
|
||||
|
||||
// Failure
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step', line).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$/);
|
||||
});
|
||||
});
|
||||
|
||||
it('should include line if provided', function() {
|
||||
specUI = ui.addSpec(spec);
|
||||
specUI.addStep('some step', line).finish('error!');
|
||||
specUI.finish();
|
||||
|
||||
var errorHtml = context.find('#describe-10 .tests li pre').html();
|
||||
expect(errorHtml.indexOf('unknown:-1')).toEqual(0);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
describe('angular.scenario.ObjectModel', function() {
|
||||
var model;
|
||||
var runner;
|
||||
var spec, step;
|
||||
|
||||
beforeEach(function() {
|
||||
spec = {
|
||||
name: 'test spec',
|
||||
definition: {
|
||||
id: 10,
|
||||
name: 'describe 1'
|
||||
}
|
||||
};
|
||||
step = {
|
||||
name: 'test step',
|
||||
line: function() { return ''; }
|
||||
};
|
||||
runner = new angular.scenario.testing.MockRunner();
|
||||
model = new angular.scenario.ObjectModel(runner);
|
||||
});
|
||||
|
||||
it('should value default empty value', function() {
|
||||
expect(model.value).toEqual({
|
||||
name: '',
|
||||
children: []
|
||||
});
|
||||
});
|
||||
|
||||
it('should add spec and create describe blocks on SpecBegin event', function() {
|
||||
runner.emit('SpecBegin', {
|
||||
name: 'test spec',
|
||||
definition: {
|
||||
id: 10,
|
||||
name: 'describe 2',
|
||||
parent: {
|
||||
id: 12,
|
||||
name: 'describe 1'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(model.value.children['describe 1']).toBeDefined();
|
||||
expect(model.value.children['describe 1'].children['describe 2']).toBeDefined();
|
||||
expect(model.value.children['describe 1'].children['describe 2'].specs['test spec']).toBeDefined();
|
||||
});
|
||||
|
||||
it('should add step to spec on StepBegin', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
expect(model.value.children['describe 1'].specs['test spec'].steps.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should update spec timer duration on SpecEnd event', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
expect(model.value.children['describe 1'].specs['test spec'].duration).toBeDefined();
|
||||
});
|
||||
|
||||
it('should update step timer duration on StepEnd event', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
expect(model.value.children['describe 1'].specs['test spec'].steps[0].duration).toBeDefined();
|
||||
});
|
||||
|
||||
it('should set spec status on SpecEnd to success if no status set', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
expect(model.value.children['describe 1'].specs['test spec'].status).toEqual('success');
|
||||
});
|
||||
|
||||
it('should set status to error after SpecError', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('SpecError', spec, 'error');
|
||||
|
||||
expect(model.value.children['describe 1'].specs['test spec'].status).toEqual('error');
|
||||
});
|
||||
|
||||
it('should set spec status to failure if step fails', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepFailure', spec, step, 'error');
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
expect(model.value.children['describe 1'].specs['test spec'].status).toEqual('failure');
|
||||
});
|
||||
|
||||
it('should set spec status to error if step errors', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepError', spec, step, 'error');
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepFailure', spec, step, 'error');
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
expect(model.value.children['describe 1'].specs['test spec'].status).toEqual('error');
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
* Mock spec runner.
|
||||
*/
|
||||
function MockSpecRunner() {}
|
||||
MockSpecRunner.prototype.run = function(ui, spec, specDone) {
|
||||
MockSpecRunner.prototype.run = function(spec, specDone) {
|
||||
spec.before.call(this);
|
||||
spec.body.call(this);
|
||||
spec.after.call(this);
|
||||
@@ -16,7 +16,7 @@ MockSpecRunner.prototype.addFuture = function(name, fn, line) {
|
||||
describe('angular.scenario.Runner', function() {
|
||||
var $window;
|
||||
var runner;
|
||||
|
||||
|
||||
beforeEach(function() {
|
||||
// Trick to get the scope out of a DSL statement
|
||||
angular.scenario.dsl('dslAddFuture', function() {
|
||||
@@ -41,13 +41,18 @@ describe('angular.scenario.Runner', function() {
|
||||
location: {}
|
||||
};
|
||||
runner = new angular.scenario.Runner($window);
|
||||
runner.createSpecRunner_ = function(scope) {
|
||||
return scope.$new(MockSpecRunner);
|
||||
};
|
||||
runner.on('SpecError', rethrow);
|
||||
runner.on('StepError', rethrow);
|
||||
});
|
||||
|
||||
|
||||
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;
|
||||
@@ -57,7 +62,7 @@ describe('angular.scenario.Runner', function() {
|
||||
expect(angular.isFunction(func)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should construct valid describe trees with public API', function() {
|
||||
var before = [];
|
||||
var after = [];
|
||||
@@ -85,16 +90,16 @@ describe('angular.scenario.Runner', function() {
|
||||
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();
|
||||
});
|
||||
});
|
||||
runner.run(null/*ui*/, null/*application*/, MockSpecRunner, rethrow);
|
||||
runner.run(null/*application*/);
|
||||
});
|
||||
|
||||
|
||||
it('should create a new scope for each DSL chain', function() {
|
||||
$window.describe('describe', function() {
|
||||
$window.it('1', function() {
|
||||
@@ -107,6 +112,6 @@ describe('angular.scenario.Runner', function() {
|
||||
expect(scope.chained).toEqual(2);
|
||||
});
|
||||
});
|
||||
runner.run(null/*ui*/, null/*application*/, MockSpecRunner, rethrow);
|
||||
runner.run(null/*application*/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,39 +1,3 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@@ -47,9 +11,9 @@ ApplicationMock.prototype = {
|
||||
};
|
||||
|
||||
describe('angular.scenario.SpecRunner', function() {
|
||||
var $window;
|
||||
var $window, $root, log;
|
||||
var runner;
|
||||
|
||||
|
||||
function createSpec(name, body) {
|
||||
return {
|
||||
name: name,
|
||||
@@ -60,14 +24,22 @@ describe('angular.scenario.SpecRunner', function() {
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
log = [];
|
||||
$window = {};
|
||||
$window.setTimeout = function(fn, timeout) {
|
||||
fn();
|
||||
};
|
||||
runner = angular.scope();
|
||||
runner.application = new ApplicationMock($window);
|
||||
runner.$window = $window;
|
||||
runner.$become(angular.scenario.SpecRunner);
|
||||
$root = angular.scope({
|
||||
emit: function(eventName) {
|
||||
log.push(eventName);
|
||||
},
|
||||
on: function(eventName) {
|
||||
log.push('Listener Added for ' + eventName);
|
||||
}
|
||||
});
|
||||
$root.application = new ApplicationMock($window);
|
||||
$root.$window = $window;
|
||||
runner = $root.$new(angular.scenario.SpecRunner);
|
||||
});
|
||||
|
||||
it('should bind futures to the spec', function() {
|
||||
@@ -92,84 +64,82 @@ describe('angular.scenario.SpecRunner', function() {
|
||||
|
||||
it('should execute spec function and notify UI', function() {
|
||||
var finished;
|
||||
var ui = new UIMock();
|
||||
var spec = createSpec('test spec', function() {
|
||||
this.test = 'some value';
|
||||
var spec = createSpec('test spec', function() {
|
||||
this.test = 'some value';
|
||||
});
|
||||
runner.addFuture('test future', function(done) {
|
||||
done();
|
||||
});
|
||||
runner.run(ui, spec, function() {
|
||||
runner.run(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:'
|
||||
expect(log).toEqual([
|
||||
'SpecBegin',
|
||||
'StepBegin',
|
||||
'StepEnd',
|
||||
'SpecEnd'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should execute notify UI on spec setup error', function() {
|
||||
var finished;
|
||||
var ui = new UIMock();
|
||||
var spec = createSpec('test spec', function() {
|
||||
var spec = createSpec('test spec', function() {
|
||||
throw 'message';
|
||||
});
|
||||
runner.run(ui, spec, function() {
|
||||
runner.run(spec, function() {
|
||||
finished = true;
|
||||
});
|
||||
expect(finished).toBeTruthy();
|
||||
expect(ui.log).toEqual([
|
||||
'addSpec:test spec',
|
||||
'spec error:message'
|
||||
expect(log).toEqual([
|
||||
'SpecBegin',
|
||||
'SpecError',
|
||||
'SpecEnd'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should execute notify UI on step failure', function() {
|
||||
var finished;
|
||||
var ui = new UIMock();
|
||||
var spec = createSpec('test spec');
|
||||
runner.addFuture('test future', function(done) {
|
||||
done('failure message');
|
||||
});
|
||||
runner.run(ui, spec, function() {
|
||||
runner.run(spec, function() {
|
||||
finished = true;
|
||||
});
|
||||
expect(finished).toBeTruthy();
|
||||
expect(ui.log).toEqual([
|
||||
'addSpec:test spec',
|
||||
'addStep:test future',
|
||||
'step finish:failure message',
|
||||
'spec finish:'
|
||||
expect(log).toEqual([
|
||||
'SpecBegin',
|
||||
'StepBegin',
|
||||
'StepFailure',
|
||||
'StepEnd',
|
||||
'SpecEnd'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should execute notify UI on step error', function() {
|
||||
var finished;
|
||||
var ui = new UIMock();
|
||||
var spec = createSpec('test spec', function() {
|
||||
this.addFuture('test future', function(done) {
|
||||
throw 'error message';
|
||||
});
|
||||
});
|
||||
runner.run(ui, spec, function() {
|
||||
runner.run(spec, function() {
|
||||
finished = true;
|
||||
});
|
||||
expect(finished).toBeTruthy();
|
||||
expect(ui.log).toEqual([
|
||||
'addSpec:test spec',
|
||||
'addStep:test future',
|
||||
'step error:error message',
|
||||
'spec finish:'
|
||||
expect(log).toEqual([
|
||||
'SpecBegin',
|
||||
'StepBegin',
|
||||
'StepError',
|
||||
'StepEnd',
|
||||
'SpecEnd'
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should run after handlers even if error in body of spec', function() {
|
||||
var finished, after;
|
||||
var ui = new UIMock();
|
||||
var spec = createSpec('test spec', function() {
|
||||
this.addFuture('body', function(done) {
|
||||
throw 'error message';
|
||||
@@ -181,18 +151,19 @@ describe('angular.scenario.SpecRunner', function() {
|
||||
done();
|
||||
});
|
||||
};
|
||||
runner.run(ui, spec, function() {
|
||||
runner.run(spec, function() {
|
||||
finished = true;
|
||||
});
|
||||
expect(finished).toBeTruthy();
|
||||
expect(after).toBeTruthy();
|
||||
expect(ui.log).toEqual([
|
||||
'addSpec:test spec',
|
||||
'addStep:body',
|
||||
'step error:error message',
|
||||
'addStep:after',
|
||||
'step finish:',
|
||||
'spec finish:'
|
||||
expect(log).toEqual([
|
||||
'SpecBegin',
|
||||
'StepBegin',
|
||||
'StepError',
|
||||
'StepEnd',
|
||||
'StepBegin',
|
||||
'StepEnd',
|
||||
'SpecEnd'
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
+47
-45
@@ -1,41 +1,21 @@
|
||||
/**
|
||||
* Very basic Mock of angular.
|
||||
*/
|
||||
function AngularMock() {
|
||||
this.reset();
|
||||
this.service = this;
|
||||
}
|
||||
|
||||
AngularMock.prototype.reset = function() {
|
||||
this.log = [];
|
||||
};
|
||||
|
||||
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;
|
||||
var $window, $root;
|
||||
var application, eventLog;
|
||||
|
||||
beforeEach(function() {
|
||||
eventLog = [];
|
||||
$window = {
|
||||
document: _jQuery("<div></div>"),
|
||||
angular: new AngularMock()
|
||||
angular: new angular.scenario.testing.MockAngular()
|
||||
};
|
||||
$root = angular.scope();
|
||||
$root = angular.scope({
|
||||
emit: function(eventName) {
|
||||
eventLog.push(eventName);
|
||||
},
|
||||
on: function(eventName) {
|
||||
eventLog.push('Listener Added for ' + eventName);
|
||||
}
|
||||
});
|
||||
$root.futures = [];
|
||||
$root.futureLog = [];
|
||||
$root.$window = $window;
|
||||
@@ -54,7 +34,7 @@ describe("angular.scenario.dsl", function() {
|
||||
};
|
||||
});
|
||||
$root.application = new angular.scenario.Application($window.document);
|
||||
$root.application.getWindow = function() {
|
||||
$root.application.getWindow_ = function() {
|
||||
return $window;
|
||||
};
|
||||
$root.application.navigateTo = function(url, callback) {
|
||||
@@ -74,13 +54,14 @@ describe("angular.scenario.dsl", function() {
|
||||
expect($root.futureLog).toEqual([]);
|
||||
$window.resume();
|
||||
expect($root.futureLog).
|
||||
toEqual(['waiting for you to call resume() in the console']);
|
||||
toEqual(['waiting for you to resume']);
|
||||
expect(eventLog).toContain('InteractiveWait');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pause', function() {
|
||||
beforeEach(function() {
|
||||
$root.setTimeout = function(fn, value) {
|
||||
$root.$window.setTimeout = function(fn, value) {
|
||||
$root.timerValue = value;
|
||||
fn();
|
||||
};
|
||||
@@ -127,13 +108,6 @@ describe("angular.scenario.dsl", function() {
|
||||
expect($window.location).toEqual('http://myurl');
|
||||
expect($root.futureResult).toEqual('http://myurl');
|
||||
});
|
||||
|
||||
it('should wait for angular notify when no requests pending', function() {
|
||||
$root.dsl.navigateTo('url');
|
||||
expect($window.angular.log).toContain('$brower.poll()');
|
||||
expect($window.angular.log).
|
||||
toContain('$brower.notifyWhenNoOutstandingRequests()');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Element Finding', function() {
|
||||
@@ -190,13 +164,33 @@ describe("angular.scenario.dsl", function() {
|
||||
describe('Element', function() {
|
||||
it('should execute click', function() {
|
||||
var clicked;
|
||||
doc.append('<a href=""></a>');
|
||||
// Hash is important, otherwise we actually
|
||||
// go to a different page and break the runner
|
||||
doc.append('<a href="#"></a>');
|
||||
doc.find('a').click(function() {
|
||||
clicked = true;
|
||||
});
|
||||
$root.dsl.element('a').click();
|
||||
});
|
||||
|
||||
it('should navigate page if click on anchor', function() {
|
||||
expect($window.location).not.toEqual('#foo');
|
||||
doc.append('<a href="#foo"></a>');
|
||||
$root.dsl.element('a').click();
|
||||
expect($window.location).toMatch(/#foo$/);
|
||||
});
|
||||
|
||||
it('should count matching elements', function() {
|
||||
doc.append('<span></span><span></span>');
|
||||
$root.dsl.element('span').count();
|
||||
expect($root.futureResult).toEqual(2);
|
||||
});
|
||||
|
||||
it('should return count of 0 if no matching elements', function() {
|
||||
$root.dsl.element('span').count();
|
||||
expect($root.futureResult).toEqual(0);
|
||||
});
|
||||
|
||||
it('should get attribute', function() {
|
||||
doc.append('<div id="test" class="foo"></div>');
|
||||
$root.dsl.element('#test').attr('class');
|
||||
@@ -222,11 +216,11 @@ describe("angular.scenario.dsl", function() {
|
||||
});
|
||||
|
||||
it('should execute custom query', function() {
|
||||
doc.append('<a id="test" href="myUrl"></a>');
|
||||
doc.append('<a id="test" href="http://example.com/myUrl"></a>');
|
||||
$root.dsl.element('#test').query(function(elements, done) {
|
||||
done(null, elements.attr('href'));
|
||||
});
|
||||
expect($root.futureResult).toEqual('myUrl');
|
||||
expect($root.futureResult).toEqual('http://example.com/myUrl');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -247,6 +241,12 @@ describe("angular.scenario.dsl", function() {
|
||||
expect($root.futureResult).toEqual(2);
|
||||
});
|
||||
|
||||
it('should return 0 if repeater doesnt match', function() {
|
||||
doc.find('ul').html('');
|
||||
chain.count();
|
||||
expect($root.futureResult).toEqual(0);
|
||||
});
|
||||
|
||||
it('should get a row of bindings', function() {
|
||||
chain.row(1);
|
||||
expect($root.futureResult).toEqual(['felisa', 'female']);
|
||||
@@ -260,12 +260,14 @@ describe("angular.scenario.dsl", function() {
|
||||
|
||||
describe('Binding', function() {
|
||||
it('should select binding by name', function() {
|
||||
if (msie) return; // TODO reenable!
|
||||
doc.append('<span ng:bind="foo.bar">some value</span>');
|
||||
$root.dsl.binding('foo.bar');
|
||||
expect($root.futureResult).toEqual('some value');
|
||||
});
|
||||
|
||||
it('should select binding in template by name', function() {
|
||||
if (msie) return; // TODO reenable!
|
||||
doc.append('<pre ng:bind-template="foo {{bar}} baz">foo some baz</pre>');
|
||||
$root.dsl.binding('bar');
|
||||
expect($root.futureResult).toEqual('foo some baz');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
describe('angular.scenario.matchers', function () {
|
||||
var matchers;
|
||||
|
||||
|
||||
function expectMatcher(value, test) {
|
||||
delete matchers.error;
|
||||
delete matchers.future.value;
|
||||
@@ -10,9 +10,9 @@ describe('angular.scenario.matchers', function () {
|
||||
test();
|
||||
expect(matchers.error).toBeUndefined();
|
||||
}
|
||||
|
||||
|
||||
beforeEach(function() {
|
||||
/**
|
||||
/**
|
||||
* Mock up the future system wrapped around matchers.
|
||||
*
|
||||
* @see Scenario.js#angular.scenario.matcher
|
||||
@@ -27,7 +27,7 @@ describe('angular.scenario.matchers', function () {
|
||||
};
|
||||
angular.extend(matchers, angular.scenario.matcher);
|
||||
});
|
||||
|
||||
|
||||
it('should handle basic matching', function() {
|
||||
expectMatcher(10, function() { matchers.toEqual(10); });
|
||||
expectMatcher('value', function() { matchers.toBeDefined(); });
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
angular.scenario.testing = angular.scenario.testing || {};
|
||||
|
||||
angular.scenario.testing.MockAngular = function() {
|
||||
this.reset();
|
||||
this.service = this;
|
||||
};
|
||||
|
||||
angular.scenario.testing.MockAngular.prototype.reset = function() {
|
||||
this.log = [];
|
||||
};
|
||||
|
||||
angular.scenario.testing.MockAngular.prototype.$browser = function() {
|
||||
this.log.push('$brower()');
|
||||
return this;
|
||||
};
|
||||
|
||||
angular.scenario.testing.MockAngular.prototype.poll = function() {
|
||||
this.log.push('$brower.poll()');
|
||||
return this;
|
||||
};
|
||||
|
||||
angular.scenario.testing.MockAngular.prototype.notifyWhenNoOutstandingRequests = function(fn) {
|
||||
this.log.push('$brower.notifyWhenNoOutstandingRequests()');
|
||||
fn();
|
||||
};
|
||||
|
||||
angular.scenario.testing.MockRunner = function() {
|
||||
this.listeners = [];
|
||||
};
|
||||
|
||||
angular.scenario.testing.MockRunner.prototype.on = function(eventName, fn) {
|
||||
this.listeners[eventName] = this.listeners[eventName] || [];
|
||||
this.listeners[eventName].push(fn);
|
||||
};
|
||||
|
||||
angular.scenario.testing.MockRunner.prototype.emit = function(eventName) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
angular.foreach(this.listeners[eventName] || [], function(fn) {
|
||||
fn.apply(this, args);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
describe('angular.scenario.output.html', function() {
|
||||
var runner, spec, listeners;
|
||||
var ui, context;
|
||||
|
||||
beforeEach(function() {
|
||||
listeners = [];
|
||||
spec = {
|
||||
name: 'test spec',
|
||||
definition: {
|
||||
id: 10,
|
||||
name: 'child',
|
||||
children: [],
|
||||
parent: {
|
||||
id: 20,
|
||||
name: 'parent',
|
||||
children: []
|
||||
}
|
||||
}
|
||||
};
|
||||
step = {
|
||||
name: 'some step',
|
||||
line: function() { return 'unknown:-1'; }
|
||||
};
|
||||
runner = new angular.scenario.testing.MockRunner();
|
||||
context = _jQuery("<div></div>");
|
||||
ui = angular.scenario.output.html(context, runner);
|
||||
});
|
||||
|
||||
it('should create nested describe context', function() {
|
||||
runner.emit('SpecBegin', 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('test spec');
|
||||
expect(context.find('#describe-10 .tests > li').hasClass('status-pending')).
|
||||
toBeTruthy();
|
||||
});
|
||||
|
||||
it('should add link on InteractiveWait', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('InteractiveWait', spec, step);
|
||||
expect(context.find('.test-actions .test-title:first').text()).toEqual('some step');
|
||||
expect(lowercase(context.find('.test-actions .test-title:last').html())).toEqual(
|
||||
'waiting for you to <a href="javascript:resume()">resume</a>.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should update totals when steps complete', function() {
|
||||
// Failure
|
||||
for (var i=0; i < 3; ++i) {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepFailure', spec, step, 'error');
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
}
|
||||
|
||||
// Error
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('SpecError', spec, 'error');
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
// Error
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepError', spec, step, 'error');
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
// Success
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
expect(parseInt(context.find('#status-legend .status-failure').text(), 10)).
|
||||
toEqual(3);
|
||||
expect(parseInt(context.find('#status-legend .status-error').text(), 10)).
|
||||
toEqual(2);
|
||||
expect(parseInt(context.find('#status-legend .status-success').text(), 10)).
|
||||
toEqual(1);
|
||||
});
|
||||
|
||||
it('should update timer when test completes', function() {
|
||||
// Success
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
// Failure
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepFailure', spec, step, 'error');
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
// Error
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('SpecError', spec, 'error');
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
context.find('#describe-10 .tests > li .test-info .timer-result').
|
||||
each(function(index, timer) {
|
||||
expect(timer.innerHTML).toMatch(/ms$/);
|
||||
});
|
||||
});
|
||||
|
||||
it('should include line if provided', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepFailure', spec, step, 'error');
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
|
||||
var errorHtml = context.find('#describe-10 .tests li pre').html();
|
||||
expect(errorHtml.indexOf('unknown:-1')).toEqual(0);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
describe('angular.scenario.output.json', function() {
|
||||
var output, context;
|
||||
var runner, $window;
|
||||
var spec, step;
|
||||
|
||||
beforeEach(function() {
|
||||
$window = {};
|
||||
context = _jQuery('<div></div>');
|
||||
runner = new angular.scenario.testing.MockRunner();
|
||||
output = angular.scenario.output.json(context, runner);
|
||||
spec = {
|
||||
name: 'test spec',
|
||||
definition: {
|
||||
id: 10,
|
||||
name: 'describe'
|
||||
}
|
||||
};
|
||||
step = {
|
||||
name: 'some step',
|
||||
line: function() { return 'unknown:-1'; }
|
||||
};
|
||||
});
|
||||
|
||||
it('should put json in context on RunnerEnd', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
runner.emit('RunnerEnd');
|
||||
|
||||
expect(angular.fromJson(context.html()).children['describe']
|
||||
.specs['test spec'].status).toEqual('success');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
describe('angular.scenario.output.object', function() {
|
||||
var output;
|
||||
var runner, $window;
|
||||
var spec, step;
|
||||
|
||||
beforeEach(function() {
|
||||
$window = {};
|
||||
runner = new angular.scenario.testing.MockRunner();
|
||||
runner.$window = $window;
|
||||
output = angular.scenario.output.object(null, runner);
|
||||
spec = {
|
||||
name: 'test spec',
|
||||
definition: {
|
||||
id: 10,
|
||||
name: 'describe',
|
||||
children: []
|
||||
}
|
||||
};
|
||||
step = {
|
||||
name: 'some step',
|
||||
line: function() { return 'unknown:-1'; }
|
||||
};
|
||||
});
|
||||
|
||||
it('should create a global variable $result', function() {
|
||||
expect($window.$result).toBeDefined();
|
||||
});
|
||||
|
||||
it('should maintain live state in $result', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
|
||||
expect($window.$result.children['describe']
|
||||
.specs['test spec'].steps[0].duration).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
describe('angular.scenario.output.json', function() {
|
||||
var output, context;
|
||||
var runner, $window;
|
||||
var spec, step;
|
||||
|
||||
beforeEach(function() {
|
||||
$window = {};
|
||||
context = _jQuery('<div></div>');
|
||||
runner = new angular.scenario.testing.MockRunner();
|
||||
output = angular.scenario.output.xml(context, runner);
|
||||
spec = {
|
||||
name: 'test spec',
|
||||
definition: {
|
||||
id: 10,
|
||||
name: 'describe'
|
||||
}
|
||||
};
|
||||
step = {
|
||||
name: 'some step',
|
||||
line: function() { return 'unknown:-1'; }
|
||||
};
|
||||
});
|
||||
|
||||
it('should create XML nodes for object model', function() {
|
||||
runner.emit('SpecBegin', spec);
|
||||
runner.emit('StepBegin', spec, step);
|
||||
runner.emit('StepEnd', spec, step);
|
||||
runner.emit('SpecEnd', spec);
|
||||
runner.emit('RunnerEnd');
|
||||
expect(context.find('it').attr('status')).toEqual('success');
|
||||
expect(context.find('it step').attr('status')).toEqual('success');
|
||||
});
|
||||
});
|
||||
+33
-33
@@ -85,7 +85,7 @@ describe("service", function(){
|
||||
it("should inject $location", function() {
|
||||
expect(scope.$location).toBeDefined();
|
||||
});
|
||||
|
||||
|
||||
it("update should update location object immediately", function() {
|
||||
var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=';
|
||||
scope.$location.update(href);
|
||||
@@ -99,25 +99,25 @@ describe("service", function(){
|
||||
expect(scope.$location.hashPath).toEqual('path');
|
||||
expect(scope.$location.hashSearch).toEqual({key: 'value', flag: true, key2: ''});
|
||||
});
|
||||
|
||||
|
||||
it('toString() should return actual representation', function() {
|
||||
var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=';
|
||||
scope.$location.update(href);
|
||||
expect(scope.$location.toString()).toEqual(href);
|
||||
scope.$eval();
|
||||
|
||||
|
||||
scope.$location.host = 'new';
|
||||
scope.$location.path = '';
|
||||
expect(scope.$location.toString()).toEqual('http://new:123?query=value#path?key=value&flag&key2=');
|
||||
});
|
||||
|
||||
|
||||
it('toString() should not update browser', function() {
|
||||
var url = $browser.getUrl();
|
||||
scope.$location.update('http://www.angularjs.org');
|
||||
expect(scope.$location.toString()).toEqual('http://www.angularjs.org');
|
||||
expect($browser.getUrl()).toEqual(url);
|
||||
});
|
||||
|
||||
|
||||
it('should update browser at the end of $eval', function() {
|
||||
var url = $browser.getUrl();
|
||||
scope.$location.update('http://www.angularjs.org/');
|
||||
@@ -136,12 +136,12 @@ describe("service", function(){
|
||||
expect(scope.$location.hashPath).toEqual('');
|
||||
expect(scope.$location.hashSearch).toEqual({});
|
||||
});
|
||||
|
||||
|
||||
it('should update hash on hashPath or hashSearch update', function() {
|
||||
scope.$location.update('http://server/#path?a=b');
|
||||
scope.$eval();
|
||||
scope.$location.update({hashPath: '', hashSearch: {}});
|
||||
|
||||
|
||||
expect(scope.$location.hash).toEqual('');
|
||||
});
|
||||
|
||||
@@ -154,7 +154,7 @@ describe("service", function(){
|
||||
expect(scope.$location.hashPath).toEqual('');
|
||||
expect(scope.$location.hashSearch).toEqual({});
|
||||
});
|
||||
|
||||
|
||||
it('should update hash on hashPath or hashSearch property change', function() {
|
||||
scope.$location.update('http://server/#path?a=b');
|
||||
scope.$eval();
|
||||
@@ -178,35 +178,35 @@ describe("service", function(){
|
||||
scope.$eval();
|
||||
expect(log).toEqual('/abc;');
|
||||
});
|
||||
|
||||
|
||||
it('udpate() should accept hash object and update only given properties', function() {
|
||||
scope.$location.update("http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
||||
scope.$location.update({host: 'new', port: 24});
|
||||
|
||||
|
||||
expect(scope.$location.host).toEqual('new');
|
||||
expect(scope.$location.port).toEqual(24);
|
||||
expect(scope.$location.protocol).toEqual('http');
|
||||
expect(scope.$location.href).toEqual("http://new:24/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
||||
});
|
||||
|
||||
|
||||
it('updateHash() should accept one string argument to update path', function() {
|
||||
scope.$location.updateHash('path');
|
||||
expect(scope.$location.hash).toEqual('path');
|
||||
expect(scope.$location.hashPath).toEqual('path');
|
||||
});
|
||||
|
||||
|
||||
it('updateHash() should accept one hash argument to update search', function() {
|
||||
scope.$location.updateHash({a: 'b'});
|
||||
expect(scope.$location.hash).toEqual('?a=b');
|
||||
expect(scope.$location.hashSearch).toEqual({a: 'b'});
|
||||
});
|
||||
|
||||
|
||||
it('updateHash() should accept path and search both', function() {
|
||||
scope.$location.updateHash('path', {a: 'b'});
|
||||
expect(scope.$location.hash).toEqual('path?a=b');
|
||||
expect(scope.$location.hashSearch).toEqual({a: 'b'});
|
||||
expect(scope.$location.hashPath).toEqual('path');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("$invalidWidgets", function(){
|
||||
@@ -541,7 +541,7 @@ describe("service", function(){
|
||||
describe('$cookieStore', function() {
|
||||
|
||||
it('should serialize objects to json', function() {
|
||||
scope.$cookieStore.put('objectCookie', {id: 123, name: 'blah'});
|
||||
scope.$inject('$cookieStore').put('objectCookie', {id: 123, name: 'blah'});
|
||||
scope.$eval(); //force eval in test
|
||||
expect($browser.cookies()).toEqual({'objectCookie': '{"id":123,"name":"blah"}'});
|
||||
});
|
||||
@@ -550,65 +550,65 @@ describe("service", function(){
|
||||
it('should deserialize json to object', function() {
|
||||
$browser.cookies('objectCookie', '{"id":123,"name":"blah"}');
|
||||
$browser.poll();
|
||||
expect(scope.$cookieStore.get('objectCookie')).toEqual({id: 123, name: 'blah'});
|
||||
expect(scope.$inject('$cookieStore').get('objectCookie')).toEqual({id: 123, name: 'blah'});
|
||||
});
|
||||
|
||||
|
||||
it('should delete objects from the store when remove is called', function() {
|
||||
scope.$cookieStore.put('gonner', { "I'll":"Be Back"});
|
||||
scope.$inject('$cookieStore').put('gonner', { "I'll":"Be Back"});
|
||||
scope.$eval(); //force eval in test
|
||||
expect($browser.cookies()).toEqual({'gonner': '{"I\'ll":"Be Back"}'});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
describe('URL_MATCH', function() {
|
||||
|
||||
it('should parse basic url', function() {
|
||||
var match = URL_MATCH.exec('http://www.angularjs.org/path?search#hash?x=x');
|
||||
|
||||
|
||||
expect(match[1]).toEqual('http');
|
||||
expect(match[3]).toEqual('www.angularjs.org');
|
||||
expect(match[6]).toEqual('/path');
|
||||
expect(match[8]).toEqual('search');
|
||||
expect(match[10]).toEqual('hash?x=x');
|
||||
});
|
||||
|
||||
|
||||
it('should parse file://', function(){
|
||||
var match = URL_MATCH.exec('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html');
|
||||
|
||||
|
||||
expect(match[1]).toEqual('file');
|
||||
expect(match[3]).toEqual('');
|
||||
expect(match[5]).toEqual(null);
|
||||
expect(match[5]).toBeFalsy();
|
||||
expect(match[6]).toEqual('/Users/Shared/misko/work/angular.js/scenario/widgets.html');
|
||||
expect(match[8]).not.toBeDefined();
|
||||
expect(match[8]).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
it('should parse url with "-" in host', function(){
|
||||
var match = URL_MATCH.exec('http://a-b1.c-d.09/path');
|
||||
|
||||
|
||||
expect(match[1]).toEqual('http');
|
||||
expect(match[3]).toEqual('a-b1.c-d.09');
|
||||
expect(match[5]).toEqual(null);
|
||||
expect(match[5]).toBeFalsy();
|
||||
expect(match[6]).toEqual('/path');
|
||||
expect(match[8]).not.toBeDefined();
|
||||
expect(match[8]).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
it('should parse host without "/" at the end', function() {
|
||||
var match = URL_MATCH.exec('http://host.org');
|
||||
expect(match[3]).toEqual('host.org');
|
||||
|
||||
|
||||
match = URL_MATCH.exec('http://host.org#');
|
||||
expect(match[3]).toEqual('host.org');
|
||||
|
||||
|
||||
match = URL_MATCH.exec('http://host.org?');
|
||||
expect(match[3]).toEqual('host.org');
|
||||
});
|
||||
|
||||
|
||||
it('should match with just "/" path', function() {
|
||||
var match = URL_MATCH.exec('http://server/#?book=moby');
|
||||
|
||||
|
||||
expect(match[10]).toEqual('?book=moby');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
jstd = jstestdriver;
|
||||
dump = bind(jstd.console, jstd.console.log);
|
||||
/**
|
||||
* Here is the problem: http://bugs.jquery.com/ticket/7292
|
||||
* basically jQuery treats change event on some browsers (IE) as a
|
||||
* special event and changes it form 'change' to 'click/keyup' and
|
||||
* few others. This horrible hack removes the special treatment
|
||||
*/
|
||||
_jQuery.event.special.change = undefined;
|
||||
|
||||
|
||||
if (window.jstestdriver) {
|
||||
jstd = jstestdriver;
|
||||
dump = bind(jstd.console, jstd.console.log);
|
||||
}
|
||||
|
||||
beforeEach(function(){
|
||||
this.addMatchers({
|
||||
@@ -74,7 +85,10 @@ function sortedHtml(element) {
|
||||
var html = "";
|
||||
foreach(jqLite(element), function toString(node) {
|
||||
if (node.nodeName == "#text") {
|
||||
html += escapeHtml(node.nodeValue);
|
||||
html += node.nodeValue.
|
||||
replace(/&(\w+[&;\W])?/g, function(match, entity){return entity?match:'&';}).
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>');
|
||||
} else {
|
||||
html += '<' + node.nodeName.toLowerCase();
|
||||
var attributes = node.attributes || [];
|
||||
|
||||
+14
-3
@@ -345,9 +345,9 @@ describe("widget", function(){
|
||||
it('should initialize to selected', function(){
|
||||
compile(
|
||||
'<select name="selection">' +
|
||||
'<option>A</option>' +
|
||||
'<option selected>B</option>' +
|
||||
'</select>');
|
||||
'<option>A</option>' +
|
||||
'<option selected>B</option>' +
|
||||
'</select>');
|
||||
expect(scope.selection).toEqual('B');
|
||||
scope.selection = 'A';
|
||||
scope.$eval();
|
||||
@@ -497,6 +497,17 @@ describe("widget", function(){
|
||||
|
||||
expect(element.text()).toEqual('');
|
||||
});
|
||||
|
||||
it('should allow this for scope', function(){
|
||||
var element = jqLite('<ng:include src="url" scope="this"></ng:include>');
|
||||
var scope = angular.compile(element);
|
||||
scope.url = 'myUrl';
|
||||
scope.$inject('$xhr.cache').data.myUrl = {value:'{{c=c+1}}'};
|
||||
scope.$init();
|
||||
// This should not be 4, but to fix this properly
|
||||
// we need to have real events on the scopes.
|
||||
expect(element.text()).toEqual('4');
|
||||
});
|
||||
});
|
||||
|
||||
describe('a', function() {
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# <angular/> build config file
|
||||
---
|
||||
version: 0.9.0
|
||||
codename: dragon-breath
|
||||
version: 0.9.1
|
||||
codename: repulsion-field
|
||||
|
||||
Reference in New Issue
Block a user