-
Notifications
You must be signed in to change notification settings - Fork 3
Whirlwind Tutorial
After reading the project layout documentation, you probably want a full example. Or you want a full example first, and might get around to reading those docs later.
This "tutorial" goes through the steps in creating as simple an app as possible, while showing all the features of Rupert. It is Hello, World, with a small twist - each username will only be greeted a single time! (If you're impatient, all the code is at https://github.com/DavidSouther/rupert-demo-app)
Rupert recommends your client side code be broken into modules for each component, in their own folders. The form will be a custom directive, a small service caching API calls, and a template. Let's build the template first.
form(
name="helloForm"
ng-submit="sayhello(name)"
ng-init="hello = null; met = null; error = null;"
)
.row
.col-sm-4.col-sm-offset-4
div(ng-hide="hello || met || error")
input.Name.form-control.input-lg(
placeholder="Name"
name="Name"
ng-model="name"
)
div(ng-show="hello")
| Pleased to meet you, {{ name }}!
div(ng-show="met")
| We've already met, {{ name }}! So nice to see you again!
div(ng-show="error")
| I'm sorry, I had trouble hearing you. Please try again later!
This template gets loaded as a directive.
angular.module('rupert-demo.hello', [
'rupert-demo.hello.service'
'hello.template'
]).directive 'hello', (HelloSvc)->
restrict: 'AE'
templateUrl: 'hello'
controller: ($scope)->
$scope.sayhello = (name)->
HelloSvc.sayhello(name)
.then (reply)->
if reply.alreadymet is true
$scope.met = reply.name
else
$scope.hello = reply.name
.catch (err)->
$scope.error = err
We can put the form on the page by editing a bit of our index file.
//- ...
body
.container
hello
//- ...
And add the directive to our global module list.
angular.module('rupert-demo', [
'rupert-demo.hello'
]);
You see the form uses a service.
angular.module('rupert-demo.hello.service', [
]).factory 'HelloSvc', ($http, $q)->
names = {}
sayhello = (name)->
name = name.toLowerCase()
if names[name]
return $q((r)-> r({alreadymet: true, name}))
$http.post('/api/hello', {name}).then (_)-> names[name] = _.data
{ sayhello }
That service has some logic, that we should test.
describe 'Hello Service', ->
beforeEach ->
module 'rupert-demo.hello.service'
beforeEach inject ($httpBackend)->
$httpBackend
.whenPOST('/api/hello', '{"name":"david"}')
.respond(200, JSON.stringify({name: 'david'}))
$httpBackend
.whenPOST('/api/hello', '{"name":"rupert"}')
.respond(200, JSON.stringify({name: 'rupert', alreadymet: yes}))
afterEach inject ($httpBackend)->
$httpBackend.verifyNoOutstandingExpectation()
$httpBackend.verifyNoOutstandingRequest()
it 'accepts hellos', inject (HelloSvc, $httpBackend)->
HelloSvc.should.have.property('sayhello').that.is.instanceof(Function)
HelloSvc.sayhello('David').then (reply)-> reply.name.should.equal 'david'
$httpBackend.expectPOST('/api/hello')
$httpBackend.flush()
it 'remembers names', inject (HelloSvc, $httpBackend, $timeout)->
HelloSvc.sayhello('Rupert').then (reply)->
reply.name.should.equal 'rupert'
reply.should.have.property('alreadymet').that.equals(true)
$httpBackend.expectPOST('/api/hello')
$httpBackend.flush()
HelloSvc.sayhello('Rupert').then (reply)-> reply.name.should.equal 'rupert'
$timeout.flush()
## NO REQUEST The second time.
And that's a complete client!
The backend needs an endpoint to handle the /api/hello
request.
names = {}
module.exports = (app, config)->
app.post '/api/hello', (request, response)->
reply = {name: request.body.name}
if names[reply.name]
reply.alreadymet = yes
names[reply.name] = yes
response.send reply
Of course we test it!
request = superRupert(require('./route'))
describe "API", ->
it "checks for a hello", (done)->
request.post('/api/hello')
.send({name: 'david'})
.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', /application\/json/)
.end (err, response)->
return done(err) if err
try
response.body.should.have.property('name')
response.body.should.not.have.property('alreadymet')
response.body.name.should.equal('david')
catch e
reurn done e
done()
it "checks for a repeat hello", (done)->
request.post('/api/hello')
.send({name: 'rupert'})
.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', /application\/json/)
.end (err, response)->
return done(err) if err
try
# The first request should not have already met Rupert.
response.body.should.have.property('name')
response.body.should.not.have.property('alreadymet')
response.body.name.should.equal('rupert')
catch e
return done e
request.post('/api/hello')
.send({name: 'rupert'})
.expect(200)
.expect('Content-Type', /application\/json/)
.end (err, response)->
return done(err) if err
try
# The second request, of course, should have already met Rupert.
response.body.should.have.property('name')
response.body.should.have.property('alreadymet')
response.body.name.should.equal('rupert')
response.body.alreadymet.should.equal(true)
catch e
return done e
done()
That's it, that's all. A nice API endpoint!
Write the user story for our wonderful users!
Feature: A Friendly Hello
As a user of our Site
Rupert wants get a friendly greeting
So that he feels great this morning
Scenario: Hello
Given I am on the page
When I give my name as "Rupert"
Then I should see a friendly greeting
Scenario: Already Met
Given I am on the page
When I give my name as "Rupert"
And I give my name as "Rupert" again
Then I should see a friendly already met you
There are some custom steps in that task list.
mappings = require '../mappings'
steps = ->
@Given /on the (?:site|page)/, =>
@world.visit(@ROOT)
.then => @protractor.waitForAngular()
@When /give.*name.*"([^"]+)"/, (name)=>
@world.fill('Name', name, yes)
.then => @protractor.waitForAngular()
@When /give.*name.*"([^"]+)" again/, (name)=>
@world.visit(@ROOT)
@world.fill('Name', name, yes)
.then => @protractor.waitForAngular()
@Then /friendly greeting/, =>
@world.find(mappings.greeting).isDisplayed()
.then (isDisplayed)->
isDisplayed.should.equal true
@Then /friendly already met you/, =>
@world.find(mappings.metyou).isDisplayed()
.then (isDisplayed)->
isDisplayed.should.equal true
module.exports = require('rupert-grunt/steps')(steps, {protractor: yes})
This uses the selenium mappings model for creating tests that use a ubiquitous language.
module.exports =
greeting: '[ng-show="hello"]'
metyou: '[ng-show="met"]'
And there you have it, a fully functioning front-edge application! All the code is at https://github.com/DavidSouther/rupert-demo-app