Add Clinical Meteor packages to Lesion Tracker

This commit is contained in:
Erik Ziegler 2016-01-25 17:14:42 +01:00
parent 383b4ff4c0
commit b2fa6ead3b
56 changed files with 2355 additions and 9 deletions

View File

@ -24,7 +24,6 @@ dicomweb
accounts-password
ian:accounts-ui-bootstrap-3
iron:router
twbs:bootstrap
http
arsnebula:reactive-promise
@ -37,3 +36,14 @@ reactive-dict
lesiontracker
practicalmeteor:loglevel
momentjs:moment
clinical:router
clinical:extended-api
clinical:active-entry
clinical:static-pages
clinical:error-pages
clinical:theming
clinical:theming-ui
clinical:fonts
clinical:glass-ui
clinical:hipaa-logger

View File

@ -3,6 +3,7 @@ accounts-password@1.1.4
anti:i18n@0.4.3
arsnebula:reactive-promise@0.9.1
autoupdate@1.2.4
awatson1978:fonts-helveticas@1.0.4
babel-compiler@5.8.24_1
babel-runtime@0.1.4
base64@1.0.4
@ -15,6 +16,20 @@ caching-compiler@1.0.0
caching-html-compiler@1.0.2
callback-hook@1.0.4
check@1.1.0
clinical:active-entry@1.5.14
clinical:barcode@3.0.1
clinical:error-pages@0.1.1
clinical:extended-api@2.2.1
clinical:fonts@1.0.0
clinical:glass-ui@1.3.7
clinical:hipaa-logger@1.0.1
clinical:router@2.0.17
clinical:router-location@2.0.14
clinical:router-middleware-stack@2.0.13
clinical:router-url@2.0.15
clinical:static-pages@1.0.7
clinical:theming@0.3.1
clinical:theming-ui@0.2.3
coffeescript@1.0.11
cornerstone@0.0.1
ddp@1.2.2
@ -33,6 +48,7 @@ es5-shim@4.1.14
fastclick@1.0.7
fortawesome:fontawesome@4.4.0
geojson-utils@1.0.4
grove:less@0.1.1
hangingprotocols@0.0.1
hot-code-push@1.0.0
html-tools@1.0.5
@ -45,10 +61,6 @@ iron:controller@1.0.12
iron:core@1.0.11
iron:dynamic-template@1.0.12
iron:layout@1.0.12
iron:location@1.0.11
iron:middleware-stack@1.0.11
iron:router@1.0.12
iron:url@1.0.11
jquery@1.11.4
launch-screen@1.0.4
lesiontracker@0.0.1

View File

@ -0,0 +1,36 @@
if (Meteor.isClient){
ActiveEntry.configure({
logo: {
url: '/mini-circles.png',
displayed: true
},
signIn: {
displayFullName: true,
destination: '/worklist'
},
signUp: {
destination: '/worklist'
},
themeColors: {
primary: 'black'
}
});
}
if (Meteor.isServer){
Accounts.emailTemplates.siteName = 'AwesomeSite';
Accounts.emailTemplates.from = 'AwesomeSite Admin <accounts@example.com>';
Accounts.emailTemplates.enrollAccount.subject = function(user) {
return 'Welcome to Awesome Town, ' + user.profile.name;
};
Accounts.emailTemplates.enrollAccount.text = function(user, url) {
return 'You have been selected to participate in building a better future!'
+ ' To activate your account, simply click the link below:\n\n'
+ url;
};
Meteor.startup(function() {
//process.env.MAIL_URL = 'smtp://sandboxid.mailgun.org:mypassword@smtp.mailgun.org:587';
});
}

View File

