Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Stas Sușcov - ”Ember.js și Jasmine BDD. Proiect...

GeekMeetRo
October 20, 2012

Stas Sușcov - ”Ember.js și Jasmine BDD. Proiectul TodoMVC.”

GeekMeetRo

October 20, 2012
Tweet

More Decks by GeekMeetRo

Other Decks in Technology

Transcript

  1. Ember.js și Jasmine BDD. În contextul proiectului TodoMVC. Stas Sușcov

    ([email protected]) GeekMeet #12, Cluj-Napoca, Transilvania 20 Octombrie, 2012 GeekMeet #12, Cluj-Napoca, Transilvania 1 / 19 20 Octombrie, 2012
  2. Despre mine un nerd dezvoltator pretențios și pedant interese: web/operations

    nu fac frontend sau design (Œ(papa vin bicicleta)) GeekMeet #12, Cluj-Napoca, Transilvania 2 / 19 20 Octombrie, 2012
  3. Despre mine un nerd dezvoltator pretențios și pedant interese: web/operations

    nu fac frontend sau design (Œ(papa vin bicicleta)) GeekMeet #12, Cluj-Napoca, Transilvania 2 / 19 20 Octombrie, 2012
  4. Despre mine un nerd dezvoltator pretențios și pedant interese: web/operations

    nu fac frontend sau design (Œ(papa vin bicicleta)) GeekMeet #12, Cluj-Napoca, Transilvania 2 / 19 20 Octombrie, 2012
  5. Despre mine un nerd dezvoltator pretențios și pedant interese: web/operations

    nu fac frontend sau design (Œ(papa vin bicicleta)) GeekMeet #12, Cluj-Napoca, Transilvania 2 / 19 20 Octombrie, 2012
  6. Despre mine un nerd dezvoltator pretențios și pedant interese: web/operations

    nu fac frontend sau design (Œ(papa vin bicicleta)) GeekMeet #12, Cluj-Napoca, Transilvania 2 / 19 20 Octombrie, 2012
  7. Astăzi când zic JavaScript, nu mă refer la Node.js, CoffeeScript

    sau LiveScript! GeekMeet #12, Cluj-Napoca, Transilvania 3 / 19 20 Octombrie, 2012
  8. Povestea mea căutam o soluție pentru frontend pentru non-frontend-iști sa

    știe MVC sa fie populară insa nu prea recentă sa știe chestii fancy gen: binding-uri observere rutare sa nu necesite HTML/nu fie foarte declarativ sa nu fie împrăștiat GeekMeet #12, Cluj-Napoca, Transilvania 4 / 19 20 Octombrie, 2012
  9. Povestea mea căutam o soluție pentru frontend pentru non-frontend-iști sa

    știe MVC sa fie populară insa nu prea recentă sa știe chestii fancy gen: binding-uri observere rutare sa nu necesite HTML/nu fie foarte declarativ sa nu fie împrăștiat GeekMeet #12, Cluj-Napoca, Transilvania 4 / 19 20 Octombrie, 2012
  10. Povestea mea căutam o soluție pentru frontend pentru non-frontend-iști sa

    știe MVC sa fie populară insa nu prea recentă sa știe chestii fancy gen: binding-uri observere rutare sa nu necesite HTML/nu fie foarte declarativ sa nu fie împrăștiat GeekMeet #12, Cluj-Napoca, Transilvania 4 / 19 20 Octombrie, 2012
  11. Povestea mea căutam o soluție pentru frontend pentru non-frontend-iști sa

    știe MVC sa fie populară insa nu prea recentă sa știe chestii fancy gen: binding-uri observere rutare sa nu necesite HTML/nu fie foarte declarativ sa nu fie împrăștiat GeekMeet #12, Cluj-Napoca, Transilvania 4 / 19 20 Octombrie, 2012
  12. Povestea mea căutam o soluție pentru frontend pentru non-frontend-iști sa

    știe MVC sa fie populară insa nu prea recentă sa știe chestii fancy gen: binding-uri observere rutare sa nu necesite HTML/nu fie foarte declarativ sa nu fie împrăștiat GeekMeet #12, Cluj-Napoca, Transilvania 4 / 19 20 Octombrie, 2012
  13. Povestea mea căutam o soluție pentru frontend pentru non-frontend-iști sa

    știe MVC sa fie populară insa nu prea recentă sa știe chestii fancy gen: binding-uri observere rutare sa nu necesite HTML/nu fie foarte declarativ sa nu fie împrăștiat GeekMeet #12, Cluj-Napoca, Transilvania 4 / 19 20 Octombrie, 2012
  14. Povestea mea căutam o soluție pentru frontend pentru non-frontend-iști sa

    știe MVC sa fie populară insa nu prea recentă sa știe chestii fancy gen: binding-uri observere rutare sa nu necesite HTML/nu fie foarte declarativ sa nu fie împrăștiat GeekMeet #12, Cluj-Napoca, Transilvania 4 / 19 20 Octombrie, 2012
  15. Povestea mea căutam o soluție pentru frontend pentru non-frontend-iști sa

    știe MVC sa fie populară insa nu prea recentă sa știe chestii fancy gen: binding-uri observere rutare sa nu necesite HTML/nu fie foarte declarativ sa nu fie împrăștiat GeekMeet #12, Cluj-Napoca, Transilvania 4 / 19 20 Octombrie, 2012
  16. Povestea mea căutam o soluție pentru frontend pentru non-frontend-iști sa

    știe MVC sa fie populară insa nu prea recentă sa știe chestii fancy gen: binding-uri observere rutare sa nu necesite HTML/nu fie foarte declarativ sa nu fie împrăștiat GeekMeet #12, Cluj-Napoca, Transilvania 4 / 19 20 Octombrie, 2012
  17. Treaba cu TodoMVC are ca scop să prezinte cât mai

    multe librării (framework) JavaScript în mare măsură MV* urmează strict un set de specificații se propune să oferă și teste (însă se mai lucrează aici) are nișe persoane interesante la colaboratori Addy Osmani Sindre Sorhus colaborează cu autorii/dezvoltatorii de bază din comunitățile populare este un proiect educațional extraordinar http://todomvc.com GeekMeet #12, Cluj-Napoca, Transilvania 5 / 19 20 Octombrie, 2012
  18. Treaba cu TodoMVC are ca scop să prezinte cât mai

    multe librării (framework) JavaScript în mare măsură MV* urmează strict un set de specificații se propune să oferă și teste (însă se mai lucrează aici) are nișe persoane interesante la colaboratori Addy Osmani Sindre Sorhus colaborează cu autorii/dezvoltatorii de bază din comunitățile populare este un proiect educațional extraordinar http://todomvc.com GeekMeet #12, Cluj-Napoca, Transilvania 5 / 19 20 Octombrie, 2012
  19. Treaba cu TodoMVC are ca scop să prezinte cât mai

    multe librării (framework) JavaScript în mare măsură MV* urmează strict un set de specificații se propune să oferă și teste (însă se mai lucrează aici) are nișe persoane interesante la colaboratori Addy Osmani Sindre Sorhus colaborează cu autorii/dezvoltatorii de bază din comunitățile populare este un proiect educațional extraordinar http://todomvc.com GeekMeet #12, Cluj-Napoca, Transilvania 5 / 19 20 Octombrie, 2012
  20. Treaba cu TodoMVC are ca scop să prezinte cât mai

    multe librării (framework) JavaScript în mare măsură MV* urmează strict un set de specificații se propune să oferă și teste (însă se mai lucrează aici) are nișe persoane interesante la colaboratori Addy Osmani Sindre Sorhus colaborează cu autorii/dezvoltatorii de bază din comunitățile populare este un proiect educațional extraordinar http://todomvc.com GeekMeet #12, Cluj-Napoca, Transilvania 5 / 19 20 Octombrie, 2012
  21. Treaba cu TodoMVC are ca scop să prezinte cât mai

    multe librării (framework) JavaScript în mare măsură MV* urmează strict un set de specificații se propune să oferă și teste (însă se mai lucrează aici) are nișe persoane interesante la colaboratori Addy Osmani Sindre Sorhus colaborează cu autorii/dezvoltatorii de bază din comunitățile populare este un proiect educațional extraordinar http://todomvc.com GeekMeet #12, Cluj-Napoca, Transilvania 5 / 19 20 Octombrie, 2012
  22. Treaba cu TodoMVC are ca scop să prezinte cât mai

    multe librării (framework) JavaScript în mare măsură MV* urmează strict un set de specificații se propune să oferă și teste (însă se mai lucrează aici) are nișe persoane interesante la colaboratori Addy Osmani Sindre Sorhus colaborează cu autorii/dezvoltatorii de bază din comunitățile populare este un proiect educațional extraordinar http://todomvc.com GeekMeet #12, Cluj-Napoca, Transilvania 5 / 19 20 Octombrie, 2012
  23. Treaba cu TodoMVC are ca scop să prezinte cât mai

    multe librării (framework) JavaScript în mare măsură MV* urmează strict un set de specificații se propune să oferă și teste (însă se mai lucrează aici) are nișe persoane interesante la colaboratori Addy Osmani Sindre Sorhus colaborează cu autorii/dezvoltatorii de bază din comunitățile populare este un proiect educațional extraordinar http://todomvc.com GeekMeet #12, Cluj-Napoca, Transilvania 5 / 19 20 Octombrie, 2012
  24. Treaba cu TodoMVC are ca scop să prezinte cât mai

    multe librării (framework) JavaScript în mare măsură MV* urmează strict un set de specificații se propune să oferă și teste (însă se mai lucrează aici) are nișe persoane interesante la colaboratori Addy Osmani Sindre Sorhus colaborează cu autorii/dezvoltatorii de bază din comunitățile populare este un proiect educațional extraordinar http://todomvc.com GeekMeet #12, Cluj-Napoca, Transilvania 5 / 19 20 Octombrie, 2012
  25. Treaba cu TodoMVC are ca scop să prezinte cât mai

    multe librării (framework) JavaScript în mare măsură MV* urmează strict un set de specificații se propune să oferă și teste (însă se mai lucrează aici) are nișe persoane interesante la colaboratori Addy Osmani Sindre Sorhus colaborează cu autorii/dezvoltatorii de bază din comunitățile populare este un proiect educațional extraordinar http://todomvc.com GeekMeet #12, Cluj-Napoca, Transilvania 5 / 19 20 Octombrie, 2012
  26. Am ales Ember.js pentru că vezi mai sus º pentru

    că are convenții (cough-cough rails) pentru că e doar o librărie, nu zece! pentru că e fun și mai jos voi încerca să demonstrez acest lucru http://emberjs.com GeekMeet #12, Cluj-Napoca, Transilvania 6 / 19 20 Octombrie, 2012
  27. Am ales Ember.js pentru că vezi mai sus º pentru

    că are convenții (cough-cough rails) pentru că e doar o librărie, nu zece! pentru că e fun și mai jos voi încerca să demonstrez acest lucru http://emberjs.com GeekMeet #12, Cluj-Napoca, Transilvania 6 / 19 20 Octombrie, 2012
  28. Am ales Ember.js pentru că vezi mai sus º pentru

    că are convenții (cough-cough rails) pentru că e doar o librărie, nu zece! pentru că e fun și mai jos voi încerca să demonstrez acest lucru http://emberjs.com GeekMeet #12, Cluj-Napoca, Transilvania 6 / 19 20 Octombrie, 2012
  29. Am ales Ember.js pentru că vezi mai sus º pentru

    că are convenții (cough-cough rails) pentru că e doar o librărie, nu zece! pentru că e fun și mai jos voi încerca să demonstrez acest lucru http://emberjs.com GeekMeet #12, Cluj-Napoca, Transilvania 6 / 19 20 Octombrie, 2012
  30. Demo Ember.js (tentativă de blog) am scris o aplicație simplă

    pentru a trezi interesul vostru unde următorul pas ar fi să vă jucați cu codul din prezentare și să-i dați o șansă proiectului Ember.js din suita TodoMVC este loc de niște contribuții cum ar fi mai multe teste actualizat cu modificările de API rescris Todo.js (modelul) GeekMeet #12, Cluj-Napoca, Transilvania 7 / 19 20 Octombrie, 2012
  31. Demo Ember.js (tentativă de blog) am scris o aplicație simplă

    pentru a trezi interesul vostru unde următorul pas ar fi să vă jucați cu codul din prezentare și să-i dați o șansă proiectului Ember.js din suita TodoMVC este loc de niște contribuții cum ar fi mai multe teste actualizat cu modificările de API rescris Todo.js (modelul) GeekMeet #12, Cluj-Napoca, Transilvania 7 / 19 20 Octombrie, 2012
  32. Demo Ember.js (tentativă de blog) am scris o aplicație simplă

    pentru a trezi interesul vostru unde următorul pas ar fi să vă jucați cu codul din prezentare și să-i dați o șansă proiectului Ember.js din suita TodoMVC este loc de niște contribuții cum ar fi mai multe teste actualizat cu modificările de API rescris Todo.js (modelul) GeekMeet #12, Cluj-Napoca, Transilvania 7 / 19 20 Octombrie, 2012
  33. Demo Ember.js (tentativă de blog) am scris o aplicație simplă

    pentru a trezi interesul vostru unde următorul pas ar fi să vă jucați cu codul din prezentare și să-i dați o șansă proiectului Ember.js din suita TodoMVC este loc de niște contribuții cum ar fi mai multe teste actualizat cu modificările de API rescris Todo.js (modelul) GeekMeet #12, Cluj-Napoca, Transilvania 7 / 19 20 Octombrie, 2012
  34. Demo Ember.js (tentativă de blog) am scris o aplicație simplă

    pentru a trezi interesul vostru unde următorul pas ar fi să vă jucați cu codul din prezentare și să-i dați o șansă proiectului Ember.js din suita TodoMVC este loc de niște contribuții cum ar fi mai multe teste actualizat cu modificările de API rescris Todo.js (modelul) GeekMeet #12, Cluj-Napoca, Transilvania 7 / 19 20 Octombrie, 2012
  35. Demo Ember.js (tentativă de blog) am scris o aplicație simplă

    pentru a trezi interesul vostru unde următorul pas ar fi să vă jucați cu codul din prezentare și să-i dați o șansă proiectului Ember.js din suita TodoMVC este loc de niște contribuții cum ar fi mai multe teste actualizat cu modificările de API rescris Todo.js (modelul) GeekMeet #12, Cluj-Napoca, Transilvania 7 / 19 20 Octombrie, 2012
  36. Demo Ember.js (tentativă de blog) am scris o aplicație simplă

    pentru a trezi interesul vostru unde următorul pas ar fi să vă jucați cu codul din prezentare și să-i dați o șansă proiectului Ember.js din suita TodoMVC este loc de niște contribuții cum ar fi mai multe teste actualizat cu modificările de API rescris Todo.js (modelul) GeekMeet #12, Cluj-Napoca, Transilvania 7 / 19 20 Octombrie, 2012
  37. app.js are ca scop instanțierea aplicației ApplicationController și ApplicationView sunt

    musai! rootElement e pentru a specifica unde să-și facă de cap aplicația la {{outlet}} se leagă connectOutlets 1 var App = Ember.Application.create({ 2 VERSION: ’0.1.0’, 3 rootElement: ’#content’, 4 ApplicationController: Ember.Controller.extend(), 5 ArticleController: Ember.Controller.extend(), 6 ApplicationView: Ember.View.extend({ 7 template: Ember.Handlebars.compile( ’{{outlet}}’ ) 8 }) 9 // The rest of the Models/Controllers/Views 10 // will self append to this namespace 11 }); GeekMeet #12, Cluj-Napoca, Transilvania 8 / 19 20 Octombrie, 2012
  38. app.js are ca scop instanțierea aplicației ApplicationController și ApplicationView sunt

    musai! rootElement e pentru a specifica unde să-și facă de cap aplicația la {{outlet}} se leagă connectOutlets 1 var App = Ember.Application.create({ 2 VERSION: ’0.1.0’, 3 rootElement: ’#content’, 4 ApplicationController: Ember.Controller.extend(), 5 ArticleController: Ember.Controller.extend(), 6 ApplicationView: Ember.View.extend({ 7 template: Ember.Handlebars.compile( ’{{outlet}}’ ) 8 }) 9 // The rest of the Models/Controllers/Views 10 // will self append to this namespace 11 }); GeekMeet #12, Cluj-Napoca, Transilvania 8 / 19 20 Octombrie, 2012
  39. app.js are ca scop instanțierea aplicației ApplicationController și ApplicationView sunt

    musai! rootElement e pentru a specifica unde să-și facă de cap aplicația la {{outlet}} se leagă connectOutlets 1 var App = Ember.Application.create({ 2 VERSION: ’0.1.0’, 3 rootElement: ’#content’, 4 ApplicationController: Ember.Controller.extend(), 5 ArticleController: Ember.Controller.extend(), 6 ApplicationView: Ember.View.extend({ 7 template: Ember.Handlebars.compile( ’{{outlet}}’ ) 8 }) 9 // The rest of the Models/Controllers/Views 10 // will self append to this namespace 11 }); GeekMeet #12, Cluj-Napoca, Transilvania 8 / 19 20 Octombrie, 2012
  40. app.js are ca scop instanțierea aplicației ApplicationController și ApplicationView sunt

    musai! rootElement e pentru a specifica unde să-și facă de cap aplicația la {{outlet}} se leagă connectOutlets 1 var App = Ember.Application.create({ 2 VERSION: ’0.1.0’, 3 rootElement: ’#content’, 4 ApplicationController: Ember.Controller.extend(), 5 ArticleController: Ember.Controller.extend(), 6 ApplicationView: Ember.View.extend({ 7 template: Ember.Handlebars.compile( ’{{outlet}}’ ) 8 }) 9 // The rest of the Models/Controllers/Views 10 // will self append to this namespace 11 }); GeekMeet #12, Cluj-Napoca, Transilvania 8 / 19 20 Octombrie, 2012
  41. app/router.js ruta root e musai și de acolo va porni

    aplicația implicit enter se apelează când ruta este accesată connectOutlets se apelează când ruta e accesată pentru a conecta controllere specifice 1 var Router = Ember.Router.extend({ 2 3 root: Ember.Route.extend({ 4 goToPublish: Ember.Route.transitionTo( ’root.publish’ ), 5 6 // GET /#/ 7 index: Ember.Route.extend({ 8 route: ’/’, 9 redirectsTo: ’publish’ 10 }), 11 12 // GET /#/publish 13 publish: Ember.Route.extend({ 14 route: ’/publish’, 15 16 connectOutlets: function( router ) { 17 router.get( ’applicationController’ ).connectOutlet( ’publish’ ); 18 } 19 }), 20 21 // GET /#/article/:id 22 article: Ember.Route.extend({ 23 route: ’/article/:id’, 24 GeekMeet #12, Cluj-Napoca, Transilvania 9 / 19 20 Octombrie, 2012
  42. app/router.js ruta root e musai și de acolo va porni

    aplicația implicit enter se apelează când ruta este accesată connectOutlets se apelează când ruta e accesată pentru a conecta controllere specifice 1 var Router = Ember.Router.extend({ 2 3 root: Ember.Route.extend({ 4 goToPublish: Ember.Route.transitionTo( ’root.publish’ ), 5 6 // GET /#/ 7 index: Ember.Route.extend({ 8 route: ’/’, 9 redirectsTo: ’publish’ 10 }), 11 12 // GET /#/publish 13 publish: Ember.Route.extend({ 14 route: ’/publish’, 15 16 connectOutlets: function( router ) { 17 router.get( ’applicationController’ ).connectOutlet( ’publish’ ); 18 } 19 }), 20 21 // GET /#/article/:id 22 article: Ember.Route.extend({ 23 route: ’/article/:id’, 24 GeekMeet #12, Cluj-Napoca, Transilvania 9 / 19 20 Octombrie, 2012
  43. app/router.js ruta root e musai și de acolo va porni

    aplicația implicit enter se apelează când ruta este accesată connectOutlets se apelează când ruta e accesată pentru a conecta controllere specifice 1 var Router = Ember.Router.extend({ 2 3 root: Ember.Route.extend({ 4 goToPublish: Ember.Route.transitionTo( ’root.publish’ ), 5 6 // GET /#/ 7 index: Ember.Route.extend({ 8 route: ’/’, 9 redirectsTo: ’publish’ 10 }), 11 12 // GET /#/publish 13 publish: Ember.Route.extend({ 14 route: ’/publish’, 15 16 connectOutlets: function( router ) { 17 router.get( ’applicationController’ ).connectOutlet( ’publish’ ); 18 } 19 }), 20 21 // GET /#/article/:id 22 article: Ember.Route.extend({ 23 route: ’/article/:id’, 24 GeekMeet #12, Cluj-Napoca, Transilvania 9 / 19 20 Octombrie, 2012
  44. app/model/article.js este modelul aplicației (clasă ActiveRecord dacă doriți) .property() sunt

    definește proprietăți computate (atribute dinamice) este valabil pentru oricare componentă din Ember.js init este constructorul (new, initialize, construct()) metodele clasei find face parte din convenție și este apelat de rută dacă e cazul all este la fel parte din convenție, apelat la fel de rută dacă e cazul 1 var Article = Ember.Object.extend({ 2 id: null, 3 title: null, 4 content: null, 5 6 // Handle Generation of the slug as a property 7 slug: function() { 8 var slug = this.get( ’title’ ).toLowerCase(); 9 return slug.replace( /\s+?/g, ’-’ ); 10 }.property( ’title’ ).cacheable(), 11 12 // Constructor 13 init: function() { 14 var id = Date.now(); 15 24 Article.reopenClass({ 25 db: {}, 26 27 find: function( id ) { 28 return this.db[ id ]; 29 }, 30 31 all: function() { 32 return this.db; 33 } 34 35 }); GeekMeet #12, Cluj-Napoca, Transilvania 10 / 19 20 Octombrie, 2012
  45. app/model/article.js este modelul aplicației (clasă ActiveRecord dacă doriți) .property() sunt

    definește proprietăți computate (atribute dinamice) este valabil pentru oricare componentă din Ember.js init este constructorul (new, initialize, construct()) metodele clasei find face parte din convenție și este apelat de rută dacă e cazul all este la fel parte din convenție, apelat la fel de rută dacă e cazul 1 var Article = Ember.Object.extend({ 2 id: null, 3 title: null, 4 content: null, 5 6 // Handle Generation of the slug as a property 7 slug: function() { 8 var slug = this.get( ’title’ ).toLowerCase(); 9 return slug.replace( /\s+?/g, ’-’ ); 10 }.property( ’title’ ).cacheable(), 11 12 // Constructor 13 init: function() { 14 var id = Date.now(); 15 24 Article.reopenClass({ 25 db: {}, 26 27 find: function( id ) { 28 return this.db[ id ]; 29 }, 30 31 all: function() { 32 return this.db; 33 } 34 35 }); GeekMeet #12, Cluj-Napoca, Transilvania 10 / 19 20 Octombrie, 2012
  46. app/model/article.js este modelul aplicației (clasă ActiveRecord dacă doriți) .property() sunt

    definește proprietăți computate (atribute dinamice) este valabil pentru oricare componentă din Ember.js init este constructorul (new, initialize, construct()) metodele clasei find face parte din convenție și este apelat de rută dacă e cazul all este la fel parte din convenție, apelat la fel de rută dacă e cazul 1 var Article = Ember.Object.extend({ 2 id: null, 3 title: null, 4 content: null, 5 6 // Handle Generation of the slug as a property 7 slug: function() { 8 var slug = this.get( ’title’ ).toLowerCase(); 9 return slug.replace( /\s+?/g, ’-’ ); 10 }.property( ’title’ ).cacheable(), 11 12 // Constructor 13 init: function() { 14 var id = Date.now(); 15 24 Article.reopenClass({ 25 db: {}, 26 27 find: function( id ) { 28 return this.db[ id ]; 29 }, 30 31 all: function() { 32 return this.db; 33 } 34 35 }); GeekMeet #12, Cluj-Napoca, Transilvania 10 / 19 20 Octombrie, 2012
  47. app/model/article.js este modelul aplicației (clasă ActiveRecord dacă doriți) .property() sunt

    definește proprietăți computate (atribute dinamice) este valabil pentru oricare componentă din Ember.js init este constructorul (new, initialize, construct()) metodele clasei find face parte din convenție și este apelat de rută dacă e cazul all este la fel parte din convenție, apelat la fel de rută dacă e cazul 1 var Article = Ember.Object.extend({ 2 id: null, 3 title: null, 4 content: null, 5 6 // Handle Generation of the slug as a property 7 slug: function() { 8 var slug = this.get( ’title’ ).toLowerCase(); 9 return slug.replace( /\s+?/g, ’-’ ); 10 }.property( ’title’ ).cacheable(), 11 12 // Constructor 13 init: function() { 14 var id = Date.now(); 15 24 Article.reopenClass({ 25 db: {}, 26 27 find: function( id ) { 28 return this.db[ id ]; 29 }, 30 31 all: function() { 32 return this.db; 33 } 34 35 }); GeekMeet #12, Cluj-Napoca, Transilvania 10 / 19 20 Octombrie, 2012
  48. app/model/article.js este modelul aplicației (clasă ActiveRecord dacă doriți) .property() sunt

    definește proprietăți computate (atribute dinamice) este valabil pentru oricare componentă din Ember.js init este constructorul (new, initialize, construct()) metodele clasei find face parte din convenție și este apelat de rută dacă e cazul all este la fel parte din convenție, apelat la fel de rută dacă e cazul 1 var Article = Ember.Object.extend({ 2 id: null, 3 title: null, 4 content: null, 5 6 // Handle Generation of the slug as a property 7 slug: function() { 8 var slug = this.get( ’title’ ).toLowerCase(); 9 return slug.replace( /\s+?/g, ’-’ ); 10 }.property( ’title’ ).cacheable(), 11 12 // Constructor 13 init: function() { 14 var id = Date.now(); 15 24 Article.reopenClass({ 25 db: {}, 26 27 find: function( id ) { 28 return this.db[ id ]; 29 }, 30 31 all: function() { 32 return this.db; 33 } 34 35 }); GeekMeet #12, Cluj-Napoca, Transilvania 10 / 19 20 Octombrie, 2012
  49. app/model/article.js este modelul aplicației (clasă ActiveRecord dacă doriți) .property() sunt

    definește proprietăți computate (atribute dinamice) este valabil pentru oricare componentă din Ember.js init este constructorul (new, initialize, construct()) metodele clasei find face parte din convenție și este apelat de rută dacă e cazul all este la fel parte din convenție, apelat la fel de rută dacă e cazul 1 var Article = Ember.Object.extend({ 2 id: null, 3 title: null, 4 content: null, 5 6 // Handle Generation of the slug as a property 7 slug: function() { 8 var slug = this.get( ’title’ ).toLowerCase(); 9 return slug.replace( /\s+?/g, ’-’ ); 10 }.property( ’title’ ).cacheable(), 11 12 // Constructor 13 init: function() { 14 var id = Date.now(); 15 24 Article.reopenClass({ 25 db: {}, 26 27 find: function( id ) { 28 return this.db[ id ]; 29 }, 30 31 all: function() { 32 return this.db; 33 } 34 35 }); GeekMeet #12, Cluj-Napoca, Transilvania 10 / 19 20 Octombrie, 2012
  50. app/model/article.js este modelul aplicației (clasă ActiveRecord dacă doriți) .property() sunt

    definește proprietăți computate (atribute dinamice) este valabil pentru oricare componentă din Ember.js init este constructorul (new, initialize, construct()) metodele clasei find face parte din convenție și este apelat de rută dacă e cazul all este la fel parte din convenție, apelat la fel de rută dacă e cazul 1 var Article = Ember.Object.extend({ 2 id: null, 3 title: null, 4 content: null, 5 6 // Handle Generation of the slug as a property 7 slug: function() { 8 var slug = this.get( ’title’ ).toLowerCase(); 9 return slug.replace( /\s+?/g, ’-’ ); 10 }.property( ’title’ ).cacheable(), 11 12 // Constructor 13 init: function() { 14 var id = Date.now(); 15 24 Article.reopenClass({ 25 db: {}, 26 27 find: function( id ) { 28 return this.db[ id ]; 29 }, 30 31 all: function() { 32 return this.db; 33 } 34 35 }); GeekMeet #12, Cluj-Napoca, Transilvania 10 / 19 20 Octombrie, 2012
  51. app/controller/publish.js controllerele sunt simple, modelele ar trebui să fie baza

    controllerele conțin logica din view-uri, știu de modele și de rute 1 var PublishController = Ember.Controller.extend({ 2 publish: function() { 3 var article = App.Article.create({ 4 title: this.get( ’parentView.title’ ), 5 content: this.get( ’parentView.content’ ) 6 }); 7 8 this.set( ’parentView.title’, ’’ ); 9 this.set( ’parentView.content’, ’’ ); 10 11 this.get( ’controller.namespace.router’ ).transitionTo( 12 ’article’, article ); 13 }, 14 }); GeekMeet #12, Cluj-Napoca, Transilvania 11 / 19 20 Octombrie, 2012
  52. app/controller/publish.js controllerele sunt simple, modelele ar trebui să fie baza

    controllerele conțin logica din view-uri, știu de modele și de rute 1 var PublishController = Ember.Controller.extend({ 2 publish: function() { 3 var article = App.Article.create({ 4 title: this.get( ’parentView.title’ ), 5 content: this.get( ’parentView.content’ ) 6 }); 7 8 this.set( ’parentView.title’, ’’ ); 9 this.set( ’parentView.content’, ’’ ); 10 11 this.get( ’controller.namespace.router’ ).transitionTo( 12 ’article’, article ); 13 }, 14 }); GeekMeet #12, Cluj-Napoca, Transilvania 11 / 19 20 Octombrie, 2012
  53. app/view/publish.js poate conține mai multe șabloane/view-uri care pot fi diferite

    vezi childViews, Ember.TextField, Ember.TextArea, Ember.ContainerView, etc. orice atribut care se termină în Binding, reprezintă o referință la calea specificată este valabil pentru oricare componentă din Ember.js ideal, șabloanele generează HTML-ul șabloanele știu de evenimente, exemplu: click, insertNewline, etc. 1 var PublishView = Ember.ContainerView.extend({ 2 childViews: [ ’titleView’, ’contentView’, ’buttonView’ ], 3 titleBinding: ’titleView.value’, 4 contentBinding: ’contentView.value’, 5 6 isEmpty: function() { 7 var empty = false; 8 title = this.get( ’title’ ), 9 content = this.get( ’content’ ); 10 11 if ( title && title.trim() === ’’ && content && content.trim() === ’’ ) { 12 empty = true; 13 } 14 15 return empty; 16 }.property( ’title’, ’content’ ), GeekMeet #12, Cluj-Napoca, Transilvania 12 / 19 20 Octombrie, 2012
  54. app/view/publish.js poate conține mai multe șabloane/view-uri care pot fi diferite

    vezi childViews, Ember.TextField, Ember.TextArea, Ember.ContainerView, etc. orice atribut care se termină în Binding, reprezintă o referință la calea specificată este valabil pentru oricare componentă din Ember.js ideal, șabloanele generează HTML-ul șabloanele știu de evenimente, exemplu: click, insertNewline, etc. 1 var PublishView = Ember.ContainerView.extend({ 2 childViews: [ ’titleView’, ’contentView’, ’buttonView’ ], 3 titleBinding: ’titleView.value’, 4 contentBinding: ’contentView.value’, 5 6 isEmpty: function() { 7 var empty = false; 8 title = this.get( ’title’ ), 9 content = this.get( ’content’ ); 10 11 if ( title && title.trim() === ’’ && content && content.trim() === ’’ ) { 12 empty = true; 13 } 14 15 return empty; 16 }.property( ’title’, ’content’ ), GeekMeet #12, Cluj-Napoca, Transilvania 12 / 19 20 Octombrie, 2012
  55. app/view/publish.js poate conține mai multe șabloane/view-uri care pot fi diferite

    vezi childViews, Ember.TextField, Ember.TextArea, Ember.ContainerView, etc. orice atribut care se termină în Binding, reprezintă o referință la calea specificată este valabil pentru oricare componentă din Ember.js ideal, șabloanele generează HTML-ul șabloanele știu de evenimente, exemplu: click, insertNewline, etc. 1 var PublishView = Ember.ContainerView.extend({ 2 childViews: [ ’titleView’, ’contentView’, ’buttonView’ ], 3 titleBinding: ’titleView.value’, 4 contentBinding: ’contentView.value’, 5 6 isEmpty: function() { 7 var empty = false; 8 title = this.get( ’title’ ), 9 content = this.get( ’content’ ); 10 11 if ( title && title.trim() === ’’ && content && content.trim() === ’’ ) { 12 empty = true; 13 } 14 15 return empty; 16 }.property( ’title’, ’content’ ), GeekMeet #12, Cluj-Napoca, Transilvania 12 / 19 20 Octombrie, 2012
  56. app/view/publish.js poate conține mai multe șabloane/view-uri care pot fi diferite

    vezi childViews, Ember.TextField, Ember.TextArea, Ember.ContainerView, etc. orice atribut care se termină în Binding, reprezintă o referință la calea specificată este valabil pentru oricare componentă din Ember.js ideal, șabloanele generează HTML-ul șabloanele știu de evenimente, exemplu: click, insertNewline, etc. 1 var PublishView = Ember.ContainerView.extend({ 2 childViews: [ ’titleView’, ’contentView’, ’buttonView’ ], 3 titleBinding: ’titleView.value’, 4 contentBinding: ’contentView.value’, 5 6 isEmpty: function() { 7 var empty = false; 8 title = this.get( ’title’ ), 9 content = this.get( ’content’ ); 10 11 if ( title && title.trim() === ’’ && content && content.trim() === ’’ ) { 12 empty = true; 13 } 14 15 return empty; 16 }.property( ’title’, ’content’ ), GeekMeet #12, Cluj-Napoca, Transilvania 12 / 19 20 Octombrie, 2012
  57. app/view/publish.js poate conține mai multe șabloane/view-uri care pot fi diferite

    vezi childViews, Ember.TextField, Ember.TextArea, Ember.ContainerView, etc. orice atribut care se termină în Binding, reprezintă o referință la calea specificată este valabil pentru oricare componentă din Ember.js ideal, șabloanele generează HTML-ul șabloanele știu de evenimente, exemplu: click, insertNewline, etc. 1 var PublishView = Ember.ContainerView.extend({ 2 childViews: [ ’titleView’, ’contentView’, ’buttonView’ ], 3 titleBinding: ’titleView.value’, 4 contentBinding: ’contentView.value’, 5 6 isEmpty: function() { 7 var empty = false; 8 title = this.get( ’title’ ), 9 content = this.get( ’content’ ); 10 11 if ( title && title.trim() === ’’ && content && content.trim() === ’’ ) { 12 empty = true; 13 } 14 15 return empty; 16 }.property( ’title’, ’content’ ), GeekMeet #12, Cluj-Napoca, Transilvania 12 / 19 20 Octombrie, 2012
  58. app/view/publish.js poate conține mai multe șabloane/view-uri care pot fi diferite

    vezi childViews, Ember.TextField, Ember.TextArea, Ember.ContainerView, etc. orice atribut care se termină în Binding, reprezintă o referință la calea specificată este valabil pentru oricare componentă din Ember.js ideal, șabloanele generează HTML-ul șabloanele știu de evenimente, exemplu: click, insertNewline, etc. 1 var PublishView = Ember.ContainerView.extend({ 2 childViews: [ ’titleView’, ’contentView’, ’buttonView’ ], 3 titleBinding: ’titleView.value’, 4 contentBinding: ’contentView.value’, 5 6 isEmpty: function() { 7 var empty = false; 8 title = this.get( ’title’ ), 9 content = this.get( ’content’ ); 10 11 if ( title && title.trim() === ’’ && content && content.trim() === ’’ ) { 12 empty = true; 13 } 14 15 return empty; 16 }.property( ’title’, ’content’ ), GeekMeet #12, Cluj-Napoca, Transilvania 12 / 19 20 Octombrie, 2012
  59. app/view/article.js este un șablon care folosește un cod HTML predefinit

    (template) orice șablon poate folosi cod cu sintaxa Handlebars.js poate avea și un layout poate folosi helpere, exemplu: action, log, bindAttr, etc. 1 var ArticleView = Ember.View.extend({ 2 template: Ember.Handlebars.compile( 3 ’<h3>{{content.title}}</h3><p>{{content.content}}</p>’ + 4 ’<a href=”#” {{action goToPublish href=true}}>Publish more!</a>’ 5 ) GeekMeet #12, Cluj-Napoca, Transilvania 13 / 19 20 Octombrie, 2012
  60. app/view/article.js este un șablon care folosește un cod HTML predefinit

    (template) orice șablon poate folosi cod cu sintaxa Handlebars.js poate avea și un layout poate folosi helpere, exemplu: action, log, bindAttr, etc. 1 var ArticleView = Ember.View.extend({ 2 template: Ember.Handlebars.compile( 3 ’<h3>{{content.title}}</h3><p>{{content.content}}</p>’ + 4 ’<a href=”#” {{action goToPublish href=true}}>Publish more!</a>’ 5 ) GeekMeet #12, Cluj-Napoca, Transilvania 13 / 19 20 Octombrie, 2012
  61. app/view/article.js este un șablon care folosește un cod HTML predefinit

    (template) orice șablon poate folosi cod cu sintaxa Handlebars.js poate avea și un layout poate folosi helpere, exemplu: action, log, bindAttr, etc. 1 var ArticleView = Ember.View.extend({ 2 template: Ember.Handlebars.compile( 3 ’<h3>{{content.title}}</h3><p>{{content.content}}</p>’ + 4 ’<a href=”#” {{action goToPublish href=true}}>Publish more!</a>’ 5 ) GeekMeet #12, Cluj-Napoca, Transilvania 13 / 19 20 Octombrie, 2012
  62. app/view/article.js este un șablon care folosește un cod HTML predefinit

    (template) orice șablon poate folosi cod cu sintaxa Handlebars.js poate avea și un layout poate folosi helpere, exemplu: action, log, bindAttr, etc. 1 var ArticleView = Ember.View.extend({ 2 template: Ember.Handlebars.compile( 3 ’<h3>{{content.title}}</h3><p>{{content.content}}</p>’ + 4 ’<a href=”#” {{action goToPublish href=true}}>Publish more!</a>’ 5 ) GeekMeet #12, Cluj-Napoca, Transilvania 13 / 19 20 Octombrie, 2012
  63. A scrie teste pe JavaScript e mai simplu decât în

    orice alt limbaj de programare (discutabil Ruby), deci nu ai scuze pentru a nu scrie teste! GeekMeet #12, Cluj-Napoca, Transilvania 14 / 19 20 Octombrie, 2012
  64. Testarea aplicației prefer Jasmine BDD pentru că . . .

    este doar o librărie, nu zece! pentru cowboys, vezi alte opțiuni tot timpul folosiți unelte pentru a câștiga timp, in cazul nostru, Phantom.js testele de browser pot fi rulate pragmatic pentru a câștiga timp pentru a întreține proiectul sănătos, folosiți un serviciu de CI (integrare continuă) exemplu: guard-phantomjs-jasmine http://pivotal.github.com/jasmine/ GeekMeet #12, Cluj-Napoca, Transilvania 15 / 19 20 Octombrie, 2012
  65. Testarea aplicației prefer Jasmine BDD pentru că . . .

    este doar o librărie, nu zece! pentru cowboys, vezi alte opțiuni tot timpul folosiți unelte pentru a câștiga timp, in cazul nostru, Phantom.js testele de browser pot fi rulate pragmatic pentru a câștiga timp pentru a întreține proiectul sănătos, folosiți un serviciu de CI (integrare continuă) exemplu: guard-phantomjs-jasmine http://pivotal.github.com/jasmine/ GeekMeet #12, Cluj-Napoca, Transilvania 15 / 19 20 Octombrie, 2012
  66. Testarea aplicației prefer Jasmine BDD pentru că . . .

    este doar o librărie, nu zece! pentru cowboys, vezi alte opțiuni tot timpul folosiți unelte pentru a câștiga timp, in cazul nostru, Phantom.js testele de browser pot fi rulate pragmatic pentru a câștiga timp pentru a întreține proiectul sănătos, folosiți un serviciu de CI (integrare continuă) exemplu: guard-phantomjs-jasmine http://pivotal.github.com/jasmine/ GeekMeet #12, Cluj-Napoca, Transilvania 15 / 19 20 Octombrie, 2012
  67. Testarea aplicației prefer Jasmine BDD pentru că . . .

    este doar o librărie, nu zece! pentru cowboys, vezi alte opțiuni tot timpul folosiți unelte pentru a câștiga timp, in cazul nostru, Phantom.js testele de browser pot fi rulate pragmatic pentru a câștiga timp pentru a întreține proiectul sănătos, folosiți un serviciu de CI (integrare continuă) exemplu: guard-phantomjs-jasmine http://pivotal.github.com/jasmine/ GeekMeet #12, Cluj-Napoca, Transilvania 15 / 19 20 Octombrie, 2012
  68. Testarea aplicației prefer Jasmine BDD pentru că . . .

    este doar o librărie, nu zece! pentru cowboys, vezi alte opțiuni tot timpul folosiți unelte pentru a câștiga timp, in cazul nostru, Phantom.js testele de browser pot fi rulate pragmatic pentru a câștiga timp pentru a întreține proiectul sănătos, folosiți un serviciu de CI (integrare continuă) exemplu: guard-phantomjs-jasmine http://pivotal.github.com/jasmine/ GeekMeet #12, Cluj-Napoca, Transilvania 15 / 19 20 Octombrie, 2012
  69. Testarea aplicației prefer Jasmine BDD pentru că . . .

    este doar o librărie, nu zece! pentru cowboys, vezi alte opțiuni tot timpul folosiți unelte pentru a câștiga timp, in cazul nostru, Phantom.js testele de browser pot fi rulate pragmatic pentru a câștiga timp pentru a întreține proiectul sănătos, folosiți un serviciu de CI (integrare continuă) exemplu: guard-phantomjs-jasmine http://pivotal.github.com/jasmine/ GeekMeet #12, Cluj-Napoca, Transilvania 15 / 19 20 Octombrie, 2012
  70. Unit teste pentru modele describe pentru a specifica ce testezi

    it pentru a descrie testul beforeEach pentru a nu te repeta folosește mai multe describe-uri pentru a specifica schimbările de context exemplu, testezi metodele clasei și nu a instanței 1 describe( ’Article’, function() { 2 3 describe( ’initialization with no values’, function() { 4 var article; 5 6 beforeEach( function() { 7 article = App.Article.create(); 8 } ); 9 10 it( ’has a set of default attributes’, function() { 11 expect( article.get( ’id’ ) ).toMatch( /\d+/ ); 12 expect( article.get( ’title’ ) ).toBeNull(); 13 expect( article.get( ’content’ ) ).toBeNull(); 14 }); 15 16 it( ’throws an exception when calling slug’, function() { 17 expect( function(){ article.get( ’slug’ ) } ).toThrow(); 18 }); 19 20 } ); 21 GeekMeet #12, Cluj-Napoca, Transilvania 16 / 19 20 Octombrie, 2012
  71. Unit teste pentru modele describe pentru a specifica ce testezi

    it pentru a descrie testul beforeEach pentru a nu te repeta folosește mai multe describe-uri pentru a specifica schimbările de context exemplu, testezi metodele clasei și nu a instanței 1 describe( ’Article’, function() { 2 3 describe( ’initialization with no values’, function() { 4 var article; 5 6 beforeEach( function() { 7 article = App.Article.create(); 8 } ); 9 10 it( ’has a set of default attributes’, function() { 11 expect( article.get( ’id’ ) ).toMatch( /\d+/ ); 12 expect( article.get( ’title’ ) ).toBeNull(); 13 expect( article.get( ’content’ ) ).toBeNull(); 14 }); 15 16 it( ’throws an exception when calling slug’, function() { 17 expect( function(){ article.get( ’slug’ ) } ).toThrow(); 18 }); 19 20 } ); 21 GeekMeet #12, Cluj-Napoca, Transilvania 16 / 19 20 Octombrie, 2012
  72. Unit teste pentru modele describe pentru a specifica ce testezi

    it pentru a descrie testul beforeEach pentru a nu te repeta folosește mai multe describe-uri pentru a specifica schimbările de context exemplu, testezi metodele clasei și nu a instanței 1 describe( ’Article’, function() { 2 3 describe( ’initialization with no values’, function() { 4 var article; 5 6 beforeEach( function() { 7 article = App.Article.create(); 8 } ); 9 10 it( ’has a set of default attributes’, function() { 11 expect( article.get( ’id’ ) ).toMatch( /\d+/ ); 12 expect( article.get( ’title’ ) ).toBeNull(); 13 expect( article.get( ’content’ ) ).toBeNull(); 14 }); 15 16 it( ’throws an exception when calling slug’, function() { 17 expect( function(){ article.get( ’slug’ ) } ).toThrow(); 18 }); 19 20 } ); 21 GeekMeet #12, Cluj-Napoca, Transilvania 16 / 19 20 Octombrie, 2012
  73. Unit teste pentru modele describe pentru a specifica ce testezi

    it pentru a descrie testul beforeEach pentru a nu te repeta folosește mai multe describe-uri pentru a specifica schimbările de context exemplu, testezi metodele clasei și nu a instanței 1 describe( ’Article’, function() { 2 3 describe( ’initialization with no values’, function() { 4 var article; 5 6 beforeEach( function() { 7 article = App.Article.create(); 8 } ); 9 10 it( ’has a set of default attributes’, function() { 11 expect( article.get( ’id’ ) ).toMatch( /\d+/ ); 12 expect( article.get( ’title’ ) ).toBeNull(); 13 expect( article.get( ’content’ ) ).toBeNull(); 14 }); 15 16 it( ’throws an exception when calling slug’, function() { 17 expect( function(){ article.get( ’slug’ ) } ).toThrow(); 18 }); 19 20 } ); 21 GeekMeet #12, Cluj-Napoca, Transilvania 16 / 19 20 Octombrie, 2012
  74. Unit teste pentru modele describe pentru a specifica ce testezi

    it pentru a descrie testul beforeEach pentru a nu te repeta folosește mai multe describe-uri pentru a specifica schimbările de context exemplu, testezi metodele clasei și nu a instanței 1 describe( ’Article’, function() { 2 3 describe( ’initialization with no values’, function() { 4 var article; 5 6 beforeEach( function() { 7 article = App.Article.create(); 8 } ); 9 10 it( ’has a set of default attributes’, function() { 11 expect( article.get( ’id’ ) ).toMatch( /\d+/ ); 12 expect( article.get( ’title’ ) ).toBeNull(); 13 expect( article.get( ’content’ ) ).toBeNull(); 14 }); 15 16 it( ’throws an exception when calling slug’, function() { 17 expect( function(){ article.get( ’slug’ ) } ).toThrow(); 18 }); 19 20 } ); 21 GeekMeet #12, Cluj-Napoca, Transilvania 16 / 19 20 Octombrie, 2012
  75. Teste de integrare pentru controllere folosește spy-uri (șpioni) sau altfel

    numiți stub, mocks pentru a izola testele dacă Jasmine nu are suport pentru așteptările (expectations) unui test, rescrie testul 1 beforeEach( function() { 2 view = App.PublishView.create(); 3 controller = App.PublishController.create({ 4 namespace: Ember.Object.create({ 5 router: Ember.Object.create({}) 6 }) 7 }); 8 9 view.set( ’controller’, controller ); 10 } ); 11 12 it( ’handles article publication’, function() { 13 var transitionSpy = jasmine.createSpy(); 14 var count = Object.keys( App.Article.all() ).length; 15 var newCount; 16 17 controller.set( ’namespace.router.transitionTo’, transitionSpy ); 18 view.set( ’title’, ’A title’ ); 19 view.set( ’content’, ’Some content...’ ); 20 21 controller.publish.apply( view.get( ’buttonView’ ) ); 22 23 newCount = Object.keys( App.Article.all() ).length; 24 expect( transitionSpy ).toHaveBeenCalled(); GeekMeet #12, Cluj-Napoca, Transilvania 17 / 19 20 Octombrie, 2012
  76. Teste de integrare pentru controllere folosește spy-uri (șpioni) sau altfel

    numiți stub, mocks pentru a izola testele dacă Jasmine nu are suport pentru așteptările (expectations) unui test, rescrie testul 1 beforeEach( function() { 2 view = App.PublishView.create(); 3 controller = App.PublishController.create({ 4 namespace: Ember.Object.create({ 5 router: Ember.Object.create({}) 6 }) 7 }); 8 9 view.set( ’controller’, controller ); 10 } ); 11 12 it( ’handles article publication’, function() { 13 var transitionSpy = jasmine.createSpy(); 14 var count = Object.keys( App.Article.all() ).length; 15 var newCount; 16 17 controller.set( ’namespace.router.transitionTo’, transitionSpy ); 18 view.set( ’title’, ’A title’ ); 19 view.set( ’content’, ’Some content...’ ); 20 21 controller.publish.apply( view.get( ’buttonView’ ) ); 22 23 newCount = Object.keys( App.Article.all() ).length; 24 expect( transitionSpy ).toHaveBeenCalled(); GeekMeet #12, Cluj-Napoca, Transilvania 17 / 19 20 Octombrie, 2012
  77. Legături utile TodoMVC – https://github.com/addyosmani/todomvc/ Ember.js – http://emberjs.com/ Jasmine BDD

    – http://pivotal.github.com/jasmine/ Idiomatic.js – https://github.com/rwldrn/idiomatic.js/ Phantom.js – http://phantomjs.org/ guard-phantomjs-jasmine – https://github.com/stas/guard-phantomjs-jasmine https: //github.com/stas/emberjs-jasmine-slides-geekmeet GeekMeet #12, Cluj-Napoca, Transilvania 19 / 19 20 Octombrie, 2012