diff --git a/cucumber/features/login.feature b/cucumber/features/login.feature
new file mode 100644
index 0000000..36cc4de
--- /dev/null
+++ b/cucumber/features/login.feature
@@ -0,0 +1,38 @@
+Feature: Allow users to login and logout
+
+ As a user
+ I want to login and logout
+ So that I can prove my identity and see personalized data
+
+ Background:
+ Given I am on the home page
+
+ @watch
+ Scenario: A user can login
+ Given I have an account and I logged in
+ When I sign out
+ When I enter my email and password
+ Then I should be logged in
+ And I can edit my profile
+
+ @watch
+ Scenario: A user cannot login with bad information
+ Given I have an account and I logged in
+ When I sign out
+ When I enter incorrect authentication information
+ Then I should see a user not found error
+
+ @watch
+ Scenario: A user can register and login
+ Given I register with some name, password and email
+ Then I should be registered
+ And I should be logged in
+ When I sign out
+ When I enter my email and password
+ Then I should be logged in
+ And I can edit my profile
+
+ @skip
+ Scenario: A user cannot register without accepting service conditions
+ Given I register with some name, password and email but without accept conditions
+ Then I shouldn't be registered
diff --git a/cucumber/features/steps_definitions/helper.js b/cucumber/features/steps_definitions/helper.js
new file mode 100644
index 0000000..9961796
--- /dev/null
+++ b/cucumber/features/steps_definitions/helper.js
@@ -0,0 +1,30 @@
+// https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript
+function makeid(size) {
+ let text = '';
+ const possible = 'abcdefghijklmnopqrstuvwxyz0123456789';
+ for (let i = 0; i < size; i += 1) {
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
+ }
+ return text;
+}
+
+export const randomUsername = () => makeid(7);
+
+export const randomPassword = () => randomUsername();
+
+export const randomEmail = () => `${randomUsername()}@example.com`;
+
+export const goHome = (client) => {
+ client.url(process.env.ROOT_URL);
+ client.waitForVisible('#react-root', 10000);
+ expect(client.isVisible('#react-root')).toBe(true);
+ client.waitUntil(() => client.isVisible('.pg-loading-center-middle') === false, 5000);
+ client.waitUntil(() => client.isVisible('.pg-loading-center-middle') === false, 5000);
+ // Close alert
+ if (client.isVisible('.bert-content')) {
+ client.click('.bert-content');
+ }
+ if (client.isVisible('#acceptCookies')) {
+ client.click('#acceptCookies');
+ }
+};
diff --git a/cucumber/features/steps_definitions/hooks.js b/cucumber/features/steps_definitions/hooks.js
new file mode 100644
index 0000000..5d97a50
--- /dev/null
+++ b/cucumber/features/steps_definitions/hooks.js
@@ -0,0 +1,39 @@
+/* global client */
+
+(function () {
+ module.exports = function () {
+ this.Before(() => {
+ // global.expect = require('@xolvio/jasmine-expect').expect;
+
+ if (!this.initMyCmds) {
+ client.addCommand('waitForClickable', function elementClickable(selector, timeout) {
+ this.waitForVisible(selector, timeout);
+ this.waitForEnabled(selector, timeout);
+ });
+ this.initMyCmds = true;
+ }
+
+ // server.call('fixtures/reset');
+ // server.call('emailStub/reset');
+ // server.call('emailStub/stub');
+ // server.call('fixtures/stubCloudFrontClient');
+
+ // go to the page first so we have access to the Meteor object
+ // console.log('Before all tests...');
+ // client.url(process.env.ROOT_URL);
+ /* client.executeAsync(function (done) {
+ * var customer = {
+ * id: 'randomId',
+ * email: 'me@example.com',
+ * subscriptions: {
+ * data: [{
+ * current_period_start: 1436716844,
+ * current_period_end: 1436716844
+ * }]
+ * }
+ * }; */
+ // this call will stub stripe both on the client and server
+ // Meteor.call('stripeStub/stub', customer, done);
+ });
+ };
+}());
diff --git a/cucumber/features/steps_definitions/login.js b/cucumber/features/steps_definitions/login.js
new file mode 100644
index 0000000..84f4d26
--- /dev/null
+++ b/cucumber/features/steps_definitions/login.js
@@ -0,0 +1,130 @@
+/* global module expect require process client */
+
+const {
+ goHome, randomUsername, randomPassword, randomEmail
+} = require('./helper.js');
+
+let firstName;
+let lastName;
+let pass;
+let email;
+
+const setUserValues = (isEdit) => {
+ client.waitForVisible('input[name="firstName"]', 5000);
+ firstName = randomUsername();
+ lastName = randomUsername();
+ email = randomEmail();
+ client.setValue('input[name="firstName"]', firstName);
+ client.setValue('input[name="lastName"]', lastName);
+ client.setValue('input[name="emailAddress"]', email);
+ if (isEdit) {
+ client.setValue('input[name="currentPassword"]', pass);
+ pass = randomPassword();
+ client.setValue('input[name="newPassword"]', pass);
+ } else {
+ pass = randomPassword();
+ client.setValue('input[name="password"]', pass);
+ }
+};
+
+module.exports = function doSteps(notos) {
+ this.Given(/^I am on the home page$/, () => {
+ goHome(client);
+ });
+
+ function register() {
+ if (client.isVisible('#logout')) {
+ client.click('#logout');
+ }
+ client.waitForVisible('#signup', 5000);
+ client.click('#signup');
+ setUserValues();
+ if (!notos) {
+ client.click('input[name="tos"]');
+ }
+ client.click('#signUpSubmit');
+ }
+
+ this.Given(/^I have an account and I logged in$/, () => {
+ register();
+ });
+
+ function checkName() {
+ client.waitForVisible('#profile', 5000);
+ client.waitForText('#profile', firstName);
+ client.waitForText('#profile', lastName);
+ }
+
+ this.Then(/^I should be logged in$/, () => {
+ client.waitUntil(() => client.isVisible('#logout') === true, 10000);
+ });
+
+ /* this.Given(/^I am signed out$/, () => {
+ * client.waitUntil(() => client.isVisible('#logout') === true, 10000);
+ * });
+ */
+
+ function closeAlert() {
+ client.waitForVisible('.bert-alert', 5000);
+ client.click('.bert-content');
+ client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000);
+ }
+
+ this.When(/^I sign out$/, () => {
+ if (client.isVisible('.bert-alert')) {
+ client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000);
+ }
+ client.waitForVisible('#logout', 5000);
+ client.click('#logout');
+ });
+
+ function login(badpass) {
+ client.waitForVisible('#login', 10000);
+ client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000);
+ client.click('#login');
+ client.waitForVisible('#loginSubmit', 5000);
+ client.setValue('input[name="emailAddress"]', email);
+ client.setValue('input[name="password"]', badpass ? randomPassword() : pass);
+ client.click('#loginSubmit');
+ closeAlert();
+ }
+
+ this.When(/^I enter my email and password$/, () => {
+ login();
+ });
+
+ this.Then(/^I can edit my profile$/, () => {
+ client.waitForVisible('#profile', 5000);
+ client.click('#profile');
+ setUserValues(true);
+ client.click('#profileSubmit');
+ closeAlert();
+ checkName();
+ });
+
+ this.When(/^I enter incorrect authentication information$/, () => {
+ login(true);
+ });
+
+ this.Then(/^I should see a user not found error$/, () => {
+ client.waitForVisible('.alert-danger', 10000, true);
+ });
+
+ this.Given(/^I register with some name, password and email$/, () => {
+ register();
+ });
+
+ this.Then(/^I should be registered$/, () => {
+ client.waitForVisible('.bert-alert', 10000, true);
+ closeAlert();
+ });
+
+ this.Given(/^I register with some name, password and email but without accept conditions$/, () => {
+ register(true);
+ });
+
+ this.Then(/^I shouldn't be registered$/, () => {
+ client.waitForVisible('.bert-alert', 10000, true);
+ client.waitForVisible('#login', 10000, true);
+ });
+};
diff --git a/imports/ui/components/AuthenticatedNavigation/AuthenticatedNavigation.js b/imports/ui/components/AuthenticatedNavigation/AuthenticatedNavigation.js
index b98ec58..e479f8f 100644
--- a/imports/ui/components/AuthenticatedNavigation/AuthenticatedNavigation.js
+++ b/imports/ui/components/AuthenticatedNavigation/AuthenticatedNavigation.js
@@ -1,9 +1,12 @@
+/* eslint-disable import/no-absolute-path */
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { LinkContainer } from 'react-router-bootstrap';
/* import { Nav, NavDropdown } from 'react-bootstrap'; */
import { translate, Trans } from 'react-i18next';
+import { testId } from '/imports/ui/components/Utils/TestUtils';
+
/*
FIXME:
navitem needs a nav-link class but does not works
@@ -22,10 +25,10 @@ const AuthenticatedNavigation = ({ name, history, props }) => (
{/* Mis alertas */}
-
+ {name}
-
+ history.push('/logout')} href="/logout">Cerrar sesión
{/* */}
diff --git a/imports/ui/components/Navigation/Navigation.js b/imports/ui/components/Navigation/Navigation.js
index 6a7a158..883a4b5 100644
--- a/imports/ui/components/Navigation/Navigation.js
+++ b/imports/ui/components/Navigation/Navigation.js
@@ -1,9 +1,12 @@
+/* eslint-disable import/no-absolute-path */
+
import React from 'react';
import PropTypes from 'prop-types';
import { Navbar } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import { LinkContainer } from 'react-router-bootstrap';
import { Trans, translate } from 'react-i18next';
+import { testId } from '/imports/ui/components/Utils/TestUtils';
import BetaRibbon from '../../components/BetaRibbon/BetaRibbon';
import PublicNavigation from '../PublicNavigation/PublicNavigation';
import AuthenticatedNavigation from '../AuthenticatedNavigation/AuthenticatedNavigation';
@@ -38,12 +41,12 @@ const Navigation = props => (
{/* Sandbox */}
-
+
{props.authenticated ? Mis zonas : Participar}
-
+ {props.t('activeFires')}
diff --git a/imports/ui/components/PublicNavigation/PublicNavigation.js b/imports/ui/components/PublicNavigation/PublicNavigation.js
index 901d384..d59714e 100644
--- a/imports/ui/components/PublicNavigation/PublicNavigation.js
+++ b/imports/ui/components/PublicNavigation/PublicNavigation.js
@@ -1,8 +1,11 @@
+/* eslint-disable import/no-absolute-path */
import React from 'react';
import PropTypes from 'prop-types';
import { LinkContainer } from 'react-router-bootstrap';
/* import { Nav } from 'react-bootstrap'; */
import { translate } from 'react-i18next';
+import { testId } from '/imports/ui/components/Utils/TestUtils';
+
/*
FIXME:
navitem needs a nav-link class but does not works
@@ -15,10 +18,10 @@ import NavItem from '../NavItem/NavItem';
const PublicNavigation = props => (
{/* */}
diff --git a/imports/ui/components/Utils/TestUtils.js b/imports/ui/components/Utils/TestUtils.js
new file mode 100644
index 0000000..23cf121
--- /dev/null
+++ b/imports/ui/components/Utils/TestUtils.js
@@ -0,0 +1,9 @@
+import { Meteor } from 'meteor/meteor';
+import { Random } from 'meteor/random';
+
+export const testId = (id) => {
+ if (Meteor.isDevelopment) {
+ return id;
+ }
+ return Random.id;
+};
diff --git a/imports/ui/pages/Login/Login.js b/imports/ui/pages/Login/Login.js
index bdbb696..8d7dc5c 100644
--- a/imports/ui/pages/Login/Login.js
+++ b/imports/ui/pages/Login/Login.js
@@ -1,3 +1,5 @@
+/* eslint-disable import/no-absolute-path */
+
import React from 'react';
import PropTypes from 'prop-types';
import { Row, FormGroup, ControlLabel, Button } from 'react-bootstrap';
@@ -6,6 +8,7 @@ import { Meteor } from 'meteor/meteor';
import { Bert } from 'meteor/themeteorchef:bert';
import { translate } from 'react-i18next';
import { T9n } from 'meteor-accounts-t9n';
+import { testId } from '/imports/ui/components/Utils/TestUtils';
import Col from '../../components/Col/Col';
import OAuthLoginButtons from '../../components/OAuthLoginButtons/OAuthLoginButtons';
import AccountPageFooter from '../../components/AccountPageFooter/AccountPageFooter';
@@ -101,7 +104,7 @@ class Login extends React.Component {
className="form-control"
/>
-
+
{this.t('¿No tienes una cuenta?')} {this.t('Regístrate')}.
diff --git a/imports/ui/pages/Profile/Profile.js b/imports/ui/pages/Profile/Profile.js
index 21d59ed..26766bb 100644
--- a/imports/ui/pages/Profile/Profile.js
+++ b/imports/ui/pages/Profile/Profile.js
@@ -1,13 +1,15 @@
/* eslint-disable no-underscore-dangle */
+/* eslint-disable import/no-absolute-path */
import React from 'react';
import PropTypes from 'prop-types';
import { Row, FormGroup, ControlLabel, Button } from 'react-bootstrap';
-import Col from '../../components/Col/Col';
import _ from 'lodash';
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { Bert } from 'meteor/themeteorchef:bert';
+import { testId } from '/imports/ui/components/Utils/TestUtils';
+import Col from '../../components/Col/Col';
import { createContainer } from 'meteor/react-meteor-data';
import InputHint from '../../components/InputHint/InputHint';
import validate from '../../../modules/validate';
@@ -232,7 +234,7 @@ class Profile extends React.Component {
/>
{this.t('Usa al menos seis caracteres.')}
-
+
) : ;
}
diff --git a/imports/ui/pages/Signup/Signup.js b/imports/ui/pages/Signup/Signup.js
index 7857921..ba8f595 100644
--- a/imports/ui/pages/Signup/Signup.js
+++ b/imports/ui/pages/Signup/Signup.js
@@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { Bert } from 'meteor/themeteorchef:bert';
+import { testId } from '/imports/ui/components/Utils/TestUtils';
import Icon from '../../components/Icon/Icon';
import OAuthLoginButtons from '../../components/OAuthLoginButtons/OAuthLoginButtons';
import InputHint from '../../components/InputHint/InputHint';
@@ -186,11 +187,11 @@ class Signup extends React.Component {
/>
{t('Usa al menos seis caracteres.')}
- this.setTermsAccept(e.target.checked)}>
+ this.setTermsAccept(e.target.checked)}>
Acepto las condiciones de servicio de este sitio
-
+
{t('¿Ya tienes un cuenta?')} {t('Iniciar sesión')}.