Showing microposts Although we don’t yet have a way to create microposts through the web - that comes in “Manipulating microposts Section” - this won’t stop us from displaying them (and testing that display).
Rendering microposts Our plan is to display the microposts for each user on their respective profile page, together with a running count of how many microposts they’ve made. As we’ll see, many of the ideas are similar to our work in “Showing all users Section” on showing all users.
public/partials/microposts/_micropost.html
<a href ui-sref= "user_detail({id: micropost.user.id})" ui-sref-opts= "{reload: true}" >
<img class= "gravatar" gravatar_for= "{{ micropost.user.email }}" alt= "{{ micropost.user.name }}" options-size= "50" />
</a>
<span class= "user" >
<a href ui-sref= "user_detail({id: micropost.user.id})" ui-sref-opts= "{reload: true}" >
{{ micropost.user.name }}
</a>
</span>
<span class= "content" > {{ micropost.content }}</span>
<span time-ago class= "timestamp" >
Posted {{ time_ago_in_words(micropost.createdAt) }}.
</span>
This uses the timeAgo
module, whose meaning is probably clear and whose effect we will see in the next section.
~/sample_app $ npm install time-ago --save
public/index.html
...
<script src= "../node_modules/pluralize/pluralize.js" ></script>
<script src= "../node_modules/time-ago/timeago.js" ></script>
...
<script src= "directives/message.js" ></script>
<script src= "directives/time.js" ></script>
...
public/app.js
'use strict' ;
var sampleApp = angular . module ( 'sampleApp' , [
'timeDirective' ,
...
]);
...
public/directives/time.js
var timeDirective = angular . module ( 'timeDirective' , []);
timeDirective . directive ( 'timeAgo' , function () {
return {
restrict : 'A' ,
link : function ( scope , elem , attrs ) {
scope . time_ago_in_words = function ( input ) {
return timeago (). ago ( input );
};
}
};
});
Adding an microposts instance variable to the user show
app/controllers/users_controller.js
var sessionHelper = require ( '../helpers/sessions_helper.js' );
function UsersController () {
...
this . show = function ( req , res , next ) {
var offset = ( req . query . page - 1 ) * req . query . limit ;
var user = ModelSync ( User . findById ( req . params . id ) );
var microposts = ModelSync ( Micropost . findAndCountAll ({
where : { user_id : user . id },
include : [ { model : User } ],
order : 'micropost.createdAt DESC' ,
offset : offset ,
limit : req . query . limit
}) );
res . end ( JSON . stringify ({
id : user . id ,
email : user . email ,
name : user . name ,
microposts : microposts
}));
};
...
}
module . exports = UsersController ;
Adding microposts to the user show page.
public/app.js
...
sampleApp . config ([ '$stateProvider' , '$urlRouterProvider' , function ( $stateProvider , $urlRouterProvider ) {
$urlRouterProvider . otherwise ( '/home' );
$stateProvider
...
. state ( 'user_detail' , {
url : '/users/:id?page&limit' ,
templateUrl : 'partials/users/show.html' ,
resolve : {
current_user : current_user ,
user : [ '$q' , '$stateParams' , 'User' , function ( $q , $stateParams , User ){
$stateParams . page = $stateParams . page ? $stateParams . page : 1 ;
$stateParams . limit = $stateParams . limit ? $stateParams . limit : 30 ;
var deferred = $q . defer ();
User . get ({ id : $stateParams . id , page : $stateParams . page , limit : $stateParams . limit }, function ( user ) {
deferred . resolve ( user );
}, function ( error ) {
deferred . reject ();
});
return deferred . promise ;
}]
},
controller : 'UsersDetailCtrl'
})
...
}]);
...
public/partials/users/show.html
<div class= "row" >
<aside class= "col-md-4" >
<section class= "user_info" >
<h1>
<img gravatar_for= "{{ user.email }}" alt= "{{ user.name }}" />
{{ user.name }}
</h1>
</section>
</aside>
<div class= "col-md-8" ng-if= "user.microposts.count" >
<h3> Microposts ({{ user.microposts.count }})</h3>
<ol class= "microposts" >
<li ng-repeat= "micropost in user.microposts.rows" id= "micropost-" ng-include= "'partials/microposts/_micropost.html'" ></li>
</ol>
<uib-pagination total-items= "totalItems" ng-model= "currentPage" ng-change= "pageChanged()" max-size= "5" class= "pagination-sm" boundary-link-numbers= "true" rotate= "false" items-per-page= "itemsPerPage" ></uib-pagination>
</div>
</div>
public/controllers/users_controller.js
...
usersController . controller (
'UsersDetailCtrl' ,
[ '$scope' , '$rootScope' , 'user' , '$state' , '$stateParams' , function ( $scope , $rootScope , user , $state , $stateParams ) {
$rootScope . provide_title = user . name ;
$scope . user = user ;
$scope . totalItems = user . microposts . count ;
}]
);
...
Sample microposts Adding sample microposts for all the users actually takes a rather long time, so first we’ll select just the first six users
db/seeds.js
require ( 'trainjs' ). initServer ();
var faker = require ( 'faker' );
var time = 1 ;
function createUser () {
var name = faker . name . findName ();
var email = 'example-' + time + '@railstutorial.org' ;
var password = 'password' ;
User . create ({
name : name ,
email : email ,
password : password ,
password_confirmation : password ,
activated : true ,
activated_at : new Date (). getTime ()
}). then ( function ( user_example ) {
if ( time < 98 ) {
if ( time <= 4 ) {
createMicroposts ( user_example . id );
}
time ++ ;
createUser ();
}
});
}
function createMicroposts ( user_id ) {
for ( var i = 0 ; i < 50 ; i ++ ) {
var content = faker . lorem . sentence ();
Micropost . create ({
content : content ,
user_id : user_id
}). then ( function () {
});
}
}
User . create ({
name : "Example User" ,
email : "[email protected] " ,
password : "123456" ,
password_confirmation : "123456" ,
admin : true ,
activated : true ,
activated_at : new Date (). getTime ()
}). then ( function ( user1 ) {
createMicroposts ( user1 . id );
User . create ({
name : "Example User" ,
email : "[email protected] " ,
password : "password" ,
password_confirmation : "password" ,
activated : true ,
activated_at : new Date (). getTime ()
}). then ( function ( user2 ) {
createMicroposts ( user2 . id );
createUser ();
});
});
At this point, we can reseed the development database as usual
~/sample_app $ rm -f db/development.sqlite3
~/sample_app $ sequelize db:migrate
~/sample_app $ node db/seeds.js
The CSS for microposts
public/assets/stylesheets/custom.css
/* microposts */
.microposts {
list-style : none ;
padding : 0 ;
}
.microposts li {
padding : 10px 0 ;
border-top : 1px solid #e8e8e8 ;
}
.microposts .user {
margin-top : 5em ;
padding-top : 0 ;
}
.microposts .content {
display : block ;
margin-left : 60px ;
}
.microposts .content img {
display : block ;
padding : 5px 0 ;
}
.microposts .timestamp {
color : #999 ;
display : block ;
margin-left : 60px ;
}
.microposts .gravatar {
float : left ;
margin-right : 10px ;
margin-top : 5px ;
}
aside textarea {
height : 100px ;
margin-bottom : 5px ;
}
span .picture {
margin-top : 10px ;
}
span .picture input {
border : 0 ;
}
Profile micropost tests Because newly activated users get redirected to their profile pages, we already have a test that the profile page renders correctly. In this section, we’ll write a short integration test for some of the other elements on the profile page, including the work from this section.
public/test/e2e_test/integration/users_profile_test.js
describe ( 'UsersProfileTest' , function () {
it ( 'profile display' , function ( done ) {
browser . get ( 'http://localhost:1337/#/login' );
var profile = function () {
browser . get ( 'http://localhost:1337/#/login' );
element ( by . css ( '[name="email"]' )). clear ( '' );
element ( by . css ( '[name="password"]' )). clear ( '' );
element ( by . css ( '[name="email"]' )). sendKeys ( '[email protected] ' );
element ( by . css ( '[name="password"]' )). sendKeys ( 'password' );
element ( by . css ( '[name="commit"]' )). click ();
expect ( browser . getTitle ()). toEqual ( 'Example User | Node On Train Tutorial Sample App' );
expect ( element ( by . css ( '.user_info > h1' )). getText () ). toEqual ( 'Example User' );
expect ( element ( by . css ( '.user_info > h1 > img[gravatar_for]' )). isDisplayed () ). toBeTruthy ();
expect ( element ( by . css ( '[ng-if="user.microposts.count"] > h3' )). getText () ). toContain ( '50' );
expect ( element . all ( by . css ( '.pagination-page' )). count () ). toBeGreaterThan ( 0 );
done ();
};
element . all ( by . css ( '[ui-sref="login"]' )). isDisplayed (). then ( function ( result ) {
if ( result . length > 0 ) {
profile ();
} else {
element ( by . css ( '.dropdown' )). click ();
element ( by . css ( '[ui-sref="logout"]' )). click ();
profile ();
}
});
})
})
Because the application code was working, the test suite should be successful
~/sample_app $ protractor protractor.conf.js
29 specs, 0 failures