Add browser test server

This commit is contained in:
Ritchie Martori 2014-01-30 12:23:02 -08:00
parent 41bc8a3172
commit 9515554647
11 changed files with 14807 additions and 4721 deletions

View File

@ -16,26 +16,46 @@ module.exports = function(grunt) {
banner: '<%= banner %>'
},
dist: {
src: '<%= concat.dist.dest %>',
dest: 'dist/<%= pkg.name %>.min.js'
files: {
'dist/loopback.min.js': ['dist/loopback.js']
}
}
},
jshint: {
options: {
curly: true,
curly: false,
eqeqeq: true,
immed: true,
latedef: true,
latedef: false,
newcap: true,
noarg: true,
sub: true,
undef: true,
unused: true,
unused: false,
boss: true,
eqnull: true,
browser: true,
asi: true,
node: true,
laxbreak: true,
globals: {
jQuery: true
require: true,
jQuery: true,
process: true,
/* MOCHA */
describe: false,
it: false,
before: false,
beforeEach: false,
after: false,
afterEach: false,
assert: false,
request: false,
app: false,
loopback: false,
expect: true,
GeoPoint: true,
assertValidDataSource: true
}
},
gruntfile: {
@ -61,6 +81,7 @@ module.exports = function(grunt) {
'dist/loopback.js': ['index.js'],
},
options: {
ignore: ['nodemailer', 'passport']
}
}
},
@ -96,13 +117,13 @@ module.exports = function(grunt) {
// todo appium
// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-mocha-selenium');
// Default task.
grunt.registerTask('default', ['jshint', 'uglify']);
grunt.registerTask('default', ['browserify', 'mochaSelenium']);
};

8423
dist/loopback.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,6 @@ var express = require('express')
* methods to create models and data sources. The module itself is a function
* that creates loopback `app`. For example,
*
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
@ -27,6 +26,18 @@ var express = require('express')
var loopback = exports = module.exports = createApplication;
/**
* Is this a browser environment?
*/
loopback.isBrowser = typeof window !== 'undefined';
/**
* Is this a server environment?
*/
loopback.isServer = !loopback.isBrowser;
/**
* Framework version.
*/
@ -320,11 +331,11 @@ var dataSourceTypes = {
MAIL: 'mail'
};
loopback.Email.autoAttach = dataSourceTypes.MAIL;
loopback.User.autoAttach = dataSourceTypes.DB;
loopback.AccessToken.autoAttach = dataSourceTypes.DB;
loopback.Role.autoAttach = dataSourceTypes.DB;
loopback.RoleMapping.autoAttach = dataSourceTypes.DB;
loopback.AccessToken.autoAttach = dataSourceTypes.DB;
if(loopback.isServer) loopback.Email.autoAttach = dataSourceTypes.MAIL;
loopback.ACL.autoAttach = dataSourceTypes.DB;
loopback.Scope.autoAttach = dataSourceTypes.DB;
loopback.Application.autoAttach = dataSourceTypes.DB;

View File

@ -49,7 +49,10 @@
"browserify": "~3.14.1",
"grunt": "~0.4.2",
"grunt-browserify": "~1.3.0",
"grunt-mocha-selenium": "~0.7.0"
"grunt-mocha-selenium": "~0.7.0",
"grunt-contrib-uglify": "~0.3.2",
"grunt-contrib-jshint": "~0.8.0",
"grunt-contrib-watch": "~0.5.3"
},
"repository": {
"type": "git",

4696
test/browser/chai.js Normal file

File diff suppressed because it is too large Load Diff

47
test/browser/index.js Normal file
View File

@ -0,0 +1,47 @@
// test server
var loopback = require('../../');
var testServer = loopback();
var fs = require('fs');
var path = require('path');
var browserify = require('browserify');
var TEST_DIR = path.join(__dirname, '..');
var FIXTURES_DIR = path.join(TEST_DIR, 'fixtures');
testServer.set('views', __dirname);
testServer.get('/', function(req, res) {
res.render('test.html.ejs');
});
testServer.get('/loopback.js', function(req, res) {
res.send(
fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'loopback.js'))
)
});
testServer.get('/tests.js', function(req, res) {
var files = [
path.join(TEST_DIR, 'support.js'),
path.join(TEST_DIR, 'model.test.js'),
path.join(TEST_DIR, 'change.test.js'),
path.join(TEST_DIR, 'geo-point.test.js')
];
var b = browserify({
entries: files,
basedir: TEST_DIR,
debug: true
});
b.ignore('nodemailer');
b.ignore('passport');
b.ignore('superagent');
b.ignore('supertest');
b.bundle({
debug: true
}).pipe(res);
});
testServer.use(loopback.static(__dirname));
testServer.listen(4040, function() {
console.log('test server listening on port', testServer.get('port'));
});

270
test/browser/mocha.css Normal file
View File

