From 2cde6402401064412bba7c87d56798223b380d9d Mon Sep 17 00:00:00 2001 From: fede-rodes Date: Sun, 8 Jul 2018 23:36:04 +0200 Subject: [PATCH 1/2] improvements on header component + adding tests to getRouteLabel function --- imports/api/constants.js | 9 +---- imports/entry-points/server/fixtures.js | 27 +++++++------ .../smart/header/get-route-label.js | 15 ++++++++ .../smart/header/get-route-label.test.js | 16 ++++++++ .../smart/{header.js => header/index.js} | 38 +++++++++---------- imports/ui/components/smart/menu.js | 14 +++++-- imports/ui/routes.js | 8 ++-- settings.sample.json | 5 ++- 8 files changed, 80 insertions(+), 52 deletions(-) create mode 100644 imports/ui/components/smart/header/get-route-label.js create mode 100644 imports/ui/components/smart/header/get-route-label.test.js rename imports/ui/components/smart/{header.js => header/index.js} (64%) diff --git a/imports/api/constants.js b/imports/api/constants.js index 185380a..2e27c6a 100644 --- a/imports/api/constants.js +++ b/imports/api/constants.js @@ -10,13 +10,8 @@ const Constants = { AUTH_SERVICES: ['password', 'facebook'], ALL_ROLES: ['admin', 'normal'], ROUTES: [ - { path: '/', label: 'Home', auth: true }, - { path: '/login', label: 'Login' }, - { path: '/signup', label: 'Signup' }, - { path: '/verify-email', label: 'Verify Email' }, - { path: '/link-expired', label: 'Link Expired' }, - { path: '/forgot-password', label: 'Forgot Password' }, - { path: '/reset-password', label: 'Reset Password' }, + { path: '/', label: 'Home' }, + { path: '/admin', label: 'Admin', admin: true }, ], }; diff --git a/imports/entry-points/server/fixtures.js b/imports/entry-points/server/fixtures.js index 84683e9..9114993 100644 --- a/imports/entry-points/server/fixtures.js +++ b/imports/entry-points/server/fixtures.js @@ -1,27 +1,26 @@ +import { Meteor } from 'meteor/meteor'; import { Roles } from 'meteor/alanning:roles'; import { Accounts } from 'meteor/accounts-base'; import Users from '../../api/users/'; -// OBSERVATION: use the following mutation to set email to verified: -// db.users.update( -// {_id: "yourUserId", "emails.address": "yourEmailGoesHere"}, -// {$set: {"emails.$.verified": true }} -// ) - -// Insert admin user -const users = [ - { email: 'admin@admin.com', password: '123456', roles: ['admin'] }, -]; +const { admins } = Meteor.settings; -users.forEach(({ email, password, roles }) => { - const userExists = Users.collection.findOne({ 'emails.address': email }); +// Insert admin users +admins.forEach(({ email, roles }) => { + const user = Users.collection.findOne({ 'emails.address': email }); // In case user already exists, do nothing - if (userExists) { + if (user) { return; } // Otherwise, insert user and set his role to 'admin' - const userId = Accounts.createUser({ email, password }); + const userId = Accounts.createUser({ email }); Roles.addUsersToRoles(userId, roles); }); + +// OBSERVATION: use the following operation to set email to verified: +// db.users.update( +// {_id: "yourUserId", "emails.address": "yourEmailGoesHere"}, +// {$set: {"emails.$.verified": true }} +// ) diff --git a/imports/ui/components/smart/header/get-route-label.js b/imports/ui/components/smart/header/get-route-label.js new file mode 100644 index 0000000..65597b6 --- /dev/null +++ b/imports/ui/components/smart/header/get-route-label.js @@ -0,0 +1,15 @@ +import { matchPath } from 'react-router-dom'; +import Constants from '../../../../api/constants'; + +/** + * @summary mapping function between route pathname ('/admin') and route + * name/label ('Admin'). + */ +const getRouteLabel = (pathname) => { + const route = Constants.ROUTES.find(({ path }) => ( + matchPath(pathname, { path, exact: true }) + )); + return route ? route.label : undefined; +}; + +export default getRouteLabel; diff --git a/imports/ui/components/smart/header/get-route-label.test.js b/imports/ui/components/smart/header/get-route-label.test.js new file mode 100644 index 0000000..456c880 --- /dev/null +++ b/imports/ui/components/smart/header/get-route-label.test.js @@ -0,0 +1,16 @@ +import Constants from '../../../../api/constants'; +import getRouteLabel from './get-route-label'; + +describe('getRouteLabel', () => { + it('should return the right label when existing route is provided', () => { + Constants.ROUTES.forEach((route) => { + expect(getRouteLabel(route.path)).toBe(route.label); + }); + }); + + it('should return undefined when route is not found', () => { + expect(getRouteLabel('/some-wired-path')).toBe(undefined); + expect(getRouteLabel('/admin ')).toBe(undefined); + expect(getRouteLabel('/ ')).toBe(undefined); + }); +}); diff --git a/imports/ui/components/smart/header.js b/imports/ui/components/smart/header/index.js similarity index 64% rename from imports/ui/components/smart/header.js rename to imports/ui/components/smart/header/index.js index b3611f5..bbbaf1a 100644 --- a/imports/ui/components/smart/header.js +++ b/imports/ui/components/smart/header/index.js @@ -2,8 +2,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router-dom'; import { propType } from 'graphql-anywhere'; -import userFragment from '../../apollo-client/user/fragment/user'; -import Constants from '../../../api/constants'; +import userFragment from '../../../apollo-client/user/fragment/user'; +import getRouteLabel from './get-route-label'; //------------------------------------------------------------------------------ // AUX FUNCTIONS: @@ -15,22 +15,6 @@ const showHideBurgerBtn = (curUser) => { menuIconElement.classList[curUser ? 'add' : 'remove']('header__burger--show'); }; //------------------------------------------------------------------------------ -const getRouteLabel = (pathname) => { - const route = Constants.ROUTES - // Sort routes by longest route paths first. '/' will be the last route in - // the list - .sort((r1, r2) => ( - r2.path.length - r1.path.length - )) - // Use regular expression to find current route based on path. '/' must be - // the last path we test, otherwise the test will always return true - .find(({ path }) => { - const reg = new RegExp(path); - return reg.test(pathname); - }); - return route ? route.label : undefined; -}; -//------------------------------------------------------------------------------ // COMPONENT: //------------------------------------------------------------------------------ class Header extends React.Component { @@ -49,10 +33,22 @@ class Header extends React.Component { } } + get curRouteLabel() { + const { curUser, location } = this.props; + + // Get label matching current route ('/' --> 'Home', '/b$^$%^$' --> undefined) + const curRouteLabel = getRouteLabel(location.pathname); + + if (!curRouteLabel) { + return 'Not Found'; + } else if (!curUser) { + return 'Login'; + } + return curRouteLabel; + } + render() { - const { location } = this.props; - const label = getRouteLabel(location.pathname); - return {label || 'Not Found'}; + return {this.curRouteLabel}; } } diff --git a/imports/ui/components/smart/menu.js b/imports/ui/components/smart/menu.js index 4c342b7..d92f189 100644 --- a/imports/ui/components/smart/menu.js +++ b/imports/ui/components/smart/menu.js @@ -1,5 +1,6 @@ import React from 'react'; import { Link } from 'react-router-dom'; +import { Roles } from 'meteor/alanning:roles'; import { propType } from 'graphql-anywhere'; import userFragment from '../../apollo-client/user/fragment/user'; import Constants from '../../../api/constants'; @@ -14,11 +15,16 @@ const Menu = ({ curUser }) => { return null; } - // Display authenticated routes plus logout button + // Get list of routes to be displayed on the side-menu. Include admin route + // if and only if current user is admin + const routes = Constants.ROUTES + .filter(({ admin }) => ( + (admin && Roles.userIsInRole(curUser._id, ['admin'])) || !admin + )); + + // Display menu routes plus logout button return [ - Constants.ROUTES - .filter(({ auth }) => auth) - .map(({ path, label }) => ( + routes.map(({ path, label }) => (
  • import('./pages/auth/login-pag const Routes = props => ( + {/* HOME */} import('./pages/home-page') })} overlay={LoginPage} {...props} /> + {/* ADMIN */} import('./pages/admin/admin-page') })} overlay={LoginPage} {...props} /> + {/* NOT FOUND */} import('./pages/not-found-page') })} /> diff --git a/settings.sample.json b/settings.sample.json index 3a33d36..aae7a2f 100644 --- a/settings.sample.json +++ b/settings.sample.json @@ -23,5 +23,8 @@ "vapid": { "publicKey": "WEB_PUSH_PUBLIC_KEY" } - } + }, + "admins": [ + { "email": "admin@admin.com", "roles": ["admin"] } + ] } From f591b3dc51cbbbd419053695f9dd8fbcb33cc726 Mon Sep 17 00:00:00 2001 From: fede-rodes Date: Wed, 11 Jul 2018 00:55:49 +0200 Subject: [PATCH 2/2] refactoring header component + adding header title tests --- .gitignore | 1 + client/main.html | 6 +- imports/entry-points/client/startup.js | 9 ++- imports/ui/{app.js => app/index.js} | 6 +- imports/ui/{app.test.js => app/index.test.js} | 2 +- .../{index.js => burger-btn-controller.js} | 31 ++-------- .../components/smart/header/header-title.js | 41 ++++++++++++ .../smart/header/header-title.test.js | 62 +++++++++++++++++++ 8 files changed, 123 insertions(+), 35 deletions(-) rename imports/ui/{app.js => app/index.js} (94%) rename imports/ui/{app.test.js => app/index.test.js} (91%) rename imports/ui/components/smart/header/{index.js => burger-btn-controller.js} (62%) create mode 100644 imports/ui/components/smart/header/header-title.js create mode 100644 imports/ui/components/smart/header/header-title.test.js diff --git a/.gitignore b/.gitignore index 979831c..5dcce69 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ imports/ui/service-worker-2.js imports/ui/components/smart/pwa/subscribe-btns/ imports/ui/layouts/default/ TODO.txt +coverage/ diff --git a/client/main.html b/client/main.html index 8176d8b..8d61497 100644 --- a/client/main.html +++ b/client/main.html @@ -74,16 +74,18 @@
    - +
    - +
    +