@ -5,15 +5,14 @@ Object.keys(ViewerData).forEach(function(contentId) {
var tabData = ViewerData[contentId];
var data = {
title: tabData.title,
contentid: tabData.contentid,
contentid: tabData.contentid
};
WorklistTabs.insert(data);
});
Router.configure({
layoutTemplate: 'layoutLesionTracker',
loadingTemplate: 'layoutLesionTracker',
notFoundTemplate: 'notFound'
loadingTemplate: 'layoutLesionTracker'
});
Router.onBeforeAction('loading');
@ -32,6 +31,10 @@ Router.route('/', function() {
this.render('worklist', routerOptions);
});
Router.route('/worklist', function() {
this.render('worklist', routerOptions);
});
Router.route('/viewer/:_id', {
layoutTemplate: 'layoutLesionTracker',
name: 'viewer',

View File

@ -0,0 +1,13 @@
language: node_js
node_js:
- "0.10"
#sudo: required
sudo: required
env:
global:
- METEOR_ENV=development
before_install:
- "curl -L http://git.io/ejPSng | /bin/sh"

View File

@ -0,0 +1,29 @@
## Contributing
**Quality Assurance**
- Pull Requests will be generally accepted as long as the QA tests pass on [Circle CI](https://circleci.com/gh/clinical-meteor/clinical-active-entry).
- Begin a Pull Request by logging an Issue for discussion.
- Next, clone the package into a project for local development.
- Use ``git checkout -b newfeature`` to create a new branch.
- Run the package verification tests to ensure that the test runner works.
- Add an it() clause at the bottom of the [activeEntryTests.js](https://github.com/clinical-meteor/clinical-active-entry/blob/master/tests/gagarin/activeEntryTests.js) file for each new feature you wish to implement.
- Sketch out a test script for a feature.
- Run the verification tests, and confirm that the new test fails.
- Update the package with the new feature until the new test passes.
- Push the package to the repo on GitHub.
- Submit a pull-request.
- If the PR passes tests on Circle CI, we'll merge it in!
**Reference Implementation**
- Please see the [ChecklistManifest](http://checklist-manifesto.meteor.com/) and it's [source code](https://github.com/clinical-meteor/checklist-manifesto) for a reference implementation of the ActiveEntry package. It's being built as a workqueue, and is being designed to be FDA, HIPAA, and HL7 FHIR compliant.
**Feature Toggling**
- In general, it's a best practice to implement features that can be enabled/disabled. Some people will want a feature; others will not. The best way to toggle features is adding them to a config file, which is what the [ActiveEntry](https://github.com/clinical-meteor/clinical-active-entry/blob/master/lib/ActiveEntry.js) object is for.
- It's recommended that fields be added to the ActiveEntryConfig object that can be used to enabled/disable the feature you are implementing.
- The ActiveEntryConfig object can be roughly considered as analogous to the ``props`` field that is passed into React templates.
**Theming and Styling**
- Different apps require different presentation/style layers, so Clinical Meteor has implemented a [Theme](https://github.com/clinical-meteor/clinical-theming/blob/master/objects/Theme.js) object which manages theming. Generally speaking, we're trying to avoid the inclusion of third-party component libraries, UI widgets, and CSS frameworks, and keeping the Clinical Meteor packages as close to the default HTML that Blaze produces as possible (while using line styles for animation effects).
- Generally speaking, Bootstrap HTML structure in acceptable in designing pages, but avoid including global CSS files, and keep any CSS locally scoped using LESS nested classes. This approach allows people to add the Bootstrap library if they want it, prevents cascading leaks, and allows more fine-grained control over CSS to implement animation effects.
- Pull requests that include visual integration with other projects are acceptable, but please submit an issue for discussion before submitting PRs with wholesale changes that include Jade, Material UI, React, etc.
- The ActiveEntry pages are designed to be used in an IronRouter Layout template. As such, no height/width constraints are provided. It's assumed that the layout template will take care of such things. But basic padding/margin is provided.

127
Packages/active-entry/README.md Executable file
View File

@ -0,0 +1,127 @@
## clinical:active-entry
This package provides the SignIn, SignUp, and ForgotPassword pages.
[![Circle CI](https://circleci.com/gh/clinical-meteor/clinical-active-entry/tree/master.svg?style=svg)](https://circleci.com/gh/clinical-meteor/clinical-active-entry/tree/master)
===============================
#### Installation
````
meteor add clinical:active-entry
````
===============================
#### Entry Flowchart
The following diagram represents the entry workflow that is being implemented in this package. This package is under active development, and is about half completed. Pull requests which help implement the following workflow will be fast-tracked and accepted into the package.
![entry-workflow](https://raw.githubusercontent.com/clinical-meteor/clinical-active-entry/master/docs/Entry%20Workflow.png)
===============================
#### Routing API
````
/entrySignIn
/entrySignUp
/forgotPassword
````
===============================
#### Component API
````
{{> entrySignIn }}
{{> entrySignUp }}
{{> forgotPassword }}
````
===============================
#### ActiveEntry Configuration
````js
if(Meteor.isClient){
ActiveEntry.configure({
logo: {
url: "/mini-circles.png",
displayed: true
},
signIn: {
displayFullName: true,
destination: "/table/users"
},
signUp: {
destination: "/table/users"
},
themeColors: {
primary: ""
}
});
}
if(Meteor.isServer){
Accounts.emailTemplates.siteName = "AwesomeSite";
Accounts.emailTemplates.from = "AwesomeSite Admin <accounts@example.com>";
Accounts.emailTemplates.enrollAccount.subject = function (user) {
return "Welcome to Awesome Town, " + user.profile.name;
};
Accounts.emailTemplates.enrollAccount.text = function (user, url) {
return "You have been selected to participate in building a better future!"
+ " To activate your account, simply click the link below:\n\n"
+ url;
};
Meteor.startup(function(){
process.env.MAIL_URL = 'smtp://sandboxid.mailgun.org:mypassword@smtp.mailgun.org:587';
})
}
````
Alternatively, you may want to set the ``MAIL_URL`` via an external environment variable, particularly if you're using a SaaS hosting provider.
````sh
MAIL_URL = 'smtp://sandboxid.mailgun.org:mypassword@smtp.mailgun.org:587' meteor
````
===============================
#### Local Development
Simply clone the repository into your ``/packages`` directory. You can also specify the packages you want to develop locally in your ``.git-packages.json`` file, and use starrynight to fetch them.
````bash
# clone a single package into your application
git clone http://github.com/clinical-meteor/clinical-active-entry packages/active-entry
# fetch all the packages listed in git-packages.json
starrynight fetch
````
===============================
#### Quality Assurance Testing
There are two types of quality assurance tests you can run: verification and validation tests. Verification tests are similar to unit or integration tests; and can run either at the application or package level. Validation tests are application-wide, but often require commands exposed in packages. So you'll need to run the ``autoconfig`` command to scan the filesystem for validation commands. See [http://starrynight.meteor.com/](http://starrynight.meteor.com/) for more details.
````bash
# install the testing utility
npm install -g starrynight
# verification testing (a.k.a. package-level unit/integration testing)
starrynight run-tests --type package-verification
#validation testing (a.k.a. application acceptance/end-to-end testing)
starrynight autoscan
starrynight run-tests --type validation
````
===============================
#### Contributing
See our [notes on contributing](https://github.com/clinical-meteor/clinical-active-entry/blob/master/Contributing.md).
===============================
#### Licensing
![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)

View File

@ -0,0 +1,61 @@
## Customize the test machine
machine:
# Timezone
timezone:
America/Los_Angeles # Set the timezone
# Add some environment variables
environment:
CIRCLE_ENV: test
CXX: g++-4.8
DISPLAY: :99.0
NPM_PREFIX: /home/ubuntu/nvm/v0.10.33
## Customize checkout
checkout:
post:
#- git submodule sync
#- git submodule update --init --recursive # use submodules
#general:
# build_dir: helloworld
## Customize dependencies
dependencies:
cache_directories:
- "~/.meteor" # relative to the user's home directory
override:
- meteor || curl https://install.meteor.com | /bin/sh
- npm install starrynight -g
- meteor create --release METEOR@1.1.0.3 helloworld
- cd helloworld
- cd helloworld && ls -la
- cd helloworld && rm helloworld.*
- cd helloworld && meteor add anti:gagarin@0.4.11 accounts-base accounts-password session meteor-platform clinical:active-entry clinical:user-model
- cd helloworld && git clone http://github.com/clinical-meteor/clinical-active-entry packages/active-entry
- cd helloworld && starrynight autoconfig
## Customize test commands
test:
pre:
- cd helloworld && meteor:
background: true
- sleep 80
override:
- cd helloworld && starrynight run-tests --type package-verification
## Customize deployment commands
#deployment:
# production:
# branch: master
# commands:
# - printf "<Meteor username>\n<Meteor password>\n" | meteor deploy circlecivelocity.meteor.com
## Custom notifications
#notify:
#webhooks:
# A list of hashes representing hooks. Only the url field is supported.
#- url: https://someurl.com/hooks/circle

View File

@ -0,0 +1,52 @@
/**
* @summary Determines if a block of code should be displayed based on whether the logo is set to be displayed.
* @locus Client
* @memberOf Entry
* @name {{logoIsDisplayed}}
* @version 1.2.3
* @returns {Boolean}
* @example
* ```html
* {{#if logoIsDisplayed}}
* <div class="entryLogo" style="background-image: url('{{logoUrl}}')"></div>
* {{/if}}
* ```
*/
Template.registerHelper("logoIsDisplayed", function (argument){
var config = Session.get('Photonic.ActiveEntry');
if(config && config.logo && config.logo.displayed){
return config.logo.displayed;
}else{
return false;
}
});
/**
* @summary Retruns the Url of the logo asset.
* @locus Client
* @memberOf Entry
* @name {{logoUrl}}
* @version 1.2.3
* @returns {String}
* @example
* ```html
* {{#if logoIsDisplayed}}
* <div class="entryLogo" style="background-image: url('{{logoUrl}}')"></div>
* {{/if}}
* ```
*/
Template.registerHelper("logoUrl", function (argument){
var config = Session.get('Photonic.ActiveEntry');
if(config && config.logo && config.logo.url){
return config.logo.url;
}else{
return "";
}
});
Template.registerHelper("getButtonColor", function (argument){
return "background-color: " + Theme.getPaletteColor("colorB") + "; ";
});

View File

@ -0,0 +1,168 @@
// @import 'client/app/stylesheets/util/reset.lessimport';
// @import 'client/app/stylesheets/util/lesshat.lessimport';
// @import 'client/app/stylesheets/util/typography.lessimport';
// this probably wants to be moved into clinical:glass-ui
input, button, select{
font-size: 14px;
line-height: 20px;
font-family: 'Open Sans', "Helvetica Neue", Helvetica, Arial, sans-serif;
font-style: 400;
padding: .75rem 0;
line-height: 1.5rem !important;
border: none;
border-radius: 0;
box-sizing: border-box;
//color: #333333;
outline: none;
padding-left: 3em;
}
.entryLogo{
//background-color: lightgray;
height: 200px;
width: 200px;
background-size: contain;
background-repeat: no-repeat;
margin-left: auto;
margin-right: auto;
}
.btn-primary{
background-color: forestgreen;
color: #ffffff;
}
.btn-gray{
background-color: #aaaaaa;
color: white;
}
.btn-main{
font-weight: 300;
letter-spacing: 3px;
text-transform: uppercase;
}
.input-symbol {
display: inline-block;
position: relative;
}
.entryPage {
text-align: center;
input, button{
width: 100%;
}
.fa{
position: absolute;
left: 0px;
top: 0px;
padding-left: 15px;
padding-top: 14px;
}
.checkmarkIcon{
position: absolute;
color: #831d2c;
}
.btn-gray{
width: 100%;
}
.checkmarkTitle{
padding-bottom: 100px;
}
.fa{
position: absolute;
left: 0px;
top: 0px;
padding-left: 15px;
padding-top: 16px;
// color: #555555;
}
.wrapper-auth {
//padding-top: 4em;
@media screen and (min-width: 40em) {
margin: 0 auto;
max-width: 480px;
width: 80%;
}
@media screen and (max-width: 480px) {
margin: 0 auto;
padding-left: 20px;
padding-right: 20px;
}
// .input-symbol{
// border: 1px solid gray;
// }
.title-auth {
color: black;
margin-bottom: .75rem;
}
.subtitle-auth {
color: black;
//margin: 0 15% 3rem;
margin-bottom: 2rem;
padding: 15px;
}
form {
input{
color: #333333;
height: 48px;
border-left: solid 1px !important;
}
.input-symbol {
margin-bottom: 1px;
width: 100%;
}
.btn-primary {
//margin: 1em 5% 0;
//width: 90%;
margin-top: 1em;
@media screen and (min-width: 40em) {
// margin-left: 0;
// margin-right: 0;
width: 100%;
}
}
}
.list-errors {
margin-top: -2rem;
.taskItems {
//.title-caps;
background: white;
color: red;
font-size: .625em; // 10px
margin-bottom: 1px;
padding: .7rem 0;
}
}
}
.link-auth-alt {
//.font-s1;
//.position(absolute, auto, 0, 1em, 0);
color: black;
display: inline-block;
@media screen and (min-width: 40em) {
bottom: 0;
margin-top: 1rem;
position: relative;
}
}
// @media only screen and (max-width: 704px) {
// .fa{
// left: 5%;
// }
// }
}

View File

@ -0,0 +1,10 @@
exports.command = function () {
this
.execute(function () {
return Meteor.logout();
}).pause(1000);
return this;
};

View File

@ -0,0 +1,23 @@
exports.command = function (email, password) {
this.verify.elementPresent("#entrySignIn");
if (email) {
this
.verify.elementPresent("#signInPageEmailInput")
.clearValue("#signInPageEmailInput")
.setValue("#signInPageEmailInput", email);
}
if (password) {
this
.verify.elementPresent("#signInPagePasswordInput")
.clearValue("#signInPagePasswordInput")
.setValue("#signInPagePasswordInput", password);
}
this.click("#signInToAppButton").pause(1000);
return this;
};

View File

@ -0,0 +1,22 @@
exports.command = function (fullname) {
this
.sectionBreak(".signOut()");
if (fullname) {
this
.verify.elementPresent("#usernameLink")
.verify.containsText("#usernameLink", fullname);
}
this
.verify.elementPresent("#logoutLink")
.click("#logoutLink").pause(1000);
if (fullname) {
this
.verify.elementPresent("#usernameLink")
.verify.containsText("#usernameLink", "Sign In");
}
return this;
};

View File

@ -0,0 +1,35 @@
exports.command = function (email, password, title, message) {
this
// .verify.element("#entrySignIn").to.be.visible
// .verify.element("#signInPageTitle").to.be.visible
// .verify.element("#signInPageMessage").to.be.visible
// .verify.element("#signInPageEmailInput").to.be.visible
// .verify.element("#signInPagePasswordInput").to.be.visible
// .verify.element("#signInToAppButton").to.be.visible
// .verify.element("#needAnAccountButton").to.be.visible
//
.verify.elementPresent("#entrySignIn")
.verify.elementPresent("#signInPageTitle")
.verify.elementPresent("#signInPageMessage")
.verify.elementPresent("#signInPageEmailInput")
.verify.elementPresent("#signInPagePasswordInput")
.verify.elementPresent("#signInToAppButton")
.verify.elementPresent("#needAnAccountButton");
if (email) {
this.verify.containsText("#signInPageEmailInput", email);
}
if (password) {
this.verify.containsText("#signInPageEmailInput", password);
}
if (title) {
this.verify.containsText("#signInPageTitle", title);
}
if (message) {
this.verify.containsText("#signInPageMessage", message);
}
return this;
};

View File

@ -0,0 +1,46 @@
<template name="entrySignIn">
<div id="entrySignIn" class="page entryPage" style="{{getOpacityWithCorner}}">
<div class="content-scrollable">
<div class="wrapper-auth">
{{#if logoIsDisplayed}}
<div id="entryAppLogo" class="entryLogo" style="background-image: url('{{logoUrl}}')"></div>
{{/if}}
{{#if isLoggedIn}}
<h1 id="signInPageTitle" class="title-auth">{{fullName}}</h1>
<div id="signInPageMessage" class="subtitle-auth">You are currently signed in.</div>
<button id="logoutButton" class="btn-primary btn-main btn-large" style="{{getButtonColor}}">Logout</button>
{{else}}
<h1 id="signInPageTitle" class="title-auth">Sign In.</h1>
<p id="signInPageMessage" class="subtitle-auth" style="{{getSignInMessageColor}}">{{getSignInMessage}}</p>
<form>
<div class="input-symbol">
<input id="signInPageEmailInput" type="text" name="email" placeholder="Your Email" style="{{getEmailValidationStyling}}" />
<i class="fa fa-envelope-o" title="Your Email"></i>
</div>
<br><br>
<div class="input-symbol">
<input id="signInPagePasswordInput" type="password" name="password" placeholder="Password" style={{getPasswordValidationStyling}} />
<span class="fa fa-lock" title="Password"></span>
</div>
</form>
<br><br>
<button id="signInToAppButton" class="btn-primary btn-main btn-large" style="{{getButtonColor}}">{{getButtonText}}</button>
<br><br>
<button id="needAnAccountButton" class="btn-gray btn-main btn-large">Need an account?</button>
<br><br>
<button id="forgotPasswordButton" class="btn-gray btn-main btn-large">Forgot password?</button>
{{/if}}
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,131 @@
// REFACTOR: Move to ActiveRecord object
Session.set("defaultSignInMessage", "Improve your clinical practice with checklists.");
//==================================================================================================
// ROUTER
Router.route('/entrySignIn', {
template: 'entrySignIn',
name: 'entrySignIn'
});
Router.route('/sign-in', {
template: 'entrySignIn',
name: 'signInRoute'
});
//==================================================================================================
// COMPONENT OUTPUTS
Template.entrySignIn.helpers({
getSignInMessageColor: function (){
if (ActiveEntry.errorMessages.get('signInError')) {
return "color: #a94442; background-color: #f2dede; border-color: #ebccd1;"
} else {
return "color: black;"
}
},
getSignInMessage: function (){
if (ActiveEntry.errorMessages.get('signInError')) {
return ActiveEntry.errorMessages.get('signInError');
} else {
return Session.get('defaultSignInMessage');
}
},
getButtonText: function () {
return "Sign In";
// if (ActiveEntry.errorMessages.get('signInError')){
// return ActiveEntry.errorMessages.get('signInError');
// } else {
// return "Sign In";
// }
},
getEmailValidationStyling: function () {
if (ActiveEntry.errorMessages.equals('email', "Email is required")) {
return "border: 1px solid #a94442";
} else if (ActiveEntry.errorMessages.equals('email', "Email is poorly formatted")) {
return "border: 1px solid #f2dede";
} else if (ActiveEntry.errorMessages.equals('email', "Email present")) {
return "border: 1px solid green";
} else {
return "border: 1px solid gray";
}
},
getPasswordValidationStyling: function () {
if (ActiveEntry.errorMessages.equals('password', "Password is required")) {
return "border: 1px solid #a94442";
} else if (ActiveEntry.errorMessages.equals('password', "Password is weak")) {
return "border: 1px solid #f2dede";
} else if (ActiveEntry.errorMessages.equals('password', "Password present")) {
return "border: 1px solid green";
} else {
return "border: 1px solid gray";
}
}
});
//==================================================================================================
// COMPONENT OUTPUTS
Template.entrySignIn.events({
'click #logoutButton': function () {
Meteor.logout();
},
'click #forgotPasswordButton': function (event) {
event.preventDefault();
Router.go('/forgotPassword');
},
"click #needAnAccountButton": function (event) {
event.preventDefault();
Router.go('/entrySignUp');
},
'keyup input[name="email"]': function (event, template) {
var email = $('input[name="email"]').val();
ActiveEntry.verifyEmail(email);
ActiveEntry.errorMessages.set('signInError', null);
},
'change input[name="email"]': function (event, template) {
var email = $('input[name="email"]').val();
ActiveEntry.verifyEmail(email);
ActiveEntry.errorMessages.set('signInError', null);
},
'keyup #signInPagePasswordInput': function (event, template) {
var password = $('input[name="password"]').val();
ActiveEntry.verifyPassword(password);
ActiveEntry.errorMessages.set('signInError', null);
},
'change #signInPagePasswordInput': function (event, template) {
var password = $('input[name="password"]').val();
ActiveEntry.verifyPassword(password);
ActiveEntry.errorMessages.set('signInError', null);
},
// 'submit': function (event, template) {
// event.preventDefault();
// var emailValue = template.$('[name=email]').val();
// var passwordValue = template.$('[name=password]').val();
//
// ActiveEntry.signIn(emailValue, passwordValue);
// },
'click #signInToAppButton': function (event, template){
console.log('click #signInToAppButton');
// var emailValue = template.$('[name=email]').val();
// var passwordValue = template.$('[name=password]').val();
var emailValue = template.$('#signInPageEmailInput').val();
var passwordValue = template.$('#signInPagePasswordInput').val();
ActiveEntry.signIn(emailValue, passwordValue);
event.preventDefault();
}
});
//==================================================================================================

View File

@ -0,0 +1,17 @@
#entrySignIn{
padding: 40px;
input{
padding-left: 40px;
color: #333333 !important;
}
#errorMessages{
padding-top: .5rem;
padding-bottom: .5rem;
font-size: 14px;
box-sizing: border-box;
width: 100.5%;
margin-bottom: 15px;
color: red;
font-weight: bold;
}
}

View File

@ -0,0 +1,22 @@
// async version calls method on the server
exports.command = function () {
var client = this;
this
.timeoutsAsyncScript(5000)
.executeAsync(function (data, meteorCallback) {
//return HipaaLogger.logEventObject(data);
Meteor.call("dropEntryUsers", data, function (meteorError, meteorResult) {
var response = (meteorError ? {
error: meteorError
} : {
result: meteorResult
});
meteorCallback(response);
});
}, [], function (result) {
//console.log("result.value", result.value);
//client.assert.ok(result.value);
}).pause(1000);
return this;
};

View File

@ -0,0 +1,11 @@
// resetEntry.js
exports.command = function () {
this
.timeoutsAsyncScript(5000)
.executeAsync(function (data, meteorCallback) {
ActiveEntry.reset();
}, []).pause(1000);
return this;
};

View File

@ -0,0 +1,37 @@
exports.command = function (email, password, fullName) {
this.verify.elementPresent("#entrySignUp");
if (email) {
this
.verify.elementPresent("#signUpPageEmailInput")
.clearValue("#signUpPageEmailInput")
.setValue("#signUpPageEmailInput", email);
}
if (password) {
this
.verify.elementPresent("#signUpPagePasswordInput")
.clearValue("#signUpPagePasswordInput")
.setValue("#signUpPagePasswordInput", password)
.verify.elementPresent("#signUpPagePasswordConfirmInput")
.clearValue("#signUpPagePasswordConfirmInput")
.setValue("#signUpPagePasswordConfirmInput", password);
}
if (fullName) {
this
.verify.elementPresent("#signUpPageFullNameInput")
.clearValue("#signUpPageFullNameInput")
.setValue("#signUpPageFullNameInput", fullName);
}
this.click("#signUpPageJoinNowButton").pause(300);
return this;
};

View File

@ -0,0 +1,31 @@
exports.command = function (email, password, fullName, message) {
this
.verify.elementPresent("#entrySignUp")
.verify.elementPresent("#signUpPageTitle")
.verify.elementPresent("#signUpPageMessage")
.verify.elementPresent("#signUpPageFullNameInput")
.verify.elementPresent("#signUpPageEmailInput")
.verify.elementPresent("#signUpPagePasswordInput")
.verify.elementPresent("#signUpPagePasswordConfirmInput")
.verify.elementPresent("#signUpPageJoinNowButton")
.verify.elementPresent("#signUpPageSignInButton");
if (email) {
this.verify.containsText("#signUpPageEmailInput", email);
}
if (password) {
this.verify.containsText("#signUpPagePasswordInput", password)
.verify.containsText("#signUpPagePasswordConfirmInput", password);
}
if (fullName) {
this.verify.containsText("#signUpPageFullNameInput", fullName);
}
if (message) {
this.verify.elementPresent("#errorMessages")
.verify.containsText("#errorMessages", message);
}
return this;
};

View File

@ -0,0 +1,63 @@
<template name="entrySignUp">
<div id="entrySignUp" class="page entryPage" style="{{getOpacityWithCorner}}">
<div class="content-scrollable">
<div class="wrapper-auth">
{{#if logoIsDisplayed}}
<div id="entryAppLogo" class="entryLogo" style="background-image: url('{{logoUrl}}')"></div>
{{/if}}
<h1 id="signUpPageTitle" class="title-auth">Join.</h1>
<div id="signUpPageMessage" class="subtitle-auth" style="{{getSignUpMessageColor}}">{{getSignUpMessage}}</div>
<form>
{{#if errorMessages}}
<div id="errorMessages" class="list-errors">
{{#each errorMessages}}
<div class="alert alert-danger list-item">{{this}}</div>
{{/each}}
</div>
{{/if}}
<div class="input-symbol ">
<input id="signUpPageFullNameInput" type="text" name="fullName" placeholder="Full Name" style="{{getFullNameStyling}}" />
<i class="fa fa-user" title="Full Name"></i>
</div>
<br>
<br>
<div class="input-symbol">
<input id="signUpPageEmailInput" type="email" name="email" placeholder="Email Address" style="{{getEmailStyling}}" />
<i class="fa fa-envelope-o" title="Your Email"></i>
</div>
<br>
<br>
<div class="input-symbol ">
<input id="signUpPagePasswordInput" type="password" name="password" placeholder="Password" style="{{getPasswordStyling}}" />
<span class="fa fa-lock" title="Password"></span>
<!-- <span class="icon-lock" title="Password"></span> -->
</div>
<br>
<br>
<div class="input-symbol">
<input id="signUpPagePasswordConfirmInput" type="password" name="confirm" placeholder="Confirm Password" style="{{getConfirmPasswordStyling}}" />
<span class="fa fa-lock" title="Confirm Password"></span>
<!-- <span class="icon-lock" title="Confirm Password"></span> -->
</div>
</form>
<br>
<br>
<button id="signUpPageJoinNowButton" type="submit" class="btn-primary btn-main btn-large" style="{{getButtonColor}}">Join Now</button>
<br>
<br>
<button id="signUpPageSignInButton" class="btn-gray btn-main btn-large">Have an account? Sign in</button>
</div>
<!-- <a id="signUpPageSignInButton" href="{{pathFor 'entrySignIn'}}" class="link-auth-alt">Have an account? Sign in</a> -->
</div>
</div>
</template>

View File

@ -0,0 +1,139 @@
//==================================================================================================
// ROUTER
Router.route('/entrySignUp', {
template: 'entrySignUp',
name: 'entrySignUp'
});
Router.route('/sign-up', {
template: 'entrySignUp',
name: 'signUpRoute'
});
//==================================================================================================
Template.entrySignUp.helpers({
getSignUpMessageColor: function (){
if (ActiveEntry.errorMessages.get('signInError')) {
return "color: #a94442; background-color: #f2dede; border-color: #ebccd1;"
} else {
return "color: black;"
}
},
getSignUpMessage: function (){
if (ActiveEntry.errorMessages.get('signInError')) {
return ActiveEntry.errorMessages.get('signInError');
} else {
return Session.get('defaultSignInMessage');
}
},
getButtonText: function () {
if (ActiveEntry.errorMessages.get('signInError')) {
return ActiveEntry.errorMessages.get('signInError').message;
} else {
return "Sign In";
}
},
getEmailStyling: function () {
if (ActiveEntry.errorMessages.equals('email', "Email is required")) {
return "border: 1px solid #a94442";
} else if (ActiveEntry.errorMessages.equals('email', "Email is poorly formatted")) {
return "border: 1px solid #f2dede";
} else if (ActiveEntry.errorMessages.equals('email', "Email present")) {
return "border: 1px solid green";
} else {
return "border: 1px solid gray";
}
},
getPasswordStyling: function () {
if (ActiveEntry.errorMessages.equals('password', "Password is required")) {
return "border: 1px solid #a94442";
} else if (ActiveEntry.errorMessages.equals('password', "Password is weak")) {
return "border: 1px solid #f2dede";
} else if (ActiveEntry.errorMessages.equals('password', "Password present")) {
return "border: 1px solid green";
} else {
return "border: 1px solid gray";
}
},
getConfirmPasswordStyling: function () {
if (ActiveEntry.errorMessages.equals('confirm', "Password is required")) {
return "border: 1px solid #a94442";
} else if (ActiveEntry.errorMessages.equals('confirm', "Password is weak")) {
return "border: 1px solid #f2dede";
} else if (ActiveEntry.errorMessages.equals('confirm', "Passwords match")) {
return "border: 1px solid green";
} else {
return "border: 1px solid gray";
}
},
getFullNameStyling: function () {
if (ActiveEntry.errorMessages.equals('fullName', "Name is required")) {
return "border: 1px solid #a94442";
} else if (ActiveEntry.errorMessages.equals('fullName', "Name is probably not complete")) {
return "border: 1px solid #f2dede";
} else if (ActiveEntry.errorMessages.equals('fullName', "Name present")) {
return "border: 1px solid green";
} else {
return "border: 1px solid gray";
}
}
});
Template.entrySignUp.events({
"click #signUpPageSignInButton": function (event) {
event.preventDefault();
Router.go('/entrySignIn');
},
'change, keyup #signUpPageEmailInput': function (event, template) {
var email = $('[name="email"]').val();
ActiveEntry.verifyEmail(email);
ActiveEntry.errorMessages.set('signInError', null);
},
'change, keyup #signUpPagePasswordInput': function (event, template) {
var password = $('[name="password"]').val();
ActiveEntry.verifyPassword(password);
ActiveEntry.errorMessages.set('signInError', null);
},
'change, keyup #signUpPagePasswordConfirmInput': function (event, template) {
var password = $('[name="password"]').val();
var confirmPassword = $('[name="confirm"]').val();
// var password = $('#signUpPagePasswordInput').val();
// var confirmPassword = $('#signUpPagePasswordConfirmInput').val();
ActiveEntry.verifyConfirmPassword(password, confirmPassword);
ActiveEntry.errorMessages.set('signInError', null);
},
'change, keyup #signUpPageFullNameInput': function (event, template) {
var fullName = template.$('[name="fullName"]').val();
ActiveEntry.verifyFullName(fullName);
ActiveEntry.errorMessages.set('signInError', null);
},
// TODO: this is outdated, and should be changed to match the signature/pattern in entrySignIn
'click #signUpPageJoinNowButton': function (event, template) {
event.preventDefault();
ActiveEntry.errorMessages.set('signInError', null);
ActiveEntry.verifyPassword(event, template);
var newUser = {
fullName: template.$('[name="fullName"]').val(),
email: template.$('[name="email"]').val(),
password: template.$('[name="password"]').val(),
confirm: template.$('[name="confirm"]').val()
};
ActiveEntry.signUp(
newUser.email,
newUser.password,
newUser.confirm,
newUser.fullName
);
}
});

View File

@ -0,0 +1,7 @@
#entrySignUp{
padding: 40px;
input{
padding-left: 40px !important;
color: black;
}
}

View File

@ -0,0 +1,4 @@
exports.command = function () {
this
.verify.elementPresent("#forgotPassword"); return this;
};

View File

@ -0,0 +1,24 @@
<template name="forgotPassword">
<div id="forgotPassword" class="page entryPage" style="{{getOpacityWithCorner}}">
<div class="content-scrollable">
<div class="wrapper-auth">
<div class="entryLogo" style="background-image: url('{{getLogoUrl}}')"></div>
<h1 id="signInPageTitle" class="title-auth">Forgot Password</h1>
<div id="signInPageMessage" class="subtitle-auth" >Imdivrove your clincal practice with checklists.</div>
<form>
<div class="input-symbol">
<input id="signInPageEmailInput" type="email" name="email" placeholder="Your Email" style="{{getForgotPasswordStyle}}" />
<i class="fa fa-envelope-o" title="Your Email"></i>
</div>
<br><br>
<button id="sendReminderButton" type="submit" class="btn-gray btn-main btn-large" style="{{getButtonColor}}">Send Reminder</button><br><br>
</form>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,22 @@
//==========================================
Router.route('/forgotPassword', {
name: "forgotPassword",
template: "forgotPassword"
});
Template.forgotPassword.helpers({
getForgotPasswordStyle: function (){
return "border: 1px solid gray";
}
});
Template.forgotPassword.events({
"submit": function (event, template) {
event.preventDefault();
console.log('send reminder!');
Accounts.forgotPassword({email: $('#signInPageEmailInput').val()});
}
});

View File

@ -0,0 +1,5 @@
#forgotPassword{
input{
padding-left: 40px;
}
}

View File

@ -0,0 +1,48 @@
if (Meteor.isClient) {
Accounts.onResetPasswordLink(function (token, done){
console.log('Accounts.onResearchPasswordLink');
console.log('Sending reset password email...');
console.log('NOT IMPLEMENTED YET. PLEASE LOG AN ISSUE');
console.log('token: ' + token);
done();
});
Accounts.onEnrollmentLink(function (token, done){
console.log('Accounts.onResearchPasswordLink');
console.log('Sending enrollment email...');
console.log('NOT IMPLEMENTED YET. PLEASE LOG AN ISSUE');
console.log('token: ' + token);
done();
});
Accounts.onEmailVerificationLink(function (token, done){
console.log('Accounts.onEmailVerificationLink');
console.log('Sending verification email...');
console.log('NOT IMPLEMENTED YET. PLEASE LOG AN ISSUE');
console.log('token: ' + token);
done();
});
}
if (Meteor.isServer){
// Support for playing D&D: Roll 3d6 for dexterity
Accounts.onCreateUser(function(options, user) {
var d6 = function () { return Math.floor(Random.fraction() * 6) + 1; };
user.dexterity = d6() + d6() + d6();
user.role = "user";
// We still want the default hook's 'profile' behavior.
if (options.profile){
user.profile = options.profile;
}
return user;
});
}

View File

@ -0,0 +1,150 @@
ActiveEntry = {};
ActiveEntry.isAbc = function () {
return "abc";
};
if (Meteor.isClient) {
Session.setDefault('Photonic.ActiveEntry', {
logo: {
url: "https://upload.wikimedia.org/wikipedia/commons/1/1a/Photon-photon_scattering.png",
displayed: true
},
signIn: {
displayFullName: true,
destination: "/"
},
signUp: {
destination: "/"
},
themeColors: {
primary: ""
}
});
}
if (Meteor.isClient) {
ActiveEntry.errorMessages = new ReactiveDict('errorMessages');
ActiveEntry.errorMessages.set('signInError', false);
}
ActiveEntry.configure = function (configObject) {
if (Meteor.isClient) {
Session.set('Photonic.ActiveEntry', configObject);
}
};
ActiveEntry.verifyPassword = function (password) {
if (password.length === 0) {
ActiveEntry.errorMessages.set('password', 'Password is required');
} else if (password.length < 8) {
ActiveEntry.errorMessages.set('password', 'Password is weak');
} else if (password.length >= 8) {
ActiveEntry.errorMessages.set('password', 'Password present');
}
};
ActiveEntry.verifyConfirmPassword = function (password, confirmPassword) {
if (confirmPassword.length === 0) {
ActiveEntry.errorMessages.set('confirm', 'Password is required');
} else if (confirmPassword.length < 8) {
ActiveEntry.errorMessages.set('confirm', 'Password is weak');
}
if (confirmPassword === password) {
ActiveEntry.errorMessages.set('confirm', 'Passwords match');
}
};
ActiveEntry.verifyEmail = function (email) {
if (email.length === 0) {
ActiveEntry.errorMessages.set('email', 'Email is required');
} else if (email.indexOf("@") === -1){
ActiveEntry.errorMessages.set('email', 'Email is poorly formatted');
} else if (email.indexOf("@") >= 0){
ActiveEntry.errorMessages.set('email', 'Email present');
}
};
ActiveEntry.verifyFullName = function (fullName) {
if (fullName.length === 0) {
ActiveEntry.errorMessages.set('fullName', 'Name is required');
} else if (fullName.indexOf(" ") === -1){
ActiveEntry.errorMessages.set('fullName', 'Name is probably not complete');
} else if (fullName.indexOf(" ") >= 0){
ActiveEntry.errorMessages.set('fullName', 'Name present');
}
};
ActiveEntry.signIn = function (emailValue, passwordValue){
ActiveEntry.verifyPassword(passwordValue);
ActiveEntry.verifyEmail(emailValue);
Meteor.loginWithPassword({email: emailValue}, passwordValue, function (error, result) {
if (error) {
ActiveEntry.errorMessages.set('signInError', error.message);
} else {
console.log('result', result);
var ActiveEntryConfig = Session.get('Photonic.ActiveEntry');
console.log('ActiveEntryConfig', JSON.stringify(ActiveEntryConfig));
Router.go(ActiveEntryConfig.signIn.destination);
}
});
};
ActiveEntry.signUp = function (emailValue, passwordValue, confirmPassword, fullName){
ActiveEntry.verifyEmail(emailValue);
ActiveEntry.verifyPassword(passwordValue);
ActiveEntry.verifyConfirmPassword(passwordValue, confirmPassword);
ActiveEntry.verifyFullName(fullName);
Accounts.createUser({
email: emailValue,
password: passwordValue,
profile: {
fullName: fullName
}
}, function (error, result) {
if (error) {
console.log(error);
ActiveEntry.errorMessages.set('signInError', error.message);
} else {
var ActiveEntryConfig = Session.get('Photonic.ActiveEntry');
Router.go(ActiveEntryConfig.signUp.destination);
}
});
// Meteor.loginWithPassword({email: emailValue}, passwordValue, function (error, result) {
// if (error) {
// console.log(error);
// Session.set('errorMessage', error);
// }
//
// if (result) {
// console.log('result', result);
// }
// var ActiveEntryConfig = Session.get('Photonic.ActiveEntry');
// console.log('ActiveEntryConfig', JSON.stringify(ActiveEntryConfig));
// Router.go(ActiveEntryConfig.signIn.destination);
// });
};
ActiveEntry.signOut = function (){
Meteor.logout();
};
ActiveEntry.reset = function (){
ActiveEntry.errorMessages.set('fullName', false);
ActiveEntry.errorMessages.set('email', false);
ActiveEntry.errorMessages.set('confirm', false);
ActiveEntry.errorMessages.set('password', false);
};
ActiveEntry.logoIsDisplayed = function (){
var ActiveEntryConfig = Session.get('Photonic.ActiveEntry');
return ActiveEntryConfig.logo.displayed;
}

View File

@ -0,0 +1,73 @@
Package.describe({
name: 'clinical:active-entry',
version: '1.5.14',
summary: 'SignIn, SignUp, and ForgotPassword pages for Clinical Framework.',
git: 'https://github.com/clinical-meteor/clinical-active-entry',
documentation: 'README.md'
});
Package.onUse(function (api) {
api.versionsFrom('1.1.0.3');
api.use([
'meteor-platform',
'templating',
'clinical:router@2.0.17',
'grove:less@0.1.1',
'session',
'reactive-dict',
'accounts-base',
'accounts-password'
], ['client']);
api.use([
'accounts-base',
'accounts-password'
], ['server']);
api.addFiles([
'lib/ActiveEntry.js',
'lib/Accounts.js'
]);
api.imply('accounts-base');
api.imply('accounts-password');
api.addFiles([
'components/entryPages.js',
'components/entryPages.less',
'components/entrySignIn/entrySignIn.html',
'components/entrySignIn/entrySignIn.js',
'components/entrySignIn/entrySignIn.less',
'components/entrySignUp/entrySignUp.html',
'components/entrySignUp/entrySignUp.js',
'components/entrySignUp/entrySignUp.less',
'components/forgotPassword/forgotPassword.html',
'components/forgotPassword/forgotPassword.js',
'components/forgotPassword/forgotPassword.less',
], ['client']);
api.addFiles('server/methods.js', "server", {testOnly: true});
api.export("ActiveEntry");
});
Package.onTest(function (api) {
api.use([
'templating',
'clinical:router@2.0.13',
'grove:less@0.1.1',
'standard-app-packages'
], ['client']);
api.use('tinytest');
api.use('clinical:active-entry');
api.use('clinical:verification');
api.addFiles('tests/gagarin/activeEntryTests.js');
});

View File

@ -0,0 +1,12 @@
Meteor.methods({
initializeEntryUsers: function (){
console.log('Initializing Users', Meteor.users.find().fetch());
},
dropEntryUsers: function (){
console.log('Drop Users', Meteor.users.find().fetch());
Meteor.users.find().forEach(function(user){
Meteor.users.remove({_id: user._id});
});
}
});

View File

@ -0,0 +1,197 @@
// var nightwatch = require('nightwatch');
describe('clinical:active-entry', function () {
var server = meteor();
var client = browser(server);
// before(function () {
// return server.promise(function (resolve){
// Meteor.users.find().forEach(function(user){
// Meteor.users.remove({_id: user._id});
// }, function(){
// resolve();
// });
// });
// });
// afterEach(function (){
// return client.promise(function (resolve){
// Meteor.logout(function(error, result){
// resolve();
// });
// });
// });
it("ActiveEntry object should be loaded on client and server", function () {
return server.execute(function () {
expect(ActiveEntry.isAbc()).to.equal('abc');
}).then(function (data){
return client.execute(function (a) {
expect(ActiveEntry.isAbc()).to.equal('abc');
});
});
});
it("Error messages should be empty by default", function () {
return client.execute(function () {
expect(ActiveEntry.errorMessages.get('signInError')).to.equal(false);
});
});
// ActiveEntry.verifyEmail
it('Email validation confirms it is a properly formatted email.', function () {
return client.execute(function (a) {
ActiveEntry.verifyEmail('janedoe@somewhere.com');
expect(ActiveEntry.errorMessages.get('email')).to.equal("Email present");
ActiveEntry.verifyEmail('');
expect(ActiveEntry.errorMessages.get('email')).to.equal("Email is required");
ActiveEntry.verifyEmail('janedoe.somewhere.com');
expect(ActiveEntry.errorMessages.get('email')).to.equal("Email is poorly formatted");
});
});
// ActiveEntry.verifyPassword
it('Password validation confirms it is a properly formatted password.', function () {
return client.execute(function (a) {
ActiveEntry.verifyPassword('');
expect(ActiveEntry.errorMessages.get('password')).to.equal("Password is required");
ActiveEntry.verifyPassword('kittens');
expect(ActiveEntry.errorMessages.get('password')).to.equal("Password is weak");
ActiveEntry.verifyPassword('kittens123');
expect(ActiveEntry.errorMessages.get('password')).to.equal("Password present");
});
});
// ActiveEntry.verifyConfirmPassword
it('Password match validation confirms that two passwords are the same.', function () {
return client.execute(function (a) {
ActiveEntry.verifyConfirmPassword('kittens123', '');
expect(ActiveEntry.errorMessages.get('confirm')).to.equal("Password is required");
ActiveEntry.verifyConfirmPassword('kittens123', 'kittens');
expect(ActiveEntry.errorMessages.get('confirm')).to.equal("Password is weak");
ActiveEntry.verifyConfirmPassword('kittens123', 'kittens123');
expect(ActiveEntry.errorMessages.get('confirm')).to.equal("Passwords match");
});
});
// ActiveEntry.verifyFullName
it('Fullname validation confirms that at least a first and last name are entered.', function () {
return client.execute(function (a) {
ActiveEntry.verifyFullName('');
expect(ActiveEntry.errorMessages.get('fullName')).to.equal("Name is required");
ActiveEntry.verifyFullName('Jane');
expect(ActiveEntry.errorMessages.get('fullName')).to.equal("Name is probably not complete");
ActiveEntry.verifyFullName('Jane Doe');
expect(ActiveEntry.errorMessages.get('fullName')).to.equal("Name present");
});
});
// ActiveEntry.signIn
it('Newly created user record should have role, profile, and name set.', function () {
return client.execute(function () {
ActiveEntry.signUp('janedoe@test.org', 'janedoe123', 'janedoe123', 'Jane Doe');
expect(ActiveEntry.errorMessages.get('fullName')).to.equal("Name present");
}).then(function (){
return server.wait(300, 'until account is created on the server', function () {
return Meteor.users.findOne({'emails.address': 'janedoe@test.org'});
}).then(function (user){
expect(user.role).to.equal('user');
expect(user.profile.fullName).to.equal('Jane Doe');
});
});
});
it("Newly created user should have fullName(), preferredName(), and familyName() methods.", function () {
return server.execute(function () {
var user = Meteor.users.findOne({'emails.address': 'janedoe@test.org'});
expect(user).to.be.ok;
expect(user.fullName()).to.equal('Jane Doe');
expect(user.givenName()).to.equal('Jane');
expect(user.familyName()).to.equal('Doe');
}).then(function(){
// client.wait(500, "until user is logged out", function(){
// Meteor.logout();
// });
return client.promise(function (resolve){
Meteor.logout(function(error, result){
resolve();
});
});
});
});
it("Newly created user can sign in to the application.", function () {
return client.execute(function () {
expect(Meteor.userId()).to.not.exist;
ActiveEntry.signIn('janedoe@test.org', 'janedoe123');
}).then(function (){
client.wait(3000, "for user to sign in", function (){
expect(Meteor.userId()).to.exist;
});
});
});
it("Newly created user can sign out of the application.", function () {
return client.execute(function () {
expect(Meteor.userId()).to.not.exist;
ActiveEntry.signIn('janedoe@test.org', 'janedoe123');
}).then(function (){
client.wait(3000, "for user to sign in", function (){
expect(Meteor.userId()).to.exist;
ActiveEntry.signOut('janedoe@test.org');
}).then(function (){
expect(Meteor.userId()).to.not.exist;
});
});
});
// it("config should be able to change company logo", function () {
//
// });
// it("config should be able to change entry message text", function () {
//
// });
// it("new user should be able to register on desktop", function () {
// client.location = "/sign-in";
//
// client.execute(function () {
// expect($('#entrySignIn')).to.exist();
// expect($('#signInPageEmailInput')).to.exist();
// expect($('#signInPagePasswordInput')).to.exist();
// expect($('#signInToAppButton')).to.exist();
// }).setValue('#signInPageEmailInput', 'house@test.org')
// .setValue('#signInPagePasswordInput', 'house@test.org')
// .click('#signInToAppButton').execute(function(){
// expect($('#entrySignIn')).to.exist();
// expect($('#signInPageEmailInput')).to.exist();
// expect($('#signInPagePasswordInput')).to.exist();
// expect($('#signInToAppButton')).to.exist();
// // expect($('#entrySignIn')).to.not.exist();
// // expect($('#signInPageEmailInput')).to.not.exist();
// // expect($('#signInPagePasswordInput')).to.not.exist();
// // expect($('#signInToAppButton')).to.not.exist();
// });
// // .then(function(data){
// // return server.execute(3000, 'until the account is created', function () {
// // expect(Meteor.users.find().count()).to.be.above(20);
// // });
// // });
// });
});

View File

@ -0,0 +1,22 @@
// async version calls method on the server
exports.command = function () {
var client = this;
this
.timeoutsAsyncScript(5000)
.executeAsync(function (data, meteorCallback) {
//return HipaaLogger.logEventObject(data);
Meteor.call("dropCollaborations", data, function (meteorError, meteorResult) {
var response = (meteorError ? {
error: meteorError
} : {
result: meteorResult
});
meteorCallback(response);
});
}, [], function (result) {
console.log("result.value", result.value);
client.assert.ok(result.value);
}).pause(1000);
return this;
};

View File

@ -0,0 +1,184 @@
// when new user fills out form and registers, new user should get created
// when user signs in with username and password, should redirect to home page
// newly created user record should have role
// newly created user record should have profile
// newly created user record should have full, preferred, and family name
// user object should return first name
// user object should return last name
// user should be able to request reset password email
// user should be able to request be able to create new account
// guest should be notified if username already exists
// guest should be notified if passwords do not match
// guest should be notified if email is not correctly formatted
// new user should be able to register on desktop
// new user should be able to register on tablet
// existing user should be able to sign in on desktop
// existing user should be able to sign in on tablet
// existing user should be able to sign in on phone
// company logo should display on sign//in page
module.exports = {
tags: ['users', 'entry'],
before: function(client){
client
.url("http://localhost:3000/entrySignUp")
.initializeUsers()
.resizeWindow(1024, 768)
},
"new user should be able to register on desktop" : function (client) {
client
.verify.elementPresent("#entrySignUp")
.verify.elementPresent("#signUpPageTitle")
.verify.elementPresent("#signUpPageMessage")
.verify.elementPresent("#signUpPageEmailInput")
.verify.elementPresent("#signUpPagePasswordInput")
.verify.elementPresent("#signUpPageJoinNowButton")
.verify.elementPresent("#signUpPageSignInButton")
},
"company logo should display on sign-in page" : function (client) {
client
.verify.elementPresent("#entrySignUp")
.verify.elementPresent("#entryAppLogo")
},
"user should be able to request be able to create new account" : function (client) {
client.verify.elementPresent("#signUpPageEmailInput")
.verify.elementPresent("#signUpPagePasswordInput")
.verify.elementPresent("#signUpPagePasswordInput")
.verify.elementPresent("#signUpPageJoinNowButton")
},
"guest should be notified if password is insecure" : function (client) {
client
.clearValue("input")
.verify.elementPresent("#signUpPagePasswordInput")
.verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid gray')
.setValue("#signUpPagePasswordInput", "jan")
.verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid rgb(242, 222, 222)')
.setValue("#signUpPagePasswordInput", "icedoe123")
.verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid green')
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid gray')
.setValue("#signUpPagePasswordConfirmInput", "ja")
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid rgb(242, 222, 222)')
.clearValue("#signUpPagePasswordConfirmInput")
.setValue("#signUpPagePasswordConfirmInput", "janicedoe123")
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid green')
},
"guest should be notified if passwords do not match" : function (client) {
client
.clearValue("#signUpPagePasswordConfirmInput")
.clearValue("#signUpPagePasswordInput")
.resetEntry()
.pause(500)
.verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid gray')
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid gray')
.setValue("#signUpPagePasswordInput", "janicedoe123")
.verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid green')
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid gray')
.setValue("#signUpPagePasswordConfirmInput", "janicedoe123")
.verify.cssProperty('#signUpPagePasswordInput', 'border', '1px solid green')
.verify.cssProperty('#signUpPagePasswordConfirmInput', 'border', '1px solid green')
},
"guest should be notified if email is not correctly formatted" : function (client) {
client
.clearValue("#signUpPageEmailInput")
.resetEntry()
.verify.elementPresent("#signUpPageEmailInput")
.verify.cssProperty('#signUpPageEmailInput', 'border', '1px solid gray')
.setValue("#signUpPageEmailInput", "janicedoe")
.verify.cssProperty('#signUpPageEmailInput', 'border', '1px solid rgb(242, 222, 222)')
.setValue("#signUpPageEmailInput", "@symptomatic.io")
.verify.cssProperty('#signUpPageEmailInput', 'border', '1px solid green')
},
"when new user fills out form and registers, new user should get created" : function (client) {
client
.verify.elementPresent("#entrySignUp")
.clearValue("#signUpPagePasswordConfirmInput")
.clearValue("#signUpPagePasswordInput")
.clearValue("#signUpPageFullNameInput")
.clearValue("#signUpPageEmailInput")
.resetEntry()
.setValue("#signUpPageFullNameInput", "Janice Doe")
.setValue("#signUpPageEmailInput", "janicedoe@symptomatic.io")
.setValue("#signUpPagePasswordInput", "janicedoe123")
.setValue("#signUpPagePasswordConfirmInput", "janicedoe123")
.click("#signUpPageJoinNowButton").pause(1000)
.verify.containsText("#usernameLink", "janicedoe@symptomatic.io")
},
"user should be able to signout" : function (client) {
client
.verify.elementPresent("#logoutButton")
.click("#logoutButton").pause(300)
.verify.containsText("#usernameLink", "Sign In")
},
"user should be able to request reset password email" : function (client) {
client
.url("http://localhost:3000/entrySignIn")
.verify.elementPresent("#forgotPasswordButton")
.click("#forgotPasswordButton")
.verify.elementPresent("#forgotPassword")
.verify.elementPresent("#signInPageEmailInput")
.verify.elementPresent("#sendReminderButton")
},
"existing user should be able to sign in on desktop" : function (client) {
client
.url("http://localhost:3000/entrySignIn")
.resizeWindow(1600, 1200)
.verify.containsText("#usernameLink", "Sign In")
.signIn("janicedoe@symptomatic.io", "janicedoe123").pause(500)
.verify.containsText("#usernameLink", "janicedoe@symptomatic.io")
.click("#logoutButton").pause(200)
.verify.containsText("#usernameLink", "Sign In")
},
"existing user should be able to sign in on tablet" : function (client) {
client
.url("http://localhost:3000/entrySignIn")
.resizeWindow(1024, 768)
.verify.containsText("#usernameLink", "Sign In")
.signIn("janicedoe@symptomatic.io", "janicedoe123").pause(500)
.verify.containsText("#usernameLink", "janicedoe@symptomatic.io")
.click("#logoutButton").pause(200)
.verify.containsText("#usernameLink", "Sign In")
},
"existing user should be able to sign in on phone" : function (client) {
client
.url("http://localhost:3000/entrySignIn")
.resizeWindow(320, 960)
// .verify.containsText("#usernameLink", "Sign In")
.signIn("janicedoe@symptomatic.io", "janicedoe123").pause(500)
.click("#sidebarToggle").pause(300)
.verify.containsText("#usernameLink", "janicedoe@symptomatic.io")
.click("#logoutButton").pause(200)
.verify.containsText("#usernameLink", "Sign In")
},
"if anonymous user tries to log in with non-existing account, a message is shown" : function (client) {
client
.url("http://localhost:3000/entrySignIn")
.resizeWindow(1024, 768)
.signIn("alice@symptomatic.io", "alice123").pause(500)
.verify.containsText("#signInPageMessage", "User not found [403]")
.verify.cssProperty("#signInPageMessage", "color", "rgba(169, 68, 66, 1)")
.verify.cssProperty("#signInPageMessage", "background-color", "rgba(242, 222, 222, 1)")
.verify.cssProperty("#signInPageMessage", "border-color", "rgb(235, 204, 209)")
},
"anonymous guest should be notified if email already exists" : function (client) {
client
.url("http://localhost:3000/entrySignUp")
.resizeWindow(1024, 768)
.signUp("janicedoe@symptomatic.io", "janicedoe123").pause(500)
.click("#signUpPageEmailInput").pause(500)
.verify.elementPresent("#signUpPageMessage")
.verify.containsText("#signUpPageMessage", "Email already exists. [403]")
},
after: function(client){
client
.dropEntryUsers()
.end();
}
};

View File

@ -0,0 +1,15 @@
## clinical:themes
Theme selection component for ClinicalFramework apps.
------------------------
### Installation
````
meteor add clinical:themes
````
------------------------
### License
MIT License. Use as you wish, including for commercial purposes.

View File

@ -0,0 +1,31 @@
Package.describe({
name: 'clinical:themes',
version: '0.0.1',
// Brief, one-line summary of the package.
summary: 'Theme selection component for ClinicalFramework apps.',
// URL to the Git repository containing the source code for this package.
git: 'https://github.com/awatson1978/clinical-themes',
// By default, Meteor will default to using README.md for documentation.
// To avoid submitting documentation, set this field to null.
documentation: 'README.md'
});
Package.onUse(function (api) {
api.versionsFrom('1.2.1');
api.use('meteor-platform');
api.use('less');
api.addFiles('lib/ActiveThemes.js');
api.addFiles('components/themeCard.html');
api.addFiles('components/themeCard.js');
api.addFiles('components/themeCard.less');
});
Package.onTest(function (api) {
api.use('tinytest');
api.use('clinical:themes');
api.addFiles('tests/clinical-themes.js');
});

View File

@ -0,0 +1,10 @@
// Write your tests here!
// Here is an example.
describe('clinical:collaborations-ui', function () {
it.client('runs only in client', function () {
expect(Meteor.isClient).to.be.true;
});
it.server('runs only in server', function () {
expect(Meteor.isServer).to.be.true;
});
});

View File

@ -0,0 +1 @@
.build*

View File

@ -0,0 +1,84 @@
## clinical:ui-vocabulary
Package to provide semantic CSS classes to your app. Based on Bootstrap3.
=========================
#### Installation
Simply nstall the clinical-ui-vocabulary package from the command line, like so:
````js
meteor add clinical:ui-vocabulary
````
Then feel free to use the following CSS classes in your application.
=========================
#### Vocabulary - Anchoring
- fixed-layout
- left-anchored
- right-anchored
- bottom-anchored
- top-anchored
- right-aligned
- left-aligned
- absolute-layout
- fixed-layout
=========================
#### Vocabulary - Borders
- gray-border
- with-rounded-corners
=========================
#### Vocabulary - Fonts
- monospace
=========================
#### Vocabulary - Haptics
- clickable
- unselectable
=========================
#### Vocabulary - Padding
- well-behaved
- padded
- padded-horizontal, with-horizontal-padding, horizontally-padded
- padded-vertical, with-vertical-padding, vertically-padded
- with-right-padding
- with-left-padding
- without-padding, nopadding, no-padding
- without-right-padding
- without-left-padding
- without-top-padding
- without-bottom-padding
- without-vertical-padding
- without-horizontal-padding
=========================
#### Vocabulary - Colors
- dark
- light
- white
- black
- gray
- lightgray
- darkgray
- transparent
- glass
- fog
- mostly-opaque
- red
- blue
- green
- maroon
- etc

View File

@ -0,0 +1,56 @@
//---------------------------------
// layouts
.absolute-layout{
position:absolute;
}
.fixed-layout{
position: fixed;
}
//---------------------------------
// anchoring
.left-anchored{
position: fixed;
left: 0px;
}
.right-anchored{
position: fixed;
right: 0px;
}
.float-right{
position: absolute;
float: right;
right: 0px;
}
.bottom-anchored{
position: fixed;
bottom: 0px;
}
.top-anchored{
position: fixed;
top: 0px;
}
// alignment
.right-aligned{
text-align: right;
}
.left-aligned{
text-align: left;
}
// layouts
.absolute-layout{
position:absolute;
}
.fixed-layout{
position: fixed;
}

View File

@ -0,0 +1,6 @@
.with-rounded-corners {
-moz-border-radius: .4em;
-webkit-border-radius: .4em;
border-radius: .4em;
}

View File

@ -0,0 +1,66 @@
.dark{
color: white;
background-color: black;
}
.light{
color: black;
background-color: white;
}
.maroon{
color: #6B1A2C;
}
.green{
color: green;
}
.red{
color: red;
}
.blue{
color: blue;
}
.yellow{
color: yellow;
}
.purple{
color: purple;
}
.orange{
color: orange;
}
.black{
color: black;
}
.white{
color: white;
}
.gray{
color: gray;
}
.lightgray{
color: lightgray;
}
.darkgray{
color: darkgray;
}
.notepad{
background-color: lightyellow;
}
.transparent{
opacity: 0.3;
}
.glass{
opacity: 0.8;
background: lightgray;
}
.fog{
opacity: 0.8;
background: gray;
}
.mostly-opaque{
opacity: 0.2;
}
.mostly-transparent{
}

View File

@ -0,0 +1,3 @@
.monospace{
font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
}

View File

@ -0,0 +1,13 @@
//-----------------------------------------
// Clicking
.clickable{
cursor: pointer;
}
.unselectable{
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}

View File

@ -0,0 +1,27 @@
Package.describe({
summary: "UI vocabulary for ClinicalFramework.",
version: "1.0.5",
git: "http://github.com/awatson1978/clinical-ui-vocabulary.git",
name: "clinical:ui-vocabulary"
});
Package.onUse(function(api) {
api.versionsFrom('1.2.1');
api.use('ian:bootstrap-3@3.3.1');
api.addFiles('anchoring.less', 'client');
api.addFiles('borders.less', 'client');
api.addFiles('colors.less', 'client');
api.addFiles('fonts.less', 'client');
api.addFiles('haptics.less', 'client');
api.addFiles('padding.less', 'client');
api.addFiles('pages.less', 'client');
api.addFiles('sizing.less', 'client');
api.addFiles('text.less', 'client');
});
Package.onTest(function(api) {
api.use('tinytest');
});

View File

@ -0,0 +1,111 @@
.well-behaved{
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
//-----------------------------------------
// Padding
.padded{
box-sizing: border-box;
padding: 20px;
}
.padded-horizontal, .with-horizontal-padding, .horizontally-padded{
box-sizing: border-box;
padding-left: 20px;
padding-right: 20px;
}
.padded-vertical, .with-vertical-padding, .vertically-padded{
box-sizing: border-box;
padding-top: 20px;
padding-bottom: 20px;
}
.with-right-padding{
padding-right: 20px !important;
}
.with-left-padding{
padding-left: 20px !important;
}
//-----------------------------------------
// Without Padding
.without-padding, .nopadding, .no-padding{
padding: 0px;
}
.without-right-padding{
padding-right: 0px !important;
}
.without-left-padding{
padding-left: 0px !important;
}
.without-top-padding{
padding-top: 0px !important;
}
.without-bottom-padding{
padding-bottom: 0px !important;
}
.without-vertical-padding{
padding-top: 0px !important;
padding-bottom: 0px !important;
}
.without-vertical-padding{
padding-left: 0px !important;
padding-right: 0px !important;
}
//-----------------------------------------
// Spacers
.with-bottom-spacer, .with-bottom-padding{
padding-bottom: 20px !important;
}
.with-top-spacer, .with-top-padding{
padding-top:20px !important;
}
.with-right-spacer{
margin-right: 5px !important;
}
.with-right-spacer{
margin-left: 5px !important;
}
//-----------------------------------------
// Margins
.margined, .with-margins{
margin-left: 20px;
margin-right: 20px;
margin-top: 20px;
margin-bottom: 20px;
}
.with-bottom-margin{
margin-bottom: 20px !important;
}
.with-top-margin{
margin-top:20px !important;
}
.with-right-margin{
margin-right: 20px !important;
}
.with-left-margin{
margin-left:20px !important;
}
//-----------------------------------------
// Margins
.spacer{
height: 20px;
}

View File

@ -0,0 +1,4 @@
.page{
padding-top: 50px;
padding-bottom: 50px;
}

View File

@ -0,0 +1,10 @@
//-----------------------------------------
// Sizing
.fullwidth{
width: 100%;
}
.fullheight{
height: 100%;
}

View File

@ -0,0 +1,36 @@
//-----------------------------------------
// Text and Fonts
.bold{
font-weight: bold;
}
.centered{
text-align: center;
}
.strikeout{
text-decoration: line-through;
color: lightgray !important;
}
.dotted{
border-style: dotted;
border-width: 2px;
border-color: lightgray;
}
.strikeout{
text-decoration: line-through;
color: lightgray !important;
}
.hidden{
visibility: hidden;
display: none;
}
.visible{
visibility: visible;
display: block;
}
.largish{
font-size: 32px;
}

View File

@ -0,0 +1,27 @@
{
"dependencies": [
[
"ian:bootstrap-3",
"3.3.1"
],
[
"jquery",
"1.0.1"
],
[
"less",
"1.0.11"
],
[
"meteor",
"1.1.3"
],
[
"underscore",
"1.0.1"
]
],
"pluginDependencies": [],
"toolVersion": "meteor-tool@1.0.35",
"format": "1.0"
}

View File

@ -100,7 +100,7 @@ Package.onUse(function(api) {
api.addFiles('lib/deactivateAllToolData.js', 'client');
api.addFiles('lib/clearTools.js', 'client');
// Export gloabal functions
// Export global functions
api.export('activateLesion','client');
api.export('activateMeasurements','client');
api.export('deactivateAllToolData','client');