@ -0,0 +1,270 @@
@charset "utf-8";
body {
margin:0;
}
#mocha {
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 60px 50px;
}
#mocha ul,
#mocha li {
margin: 0;
padding: 0;
}
#mocha ul {
list-style: none;
}
#mocha h1,
#mocha h2 {
margin: 0;
}
#mocha h1 {
margin-top: 15px;
font-size: 1em;
font-weight: 200;
}
#mocha h1 a {
text-decoration: none;
color: inherit;
}
#mocha h1 a:hover {
text-decoration: underline;
}
#mocha .suite .suite h1 {
margin-top: 0;
font-size: .8em;
}
#mocha .hidden {
display: none;
}
#mocha h2 {
font-size: 12px;
font-weight: normal;
cursor: pointer;
}
#mocha .suite {
margin-left: 15px;
}
#mocha .test {
margin-left: 15px;
overflow: hidden;
}
#mocha .test.pending:hover h2::after {
content: '(pending)';
font-family: arial, sans-serif;
}
#mocha .test.pass.medium .duration {
background: #c09853;
}
#mocha .test.pass.slow .duration {
background: #b94a48;
}
#mocha .test.pass::before {
content: '✓';
font-size: 12px;
display: block;
float: left;
margin-right: 5px;
color: #00d6b2;
}
#mocha .test.pass .duration {
font-size: 9px;
margin-left: 5px;
padding: 2px 5px;
color: #fff;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
-ms-border-radius: 5px;
-o-border-radius: 5px;
border-radius: 5px;
}
#mocha .test.pass.fast .duration {
display: none;
}
#mocha .test.pending {
color: #0b97c4;
}
#mocha .test.pending::before {
content: '◦';
color: #0b97c4;
}
#mocha .test.fail {
color: #c00;
}
#mocha .test.fail pre {
color: black;
}
#mocha .test.fail::before {
content: '✖';
font-size: 12px;
display: block;
float: left;
margin-right: 5px;
color: #c00;
}
#mocha .test pre.error {
color: #c00;
max-height: 300px;
overflow: auto;
}
/**
* (1): approximate for browsers not supporting calc
* (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
* ^^ seriously
*/
#mocha .test pre {
display: block;
float: left;
clear: left;
font: 12px/1.5 monaco, monospace;
margin: 5px;
padding: 15px;
border: 1px solid #eee;
max-width: 85%; /*(1)*/
max-width: calc(100% - 42px); /*(2)*/
word-wrap: break-word;
border-bottom-color: #ddd;
-webkit-border-radius: 3px;
-webkit-box-shadow: 0 1px 3px #eee;
-moz-border-radius: 3px;
-moz-box-shadow: 0 1px 3px #eee;
border-radius: 3px;
}
#mocha .test h2 {
position: relative;
}
#mocha .test a.replay {
position: absolute;
top: 3px;
right: 0;
text-decoration: none;
vertical-align: middle;
display: block;
width: 15px;
height: 15px;
line-height: 15px;
text-align: center;
background: #eee;
font-size: 15px;
-moz-border-radius: 15px;
border-radius: 15px;
-webkit-transition: opacity 200ms;
-moz-transition: opacity 200ms;
transition: opacity 200ms;
opacity: 0.3;
color: #888;
}
#mocha .test:hover a.replay {
opacity: 1;
}
#mocha-report.pass .test.fail {
display: none;
}
#mocha-report.fail .test.pass {
display: none;
}
#mocha-report.pending .test.pass,
#mocha-report.pending .test.fail {
display: none;
}
#mocha-report.pending .test.pass.pending {
display: block;
}
#mocha-error {
color: #c00;
font-size: 1.5em;
font-weight: 100;
letter-spacing: 1px;
}
#mocha-stats {
position: fixed;
top: 15px;
right: 10px;
font-size: 12px;
margin: 0;
color: #888;
z-index: 1;
}
#mocha-stats .progress {
float: right;
padding-top: 0;
}
#mocha-stats em {
color: black;
}
#mocha-stats a {
text-decoration: none;
color: inherit;
}
#mocha-stats a:hover {
border-bottom: 1px solid #eee;
}
#mocha-stats li {
display: inline-block;
margin: 0 5px;
list-style: none;
padding-top: 11px;
}
#mocha-stats canvas {
width: 40px;
height: 40px;
}
#mocha code .comment { color: #ddd; }
#mocha code .init { color: #2f6fad; }
#mocha code .string { color: #5890ad; }
#mocha code .keyword { color: #8a6343; }
#mocha code .number { color: #2f6fad; }
@media screen and (max-device-width: 480px) {
#mocha {
margin: 60px 0px;
}
#mocha #stats {
position: absolute;
}
}

5812
test/browser/mocha.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
<html>
<head>
<meta charset="utf-8">
<title>LoopBack Mocha Tests</title>
<link rel="stylesheet" href="mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="chai.js"></script>
<script src="mocha.js"></script>
<script>
mocha.setup('bdd');
</script>
<script src="tests.js"></script>
<script>
mocha.checkLeaks();
mocha.globals([]);
mocha.run();
</script>
</body>
</html>

View File

