49 | );
50 | }
51 | });
--------------------------------------------------------------------------------
/client/templates/posts/post_list.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This component renders a list of posts. Get all post and for each of them pass
3 | * the props in PostItem decodeURIComponent
4 | *
5 | * Render
6 | * PostItem
7 | */
8 | PostList = React.createClass({
9 | render () {
10 | // Iterate through all posts and create a post item for each of them
11 | let posts = this.props.allPosts.map(function (post) {
12 | return
20 | });
21 | return (
22 |
78 | )
79 | }
80 |
81 | }
82 | });
--------------------------------------------------------------------------------
/client/templates/posts/post_submit.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This component renders a post input form.
3 | * Props
4 | * title String The title of the input field
5 | * placeholder String The placeholder of the input
6 | * errorclassName
7 | * errorMessage
8 | */
9 | PostInput = React.createClass({
10 | propTypes: {
11 | title: React.PropTypes.string.isRequired,
12 | placeholder: React.PropTypes.string.isRequired
13 | },
14 | render () {
15 | const errorClassName = "form-group " + this.props.errorClassName;
16 | const inputClass = "form-control "+this.props.title.toLowerCase();
17 | return (
18 |
19 |
20 |
21 |
22 |
30 | {this.props.errorMessage}
31 |
32 |
33 | );
34 | }
35 | });
36 |
37 | /**
38 | * This component renders a form of two input and a button to submit a post
39 | *
40 | */
41 | PostSubmit = React.createClass({
42 | mixins: [ReactMeteorData],
43 | getMeteorData () {
44 | return {
45 | userIsLogged: Meteor.userId()
46 | }
47 | },
48 | // We set 4 different variables 2 for each input
49 | // The one is for the message and the other for the css class
50 | getInitialState () {
51 | return {
52 | errorsTitle: "",
53 | errorsTitleClass: "",
54 | errorsUrl: "",
55 | errorsUrlClass: "",
56 | titleValue: "",
57 | urlValue: ""
58 | }
59 | },
60 | formSubmission (event) {
61 | event.preventDefault();
62 |
63 | // We get the values from inputs
64 | var post = {
65 | url: event.target.url.value,
66 | title: event.target.title.value
67 | };
68 | // We check if all inputs have values
69 | var fieldsErrors = validatePost(post);
70 | // If we didn't fill any of the inputs then we return a message and an error class
71 | if (!_.isEmpty(fieldsErrors)) {
72 | if (fieldsErrors.title) {
73 | this.setState({
74 | errorsTitle: fieldsErrors.title,
75 | errorsTitleClass: "has-error"
76 | });
77 | } else {
78 | this.setState({
79 | errorsTitle: "",
80 | errorsTitleClass: ""
81 | });
82 | }
83 | if (fieldsErrors.url) {
84 | this.setState({
85 | errorsUrl: fieldsErrors.url,
86 | errorsUrlClass: "has-error"
87 | });
88 | } else {
89 | this.setState({
90 | errorsUrl: "",
91 | errorsUrlClass: ""
92 | });
93 | }
94 | return;
95 | }
96 | Meteor.call('postInsert', post, function (error, result) {
97 | // display the error to the user and abort
98 | if (error)
99 | return throwError(error.reason);
100 |
101 | // show this result but route anyway
102 | if (result.postExists)
103 | throwError('This link has already been posted');
104 |
105 | FlowRouter.go(`/posts/${ result._id }`);
106 | });
107 | },
108 | render () {
109 | if (this.data.userIsLogged) {
110 | return (
111 | /**
112 | * We change onSubmit
113 | * from
114 | * onSubmit = {this.formSubmission}
115 | * to
116 | * onSubmit = {(e) => this.formSubmission(e)}
117 | * for unit test events
118 | * More details on this stackoverflow post
119 | * http://stackoverflow.com/questions/26470679/test-a-form-with-jest-and-react-js-testutils
120 | */
121 |
138 | );
139 | } else {
140 | return (
141 |
142 | );
143 | }
144 |
145 | }
146 | });
--------------------------------------------------------------------------------
/lib/collections/comments.jsx:
--------------------------------------------------------------------------------
1 | Comments = new Mongo.Collection("comments");
2 |
3 | Meteor.methods({
4 | commentInsert (commentAttributes) {
5 | check(this.userId, String);
6 | check(commentAttributes, {
7 | postId: String,
8 | body: String
9 | });
10 |
11 | var user = Meteor.user();
12 | var post = Posts.findOne(commentAttributes.postId);
13 |
14 | if (!post)
15 | throw new Meteor.Error('invalid-comment', 'You must comment on a post');
16 |
17 | comment = _.extend(commentAttributes, {
18 | userId: user._id,
19 | author: user.username,
20 | submitted: new Date()
21 | });
22 |
23 | // update the post with the number of comments
24 | Posts.update(comment.postId, {$inc: {commentsCount: 1}});
25 |
26 | return Comments.insert(comment);
27 | }
28 | });
--------------------------------------------------------------------------------
/lib/collections/post.jsx:
--------------------------------------------------------------------------------
1 | Posts = new Mongo.Collection("posts");
2 |
3 | Posts.allow({
4 | update (userId, post) { return ownsDocument(userId, post); },
5 | remove (userId, post) { return ownsDocument(userId, post); },
6 | });
7 |
8 | validatePost = function (post) {
9 | var errors = {};
10 |
11 | if (!post.title)
12 | errors.title = "Please fill in a headline";
13 |
14 | if (!post.url)
15 | errors.url = "Please fill in a URL";
16 |
17 | return errors;
18 | }
19 |
20 | Meteor.methods({
21 | postInsert (postAttributes) {
22 | check(this.userId, String);
23 | check(postAttributes, {
24 | title: String,
25 | url: String
26 | });
27 |
28 | var errors = validatePost(postAttributes);
29 | if (errors.title || errors.url)
30 | throw new Meteor.Error('invalid-post', "You must set a title and URL for your post");
31 |
32 | var postWithSameLink = Posts.findOne({url: postAttributes.url});
33 | if (postWithSameLink) {
34 | return {
35 | postExists: true,
36 | _id: postWithSameLink._id
37 | }
38 | }
39 |
40 | var user = Meteor.user();
41 | var post = _.extend(postAttributes, {
42 | userId: user._id,
43 | author: user.username,
44 | submitted: new Date(),
45 | commentsCount: 0
46 | });
47 |
48 | var postId = Posts.insert(post);
49 |
50 | return {
51 | _id: postId
52 | };
53 | }
54 | });
--------------------------------------------------------------------------------
/lib/permissions.js:
--------------------------------------------------------------------------------
1 | // check that the userId specified owns the documents
2 | ownsDocument = (userId, doc) => {
3 | return doc && doc.userId === userId;
4 | }
--------------------------------------------------------------------------------
/lib/routes/routers.jsx:
--------------------------------------------------------------------------------
1 | FlowRouter.route('/', {
2 | name: "postsList",
3 | subscriptions (params) {
4 | this.register("posts", Meteor.subscribe("posts"));
5 | },
6 | action () {
7 | React.render(, document.getElementById("yield-section"));
8 | }
9 | });
10 |
11 | FlowRouter.route('/posts/:_id', {
12 | name: "postPage",
13 | subscriptions (params) {
14 | this.register("post", Meteor.subscribe("post", params._id));
15 | this.register("comments", Meteor.subscribe("comments", params._id));
16 | },
17 | action (params) {
18 | React.render(, document.getElementById("yield-section"));
19 | }
20 | });
21 |
22 | FlowRouter.route('/posts/:_id/edit', {
23 | name: "postEdit",
24 | subscriptions (params) {
25 | this.register("post", Meteor.subscribe("post", params._id));
26 | },
27 | action (params) {
28 | React.render(, document.getElementById("yield-section"))
29 | }
30 | });
31 |
32 | FlowRouter.route('/submit', {
33 | name: "postSubmit",
34 | action (params) {
35 | React.render(, document.getElementById("yield-section"));
36 | }
37 | });
38 |
39 | FlowRouter.route('/authentication', {
40 | name: "authentication",
41 | action (params) {
42 | React.render(, document.getElementById("yield-section"));
43 | }
44 | });
45 |
46 | FlowRouter.notFound = {
47 | action () {
48 | React.render(, document.getElementById("yield-section"));
49 | }
50 | };
--------------------------------------------------------------------------------
/server/fixtures.js:
--------------------------------------------------------------------------------
1 | // Fixture data
2 | if (Posts.find().count() === 0) {
3 | var now = new Date().getTime();
4 |
5 | // create two users
6 | var tomId = Meteor.users.insert({
7 | profile: { name: 'Tom Coleman' }
8 | });
9 | var tom = Meteor.users.findOne(tomId);
10 | var sachaId = Meteor.users.insert({
11 | profile: { name: 'Sacha Greif' }
12 | });
13 | var sacha = Meteor.users.findOne(sachaId);
14 |
15 | var telescopeId = Posts.insert({
16 | title: 'Introducing Telescope',
17 | userId: sacha._id,
18 | author: sacha.profile.name,
19 | url: 'http://sachagreif.com/introducing-telescope/',
20 | submitted: new Date(now - 7 * 3600 * 1000),
21 | commentsCount: 2
22 | });
23 |
24 | Comments.insert({
25 | postId: telescopeId,
26 | userId: tom._id,
27 | author: tom.profile.name,
28 | submitted: new Date(now - 5 * 3600 * 1000),
29 | body: 'Interesting project Sacha, can I get involved?'
30 | });
31 |
32 | Comments.insert({
33 | postId: telescopeId,
34 | userId: sacha._id,
35 | author: sacha.profile.name,
36 | submitted: new Date(now - 3 * 3600 * 1000),
37 | body: 'You sure can Tom!'
38 | });
39 |
40 | Posts.insert({
41 | title: 'Meteor',
42 | userId: tom._id,
43 | author: tom.profile.name,
44 | url: 'http://meteor.com',
45 | submitted: new Date(now - 10 * 3600 * 1000),
46 | commentsCount: 0
47 | });
48 |
49 | Posts.insert({
50 | title: 'The Meteor Book',
51 | userId: tom._id,
52 | author: tom.profile.name,
53 | url: 'http://themeteorbook.com',
54 | submitted: new Date(now - 12 * 3600 * 1000),
55 | commentsCount: 0
56 | });
57 | }
--------------------------------------------------------------------------------
/server/publications.js:
--------------------------------------------------------------------------------
1 | Meteor.publish("posts", function () {
2 | return Posts.find();
3 | });
4 | Meteor.publish("post", function (postId) {
5 | return Posts.find({_id:postId});
6 | });
7 | Meteor.publish('comments', function(postId) {
8 | check(postId, String);
9 | return Comments.find({postId: postId});
10 | });
--------------------------------------------------------------------------------
/server/userReg.js:
--------------------------------------------------------------------------------
1 | Meteor.methods({
2 | "registerUser": function (userData) {
3 | check(userData, {
4 | username: String,
5 | password: String,
6 | email: String
7 | });
8 | return Accounts.createUser(userData);
9 | }
10 | });
--------------------------------------------------------------------------------
/tests/cucumber/features/authentication/authentication.feature:
--------------------------------------------------------------------------------
1 | Feature: Allow user to login and logout
2 |
3 | As existing user of the Microscope
4 | I want to login and logout
5 | So that I can prove my identity and see personalized data
6 |
7 |
8 | Background:
9 | Given I am signed out
10 |
11 | @rerun
12 | Scenario: A user can login with valid information
13 | Given I am on the home page
14 | When I click on sign in link
15 | And I enter my authentication information
16 | Then I should be logged in
17 |
18 | @rerun
19 | Scenario: A user cannot login with invalid information
20 | Given I am on the home page
21 | When I click on sign in link
22 | And I enter my false authentication information
23 | Then I should see a user not found error
24 |
25 | @rerun
26 | Scenario: A user cannot login with invalid email address
27 | Given I am on the home page
28 | When I click on sign in link
29 | And I enter my invalid email address
30 | Then I should see an invalid email error message
31 |
32 | @rerun
33 | Scenario: A user cannot login with invalid password
34 | Given I am on the home page
35 | When I click on sign in link
36 | And I enter my invalid password
37 | Then I should see an incorrect password error message
38 |
--------------------------------------------------------------------------------
/tests/cucumber/features/authentication/authentication.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | module.exports = function () {
5 |
6 | var actual, expected;
7 | // General variables
8 | var myEmail = "miltos@example.com";
9 | var myPass = "passpass";
10 | var signIn = "Sign in";
11 | var wrongEmail = "stamos@example.com";
12 | var wrongPass = "wrongPassword";
13 | var invalidWord = "Invalid";
14 |
15 | // Npm modules
16 | var url = require("url");
17 |
18 | /**
19 | * Scenario: A user can login with valid information
20 | */
21 |
22 | this.Given(/^I am signed out$/, function () {
23 | client.url(process.env.ROOT_URL);
24 | client.waitForExist(".container");
25 | client.waitForVisible(".container");
26 | client.waitForVisible("#login-sign-in-link");
27 |
28 | actual = client.getText("#login-sign-in-link");
29 | expected = "Sign in";
30 |
31 | expect(actual).toContain(expected);
32 | });
33 |
34 | this.Given(/^I am on the home page$/, function () {
35 | // We navigate into home page
36 | client.url(process.env.ROOT_URL);
37 | });
38 |
39 | this.When(/^I click on sign in link$/, function () {
40 | // We navigate into home page
41 | client.url(process.env.ROOT_URL);
42 |
43 | // Wait for the page to load
44 | client.waitForExist(".container", 1000);
45 | client.waitForVisible(".container", 1000);
46 | // We click the login button
47 | client.click("#login-sign-in-link");
48 | });
49 |
50 | this.When(/^I enter my authentication information$/, function () {
51 | return loginWithCredentials(browser, myEmail, myPass)
52 | });
53 |
54 | this.Then(/^I should be logged in$/, function () {
55 |
56 | //We wait if our email address will appear instead of Sign in
57 | client.waitForExist("#login-name-link");
58 |
59 | actual = client.getText("#login-name-link");
60 | expected = myEmail;
61 | expect(actual).toContain(expected);
62 |
63 | });
64 |
65 | /**
66 | * Scenario: A user cannot login with invalid information
67 | */
68 |
69 | this.When(/^I enter my false authentication information$/, function () {
70 | return loginWithCredentials(browser, wrongEmail, wrongPass);
71 | });
72 |
73 | this.Then(/^I should see a user not found error$/, function () {
74 | // We wait the User not found message to appear
75 | client.waitForExist(".error-message");
76 |
77 | actual = client.getText(".error-message");
78 | expected = "User not found";
79 | expect(actual).toContain(expected);
80 |
81 | });
82 |
83 | /**
84 | * Scenario: A user cannot login with invalid email address
85 | */
86 |
87 | this.When(/^I enter my invalid email address$/, function () {
88 | return loginWithCredentials(client, invalidWord, wrongPass);
89 | });
90 |
91 | this.Then(/^I should see an invalid email error message$/, function () {
92 | // We wait the Invalid email message to appear
93 | client.waitForExist(".error-message");
94 |
95 | actual = client.getText(".error-message");
96 | expected = "Invalid email";
97 | expect(actual).toContain(expected);
98 |
99 | });
100 |
101 | /**
102 | * Scenario: A user cannot login with invalid password
103 | */
104 |
105 | this.When(/^I enter my invalid password$/, function () {
106 | // We enter into sign in fields wrong information
107 | return loginWithCredentials(client, myEmail, wrongPass);
108 | });
109 |
110 | this.Then(/^I should see an incorrect password error message$/, function () {
111 | // We wait the Incorrect password message to appear
112 | client.waitForExist(".error-message");
113 |
114 | actual = client.getText(".error-message");
115 | expected = "Incorrect password";
116 | expect(actual).toBe(expected);
117 | });
118 | }
119 |
120 | /**
121 | * This function get the this object and en email and password and try to login the user
122 | * @param self
123 | * @param email
124 | * @param pass
125 | * @return {*|{phasedRegistrationNames}}
126 | */
127 | function loginWithCredentials(client, email, pass) {
128 | client.waitForExist("#login-email");
129 | // We set the values into email and password
130 | client.setValue("#login-email", email);
131 | client.setValue("#login-password", pass);
132 |
133 | // We click the Sign In button
134 | client.click('#login-buttons-password')
135 | }
136 | })();
--------------------------------------------------------------------------------
/tests/cucumber/features/navigation/navigate_steps.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | module.exports = function () {
5 | var actual, expected;
6 | var url = require('url');
7 |
8 | this.When(/^I navigate to "([^"]*)"$/, function (relativePath) {
9 | //We get from scenario the path and navigate to it
10 | client.url(url.resolve(process.env.ROOT_URL, relativePath));
11 | client.waitForExist('.app-title');
12 | });
13 |
14 |
15 | /**
16 | * Scenario: An unregistered user cannot submit a new post
17 | */
18 | this.When(/^I navigate to submit page$/, function () {
19 | //We navigate into /submit path
20 | client.url(url.resolve(process.env.ROOT_URL, "/submit"));
21 | client.waitForExist('.app-title');
22 | });
23 |
24 | this.Then(/^I should see an Access Denied message$/, function () {
25 | // We wait to see the Access Denied message
26 | client.waitForExist(".access-denied");
27 |
28 | actual = client.getText(".access-denied h2");
29 | expected = "Access Denied";
30 |
31 | expect(actual).toBe(expected);
32 |
33 | });
34 |
35 | /**
36 | * Scenario: An unregistered user cannot add a comment
37 | */
38 | this.When(/^I navigate to a post$/, function () {
39 | // We get a postId
40 | var postId =server.call("randomPost");
41 | // We navigate into post with the appropriate url
42 | client.url(url.resolve(process.env.ROOT_URL, "/posts/" + postId));
43 | client.waitForExist('.app-title');
44 | });
45 |
46 | this.Then(/^I should not be able to insert comment$/, function () {
47 | // We wait for login-leave-comment id and we check if the message
48 | // Please log in to leave a comment. appeared
49 | client.waitForExist("#login-leave-comment");
50 |
51 | actual = client.getText("#login-leave-comment");
52 | expected = "Please log in to leave a comment.";
53 |
54 | expect(actual).toBe(expected);
55 | });
56 |
57 | }
58 | })()
59 |
--------------------------------------------------------------------------------
/tests/cucumber/features/navigation/navigation.feature:
--------------------------------------------------------------------------------
1 | Feature: Restrict access to unregistered user
2 |
3 | As unregistered user
4 | I want to navigate only on permitted pages
5 | So that I can see the public content of the page
6 |
7 | Background:
8 | Given I am signed out
9 |
10 | @rerun
11 | Scenario: An unregistered user cannot submit a new post
12 | Given I am on the home page
13 | When I navigate to submit page
14 | Then I should see an Access Denied message
15 |
16 | @rerun
17 | Scenario: An unregistered user cannot add a comment
18 | Given I am on the home page
19 | When I navigate to a post
20 | Then I should not be able to insert comment
21 |
--------------------------------------------------------------------------------
/tests/cucumber/features/posts/submit_new_post/submit_new_post.feature:
--------------------------------------------------------------------------------
1 | Feature: Create New Post
2 |
3 | As existing user of the Microscope
4 | I want to login
5 | So that I can submit new post
6 |
7 | Background: Logged in and already at submit page
8 | Given I am logged in
9 | And I navigate to submit new post page
10 |
11 | @rerun
12 | Scenario: Submit a new post
13 | When I fill in all form's fields
14 | And I submit the form
15 | Then I should see the new post
16 |
17 | @rerun
18 | Scenario: Existing Post
19 | When I fill form's fields with existing post
20 | And I submit the form
21 | Then I should see an error message
22 |
23 |
24 |
--------------------------------------------------------------------------------
/tests/cucumber/features/posts/submit_new_post/submit_new_post.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | module.exports = function () {
5 | var actual, expected;
6 |
7 | /**
8 | * Scenario: Submit a new post
9 | */
10 |
11 | var url = require('url');
12 |
13 | this.Given(/^I am logged in$/, function () {
14 |
15 | // Wait
16 | // We navigate into home page
17 | client.url(process.env.ROOT_URL);
18 |
19 | // Wait for the page to load
20 | client.waitForExist(".container", 1000);
21 | client.waitForVisible(".container", 1000);
22 |
23 | // We click the login button
24 | client.click("#login-sign-in-link");
25 | client.waitForExist("#login-email");
26 |
27 | // We set the values into email and password
28 | client.setValue("#login-email", "miltos@example.com");
29 | client.setValue("#login-password", "passpass");
30 |
31 | // We click the Sign In button
32 | client.click('#login-buttons-password');
33 | });
34 |
35 | this.Given(/^I navigate to submit new post page$/, function () {
36 | // We wait for the submit button to exist
37 | client.waitForExist(".submit-post-but");
38 | client.waitForVisible(".submit-post-but");
39 | //pause(1000).
40 | // We click the submit button
41 | client.click(".submit-post-but");
42 | client.isVisible(".sub-post-but");
43 | });
44 |
45 | this.When(/^I fill in all form's fields$/, function () {
46 | // We just wait for the submit button to exist and then
47 | // we fill the fields
48 | client.waitForExist(".sub-post-but");
49 | client.setValue("#title", "Meteor Point");
50 | client.setValue("#url", "http://www.meteorpoint.com");
51 | });
52 |
53 | this.When(/^I submit the form/, function () {
54 | // We click on submit button
55 | client.submitForm(".sub-post-but");
56 | });
57 |
58 | this.Then(/^I should see the new post$/, function () {
59 | // After the post submission we wait for post-title to exist and check if it is equal with
60 | // the passing post title from above
61 | client.waitForExist(".post-title");
62 |
63 | actual = client.getText(".post-title");
64 | expected = "Meteor Point";
65 |
66 | expect(actual).toBe(expected);
67 | });
68 |
69 | /**
70 | * Scenario: Existing Post
71 | */
72 |
73 | this.Given(/^I fill form's fields with existing post$/, function () {
74 |
75 | client.waitForExist(".sub-post-but");
76 | client.setValue("#title", "Introducing Telescope");
77 | client.setValue("#url", "http://sachagreif.com/introducing-telescope/");
78 | });
79 |
80 | this.Then(/^I should see an error message$/, function () {
81 | // We check if the error class is visible on the screen
82 | client.waitForExist(".error-alert")
83 | client.isVisible(".error-alert");
84 | });
85 | }
86 |
87 | })();
--------------------------------------------------------------------------------
/tests/cucumber/features/static_pages/static_pages.feature:
--------------------------------------------------------------------------------
1 | Feature: Static Pages
2 |
3 | As a new user
4 | I want to be able to access all static pages
5 | So that I can see what this app is all about
6 |
7 | Background:
8 | Given I am a new user
9 |
10 | @rerun
11 | Scenario: Visit home page
12 | When I navigate to "/"
13 | Then I should see the title on the header "Microscope"
--------------------------------------------------------------------------------
/tests/cucumber/features/static_pages/static_pages.js:
--------------------------------------------------------------------------------
1 | (function () {
2 |
3 | 'use strict';
4 |
5 | module.exports = function () {
6 |
7 | this.Given(/^I am a new user$/, function (callback) {
8 | callback();
9 | });
10 |
11 | this.Then(/^I should see the title on the header "([^"]*)"$/, function (expectedTitle) {
12 | //As a new user when I navigate to home page must see application's title which is Microscope
13 | client.waitForExist('.app-title');
14 |
15 | var actual = client.getText('.app-title');
16 | var expected = expectedTitle;
17 |
18 | expect(actual).toBe(expected);
19 | });
20 | }
21 | })();
--------------------------------------------------------------------------------
/tests/cucumber/features/support/hooks.js:
--------------------------------------------------------------------------------
1 | (function () {
2 |
3 | 'use strict';
4 |
5 | module.exports = function () {
6 | this.Before(function () {
7 | // This code runs before every scenario
8 | server.call("removePosts");
9 | server.call("addInitialPosts");
10 | server.call('addUser', {email: "miltos@example.com"});
11 | });
12 | };
13 |
14 | })();
--------------------------------------------------------------------------------
/tests/cucumber/fixtures/posts.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | Meteor.methods({
5 | // We remove all Posts from mirror db
6 | removePosts: function () {
7 | Posts.remove({});
8 | },
9 | randomPost: function () {
10 | var postId = Posts.findOne();
11 | return postId._id;
12 | },
13 | // We add some initial data
14 | addInitialPosts: function () {
15 | var now = new Date().getTime();
16 |
17 | // create two users
18 | var tomId = Meteor.users.insert({
19 | profile: {name: 'Tom Coleman'}
20 | });
21 | var tom = Meteor.users.findOne(tomId);
22 | var sachaId = Meteor.users.insert({
23 | profile: {name: 'Sacha Greif'}
24 | });
25 | var sacha = Meteor.users.findOne(sachaId);
26 |
27 | var telescopeId = Posts.insert({
28 | title: 'Introducing Telescope',
29 | userId: sacha._id,
30 | author: sacha.profile.name,
31 | url: 'http://sachagreif.com/introducing-telescope/',
32 | submitted: new Date(now - 7 * 3600 * 1000),
33 | commentsCount: 2
34 | });
35 |
36 | Comments.insert({
37 | postId: telescopeId,
38 | userId: tom._id,
39 | author: tom.profile.name,
40 | submitted: new Date(now - 5 * 3600 * 1000),
41 | body: 'Interesting project Sacha, can I get involved?'
42 | });
43 |
44 | Comments.insert({
45 | postId: telescopeId,
46 | userId: sacha._id,
47 | author: sacha.profile.name,
48 | submitted: new Date(now - 3 * 3600 * 1000),
49 | body: 'You sure can Tom!'
50 | });
51 |
52 | Posts.insert({
53 | title: 'Meteor',
54 | userId: tom._id,
55 | author: tom.profile.name,
56 | url: 'http://meteor.com',
57 | submitted: new Date(now - 10 * 3600 * 1000),
58 | commentsCount: 0
59 | });
60 |
61 | Posts.insert({
62 | title: 'The Meteor Book',
63 | userId: tom._id,
64 | author: tom.profile.name,
65 | url: 'http://themeteorbook.com',
66 | submitted: new Date(now - 12 * 3600 * 1000),
67 | commentsCount: 0
68 | })
69 | }
70 | });
71 |
72 |
73 | })();
--------------------------------------------------------------------------------
/tests/cucumber/fixtures/users.js:
--------------------------------------------------------------------------------
1 | ( function () {
2 |
3 | 'use strict';
4 |
5 | Meteor.methods({
6 | addUser: function (opts) {
7 | Meteor.users.remove({});
8 | Accounts.createUser({
9 | email: opts.email,
10 | password: opts.password ? opts.password : "passpass"
11 | });
12 | }
13 | });
14 |
15 | })();
--------------------------------------------------------------------------------
/tests/jasmine/client/integration/components/headerComponentSpec.js:
--------------------------------------------------------------------------------
1 | describe("Header Component", function () {
2 | var renderComponentWithProps, post;
3 |
4 | beforeEach(function () {
5 |
6 | renderComponentWithProps = function (component, props, renderType) {
7 | if (renderType === "shallow") {
8 | post = createComponent(component, props);
9 | } else if (renderType == "normal") {
10 | post = renderComponent(component, props);
11 | }
12 | }
13 | });
14 |
15 | it("should not show post submit button to anonymous user", function () {
16 | // We render our component
17 | renderComponentWithProps(Header, {}, "normal");
18 | // We try to find the submit button
19 | var postSubmitLink = TestUtils.scryRenderedDOMComponentsWithClass(post, "submit-post-but");
20 |
21 | // We get the length of the search result
22 | var actual = postSubmitLink.length;
23 | // We expect to be zero
24 | var expected = 0;
25 | expect(actual).toBe(expected);
26 |
27 | });
28 |
29 | it("should be able to login normal user", function (done) {
30 | // We login our user
31 | Meteor.loginWithPassword("miltos@example.com", "passpass", function (err) {
32 | // We expect not to have errors
33 | expect(err).toBeUndefined();
34 | done();
35 | });
36 | });
37 |
38 | it("should be show submit post button to registered user", function () {
39 | // We render our component
40 | renderComponentWithProps(Header, {}, "normal");
41 | // We search for the submit button
42 | var postSubmitLink = TestUtils.scryRenderedDOMComponentsWithClass(post, "submit-post-but");
43 |
44 | // We get the length of the above search results
45 | var actual = postSubmitLink.length;
46 | // We expect to find our button
47 | var expected = 1;
48 | expect(actual).toBe(expected);
49 | });
50 |
51 | it("should be able to logout", function (done) {
52 | // We logout our user
53 | Meteor.logout(function (err) {
54 | expect(err).toBeUndefined();
55 | done();
56 | });
57 | });
58 |
59 | it("should be throw error if email is wrong", function (done) {
60 | // We login our user with wrong email
61 | Meteor.loginWithPassword("WrongUser", "passpass", function (err) {
62 | // We expect errors to be returned
63 | expect(err).toBeDefined();
64 | done();
65 | })
66 | });
67 |
68 | it("should be throw error if credentials are wrong", function (done) {
69 | // We login our user with wrong password
70 | Meteor.loginWithPassword("miltos@example.com", "WrongPass", function (err) {
71 | // We expect errors to be returned
72 | expect(err).toBeDefined();
73 | done();
74 | })
75 | })
76 | });
--------------------------------------------------------------------------------
/tests/jasmine/client/integration/fixtures/users.js:
--------------------------------------------------------------------------------
1 | // We add a new user
2 | Meteor.startup(function() {
3 | if (Meteor.users.find().count() == 0) {
4 | var users = [
5 | {name:"Miltos",email:"miltos@example.com",roles:[], password: "passpass"},
6 | ];
7 | _.each(users, function (user) {
8 | var id = Accounts.createUser({
9 | email: user.email,
10 | password: user.password,
11 | profile: { name: user.name }
12 | });
13 | });
14 | };
15 | });
--------------------------------------------------------------------------------
/tests/jasmine/client/integration/spec_helper.js:
--------------------------------------------------------------------------------
1 | TestUtils = React.addons.TestUtils;
2 | Simulate = TestUtils.Simulate;
3 |
4 | renderComponent = function (comp, props) {
5 | return TestUtils.renderIntoDocument(
6 | React.createElement(comp, props)
7 | );
8 | };
9 |
10 | simulateClickOn = function($el) {
11 | React.addons.TestUtils.Simulate.click($el[0]);
12 | };
--------------------------------------------------------------------------------
/tests/jasmine/client/integration/spec_helper_shallow.js:
--------------------------------------------------------------------------------
1 | TestUtils = React.addons.TestUtils;
2 | Simulate = TestUtils.Simulate;
3 |
4 | createComponent = function (component, props){ // ...children) {
5 | const shallowRenderer = TestUtils.createRenderer();
6 | // We don't render our component into DOM but we make a shallow render which is more fast
7 | //Instead of rendering into a DOM the idea of shallow rendering is to instantiate a component
8 | // and get the result of its render method, which is a ReactElement.
9 | // From here you can do things like check its props and children and verify it works as expected.
10 | // based on this article http://simonsmith.io/unit-testing-react-components-without-a-dom/
11 | shallowRenderer.render(React.createElement(component, props));//, children.length > 1 ? children : children[0]));
12 | return shallowRenderer.getRenderOutput();
13 | }
14 |
15 | simulateClickOn = function($el) {
16 | React.addons.TestUtils.Simulate.click($el[0]);
17 | };
--------------------------------------------------------------------------------
/tests/jasmine/client/unit/components/post_item_spec.js:
--------------------------------------------------------------------------------
1 | describe("PostItem", function () {
2 | var defProps, renderWithProps, component, el, $el;
3 |
4 | beforeEach(function() {
5 | defProps = {
6 | _id: "XYZ",
7 | title: "Meteor Point",
8 | url: "http://www.meteorpoint.com",
9 | author: "Miltos",
10 | commentsCount: 5
11 | }
12 | renderWithProps = function(props) {
13 | component = renderComponent(PostItem, props);
14 | el = React.findDOMNode(component);
15 | $el = $(el);
16 | };
17 | });
18 |
19 | it("should get the domain from the url", function () {
20 | expect(PostItem.prototype.getDomain("http://www.meteor.com")).toBe("www.meteor.com")
21 | });
22 |
23 | it("should print out post's title", function () {
24 | // We render the compoent
25 | renderWithProps(defProps);
26 | // We expect post's title to render
27 | // We use the jQuery to get the text from post-title class
28 | expect($el.children().find(".post-title").text()).toEqual("Meteor Point");
29 | });
30 |
31 | it("should display Edit button when user is the author", function () {
32 | // We create fake object to pass for Meteor.user() function
33 | var user = {
34 | username: "Miltos"
35 | }
36 | spyOn(Meteor, "user").and.returnValue(user);
37 | spyOn(Meteor, "userId").and.returnValue(true);
38 | // We render the object and we wait at jQuery object $el on text function
39 | // to find the Edit word
40 | renderWithProps(defProps);
41 | expect($el.text()).toContain("Edit");
42 |
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/tests/jasmine/client/unit/components/post_list_spec.js:
--------------------------------------------------------------------------------
1 | describe("PostList", function () {
2 | var renderComponentWithProps, post, el, $el, posts;
3 |
4 | beforeEach(function () {
5 |
6 | renderComponentWithProps = function (component, props, renderType, data) {
7 | if (renderType === "shallow") {
8 | post = createComponent(component, props);
9 | } else if (renderType == "normal") {
10 | post = renderComponent(component, props);
11 | el = React.findDOMNode(post);
12 | $el = $(el);
13 | }
14 | }
15 | // We set mock data
16 | posts = {
17 | allPosts: [
18 | {id: 1, title: "First Post", url:"Not found", author:"Miltos", commentsCount: 5},
19 | {id: 2, title: "Second Post", url:"401 for the win", author:"Milkos", commentsCount: 3}
20 | ]
21 | }
22 | });
23 |
24 | it("should render a list of posts", function () {
25 |
26 |
27 | // We shallow render our component with our mock data
28 | renderComponentWithProps(PostList, posts , "shallow");
29 | // We get the number of its children
30 | var actual = post.props.children.length;
31 | // We expect to has 2 posts as the length of mock data
32 | var expected = posts.allPosts.length;
33 |
34 | expect(actual).toBe(expected);
35 | });
36 |
37 | it("should render a list of PostItem", function () {
38 |
39 | // We shallow render our component with our mock data
40 | renderComponentWithProps(PostList, posts , "shallow");
41 | // We check for every children of PostList component to be element of type PostItem
42 | var items = post.props.children.filter(function (postLitItem) {
43 | return TestUtils.isElementOfType(postLitItem, PostItem);
44 | });
45 | // The actual length of items array.
46 | var actual = items.length;
47 | // The expected size of items array. As the number of posts
48 | var expected = posts.allPosts.length;
49 | expect(actual).toBe(expected);
50 |
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/tests/jasmine/client/unit/components/post_submit_spec.js:
--------------------------------------------------------------------------------
1 | describe("PostSubmit", function () {
2 | var renderComponentWithProps, post;
3 |
4 | beforeEach(function () {
5 |
6 | renderComponentWithProps = function (component, props, renderType) {
7 | if (renderType === "shallow") {
8 | post = createComponent(component, props);
9 | } else if (renderType == "normal") {
10 | post = renderComponent(component, props);
11 | }
12 | }
13 | });
14 |
15 | describe("User is logged in", function () {
16 |
17 | beforeEach(function () {
18 | // We spyOn Meteor.userId to provide a user id
19 | spyOn(Meteor, "userId").and.returnValue("xyz");
20 | });
21 |
22 | it("should generate a submit form", function () {
23 | // We shallow render our component
24 | renderComponentWithProps(PostSubmit, {}, "shallow");
25 | // We get the length of component's children
26 | var actual = post.props.children.length;
27 | // We expect to has 3 children the 2 input fields and the button
28 | var expected = 3;
29 | expect(actual).toBe(expected);
30 | });
31 |
32 | it("should render an input for post's title", function () {
33 | // We render the component
34 | renderComponentWithProps(PostSubmit, {}, "shallow");
35 | //We get the post title children from PostSubmit component
36 | var postTitle = post.props.children[0];
37 |
38 | // We write what is the actual output and what the expected to
39 | // make our tests more readable
40 | var actual = postTitle.props.title;
41 | var expected = "Title";
42 |
43 | expect(actual).toBe(expected);
44 | });
45 |
46 | it("should render an input for post's url", function () {
47 | // We render the component
48 | renderComponentWithProps(PostSubmit, {}, "shallow");
49 | // We get the post url children from PostSubmit component
50 | var postUrl = post.props.children[1];
51 |
52 | var actual = postUrl.props.title;
53 | var expected = "URL";
54 |
55 | expect(actual).toBe(expected);
56 | });
57 |
58 | it("should render an error when the title is empty", function () {
59 | // We render our component
60 | renderComponentWithProps(PostSubmit, {}, "normal");
61 | // We get all input fields from our component
62 | var inputs = TestUtils.scryRenderedDOMComponentsWithTag(post, "input");
63 | // We find the title input component
64 | var titleInput = inputs.find((el) => {
65 | return el.props.name == 'title'
66 | });
67 | // We find the error area above title input. These area has a span tag
68 | var titleError = React.findDOMNode(titleInput).parentNode.querySelector("span");
69 |
70 |
71 | // We search for form tag into rendered component
72 | var form = TestUtils.findRenderedDOMComponentWithTag(post, "form");
73 | // We simulate the submission
74 | // on this submission the default value for title and url are "" (empty)
75 | // so after submit we will have an error message
76 | TestUtils.Simulate.submit(form.getDOMNode());
77 |
78 | expect(titleError.innerHTML).toBe("Please fill in a headline");
79 | });
80 |
81 | it("should render an error when the url input is empty", function () {
82 | // We render our component
83 | renderComponentWithProps(PostSubmit, {}, "normal");
84 | // We get all input fields from our component
85 | var inputs = TestUtils.scryRenderedDOMComponentsWithTag(post, "input");
86 | // We find the url input component
87 | var urlInput = inputs.find((el) => {
88 | return el.props.name == 'url'
89 | });
90 | // We find the error section below url input
91 | var urlError = React.findDOMNode(urlInput).parentNode.querySelector("span");
92 | // We find the title input component
93 | var titleInput = inputs.find((el) => {
94 | return el.props.name == 'title'
95 | });
96 | // We change the value of the title input so only the url input to be empty
97 | TestUtils.Simulate.change(titleInput, {target: {value: "someValue"}});
98 | // We search for form tag into rendered component
99 | var form = TestUtils.findRenderedDOMComponentWithTag(post, "form");
100 | // We simulate the submission
101 | // on this submission the default value for url is "" (empty)
102 | // so after submit we will have an error message
103 | TestUtils.Simulate.submit(form.getDOMNode());
104 | // We expect to be rendered the error message for url
105 | expect(urlError.innerHTML).toBe("Please fill in a URL");
106 | });
107 |
108 |
109 | it("should call formSubmission when submit the form", () => {
110 | // We render into DOM our component
111 | renderComponentWithProps(PostSubmit, {}, "normal");
112 | // We spy on formSubmission function which is responsible
113 | // to submit the new post
114 | spyOn(post, "formSubmission");
115 |
116 | // We search for form tag into rendered component
117 | var form = TestUtils.findRenderedDOMComponentWithTag(post, "form");
118 | // We simulate the submission
119 | TestUtils.Simulate.submit(form.getDOMNode());
120 | // We expect after the submission our function to have been called
121 | expect(post.formSubmission).toHaveBeenCalled();
122 | });
123 | });
124 |
125 | describe("User is not logged in", function () {
126 | beforeEach(function () {
127 | // We spyOn Meteor.userId to provide null userId
128 | spyOn(Meteor, "userId").and.returnValue(null);
129 | });
130 |
131 | it("should render AccessDenied component ", () => {
132 | // We render the component
133 | renderComponentWithProps(PostSubmit, {}, "shallow");
134 |
135 |
136 | var actual = post.type;
137 | var expected = AccessDenied;
138 |
139 | expect(actual).toBe(expected);
140 | });
141 | });
142 |
143 |
144 | });
--------------------------------------------------------------------------------
/tests/jasmine/client/unit/spec_helper.js:
--------------------------------------------------------------------------------
1 | TestUtils = React.addons.TestUtils;
2 | Simulate = TestUtils.Simulate;
3 |
4 | renderComponent = function (comp, props) {
5 | return TestUtils.renderIntoDocument(
6 | React.createElement(comp, props)
7 | );
8 | };
9 |
--------------------------------------------------------------------------------
/tests/jasmine/client/unit/spec_helper_shallow.js:
--------------------------------------------------------------------------------
1 | TestUtils = React.addons.TestUtils;
2 | Simulate = TestUtils.Simulate;
3 |
4 | createComponent = function (component, props){ // ...children) {
5 | const shallowRenderer = TestUtils.createRenderer();
6 | // We don't render our component into DOM but we make a shallow render which is more fast
7 | //Instead of rendering into a DOM the idea of shallow rendering is to instantiate a component
8 | // and get the result of its render method, which is a ReactElement.
9 | // From here you can do things like check its props and children and verify it works as expected.
10 | // based on this article http://simonsmith.io/unit-testing-react-components-without-a-dom/
11 | shallowRenderer.render(React.createElement(component, props));//, children.length > 1 ? children : children[0]));
12 | return shallowRenderer.getRenderOutput();
13 | }
14 |
15 | simulateClickOn = function($el) {
16 | React.addons.TestUtils.Simulate.click($el[0]);
17 | };
--------------------------------------------------------------------------------