@ -262,127 +262,61 @@ describe('Model', function() {
});
});
});
describe('Remote Methods', function(){
beforeEach(function () {
User.login = function (username, password, fn) {
if(username === 'foo' && password === 'bar') {
fn(null, 123);
} else {
throw new Error('bad username and password!');
if(loopback.isServer) {
describe('Remote Methods', function(){
beforeEach(function () {
User.login = function (username, password, fn) {
if(username === 'foo' && password === 'bar') {
fn(null, 123);
} else {
throw new Error('bad username and password!');
}
}
}
loopback.remoteMethod(
User.login,
{
accepts: [
{arg: 'username', type: 'string', required: true},
{arg: 'password', type: 'string', required: true}
],
returns: {arg: 'sessionId', type: 'any', root: true},
http: {path: '/sign-in', verb: 'get'}
}
);
loopback.remoteMethod(
User.login,
{
accepts: [
{arg: 'username', type: 'string', required: true},
{arg: 'password', type: 'string', required: true}
],
returns: {arg: 'sessionId', type: 'any', root: true},
http: {path: '/sign-in', verb: 'get'}
}
);
app.use(loopback.rest());
app.model(User);
});
app.use(loopback.rest());
app.model(User);
});
describe('Example Remote Method', function () {
it('Call the method using HTTP / REST', function(done) {
request(app)
.get('/users/sign-in?username=foo&password=bar')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res){
if(err) return done(err);
assert.equal(res.body, 123);
done();
});
});
describe('Example Remote Method', function () {
it('Call the method using HTTP / REST', function(done) {
request(app)
.get('/users/sign-in?username=foo&password=bar')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res){
if(err) return done(err);
assert.equal(res.body, 123);
done();
});
});
it('Converts null result of findById to 404 Not Found', function(done) {
request(app)
.get('/users/not-found')
.expect(404)
.end(done);
});
});
describe('Model.beforeRemote(name, fn)', function(){
it('Run a function before a remote method is called by a client', function(done) {
var hookCalled = false;
User.beforeRemote('create', function(ctx, user, next) {
hookCalled = true;
next();
it('Converts null result of findById to 404 Not Found', function(done) {
request(app)
.get('/users/not-found')
.expect(404)
.end(done);
});
// invoke save
request(app)
.post('/users')
.send({data: {first: 'foo', last: 'bar'}})
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if(err) return done(err);
assert(hookCalled, 'hook wasnt called');
done();
});
});
});
describe('Model.afterRemote(name, fn)', function(){
it('Run a function after a remote method is called by a client', function(done) {
var beforeCalled = false;
var afterCalled = false;
User.beforeRemote('create', function(ctx, user, next) {
assert(!afterCalled);
beforeCalled = true;
next();
});
User.afterRemote('create', function(ctx, user, next) {
assert(beforeCalled);
afterCalled = true;
next();
});
// invoke save
request(app)
.post('/users')
.send({data: {first: 'foo', last: 'bar'}})
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res) {
if(err) return done(err);
assert(beforeCalled, 'before hook was not called');
assert(afterCalled, 'after hook was not called');
done();
});
});
});
describe('Remote Method invoking context', function () {
// describe('ctx.user', function() {
// it("The remote user model calling the method remotely", function(done) {
// done(new Error('test not implemented'));
// });
// });
describe('ctx.req', function() {
it("The express ServerRequest object", function(done) {
describe('Model.beforeRemote(name, fn)', function(){
it('Run a function before a remote method is called by a client', function(done) {
var hookCalled = false;
User.beforeRemote('create', function(ctx, user, next) {
hookCalled = true;
assert(ctx.req);
assert(ctx.req.url);
assert(ctx.req.method);
assert(ctx.res);
assert(ctx.res.write);
assert(ctx.res.end);
next();
});
@ -394,24 +328,25 @@ describe('Model', function() {
.expect(200)
.end(function(err, res) {
if(err) return done(err);
assert(hookCalled);
assert(hookCalled, 'hook wasnt called');
done();
});
});
});
describe('ctx.res', function() {
it("The express ServerResponse object", function(done) {
var hookCalled = false;
describe('Model.afterRemote(name, fn)', function(){
it('Run a function after a remote method is called by a client', function(done) {
var beforeCalled = false;
var afterCalled = false;
User.beforeRemote('create', function(ctx, user, next) {
hookCalled = true;
assert(ctx.req);
assert(ctx.req.url);
assert(ctx.req.method);
assert(ctx.res);
assert(ctx.res.write);
assert(ctx.res.end);
assert(!afterCalled);
beforeCalled = true;
next();
});
User.afterRemote('create', function(ctx, user, next) {
assert(beforeCalled);
afterCalled = true;
next();
});
@ -423,7 +358,8 @@ describe('Model', function() {
.expect(200)
.end(function(err, res) {
if(err) return done(err);
assert(hookCalled);
assert(beforeCalled, 'before hook was not called');
assert(afterCalled, 'after hook was not called');
done();
});
});

View File

@ -24,13 +24,15 @@ beforeEach(function () {
connector: loopback.Memory
});
loopback.setDefaultDataSourceForType('mail', {
connector: loopback.Mail,
transports: [
{type: 'STUB'}
]
});
if(loopback.isServer) {
loopback.setDefaultDataSourceForType('mail', {
connector: loopback.Mail,
transports: [
{type: 'STUB'}
]
});
}
// auto attach data sources to models
loopback.autoAttach();
});